fix: ban rule-based spam and add project icon

This commit is contained in:
ngfchl
2026-07-07 13:04:16 +08:00
parent 3f4af40d0d
commit 2b38fc91bd
10 changed files with 412 additions and 9 deletions
+1
View File
@@ -3,6 +3,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
<title>TG Spam Guard</title>
</head>
<body>
+24
View File
@@ -0,0 +1,24 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" role="img" aria-label="TG Spam Guard">
<defs>
<linearGradient id="bg" x1="18" y1="12" x2="112" y2="116" gradientUnits="userSpaceOnUse">
<stop stop-color="#4f46e5"/>
<stop offset="0.52" stop-color="#0ea5e9"/>
<stop offset="1" stop-color="#10b981"/>
</linearGradient>
<linearGradient id="shield" x1="36" y1="24" x2="92" y2="104" gradientUnits="userSpaceOnUse">
<stop stop-color="#ffffff"/>
<stop offset="1" stop-color="#dbeafe"/>
</linearGradient>
<filter id="shadow" x="-30%" y="-30%" width="160%" height="160%">
<feDropShadow dx="0" dy="8" stdDeviation="8" flood-color="#0f172a" flood-opacity="0.22"/>
</filter>
</defs>
<rect width="128" height="128" rx="30" fill="url(#bg)"/>
<circle cx="100" cy="28" r="18" fill="#fff" opacity="0.16"/>
<circle cx="24" cy="102" r="24" fill="#fff" opacity="0.12"/>
<path filter="url(#shadow)" d="M64 18 98 31v28c0 24.8-13.9 43.8-34 52-20.1-8.2-34-27.2-34-52V31l34-13Z" fill="url(#shield)"/>
<path d="M64 28 89 38v22c0 18.9-9.6 33.7-25 41-15.4-7.3-25-22.1-25-41V38l25-10Z" fill="#eef6ff" stroke="#bfdbfe" stroke-width="2"/>
<path d="M37 59 86 39c3.1-1.3 5.9 2 4.2 4.9L76.7 68.2a7 7 0 0 1-4.2 3.3L60 75.2l-7.5 12.1c-1.6 2.6-5.6 1.5-5.6-1.6V78l-12.2-4.1c-3.4-1.1-3.7-5.9-.4-7.3L37 59Z" fill="#0ea5e9"/>
<path d="M54.6 74.5 74.8 52.2 48.7 68.7l5.9 5.8Z" fill="#ffffff" opacity="0.92"/>
<path d="M84 82.5 93.5 92m0-9.5L84 92" stroke="#ef4444" stroke-width="7" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

