hikka_security.py 45 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159
  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. import time
  11. from typing import List, Union
  12. from telethon.tl.types import Message, PeerUser, User
  13. from telethon.utils import get_display_name
  14. from telethon.hints import EntityLike
  15. from .. import loader, security, utils, main
  16. from ..inline.types import InlineCall, InlineMessage
  17. from ..security import (
  18. DEFAULT_PERMISSIONS,
  19. EVERYONE,
  20. GROUP_ADMIN,
  21. GROUP_ADMIN_ADD_ADMINS,
  22. GROUP_ADMIN_BAN_USERS,
  23. GROUP_ADMIN_CHANGE_INFO,
  24. GROUP_ADMIN_DELETE_MESSAGES,
  25. GROUP_ADMIN_INVITE_USERS,
  26. GROUP_ADMIN_PIN_MESSAGES,
  27. GROUP_MEMBER,
  28. GROUP_OWNER,
  29. PM,
  30. SUDO,
  31. SUPPORT,
  32. )
  33. logger = logging.getLogger(__name__)
  34. @loader.tds
  35. class HikkaSecurityMod(loader.Module):
  36. """Control security settings"""
  37. service_strings = {
  38. "for": "for",
  39. "forever": "forever",
  40. "user": "user",
  41. "chat": "chat",
  42. "command": "command",
  43. "module": "module",
  44. "day": "day",
  45. "days": "days",
  46. "hour": "hour",
  47. "hours": "hours",
  48. "minute": "minute",
  49. "minutes": "minutes",
  50. "second": "second",
  51. "seconds": "seconds",
  52. }
  53. service_strings_ru = {
  54. "for": "на",
  55. "forever": "навсегда",
  56. "command": "команду",
  57. "module": "модуль",
  58. "chat": "чату",
  59. "user": "пользователю",
  60. "day": "день",
  61. "days": "дня(-ей)",
  62. "hour": "час",
  63. "hours": "часа(-ов)",
  64. "minute": "минута",
  65. "minutes": "минут(-ы)",
  66. "second": "секунда",
  67. "seconds": "секунд(-ы)",
  68. }
  69. strings = {
  70. "name": "HikkaSecurity",
  71. "no_command": "🚫 <b>Command </b><code>{}</code><b> not found!</b>",
  72. "permissions": (
  73. "🔐 <b>Here you can configure permissions for </b><code>{}{}</code>"
  74. ),
  75. "close_menu": "🙈 Close this menu",
  76. "global": (
  77. "🔐 <b>Here you can configure global bounding mask. If the permission is"
  78. " excluded here, it is excluded everywhere!</b>"
  79. ),
  80. "owner": "😎 Owner",
  81. "sudo": "🧐 Sudo",
  82. "support": "🤓 Support",
  83. "group_owner": "🧛‍♂️ Group owner",
  84. "group_admin_add_admins": "🧑‍⚖️ Admin (add members)",
  85. "group_admin_change_info": "🧑‍⚖️ Admin (change info)",
  86. "group_admin_ban_users": "🧑‍⚖️ Admin (ban)",
  87. "group_admin_delete_messages": "🧑‍⚖️ Admin (delete msgs)",
  88. "group_admin_pin_messages": "🧑‍⚖️ Admin (pin)",
  89. "group_admin_invite_users": "🧑‍⚖️ Admin (invite)",
  90. "group_admin": "🧑‍⚖️ Admin (any)",
  91. "group_member": "👥 In group",
  92. "pm": "🤙 In PM",
  93. "everyone": "🌍 Everyone (Inline)",
  94. "owner_list": (
  95. "<emoji document_id='5386399931378440814'>😎</emoji> <b>Users in group"
  96. " </b><code>owner</code><b>:</b>\n\n{}"
  97. ),
  98. "sudo_list": (
  99. "<emoji document_id='5418133868475587618'>🧐</emoji> <b>Users in group"
  100. " </b><code>sudo</code><b>:</b>\n\n{}"
  101. ),
  102. "support_list": (
  103. "<emoji document_id='5415729507128580146'>🤓</emoji> <b>Users in group"
  104. " </b><code>support</code><b>:</b>\n\n{}"
  105. ),
  106. "no_owner": (
  107. "<emoji document_id='5386399931378440814'>😎</emoji> <b>There is no users in"
  108. " group </b><code>owner</code>"
  109. ),
  110. "no_sudo": (
  111. "<emoji document_id='5418133868475587618'>🧐</emoji> <b>There is no users in"
  112. " group </b><code>sudo</code>"
  113. ),
  114. "no_support": (
  115. "<emoji document_id='5415729507128580146'>🤓</emoji> <b>There is no users in"
  116. " group </b><code>support</code>"
  117. ),
  118. "owner_added": (
  119. '<emoji document_id="5386399931378440814">😎</emoji> <b><a'
  120. ' href="tg://user?id={}">{}</a> added to group </b><code>owner</code>'
  121. ),
  122. "sudo_added": (
  123. '<emoji document_id="5418133868475587618">🧐</emoji> <b><a'
  124. ' href="tg://user?id={}">{}</a> added to group </b><code>sudo</code>'
  125. ),
  126. "support_added": (
  127. '<emoji document_id="5415729507128580146">🤓</emoji> <b><a'
  128. ' href="tg://user?id={}">{}</a> added to group </b><code>support</code>'
  129. ),
  130. "owner_removed": (
  131. '<emoji document_id="5386399931378440814">😎</emoji> <b><a'
  132. ' href="tg://user?id={}">{}</a> removed from group </b><code>owner</code>'
  133. ),
  134. "sudo_removed": (
  135. '<emoji document_id="5418133868475587618">🧐</emoji> <b><a'
  136. ' href="tg://user?id={}">{}</a> removed from group </b><code>sudo</code>'
  137. ),
  138. "support_removed": (
  139. '<emoji document_id="5415729507128580146">🤓</emoji> <b><a'
  140. ' href="tg://user?id={}">{}</a> removed from group </b><code>support</code>'
  141. ),
  142. "no_user": (
  143. "<emoji document_id='5415905755406539934'>🚫</emoji> <b>Specify user to"
  144. " permit</b>"
  145. ),
  146. "not_a_user": (
  147. "<emoji document_id='5415905755406539934'>🚫</emoji> <b>Specified entity is"
  148. " not a user</b>"
  149. ),
  150. "li": '⦿ <b><a href="tg://user?id={}">{}</a></b>',
  151. "warning": (
  152. "⚠️ <b>Please, confirm, that you want to add <a"
  153. ' href="tg://user?id={}">{}</a> to group </b><code>{}</code><b>!\nThis'
  154. " action may reveal personal info and grant full or partial access to"
  155. " userbot to this user</b>"
  156. ),
  157. "cancel": "🚫 Cancel",
  158. "confirm": "👑 Confirm",
  159. "enable_nonick_btn": "🔰 Enable",
  160. "self": (
  161. "<emoji document_id='5415905755406539934'>🚫</emoji> <b>You can't"
  162. " promote/demote yourself!</b>"
  163. ),
  164. "suggest_nonick": "🔰 <i>Do you want to enable NoNick for this user?</i>",
  165. "user_nn": '🔰 <b>NoNick for <a href="tg://user?id={}">{}</a> enabled</b>',
  166. "what": (
  167. "<emoji document_id='6053166094816905153'>🚫</emoji> <b>You need to specify"
  168. " the type of target as first argument (</b><code>user</code><b> or"
  169. " </b><code>chat</code><b>)</b>"
  170. ),
  171. "no_target": (
  172. "<emoji document_id='6053166094816905153'>🚫</emoji> <b>You didn't specify"
  173. " the target of security rule</b>"
  174. ),
  175. "no_rule": (
  176. "<emoji document_id='6053166094816905153'>🚫</emoji> <b>You didn't specify"
  177. " the rule (module or command)</b>"
  178. ),
  179. "confirm_rule": (
  180. "🔐 <b>Please, confirm that you want to give {} <a href='{}'>{}</a> a"
  181. " permission to use {} </b><code>{}</code><b> {}?</b>"
  182. ),
  183. "rule_added": (
  184. "🔐 <b>You gave {} <a href='{}'>{}</a> a"
  185. " permission to use {} </b><code>{}</code><b> {}</b>"
  186. ),
  187. "confirm_btn": "👑 Confirm",
  188. "cancel_btn": "🚫 Cancel",
  189. "multiple_rules": (
  190. "🔐 <b>Unable to unambiguously determine the security rule. Please, choose"
  191. " the one you meant:</b>\n\n{}"
  192. ),
  193. "rules": (
  194. "<emoji document_id='5472308992514464048'>🔐</emoji> <b>Targeted security"
  195. " rules:</b>\n\n{}"
  196. ),
  197. "no_rules": (
  198. "<emoji document_id='6053166094816905153'>🚫</emoji> <b>No targeted security"
  199. " rules</b>"
  200. ),
  201. "owner_target": (
  202. "<emoji document_id='6053166094816905153'>🚫</emoji> <b>This user is owner"
  203. " and can't be promoted by targeted security</b>"
  204. ),
  205. "rules_removed": (
  206. "<emoji document_id='5472308992514464048'>🔐</emoji> <b>Targeted security"
  207. ' rules for <a href="{}">{}</a> removed</b>'
  208. ),
  209. **service_strings,
  210. }
  211. strings_ru = {
  212. "no_command": (
  213. "<emoji document_id='5415905755406539934'>🚫</emoji> <b>Команда"
  214. " </b><code>{}</code><b> не найдена!</b>"
  215. ),
  216. "permissions": (
  217. "🔐 <b>Здесь можно настроить разрешения для команды </b><code>{}{}</code>"
  218. ),
  219. "close_menu": "🙈 Закрыть это меню",
  220. "global": (
  221. "🔐 <b>Здесь можно настроить глобальную исключающую маску. Если тумблер"
  222. " выключен здесь, он выключен для всех команд</b>"
  223. ),
  224. "owner": "😎 Владелец",
  225. "sudo": "🧐 Sudo",
  226. "support": "🤓 Помощник",
  227. "group_owner": "🧛‍♂️ Влад. группы",
  228. "group_admin_add_admins": "🧑‍⚖️ Админ (добавлять участников)",
  229. "group_admin_change_info": "🧑‍⚖️ Админ (изменять инфо)",
  230. "group_admin_ban_users": "🧑‍⚖️ Админ (банить)",
  231. "group_admin_delete_messages": "🧑‍⚖️ Админ (удалять сообщения)",
  232. "group_admin_pin_messages": "🧑‍⚖️ Админ (закреплять)",
  233. "group_admin_invite_users": "🧑‍⚖️ Админ (приглашать)",
  234. "group_admin": "🧑‍⚖️ Админ (любой)",
  235. "group_member": "👥 В группе",
  236. "pm": "🤙 В лс",
  237. "owner_list": (
  238. "<emoji document_id='5386399931378440814'>😎</emoji> <b>Пользователи группы"
  239. " </b><code>owner</code><b>:</b>\n\n{}"
  240. ),
  241. "sudo_list": (
  242. "<emoji document_id='5418133868475587618'>🧐</emoji> <b>Пользователи группы"
  243. " </b><code>sudo</code><b>:</b>\n\n{}"
  244. ),
  245. "support_list": (
  246. "<emoji document_id='5415729507128580146'>🤓</emoji> <b>Пользователи группы"
  247. " </b><code>support</code><b>:</b>\n\n{}"
  248. ),
  249. "no_owner": (
  250. "<emoji document_id='5386399931378440814'>😎</emoji> <b>Нет пользователей в"
  251. " группе </b><code>owner</code>"
  252. ),
  253. "no_sudo": (
  254. "<emoji document_id='5418133868475587618'>🧐</emoji> <b>Нет пользователей в"
  255. " группе </b><code>sudo</code>"
  256. ),
  257. "no_support": (
  258. "<emoji document_id='5415729507128580146'>🤓</emoji> <b>Нет пользователей в"
  259. " группе </b><code>support</code>"
  260. ),
  261. "no_user": (
  262. "<emoji document_id='5415905755406539934'>🚫</emoji> <b>Укажи, кому выдавать"
  263. " права</b>"
  264. ),
  265. "not_a_user": (
  266. "<emoji document_id='5415905755406539934'>🚫</emoji> <b>Указанная цель - не"
  267. " пользователь</b>"
  268. ),
  269. "cancel": "🚫 Отмена",
  270. "confirm": "👑 Подтвердить",
  271. "self": (
  272. "<emoji document_id='5415905755406539934'>🚫</emoji> <b>Нельзя управлять"
  273. " своими правами!</b>"
  274. ),
  275. "warning": (
  276. '⚠️ <b>Ты действительно хочешь добавить <a href="tg://user?id={}">{}</a> в'
  277. " группу </b><code>{}</code><b>!\nЭто действие может передать частичный или"
  278. " полный доступ к юзерботу этому пользователю!</b>"
  279. ),
  280. "suggest_nonick": (
  281. "🔰 <i>Хочешь ли ты включить NoNick для этого пользователя?</i>"
  282. ),
  283. "user_nn": '🔰 <b>NoNick для <a href="tg://user?id={}">{}</a> включен</b>',
  284. "enable_nonick_btn": "🔰 Включить",
  285. "owner_added": (
  286. '<emoji document_id="5386399931378440814">😎</emoji> <b><a'
  287. ' href="tg://user?id={}">{}</a> добавлен в группу </b><code>owner</code>'
  288. ),
  289. "sudo_added": (
  290. '<emoji document_id="5418133868475587618">🧐</emoji> <b><a'
  291. ' href="tg://user?id={}">{}</a> добавлен в группу </b><code>sudo</code>'
  292. ),
  293. "support_added": (
  294. '<emoji document_id="5415729507128580146">🤓</emoji> <b><a'
  295. ' href="tg://user?id={}">{}</a> добавлен в группу </b><code>support</code>'
  296. ),
  297. "owner_removed": (
  298. '<emoji document_id="5386399931378440814">😎</emoji> <b><a'
  299. ' href="tg://user?id={}">{}</a> удален из группы </b><code>owner</code>'
  300. ),
  301. "sudo_removed": (
  302. '<emoji document_id="5418133868475587618">🧐</emoji> <b><a'
  303. ' href="tg://user?id={}">{}</a> удален из группы </b><code>sudo</code>'
  304. ),
  305. "support_removed": (
  306. '<emoji document_id="5415729507128580146">🤓</emoji> <b><a'
  307. ' href="tg://user?id={}">{}</a> удален из группы </b><code>support</code>'
  308. ),
  309. "_cls_doc": "Управление настройками безопасности",
  310. "what": (
  311. "<emoji document_id='6053166094816905153'>🚫</emoji> <b>Вам нужно указать"
  312. " тип цели первым аргументов (</b><code>user</code><b> or"
  313. " </b><code>chat</code><b>)</b>"
  314. ),
  315. "no_target": (
  316. "<emoji document_id='6053166094816905153'>🚫</emoji> <b>Не указана цель"
  317. " правила безопасности</b>"
  318. ),
  319. "no_rule": (
  320. "<emoji document_id='6053166094816905153'>🚫</emoji> <b>Не указано правило"
  321. " безопасности (модуль или команда)</b>"
  322. ),
  323. "confirm_rule": (
  324. "🔐 <b>Пожалуйста, подтвердите что хотите выдать {} <a href='{}'>{}</a>"
  325. " право использовать {} </b><code>{}</code><b> {}</b>"
  326. ),
  327. "multiple_rules": (
  328. "🔐 <b>Не получилось однозначно распознать правила безопасности. Выберите"
  329. " то, которое имели ввиду:</b>\n\n{}"
  330. ),
  331. "rule_added": (
  332. "🔐 <b>Вы выдали {} <a href='{}'>{}</a> право"
  333. " использовать {} </b><code>{}</code><b> {}</b>"
  334. ),
  335. "rules": (
  336. "<emoji document_id='5472308992514464048'>🔐</emoji> <b>Таргетированные"
  337. " правила безопасности:</b>\n\n{}"
  338. ),
  339. "no_rules": (
  340. "<emoji document_id='6053166094816905153'>🚫</emoji> <b>Нет таргетированных"
  341. " правил безопасности</b>"
  342. ),
  343. "owner_target": (
  344. "<emoji document_id='6053166094816905153'>🚫</emoji> <b>Этот пользователь -"
  345. " владелец, его права не могут управляться таргетированной"
  346. " безопасностью</b>"
  347. ),
  348. "rules_removed": (
  349. "<emoji document_id='5472308992514464048'>🔐</emoji> <b>Правила"
  350. ' таргетированной безопасности для <a href="{}">{}</a> удалены</b>'
  351. ),
  352. **service_strings_ru,
  353. }
  354. async def inline__switch_perm(
  355. self,
  356. call: InlineCall,
  357. command: str,
  358. group: str,
  359. level: bool,
  360. is_inline: bool,
  361. ):
  362. cmd = (
  363. self.allmodules.inline_handlers[command]
  364. if is_inline
  365. else self.allmodules.commands[command]
  366. )
  367. mask = self._db.get(security.__name__, "masks", {}).get(
  368. f"{cmd.__module__}.{cmd.__name__}",
  369. getattr(cmd, "security", security.DEFAULT_PERMISSIONS),
  370. )
  371. bit = security.BITMAP[group.upper()]
  372. if level:
  373. mask |= bit
  374. else:
  375. mask &= ~bit
  376. masks = self._db.get(security.__name__, "masks", {})
  377. masks[f"{cmd.__module__}.{cmd.__name__}"] = mask
  378. self._db.set(security.__name__, "masks", masks)
  379. if (
  380. not self._db.get(security.__name__, "bounding_mask", DEFAULT_PERMISSIONS)
  381. & bit
  382. and level
  383. ):
  384. await call.answer(
  385. "Security value set but not applied. Consider enabling this value in"
  386. f" .{'inlinesec' if is_inline else 'security'}",
  387. show_alert=True,
  388. )
  389. else:
  390. await call.answer("Security value set!")
  391. await call.edit(
  392. self.strings("permissions").format(
  393. f"@{self.inline.bot_username} " if is_inline else self.get_prefix(),
  394. command,
  395. ),
  396. reply_markup=self._build_markup(cmd, is_inline),
  397. )
  398. async def inline__switch_perm_bm(
  399. self,
  400. call: InlineCall,
  401. group: str,
  402. level: bool,
  403. is_inline: bool,
  404. ):
  405. mask = self._db.get(security.__name__, "bounding_mask", DEFAULT_PERMISSIONS)
  406. bit = security.BITMAP[group.upper()]
  407. if level:
  408. mask |= bit
  409. else:
  410. mask &= ~bit
  411. self._db.set(security.__name__, "bounding_mask", mask)
  412. await call.answer("Bounding mask value set!")
  413. await call.edit(
  414. self.strings("global"),
  415. reply_markup=self._build_markup_global(is_inline),
  416. )
  417. def _build_markup(
  418. self,
  419. command: callable,
  420. is_inline: bool = False,
  421. ) -> List[List[dict]]:
  422. perms = self._get_current_perms(command, is_inline)
  423. return (
  424. utils.chunks(
  425. [
  426. {
  427. "text": f"{'✅' if level else '🚫'} {self.strings[group]}",
  428. "callback": self.inline__switch_perm,
  429. "args": (
  430. command.__name__.rsplit("_inline_handler", maxsplit=1)[0],
  431. group,
  432. not level,
  433. is_inline,
  434. ),
  435. }
  436. for group, level in perms.items()
  437. ],
  438. 2,
  439. )
  440. + [[{"text": self.strings("close_menu"), "action": "close"}]]
  441. if is_inline
  442. else utils.chunks(
  443. [
  444. {
  445. "text": f"{'✅' if level else '🚫'} {self.strings[group]}",
  446. "callback": self.inline__switch_perm,
  447. "args": (
  448. command.__name__.rsplit("cmd", maxsplit=1)[0],
  449. group,
  450. not level,
  451. is_inline,
  452. ),
  453. }
  454. for group, level in perms.items()
  455. ],
  456. 2,
  457. )
  458. + [
  459. [
  460. {
  461. "text": self.strings("close_menu"),
  462. "action": "close",
  463. }
  464. ]
  465. ]
  466. )
  467. def _build_markup_global(self, is_inline: bool = False) -> List[List[dict]]:
  468. perms = self._get_current_bm(is_inline)
  469. return utils.chunks(
  470. [
  471. {
  472. "text": f"{'✅' if level else '🚫'} {self.strings[group]}",
  473. "callback": self.inline__switch_perm_bm,
  474. "args": (group, not level, is_inline),
  475. }
  476. for group, level in perms.items()
  477. ],
  478. 2,
  479. ) + [[{"text": self.strings("close_menu"), "action": "close"}]]
  480. def _get_current_bm(self, is_inline: bool = False) -> dict:
  481. return self._perms_map(
  482. self._db.get(security.__name__, "bounding_mask", DEFAULT_PERMISSIONS),
  483. is_inline,
  484. )
  485. @staticmethod
  486. def _perms_map(perms: int, is_inline: bool) -> dict:
  487. return (
  488. {
  489. "sudo": bool(perms & SUDO),
  490. "support": bool(perms & SUPPORT),
  491. "everyone": bool(perms & EVERYONE),
  492. }
  493. if is_inline
  494. else {
  495. "sudo": bool(perms & SUDO),
  496. "support": bool(perms & SUPPORT),
  497. "group_owner": bool(perms & GROUP_OWNER),
  498. "group_admin_add_admins": bool(perms & GROUP_ADMIN_ADD_ADMINS),
  499. "group_admin_change_info": bool(perms & GROUP_ADMIN_CHANGE_INFO),
  500. "group_admin_ban_users": bool(perms & GROUP_ADMIN_BAN_USERS),
  501. "group_admin_delete_messages": bool(
  502. perms & GROUP_ADMIN_DELETE_MESSAGES
  503. ),
  504. "group_admin_pin_messages": bool(perms & GROUP_ADMIN_PIN_MESSAGES),
  505. "group_admin_invite_users": bool(perms & GROUP_ADMIN_INVITE_USERS),
  506. "group_admin": bool(perms & GROUP_ADMIN),
  507. "group_member": bool(perms & GROUP_MEMBER),
  508. "pm": bool(perms & PM),
  509. "everyone": bool(perms & EVERYONE),
  510. }
  511. )
  512. def _get_current_perms(
  513. self,
  514. command: callable,
  515. is_inline: bool = False,
  516. ) -> dict:
  517. config = self._db.get(security.__name__, "masks", {}).get(
  518. f"{command.__module__}.{command.__name__}",
  519. getattr(command, "security", self._client.dispatcher.security.default),
  520. )
  521. return self._perms_map(config, is_inline)
  522. @loader.owner
  523. @loader.command(ru_doc="[команда] - Настроить разрешения для команды")
  524. async def security(self, message: Message):
  525. """[command] - Configure command's security settings"""
  526. args = utils.get_args_raw(message).lower().strip()
  527. if args and args not in self.allmodules.commands:
  528. await utils.answer(message, self.strings("no_command").format(args))
  529. return
  530. if not args:
  531. await self.inline.form(
  532. self.strings("global"),
  533. reply_markup=self._build_markup_global(),
  534. message=message,
  535. ttl=5 * 60,
  536. )
  537. return
  538. cmd = self.allmodules.commands[args]
  539. await self.inline.form(
  540. self.strings("permissions").format(self.get_prefix(), args),
  541. reply_markup=self._build_markup(cmd),
  542. message=message,
  543. ttl=5 * 60,
  544. )
  545. @loader.owner
  546. @loader.command(ru_doc="[команда] - Настроить разрешения для инлайн команды")
  547. async def inlinesec(self, message: Message):
  548. """[command] - Configure inline command's security settings"""
  549. args = utils.get_args_raw(message).lower().strip()
  550. if not args:
  551. await self.inline.form(
  552. self.strings("global"),
  553. reply_markup=self._build_markup_global(True),
  554. message=message,
  555. ttl=5 * 60,
  556. )
  557. return
  558. if args not in self.allmodules.inline_handlers:
  559. await utils.answer(message, self.strings("no_command").format(args))
  560. return
  561. i_handler = self.allmodules.inline_handlers[args]
  562. await self.inline.form(
  563. self.strings("permissions").format(f"@{self.inline.bot_username} ", args),
  564. reply_markup=self._build_markup(i_handler, True),
  565. message=message,
  566. ttl=5 * 60,
  567. )
  568. async def _resolve_user(self, message: Message):
  569. reply = await message.get_reply_message()
  570. args = utils.get_args_raw(message)
  571. if not args and not reply:
  572. await utils.answer(message, self.strings("no_user"))
  573. return
  574. user = None
  575. if args:
  576. try:
  577. if str(args).isdigit():
  578. args = int(args)
  579. user = await self._client.get_entity(args)
  580. except Exception:
  581. pass
  582. if user is None:
  583. user = await self._client.get_entity(reply.sender_id)
  584. if not isinstance(user, (User, PeerUser)):
  585. await utils.answer(message, self.strings("not_a_user"))
  586. return
  587. if user.id == self.tg_id:
  588. await utils.answer(message, self.strings("self"))
  589. return
  590. return user
  591. async def _add_to_group(
  592. self,
  593. message: Union[Message, InlineCall], # noqa: F821
  594. group: str,
  595. confirmed: bool = False,
  596. user: int = None,
  597. ):
  598. if user is None:
  599. user = await self._resolve_user(message)
  600. if not user:
  601. return
  602. if isinstance(user, int):
  603. user = await self._client.get_entity(user)
  604. if not confirmed:
  605. await self.inline.form(
  606. self.strings("warning").format(
  607. user.id,
  608. utils.escape_html(get_display_name(user)),
  609. group,
  610. ),
  611. message=message,
  612. ttl=10 * 60,
  613. reply_markup=[
  614. {
  615. "text": self.strings("cancel"),
  616. "action": "close",
  617. },
  618. {
  619. "text": self.strings("confirm"),
  620. "callback": self._add_to_group,
  621. "args": (group, True, user.id),
  622. },
  623. ],
  624. )
  625. return
  626. if user.id not in getattr(self._client.dispatcher.security, group):
  627. getattr(self._client.dispatcher.security, group).append(user.id)
  628. m = (
  629. self.strings(f"{group}_added").format(
  630. user.id,
  631. utils.escape_html(get_display_name(user)),
  632. )
  633. + "\n\n"
  634. + self.strings("suggest_nonick")
  635. )
  636. await utils.answer(message, m)
  637. await message.edit(
  638. m,
  639. reply_markup=[
  640. {
  641. "text": self.strings("cancel"),
  642. "action": "close",
  643. },
  644. {
  645. "text": self.strings("enable_nonick_btn"),
  646. "callback": self._enable_nonick,
  647. "args": (user,),
  648. },
  649. ],
  650. )
  651. async def _enable_nonick(self, call: InlineCall, user: User):
  652. self._db.set(
  653. main.__name__,
  654. "nonickusers",
  655. list(set(self._db.get(main.__name__, "nonickusers", []) + [user.id])),
  656. )
  657. await call.edit(
  658. self.strings("user_nn").format(
  659. user.id,
  660. utils.escape_html(get_display_name(user)),
  661. )
  662. )
  663. await call.unload()
  664. async def _remove_from_group(self, message: Message, group: str):
  665. user = await self._resolve_user(message)
  666. if not user:
  667. return
  668. if user.id in getattr(self._client.dispatcher.security, group):
  669. getattr(self._client.dispatcher.security, group).remove(user.id)
  670. m = self.strings(f"{group}_removed").format(
  671. user.id,
  672. utils.escape_html(get_display_name(user)),
  673. )
  674. await utils.answer(message, m)
  675. async def _list_group(self, message: Message, group: str):
  676. _resolved_users = []
  677. for user in getattr(self._client.dispatcher.security, group) + (
  678. [self.tg_id] if group == "owner" else []
  679. ):
  680. try:
  681. _resolved_users += [await self._client.get_entity(user)]
  682. except Exception:
  683. pass
  684. if _resolved_users:
  685. await utils.answer(
  686. message,
  687. self.strings(f"{group}_list").format(
  688. "\n".join(
  689. [
  690. self.strings("li").format(
  691. i.id, utils.escape_html(get_display_name(i))
  692. )
  693. for i in _resolved_users
  694. ]
  695. )
  696. ),
  697. )
  698. else:
  699. await utils.answer(message, self.strings(f"no_{group}"))
  700. @loader.command(ru_doc="<пользователь> - Добавить пользователя в группу `sudo`")
  701. async def sudoadd(self, message: Message):
  702. """<user> - Add user to `sudo`"""
  703. await self._add_to_group(message, "sudo")
  704. @loader.command(ru_doc="<пользователь> - Добавить пользователя в группу `owner`")
  705. async def owneradd(self, message: Message):
  706. """<user> - Add user to `owner`"""
  707. await self._add_to_group(message, "owner")
  708. @loader.command(ru_doc="<пользователь> - Добавить пользователя в группу `support`")
  709. async def supportadd(self, message: Message):
  710. """<user> - Add user to `support`"""
  711. await self._add_to_group(message, "support")
  712. @loader.command(ru_doc="<пользователь> - Удалить пользователя из группы `sudo`")
  713. async def sudorm(self, message: Message):
  714. """<user> - Remove user from `sudo`"""
  715. await self._remove_from_group(message, "sudo")
  716. @loader.command(ru_doc="<пользователь> - Удалить пользователя из группы `owner`")
  717. async def ownerrm(self, message: Message):
  718. """<user> - Remove user from `owner`"""
  719. await self._remove_from_group(message, "owner")
  720. @loader.command(ru_doc="<пользователь> - Удалить пользователя из группы `support`")
  721. async def supportrm(self, message: Message):
  722. """<user> - Remove user from `support`"""
  723. await self._remove_from_group(message, "support")
  724. @loader.command(ru_doc="Показать список пользователей в группе `sudo`")
  725. async def sudolist(self, message: Message):
  726. """List users in `sudo`"""
  727. await self._list_group(message, "sudo")
  728. @loader.command(ru_doc="Показать список пользователей в группе `owner`")
  729. async def ownerlist(self, message: Message):
  730. """List users in `owner`"""
  731. await self._list_group(message, "owner")
  732. @loader.command(ru_doc="Показать список пользователей в группе `support`")
  733. async def supportlist(self, message: Message):
  734. """List users in `support`"""
  735. await self._list_group(message, "support")
  736. def _lookup(self, needle: str) -> str:
  737. return (
  738. []
  739. if needle.lower().startswith(self.get_prefix())
  740. else (
  741. [f"module/{self.lookup(needle).__class__.__name__}"]
  742. if self.lookup(needle)
  743. else []
  744. )
  745. ) + (
  746. [f"command/{needle.lower().strip(self.get_prefix())}"]
  747. if needle.lower().strip(self.get_prefix()) in self.allmodules.commands
  748. else []
  749. )
  750. @staticmethod
  751. def _extract_time(args: list) -> int:
  752. suffixes = {
  753. "d": 24 * 60 * 60,
  754. "h": 60 * 60,
  755. "m": 60,
  756. "s": 1,
  757. }
  758. for suffix, quantifier in suffixes.items():
  759. duration = next(
  760. (
  761. int(arg.rsplit(suffix, maxsplit=1)[0])
  762. for arg in args
  763. if arg.endswith(suffix)
  764. and arg.rsplit(suffix, maxsplit=1)[0].isdigit()
  765. ),
  766. None,
  767. )
  768. if duration is not None:
  769. return duration * quantifier
  770. return 0
  771. def _convert_time(self, duration: int) -> str:
  772. return (
  773. (
  774. f"{duration // (24 * 60 * 60)} "
  775. + self.strings(f"day{'s' if duration // (24 * 60 * 60) > 1 else ''}")
  776. )
  777. if duration >= 24 * 60 * 60
  778. else (
  779. (
  780. f"{duration // (60 * 60)} "
  781. + self.strings(f"hour{'s' if duration // (60 * 60) > 1 else ''}")
  782. )
  783. if duration >= 60 * 60
  784. else (
  785. (
  786. f"{duration // 60} "
  787. + self.strings(f"minute{'s' if duration // 60 > 1 else ''}")
  788. )
  789. if duration >= 60
  790. else (
  791. f"{duration} "
  792. + self.strings(f"second{'s' if duration > 1 else ''}")
  793. )
  794. )
  795. )
  796. )
  797. async def _add_rule(
  798. self,
  799. call: InlineCall,
  800. target_type: str,
  801. target: EntityLike,
  802. rule: str,
  803. duration: int,
  804. ):
  805. self._client.dispatcher.security.add_rule(
  806. target_type,
  807. target,
  808. rule,
  809. duration,
  810. )
  811. await call.edit(
  812. self.strings("rule_added").format(
  813. self.strings(target_type),
  814. utils.get_entity_url(target),
  815. utils.escape_html(get_display_name(target)),
  816. self.strings(rule.split("/", maxsplit=1)[0]),
  817. rule.split("/", maxsplit=1)[1],
  818. (self.strings("for") + " " + self._convert_time(duration))
  819. if duration
  820. else self.strings("forever"),
  821. )
  822. )
  823. async def _confirm(
  824. self,
  825. obj: Union[Message, InlineMessage],
  826. target_type: str,
  827. target: EntityLike,
  828. rule: str,
  829. duration: int,
  830. ):
  831. await utils.answer(
  832. obj,
  833. self.strings("confirm_rule").format(
  834. self.strings(target_type),
  835. utils.get_entity_url(target),
  836. utils.escape_html(get_display_name(target)),
  837. self.strings(rule.split("/", maxsplit=1)[0]),
  838. rule.split("/", maxsplit=1)[1],
  839. (self.strings("for") + " " + self._convert_time(duration))
  840. if duration
  841. else self.strings("forever"),
  842. ),
  843. reply_markup=[
  844. {
  845. "text": self.strings("confirm_btn"),
  846. "callback": self._add_rule,
  847. "args": (target_type, target, rule, duration),
  848. },
  849. {"text": self.strings("cancel_btn"), "action": "close"},
  850. ],
  851. )
  852. async def _tsec_chat(self, message: Message, args: list):
  853. if len(args) == 1 and message.is_private:
  854. await utils.answer(message, self.strings("no_target"))
  855. return
  856. if len(args) >= 2:
  857. try:
  858. if not args[1].isdigit() and not args[1].startswith("@"):
  859. raise ValueError
  860. target = await self._client.get_entity(
  861. int(args[1]) if args[1].isdigit() else args[1]
  862. )
  863. except (ValueError, TypeError):
  864. if not message.is_private:
  865. target = await self._client.get_entity(message.peer_id)
  866. else:
  867. await utils.answer(message, self.strings("no_target"))
  868. return
  869. duration = self._extract_time(args)
  870. possible_rules = utils.array_sum([self._lookup(arg) for arg in args])
  871. if not possible_rules:
  872. await utils.answer(message, self.strings("no_rule"))
  873. return
  874. if len(possible_rules) > 1:
  875. def case(text: str) -> str:
  876. return text.upper()[0] + text[1:]
  877. await self.inline.form(
  878. message=message,
  879. text=self.strings("multiple_rules").format(
  880. "\n".join(
  881. f"🛡 <b>{case(self.strings(i.split('/')[0]))} </b><code>{i.split('/', maxsplit=1)[1]}</code>"
  882. for i in possible_rules
  883. )
  884. ),
  885. reply_markup=utils.chunks(
  886. [
  887. {
  888. "text": (
  889. f"🛡 {case(self.strings(i.split('/')[0]))} {i.split('/', maxsplit=1)[1]}"
  890. ),
  891. "callback": self._confirm,
  892. "args": ("chat", target, i, duration),
  893. }
  894. for i in possible_rules
  895. ],
  896. 3,
  897. ),
  898. )
  899. return
  900. await self._confirm(message, "chat", target, possible_rules[0], duration)
  901. async def _tsec_user(self, message: Message, args: list):
  902. if len(args) == 1 and not message.is_private and not message.is_reply:
  903. await utils.answer(message, self.strings("no_target"))
  904. return
  905. if len(args) >= 2:
  906. try:
  907. if not args[1].isdigit() and not args[1].startswith("@"):
  908. raise ValueError
  909. target = await self._client.get_entity(
  910. int(args[1]) if args[1].isdigit() else args[1]
  911. )
  912. except (ValueError, TypeError):
  913. if message.is_private:
  914. target = await self._client.get_entity(message.peer_id)
  915. elif message.is_reply:
  916. target = await self._client.get_entity(
  917. (await message.get_reply_message()).sender_id
  918. )
  919. else:
  920. await utils.answer(message, self.strings("no_target"))
  921. return
  922. if target.id in self._client.dispatcher.security.owner:
  923. await utils.answer(message, self.strings("owner_target"))
  924. return
  925. duration = self._extract_time(args)
  926. possible_rules = utils.array_sum([self._lookup(arg) for arg in args])
  927. if not possible_rules:
  928. await utils.answer(message, self.strings("no_rule"))
  929. return
  930. if len(possible_rules) > 1:
  931. def case(text: str) -> str:
  932. return text.upper()[0] + text[1:]
  933. await self.inline.form(
  934. message=message,
  935. text=self.strings("multiple_rules").format(
  936. "\n".join(
  937. f"🛡 <b>{case(self.strings(i.split('/')[0]))} </b><code>{i.split('/', maxsplit=1)[1]}</code>"
  938. for i in possible_rules
  939. )
  940. ),
  941. reply_markup=utils.chunks(
  942. [
  943. {
  944. "text": (
  945. f"🛡 {case(self.strings(i.split('/')[0]))} {i.split('/', maxsplit=1)[1]}"
  946. ),
  947. "callback": self._confirm,
  948. "args": ("user", target, i, duration),
  949. }
  950. for i in possible_rules
  951. ],
  952. 3,
  953. ),
  954. )
  955. return
  956. await self._confirm(message, "user", target, possible_rules[0], duration)
  957. @loader.command(
  958. ru_doc='<"user"/"chat"> - Удалить правило таргетированной безопасности'
  959. )
  960. async def tsecrm(self, message: Message):
  961. """<"user"/"chat"> - Remove targeted security rule"""
  962. if (
  963. not self._client.dispatcher.security.tsec_chat
  964. and not self._client.dispatcher.security.tsec_user
  965. ):
  966. await utils.answer(message, self.strings("no_rules"))
  967. return
  968. args = utils.get_args_raw(message)
  969. if not args or args not in {"user", "chat"}:
  970. await utils.answer(message, self.strings("no_target"))
  971. return
  972. if args == "user":
  973. if not message.is_private and not message.is_reply:
  974. await utils.answer(message, self.strings("no_target"))
  975. return
  976. if message.is_private:
  977. target = await self._client.get_entity(message.peer_id)
  978. elif message.is_reply:
  979. target = await self._client.get_entity(
  980. (await message.get_reply_message()).sender_id
  981. )
  982. else:
  983. await utils.answer(message, self.strings("no_target"))
  984. return
  985. if not self._client.dispatcher.security.remove_rules("user", target.id):
  986. await utils.answer(message, self.strings("no_rules"))
  987. return
  988. await utils.answer(
  989. message,
  990. self.strings("rules_removed").format(
  991. utils.get_entity_url(target),
  992. utils.escape_html(get_display_name(target)),
  993. ),
  994. )
  995. return
  996. if message.is_private:
  997. await utils.answer(message, self.strings("no_target"))
  998. return
  999. target = await self._client.get_entity(message.peer_id)
  1000. if not self._client.dispatcher.security.remove_rules("chat", target.id):
  1001. await utils.answer(message, self.strings("no_rules"))
  1002. return
  1003. await utils.answer(
  1004. message,
  1005. self.strings("rules_removed").format(
  1006. utils.get_entity_url(target),
  1007. utils.escape_html(get_display_name(target)),
  1008. ),
  1009. )
  1010. @loader.command(
  1011. ru_doc=(
  1012. '<"user"/"chat"> [цель - пользователь или чат] [правило - команда или'
  1013. " модуль] [время] - Настроить таргетированную безопасность"
  1014. )
  1015. )
  1016. async def tsec(self, message: Message):
  1017. """<"user"/"chat"> [target user or chat] [rule (command/module)] [time] - Add new targeted security rule"""
  1018. args = utils.get_args(message)
  1019. if not args:
  1020. if (
  1021. not self._client.dispatcher.security.tsec_chat
  1022. and not self._client.dispatcher.security.tsec_user
  1023. ):
  1024. await utils.answer(message, self.strings("no_rules"))
  1025. return
  1026. await utils.answer(
  1027. message,
  1028. self.strings("rules").format(
  1029. "\n".join(
  1030. [
  1031. "<emoji document_id='6037355667365300960'>👥</emoji> <b><a"
  1032. f" href='{rule['entity_url']}'>{utils.escape_html(rule['entity_name'])}</a>"
  1033. f" {self._convert_time(int(rule['expires'] - time.time()))} {self.strings('for')} {self.strings(rule['rule_type'])}</b>"
  1034. f" <code>{rule['rule']}</code>"
  1035. for rule in self._client.dispatcher.security.tsec_chat
  1036. ]
  1037. + [
  1038. "<emoji document_id='6037122016849432064'>👤</emoji> <b><a"
  1039. f" href='{rule['entity_url']}'>{utils.escape_html(rule['entity_name'])}</a>"
  1040. f" {self._convert_time(int(rule['expires'] - time.time()))} {self.strings('for')} {self.strings(rule['rule_type'])}</b>"
  1041. f" <code>{rule['rule']}</code>"
  1042. for rule in self._client.dispatcher.security.tsec_user
  1043. ]
  1044. )
  1045. ),
  1046. )
  1047. return
  1048. if args[0] not in {"user", "chat"}:
  1049. await utils.answer(message, self.strings("what"))
  1050. return
  1051. await getattr(self, f"_tsec_{args[0]}")(message, args)