update deps

This commit is contained in:
Aine 2024-12-19 12:08:10 +02:00
parent 0346ad2779
commit 449762ae99
No known key found for this signature in database
GPG key ID: 34969C908CCA2804
33 changed files with 404 additions and 1630 deletions

14
go.mod
View file

@ -13,7 +13,7 @@ require (
github.com/etkecc/go-fswatcher v1.0.1 github.com/etkecc/go-fswatcher v1.0.1
github.com/etkecc/go-healthchecks/v2 v2.2.2 github.com/etkecc/go-healthchecks/v2 v2.2.2
github.com/etkecc/go-kit v1.5.0 github.com/etkecc/go-kit v1.5.0
github.com/etkecc/go-linkpearl v0.0.0-20241212081007-8dacb4a09830 github.com/etkecc/go-linkpearl v0.0.0-20241219100412-83bcd2416637
github.com/etkecc/go-mxidwc v1.0.1 github.com/etkecc/go-mxidwc v1.0.1
github.com/etkecc/go-psd v0.0.0-20241014083754-1bc4ec777ba8 github.com/etkecc/go-psd v0.0.0-20241014083754-1bc4ec777ba8
github.com/etkecc/go-secgen v1.4.0 github.com/etkecc/go-secgen v1.4.0
@ -29,9 +29,9 @@ require (
github.com/raja/argon2pw v1.0.2-0.20210910183755-a391af63bd39 github.com/raja/argon2pw v1.0.2-0.20210910183755-a391af63bd39
github.com/rs/zerolog v1.33.0 github.com/rs/zerolog v1.33.0
github.com/swaggo/swag v1.16.3 github.com/swaggo/swag v1.16.3
golang.org/x/exp v0.0.0-20241210194714-1829a127f884 golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67
maunium.net/go/mautrix v0.22.0 maunium.net/go/mautrix v0.22.1
modernc.org/sqlite v1.34.2 modernc.org/sqlite v1.34.3
) )
require ( require (
@ -70,14 +70,14 @@ require (
github.com/tidwall/pretty v1.2.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/sjson v1.2.5 // indirect github.com/tidwall/sjson v1.2.5 // indirect
github.com/yuin/goldmark v1.7.8 // indirect github.com/yuin/goldmark v1.7.8 // indirect
go.mau.fi/util v0.8.2 // indirect go.mau.fi/util v0.8.3 // indirect
golang.org/x/crypto v0.31.0 // indirect golang.org/x/crypto v0.31.0 // indirect
golang.org/x/net v0.32.0 // indirect golang.org/x/net v0.33.0 // indirect
golang.org/x/sys v0.28.0 // indirect golang.org/x/sys v0.28.0 // indirect
golang.org/x/text v0.21.0 // indirect golang.org/x/text v0.21.0 // indirect
golang.org/x/tools v0.28.0 // indirect golang.org/x/tools v0.28.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
modernc.org/gc/v3 v3.0.0-20241004144649-1aea3fae8852 // indirect modernc.org/gc/v3 v3.0.0-20241213165251-3bc300f6d0c9 // indirect
modernc.org/libc v1.61.4 // indirect modernc.org/libc v1.61.4 // indirect
modernc.org/mathutil v1.6.0 // indirect modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.8.0 // indirect modernc.org/memory v1.8.0 // indirect

32
go.sum
View file

@ -38,8 +38,8 @@ github.com/etkecc/go-healthchecks/v2 v2.2.2 h1:YV7e+Ga8JY3aZX4Qf6Q1Ca+DnqbT5Drjl
github.com/etkecc/go-healthchecks/v2 v2.2.2/go.mod h1:IowWGN4F6By6z0eh63+639OscylgtdvT9ITjfN6hnZA= github.com/etkecc/go-healthchecks/v2 v2.2.2/go.mod h1:IowWGN4F6By6z0eh63+639OscylgtdvT9ITjfN6hnZA=
github.com/etkecc/go-kit v1.5.0 h1:i7QxpWxgoIBEDbUW5dbaDHnwbVXDCHw3Y5B9Xm++v0k= github.com/etkecc/go-kit v1.5.0 h1:i7QxpWxgoIBEDbUW5dbaDHnwbVXDCHw3Y5B9Xm++v0k=
github.com/etkecc/go-kit v1.5.0/go.mod h1:yikghi8YaYbTjRXNtx82g0LFv90YqZi2vLf5Chw0ysg= github.com/etkecc/go-kit v1.5.0/go.mod h1:yikghi8YaYbTjRXNtx82g0LFv90YqZi2vLf5Chw0ysg=
github.com/etkecc/go-linkpearl v0.0.0-20241212081007-8dacb4a09830 h1:5c8Zj0UKUeL5Bw7taV/xWjWDbT1yDgbezRC/0zjS2T8= github.com/etkecc/go-linkpearl v0.0.0-20241219100412-83bcd2416637 h1:IpNGgNynfoTqz5JQRx1PKa6pXLlOQ90u9YJ3UKTTIa8=
github.com/etkecc/go-linkpearl v0.0.0-20241212081007-8dacb4a09830/go.mod h1:3aohTqjkvDDS5TTI4EPnEDblyXSLtu6BMJqFFz71OvA= github.com/etkecc/go-linkpearl v0.0.0-20241219100412-83bcd2416637/go.mod h1:frjbaXFfD9H4X7JbCyeBEQ2fgkSm6/67tiR9NTgcx58=
github.com/etkecc/go-mxidwc v1.0.1 h1:t4Kq3FxSlQjt1i7RpzE5q3cOWjJ0vrTzzGZRSpgh8mg= github.com/etkecc/go-mxidwc v1.0.1 h1:t4Kq3FxSlQjt1i7RpzE5q3cOWjJ0vrTzzGZRSpgh8mg=
github.com/etkecc/go-mxidwc v1.0.1/go.mod h1:WFlntcH4mdual/gNi6X7a6rSJERNuLjdrwM3K/tucQA= github.com/etkecc/go-mxidwc v1.0.1/go.mod h1:WFlntcH4mdual/gNi6X7a6rSJERNuLjdrwM3K/tucQA=
github.com/etkecc/go-psd v0.0.0-20241014083754-1bc4ec777ba8 h1:ariQTBlHHe71O507IZ97QSHFrk0Eo2vlMm1skJ97Y7Q= github.com/etkecc/go-psd v0.0.0-20241014083754-1bc4ec777ba8 h1:ariQTBlHHe71O507IZ97QSHFrk0Eo2vlMm1skJ97Y7Q=
@ -147,8 +147,8 @@ github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02n
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/swaggo/swag v1.16.3 h1:PnCYjPCah8FK4I26l2F/KQ4yz3sILcVUN3cTlBFA9Pg= github.com/swaggo/swag v1.16.3 h1:PnCYjPCah8FK4I26l2F/KQ4yz3sILcVUN3cTlBFA9Pg=
github.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI/gRk= github.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI/gRk=
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
@ -163,18 +163,18 @@ github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic= github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
go.mau.fi/util v0.8.2 h1:zWbVHwdRKwI6U9AusmZ8bwgcLosikwbb4GGqLrNr1YE= go.mau.fi/util v0.8.3 h1:sulhXtfquMrQjsOP67x9CzWVBYUwhYeoo8hNQIpCWZ4=
go.mau.fi/util v0.8.2/go.mod h1:BHHC9R2WLMJd1bwTZfTcFxUgRFmUgUmiWcT4RbzUgiA= go.mau.fi/util v0.8.3/go.mod h1:c00Db8xog70JeIsEvhdHooylTkTkakgnAOsZ04hplQY=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/exp v0.0.0-20241210194714-1829a127f884 h1:Y/Mj/94zIQQGHVSv1tTtQBDaQaJe62U9bkDZKKyhPCU= golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 h1:1UoZQm6f0P/ZO0w1Ri+f+ifG/gXhegadRdwBIXEFWDo=
golang.org/x/exp v0.0.0-20241210194714-1829a127f884/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -205,8 +205,8 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
maunium.net/go/mautrix v0.22.0 h1:nLrnLYiMyFV6qZPqpkNogkOPgm2dQTYiQXlu9Nc3rz8= maunium.net/go/mautrix v0.22.1 h1:2lCM37vmVzZGE0tWD7UOySMtAuC5hq6Pw33KlY2VU/c=
maunium.net/go/mautrix v0.22.0/go.mod h1:oqwf9WYC/brqucM+heYk4gX11O59nP+ljvyxVhndFIM= maunium.net/go/mautrix v0.22.1/go.mod h1:1rhqwH34Rz54ZqzdQYkmNW6rQUymNeTdaLA4l9LK6AI=
modernc.org/cc/v4 v4.23.1 h1:WqJoPL3x4cUufQVHkXpXX7ThFJ1C4ik80i2eXEXbhD8= modernc.org/cc/v4 v4.23.1 h1:WqJoPL3x4cUufQVHkXpXX7ThFJ1C4ik80i2eXEXbhD8=
modernc.org/cc/v4 v4.23.1/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= modernc.org/cc/v4 v4.23.1/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
modernc.org/ccgo/v4 v4.23.1 h1:N49a7JiWGWV7lkPE4yYcvjkBGZQi93/JabRYjdWmJXc= modernc.org/ccgo/v4 v4.23.1 h1:N49a7JiWGWV7lkPE4yYcvjkBGZQi93/JabRYjdWmJXc=
@ -215,8 +215,8 @@ modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
modernc.org/gc/v2 v2.5.0 h1:bJ9ChznK1L1mUtAQtxi0wi5AtAs5jQuw4PrPHO5pb6M= modernc.org/gc/v2 v2.5.0 h1:bJ9ChznK1L1mUtAQtxi0wi5AtAs5jQuw4PrPHO5pb6M=
modernc.org/gc/v2 v2.5.0/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU= modernc.org/gc/v2 v2.5.0/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
modernc.org/gc/v3 v3.0.0-20241004144649-1aea3fae8852 h1:IYXPPTTjjoSHvUClZIYexDiO7g+4x+XveKT4gCIAwiY= modernc.org/gc/v3 v3.0.0-20241213165251-3bc300f6d0c9 h1:ovz6yUKX71igz2yvk4NpiCL5fvdjZAI+DhuDEGx1xyU=
modernc.org/gc/v3 v3.0.0-20241004144649-1aea3fae8852/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4= modernc.org/gc/v3 v3.0.0-20241213165251-3bc300f6d0c9/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
modernc.org/libc v1.61.4 h1:wVyqEx6tlltte9lPTjq0kDAdtdM9c4JH8rU6M1ZVawA= modernc.org/libc v1.61.4 h1:wVyqEx6tlltte9lPTjq0kDAdtdM9c4JH8rU6M1ZVawA=
modernc.org/libc v1.61.4/go.mod h1:VfXVuM/Shh5XsMNrh3C6OkfL78G3loa4ZC/Ljv9k7xc= modernc.org/libc v1.61.4/go.mod h1:VfXVuM/Shh5XsMNrh3C6OkfL78G3loa4ZC/Ljv9k7xc=
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
@ -227,8 +227,8 @@ modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc= modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
modernc.org/sqlite v1.34.2 h1:J9n76TPsfYYkFkZ9Uy1QphILYifiVEwwOT7yP5b++2Y= modernc.org/sqlite v1.34.3 h1:494MIwJKBLd0tErBYkRar2HvEpy04Bl0ykPEm4XLhbo=
modernc.org/sqlite v1.34.2/go.mod h1:dnR723UrTtjKpoHCAMN0Q/gZ9MT4r+iRvIBb9umWFkU= modernc.org/sqlite v1.34.3/go.mod h1:dnR723UrTtjKpoHCAMN0Q/gZ9MT4r+iRvIBb9umWFkU=
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=

View file

@ -165,7 +165,7 @@ func (b *Bot) IncomingEmail(ctx context.Context, eml *email.Email) error {
contentOpts.Stripify = false contentOpts.Stripify = false
content := eml.Content(threadID, contentOpts, b.psdc) content := eml.Content(threadID, contentOpts, b.psdc)
eml.Files = append(eml.Files, //nolint:forcetypeassert // that's ok eml.Files = append(eml.Files, //nolint:forcetypeassert // that's ok
utils.NewFile("original.md", []byte(content.Parsed.(*event.MessageEventContent).Body)), utils.NewFile("original.md", []byte(content.Parsed.(*event.MessageEventContent).Body)), //nolint:errcheck // that's ok
) )
} }
@ -194,7 +194,7 @@ func (b *Bot) IncomingEmail(ctx context.Context, eml *email.Email) error {
contentOpts.Stripify = false contentOpts.Stripify = false
content := eml.ContentBody(threadID, contentOpts) content := eml.ContentBody(threadID, contentOpts)
eml.Files = append(eml.Files, //nolint:forcetypeassert // that's ok eml.Files = append(eml.Files, //nolint:forcetypeassert // that's ok
utils.NewFile("original.md", []byte(content.Parsed.(*event.MessageEventContent).Body)), utils.NewFile("original.md", []byte(content.Parsed.(*event.MessageEventContent).Body)), //nolint:errcheck // that's ok
) )
} }
_, berr := b.lp.Send(ctx, roomID, eml.ContentBody(threadID, cfg.ContentOptions())) _, berr := b.lp.Send(ctx, roomID, eml.ContentBody(threadID, cfg.ContentOptions()))

View file

