From ba8ffb9a76daaa9c70fa241a7a69550edc233a9f Mon Sep 17 00:00:00 2001 From: mango Date: Wed, 25 Feb 2026 17:32:50 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E9=87=8D=E6=9E=84UI=E7=BB=93=E6=9E=84+?= =?UTF-8?q?=E5=88=86=E7=BB=84=E6=A3=80=E6=B5=8B+=E5=88=86=E7=BB=84?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E8=8A=82=E7=82=B9+=E8=BF=94=E5=9B=9E?= =?UTF-8?q?=E6=8C=89=E9=92=AE=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bot.py | 160 +++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 140 insertions(+), 20 deletions(-) diff --git a/bot.py b/bot.py index 6031129..0c1a428 100644 --- a/bot.py +++ b/bot.py @@ -109,20 +109,20 @@ def add_sub_to_group(link, user, group_id): async def cmd_help(update: Update, context: ContextTypes.DEFAULT_TYPE): if not await is_member(update, context): return await update.message.reply_text('⛔ 仅限群成员使用') + data = load_data() + default_cnt = len([s for s in data['subs'] if s.get('alive', True)]) + sg_cnt = len(data.get('sub_groups', [])) buttons = [ - [InlineKeyboardButton("📋 订阅列表", callback_data='menu_list'), - InlineKeyboardButton("📥 获取订阅", callback_data='menu_get')], - [InlineKeyboardButton("➕ 添加订阅", callback_data='menu_add'), - InlineKeyboardButton("🗑 删除订阅", callback_data='menu_del')], - [InlineKeyboardButton("🔍 检测存活", callback_data='menu_check')], - [InlineKeyboardButton("📁 分组管理", callback_data='sg_list')], + [InlineKeyboardButton(f"📦 默认节点池 ({default_cnt})", callback_data='menu_default')], + [InlineKeyboardButton(f"📁 分组管理 ({sg_cnt}个分组)", callback_data='sg_list')], + [InlineKeyboardButton("🔍 全部检测", callback_data='menu_check')], ] if update.effective_user.id == ADMIN_ID: buttons.append([InlineKeyboardButton("⚙️ 绑定当前群", callback_data='menu_setgroup')]) await send_del(update.message, '🚀 *订阅管理 Bot*\n\n' - '直接发订阅链接自动入库\n' - '或点击下方按钮操作:', + '发送订阅链接自动入库到默认节点池\n' + '或通过分组管理创建独立订阅', parse_mode='Markdown', reply_markup=InlineKeyboardMarkup(buttons)) # --- menu callbacks --- @@ -132,7 +132,40 @@ async def cb_menu(update: Update, context: ContextTypes.DEFAULT_TYPE): action = q.data data = load_data() - if action == 'menu_list': + if action == 'menu_default': + cnt = len(data['subs']) + alive_cnt = len([s for s in data['subs'] if s.get('alive', True)]) + buttons = [ + [InlineKeyboardButton("📋 节点列表", callback_data='menu_list'), + InlineKeyboardButton("📥 获取订阅", callback_data='menu_get')], + [InlineKeyboardButton("➕ 添加节点", callback_data='menu_add'), + InlineKeyboardButton("🗑 删除节点", callback_data='menu_del')], + [InlineKeyboardButton("◀️ 返回主菜单", callback_data='menu_home')], + ] + try: + await q.edit_message_text( + f'📦 *默认节点池*\n\n总计 {cnt} 个节点,{alive_cnt} 个可用', + parse_mode='Markdown', reply_markup=InlineKeyboardMarkup(buttons)) + except: + await send_del(q.message, + f'📦 *默认节点池*\n\n总计 {cnt} 个节点,{alive_cnt} 个可用', + parse_mode='Markdown', reply_markup=InlineKeyboardMarkup(buttons)) + + elif action == 'menu_home': + default_cnt = len([s for s in data['subs'] if s.get('alive', True)]) + sg_cnt = len(data.get('sub_groups', [])) + buttons = [ + [InlineKeyboardButton(f"📦 默认节点池 ({default_cnt})", callback_data='menu_default')], + [InlineKeyboardButton(f"📁 分组管理 ({sg_cnt}个分组)", callback_data='sg_list')], + [InlineKeyboardButton("🔍 全部检测", callback_data='menu_check')], + ] + try: + await q.edit_message_text( + '🚀 *订阅管理 Bot*\n\n发送订阅链接自动入库到默认节点池\n或通过分组管理创建独立订阅', + parse_mode='Markdown', reply_markup=InlineKeyboardMarkup(buttons)) + except: pass + + elif action == 'menu_list': if not data['subs']: return await send_del(q.message, '📭 暂无订阅') lines = [f"`{i+1}` {'🟢' if s.get('alive',True) else '🔴'} [{s['type']}] {s['name']}" @@ -631,7 +664,7 @@ async def cb_subgroups(update: Update, context: ContextTypes.DEFAULT_TYPE): cnt = len(sg.get('nodes', [])) buttons.append([InlineKeyboardButton(f"📁 {sg['name']} ({cnt}个节点)", callback_data=f"sg_detail_{sg['id']}")]) buttons.append([InlineKeyboardButton("➕ 创建分组", callback_data='sg_create')]) - buttons.append([InlineKeyboardButton("◀️ 返回主菜单", callback_data='menu_back')]) + buttons.append([InlineKeyboardButton("◀️ 返回主菜单", callback_data='sg_back')]) text = f"📁 *分组管理*\n\n共 {len(sgs)} 个分组" if sgs else "📁 *分组管理*\n\n暂无分组,点击创建" try: await q.edit_message_text(text, parse_mode='Markdown', reply_markup=InlineKeyboardMarkup(buttons)) @@ -659,6 +692,8 @@ async def cb_subgroups(update: Update, context: ContextTypes.DEFAULT_TYPE): buttons = [ [InlineKeyboardButton("📋 节点列表", callback_data=f"sg_nodes_{gid}"), InlineKeyboardButton("➕ 添加节点", callback_data=f"sg_add_{gid}")], + [InlineKeyboardButton("🗑 删除节点", callback_data=f"sg_delnodes_{gid}"), + InlineKeyboardButton("🔍 检测节点", callback_data=f"sg_check_{gid}")], [InlineKeyboardButton("📥 获取订阅", callback_data=f"sg_getsub_{gid}"), InlineKeyboardButton("🔄 重置链接", callback_data=f"sg_reset_{gid}")], [InlineKeyboardButton("✏️ 重命名", callback_data=f"sg_rename_{gid}"), @@ -699,6 +734,91 @@ async def cb_subgroups(update: Update, context: ContextTypes.DEFAULT_TYPE): except: await send_del(q.message, f'📎 请发送要添加到 [{name}] 的订阅链接:') + elif action.startswith('sg_check_'): + gid = action[len('sg_check_'):] + sg = next((g for g in sgs if g['id'] == gid), None) + if not sg: return + nodes = sg.get('nodes', []) + if not nodes: + buttons = [[InlineKeyboardButton("◀️ 返回", callback_data=f"sg_detail_{gid}")]] + return await send_del(q.message, f"📁 *{sg['name']}* 暂无节点", + parse_mode='Markdown', reply_markup=InlineKeyboardMarkup(buttons)) + msg = await send_del(q.message, f"🔍 检测 *{sg['name']}* 中...", parse_mode='Markdown', delay=120) + results = [] + for s in nodes: + ok = await check_node(s['link'], s['type']) + s['alive'] = ok + results.append(f"{'🟢' if ok else '🔴'} [{s['type']}] {s['name']}") + save_data(data) + try: await msg.delete() + except: pass + buttons = [[InlineKeyboardButton("◀️ 返回", callback_data=f"sg_detail_{gid}")]] + await send_del(q.message, f"📊 *{sg['name']}* 检测结果\n\n" + '\n'.join(results), + parse_mode='Markdown', reply_markup=InlineKeyboardMarkup(buttons)) + + elif action.startswith('sg_delnodes_'): + gid = action[len('sg_delnodes_'):] + sg = next((g for g in sgs if g['id'] == gid), None) + if not sg: return + nodes = sg.get('nodes', []) + if not nodes: + buttons = [[InlineKeyboardButton("◀️ 返回", callback_data=f"sg_detail_{gid}")]] + return await send_del(q.message, f"📁 *{sg['name']}* 暂无节点", + parse_mode='Markdown', reply_markup=InlineKeyboardMarkup(buttons)) + buttons = [] + for i, s in enumerate(nodes): + st = '🟢' if s.get('alive', True) else '🔴' + buttons.append([InlineKeyboardButton( + f"{st} [{s['type']}] {s['name']}", callback_data=f"sg_delnode_{gid}_{i}")]) + buttons.append([InlineKeyboardButton("🗑 删除所有不可用", callback_data=f"sg_deldown_{gid}")]) + buttons.append([InlineKeyboardButton("◀️ 返回", callback_data=f"sg_detail_{gid}")]) + try: + await q.edit_message_text(f"🗑 *{sg['name']}* — 选择要删除的节点:", + parse_mode='Markdown', reply_markup=InlineKeyboardMarkup(buttons)) + except: + await send_del(q.message, f"🗑 *{sg['name']}* — 选择要删除的节点:", + parse_mode='Markdown', reply_markup=InlineKeyboardMarkup(buttons)) + + elif action.startswith('sg_delnode_'): + # sg_delnode_{gid}_{idx} + parts = action[len('sg_delnode_'):].rsplit('_', 1) + if len(parts) != 2: return + gid, idx_str = parts + sg = next((g for g in sgs if g['id'] == gid), None) + if not sg: return + idx = int(idx_str) + nodes = sg.get('nodes', []) + if idx >= len(nodes): + return await send_del(q.message, '❌ 已失效') + removed = nodes.pop(idx) + save_data(data) + buttons = [[InlineKeyboardButton("◀️ 返回", callback_data=f"sg_detail_{gid}")]] + try: + await q.edit_message_text(f"✅ 已删除: [{removed['type']}] {removed['name']}", + reply_markup=InlineKeyboardMarkup(buttons)) + except: + await send_del(q.message, f"✅ 已删除: [{removed['type']}] {removed['name']}", + reply_markup=InlineKeyboardMarkup(buttons)) + + elif action.startswith('sg_deldown_'): + gid = action[len('sg_deldown_'):] + sg = next((g for g in sgs if g['id'] == gid), None) + if not sg: return + dead = [s for s in sg.get('nodes', []) if not s.get('alive', True)] + if not dead: + buttons = [[InlineKeyboardButton("◀️ 返回", callback_data=f"sg_detail_{gid}")]] + return await send_del(q.message, '没有不可用节点', reply_markup=InlineKeyboardMarkup(buttons)) + names = [s['name'] for s in dead] + sg['nodes'] = [s for s in sg['nodes'] if s.get('alive', True)] + save_data(data) + buttons = [[InlineKeyboardButton("◀️ 返回", callback_data=f"sg_detail_{gid}")]] + try: + await q.edit_message_text(f"🗑 已删除 {len(names)} 个不可用:\n" + '\n'.join(f'• {n}' for n in names), + reply_markup=InlineKeyboardMarkup(buttons)) + except: + await send_del(q.message, f"🗑 已删除 {len(names)} 个不可用:\n" + '\n'.join(f'• {n}' for n in names), + reply_markup=InlineKeyboardMarkup(buttons)) + elif action.startswith('sg_getsub_'): gid = action[len('sg_getsub_'):] sg = next((g for g in sgs if g['id'] == gid), None) @@ -761,19 +881,19 @@ async def cb_subgroups(update: Update, context: ContextTypes.DEFAULT_TYPE): except: await send_del(q.message, "✅ 分组已删除", reply_markup=InlineKeyboardMarkup(buttons)) - elif action == 'menu_back': + elif action == 'sg_back': # 返回主菜单 + default_cnt = len([s for s in data['subs'] if s.get('alive', True)]) + sg_cnt = len(data.get('sub_groups', [])) buttons = [ - [InlineKeyboardButton("📋 订阅列表", callback_data='menu_list'), - InlineKeyboardButton("📥 获取订阅", callback_data='menu_get')], - [InlineKeyboardButton("➕ 添加订阅", callback_data='menu_add'), - InlineKeyboardButton("🗑 删除订阅", callback_data='menu_del')], - [InlineKeyboardButton("🔍 检测存活", callback_data='menu_check')], - [InlineKeyboardButton("📁 分组管理", callback_data='sg_list')], + [InlineKeyboardButton(f"📦 默认节点池 ({default_cnt})", callback_data='menu_default')], + [InlineKeyboardButton(f"📁 分组管理 ({sg_cnt}个分组)", callback_data='sg_list')], + [InlineKeyboardButton("🔍 全部检测", callback_data='menu_check')], ] try: - await q.edit_message_text('🚀 *订阅管理 Bot*\n\n直接发订阅链接自动入库\n或点击下方按钮操作:', - parse_mode='Markdown', reply_markup=InlineKeyboardMarkup(buttons)) + await q.edit_message_text( + '🚀 *订阅管理 Bot*\n\n发送订阅链接自动入库到默认节点池\n或通过分组管理创建独立订阅', + parse_mode='Markdown', reply_markup=InlineKeyboardMarkup(buttons)) except: pass async def auto_cleanup(bot_token): @@ -826,7 +946,7 @@ def main(): app.add_handler(CallbackQueryHandler(cb_multisel, pattern='^msel_')) app.add_handler(CallbackQueryHandler(cb_multiout, pattern='^mout_')) app.add_handler(CallbackQueryHandler(cb_getsub, pattern='^(get_|pick_|send_|fmt_|out_)')) - app.add_handler(CallbackQueryHandler(cb_subgroups, pattern='^(sg_|menu_back)')) + app.add_handler(CallbackQueryHandler(cb_subgroups, pattern='^sg_')) app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, auto_detect)) log.info('Sub Bot starting polling...') threading.Thread(target=start_http, daemon=True).start()