Skip to content

Commit 3733a5a

Browse files
committed
Add 'Message.OriginalUsername' and use it for Discord name spoofing.
Discord does not allow replies to be created via webhook - discord/discord-api-docs#2251 - discord/discord-api-docs#3282 in that case, discord.go falls back to using the REST API, which is fine except it doesn't allow masquerading the displayed username/avatar. Trying to set a single RemoteNickFormat that covers both cases nicely is impossible. Instead, force the webhook case to use the plain username, as if `RemoteNickFormat="{NICK}"` in that case, but respect it in the reply case, which has to inline the username in the message text.
1 parent 1957793 commit 3733a5a

File tree

4 files changed

+21
-35
lines changed

4 files changed

+21
-35
lines changed

bridge/config/config.go

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -35,19 +35,20 @@ const (
3535
const ParentIDNotFound = "msg-parent-not-found"
3636

3737
type Message struct {
38-
Text string `json:"text"`
39-
Channel string `json:"channel"`
40-
Username string `json:"username"`
41-
UserID string `json:"userid"` // userid on the bridge
42-
Avatar string `json:"avatar"`
43-
Account string `json:"account"`
44-
Event string `json:"event"`
45-
Protocol string `json:"protocol"`
46-
Gateway string `json:"gateway"`
47-
ParentID string `json:"parent_id"`
48-
Timestamp time.Time `json:"timestamp"`
49-
ID string `json:"id"`
50-
Extra map[string][]interface{}
38+
Text string `json:"text"`
39+
Channel string `json:"channel"`
40+
Username string `json:"username"`
41+
OriginalUsername string `json:"original_username"` // Username before RemoteNickFormat gets applied
42+
UserID string `json:"userid"` // userid on the bridge
43+
Avatar string `json:"avatar"`
44+
Account string `json:"account"`
45+
Event string `json:"event"`
46+
Protocol string `json:"protocol"`
47+
Gateway string `json:"gateway"`
48+
ParentID string `json:"parent_id"`
49+
Timestamp time.Time `json:"timestamp"`
50+
ID string `json:"id"`
51+
Extra map[string][]interface{}
5152
}
5253

5354
func (m Message) ParentNotFound() bool {

bridge/discord/discord.go

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -287,22 +287,6 @@ func (b *Bdiscord) Send(msg config.Message) (string, error) {
287287
return b.handleEventWebhook(&msg, channelID)
288288
}
289289

290-
if msg.ParentID != "" && b.GetString("RemoteNickFormat") == "{NICK}" {
291-
// Discord does not allow replies to be created via webhook
292-
// - https://github.com/discord/discord-api-docs/issues/2251
293-
// - https://github.com/discord/discord-api-docs/discussions/3282
294-
// discord.go falls back to using the REST API, which is fine except it
295-
// doesn't allow masquerading the displayed username/avatar, so instead
296-
// the nickname is placed inline in the message body, as other bridges.
297-
// Usually with webhooks you would want RemoteNickFormat="{NICK}",
298-
// but this looks bad when inlined, so as a janky workaround,
299-
// hardcode the outgoing Discord message as if a _separate_ format string like RemoteNickFormatInline="<{NICK}> ".
300-
//
301-
// Note that at this point in the code, gateway.go has already applied RemoteNickFormat,
302-
// so msg.Username has already been modified once. The only way
303-
msg.Username = fmt.Sprintf("<%s> ", msg.Username)
304-
}
305-
306290
return b.handleEventBotUser(&msg, channelID)
307291
}
308292

bridge/discord/webhook.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ func (b *Bdiscord) maybeGetLocalAvatar(msg *config.Message) string {
3333
continue
3434
}
3535

36-
member, err := b.getGuildMemberByNick(msg.Username)
36+
member, err := b.getGuildMemberByNick(msg.OriginalUsername)
3737
if err != nil {
3838
return ""
3939
}
@@ -51,7 +51,7 @@ func (b *Bdiscord) webhookSendTextOnly(msg *config.Message, channelID string) (s
5151
channelID,
5252
&discordgo.WebhookParams{
5353
Content: msgPart,
54-
Username: msg.Username,
54+
Username: msg.OriginalUsername,
5555
AvatarURL: msg.Avatar,
5656
AllowedMentions: b.getAllowedMentions(),
5757
},
@@ -81,7 +81,7 @@ func (b *Bdiscord) webhookSendFilesOnly(msg *config.Message, channelID string) e
8181
_, err := b.transmitter.Send(
8282
channelID,
8383
&discordgo.WebhookParams{
84-
Username: msg.Username,
84+
Username: msg.OriginalUsername,
8585
AvatarURL: msg.Avatar,
8686
Files: []*discordgo.File{&file},
8787
Content: content,
@@ -137,8 +137,8 @@ func (b *Bdiscord) handleEventWebhook(msg *config.Message, channelID string) (st
137137
}
138138

139139
// discord username must be [0..32] max
140-
if len(msg.Username) > 32 {
141-
msg.Username = msg.Username[0:32]
140+
if len(msg.OriginalUsername) > 32 {
141+
msg.OriginalUsername = msg.OriginalUsername[0:32]
142142
}
143143

144144
if msg.ID != "" {
@@ -155,7 +155,7 @@ func (b *Bdiscord) handleEventWebhook(msg *config.Message, channelID string) (st
155155
// TODO: Optimize away noop-updates of un-edited messages
156156
editErr = b.transmitter.Edit(channelID, msgIds[i], &discordgo.WebhookParams{
157157
Content: msgParts[i],
158-
Username: msg.Username,
158+
Username: msg.OriginalUsername,
159159
AllowedMentions: b.getAllowedMentions(),
160160
})
161161
if editErr != nil {

gateway/gateway.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,7 @@ func (gw *Gateway) SendMessage(
476476

477477
msg.Channel = channel.Name
478478
msg.Avatar = gw.modifyAvatar(rmsg, dest)
479+
msg.OriginalUsername = msg.Username
479480
msg.Username = gw.modifyUsername(rmsg, dest)
480481

481482
// exclude file delete event as the msg ID here is the native file ID that needs to be deleted

0 commit comments

Comments
 (0)