25 Commits

Author SHA1 Message Date
Tulir Asokan
001c88c400 Bump version to v0.7.3 2025-04-16 14:09:51 +03:00
Ping Chen
d37b5028e1 portal: fix isPlainGifMessage to get link preview working (#179) 2025-04-09 20:37:25 +09:00
Tulir Asokan
ef093b129f dependencies: update discordgo 2025-03-20 17:43:03 +02:00
batuhan
84e56c73fa portal: add com.beeper.room_type.v2 to m.bridge events (#178) 2025-03-20 16:23:00 +02:00
ginnyTheCat
5854ad0c14 portal: fix typo in msc1767 field name (#177) 2025-03-20 16:22:37 +02:00
Tulir Asokan
9605992758 portal: add id field for per-message profiles
Closes #176
2025-03-20 16:21:38 +02:00
Tulir Asokan
4d67dbcd00 client: only load main page for users 2025-02-22 20:09:40 +02:00
Tulir Asokan
31a75d871f login: add filename when sending QR image 2025-02-22 20:09:36 +02:00
Tulir Asokan
b8892ed59f dependencies: update 2025-02-22 20:04:47 +02:00
Tulir Asokan
65ef2c4ff6 portal: add support for no-mention replies 2025-02-22 19:48:16 +02:00
mat
4a8e9f5c21 portal: fix per-message profiles for guild-specific avatars (#172) 2025-02-09 02:34:44 +02:00
Tulir Asokan
4aad353603 Bump version to v0.7.2 2024-12-16 16:07:46 +02:00
Tulir Asokan
0e59e2da68 dependencies: update 2024-12-16 16:06:33 +02:00
Tulir Asokan
5a029367b3 dependencies: update 2024-11-29 20:22:31 +02:00
Tulir Asokan
f2897d9b14 client: load version number dynamically 2024-11-29 20:15:04 +02:00
Tulir Asokan
8b61dc5352 config: add support for using a proxy 2024-11-29 20:15:00 +02:00
Tulir Asokan
b330c5836e client: set referers properly 2024-11-29 20:14:52 +02:00
Tulir Asokan
8219516ede dependencies: update discordgo 2024-11-22 00:29:29 +02:00
Tulir Asokan
c01f502e04 Bump version to v0.7.1 2024-11-16 18:06:31 +02:00
Tulir Asokan
1e3b854ee1 dependencies: update golang.org/x deps and bump minimum go version 2024-11-15 13:11:13 +02:00
Tulir Asokan
a9df85fdca portal: add missing fi.mau.gif field to gifvs 2024-11-14 22:57:09 +02:00
Tulir Asokan
0d148ffad6 ci: lock closed issues automatically after 90 days 2024-11-13 15:17:33 +02:00
Tulir Asokan
024577d822 user: catch 40002 responses 2024-11-13 15:15:36 +02:00
Tulir Asokan
449c9264d8 dependencies: update discordgo 2024-11-13 14:58:00 +02:00
Tulir Asokan
a0ee1fd508 .github: update bug report template 2024-11-13 14:12:52 +02:00
19 changed files with 251 additions and 84 deletions

View File

@@ -5,3 +5,10 @@ about: If something is definitely wrong in the bridge (rather than just a setup
labels: bug labels: bug
--- ---
<!--
Remember to include relevant logs, the bridge version and any other details.
If you aren't sure what's needed, ask in the Matrix room rather than opening an
incomplete issue. Issues with insufficient detail will likely just be ignored.
-->

View File

@@ -8,8 +8,8 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
go-version: ["1.21", "1.22"] go-version: ["1.23", "1.24"]
name: Lint ${{ matrix.go-version == '1.22' && '(latest)' || '(old)' }} name: Lint ${{ matrix.go-version == '1.24' && '(latest)' || '(old)' }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4

29
.github/workflows/stale.yml vendored Normal file
View File

@@ -0,0 +1,29 @@
name: 'Lock old issues'
on:
schedule:
- cron: '0 21 * * *'
workflow_dispatch:
permissions:
issues: write
# pull-requests: write
# discussions: write
concurrency:
group: lock-threads
jobs:
lock-stale:
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@v5
id: lock
with:
issue-inactive-days: 90
process-only: issues
- name: Log processed threads
run: |
if [ '${{ steps.lock.outputs.issues }}' ]; then
echo "Issues:" && echo '${{ steps.lock.outputs.issues }}' | jq -r '.[] | "https://github.com/\(.owner)/\(.repo)/issues/\(.issue_number)"'
fi

View File

@@ -1,3 +1,28 @@
# v0.7.3 (2025-04-16)
* Added support for sending no-mention replies from Matrix
(uses intentional mentions and requires client support).
* Added file name to QR image message when logging in to fix rendering in dumb
clients that validate the file extension.
* Added `id` field to per-message profiles to match [MSC4144].
* Fixed guild avatars in per-message profiles (thanks to [@mat-1] in [#172]).
* Fixed typo in MSC1767 field name in voice messages (thanks to [@ginnyTheCat] in [#177]).
[@mat-1]: https://github.com/mat-1
[@ginnyTheCat]: https://github.com/ginnyTheCat
[#172]: https://github.com/mautrix/discord/pull/172
[#177]: https://github.com/mautrix/discord/pull/177
[MSC4144]: https://github.com/matrix-org/matrix-spec-proposals/pull/4144
# v0.7.2 (2024-12-16)
* Fixed some headers being set incorrectly.
# v0.7.1 (2024-11-16)
* Bumped minimum Go version to 1.22.
* Updated Discord version numbers.
# v0.7.0 (2024-07-16) # v0.7.0 (2024-07-16)
* Bumped minimum Go version to 1.21. * Bumped minimum Go version to 1.21.

View File

@@ -29,7 +29,7 @@ import (
"go.mau.fi/mautrix-discord/database" "go.mau.fi/mautrix-discord/database"
) )
func downloadDiscordAttachment(url string, maxSize int64) ([]byte, error) { func downloadDiscordAttachment(cli *http.Client, url string, maxSize int64) ([]byte, error) {
req, err := http.NewRequest(http.MethodGet, url, nil) req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -38,7 +38,7 @@ func downloadDiscordAttachment(url string, maxSize int64) ([]byte, error) {
req.Header.Set(key, value) req.Header.Set(key, value)
} }
resp, err := http.DefaultClient.Do(req) resp, err := cli.Do(req)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -65,16 +65,21 @@ func downloadDiscordAttachment(url string, maxSize int64) ([]byte, error) {
} }
} }
func uploadDiscordAttachment(url string, data []byte) error { func uploadDiscordAttachment(cli *http.Client, url string, data []byte) error {
req, err := http.NewRequest(http.MethodPut, url, bytes.NewReader(data)) req, err := http.NewRequest(http.MethodPut, url, bytes.NewReader(data))
if err != nil { if err != nil {
return err return err
} }
for key, value := range discordgo.DroidFetchHeaders { for key, value := range discordgo.DroidBaseHeaders {
req.Header.Set(key, value) req.Header.Set(key, value)
} }
req.Header.Set("Content-Type", "application/octet-stream")
req.Header.Set("Referer", "https://discord.com/")
req.Header.Set("Sec-Fetch-Dest", "empty")
req.Header.Set("Sec-Fetch-Mode", "cors")
req.Header.Set("Sec-Fetch-Site", "cross-site")
resp, err := http.DefaultClient.Do(req) resp, err := cli.Do(req)
if err != nil { if err != nil {
return err return err
} }
@@ -295,7 +300,7 @@ func (br *DiscordBridge) copyAttachmentToMatrix(intent *appservice.IntentAPI, ur
}() }()
var data []byte var data []byte
data, onceErr = downloadDiscordAttachment(url, br.MediaConfig.UploadSize) data, onceErr = downloadDiscordAttachment(http.DefaultClient, url, br.MediaConfig.UploadSize)
if onceErr != nil { if onceErr != nil {
return return
} }

View File

@@ -117,7 +117,7 @@ func (portal *Portal) collectBackfillMessages(log zerolog.Logger, source *User,
} }
for { for {
log.Debug().Str("before_id", before).Msg("Fetching messages for backfill") log.Debug().Str("before_id", before).Msg("Fetching messages for backfill")
newMessages, err := source.Session.ChannelMessages(protoChannelID, messageFetchChunkSize, before, "", "") newMessages, err := source.Session.ChannelMessages(protoChannelID, messageFetchChunkSize, before, "", "", portal.RefererOptIfUser(source.Session, protoChannelID)...)
if err != nil { if err != nil {
return nil, false, err return nil, false, err
} }
@@ -151,6 +151,9 @@ func (portal *Portal) collectBackfillMessages(log zerolog.Logger, source *User,
func (portal *Portal) backfillLimited(log zerolog.Logger, source *User, limit int, after string, thread *Thread) { func (portal *Portal) backfillLimited(log zerolog.Logger, source *User, limit int, after string, thread *Thread) {
messages, foundAll, err := portal.collectBackfillMessages(log, source, limit, after, thread) messages, foundAll, err := portal.collectBackfillMessages(log, source, limit, after, thread)
if err != nil { if err != nil {
if source.handlePossible40002(err) {
panic(err)
}
log.Err(err).Msg("Error collecting messages to forward backfill") log.Err(err).Msg("Error collecting messages to forward backfill")
return return
} }
@@ -180,7 +183,7 @@ func (portal *Portal) backfillUnlimitedMissed(log zerolog.Logger, source *User,
} }
for { for {
log.Debug().Str("after_id", after).Msg("Fetching chunk of messages to backfill") log.Debug().Str("after_id", after).Msg("Fetching chunk of messages to backfill")
messages, err := source.Session.ChannelMessages(protoChannelID, messageFetchChunkSize, "", after, "") messages, err := source.Session.ChannelMessages(protoChannelID, messageFetchChunkSize, "", after, "", portal.RefererOptIfUser(source.Session, protoChannelID)...)
if err != nil { if err != nil {
log.Err(err).Msg("Error fetching chunk of messages to forward backfill") log.Err(err).Msg("Error fetching chunk of messages to forward backfill")
return return

View File

@@ -239,9 +239,10 @@ func sendQRCode(ce *WrappedCommandEvent, code string) id.EventID {
} }
content := event.MessageEventContent{ content := event.MessageEventContent{
MsgType: event.MsgImage, MsgType: event.MsgImage,
Body: code, Body: code,
URL: url.CUString(), FileName: "qr.png",
URL: url.CUString(),
} }
resp, err := ce.Bot.SendMessageEvent(ce.RoomID, event.EventMessage, &content) resp, err := ce.Bot.SendMessageEvent(ce.RoomID, event.EventMessage, &content)
@@ -468,7 +469,7 @@ func fnSetRelay(ce *WrappedCommandEvent) {
return return
} }
case "create": case "create":
perms, err := ce.User.Session.UserChannelPermissions(ce.User.DiscordID, portal.Key.ChannelID) perms, err := ce.User.Session.UserChannelPermissions(ce.User.DiscordID, portal.Key.ChannelID, portal.RefererOptIfUser(ce.User.Session, "")...)
if err != nil { if err != nil {
log.Warn().Err(err).Msg("Failed to check user permissions") log.Warn().Err(err).Msg("Failed to check user permissions")
ce.Reply("Failed to check if you have permission to create webhooks") ce.Reply("Failed to check if you have permission to create webhooks")
@@ -483,7 +484,7 @@ func fnSetRelay(ce *WrappedCommandEvent) {
name = strings.Join(ce.Args[1:], " ") name = strings.Join(ce.Args[1:], " ")
} }
log.Debug().Str("webhook_name", name).Msg("Creating webhook") log.Debug().Str("webhook_name", name).Msg("Creating webhook")
webhookMeta, err = ce.User.Session.WebhookCreate(portal.Key.ChannelID, name, "") webhookMeta, err = ce.User.Session.WebhookCreate(portal.Key.ChannelID, name, "", portal.RefererOptIfUser(ce.User.Session, "")...)
if err != nil { if err != nil {
log.Warn().Err(err).Msg("Failed to create webhook") log.Warn().Err(err).Msg("Failed to create webhook")
ce.Reply("Failed to create webhook: %v", err) ce.Reply("Failed to create webhook: %v", err)

View File

@@ -61,7 +61,7 @@ func (portal *Portal) getCommand(user *User, command string) (*discordgo.Applica
defer portal.commandsLock.Unlock() defer portal.commandsLock.Unlock()
cmd, ok := portal.commands[command] cmd, ok := portal.commands[command]
if !ok { if !ok {
results, err := user.Session.ApplicationCommandsSearch(portal.Key.ChannelID, command) results, err := user.Session.ApplicationCommandsSearch(portal.Key.ChannelID, command, portal.RefererOpt(""))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -247,7 +247,7 @@ func fnCommands(ce *WrappedCommandEvent) {
} }
subcmd := strings.ToLower(ce.Args[0]) subcmd := strings.ToLower(ce.Args[0])
if subcmd == "search" { if subcmd == "search" {
results, err := ce.User.Session.ApplicationCommandsSearch(ce.Portal.Key.ChannelID, ce.Args[1]) results, err := ce.User.Session.ApplicationCommandsSearch(ce.Portal.Key.ChannelID, ce.Args[1], ce.Portal.RefererOpt(""))
if err != nil { if err != nil {
ce.Reply("Error searching for commands: %v", err) ce.Reply("Error searching for commands: %v", err)
return return
@@ -297,7 +297,7 @@ func fnExec(ce *WrappedCommandEvent) {
ce.User.pendingInteractionsLock.Lock() ce.User.pendingInteractionsLock.Lock()
ce.User.pendingInteractions[nonce] = ce ce.User.pendingInteractions[nonce] = ce
ce.User.pendingInteractionsLock.Unlock() ce.User.pendingInteractionsLock.Unlock()
err = ce.User.Session.SendInteractions(ce.Portal.GuildID, ce.Portal.Key.ChannelID, cmd, options, nonce) err = ce.User.Session.SendInteractions(ce.Portal.GuildID, ce.Portal.Key.ChannelID, cmd, options, nonce, ce.Portal.RefererOpt(""))
if err != nil { if err != nil {
ce.Reply("Error sending interaction: %v", err) ce.Reply("Error sending interaction: %v", err)
ce.User.pendingInteractionsLock.Lock() ce.User.pendingInteractionsLock.Lock()

View File

@@ -57,6 +57,8 @@ type BridgeConfig struct {
EnableWebhookAvatars bool `yaml:"enable_webhook_avatars"` EnableWebhookAvatars bool `yaml:"enable_webhook_avatars"`
UseDiscordCDNUpload bool `yaml:"use_discord_cdn_upload"` UseDiscordCDNUpload bool `yaml:"use_discord_cdn_upload"`
Proxy string `yaml:"proxy"`
CacheMedia string `yaml:"cache_media"` CacheMedia string `yaml:"cache_media"`
DirectMedia DirectMedia `yaml:"direct_media"` DirectMedia DirectMedia `yaml:"direct_media"`

View File

@@ -63,6 +63,7 @@ func DoUpgrade(helper *up.Helper) {
helper.Copy(up.Bool, "bridge", "prefix_webhook_messages") helper.Copy(up.Bool, "bridge", "prefix_webhook_messages")
helper.Copy(up.Bool, "bridge", "enable_webhook_avatars") helper.Copy(up.Bool, "bridge", "enable_webhook_avatars")
helper.Copy(up.Bool, "bridge", "use_discord_cdn_upload") helper.Copy(up.Bool, "bridge", "use_discord_cdn_upload")
helper.Copy(up.Str|up.Null, "bridge", "proxy")
helper.Copy(up.Str, "bridge", "cache_media") helper.Copy(up.Str, "bridge", "cache_media")
helper.Copy(up.Bool, "bridge", "direct_media", "enabled") helper.Copy(up.Bool, "bridge", "direct_media", "enabled")
helper.Copy(up.Str, "bridge", "direct_media", "server_name") helper.Copy(up.Str, "bridge", "direct_media", "server_name")

View File

@@ -357,7 +357,11 @@ func (dma *DirectMediaAPI) fetchNewAttachmentURL(ctx context.Context, meta *Atta
var err error var err error
messageIDStr := strconv.FormatUint(meta.MessageID, 10) messageIDStr := strconv.FormatUint(meta.MessageID, 10)
if client.IsUser { if client.IsUser {
msgs, err = client.ChannelMessages(channelIDStr, 5, "", "", messageIDStr) var refs []discordgo.RequestOption
if portal != nil {
refs = append(refs, discordgo.WithChannelReferer(portal.GuildID, channelIDStr))
}
msgs, err = client.ChannelMessages(channelIDStr, 5, "", "", messageIDStr, refs...)
} else { } else {
var msg *discordgo.Message var msg *discordgo.Message
msg, err = client.ChannelMessage(channelIDStr, messageIDStr) msg, err = client.ChannelMessage(channelIDStr, messageIDStr)

View File

@@ -168,6 +168,8 @@ bridge:
# like the official client does? The other option is sending the media in the message send request as a form part # like the official client does? The other option is sending the media in the message send request as a form part
# (which is always used by bots and webhooks). # (which is always used by bots and webhooks).
use_discord_cdn_upload: true use_discord_cdn_upload: true
# Proxy for Discord connections
proxy:
# Should mxc uris copied from Discord be cached? # Should mxc uris copied from Discord be cached?
# This can be `never` to never cache, `unencrypted` to only cache unencrypted mxc uris, or `always` to cache everything. # This can be `never` to never cache, `unencrypted` to only cache unencrypted mxc uris, or `always` to cache everything.
# If you have a media repo that generates non-unique mxc uris, you should set this to never. # If you have a media repo that generates non-unique mxc uris, you should set this to never.

30
go.mod
View File

@@ -1,22 +1,24 @@
module go.mau.fi/mautrix-discord module go.mau.fi/mautrix-discord
go 1.21 go 1.23.0
toolchain go1.24.2
require ( require (
github.com/bwmarrin/discordgo v0.27.0 github.com/bwmarrin/discordgo v0.27.0
github.com/gabriel-vasile/mimetype v1.4.3 github.com/gabriel-vasile/mimetype v1.4.8
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
github.com/gorilla/mux v1.8.0 github.com/gorilla/mux v1.8.0
github.com/gorilla/websocket v1.5.0 github.com/gorilla/websocket v1.5.0
github.com/lib/pq v1.10.9 github.com/lib/pq v1.10.9
github.com/mattn/go-sqlite3 v1.14.22 github.com/mattn/go-sqlite3 v1.14.27
github.com/rs/zerolog v1.31.0 github.com/rs/zerolog v1.34.0
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/stretchr/testify v1.8.4 github.com/stretchr/testify v1.10.0
github.com/yuin/goldmark v1.6.0 github.com/yuin/goldmark v1.7.10
go.mau.fi/util v0.2.2-0.20231228160422-22fdd4bbddeb go.mau.fi/util v0.2.2-0.20231228160422-22fdd4bbddeb
golang.org/x/exp v0.0.0-20231219180239-dc181d75b848 golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0
golang.org/x/sync v0.5.0 golang.org/x/sync v0.13.0
maunium.net/go/maulogger/v2 v2.4.1 maunium.net/go/maulogger/v2 v2.4.1
maunium.net/go/mautrix v0.16.3-0.20240712164054-e6046fbf432c maunium.net/go/mautrix v0.16.3-0.20240712164054-e6046fbf432c
) )
@@ -27,17 +29,17 @@ require (
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect github.com/mattn/go-isatty v0.0.19 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/tidwall/gjson v1.17.0 // indirect github.com/tidwall/gjson v1.18.0 // indirect
github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // 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
go.mau.fi/zeroconfig v0.1.2 // indirect go.mau.fi/zeroconfig v0.1.2 // indirect
golang.org/x/crypto v0.15.0 // indirect golang.org/x/crypto v0.37.0 // indirect
golang.org/x/net v0.18.0 // indirect golang.org/x/net v0.39.0 // indirect
golang.org/x/sys v0.14.0 // indirect golang.org/x/sys v0.32.0 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
maunium.net/go/mauflag v1.0.0 // indirect maunium.net/go/mauflag v1.0.0 // indirect
) )
replace github.com/bwmarrin/discordgo => github.com/beeper/discordgo v0.0.0-20231013182643-f333f2578a3c replace github.com/bwmarrin/discordgo => github.com/beeper/discordgo v0.0.0-20250320154217-0d7f942e6b38

53
go.sum
View File

@@ -1,13 +1,13 @@
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/beeper/discordgo v0.0.0-20231013182643-f333f2578a3c h1:WaJ9eX8eyOBHD8te5t7xzm27uwhfaN94o8vUVFXliyA= github.com/beeper/discordgo v0.0.0-20250320154217-0d7f942e6b38 h1:1WoSvVGM1pI9f+x7EGD2QPaXSUQeF3B7Lox7bmVe//s=
github.com/beeper/discordgo v0.0.0-20231013182643-f333f2578a3c/go.mod h1:59+AOzzjmL6onAh62nuLXmn7dJCaC/owDLWbGtjTcFA= github.com/beeper/discordgo v0.0.0-20250320154217-0d7f942e6b38/go.mod h1:59+AOzzjmL6onAh62nuLXmn7dJCaC/owDLWbGtjTcFA=
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 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/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
@@ -22,46 +22,47 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.27 h1:drZCnuvf37yPfs95E5jd9s3XhdVWLal+6BOK6qrv6IU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mattn/go-sqlite3 v1.14.27/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A= github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= 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 h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.17.0 h1:/Jocvlh98kcTfpN2+JzGQWQcqrPQwDrVEMApx/M5ZwM= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
github.com/tidwall/gjson v1.17.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
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 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.6.0 h1:boZcn2GTjpsynOsC0iJHnBWa4Bi0qzfJjthwauItG68= github.com/yuin/goldmark v1.7.10 h1:S+LrtBjRmqMac2UdtB6yyCEJm+UILZ2fefI4p7o0QpI=
github.com/yuin/goldmark v1.6.0/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.7.10/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
go.mau.fi/util v0.2.2-0.20231228160422-22fdd4bbddeb h1:Is+6vDKgINRy9KHodvi7NElxoDaWA8sc2S3cF3+QWjs= go.mau.fi/util v0.2.2-0.20231228160422-22fdd4bbddeb h1:Is+6vDKgINRy9KHodvi7NElxoDaWA8sc2S3cF3+QWjs=
go.mau.fi/util v0.2.2-0.20231228160422-22fdd4bbddeb/go.mod h1:tiBX6nxVSOjU89jVQ7wBh3P8KjM26Lv1k7/I5QdSvBw= go.mau.fi/util v0.2.2-0.20231228160422-22fdd4bbddeb/go.mod h1:tiBX6nxVSOjU89jVQ7wBh3P8KjM26Lv1k7/I5QdSvBw=
go.mau.fi/zeroconfig v0.1.2 h1:DKOydWnhPMn65GbXZOafgkPm11BvFashZWLct0dGFto= go.mau.fi/zeroconfig v0.1.2 h1:DKOydWnhPMn65GbXZOafgkPm11BvFashZWLct0dGFto=
go.mau.fi/zeroconfig v0.1.2/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= go.mau.fi/zeroconfig v0.1.2/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70=
golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/exp v0.0.0-20231219180239-dc181d75b848 h1:+iq7lrkxmFNBM7xx+Rae2W6uyPfhPeDWD+n+JgppptE= golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
golang.org/x/exp v0.0.0-20231219180239-dc181d75b848/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 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/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= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=

View File

@@ -185,7 +185,7 @@ func main() {
Name: "mautrix-discord", Name: "mautrix-discord",
URL: "https://github.com/mautrix/discord", URL: "https://github.com/mautrix/discord",
Description: "A Matrix-Discord puppeting bridge.", Description: "A Matrix-Discord puppeting bridge.",
Version: "0.7.0", Version: "0.7.3",
ProtocolName: "Discord", ProtocolName: "Discord",
BeeperServiceName: "discordgo", BeeperServiceName: "discordgo",
BeeperNetworkName: "discord", BeeperNetworkName: "discord",

View File

@@ -13,6 +13,7 @@ import (
"net/http" "net/http"
"reflect" "reflect"
"regexp" "regexp"
"slices"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
@@ -300,7 +301,8 @@ func (portal *Portal) MainIntent() *appservice.IntentAPI {
type CustomBridgeInfoContent struct { type CustomBridgeInfoContent struct {
event.BridgeEventContent event.BridgeEventContent
RoomType string `json:"com.beeper.room_type,omitempty"` RoomType string `json:"com.beeper.room_type,omitempty"`
RoomTypeV2 string `json:"com.beeper.room_type.v2,omitempty"`
} }
func init() { func init() {
@@ -343,7 +345,14 @@ func (portal *Portal) getBridgeInfo() (string, CustomBridgeInfoContent) {
if portal.Type == discordgo.ChannelTypeDM || portal.Type == discordgo.ChannelTypeGroupDM { if portal.Type == discordgo.ChannelTypeDM || portal.Type == discordgo.ChannelTypeGroupDM {
roomType = "dm" roomType = "dm"
} }
return bridgeInfoStateKey, CustomBridgeInfoContent{bridgeInfo, roomType} var roomTypeV2 string
if portal.Type == discordgo.ChannelTypeDM {
roomTypeV2 = "dm"
} else if portal.Type == discordgo.ChannelTypeGroupDM {
roomTypeV2 = "group_dm"
}
return bridgeInfoStateKey, CustomBridgeInfoContent{bridgeInfo, roomType, roomTypeV2}
} }
func (portal *Portal) UpdateBridgeInfo() { func (portal *Portal) UpdateBridgeInfo() {
@@ -1167,7 +1176,7 @@ func (portal *Portal) startThreadFromMatrix(sender *User, threadRoot id.EventID)
AutoArchiveDuration: 24 * 60, AutoArchiveDuration: 24 * 60,
Type: discordgo.ChannelTypeGuildPublicThread, Type: discordgo.ChannelTypeGuildPublicThread,
Location: "Message", Location: "Message",
}) }, portal.RefererOptIfUser(sender.Session, "")...)
if err != nil { if err != nil {
return "", fmt.Errorf("error starting thread: %v", err) return "", fmt.Errorf("error starting thread: %v", err)
} }
@@ -1497,6 +1506,20 @@ func (portal *Portal) convertReplyMessageToEmbed(eventID id.EventID, url string)
return embed, nil return embed, nil
} }
func (portal *Portal) RefererOpt(threadID string) discordgo.RequestOption {
if threadID != "" && threadID != portal.Key.ChannelID {
return discordgo.WithThreadReferer(portal.GuildID, portal.Key.ChannelID, threadID)
}
return discordgo.WithChannelReferer(portal.GuildID, portal.Key.ChannelID)
}
func (portal *Portal) RefererOptIfUser(sess *discordgo.Session, threadID string) []discordgo.RequestOption {
if sess == nil || !sess.IsUser {
return nil
}
return []discordgo.RequestOption{portal.RefererOpt(threadID)}
}
func (portal *Portal) handleMatrixMessage(sender *User, evt *event.Event) { func (portal *Portal) handleMatrixMessage(sender *User, evt *event.Event) {
if portal.IsPrivateChat() && sender.DiscordID != portal.Key.Receiver { if portal.IsPrivateChat() && sender.DiscordID != portal.Key.Receiver {
go portal.sendMessageMetrics(evt, errUserNotReceiver, "Ignoring") go portal.sendMessageMetrics(evt, errUserNotReceiver, "Ignoring")
@@ -1576,9 +1599,12 @@ func (portal *Portal) handleMatrixMessage(sender *User, evt *event.Event) {
} }
} }
if replyToMXID := content.RelatesTo.GetNonFallbackReplyTo(); replyToMXID != "" { replyToMXID := content.RelatesTo.GetNonFallbackReplyTo()
var replyToUser id.UserID
if replyToMXID != "" {
replyTo := portal.bridge.DB.Message.GetByMXID(portal.Key, replyToMXID) replyTo := portal.bridge.DB.Message.GetByMXID(portal.Key, replyToMXID)
if replyTo != nil && replyTo.ThreadID == threadID { if replyTo != nil && replyTo.ThreadID == threadID {
replyToUser = replyTo.SenderMXID
if isWebhookSend { if isWebhookSend {
messageURL := fmt.Sprintf("https://discord.com/channels/%s/%s/%s", portal.GuildID, channelID, replyTo.DiscordID) messageURL := fmt.Sprintf("https://discord.com/channels/%s/%s/%s", portal.GuildID, channelID, replyTo.DiscordID)
embed, err := portal.convertReplyMessageToEmbed(replyTo.MXID, messageURL) embed, err := portal.convertReplyMessageToEmbed(replyTo.MXID, messageURL)
@@ -1626,14 +1652,14 @@ func (portal *Portal) handleMatrixMessage(sender *User, evt *event.Event) {
Name: att.Filename, Name: att.Filename,
ID: sender.NextDiscordUploadID(), ID: sender.NextDiscordUploadID(),
}}, }},
}) }, portal.RefererOpt(threadID))
if err != nil { if err != nil {
go portal.sendMessageMetrics(evt, err, "Error preparing to reupload media in") go portal.sendMessageMetrics(evt, err, "Error preparing to reupload media in")
return return
} }
prepared := prep.Attachments[0] prepared := prep.Attachments[0]
att.UploadedFilename = prepared.UploadFilename att.UploadedFilename = prepared.UploadFilename
err = uploadDiscordAttachment(prepared.UploadURL, data) err = uploadDiscordAttachment(sender.Session.Client, prepared.UploadURL, data)
if err != nil { if err != nil {
go portal.sendMessageMetrics(evt, err, "Error reuploading media in") go portal.sendMessageMetrics(evt, err, "Error reuploading media in")
return return
@@ -1649,10 +1675,22 @@ func (portal *Portal) handleMatrixMessage(sender *User, evt *event.Event) {
go portal.sendMessageMetrics(evt, fmt.Errorf("%w %q", errUnknownMsgType, content.MsgType), "Ignoring") go portal.sendMessageMetrics(evt, fmt.Errorf("%w %q", errUnknownMsgType, content.MsgType), "Ignoring")
return return
} }
silentReply := content.Mentions != nil && replyToMXID != "" &&
(len(content.Mentions.UserIDs) == 0 || (replyToUser != "" && !slices.Contains(content.Mentions.UserIDs, replyToUser)))
if silentReply && sendReq.AllowedMentions != nil {
sendReq.AllowedMentions.RepliedUser = false
}
if !isWebhookSend { if !isWebhookSend {
// AllowedMentions must not be set for real users, and it's also not that useful for personal bots. // AllowedMentions must not be set for real users, and it's also not that useful for personal bots.
// It's only important for relaying, where the webhook may have higher permissions than the user on Matrix. // It's only important for relaying, where the webhook may have higher permissions than the user on Matrix.
sendReq.AllowedMentions = nil if silentReply {
sendReq.AllowedMentions = &discordgo.MessageAllowedMentions{
Parse: []discordgo.AllowedMentionType{discordgo.AllowedMentionTypeUsers, discordgo.AllowedMentionTypeRoles, discordgo.AllowedMentionTypeEveryone},
RepliedUser: false,
}
} else {
sendReq.AllowedMentions = nil
}
} else if strings.Contains(sendReq.Content, "@everyone") || strings.Contains(sendReq.Content, "@here") { } else if strings.Contains(sendReq.Content, "@everyone") || strings.Contains(sendReq.Content, "@here") {
powerLevels, err := portal.MainIntent().PowerLevels(portal.MXID) powerLevels, err := portal.MainIntent().PowerLevels(portal.MXID)
if err != nil { if err != nil {
@@ -1667,20 +1705,20 @@ func (portal *Portal) handleMatrixMessage(sender *User, evt *event.Event) {
var msg *discordgo.Message var msg *discordgo.Message
var err error var err error
if !isWebhookSend { if !isWebhookSend {
msg, err = sess.ChannelMessageSendComplex(channelID, &sendReq) msg, err = sess.ChannelMessageSendComplex(channelID, &sendReq, portal.RefererOptIfUser(sess, threadID)...)
} else { } else {
username, avatarURL := portal.getRelayUserMeta(sender) username, avatarURL := portal.getRelayUserMeta(sender)
msg, err = relayClient.WebhookThreadExecute(portal.RelayWebhookID, portal.RelayWebhookSecret, true, threadID, &discordgo.WebhookParams{ msg, err = relayClient.WebhookThreadExecute(portal.RelayWebhookID, portal.RelayWebhookSecret, true, threadID, &discordgo.WebhookParams{
Content: sendReq.Content, Content: sendReq.Content,
Username: username, Username: username,
AvatarURL: avatarURL, AvatarURL: avatarURL,
TTS: sendReq.TTS,
Files: sendReq.Files, Files: sendReq.Files,
Components: sendReq.Components, Components: sendReq.Components,
Embeds: sendReq.Embeds, Embeds: sendReq.Embeds,
AllowedMentions: sendReq.AllowedMentions, AllowedMentions: sendReq.AllowedMentions,
}) })
} }
sender.handlePossible40002(err)
go portal.sendMessageMetrics(evt, err, "Error sending") go portal.sendMessageMetrics(evt, err, "Error sending")
if msg != nil { if msg != nil {
dbMsg := portal.bridge.DB.Message.New() dbMsg := portal.bridge.DB.Message.New()
@@ -1915,7 +1953,7 @@ func (portal *Portal) handleMatrixReaction(sender *User, evt *event.Event) {
return return
} }
err := sender.Session.MessageReactionAdd(msg.DiscordProtoChannelID(), msg.DiscordID, emojiID) err := sender.Session.MessageReactionAddUser(portal.GuildID, msg.DiscordProtoChannelID(), msg.DiscordID, emojiID)
go portal.sendMessageMetrics(evt, err, "Error sending") go portal.sendMessageMetrics(evt, err, "Error sending")
if err == nil { if err == nil {
dbReaction := portal.bridge.DB.Reaction.New() dbReaction := portal.bridge.DB.Reaction.New()
@@ -2051,7 +2089,7 @@ func (portal *Portal) handleMatrixRedaction(sender *User, evt *event.Event) {
var err error var err error
// TODO add support for deleting individual attachments from messages // TODO add support for deleting individual attachments from messages
if sess != nil { if sess != nil {
err = sess.ChannelMessageDelete(message.DiscordProtoChannelID(), message.DiscordID) err = sess.ChannelMessageDelete(message.DiscordProtoChannelID(), message.DiscordID, portal.RefererOptIfUser(sess, message.ThreadID)...)
} else { } else {
// TODO pre-validate that the message was sent by the webhook? // TODO pre-validate that the message was sent by the webhook?
err = relayClient.WebhookMessageDelete(portal.RelayWebhookID, portal.RelayWebhookSecret, message.DiscordID) err = relayClient.WebhookMessageDelete(portal.RelayWebhookID, portal.RelayWebhookSecret, message.DiscordID)
@@ -2066,7 +2104,7 @@ func (portal *Portal) handleMatrixRedaction(sender *User, evt *event.Event) {
if sess != nil { if sess != nil {
reaction := portal.bridge.DB.Reaction.GetByMXID(evt.Redacts) reaction := portal.bridge.DB.Reaction.GetByMXID(evt.Redacts)
if reaction != nil && reaction.Channel == portal.Key { if reaction != nil && reaction.Channel == portal.Key {
err := sess.MessageReactionRemove(reaction.DiscordProtoChannelID(), reaction.MessageID, reaction.EmojiName, reaction.Sender) err := sess.MessageReactionRemoveUser(portal.GuildID, reaction.DiscordProtoChannelID(), reaction.MessageID, reaction.EmojiName, reaction.Sender)
go portal.sendMessageMetrics(evt, err, "Error sending") go portal.sendMessageMetrics(evt, err, "Error sending")
if err == nil { if err == nil {
reaction.Delete() reaction.Delete()
@@ -2135,7 +2173,7 @@ func (portal *Portal) HandleMatrixReadReceipt(brUser bridge.User, eventID id.Eve
Msg("Dropping read receipt: thread ID mismatch") Msg("Dropping read receipt: thread ID mismatch")
return return
} }
resp, err := sender.Session.ChannelMessageAckNoToken(msg.DiscordProtoChannelID(), msg.DiscordID) resp, err := sender.Session.ChannelMessageAckNoToken(msg.DiscordProtoChannelID(), msg.DiscordID, portal.RefererOpt(msg.DiscordProtoChannelID()))
if err != nil { if err != nil {
log.Err(err).Msg("Failed to send read receipt to Discord") log.Err(err).Msg("Failed to send read receipt to Discord")
} else if resp.Token != nil { } else if resp.Token != nil {
@@ -2169,7 +2207,7 @@ func (portal *Portal) HandleMatrixTyping(newTyping []id.UserID) {
user := portal.bridge.GetUserByMXID(userID) user := portal.bridge.GetUserByMXID(userID)
if user != nil && user.Session != nil { if user != nil && user.Session != nil {
user.ViewingChannel(portal) user.ViewingChannel(portal)
err := user.Session.ChannelTyping(portal.Key.ChannelID) err := user.Session.ChannelTyping(portal.Key.ChannelID, portal.RefererOptIfUser(user.Session, "")...)
if err != nil { if err != nil {
portal.log.Warn().Err(err). portal.log.Warn().Err(err).
Str("user_id", user.MXID.String()). Str("user_id", user.MXID.String()).

View File

@@ -18,6 +18,8 @@ package main
import ( import (
"context" "context"
"crypto/sha256"
"encoding/hex"
"fmt" "fmt"
"html" "html"
"strconv" "strconv"
@@ -101,7 +103,7 @@ func (portal *Portal) cleanupConvertedStickerInfo(content *event.MessageEventCon
} }
} }
func (portal *Portal) convertDiscordSticker(ctx context.Context, intent *appservice.IntentAPI, sticker *discordgo.Sticker) *ConvertedMessage { func (portal *Portal) convertDiscordSticker(ctx context.Context, intent *appservice.IntentAPI, sticker *discordgo.StickerItem) *ConvertedMessage {
var mime string var mime string
switch sticker.FormatType { switch sticker.FormatType {
case discordgo.StickerFormatTypePNG: case discordgo.StickerFormatTypePNG:
@@ -165,7 +167,7 @@ func (portal *Portal) convertDiscordAttachment(ctx context.Context, intent *apps
if att.Waveform != nil { if att.Waveform != nil {
// TODO convert waveform // TODO convert waveform
extra = map[string]any{ extra = map[string]any{
"org.matrix.1767.audio": map[string]any{ "org.matrix.msc1767.audio": map[string]any{
"duration": int(att.DurationSeconds * 1000), "duration": int(att.DurationSeconds * 1000),
}, },
"org.matrix.msc3245.voice": map[string]any{}, "org.matrix.msc3245.voice": map[string]any{},
@@ -252,6 +254,7 @@ func (portal *Portal) convertDiscordVideoEmbed(ctx context.Context, intent *apps
if content.MsgType == event.MsgVideo && embed.Type == discordgo.EmbedTypeGifv { if content.MsgType == event.MsgVideo && embed.Type == discordgo.EmbedTypeGifv {
extra["info"] = map[string]any{ extra["info"] = map[string]any{
"fi.mau.discord.gifv": true, "fi.mau.discord.gifv": true,
"fi.mau.gif": true,
"fi.mau.loop": true, "fi.mau.loop": true,
"fi.mau.autoplay": true, "fi.mau.autoplay": true,
"fi.mau.hide_controls": true, "fi.mau.hide_controls": true,
@@ -341,10 +344,10 @@ func (puppet *Puppet) addMemberMeta(part *ConvertedMessage, msg *discordgo.Messa
var discordAvatarURL string var discordAvatarURL string
if msg.Member.Avatar != "" { if msg.Member.Avatar != "" {
var err error var err error
avatarURL, discordAvatarURL, err = puppet.bridge.reuploadUserAvatar(puppet.DefaultIntent(), msg.GuildID, msg.Author.ID, msg.Author.Avatar) avatarURL, discordAvatarURL, err = puppet.bridge.reuploadUserAvatar(puppet.DefaultIntent(), msg.GuildID, msg.Author.ID, msg.Member.Avatar)
if err != nil { if err != nil {
puppet.log.Warn().Err(err). puppet.log.Warn().Err(err).
Str("avatar_id", msg.Author.Avatar). Str("avatar_id", msg.Member.Avatar).
Msg("Failed to reupload guild user avatar") Msg("Failed to reupload guild user avatar")
} }
} }
@@ -356,8 +359,7 @@ func (puppet *Puppet) addMemberMeta(part *ConvertedMessage, msg *discordgo.Messa
} }
if msg.Member.Nick != "" || !avatarURL.IsEmpty() { if msg.Member.Nick != "" || !avatarURL.IsEmpty() {
perMessageProfile := map[string]any{ perMessageProfile := map[string]any{
"is_multiple_users": false, "id": fmt.Sprintf("%s_%s", msg.GuildID, msg.Author.ID),
"displayname": msg.Member.Nick, "displayname": msg.Member.Nick,
"avatar_url": avatarURL.String(), "avatar_url": avatarURL.String(),
} }
@@ -395,9 +397,9 @@ func (puppet *Puppet) addWebhookMeta(part *ConvertedMessage, msg *discordgo.Mess
"avatar_url": msg.Author.AvatarURL(""), "avatar_url": msg.Author.AvatarURL(""),
"avatar_mxc": avatarURL.String(), "avatar_mxc": avatarURL.String(),
} }
profileID := sha256.Sum256(fmt.Appendf(nil, "%s:%s", msg.Author.Username, msg.Author.Avatar))
part.Extra["com.beeper.per_message_profile"] = map[string]any{ part.Extra["com.beeper.per_message_profile"] = map[string]any{
"is_multiple_users": true, "id": hex.EncodeToString(profileID[:]),
"avatar_url": avatarURL.String(), "avatar_url": avatarURL.String(),
"displayname": msg.Author.Username, "displayname": msg.Author.Username,
} }
@@ -632,7 +634,7 @@ func isPlainGifMessage(msg *discordgo.Message) bool {
} }
embed := msg.Embeds[0] embed := msg.Embeds[0]
isGifVideo := embed.Type == discordgo.EmbedTypeGifv && embed.Video != nil isGifVideo := embed.Type == discordgo.EmbedTypeGifv && embed.Video != nil
isGifImage := embed.Type == discordgo.EmbedTypeImage && embed.Image == nil && embed.Thumbnail != nil isGifImage := embed.Type == discordgo.EmbedTypeImage && embed.Image == nil && embed.Thumbnail != nil && embed.Title == ""
contentIsOnlyURL := msg.Content == embed.URL || discordLinkRegexFull.MatchString(msg.Content) contentIsOnlyURL := msg.Content == embed.URL || discordLinkRegexFull.MatchString(msg.Content)
return contentIsOnlyURL && (isGifVideo || isGifImage) return contentIsOnlyURL && (isGifVideo || isGifImage)
} }

View File

@@ -112,6 +112,10 @@ func (thread *Thread) maybeInitialBackfill(source *User) {
thread.Parent.forwardBackfillInitial(source, thread) thread.Parent.forwardBackfillInitial(source, thread)
} }
func (thread *Thread) RefererOpt() discordgo.RequestOption {
return discordgo.WithThreadReferer(thread.Parent.GuildID, thread.ParentID, thread.ID)
}
func (thread *Thread) Join(user *User) { func (thread *Thread) Join(user *User) {
if user.IsInPortal(thread.ID) { if user.IsInPortal(thread.ID) {
return return
@@ -137,7 +141,7 @@ func (thread *Thread) Join(user *User) {
var err error var err error
if user.Session.IsUser { if user.Session.IsUser {
err = user.Session.ThreadJoinWithLocation(thread.ID, discordgo.ThreadJoinLocationContextMenu) err = user.Session.ThreadJoin(thread.ID, discordgo.WithLocationParam(discordgo.ThreadJoinLocationContextMenu), thread.RefererOpt())
} else { } else {
err = user.Session.ThreadJoin(thread.ID) err = user.Session.ThreadJoin(thread.ID)
} }

41
user.go
View File

@@ -2,11 +2,14 @@ package main
import ( import (
"context" "context"
"crypto/tls"
"errors" "errors"
"fmt" "fmt"
"math/rand" "math/rand"
"net/http" "net/http"
"net/url"
"os" "os"
"runtime/debug"
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
@@ -546,6 +549,19 @@ func (user *User) Connect() error {
if err != nil { if err != nil {
return err return err
} }
if user.bridge.Config.Bridge.Proxy != "" {
u, _ := url.Parse(user.bridge.Config.Bridge.Proxy)
tlsConf := &tls.Config{
InsecureSkipVerify: os.Getenv("DISCORD_SKIP_TLS_VERIFICATION") == "true",
}
session.Client.Transport = &http.Transport{
Proxy: http.ProxyURL(u),
TLSClientConfig: tlsConf,
ForceAttemptHTTP2: true,
}
session.Dialer.Proxy = http.ProxyURL(u)
session.Dialer.TLSClientConfig = tlsConf
}
// TODO move to config // TODO move to config
if os.Getenv("DISCORD_DEBUG") == "1" { if os.Getenv("DISCORD_DEBUG") == "1" {
session.LogLevel = discordgo.LogDebug session.LogLevel = discordgo.LogDebug
@@ -561,6 +577,13 @@ func (user *User) Connect() error {
} }
session.EventHandler = user.eventHandlerSync session.EventHandler = user.eventHandlerSync
if session.IsUser {
err = session.LoadMainPage(context.TODO())
if err != nil {
user.log.Warn().Err(err).Msg("Failed to load main page")
}
}
user.Session = session user.Session = session
for { for {
@@ -579,6 +602,15 @@ func (user *User) eventHandlerSync(rawEvt any) {
} }
func (user *User) eventHandler(rawEvt any) { func (user *User) eventHandler(rawEvt any) {
defer func() {
err := recover()
if err != nil {
user.log.Error().
Bytes(zerolog.ErrorStackFieldName, debug.Stack()).
Any(zerolog.ErrorFieldName, err).
Msg("Panic in Discord event handler")
}
}()
switch evt := rawEvt.(type) { switch evt := rawEvt.(type) {
case *discordgo.Ready: case *discordgo.Ready:
user.readyHandler(evt) user.readyHandler(evt)
@@ -999,6 +1031,15 @@ func (user *User) invalidAuthHandler(_ *discordgo.InvalidAuth) {
go user.Logout(false) go user.Logout(false)
} }
func (user *User) handlePossible40002(err error) bool {
var restErr *discordgo.RESTError
if !errors.As(err, &restErr) || restErr.Message == nil || restErr.Message.Code != discordgo.ErrCodeActionRequiredVerifiedAccount {
return false
}
user.BridgeState.Send(status.BridgeState{StateEvent: status.StateBadCredentials, Error: "dc-http-40002", Message: restErr.Message.Message})
return true
}
func (user *User) guildCreateHandler(g *discordgo.GuildCreate) { func (user *User) guildCreateHandler(g *discordgo.GuildCreate) {
user.log.Info(). user.log.Info().
Str("guild_id", g.ID). Str("guild_id", g.ID).