Skip to content

Commit 7db2586

Browse files
committed
fix(listener): 拦贴纸/GIF聚合站 + 裸媒体文件,避免 Discord 表情包误入分享库
事故:用户 yhn 在分享频道发了一个 Discord 贴纸(klipy GIF),message.content 里就是裸 https://klipy.com/gifs/... URL,listener 当成正常分享走完 OG fetch + 分类,被打成 APPROVED 上架成 #18。 原 _SKIP_HOSTS 只拦了 discord.com / cdn.discordapp.com 等 Discord 自家域,没考虑贴纸面板默认走 tenor / klipy / giphy。同类问题:mmbiz.qpic.cn 这类纯图片直链(#5)也不该入库。 改法两层:(1) _SKIP_HOSTS 加入 tenor / klipy / giphy 全套;(2) 兜底在 path 上做媒体扩展名(.gif/.png/.jpg/.mp4/...)匹配,host 永远穷举不完。匹配只看 path,query 里出现 .jpg 不算(避免误伤带 ?file=foo.jpg 的正常 API 链接)。+19 个测试 case 覆盖。
1 parent daba7da commit 7db2586

2 files changed

Lines changed: 103 additions & 9 deletions

File tree

src/chat_bot/cogs/listener.py

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,31 +28,72 @@
2828

2929
_URL_RE = re.compile(r"https?://[^\s<>\"'\]\)]+", re.IGNORECASE)
3030

31-
# 跳过 Discord 自身的各种链接:用户经常复制错(比如右键"复制消息链接"会粘
32-
# discord.com/channels/.../... 出来,这不该被当作"分享"入库)。静默忽略,不
33-
# 回复也不提交,像 bot 没看到一样。
31+
# 跳过的链接源。两层:
32+
# 1. Discord 自身(消息链接 / 附件 CDN)—— 用户复制消息链接时常误粘
33+
# 2. 贴纸 / GIF / meme 聚合站 —— Discord 内置贴纸面板会发 tenor/klipy/giphy
34+
# 链接出来,message.content 里就是裸 URL。这些不是"分享资源",不该入库
35+
# 静默忽略,不回复不提交,像 bot 没看到一样。
3436
_SKIP_HOSTS = frozenset({
35-
# 主站
37+
# Discord 主站
3638
"discord.com",
3739
"www.discord.com",
3840
"canary.discord.com",
3941
"ptb.discord.com",
40-
# 邀请短链
42+
# Discord 邀请短链
4143
"discord.gg",
42-
# 附件 / CDN
44+
# Discord 附件 / CDN
4345
"discordapp.com",
4446
"cdn.discordapp.com",
4547
"media.discordapp.net",
48+
# 贴纸 / GIF 聚合(Discord 贴纸面板默认走这些)
49+
"tenor.com",
50+
"media.tenor.com",
51+
"c.tenor.com",
52+
"giphy.com",
53+
"media.giphy.com",
54+
"media0.giphy.com",
55+
"media1.giphy.com",
56+
"media2.giphy.com",
57+
"media3.giphy.com",
58+
"media4.giphy.com",
59+
"klipy.com",
60+
"media.klipy.com",
4661
})
4762

63+
# 兜底:只指向静态媒体文件的 URL(路径以这些扩展名结尾)一律跳过——常见于
64+
# WeChat / 各种图床的裸图片链接,非分享资源。把扩展名匹配做在 path 上避免误伤
65+
# 带 query 的正常链接(query 里出现 .jpg 不算)。
66+
_MEDIA_EXTENSIONS = (
67+
".gif",
68+
".png",
69+
".jpg",
70+
".jpeg",
71+
".webp",
72+
".bmp",
73+
".svg",
74+
".ico",
75+
".mp4",
76+
".webm",
77+
".mov",
78+
".m4v",
79+
".mp3",
80+
".wav",
81+
".ogg",
82+
".flac",
83+
)
84+
4885

4986
def _should_skip(url: str) -> bool:
50-
"""URL 是否属于需要跳过的源(当前只屏蔽 Discord 自身域名)。"""
87+
"""URL 是否属于需要跳过的源Discord 域、贴纸聚合、或裸媒体文件。"""
5188
try:
52-
host = urlparse(url).netloc.lower().split(":")[0]
89+
parsed = urlparse(url)
5390
except Exception:
5491
return False
55-
return host in _SKIP_HOSTS
92+
host = parsed.netloc.lower().split(":")[0]
93+
if host in _SKIP_HOSTS:
94+
return True
95+
# path 走小写匹配,跟 query 解耦:?foo=bar.jpg 不会误命中
96+
return parsed.path.lower().endswith(_MEDIA_EXTENSIONS)
5697

5798
# 轮询最终状态的参数:每 2s 查一次,最多 30s
5899
_POLL_INTERVAL_SEC = 2.0

tests/test_listener_skip.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,59 @@ def test_should_skip_discord_urls(url: str) -> None:
3131
assert _should_skip(url) is True
3232

3333

34+
@pytest.mark.parametrize(
35+
"url",
36+
[
37+
# 这次实事故的 klipy GIF
38+
"https://klipy.com/gifs/hello-8126--k01KQ1SBY07FP9N8QRABJGVNGQC",
39+
# Tenor(Discord 贴纸面板默认)
40+
"https://tenor.com/view/cat-cute-gif-1234567",
41+
"https://media.tenor.com/AbCdEfGhIj/cat.gif",
42+
# Giphy(也常见)
43+
"https://giphy.com/gifs/cat-cute-AbCdEfGhIj",
44+
"https://media2.giphy.com/media/AbCdEfGhIj/giphy.gif",
45+
# Klipy CDN
46+
"https://media.klipy.com/some.gif",
47+
],
48+
)
49+
def test_should_skip_sticker_gif_aggregators(url: str) -> None:
50+
assert _should_skip(url) is True
51+
52+
53+
@pytest.mark.parametrize(
54+
"url",
55+
[
56+
# 裸图片(WeChat 图床、随便哪个 host 的图片直链)
57+
"https://mmbiz.qpic.cn/mmbiz_jpg/abc/640.jpg",
58+
"https://example.com/path/photo.PNG",
59+
"https://i.example.com/cat.gif",
60+
"https://example.com/foo.webp",
61+
# 视频/音频直链
62+
"https://example.com/clip.mp4",
63+
"https://example.com/audio.mp3",
64+
# SVG(即便 host 不在黑名单也拦,配合服务端 SVG 上传黑名单)
65+
"https://example.com/icon.svg",
66+
],
67+
)
68+
def test_should_skip_bare_media_files(url: str) -> None:
69+
assert _should_skip(url) is True
70+
71+
72+
@pytest.mark.parametrize(
73+
"url",
74+
[
75+
# path 不带媒体扩展,但 query 里出现 .jpg —— 不该误命中
76+
"https://example.com/api?file=foo.jpg",
77+
# 微信公众号文章 URL(典型分享)
78+
"https://mp.weixin.qq.com/s/abc",
79+
# 小红书帖子(path 没扩展名)
80+
"https://www.xiaohongshu.com/explore/abc123",
81+
],
82+
)
83+
def test_should_not_skip_normal_articles_with_media_query(url: str) -> None:
84+
assert _should_skip(url) is False
85+
86+
3487
@pytest.mark.parametrize(
3588
"url",
3689
[

0 commit comments

Comments
 (0)