diff --git a/cmd/dummybridge/Dockerfile b/cmd/dummybridge/Dockerfile index 8a3e838..65bcc74 100644 --- a/cmd/dummybridge/Dockerfile +++ b/cmd/dummybridge/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1-alpine3.20 AS builder +FROM golang:1-alpine3.23 AS builder RUN apk add --no-cache build-base olm-dev diff --git a/go.mod b/go.mod index d76c069..5e2593b 100644 --- a/go.mod +++ b/go.mod @@ -1,39 +1,38 @@ module github.com/beeper/dummybridge -go 1.23.0 +go 1.24.0 -toolchain go1.24.1 +toolchain go1.25.0 require ( github.com/rs/zerolog v1.34.0 - go.mau.fi/util v0.8.6 - maunium.net/go/mautrix v0.23.3-0.20250320134109-06f200da0d10 + go.mau.fi/util v0.9.5-0.20260113180831-8cda92561373 + maunium.net/go/mautrix v0.26.2-0.20260114150632-75f9cb369bea ) require ( filippo.io/edwards25519 v1.1.0 // indirect - github.com/coreos/go-systemd/v22 v22.5.0 // indirect - github.com/gorilla/mux v1.8.0 // indirect - github.com/gorilla/websocket v1.5.0 // indirect + github.com/coder/websocket v1.8.14 // indirect + github.com/coreos/go-systemd/v22 v22.6.0 // indirect github.com/lib/pq v1.10.9 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-sqlite3 v1.14.24 // indirect - github.com/petermattis/goid v0.0.0-20250303134427-723919f7f203 // indirect + github.com/mattn/go-sqlite3 v1.14.32 // indirect + github.com/petermattis/goid v0.0.0-20251121121749-a11dd1a45f9a // indirect github.com/rs/xid v1.6.0 // indirect github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect github.com/tidwall/gjson v1.18.0 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect github.com/tidwall/sjson v1.2.5 // indirect - github.com/yuin/goldmark v1.7.8 // indirect - go.mau.fi/zeroconfig v0.1.3 // indirect - golang.org/x/crypto v0.36.0 // indirect - golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect - golang.org/x/net v0.37.0 // indirect - golang.org/x/sync v0.12.0 // indirect - golang.org/x/sys v0.31.0 // indirect - golang.org/x/text v0.23.0 // indirect + github.com/yuin/goldmark v1.7.13 // indirect + go.mau.fi/zeroconfig v0.2.0 // indirect + golang.org/x/crypto v0.46.0 // indirect + golang.org/x/exp v0.0.0-20251209150349-8475f28825e9 // indirect + golang.org/x/net v0.48.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.39.0 // indirect + golang.org/x/text v0.32.0 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect maunium.net/go/mauflag v1.0.0 // indirect diff --git a/go.sum b/go.sum index f0172cf..faf55f8 100644 --- a/go.sum +++ b/go.sum @@ -2,15 +2,14 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= -github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= +github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g= +github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/go-systemd/v22 v22.6.0 h1:aGVa/v8B7hpb0TKl0MWoAavPDmHvobFe5R5zn0bCJWo= +github.com/coreos/go-systemd/v22 v22.6.0/go.mod h1:iG+pp635Fo7ZmV/j14KUcmEyWF+0X7Lua8rrTWzYgWU= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= -github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= -github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= @@ -20,10 +19,10 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= -github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= -github.com/petermattis/goid v0.0.0-20250303134427-723919f7f203 h1:E7Kmf11E4K7B5hDti2K2NqPb1nlYlGYsu02S1JNd/Bs= -github.com/petermattis/goid v0.0.0-20250303134427-723919f7f203/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= +github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs= +github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/petermattis/goid v0.0.0-20251121121749-a11dd1a45f9a h1:VweslR2akb/ARhXfqSfRbj1vpWwYXf3eeAUyw/ndms0= +github.com/petermattis/goid v0.0.0-20251121121749-a11dd1a45f9a/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -33,8 +32,8 @@ github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= @@ -45,27 +44,29 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= 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/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic= -github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= -go.mau.fi/util v0.8.6 h1:AEK13rfgtiZJL2YsNK+W4ihhYCuukcRom8WPP/w/L54= -go.mau.fi/util v0.8.6/go.mod h1:uNB3UTXFbkpp7xL1M/WvQks90B/L4gvbLpbS0603KOE= -go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= -go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= -golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= -golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= -golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw= -golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM= -golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= -golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= -golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= -golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA= +github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= +go.mau.fi/util v0.9.4 h1:gWdUff+K2rCynRPysXalqqQyr2ahkSWaestH6YhSpso= +go.mau.fi/util v0.9.4/go.mod h1:647nVfwUvuhlZFOnro3aRNPmRd2y3iDha9USb8aKSmM= +go.mau.fi/util v0.9.5-0.20260113180831-8cda92561373 h1:LjFGO80c9mGeYCvrBsASvK9jx3oPkXo++l9quy4YMls= +go.mau.fi/util v0.9.5-0.20260113180831-8cda92561373/go.mod h1:647nVfwUvuhlZFOnro3aRNPmRd2y3iDha9USb8aKSmM= +go.mau.fi/zeroconfig v0.2.0 h1:e/OGEERqVRRKlgaro7E6bh8xXiKFSXB3eNNIud7FUjU= +go.mau.fi/zeroconfig v0.2.0/go.mod h1:J0Vn0prHNOm493oZoQ84kq83ZaNCYZnq+noI1b1eN8w= +golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= +golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= +golang.org/x/exp v0.0.0-20251209150349-8475f28825e9 h1:MDfG8Cvcqlt9XXrmEiD4epKn7VJHZO84hejP9Jmp0MM= +golang.org/x/exp v0.0.0-20251209150349-8475f28825e9/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU= +golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= @@ -74,5 +75,7 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= maunium.net/go/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M= maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA= -maunium.net/go/mautrix v0.23.3-0.20250320134109-06f200da0d10 h1:XE2szxSvJVngk+qHqlKO2fiBZwcAp3oYq+9P5jlAm6w= -maunium.net/go/mautrix v0.23.3-0.20250320134109-06f200da0d10/go.mod h1:pCYLHmo02Jauak/9VlTkbGPrBMvLXsGqTGMNOx+L2PE= +maunium.net/go/mautrix v0.26.2-0.20251219113255-4825e41d5c5f h1:JbZuKX9klZoCbuku4WcBCaf/ALi3J+Qakyp4nReitqo= +maunium.net/go/mautrix v0.26.2-0.20251219113255-4825e41d5c5f/go.mod h1:UySSpb8OqXG1sMJ6dDqyzmfcqr2ayZK+KzwqOTAkAOM= +maunium.net/go/mautrix v0.26.2-0.20260114150632-75f9cb369bea h1:4yYtKBvHOXUHiW1af7vekME3hQG3mxAHKDT286mgSzI= +maunium.net/go/mautrix v0.26.2-0.20260114150632-75f9cb369bea/go.mod h1:vUvbzvY00Tbl8WjBdp5afvc69uIxujnfGHpAugxvR/Q= diff --git a/pkg/connector/client.go b/pkg/connector/client.go index 813bfac..21ada6b 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "sync" + "time" "github.com/rs/zerolog/log" "go.mau.fi/util/jsontime" @@ -26,6 +27,44 @@ type DummyClient struct { var _ bridgev2.NetworkAPI = (*DummyClient)(nil) var _ bridgev2.IdentifierResolvingNetworkAPI = (*DummyClient)(nil) +var _ bridgev2.BackfillingNetworkAPI = (*DummyClient)(nil) +var _ bridgev2.DeleteChatHandlingNetworkAPI = (*DummyClient)(nil) +var _ bridgev2.MessageRequestAcceptingNetworkAPI = (*DummyClient)(nil) + +var dummyRoomCaps = &event.RoomFeatures{ + ID: "com.beeper.dummy.capabilities", + + Formatting: map[event.FormattingFeature]event.CapabilitySupportLevel{ + event.FmtBold: event.CapLevelFullySupported, + event.FmtItalic: event.CapLevelFullySupported, + event.FmtStrikethrough: event.CapLevelFullySupported, + event.FmtInlineCode: event.CapLevelFullySupported, + event.FmtCodeBlock: event.CapLevelFullySupported, + }, + + File: map[event.CapabilityMsgType]*event.FileFeatures{ + event.MsgImage: {MimeTypes: map[string]event.CapabilitySupportLevel{"*/*": event.CapLevelFullySupported}}, + event.MsgAudio: {MimeTypes: map[string]event.CapabilitySupportLevel{"*/*": event.CapLevelFullySupported}}, + event.MsgVideo: {MimeTypes: map[string]event.CapabilitySupportLevel{"*/*": event.CapLevelFullySupported}}, + event.MsgFile: {MimeTypes: map[string]event.CapabilitySupportLevel{"*/*": event.CapLevelFullySupported}, Caption: event.CapLevelFullySupported}, + }, + + MaxTextLength: 65536, + LocationMessage: event.CapLevelFullySupported, + Reply: event.CapLevelFullySupported, + Edit: event.CapLevelFullySupported, + Delete: event.CapLevelFullySupported, + Reaction: event.CapLevelFullySupported, + ReactionCount: 1, + ReadReceipts: true, + TypingNotifications: true, + + MessageRequest: &event.MessageRequestFeatures{ + AcceptWithButton: event.CapLevelFullySupported, + }, + + DeleteChat: true, +} func (dc *DummyClient) Connect(ctx context.Context) { state := status.BridgeState{ @@ -66,7 +105,7 @@ func (dc *DummyClient) IsLoggedIn() bool { func (dc *DummyClient) LogoutRemote(ctx context.Context) {} func (dc *DummyClient) GetCapabilities(ctx context.Context, portal *bridgev2.Portal) *event.RoomFeatures { - return &event.RoomFeatures{} + return dummyRoomCaps } func (dc *DummyClient) IsThisUser(ctx context.Context, userID networkid.UserID) bool { @@ -74,7 +113,29 @@ func (dc *DummyClient) IsThisUser(ctx context.Context, userID networkid.UserID) } func (dc *DummyClient) GetChatInfo(ctx context.Context, portal *bridgev2.Portal) (*bridgev2.ChatInfo, error) { - return &bridgev2.ChatInfo{}, nil + portalIDPrefix := string(portal.ID) + if len(portalIDPrefix) > 6 { + portalIDPrefix = portalIDPrefix[:6] + } + portalName := fmt.Sprintf("Dummy Portal %s", portalIDPrefix) + portalTopic := "DummyBridge test portal" + + roomType := portal.RoomType + if roomType == "" { + roomType = database.RoomTypeDM + } + + chatInfo := &bridgev2.ChatInfo{ + Type: ptr.Ptr(roomType), + } + + if portal.Name == "" { + chatInfo.Name = ptr.Ptr(portalName) + } + if portal.Topic == "" { + chatInfo.Topic = ptr.Ptr(portalTopic) + } + return chatInfo, nil } func (tc *DummyClient) GetUserInfo(ctx context.Context, ghost *bridgev2.Ghost) (*bridgev2.UserInfo, error) { @@ -89,11 +150,51 @@ func (tc *DummyClient) GetUserInfo(ctx context.Context, ghost *bridgev2.Ghost) ( } func (dc *DummyClient) HandleMatrixMessage(ctx context.Context, msg *bridgev2.MatrixMessage) (message *bridgev2.MatrixMessageResponse, err error) { + // Dummy message requests are accepted by sending a message. + if msg.Portal != nil && msg.Portal.MessageRequest { + msg.Portal.MessageRequest = false + msg.Portal.UpdateBridgeInfo(ctx) + _ = msg.Portal.Save(ctx) + } + + messageID := randomMessageID() + if msg.Event != nil && msg.Event.Unsigned.TransactionID != "" { + messageID = networkid.MessageID(msg.Event.Unsigned.TransactionID) + } + + timestamp := time.Now() + if msg.Event != nil && msg.Event.Timestamp != 0 { + timestamp = time.UnixMilli(msg.Event.Timestamp) + } + return &bridgev2.MatrixMessageResponse{ - DB: &database.Message{}, + DB: &database.Message{ + ID: messageID, + SenderID: networkid.UserID(dc.UserLogin.ID), + Timestamp: timestamp, + }, + StreamOrder: time.Now().UnixNano(), }, nil } +func (dc *DummyClient) HandleMatrixDeleteChat(ctx context.Context, msg *bridgev2.MatrixDeleteChat) error { + // bridgev2 will delete the portal + Matrix room after this returns nil. + // For dummybridge, there's no separate remote-side deletion to do. + return nil +} + +func (dc *DummyClient) HandleMatrixAcceptMessageRequest(ctx context.Context, msg *bridgev2.MatrixAcceptMessageRequest) error { + // Explicitly clear the request flag so bridgev2 doesn't need to. + // This makes the behavior deterministic and keeps the state update close + // to the connector implementation. + if msg.Portal != nil && msg.Portal.MessageRequest { + msg.Portal.MessageRequest = false + msg.Portal.UpdateBridgeInfo(ctx) + return msg.Portal.Save(ctx) + } + return nil +} + func (dc *DummyClient) ResolveIdentifier(ctx context.Context, identifier string, createChat bool) (*bridgev2.ResolveIdentifierResponse, error) { userID := networkid.UserID(identifier) portalID := randomPortalID() diff --git a/pkg/connector/commands.go b/pkg/connector/commands.go index 7ee8a44..80859eb 100644 --- a/pkg/connector/commands.go +++ b/pkg/connector/commands.go @@ -11,8 +11,8 @@ import ( "strings" "time" - "maunium.net/go/mautrix/bridgev2/status" "maunium.net/go/mautrix/bridgev2/commands" + "maunium.net/go/mautrix/bridgev2/status" "maunium.net/go/mautrix/event" "maunium.net/go/mautrix/id" ) @@ -20,6 +20,8 @@ import ( var AllCommands = []commands.CommandHandler{ SendStateCommand, NewRoomCommand, + NewRequestDMCommand, + NewRequestGroupCommand, GhostsCommand, MessagesCommand, FileCommand, diff --git a/pkg/connector/generators.go b/pkg/connector/generators.go index a47bbf9..5c812c2 100644 --- a/pkg/connector/generators.go +++ b/pkg/connector/generators.go @@ -8,6 +8,7 @@ import ( "go.mau.fi/util/ptr" "go.mau.fi/util/random" "maunium.net/go/mautrix/bridgev2" + "maunium.net/go/mautrix/bridgev2/database" "maunium.net/go/mautrix/bridgev2/networkid" "maunium.net/go/mautrix/event" ) @@ -40,7 +41,21 @@ func generatePortal(ctx context.Context, br *bridgev2.Bridge, login *bridgev2.Us return nil, fmt.Errorf("failed to get portal by key: %w", err) } + portalIDPrefix := string(portalID) + if len(portalIDPrefix) > 6 { + portalIDPrefix = portalIDPrefix[:6] + } + portalName := fmt.Sprintf("Dummy Portal %s", portalIDPrefix) + portalTopic := "DummyBridge test portal" + roomType := database.RoomTypeDM + if members > 1 { + roomType = database.RoomTypeGroupDM + } + chatInfo := bridgev2.ChatInfo{ + Name: ptr.Ptr(portalName), + Topic: ptr.Ptr(portalTopic), + Type: ptr.Ptr(roomType), CanBackfill: true, Members: &bridgev2.ChatMemberList{ Members: []bridgev2.ChatMember{ diff --git a/pkg/connector/message_requests.go b/pkg/connector/message_requests.go new file mode 100644 index 0000000..a1b3b21 --- /dev/null +++ b/pkg/connector/message_requests.go @@ -0,0 +1,163 @@ +package connector + +import ( + "context" + "fmt" + "strconv" + "time" + + "go.mau.fi/util/ptr" + "maunium.net/go/mautrix/bridgev2" + "maunium.net/go/mautrix/bridgev2/commands" + "maunium.net/go/mautrix/bridgev2/database" + "maunium.net/go/mautrix/bridgev2/networkid" + "maunium.net/go/mautrix/bridgev2/simplevent" + "maunium.net/go/mautrix/event" +) + +var NewRequestDMCommand = &commands.FullHandler{ + Func: func(e *commands.Event) { + login := e.User.GetDefaultLogin() + sendInitialMessage := true + for _, arg := range e.Args { + if arg == "--no-message" { + sendInitialMessage = false + } + } + + portal, err := createMessageRequestPortal(e.Ctx, e.Bridge, login, false, 1, sendInitialMessage) + if err != nil { + e.Reply(err.Error()) + return + } + e.Reply("Created DM message request %s", portal.MXID) + }, + Name: "new-request-dm", + Help: commands.HelpMeta{ + Description: "Create a DM message request room", + Args: "[--no-message]", + Section: DummyHelpsection, + }, + RequiresLogin: true, +} + +var NewRequestGroupCommand = &commands.FullHandler{ + Func: func(e *commands.Event) { + login := e.User.GetDefaultLogin() + + remoteMembers := 2 + sendInitialMessage := true + for _, arg := range e.Args { + if arg == "--no-message" { + sendInitialMessage = false + continue + } + if n, err := strconv.Atoi(arg); err == nil { + remoteMembers = n + } + } + if remoteMembers < 2 { + remoteMembers = 2 + } + + portal, err := createMessageRequestPortal(e.Ctx, e.Bridge, login, true, remoteMembers, sendInitialMessage) + if err != nil { + e.Reply(err.Error()) + return + } + e.Reply("Created group message request %s", portal.MXID) + }, + Name: "new-request-group", + Help: commands.HelpMeta{ + Description: "Create a group message request room", + Args: "[nmembers] [--no-message]", + Section: DummyHelpsection, + }, + RequiresLogin: true, +} + +func createMessageRequestPortal( + ctx context.Context, + br *bridgev2.Bridge, + login *bridgev2.UserLogin, + isGroup bool, + remoteMembers int, + sendInitialMessage bool, +) (*bridgev2.Portal, error) { + portalID := randomPortalID() + portalKey := networkid.PortalKey{ID: portalID, Receiver: login.ID} + + portal, err := br.GetPortalByKey(ctx, portalKey) + if err != nil { + return nil, fmt.Errorf("failed to get portal by key: %w", err) + } + + // Message request flag in bridgev2 is stored in the portal and surfaced via m.bridge. + isMessageRequest := true + roomType := database.RoomTypeDM + var name *string + if isGroup { + roomType = database.RoomTypeGroupDM + portalIDPrefix := string(portalID) + if len(portalIDPrefix) > 6 { + portalIDPrefix = portalIDPrefix[:6] + } + groupName := fmt.Sprintf("Dummy Request Group %s", portalIDPrefix) + name = &groupName + } + portalTopic := "DummyBridge message request" + + chatInfo := bridgev2.ChatInfo{ + Name: name, + Topic: &portalTopic, + Type: ptr.Ptr(roomType), + MessageRequest: &isMessageRequest, + CanBackfill: true, + Members: &bridgev2.ChatMemberList{Members: []bridgev2.ChatMember{{ + EventSender: bridgev2.EventSender{IsFromMe: true, Sender: networkid.UserID(login.ID)}, + Membership: event.MembershipJoin, + PowerLevel: ptr.Ptr(100), + }}}, + } + + firstGhost := stablePortalUserIDByIndex(portalID, 0) + for i := 0; i < remoteMembers; i++ { + userID := stablePortalUserIDByIndex(portalID, i) + ghost, err := br.GetGhostByID(ctx, userID) + if err != nil { + return nil, fmt.Errorf("failed to get ghost by id: %w", err) + } + ghost.UpdateName(ctx, fmt.Sprintf("Dummy User %d", i+1)) + chatInfo.Members.Members = append(chatInfo.Members.Members, bridgev2.ChatMember{ + EventSender: bridgev2.EventSender{Sender: userID}, + Membership: event.MembershipJoin, + }) + } + + if err := portal.CreateMatrixRoom(ctx, login, &chatInfo); err != nil { + return nil, err + } + + if sendInitialMessage { + // Send one incoming message so it shows up with a preview/unread state. + login.QueueRemoteEvent(&simplevent.PreConvertedMessage{ + EventMeta: simplevent.EventMeta{ + Type: bridgev2.RemoteEventMessage, + PortalKey: portal.PortalKey, + Sender: bridgev2.EventSender{Sender: firstGhost}, + Timestamp: time.Now(), + StreamOrder: time.Now().UnixNano(), + }, + Data: &bridgev2.ConvertedMessage{Parts: []*bridgev2.ConvertedMessagePart{{ + Type: event.EventMessage, + Content: &event.MessageEventContent{ + MsgType: event.MsgText, + Body: "Hey! This is a dummy message request.", + }, + }}}, + ID: randomMessageID(), + }) + } + + return portal, nil +}