1+ from typing import List , Dict , Optional
2+ from datetime import datetime
3+ import html
4+ from telegram import (
5+ Update ,
6+ InlineKeyboardButton ,
7+ InlineKeyboardMarkup ,
8+ BotCommand ,
9+ BotCommandScopeChat ,
10+ BotCommandScopeAllPrivateChats ,
11+ BotCommandScopeAllChatAdministrators ,
12+ BotCommandScopeAllGroupChats ,
13+ BotCommandScopeDefault
14+ )
15+ from telegram .ext import ContextTypes , Application
16+ from telegram .constants import ParseMode
17+ from config import ADMIN_ID
18+ from database .storage import bot_data , save_data
19+
20+ # 检查管理员权限
21+ async def check_is_admin (update : Update ) -> bool :
22+ if not ADMIN_ID : return False
23+ user_id = update .effective_user .id
24+ chat_id = update .effective_chat .id
25+ return user_id == ADMIN_ID or chat_id == ADMIN_ID
26+
27+ # ================= 补回丢失的函数:获取用户管理的群组 =================
28+ async def get_user_managed_groups (context : ContextTypes .DEFAULT_TYPE , user_id : int ) -> List [dict ]:
29+ """获取用户有管理权的群组列表 (用于 /setup 菜单)"""
30+ managed = []
31+ # 遍历数据库中已登记的群组
32+ for chat_id_str , info in bot_data ["groups" ].items ():
33+ try :
34+ # 调用 Telegram API 检查该用户在群里的身份
35+ member = await context .bot .get_chat_member (chat_id = int (chat_id_str ), user_id = user_id )
36+ if member .status in ['creator' , 'administrator' ]:
37+ managed .append ({"id" : chat_id_str , "title" : info .get ("title" , "未知群组" )})
38+ except Exception :
39+ # 如果Bot被踢了或者没权限看,跳过
40+ continue
41+ return managed
42+ # ===================================================================
43+
44+ async def post_init (application : Application ):
45+ """配置指令菜单 (Bot启动时执行)"""
46+ bot = application .bot
47+ await bot .delete_my_commands (scope = BotCommandScopeDefault ())
48+
49+ # 1. 公共指令
50+ public_cmds = [
51+ BotCommand ("fear" , "😨 恐慌指数" ),
52+ BotCommand ("help" , "💡 帮助" )
53+ ]
54+ await bot .set_my_commands (public_cmds , scope = BotCommandScopeAllGroupChats ())
55+
56+ # 2. 私聊指令
57+ private_cmds = public_cmds + [
58+ BotCommand ("start" , "开始" ),
59+ BotCommand ("setup" , "⚙️ 配置" ),
60+ BotCommand ("id" , "获取ID" )
61+ ]
62+ await bot .set_my_commands (private_cmds , scope = BotCommandScopeAllPrivateChats ())
63+
64+ # 3. 群管指令
65+ admin_cmds = public_cmds + [
66+ BotCommand ("request_broadcast" , "📢 申请广播" ),
67+ BotCommand ("id" , "🆔 群ID" )
68+ ]
69+ await bot .set_my_commands (admin_cmds , scope = BotCommandScopeAllChatAdministrators ())
70+
71+ # 4. 终极管理员指令
72+ if ADMIN_ID :
73+ root_cmds = public_cmds + [
74+ BotCommand ("report" , "📊 审计" ),
75+ BotCommand ("revoke" , "🚫 移除" ),
76+ BotCommand ("id" , "🆔 获取ID" )
77+ ]
78+ try :
79+ await bot .set_my_commands (root_cmds , scope = BotCommandScopeChat (chat_id = ADMIN_ID ))
80+ except Exception :
81+ pass
82+
83+ # 申请广播
84+ async def cmd_request_broadcast (update : Update , context : ContextTypes .DEFAULT_TYPE ):
85+ chat = update .effective_chat
86+ if chat .type == 'private' or not ADMIN_ID : return
87+
88+ req_id = f"{ chat .id } _{ int (datetime .now ().timestamp ())} "
89+ bot_data ["pending_req" ].append ({
90+ "req_id" : req_id ,
91+ "chat_id" : chat .id ,
92+ "title" : chat .title
93+ })
94+ save_data ()
95+
96+ kb = [[InlineKeyboardButton ("✅ 批准" , callback_data = f"appr_yes_{ req_id } " ), InlineKeyboardButton ("❌ 拒绝" , callback_data = f"appr_no_{ req_id } " )]]
97+ await context .bot .send_message (ADMIN_ID , f"📩 **广播申请**\n 群组: { chat .title } " , reply_markup = InlineKeyboardMarkup (kb ), parse_mode = ParseMode .MARKDOWN )
98+ await update .message .reply_text ("✅ 已提交申请,等待审核。" )
99+
100+ # 审批回调
101+ async def callback_approval (update : Update , context : ContextTypes .DEFAULT_TYPE ):
102+ query = update .callback_query
103+ parts = query .data .split ("_" , 2 )
104+ action = parts [1 ]
105+ req_id = parts [2 ]
106+
107+ req = next ((r for r in bot_data ["pending_req" ] if r .get ("req_id" ) == req_id ), None )
108+
109+ if not req :
110+ await query .edit_message_text ("⚠️ 请求失效或已处理" )
111+ return
112+
113+ if action == "yes" :
114+ cid = str (req ["chat_id" ])
115+ if cid not in bot_data ["groups" ]: bot_data ["groups" ][cid ] = {"title" : req ["title" ]}
116+
117+ bot_data ["groups" ][cid ].update ({
118+ "is_broadcast" : True ,
119+ "is_paused" : False ,
120+ "sub_news" : True , "sub_marketing" : True , "sub_analysis" : True , "sub_signal" : True
121+ })
122+ save_data ()
123+ await query .edit_message_text (f"✅ 已批准: { req ['title' ]} " )
124+ try : await context .bot .send_message (req ["chat_id" ], "🎉 恭喜!广播申请已通过。管理员请私聊 Bot 使用 /setup 配置。" )
125+ except : pass
126+ else :
127+ await query .edit_message_text (f"❌ 已拒绝: { req ['title' ]} " )
128+
129+ bot_data ["pending_req" ].remove (req )
130+ save_data ()
131+
132+ async def cmd_report (update : Update , context : ContextTypes .DEFAULT_TYPE ):
133+ if not await check_is_admin (update ): return
134+ txt = "📊 **群组审计**\n \n "
135+ for cid , i in bot_data ["groups" ].items ():
136+ if not i .get ("is_broadcast" ): continue
137+ status = "🔴暂停" if i .get ("is_paused" ) else "🟢正常"
138+ txt += f"**{ html .escape (i ['title' ])} **\n ID: `{ cid } `\n 状态: { status } \n 话题: News:`{ i .get ('topic_news' )} ` Sig:`{ i .get ('topic_signal' )} `\n \n "
139+ if len (txt ) > 4000 : txt = txt [:4000 ]
140+ await update .message .reply_text (txt , parse_mode = ParseMode .MARKDOWN )
141+
142+ async def cmd_revoke (update : Update , context : ContextTypes .DEFAULT_TYPE ):
143+ if not await check_is_admin (update ) or not context .args : return
144+ tid = context .args [0 ]
145+ if tid in bot_data ["groups" ]:
146+ bot_data ["groups" ][tid ]["is_broadcast" ] = False
147+ save_data ()
148+ await update .message .reply_text (f"✅ 已移除权限: { tid } " )
0 commit comments