__main__.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509
  1. """
  2. MIT License
  3. Copyright (c) 2024 TheHamkerCat
  4. Permission is hereby granted, free of charge, to any person obtaining a copy
  5. of this software and associated documentation files (the "Software"), to deal
  6. in the Software without restriction, including without limitation the rights
  7. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. copies of the Software, and to permit persons to whom the Software is
  9. furnished to do so, subject to the following conditions:
  10. The above copyright notice and this permission notice shall be included in all
  11. copies or substantial portions of the Software.
  12. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  13. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  14. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  15. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  16. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  17. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  18. SOFTWARE.
  19. """
  20. import asyncio
  21. import importlib
  22. import os
  23. import re
  24. import time
  25. from contextlib import closing, suppress
  26. import psutil
  27. from pyrogram import filters, idle
  28. from pyrogram.enums import ChatType, ParseMode
  29. from pyrogram.types import BotCommand, InlineKeyboardButton, InlineKeyboardMarkup
  30. from uvloop import install
  31. from wbb import (
  32. BOT_NAME,
  33. BOT_USERNAME,
  34. LOG_GROUP_ID,
  35. USERBOT_CONNECTED,
  36. USERBOT_NAME,
  37. aiohttpsession,
  38. app,
  39. app2,
  40. bot_start_time,
  41. log,
  42. )
  43. from wbb.core.keyboard import ikb
  44. from wbb.modules import ALL_MODULES
  45. from wbb.utils import paginate_modules
  46. from wbb.utils.constants import MARKDOWN
  47. from wbb.utils.dbfunctions import clean_restart_stage, get_rules
  48. from wbb.utils.functions import extract_text_and_keyb
  49. loop = asyncio.get_event_loop()
  50. HELPABLE = {}
  51. HELP_MODULE_KEYS = {
  52. "admin",
  53. "admin_misc",
  54. "admin_panel",
  55. "antiservice",
  56. "blacklist",
  57. "chatbot",
  58. "feds",
  59. "filters",
  60. "flood",
  61. "giveaway",
  62. "greetings",
  63. "karma",
  64. "locks",
  65. "notes",
  66. "points",
  67. "rules",
  68. }
  69. BOT_COMMANDS = [
  70. BotCommand("start", "打开机器人首页"),
  71. BotCommand("help", "查看中文功能帮助"),
  72. BotCommand("manage", "打开私聊群管理菜单"),
  73. BotCommand("cancel", "取消当前管理流程"),
  74. BotCommand("points", "查看积分余额"),
  75. BotCommand("checkin", "领取每日签到积分"),
  76. BotCommand("points_rank", "查看群积分排行榜"),
  77. BotCommand("points_history", "查看最近积分流水"),
  78. BotCommand("giveaway", "创建群抽奖"),
  79. BotCommand("gjoin", "报名参加抽奖"),
  80. BotCommand("glist", "查看进行中的抽奖"),
  81. BotCommand("gparticipants", "查看抽奖参与者"),
  82. BotCommand("gend", "立即开奖"),
  83. BotCommand("gcancel", "取消抽奖并退款"),
  84. BotCommand("greroll", "重新抽取中奖者"),
  85. BotCommand("rules", "查看本群群规"),
  86. ]
  87. async def bot_sys_stats() -> str:
  88. uptime = max(0, int(time.time() - bot_start_time))
  89. days, remainder = divmod(uptime, 86400)
  90. hours, remainder = divmod(remainder, 3600)
  91. minutes, seconds = divmod(remainder, 60)
  92. duration = f"{days} 天 {hours} 小时 {minutes} 分 {seconds} 秒"
  93. process = psutil.Process(os.getpid())
  94. return (
  95. f"机器人:{BOT_NAME}\n"
  96. f"运行时长:{duration}\n"
  97. f"进程内存:{round(process.memory_info().rss / 1024 ** 2)} MB\n"
  98. f"处理器:{psutil.cpu_percent()}%\n"
  99. f"系统内存:{psutil.virtual_memory().percent}%\n"
  100. f"磁盘:{psutil.disk_usage('/').percent}%"
  101. )
  102. async def start_bot():
  103. global HELPABLE
  104. for module in ALL_MODULES:
  105. imported_module = importlib.import_module("wbb.modules." + module)
  106. if (
  107. module in HELP_MODULE_KEYS
  108. and hasattr(imported_module, "__MODULE__")
  109. and imported_module.__MODULE__
  110. ):
  111. if (
  112. hasattr(imported_module, "__HELP__")
  113. and imported_module.__HELP__
  114. ):
  115. HELPABLE[module] = imported_module
  116. bot_modules = ""
  117. j = 1
  118. for i in ALL_MODULES:
  119. if j == 4:
  120. bot_modules += "|{:<15}|\n".format(i)
  121. j = 0
  122. else:
  123. bot_modules += "|{:<15}".format(i)
  124. j += 1
  125. print("+===============================================================+")
  126. print("| WBB |")
  127. print("+===============+===============+===============+===============+")
  128. print(bot_modules)
  129. print("+===============+===============+===============+===============+")
  130. log.info(f"机器人已启动:{BOT_NAME}")
  131. if USERBOT_CONNECTED:
  132. log.info(f"用户客户端已启动:{USERBOT_NAME}")
  133. try:
  134. await app.set_bot_commands(BOT_COMMANDS)
  135. log.info("已注册中文 Telegram 命令菜单")
  136. except Exception as exc:
  137. log.error(f"注册 Telegram 命令菜单失败:{exc}")
  138. restart_data = await clean_restart_stage()
  139. try:
  140. log.info("正在发送上线状态")
  141. if restart_data:
  142. await app.edit_message_text(
  143. restart_data["chat_id"],
  144. restart_data["message_id"],
  145. "**机器人已成功重启。**",
  146. )
  147. else:
  148. await app.send_message(LOG_GROUP_ID, "机器人已启动。")
  149. except Exception:
  150. pass
  151. await idle()
  152. await aiohttpsession.close()
  153. log.info("正在停止客户端")
  154. await app.stop()
  155. if USERBOT_CONNECTED:
  156. await app2.stop()
  157. log.info("正在取消异步任务")
  158. for task in asyncio.all_tasks():
  159. task.cancel()
  160. log.info("机器人已停止")
  161. home_keyboard_pm = InlineKeyboardMarkup(
  162. [
  163. [
  164. InlineKeyboardButton(
  165. text="功能帮助", callback_data="bot_commands"
  166. ),
  167. InlineKeyboardButton(
  168. text="群组管理",
  169. callback_data="manage_hint",
  170. ),
  171. ],
  172. [
  173. InlineKeyboardButton(
  174. text="运行状态",
  175. callback_data="stats_callback",
  176. )
  177. ],
  178. [
  179. InlineKeyboardButton(
  180. text="添加机器人到群组",
  181. url=f"http://t.me/{BOT_USERNAME}?startgroup=new",
  182. )
  183. ],
  184. ]
  185. )
  186. home_text_pm = (
  187. f"你好,我是 {BOT_NAME}。\n\n"
  188. "我可以协助管理群组、自动回复、风控、积分和抽奖。"
  189. "群管理员可发送 /manage 打开私聊管理菜单。"
  190. )
  191. keyboard = InlineKeyboardMarkup(
  192. [
  193. [
  194. InlineKeyboardButton(
  195. text="查看帮助",
  196. url=f"t.me/{BOT_USERNAME}?start=help",
  197. ),
  198. InlineKeyboardButton(
  199. text="运行状态",
  200. callback_data="stats_callback",
  201. ),
  202. ],
  203. ]
  204. )
  205. FED_MARKUP = InlineKeyboardMarkup(
  206. [
  207. [
  208. InlineKeyboardButton(
  209. "联邦创建者命令", callback_data="fed_owner"
  210. ),
  211. InlineKeyboardButton(
  212. "联邦管理员命令", callback_data="fed_admin"
  213. ),
  214. ],
  215. [
  216. InlineKeyboardButton("普通成员命令", callback_data="fed_user"),
  217. ],
  218. [
  219. InlineKeyboardButton("返回", callback_data="help_back"),
  220. ],
  221. ]
  222. )
  223. @app.on_message(filters.command("start"))
  224. async def start(_, message):
  225. if message.chat.type != ChatType.PRIVATE:
  226. return await message.reply(
  227. "请私聊我查看详细功能。", reply_markup=keyboard
  228. )
  229. if len(message.text.split()) > 1:
  230. user = await app.get_users(message.from_user.id)
  231. name = (message.text.split(None, 1)[1]).lower()
  232. match = re.match(r"rules_(.*)", name)
  233. if match:
  234. chat_id = match.group(1)
  235. user_id = message.from_user.id
  236. chat = await app.get_chat(int(chat_id))
  237. text = f"**`{chat.title}` 的群规:\n\n**"
  238. rules = await get_rules(int(chat_id))
  239. if rules:
  240. text = text + rules
  241. if "{chat}" in text:
  242. text = text.replace("{chat}", chat.title)
  243. if "{name}" in text:
  244. text = text.replace("{name}", user.mention)
  245. keyb = None
  246. if re.findall(r"\[.+\,.+\]", text):
  247. text, keyb = extract_text_and_keyb(ikb, text)
  248. await app.send_message(user_id, text=text, reply_markup=keyb)
  249. else:
  250. await app.send_message(
  251. user_id,
  252. "该群管理员尚未设置群规。",
  253. )
  254. return
  255. if name == "mkdwn_help":
  256. await message.reply(
  257. MARKDOWN,
  258. parse_mode=ParseMode.HTML,
  259. disable_web_page_preview=True,
  260. )
  261. elif "_" in name:
  262. module = name.split("_", 1)[1]
  263. if module not in HELPABLE:
  264. return await message.reply("未找到对应的中文帮助内容。")
  265. text = (
  266. f"以下是 **{HELPABLE[module].__MODULE__}** 的使用说明:\n"
  267. + HELPABLE[module].__HELP__
  268. )
  269. if module == "feds":
  270. return await message.reply(
  271. text=text,
  272. reply_markup=FED_MARKUP,
  273. disable_web_page_preview=True,
  274. )
  275. await message.reply(
  276. text,
  277. reply_markup=InlineKeyboardMarkup(
  278. [[InlineKeyboardButton("返回", callback_data="help_back")]]
  279. ),
  280. disable_web_page_preview=True,
  281. )
  282. elif name == "help":
  283. text, keyb = await help_parser(message.from_user.first_name)
  284. await message.reply(
  285. text,
  286. reply_markup=keyb,
  287. )
  288. else:
  289. await message.reply(
  290. home_text_pm,
  291. reply_markup=home_keyboard_pm,
  292. )
  293. return
  294. @app.on_message(filters.command("help"))
  295. async def help_command(_, message):
  296. if message.chat.type != ChatType.PRIVATE:
  297. if len(message.command) >= 2:
  298. name = (message.text.split(None, 1)[1]).replace(" ", "_").lower()
  299. if str(name) in HELPABLE:
  300. key = InlineKeyboardMarkup(
  301. [
  302. [
  303. InlineKeyboardButton(
  304. text="查看帮助",
  305. url=f"t.me/{BOT_USERNAME}?start=help_{name}",
  306. )
  307. ],
  308. ]
  309. )
  310. await message.reply(
  311. f"点击下方按钮查看“{HELPABLE[name].__MODULE__}”的帮助。",
  312. reply_markup=key,
  313. )
  314. else:
  315. await message.reply(
  316. "请私聊我查看详细功能。", reply_markup=keyboard
  317. )
  318. else:
  319. await message.reply(
  320. "请私聊我查看详细功能。", reply_markup=keyboard
  321. )
  322. else:
  323. if len(message.command) >= 2:
  324. name = (message.text.split(None, 1)[1]).replace(" ", "_").lower()
  325. if str(name) in HELPABLE:
  326. text = (
  327. f"以下是 **{HELPABLE[name].__MODULE__}** 的使用说明:\n"
  328. + HELPABLE[name].__HELP__
  329. )
  330. await message.reply(text, disable_web_page_preview=True)
  331. else:
  332. text, help_keyboard = await help_parser(
  333. message.from_user.first_name
  334. )
  335. await message.reply(
  336. text,
  337. reply_markup=help_keyboard,
  338. disable_web_page_preview=True,
  339. )
  340. else:
  341. text, help_keyboard = await help_parser(
  342. message.from_user.first_name
  343. )
  344. await message.reply(
  345. text, reply_markup=help_keyboard, disable_web_page_preview=True
  346. )
  347. return
  348. async def help_parser(name, keyboard=None):
  349. if not keyboard:
  350. keyboard = InlineKeyboardMarkup(paginate_modules(0, HELPABLE, "help"))
  351. return (
  352. """你好,{first_name},我是 {bot_name}。
  353. 我可以管理群组、自动回复、积分和抽奖。
  354. 请选择下方功能分类查看中文命令说明。
  355. """.format(
  356. first_name=name,
  357. bot_name=BOT_NAME,
  358. ),
  359. keyboard,
  360. )
  361. @app.on_callback_query(filters.regex("bot_commands"))
  362. async def commands_callbacc(_, CallbackQuery):
  363. text, keyboard = await help_parser(CallbackQuery.from_user.mention)
  364. await app.send_message(
  365. CallbackQuery.message.chat.id,
  366. text=text,
  367. reply_markup=keyboard,
  368. )
  369. await CallbackQuery.message.delete()
  370. @app.on_callback_query(filters.regex("^manage_hint$"))
  371. async def manage_hint_callback(_, query):
  372. await query.answer("请发送 /manage 打开群组管理菜单。", show_alert=True)
  373. @app.on_callback_query(filters.regex("stats_callback"))
  374. async def stats_callbacc(_, CallbackQuery):
  375. text = await bot_sys_stats()
  376. await app.answer_callback_query(CallbackQuery.id, text, show_alert=True)
  377. @app.on_callback_query(filters.regex(r"help_(.*?)"))
  378. async def help_button(client, query):
  379. home_match = re.match(r"help_home\((.+?)\)", query.data)
  380. mod_match = re.match(r"help_module\((.+?)\)", query.data)
  381. prev_match = re.match(r"help_prev\((.+?)\)", query.data)
  382. next_match = re.match(r"help_next\((.+?)\)", query.data)
  383. back_match = re.match(r"help_back", query.data)
  384. create_match = re.match(r"help_create", query.data)
  385. top_text = f"""
  386. 你好,{query.from_user.first_name},我是 {BOT_NAME}。
  387. 我可以管理群组、自动回复、积分和抽奖。
  388. 请选择下方功能分类查看中文命令说明。
  389. 常用命令:
  390. - /start:打开机器人首页
  391. - /help:查看功能帮助
  392. - /manage:打开私聊群管理菜单
  393. """
  394. if mod_match:
  395. module = (mod_match.group(1)).replace(" ", "_")
  396. text = (
  397. "{} **{}**:\n".format(
  398. "以下是使用说明", HELPABLE[module].__MODULE__
  399. )
  400. + HELPABLE[module].__HELP__
  401. )
  402. if module == "feds":
  403. return await query.message.edit(
  404. text=text,
  405. reply_markup=FED_MARKUP,
  406. disable_web_page_preview=True,
  407. )
  408. await query.message.edit(
  409. text=text,
  410. reply_markup=InlineKeyboardMarkup(
  411. [[InlineKeyboardButton("返回", callback_data="help_back")]]
  412. ),
  413. disable_web_page_preview=True,
  414. )
  415. elif home_match:
  416. await app.send_message(
  417. query.from_user.id,
  418. text=home_text_pm,
  419. reply_markup=home_keyboard_pm,
  420. )
  421. await query.message.delete()
  422. elif prev_match:
  423. curr_page = int(prev_match.group(1))
  424. await query.message.edit(
  425. text=top_text,
  426. reply_markup=InlineKeyboardMarkup(
  427. paginate_modules(curr_page - 1, HELPABLE, "help")
  428. ),
  429. disable_web_page_preview=True,
  430. )
  431. elif next_match:
  432. next_page = int(next_match.group(1))
  433. await query.message.edit(
  434. text=top_text,
  435. reply_markup=InlineKeyboardMarkup(
  436. paginate_modules(next_page + 1, HELPABLE, "help")
  437. ),
  438. disable_web_page_preview=True,
  439. )
  440. elif back_match:
  441. await query.message.edit(
  442. text=top_text,
  443. reply_markup=InlineKeyboardMarkup(
  444. paginate_modules(0, HELPABLE, "help")
  445. ),
  446. disable_web_page_preview=True,
  447. )
  448. elif create_match:
  449. text, keyboard = await help_parser(query)
  450. await query.message.edit(
  451. text=text,
  452. reply_markup=keyboard,
  453. disable_web_page_preview=True,
  454. )
  455. return await client.answer_callback_query(query.id)
  456. if __name__ == "__main__":
  457. install()
  458. with closing(loop):
  459. with suppress(asyncio.exceptions.CancelledError):
  460. loop.run_until_complete(start_bot())
  461. loop.run_until_complete(asyncio.sleep(3.0)) # task cancel wait