feat: add whitelist moderation flow
This commit is contained in:
@@ -47,6 +47,20 @@ async def ensure_runtime_schema():
|
|||||||
ALTER TABLE verify_queue ADD COLUMN IF NOT EXISTS chat_id BIGINT NOT NULL DEFAULT 0;
|
ALTER TABLE verify_queue ADD COLUMN IF NOT EXISTS chat_id BIGINT NOT NULL DEFAULT 0;
|
||||||
ALTER TABLE warnings ADD COLUMN IF NOT EXISTS chat_id BIGINT NOT NULL DEFAULT 0;
|
ALTER TABLE warnings ADD COLUMN IF NOT EXISTS chat_id BIGINT NOT NULL DEFAULT 0;
|
||||||
ALTER TABLE game_scores ADD COLUMN IF NOT EXISTS chat_id BIGINT NOT NULL DEFAULT 0;
|
ALTER TABLE game_scores ADD COLUMN IF NOT EXISTS chat_id BIGINT NOT NULL DEFAULT 0;
|
||||||
|
CREATE TABLE IF NOT EXISTS whitelists (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
chat_id BIGINT NOT NULL DEFAULT 0,
|
||||||
|
user_id BIGINT NOT NULL,
|
||||||
|
username VARCHAR(255) NULL,
|
||||||
|
first_name VARCHAR(255) NULL,
|
||||||
|
reason VARCHAR(500) NULL,
|
||||||
|
operator_id BIGINT NULL,
|
||||||
|
enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
||||||
|
UNIQUE(chat_id, user_id)
|
||||||
|
);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_whitelists_user_enabled ON whitelists(user_id, enabled);
|
||||||
CREATE TABLE IF NOT EXISTS shop_items (
|
CREATE TABLE IF NOT EXISTS shop_items (
|
||||||
id BIGSERIAL PRIMARY KEY,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
|||||||
@@ -62,6 +62,23 @@ class Action(TimestampMixin, Model):
|
|||||||
indexes = (("action", "created_at"), ("user_id", "created_at"), ("operator_id", "created_at"))
|
indexes = (("action", "created_at"), ("user_id", "created_at"), ("operator_id", "created_at"))
|
||||||
|
|
||||||
|
|
||||||
|
class Whitelist(TimestampMixin, Model):
|
||||||
|
"""白名单用户:命中垃圾规则时只删除消息,不封禁账号。"""
|
||||||
|
id = fields.BigIntField(pk=True, generated=True)
|
||||||
|
chat_id = fields.BigIntField(default=0)
|
||||||
|
user_id = fields.BigIntField()
|
||||||
|
username = fields.CharField(max_length=255, null=True)
|
||||||
|
first_name = fields.CharField(max_length=255, null=True)
|
||||||
|
reason = fields.CharField(max_length=500, null=True)
|
||||||
|
operator_id = fields.BigIntField(null=True)
|
||||||
|
enabled = fields.BooleanField(default=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
table = "whitelists"
|
||||||
|
indexes = (("chat_id", "user_id"), ("user_id", "enabled"))
|
||||||
|
unique_together = (("chat_id", "user_id"),)
|
||||||
|
|
||||||
|
|
||||||
class VerifyQueue(TimestampMixin, Model):
|
class VerifyQueue(TimestampMixin, Model):
|
||||||
"""入群验证队列"""
|
"""入群验证队列"""
|
||||||
id = fields.BigIntField(pk=True, generated=True)
|
id = fields.BigIntField(pk=True, generated=True)
|
||||||
|
|||||||
+19
-1
@@ -556,7 +556,7 @@ function openChatContextMenu(event: MouseEvent, item: ChatItem) {
|
|||||||
chatContextMenu.item = item;
|
chatContextMenu.item = item;
|
||||||
chatContextMenu.open = true;
|
chatContextMenu.open = true;
|
||||||
const menuWidth = 188;
|
const menuWidth = 188;
|
||||||
const menuHeight = item.deleted ? 152 : 192;
|
const menuHeight = item.deleted ? 184 : 224;
|
||||||
chatContextMenu.x = Math.min(event.clientX, window.innerWidth - menuWidth - 8);
|
chatContextMenu.x = Math.min(event.clientX, window.innerWidth - menuWidth - 8);
|
||||||
chatContextMenu.y = Math.min(event.clientY, window.innerHeight - menuHeight - 8);
|
chatContextMenu.y = Math.min(event.clientY, window.innerHeight - menuHeight - 8);
|
||||||
}
|
}
|
||||||
@@ -569,6 +569,23 @@ async function contextAddToRules() {
|
|||||||
closeChatContextMenu();
|
closeChatContextMenu();
|
||||||
if (item) await addChatMessageToRules(item);
|
if (item) await addChatMessageToRules(item);
|
||||||
}
|
}
|
||||||
|
async function contextAddWhitelist() {
|
||||||
|
const item = chatContextMenu.item;
|
||||||
|
closeChatContextMenu();
|
||||||
|
if (!item?.user_id) return showToast('当前消息缺少用户 ID,无法加入白名单');
|
||||||
|
if (!await askConfirm('加入白名单', `确认将 @${item.username || item.first_name || item.user_id} 加入白名单?
|
||||||
|
白名单用户命中垃圾规则时只删除消息,不封禁账号。`, { confirmText: '加入白名单' })) return;
|
||||||
|
try {
|
||||||
|
await fetchJson(`/api/messages/${item.id}/whitelist`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ reason: 'Web 右键加入白名单' })
|
||||||
|
});
|
||||||
|
showToast('已加入白名单:后续只删消息,不封禁账号');
|
||||||
|
} catch (e: any) {
|
||||||
|
showToast(`加入白名单失败:${e.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
async function contextDeleteMessage(ban = false) {
|
async function contextDeleteMessage(ban = false) {
|
||||||
const item = chatContextMenu.item;
|
const item = chatContextMenu.item;
|
||||||
closeChatContextMenu();
|
closeChatContextMenu();
|
||||||
@@ -885,6 +902,7 @@ onBeforeUnmount(() => {
|
|||||||
<button class="danger" :disabled="!!chatContextMenu.item?.deleted" @click="contextDeleteMessage(false)">🗑️ 删除</button>
|
<button class="danger" :disabled="!!chatContextMenu.item?.deleted" @click="contextDeleteMessage(false)">🗑️ 删除</button>
|
||||||
<button class="warning" :disabled="!chatContextMenu.item?.user_id" @click="contextDeleteAndBanMessage">⛔ 删除并封禁</button>
|
<button class="warning" :disabled="!chatContextMenu.item?.user_id" @click="contextDeleteAndBanMessage">⛔ 删除并封禁</button>
|
||||||
<button class="warning" :disabled="!chatContextMenu.item?.user_id" @click="contextBanMessage">🚫 封印用户</button>
|
<button class="warning" :disabled="!chatContextMenu.item?.user_id" @click="contextBanMessage">🚫 封印用户</button>
|
||||||
|
<button :disabled="!chatContextMenu.item?.user_id" @click="contextAddWhitelist">🛡️ 加入白名单</button>
|
||||||
<button @click="contextAddToRules">📥 写入拦截库</button>
|
<button @click="contextAddToRules">📥 写入拦截库</button>
|
||||||
<button @click="contextCopyText">📋 复制内容</button>
|
<button @click="contextCopyText">📋 复制内容</button>
|
||||||
<button @click="contextReplyMessage">↩️ 回复</button>
|
<button @click="contextReplyMessage">↩️ 回复</button>
|
||||||
|
|||||||
+6
-7
@@ -219,7 +219,7 @@ async def cmd_mute(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|||||||
asyncio.create_task(auto_delete(reply))
|
asyncio.create_task(auto_delete(reply))
|
||||||
|
|
||||||
|
|
||||||
async def require_reply_shortcut(update: Update, command: str, usage: str) -> bool:
|
async def require_reply_shortcut(update: Update, context: ContextTypes.DEFAULT_TYPE, usage: str) -> bool:
|
||||||
"""引用消息快捷管理命令必须回复目标消息。"""
|
"""引用消息快捷管理命令必须回复目标消息。"""
|
||||||
if update.message and update.message.reply_to_message:
|
if update.message and update.message.reply_to_message:
|
||||||
return True
|
return True
|
||||||
@@ -233,7 +233,7 @@ async def cmd_dban(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|||||||
"""删除被引用消息并封禁用户。"""
|
"""删除被引用消息并封禁用户。"""
|
||||||
if not await require_admin(update, context):
|
if not await require_admin(update, context):
|
||||||
return
|
return
|
||||||
if not await require_reply_shortcut(update, "dban", "/dban"):
|
if not await require_reply_shortcut(update, context, "/dban"):
|
||||||
return
|
return
|
||||||
await cmd_ban(update, context)
|
await cmd_ban(update, context)
|
||||||
|
|
||||||
@@ -242,7 +242,7 @@ async def cmd_dmute(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|||||||
"""删除被引用消息并禁言用户。"""
|
"""删除被引用消息并禁言用户。"""
|
||||||
if not await require_admin(update, context):
|
if not await require_admin(update, context):
|
||||||
return
|
return
|
||||||
if not await require_reply_shortcut(update, "dmute", "/dmute [分钟]"):
|
if not await require_reply_shortcut(update, context, "/dmute [分钟]"):
|
||||||
return
|
return
|
||||||
await cmd_mute(update, context)
|
await cmd_mute(update, context)
|
||||||
|
|
||||||
@@ -251,7 +251,7 @@ async def cmd_dwarn(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|||||||
"""删除被引用消息并警告用户。"""
|
"""删除被引用消息并警告用户。"""
|
||||||
if not await require_admin(update, context):
|
if not await require_admin(update, context):
|
||||||
return
|
return
|
||||||
if not await require_reply_shortcut(update, "dwarn", "/dwarn [原因]"):
|
if not await require_reply_shortcut(update, context, "/dwarn [原因]"):
|
||||||
return
|
return
|
||||||
await cmd_warn(update, context)
|
await cmd_warn(update, context)
|
||||||
|
|
||||||
@@ -306,11 +306,10 @@ async def cmd_help(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|||||||
"✅ /checkin - 每日签到\n"
|
"✅ /checkin - 每日签到\n"
|
||||||
"🎮 /trivia - 知识答题\n"
|
"🎮 /trivia - 知识答题\n"
|
||||||
"🏆 /rank - 查看积分排行\n"
|
"🏆 /rank - 查看积分排行\n"
|
||||||
"🎁 /lottery - 发起群内抽奖,进入私聊设置奖品和条件\n"
|
|
||||||
" 私聊格式:奖品 | 参与条件 | 结束条件\n"
|
|
||||||
" 示例:月卡一张 | all | people:3\n"
|
|
||||||
"🔗 /bind - 绑定账号\n\n"
|
"🔗 /bind - 绑定账号\n\n"
|
||||||
"管理员操作:\n"
|
"管理员操作:\n"
|
||||||
|
"🎁 /lottery - 发起群内抽奖,进入私聊设置奖品和条件\n"
|
||||||
|
"🎛️ /panel - 打开管理操作面板\n"
|
||||||
"🌙 /night on/off - 开启/关闭夜间模式\n"
|
"🌙 /night on/off - 开启/关闭夜间模式\n"
|
||||||
"🔨 /ban 用户ID/@历史用户名 - 封禁用户\n"
|
"🔨 /ban 用户ID/@历史用户名 - 封禁用户\n"
|
||||||
"🔇 /mute 用户ID/@历史用户名 [分钟] - 禁言用户,默认 10 分钟\n"
|
"🔇 /mute 用户ID/@历史用户名 [分钟] - 禁言用户,默认 10 分钟\n"
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import logging
|
|||||||
|
|
||||||
import config
|
import config
|
||||||
from services.message_utils import record_bot_message
|
from services.message_utils import record_bot_message
|
||||||
|
from services.admin_utils import require_admin, is_admin
|
||||||
|
|
||||||
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
|
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
|
||||||
from telegram.ext import ApplicationHandlerStop, CallbackQueryHandler, ContextTypes, CommandHandler, MessageHandler, filters
|
from telegram.ext import ApplicationHandlerStop, CallbackQueryHandler, ContextTypes, CommandHandler, MessageHandler, filters
|
||||||
@@ -285,6 +286,8 @@ async def cmd_buy(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|||||||
|
|
||||||
|
|
||||||
async def cmd_panel(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
async def cmd_panel(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
|
if not await require_admin(update, context):
|
||||||
|
return
|
||||||
reply = await update.message.reply_text(
|
reply = await update.message.reply_text(
|
||||||
"🎛️ 操作面板\n请选择要执行的操作:\n\n⏱️ 60 秒无操作后自动消失。",
|
"🎛️ 操作面板\n请选择要执行的操作:\n\n⏱️ 60 秒无操作后自动消失。",
|
||||||
reply_markup=panel_keyboard(),
|
reply_markup=panel_keyboard(),
|
||||||
@@ -294,6 +297,9 @@ async def cmd_panel(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|||||||
|
|
||||||
async def handle_panel_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
async def handle_panel_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
query = update.callback_query
|
query = update.callback_query
|
||||||
|
if not await is_admin(update, context):
|
||||||
|
await query.answer("只有管理员可以使用操作面板", show_alert=True)
|
||||||
|
return
|
||||||
await query.answer()
|
await query.answer()
|
||||||
user = query.from_user
|
user = query.from_user
|
||||||
action = query.data.split(":", 1)[1]
|
action = query.data.split(":", 1)[1]
|
||||||
|
|||||||
+8
-3
@@ -7,7 +7,7 @@ from telegram import Update
|
|||||||
from telegram.ext import ContextTypes, MessageHandler, filters
|
from telegram.ext import ContextTypes, MessageHandler, filters
|
||||||
|
|
||||||
import config
|
import config
|
||||||
from db.models import Message, Ban, Action
|
from db.models import Message, Ban, Action, Whitelist
|
||||||
from services.spam_detector import detect
|
from services.spam_detector import detect
|
||||||
from web.api import broadcast_sse, json_safe
|
from web.api import broadcast_sse, json_safe
|
||||||
|
|
||||||
@@ -107,10 +107,15 @@ async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"❌ 删除消息失败: {e}")
|
logger.error(f"❌ 删除消息失败: {e}")
|
||||||
|
|
||||||
# 管理员可用于测试规则:删消息但不封禁,避免把群主/管理员踢掉
|
# 管理员/白名单:只删消息,不封禁,避免误封可信用户
|
||||||
if user.id in config.ADMIN_USER_IDS:
|
if user.id in config.ADMIN_USER_IDS:
|
||||||
await Action.create(action="SPAM_ADMIN_TEST", chat_id=update.effective_chat.id, user_id=user.id, username=username, details={"reason": reason})
|
await Action.create(action="SPAM_ADMIN_TEST", chat_id=update.effective_chat.id, user_id=user.id, username=username, details={"reason": reason})
|
||||||
logger.warning(f"🧪 管理员垃圾规则测试命中,不封禁: [{username}] {reason}")
|
logger.warning(f"🧪 管理员垃圾规则测试命中,只删不封: [{username}] {reason}")
|
||||||
|
return
|
||||||
|
whitelist = await Whitelist.get_or_none(chat_id=update.effective_chat.id, user_id=user.id, enabled=True)
|
||||||
|
if whitelist:
|
||||||
|
await Action.create(action="SPAM_WHITELIST_DELETE_ONLY", chat_id=update.effective_chat.id, user_id=user.id, username=username, details={"reason": reason, "whitelist_id": whitelist.id})
|
||||||
|
logger.warning(f"🛡️ 白名单用户命中垃圾规则,只删不封: [{username}] {reason}")
|
||||||
return
|
return
|
||||||
|
|
||||||
# 封禁
|
# 封禁
|
||||||
|
|||||||
@@ -130,14 +130,12 @@ async def start_tg_bot():
|
|||||||
BotCommand("stats", "📊 今日统计"),
|
BotCommand("stats", "📊 今日统计"),
|
||||||
BotCommand("checkin", "✅ 每日签到"),
|
BotCommand("checkin", "✅ 每日签到"),
|
||||||
BotCommand("trivia", "🎮 知识答题"),
|
BotCommand("trivia", "🎮 知识答题"),
|
||||||
BotCommand("lottery", "🎁 发起群内抽奖"),
|
|
||||||
BotCommand("shop", "🛒 积分商城"),
|
BotCommand("shop", "🛒 积分商城"),
|
||||||
BotCommand("panel", "🎛️ 操作面板"),
|
|
||||||
BotCommand("bind", "🔗 绑定账号"),
|
BotCommand("bind", "🔗 绑定账号"),
|
||||||
BotCommand("me", "👤 查看授权信息"),
|
BotCommand("me", "👤 查看授权信息"),
|
||||||
BotCommand("dban", "🗑️ 删除并封禁"),
|
BotCommand("dban", "🛡️ 管理:删封"),
|
||||||
BotCommand("dmute", "🔇 删除并禁言"),
|
BotCommand("dmute", "🛡️ 管理:删禁言"),
|
||||||
BotCommand("dwarn", "⚠️ 删除并警告"),
|
BotCommand("dwarn", "🛡️ 管理:删警告"),
|
||||||
BotCommand("help", "📖 帮助"),
|
BotCommand("help", "📖 帮助"),
|
||||||
]
|
]
|
||||||
await tg_app.bot.delete_my_commands()
|
await tg_app.bot.delete_my_commands()
|
||||||
|
|||||||
+40
-1
@@ -21,7 +21,7 @@ from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse
|
|||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
from db.models import Action, Ban, GameScore, Message, LotteryEvent, LotteryParticipant, ShopItem, ShopTransaction
|
from db.models import Action, Ban, GameScore, Message, LotteryEvent, LotteryParticipant, ShopItem, ShopTransaction, Whitelist
|
||||||
from config import WEB_AUTH_ENABLED, WEB_AUTH_PASSWORD, WEB_AUTH_USERNAME, WEB_SESSION_SECRET, WEB_TRUST_SAME_SUBNET
|
from config import WEB_AUTH_ENABLED, WEB_AUTH_PASSWORD, WEB_AUTH_USERNAME, WEB_SESSION_SECRET, WEB_TRUST_SAME_SUBNET
|
||||||
from services.stats_service import get_daily_stats
|
from services.stats_service import get_daily_stats
|
||||||
from services.time_utils import format_dt, from_timestamp, now_tz
|
from services.time_utils import format_dt, from_timestamp, now_tz
|
||||||
@@ -924,6 +924,8 @@ async def api_ban_message_user(message_id: int, data: BanPayload | None = None):
|
|||||||
return JSONResponse({"ok": False, "error": "消息不存在"}, status_code=404)
|
return JSONResponse({"ok": False, "error": "消息不存在"}, status_code=404)
|
||||||
if not rec.chat_id or not rec.user_id:
|
if not rec.chat_id or not rec.user_id:
|
||||||
return JSONResponse({"ok": False, "error": "缺少 chat_id 或 user_id,无法封禁"}, status_code=400)
|
return JSONResponse({"ok": False, "error": "缺少 chat_id 或 user_id,无法封禁"}, status_code=400)
|
||||||
|
if await Whitelist.get_or_none(chat_id=rec.chat_id, user_id=rec.user_id, enabled=True):
|
||||||
|
return JSONResponse({"ok": False, "error": "该用户在白名单中,不允许封禁;如需处理请先移出白名单"}, status_code=409)
|
||||||
opts = build_ban_options((data.duration_minutes if data else 0))
|
opts = build_ban_options((data.duration_minutes if data else 0))
|
||||||
try:
|
try:
|
||||||
if opts["until_date"]:
|
if opts["until_date"]:
|
||||||
@@ -960,6 +962,39 @@ async def api_ban_message_user(message_id: int, data: BanPayload | None = None):
|
|||||||
return JSONResponse({"ok": False, "error": f"封禁失败: {type(e).__name__}: {e}"}, status_code=502)
|
return JSONResponse({"ok": False, "error": f"封禁失败: {type(e).__name__}: {e}"}, status_code=502)
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/api/messages/{message_id}/whitelist")
|
||||||
|
async def api_add_message_user_to_whitelist(message_id: int, request: Request):
|
||||||
|
"""把聊天记录对应用户加入白名单。白名单用户自动检测只删消息不封号。"""
|
||||||
|
rec = await Message.get_or_none(id=message_id)
|
||||||
|
if not rec:
|
||||||
|
return JSONResponse({"ok": False, "error": "消息不存在"}, status_code=404)
|
||||||
|
if not rec.chat_id or not rec.user_id:
|
||||||
|
return JSONResponse({"ok": False, "error": "缺少 chat_id 或 user_id,无法加入白名单"}, status_code=400)
|
||||||
|
data = await safe_json(request)
|
||||||
|
reason = str(data.get("reason") or "Web 右键加入白名单")[:500]
|
||||||
|
item = await Whitelist.get_or_none(chat_id=rec.chat_id, user_id=rec.user_id)
|
||||||
|
if item:
|
||||||
|
item.enabled = True
|
||||||
|
item.username = rec.username
|
||||||
|
item.first_name = rec.first_name
|
||||||
|
item.reason = reason
|
||||||
|
item.updated_at = now_tz()
|
||||||
|
await item.save()
|
||||||
|
else:
|
||||||
|
item = await Whitelist.create(chat_id=rec.chat_id, user_id=rec.user_id, username=rec.username, first_name=rec.first_name, reason=reason)
|
||||||
|
await Action.create(action="WHITELIST_ADD", chat_id=rec.chat_id, user_id=rec.user_id, username=rec.username, details={"message_id": rec.id, "reason": reason})
|
||||||
|
return JSONResponse(json_safe({"ok": True, "id": item.id, "chat_id": item.chat_id, "user_id": item.user_id, "username": item.username, "first_name": item.first_name, "reason": item.reason, "enabled": item.enabled}))
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/api/whitelist")
|
||||||
|
async def api_list_whitelist(chat_id: int | None = None, limit: int = Query(default=200, ge=1, le=1000)):
|
||||||
|
qs = Whitelist.filter(enabled=True)
|
||||||
|
if chat_id is not None:
|
||||||
|
qs = qs.filter(chat_id=chat_id)
|
||||||
|
rows = await qs.order_by("-updated_at").limit(limit).values()
|
||||||
|
return JSONResponse(json_safe(list(rows)))
|
||||||
|
|
||||||
|
|
||||||
@app.post("/api/chat/{message_id}/delete")
|
@app.post("/api/chat/{message_id}/delete")
|
||||||
@app.post("/api/messages/{message_id}/delete")
|
@app.post("/api/messages/{message_id}/delete")
|
||||||
async def api_delete_message(message_id: int, request: Request):
|
async def api_delete_message(message_id: int, request: Request):
|
||||||
@@ -998,6 +1033,10 @@ async def api_delete_message(message_id: int, request: Request):
|
|||||||
await rec.save()
|
await rec.save()
|
||||||
|
|
||||||
ban = None
|
ban = None
|
||||||
|
if ban_user and rec.user_id:
|
||||||
|
if await Whitelist.get_or_none(chat_id=rec.chat_id, user_id=rec.user_id, enabled=True):
|
||||||
|
await Action.create(action="DELETE_BAN_SKIPPED_WHITELIST", chat_id=rec.chat_id, user_id=rec.user_id, username=rec.username, details={"message_id": rec.id})
|
||||||
|
ban_user = False
|
||||||
if ban_user and rec.user_id:
|
if ban_user and rec.user_id:
|
||||||
try:
|
try:
|
||||||
if ban_opts["until_date"]:
|
if ban_opts["until_date"]:
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -5,7 +5,7 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/static/icon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/static/icon.svg" />
|
||||||
<title>TG Spam Guard</title>
|
<title>TG Spam Guard</title>
|
||||||
<script type="module" crossorigin src="/static/assets/index-CkIgY8f5.js"></script>
|
<script type="module" crossorigin src="/static/assets/index-X2_fZIhe.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/static/assets/index-B9Kyvn7u.css">
|
<link rel="stylesheet" crossorigin href="/static/assets/index-B9Kyvn7u.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
Reference in New Issue
Block a user