hikka_settings.py 47 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232
  1. # █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
  2. # █▀█ █ █ █ █▀█ █▀▄ █
  3. # © Copyright 2022
  4. # https://t.me/hikariatama
  5. #
  6. # 🔒 Licensed under the GNU AGPLv3
  7. # 🌐 https://www.gnu.org/licenses/agpl-3.0.html
  8. import logging
  9. import atexit
  10. import random
  11. import sys
  12. import os
  13. import telethon
  14. from telethon.tl.types import Message
  15. from telethon.tl.functions.messages import (
  16. GetDialogFiltersRequest,
  17. UpdateDialogFilterRequest,
  18. )
  19. from telethon.tl.functions.channels import JoinChannelRequest
  20. from telethon.utils import get_display_name
  21. from .. import loader, main, utils
  22. from ..inline.types import InlineCall
  23. logger = logging.getLogger(__name__)
  24. ALL_INVOKES = [
  25. "clear_entity_cache",
  26. "clear_fulluser_cache",
  27. "clear_fullchannel_cache",
  28. "clear_perms_cache",
  29. "clear_cache",
  30. "reload_core",
  31. "inspect_cache",
  32. "inspect_modules",
  33. ]
  34. def restart(*argv):
  35. os.execl(
  36. sys.executable,
  37. sys.executable,
  38. "-m",
  39. os.path.relpath(utils.get_base_dir()),
  40. *argv,
  41. )
  42. @loader.tds
  43. class HikkaSettingsMod(loader.Module):
  44. """Advanced settings for Hikka Userbot"""
  45. strings = {
  46. "name": "HikkaSettings",
  47. "watchers": (
  48. "<emoji document_id=5424885441100782420>👀</emoji>"
  49. " <b>Watchers:</b>\n\n<b>{}</b>"
  50. ),
  51. "no_args": (
  52. "<emoji document_id=5447207618793708263>🚫</emoji> <b>No arguments"
  53. " specified</b>"
  54. ),
  55. "invoke404": (
  56. "<emoji document_id=5447207618793708263>🚫</emoji> <b>Internal debug method"
  57. " </b><code>{}</code><b> not found, ergo can't be invoked</b>"
  58. ),
  59. "module404": (
  60. "<emoji document_id=5447207618793708263>🚫</emoji> <b>Module</b>"
  61. " <code>{}</code> <b>not found</b>"
  62. ),
  63. "invoke": (
  64. "<emoji document_id=5215519585150706301>👍</emoji> <b>Invoked internal debug"
  65. " method </b><code>{}</code>\n\n<emoji"
  66. " document_id=5784891605601225888>🔵</emoji> <b>Result: \n{}</b>"
  67. ),
  68. "invoking": (
  69. "<emoji document_id=5213452215527677338>⏳</emoji> <b>Invoking internal"
  70. " debug method </b><code>{}</code><b> of </b><code>{}</code><b>...</b>"
  71. ),
  72. "mod404": (
  73. "<emoji document_id=5447207618793708263>🚫</emoji> <b>Watcher {} not"
  74. " found</b>"
  75. ),
  76. "disabled": (
  77. "<emoji document_id=5424885441100782420>👀</emoji> <b>Watcher {} is now"
  78. " <u>disabled</u></b>"
  79. ),
  80. "enabled": (
  81. "<emoji document_id=5424885441100782420>👀</emoji> <b>Watcher {} is now"
  82. " <u>enabled</u></b>"
  83. ),
  84. "args": (
  85. "<emoji document_id=5447207618793708263>🚫</emoji> <b>You need to specify"
  86. " watcher name</b>"
  87. ),
  88. "user_nn": (
  89. "<emoji document_id=5469791106591890404>🪄</emoji> <b>NoNick for this user"
  90. " is now {}</b>"
  91. ),
  92. "no_cmd": (
  93. "<emoji document_id=5469791106591890404>🪄</emoji> <b>Please, specify"
  94. " command to toggle NoNick for</b>"
  95. ),
  96. "cmd_nn": (
  97. "<emoji document_id=5469791106591890404>🪄</emoji> <b>NoNick for"
  98. " </b><code>{}</code><b> is now {}</b>"
  99. ),
  100. "cmd404": (
  101. "<emoji document_id=5469791106591890404>🪄</emoji> <b>Command not found</b>"
  102. ),
  103. "inline_settings": "⚙️ <b>Here you can configure your Hikka settings</b>",
  104. "confirm_update": (
  105. "🧭 <b>Please, confirm that you want to update. Your userbot will be"
  106. " restarted</b>"
  107. ),
  108. "confirm_restart": "🔄 <b>Please, confirm that you want to restart</b>",
  109. "suggest_fs": "✅ Suggest FS for modules",
  110. "do_not_suggest_fs": "🚫 Suggest FS for modules",
  111. "use_fs": "✅ Always use FS for modules",
  112. "do_not_use_fs": "🚫 Always use FS for modules",
  113. "btn_restart": "🔄 Restart",
  114. "btn_update": "🧭 Update",
  115. "close_menu": "😌 Close menu",
  116. "custom_emojis": "✅ Custom emojis",
  117. "no_custom_emojis": "🚫 Custom emojis",
  118. "suggest_subscribe": "✅ Suggest subscribe to channel",
  119. "do_not_suggest_subscribe": "🚫 Suggest subscribe to channel",
  120. "private_not_allowed": (
  121. "<emoji document_id=5447207618793708263>🚫</emoji> <b>This command must be"
  122. " executed in chat</b>"
  123. ),
  124. "nonick_warning": (
  125. "Warning! You enabled NoNick with default prefix! "
  126. "You may get muted in Hikka chats. Change prefix or "
  127. "disable NoNick!"
  128. ),
  129. "reply_required": (
  130. "<emoji document_id=5447207618793708263>🚫</emoji> <b>Reply to a message"
  131. " of user, which needs to be added to NoNick</b>"
  132. ),
  133. "deauth_confirm": (
  134. "⚠️ <b>This action will fully remove Hikka from this account and can't be"
  135. " reverted!</b>\n\n<i>- Hikka chats will be removed\n- Session will be"
  136. " terminated and removed\n- Hikka inline bot will be removed</i>"
  137. ),
  138. "deauth_confirm_step2": (
  139. "⚠️ <b>Are you really sure you want to delete Hikka?</b>"
  140. ),
  141. "deauth_yes": "I'm sure",
  142. "deauth_no_1": "I'm not sure",
  143. "deauth_no_2": "I'm uncertain",
  144. "deauth_no_3": "I'm struggling to answer",
  145. "deauth_cancel": "🚫 Cancel",
  146. "deauth_confirm_btn": "😢 Delete",
  147. "uninstall": "😢 <b>Uninstalling Hikka...</b>",
  148. "uninstalled": (
  149. "😢 <b>Hikka uninstalled. Web interface is still active, you can add another"
  150. " account</b>"
  151. ),
  152. "logs_cleared": "🗑 <b>Logs cleared</b>",
  153. "cmd_nn_list": (
  154. "<emoji document_id=5469791106591890404>🪄</emoji> <b>NoNick is enabled"
  155. " for these commands:</b>\n\n{}"
  156. ),
  157. "user_nn_list": (
  158. "<emoji document_id=5469791106591890404>🪄</emoji> <b>NoNick is enabled"
  159. " for these users:</b>\n\n{}"
  160. ),
  161. "chat_nn_list": (
  162. "<emoji document_id=5469791106591890404>🪄</emoji> <b>NoNick is enabled"
  163. " for these chats:</b>\n\n{}"
  164. ),
  165. "nothing": (
  166. "<emoji document_id=5427052514094619126>🤷‍♀️</emoji> <b>Nothing to"
  167. " show...</b>"
  168. ),
  169. "privacy_leak": (
  170. "⚠️ <b>This command gives access to your Hikka web interface. It's not"
  171. " recommended to run it in public group chats. Consider using it in <a"
  172. " href='tg://openmessage?user_id={}'>Saved messages</a>. Type"
  173. " </b><code>{}proxypass force_insecure</code><b> to ignore this warning</b>"
  174. ),
  175. "privacy_leak_nowarn": (
  176. "⚠️ <b>This command gives access to your Hikka web interface. It's not"
  177. " recommended to run it in public group chats. Consider using it in <a"
  178. " href='tg://openmessage?user_id={}'>Saved messages</a>.</b>"
  179. ),
  180. "opening_tunnel": "🔁 <b>Opening tunnel to Hikka web interface...</b>",
  181. "tunnel_opened": "🎉 <b>Tunnel opened. This link is valid for about 1 hour</b>",
  182. "web_btn": "🌍 Web interface",
  183. "btn_yes": "🚸 Open anyway",
  184. "btn_no": "🔻 Cancel",
  185. "lavhost_web": (
  186. "✌️ <b>This link leads to your Hikka web interface on lavHost</b>\n\n<i>💡"
  187. " You'll need to authorize using lavHost credentials, specified on"
  188. " registration</i>"
  189. ),
  190. "disable_stats": "✅ Anonymous stats allowed",
  191. "enable_stats": "🚫 Anonymous stats disabled",
  192. }
  193. strings_ru = {
  194. "watchers": (
  195. "<emoji document_id=5424885441100782420>👀</emoji>"
  196. " <b>Смотрители:</b>\n\n<b>{}</b>"
  197. ),
  198. "mod404": (
  199. "<emoji document_id=5447207618793708263>🚫</emoji> <b>Смотритель {} не"
  200. " найден</b>"
  201. ),
  202. "disabled": (
  203. "<emoji document_id=5424885441100782420>👀</emoji> <b>Смотритель {} теперь"
  204. " <u>выключен</u></b>"
  205. ),
  206. "enabled": (
  207. "<emoji document_id=5424885441100782420>👀</emoji> <b>Смотритель {} теперь"
  208. " <u>включен</u></b>"
  209. ),
  210. "args": (
  211. "<emoji document_id=5447207618793708263>🚫</emoji> <b>Укажи имя"
  212. " смотрителя</b>"
  213. ),
  214. "user_nn": (
  215. "<emoji document_id=5469791106591890404>🪄</emoji> <b>Состояние NoNick для"
  216. " этого пользователя: {}</b>"
  217. ),
  218. "no_cmd": (
  219. "<emoji document_id=5469791106591890404>🪄</emoji> <b>Укажи команду, для"
  220. " которой надо включить\\выключить NoNick</b>"
  221. ),
  222. "cmd_nn": (
  223. "<emoji document_id=5469791106591890404>🪄</emoji> <b>Состояние NoNick для"
  224. " </b><code>{}</code><b>: {}</b>"
  225. ),
  226. "cmd404": (
  227. "<emoji document_id=5469791106591890404>🪄</emoji> <b>Команда не найдена</b>"
  228. ),
  229. "inline_settings": "⚙️ <b>Здесь можно управлять настройками Hikka</b>",
  230. "confirm_update": "🧭 <b>Подтвердите обновление. Юзербот будет перезагружен</b>",
  231. "confirm_restart": "🔄 <b>Подтвердите перезагрузку</b>",
  232. "suggest_fs": "✅ Предлагать сохранение модулей",
  233. "do_not_suggest_fs": "🚫 Предлагать сохранение модулей",
  234. "use_fs": "✅ Всегда сохранять модули",
  235. "do_not_use_fs": "🚫 Всегда сохранять модули",
  236. "btn_restart": "🔄 Перезагрузка",
  237. "btn_update": "🧭 Обновление",
  238. "close_menu": "😌 Закрыть меню",
  239. "custom_emojis": "✅ Кастомные эмодзи",
  240. "no_custom_emojis": "🚫 Кастомные эмодзи",
  241. "suggest_subscribe": "✅ Предлагать подписку на канал",
  242. "do_not_suggest_subscribe": "🚫 Предлагать подписку на канал",
  243. "private_not_allowed": (
  244. "<emoji document_id=5447207618793708263>🚫</emoji> <b>Эту команду нужно"
  245. " выполнять в чате</b>"
  246. ),
  247. "_cls_doc": "Дополнительные настройки Hikka",
  248. "nonick_warning": (
  249. "Внимание! Ты включил NoNick со стандартным префиксом! "
  250. "Тебя могут замьютить в чатах Hikka. Измени префикс или "
  251. "отключи глобальный NoNick!"
  252. ),
  253. "reply_required": (
  254. "<emoji document_id=5447207618793708263>🚫</emoji> <b>Ответь на сообщение"
  255. " пользователя, для которого нужно включить NoNick</b>"
  256. ),
  257. "deauth_confirm": (
  258. "⚠️ <b>Это действие полностью удалит Hikka с этого аккаунта! Его нельзя"
  259. " отменить</b>\n\n<i>- Все чаты, связанные с Hikka будут удалены\n- Сессия"
  260. " Hikka будет сброшена\n- Инлайн бот Hikka будет удален</i>"
  261. ),
  262. "deauth_confirm_step2": "⚠️ <b>Ты точно уверен, что хочешь удалить Hikka?</b>",
  263. "deauth_yes": "Я уверен",
  264. "deauth_no_1": "Я не уверен",
  265. "deauth_no_2": "Не точно",
  266. "deauth_no_3": "Нет",
  267. "deauth_cancel": "🚫 Отмена",
  268. "deauth_confirm_btn": "😢 Удалить",
  269. "uninstall": "😢 <b>Удаляю Hikka...</b>",
  270. "uninstalled": (
  271. "😢 <b>Hikka удалена. Веб-интерфейс все еще активен, можно добавить другие"
  272. " аккаунты!</b>"
  273. ),
  274. "logs_cleared": "🗑 <b>Логи очищены</b>",
  275. "cmd_nn_list": (
  276. "<emoji document_id=5469791106591890404>🪄</emoji> <b>NoNick включен для"
  277. " этих команд:</b>\n\n{}"
  278. ),
  279. "user_nn_list": (
  280. "<emoji document_id=5469791106591890404>🪄</emoji> <b>NoNick включен для"
  281. " этих пользователей:</b>\n\n{}"
  282. ),
  283. "chat_nn_list": (
  284. "<emoji document_id=5469791106591890404>🪄</emoji> <b>NoNick включен для"
  285. " этих чатов:</b>\n\n{}"
  286. ),
  287. "nothing": (
  288. "<emoji document_id=5427052514094619126>🤷‍♀️</emoji> <b>Нечего"
  289. " показывать...</b>"
  290. ),
  291. "privacy_leak": (
  292. "⚠️ <b>Эта команда дает доступ к веб-интерфейсу Hikka. Ее выполнение в"
  293. " публичных чатах является угрозой безопасности. Предпочтительно выполнять"
  294. " ее в <a href='tg://openmessage?user_id={}'>Избранных сообщениях</a>."
  295. " Выполни </b><code>{}proxypass force_insecure</code><b> чтобы отключить"
  296. " это предупреждение</b>"
  297. ),
  298. "privacy_leak_nowarn": (
  299. "⚠️ <b>Эта команда дает доступ к веб-интерфейсу Hikka. Ее выполнение в"
  300. " публичных чатах является угрозой безопасности. Предпочтительно выполнять"
  301. " ее в <a href='tg://openmessage?user_id={}'>Избранных сообщениях</a>.</b>"
  302. ),
  303. "opening_tunnel": "🔁 <b>Открываю тоннель к веб-интерфейсу Hikka...</b>",
  304. "tunnel_opened": (
  305. "🎉 <b>Тоннель открыт. Эта ссылка будет активна не более часа</b>"
  306. ),
  307. "web_btn": "🌍 Веб-интерфейс",
  308. "btn_yes": "🚸 Все равно открыть",
  309. "btn_no": "🔻 Закрыть",
  310. "lavhost_web": (
  311. "✌️ <b>По этой ссылке ты попадешь в веб-интерфейс Hikka на"
  312. " lavHost</b>\n\n<i>💡 Тебе нужно будет авторизоваться, используя данные,"
  313. " указанные при настройке lavHost</i>"
  314. ),
  315. "disable_stats": "✅ Анонимная стата разрешена",
  316. "enable_stats": "🚫 Анонимная стата запрещена",
  317. }
  318. def get_watchers(self) -> tuple:
  319. return [
  320. str(watcher.__self__.__class__.strings["name"])
  321. for watcher in self.allmodules.watchers
  322. if watcher.__self__.__class__.strings is not None
  323. ], self._db.get(main.__name__, "disabled_watchers", {})
  324. async def _uninstall(self, call: InlineCall):
  325. await call.edit(self.strings("uninstall"))
  326. async with self._client.conversation("@BotFather") as conv:
  327. for msg in [
  328. "/deletebot",
  329. f"@{self.inline.bot_username}",
  330. "Yes, I am totally sure.",
  331. ]:
  332. m = await conv.send_message(msg)
  333. r = await conv.get_response()
  334. logger.debug(">> %s", m.raw_text)
  335. logger.debug("<< %s", r.raw_text)
  336. await m.delete()
  337. await r.delete()
  338. async for dialog in self._client.iter_dialogs(
  339. None,
  340. ignore_migrated=True,
  341. ):
  342. if (
  343. dialog.name
  344. in {
  345. "hikka-logs",
  346. "hikka-onload",
  347. "hikka-assets",
  348. "hikka-backups",
  349. "hikka-acc-switcher",
  350. "silent-tags",
  351. }
  352. and dialog.is_channel
  353. and (
  354. dialog.entity.participants_count == 1
  355. or dialog.entity.participants_count == 2
  356. and dialog.name in {"hikka-logs", "silent-tags"}
  357. )
  358. or (
  359. self._client.loader.inline.init_complete
  360. and dialog.entity.id == self._client.loader.inline.bot_id
  361. )
  362. ):
  363. await self._client.delete_dialog(dialog.entity)
  364. folders = await self._client(GetDialogFiltersRequest())
  365. if any(folder.title == "hikka" for folder in folders):
  366. folder_id = max(
  367. folders,
  368. key=lambda x: x.id,
  369. ).id
  370. await self._client(UpdateDialogFilterRequest(id=folder_id))
  371. for handler in logging.getLogger().handlers:
  372. handler.setLevel(logging.CRITICAL)
  373. await self._client.log_out()
  374. await call.edit(self.strings("uninstalled"))
  375. if "LAVHOST" in os.environ:
  376. os.system("lavhost restart")
  377. return
  378. atexit.register(restart, *sys.argv[1:])
  379. sys.exit(0)
  380. async def _uninstall_confirm_step_2(self, call: InlineCall):
  381. await call.edit(
  382. self.strings("deauth_confirm_step2"),
  383. utils.chunks(
  384. list(
  385. sorted(
  386. [
  387. {
  388. "text": self.strings("deauth_yes"),
  389. "callback": self._uninstall,
  390. },
  391. *[
  392. {
  393. "text": self.strings(f"deauth_no_{i}"),
  394. "action": "close",
  395. }
  396. for i in range(1, 4)
  397. ],
  398. ],
  399. key=lambda _: random.random(),
  400. )
  401. ),
  402. 2,
  403. )
  404. + [
  405. [
  406. {
  407. "text": self.strings("deauth_cancel"),
  408. "action": "close",
  409. }
  410. ]
  411. ],
  412. )
  413. @loader.owner
  414. @loader.command(ru_doc="Удалить Hikka")
  415. async def uninstall_hikka(self, message: Message):
  416. """Uninstall Hikka"""
  417. await self.inline.form(
  418. self.strings("deauth_confirm"),
  419. message,
  420. [
  421. {
  422. "text": self.strings("deauth_confirm_btn"),
  423. "callback": self._uninstall_confirm_step_2,
  424. },
  425. {"text": self.strings("deauth_cancel"), "action": "close"},
  426. ],
  427. )
  428. @loader.command(ru_doc="Очистить логи")
  429. async def clearlogs(self, message: Message):
  430. """Clear logs"""
  431. for handler in logging.getLogger().handlers:
  432. handler.buffer = []
  433. handler.handledbuffer = []
  434. handler.tg_buff = ""
  435. await utils.answer(message, self.strings("logs_cleared"))
  436. @loader.command(ru_doc="Показать активные смотрители")
  437. async def watchers(self, message: Message):
  438. """List current watchers"""
  439. watchers, disabled_watchers = self.get_watchers()
  440. watchers = [
  441. f"♻️ {watcher}"
  442. for watcher in watchers
  443. if watcher not in list(disabled_watchers.keys())
  444. ]
  445. watchers += [f"💢 {k} {v}" for k, v in disabled_watchers.items()]
  446. await utils.answer(
  447. message, self.strings("watchers").format("\n".join(watchers))
  448. )
  449. @loader.command(ru_doc="<module> - Включить/выключить смотрителя в текущем чате")
  450. async def watcherbl(self, message: Message):
  451. """<module> - Toggle watcher in current chat"""
  452. args = utils.get_args_raw(message)
  453. if not args:
  454. await utils.answer(message, self.strings("args"))
  455. return
  456. watchers, disabled_watchers = self.get_watchers()
  457. if args.lower() not in map(lambda x: x.lower(), watchers):
  458. await utils.answer(message, self.strings("mod404").format(args))
  459. return
  460. args = next((x.lower() == args.lower() for x in watchers), False)
  461. current_bl = [
  462. v for k, v in disabled_watchers.items() if k.lower() == args.lower()
  463. ]
  464. current_bl = current_bl[0] if current_bl else []
  465. chat = utils.get_chat_id(message)
  466. if chat not in current_bl:
  467. if args in disabled_watchers:
  468. for k in disabled_watchers:
  469. if k.lower() == args.lower():
  470. disabled_watchers[k].append(chat)
  471. break
  472. else:
  473. disabled_watchers[args] = [chat]
  474. await utils.answer(
  475. message,
  476. self.strings("disabled").format(args) + " <b>in current chat</b>",
  477. )
  478. else:
  479. for k in disabled_watchers.copy():
  480. if k.lower() == args.lower():
  481. disabled_watchers[k].remove(chat)
  482. if not disabled_watchers[k]:
  483. del disabled_watchers[k]
  484. break
  485. await utils.answer(
  486. message,
  487. self.strings("enabled").format(args) + " <b>in current chat</b>",
  488. )
  489. self._db.set(main.__name__, "disabled_watchers", disabled_watchers)
  490. @loader.command(
  491. ru_doc=(
  492. "<модуль> - Управление глобальными правилами смотрителя\n"
  493. "Аргументы:\n"
  494. "[-c - только в чатах]\n"
  495. "[-p - только в лс]\n"
  496. "[-o - только исходящие]\n"
  497. "[-i - только входящие]"
  498. )
  499. )
  500. async def watchercmd(self, message: Message):
  501. """<module> - Toggle global watcher rules
  502. Args:
  503. [-c - only in chats]
  504. [-p - only in pm]
  505. [-o - only out]
  506. [-i - only incoming]"""
  507. args = utils.get_args_raw(message)
  508. if not args:
  509. return await utils.answer(message, self.strings("args"))
  510. chats, pm, out, incoming = False, False, False, False
  511. if "-c" in args:
  512. args = args.replace("-c", "").replace(" ", " ").strip()
  513. chats = True
  514. if "-p" in args:
  515. args = args.replace("-p", "").replace(" ", " ").strip()
  516. pm = True
  517. if "-o" in args:
  518. args = args.replace("-o", "").replace(" ", " ").strip()
  519. out = True
  520. if "-i" in args:
  521. args = args.replace("-i", "").replace(" ", " ").strip()
  522. incoming = True
  523. if chats and pm:
  524. pm = False
  525. if out and incoming:
  526. incoming = False
  527. watchers, disabled_watchers = self.get_watchers()
  528. if args.lower() not in [watcher.lower() for watcher in watchers]:
  529. return await utils.answer(message, self.strings("mod404").format(args))
  530. args = [watcher for watcher in watchers if watcher.lower() == args.lower()][0]
  531. if chats or pm or out or incoming:
  532. disabled_watchers[args] = [
  533. *(["only_chats"] if chats else []),
  534. *(["only_pm"] if pm else []),
  535. *(["out"] if out else []),
  536. *(["in"] if incoming else []),
  537. ]
  538. self._db.set(main.__name__, "disabled_watchers", disabled_watchers)
  539. await utils.answer(
  540. message,
  541. self.strings("enabled").format(args)
  542. + f" (<code>{disabled_watchers[args]}</code>)",
  543. )
  544. return
  545. if args in disabled_watchers and "*" in disabled_watchers[args]:
  546. await utils.answer(message, self.strings("enabled").format(args))
  547. del disabled_watchers[args]
  548. self._db.set(main.__name__, "disabled_watchers", disabled_watchers)
  549. return
  550. disabled_watchers[args] = ["*"]
  551. self._db.set(main.__name__, "disabled_watchers", disabled_watchers)
  552. await utils.answer(message, self.strings("disabled").format(args))
  553. @loader.command(ru_doc="Включить NoNick для определенного пользователя")
  554. async def nonickuser(self, message: Message):
  555. """Allow no nickname for certain user"""
  556. reply = await message.get_reply_message()
  557. if not reply:
  558. await utils.answer(message, self.strings("reply_required"))
  559. return
  560. u = reply.sender_id
  561. if not isinstance(u, int):
  562. u = u.user_id
  563. nn = self._db.get(main.__name__, "nonickusers", [])
  564. if u not in nn:
  565. nn += [u]
  566. nn = list(set(nn)) # skipcq: PTC-W0018
  567. await utils.answer(message, self.strings("user_nn").format("on"))
  568. else:
  569. nn = list(set(nn) - {u})
  570. await utils.answer(message, self.strings("user_nn").format("off"))
  571. self._db.set(main.__name__, "nonickusers", nn)
  572. @loader.command(ru_doc="Включить NoNick для определенного чата")
  573. async def nonickchat(self, message: Message):
  574. """Allow no nickname in certain chat"""
  575. if message.is_private:
  576. await utils.answer(message, self.strings("private_not_allowed"))
  577. return
  578. chat = utils.get_chat_id(message)
  579. nn = self._db.get(main.__name__, "nonickchats", [])
  580. if chat not in nn:
  581. nn += [chat]
  582. nn = list(set(nn)) # skipcq: PTC-W0018
  583. await utils.answer(
  584. message,
  585. self.strings("cmd_nn").format(
  586. utils.escape_html((await message.get_chat()).title),
  587. "on",
  588. ),
  589. )
  590. else:
  591. nn = list(set(nn) - {chat})
  592. await utils.answer(
  593. message,
  594. self.strings("cmd_nn").format(
  595. utils.escape_html((await message.get_chat()).title),
  596. "off",
  597. ),
  598. )
  599. self._db.set(main.__name__, "nonickchats", nn)
  600. @loader.command(ru_doc="Включить NoNick для определенной команды")
  601. async def nonickcmdcmd(self, message: Message):
  602. """Allow certain command to be executed without nickname"""
  603. args = utils.get_args_raw(message)
  604. if not args:
  605. await utils.answer(message, self.strings("no_cmd"))
  606. return
  607. if args not in self.allmodules.commands:
  608. await utils.answer(message, self.strings("cmd404"))
  609. return
  610. nn = self._db.get(main.__name__, "nonickcmds", [])
  611. if args not in nn:
  612. nn += [args]
  613. nn = list(set(nn))
  614. await utils.answer(
  615. message,
  616. self.strings("cmd_nn").format(
  617. self.get_prefix() + args,
  618. "on",
  619. ),
  620. )
  621. else:
  622. nn = list(set(nn) - {args})
  623. await utils.answer(
  624. message,
  625. self.strings("cmd_nn").format(
  626. self.get_prefix() + args,
  627. "off",
  628. ),
  629. )
  630. self._db.set(main.__name__, "nonickcmds", nn)
  631. @loader.command(ru_doc="Показать список активных NoNick команд")
  632. async def nonickcmds(self, message: Message):
  633. """Returns the list of NoNick commands"""
  634. if not self._db.get(main.__name__, "nonickcmds", []):
  635. await utils.answer(message, self.strings("nothing"))
  636. return
  637. await utils.answer(
  638. message,
  639. self.strings("cmd_nn_list").format(
  640. "\n".join(
  641. [
  642. f"▫️ <code>{self.get_prefix()}{cmd}</code>"
  643. for cmd in self._db.get(main.__name__, "nonickcmds", [])
  644. ]
  645. )
  646. ),
  647. )
  648. @loader.command(ru_doc="Показать список активных NoNick пользователей")
  649. async def nonickusers(self, message: Message):
  650. """Returns the list of NoNick users"""
  651. users = []
  652. for user_id in self._db.get(main.__name__, "nonickusers", []).copy():
  653. try:
  654. user = await self._client.get_entity(user_id)
  655. except Exception:
  656. self._db.set(
  657. main.__name__,
  658. "nonickusers",
  659. list(
  660. (
  661. set(self._db.get(main.__name__, "nonickusers", []))
  662. - {user_id}
  663. )
  664. ),
  665. )
  666. logger.warning("User %s removed from nonickusers list", user_id)
  667. continue
  668. users += [
  669. '▫️ <b><a href="tg://user?id={}">{}</a></b>'.format(
  670. user_id,
  671. utils.escape_html(get_display_name(user)),
  672. )
  673. ]
  674. if not users:
  675. await utils.answer(message, self.strings("nothing"))
  676. return
  677. await utils.answer(
  678. message,
  679. self.strings("user_nn_list").format("\n".join(users)),
  680. )
  681. @loader.command(ru_doc="Показать список активных NoNick чатов")
  682. async def nonickchats(self, message: Message):
  683. """Returns the list of NoNick chats"""
  684. chats = []
  685. for chat in self._db.get(main.__name__, "nonickchats", []):
  686. try:
  687. chat_entity = await self._client.get_entity(int(chat))
  688. except Exception:
  689. self._db.set(
  690. main.__name__,
  691. "nonickchats",
  692. list(
  693. (set(self._db.get(main.__name__, "nonickchats", [])) - {chat})
  694. ),
  695. )
  696. logger.warning("Chat %s removed from nonickchats list", chat)
  697. continue
  698. chats += [
  699. '▫️ <b><a href="{}">{}</a></b>'.format(
  700. utils.get_entity_url(chat_entity),
  701. utils.escape_html(get_display_name(chat_entity)),
  702. )
  703. ]
  704. if not chats:
  705. await utils.answer(message, self.strings("nothing"))
  706. return
  707. await utils.answer(
  708. message,
  709. self.strings("user_nn_list").format("\n".join(chats)),
  710. )
  711. async def inline__setting(self, call: InlineCall, key: str, state: bool = False):
  712. if callable(key):
  713. key()
  714. telethon.extensions.html.CUSTOM_EMOJIS = not main.get_config_key(
  715. "disable_custom_emojis"
  716. )
  717. else:
  718. self._db.set(main.__name__, key, state)
  719. if key == "no_nickname" and state and self.get_prefix() == ".":
  720. await call.answer(
  721. self.strings("nonick_warning"),
  722. show_alert=True,
  723. )
  724. else:
  725. await call.answer("Configuration value saved!")
  726. await call.edit(
  727. self.strings("inline_settings"),
  728. reply_markup=self._get_settings_markup(),
  729. )
  730. async def inline__update(
  731. self,
  732. call: InlineCall,
  733. confirm_required: bool = False,
  734. ):
  735. if confirm_required:
  736. await call.edit(
  737. self.strings("confirm_update"),
  738. reply_markup=[
  739. {"text": "🪂 Update", "callback": self.inline__update},
  740. {"text": "🚫 Cancel", "action": "close"},
  741. ],
  742. )
  743. return
  744. await call.answer("You userbot is being updated...", show_alert=True)
  745. await call.delete()
  746. m = await self._client.send_message("me", f"{self.get_prefix()}update --force")
  747. await self.allmodules.commands["update"](m)
  748. async def inline__restart(
  749. self,
  750. call: InlineCall,
  751. confirm_required: bool = False,
  752. ):
  753. if confirm_required:
  754. await call.edit(
  755. self.strings("confirm_restart"),
  756. reply_markup=[
  757. {"text": "🔄 Restart", "callback": self.inline__restart},
  758. {"text": "🚫 Cancel", "action": "close"},
  759. ],
  760. )
  761. return
  762. await call.answer("You userbot is being restarted...", show_alert=True)
  763. await call.delete()
  764. await self.allmodules.commands["restart"](
  765. await self._client.send_message("me", f"{self.get_prefix()}restart --force")
  766. )
  767. def _get_settings_markup(self) -> list:
  768. return [
  769. [
  770. (
  771. {
  772. "text": "✅ NoNick",
  773. "callback": self.inline__setting,
  774. "args": (
  775. "no_nickname",
  776. False,
  777. ),
  778. }
  779. if self._db.get(main.__name__, "no_nickname", False)
  780. else {
  781. "text": "🚫 NoNick",
  782. "callback": self.inline__setting,
  783. "args": (
  784. "no_nickname",
  785. True,
  786. ),
  787. }
  788. ),
  789. (
  790. {
  791. "text": "✅ Grep",
  792. "callback": self.inline__setting,
  793. "args": (
  794. "grep",
  795. False,
  796. ),
  797. }
  798. if self._db.get(main.__name__, "grep", False)
  799. else {
  800. "text": "🚫 Grep",
  801. "callback": self.inline__setting,
  802. "args": (
  803. "grep",
  804. True,
  805. ),
  806. }
  807. ),
  808. (
  809. {
  810. "text": "✅ InlineLogs",
  811. "callback": self.inline__setting,
  812. "args": (
  813. "inlinelogs",
  814. False,
  815. ),
  816. }
  817. if self._db.get(main.__name__, "inlinelogs", True)
  818. else {
  819. "text": "🚫 InlineLogs",
  820. "callback": self.inline__setting,
  821. "args": (
  822. "inlinelogs",
  823. True,
  824. ),
  825. }
  826. ),
  827. ],
  828. [
  829. {
  830. "text": self.strings("do_not_suggest_fs"),
  831. "callback": self.inline__setting,
  832. "args": (
  833. "disable_modules_fs",
  834. False,
  835. ),
  836. }
  837. if self._db.get(main.__name__, "disable_modules_fs", False)
  838. else {
  839. "text": self.strings("suggest_fs"),
  840. "callback": self.inline__setting,
  841. "args": (
  842. "disable_modules_fs",
  843. True,
  844. ),
  845. }
  846. ],
  847. [
  848. (
  849. {
  850. "text": self.strings("use_fs"),
  851. "callback": self.inline__setting,
  852. "args": (
  853. "permanent_modules_fs",
  854. False,
  855. ),
  856. }
  857. if self._db.get(main.__name__, "permanent_modules_fs", False)
  858. else {
  859. "text": self.strings("do_not_use_fs"),
  860. "callback": self.inline__setting,
  861. "args": (
  862. "permanent_modules_fs",
  863. True,
  864. ),
  865. }
  866. ),
  867. ],
  868. [
  869. (
  870. {
  871. "text": self.strings("suggest_subscribe"),
  872. "callback": self.inline__setting,
  873. "args": (
  874. "suggest_subscribe",
  875. False,
  876. ),
  877. }
  878. if self._db.get(main.__name__, "suggest_subscribe", True)
  879. else {
  880. "text": self.strings("do_not_suggest_subscribe"),
  881. "callback": self.inline__setting,
  882. "args": (
  883. "suggest_subscribe",
  884. True,
  885. ),
  886. }
  887. ),
  888. ],
  889. [
  890. (
  891. {
  892. "text": self.strings("no_custom_emojis"),
  893. "callback": self.inline__setting,
  894. "args": (
  895. lambda: main.save_config_key(
  896. "disable_custom_emojis", False
  897. ),
  898. ),
  899. }
  900. if main.get_config_key("disable_custom_emojis")
  901. else {
  902. "text": self.strings("custom_emojis"),
  903. "callback": self.inline__setting,
  904. "args": (
  905. lambda: main.save_config_key("disable_custom_emojis", True),
  906. ),
  907. }
  908. ),
  909. ],
  910. [
  911. (
  912. {
  913. "text": self.strings("disable_stats"),
  914. "callback": self.inline__setting,
  915. "args": ("stats", False),
  916. }
  917. if self._db.get(main.__name__, "stats", True)
  918. else {
  919. "text": self.strings("enable_stats"),
  920. "callback": self.inline__setting,
  921. "args": (
  922. "stats",
  923. True,
  924. ),
  925. }
  926. ),
  927. ],
  928. [
  929. {
  930. "text": self.strings("btn_restart"),
  931. "callback": self.inline__restart,
  932. "args": (True,),
  933. },
  934. {
  935. "text": self.strings("btn_update"),
  936. "callback": self.inline__update,
  937. "args": (True,),
  938. },
  939. ],
  940. [{"text": self.strings("close_menu"), "action": "close"}],
  941. ]
  942. @loader.owner
  943. @loader.command(ru_doc="Показать настройки")
  944. async def settings(self, message: Message):
  945. """Show settings menu"""
  946. await self.inline.form(
  947. self.strings("inline_settings"),
  948. message=message,
  949. reply_markup=self._get_settings_markup(),
  950. )
  951. @loader.owner
  952. @loader.command(ru_doc="Открыть тоннель к веб-интерфейсу Hikka")
  953. async def weburl(self, message: Message, force: bool = False):
  954. """Opens web tunnel to your Hikka web interface"""
  955. if "LAVHOST" in os.environ:
  956. form = await self.inline.form(
  957. self.strings("lavhost_web"),
  958. message=message,
  959. reply_markup={
  960. "text": self.strings("web_btn"),
  961. "url": await main.hikka.web.get_url(proxy_pass=False),
  962. },
  963. gif="https://t.me/hikari_assets/28",
  964. )
  965. return
  966. if (
  967. not force
  968. and not message.is_private
  969. and "force_insecure" not in message.raw_text.lower()
  970. ):
  971. try:
  972. if not await self.inline.form(
  973. self.strings("privacy_leak_nowarn").format(self._client.tg_id),
  974. message=message,
  975. reply_markup=[
  976. {
  977. "text": self.strings("btn_yes"),
  978. "callback": self.weburl,
  979. "args": (True,),
  980. },
  981. {"text": self.strings("btn_no"), "action": "close"},
  982. ],
  983. gif="https://i.gifer.com/embedded/download/Z5tS.gif",
  984. ):
  985. raise Exception
  986. except Exception:
  987. await utils.answer(
  988. message,
  989. self.strings("privacy_leak").format(
  990. self._client.tg_id,
  991. self.get_prefix(),
  992. ),
  993. )
  994. return
  995. if force:
  996. form = message
  997. await form.edit(
  998. self.strings("opening_tunnel"),
  999. reply_markup={"text": "🕔 Wait...", "data": "empty"},
  1000. gif=(
  1001. "https://i.gifer.com/origin/e4/e43e1b221fd960003dc27d2f2f1b8ce1.gif"
  1002. ),
  1003. )
  1004. else:
  1005. form = await self.inline.form(
  1006. self.strings("opening_tunnel"),
  1007. message=message,
  1008. reply_markup={"text": "🕔 Wait...", "data": "empty"},
  1009. gif=(
  1010. "https://i.gifer.com/origin/e4/e43e1b221fd960003dc27d2f2f1b8ce1.gif"
  1011. ),
  1012. )
  1013. url = await main.hikka.web.get_url(proxy_pass=True)
  1014. await form.edit(
  1015. self.strings("tunnel_opened"),
  1016. reply_markup={"text": self.strings("web_btn"), "url": url},
  1017. gif="https://t.me/hikari_assets/48",
  1018. )
  1019. @loader.loop(interval=1, autostart=True)
  1020. async def loop(self):
  1021. obj = self.allmodules.get_approved_channel
  1022. if not obj:
  1023. return
  1024. channel, event = obj
  1025. try:
  1026. await self._client(JoinChannelRequest(channel))
  1027. except Exception:
  1028. logger.exception("Failed to join channel")
  1029. event.status = False
  1030. event.set()
  1031. else:
  1032. event.status = True
  1033. event.set()
  1034. def _get_all_IDM(self, module: str):
  1035. return {
  1036. getattr(getattr(self.lookup(module), name), "name", name): getattr(
  1037. self.lookup(module), name
  1038. )
  1039. for name in dir(self.lookup(module))
  1040. if getattr(getattr(self.lookup(module), name), "is_debug_method", False)
  1041. }
  1042. @loader.command()
  1043. async def invoke(self, message: Message):
  1044. """<module or `core` for built-in methods> <method> - Only for debugging purposes. DO NOT USE IF YOU'RE NOT A DEVELOPER
  1045. """
  1046. args = utils.get_args_raw(message)
  1047. if not args or len(args.split()) < 2:
  1048. await utils.answer(message, self.strings("no_args"))
  1049. return
  1050. module = args.split()[0]
  1051. method = args.split(maxsplit=1)[1]
  1052. if module != "core" and not self.lookup(module):
  1053. await utils.answer(message, self.strings("module404").format(module))
  1054. return
  1055. if (
  1056. module == "core"
  1057. and method not in ALL_INVOKES
  1058. or module != "core"
  1059. and method not in self._get_all_IDM(module)
  1060. ):
  1061. await utils.answer(message, self.strings("invoke404").format(method))
  1062. return
  1063. message = await utils.answer(
  1064. message, self.strings("invoking").format(method, module)
  1065. )
  1066. result = ""
  1067. if module == "core":
  1068. if method == "clear_entity_cache":
  1069. result = (
  1070. f"Dropped {len(self._client._hikka_entity_cache)} cache records"
  1071. )
  1072. self._client._hikka_entity_cache = {}
  1073. elif method == "clear_fulluser_cache":
  1074. result = (
  1075. f"Dropped {len(self._client._hikka_fulluser_cache)} cache records"
  1076. )
  1077. self._client._hikka_fulluser_cache = {}
  1078. elif method == "clear_fullchannel_cache":
  1079. result = (
  1080. f"Dropped {len(self._client._hikka_fullchannel_cache)} cache"
  1081. " records"
  1082. )
  1083. self._client._hikka_fullchannel_cache = {}
  1084. elif method == "clear_perms_cache":
  1085. result = f"Dropped {len(self._client._hikka_perms_cache)} cache records"
  1086. self._client._hikka_perms_cache = {}
  1087. elif method == "clear_cache":
  1088. result = (
  1089. f"Dropped {len(self._client._hikka_entity_cache)} entity cache"
  1090. " records\nDropped"
  1091. f" {len(self._client._hikka_fulluser_cache)} fulluser cache"
  1092. " records\nDropped"
  1093. f" {len(self._client._hikka_fullchannel_cache)} fullchannel cache"
  1094. " records"
  1095. )
  1096. self._client._hikka_entity_cache = {}
  1097. self._client._hikka_fulluser_cache = {}
  1098. self._client._hikka_fullchannel_cache = {}
  1099. elif method == "reload_core":
  1100. core_quantity = await self.lookup("loader").reload_core()
  1101. result = f"Reloaded {core_quantity} core modules"
  1102. elif method == "inspect_cache":
  1103. result = (
  1104. "Entity cache:"
  1105. f" {len(self._client._hikka_entity_cache)} records\nFulluser cache:"
  1106. f" {len(self._client._hikka_fulluser_cache)} records\nFullchannel"
  1107. f" cache: {len(self._client._hikka_fullchannel_cache)} records"
  1108. )
  1109. elif method == "inspect_modules":
  1110. result = (
  1111. "Loaded modules: {}\nLoaded core modules: {}\nLoaded user"
  1112. " modules: {}"
  1113. ).format(
  1114. len(self.allmodules.modules),
  1115. sum(
  1116. module.__origin__.startswith("<core")
  1117. for module in self.allmodules.modules
  1118. ),
  1119. sum(
  1120. not module.__origin__.startswith("<core")
  1121. for module in self.allmodules.modules
  1122. ),
  1123. )
  1124. else:
  1125. result = await self._get_all_IDM(module)[method](message)
  1126. await utils.answer(
  1127. message,
  1128. self.strings("invoke").format(method, utils.escape_html(result)),
  1129. )