Skip to content

Commit 0fef5e2

Browse files
committed
fix(discord): prevent duplicate link expansion and add regex tests
Address Copilot review feedback: - Move resolveDiscordRefs(content) before the referenced message concatenation to prevent message links in quoted replies from being expanded twice. - Add unit tests for channelRefRe and msgLinkRe regex patterns, covering valid/invalid inputs and the 3-link cap.
1 parent 3826333 commit 0fef5e2

2 files changed

Lines changed: 102 additions & 1 deletion

File tree

pkg/channels/discord/discord.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,10 @@ func (c *DiscordChannel) handleMessage(s *discordgo.Session, m *discordgo.Messag
345345
content = c.stripBotMention(content)
346346
}
347347

348+
// Resolve Discord refs in main content before concatenation to avoid
349+
// double-expanding links that appear in the referenced message.
350+
content = c.resolveDiscordRefs(s, content, m.GuildID)
351+
348352
// Prepend referenced (quoted) message content if this is a reply
349353
if m.MessageReference != nil && m.ReferencedMessage != nil {
350354
refContent := m.ReferencedMessage.Content
@@ -358,7 +362,6 @@ func (c *DiscordChannel) handleMessage(s *discordgo.Session, m *discordgo.Messag
358362
refAuthor, refContent, content)
359363
}
360364
}
361-
content = c.resolveDiscordRefs(s, content, m.GuildID)
362365

363366
senderID := m.Author.ID
364367

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package discord
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func TestChannelRefRegex(t *testing.T) {
8+
tests := []struct {
9+
name string
10+
input string
11+
wantID string
12+
wantOK bool
13+
}{
14+
{"basic channel ref", "<#123456789>", "123456789", true},
15+
{"long id", "<#1476410172178956413>", "1476410172178956413", true},
16+
{"no match plain text", "hello world", "", false},
17+
{"no match partial", "<#>", "", false},
18+
{"no match letters", "<#abc>", "", false},
19+
}
20+
21+
for _, tt := range tests {
22+
t.Run(tt.name, func(t *testing.T) {
23+
matches := channelRefRe.FindStringSubmatch(tt.input)
24+
if tt.wantOK {
25+
if len(matches) < 2 || matches[1] != tt.wantID {
26+
t.Errorf("channelRefRe(%q) = %v, want ID %q", tt.input, matches, tt.wantID)
27+
}
28+
} else {
29+
if len(matches) >= 2 {
30+
t.Errorf("channelRefRe(%q) should not match, got %v", tt.input, matches)
31+
}
32+
}
33+
})
34+
}
35+
}
36+
37+
func TestMsgLinkRegex(t *testing.T) {
38+
tests := []struct {
39+
name string
40+
input string
41+
wantGuild string
42+
wantChan string
43+
wantMsg string
44+
wantOK bool
45+
}{
46+
{
47+
"discord.com link",
48+
"https://discord.com/channels/111/222/333",
49+
"111", "222", "333", true,
50+
},
51+
{
52+
"discordapp.com link",
53+
"https://discordapp.com/channels/111/222/333",
54+
"111", "222", "333", true,
55+
},
56+
{
57+
"real world ids",
58+
"check this https://discord.com/channels/1476408339557519530/1476410172178956413/1476412345678901234 please",
59+
"1476408339557519530", "1476410172178956413", "1476412345678901234", true,
60+
},
61+
{"no match http", "http://discord.com/channels/1/2/3", "", "", "", false},
62+
{"no match missing segment", "https://discord.com/channels/1/2", "", "", "", false},
63+
{"no match plain text", "hello world", "", "", "", false},
64+
}
65+
66+
for _, tt := range tests {
67+
t.Run(tt.name, func(t *testing.T) {
68+
matches := msgLinkRe.FindStringSubmatch(tt.input)
69+
if tt.wantOK {
70+
if len(matches) < 4 {
71+
t.Fatalf("msgLinkRe(%q) didn't match, want guild=%s chan=%s msg=%s",
72+
tt.input, tt.wantGuild, tt.wantChan, tt.wantMsg)
73+
}
74+
if matches[1] != tt.wantGuild || matches[2] != tt.wantChan || matches[3] != tt.wantMsg {
75+
t.Errorf("msgLinkRe(%q) = guild=%s chan=%s msg=%s, want %s/%s/%s",
76+
tt.input, matches[1], matches[2], matches[3],
77+
tt.wantGuild, tt.wantChan, tt.wantMsg)
78+
}
79+
} else {
80+
if len(matches) >= 4 {
81+
t.Errorf("msgLinkRe(%q) should not match, got %v", tt.input, matches)
82+
}
83+
}
84+
})
85+
}
86+
}
87+
88+
func TestMsgLinkRegex_MultipleMatches(t *testing.T) {
89+
input := "see https://discord.com/channels/1/2/3 and https://discord.com/channels/4/5/6 and https://discord.com/channels/7/8/9 and https://discord.com/channels/10/11/12"
90+
matches := msgLinkRe.FindAllStringSubmatch(input, 3)
91+
if len(matches) != 3 {
92+
t.Fatalf("expected 3 matches (capped), got %d", len(matches))
93+
}
94+
// Verify the 3rd match is 7/8/9 (not 10/11/12)
95+
if matches[2][1] != "7" || matches[2][2] != "8" || matches[2][3] != "9" {
96+
t.Errorf("3rd match = %v, want guild=7 chan=8 msg=9", matches[2])
97+
}
98+
}

0 commit comments

Comments
 (0)