Heroku/hikka/modules/hikka_security.py

687 lines
27 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
# █▀█ █ █ █ █▀█ █▀▄ █
# © Copyright 2022
# https://t.me/hikariatama
#
# 🔒 Licensed under the GNU AGPLv3
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
# scope: inline
import logging
from typing import List, Union
from telethon.tl.types import Message, PeerUser, User
from telethon.utils import get_display_name
from .. import loader, security, utils, main
from ..inline.types import InlineCall
from ..security import (
DEFAULT_PERMISSIONS,
EVERYONE,
GROUP_ADMIN,
GROUP_ADMIN_ADD_ADMINS,
GROUP_ADMIN_BAN_USERS,
GROUP_ADMIN_CHANGE_INFO,
GROUP_ADMIN_DELETE_MESSAGES,
GROUP_ADMIN_INVITE_USERS,
GROUP_ADMIN_PIN_MESSAGES,
GROUP_MEMBER,
GROUP_OWNER,
PM,
SUDO,
SUPPORT,
)
logger = logging.getLogger(__name__)
@loader.tds
class HikkaSecurityMod(loader.Module):
"""Control security settings"""
strings = {
"name": "HikkaSecurity",
"no_command": "🚫 <b>Command </b><code>{}</code><b> not found!</b>",
"permissions": (
"🔐 <b>Here you can configure permissions for </b><code>{}{}</code>"
),
"close_menu": "🙈 Close this menu",
"global": (
"🔐 <b>Here you can configure global bounding mask. If the permission is"
" excluded here, it is excluded everywhere!</b>"
),
"owner": "<emoji document_id='5386399931378440814'>😎</emoji> Owner",
"sudo": "🤵 Sudo",
"support": "<emoji document_id='5415729507128580146'>🤓</emoji> Support",
"group_owner": "🧛‍♂️ Group owner",
"group_admin_add_admins": "🧑‍⚖️ Admin (add members)",
"group_admin_change_info": "🧑‍⚖️ Admin (change info)",
"group_admin_ban_users": "🧑‍⚖️ Admin (ban)",
"group_admin_delete_messages": "🧑‍⚖️ Admin (delete msgs)",
"group_admin_pin_messages": "🧑‍⚖️ Admin (pin)",
"group_admin_invite_users": "🧑‍⚖️ Admin (invite)",
"group_admin": "🧑‍⚖️ Admin (any)",
"group_member": "👥 In group",
"pm": "🤙 In PM",
"everyone": "🌍 Everyone (Inline)",
"owner_list": (
"<emoji document_id='5386399931378440814'>😎</emoji> <b>Users in group"
" </b><code>owner</code><b>:</b>\n\n{}"
),
"sudo_list": (
"<emoji document_id='5418133868475587618'>🧐</emoji> <b>Users in group"
" </b><code>sudo</code><b>:</b>\n\n{}"
),
"support_list": (
"<emoji document_id='5415729507128580146'>🤓</emoji> <b>Users in group"
" </b><code>support</code><b>:</b>\n\n{}"
),
"no_owner": (
"<emoji document_id='5386399931378440814'>😎</emoji> <b>There is no users in"
" group </b><code>owner</code>"
),
"no_sudo": (
"<emoji document_id='5418133868475587618'>🧐</emoji> <b>There is no users in"
" group </b><code>sudo</code>"
),
"no_support": (
"<emoji document_id='5415729507128580146'>🤓</emoji> <b>There is no users in"
" group </b><code>support</code>"
),
"owner_added": (
'<emoji document_id="5386399931378440814">😎</emoji> <b><a'
' href="tg://user?id={}">{}</a> added to group </b><code>owner</code>'
),
"sudo_added": (
'<emoji document_id="5418133868475587618">🧐</emoji> <b><a'
' href="tg://user?id={}">{}</a> added to group </b><code>sudo</code>'
),
"support_added": (
'<emoji document_id="5415729507128580146">🤓</emoji> <b><a'
' href="tg://user?id={}">{}</a> added to group </b><code>support</code>'
),
"owner_removed": (
'<emoji document_id="5386399931378440814">😎</emoji> <b><a'
' href="tg://user?id={}">{}</a> removed from group </b><code>owner</code>'
),
"sudo_removed": (
'<emoji document_id="5418133868475587618">🧐</emoji> <b><a'
' href="tg://user?id={}">{}</a> removed from group </b><code>sudo</code>'
),
"support_removed": (
'<emoji document_id="5415729507128580146">🤓</emoji> <b><a'
' href="tg://user?id={}">{}</a> removed from group </b><code>support</code>'
),
"no_user": (
"<emoji document_id='5415905755406539934'>🚫</emoji> <b>Specify user to"
" permit</b>"
),
"not_a_user": (
"<emoji document_id='5415905755406539934'>🚫</emoji> <b>Specified entity is"
" not a user</b>"
),
"li": '⦿ <b><a href="tg://user?id={}">{}</a></b>',
"warning": (
"⚠️ <b>Please, confirm, that you want to add <a"
' href="tg://user?id={}">{}</a> to group </b><code>{}</code><b>!\nThis'
" action may reveal personal info and grant full or partial access to"
" userbot to this user</b>"
),
"cancel": "🚫 Cancel",
"confirm": "👑 Confirm",
"enable_nonick_btn": "🔰 Enable",
"self": (
"<emoji document_id='5415905755406539934'>🚫</emoji> <b>You can't"
" promote/demote yourself!</b>"
),
"suggest_nonick": "🔰 <i>Do you want to enable NoNick for this user?</i>",
"user_nn": '🔰 <b>NoNick for <a href="tg://user?id={}">{}</a> enabled</b>',
}
strings_ru = {
"no_command": (
"<emoji document_id='5415905755406539934'>🚫</emoji> <b>Команда"
" </b><code>{}</code><b> не найдена!</b>"
),
"permissions": (
"🔐 <b>Здесь можно настроить разрешения для команды </b><code>{}{}</code>"
),
"close_menu": "🙈 Закрыть это меню",
"global": (
"🔐 <b>Здесь можно настроить глобальную исключающую маску. Если тумблер"
" выключен здесь, он выключен для всех команд</b>"
),
"owner": "<emoji document_id='5386399931378440814'>😎</emoji> Владелец",
"sudo": "🤵 Sudo",
"support": "<emoji document_id='5415729507128580146'>🤓</emoji> Помощник",
"group_owner": "🧛‍♂️ Влад. группы",
"group_admin_add_admins": "🧑‍⚖️ Админ (добавлять участников)",
"group_admin_change_info": "🧑‍⚖️ Админ (изменять инфо)",
"group_admin_ban_users": "🧑‍⚖️ Админ (банить)",
"group_admin_delete_messages": "🧑‍⚖️ Админ (удалять сообщения)",
"group_admin_pin_messages": "🧑‍⚖️ Админ (закреплять)",
"group_admin_invite_users": "🧑‍⚖️ Админ (приглашать)",
"group_admin": "🧑‍⚖️ Админ (любой)",
"group_member": "👥 В группе",
"pm": "🤙 В лс",
"owner_list": (
"<emoji document_id='5386399931378440814'>😎</emoji> <b>Пользователи группы"
" </b><code>owner</code><b>:</b>\n\n{}"
),
"sudo_list": (
"<emoji document_id='5418133868475587618'>🧐</emoji> <b>Пользователи группы"
" </b><code>sudo</code><b>:</b>\n\n{}"
),
"support_list": (
"<emoji document_id='5415729507128580146'>🤓</emoji> <b>Пользователи группы"
" </b><code>support</code><b>:</b>\n\n{}"
),
"no_owner": (
"<emoji document_id='5386399931378440814'>😎</emoji> <b>Нет пользователей в"
" группе </b><code>owner</code>"
),
"no_sudo": (
"<emoji document_id='5418133868475587618'>🧐</emoji> <b>Нет пользователей в"
" группе </b><code>sudo</code>"
),
"no_support": (
"<emoji document_id='5415729507128580146'>🤓</emoji> <b>Нет пользователей в"
" группе </b><code>support</code>"
),
"no_user": (
"<emoji document_id='5415905755406539934'>🚫</emoji> <b>Укажи, кому выдавать"
" права</b>"
),
"not_a_user": (
"<emoji document_id='5415905755406539934'>🚫</emoji> <b>Указанная цель - не"
" пользователь</b>"
),
"cancel": "🚫 Отмена",
"confirm": "👑 Подтвердить",
"self": (
"<emoji document_id='5415905755406539934'>🚫</emoji> <b>Нельзя управлять"
" своими правами!</b>"
),
"warning": (
'⚠️ <b>Ты действительно хочешь добавить <a href="tg://user?id={}">{}</a> в'
" группу </b><code>{}</code><b>!\nЭто действие может передать частичный или"
" полный доступ к юзерботу этому пользователю!</b>"
),
"suggest_nonick": (
"🔰 <i>Хочешь ли ты включить NoNick для этого пользователя?</i>"
),
"user_nn": '🔰 <b>NoNick для <a href="tg://user?id={}">{}</a> включен</b>',
"enable_nonick_btn": "🔰 Включить",
"owner_added": (
'<emoji document_id="5386399931378440814">😎</emoji> <b><a'
' href="tg://user?id={}">{}</a> добавлен в группу </b><code>owner</code>'
),
"sudo_added": (
'<emoji document_id="5418133868475587618">🧐</emoji> <b><a'
' href="tg://user?id={}">{}</a> добавлен в группу </b><code>sudo</code>'
),
"support_added": (
'<emoji document_id="5415729507128580146">🤓</emoji> <b><a'
' href="tg://user?id={}">{}</a> добавлен в группу </b><code>support</code>'
),
"owner_removed": (
'<emoji document_id="5386399931378440814">😎</emoji> <b><a'
' href="tg://user?id={}">{}</a> удален из группы </b><code>owner</code>'
),
"sudo_removed": (
'<emoji document_id="5418133868475587618">🧐</emoji> <b><a'
' href="tg://user?id={}">{}</a> удален из группы </b><code>sudo</code>'
),
"support_removed": (
'<emoji document_id="5415729507128580146">🤓</emoji> <b><a'
' href="tg://user?id={}">{}</a> удален из группы </b><code>support</code>'
),
"_cls_doc": "Управление настройками безопасности",
}
async def inline__switch_perm(
self,
call: InlineCall,
command: str,
group: str,
level: bool,
is_inline: bool,
):
cmd = (
self.allmodules.inline_handlers[command]
if is_inline
else self.allmodules.commands[command]
)
mask = self._db.get(security.__name__, "masks", {}).get(
f"{cmd.__module__}.{cmd.__name__}",
getattr(cmd, "security", security.DEFAULT_PERMISSIONS),
)
bit = security.BITMAP[group.upper()]
if level:
mask |= bit
else:
mask &= ~bit
masks = self._db.get(security.__name__, "masks", {})
masks[f"{cmd.__module__}.{cmd.__name__}"] = mask
self._db.set(security.__name__, "masks", masks)
if (
not self._db.get(security.__name__, "bounding_mask", DEFAULT_PERMISSIONS)
& bit
and level
):
await call.answer(
"Security value set but not applied. Consider enabling this value in"
f" .{'inlinesec' if is_inline else 'security'}",
show_alert=True,
)
else:
await call.answer("Security value set!")
await call.edit(
self.strings("permissions").format(
f"@{self.inline.bot_username} " if is_inline else self.get_prefix(),
command,
),
reply_markup=self._build_markup(cmd, is_inline),
)
async def inline__switch_perm_bm(
self,
call: InlineCall,
group: str,
level: bool,
is_inline: bool,
):
mask = self._db.get(security.__name__, "bounding_mask", DEFAULT_PERMISSIONS)
bit = security.BITMAP[group.upper()]
if level:
mask |= bit
else:
mask &= ~bit
self._db.set(security.__name__, "bounding_mask", mask)
await call.answer("Bounding mask value set!")
await call.edit(
self.strings("global"),
reply_markup=self._build_markup_global(is_inline),
)
def _build_markup(
self,
command: callable,
is_inline: bool = False,
) -> List[List[dict]]:
perms = self._get_current_perms(command, is_inline)
return (
utils.chunks(
[
{
"text": f"{'' if level else '🚫'} {self.strings[group]}",
"callback": self.inline__switch_perm,
"args": (
command.__name__.rsplit("_inline_handler", maxsplit=1)[0],
group,
not level,
is_inline,
),
}
for group, level in perms.items()
],
2,
)
+ [[{"text": self.strings("close_menu"), "action": "close"}]]
if is_inline
else utils.chunks(
[
{
"text": f"{'' if level else '🚫'} {self.strings[group]}",
"callback": self.inline__switch_perm,
"args": (
command.__name__.rsplit("cmd", maxsplit=1)[0],
group,
not level,
is_inline,
),
}
for group, level in perms.items()
],
2,
)
+ [
[
{
"text": self.strings("close_menu"),
"action": "close",
}
]
]
)
def _build_markup_global(self, is_inline: bool = False) -> List[List[dict]]:
perms = self._get_current_bm(is_inline)
return utils.chunks(
[
{
"text": f"{'' if level else '🚫'} {self.strings[group]}",
"callback": self.inline__switch_perm_bm,
"args": (group, not level, is_inline),
}
for group, level in perms.items()
],
2,
) + [[{"text": self.strings("close_menu"), "action": "close"}]]
def _get_current_bm(self, is_inline: bool = False) -> dict:
return self._perms_map(
self._db.get(security.__name__, "bounding_mask", DEFAULT_PERMISSIONS),
is_inline,
)
@staticmethod
def _perms_map(perms: int, is_inline: bool) -> dict:
return (
{
"sudo": bool(perms & SUDO),
"support": bool(perms & SUPPORT),
"everyone": bool(perms & EVERYONE),
}
if is_inline
else {
"sudo": bool(perms & SUDO),
"support": bool(perms & SUPPORT),
"group_owner": bool(perms & GROUP_OWNER),
"group_admin_add_admins": bool(perms & GROUP_ADMIN_ADD_ADMINS),
"group_admin_change_info": bool(perms & GROUP_ADMIN_CHANGE_INFO),
"group_admin_ban_users": bool(perms & GROUP_ADMIN_BAN_USERS),
"group_admin_delete_messages": bool(
perms & GROUP_ADMIN_DELETE_MESSAGES
),
"group_admin_pin_messages": bool(perms & GROUP_ADMIN_PIN_MESSAGES),
"group_admin_invite_users": bool(perms & GROUP_ADMIN_INVITE_USERS),
"group_admin": bool(perms & GROUP_ADMIN),
"group_member": bool(perms & GROUP_MEMBER),
"pm": bool(perms & PM),
"everyone": bool(perms & EVERYONE),
}
)
def _get_current_perms(
self,
command: callable,
is_inline: bool = False,
) -> dict:
config = self._db.get(security.__name__, "masks", {}).get(
f"{command.__module__}.{command.__name__}",
getattr(command, "security", self._client.dispatcher.security._default),
)
return self._perms_map(config, is_inline)
@loader.owner
@loader.command(ru_doc="[команда] - Настроить разрешения для команды")
async def security(self, message: Message):
"""[command] - Configure command's security settings"""
args = utils.get_args_raw(message).lower().strip()
if args and args not in self.allmodules.commands:
await utils.answer(message, self.strings("no_command").format(args))
return
if not args:
await self.inline.form(
self.strings("global"),
reply_markup=self._build_markup_global(),
message=message,
ttl=5 * 60,
)
return
cmd = self.allmodules.commands[args]
await self.inline.form(
self.strings("permissions").format(self.get_prefix(), args),
reply_markup=self._build_markup(cmd),
message=message,
ttl=5 * 60,
)
@loader.owner
@loader.command(ru_doc="[команда] - Настроить разрешения для инлайн команды")
async def inlinesec(self, message: Message):
"""[command] - Configure inline command's security settings"""
args = utils.get_args_raw(message).lower().strip()
if not args:
await self.inline.form(
self.strings("global"),
reply_markup=self._build_markup_global(True),
message=message,
ttl=5 * 60,
)
return
if args not in self.allmodules.inline_handlers:
await utils.answer(message, self.strings("no_command").format(args))
return
i_handler = self.allmodules.inline_handlers[args]
await self.inline.form(
self.strings("permissions").format(f"@{self.inline.bot_username} ", args),
reply_markup=self._build_markup(i_handler, True),
message=message,
ttl=5 * 60,
)
async def _resolve_user(self, message: Message):
reply = await message.get_reply_message()
args = utils.get_args_raw(message)
if not args and not reply:
await utils.answer(message, self.strings("no_user"))
return
user = None
if args:
try:
if str(args).isdigit():
args = int(args)
user = await self._client.get_entity(args)
except Exception:
pass
if user is None:
user = await self._client.get_entity(reply.sender_id)
if not isinstance(user, (User, PeerUser)):
await utils.answer(message, self.strings("not_a_user"))
return
if user.id == self.tg_id:
await utils.answer(message, self.strings("self"))
return
return user
async def _add_to_group(
self,
message: Union[Message, InlineCall], # noqa: F821
group: str,
confirmed: bool = False,
user: int = None,
):
if user is None:
user = await self._resolve_user(message)
if not user:
return
if isinstance(user, int):
user = await self._client.get_entity(user)
if not confirmed:
await self.inline.form(
self.strings("warning").format(
user.id,
utils.escape_html(get_display_name(user)),
group,
),
message=message,
ttl=10 * 60,
reply_markup=[
{
"text": self.strings("cancel"),
"action": "close",
},
{
"text": self.strings("confirm"),
"callback": self._add_to_group,
"args": (group, True, user.id),
},
],
)
return
self._db.set(
security.__name__,
group,
list(set(self._db.get(security.__name__, group, []) + [user.id])),
)
m = (
self.strings(f"{group}_added").format(
user.id,
utils.escape_html(get_display_name(user)),
)
+ "\n\n"
+ self.strings("suggest_nonick")
)
await utils.answer(message, m)
await message.edit(
m,
reply_markup=[
{
"text": self.strings("cancel"),
"action": "close",
},
{
"text": self.strings("enable_nonick_btn"),
"callback": self._enable_nonick,
"args": (user,),
},
],
)
async def _enable_nonick(self, call: InlineCall, user: User):
self._db.set(
main.__name__,
"nonickusers",
list(set(self._db.get(main.__name__, "nonickusers", []) + [user.id])),
)
await call.edit(
self.strings("user_nn").format(
user.id,
utils.escape_html(get_display_name(user)),
)
)
await call.unload()
async def _remove_from_group(self, message: Message, group: str):
user = await self._resolve_user(message)
if not user:
return
self._db.set(
security.__name__,
group,
list(set(self._db.get(security.__name__, group, [])) - {user.id}),
)
m = self.strings(f"{group}_removed").format(
user.id,
utils.escape_html(get_display_name(user)),
)
await utils.answer(message, m)
async def _list_group(self, message: Message, group: str):
_resolved_users = []
for user in self._db.get(security.__name__, group, []) + (
[self.tg_id] if group == "owner" else []
):
try:
_resolved_users += [await self._client.get_entity(user)]
except Exception:
pass
if _resolved_users:
await utils.answer(
message,
self.strings(f"{group}_list").format(
"\n".join(
[
self.strings("li").format(
i.id, utils.escape_html(get_display_name(i))
)
for i in _resolved_users
]
)
),
)
else:
await utils.answer(message, self.strings(f"no_{group}"))
@loader.command(ru_doc="<пользователь> - Добавить пользователя в группу `sudo`")
async def sudoadd(self, message: Message):
"""<user> - Add user to `sudo`"""
await self._add_to_group(message, "sudo")
@loader.command(ru_doc="<пользователь> - Добавить пользователя в группу `owner`")
async def owneradd(self, message: Message):
"""<user> - Add user to `owner`"""
await self._add_to_group(message, "owner")
@loader.command(ru_doc="<пользователь> - Добавить пользователя в группу `support`")
async def supportadd(self, message: Message):
"""<user> - Add user to `support`"""
await self._add_to_group(message, "support")
@loader.command(ru_doc="<пользователь> - Удалить пользователя из группы `sudo`")
async def sudorm(self, message: Message):
"""<user> - Remove user from `sudo`"""
await self._remove_from_group(message, "sudo")
@loader.command(ru_doc="<пользователь> - Удалить пользователя из группы `owner`")
async def ownerrm(self, message: Message):
"""<user> - Remove user from `owner`"""
await self._remove_from_group(message, "owner")
@loader.command(ru_doc="<пользователь> - Удалить пользователя из группы `support`")
async def supportrm(self, message: Message):
"""<user> - Remove user from `support`"""
await self._remove_from_group(message, "support")
@loader.command(ru_doc="Показать список пользователей в группе `sudo`")
async def sudolist(self, message: Message):
"""List users in `sudo`"""
await self._list_group(message, "sudo")
@loader.command(ru_doc="Показать список пользователей в группе `owner`")
async def ownerlist(self, message: Message):
"""List users in `owner`"""
await self._list_group(message, "owner")
@loader.command(ru_doc="Показать список пользователей в группе `support`")
async def supportlist(self, message: Message):
"""List users in `support`"""
await self._list_group(message, "support")