@ -5,22 +5,30 @@ import (
"net/http" "net/http"
) )
var AutoAllowCORS = true
func WriteJSONResponse(w http.ResponseWriter, httpStatusCode int, jsonData any) { func WriteJSONResponse(w http.ResponseWriter, httpStatusCode int, jsonData any) {
AddCORSHeaders(w) if AutoAllowCORS {
AddCORSHeaders(w)
}
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
w.WriteHeader(httpStatusCode) w.WriteHeader(httpStatusCode)
_ = json.NewEncoder(w).Encode(jsonData) _ = json.NewEncoder(w).Encode(jsonData)
} }
func WriteJSONData(w http.ResponseWriter, httpStatusCode int, data []byte) { func WriteJSONData(w http.ResponseWriter, httpStatusCode int, data []byte) {
AddCORSHeaders(w) if AutoAllowCORS {
AddCORSHeaders(w)
}
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
w.WriteHeader(httpStatusCode) w.WriteHeader(httpStatusCode)
_, _ = w.Write(data) _, _ = w.Write(data)
} }
func WriteEmptyJSONResponse(w http.ResponseWriter, httpStatusCode int) { func WriteEmptyJSONResponse(w http.ResponseWriter, httpStatusCode int) {
AddCORSHeaders(w) if AutoAllowCORS {
AddCORSHeaders(w)
}
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
w.WriteHeader(httpStatusCode) w.WriteHeader(httpStatusCode)
_, _ = w.Write([]byte("{}")) _, _ = w.Write([]byte("{}"))

View file

@ -45,6 +45,8 @@ type Complex interface {
// that supports the operators < <= >= >. // that supports the operators < <= >= >.
// If future releases of Go add new ordered types, // If future releases of Go add new ordered types,
// this constraint will be modified to include them. // this constraint will be modified to include them.
//
// This type is redundant since Go 1.21 introduced [cmp.Ordered].
type Ordered interface { type Ordered interface {
Integer | Float | ~string Integer | Float | ~string
} }

58
vendor/golang.org/x/exp/maps/maps.go generated vendored
View file

@ -5,9 +5,20 @@
// Package maps defines various functions useful with maps of any type. // Package maps defines various functions useful with maps of any type.
package maps package maps
import "maps"
// TODO(adonovan): when https://go.dev/issue/32816 is accepted, all of
// these functions except Keys and Values should be annotated
// (provisionally with "//go:fix inline") so that tools can safely and
// automatically replace calls to exp/maps with calls to std maps by
// inlining them.
// Keys returns the keys of the map m. // Keys returns the keys of the map m.
// The keys will be in an indeterminate order. // The keys will be in an indeterminate order.
func Keys[M ~map[K]V, K comparable, V any](m M) []K { func Keys[M ~map[K]V, K comparable, V any](m M) []K {
// The simplest true equivalent using std is:
// return slices.AppendSeq(make([]K, 0, len(m)), maps.Keys(m)).
r := make([]K, 0, len(m)) r := make([]K, 0, len(m))
for k := range m { for k := range m {
r = append(r, k) r = append(r, k)
@ -18,6 +29,9 @@ func Keys[M ~map[K]V, K comparable, V any](m M) []K {
// Values returns the values of the map m. // Values returns the values of the map m.
// The values will be in an indeterminate order. // The values will be in an indeterminate order.
func Values[M ~map[K]V, K comparable, V any](m M) []V { func Values[M ~map[K]V, K comparable, V any](m M) []V {
// The simplest true equivalent using std is:
// return slices.AppendSeq(make([]V, 0, len(m)), maps.Values(m)).
r := make([]V, 0, len(m)) r := make([]V, 0, len(m))
for _, v := range m { for _, v := range m {
r = append(r, v) r = append(r, v)
@ -28,50 +42,24 @@ func Values[M ~map[K]V, K comparable, V any](m M) []V {
// Equal reports whether two maps contain the same key/value pairs. // Equal reports whether two maps contain the same key/value pairs.
// Values are compared using ==. // Values are compared using ==.
func Equal[M1, M2 ~map[K]V, K, V comparable](m1 M1, m2 M2) bool { func Equal[M1, M2 ~map[K]V, K, V comparable](m1 M1, m2 M2) bool {
if len(m1) != len(m2) { return maps.Equal(m1, m2)
return false
}
for k, v1 := range m1 {
if v2, ok := m2[k]; !ok || v1 != v2 {
return false
}
}
return true
} }
// EqualFunc is like Equal, but compares values using eq. // EqualFunc is like Equal, but compares values using eq.
// Keys are still compared with ==. // Keys are still compared with ==.
func EqualFunc[M1 ~map[K]V1, M2 ~map[K]V2, K comparable, V1, V2 any](m1 M1, m2 M2, eq func(V1, V2) bool) bool { func EqualFunc[M1 ~map[K]V1, M2 ~map[K]V2, K comparable, V1, V2 any](m1 M1, m2 M2, eq func(V1, V2) bool) bool {
if len(m1) != len(m2) { return maps.EqualFunc(m1, m2, eq)
return false
}
for k, v1 := range m1 {
if v2, ok := m2[k]; !ok || !eq(v1, v2) {
return false
}
}
return true
} }
// Clear removes all entries from m, leaving it empty. // Clear removes all entries from m, leaving it empty.
func Clear[M ~map[K]V, K comparable, V any](m M) { func Clear[M ~map[K]V, K comparable, V any](m M) {
for k := range m { clear(m)
delete(m, k)
}
} }
// Clone returns a copy of m. This is a shallow clone: // Clone returns a copy of m. This is a shallow clone:
// the new keys and values are set using ordinary assignment. // the new keys and values are set using ordinary assignment.
func Clone[M ~map[K]V, K comparable, V any](m M) M { func Clone[M ~map[K]V, K comparable, V any](m M) M {
// Preserve nil in case it matters. return maps.Clone(m)
if m == nil {
return nil
}
r := make(M, len(m))
for k, v := range m {
r[k] = v
}
return r
} }
// Copy copies all key/value pairs in src adding them to dst. // Copy copies all key/value pairs in src adding them to dst.
@ -79,16 +67,10 @@ func Clone[M ~map[K]V, K comparable, V any](m M) M {
// the value in dst will be overwritten by the value associated // the value in dst will be overwritten by the value associated
// with the key in src. // with the key in src.
func Copy[M1 ~map[K]V, M2 ~map[K]V, K comparable, V any](dst M1, src M2) { func Copy[M1 ~map[K]V, M2 ~map[K]V, K comparable, V any](dst M1, src M2) {
for k, v := range src { maps.Copy(dst, src)
dst[k] = v
}
} }
// DeleteFunc deletes any key/value pairs from m for which del returns true. // DeleteFunc deletes any key/value pairs from m for which del returns true.
func DeleteFunc[M ~map[K]V, K comparable, V any](m M, del func(K, V) bool) { func DeleteFunc[M ~map[K]V, K comparable, V any](m M, del func(K, V) bool) {
for k, v := range m { maps.DeleteFunc(m, del)
if del(k, v) {
delete(m, k)
}
}
} }

View file

@ -1,44 +0,0 @@
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package slices
import "golang.org/x/exp/constraints"
// min is a version of the predeclared function from the Go 1.21 release.
func min[T constraints.Ordered](a, b T) T {
if a < b || isNaN(a) {
return a
}
return b
}
// max is a version of the predeclared function from the Go 1.21 release.
func max[T constraints.Ordered](a, b T) T {
if a > b || isNaN(a) {
return a
}
return b
}
// cmpLess is a copy of cmp.Less from the Go 1.21 release.
func cmpLess[T constraints.Ordered](x, y T) bool {
return (isNaN(x) && !isNaN(y)) || x < y
}
// cmpCompare is a copy of cmp.Compare from the Go 1.21 release.
func cmpCompare[T constraints.Ordered](x, y T) int {
xNaN := isNaN(x)
yNaN := isNaN(y)
if xNaN && yNaN {
return 0
}
if xNaN || x < y {
return -1
}
if yNaN || x > y {
return +1
}
return 0
}

View file

@ -6,26 +6,22 @@
package slices package slices
import ( import (
"unsafe" "cmp"
"slices"
"golang.org/x/exp/constraints"
) )
// TODO(adonovan): when https://go.dev/issue/32816 is accepted, all of
// these functions should be annotated (provisionally with "//go:fix
// inline") so that tools can safely and automatically replace calls
// to exp/slices with calls to std slices by inlining them.
// Equal reports whether two slices are equal: the same length and all // Equal reports whether two slices are equal: the same length and all
// elements equal. If the lengths are different, Equal returns false. // elements equal. If the lengths are different, Equal returns false.
// Otherwise, the elements are compared in increasing index order, and the // Otherwise, the elements are compared in increasing index order, and the
// comparison stops at the first unequal pair. // comparison stops at the first unequal pair.
// Floating point NaNs are not considered equal. // Floating point NaNs are not considered equal.
func Equal[S ~[]E, E comparable](s1, s2 S) bool { func Equal[S ~[]E, E comparable](s1, s2 S) bool {
if len(s1) != len(s2) { return slices.Equal(s1, s2)
return false
}
for i := range s1 {
if s1[i] != s2[i] {
return false
}
}
return true
} }
// EqualFunc reports whether two slices are equal using an equality // EqualFunc reports whether two slices are equal using an equality
@ -34,16 +30,7 @@ func Equal[S ~[]E, E comparable](s1, s2 S) bool {
// increasing index order, and the comparison stops at the first index // increasing index order, and the comparison stops at the first index
// for which eq returns false. // for which eq returns false.
func EqualFunc[S1 ~[]E1, S2 ~[]E2, E1, E2 any](s1 S1, s2 S2, eq func(E1, E2) bool) bool { func EqualFunc[S1 ~[]E1, S2 ~[]E2, E1, E2 any](s1 S1, s2 S2, eq func(E1, E2) bool) bool {
if len(s1) != len(s2) { return slices.EqualFunc(s1, s2, eq)
return false
}
for i, v1 := range s1 {
v2 := s2[i]
if !eq(v1, v2) {
return false
}
}
return true
} }
// Compare compares the elements of s1 and s2, using [cmp.Compare] on each pair // Compare compares the elements of s1 and s2, using [cmp.Compare] on each pair
@ -53,20 +40,8 @@ func EqualFunc[S1 ~[]E1, S2 ~[]E2, E1, E2 any](s1 S1, s2 S2, eq func(E1, E2) boo
// If both slices are equal until one of them ends, the shorter slice is // If both slices are equal until one of them ends, the shorter slice is
// considered less than the longer one. // considered less than the longer one.
// The result is 0 if s1 == s2, -1 if s1 < s2, and +1 if s1 > s2. // The result is 0 if s1 == s2, -1 if s1 < s2, and +1 if s1 > s2.
func Compare[S ~[]E, E constraints.Ordered](s1, s2 S) int { func Compare[S ~[]E, E cmp.Ordered](s1, s2 S) int {
for i, v1 := range s1 { return slices.Compare(s1, s2)
if i >= len(s2) {
return +1
}
v2 := s2[i]
if c := cmpCompare(v1, v2); c != 0 {
return c
}
}
if len(s1) < len(s2) {
return -1
}
return 0
} }
// CompareFunc is like [Compare] but uses a custom comparison function on each // CompareFunc is like [Compare] but uses a custom comparison function on each
@ -75,52 +50,30 @@ func Compare[S ~[]E, E constraints.Ordered](s1, s2 S) int {
// returns 0 the result is 0 if len(s1) == len(s2), -1 if len(s1) < len(s2), // returns 0 the result is 0 if len(s1) == len(s2), -1 if len(s1) < len(s2),
// and +1 if len(s1) > len(s2). // and +1 if len(s1) > len(s2).
func CompareFunc[S1 ~[]E1, S2 ~[]E2, E1, E2 any](s1 S1, s2 S2, cmp func(E1, E2) int) int { func CompareFunc[S1 ~[]E1, S2 ~[]E2, E1, E2 any](s1 S1, s2 S2, cmp func(E1, E2) int) int {
for i, v1 := range s1 { return slices.CompareFunc(s1, s2, cmp)
if i >= len(s2) {
return +1
}
v2 := s2[i]
if c := cmp(v1, v2); c != 0 {
return c
}
}
if len(s1) < len(s2) {
return -1
}
return 0
} }
// Index returns the index of the first occurrence of v in s, // Index returns the index of the first occurrence of v in s,
// or -1 if not present. // or -1 if not present.
func Index[S ~[]E, E comparable](s S, v E) int { func Index[S ~[]E, E comparable](s S, v E) int {
for i := range s { return slices.Index(s, v)
if v == s[i] {
return i
}
}
return -1
} }
// IndexFunc returns the first index i satisfying f(s[i]), // IndexFunc returns the first index i satisfying f(s[i]),
// or -1 if none do. // or -1 if none do.
func IndexFunc[S ~[]E, E any](s S, f func(E) bool) int { func IndexFunc[S ~[]E, E any](s S, f func(E) bool) int {
for i := range s { return slices.IndexFunc(s, f)
if f(s[i]) {
return i
}
}
return -1
} }
// Contains reports whether v is present in s. // Contains reports whether v is present in s.
func Contains[S ~[]E, E comparable](s S, v E) bool { func Contains[S ~[]E, E comparable](s S, v E) bool {
return Index(s, v) >= 0 return slices.Contains(s, v)
} }
// ContainsFunc reports whether at least one // ContainsFunc reports whether at least one
// element e of s satisfies f(e). // element e of s satisfies f(e).
func ContainsFunc[S ~[]E, E any](s S, f func(E) bool) bool { func ContainsFunc[S ~[]E, E any](s S, f func(E) bool) bool {
return IndexFunc(s, f) >= 0 return slices.ContainsFunc(s, f)
} }
// Insert inserts the values v... into s at index i, // Insert inserts the values v... into s at index i,
@ -131,92 +84,7 @@ func ContainsFunc[S ~[]E, E any](s S, f func(E) bool) bool {
// Insert panics if i is out of range. // Insert panics if i is out of range.
// This function is O(len(s) + len(v)). // This function is O(len(s) + len(v)).
func Insert[S ~[]E, E any](s S, i int, v ...E) S { func Insert[S ~[]E, E any](s S, i int, v ...E) S {
m := len(v) return slices.Insert(s, i, v...)
if m == 0 {
return s
}
n := len(s)
if i == n {
return append(s, v...)
}
if n+m > cap(s) {
// Use append rather than make so that we bump the size of
// the slice up to the next storage class.
// This is what Grow does but we don't call Grow because
// that might copy the values twice.
s2 := append(s[:i], make(S, n+m-i)...)
copy(s2[i:], v)
copy(s2[i+m:], s[i:])
return s2
}
s = s[:n+m]
// before:
// s: aaaaaaaabbbbccccccccdddd
// ^ ^ ^ ^
// i i+m n n+m
// after:
// s: aaaaaaaavvvvbbbbcccccccc
// ^ ^ ^ ^
// i i+m n n+m
//
// a are the values that don't move in s.
// v are the values copied in from v.
// b and c are the values from s that are shifted up in index.
// d are the values that get overwritten, never to be seen again.
if !overlaps(v, s[i+m:]) {
// Easy case - v does not overlap either the c or d regions.
// (It might be in some of a or b, or elsewhere entirely.)
// The data we copy up doesn't write to v at all, so just do it.
copy(s[i+m:], s[i:])
// Now we have
// s: aaaaaaaabbbbbbbbcccccccc
// ^ ^ ^ ^
// i i+m n n+m
// Note the b values are duplicated.
copy(s[i:], v)
// Now we have
// s: aaaaaaaavvvvbbbbcccccccc
// ^ ^ ^ ^
// i i+m n n+m
// That's the result we want.
return s
}
// The hard case - v overlaps c or d. We can't just shift up
// the data because we'd move or clobber the values we're trying
// to insert.
// So instead, write v on top of d, then rotate.
copy(s[n:], v)
// Now we have
// s: aaaaaaaabbbbccccccccvvvv
// ^ ^ ^ ^
// i i+m n n+m
rotateRight(s[i:], m)
// Now we have
// s: aaaaaaaavvvvbbbbcccccccc
// ^ ^ ^ ^
// i i+m n n+m
// That's the result we want.
return s
}
// clearSlice sets all elements up to the length of s to the zero value of E.
// We may use the builtin clear func instead, and remove clearSlice, when upgrading
// to Go 1.21+.
func clearSlice[S ~[]E, E any](s S) {
var zero E
for i := range s {
s[i] = zero
}
} }
// Delete removes the elements s[i:j] from s, returning the modified slice. // Delete removes the elements s[i:j] from s, returning the modified slice.
@ -225,135 +93,27 @@ func clearSlice[S ~[]E, E any](s S) {
// make a single call deleting them all together than to delete one at a time. // make a single call deleting them all together than to delete one at a time.
// Delete zeroes the elements s[len(s)-(j-i):len(s)]. // Delete zeroes the elements s[len(s)-(j-i):len(s)].
func Delete[S ~[]E, E any](s S, i, j int) S { func Delete[S ~[]E, E any](s S, i, j int) S {
_ = s[i:j:len(s)] // bounds check return slices.Delete(s, i, j)
if i == j {
return s
}
oldlen := len(s)
s = append(s[:i], s[j:]...)
clearSlice(s[len(s):oldlen]) // zero/nil out the obsolete elements, for GC
return s
} }
// DeleteFunc removes any elements from s for which del returns true, // DeleteFunc removes any elements from s for which del returns true,
// returning the modified slice. // returning the modified slice.
// DeleteFunc zeroes the elements between the new length and the original length. // DeleteFunc zeroes the elements between the new length and the original length.
func DeleteFunc[S ~[]E, E any](s S, del func(E) bool) S { func DeleteFunc[S ~[]E, E any](s S, del func(E) bool) S {
i := IndexFunc(s, del) return slices.DeleteFunc(s, del)
if i == -1 {
return s
}
// Don't start copying elements until we find one to delete.
for j := i + 1; j < len(s); j++ {
if v := s[j]; !del(v) {
s[i] = v
i++
}
}
clearSlice(s[i:]) // zero/nil out the obsolete elements, for GC
return s[:i]
} }
// Replace replaces the elements s[i:j] by the given v, and returns the // Replace replaces the elements s[i:j] by the given v, and returns the
// modified slice. Replace panics if s[i:j] is not a valid slice of s. // modified slice. Replace panics if s[i:j] is not a valid slice of s.
// When len(v) < (j-i), Replace zeroes the elements between the new length and the original length. // When len(v) < (j-i), Replace zeroes the elements between the new length and the original length.
func Replace[S ~[]E, E any](s S, i, j int, v ...E) S { func Replace[S ~[]E, E any](s S, i, j int, v ...E) S {
_ = s[i:j] // verify that i:j is a valid subslice return slices.Replace(s, i, j, v...)
if i == j {
return Insert(s, i, v...)
}
if j == len(s) {
return append(s[:i], v...)
}
tot := len(s[:i]) + len(v) + len(s[j:])
if tot > cap(s) {
// Too big to fit, allocate and copy over.
s2 := append(s[:i], make(S, tot-i)...) // See Insert
copy(s2[i:], v)
copy(s2[i+len(v):], s[j:])
return s2
}
r := s[:tot]
if i+len(v) <= j {
// Easy, as v fits in the deleted portion.
copy(r[i:], v)
if i+len(v) != j {
copy(r[i+len(v):], s[j:])
}
clearSlice(s[tot:]) // zero/nil out the obsolete elements, for GC
return r
}
// We are expanding (v is bigger than j-i).
// The situation is something like this:
// (example has i=4,j=8,len(s)=16,len(v)=6)
// s: aaaaxxxxbbbbbbbbyy
// ^ ^ ^ ^
// i j len(s) tot
// a: prefix of s
// x: deleted range
// b: more of s
// y: area to expand into
if !overlaps(r[i+len(v):], v) {
// Easy, as v is not clobbered by the first copy.
copy(r[i+len(v):], s[j:])
copy(r[i:], v)
return r
}
// This is a situation where we don't have a single place to which
// we can copy v. Parts of it need to go to two different places.
// We want to copy the prefix of v into y and the suffix into x, then
// rotate |y| spots to the right.
//
// v[2:] v[:2]
// | |
// s: aaaavvvvbbbbbbbbvv
// ^ ^ ^ ^
// i j len(s) tot
//
// If either of those two destinations don't alias v, then we're good.
y := len(v) - (j - i) // length of y portion
if !overlaps(r[i:j], v) {
copy(r[i:j], v[y:])
copy(r[len(s):], v[:y])
rotateRight(r[i:], y)
return r
}
if !overlaps(r[len(s):], v) {
copy(r[len(s):], v[:y])
copy(r[i:j], v[y:])
rotateRight(r[i:], y)
return r
}
// Now we know that v overlaps both x and y.
// That means that the entirety of b is *inside* v.
// So we don't need to preserve b at all; instead we
// can copy v first, then copy the b part of v out of
// v to the right destination.
k := startIdx(v, s[j:])
copy(r[i:], v)
copy(r[i+len(v):], r[i+k:])
return r
} }
// Clone returns a copy of the slice. // Clone returns a copy of the slice.
// The elements are copied using assignment, so this is a shallow clone. // The elements are copied using assignment, so this is a shallow clone.
func Clone[S ~[]E, E any](s S) S { func Clone[S ~[]E, E any](s S) S {
// Preserve nil in case it matters. return slices.Clone(s)
if s == nil {
return nil
}
return append(S([]E{}), s...)
} }
// Compact replaces consecutive runs of equal elements with a single copy. // Compact replaces consecutive runs of equal elements with a single copy.
@ -362,40 +122,14 @@ func Clone[S ~[]E, E any](s S) S {
// which may have a smaller length. // which may have a smaller length.
// Compact zeroes the elements between the new length and the original length. // Compact zeroes the elements between the new length and the original length.
func Compact[S ~[]E, E comparable](s S) S { func Compact[S ~[]E, E comparable](s S) S {
if len(s) < 2 { return slices.Compact(s)
return s
}
i := 1
for k := 1; k < len(s); k++ {
if s[k] != s[k-1] {
if i != k {
s[i] = s[k]
}
i++
}
}
clearSlice(s[i:]) // zero/nil out the obsolete elements, for GC
return s[:i]
} }
// CompactFunc is like [Compact] but uses an equality function to compare elements. // CompactFunc is like [Compact] but uses an equality function to compare elements.
// For runs of elements that compare equal, CompactFunc keeps the first one. // For runs of elements that compare equal, CompactFunc keeps the first one.
// CompactFunc zeroes the elements between the new length and the original length. // CompactFunc zeroes the elements between the new length and the original length.
func CompactFunc[S ~[]E, E any](s S, eq func(E, E) bool) S { func CompactFunc[S ~[]E, E any](s S, eq func(E, E) bool) S {
if len(s) < 2 { return slices.CompactFunc(s, eq)
return s
}
i := 1
for k := 1; k < len(s); k++ {
if !eq(s[k], s[k-1]) {
if i != k {
s[i] = s[k]
}
i++
}
}
clearSlice(s[i:]) // zero/nil out the obsolete elements, for GC
return s[:i]
} }
// Grow increases the slice's capacity, if necessary, to guarantee space for // Grow increases the slice's capacity, if necessary, to guarantee space for
@ -403,113 +137,15 @@ func CompactFunc[S ~[]E, E any](s S, eq func(E, E) bool) S {
// to the slice without another allocation. If n is negative or too large to // to the slice without another allocation. If n is negative or too large to
// allocate the memory, Grow panics. // allocate the memory, Grow panics.
func Grow[S ~[]E, E any](s S, n int) S { func Grow[S ~[]E, E any](s S, n int) S {
if n < 0 { return slices.Grow(s, n)
panic("cannot be negative")
}
if n -= cap(s) - len(s); n > 0 {
// TODO(https://go.dev/issue/53888): Make using []E instead of S
// to workaround a compiler bug where the runtime.growslice optimization
// does not take effect. Revert when the compiler is fixed.
s = append([]E(s)[:cap(s)], make([]E, n)...)[:len(s)]
}
return s
} }
// Clip removes unused capacity from the slice, returning s[:len(s):len(s)]. // Clip removes unused capacity from the slice, returning s[:len(s):len(s)].
func Clip[S ~[]E, E any](s S) S { func Clip[S ~[]E, E any](s S) S {
return s[:len(s):len(s)] return slices.Clip(s)
}
// Rotation algorithm explanation:
//
// rotate left by 2
// start with
// 0123456789
// split up like this
// 01 234567 89
// swap first 2 and last 2
// 89 234567 01
// join first parts
// 89234567 01
// recursively rotate first left part by 2
// 23456789 01
// join at the end
// 2345678901
//
// rotate left by 8
// start with
// 0123456789
// split up like this
// 01 234567 89
// swap first 2 and last 2
// 89 234567 01
// join last parts
// 89 23456701
// recursively rotate second part left by 6
// 89 01234567
// join at the end
// 8901234567
// TODO: There are other rotate algorithms.
// This algorithm has the desirable property that it moves each element exactly twice.
// The triple-reverse algorithm is simpler and more cache friendly, but takes more writes.
// The follow-cycles algorithm can be 1-write but it is not very cache friendly.
// rotateLeft rotates b left by n spaces.
// s_final[i] = s_orig[i+r], wrapping around.
func rotateLeft[E any](s []E, r int) {
for r != 0 && r != len(s) {
if r*2 <= len(s) {
swap(s[:r], s[len(s)-r:])
s = s[:len(s)-r]
} else {
swap(s[:len(s)-r], s[r:])
s, r = s[len(s)-r:], r*2-len(s)
}
}
}
func rotateRight[E any](s []E, r int) {
rotateLeft(s, len(s)-r)
}
// swap swaps the contents of x and y. x and y must be equal length and disjoint.
func swap[E any](x, y []E) {
for i := 0; i < len(x); i++ {
x[i], y[i] = y[i], x[i]
}
}
// overlaps reports whether the memory ranges a[0:len(a)] and b[0:len(b)] overlap.
func overlaps[E any](a, b []E) bool {
if len(a) == 0 || len(b) == 0 {
return false
}
elemSize := unsafe.Sizeof(a[0])
if elemSize == 0 {
return false
}
// TODO: use a runtime/unsafe facility once one becomes available. See issue 12445.
// Also see crypto/internal/alias/alias.go:AnyOverlap
return uintptr(unsafe.Pointer(&a[0])) <= uintptr(unsafe.Pointer(&b[len(b)-1]))+(elemSize-1) &&
uintptr(unsafe.Pointer(&b[0])) <= uintptr(unsafe.Pointer(&a[len(a)-1]))+(elemSize-1)
}
// startIdx returns the index in haystack where the needle starts.
// prerequisite: the needle must be aliased entirely inside the haystack.
func startIdx[E any](haystack, needle []E) int {
p := &needle[0]
for i := range haystack {
if p == &haystack[i] {
return i
}
}
// TODO: what if the overlap is by a non-integral number of Es?
panic("needle not found")
} }
// Reverse reverses the elements of the slice in place. // Reverse reverses the elements of the slice in place.
func Reverse[S ~[]E, E any](s S) { func Reverse[S ~[]E, E any](s S) {
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 { slices.Reverse(s)
s[i], s[j] = s[j], s[i]
}
} }

View file

@ -2,21 +2,20 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:generate go run $GOROOT/src/sort/gen_sort_variants.go -exp
package slices package slices
import ( import (
"math/bits" "cmp"
"slices"
"golang.org/x/exp/constraints"
) )
// TODO(adonovan): add a "//go:fix inline" annotation to each function
// in this file; see https://go.dev/issue/32816.
// Sort sorts a slice of any ordered type in ascending order. // Sort sorts a slice of any ordered type in ascending order.
// When sorting floating-point numbers, NaNs are ordered before other values. // When sorting floating-point numbers, NaNs are ordered before other values.
func Sort[S ~[]E, E constraints.Ordered](x S) { func Sort[S ~[]E, E cmp.Ordered](x S) {
n := len(x) slices.Sort(x)
pdqsortOrdered(x, 0, n, bits.Len(uint(n)))
} }
// SortFunc sorts the slice x in ascending order as determined by the cmp // SortFunc sorts the slice x in ascending order as determined by the cmp
@ -29,118 +28,60 @@ func Sort[S ~[]E, E constraints.Ordered](x S) {
// See https://en.wikipedia.org/wiki/Weak_ordering#Strict_weak_orderings. // See https://en.wikipedia.org/wiki/Weak_ordering#Strict_weak_orderings.
// To indicate 'uncomparable', return 0 from the function. // To indicate 'uncomparable', return 0 from the function.
func SortFunc[S ~[]E, E any](x S, cmp func(a, b E) int) { func SortFunc[S ~[]E, E any](x S, cmp func(a, b E) int) {
n := len(x) slices.SortFunc(x, cmp)
pdqsortCmpFunc(x, 0, n, bits.Len(uint(n)), cmp)
} }
// SortStableFunc sorts the slice x while keeping the original order of equal // SortStableFunc sorts the slice x while keeping the original order of equal
// elements, using cmp to compare elements in the same way as [SortFunc]. // elements, using cmp to compare elements in the same way as [SortFunc].
func SortStableFunc[S ~[]E, E any](x S, cmp func(a, b E) int) { func SortStableFunc[S ~[]E, E any](x S, cmp func(a, b E) int) {
stableCmpFunc(x, len(x), cmp) slices.SortStableFunc(x, cmp)
} }
// IsSorted reports whether x is sorted in ascending order. // IsSorted reports whether x is sorted in ascending order.
func IsSorted[S ~[]E, E constraints.Ordered](x S) bool { func IsSorted[S ~[]E, E cmp.Ordered](x S) bool {
for i := len(x) - 1; i > 0; i-- { return slices.IsSorted(x)
if cmpLess(x[i], x[i-1]) {
return false
}
}
return true
} }
// IsSortedFunc reports whether x is sorted in ascending order, with cmp as the // IsSortedFunc reports whether x is sorted in ascending order, with cmp as the
// comparison function as defined by [SortFunc]. // comparison function as defined by [SortFunc].
func IsSortedFunc[S ~[]E, E any](x S, cmp func(a, b E) int) bool { func IsSortedFunc[S ~[]E, E any](x S, cmp func(a, b E) int) bool {
for i := len(x) - 1; i > 0; i-- { return slices.IsSortedFunc(x, cmp)
if cmp(x[i], x[i-1]) < 0 {
return false
}
}
return true
} }
// Min returns the minimal value in x. It panics if x is empty. // Min returns the minimal value in x. It panics if x is empty.
// For floating-point numbers, Min propagates NaNs (any NaN value in x // For floating-point numbers, Min propagates NaNs (any NaN value in x
// forces the output to be NaN). // forces the output to be NaN).
func Min[S ~[]E, E constraints.Ordered](x S) E { func Min[S ~[]E, E cmp.Ordered](x S) E {
if len(x) < 1 { return slices.Min(x)
panic("slices.Min: empty list")
}
m := x[0]
for i := 1; i < len(x); i++ {
m = min(m, x[i])
}
return m
} }
// MinFunc returns the minimal value in x, using cmp to compare elements. // MinFunc returns the minimal value in x, using cmp to compare elements.
// It panics if x is empty. If there is more than one minimal element // It panics if x is empty. If there is more than one minimal element
// according to the cmp function, MinFunc returns the first one. // according to the cmp function, MinFunc returns the first one.
func MinFunc[S ~[]E, E any](x S, cmp func(a, b E) int) E { func MinFunc[S ~[]E, E any](x S, cmp func(a, b E) int) E {
if len(x) < 1 { return slices.MinFunc(x, cmp)
panic("slices.MinFunc: empty list")
}
m := x[0]
for i := 1; i < len(x); i++ {
if cmp(x[i], m) < 0 {
m = x[i]
}
}
return m
} }
// Max returns the maximal value in x. It panics if x is empty. // Max returns the maximal value in x. It panics if x is empty.
// For floating-point E, Max propagates NaNs (any NaN value in x // For floating-point E, Max propagates NaNs (any NaN value in x
// forces the output to be NaN). // forces the output to be NaN).
func Max[S ~[]E, E constraints.Ordered](x S) E { func Max[S ~[]E, E cmp.Ordered](x S) E {
if len(x) < 1 { return slices.Max(x)
panic("slices.Max: empty list")
}
m := x[0]
for i := 1; i < len(x); i++ {
m = max(m, x[i])
}
return m
} }
// MaxFunc returns the maximal value in x, using cmp to compare elements. // MaxFunc returns the maximal value in x, using cmp to compare elements.
// It panics if x is empty. If there is more than one maximal element // It panics if x is empty. If there is more than one maximal element
// according to the cmp function, MaxFunc returns the first one. // according to the cmp function, MaxFunc returns the first one.
func MaxFunc[S ~[]E, E any](x S, cmp func(a, b E) int) E { func MaxFunc[S ~[]E, E any](x S, cmp func(a, b E) int) E {
if len(x) < 1 { return slices.MaxFunc(x, cmp)
panic("slices.MaxFunc: empty list")
}
m := x[0]
for i := 1; i < len(x); i++ {
if cmp(x[i], m) > 0 {
m = x[i]
}
}
return m
} }
// BinarySearch searches for target in a sorted slice and returns the position // BinarySearch searches for target in a sorted slice and returns the position
// where target is found, or the position where target would appear in the // where target is found, or the position where target would appear in the
// sort order; it also returns a bool saying whether the target is really found // sort order; it also returns a bool saying whether the target is really found
// in the slice. The slice must be sorted in increasing order. // in the slice. The slice must be sorted in increasing order.
func BinarySearch[S ~[]E, E constraints.Ordered](x S, target E) (int, bool) { func BinarySearch[S ~[]E, E cmp.Ordered](x S, target E) (int, bool) {
// Inlining is faster than calling BinarySearchFunc with a lambda. return slices.BinarySearch(x, target)
n := len(x)
// Define x[-1] < target and x[n] >= target.
// Invariant: x[i-1] < target, x[j] >= target.
i, j := 0, n
for i < j {
h := int(uint(i+j) >> 1) // avoid overflow when computing h
// i ≤ h < j
if cmpLess(x[h], target) {
i = h + 1 // preserves x[i-1] < target
} else {
j = h // preserves x[j] >= target
}
}
// i == j, x[i-1] < target, and x[j] (= x[i]) >= target => answer is i.
return i, i < n && (x[i] == target || (isNaN(x[i]) && isNaN(target)))
} }
// BinarySearchFunc works like [BinarySearch], but uses a custom comparison // BinarySearchFunc works like [BinarySearch], but uses a custom comparison
@ -151,47 +92,5 @@ func BinarySearch[S ~[]E, E constraints.Ordered](x S, target E) (int, bool) {
// cmp must implement the same ordering as the slice, such that if // cmp must implement the same ordering as the slice, such that if
// cmp(a, t) < 0 and cmp(b, t) >= 0, then a must precede b in the slice. // cmp(a, t) < 0 and cmp(b, t) >= 0, then a must precede b in the slice.
func BinarySearchFunc[S ~[]E, E, T any](x S, target T, cmp func(E, T) int) (int, bool) { func BinarySearchFunc[S ~[]E, E, T any](x S, target T, cmp func(E, T) int) (int, bool) {
n := len(x) return slices.BinarySearchFunc(x, target, cmp)
// Define cmp(x[-1], target) < 0 and cmp(x[n], target) >= 0 .
// Invariant: cmp(x[i - 1], target) < 0, cmp(x[j], target) >= 0.
i, j := 0, n
for i < j {
h := int(uint(i+j) >> 1) // avoid overflow when computing h
// i ≤ h < j
if cmp(x[h], target) < 0 {
i = h + 1 // preserves cmp(x[i - 1], target) < 0
} else {
j = h // preserves cmp(x[j], target) >= 0
}
}
// i == j, cmp(x[i-1], target) < 0, and cmp(x[j], target) (= cmp(x[i], target)) >= 0 => answer is i.
return i, i < n && cmp(x[i], target) == 0
}
type sortedHint int // hint for pdqsort when choosing the pivot
const (
unknownHint sortedHint = iota
increasingHint
decreasingHint
)
// xorshift paper: https://www.jstatsoft.org/article/view/v008i14/xorshift.pdf
type xorshift uint64
func (r *xorshift) Next() uint64 {
*r ^= *r << 13
*r ^= *r >> 17
*r ^= *r << 5
return uint64(*r)
}
func nextPowerOfTwo(length int) uint {
return 1 << bits.Len(uint(length))
}
// isNaN reports whether x is a NaN without requiring the math package.
// This will always return false if T is not floating-point.
func isNaN[T constraints.Ordered](x T) bool {
return x != x
} }

View file

@ -1,479 +0,0 @@
// Code generated by gen_sort_variants.go; DO NOT EDIT.
// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package slices
// insertionSortCmpFunc sorts data[a:b] using insertion sort.
func insertionSortCmpFunc[E any](data []E, a, b int, cmp func(a, b E) int) {
for i := a + 1; i < b; i++ {
for j := i; j > a && (cmp(data[j], data[j-1]) < 0); j-- {
data[j], data[j-1] = data[j-1], data[j]
}
}
}
// siftDownCmpFunc implements the heap property on data[lo:hi].
// first is an offset into the array where the root of the heap lies.
func siftDownCmpFunc[E any](data []E, lo, hi, first int, cmp func(a, b E) int) {
root := lo
for {
child := 2*root + 1
if child >= hi {
break
}
if child+1 < hi && (cmp(data[first+child], data[first+child+1]) < 0) {
child++
}
if !(cmp(data[first+root], data[first+child]) < 0) {
return
}
data[first+root], data[first+child] = data[first+child], data[first+root]
root = child
}
}
func heapSortCmpFunc[E any](data []E, a, b int, cmp func(a, b E) int) {
first := a
lo := 0
hi := b - a
// Build heap with greatest element at top.
for i := (hi - 1) / 2; i >= 0; i-- {
siftDownCmpFunc(data, i, hi, first, cmp)
}
// Pop elements, largest first, into end of data.
for i := hi - 1; i >= 0; i-- {
data[first], data[first+i] = data[first+i], data[first]
siftDownCmpFunc(data, lo, i, first, cmp)
}
}
// pdqsortCmpFunc sorts data[a:b].
// The algorithm based on pattern-defeating quicksort(pdqsort), but without the optimizations from BlockQuicksort.
// pdqsort paper: https://arxiv.org/pdf/2106.05123.pdf
// C++ implementation: https://github.com/orlp/pdqsort
// Rust implementation: https://docs.rs/pdqsort/latest/pdqsort/
// limit is the number of allowed bad (very unbalanced) pivots before falling back to heapsort.
func pdqsortCmpFunc[E any](data []E, a, b, limit int, cmp func(a, b E) int) {
const maxInsertion = 12
var (
wasBalanced = true // whether the last partitioning was reasonably balanced
wasPartitioned = true // whether the slice was already partitioned
)
for {
length := b - a
if length <= maxInsertion {
insertionSortCmpFunc(data, a, b, cmp)
return
}
// Fall back to heapsort if too many bad choices were made.
if limit == 0 {
heapSortCmpFunc(data, a, b, cmp)
return
}
// If the last partitioning was imbalanced, we need to breaking patterns.
if !wasBalanced {
breakPatternsCmpFunc(data, a, b, cmp)
limit--
}
pivot, hint := choosePivotCmpFunc(data, a, b, cmp)
if hint == decreasingHint {
reverseRangeCmpFunc(data, a, b, cmp)
// The chosen pivot was pivot-a elements after the start of the array.
// After reversing it is pivot-a elements before the end of the array.
// The idea came from Rust's implementation.
pivot = (b - 1) - (pivot - a)
hint = increasingHint
}
// The slice is likely already sorted.
if wasBalanced && wasPartitioned && hint == increasingHint {
if partialInsertionSortCmpFunc(data, a, b, cmp) {
return
}
}
// Probably the slice contains many duplicate elements, partition the slice into
// elements equal to and elements greater than the pivot.
if a > 0 && !(cmp(data[a-1], data[pivot]) < 0) {
mid := partitionEqualCmpFunc(data, a, b, pivot, cmp)
a = mid
continue
}
mid, alreadyPartitioned := partitionCmpFunc(data, a, b, pivot, cmp)
wasPartitioned = alreadyPartitioned
leftLen, rightLen := mid-a, b-mid
balanceThreshold := length / 8
if leftLen < rightLen {
wasBalanced = leftLen >= balanceThreshold
pdqsortCmpFunc(data, a, mid, limit, cmp)
a = mid + 1
} else {
wasBalanced = rightLen >= balanceThreshold
pdqsortCmpFunc(data, mid+1, b, limit, cmp)
b = mid
}
}
}
// partitionCmpFunc does one quicksort partition.
// Let p = data[pivot]
// Moves elements in data[a:b] around, so that data[i]<p and data[j]>=p for i<newpivot and j>newpivot.
// On return, data[newpivot] = p
func partitionCmpFunc[E any](data []E, a, b, pivot int, cmp func(a, b E) int) (newpivot int, alreadyPartitioned bool) {
data[a], data[pivot] = data[pivot], data[a]
i, j := a+1, b-1 // i and j are inclusive of the elements remaining to be partitioned
for i <= j && (cmp(data[i], data[a]) < 0) {
i++
}
for i <= j && !(cmp(data[j], data[a]) < 0) {
j--
}
if i > j {
data[j], data[a] = data[a], data[j]
return j, true
}
data[i], data[j] = data[j], data[i]
i++
j--
for {
for i <= j && (cmp(data[i], data[a]) < 0) {
i++
}
for i <= j && !(cmp(data[j], data[a]) < 0) {
j--
}
if i > j {
break
}
data[i], data[j] = data[j], data[i]
i++
j--
}
data[j], data[a] = data[a], data[j]
return j, false
}
// partitionEqualCmpFunc partitions data[a:b] into elements equal to data[pivot] followed by elements greater than data[pivot].
// It assumed that data[a:b] does not contain elements smaller than the data[pivot].
func partitionEqualCmpFunc[E any](data []E, a, b, pivot int, cmp func(a, b E) int) (newpivot int) {
data[a], data[pivot] = data[pivot], data[a]
i, j := a+1, b-1 // i and j are inclusive of the elements remaining to be partitioned
for {
for i <= j && !(cmp(data[a], data[i]) < 0) {
i++
}
for i <= j && (cmp(data[a], data[j]) < 0) {
j--
}
if i > j {
break
}
data[i], data[j] = data[j], data[i]
i++
j--
}
return i
}
// partialInsertionSortCmpFunc partially sorts a slice, returns true if the slice is sorted at the end.
func partialInsertionSortCmpFunc[E any](data []E, a, b int, cmp func(a, b E) int) bool {
const (
maxSteps = 5 // maximum number of adjacent out-of-order pairs that will get shifted
shortestShifting = 50 // don't shift any elements on short arrays
)
i := a + 1
for j := 0; j < maxSteps; j++ {
for i < b && !(cmp(data[i], data[i-1]) < 0) {
i++
}
if i == b {
return true
}
if b-a < shortestShifting {
return false
}
data[i], data[i-1] = data[i-1], data[i]
// Shift the smaller one to the left.
if i-a >= 2 {
for j := i - 1; j >= 1; j-- {
if !(cmp(data[j], data[j-1]) < 0) {
break
}
data[j], data[j-1] = data[j-1], data[j]
}
}
// Shift the greater one to the right.
if b-i >= 2 {
for j := i + 1; j < b; j++ {
if !(cmp(data[j], data[j-1]) < 0) {
break
}
data[j], data[j-1] = data[j-1], data[j]
}
}
}
return false
}
// breakPatternsCmpFunc scatters some elements around in an attempt to break some patterns
// that might cause imbalanced partitions in quicksort.
func breakPatternsCmpFunc[E any](data []E, a, b int, cmp func(a, b E) int) {
length := b - a
if length >= 8 {
random := xorshift(length)
modulus := nextPowerOfTwo(length)
for idx := a + (length/4)*2 - 1; idx <= a+(length/4)*2+1; idx++ {
other := int(uint(random.Next()) & (modulus - 1))
if other >= length {
other -= length
}
data[idx], data[a+other] = data[a+other], data[idx]
}
}
}
// choosePivotCmpFunc chooses a pivot in data[a:b].
//
// [0,8): chooses a static pivot.
// [8,shortestNinther): uses the simple median-of-three method.
// [shortestNinther,∞): uses the Tukey ninther method.
func choosePivotCmpFunc[E any](data []E, a, b int, cmp func(a, b E) int) (pivot int, hint sortedHint) {
const (
shortestNinther = 50
maxSwaps = 4 * 3
)
l := b - a
var (
swaps int
i = a + l/4*1
j = a + l/4*2
k = a + l/4*3
)
if l >= 8 {
if l >= shortestNinther {
// Tukey ninther method, the idea came from Rust's implementation.
i = medianAdjacentCmpFunc(data, i, &swaps, cmp)
j = medianAdjacentCmpFunc(data, j, &swaps, cmp)
k = medianAdjacentCmpFunc(data, k, &swaps, cmp)
}
// Find the median among i, j, k and stores it into j.
j = medianCmpFunc(data, i, j, k, &swaps, cmp)
}
switch swaps {
case 0:
return j, increasingHint
case maxSwaps:
return j, decreasingHint
default:
return j, unknownHint
}
}
// order2CmpFunc returns x,y where data[x] <= data[y], where x,y=a,b or x,y=b,a.
func order2CmpFunc[E any](data []E, a, b int, swaps *int, cmp func(a, b E) int) (int, int) {
if cmp(data[b], data[a]) < 0 {
*swaps++
return b, a
}
return a, b
}
// medianCmpFunc returns x where data[x] is the median of data[a],data[b],data[c], where x is a, b, or c.
func medianCmpFunc[E any](data []E, a, b, c int, swaps *int, cmp func(a, b E) int) int {
a, b = order2CmpFunc(data, a, b, swaps, cmp)
b, c = order2CmpFunc(data, b, c, swaps, cmp)
a, b = order2CmpFunc(data, a, b, swaps, cmp)
return b
}
// medianAdjacentCmpFunc finds the median of data[a - 1], data[a], data[a + 1] and stores the index into a.
func medianAdjacentCmpFunc[E any](data []E, a int, swaps *int, cmp func(a, b E) int) int {
return medianCmpFunc(data, a-1, a, a+1, swaps, cmp)
}
func reverseRangeCmpFunc[E any](data []E, a, b int, cmp func(a, b E) int) {
i := a
j := b - 1
for i < j {
data[i], data[j] = data[j], data[i]
i++
j--
}
}
func swapRangeCmpFunc[E any](data []E, a, b, n int, cmp func(a, b E) int) {
for i := 0; i < n; i++ {
data[a+i], data[b+i] = data[b+i], data[a+i]
}
}
func stableCmpFunc[E any](data []E, n int, cmp func(a, b E) int) {
blockSize := 20 // must be > 0
a, b := 0, blockSize
for b <= n {
insertionSortCmpFunc(data, a, b, cmp)
a = b
b += blockSize
}
insertionSortCmpFunc(data, a, n, cmp)
for blockSize < n {
a, b = 0, 2*blockSize
for b <= n {
symMergeCmpFunc(data, a, a+blockSize, b, cmp)
a = b
b += 2 * blockSize
}
if m := a + blockSize; m < n {
symMergeCmpFunc(data, a, m, n, cmp)
}
blockSize *= 2
}
}
// symMergeCmpFunc merges the two sorted subsequences data[a:m] and data[m:b] using
// the SymMerge algorithm from Pok-Son Kim and Arne Kutzner, "Stable Minimum
// Storage Merging by Symmetric Comparisons", in Susanne Albers and Tomasz
// Radzik, editors, Algorithms - ESA 2004, volume 3221 of Lecture Notes in
// Computer Science, pages 714-723. Springer, 2004.
//
// Let M = m-a and N = b-n. Wolog M < N.
// The recursion depth is bound by ceil(log(N+M)).
// The algorithm needs O(M*log(N/M + 1)) calls to data.Less.
// The algorithm needs O((M+N)*log(M)) calls to data.Swap.
//
// The paper gives O((M+N)*log(M)) as the number of assignments assuming a
// rotation algorithm which uses O(M+N+gcd(M+N)) assignments. The argumentation
// in the paper carries through for Swap operations, especially as the block
// swapping rotate uses only O(M+N) Swaps.
//
// symMerge assumes non-degenerate arguments: a < m && m < b.
// Having the caller check this condition eliminates many leaf recursion calls,
// which improves performance.
func symMergeCmpFunc[E any](data []E, a, m, b int, cmp func(a, b E) int) {
// Avoid unnecessary recursions of symMerge
// by direct insertion of data[a] into data[m:b]
// if data[a:m] only contains one element.
if m-a == 1 {
// Use binary search to find the lowest index i
// such that data[i] >= data[a] for m <= i < b.
// Exit the search loop with i == b in case no such index exists.
i := m
j := b
for i < j {
h := int(uint(i+j) >> 1)
if cmp(data[h], data[a]) < 0 {
i = h + 1
} else {
j = h
}
}
// Swap values until data[a] reaches the position before i.
for k := a; k < i-1; k++ {
data[k], data[k+1] = data[k+1], data[k]
}
return
}
// Avoid unnecessary recursions of symMerge
// by direct insertion of data[m] into data[a:m]
// if data[m:b] only contains one element.
if b-m == 1 {
// Use binary search to find the lowest index i
// such that data[i] > data[m] for a <= i < m.
// Exit the search loop with i == m in case no such index exists.
i := a
j := m
for i < j {
h := int(uint(i+j) >> 1)
if !(cmp(data[m], data[h]) < 0) {
i = h + 1
} else {
j = h
}
}
// Swap values until data[m] reaches the position i.
for k := m; k > i; k-- {
data[k], data[k-1] = data[k-1], data[k]
}
return
}
mid := int(uint(a+b) >> 1)
n := mid + m
var start, r int
if m > mid {
start = n - b
r = mid
} else {
start = a
r = m
}
p := n - 1
for start < r {
c := int(uint(start+r) >> 1)
if !(cmp(data[p-c], data[c]) < 0) {
start = c + 1
} else {
r = c
}
}
end := n - start
if start < m && m < end {
rotateCmpFunc(data, start, m, end, cmp)
}
if a < start && start < mid {
symMergeCmpFunc(data, a, start, mid, cmp)
}
if mid < end && end < b {
symMergeCmpFunc(data, mid, end, b, cmp)
}
}
// rotateCmpFunc rotates two consecutive blocks u = data[a:m] and v = data[m:b] in data:
// Data of the form 'x u v y' is changed to 'x v u y'.
// rotate performs at most b-a many calls to data.Swap,
// and it assumes non-degenerate arguments: a < m && m < b.
func rotateCmpFunc[E any](data []E, a, m, b int, cmp func(a, b E) int) {
i := m - a
j := b - m
for i != j {
if i > j {
swapRangeCmpFunc(data, m-i, m, j, cmp)
i -= j
} else {
swapRangeCmpFunc(data, m-i, m+j-i, i, cmp)
j -= i
}
}
// i == j
swapRangeCmpFunc(data, m-i, m, i, cmp)
}

View file

@ -1,481 +0,0 @@
// Code generated by gen_sort_variants.go; DO NOT EDIT.
// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package slices
import "golang.org/x/exp/constraints"
// insertionSortOrdered sorts data[a:b] using insertion sort.
func insertionSortOrdered[E constraints.Ordered](data []E, a, b int) {
for i := a + 1; i < b; i++ {
for j := i; j > a && cmpLess(data[j], data[j-1]); j-- {
data[j], data[j-1] = data[j-1], data[j]
}
}
}
// siftDownOrdered implements the heap property on data[lo:hi].
// first is an offset into the array where the root of the heap lies.
func siftDownOrdered[E constraints.Ordered](data []E, lo, hi, first int) {
root := lo
for {
child := 2*root + 1
if child >= hi {
break
}
if child+1 < hi && cmpLess(data[first+child], data[first+child+1]) {
child++
}
if !cmpLess(data[first+root], data[first+child]) {
return
}
data[first+root], data[first+child] = data[first+child], data[first+root]
root = child
}
}
func heapSortOrdered[E constraints.Ordered](data []E, a, b int) {
first := a
lo := 0
hi := b - a
// Build heap with greatest element at top.
for i := (hi - 1) / 2; i >= 0; i-- {
siftDownOrdered(data, i, hi, first)
}
// Pop elements, largest first, into end of data.
for i := hi - 1; i >= 0; i-- {
data[first], data[first+i] = data[first+i], data[first]
siftDownOrdered(data, lo, i, first)
}
}
// pdqsortOrdered sorts data[a:b].
// The algorithm based on pattern-defeating quicksort(pdqsort), but without the optimizations from BlockQuicksort.
// pdqsort paper: https://arxiv.org/pdf/2106.05123.pdf
// C++ implementation: https://github.com/orlp/pdqsort
// Rust implementation: https://docs.rs/pdqsort/latest/pdqsort/
// limit is the number of allowed bad (very unbalanced) pivots before falling back to heapsort.
func pdqsortOrdered[E constraints.Ordered](data []E, a, b, limit int) {
const maxInsertion = 12
var (
wasBalanced = true // whether the last partitioning was reasonably balanced
wasPartitioned = true // whether the slice was already partitioned
)
for {
length := b - a
if length <= maxInsertion {
insertionSortOrdered(data, a, b)
return
}
// Fall back to heapsort if too many bad choices were made.
if limit == 0 {
heapSortOrdered(data, a, b)
return
}
// If the last partitioning was imbalanced, we need to breaking patterns.
if !wasBalanced {
breakPatternsOrdered(data, a, b)
limit--
}
pivot, hint := choosePivotOrdered(data, a, b)
if hint == decreasingHint {
reverseRangeOrdered(data, a, b)
// The chosen pivot was pivot-a elements after the start of the array.
// After reversing it is pivot-a elements before the end of the array.
// The idea came from Rust's implementation.
pivot = (b - 1) - (pivot - a)
hint = increasingHint
}
// The slice is likely already sorted.
if wasBalanced && wasPartitioned && hint == increasingHint {
if partialInsertionSortOrdered(data, a, b) {
return
}
}
// Probably the slice contains many duplicate elements, partition the slice into
// elements equal to and elements greater than the pivot.
if a > 0 && !cmpLess(data[a-1], data[pivot]) {
mid := partitionEqualOrdered(data, a, b, pivot)
a = mid
continue
}
mid, alreadyPartitioned := partitionOrdered(data, a, b, pivot)
wasPartitioned = alreadyPartitioned
leftLen, rightLen := mid-a, b-mid
balanceThreshold := length / 8
if leftLen < rightLen {
wasBalanced = leftLen >= balanceThreshold
pdqsortOrdered(data, a, mid, limit)
a = mid + 1
} else {
wasBalanced = rightLen >= balanceThreshold
pdqsortOrdered(data, mid+1, b, limit)
b = mid
}
}
}
// partitionOrdered does one quicksort partition.
// Let p = data[pivot]
// Moves elements in data[a:b] around, so that data[i]<p and data[j]>=p for i<newpivot and j>newpivot.
// On return, data[newpivot] = p
func partitionOrdered[E constraints.Ordered](data []E, a, b, pivot int) (newpivot int, alreadyPartitioned bool) {
data[a], data[pivot] = data[pivot], data[a]
i, j := a+1, b-1 // i and j are inclusive of the elements remaining to be partitioned
for i <= j && cmpLess(data[i], data[a]) {
i++
}
for i <= j && !cmpLess(data[j], data[a]) {
j--
}
if i > j {
data[j], data[a] = data[a], data[j]
return j, true
}
data[i], data[j] = data[j], data[i]
i++
j--
for {
for i <= j && cmpLess(data[i], data[a]) {
i++
}
for i <= j && !cmpLess(data[j], data[a]) {
j--
}
if i > j {
break
}
data[i], data[j] = data[j], data[i]
i++
j--
}
data[j], data[a] = data[a], data[j]
return j, false
}
// partitionEqualOrdered partitions data[a:b] into elements equal to data[pivot] followed by elements greater than data[pivot].
// It assumed that data[a:b] does not contain elements smaller than the data[pivot].
func partitionEqualOrdered[E constraints.Ordered](data []E, a, b, pivot int) (newpivot int) {
data[a], data[pivot] = data[pivot], data[a]
i, j := a+1, b-1 // i and j are inclusive of the elements remaining to be partitioned
for {
for i <= j && !cmpLess(data[a], data[i]) {
i++
}
for i <= j && cmpLess(data[a], data[j]) {
j--
}
if i > j {
break
}
data[i], data[j] = data[j], data[i]
i++
j--
}
return i
}
// partialInsertionSortOrdered partially sorts a slice, returns true if the slice is sorted at the end.
func partialInsertionSortOrdered[E constraints.Ordered](data []E, a, b int) bool {
const (
maxSteps = 5 // maximum number of adjacent out-of-order pairs that will get shifted
shortestShifting = 50 // don't shift any elements on short arrays
)
i := a + 1
for j := 0; j < maxSteps; j++ {
for i < b && !cmpLess(data[i], data[i-1]) {
i++
}
if i == b {
return true
}
if b-a < shortestShifting {
return false
}
data[i], data[i-1] = data[i-1], data[i]
// Shift the smaller one to the left.
if i-a >= 2 {
for j := i - 1; j >= 1; j-- {
if !cmpLess(data[j], data[j-1]) {
break
}
data[j], data[j-1] = data[j-1], data[j]
}
}
// Shift the greater one to the right.
if b-i >= 2 {
for j := i + 1; j < b; j++ {
if !cmpLess(data[j], data[j-1]) {
break
}
data[j], data[j-1] = data[j-1], data[j]
}
}
}
return false
}
// breakPatternsOrdered scatters some elements around in an attempt to break some patterns
// that might cause imbalanced partitions in quicksort.
func breakPatternsOrdered[E constraints.Ordered](data []E, a, b int) {
length := b - a
if length >= 8 {
random := xorshift(length)
modulus := nextPowerOfTwo(length)
for idx := a + (length/4)*2 - 1; idx <= a+(length/4)*2+1; idx++ {
other := int(uint(random.Next()) & (modulus - 1))
if other >= length {
other -= length
}
data[idx], data[a+other] = data[a+other], data[idx]
}
}
}
// choosePivotOrdered chooses a pivot in data[a:b].
//
// [0,8): chooses a static pivot.
// [8,shortestNinther): uses the simple median-of-three method.
// [shortestNinther,∞): uses the Tukey ninther method.
func choosePivotOrdered[E constraints.Ordered](data []E, a, b int) (pivot int, hint sortedHint) {
const (
shortestNinther = 50
maxSwaps = 4 * 3
)
l := b - a
var (
swaps int
i = a + l/4*1
j = a + l/4*2
k = a + l/4*3
)
if l >= 8 {
if l >= shortestNinther {
// Tukey ninther method, the idea came from Rust's implementation.
i = medianAdjacentOrdered(data, i, &swaps)
j = medianAdjacentOrdered(data, j, &swaps)
k = medianAdjacentOrdered(data, k, &swaps)
}
// Find the median among i, j, k and stores it into j.
j = medianOrdered(data, i, j, k, &swaps)
}
switch swaps {
case 0:
return j, increasingHint
case maxSwaps:
return j, decreasingHint
default:
return j, unknownHint
}
}
// order2Ordered returns x,y where data[x] <= data[y], where x,y=a,b or x,y=b,a.
func order2Ordered[E constraints.Ordered](data []E, a, b int, swaps *int) (int, int) {
if cmpLess(data[b], data[a]) {
*swaps++
return b, a
}
return a, b
}
// medianOrdered returns x where data[x] is the median of data[a],data[b],data[c], where x is a, b, or c.
func medianOrdered[E constraints.Ordered](data []E, a, b, c int, swaps *int) int {
a, b = order2Ordered(data, a, b, swaps)
b, c = order2Ordered(data, b, c, swaps)
a, b = order2Ordered(data, a, b, swaps)
return b
}
// medianAdjacentOrdered finds the median of data[a - 1], data[a], data[a + 1] and stores the index into a.
func medianAdjacentOrdered[E constraints.Ordered](data []E, a int, swaps *int) int {
return medianOrdered(data, a-1, a, a+1, swaps)
}
func reverseRangeOrdered[E constraints.Ordered](data []E, a, b int) {
i := a
j := b - 1
for i < j {
data[i], data[j] = data[j], data[i]
i++
j--
}
}
func swapRangeOrdered[E constraints.Ordered](data []E, a, b, n int) {
for i := 0; i < n; i++ {
data[a+i], data[b+i] = data[b+i], data[a+i]
}
}
func stableOrdered[E constraints.Ordered](data []E, n int) {
blockSize := 20 // must be > 0
a, b := 0, blockSize
for b <= n {
insertionSortOrdered(data, a, b)
a = b
b += blockSize
}
insertionSortOrdered(data, a, n)
for blockSize < n {
a, b = 0, 2*blockSize
for b <= n {
symMergeOrdered(data, a, a+blockSize, b)
a = b
b += 2 * blockSize
}
if m := a + blockSize; m < n {
symMergeOrdered(data, a, m, n)
}
blockSize *= 2
}
}
// symMergeOrdered merges the two sorted subsequences data[a:m] and data[m:b] using
// the SymMerge algorithm from Pok-Son Kim and Arne Kutzner, "Stable Minimum
// Storage Merging by Symmetric Comparisons", in Susanne Albers and Tomasz
// Radzik, editors, Algorithms - ESA 2004, volume 3221 of Lecture Notes in
// Computer Science, pages 714-723. Springer, 2004.
//
// Let M = m-a and N = b-n. Wolog M < N.
// The recursion depth is bound by ceil(log(N+M)).
// The algorithm needs O(M*log(N/M + 1)) calls to data.Less.
// The algorithm needs O((M+N)*log(M)) calls to data.Swap.
//
// The paper gives O((M+N)*log(M)) as the number of assignments assuming a
// rotation algorithm which uses O(M+N+gcd(M+N)) assignments. The argumentation
// in the paper carries through for Swap operations, especially as the block
// swapping rotate uses only O(M+N) Swaps.
//
// symMerge assumes non-degenerate arguments: a < m && m < b.
// Having the caller check this condition eliminates many leaf recursion calls,
// which improves performance.
func symMergeOrdered[E constraints.Ordered](data []E, a, m, b int) {
// Avoid unnecessary recursions of symMerge
// by direct insertion of data[a] into data[m:b]
// if data[a:m] only contains one element.
if m-a == 1 {
// Use binary search to find the lowest index i
// such that data[i] >= data[a] for m <= i < b.
// Exit the search loop with i == b in case no such index exists.
i := m
j := b
for i < j {
h := int(uint(i+j) >> 1)
if cmpLess(data[h], data[a]) {
i = h + 1
} else {
j = h
}
}
// Swap values until data[a] reaches the position before i.
for k := a; k < i-1; k++ {
data[k], data[k+1] = data[k+1], data[k]
}
return
}
// Avoid unnecessary recursions of symMerge
// by direct insertion of data[m] into data[a:m]
// if data[m:b] only contains one element.
if b-m == 1 {
// Use binary search to find the lowest index i
// such that data[i] > data[m] for a <= i < m.
// Exit the search loop with i == m in case no such index exists.
i := a
j := m
for i < j {
h := int(uint(i+j) >> 1)
if !cmpLess(data[m], data[h]) {
i = h + 1
} else {
j = h
}
}
// Swap values until data[m] reaches the position i.
for k := m; k > i; k-- {
data[k], data[k-1] = data[k-1], data[k]
}
return
}
mid := int(uint(a+b) >> 1)
n := mid + m
var start, r int
if m > mid {
start = n - b
r = mid
} else {
start = a
r = m
}
p := n - 1
for start < r {
c := int(uint(start+r) >> 1)
if !cmpLess(data[p-c], data[c]) {
start = c + 1
} else {
r = c
}
}
end := n - start
if start < m && m < end {
rotateOrdered(data, start, m, end)
}
if a < start && start < mid {
symMergeOrdered(data, a, start, mid)
}
if mid < end && end < b {
symMergeOrdered(data, mid, end, b)
}
}
// rotateOrdered rotates two consecutive blocks u = data[a:m] and v = data[m:b] in data:
// Data of the form 'x u v y' is changed to 'x v u y'.
// rotate performs at most b-a many calls to data.Swap,
// and it assumes non-degenerate arguments: a < m && m < b.
func rotateOrdered[E constraints.Ordered](data []E, a, m, b int) {
i := m - a
j := b - m
for i != j {
if i > j {
swapRangeOrdered(data, m-i, m, j)
i -= j
} else {
swapRangeOrdered(data, m-i, m+j-i, i)
j -= i
}
}
// i == j
swapRangeOrdered(data, m-i, m, i)
}

View file

@ -87,7 +87,7 @@ func parseDoctype(s string) (n *Node, quirks bool) {
} }
} }
if lastAttr := n.Attr[len(n.Attr)-1]; lastAttr.Key == "system" && if lastAttr := n.Attr[len(n.Attr)-1]; lastAttr.Key == "system" &&
strings.ToLower(lastAttr.Val) == "http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd" { strings.EqualFold(lastAttr.Val, "http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd") {
quirks = true quirks = true
} }
} }

View file

@ -40,8 +40,7 @@ func htmlIntegrationPoint(n *Node) bool {
if n.Data == "annotation-xml" { if n.Data == "annotation-xml" {
for _, a := range n.Attr { for _, a := range n.Attr {
if a.Key == "encoding" { if a.Key == "encoding" {
val := strings.ToLower(a.Val) if strings.EqualFold(a.Val, "text/html") || strings.EqualFold(a.Val, "application/xhtml+xml") {
if val == "text/html" || val == "application/xhtml+xml" {
return true return true
} }
} }

View file

@ -840,6 +840,10 @@ func afterHeadIM(p *parser) bool {
p.parseImpliedToken(StartTagToken, a.Body, a.Body.String()) p.parseImpliedToken(StartTagToken, a.Body, a.Body.String())
p.framesetOK = true p.framesetOK = true
if p.tok.Type == ErrorToken {
// Stop parsing.
return true
}
return false return false
} }
@ -1031,7 +1035,7 @@ func inBodyIM(p *parser) bool {
if p.tok.DataAtom == a.Input { if p.tok.DataAtom == a.Input {
for _, t := range p.tok.Attr { for _, t := range p.tok.Attr {
if t.Key == "type" { if t.Key == "type" {
if strings.ToLower(t.Val) == "hidden" { if strings.EqualFold(t.Val, "hidden") {
// Skip setting framesetOK = false // Skip setting framesetOK = false
return true return true
} }
@ -1459,7 +1463,7 @@ func inTableIM(p *parser) bool {
return inHeadIM(p) return inHeadIM(p)
case a.Input: case a.Input:
for _, t := range p.tok.Attr { for _, t := range p.tok.Attr {
if t.Key == "type" && strings.ToLower(t.Val) == "hidden" { if t.Key == "type" && strings.EqualFold(t.Val, "hidden") {
p.addElement() p.addElement()
p.oe.pop() p.oe.pop()
return true return true

View file

@ -1,3 +1,26 @@
## v0.22.1 (2024-12-16)
* *(crypto)* Added automatic cleanup when there are too many olm sessions with
a single device.
* *(crypto)* Added helper for getting cached device list with cross-signing
status.
* *(crypto/verificationhelper)* Added interface for persisting the state of
in-progress verifications.
* *(client)* Added `GetMutualRooms` wrapper for [MSC2666].
* *(client)* Switched `JoinRoom` to use the `via` query param instead of
`server_name` as per [MSC4156].
* *(bridgev2/commands)* Fixed `pm` command not actually starting the chat.
* *(bridgev2/interface)* Added separate network API interface for starting
chats with a Matrix ghost user. This allows treating internal user IDs
differently than arbitrary user-input strings.
* *(bridgev2/crypto)* Added support for [MSC4190]
(thanks to [@onestacked] in [#288]).
[MSC2666]: https://github.com/matrix-org/matrix-spec-proposals/pull/2666
[MSC4156]: https://github.com/matrix-org/matrix-spec-proposals/pull/4156
[MSC4190]: https://github.com/matrix-org/matrix-spec-proposals/pull/4190
[#288]: https://github.com/mautrix/go/pull/288
## v0.22.0 (2024-11-16) ## v0.22.0 (2024-11-16)
* *(hicli)* Moved package into gomuks repo. * *(hicli)* Moved package into gomuks repo.

View file

@ -21,6 +21,7 @@ import (
"github.com/rs/zerolog" "github.com/rs/zerolog"
"go.mau.fi/util/ptr" "go.mau.fi/util/ptr"
"go.mau.fi/util/random"
"go.mau.fi/util/retryafter" "go.mau.fi/util/retryafter"
"golang.org/x/exp/maps" "golang.org/x/exp/maps"
@ -240,7 +241,7 @@ func (cli *Client) SyncWithContext(ctx context.Context) error {
streamResp = true streamResp = true
} }
timeout := 30000 timeout := 30000
if isFailing { if isFailing || nextBatch == "" {
timeout = 0 timeout = 0
} }
resSync, err := cli.FullSyncRequest(ctx, ReqSync{ resSync, err := cli.FullSyncRequest(ctx, ReqSync{
@ -901,6 +902,22 @@ func (cli *Client) Login(ctx context.Context, req *ReqLogin) (resp *RespLogin, e
return return
} }
// Create a device for an appservice user using MSC4190.
func (cli *Client) CreateDeviceMSC4190(ctx context.Context, deviceID id.DeviceID, initialDisplayName string) error {
if len(deviceID) == 0 {
deviceID = id.DeviceID(strings.ToUpper(random.String(10)))
}
_, err := cli.MakeRequest(ctx, http.MethodPut, cli.BuildClientURL("v3", "devices", deviceID), &ReqPutDevice{
DisplayName: initialDisplayName,
}, nil)
if err != nil {
return err
}
cli.DeviceID = deviceID
cli.SetAppServiceDeviceID = true
return nil
}
// Logout the current user. See https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3logout // Logout the current user. See https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3logout
// This does not clear the credentials from the client instance. See ClearCredentials() instead. // This does not clear the credentials from the client instance. See ClearCredentials() instead.
func (cli *Client) Logout(ctx context.Context) (resp *RespLogout, err error) { func (cli *Client) Logout(ctx context.Context) (resp *RespLogout, err error) {
@ -942,7 +959,7 @@ func (cli *Client) JoinRoom(ctx context.Context, roomIDorAlias, serverName strin
var urlPath string var urlPath string
if serverName != "" { if serverName != "" {
urlPath = cli.BuildURLWithQuery(ClientURLPath{"v3", "join", roomIDorAlias}, map[string]string{ urlPath = cli.BuildURLWithQuery(ClientURLPath{"v3", "join", roomIDorAlias}, map[string]string{
"server_name": serverName, "via": serverName,
}) })
} else { } else {
urlPath = cli.BuildClientURL("v3", "join", roomIDorAlias) urlPath = cli.BuildClientURL("v3", "join", roomIDorAlias)
@ -978,6 +995,22 @@ func (cli *Client) GetProfile(ctx context.Context, mxid id.UserID) (resp *RespUs
return return
} }
func (cli *Client) GetMutualRooms(ctx context.Context, otherUserID id.UserID, extras ...ReqMutualRooms) (resp *RespMutualRooms, err error) {
if cli.SpecVersions != nil && !cli.SpecVersions.Supports(FeatureMutualRooms) {
err = fmt.Errorf("server does not support fetching mutual rooms")
return
}
query := map[string]string{
"user_id": otherUserID.String(),
}
if len(extras) > 0 {
query["from"] = extras[0].From
}
urlPath := cli.BuildURLWithQuery(ClientURLPath{"unstable", "uk.half-shot.msc2666", "user", "mutual_rooms"}, query)
_, err = cli.MakeRequest(ctx, http.MethodGet, urlPath, nil, &resp)
return
}
// GetDisplayName returns the display name of the user with the specified MXID. See https://spec.matrix.org/v1.2/client-server-api/#get_matrixclientv3profileuseriddisplayname // GetDisplayName returns the display name of the user with the specified MXID. See https://spec.matrix.org/v1.2/client-server-api/#get_matrixclientv3profileuseriddisplayname
func (cli *Client) GetDisplayName(ctx context.Context, mxid id.UserID) (resp *RespUserDisplayName, err error) { func (cli *Client) GetDisplayName(ctx context.Context, mxid id.UserID) (resp *RespUserDisplayName, err error) {
urlPath := cli.BuildClientURL("v3", "profile", mxid, "displayname") urlPath := cli.BuildClientURL("v3", "profile", mxid, "displayname")

View file

@ -11,6 +11,7 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"slices"
"time" "time"
"github.com/rs/zerolog" "github.com/rs/zerolog"
@ -74,6 +75,11 @@ func (mach *OlmMachine) decryptAndParseOlmCiphertext(ctx context.Context, evt *e
return nil, UnsupportedOlmMessageType return nil, UnsupportedOlmMessageType
} }
log := mach.machOrContextLog(ctx).With().
Stringer("sender_key", senderKey).
Int("olm_msg_type", int(olmType)).
Logger()
ctx = log.WithContext(ctx)
endTimeTrace := mach.timeTrace(ctx, "decrypting olm ciphertext", 5*time.Second) endTimeTrace := mach.timeTrace(ctx, "decrypting olm ciphertext", 5*time.Second)
plaintext, err := mach.tryDecryptOlmCiphertext(ctx, evt.Sender, senderKey, olmType, ciphertext) plaintext, err := mach.tryDecryptOlmCiphertext(ctx, evt.Sender, senderKey, olmType, ciphertext)
endTimeTrace() endTimeTrace()
@ -168,6 +174,8 @@ func (mach *OlmMachine) tryDecryptOlmCiphertext(ctx context.Context, sender id.U
return plaintext, nil return plaintext, nil
} }
const MaxOlmSessionsPerDevice = 5
func (mach *OlmMachine) tryDecryptOlmCiphertextWithExistingSession(ctx context.Context, senderKey id.SenderKey, olmType id.OlmMsgType, ciphertext string) ([]byte, error) { func (mach *OlmMachine) tryDecryptOlmCiphertextWithExistingSession(ctx context.Context, senderKey id.SenderKey, olmType id.OlmMsgType, ciphertext string) ([]byte, error) {
log := *zerolog.Ctx(ctx) log := *zerolog.Ctx(ctx)
endTimeTrace := mach.timeTrace(ctx, "getting sessions with sender key", time.Second) endTimeTrace := mach.timeTrace(ctx, "getting sessions with sender key", time.Second)
@ -176,6 +184,32 @@ func (mach *OlmMachine) tryDecryptOlmCiphertextWithExistingSession(ctx context.C
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to get session for %s: %w", senderKey, err) return nil, fmt.Errorf("failed to get session for %s: %w", senderKey, err)
} }
if len(sessions) > MaxOlmSessionsPerDevice*2 {
// SQL store sorts sessions, but other implementations may not, so re-sort just in case
slices.SortFunc(sessions, func(a, b *OlmSession) int {
return b.LastDecryptedTime.Compare(a.LastDecryptedTime)
})
log.Warn().
Int("session_count", len(sessions)).
Time("newest_last_decrypted_at", sessions[0].LastDecryptedTime).
Time("oldest_last_decrypted_at", sessions[len(sessions)-1].LastDecryptedTime).
Msg("Too many sessions, deleting old ones")
for i := MaxOlmSessionsPerDevice; i < len(sessions); i++ {
err = mach.CryptoStore.DeleteSession(ctx, senderKey, sessions[i])
if err != nil {
log.Warn().Err(err).
Stringer("olm_session_id", sessions[i].ID()).
Time("last_decrypt", sessions[i].LastDecryptedTime).
Msg("Failed to delete olm session")
} else {
log.Debug().
Stringer("olm_session_id", sessions[i].ID()).
Time("last_decrypt", sessions[i].LastDecryptedTime).
Msg("Deleted olm session")
}
}
sessions = sessions[:MaxOlmSessionsPerDevice]
}
for _, session := range sessions { for _, session := range sessions {
log := log.With().Str("olm_session_id", session.ID().String()).Logger() log := log.With().Str("olm_session_id", session.ID().String()).Logger()
@ -190,11 +224,13 @@ func (mach *OlmMachine) tryDecryptOlmCiphertextWithExistingSession(ctx context.C
continue continue
} }
} }
log.Debug().Str("session_description", session.Describe()).Msg("Trying to decrypt olm message")
endTimeTrace = mach.timeTrace(ctx, "decrypting olm message", time.Second) endTimeTrace = mach.timeTrace(ctx, "decrypting olm message", time.Second)
plaintext, err := session.Decrypt(ciphertext, olmType) plaintext, err := session.Decrypt(ciphertext, olmType)
endTimeTrace() endTimeTrace()
if err != nil { if err != nil {
log.Warn().Err(err).
Str("session_description", session.Describe()).
Msg("Failed to decrypt olm message")
if olmType == id.OlmMsgTypePreKey { if olmType == id.OlmMsgTypePreKey {
return nil, DecryptionFailedWithMatchingSession return nil, DecryptionFailedWithMatchingSession
} }
@ -205,7 +241,7 @@ func (mach *OlmMachine) tryDecryptOlmCiphertextWithExistingSession(ctx context.C
if err != nil { if err != nil {
log.Warn().Err(err).Msg("Failed to update olm session in crypto store after decrypting") log.Warn().Err(err).Msg("Failed to update olm session in crypto store after decrypting")
} }
log.Debug().Msg("Decrypted olm message") log.Debug().Str("session_description", session.Describe()).Msg("Decrypted olm message")
return plaintext, nil return plaintext, nil
} }
} }

View file

@ -10,6 +10,8 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"slices"
"strings"
"github.com/rs/zerolog" "github.com/rs/zerolog"
"go.mau.fi/util/exzerolog" "go.mau.fi/util/exzerolog"
@ -26,6 +28,8 @@ var (
NoSigningKeyFound = errors.New("didn't find ed25519 signing key") NoSigningKeyFound = errors.New("didn't find ed25519 signing key")
NoIdentityKeyFound = errors.New("didn't find curve25519 identity key") NoIdentityKeyFound = errors.New("didn't find curve25519 identity key")
InvalidKeySignature = errors.New("invalid signature on device keys") InvalidKeySignature = errors.New("invalid signature on device keys")
ErrUserNotTracked = errors.New("user is not tracked")
) )
func (mach *OlmMachine) LoadDevices(ctx context.Context, user id.UserID) (keys map[id.DeviceID]*id.Device) { func (mach *OlmMachine) LoadDevices(ctx context.Context, user id.UserID) (keys map[id.DeviceID]*id.Device) {
@ -40,6 +44,81 @@ func (mach *OlmMachine) LoadDevices(ctx context.Context, user id.UserID) (keys m
return nil return nil
} }
type CachedDevices struct {
Devices []*id.Device
MasterKey *id.CrossSigningKey
HasValidSelfSigningKey bool
MasterKeySignedByUs bool
}
func (mach *OlmMachine) GetCachedDevices(ctx context.Context, userID id.UserID) (*CachedDevices, error) {
userIDs, err := mach.CryptoStore.FilterTrackedUsers(ctx, []id.UserID{userID})
if err != nil {
return nil, fmt.Errorf("failed to check if user's devices are tracked: %w", err)
} else if len(userIDs) == 0 {
return nil, ErrUserNotTracked
}
ownKeys := mach.GetOwnCrossSigningPublicKeys(ctx)
var ownUserSigningKey id.Ed25519
if ownKeys != nil {
ownUserSigningKey = ownKeys.UserSigningKey
}
var resp CachedDevices
csKeys, err := mach.CryptoStore.GetCrossSigningKeys(ctx, userID)
theirMasterKey := csKeys[id.XSUsageMaster]
theirSelfSignKey := csKeys[id.XSUsageSelfSigning]
if err != nil {
return nil, fmt.Errorf("failed to get cross-signing keys: %w", err)
} else if csKeys != nil && theirMasterKey.Key != "" {
resp.MasterKey = &theirMasterKey
if theirSelfSignKey.Key != "" {
resp.HasValidSelfSigningKey, err = mach.CryptoStore.IsKeySignedBy(ctx, userID, theirSelfSignKey.Key, userID, theirMasterKey.Key)
if err != nil {
return nil, fmt.Errorf("failed to check if self-signing key is signed by master key: %w", err)
}
}
}
devices, err := mach.CryptoStore.GetDevices(ctx, userID)
if err != nil {
return nil, fmt.Errorf("failed to get devices: %w", err)
}
if userID == mach.Client.UserID {
if ownKeys != nil && ownKeys.MasterKey == theirMasterKey.Key {
resp.MasterKeySignedByUs, err = mach.CryptoStore.IsKeySignedBy(ctx, userID, theirMasterKey.Key, userID, mach.OwnIdentity().SigningKey)
}
} else if ownUserSigningKey != "" && theirMasterKey.Key != "" {
// TODO should own master key and user-signing key signatures be checked here too?
resp.MasterKeySignedByUs, err = mach.CryptoStore.IsKeySignedBy(ctx, userID, theirMasterKey.Key, mach.Client.UserID, ownUserSigningKey)
}
if err != nil {
return nil, fmt.Errorf("failed to check if user is trusted: %w", err)
}
resp.Devices = make([]*id.Device, len(devices))
i := 0
for _, device := range devices {
resp.Devices[i] = device
if resp.HasValidSelfSigningKey && device.Trust == id.TrustStateUnset {
signed, err := mach.CryptoStore.IsKeySignedBy(ctx, device.UserID, device.SigningKey, device.UserID, theirSelfSignKey.Key)
if err != nil {
return nil, fmt.Errorf("failed to check if device %s is signed by self-signing key: %w", device.DeviceID, err)
} else if signed {
if resp.MasterKeySignedByUs {
device.Trust = id.TrustStateCrossSignedVerified
} else if theirMasterKey.Key == theirMasterKey.First {
device.Trust = id.TrustStateCrossSignedTOFU
} else {
device.Trust = id.TrustStateCrossSignedUntrusted
}
}
}
i++
}
slices.SortFunc(resp.Devices, func(a, b *id.Device) int {
return strings.Compare(a.DeviceID.String(), b.DeviceID.String())
})
return &resp, nil
}
func (mach *OlmMachine) storeDeviceSelfSignatures(ctx context.Context, userID id.UserID, deviceID id.DeviceID, resp *mautrix.RespQueryKeys) { func (mach *OlmMachine) storeDeviceSelfSignatures(ctx context.Context, userID id.UserID, deviceID id.DeviceID, resp *mautrix.RespQueryKeys) {
log := zerolog.Ctx(ctx) log := zerolog.Ctx(ctx)
deviceKeys := resp.DeviceKeys[userID][deviceID] deviceKeys := resp.DeviceKeys[userID][deviceID]

View file

@ -278,7 +278,7 @@ func (mach *OlmMachine) HandleOTKCounts(ctx context.Context, otkCount *mautrix.O
log := mach.Log.With().Str("trace_id", traceID).Logger() log := mach.Log.With().Str("trace_id", traceID).Logger()
ctx = log.WithContext(ctx) ctx = log.WithContext(ctx)
log.Debug(). log.Debug().
Int("keys_left", otkCount.Curve25519). Int("keys_left", otkCount.SignedCurve25519).
Msg("Sync response said we have less than 50 signed curve25519 keys left, sharing new ones...") Msg("Sync response said we have less than 50 signed curve25519 keys left, sharing new ones...")
err := mach.ShareKeys(ctx, otkCount.SignedCurve25519) err := mach.ShareKeys(ctx, otkCount.SignedCurve25519)
if err != nil { if err != nil {

View file

@ -219,7 +219,7 @@ func (store *SQLCryptoStore) getOlmSessionCache(key id.SenderKey) map[id.Session
return data return data
} }
// GetLatestSession retrieves the Olm session for a given sender key from the database that has the largest ID. // GetLatestSession retrieves the Olm session for a given sender key from the database that had the most recent successful decryption.
func (store *SQLCryptoStore) GetLatestSession(ctx context.Context, key id.SenderKey) (*OlmSession, error) { func (store *SQLCryptoStore) GetLatestSession(ctx context.Context, key id.SenderKey) (*OlmSession, error) {
store.olmSessionCacheLock.Lock() store.olmSessionCacheLock.Lock()
defer store.olmSessionCacheLock.Unlock() defer store.olmSessionCacheLock.Unlock()
@ -274,6 +274,11 @@ func (store *SQLCryptoStore) UpdateSession(ctx context.Context, _ id.SenderKey,
return err return err
} }
func (store *SQLCryptoStore) DeleteSession(ctx context.Context, _ id.SenderKey, session *OlmSession) error {
_, err := store.DB.Exec(ctx, "DELETE FROM crypto_olm_session WHERE session_id=$1 AND account_id=$2", session.ID(), store.AccountID)
return err
}
func datePtr(t time.Time) *time.Time { func datePtr(t time.Time) *time.Time {
if t.IsZero() { if t.IsZero() {
return nil return nil

View file

@ -1,4 +1,4 @@
-- v0 -> v15: Latest revision -- v0 -> v16 (compatible with v15+): Latest revision
CREATE TABLE IF NOT EXISTS crypto_account ( CREATE TABLE IF NOT EXISTS crypto_account (
account_id TEXT PRIMARY KEY, account_id TEXT PRIMARY KEY,
device_id TEXT NOT NULL, device_id TEXT NOT NULL,
@ -43,6 +43,7 @@ CREATE TABLE IF NOT EXISTS crypto_olm_session (
last_encrypted timestamp NOT NULL, last_encrypted timestamp NOT NULL,
PRIMARY KEY (account_id, session_id) PRIMARY KEY (account_id, session_id)
); );
CREATE INDEX crypto_olm_session_sender_key_idx ON crypto_olm_session (account_id, sender_key);
CREATE TABLE IF NOT EXISTS crypto_megolm_inbound_session ( CREATE TABLE IF NOT EXISTS crypto_megolm_inbound_session (
account_id TEXT, account_id TEXT,

View file

@ -0,0 +1,2 @@
-- v16 (compatible with v15+): Add index to crypto_olm_sessions to speedup lookups by sender_key
CREATE INDEX crypto_olm_session_sender_key_idx ON crypto_olm_session (account_id, sender_key);

View file

@ -9,6 +9,7 @@ package crypto
import ( import (
"context" "context"
"fmt" "fmt"
"slices"
"sort" "sort"
"sync" "sync"
@ -47,6 +48,8 @@ type Store interface {
GetLatestSession(context.Context, id.SenderKey) (*OlmSession, error) GetLatestSession(context.Context, id.SenderKey) (*OlmSession, error)
// UpdateSession updates a session that has previously been inserted with AddSession. // UpdateSession updates a session that has previously been inserted with AddSession.
UpdateSession(context.Context, id.SenderKey, *OlmSession) error UpdateSession(context.Context, id.SenderKey, *OlmSession) error
// DeleteSession deletes the given session that has been previously inserted with AddSession.
DeleteSession(context.Context, id.SenderKey, *OlmSession) error
// PutGroupSession inserts an inbound Megolm session into the store. If an earlier withhold event has been inserted // PutGroupSession inserts an inbound Megolm session into the store. If an earlier withhold event has been inserted
// with PutWithheldGroupSession, this call should replace that. However, PutWithheldGroupSession must not replace // with PutWithheldGroupSession, this call should replace that. However, PutWithheldGroupSession must not replace
@ -132,6 +135,8 @@ type Store interface {
IsKeySignedBy(ctx context.Context, userID id.UserID, key id.Ed25519, signedByUser id.UserID, signedByKey id.Ed25519) (bool, error) IsKeySignedBy(ctx context.Context, userID id.UserID, key id.Ed25519, signedByUser id.UserID, signedByKey id.Ed25519) (bool, error)
// DropSignaturesByKey deletes the signatures made by the given user and key from the store. It returns the number of signatures deleted. // DropSignaturesByKey deletes the signatures made by the given user and key from the store. It returns the number of signatures deleted.
DropSignaturesByKey(context.Context, id.UserID, id.Ed25519) (int64, error) DropSignaturesByKey(context.Context, id.UserID, id.Ed25519) (int64, error)
// GetSignaturesForKeyBy retrieves the stored signatures for a given cross-signing or device key, by the given signer.
GetSignaturesForKeyBy(context.Context, id.UserID, id.Ed25519, id.UserID) (map[id.Ed25519]string, error)
// PutSecret stores a named secret, replacing it if it exists already. // PutSecret stores a named secret, replacing it if it exists already.
PutSecret(context.Context, id.Secret, string) error PutSecret(context.Context, id.Secret, string) error
@ -233,6 +238,19 @@ func (gs *MemoryStore) AddSession(_ context.Context, senderKey id.SenderKey, ses
return gs.save() return gs.save()
} }
func (gs *MemoryStore) DeleteSession(ctx context.Context, senderKey id.SenderKey, target *OlmSession) error {
gs.lock.Lock()
defer gs.lock.Unlock()
sessions, ok := gs.Sessions[senderKey]
if !ok {
return nil
}
gs.Sessions[senderKey] = slices.DeleteFunc(sessions, func(session *OlmSession) bool {
return session == target
})
return gs.save()
}
func (gs *MemoryStore) UpdateSession(_ context.Context, _ id.SenderKey, _ *OlmSession) error { func (gs *MemoryStore) UpdateSession(_ context.Context, _ id.SenderKey, _ *OlmSession) error {
// we don't need to do anything here because the session is a pointer and already stored in our map // we don't need to do anything here because the session is a pointer and already stored in our map
return gs.save() return gs.save()

View file

@ -111,38 +111,47 @@ type BeeperPerMessageProfile struct {
type BeeperEncodedOrder struct { type BeeperEncodedOrder struct {
order int64 order int64
suborder int64 suborder int16
} }
func NewBeeperEncodedOrder(order int64, suborder int64) BeeperEncodedOrder { func NewBeeperEncodedOrder(order int64, suborder int16) *BeeperEncodedOrder {
return BeeperEncodedOrder{order: order, suborder: suborder} return &BeeperEncodedOrder{order: order, suborder: suborder}
} }
func BeeperEncodedOrderFromString(str string) (BeeperEncodedOrder, error) { func BeeperEncodedOrderFromString(str string) (*BeeperEncodedOrder, error) {
order, suborder, err := decodeIntPair(str) order, suborder, err := decodeIntPair(str)
if err != nil { if err != nil {
return BeeperEncodedOrder{}, err return nil, err
} }
return BeeperEncodedOrder{order: order, suborder: suborder}, nil return &BeeperEncodedOrder{order: order, suborder: suborder}, nil
} }
func (b BeeperEncodedOrder) String() string { func (b *BeeperEncodedOrder) String() string {
if b == nil {
return ""
}
return encodeIntPair(b.order, b.suborder) return encodeIntPair(b.order, b.suborder)
} }
func (b BeeperEncodedOrder) OrderPair() (int64, int64) { func (b *BeeperEncodedOrder) OrderPair() (int64, int16) {
if b == nil {
return 0, 0
}
return b.order, b.suborder return b.order, b.suborder
} }
func (b BeeperEncodedOrder) IsZero() bool { func (b *BeeperEncodedOrder) IsZero() bool {
return b.order == 0 && b.suborder == 0 return b == nil || (b.order == 0 && b.suborder == 0)
} }
func (b BeeperEncodedOrder) MarshalJSON() ([]byte, error) { func (b *BeeperEncodedOrder) MarshalJSON() ([]byte, error) {
return []byte(`"` + b.String() + `"`), nil return []byte(`"` + b.String() + `"`), nil
} }
func (b *BeeperEncodedOrder) UnmarshalJSON(data []byte) error { func (b *BeeperEncodedOrder) UnmarshalJSON(data []byte) error {
if b == nil {
return fmt.Errorf("BeeperEncodedOrder: receiver is nil")
}
str := string(data) str := string(data)
if len(str) < 2 { if len(str) < 2 {
return fmt.Errorf("invalid encoded order string: %s", str) return fmt.Errorf("invalid encoded order string: %s", str)
@ -155,12 +164,13 @@ func (b *BeeperEncodedOrder) UnmarshalJSON(data []byte) error {
return nil return nil
} }
// encodeIntPair encodes two int64 integers into a lexicographically sortable string // encodeIntPair encodes an int64 and an int16 into a lexicographically sortable string
func encodeIntPair(a, b int64) string { func encodeIntPair(a int64, b int16) string {
// Create a buffer to hold the binary representation of the integers. // Create a buffer to hold the binary representation of the integers.
var buf [16]byte // Will need 8 bytes for the int64 and 2 bytes for the int16.
var buf [10]byte
// Flip the sign bit of each integer to map the entire int64 range to uint64 // Flip the sign bit of each integer to map the entire int range to uint
// in a way that preserves the order of the original integers. // in a way that preserves the order of the original integers.
// //
// Explanation: // Explanation:
@ -169,7 +179,7 @@ func encodeIntPair(a, b int64) string {
// - Non-negative numbers (with a sign bit of 0) become larger uint64 values. // - Non-negative numbers (with a sign bit of 0) become larger uint64 values.
// - This mapping preserves the original ordering when the uint64 values are compared. // - This mapping preserves the original ordering when the uint64 values are compared.
binary.BigEndian.PutUint64(buf[0:8], uint64(a)^(1<<63)) binary.BigEndian.PutUint64(buf[0:8], uint64(a)^(1<<63))
binary.BigEndian.PutUint64(buf[8:16], uint64(b)^(1<<63)) binary.BigEndian.PutUint16(buf[8:10], uint16(b)^(1<<15))
// Encode the buffer into a Base32 string without padding using the Hex encoding. // Encode the buffer into a Base32 string without padding using the Hex encoding.
// //
@ -182,8 +192,8 @@ func encodeIntPair(a, b int64) string {
return encoded return encoded
} }
// decodeIntPair decodes a string produced by encodeIntPair back into the original int64 integers // decodeIntPair decodes a string produced by encodeIntPair back into the original int64 and int16 values
func decodeIntPair(encoded string) (int64, int64, error) { func decodeIntPair(encoded string) (int64, int16, error) {
// Decode the Base32 string back into the original byte buffer. // Decode the Base32 string back into the original byte buffer.
buf, err := base32.HexEncoding.WithPadding(base32.NoPadding).DecodeString(encoded) buf, err := base32.HexEncoding.WithPadding(base32.NoPadding).DecodeString(encoded)
if err != nil { if err != nil {
@ -191,17 +201,17 @@ func decodeIntPair(encoded string) (int64, int64, error) {
} }
// Check that the decoded buffer has the expected length. // Check that the decoded buffer has the expected length.
if len(buf) != 16 { if len(buf) != 10 {
return 0, 0, fmt.Errorf("invalid encoded string length: expected 16 bytes, got %d", len(buf)) return 0, 0, fmt.Errorf("invalid encoded string length: expected 10 bytes, got %d", len(buf))
} }
// Read the uint64 values from the buffer using big-endian byte order. // Read the uint values from the buffer using big-endian byte order.
aPos := binary.BigEndian.Uint64(buf[0:8]) aPos := binary.BigEndian.Uint64(buf[0:8])
bPos := binary.BigEndian.Uint64(buf[8:16]) bPos := binary.BigEndian.Uint16(buf[8:10])
// Reverse the sign bit flip to retrieve the original int64 values. // Reverse the sign bit flip to retrieve the original values.
a := int64(aPos ^ (1 << 63)) a := int64(aPos ^ (1 << 63))
b := int64(bPos ^ (1 << 63)) b := int16(bPos ^ (1 << 15))
return a, b, nil return a, b, nil
} }

View file

@ -144,10 +144,10 @@ type Unsigned struct {
RedactedBecause *Event `json:"redacted_because,omitempty"` RedactedBecause *Event `json:"redacted_because,omitempty"`
InviteRoomState []StrippedState `json:"invite_room_state,omitempty"` InviteRoomState []StrippedState `json:"invite_room_state,omitempty"`
BeeperHSOrder int64 `json:"com.beeper.hs.order,omitempty"` BeeperHSOrder int64 `json:"com.beeper.hs.order,omitempty"`
BeeperHSSuborder int64 `json:"com.beeper.hs.suborder,omitempty"` BeeperHSSuborder int16 `json:"com.beeper.hs.suborder,omitempty"`
BeeperHSOrderString BeeperEncodedOrder `json:"com.beeper.hs.order_string,omitempty"` BeeperHSOrderString *BeeperEncodedOrder `json:"com.beeper.hs.order_string,omitempty"`
BeeperFromBackup bool `json:"com.beeper.from_backup,omitempty"` BeeperFromBackup bool `json:"com.beeper.from_backup,omitempty"`
} }
func (us *Unsigned) IsEmpty() bool { func (us *Unsigned) IsEmpty() bool {

View file

@ -41,6 +41,7 @@ type MemberEventContent struct {
IsDirect bool `json:"is_direct,omitempty"` IsDirect bool `json:"is_direct,omitempty"`
ThirdPartyInvite *ThirdPartyInvite `json:"third_party_invite,omitempty"` ThirdPartyInvite *ThirdPartyInvite `json:"third_party_invite,omitempty"`
Reason string `json:"reason,omitempty"` Reason string `json:"reason,omitempty"`
MSC3414File *EncryptedFileInfo `json:"org.matrix.msc3414.file,omitempty"`
} }
type ThirdPartyInvite struct { type ThirdPartyInvite struct {

View file

@ -91,6 +91,10 @@ type ReqLogin struct {
StoreHomeserverURL bool `json:"-"` StoreHomeserverURL bool `json:"-"`
} }
type ReqPutDevice struct {
DisplayName string `json:"display_name,omitempty"`
}
type ReqUIAuthFallback struct { type ReqUIAuthFallback struct {
Session string `json:"session"` Session string `json:"session"`
User string `json:"user"` User string `json:"user"`
@ -140,6 +144,10 @@ type ReqMembers struct {
NotMembership event.Membership `json:"not_membership,omitempty"` NotMembership event.Membership `json:"not_membership,omitempty"`
} }
type ReqMutualRooms struct {
From string `json:"-"`
}
// ReqInvite3PID is the JSON request for https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3roomsroomidinvite-1 // ReqInvite3PID is the JSON request for https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3roomsroomidinvite-1
// It is also a JSON object used in https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3createroom // It is also a JSON object used in https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3createroom
type ReqInvite3PID struct { type ReqInvite3PID struct {

View file

@ -159,6 +159,11 @@ type RespUserProfile struct {
AvatarURL id.ContentURI `json:"avatar_url"` AvatarURL id.ContentURI `json:"avatar_url"`
} }
type RespMutualRooms struct {
Joined []id.RoomID `json:"joined"`
NextBatch string `json:"next_batch,omitempty"`
}
// RespRegisterAvailable is the JSON response for https://spec.matrix.org/v1.4/client-server-api/#get_matrixclientv3registeravailable // RespRegisterAvailable is the JSON response for https://spec.matrix.org/v1.4/client-server-api/#get_matrixclientv3registeravailable
type RespRegisterAvailable struct { type RespRegisterAvailable struct {
Available bool `json:"available"` Available bool `json:"available"`

View file

@ -7,7 +7,7 @@ import (
"strings" "strings"
) )
const Version = "v0.22.0" const Version = "v0.22.1"
var GoModVersion = "" var GoModVersion = ""
var Commit = "" var Commit = ""

View file

@ -63,6 +63,7 @@ var (
FeatureAsyncUploads = UnstableFeature{UnstableFlag: "fi.mau.msc2246.stable", SpecVersion: SpecV17} FeatureAsyncUploads = UnstableFeature{UnstableFlag: "fi.mau.msc2246.stable", SpecVersion: SpecV17}
FeatureAppservicePing = UnstableFeature{UnstableFlag: "fi.mau.msc2659.stable", SpecVersion: SpecV17} FeatureAppservicePing = UnstableFeature{UnstableFlag: "fi.mau.msc2659.stable", SpecVersion: SpecV17}
FeatureAuthenticatedMedia = UnstableFeature{UnstableFlag: "org.matrix.msc3916.stable", SpecVersion: SpecV111} FeatureAuthenticatedMedia = UnstableFeature{UnstableFlag: "org.matrix.msc3916.stable", SpecVersion: SpecV111}
FeatureMutualRooms = UnstableFeature{UnstableFlag: "uk.half-shot.msc2666.query_mutual_rooms"}
BeeperFeatureHungry = UnstableFeature{UnstableFlag: "com.beeper.hungry"} BeeperFeatureHungry = UnstableFeature{UnstableFlag: "com.beeper.hungry"}
BeeperFeatureBatchSending = UnstableFeature{UnstableFlag: "com.beeper.batch_sending"} BeeperFeatureBatchSending = UnstableFeature{UnstableFlag: "com.beeper.batch_sending"}

View file

@ -5,6 +5,10 @@
package sqlite3 package sqlite3
import ( import (
"runtime"
"syscall"
"unsafe"
"modernc.org/libc" "modernc.org/libc"
) )
@ -12,3 +16,20 @@ import (
func X__ccgo_sqlite3_log(t *libc.TLS, iErrCode int32, zFormat uintptr, va uintptr) { /* sqlite3.c:29405:17: */ func X__ccgo_sqlite3_log(t *libc.TLS, iErrCode int32, zFormat uintptr, va uintptr) { /* sqlite3.c:29405:17: */
libc.X__ccgo_sqlite3_log(t, iErrCode, zFormat, va) libc.X__ccgo_sqlite3_log(t, iErrCode, zFormat, va)
} }
// https://gitlab.com/cznic/sqlite/-/issues/199
//
// We are currently stuck on libc@v1.55.3. Until that is resolved - fix the
// problem at runtime.
func PatchIssue199() {
if runtime.GOOS != "linux" || runtime.GOARCH != "arm64" {
return
}
p := unsafe.Pointer(&_aSyscall)
*(*uintptr)(unsafe.Add(p, 608)) = __ccgo_fp(_unixGetpagesizeIssue199)
}
func _unixGetpagesizeIssue199(tls *libc.TLS) (r int32) {
return int32(syscall.Getpagesize())
}

View file

@ -56,6 +56,11 @@ const (
sqliteLockedSharedcache = sqlite3.SQLITE_LOCKED | (1 << 8) sqliteLockedSharedcache = sqlite3.SQLITE_LOCKED | (1 << 8)
) )
// https://gitlab.com/cznic/sqlite/-/issues/199
func init() {
sqlite3.PatchIssue199()
}
// Error represents sqlite library error code. // Error represents sqlite library error code.
type Error struct { type Error struct {
msg string msg string

14
vendor/modules.txt vendored
View file

@ -49,7 +49,7 @@ github.com/etkecc/go-healthchecks/v2
# github.com/etkecc/go-kit v1.5.0 # github.com/etkecc/go-kit v1.5.0
## explicit; go 1.22 ## explicit; go 1.22
github.com/etkecc/go-kit github.com/etkecc/go-kit
# github.com/etkecc/go-linkpearl v0.0.0-20241212081007-8dacb4a09830 # github.com/etkecc/go-linkpearl v0.0.0-20241219100412-83bcd2416637
## explicit; go 1.22.0 ## explicit; go 1.22.0
github.com/etkecc/go-linkpearl github.com/etkecc/go-linkpearl
# github.com/etkecc/go-mxidwc v1.0.1 # github.com/etkecc/go-mxidwc v1.0.1
@ -209,7 +209,7 @@ github.com/yuin/goldmark/renderer
github.com/yuin/goldmark/renderer/html github.com/yuin/goldmark/renderer/html
github.com/yuin/goldmark/text github.com/yuin/goldmark/text
github.com/yuin/goldmark/util github.com/yuin/goldmark/util
# go.mau.fi/util v0.8.2 # go.mau.fi/util v0.8.3
## explicit; go 1.22.0 ## explicit; go 1.22.0
go.mau.fi/util/base58 go.mau.fi/util/base58
go.mau.fi/util/confusable go.mau.fi/util/confusable
@ -240,12 +240,12 @@ golang.org/x/crypto/internal/poly1305
golang.org/x/crypto/pbkdf2 golang.org/x/crypto/pbkdf2
golang.org/x/crypto/ssh golang.org/x/crypto/ssh
golang.org/x/crypto/ssh/internal/bcrypt_pbkdf golang.org/x/crypto/ssh/internal/bcrypt_pbkdf
# golang.org/x/exp v0.0.0-20241210194714-1829a127f884 # golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67
## explicit; go 1.22.0 ## explicit; go 1.22.0
golang.org/x/exp/constraints golang.org/x/exp/constraints
golang.org/x/exp/maps golang.org/x/exp/maps
golang.org/x/exp/slices golang.org/x/exp/slices
# golang.org/x/net v0.32.0 # golang.org/x/net v0.33.0
## explicit; go 1.18 ## explicit; go 1.18
golang.org/x/net/context golang.org/x/net/context
golang.org/x/net/html golang.org/x/net/html
@ -291,7 +291,7 @@ golang.org/x/tools/go/loader
# gopkg.in/yaml.v2 v2.4.0 # gopkg.in/yaml.v2 v2.4.0
## explicit; go 1.15 ## explicit; go 1.15
gopkg.in/yaml.v2 gopkg.in/yaml.v2
# maunium.net/go/mautrix v0.22.0 # maunium.net/go/mautrix v0.22.1
## explicit; go 1.22.0 ## explicit; go 1.22.0
maunium.net/go/mautrix maunium.net/go/mautrix
maunium.net/go/mautrix/crypto maunium.net/go/mautrix/crypto
@ -326,7 +326,7 @@ maunium.net/go/mautrix/format/mdext
maunium.net/go/mautrix/id maunium.net/go/mautrix/id
maunium.net/go/mautrix/pushrules maunium.net/go/mautrix/pushrules
maunium.net/go/mautrix/sqlstatestore maunium.net/go/mautrix/sqlstatestore
# modernc.org/gc/v3 v3.0.0-20241004144649-1aea3fae8852 # modernc.org/gc/v3 v3.0.0-20241213165251-3bc300f6d0c9
## explicit; go 1.20 ## explicit; go 1.20
modernc.org/gc/v3 modernc.org/gc/v3
# modernc.org/libc v1.61.4 # modernc.org/libc v1.61.4
@ -363,7 +363,7 @@ modernc.org/mathutil
# modernc.org/memory v1.8.0 # modernc.org/memory v1.8.0
## explicit; go 1.18 ## explicit; go 1.18
modernc.org/memory modernc.org/memory
# modernc.org/sqlite v1.34.2 # modernc.org/sqlite v1.34.3
## explicit; go 1.21 ## explicit; go 1.21
modernc.org/sqlite modernc.org/sqlite
modernc.org/sqlite/lib modernc.org/sqlite/lib