+6 -3
View File
@@ -268,9 +268,12 @@ onBeforeUnmount(() => {
<SConfigProvider :theme="{ theme: { primary: 'indigo' }, size: 'sm' }">
<main class="page">
<header class="topbar">
<div>
<h1>🛡 TG Spam Guard</h1>
<p>Telegram 群组安全管理面板</p>
<div class="brand">
<img class="brand-icon" src="/icon.svg" alt="TG Spam Guard" />
<div>
<h1>TG Spam Guard</h1>
<p>Telegram 群组安全管理面板</p>
</div>
</div>
<div class="top-actions">
<select v-model="selectedChatId" class="form-select" @change="reloadByGroup"><option :value="null">全部群组</option><option v-for="g in groups" :key="g.chat_id" :value="g.chat_id">{{ g.label || g.name }}</option></select>
+2
View File
@@ -8,6 +8,8 @@ body { margin: 0; min-height: 100vh; background: linear-gradient(135deg, #eef2ff
button { font-family: inherit; }
.page { min-height: 100vh; padding: 14px; padding-bottom: 82px; }
.topbar { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 14px 16px; margin-bottom: 12px; border: 1px solid rgba(148,163,184,.28); border-radius: 18px; background: rgba(255,255,255,.86); box-shadow: 0 18px 40px rgba(15,23,42,.08); backdrop-filter: blur(12px); }
.brand { display: flex; align-items: center; gap: 10px; }
.brand-icon { width: 42px; height: 42px; border-radius: 12px; box-shadow: 0 10px 24px rgba(15,23,42,.16); }
.topbar h1 { margin: 0; font-size: 22px; line-height: 1.1; }
.topbar p { margin: 4px 0 0; color: #64748b; font-size: 12px; }
.top-actions { display: flex; align-items: center; justify-content: flex-end; gap: 8px; flex-wrap: wrap; }
+20 -3
View File
@@ -90,10 +90,20 @@ async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE):
"reason": reason,
}))
# 删消息
# 删消息,并把记录标成已删除,避免前端继续显示“删除/删封”按钮
try:
await msg.delete()
message_record.manually_deleted = True
message_record.deleted_at = datetime.now()
message_record.delete_reason = "auto_spam_delete"
await message_record.save()
await Action.create(action="DELETE", chat_id=update.effective_chat.id, user_id=user.id, username=username, details={"reason": reason, "text": text[:120]})
await broadcast_sse("chat_delete", json_safe({
"id": message_record.id,
"msg_id": msg.message_id,
"chat_id": update.effective_chat.id,
"deleted_at": message_record.deleted_at,
}))
except Exception as e:
logger.error(f"❌ 删除消息失败: {e}")
@@ -135,10 +145,17 @@ async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE):
}))
logger.info(f"🔨 已封禁 [{username}] {config.BAN_DURATION_MINUTES} 分钟")
except Exception as e:
logger.error(f"❌ 封禁失败: {e}")
await Action.create(
action="BAN_FAILED",
chat_id=update.effective_chat.id,
user_id=user.id,
username=username,
details={"reason": reason, "error": f"{type(e).__name__}: {e}"},
)
logger.error(f"❌ 封禁失败 [{username or user.id}]: {type(e).__name__}: {e}")
# 标记为垃圾
await Message.filter(msg_id=msg.message_id).update(is_spam=True, spam_reason=reason)
await Message.filter(chat_id=update.effective_chat.id, msg_id=msg.message_id).update(is_spam=True, spam_reason=reason)
def register_spam(app):
+3 -1
View File
@@ -118,7 +118,9 @@ SCORE_RULES = [
# 营销常见词
Rule("免费福利", r"(?:免费领|免费送|限时领|点击领取|扫码领取|福利|红包|返现|佣金|返佣)", 40),
Rule("代理加盟", r"(?:代理|加盟|招商|推广返|分润|招募|招代理|合作共赢)", 45),
Rule("豪车炫富", r"(?:换|开|提).{0,6}(?:迈巴赫|宝马|奔驰|保时捷|法拉利|兰博基尼)", 55),
Rule("豪车炫富", r"(?:换|开|提).{0,8}(?:迈巴赫|宝马|奔驰|保时捷|法拉利|兰博基尼)", 65),
Rule("月入收益承诺", r"(?:一个月|每月|月入|月赚|下个月).{0,12}(?:\d{1,4}|几万|几十万).{0,4}(?:w|W|万|个w|个W)", 75),
Rule("安全收益承诺", r"(?:安全|稳赚|保本|不要本钱|不要本|零成本).{0,18}(?:赚|收益|月入|月赚|开奔驰|提车)", 70),
]
# 组合加权:成熟广告识别关键在“组合意图”,不是单词命中
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+24
View File
@@ -0,0 +1,24 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" role="img" aria-label="TG Spam Guard">
<defs>
<linearGradient id="bg" x1="18" y1="12" x2="112" y2="116" gradientUnits="userSpaceOnUse">
<stop stop-color="#4f46e5"/>
<stop offset="0.52" stop-color="#0ea5e9"/>
<stop offset="1" stop-color="#10b981"/>
</linearGradient>
<linearGradient id="shield" x1="36" y1="24" x2="92" y2="104" gradientUnits="userSpaceOnUse">
<stop stop-color="#ffffff"/>
<stop offset="1" stop-color="#dbeafe"/>
</linearGradient>
<filter id="shadow" x="-30%" y="-30%" width="160%" height="160%">
<feDropShadow dx="0" dy="8" stdDeviation="8" flood-color="#0f172a" flood-opacity="0.22"/>
</filter>
</defs>
<rect width="128" height="128" rx="30" fill="url(#bg)"/>
<circle cx="100" cy="28" r="18" fill="#fff" opacity="0.16"/>
<circle cx="24" cy="102" r="24" fill="#fff" opacity="0.12"/>
<path filter="url(#shadow)" d="M64 18 98 31v28c0 24.8-13.9 43.8-34 52-20.1-8.2-34-27.2-34-52V31l34-13Z" fill="url(#shield)"/>
<path d="M64 28 89 38v22c0 18.9-9.6 33.7-25 41-15.4-7.3-25-22.1-25-41V38l25-10Z" fill="#eef6ff" stroke="#bfdbfe" stroke-width="2"/>
<path d="M37 59 86 39c3.1-1.3 5.9 2 4.2 4.9L76.7 68.2a7 7 0 0 1-4.2 3.3L60 75.2l-7.5 12.1c-1.6 2.6-5.6 1.5-5.6-1.6V78l-12.2-4.1c-3.4-1.1-3.7-5.9-.4-7.3L37 59Z" fill="#0ea5e9"/>
<path d="M54.6 74.5 74.8 52.2 48.7 68.7l5.9 5.8Z" fill="#ffffff" opacity="0.92"/>
<path d="M84 82.5 93.5 92m0-9.5L84 92" stroke="#ef4444" stroke-width="7" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

+3 -2
View File
@@ -3,9 +3,10 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" type="image/svg+xml" href="/static/icon.svg" />
<title>TG Spam Guard</title>
<script type="module" crossorigin src="/static/assets/index-Cwa1Xd8Y.js"></script>
<link rel="stylesheet" crossorigin href="/static/assets/index-CVeOFNWl.css">
<script type="module" crossorigin src="/static/assets/index-B7wPhEjS.js"></script>
<link rel="stylesheet" crossorigin href="/static/assets/index-zEILxixw.css">
</head>
<body>
<div id="app"></div>