From 2a0ab87f4ec76f47089011b1f9b9125bf40beb1b Mon Sep 17 00:00:00 2001 From: Aethersailor <22260104+Aethersailor@users.noreply.github.com> Date: Sun, 1 Feb 2026 16:20:03 +0800 Subject: [PATCH 1/6] fix: avoid escaping inline code values in bot replies --- src/handlers/handler_manager.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/handlers/handler_manager.py b/src/handlers/handler_manager.py index c2508dc..ee9c9b0 100644 --- a/src/handlers/handler_manager.py +++ b/src/handlers/handler_manager.py @@ -1130,19 +1130,19 @@ async def _add_domain_to_github(self, query, user_id: int, description: str): _, remaining = self.check_user_add_limit(user_id) result_text = f"✅ **域名添加成功!**\n\n" - result_text += f"📍 **域名:** `{self.escape_markdown(target_domain)}`\n" + result_text += f"📍 **域名:** `{target_domain}`\n" result_text += f"👤 **提交者:** @{self.escape_markdown(username)}\n" if description: result_text += f"📝 **说明:** {self.escape_markdown(description)}\n" - result_text += f"📂 **文件路径:** `{self.escape_markdown(add_result['file_path'])}`\n" + result_text += f"📂 **文件路径:** `{add_result['file_path']}`\n" if add_result.get('commit_url'): result_text += f"🔗 **查看提交:** [点击查看]({add_result['commit_url']})\n" result_text += f"📝 **Commit ID:** `{add_result.get('commit_sha', '')[:8]}`\n" - result_text += f"💬 **提交信息:** `{self.escape_markdown(add_result['commit_message'])}`\n" + result_text += f"💬 **提交信息:** `{add_result['commit_message']}`\n" result_text += f"\n💡 本小时内还可添加 {remaining} 个域名" else: result_text = f"❌ **域名添加失败**\n\n" - result_text += f"📍 **域名:** `{self.escape_markdown(target_domain)}`\n" + result_text += f"📍 **域名:** `{target_domain}`\n" result_text += f"❌ **错误:** {self.escape_markdown(add_result.get('error', '未知错误'))}" keyboard = [ @@ -1195,19 +1195,19 @@ async def _add_domain_to_github_message(self, message, user_id: int, description _, remaining = self.check_user_add_limit(user_id) result_text = f"✅ **域名添加成功!**\n\n" - result_text += f"📍 **域名:** `{self.escape_markdown(target_domain)}`\n" + result_text += f"📍 **域名:** `{target_domain}`\n" result_text += f"👤 **提交者:** @{self.escape_markdown(username)}\n" if description: result_text += f"📝 **说明:** {self.escape_markdown(description)}\n" - result_text += f"📂 **文件路径:** `{self.escape_markdown(add_result['file_path'])}`\n" + result_text += f"📂 **文件路径:** `{add_result['file_path']}`\n" if add_result.get('commit_url'): result_text += f"🔗 **查看提交:** [点击查看]({add_result['commit_url']})\n" result_text += f"📝 **Commit ID:** `{add_result.get('commit_sha', '')[:8]}`\n" - result_text += f"💬 **提交信息:** `{self.escape_markdown(add_result['commit_message'])}`\n" + result_text += f"💬 **提交信息:** `{add_result['commit_message']}`\n" result_text += f"\n💡 本小时内还可添加 {remaining} 个域名" else: result_text = f"❌ **域名添加失败**\n\n" - result_text += f"📍 **域名:** `{self.escape_markdown(target_domain)}`\n" + result_text += f"📍 **域名:** `{target_domain}`\n" result_text += f"❌ **错误:** {self.escape_markdown(add_result.get('error', '未知错误'))}" keyboard = [ From faf8b073a3634ba67b36c8039a7b7e0e41aa991b Mon Sep 17 00:00:00 2001 From: Aethersailor <22260104+Aethersailor@users.noreply.github.com> Date: Sun, 1 Feb 2026 18:29:54 +0800 Subject: [PATCH 2/6] feat: add admin force-add flow for rejected domains --- src/config.py | 27 ++++++ src/handlers/group_handler.py | 16 +++- src/handlers/handler_manager.py | 146 ++++++++++++++++++++++++++++++++ src/services/github_service.py | 30 +++++-- 4 files changed, 210 insertions(+), 9 deletions(-) diff --git a/src/config.py b/src/config.py index 083aa8a..df8c931 100644 --- a/src/config.py +++ b/src/config.py @@ -47,6 +47,10 @@ def __init__(self): # 群组工作模式配置(允许机器人在这些群组中直接响应 @提及) # 支持逗号分隔的多个群组 ID,例如:-1001234567890,-1009876543210 self.ALLOWED_GROUP_IDS = self._parse_group_ids(os.getenv("ALLOWED_GROUP_IDS", "")) + + # 管理员配置(Telegram 用户 ID 列表) + # 支持逗号分隔的多个用户 ID,例如:123456789,987654321 + self.ADMIN_USER_IDS = self._parse_user_ids(os.getenv("ADMIN_USER_IDS", "")) # 数据源URL # 使用 Aethersailor GeoIP 数据库 @@ -120,6 +124,29 @@ def _parse_group_ids(self, ids_str: str) -> list: logger.warning(f"无效的 ALLOWED_GROUP_IDS: {raw_id}") return group_ids + def _parse_user_ids(self, ids_str: str) -> list: + """解析用户 ID 列表 + + Args: + ids_str: 逗号分隔的用户 ID 字符串 + + Returns: + 用户 ID 整数列表 + """ + if not ids_str.strip(): + return [] + + user_ids = [] + for raw_id in ids_str.split(","): + raw_id = raw_id.strip() + if not raw_id: + continue + try: + user_ids.append(int(raw_id)) + except ValueError: + logger.warning(f"无效的 ADMIN_USER_IDS: {raw_id}") + return user_ids + def _parse_required_group_id(self, group_id_raw: str) -> Optional[int]: """解析必需群组 ID""" if not group_id_raw: diff --git a/src/handlers/group_handler.py b/src/handlers/group_handler.py index 1ea8218..6b70af6 100644 --- a/src/handlers/group_handler.py +++ b/src/handlers/group_handler.py @@ -6,7 +6,7 @@ from typing import Optional from loguru import logger -from telegram import Update, Message +from telegram import Update, Message, InlineKeyboardButton, InlineKeyboardMarkup from telegram.ext import ContextTypes from ..config import Config @@ -220,6 +220,7 @@ async def _process_domain_request( result = await self.handler_manager.check_and_add_domain_auto(domain, username) # 根据结果回复 + reply_markup = None if result["action"] == "added": # 记录用户添加历史 self.handler_manager.record_user_add(user_id) @@ -241,13 +242,22 @@ async def _process_domain_request( result_text = f"❌ **无法添加域名**\n\n" result_text += f"📍 **域名:** `{domain}`\n" result_text += f"📋 {result['message']}" + if self.handler_manager.is_admin(user_id): + result_text += "\n\n🛡️ **管理员可使用权限按钮强制添加。**" + keyboard = [ + [InlineKeyboardButton( + "🛡️ 管理员权限添加", + callback_data=self.handler_manager.get_admin_force_add_callback(domain) + )] + ] + reply_markup = InlineKeyboardMarkup(keyboard) else: # error result_text = f"❌ **处理失败**\n\n" result_text += f"📍 **域名:** `{domain}`\n" result_text += f"❌ {result['message']}" - - await processing_msg.edit_text(result_text, parse_mode='Markdown') + + await processing_msg.edit_text(result_text, reply_markup=reply_markup, parse_mode='Markdown') except Exception as e: logger.error(f"处理域名请求失败: {e}") diff --git a/src/handlers/handler_manager.py b/src/handlers/handler_manager.py index ee9c9b0..f30b102 100644 --- a/src/handlers/handler_manager.py +++ b/src/handlers/handler_manager.py @@ -197,6 +197,14 @@ def record_user_add(self, user_id: int): """记录用户添加操作""" current_time = time.time() self.user_add_history[user_id].append(current_time) + + def is_admin(self, user_id: int) -> bool: + """检查是否管理员""" + return user_id in self.config.ADMIN_USER_IDS + + def get_admin_force_add_callback(self, domain: str) -> str: + """构建管理员权限添加的回调数据""" + return f"admin_force_add|{domain}" def validate_description(self, description: str) -> tuple[bool, str]: """验证域名说明 @@ -547,6 +555,8 @@ async def handle_callback(self, update: Update, context: ContextTypes.DEFAULT_TY await self._handle_confirm_add_callback(query, user_id, data) elif data == "skip_description": await self._handle_skip_description(query, user_id) + elif data.startswith("admin_force_add|"): + await self._handle_admin_force_add_callback(query, user_id, data) else: await query.edit_message_text("未知操作") @@ -943,6 +953,14 @@ async def _handle_add_domain_input(self, update: Update, domain_input: str, user elif self.domain_checker.should_reject(check_result): # 不符合条件,拒绝添加 result_text += "\n❌ **不符合添加条件,无法添加到直连规则。**" + if self.is_admin(user_id): + result_text += "\n🛡️ **管理员权限:** 可强制添加" + keyboard.append([ + InlineKeyboardButton( + "🛡️ 管理员权限添加", + callback_data=self.get_admin_force_add_callback(domain) + ) + ]) keyboard.append([InlineKeyboardButton("➕ 添加其他域名", callback_data="add_direct_rule")]) else: # 默认情况(理论上不会到这里) @@ -995,6 +1013,14 @@ async def _handle_add_domain_callback(self, query, user_id: int, data: str): keyboard.append([InlineKeyboardButton("❌ 取消添加", callback_data="confirm_add_no")]) else: result_text += "\n❌ **不符合添加条件,无法添加到直连规则。**" + if self.is_admin(user_id): + result_text += "\n🛡️ **管理员权限:** 可强制添加" + keyboard.append([ + InlineKeyboardButton( + "🛡️ 管理员权限添加", + callback_data=self.get_admin_force_add_callback(domain) + ) + ]) keyboard.append([InlineKeyboardButton("🏠 返回主菜单", callback_data="main_menu")]) @@ -1004,6 +1030,126 @@ async def _handle_add_domain_callback(self, query, user_id: int, data: str): except Exception as e: logger.error(f"处理添加域名回调失败: {e}") await query.edit_message_text("操作失败,请重试。") + + async def _handle_admin_force_add_callback(self, query, user_id: int, data: str): + """处理管理员权限强制添加回调""" + try: + if not self.is_admin(user_id): + return + + domain = data.split("|", 1)[1].strip() if "|" in data else "" + if not domain: + await query.edit_message_text("❌ 域名数据丢失,请重新开始。") + return + + domain = extract_second_level_domain_for_rules(domain) + if not domain: + await query.edit_message_text("❌ 无效的域名格式,请重新开始。") + return + + if is_cn_domain(domain): + await query.edit_message_text( + "ℹ️ **.cn 域名默认直连,无需手动添加。**", + parse_mode='Markdown' + ) + return + + # 检查用户添加频率限制 + can_add, _ = self.check_user_add_limit(user_id) + if not can_add: + keyboard = [ + [InlineKeyboardButton("🔍 查询域名", callback_data="query_domain")], + [InlineKeyboardButton("🏠 返回主菜单", callback_data="main_menu")] + ] + reply_markup = InlineKeyboardMarkup(keyboard) + await query.edit_message_text( + "⚠️ **添加频率限制**\n\n" + f"您在当前小时内已达到添加上限({self.MAX_ADDS_PER_HOUR}个域名)。\n\n" + "🕐 请等待一小时后再尝试添加新域名。\n\n" + "💡 此限制是为了防止系统滥用,感谢您的理解。", + reply_markup=reply_markup, + parse_mode='Markdown' + ) + return + + # 防重复检查 + github_result = await self.github_service.check_domain_in_rules(domain) + if github_result.get("exists"): + result_text = f"❌ **域名已存在于规则中**\n\n" + result_text += f"📍 **域名:** `{domain}`\n\n" + result_text += "📋 **找到的规则:**\n" + for match in github_result.get("matches", []): + result_text += f" • 第{match['line']}行: {match['rule']}\n" + + keyboard = [ + [InlineKeyboardButton("➕ 添加其他域名", callback_data="add_direct_rule")], + [InlineKeyboardButton("🏠 返回主菜单", callback_data="main_menu")] + ] + reply_markup = InlineKeyboardMarkup(keyboard) + await query.edit_message_text(result_text, reply_markup=reply_markup, parse_mode='Markdown') + return + + in_geosite = await self.data_manager.is_domain_in_geosite(domain) + if in_geosite: + result_text = f"❌ **域名已存在于 GEOSITE:CN 中**\n\n" + result_text += f"📍 **域名:** `{domain}`\n\n" + result_text += "该域名已在 GEOSITE:CN 规则中,不需要重复添加。" + + keyboard = [ + [InlineKeyboardButton("➕ 添加其他域名", callback_data="add_direct_rule")], + [InlineKeyboardButton("🏠 返回主菜单", callback_data="main_menu")] + ] + reply_markup = InlineKeyboardMarkup(keyboard) + await query.edit_message_text(result_text, reply_markup=reply_markup, parse_mode='Markdown') + return + + await query.edit_message_text("⏳ 正在执行管理员权限添加...") + check_result = await self.domain_checker.check_domain_comprehensive(domain) + if "error" in check_result: + await query.edit_message_text(f"❌ 域名检查失败:{check_result['error']}") + return + + target_domain = self.domain_checker.get_target_domain_to_add(check_result) or domain + username = query.from_user.first_name or query.from_user.username or str(query.from_user.id) + + add_result = await self.github_service.add_domain_to_rules( + target_domain, + username, + "", + force_add=True + ) + + if add_result.get("success"): + self.record_user_add(user_id) + _, remaining = self.check_user_add_limit(user_id) + + result_text = "✅ **域名添加成功(管理员权限)!**\n\n" + result_text += f"📍 **域名:** `{target_domain}`\n" + result_text += f"👤 **管理员:** @{self.escape_markdown(username)}\n" + result_text += "🛡️ **添加方式:** 管理员权限强制添加\n" + result_text += f"📂 **文件路径:** `{add_result['file_path']}`\n" + if add_result.get('commit_url'): + result_text += f"🔗 **查看提交:** [点击查看]({add_result['commit_url']})\n" + result_text += f"📝 **Commit ID:** `{add_result.get('commit_sha', '')[:8]}`\n" + result_text += f"💬 **提交信息:** `{add_result['commit_message']}`\n" + result_text += f"\n💡 本小时内还可添加 {remaining} 个域名" + else: + result_text = f"❌ **域名添加失败**\n\n" + result_text += f"📍 **域名:** `{target_domain}`\n" + result_text += f"❌ **错误:** {self.escape_markdown(add_result.get('error', '未知错误'))}" + + keyboard = [ + [InlineKeyboardButton("➕ 继续添加", callback_data="add_direct_rule")], + [InlineKeyboardButton("🏠 返回主菜单", callback_data="main_menu")] + ] + reply_markup = InlineKeyboardMarkup(keyboard) + + await query.edit_message_text(result_text, reply_markup=reply_markup, parse_mode='Markdown') + self.set_user_state(user_id, "idle") + + except Exception as e: + logger.error(f"处理管理员权限添加失败: {e}") + await query.edit_message_text("操作失败,请重试。") async def _handle_confirm_add_callback(self, query, user_id: int, data: str): """处理确认添加回调""" diff --git a/src/services/github_service.py b/src/services/github_service.py index 2a3f787..cfc1924 100644 --- a/src/services/github_service.py +++ b/src/services/github_service.py @@ -161,8 +161,14 @@ def _process_content(): logger.error(f"检查域名规则失败: {e}") return {"exists": False, "error": str(e)} - async def add_domain_to_rules(self, domain: str, user_name: str, description: str = "", - file_path: str = None) -> Dict[str, Any]: + async def add_domain_to_rules( + self, + domain: str, + user_name: str, + description: str = "", + file_path: str = None, + force_add: bool = False + ) -> Dict[str, Any]: """添加域名到规则文件""" try: if not file_path: @@ -228,10 +234,16 @@ def _prepare_update(): beijing_tz = timezone(timedelta(hours=8)) current_date = datetime.now(beijing_tz).strftime("%Y-%m-%d %H:%M:%S") - if description: - comment = f"# {description} / add by Telegram user: {user_name} / Date: {current_date}" + if force_add: + if description: + comment = f"# {description} / force add by Admin: {user_name} / Date: {current_date}" + else: + comment = f"# force add by Admin: {user_name} / Date: {current_date}" else: - comment = f"# add by Telegram user: {user_name} / Date: {current_date}" + if description: + comment = f"# {description} / add by Telegram user: {user_name} / Date: {current_date}" + else: + comment = f"# add by Telegram user: {user_name} / Date: {current_date}" rule = f"DOMAIN-SUFFIX,{domain}" @@ -243,7 +255,13 @@ def _prepare_update(): new_content = '\n'.join(lines) # 遵循 Conventional Commits 规范 - commit_title = f"feat(rules): add direct domain {domain} by Telegram Bot (Telegram user: {user_name})" + if force_add: + commit_title = ( + f"feat(rules): force add direct domain {domain} by Admin " + f"(Telegram user: {user_name})" + ) + else: + commit_title = f"feat(rules): add direct domain {domain} by Telegram Bot (Telegram user: {user_name})" commit_body = description if description else "" full_commit_message = commit_title if commit_body and commit_body.strip(): From 2241bdedeed7fe1475240db2283035f431ccdfa6 Mon Sep 17 00:00:00 2001 From: Aethersailor <22260104+Aethersailor@users.noreply.github.com> Date: Sun, 1 Feb 2026 21:43:08 +0800 Subject: [PATCH 3/6] feat: add admin id debug helpers --- src/bot.py | 1 + src/config.py | 9 +++++++-- src/handlers/handler_manager.py | 19 +++++++++++++++++++ 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/bot.py b/src/bot.py index 61b7b2e..b5fae4f 100644 --- a/src/bot.py +++ b/src/bot.py @@ -84,6 +84,7 @@ def _register_handlers(self): # 命令处理器 self.app.add_handler(CommandHandler("start", self.handler_manager.start_command)) self.app.add_handler(CommandHandler("help", self.handler_manager.help_command)) + self.app.add_handler(CommandHandler("id", self.handler_manager.id_command)) self.app.add_handler(CommandHandler("query", self.handler_manager.query_command)) self.app.add_handler(CommandHandler("add", self.handler_manager.add_command)) self.app.add_handler(CommandHandler("delete", self.handler_manager.delete_command)) diff --git a/src/config.py b/src/config.py index df8c931..04f81b6 100644 --- a/src/config.py +++ b/src/config.py @@ -3,6 +3,7 @@ """ import os +import re from typing import Optional, Dict from loguru import logger @@ -51,6 +52,10 @@ def __init__(self): # 管理员配置(Telegram 用户 ID 列表) # 支持逗号分隔的多个用户 ID,例如:123456789,987654321 self.ADMIN_USER_IDS = self._parse_user_ids(os.getenv("ADMIN_USER_IDS", "")) + if self.ADMIN_USER_IDS: + logger.info(f"已加载管理员 IDs: {self.ADMIN_USER_IDS}") + else: + logger.info("未配置管理员 IDs(ADMIN_USER_IDS)") # 数据源URL # 使用 Aethersailor GeoIP 数据库 @@ -137,8 +142,8 @@ def _parse_user_ids(self, ids_str: str) -> list: return [] user_ids = [] - for raw_id in ids_str.split(","): - raw_id = raw_id.strip() + parts = [part for part in re.split(r"[,\s;]+", ids_str.strip()) if part] + for raw_id in parts: if not raw_id: continue try: diff --git a/src/handlers/handler_manager.py b/src/handlers/handler_manager.py index f30b102..a586bd2 100644 --- a/src/handlers/handler_manager.py +++ b/src/handlers/handler_manager.py @@ -472,6 +472,24 @@ async def help_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) parse_mode='Markdown' ) + async def id_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE): + """处理 /id 命令,返回用户 ID""" + try: + if not await self.check_group_membership(update): + return + + user = update.effective_user + username = user.username or user.first_name or "未知" + text = ( + "🆔 **您的 Telegram 用户 ID:** " + f"`{user.id}`\n" + f"👤 **用户名:** @{self.escape_markdown(username)}" + ) + await update.message.reply_text(text, parse_mode='Markdown') + except Exception as e: + logger.error(f"处理 id 命令失败: {e}") + await update.message.reply_text("处理失败,请重试。") + async def query_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE): """处理 /query 命令""" user_id = update.effective_user.id @@ -1035,6 +1053,7 @@ async def _handle_admin_force_add_callback(self, query, user_id: int, data: str) """处理管理员权限强制添加回调""" try: if not self.is_admin(user_id): + logger.warning(f"管理员权限操作被拒绝: user_id={user_id}, data={data}") return domain = data.split("|", 1)[1].strip() if "|" in data else "" From 83b2b7fc99eff50800e3e3d5f152cd4fed542ca6 Mon Sep 17 00:00:00 2001 From: Aethersailor <22260104+Aethersailor@users.noreply.github.com> Date: Sun, 1 Feb 2026 21:48:54 +0800 Subject: [PATCH 4/6] fix: keep user in query/add flow after results --- src/handlers/handler_manager.py | 38 ++++++++++++++++----------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/handlers/handler_manager.py b/src/handlers/handler_manager.py index a586bd2..87c0c29 100644 --- a/src/handlers/handler_manager.py +++ b/src/handlers/handler_manager.py @@ -719,8 +719,8 @@ async def _handle_domain_query(self, update: Update, domain_input: str, user_id: await processing_msg.edit_text(result_text, reply_markup=reply_markup, parse_mode='Markdown') - # 重置用户状态 - self.set_user_state(user_id, "idle") + # 保持查询模式,便于继续输入域名 + self.set_user_state(user_id, "waiting_query_domain") return # 非.cn域名继续正常查询流程 @@ -790,8 +790,8 @@ async def _handle_domain_query(self, update: Update, domain_input: str, user_id: await processing_msg.edit_text(result_text, reply_markup=reply_markup, parse_mode='Markdown') - # 重置用户状态 - self.set_user_state(user_id, "idle") + # 保持查询模式,便于继续输入域名 + self.set_user_state(user_id, "waiting_query_domain") except Exception as e: logger.error(f"域名查询失败: {e}") @@ -820,8 +820,8 @@ async def _handle_add_domain_input(self, update: Update, domain_input: str, user reply_markup=reply_markup, parse_mode='Markdown' ) - # 重置用户状态 - self.set_user_state(user_id, "idle") + # 保持添加模式,便于继续输入域名 + self.set_user_state(user_id, "waiting_add_domain") return # 检查是否为.cn域名 @@ -841,8 +841,8 @@ async def _handle_add_domain_input(self, update: Update, domain_input: str, user reply_markup=reply_markup, parse_mode='Markdown' ) - # 重置用户状态 - self.set_user_state(user_id, "idle") + # 保持添加模式,便于继续输入域名 + self.set_user_state(user_id, "waiting_add_domain") return # 提取二级域名用于添加规则 @@ -864,8 +864,8 @@ async def _handle_add_domain_input(self, update: Update, domain_input: str, user ) else: await processing_msg.edit_text("❌ 无效的域名格式,请重新输入。") - # 重置用户状态 - self.set_user_state(user_id, "idle") + # 保持添加模式,便于继续输入域名 + self.set_user_state(user_id, "waiting_add_domain") return # 显示提取的二级域名信息 @@ -894,7 +894,7 @@ async def _handle_add_domain_input(self, update: Update, domain_input: str, user reply_markup = InlineKeyboardMarkup(keyboard) await processing_msg.edit_text(result_text, reply_markup=reply_markup, parse_mode='Markdown') - self.set_user_state(user_id, "idle") + self.set_user_state(user_id, "waiting_add_domain") return # 检查二级域名规则 @@ -915,7 +915,7 @@ async def _handle_add_domain_input(self, update: Update, domain_input: str, user reply_markup = InlineKeyboardMarkup(keyboard) await processing_msg.edit_text(result_text, reply_markup=reply_markup, parse_mode='Markdown') - self.set_user_state(user_id, "idle") + self.set_user_state(user_id, "waiting_add_domain") return # 检查GeoSite @@ -932,7 +932,7 @@ async def _handle_add_domain_input(self, update: Update, domain_input: str, user reply_markup = InlineKeyboardMarkup(keyboard) await processing_msg.edit_text(result_text, reply_markup=reply_markup, parse_mode='Markdown') - self.set_user_state(user_id, "idle") + self.set_user_state(user_id, "waiting_add_domain") return # 2. 进行域名检查 @@ -1164,7 +1164,7 @@ async def _handle_admin_force_add_callback(self, query, user_id: int, data: str) reply_markup = InlineKeyboardMarkup(keyboard) await query.edit_message_text(result_text, reply_markup=reply_markup, parse_mode='Markdown') - self.set_user_state(user_id, "idle") + self.set_user_state(user_id, "waiting_add_domain") except Exception as e: logger.error(f"处理管理员权限添加失败: {e}") @@ -1186,7 +1186,7 @@ async def _handle_confirm_add_callback(self, query, user_id: int, data: str): reply_markup=reply_markup, parse_mode='Markdown' ) - self.set_user_state(user_id, "idle") + self.set_user_state(user_id, "waiting_add_domain") return # 确认添加 @@ -1318,8 +1318,8 @@ async def _add_domain_to_github(self, query, user_id: int, description: str): await query.edit_message_text(result_text, reply_markup=reply_markup, parse_mode='Markdown') - # 重置用户状态 - self.set_user_state(user_id, "idle") + # 保持添加模式,便于继续输入域名 + self.set_user_state(user_id, "waiting_add_domain") except Exception as e: logger.error(f"添加域名到 GitHub 失败: {e}") @@ -1383,8 +1383,8 @@ async def _add_domain_to_github_message(self, message, user_id: int, description await processing_msg.edit_text(result_text, reply_markup=reply_markup, parse_mode='Markdown') - # 重置用户状态 - self.set_user_state(user_id, "idle") + # 保持添加模式,便于继续输入域名 + self.set_user_state(user_id, "waiting_add_domain") except Exception as e: logger.error(f"添加域名到 GitHub 失败: {e}") From 1d072e81953e069a2742d69190f074843e799d3b Mon Sep 17 00:00:00 2001 From: Aethersailor <22260104+Aethersailor@users.noreply.github.com> Date: Sun, 1 Feb 2026 22:14:09 +0800 Subject: [PATCH 5/6] fix: keep add flow after rejected check results --- src/handlers/handler_manager.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/handlers/handler_manager.py b/src/handlers/handler_manager.py index 87c0c29..baa436c 100644 --- a/src/handlers/handler_manager.py +++ b/src/handlers/handler_manager.py @@ -964,11 +964,12 @@ async def _handle_add_domain_input(self, update: Update, domain_input: str, user # 根据检查结果决定下一步 keyboard = [] + should_reject = self.domain_checker.should_reject(check_result) if self.domain_checker.should_add_directly(check_result): # 符合条件,提供添加选项 keyboard.append([InlineKeyboardButton("✅ 确认添加", callback_data="confirm_add_yes")]) keyboard.append([InlineKeyboardButton("❌ 取消添加", callback_data="confirm_add_no")]) - elif self.domain_checker.should_reject(check_result): + elif should_reject: # 不符合条件,拒绝添加 result_text += "\n❌ **不符合添加条件,无法添加到直连规则。**" if self.is_admin(user_id): @@ -989,6 +990,9 @@ async def _handle_add_domain_input(self, update: Update, domain_input: str, user reply_markup = InlineKeyboardMarkup(keyboard) await processing_msg.edit_text(result_text, reply_markup=reply_markup, parse_mode='Markdown') + + if should_reject: + self.set_user_state(user_id, "waiting_add_domain") except Exception as e: logger.error(f"添加域名输入处理失败: {e}") @@ -1026,7 +1030,8 @@ async def _handle_add_domain_callback(self, query, user_id: int, data: str): # 根据检查结果决定下一步 keyboard = [] - if not self.domain_checker.should_reject(check_result): + should_reject = self.domain_checker.should_reject(check_result) + if not should_reject: keyboard.append([InlineKeyboardButton("✅ 确认添加", callback_data="confirm_add_yes")]) keyboard.append([InlineKeyboardButton("❌ 取消添加", callback_data="confirm_add_no")]) else: @@ -1044,6 +1049,9 @@ async def _handle_add_domain_callback(self, query, user_id: int, data: str): reply_markup = InlineKeyboardMarkup(keyboard) await query.edit_message_text(result_text, reply_markup=reply_markup, parse_mode='Markdown') + + if should_reject: + self.set_user_state(user_id, "waiting_add_domain") except Exception as e: logger.error(f"处理添加域名回调失败: {e}") From 90d62b63fd7916ea542cfb27049937dbacacea96 Mon Sep 17 00:00:00 2001 From: Aethersailor <22260104+Aethersailor@users.noreply.github.com> Date: Mon, 2 Feb 2026 15:40:55 +0800 Subject: [PATCH 6/6] feat(docker): add ADMIN_USER_IDS and update quickstart Switch docker-compose to use the prebuilt aethersailor/rule-bot image and add a commented ADMIN_USER_IDS environment option (supports multiple Telegram IDs, lets admins force-add domains and access debug info). Update README quickstart to show downloading the compose file, editing required env vars (TELEGRAM_BOT_TOKEN, GITHUB_TOKEN, GITHUB_REPO, DIRECT_RULE_FILE), and document the ADMIN_USER_IDS admin mode. Small doc/formatting tweaks to logs/FAQ for clarity. --- README.md | 46 +++++++++++++++++++++++++++++++--------------- docker-compose.yml | 8 +++++++- 2 files changed, 38 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 00a2623..0bd079b 100644 --- a/README.md +++ b/README.md @@ -58,28 +58,33 @@ skip - 跳过说明 ## ⚡ 快速开始(Docker) -1) 创建 `docker-compose.yml` - -```yaml -services: - rule-bot: - image: aethersailor/rule-bot:latest - container_name: rule-bot - restart: unless-stopped - environment: - - TELEGRAM_BOT_TOKEN=你的机器人 Token - - GITHUB_TOKEN=你的 GitHub Token - - GITHUB_REPO=your_username/your_repository - - DIRECT_RULE_FILE=rule/Custom_Direct.list +1) 创建工作目录并下载配置文件 + +```bash +mkdir -p /opt/Rule-Bot && cd /opt/Rule-Bot +wget https://raw.githubusercontent.com/Aethersailor/Rule-Bot/main/docker-compose.yml ``` -2) 启动 +1) 编辑配置文件 + +```bash +vim docker-compose.yml +``` + +修改以下必填参数(去掉 `#` 注释,填入你的实际值): + +- `TELEGRAM_BOT_TOKEN` +- `GITHUB_TOKEN` +- `GITHUB_REPO` +- `DIRECT_RULE_FILE` + +1) 启动容器 ```bash docker compose up -d ``` -3) 查看日志 +1) 查看日志 ```bash docker compose logs -f rule-bot @@ -113,6 +118,7 @@ docker compose logs -f rule-bot | `REQUIRED_GROUP_NAME` | 群组验证名称 | 空 | | `REQUIRED_GROUP_LINK` | 群组验证链接 | 空 | | `ALLOWED_GROUP_IDS` | 群组模式允许的群组 ID,逗号分隔 | 空 | +| `ADMIN_USER_IDS` | 管理员 Telegram 用户 ID,逗号分隔 | 空 | | `TZ` | 时区 | `Asia/Shanghai` | @@ -142,6 +148,15 @@ docker compose logs -f rule-bot 同时配置 `REQUIRED_GROUP_ID/NAME/LINK` 后生效,未通过或校验失败会拒绝访问(失败即拒绝)。 +### 管理员模式(ADMIN_USER_IDS) + +配置 `ADMIN_USER_IDS` 后,指定的管理员用户可以: + +- 强制添加被系统检测拒绝的域名 +- 获取调试辅助信息 + +> 通过 @userinfobot 获取你的 Telegram 用户 ID。 + ## 📌 规则逻辑(简版) 1) 解析域名并提取二级域名 @@ -160,6 +175,7 @@ docker compose logs -f rule-bot ## 🧩 常见问题 **群组不响应消息** + 1. 关闭 Privacy Mode 2. 重新添加机器人到群组 3. 仅在消息中 @ 机器人 diff --git a/docker-compose.yml b/docker-compose.yml index a5d0520..d6cb6b9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,6 @@ services: rule-bot: - build: . + image: aethersailor/rule-bot:latest container_name: rule-bot restart: unless-stopped environment: @@ -52,6 +52,12 @@ services: # 支持逗号分隔多个群组 ID,例如:-1001234567890,-1009876543210 # 留空则关闭此功能(仅支持私聊模式) # - ALLOWED_GROUP_IDS=-1001234567890,-1009876543210 + + # 管理员配置 (可选: 指定管理员用户 ID,拥有强制添加域名的权限) + # 支持逗号分隔多个 Telegram 用户 ID,例如:123456789,987654321 + # 管理员可以强制添加被系统检测拒绝的域名 + # 留空则关闭此功能 + # - ADMIN_USER_IDS=123456789,987654321 # ======================================== # ========== 系统配置 ==========