hikka_security.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611
  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. # scope: inline
  9. import logging
  10. from typing import List, Union
  11. from telethon.tl.types import Message, PeerUser, User
  12. from telethon.utils import get_display_name
  13. from .. import loader, security, utils, main
  14. from ..inline.types import InlineCall
  15. from ..security import (
  16. DEFAULT_PERMISSIONS,
  17. EVERYONE,
  18. GROUP_ADMIN,
  19. GROUP_ADMIN_ADD_ADMINS,
  20. GROUP_ADMIN_BAN_USERS,
  21. GROUP_ADMIN_CHANGE_INFO,
  22. GROUP_ADMIN_DELETE_MESSAGES,
  23. GROUP_ADMIN_INVITE_USERS,
  24. GROUP_ADMIN_PIN_MESSAGES,
  25. GROUP_MEMBER,
  26. GROUP_OWNER,
  27. PM,
  28. SUDO,
  29. SUPPORT,
  30. )
  31. logger = logging.getLogger(__name__)
  32. @loader.tds
  33. class HikkaSecurityMod(loader.Module):
  34. """Control security settings"""
  35. strings = {
  36. "name": "HikkaSecurity",
  37. "no_command": "🚫 <b>Command </b><code>{}</code><b> not found!</b>",
  38. "permissions": (
  39. "🔐 <b>Here you can configure permissions for </b><code>{}{}</code>"
  40. ),
  41. "close_menu": "🙈 Close this menu",
  42. "global": (
  43. "🔐 <b>Here you can configure global bounding mask. If the permission is"
  44. " excluded here, it is excluded everywhere!</b>"
  45. ),
  46. "owner": "🤴 Owner",
  47. "sudo": "🤵 Sudo",
  48. "support": "🧑‍🔧 Support",
  49. "group_owner": "🧛‍♂️ Group owner",
  50. "group_admin_add_admins": "🧑‍⚖️ Admin (add members)",
  51. "group_admin_change_info": "🧑‍⚖️ Admin (change info)",
  52. "group_admin_ban_users": "🧑‍⚖️ Admin (ban)",
  53. "group_admin_delete_messages": "🧑‍⚖️ Admin (delete msgs)",
  54. "group_admin_pin_messages": "🧑‍⚖️ Admin (pin)",
  55. "group_admin_invite_users": "🧑‍⚖️ Admin (invite)",
  56. "group_admin": "🧑‍⚖️ Admin (any)",
  57. "group_member": "👥 In group",
  58. "pm": "🤙 In PM",
  59. "everyone": "🌍 Everyone (Inline)",
  60. "owner_list": "🤴 <b>Users in group </b><code>owner</code><b>:</b>\n\n{}",
  61. "sudo_list": "🧑‍✈️ <b>Users in group </b><code>sudo</code><b>:</b>\n\n{}",
  62. "support_list": "🧑‍🔧 <b>Users in group </b><code>support</code><b>:</b>\n\n{}",
  63. "no_owner": "🤴 <b>There is no users in group </b><code>owner</code>",
  64. "no_sudo": "🧑‍✈️ <b>There is no users in group </b><code>sudo</code>",
  65. "no_support": "🧑‍🔧 <b>There is no users in group </b><code>support</code>",
  66. "owner_added": (
  67. '🤴 <b><a href="tg://user?id={}">{}</a> added to group'
  68. " </b><code>owner</code>"
  69. ),
  70. "sudo_added": (
  71. '🧑‍✈️ <b><a href="tg://user?id={}">{}</a> added to group'
  72. " </b><code>sudo</code>"
  73. ),
  74. "support_added": (
  75. '🧑‍🔧 <b><a href="tg://user?id={}">{}</a> added to group'
  76. " </b><code>support</code>"
  77. ),
  78. "owner_removed": (
  79. '🤴 <b><a href="tg://user?id={}">{}</a> removed from group'
  80. " </b><code>owner</code>"
  81. ),
  82. "sudo_removed": (
  83. '🧑‍✈️ <b><a href="tg://user?id={}">{}</a> removed from group'
  84. " </b><code>sudo</code>"
  85. ),
  86. "support_removed": (
  87. '🧑‍🔧 <b><a href="tg://user?id={}">{}</a> removed from group'
  88. " </b><code>support</code>"
  89. ),
  90. "no_user": "🚫 <b>Specify user to permit</b>",
  91. "not_a_user": "🚫 <b>Specified entity is not a user</b>",
  92. "li": '⦿ <b><a href="tg://user?id={}">{}</a></b>',
  93. "warning": (
  94. "⚠️ <b>Please, confirm, that you want to add <a"
  95. ' href="tg://user?id={}">{}</a> to group </b><code>{}</code><b>!\nThis'
  96. " action may reveal personal info and grant full or partial access to"
  97. " userbot to this user</b>"
  98. ),
  99. "cancel": "🚫 Cancel",
  100. "confirm": "👑 Confirm",
  101. "enable_nonick_btn": "🔰 Enable",
  102. "self": "🚫 <b>You can't promote/demote yourself!</b>",
  103. "suggest_nonick": "🔰 <i>Do you want to enable NoNick for this user?</i>",
  104. "user_nn": '🔰 <b>NoNick for <a href="tg://user?id={}">{}</a> enabled</b>',
  105. }
  106. strings_ru = {
  107. "no_command": "🚫 <b>Команда </b><code>{}</code><b> не найдена!</b>",
  108. "permissions": (
  109. "🔐 <b>Здесь можно настроить разрешения для команды </b><code>{}{}</code>"
  110. ),
  111. "close_menu": "🙈 Закрыть это меню",
  112. "global": (
  113. "🔐 <b>Здесь можно настроить глобальную исключающую маску. Если тумблер"
  114. " выключен здесь, он выключен для всех команд</b>"
  115. ),
  116. "owner": "🤴 Владелец",
  117. "sudo": "🤵 Sudo",
  118. "support": "🧑‍🔧 Помощник",
  119. "group_owner": "🧛‍♂️ Влад. группы",
  120. "group_admin_add_admins": "🧑‍⚖️ Админ (добавлять участников)",
  121. "group_admin_change_info": "🧑‍⚖️ Админ (изменять инфо)",
  122. "group_admin_ban_users": "🧑‍⚖️ Админ (банить)",
  123. "group_admin_delete_messages": "🧑‍⚖️ Админ (удалять сообщения)",
  124. "group_admin_pin_messages": "🧑‍⚖️ Админ (закреплять)",
  125. "group_admin_invite_users": "🧑‍⚖️ Админ (приглашать)",
  126. "group_admin": "🧑‍⚖️ Админ (любой)",
  127. "group_member": "👥 В группе",
  128. "pm": "🤙 В лс",
  129. "owner_list": "🤴 <b>Пользователи группы </b><code>owner</code><b>:</b>\n\n{}",
  130. "sudo_list": "🧑‍✈️ <b>Пользователи группы </b><code>sudo</code><b>:</b>\n\n{}",
  131. "support_list": (
  132. "🧑‍🔧 <b>Пользователи группы </b><code>support</code><b>:</b>\n\n{}"
  133. ),
  134. "no_owner": "🤴 <b>Нет пользователей в группе </b><code>owner</code>",
  135. "no_sudo": "🧑‍✈️ <b>Нет пользователей в группе </b><code>sudo</code>",
  136. "no_support": "🧑‍🔧 <b>Нет пользователей в группе </b><code>support</code>",
  137. "no_user": "🚫 <b>Укажи, кому выдавать права</b>",
  138. "not_a_user": "🚫 <b>Указанная цель - не пользователь</b>",
  139. "cancel": "🚫 Отмена",
  140. "confirm": "👑 Подтвердить",
  141. "self": "🚫 <b>Нельзя управлять своими правами!</b>",
  142. "warning": (
  143. '⚠️ <b>Ты действительно хочешь добавить <a href="tg://user?id={}">{}</a> в'
  144. " группу </b><code>{}</code><b>!\nЭто действие может передать частичный или"
  145. " полный доступ к юзерботу этому пользователю!</b>"
  146. ),
  147. "suggest_nonick": (
  148. "🔰 <i>Хочешь ли ты включить NoNick для этого пользователя?</i>"
  149. ),
  150. "user_nn": '🔰 <b>NoNick для <a href="tg://user?id={}">{}</a> включен</b>',
  151. "enable_nonick_btn": "🔰 Включить",
  152. "_cmd_doc_security": "[команда] - Изменить настройки безопасности для команды",
  153. "_cmd_doc_sudoadd": "<пользователь> - Добавить пользователя в группу `sudo`",
  154. "_cmd_doc_owneradd": "<пользователь> - Добавить пользователя в группу `owner`",
  155. "_cmd_doc_supportadd": (
  156. "<пользователь> - Добавить пользователя в группу `support`"
  157. ),
  158. "_cmd_doc_sudorm": "<пользователь> - Удалить пользователя из группы `sudo`",
  159. "_cmd_doc_ownerrm": "<пользователь> - Удалить пользователя из группы `owner`",
  160. "_cmd_doc_supportrm": (
  161. "<пользователь> - Удалить пользователя из группы `support`"
  162. ),
  163. "_cmd_doc_sudolist": "Показать пользователей в группе `sudo`",
  164. "_cmd_doc_ownerlist": "Показать пользователей в группе `owner`",
  165. "_cmd_doc_supportlist": "Показать пользователей в группе `support`",
  166. "_cls_doc": "Управление настройками безопасности",
  167. }
  168. async def inline__switch_perm(
  169. self,
  170. call: InlineCall,
  171. command: str,
  172. group: str,
  173. level: bool,
  174. is_inline: bool,
  175. ):
  176. cmd = (
  177. self.allmodules.inline_handlers[command]
  178. if is_inline
  179. else self.allmodules.commands[command]
  180. )
  181. mask = self._db.get(security.__name__, "masks", {}).get(
  182. f"{cmd.__module__}.{cmd.__name__}",
  183. getattr(cmd, "security", security.DEFAULT_PERMISSIONS),
  184. )
  185. bit = security.BITMAP[group.upper()]
  186. if level:
  187. mask |= bit
  188. else:
  189. mask &= ~bit
  190. masks = self._db.get(security.__name__, "masks", {})
  191. masks[f"{cmd.__module__}.{cmd.__name__}"] = mask
  192. self._db.set(security.__name__, "masks", masks)
  193. if (
  194. not self._db.get(security.__name__, "bounding_mask", DEFAULT_PERMISSIONS)
  195. & bit
  196. and level
  197. ):
  198. await call.answer(
  199. "Security value set but not applied. Consider enabling this value in"
  200. f" .{'inlinesec' if is_inline else 'security'}",
  201. show_alert=True,
  202. )
  203. else:
  204. await call.answer("Security value set!")
  205. await call.edit(
  206. self.strings("permissions").format(
  207. f"@{self.inline.bot_username} " if is_inline else self.get_prefix(),
  208. command,
  209. ),
  210. reply_markup=self._build_markup(cmd, is_inline),
  211. )
  212. async def inline__switch_perm_bm(
  213. self,
  214. call: InlineCall,
  215. group: str,
  216. level: bool,
  217. is_inline: bool,
  218. ):
  219. mask = self._db.get(security.__name__, "bounding_mask", DEFAULT_PERMISSIONS)
  220. bit = security.BITMAP[group.upper()]
  221. if level:
  222. mask |= bit
  223. else:
  224. mask &= ~bit
  225. self._db.set(security.__name__, "bounding_mask", mask)
  226. await call.answer("Bounding mask value set!")
  227. await call.edit(
  228. self.strings("global"),
  229. reply_markup=self._build_markup_global(is_inline),
  230. )
  231. def _build_markup(
  232. self,
  233. command: callable,
  234. is_inline: bool = False,
  235. ) -> List[List[dict]]:
  236. perms = self._get_current_perms(command, is_inline)
  237. return (
  238. utils.chunks(
  239. [
  240. {
  241. "text": f"{'✅' if level else '🚫'} {self.strings[group]}",
  242. "callback": self.inline__switch_perm,
  243. "args": (
  244. command.__name__.rsplit("_inline_handler", maxsplit=1)[0],
  245. group,
  246. not level,
  247. is_inline,
  248. ),
  249. }
  250. for group, level in perms.items()
  251. ],
  252. 2,
  253. )
  254. + [[{"text": self.strings("close_menu"), "action": "close"}]]
  255. if is_inline
  256. else utils.chunks(
  257. [
  258. {
  259. "text": f"{'✅' if level else '🚫'} {self.strings[group]}",
  260. "callback": self.inline__switch_perm,
  261. "args": (
  262. command.__name__.rsplit("cmd", maxsplit=1)[0],
  263. group,
  264. not level,
  265. is_inline,
  266. ),
  267. }
  268. for group, level in perms.items()
  269. ],
  270. 2,
  271. )
  272. + [
  273. [
  274. {
  275. "text": self.strings("close_menu"),
  276. "action": "close",
  277. }
  278. ]
  279. ]
  280. )
  281. def _build_markup_global(self, is_inline: bool = False) -> List[List[dict]]:
  282. perms = self._get_current_bm(is_inline)
  283. return utils.chunks(
  284. [
  285. {
  286. "text": f"{'✅' if level else '🚫'} {self.strings[group]}",
  287. "callback": self.inline__switch_perm_bm,
  288. "args": (group, not level, is_inline),
  289. }
  290. for group, level in perms.items()
  291. ],
  292. 2,
  293. ) + [[{"text": self.strings("close_menu"), "action": "close"}]]
  294. def _get_current_bm(self, is_inline: bool = False) -> dict:
  295. return self._perms_map(
  296. self._db.get(security.__name__, "bounding_mask", DEFAULT_PERMISSIONS),
  297. is_inline,
  298. )
  299. @staticmethod
  300. def _perms_map(perms: int, is_inline: bool) -> dict:
  301. return (
  302. {
  303. "sudo": bool(perms & SUDO),
  304. "support": bool(perms & SUPPORT),
  305. "everyone": bool(perms & EVERYONE),
  306. }
  307. if is_inline
  308. else {
  309. "sudo": bool(perms & SUDO),
  310. "support": bool(perms & SUPPORT),
  311. "group_owner": bool(perms & GROUP_OWNER),
  312. "group_admin_add_admins": bool(perms & GROUP_ADMIN_ADD_ADMINS),
  313. "group_admin_change_info": bool(perms & GROUP_ADMIN_CHANGE_INFO),
  314. "group_admin_ban_users": bool(perms & GROUP_ADMIN_BAN_USERS),
  315. "group_admin_delete_messages": bool(
  316. perms & GROUP_ADMIN_DELETE_MESSAGES
  317. ),
  318. "group_admin_pin_messages": bool(perms & GROUP_ADMIN_PIN_MESSAGES),
  319. "group_admin_invite_users": bool(perms & GROUP_ADMIN_INVITE_USERS),
  320. "group_admin": bool(perms & GROUP_ADMIN),
  321. "group_member": bool(perms & GROUP_MEMBER),
  322. "pm": bool(perms & PM),
  323. "everyone": bool(perms & EVERYONE),
  324. }
  325. )
  326. def _get_current_perms(
  327. self,
  328. command: callable,
  329. is_inline: bool = False,
  330. ) -> dict:
  331. config = self._db.get(security.__name__, "masks", {}).get(
  332. f"{command.__module__}.{command.__name__}",
  333. getattr(command, "security", self._client.dispatcher.security._default),
  334. )
  335. return self._perms_map(config, is_inline)
  336. @loader.owner
  337. async def securitycmd(self, message: Message):
  338. """[command] - Configure command's security settings"""
  339. args = utils.get_args_raw(message).lower().strip()
  340. if args and args not in self.allmodules.commands:
  341. await utils.answer(message, self.strings("no_command").format(args))
  342. return
  343. if not args:
  344. await self.inline.form(
  345. self.strings("global"),
  346. reply_markup=self._build_markup_global(),
  347. message=message,
  348. ttl=5 * 60,
  349. )
  350. return
  351. cmd = self.allmodules.commands[args]
  352. await self.inline.form(
  353. self.strings("permissions").format(self.get_prefix(), args),
  354. reply_markup=self._build_markup(cmd),
  355. message=message,
  356. ttl=5 * 60,
  357. )
  358. @loader.owner
  359. async def inlineseccmd(self, message: Message):
  360. """[command] - Configure inline command's security settings"""
  361. args = utils.get_args_raw(message).lower().strip()
  362. if not args:
  363. await self.inline.form(
  364. self.strings("global"),
  365. reply_markup=self._build_markup_global(True),
  366. message=message,
  367. ttl=5 * 60,
  368. )
  369. return
  370. if args not in self.allmodules.inline_handlers:
  371. await utils.answer(message, self.strings("no_command").format(args))
  372. return
  373. i_handler = self.allmodules.inline_handlers[args]
  374. await self.inline.form(
  375. self.strings("permissions").format(f"@{self.inline.bot_username} ", args),
  376. reply_markup=self._build_markup(i_handler, True),
  377. message=message,
  378. ttl=5 * 60,
  379. )
  380. async def _resolve_user(self, message: Message):
  381. reply = await message.get_reply_message()
  382. args = utils.get_args_raw(message)
  383. if not args and not reply:
  384. await utils.answer(message, self.strings("no_user"))
  385. return
  386. user = None
  387. if args:
  388. try:
  389. if str(args).isdigit():
  390. args = int(args)
  391. user = await self._client.get_entity(args)
  392. except Exception:
  393. pass
  394. if user is None:
  395. user = await self._client.get_entity(reply.sender_id)
  396. if not isinstance(user, (User, PeerUser)):
  397. await utils.answer(message, self.strings("not_a_user"))
  398. return
  399. if user.id == self.tg_id:
  400. await utils.answer(message, self.strings("self"))
  401. return
  402. return user
  403. async def _add_to_group(
  404. self,
  405. message: Union[Message, InlineCall], # noqa: F821
  406. group: str,
  407. confirmed: bool = False,
  408. user: int = None,
  409. ):
  410. if user is None:
  411. user = await self._resolve_user(message)
  412. if not user:
  413. return
  414. if isinstance(user, int):
  415. user = await self._client.get_entity(user)
  416. if not confirmed:
  417. await self.inline.form(
  418. self.strings("warning").format(
  419. user.id,
  420. utils.escape_html(get_display_name(user)),
  421. group,
  422. ),
  423. message=message,
  424. ttl=10 * 60,
  425. reply_markup=[
  426. {
  427. "text": self.strings("cancel"),
  428. "action": "close",
  429. },
  430. {
  431. "text": self.strings("confirm"),
  432. "callback": self._add_to_group,
  433. "args": (group, True, user.id),
  434. },
  435. ],
  436. )
  437. return
  438. self._db.set(
  439. security.__name__,
  440. group,
  441. list(set(self._db.get(security.__name__, group, []) + [user.id])),
  442. )
  443. m = (
  444. self.strings(f"{group}_added").format(
  445. user.id,
  446. utils.escape_html(get_display_name(user)),
  447. )
  448. + "\n\n"
  449. + self.strings("suggest_nonick")
  450. )
  451. await utils.answer(message, m)
  452. await message.edit(
  453. m,
  454. reply_markup=[
  455. {
  456. "text": self.strings("cancel"),
  457. "action": "close",
  458. },
  459. {
  460. "text": self.strings("enable_nonick_btn"),
  461. "callback": self._enable_nonick,
  462. "args": (user,),
  463. },
  464. ],
  465. )
  466. async def _enable_nonick(self, call: InlineCall, user: User):
  467. self._db.set(
  468. main.__name__,
  469. "nonickusers",
  470. list(set(self._db.get(main.__name__, "nonickusers", []) + [user.id])),
  471. )
  472. await call.edit(
  473. self.strings("user_nn").format(
  474. user.id,
  475. utils.escape_html(get_display_name(user)),
  476. )
  477. )
  478. await call.unload()
  479. async def _remove_from_group(self, message: Message, group: str):
  480. user = await self._resolve_user(message)
  481. if not user:
  482. return
  483. self._db.set(
  484. security.__name__,
  485. group,
  486. list(set(self._db.get(security.__name__, group, [])) - {user.id}),
  487. )
  488. m = self.strings(f"{group}_removed").format(
  489. user.id,
  490. utils.escape_html(get_display_name(user)),
  491. )
  492. await utils.answer(message, m)
  493. async def _list_group(self, message: Message, group: str):
  494. _resolved_users = []
  495. for user in self._db.get(security.__name__, group, []) + (
  496. [self.tg_id] if group == "owner" else []
  497. ):
  498. try:
  499. _resolved_users += [await self._client.get_entity(user)]
  500. except Exception:
  501. pass
  502. if _resolved_users:
  503. await utils.answer(
  504. message,
  505. self.strings(f"{group}_list").format(
  506. "\n".join(
  507. [
  508. self.strings("li").format(
  509. i.id, utils.escape_html(get_display_name(i))
  510. )
  511. for i in _resolved_users
  512. ]
  513. )
  514. ),
  515. )
  516. else:
  517. await utils.answer(message, self.strings(f"no_{group}"))
  518. async def sudoaddcmd(self, message: Message):
  519. """<user> - Add user to `sudo`"""
  520. await self._add_to_group(message, "sudo")
  521. async def owneraddcmd(self, message: Message):
  522. """<user> - Add user to `owner`"""
  523. await self._add_to_group(message, "owner")
  524. async def supportaddcmd(self, message: Message):
  525. """<user> - Add user to `support`"""
  526. await self._add_to_group(message, "support")
  527. async def sudormcmd(self, message: Message):
  528. """<user> - Remove user from `sudo`"""
  529. await self._remove_from_group(message, "sudo")
  530. async def ownerrmcmd(self, message: Message):
  531. """<user> - Remove user from `owner`"""
  532. await self._remove_from_group(message, "owner")
  533. async def supportrmcmd(self, message: Message):
  534. """<user> - Remove user from `support`"""
  535. await self._remove_from_group(message, "support")
  536. async def sudolistcmd(self, message: Message):
  537. """List users in `sudo`"""
  538. await self._list_group(message, "sudo")
  539. async def ownerlistcmd(self, message: Message):
  540. """List users in `owner`"""
  541. await self._list_group(message, "owner")
  542. async def supportlistcmd(self, message: Message):
  543. """List users in `support`"""
  544. await self._list_group(message, "support")