mirror of
https://github.com/MuRuLOSE/limoka.git
synced 2026-06-16 14:34:17 +02:00
Added and updated repositories 2026-01-06 01:11:05
This commit is contained in:
999
coddrago/modules/chatmodule.py
Normal file
999
coddrago/modules/chatmodule.py
Normal file
@@ -0,0 +1,999 @@
|
||||
# meta developer: @codrago_m
|
||||
# scope: disable_onload_docs
|
||||
# packurl: https://raw.githubusercontent.com/coddrago/modules/refs/heads/main/translations/chatmodule.yml
|
||||
|
||||
import logging
|
||||
import typing
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
from telethon.tl import types
|
||||
from telethon.tl.functions import channels, messages
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger("ChatModule")
|
||||
|
||||
|
||||
@loader.tds
|
||||
class ChatModuleMod(loader.Module):
|
||||
strings = {
|
||||
"name": "ChatModule",
|
||||
}
|
||||
|
||||
async def client_ready(self, client, db):
|
||||
self._client = client
|
||||
self._db = db
|
||||
self.xdlib = await self.import_lib(
|
||||
"https://raw.githubusercontent.com/coddrago/modules/refs/heads/main/libs/xdlib.py",
|
||||
suspend_on_error=True,
|
||||
)
|
||||
|
||||
@loader.command(ru_doc="[reply] - Узнать ID")
|
||||
async def id(self, message):
|
||||
"""[reply] - Get the ID"""
|
||||
ids = [self.strings["my_id"].format(id=self.tg_id)]
|
||||
if message.is_private:
|
||||
ids.append(self.strings["user_id"].format(id=message.to_id.user_id))
|
||||
return await utils.answer(message, "\n".join(ids))
|
||||
ids.append(self.strings["chat_id"].format(id=message.chat_id))
|
||||
reply = await message.get_reply_message()
|
||||
if (
|
||||
reply
|
||||
and not getattr(reply, "is_private")
|
||||
and not getattr(reply, "sender_id") == self.tg_id
|
||||
):
|
||||
user_id = (await reply.get_sender()).id
|
||||
ids.append(self.strings["user_id"].format(id=user_id))
|
||||
return await utils.answer(message, "\n".join(ids))
|
||||
|
||||
@loader.command(
|
||||
ru_doc="[reply/-u username/id] - Посмотреть права администратора пользователя",
|
||||
)
|
||||
@loader.tag("no_pm")
|
||||
async def rights(self, message):
|
||||
"""[reply/-u username/id] - Check user's admin rights"""
|
||||
opts = self.xdlib.parse.opts(utils.get_args(message))
|
||||
reply = await message.get_reply_message()
|
||||
user = opts.get("u") or opts.get("user") or (reply.sender_id if reply else None)
|
||||
if not user:
|
||||
return await utils.answer(message, self.strings["no_user"])
|
||||
rights = await self.xdlib.chat.get_rights(message.chat, user)
|
||||
|
||||
participant = rights.participant
|
||||
user = await self._client.get_entity(user)
|
||||
if not hasattr(participant, "admin_rights"):
|
||||
return await utils.answer(
|
||||
message, self.strings["not_an_admin"].format(user=user.first_name)
|
||||
)
|
||||
if participant.admin_rights:
|
||||
can_do = []
|
||||
rights = participant.to_dict().get("admin_rights")
|
||||
for right, is_permitted in rights.items():
|
||||
if right == "_":
|
||||
continue
|
||||
if is_permitted:
|
||||
can_do.append(right)
|
||||
promoter = (
|
||||
await self._client.get_entity(participant.promoted_by)
|
||||
if hasattr(participant, "promoted_by")
|
||||
else None
|
||||
)
|
||||
return await utils.answer(
|
||||
message,
|
||||
self.strings["admin_rights"].format(
|
||||
rights="\n".join(
|
||||
[
|
||||
f"<emoji document_id=5409029658794537988>✅</emoji> {self.strings[right]}"
|
||||
for right in can_do
|
||||
]
|
||||
),
|
||||
promoter_id=promoter.id if promoter else 0,
|
||||
promoter_name=(
|
||||
promoter.first_name if promoter else self.strings["no"]
|
||||
),
|
||||
name=user.first_name,
|
||||
),
|
||||
)
|
||||
return await utils.answer(
|
||||
message, self.strings["not_an_admin"].format(user=user.first_name)
|
||||
)
|
||||
|
||||
@loader.command(
|
||||
ru_doc="Покинуть чат",
|
||||
)
|
||||
@loader.tag("no_pm")
|
||||
async def leave(self, message):
|
||||
"""Leave chat"""
|
||||
await message.delete()
|
||||
await self._client(channels.LeaveChannelRequest((await message.get_chat()).id))
|
||||
|
||||
@loader.command(
|
||||
ru_doc="[a[1-100] b[1-100]] | [reply] Удалить сообщения",
|
||||
)
|
||||
async def d(self, message):
|
||||
"""[a[1-100] b[1-100]] | [reply] - Delete messages"""
|
||||
await self.xdlib.messages.delete_messages(message)
|
||||
|
||||
@loader.command(ru_doc="[reply] - Закрепить сообщение")
|
||||
@loader.tag("only_reply")
|
||||
async def pin(self, message):
|
||||
"""[reply] - Pin a message"""
|
||||
reply = await message.get_reply_message()
|
||||
try:
|
||||
await reply.pin(notify=True, pm_oneside=False)
|
||||
except Exception as e:
|
||||
logger.error(str(e))
|
||||
return await utils.answer(message, self.strings["pin_failed"])
|
||||
await utils.answer(message, self.strings["pinned"])
|
||||
|
||||
@loader.command(ru_doc="Открепить сообщение")
|
||||
@loader.tag("only_reply")
|
||||
async def unpin(self, message):
|
||||
"""Unpin a message"""
|
||||
reply = await message.get_reply_message()
|
||||
try:
|
||||
await reply.unpin()
|
||||
except Exception as e:
|
||||
logger.error(str(e))
|
||||
return await utils.answer(message, self.strings["unpin_failed"])
|
||||
await utils.answer(message, self.strings["unpinned"])
|
||||
|
||||
@loader.command(ru_doc="[-c id] Удаляет группу/канал")
|
||||
async def dgc(self, message):
|
||||
"""[-c id] Delete chat/channel"""
|
||||
args = utils.get_args(message)
|
||||
opts = self.xdlib.parse.opts(args)
|
||||
chat_id = opts.get("c") or opts.get("chat")
|
||||
if chat_id:
|
||||
chat = await self._client.get_entity(chat_id)
|
||||
if isinstance(chat, types.Channel):
|
||||
await self._client(channels.DeleteChannelRequest(chat.id))
|
||||
elif isinstance(chat, types.Chat):
|
||||
await self._client(messages.DeleteChatRequest(chat.id))
|
||||
else:
|
||||
return await utils.answer(message, self.strings["failed_to_delete"])
|
||||
return await utils.answer(message, self.strings["successful_delete"])
|
||||
if isinstance(message.chat, types.Channel):
|
||||
await self._client(channels.DeleteChannelRequest(message.chat))
|
||||
elif isinstance(message.chat, types.Chat):
|
||||
await self._client(messages.DeleteChatRequest(message.chat))
|
||||
else:
|
||||
return await utils.answer(message, self.strings["failed_to_delete"])
|
||||
return
|
||||
|
||||
@loader.command(ru_doc="Очищает группу/канал от удаленных аккаунтов")
|
||||
@loader.tag("no_pm")
|
||||
async def flush(self, message):
|
||||
"""Removes deleted accounts from the chat/channel"""
|
||||
chat = await message.get_chat()
|
||||
|
||||
if not getattr(chat, "admin_rights", False) and not getattr(
|
||||
getattr(chat, "admin_rights", None), "ban_users", False
|
||||
):
|
||||
return await utils.answer(message, self.strings["no_rights"])
|
||||
|
||||
deleted = await self.xdlib.chat.get_deleted(chat)
|
||||
if not deleted:
|
||||
return await utils.answer(message, self.strings["no_deleted_accounts"])
|
||||
for to_delete in deleted:
|
||||
try:
|
||||
await self._client.kick_participant(chat, to_delete)
|
||||
except Exception as e:
|
||||
logger.error(str(e))
|
||||
return await utils.answer(message, self.strings["error"])
|
||||
return await utils.answer(message, self.strings["kicked_deleted_accounts"])
|
||||
|
||||
@loader.command(ru_doc="Показывает админов в группе/канале")
|
||||
@loader.tag("no_pm")
|
||||
async def admins(self, message):
|
||||
"""Shows the admins in the chat/channel"""
|
||||
admins = await self.xdlib.chat.get_admins(message.chat, True)
|
||||
creator = await self.xdlib.chat.get_creator(message.chat)
|
||||
return await utils.answer(
|
||||
message,
|
||||
self.strings["admin_list"].format(
|
||||
id=creator.id if creator else 0,
|
||||
name=creator.first_name if creator else self.strings["no"],
|
||||
admins_count=len(admins) or 0,
|
||||
admins=(
|
||||
"\n".join(
|
||||
f"<emoji document_id=5774022692642492953>✅</emoji> <a href='tg://user?id={admin.id}'>{admin.first_name}</a> [<code>{admin.id}</code>] / <code>{admin.participant.rank}</code>"
|
||||
for admin in admins
|
||||
)
|
||||
if admins
|
||||
else f"\n{self.strings['no_admins_in_chat']}"
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
@loader.command(ru_doc="Показывает ботов в группе/канале")
|
||||
@loader.tag("no_pm")
|
||||
async def bots(self, message):
|
||||
"""Shows the bots in the chat/channel"""
|
||||
bots = await self.xdlib.chat.get_bots(message.chat)
|
||||
if not bots:
|
||||
return await utils.answer(message, self.strings["no_bots_in_chat"])
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings["bot_list"].format(
|
||||
count=len(bots),
|
||||
bots="\n".join(
|
||||
[
|
||||
f"<emoji document_id=5774022692642492953>✅</emoji> <a href='tg://user?id={bot.id}'>{bot.first_name}</a> [<code>{bot.id}</code>]"
|
||||
for bot in bots
|
||||
]
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
@loader.command(ru_doc="Показывает простых участников чата/канала")
|
||||
@loader.tag("no_pm")
|
||||
async def users(self, message):
|
||||
"""Shows the users in the chat/channel"""
|
||||
users = await self.xdlib.chat.get_members(message.chat)
|
||||
if not users:
|
||||
return await utils.answer(message, self.strings["no_user_in_chat"])
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings["user_list"].format(
|
||||
count=len(users),
|
||||
users="\n".join(
|
||||
[
|
||||
f"<emoji document_id=5774022692642492953>✅</emoji> <a href='tg://user?id={user.id}'>{user.first_name}</a> [<code>{user.id}</code>]"
|
||||
for user in users
|
||||
]
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
@loader.command(ru_doc="[-u] [-t] [-r] Забанить участника")
|
||||
@loader.tag("no_pm")
|
||||
async def ban(self, message):
|
||||
"""[-u] [-t] [-r] Ban a participant temporarily or permanently"""
|
||||
opts = self.xdlib.parse.opts(utils.get_args(message))
|
||||
reason = opts.get("r")
|
||||
reply = await message.get_reply_message()
|
||||
user = opts.get("u") or (reply.sender_id if reply else None)
|
||||
user = await self._client.get_entity(user) if user else None
|
||||
strings = []
|
||||
if not user:
|
||||
return await utils.answer(message, self.strings["no_user"])
|
||||
|
||||
seconds = self.xdlib.parse.time(opts.get("t")) if opts.get("t") else None
|
||||
until_date = (
|
||||
(datetime.now(timezone.utc) + timedelta(seconds=seconds))
|
||||
if seconds
|
||||
else None
|
||||
)
|
||||
time_info = f" {self.xdlib.format.time(seconds)}" if seconds else None
|
||||
try:
|
||||
await self._client.edit_permissions(
|
||||
message.chat,
|
||||
user,
|
||||
until_date=until_date,
|
||||
view_messages=False,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(str(e))
|
||||
return await utils.answer(message, self.strings["error"])
|
||||
strings.append(
|
||||
self.strings["user_is_banned"].format(
|
||||
id=user.id,
|
||||
name=(
|
||||
getattr(user, "first_name")
|
||||
if hasattr(user, "first_name")
|
||||
else getattr(user, "title")
|
||||
),
|
||||
time_info=time_info or self.strings["forever"],
|
||||
)
|
||||
)
|
||||
|
||||
if reason:
|
||||
strings.append(self.strings["reason"].format(reason=reason))
|
||||
return await utils.answer(message, "\n".join(strings))
|
||||
|
||||
@loader.command(ru_doc="Разбанить пользователя")
|
||||
@loader.tag("no_pm")
|
||||
async def unban(self, message):
|
||||
"""[-u] Unban a user"""
|
||||
opts = self.xdlib.parse.opts(utils.get_args(message))
|
||||
reply = await message.get_reply_message()
|
||||
user = opts.get("u") or (reply.sender_id if reply else None)
|
||||
user = await self._client.get_entity(user) if user else None
|
||||
if not user:
|
||||
return await utils.answer(message, self.strings["no_user"])
|
||||
try:
|
||||
await self._client.edit_permissions(message.chat, user, view_messages=True)
|
||||
except Exception as e:
|
||||
logger.error(str(e))
|
||||
return await utils.answer(message, self.strings["error"])
|
||||
return await utils.answer(
|
||||
message,
|
||||
self.strings["user_is_unbanned"].format(
|
||||
id=user.id,
|
||||
name=(
|
||||
getattr(user, "first_name")
|
||||
if hasattr(user, "first_name")
|
||||
else getattr(user, "title")
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
@loader.command(ru_doc="[-u] [-r] Кикнуть участника")
|
||||
@loader.tag("no_pm")
|
||||
async def kick(self, message):
|
||||
"""[-u] [-r] Kick a participant"""
|
||||
opts = self.xdlib.parse.opts(utils.get_args(message))
|
||||
reason = opts.get("r")
|
||||
reply = await message.get_reply_message()
|
||||
user = opts.get("u") or (reply.sender_id if reply else None)
|
||||
user = await self._client.get_entity(user) if user else None
|
||||
strings = []
|
||||
if not user:
|
||||
return await utils.answer(message, self.strings["no_user"])
|
||||
try:
|
||||
await self._client.kick_participant(message.chat, user)
|
||||
except Exception as e:
|
||||
logging.error(str(e))
|
||||
return await utils.answer(message, self.strings["error"])
|
||||
strings.append(
|
||||
self.strings["user_is_kicked"].format(
|
||||
id=user.id,
|
||||
name=(
|
||||
getattr(user, "first_name")
|
||||
if hasattr(user, "first_name")
|
||||
else getattr(user, "title")
|
||||
),
|
||||
)
|
||||
)
|
||||
if reason:
|
||||
strings.append(self.strings["reason"].format(reason=reason))
|
||||
return await utils.answer(message, "\n".join(strings))
|
||||
|
||||
@loader.command(ru_doc="[-u] [-t] [-r] Замутить участника")
|
||||
@loader.tag("no_pm")
|
||||
async def mute(self, message):
|
||||
"""[-u] [-t] [-r] Mute a participant temporarily or permanently"""
|
||||
opts = self.xdlib.parse.opts(utils.get_args(message))
|
||||
reason = opts.get("r")
|
||||
reply = await message.get_reply_message()
|
||||
user = opts.get("u") or (reply.sender_id if reply else None)
|
||||
user = await self._client.get_entity(user) if user else None
|
||||
strings = []
|
||||
if not user:
|
||||
return await utils.answer(message, self.strings["no_user"])
|
||||
|
||||
seconds = self.xdlib.parse.time(opts.get("t")) if opts.get("t") else None
|
||||
until_date = (
|
||||
(datetime.now(timezone.utc) + timedelta(seconds=seconds))
|
||||
if seconds
|
||||
else None
|
||||
)
|
||||
time_info = f" {self.xdlib.format.time(seconds)}" if seconds else None
|
||||
try:
|
||||
await self._client.edit_permissions(
|
||||
message.chat,
|
||||
user,
|
||||
until_date=until_date,
|
||||
send_messages=False,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(str(e))
|
||||
return await utils.answer(message, self.strings["error"])
|
||||
strings.append(
|
||||
self.strings["user_is_muted"].format(
|
||||
id=user.id,
|
||||
name=(
|
||||
getattr(user, "first_name")
|
||||
if hasattr(user, "first_name")
|
||||
else getattr(user, "title")
|
||||
),
|
||||
time_info=time_info or self.strings["forever"],
|
||||
)
|
||||
)
|
||||
|
||||
if reason:
|
||||
strings.append(self.strings["reason"].format(reason=reason))
|
||||
return await utils.answer(message, "\n".join(strings))
|
||||
|
||||
@loader.command(ru_doc="Размутить участника")
|
||||
@loader.tag("no_pm")
|
||||
async def unmute(self, message):
|
||||
"""Unmute a participant"""
|
||||
opts = self.xdlib.parse.opts(utils.get_args(message))
|
||||
reply = await message.get_reply_message()
|
||||
user = opts.get("u") or (reply.sender_id if reply else None)
|
||||
user = await self._client.get_entity(user) if user else None
|
||||
if not user:
|
||||
return await utils.answer(message, self.strings["no_user"])
|
||||
try:
|
||||
await self._client.edit_permissions(message.chat, user, send_messages=True)
|
||||
except Exception as e:
|
||||
logger.error(str(e))
|
||||
return await utils.answer(message, self.strings["error"])
|
||||
return await utils.answer(
|
||||
message,
|
||||
self.strings["user_is_unmuted"].format(
|
||||
id=user.id,
|
||||
name=(
|
||||
getattr(user, "first_name")
|
||||
if hasattr(user, "first_name")
|
||||
else getattr(user, "title")
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
@loader.command(
|
||||
ru_doc="[-g|--group name] [-c|--channel name] - Создать группу/канал"
|
||||
)
|
||||
async def create(self, message):
|
||||
"""[-g|--group name] [-c|--channel name] - Create group/channel"""
|
||||
opts = self.xdlib.parse.opts(utils.get_args(message))
|
||||
group_name = opts.get("g") or opts.get("group")
|
||||
channel_name = opts.get("c") or opts.get("channel")
|
||||
if channel_name:
|
||||
result = await self._client(
|
||||
channels.CreateChannelRequest(
|
||||
title=channel_name, broadcast=True, about=""
|
||||
)
|
||||
)
|
||||
chat = await self.xdlib.chat.get_info(result.chats[0])
|
||||
return await utils.answer(
|
||||
message,
|
||||
self.strings["channel_created"].format(
|
||||
link=chat.get("link"), title=channel_name
|
||||
),
|
||||
)
|
||||
if group_name:
|
||||
result = await self._client(
|
||||
channels.CreateChannelRequest(
|
||||
title=group_name, megagroup=True, about=""
|
||||
)
|
||||
)
|
||||
chat = await self.xdlib.chat.get_info(result.chats[0])
|
||||
return await utils.answer(
|
||||
message,
|
||||
self.strings["group_created"].format(
|
||||
link=chat.get("link"), title=group_name
|
||||
),
|
||||
)
|
||||
return await utils.answer(message, self.strings["invalid_args"])
|
||||
|
||||
@loader.command(
|
||||
ru_doc="Отключает звук и архивирует чат",
|
||||
)
|
||||
async def dnd(self, message):
|
||||
"""Mutes and archives the current chat"""
|
||||
dnd = await utils.dnd(self._client, await message.get_chat())
|
||||
if dnd:
|
||||
return await utils.answer(message, self.strings["dnd"])
|
||||
else:
|
||||
return await utils.answer(message, self.strings["dnd_failed"])
|
||||
|
||||
@loader.command(
|
||||
ru_doc="-u username/id - Пригласить пользователя в чат (-b пригласить инлайн бота)"
|
||||
)
|
||||
async def invite(self, message):
|
||||
"""-u username/id - Invite a user to the chat (use -b to invite the inline bot)"""
|
||||
args = utils.get_args(message)
|
||||
opts = self.xdlib.parse.opts(args)
|
||||
if opts.get("b") or opts.get("bot"):
|
||||
invited = await self.xdlib.chat.invite_bot(self._client, message.chat)
|
||||
entity = await self._client.get_entity(self.inline.bot_id)
|
||||
if invited:
|
||||
return await utils.answer(
|
||||
message,
|
||||
self.strings["user_invited"].format(
|
||||
user=entity.first_name, id=entity.id
|
||||
),
|
||||
)
|
||||
return await utils.answer(message, self.strings["user_not_invited"])
|
||||
reply = await message.get_reply_message()
|
||||
user = opts.get("u") or opts.get("user") or (reply.sender_id if reply else None)
|
||||
if not user:
|
||||
return await utils.answer(message, self.strings["no_user"])
|
||||
entity = await self._client.get_entity(user)
|
||||
invited = await self.xdlib.chat.invite_user(message.chat, user)
|
||||
if invited:
|
||||
return await utils.answer(
|
||||
message,
|
||||
self.strings["user_invited"].format(
|
||||
user=entity.first_name, id=entity.id
|
||||
),
|
||||
)
|
||||
return await utils.answer(message, self.strings["user_not_invited"])
|
||||
|
||||
@loader.command(ru_doc="[-i] Получить информацию о сущности")
|
||||
async def inspect(self, message):
|
||||
"""[-i] Get the info about the entity"""
|
||||
opts = self.xdlib.parse.opts(utils.get_args(message))
|
||||
reply = await message.get_reply_message()
|
||||
target = (
|
||||
opts.get("i")
|
||||
or (reply.sender if reply else await message.get_chat())
|
||||
or None
|
||||
)
|
||||
if not target:
|
||||
return await utils.answer(message, self.strings["no_user"])
|
||||
ent = await self._client.get_entity(target)
|
||||
if isinstance(ent, types.Channel):
|
||||
try:
|
||||
chatinfo = await self.xdlib.chat.get_info(ent)
|
||||
photo = chatinfo.get("chat_photo")
|
||||
photo = photo if not isinstance(photo, types.PhotoEmpty) else None
|
||||
return await utils.answer(
|
||||
message,
|
||||
self.strings["chatinfo"].format(
|
||||
id=chatinfo.get("id"),
|
||||
title=chatinfo.get("title"),
|
||||
about=chatinfo.get("about") or self.strings["no"],
|
||||
admins_count=chatinfo.get("admins_count"),
|
||||
online_count=chatinfo.get("online_count"),
|
||||
participants_count=chatinfo.get("participants_count"),
|
||||
kicked_count=chatinfo.get("kicked_count"),
|
||||
slowmode_seconds=(
|
||||
self.xdlib.format.time(chatinfo.get("slowmode_seconds"))
|
||||
if chatinfo.get("slowmode_seconds")
|
||||
else self.strings["no"]
|
||||
),
|
||||
call=(
|
||||
self.strings["yes"]
|
||||
if chatinfo.get("call")
|
||||
else self.strings["no"]
|
||||
),
|
||||
ttl_period=(
|
||||
self.xdlib.format.time(chatinfo.get("ttl_period"))
|
||||
if chatinfo.get("ttl_period")
|
||||
else self.strings["no"]
|
||||
),
|
||||
requests_pending=chatinfo.get("requests_pending"),
|
||||
recent_requesters=", ".join(
|
||||
[
|
||||
f"<code>{user}</code>"
|
||||
for user in chatinfo.get("recent_requesters")
|
||||
]
|
||||
)
|
||||
or self.strings["no"],
|
||||
linked_chat_id=chatinfo.get("linked_chat_id")
|
||||
or self.strings["no"],
|
||||
antispam=(
|
||||
self.strings["yes"]
|
||||
if chatinfo.get("antispam")
|
||||
else self.strings["no"]
|
||||
),
|
||||
participants_hidden=(
|
||||
self.strings["yes"]
|
||||
if chatinfo.get("participants_hidden")
|
||||
else self.strings["no"]
|
||||
),
|
||||
link=chatinfo.get("link") or self.strings["no"],
|
||||
is_forum=(
|
||||
self.strings["yes"]
|
||||
if chatinfo.get("is_forum")
|
||||
else self.strings["no"]
|
||||
),
|
||||
type_of=(
|
||||
self.strings["type_group"]
|
||||
if chatinfo.get("is_group")
|
||||
else (
|
||||
self.strings["type_channel"]
|
||||
if chatinfo.get("is_channel")
|
||||
else self.strings["type_unknown"]
|
||||
)
|
||||
),
|
||||
),
|
||||
file=(
|
||||
types.InputMediaPhoto(
|
||||
types.InputPhoto(
|
||||
photo.id, photo.access_hash, photo.file_reference
|
||||
)
|
||||
)
|
||||
if photo
|
||||
else None
|
||||
),
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(str(e))
|
||||
return await utils.answer(message, self.strings["error"])
|
||||
if isinstance(ent, types.User):
|
||||
try:
|
||||
userinfo = await self.xdlib.user.get_info(ent)
|
||||
photo = userinfo.get("profile_photo")
|
||||
working_hours = (
|
||||
userinfo.get("business_work_hours").weekly_open
|
||||
if userinfo.get("business_work_hours")
|
||||
else 0
|
||||
)
|
||||
weekdays = [
|
||||
self.strings["monday"],
|
||||
self.strings["tuesday"],
|
||||
self.strings["wednesday"],
|
||||
self.strings["thursday"],
|
||||
self.strings["friday"],
|
||||
self.strings["saturday"],
|
||||
self.strings["sunday"],
|
||||
]
|
||||
personal_channel = userinfo.get("personal_channel")
|
||||
working_hours_output = []
|
||||
if working_hours:
|
||||
for item in working_hours:
|
||||
day_index = item.start_minute // (24 * 60)
|
||||
day = weekdays[day_index]
|
||||
|
||||
start = self.xdlib.parse.minutes_to_hhmm(item.start_minute)
|
||||
end = self.xdlib.parse.minutes_to_hhmm(item.end_minute)
|
||||
working_hours_output.append(f"<b>{day}: {start} - {end}</b>")
|
||||
return await utils.answer(
|
||||
message,
|
||||
self.strings["userinfo"].format(
|
||||
common_chats_count=userinfo.get("common_chats_count") or 0,
|
||||
phone=userinfo.get("phone") or self.strings["no"],
|
||||
common_chats=(
|
||||
", ".join(
|
||||
[
|
||||
f"<a href='{(await self.xdlib.chat.get_info(channel)).get('link')}'>{channel.title}</a>"
|
||||
for channel in userinfo.get("common_chats")
|
||||
]
|
||||
)
|
||||
if userinfo.get("common_chats")
|
||||
else self.strings["no"]
|
||||
),
|
||||
user_id=userinfo.get("id", 0),
|
||||
first_name=userinfo.get("first_name") or self.strings["no"],
|
||||
last_name=userinfo.get("last_name") or self.strings["no"],
|
||||
about=userinfo.get("about") or self.strings["no"],
|
||||
emoji_status=(
|
||||
f"<emoji document_id={userinfo.get('emoji_status')}>🌙</emoji>"
|
||||
if userinfo.get("emoji_status")
|
||||
else self.strings["no"]
|
||||
),
|
||||
business_work_hours=", ".join(working_hours_output)
|
||||
or self.strings["no"],
|
||||
birthday=(
|
||||
f"{userinfo.get('birthday').day or ''}."
|
||||
f"{userinfo.get('birthday').month or ''}."
|
||||
f"{userinfo.get('birthday').year or ''}"
|
||||
if userinfo.get("birthday")
|
||||
else self.strings["no"]
|
||||
),
|
||||
stargifts_count=userinfo.get("stargifts_count")
|
||||
or self.strings["no"],
|
||||
usernames=(
|
||||
", ".join(
|
||||
[
|
||||
f"@{username}"
|
||||
for username in userinfo.get("usernames")
|
||||
]
|
||||
)
|
||||
if userinfo.get("usernames")
|
||||
else self.strings["no"]
|
||||
),
|
||||
personal_channel=(
|
||||
f"<a href='{(await self.xdlib.chat.get_info(personal_channel)).get('link')}'>"
|
||||
f"{personal_channel.title}</a>"
|
||||
if personal_channel
|
||||
else self.strings["no"]
|
||||
),
|
||||
),
|
||||
file=(
|
||||
types.InputMediaPhoto(
|
||||
types.InputPhoto(
|
||||
photo.id, photo.access_hash, photo.file_reference
|
||||
)
|
||||
)
|
||||
if photo
|
||||
else None
|
||||
),
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
return await utils.answer(message, self.strings["error"])
|
||||
|
||||
@loader.command(ru_doc="[-a] [-d] Управлять заявками на вступление")
|
||||
@loader.tag("no_pm")
|
||||
async def requests(self, message):
|
||||
"""[-a] [-d] Manage join requests"""
|
||||
opts = self.xdlib.parse.opts(utils.get_args(message))
|
||||
approve_list = [x for x in str(opts.get("a", "")).split(",") if x]
|
||||
dismiss_list = [x for x in str(opts.get("d", "")).split(",") if x]
|
||||
sanitized_approve_list = [int(x) if x.isdigit() else x for x in approve_list]
|
||||
sanitized_dismiss_list = [int(x) if x.isdigit() else x for x in approve_list]
|
||||
all_list = sanitized_approve_list + sanitized_dismiss_list
|
||||
all_targets = [
|
||||
await self._client.get_entity(
|
||||
int(ent.strip()) if ent.strip().isdigit() else ent.strip()
|
||||
)
|
||||
for ent in all_list
|
||||
]
|
||||
for approve in approve_list:
|
||||
if approve.isdigit():
|
||||
await self.xdlib.chat.join_request(message.chat, int(approve), True)
|
||||
else:
|
||||
await self.xdlib.chat.join_request(message.chat, approve, True)
|
||||
for dismiss in dismiss_list:
|
||||
if dismiss.isdigit():
|
||||
await self.xdlib.chat.join_request(message.chat, int(dismiss), False)
|
||||
else:
|
||||
await self.xdlib.chat.join_request(message.chat, dismiss, False)
|
||||
return await utils.answer(
|
||||
message,
|
||||
self.strings["requests_checked"].format(
|
||||
entities=", ".join(
|
||||
ent.first_name
|
||||
or getattr(ent, "username", None)
|
||||
or str(getattr(ent, "id", "unknown"))
|
||||
for ent in all_targets
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
@loader.command(ru_doc="Получить все свои чаты/каналы")
|
||||
async def owns(self, message):
|
||||
"""Get all your chats/channels"""
|
||||
owns = await self.xdlib.dialog.get_owns(self._client)
|
||||
return await utils.answer(
|
||||
message,
|
||||
self.strings["owns"].format(
|
||||
num=len(owns),
|
||||
owns="\n".join(
|
||||
[
|
||||
f"<emoji document_id=5458833171846029357>✅</emoji> {own.title} [<code>{str(own.id).replace('-100', '')}</code>]"
|
||||
for own in owns
|
||||
]
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
@loader.command(ru_doc="[-r] [-u] [-f] - Выдать админку участнику")
|
||||
@loader.tag("no_pm")
|
||||
async def promote(self, message):
|
||||
"""[-r] [-u] [-f] - Promote a participant"""
|
||||
reply = await message.get_reply_message()
|
||||
opts = self.xdlib.parse.opts(utils.get_args(message))
|
||||
user = opts.get("u") or getattr(reply, "sender_id") or None
|
||||
if not user:
|
||||
return await utils.answer(message, self.strings["no_user"])
|
||||
user = await self._client.get_entity(user)
|
||||
rank = (opts.get("r")) or "XD Admin"
|
||||
chat = await message.get_chat()
|
||||
rights = await self.xdlib.chat.get_rights(message.chat, user)
|
||||
if (
|
||||
not chat.admin_rights
|
||||
or not getattr(chat.admin_rights, "add_admins")
|
||||
or (
|
||||
getattr(rights.participant, "promoted_by", self.tg_id) != self.tg_id
|
||||
and not getattr(chat, "creator", False)
|
||||
)
|
||||
):
|
||||
return await utils.answer(message, self.strings["no_rights"])
|
||||
full = opts.get("f")
|
||||
if full:
|
||||
my_rights = [
|
||||
r for r, y in chat.admin_rights.to_dict().items() if y and r != "_"
|
||||
]
|
||||
perms = self.xdlib.admin_rights(0)
|
||||
perms = perms.add(*my_rights)
|
||||
await self.xdlib.admin.set_rights(chat, user, perms.to_int(), rank)
|
||||
return await utils.answer(
|
||||
message,
|
||||
self.strings["promoted"].format(
|
||||
id=user.id,
|
||||
name=user.first_name
|
||||
if hasattr(user, "first_name")
|
||||
else user.title
|
||||
if hasattr(user, "title")
|
||||
else "None",
|
||||
rights=self.strings["full_rights"],
|
||||
),
|
||||
)
|
||||
mask = (
|
||||
self.xdlib.admin_rights.to_mask(rights.participant.admin_rights)
|
||||
if hasattr(rights.participant, "admin_rights")
|
||||
else 0
|
||||
)
|
||||
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings["promote"].format(
|
||||
id=user.id,
|
||||
name=user.first_name
|
||||
if hasattr(user, "first_name")
|
||||
else user.title
|
||||
if hasattr(user, "title")
|
||||
else "None",
|
||||
rank=rank,
|
||||
),
|
||||
reply_markup=await self.build_markup(user.id, chat.id, mask, rank),
|
||||
)
|
||||
|
||||
@loader.command(ru_doc="[-t] [-u] - Ограничить участника")
|
||||
@loader.tag("no_pm")
|
||||
async def restrict(self, message):
|
||||
"""[-t] [-u] - Restrict a participant"""
|
||||
reply = await message.get_reply_message()
|
||||
opts = self.xdlib.parse.opts(utils.get_args(message))
|
||||
|
||||
user = opts.get("u") or getattr(reply, "sender_id") or None
|
||||
if not user:
|
||||
return await utils.answer(message, self.strings["no_user"])
|
||||
|
||||
user = await self._client.get_entity(user)
|
||||
chat = await message.get_chat()
|
||||
|
||||
if not chat.admin_rights or not getattr(chat.admin_rights, "ban_users"):
|
||||
return await utils.answer(message, self.strings["no_rights"])
|
||||
duration = opts.get("t", None)
|
||||
if duration:
|
||||
duration = self.xdlib.format.time(self.xdlib.parse.time(duration))
|
||||
|
||||
rights = await self.xdlib.chat.get_rights(chat, user)
|
||||
mask = (
|
||||
self.xdlib.banned_rights.MAX_MASK
|
||||
- self.xdlib.banned_rights.to_mask(rights.participant.banned_rights)
|
||||
if hasattr(rights.participant, "banned_rights")
|
||||
else 0
|
||||
)
|
||||
rank = "-"
|
||||
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings["restrict"].format(
|
||||
id=user.id,
|
||||
name=user.first_name
|
||||
if hasattr(user, "first_name")
|
||||
else user.title
|
||||
if hasattr(user, "title")
|
||||
else "None",
|
||||
time=f" {duration}" if duration else self.strings["forever"],
|
||||
),
|
||||
reply_markup=await self.build_markup(
|
||||
user.id,
|
||||
chat.id,
|
||||
mask,
|
||||
rank,
|
||||
mode="restrict",
|
||||
duration=f" {duration}" if duration else None,
|
||||
),
|
||||
)
|
||||
|
||||
async def build_markup(
|
||||
self,
|
||||
user_id: int,
|
||||
chat_id: int,
|
||||
mask: int,
|
||||
rank: str,
|
||||
duration: typing.Optional[int] = None,
|
||||
mode="admin",
|
||||
):
|
||||
rights_cls = (
|
||||
self.xdlib.admin_rights if mode == "admin" else self.xdlib.banned_rights
|
||||
)
|
||||
rights_names = rights_cls.RIGHTS_LIST
|
||||
rights = rights_cls(mask)
|
||||
chat = await self._client.get_entity(chat_id)
|
||||
|
||||
markup = utils.chunks(
|
||||
[
|
||||
{
|
||||
"text": f"{'🟢' if rights.has_index(idx) else '🔴'} {self.strings[name]}",
|
||||
"callback": self._toggle_right,
|
||||
"args": (user_id, chat_id, mask, idx, rank, mode, duration),
|
||||
}
|
||||
for idx, name in enumerate(rights_names)
|
||||
if (
|
||||
name != "until_date"
|
||||
and not (
|
||||
getattr(chat.default_banned_rights, name, True)
|
||||
if mode != "admin"
|
||||
else False
|
||||
)
|
||||
)
|
||||
],
|
||||
2,
|
||||
)
|
||||
|
||||
markup.append(
|
||||
[
|
||||
{
|
||||
"text": self.strings["apply"],
|
||||
"callback": self._apply_rights,
|
||||
"args": (user_id, chat_id, mask, rank, mode, duration),
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
markup.append([{"text": self.strings["close"], "action": "close"}])
|
||||
return markup
|
||||
|
||||
async def _toggle_right(
|
||||
self,
|
||||
call,
|
||||
user_id: int,
|
||||
chat_id: int,
|
||||
mask: int,
|
||||
idx: int,
|
||||
rank: str,
|
||||
mode: str,
|
||||
duration: str,
|
||||
):
|
||||
new_mask = mask ^ (1 << idx)
|
||||
|
||||
new_markup = await self.build_markup(
|
||||
user_id, chat_id, new_mask, rank, mode=mode, duration=duration
|
||||
)
|
||||
|
||||
user = await self._client.get_entity(user_id)
|
||||
|
||||
title = self.strings["promote"] if mode == "admin" else self.strings["restrict"]
|
||||
|
||||
await utils.answer(
|
||||
call,
|
||||
title.format(
|
||||
id=user_id,
|
||||
name=user.first_name
|
||||
if hasattr(user, "first_name")
|
||||
else user.title
|
||||
if hasattr(user, "title")
|
||||
else "None",
|
||||
rank=rank,
|
||||
time=f" {duration}" if duration else self.strings["forever"],
|
||||
),
|
||||
reply_markup=new_markup,
|
||||
)
|
||||
|
||||
async def _apply_rights(
|
||||
self,
|
||||
call,
|
||||
user_id: int,
|
||||
chat_id: int,
|
||||
mask: int,
|
||||
rank: str,
|
||||
mode: str,
|
||||
duration: typing.Optional[str] = None,
|
||||
):
|
||||
user = await self._client.get_entity(user_id)
|
||||
chat = await self._client.get_entity(chat_id)
|
||||
|
||||
if mode == "admin":
|
||||
ok = await self.xdlib.admin.set_rights(chat, user, mask, rank)
|
||||
rights_items = self.xdlib.admin_rights(mask).to_dict()
|
||||
else:
|
||||
ok = await self.xdlib.chat.set_restrictions(
|
||||
chat, user, mask, duration=duration
|
||||
)
|
||||
rights_items = self.xdlib.banned_rights(mask).to_dict()
|
||||
|
||||
rights_list = [r for r, v in rights_items.items() if v]
|
||||
|
||||
if ok:
|
||||
text = (
|
||||
self.strings["promoted"]
|
||||
if mode == "admin" and mask
|
||||
else self.strings["demoted"]
|
||||
if mode == "admin" and not mask
|
||||
else self.strings["restricted"]
|
||||
)
|
||||
|
||||
await utils.answer(
|
||||
call,
|
||||
text.format(
|
||||
id=user_id,
|
||||
name=user.first_name
|
||||
if hasattr(user, "first_name")
|
||||
else user.title
|
||||
if hasattr(user, "title")
|
||||
else "None",
|
||||
rights=", ".join([self.strings[r] for r in rights_list])
|
||||
if rights_list
|
||||
else self.strings["no"],
|
||||
duration=f" {duration}" if duration else self.strings["forever"],
|
||||
time=f" {duration}" if duration else self.strings["forever"],
|
||||
),
|
||||
reply_markup=[[{"text": self.strings["close"], "action": "close"}]],
|
||||
)
|
||||
else:
|
||||
await utils.answer(
|
||||
call,
|
||||
self.strings["error"],
|
||||
reply_markup=[[{"text": self.strings["close"], "action": "close"}]],
|
||||
)
|
||||
515
coddrago/modules/dbmod.py
Normal file
515
coddrago/modules/dbmod.py
Normal file
@@ -0,0 +1,515 @@
|
||||
# meta developer: @codrago_m
|
||||
|
||||
import html
|
||||
from .. import loader, utils
|
||||
|
||||
|
||||
class DBMod(loader.Module):
|
||||
strings = {
|
||||
"name": "DBMod",
|
||||
"del_text": "<b>Database</b>\n\nSelect a key to view",
|
||||
"deleted": "🗑 Key {key} deleted",
|
||||
"deleted_all": "🗑 Deleted {count} keys",
|
||||
"close_btn": "❌ Close",
|
||||
"back_btn": "⬅ Back",
|
||||
"del_btn": "🗑 Delete",
|
||||
"del_all_btn": "💣 Delete all",
|
||||
"not_found": "🔍 Key {key} not found",
|
||||
"invalid_key": "⚠ Invalid key",
|
||||
"page": "📄 Page {current}/{total}",
|
||||
"module_not_found": "🔍 Module '{module}' not found in database",
|
||||
"confirm_delete": "⚠ Are you sure you want to delete this?",
|
||||
"view_path": "<b>Path: {path}</b>",
|
||||
"root_path": "Root",
|
||||
"value_display": "<b>Value:</b> <code>{value}</code>",
|
||||
"yes_btn": "✅ Yes",
|
||||
"no_btn": "❌ No",
|
||||
"list_item_display": "<b>List item [{index}]</b>",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"del_text": "<b>База данных</b>\n\nВыберите ключ для просмотра",
|
||||
"deleted": "🗑 Ключ {key} удален",
|
||||
"deleted_all": "🗑 Удалено {count} ключей",
|
||||
"close_btn": "❌ Закрыть",
|
||||
"back_btn": "⬅ Назад",
|
||||
"del_btn": "🗑 Удалить",
|
||||
"del_all_btn": "💣 Удалить все",
|
||||
"not_found": "🔍 Ключ {key} не найден",
|
||||
"invalid_key": "⚠ Некорректный ключ",
|
||||
"page": "📄 Страница {current}/{total}",
|
||||
"module_not_found": "🔍 Модуль '{module}' не найден в базе данных",
|
||||
"confirm_delete": "⚠ Вы уверены, что хотите удалить это?",
|
||||
"view_path": "<b>Путь: {path}</b>",
|
||||
"root_path": "Корень",
|
||||
"value_display": "<b>Значение:</b> <code>{value}</code>",
|
||||
"yes_btn": "✅ Да",
|
||||
"no_btn": "❌ Нет",
|
||||
"list_item_display": "<b>Элемент списка [{index}]</b>",
|
||||
}
|
||||
|
||||
async def client_ready(self):
|
||||
self.page_state = {}
|
||||
|
||||
def _make_path_text(self, key_path):
|
||||
path = "/".join(map(str, key_path)) if key_path else self.strings["root_path"]
|
||||
return self.strings["view_path"].format(path=path)
|
||||
|
||||
def _make_list_item_path_text(self, key_path, index):
|
||||
"""Создает заголовок для элемента списка"""
|
||||
if key_path:
|
||||
path = "/".join(map(str, key_path)) + f"[{index}]"
|
||||
else:
|
||||
path = f"[{index}]"
|
||||
return self.strings["list_item_display"].format(index=index)
|
||||
|
||||
async def show_menu(self, message, key_path=None, page=0):
|
||||
if key_path is None:
|
||||
key_path = []
|
||||
self.page_state[tuple(key_path)] = page
|
||||
|
||||
current_data = self._db
|
||||
for key in key_path:
|
||||
if isinstance(current_data, (dict, list)):
|
||||
if isinstance(current_data, dict) and key in current_data:
|
||||
current_data = current_data[key]
|
||||
elif (
|
||||
isinstance(current_data, list)
|
||||
and isinstance(key, int)
|
||||
and 0 <= key < len(current_data)
|
||||
):
|
||||
current_data = current_data[key]
|
||||
else:
|
||||
await utils.answer(message, self.strings["invalid_key"])
|
||||
return
|
||||
else:
|
||||
await utils.answer(message, self.strings["invalid_key"])
|
||||
return
|
||||
|
||||
header = self._make_path_text(key_path)
|
||||
|
||||
if isinstance(current_data, (dict, list)) and current_data:
|
||||
markup = self.generate_nested_markup(current_data, key_path, page)
|
||||
await utils.answer(message, header, reply_markup=markup)
|
||||
else:
|
||||
text = f"{header}\n\n" + self.strings["value_display"].format(
|
||||
value=html.escape(str(current_data))
|
||||
)
|
||||
markup = self.generate_value_markup(key_path, page)
|
||||
await utils.answer(message, text, reply_markup=markup)
|
||||
|
||||
async def navigate_db(self, call, key_path=None, page=0):
|
||||
if key_path is None:
|
||||
key_path = []
|
||||
self.page_state[tuple(key_path)] = page
|
||||
|
||||
current_data = self._db
|
||||
for key in key_path:
|
||||
if isinstance(current_data, (dict, list)):
|
||||
if isinstance(current_data, dict) and key in current_data:
|
||||
current_data = current_data[key]
|
||||
elif (
|
||||
isinstance(current_data, list)
|
||||
and isinstance(key, int)
|
||||
and 0 <= key < len(current_data)
|
||||
):
|
||||
current_data = current_data[key]
|
||||
else:
|
||||
await call.answer(self.strings["invalid_key"])
|
||||
return
|
||||
else:
|
||||
await call.answer(self.strings["invalid_key"])
|
||||
return
|
||||
|
||||
is_list_item = False
|
||||
if key_path:
|
||||
parent_data = self._db
|
||||
for key in key_path[:-1]:
|
||||
if isinstance(parent_data, (dict, list)):
|
||||
if isinstance(parent_data, dict) and key in parent_data:
|
||||
parent_data = parent_data[key]
|
||||
elif (
|
||||
isinstance(parent_data, list)
|
||||
and isinstance(key, int)
|
||||
and 0 <= key < len(parent_data)
|
||||
):
|
||||
parent_data = parent_data[key]
|
||||
else:
|
||||
break
|
||||
|
||||
if (
|
||||
isinstance(parent_data, list)
|
||||
and isinstance(key_path[-1], int)
|
||||
and 0 <= key_path[-1] < len(parent_data)
|
||||
):
|
||||
is_list_item = True
|
||||
|
||||
if is_list_item:
|
||||
header = self._make_list_item_path_text(key_path[:-1], key_path[-1])
|
||||
text = f"{header}\n\n" + self.strings["value_display"].format(
|
||||
value=html.escape(str(current_data))
|
||||
)
|
||||
await call.edit(
|
||||
text, reply_markup=self.generate_list_item_markup(key_path, page)
|
||||
)
|
||||
elif isinstance(current_data, (dict, list)) and current_data:
|
||||
header = self._make_path_text(key_path)
|
||||
await call.edit(
|
||||
header,
|
||||
reply_markup=self.generate_nested_markup(current_data, key_path, page),
|
||||
)
|
||||
else:
|
||||
header = self._make_path_text(key_path)
|
||||
text = f"{header}\n\n" + self.strings["value_display"].format(
|
||||
value=html.escape(str(current_data))
|
||||
)
|
||||
await call.edit(
|
||||
text, reply_markup=self.generate_value_markup(key_path, page)
|
||||
)
|
||||
|
||||
def generate_nested_markup(self, data, key_path, page=0):
|
||||
if isinstance(data, list) and data:
|
||||
return self.generate_list_markup(data, key_path, page)
|
||||
|
||||
items = list(data.items()) if isinstance(data, dict) else []
|
||||
items_per_page = 9
|
||||
total_pages = (len(items) + items_per_page - 1) // items_per_page
|
||||
start_idx = page * items_per_page
|
||||
end_idx = min(start_idx + items_per_page, len(items))
|
||||
page_items = items[start_idx:end_idx]
|
||||
|
||||
markup = []
|
||||
row = []
|
||||
for i, (key, value) in enumerate(page_items):
|
||||
if i % 3 == 0 and row:
|
||||
markup.append(row)
|
||||
row = []
|
||||
row.append(
|
||||
{
|
||||
"text": f"{key}",
|
||||
"callback": self.navigate_db,
|
||||
"args": [key_path + [key], 0],
|
||||
}
|
||||
)
|
||||
if row:
|
||||
markup.append(row)
|
||||
|
||||
nav_buttons = []
|
||||
if key_path:
|
||||
parent_page = self.page_state.get(tuple(key_path[:-1]), 0)
|
||||
nav_buttons.append(
|
||||
{
|
||||
"text": self.strings["back_btn"],
|
||||
"callback": self.navigate_db,
|
||||
"args": [key_path[:-1], parent_page],
|
||||
}
|
||||
)
|
||||
|
||||
if total_pages > 1:
|
||||
if page > 0:
|
||||
nav_buttons.append(
|
||||
{
|
||||
"text": "◀️",
|
||||
"callback": self.navigate_db,
|
||||
"args": [key_path, page - 1],
|
||||
}
|
||||
)
|
||||
nav_buttons.append(
|
||||
{
|
||||
"text": self.strings["page"].format(
|
||||
current=page + 1, total=total_pages
|
||||
),
|
||||
"callback": self.navigate_db,
|
||||
"args": [key_path, page],
|
||||
}
|
||||
)
|
||||
if page < total_pages - 1:
|
||||
nav_buttons.append(
|
||||
{
|
||||
"text": "▶️",
|
||||
"callback": self.navigate_db,
|
||||
"args": [key_path, page + 1],
|
||||
}
|
||||
)
|
||||
if nav_buttons:
|
||||
markup.append(nav_buttons)
|
||||
|
||||
if key_path:
|
||||
markup.append(
|
||||
[
|
||||
{
|
||||
"text": self.strings["del_all_btn"],
|
||||
"callback": self.confirm_delete_all,
|
||||
"args": [key_path],
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
if not key_path:
|
||||
markup.append([{"text": self.strings["close_btn"], "action": "close"}])
|
||||
return markup
|
||||
|
||||
def generate_list_markup(self, data, key_path, page=0):
|
||||
"""Генерирует разметку для списка, показывая элементы напрямую"""
|
||||
items_per_page = 9
|
||||
total_pages = (len(data) + items_per_page - 1) // items_per_page
|
||||
start_idx = page * items_per_page
|
||||
end_idx = min(start_idx + items_per_page, len(data))
|
||||
page_items = list(enumerate(data[start_idx:end_idx], start_idx))
|
||||
|
||||
markup = []
|
||||
row = []
|
||||
for i, (index, value) in enumerate(page_items):
|
||||
if i % 3 == 0 and row:
|
||||
markup.append(row)
|
||||
row = []
|
||||
|
||||
if isinstance(value, (dict, list)):
|
||||
btn_text = f"[{index}]"
|
||||
else:
|
||||
value_str = str(value)
|
||||
if len(value_str) > 10:
|
||||
btn_text = f"{value_str[:10]}..."
|
||||
else:
|
||||
btn_text = value_str
|
||||
|
||||
row.append(
|
||||
{
|
||||
"text": btn_text,
|
||||
"callback": self.navigate_db,
|
||||
"args": [key_path + [index], 0],
|
||||
}
|
||||
)
|
||||
if row:
|
||||
markup.append(row)
|
||||
|
||||
nav_buttons = []
|
||||
if key_path:
|
||||
parent_page = self.page_state.get(tuple(key_path[:-1]), 0)
|
||||
nav_buttons.append(
|
||||
{
|
||||
"text": self.strings["back_btn"],
|
||||
"callback": self.navigate_db,
|
||||
"args": [key_path[:-1], parent_page],
|
||||
}
|
||||
)
|
||||
|
||||
if total_pages > 1:
|
||||
if page > 0:
|
||||
nav_buttons.append(
|
||||
{
|
||||
"text": "◀️",
|
||||
"callback": self.navigate_db,
|
||||
"args": [key_path, page - 1],
|
||||
}
|
||||
)
|
||||
nav_buttons.append(
|
||||
{
|
||||
"text": self.strings["page"].format(
|
||||
current=page + 1, total=total_pages
|
||||
),
|
||||
"callback": self.navigate_db,
|
||||
"args": [key_path, page],
|
||||
}
|
||||
)
|
||||
if page < total_pages - 1:
|
||||
nav_buttons.append(
|
||||
{
|
||||
"text": "▶️",
|
||||
"callback": self.navigate_db,
|
||||
"args": [key_path, page + 1],
|
||||
}
|
||||
)
|
||||
if nav_buttons:
|
||||
markup.append(nav_buttons)
|
||||
|
||||
if key_path:
|
||||
markup.append(
|
||||
[
|
||||
{
|
||||
"text": self.strings["del_all_btn"],
|
||||
"callback": self.confirm_delete_all,
|
||||
"args": [key_path],
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
return markup
|
||||
|
||||
def generate_list_item_markup(self, key_path, page=0):
|
||||
"""Генерирует разметку для отдельного элемента списка"""
|
||||
parent_page = self.page_state.get(tuple(key_path[:-1]), 0)
|
||||
return [
|
||||
[
|
||||
{
|
||||
"text": self.strings["del_btn"],
|
||||
"callback": self.delete_key,
|
||||
"args": [key_path],
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"text": self.strings["back_btn"],
|
||||
"callback": self.navigate_db,
|
||||
"args": [key_path[:-1], parent_page],
|
||||
}
|
||||
],
|
||||
]
|
||||
|
||||
def generate_value_markup(self, key_path, page=0):
|
||||
parent_page = self.page_state.get(tuple(key_path[:-1]), 0)
|
||||
return [
|
||||
[
|
||||
{
|
||||
"text": self.strings["del_btn"],
|
||||
"callback": self.delete_key,
|
||||
"args": [key_path],
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"text": self.strings["back_btn"],
|
||||
"callback": self.navigate_db,
|
||||
"args": [key_path[:-1], parent_page],
|
||||
}
|
||||
],
|
||||
]
|
||||
|
||||
async def confirm_delete_all(self, call, key_path):
|
||||
await call.edit(
|
||||
self.strings["confirm_delete"],
|
||||
reply_markup=[
|
||||
[
|
||||
{
|
||||
"text": self.strings["yes_btn"],
|
||||
"callback": self.delete_all_keys,
|
||||
"args": [key_path],
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"text": self.strings["no_btn"],
|
||||
"callback": self.navigate_db,
|
||||
"args": [
|
||||
key_path,
|
||||
self.page_state.get(tuple(key_path), 0),
|
||||
],
|
||||
}
|
||||
],
|
||||
],
|
||||
)
|
||||
|
||||
async def delete_all_keys(self, call, key_path):
|
||||
if not key_path:
|
||||
count = len(self._db)
|
||||
self._db.clear()
|
||||
self._db.save()
|
||||
await call.answer(self.strings["deleted_all"].format(count=count))
|
||||
await self.navigate_db(call, [], self.page_state.get((), 0))
|
||||
else:
|
||||
current = self._db
|
||||
for key in key_path[:-1]:
|
||||
if isinstance(current, (dict, list)):
|
||||
if isinstance(current, dict) and key in current:
|
||||
current = current[key]
|
||||
elif (
|
||||
isinstance(current, list)
|
||||
and isinstance(key, int)
|
||||
and 0 <= key < len(current)
|
||||
):
|
||||
current = current[key]
|
||||
else:
|
||||
await call.answer(
|
||||
self.strings["not_found"].format(key=key_path[-1])
|
||||
)
|
||||
return
|
||||
|
||||
if isinstance(current, (dict, list)) and key_path[-1] in current:
|
||||
if isinstance(current[key_path[-1]], (dict, list)):
|
||||
count = len(current[key_path[-1]])
|
||||
else:
|
||||
count = 1
|
||||
del current[key_path[-1]]
|
||||
self._db.save()
|
||||
await call.answer(self.strings["deleted_all"].format(count=count))
|
||||
await self.navigate_db(
|
||||
call,
|
||||
key_path[:-1],
|
||||
self.page_state.get(tuple(key_path[:-1]), 0),
|
||||
)
|
||||
else:
|
||||
await call.answer(self.strings["not_found"].format(key=key_path[-1]))
|
||||
|
||||
async def delete_key(self, call, key_path):
|
||||
parent_page = self.page_state.get(tuple(key_path[:-1]), 0)
|
||||
|
||||
if len(key_path) == 1:
|
||||
if key_path[0] in self._db:
|
||||
del self._db[key_path[0]]
|
||||
self._db.save()
|
||||
await call.answer(self.strings["deleted"].format(key=key_path[0]))
|
||||
await self.navigate_db(call, [], parent_page)
|
||||
else:
|
||||
await call.answer(self.strings["not_found"].format(key=key_path[0]))
|
||||
else:
|
||||
current = self._db
|
||||
for key in key_path[:-1]:
|
||||
if isinstance(current, (dict, list)):
|
||||
if isinstance(current, dict) and key in current:
|
||||
current = current[key]
|
||||
elif (
|
||||
isinstance(current, list)
|
||||
and isinstance(key, int)
|
||||
and 0 <= key < len(current)
|
||||
):
|
||||
current = current[key]
|
||||
else:
|
||||
await call.answer(
|
||||
self.strings["not_found"].format(key=key_path[-1])
|
||||
)
|
||||
return
|
||||
|
||||
if isinstance(current, dict) and key_path[-1] in current:
|
||||
deleted_value = current[key_path[-1]]
|
||||
del current[key_path[-1]]
|
||||
key_display = key_path[-1]
|
||||
self._db.save()
|
||||
await call.answer(self.strings["deleted"].format(key=key_display))
|
||||
await self.navigate_db(call, key_path[:-1], parent_page)
|
||||
elif (
|
||||
isinstance(current, list)
|
||||
and isinstance(key_path[-1], int)
|
||||
and 0 <= key_path[-1] < len(current)
|
||||
):
|
||||
deleted_value = current.pop(key_path[-1])
|
||||
key_display = f"[{key_path[-1]}] = {deleted_value}"
|
||||
self._db.save()
|
||||
await call.answer(self.strings["deleted"].format(key=key_display))
|
||||
await self.navigate_db(call, key_path[:-1], parent_page)
|
||||
else:
|
||||
await call.answer(self.strings["not_found"].format(key=key_path[-1]))
|
||||
|
||||
def find_module_key(self, module_name):
|
||||
module_name_lower = module_name.lower()
|
||||
for key in self._db.keys():
|
||||
if key.lower() == module_name_lower:
|
||||
return key
|
||||
return None
|
||||
|
||||
@loader.command(ru_doc="Просмотр базы данных")
|
||||
async def mydb(self, message):
|
||||
"""Viewing the database"""
|
||||
args = utils.get_args_raw(message)
|
||||
if args:
|
||||
module_key = self.find_module_key(args)
|
||||
if module_key:
|
||||
await self.show_menu(
|
||||
message, [module_key], self.page_state.get((module_key,), 0)
|
||||
)
|
||||
return
|
||||
else:
|
||||
await utils.answer(
|
||||
message, self.strings["module_not_found"].format(module=args)
|
||||
)
|
||||
return
|
||||
await self.show_menu(message, [], self.page_state.get((), 0))
|
||||
@@ -18,3 +18,8 @@ promoclaimer
|
||||
passwordgen
|
||||
send
|
||||
lastfm
|
||||
dbmod
|
||||
chatmodule
|
||||
stats
|
||||
tagwatcher
|
||||
hardspam
|
||||
62
coddrago/modules/hardspam.py
Normal file
62
coddrago/modules/hardspam.py
Normal file
@@ -0,0 +1,62 @@
|
||||
# meta developer: @codrago_m
|
||||
|
||||
import asyncio
|
||||
from .. import loader, utils
|
||||
from herokutl.tl.types import InputDocument
|
||||
from herokutl.errors.rpcerrorlist import MediaEmptyError
|
||||
|
||||
|
||||
@loader.tds
|
||||
class HardSpam(loader.Module):
|
||||
strings = {
|
||||
"name": "HardSpam",
|
||||
"spam_help": "<b>Usage sample: {}hspam [-c|--clean] 23 text</b>",
|
||||
}
|
||||
strings_ru = {
|
||||
"spam_help": "<b>Пример использования: {}hspam [-c|--clean] 23 text</b>",
|
||||
}
|
||||
|
||||
async def send_msgs(self, c, chat_id, text):
|
||||
msg = await c.send_message(chat_id, text)
|
||||
return msg.id
|
||||
|
||||
async def send_medias(self, c, chat_id, document, text):
|
||||
msg = await c.send_file(chat_id, document, caption=None if text is None else text)
|
||||
return msg.id
|
||||
|
||||
@loader.command(
|
||||
ru_doc="[-c|--clean] n text - Отправить n кол-во сообщений одновременно"
|
||||
)
|
||||
async def hspamcmd(self, message):
|
||||
"""[-c|--clean] n text - Send n number of messages at the same time"""
|
||||
args = utils.get_args(message)
|
||||
r = await message.get_reply_message()
|
||||
delete_all = False
|
||||
|
||||
if "--clean" in args:
|
||||
delete_all = True
|
||||
args.remove("--clean")
|
||||
elif "-c" in args:
|
||||
delete_all = True
|
||||
args.remove("-c")
|
||||
if not args[0].isdigit() or len(args) < 1:
|
||||
return await utils.answer(
|
||||
message,
|
||||
self.strings["spam_help"].format(self.get_prefix()),
|
||||
)
|
||||
|
||||
number = int(args[0])
|
||||
text = " ".join(args[1:])
|
||||
if r and r.media:
|
||||
document = InputDocument(id=r.media.document.id, access_hash=r.media.document.access_hash, file_reference=r.media.document.file_reference)
|
||||
tasks = [
|
||||
self.send_medias(self._client, message.chat_id, document, None if text is None else text) for i in range(number)
|
||||
]
|
||||
else:
|
||||
tasks = [
|
||||
self.send_msgs(self._client, message.chat_id, text) for _ in range(number)
|
||||
]
|
||||
message_ids = await asyncio.gather(*tasks)
|
||||
if delete_all:
|
||||
await self._client.delete_messages(message.chat_id, message_ids)
|
||||
return await message.delete()
|
||||
764
coddrago/modules/libs/xdlib.py
Normal file
764
coddrago/modules/libs/xdlib.py
Normal file
@@ -0,0 +1,764 @@
|
||||
# This file is part of XDesai Mods.
|
||||
# I made this library to share various utility functions across my modules.
|
||||
# You can use this library in your own modules as well.
|
||||
|
||||
# P.S this library is still under development and may receive updates in the future.
|
||||
|
||||
# meta developer: @codrago_m
|
||||
|
||||
import logging
|
||||
import re
|
||||
import typing
|
||||
|
||||
from telethon.errors.rpcerrorlist import (
|
||||
UserNotParticipantError,
|
||||
HideRequesterMissingError,
|
||||
)
|
||||
from telethon.functions import messages, channels
|
||||
from telethon import types
|
||||
|
||||
from .. import loader, utils
|
||||
from ..types import SelfUnload
|
||||
|
||||
logger = logging.getLogger("XDLib")
|
||||
|
||||
|
||||
class XDLib(loader.Library):
|
||||
"""A library with various utility functions for codrago modules."""
|
||||
|
||||
developer = "@codrago_m"
|
||||
|
||||
strings = {
|
||||
"name": "XDLib",
|
||||
"desc": "A library with various utility functions for codrago modules.",
|
||||
"request_join_reason": "Stay tuned for updates.",
|
||||
}
|
||||
|
||||
async def init(self):
|
||||
self.format = FormatUtils()
|
||||
self.parse = ParseUtils()
|
||||
self.messages = MessageUtils(self._client)
|
||||
self.admin = AdminUtils(self._client, self)
|
||||
self.chat = ChatUtils(self._client, self._db)
|
||||
self.dialog = DialogUtils(self._client)
|
||||
self.user = UserUtils(self._client, self._db)
|
||||
self.admin_rights = AdminRights
|
||||
self.banned_rights = BannedRights
|
||||
|
||||
def unload_lib(self, name: str):
|
||||
instance = self.lookup(name)
|
||||
if isinstance(instance, loader.Library):
|
||||
self.allmodules.libraries.remove(instance)
|
||||
logger.info(f"Unloaded library: {name}")
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class UserUtils:
|
||||
def __init__(self, client, db):
|
||||
self._client = client
|
||||
self._db = db
|
||||
|
||||
async def get_info(
|
||||
self, user_id: typing.Union[str, int, types.PeerUser, types.User]
|
||||
):
|
||||
userfull = await self._client.get_fulluser(user_id)
|
||||
full_user = userfull.full_user
|
||||
user = userfull.users[0]
|
||||
usernames = user.usernames or [user] or None
|
||||
unames = []
|
||||
if usernames:
|
||||
for username in usernames:
|
||||
unames.append(username.username)
|
||||
personal_channel = (
|
||||
await self._client.get_entity(full_user.personal_channel_id)
|
||||
if full_user.personal_channel_id
|
||||
else None
|
||||
)
|
||||
common = await self._client(
|
||||
messages.GetCommonChatsRequest(user_id=user_id, max_id=0, limit=100)
|
||||
)
|
||||
|
||||
return {
|
||||
"common_chats_count": full_user.common_chats_count,
|
||||
"common_chats": common.chats,
|
||||
"id": user.id,
|
||||
"personal_photo": full_user.personal_photo,
|
||||
"business_work_hours": full_user.business_work_hours,
|
||||
"business_intro": full_user.business_intro,
|
||||
"birthday": full_user.birthday,
|
||||
"personal_channel": personal_channel or None,
|
||||
"stargifts_count": full_user.stargifts_count,
|
||||
"first_name": user.first_name,
|
||||
"last_name": user.last_name,
|
||||
"usernames": unames,
|
||||
"emoji_status": getattr(user.emoji_status, "document_id", None),
|
||||
"color": user.color,
|
||||
"blocked": full_user.blocked,
|
||||
"about": full_user.about,
|
||||
"profile_photo": full_user.profile_photo,
|
||||
"phone": user.phone,
|
||||
}
|
||||
|
||||
|
||||
class ParseUtils:
|
||||
def minutes_to_hhmm(self, m):
|
||||
h = (m // 60) % 24
|
||||
mm = m % 60
|
||||
return f"{h:02d}:{mm:02d}"
|
||||
|
||||
def opts(self, args: list) -> typing.Dict[str, typing.Any]:
|
||||
"""
|
||||
Parses command-line style options from a list of arguments.
|
||||
Supports sequential operations (+, -, *, /) for numeric values.
|
||||
"""
|
||||
options = {}
|
||||
i = 0
|
||||
|
||||
def auto_cast(value: str):
|
||||
if not value:
|
||||
return True
|
||||
low = value.lower()
|
||||
if low in {"true", "yes", "on"}:
|
||||
return True
|
||||
if low in {"false", "no", "off"}:
|
||||
return False
|
||||
if re.fullmatch(r"-?\d+", value):
|
||||
return int(value)
|
||||
if re.fullmatch(r"-?\d+\.\d+", value):
|
||||
return float(value)
|
||||
return value
|
||||
|
||||
def apply_operations(base, ops: list[str]):
|
||||
val = base
|
||||
for op_str in ops:
|
||||
m = re.fullmatch(r"([+*/])(\d+(\.\d+)?)", op_str)
|
||||
if not m:
|
||||
val = auto_cast(op_str)
|
||||
continue
|
||||
op, number, _ = m.groups()
|
||||
number = float(number) if "." in number else int(number)
|
||||
if op == "+":
|
||||
val += number
|
||||
elif op == "*":
|
||||
val *= number
|
||||
elif op == "/":
|
||||
val /= number
|
||||
return val
|
||||
|
||||
while i < len(args):
|
||||
arg = args[i]
|
||||
|
||||
if "=" in arg:
|
||||
key, value = arg.split("=", 1)
|
||||
key = key.lstrip("-")
|
||||
options[key] = auto_cast(value.strip("\"'"))
|
||||
|
||||
elif arg.startswith("-"):
|
||||
key = arg.lstrip("-")
|
||||
values = []
|
||||
i += 1
|
||||
while i < len(args) and not args[i].startswith("-"):
|
||||
values.append(args[i].strip("\"'"))
|
||||
i += 1
|
||||
i -= 1
|
||||
|
||||
if key in options and isinstance(options[key], (int, float)):
|
||||
options[key] = apply_operations(options[key], values)
|
||||
else:
|
||||
if values:
|
||||
base = auto_cast(values[0])
|
||||
options[key] = apply_operations(base, values[1:])
|
||||
else:
|
||||
options[key] = True
|
||||
|
||||
i += 1
|
||||
|
||||
return options
|
||||
|
||||
def bool(self, value: str) -> bool:
|
||||
"""Parses a string into a boolean value."""
|
||||
true_values = {"true", "yes", "1", "on"}
|
||||
false_values = {"false", "no", "0", "off"}
|
||||
low_value = value.lower()
|
||||
if low_value in true_values:
|
||||
return True
|
||||
elif low_value in false_values:
|
||||
return False
|
||||
else:
|
||||
raise ValueError(f"Cannot parse boolean from '{value}'")
|
||||
|
||||
def time(self, time_str: str) -> int:
|
||||
"""Parses a time duration string into seconds."""
|
||||
time_units = {
|
||||
"s": 1,
|
||||
"m": 60,
|
||||
"h": 3600,
|
||||
"d": 86400,
|
||||
"w": 604800,
|
||||
"y": 31536000,
|
||||
}
|
||||
total_seconds = 0
|
||||
pattern = r"(\d+)([smhdwy])"
|
||||
matches = re.findall(pattern, time_str)
|
||||
for value, unit in matches:
|
||||
total_seconds += int(value) * time_units[unit]
|
||||
return total_seconds
|
||||
|
||||
def size(self, size_str: str) -> int:
|
||||
"""Parses a size string into bytes."""
|
||||
size_units = {
|
||||
"b": 1,
|
||||
"kb": 1024,
|
||||
"mb": 1024**2,
|
||||
"gb": 1024**3,
|
||||
"tb": 1024**4,
|
||||
}
|
||||
pattern = r"(\d+)([bkmgt]b?)"
|
||||
match = re.match(pattern, size_str.lower())
|
||||
if match:
|
||||
value, unit = match.groups()
|
||||
return int(value) * size_units[unit]
|
||||
return 0
|
||||
|
||||
def mentions(self, msg) -> typing.List[str]:
|
||||
"""Extracts mentions from a given message."""
|
||||
if msg.entities:
|
||||
mentions = []
|
||||
for entity in msg.entities:
|
||||
if isinstance(entity, types.MessageEntityMention):
|
||||
offset = entity.offset
|
||||
length = entity.length
|
||||
mentions.append(msg.message[offset : offset + length])
|
||||
elif isinstance(entity, types.MessageEntityMentionName):
|
||||
mentions.append(entity.user_id)
|
||||
return mentions
|
||||
return []
|
||||
|
||||
def urls(self, msg) -> typing.List[str]:
|
||||
"""Extracts URLs from a given message."""
|
||||
if msg.entities or msg.media:
|
||||
urls = []
|
||||
for entity in msg.entities:
|
||||
if isinstance(entity, types.MessageEntityTextUrl):
|
||||
urls.append(entity.url)
|
||||
elif isinstance(entity, types.MessageEntityUrl):
|
||||
offset = entity.offset
|
||||
length = entity.length
|
||||
urls.append(msg.message[offset : offset + length])
|
||||
elif msg.media and hasattr(msg.media, "webpage"):
|
||||
if msg.media.webpage.url:
|
||||
urls.append(msg.media.webpage.url)
|
||||
return urls
|
||||
return []
|
||||
|
||||
|
||||
class DialogUtils:
|
||||
def __init__(self, client) -> None:
|
||||
self._client = client
|
||||
|
||||
async def get_all(self, client):
|
||||
dialogs = []
|
||||
async for dialog in client.iter_dialogs():
|
||||
dialogs.append(dialog)
|
||||
return dialogs
|
||||
|
||||
async def get_chats(self, client):
|
||||
return [
|
||||
chat
|
||||
for chat in await self.get_all(client)
|
||||
if chat.is_group and chat.is_channel
|
||||
]
|
||||
|
||||
async def get_pms(self, client):
|
||||
return [pm for pm in await self.get_all(client) if pm.is_private]
|
||||
|
||||
async def get_channels(self, client):
|
||||
return [
|
||||
channel
|
||||
for channel in await self.get_all(client)
|
||||
if channel.is_channel and not channel.is_group
|
||||
]
|
||||
|
||||
async def get_owns(self, client):
|
||||
return [
|
||||
ent
|
||||
for ent in await self.get_all(client)
|
||||
if hasattr(ent.entity, "creator") and ent.entity.creator
|
||||
]
|
||||
|
||||
|
||||
class MessageUtils:
|
||||
def __init__(self, client):
|
||||
self._client = client
|
||||
|
||||
async def delete_messages(self, msg):
|
||||
"""Deletes multiple messages based on a specific pattern."""
|
||||
reply = await msg.get_reply_message()
|
||||
pattern = r"([ab])(\d+)"
|
||||
matches = re.findall(pattern, utils.get_args_raw(msg))
|
||||
|
||||
ids_to_delete = [msg.id]
|
||||
if reply:
|
||||
ids_to_delete.append(reply.id)
|
||||
|
||||
for direction, count_str in matches:
|
||||
count = int(count_str)
|
||||
if direction == "a": # after
|
||||
if reply:
|
||||
async for m in self._client.iter_messages(
|
||||
msg.chat_id, min_id=reply.id, limit=count, reverse=True
|
||||
):
|
||||
ids_to_delete.append(m.id)
|
||||
elif direction == "b": # before
|
||||
async for m in self._client.iter_messages(
|
||||
msg.chat_id, max_id=(reply if reply else msg).id, limit=count
|
||||
):
|
||||
ids_to_delete.append(m.id)
|
||||
|
||||
await self._client.delete_messages(msg.chat_id, message_ids=ids_to_delete)
|
||||
|
||||
async def get_sender(self, message):
|
||||
if message.out:
|
||||
return await self._client.get_me()
|
||||
if message.is_private:
|
||||
return message.peer_id
|
||||
if message.is_group and message.is_channel:
|
||||
return message.sender or message.chat
|
||||
|
||||
|
||||
class ChatUtils:
|
||||
def __init__(self, client, db) -> None:
|
||||
self._client = client
|
||||
self._db = db
|
||||
|
||||
async def set_restrictions(
|
||||
self, chat, user, mask: int, duration: int = None
|
||||
) -> bool:
|
||||
"""
|
||||
Sets chat restrictions (mute/ban permissions) for a user based on a mask.
|
||||
|
||||
:param chat: Chat entity
|
||||
:param user: User entity
|
||||
:param mask: Bitmask of BannedRights
|
||||
:param duration: Ban duration in seconds (None = forever)
|
||||
"""
|
||||
|
||||
try:
|
||||
rights = BannedRights(mask)
|
||||
|
||||
rights_dict = rights.to_dict()
|
||||
|
||||
rights_dict["until_date"] = (
|
||||
None if duration is None else utils.timestamp() + duration
|
||||
)
|
||||
|
||||
new_banned_rights = types.ChatBannedRights(**rights_dict)
|
||||
|
||||
await self._client(
|
||||
channels.EditBannedRequest(
|
||||
channel=chat,
|
||||
participant=user,
|
||||
banned_rights=new_banned_rights,
|
||||
)
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
except Exception:
|
||||
logger.error(
|
||||
f"Failed to set restrictions with mask {mask} for user {user.id} in chat {chat}",
|
||||
exc_info=True,
|
||||
)
|
||||
return False
|
||||
|
||||
async def get_admin_logs(self, chat, limit: int = 5, **kwargs):
|
||||
logs = []
|
||||
for log_event in await self._client.get_admin_log(chat, limit=limit, **kwargs):
|
||||
logs.append(log_event)
|
||||
return logs
|
||||
|
||||
async def get_user_messages(self, chat, user_id):
|
||||
msgs = []
|
||||
async for msg in self._client.iter_messages(chat, from_user=user_id):
|
||||
msgs.append(msg)
|
||||
return msgs
|
||||
|
||||
async def join_request(self, chat, user_id, approved):
|
||||
try:
|
||||
await self._client(
|
||||
messages.HideChatJoinRequestRequest(
|
||||
peer=chat, user_id=user_id, approved=approved
|
||||
)
|
||||
)
|
||||
except HideRequesterMissingError:
|
||||
logger.error("Request not found")
|
||||
|
||||
async def join_requests(self, chat, approved):
|
||||
try:
|
||||
await self._client(
|
||||
messages.HideAllChatJoinRequestsRequest(
|
||||
peer=chat,
|
||||
approved=approved,
|
||||
)
|
||||
)
|
||||
except HideRequesterMissingError:
|
||||
logger.error("Request not found")
|
||||
|
||||
async def get_members(self, chat):
|
||||
try:
|
||||
members = await self._client.get_participants(chat)
|
||||
if members:
|
||||
return members
|
||||
return None
|
||||
except Exception:
|
||||
logger.error(f"Couldn't get members of the chat {chat}")
|
||||
return None
|
||||
|
||||
async def get_deleted(self, chat):
|
||||
try:
|
||||
members = await self._client.get_participants(chat)
|
||||
deleted = [member for member in members if getattr(member, "deleted")]
|
||||
if deleted:
|
||||
return deleted
|
||||
return None
|
||||
except Exception:
|
||||
logger.error(f"Couldn't get members of the chat {chat}")
|
||||
return None
|
||||
|
||||
async def get_bots(self, chat):
|
||||
try:
|
||||
bots = await self._client.get_participants(
|
||||
chat, filter=types.ChannelParticipantsBots()
|
||||
)
|
||||
if bots:
|
||||
return bots
|
||||
return None
|
||||
except Exception:
|
||||
logger.error(f"Couldn't get bots from the chat {chat}")
|
||||
return None
|
||||
|
||||
async def get_admins(self, chat, only_users: bool = False):
|
||||
try:
|
||||
admins = await self._client.get_participants(
|
||||
chat, filter=types.ChannelParticipantsAdmins()
|
||||
)
|
||||
users = [
|
||||
user
|
||||
for user in admins
|
||||
if user
|
||||
and not getattr(user, "bot")
|
||||
and not isinstance(
|
||||
getattr(user, "participant"), types.ChannelParticipantCreator
|
||||
)
|
||||
]
|
||||
if only_users:
|
||||
return users
|
||||
return admins
|
||||
except Exception:
|
||||
logger.error(f"Couldn't get admins from the chat {chat}")
|
||||
return None
|
||||
|
||||
async def get_creator(self, chat):
|
||||
try:
|
||||
admins = await self._client.get_participants(
|
||||
chat, filter=types.ChannelParticipantsAdmins()
|
||||
)
|
||||
if not admins:
|
||||
return None
|
||||
for admin in admins:
|
||||
if hasattr(admin, "participant") and isinstance(
|
||||
getattr(admin, "participant"), types.ChannelParticipantCreator
|
||||
):
|
||||
return admin
|
||||
return None
|
||||
except Exception:
|
||||
logger.error(f"Couldn't get the creator from the chat {chat}")
|
||||
return None
|
||||
|
||||
async def is_member(self, chat, user) -> bool:
|
||||
"""Checks if a user is a member of a chat."""
|
||||
try:
|
||||
perms = await self._client.get_perms_cached(chat, user)
|
||||
return True if perms else False
|
||||
except UserNotParticipantError:
|
||||
return False
|
||||
except Exception:
|
||||
logger.error(
|
||||
f"Failed to check membership for user {user} in chat {chat.title}",
|
||||
exc_info=True,
|
||||
)
|
||||
return False
|
||||
|
||||
async def get_rights(self, chat, user):
|
||||
"""Checks if a user is a member of a chat."""
|
||||
try:
|
||||
perms = await self._client.get_perms_cached(chat, user)
|
||||
return perms
|
||||
except UserNotParticipantError:
|
||||
return None
|
||||
except Exception:
|
||||
logger.error(
|
||||
f"Failed to check membership for user {user} in chat {chat.title}",
|
||||
exc_info=True,
|
||||
)
|
||||
return None
|
||||
|
||||
async def invite_user(self, chat, user):
|
||||
"""Invites a user to a chat."""
|
||||
try:
|
||||
await self._client(
|
||||
channels.InviteToChannelRequest(channel=chat, users=[user])
|
||||
)
|
||||
return True
|
||||
except Exception:
|
||||
logger.error(
|
||||
f"Failed to invite user {user} to chat {chat.title}", exc_info=True
|
||||
)
|
||||
return False
|
||||
|
||||
async def get_info(self, chat) -> dict:
|
||||
try:
|
||||
chat_full = await self._client.get_fullchannel(chat)
|
||||
full_chat = chat_full.full_chat
|
||||
chat = chat_full.chats[0]
|
||||
return {
|
||||
"id": full_chat.id or 0,
|
||||
"about": full_chat.about or "",
|
||||
"chat_photo": full_chat.chat_photo,
|
||||
"admins_count": full_chat.admins_count or 0,
|
||||
"online_count": full_chat.online_count or 0,
|
||||
"participants_count": full_chat.participants_count or 0,
|
||||
"kicked_count": full_chat.kicked_count,
|
||||
"slowmode_seconds": full_chat.slowmode_seconds or 0,
|
||||
"call": full_chat.call or None,
|
||||
"title": chat.title or "",
|
||||
"ttl_period": full_chat.ttl_period or 0,
|
||||
"available_reactions": full_chat.available_reactions or None,
|
||||
"requests_pending": full_chat.requests_pending or 0,
|
||||
"recent_requesters": full_chat.recent_requesters or [],
|
||||
"is_forum": getattr(chat, "forum"),
|
||||
"linked_chat_id": full_chat.linked_chat_id or 0,
|
||||
"antispam": full_chat.antispam or False,
|
||||
"participants_hidden": full_chat.participants_hidden or False,
|
||||
"link": (
|
||||
f"https://t.me/{chat.username}"
|
||||
if chat.username
|
||||
else (
|
||||
full_chat.exported_invite.link
|
||||
if full_chat.exported_invite
|
||||
else ""
|
||||
)
|
||||
),
|
||||
"is_channel": chat.broadcast or False,
|
||||
"is_group": chat.megagroup or False,
|
||||
}
|
||||
except Exception:
|
||||
logger.error("Failed to get the chat info")
|
||||
return {}
|
||||
|
||||
async def invite_bot(self, client, chat) -> bool:
|
||||
"""Invites an inline bot to a chat."""
|
||||
try:
|
||||
await self._client(
|
||||
channels.InviteToChannelRequest(
|
||||
chat,
|
||||
[client.loader.inline.bot_username or client.loader.inline.bot_id],
|
||||
)
|
||||
)
|
||||
except Exception:
|
||||
logger.error("Failed to invite inline bot to chat", exc_info=True)
|
||||
return False
|
||||
|
||||
rights = AdminRights.all()
|
||||
rights.remove("anonymous")
|
||||
admin = AdminUtils(self._client, self._db)
|
||||
await admin.set_rights(
|
||||
chat,
|
||||
client.loader.inline.bot_username or client.loader.inline.bot_id,
|
||||
rights.to_int(),
|
||||
rank="XD Bot",
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
class AdminUtils:
|
||||
def __init__(self, client, lib) -> None:
|
||||
self._client = client
|
||||
self._lib = lib
|
||||
|
||||
async def set_role(self, chat, user, role_name, rank="XD Admin") -> bool:
|
||||
rights_obj = self._lib.roles.get_role_perms(role_name)
|
||||
if rights_obj is None:
|
||||
return False
|
||||
|
||||
return await self.set_rights(chat, user, rights_obj.to_int(), rank)
|
||||
|
||||
async def set_rights(self, chat, user, mask: int, rank: str = "XD Admin") -> bool:
|
||||
"""Sets admin rights for a user in a chat based on a mask."""
|
||||
try:
|
||||
rights = AdminRights(mask)
|
||||
|
||||
new_admin_rights = rights.to_chat_rights()
|
||||
|
||||
await self._client(
|
||||
channels.EditAdminRequest(
|
||||
chat,
|
||||
user,
|
||||
new_admin_rights,
|
||||
rank=rank,
|
||||
)
|
||||
)
|
||||
return True
|
||||
except Exception:
|
||||
logger.error(
|
||||
f"Failed to set rights with mask {mask} for user {user.id} in chat {chat.title}",
|
||||
exc_info=True,
|
||||
)
|
||||
return False
|
||||
|
||||
|
||||
class FormatUtils:
|
||||
def bytes(self, size: int) -> str:
|
||||
"""Formats a size in bytes into a human-readable string."""
|
||||
if size < 1024:
|
||||
if size == 1:
|
||||
return f"{size} byte"
|
||||
return f"{size} bytes"
|
||||
elif size < 1024**2:
|
||||
return f"{size / 1024:.2f} KB"
|
||||
elif size < 1024**3:
|
||||
return f"{size / 1024**2:.2f} MB"
|
||||
elif size < 1024**4:
|
||||
return f"{size / 1024**3:.2f} GB"
|
||||
else:
|
||||
return f"{size / 1024**4:.2f} TB"
|
||||
|
||||
def time(self, seconds: int) -> str:
|
||||
"""Formats a time duration in seconds into a human-readable string."""
|
||||
intervals = (
|
||||
("years", 31536000),
|
||||
("months", 2592000),
|
||||
("weeks", 604800),
|
||||
("days", 86400),
|
||||
("hours", 3600),
|
||||
("minutes", 60),
|
||||
("seconds", 1),
|
||||
)
|
||||
result = []
|
||||
for name, count in intervals:
|
||||
value = seconds // count
|
||||
if value:
|
||||
seconds -= value * count
|
||||
if value == 1:
|
||||
name = name.rstrip("s")
|
||||
result.append(f"{value} {name}")
|
||||
return ", ".join(result) if result else "0 seconds"
|
||||
|
||||
|
||||
class Rights:
|
||||
RIGHTS_LIST: typing.List = []
|
||||
|
||||
def __init__(self, mask: int = 0):
|
||||
self.mask = mask & self.MAX_MASK
|
||||
|
||||
def __init_subclass__(cls, **kwargs):
|
||||
super().__init_subclass__(**kwargs)
|
||||
cls.RIGHTS = {name: 1 << i for i, name in enumerate(cls.RIGHTS_LIST)}
|
||||
cls.MAX_MASK = (1 << len(cls.RIGHTS_LIST)) - 1
|
||||
|
||||
def add(self, *right_names: str) -> None:
|
||||
for name in right_names:
|
||||
if name in self.RIGHTS:
|
||||
self.mask |= self.RIGHTS[name]
|
||||
else:
|
||||
return None
|
||||
return self
|
||||
|
||||
def remove(self, *right_names: str) -> None:
|
||||
for name in right_names:
|
||||
if name in self.RIGHTS:
|
||||
self.mask &= ~self.RIGHTS[name]
|
||||
else:
|
||||
return None
|
||||
return self
|
||||
|
||||
def has(self, right_name: str) -> bool:
|
||||
return bool(self.mask & self.RIGHTS.get(right_name, 0))
|
||||
|
||||
def add_index(self, idx: int) -> None:
|
||||
if 0 <= idx < len(self.RIGHTS_LIST):
|
||||
self.mask |= 1 << idx
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
def to_mask(self, chat_rights):
|
||||
mask = 0
|
||||
for right, rmask in self.RIGHTS.items():
|
||||
if (
|
||||
getattr(chat_rights, right)
|
||||
and isinstance(chat_rights, types.ChatAdminRights)
|
||||
) or (
|
||||
not getattr(chat_rights, right)
|
||||
and isinstance(chat_rights, types.ChatBannedRights)
|
||||
):
|
||||
mask |= rmask
|
||||
return mask
|
||||
|
||||
def remove_index(self, idx: int) -> None:
|
||||
if 0 <= idx < len(self.RIGHTS_LIST):
|
||||
self.mask &= ~(1 << idx)
|
||||
return self
|
||||
|
||||
def has_index(self, idx: int) -> bool:
|
||||
if 0 <= idx < len(self.RIGHTS_LIST):
|
||||
return bool(self.mask & (1 << idx))
|
||||
return False
|
||||
|
||||
def to_dict(self) -> dict[str, bool]:
|
||||
return {name: self.has(name) for name in self.RIGHTS_LIST}
|
||||
|
||||
def to_int(self) -> int:
|
||||
return self.mask
|
||||
|
||||
def to_chat_rights(self):
|
||||
return (
|
||||
types.ChatBannedRights(**self.to_dict())
|
||||
if self.__class__.__name__ == "BannedRights"
|
||||
else types.ChatAdminRights(**self.to_dict())
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def stringify(cls) -> str:
|
||||
max_len = max(len(name) for name in cls.RIGHTS_LIST)
|
||||
lines = []
|
||||
for name in cls.RIGHTS_LIST:
|
||||
mask = cls.RIGHTS[name]
|
||||
lines.append(f"{name.ljust(max_len)} — {mask}")
|
||||
return "\n".join(lines)
|
||||
|
||||
@classmethod
|
||||
def list_rights(cls) -> list[tuple[int, str]]:
|
||||
return [(i, name) for i, name in enumerate(cls.RIGHTS_LIST)]
|
||||
|
||||
@classmethod
|
||||
def from_int(cls, mask: int):
|
||||
return cls(mask)
|
||||
|
||||
@classmethod
|
||||
def all(cls):
|
||||
return cls(cls.MAX_MASK)
|
||||
|
||||
@classmethod
|
||||
def none(cls):
|
||||
return cls(0)
|
||||
|
||||
|
||||
class BannedRights(Rights):
|
||||
RIGHTS_LIST = [
|
||||
x for x in types.ChatBannedRights(until_date=None).to_dict().keys() if x != "_"
|
||||
]
|
||||
|
||||
|
||||
class AdminRights(Rights):
|
||||
RIGHTS_LIST = [x for x in types.ChatAdminRights().to_dict().keys() if x != "_"]
|
||||
120
coddrago/modules/stats.py
Normal file
120
coddrago/modules/stats.py
Normal file
@@ -0,0 +1,120 @@
|
||||
# meta developer: @codrago_m
|
||||
|
||||
from .. import loader, utils
|
||||
from telethon.tl.functions.contacts import GetBlockedRequest
|
||||
|
||||
|
||||
@loader.tds
|
||||
class Stats(loader.Module):
|
||||
"""Показывает статистику твоего аккаунта"""
|
||||
|
||||
strings = {
|
||||
"name": "Stats",
|
||||
"stats": """
|
||||
<emoji document_id=5774022692642492953>✅</emoji><b> Account Statistics</b>
|
||||
|
||||
</b><emoji document_id=5208454037531280484>💜</emoji><b> Total chats: </b><code>{all_chats}</code><b>
|
||||
|
||||
</b><emoji document_id=6035084557378654059>👤</emoji><b> Private chats: </b><code>{users}</code><b>
|
||||
</b><emoji document_id=6030400221232501136>🤖</emoji><b> Bots: </b><code>{bots}</code><b>
|
||||
</b><emoji document_id=6032609071373226027>👥</emoji><b> Groups: </b><code>{groups}</code><b>
|
||||
</b><emoji document_id=5870886806601338791>👥</emoji><b> Channels: </b><code>{channels}</code><b>
|
||||
</b><emoji document_id=5870563425628721113>📨</emoji><b> Archived chats: </b><code>{archived}</code><b>
|
||||
</b><emoji document_id=5870948572526022116>✋</emoji><b> Total blocked: </b><code>{blocked}</code>
|
||||
<b>Ͱ</b><emoji document_id=6035084557378654059>👤</emoji><b> Users: </b><code>{blocked_users}</code>
|
||||
<b>Ͱ</b><emoji document_id=6030400221232501136>🤖</emoji><b> Bots: </b><code>{blocked_bots}</code>""",
|
||||
"loading_stats": "<b><emoji document_id=5309893756244206277>🫥</emoji> Loading statistics...</b>",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"name": "Stats",
|
||||
"stats": """
|
||||
<emoji document_id=5774022692642492953>✅</emoji><b> Статистика аккаунта
|
||||
|
||||
</b><emoji document_id=5208454037531280484>💜</emoji><b> Всего чатов: </b><code>{all_chats}</code><b>
|
||||
|
||||
</b><emoji document_id=6035084557378654059>👤</emoji><b> Личных чатов: </b><code>{users}</code><b>
|
||||
</b><emoji document_id=6030400221232501136>🤖</emoji><b> Ботов: </b><code>{bots}</code><b>
|
||||
</b><emoji document_id=6032609071373226027>👥</emoji><b> Групп: </b><code>{groups}</code><b>
|
||||
</b><emoji document_id=5870886806601338791>👥</emoji><b> Каналов: </b><code>{channels}</code><b>
|
||||
</b><emoji document_id=5870563425628721113>📨</emoji><b> Архивированных чатов: </b><code>{archived}</code><b>
|
||||
</b><emoji document_id=5870948572526022116>✋</emoji><b> Всего заблокированных: </b><code>{blocked}</code>
|
||||
<b>Ͱ</b><emoji document_id=6035084557378654059>👤</emoji><b> Пользователи: </b><code>{blocked_users}</code>
|
||||
<b>Ͱ</b><emoji document_id=6030400221232501136>🤖</emoji><b> Боты: </b><code>{blocked_bots}</code>""",
|
||||
"loading_stats": "<b><emoji document_id=5309893756244206277>🫥</emoji> Загрузка статистики...</b>",
|
||||
}
|
||||
|
||||
async def client_ready(self, client, db):
|
||||
self.db = db
|
||||
self._client = client
|
||||
|
||||
@loader.command()
|
||||
async def stats(self, message):
|
||||
"""Получить статистику"""
|
||||
await utils.answer(message, self.strings["loading_stats"])
|
||||
users = 0
|
||||
bots = 0
|
||||
groups = 0
|
||||
channels = 0
|
||||
all_chats = 0
|
||||
archived = 0
|
||||
blocked_bots = 0
|
||||
blocked_users = 0
|
||||
|
||||
limit = 100
|
||||
offset = 0
|
||||
total_blocked = 0
|
||||
while True:
|
||||
blocked_chats = await self._client(
|
||||
GetBlockedRequest(offset=offset, limit=limit)
|
||||
)
|
||||
for user in blocked_chats.users:
|
||||
if user.bot:
|
||||
blocked_bots += 1
|
||||
else:
|
||||
blocked_users += 1
|
||||
blocked = len(blocked_chats.users)
|
||||
total_blocked += blocked
|
||||
|
||||
if blocked < limit:
|
||||
break
|
||||
|
||||
offset += limit
|
||||
|
||||
async for dialog in self._client.iter_dialogs():
|
||||
if getattr(dialog, "archived", False):
|
||||
archived += 1
|
||||
if dialog.is_user:
|
||||
if getattr(dialog.entity, "bot", False):
|
||||
bots += 1
|
||||
all_chats += 1
|
||||
else:
|
||||
users += 1
|
||||
all_chats += 1
|
||||
elif getattr(dialog, "is_group", False):
|
||||
groups += 1
|
||||
all_chats += 1
|
||||
elif dialog.is_channel:
|
||||
if getattr(dialog.entity, "megagroup", False) or getattr(
|
||||
dialog.entity, "gigagroup", False
|
||||
):
|
||||
groups += 1
|
||||
all_chats += 1
|
||||
elif getattr(dialog.entity, "broadcast", False):
|
||||
channels += 1
|
||||
all_chats += 1
|
||||
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings["stats"].format(
|
||||
users=users,
|
||||
bots=bots,
|
||||
channels=channels,
|
||||
groups=groups,
|
||||
all_chats=all_chats,
|
||||
blocked=total_blocked,
|
||||
archived=archived,
|
||||
blocked_users=blocked_users,
|
||||
blocked_bots=blocked_bots,
|
||||
),
|
||||
)
|
||||
245
coddrago/modules/tagwatcher.py
Normal file
245
coddrago/modules/tagwatcher.py
Normal file
@@ -0,0 +1,245 @@
|
||||
# meta developer: @codrago_m
|
||||
|
||||
import logging
|
||||
from .. import utils, loader, main
|
||||
from telethon.tl.functions.messages import MarkDialogUnreadRequest
|
||||
|
||||
logger = logging.getLogger("TagWatcher")
|
||||
|
||||
|
||||
@loader.tds
|
||||
class TagWatcher(loader.Module):
|
||||
"""Informs when you are tagged in chats and automatically reads pm."""
|
||||
|
||||
strings = {
|
||||
"name": "TagWatcher",
|
||||
"_cfg_doc_blacklist_chats": "List of chat IDs to ignore notifications from.",
|
||||
"_cfg_doc_blacklist_users": "List of user IDs to ignore notifications from.",
|
||||
"_cfg_doc_enabled": "Enable/Disable the module.",
|
||||
"_cfg_doc_ignore_bots": "Ignore messages from bots.",
|
||||
"_cfg_doc_pm_autoread": "Automatically mark private messages as read when you receive a message.",
|
||||
"_cfg_doc_ignore_chats": "List of chat IDs to ignore tags from.",
|
||||
"_cfg_doc_ignore_users": "List of user IDs to ignore tags from.",
|
||||
"_cfg_doc_pm_mark_unread": "Mark the PM as unread after automatically reading it.",
|
||||
"_cfg_doc_custom_notif_text": "Custom notification text. Available variables: {title}, {chat_id}, {name}, {user_id}, {msg_content}, {reply_content}, {link}.",
|
||||
"enabled": "<emoji document_id=5208808350858364013>✅</emoji> <b>TagWatcher is enabled.</b>",
|
||||
"disabled": "<emoji document_id=5219776129669276751>❌</emoji> <b>TagWatcher is disabled.</b>",
|
||||
"mentioned": "<b>You were mentioned in <code>{title}</code> [ <code>{chat_id}</code> ] by <a href='tg://user?id={user_id}'>{name}</a> [ <code>{user_id}</code> ]:</b>\nReplying to message:\n{reply_content}\n<b>Message content:</b> {msg_content}\n\n<a href='{link}'>Go to message</a>",
|
||||
"reply_content": "<b>Reply content:</b> {reply_content}",
|
||||
"no_message_content": "❓ Empty message text",
|
||||
"msg_link_btn": "<a href='{msg_url}'>Go to message</a>",
|
||||
"first_msg": "<b>This is the channel where you will receive notifications when someone mentions you in chats.</b>\n\nYou can disable notifications using the <code>{prefix}tagwatcher</code> (<code>{prefix}tw</code>) command.",
|
||||
"request_join_reason": "Stay tuned for updates.",
|
||||
}
|
||||
strings_ru = {
|
||||
"_cls_doc": "Сообщает когда вас отмечают в чатах.",
|
||||
"_cfg_doc_blacklist_chats": "Список ID чатов, от которых уведомления не будут приходить.",
|
||||
"_cfg_doc_blacklist_users": "Список ID пользователей, от которых уведомления не будут приходить.",
|
||||
"_cfg_doc_enabled": "Включить/Выключить модуль.",
|
||||
"_cfg_doc_ignore_bots": "Игнорировать сообщения от ботов.",
|
||||
"_cfg_doc_pm_autoread": "Автоматически отмечать личные сообщения как прочтённые при получении сообщения.",
|
||||
"_cfg_doc_ignore_chats": "Список ID чатов, от которых не будут срабатывать упоминания.",
|
||||
"_cfg_doc_ignore_users": "Список ID пользователей, от которых не будут срабатывать упоминания.",
|
||||
"_cfg_doc_pm_mark_unread": "Помечать ЛС как непрочитанные после автоматического прочтения.",
|
||||
"_cfg_doc_custom_notif_text": "Пользовательский текст уведомления. Доступные переменные: {title}, {chat_id}, {name}, {user_id}, {msg_content}, {reply_content}, {link}.",
|
||||
"enabled": "<emoji document_id=5208808350858364013>✅</emoji> <b>TagWatcher включен.</b>",
|
||||
"disabled": "<emoji document_id=5219776129669276751>❌</emoji> <b>TagWatcher выключен.</b>",
|
||||
"mentioned": "<b>Вас отметил(а) <a href='tg://user?id={user_id}'>{name}</a> [ <code>{user_id}</code> ] в <code>{title}</code> [ <code>{chat_id}</code> ]:</b>\nВ ответ на сообщение:\n{reply_content}\n<b>Текст сообщения:</b> {msg_content}\n\n<a href='{link}'>Перейти к сообщению</a>",
|
||||
"reply_content": "<b>Ответ на сообщение:</b> {reply_content}",
|
||||
"no_message_content": "❓ Пустой текст сообщения",
|
||||
"msg_link_btn": "<a href='{msg_url}'>Перейти к сообщению</a>",
|
||||
"first_msg": "<b>Это канал, в который вы будете получать уведомления, когда кто-то упомянет вас в чатах.</b>\n\nВы можете отключить уведомления с помощью команды <code>{prefix}tagwatcher</code> (<code>{prefix}tw</code>).",
|
||||
"request_join_reason": "Следите за обновлениями модулей.",
|
||||
}
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"custom_notif_text",
|
||||
None,
|
||||
doc=lambda: self.strings["_cfg_doc_custom_notif_text"],
|
||||
validator=loader.validators.Union(
|
||||
loader.validators.String(), loader.validators.NoneType()
|
||||
),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"ignore_bots",
|
||||
True,
|
||||
doc=lambda: self.strings["_cfg_doc_ignore_bots"],
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"ignore_chats",
|
||||
[],
|
||||
doc=lambda: self.strings["_cfg_doc_ignore_chats"],
|
||||
validator=loader.validators.Series(
|
||||
validator=loader.validators.TelegramID()
|
||||
),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"blacklist_chats",
|
||||
[],
|
||||
doc=lambda: self.strings["_cfg_doc_blacklist_chats"],
|
||||
validator=loader.validators.Series(
|
||||
validator=loader.validators.TelegramID()
|
||||
),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"ignore_users",
|
||||
[],
|
||||
doc=lambda: self.strings["_cfg_doc_ignore_users"],
|
||||
validator=loader.validators.Series(
|
||||
validator=loader.validators.TelegramID()
|
||||
),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"blacklist_users",
|
||||
[],
|
||||
doc=lambda: self.strings["_cfg_doc_blacklist_users"],
|
||||
validator=loader.validators.Series(
|
||||
validator=loader.validators.TelegramID()
|
||||
),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"pm_autoread",
|
||||
False,
|
||||
doc=lambda: self.strings["_cfg_doc_pm_autoread"],
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"pm_mark_unread",
|
||||
False,
|
||||
doc=lambda: self.strings["_cfg_doc_pm_mark_unread"],
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
)
|
||||
|
||||
async def client_ready(self):
|
||||
await self.request_join("@xdesai_modules", self.strings["request_join_reason"])
|
||||
self.xdlib = await self.import_lib(
|
||||
"https://raw.githubusercontent.com/xdesai96/modules/refs/heads/main/libs/xdlib.py",
|
||||
suspend_on_error=True,
|
||||
)
|
||||
|
||||
self.asset_channel = self._db.get("legacy.forums", "channel_id", 0)
|
||||
self._notif_topic = await utils.asset_forum_topic(
|
||||
self._client,
|
||||
self._db,
|
||||
self.asset_channel,
|
||||
"TagWatcher",
|
||||
description="Here will be notifications about mentions in chats.",
|
||||
icon_emoji_id=5409025823388741707,
|
||||
)
|
||||
|
||||
async def render_text(self, m):
|
||||
if self.config["custom_notif_text"]:
|
||||
text = self.config["custom_notif_text"]
|
||||
else:
|
||||
text = self.strings["mentioned"]
|
||||
chat = await m.get_chat()
|
||||
sender = await self.xdlib.messages.get_sender(m)
|
||||
title = (
|
||||
utils.escape_html(chat.title)
|
||||
if hasattr(chat, "title")
|
||||
else utils.escape_html(
|
||||
sender.first_name if hasattr(sender, "first_name") else sender.title
|
||||
)
|
||||
)
|
||||
name = (
|
||||
utils.escape_html(
|
||||
sender.first_name if hasattr(sender, "first_name") else sender.title
|
||||
)
|
||||
if sender
|
||||
else "Unknown"
|
||||
)
|
||||
msg_content = (
|
||||
utils.escape_html(m.message)
|
||||
if m.message
|
||||
else self.strings["no_message_content"]
|
||||
)
|
||||
id = sender.id if sender else 0
|
||||
reply_content = ""
|
||||
if m.is_reply:
|
||||
reply = await m.get_reply_message()
|
||||
if reply:
|
||||
reply_content = (
|
||||
utils.escape_html(reply.message)
|
||||
if reply.message
|
||||
else self.strings["no_message_content"]
|
||||
)
|
||||
return text.format(
|
||||
title=title,
|
||||
name=name,
|
||||
chat_id=chat.id,
|
||||
user_id=id,
|
||||
msg_content=msg_content,
|
||||
reply_content=reply_content,
|
||||
link=await m.link,
|
||||
)
|
||||
|
||||
@loader.command(
|
||||
ru_doc="Вкл/выкл TagWatcher.",
|
||||
alias="tw",
|
||||
)
|
||||
async def tagwatcher(self, m):
|
||||
"""Enable/Disable TagWatcher."""
|
||||
try:
|
||||
disabled = self._db.pointer(main.__name__, "disabled_watchers", {})
|
||||
if self.strings["name"] in list(disabled.keys()):
|
||||
del disabled[self.strings["name"]]
|
||||
await utils.answer(m, self.strings["enabled"])
|
||||
else:
|
||||
disabled[self.strings["name"]] = ["*"]
|
||||
await utils.answer(m, self.strings["disabled"])
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
|
||||
@loader.watcher("only_pm")
|
||||
async def pm_reader(self, m):
|
||||
"""To automatically mark private messages as read."""
|
||||
if self.config["pm_autoread"]:
|
||||
chat = await m.get_chat()
|
||||
if chat.id in self.config["ignore_users"] or chat.bot:
|
||||
return
|
||||
try:
|
||||
await self._client.send_read_acknowledge(
|
||||
chat.id, m, clear_mentions=True
|
||||
)
|
||||
if self.config["pm_mark_unread"]:
|
||||
peer = await self._client.get_input_entity(chat.id)
|
||||
await self._client(
|
||||
MarkDialogUnreadRequest(peer, True if not m.out else False)
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
|
||||
@loader.watcher("mention", "no_pm")
|
||||
async def inform(self, m):
|
||||
"""To inform when you are mentioned in chats."""
|
||||
try:
|
||||
sender = await utils.get_user(m)
|
||||
if (
|
||||
utils.get_chat_id(m) in self.config["ignore_chats"]
|
||||
or sender.id in self.config["ignore_users"]
|
||||
):
|
||||
return
|
||||
await self._client.send_read_acknowledge(m.chat_id, m, clear_mentions=True)
|
||||
if (
|
||||
not sender
|
||||
or utils.get_chat_id(m) in self.config["blacklist_chats"]
|
||||
or utils.get_chat_id(m) == self._notif_topic.id
|
||||
or sender.id in self.config["blacklist_users"]
|
||||
):
|
||||
return
|
||||
if self.config["ignore_bots"]:
|
||||
if hasattr(sender, "bot"):
|
||||
if sender.bot:
|
||||
return
|
||||
await self.inline.bot.send_message(
|
||||
int(f"-100{self.asset_channel}"),
|
||||
await self.render_text(m),
|
||||
disable_web_page_preview=True,
|
||||
message_thread_id=self._notif_topic.id,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
197
coddrago/modules/translations/chatmodule.yml
Normal file
197
coddrago/modules/translations/chatmodule.yml
Normal file
@@ -0,0 +1,197 @@
|
||||
en:
|
||||
my_id: "<emoji document_id=5361912768045792571>👑</emoji><b> My ID: </b><code>{id}</code>"
|
||||
chat_id: "<emoji document_id=5886436057091673541>💬</emoji> <b>Chat ID:</b> <code>{id}</code>"
|
||||
user_id: "<emoji document_id=6035084557378654059>👤</emoji> <b>User's ID:</b> <code>{id}</code>"
|
||||
user_not_participant: "<emoji document_id=5019523782004441717>❌</emoji> <b>User is not in this group.</b>"
|
||||
admin_rights: "<blockquote expandable><emoji document_id=6023985764885338464>📜</emoji> {name} <b>Rights in this chat:\n\n{rights}</b>\n\n<emoji document_id=5287734473775918473>🔼</emoji><b> Promoted by: {promoter_name}</b> [{promoter_id}]</blockquote>"
|
||||
not_an_admin: "<emoji document_id=5019523782004441717>❌</emoji><b> {user} is not an admin.</b>"
|
||||
no_rights: "<emoji document_id=5019523782004441717>❌</emoji> <b>I don't have enough rights :(</b>"
|
||||
no_user: "<emoji document_id=5019523782004441717>❌</emoji> <b>User not found.</b>"
|
||||
change_info: "Change Info"
|
||||
delete_messages: "Delete messages"
|
||||
other: "Other"
|
||||
ban_users: "Ban users"
|
||||
invite_users: "Invite Users"
|
||||
pin_messages: "Pin Messages"
|
||||
add_admins: "Add Admins"
|
||||
manage_call: "Manage Call"
|
||||
post_stories: "Post Stories"
|
||||
edit_stories: "Edit Stories"
|
||||
delete_stories: "Delete Stories"
|
||||
anonymous: "Anonymous"
|
||||
manage_topics: "Manage Topics"
|
||||
post_messages: "Post messages"
|
||||
edit_messages: "Edit messages"
|
||||
until_date: "Until: {until_date}"
|
||||
view_messages: "View messages"
|
||||
send_messages: "Send messages"
|
||||
send_media: "Send media"
|
||||
send_stickers: "Send stickers"
|
||||
send_gifs: "Send GIFs"
|
||||
send_games: "Send games"
|
||||
send_inline: "Use inline bots"
|
||||
embed_links: "Embed links"
|
||||
send_polls: "Send polls"
|
||||
send_photos: "Send photos"
|
||||
send_videos: "Send videos"
|
||||
send_roundvideos: "Send round videos"
|
||||
send_audios: "Send audio"
|
||||
send_voices: "Send voice messages"
|
||||
send_docs: "Send documents"
|
||||
send_plain: "Send plain text"
|
||||
invalid_args: "<emoji document_id=5219776129669276751>❌</emoji> <b>Invalid args.</b>"
|
||||
error: "<emoji document_id=5458497936763676259>😖</emoji><b> Something went wrong. Check the logs.</b>"
|
||||
successful_delete: "<emoji document_id=5409029658794537988>✅</emoji> <b>Entity successfully deleted</b>"
|
||||
no_deleted_accounts: "<emoji document_id=5238020759900668600>😶🌫️</emoji> <b>No deleted accounts found here</b>"
|
||||
kicked_deleted_accounts: "<emoji document_id=5408832111773757273>🗑</emoji> <b>Removed deleted accounts from the chat</b>"
|
||||
admins_in_chat: "<emoji document_id=5276229330131772747>👑</emoji> <b>Admins in <code>{title}</code> ({count}):</b>\n"
|
||||
no_admins_in_chat: "<emoji document_id=5458610095539645297>✖️</emoji> <b>No admins in this chat.</b>"
|
||||
bot_list: "<blockquote expandable><emoji document_id=5355051922862653659>🤖</emoji><b> Bots ({count}):</b>\n{bots}</blockquote>"
|
||||
no_bots_in_chat: "<emoji document_id=5458610095539645297>✖️</emoji> <b>No bots in this chat.</b>"
|
||||
user_list: "<blockquote expandable><emoji document_id=5408846628763217930>👤</emoji><b> Users ({count}):</b>\n{users}</blockquote>"
|
||||
no_user_in_chat: "<emoji document_id=5458610095539645297>✖️</emoji> <b>No users in this chat.</b>"
|
||||
user_is_banned: "<emoji document_id=5348402067947929537>🚫</emoji> <b>{name} [<code>{id}</code>] has been banned for{time_info}.</b>"
|
||||
user_is_unbanned: "<emoji document_id=5355277430120523169>👋</emoji> <b>{name} [<code>{id}</code>] has been unbanned.</b>"
|
||||
user_is_kicked: "<emoji document_id=5983033346207256798>🚪</emoji> <b><code>{name}</code> [<code>{id}</code>] has been kicked.</b>"
|
||||
user_is_muted: "<emoji document_id=5409380965644514142>🔕</emoji> <b>{name} [<code>{id}</code>] has been muted for{time_info}.</b>"
|
||||
reason: "<i>Reason: {reason}</i>"
|
||||
forever: "ever"
|
||||
user_is_unmuted: "<emoji document_id=5409331062419502443>🔉</emoji> <b>{name} [<code>{id}</code>] has been unmuted.</b>"
|
||||
title_changed: "<b>The {type_of} title was successfully changed from <code>{old_title}</code> to <code>{new_title}</code>.</b>"
|
||||
channel_created: "<emoji document_id=6296367896398399651>✅</emoji> <b>The channel <code>{title}</code> is created.\n</b><emoji document_id=5237918475254526196>🔗</emoji><b> Invite link: {link}</b>"
|
||||
group_created: "<emoji document_id=6296367896398399651>✅</emoji> <b>The group <code>{title}</code> is created.\n</b><emoji document_id=5237918475254526196>🔗</emoji><b> Invite link: {link}</b>"
|
||||
user_blocked: "<emoji document_id=5019523782004441717>❌</emoji> <b><a href='tg://user?id={user_id}'>{user}</a> is blocked.</b>"
|
||||
user_privacy_restricted: "<emoji document_id=5019523782004441717>❌</emoji> <b><a href='tg://user?id={user_id}'>{user}</a> privacy settings restrict this action.</b>"
|
||||
user_not_mutual_contact: "<emoji document_id=5019523782004441717>❌</emoji> <b><a href='tg://user?id={user_id}'>{user}</a> is not a mutual contact.</b>"
|
||||
user_kicked: "<emoji document_id=5019523782004441717>❌</emoji> <b><a href='tg://user?id={user_id}'>{user}</a> is kicked from the chat.</b>"
|
||||
user_invited: "<emoji document_id=5409029658794537988>✅</emoji> <b>User <a href='tg://user?id={id}'>{user}</a> is invited to the chat.</b>"
|
||||
user_not_invited: "<emoji document_id=5019523782004441717>❌</emoji> <b>User could not be invited to the chat.</b>"
|
||||
admin_list: "<blockquote expandable><emoji document_id=5361912768045792571>👑</emoji> <b>The creator is <a href='tg://user?id={id}'>{name}</a>\n\nAdmins ({admins_count}):</b>\n{admins}</blockquote>"
|
||||
dnd: "<emoji document_id=5384262794306669858>🔕</emoji> <b>Chat muted and archived</b>"
|
||||
dnd_failed: "<emoji document_id=5312383351217201533>⚠️</emoji> <b>Failed to mute and archive chat</b>"
|
||||
pinned: "<emoji document_id=6296367896398399651>✅</emoji> <b>Pinned the message</b>"
|
||||
pin_failed: "<emoji document_id=5458610095539645297>✖️</emoji><b> Failed to pin the message</b>"
|
||||
unpinned: "<emoji document_id=6296367896398399651>✅</emoji> <b>Unpinned the message</b>"
|
||||
unpin_failed: "<emoji document_id=5458610095539645297>✖️</emoji><b> Failed to unpin the message</b>"
|
||||
type_group: "Group"
|
||||
type_channel: "Channel"
|
||||
type_unknown: "Unknown"
|
||||
yes: "<emoji document_id=5408909562919007848>✅</emoji> Yes"
|
||||
no: "<emoji document_id=5361566877149578396>✖️</emoji> No"
|
||||
chatinfo: "<blockquote><emoji document_id=5983036958274752500>🔒</emoji><b> Type: {type_of}\n</b><emoji document_id=5985457743576698865>#️⃣</emoji><b> Chat ID: </b><code>{id}</code><b>\n</b><emoji document_id=5408849420491962048>🔥</emoji><b> Title: {title}\n<emoji document_id=5258328383183396223>📖</emoji><b> Forum:</b> {is_forum}</blockquote>\n</b><blockquote><emoji document_id=5870676941614354370>🖋</emoji><b> About: {about}</blockquote>\n</b><blockquote><emoji document_id=5805553606635559688>👑</emoji><b> Admin count: {admins_count}\n</b><emoji document_id=5433648711982921307>✅</emoji><b> Online count: {online_count}\n</b><emoji document_id=6024039683904772353>👤</emoji><b> Participants count: {participants_count}\n</b><emoji document_id=5816617137447376501>🚫</emoji><b> Kicked сount: {kicked_count}\n</b><emoji document_id=5431560533243346887>🔀</emoji><b> Requests pending: {requests_pending}</blockquote>\n</b><blockquote><emoji document_id=5408910404732595664>🕐</emoji><b> Slowmode period: {slowmode_seconds}\n</b><emoji document_id=6019279794988915337>📞</emoji><b> Call: {call}\n</b><emoji document_id=5408832111773757273>🗑</emoji><b> TTL period: {ttl_period}\n</b><emoji document_id=5408846628763217930>👤</emoji><b> Recent requesters: {recent_requesters}</blockquote>\n</b><blockquote><emoji document_id=6021690418398239007>👥</emoji><b> Linked Chat ID: {linked_chat_id}\n</b><emoji document_id=6019328362479097179>🛡</emoji><b> Antispam: {antispam}\n</b><emoji document_id=6024008227564296298>👁</emoji><b> Participants hidden: {participants_hidden}</blockquote>\n</b><emoji document_id=6028171274939797252>🔗</emoji><b> Link: {link}</b>"
|
||||
requests_checked: "<emoji document_id=5409029658794537988>✅</emoji> <b>Checked requests from {entities}</b>"
|
||||
promoted: "<emoji document_id=5458614983212427372>👑</emoji> <b><a href='tg://user?id={id}'>{name}</a> is promoted!\n<emoji document_id=5409029658794537988>✅</emoji> Rights: {rights}</b>"
|
||||
full_rights: "Full rights"
|
||||
promote: "<b>Select rights for <a href='tg://user?id={id}'>{name}</a>!\nRank: {rank}</b>"
|
||||
restrict: "<b>Restricting <a href='tg://user?id={id}'>{name}</a> for{time}\n\n<i>Select which actions to restrict. Options marked in green will be applied.</i></b>"
|
||||
restricted: "<emoji document_id=5208491751639106607>🚫</emoji> <b><a href='tg://user?id={id}'>{name}</a> has been restricted for{time}</b>"
|
||||
demoted: "<emoji document_id=5447183459602669338>🔽</emoji> <b><a href='tg://user?id={id}'>{name}</a> is demoted</b>"
|
||||
userinfo: "<blockquote><emoji document_id=5985457743576698865>#️⃣</emoji><b> ID: {user_id}\n</b><emoji document_id=5408849420491962048>🔥</emoji><b> First name: {first_name}\n</b><emoji document_id=5408849420491962048>🔥</emoji><b> Last name: {last_name}\n</b><emoji document_id=5208889774848361126>📞</emoji><b> Phone number: {phone}\n</b><emoji document_id=5364052602357044385>🐶</emoji><b> Usernames: {usernames}</b></blockquote><b>\n</b><blockquote><emoji document_id=5985616786215669454>ℹ️</emoji><b> About: {about}</b></blockquote><b>\n</b><blockquote><emoji document_id=5408910404732595664>🕐</emoji><b> Work hours: </b>{business_work_hours}\n<emoji document_id=5408892365869952851>❤️</emoji><b> Emoji status: {emoji_status}</b></blockquote><b>\n</b><blockquote><emoji document_id=5409331062419502443>🔉</emoji><b> Personal channel: {personal_channel}\n</b><emoji document_id=6024041612345088787>🎂</emoji><b> Birthday: {birthday}\n</b><emoji document_id=6037175527846975726>🎁</emoji><b> Gifts count: {stargifts_count}</b></blockquote><b>\n</b><blockquote><emoji document_id=5208842667647061916>🚨</emoji><b> Common chats count: {common_chats_count}\n</b><emoji document_id=5985401861757210746>👥</emoji><b> Common chats: {common_chats}</b></blockquote>"
|
||||
monday: "Monday"
|
||||
tuesday: "Tuesday"
|
||||
wednesday: "Wednesday"
|
||||
thursday: "Thursday"
|
||||
friday: "Friday"
|
||||
saturday: "Saturday"
|
||||
sunday: "Sunday"
|
||||
owns: "<emoji document_id=5458614983212427372>👑</emoji><b> My kingdoms [{num}]:</b>\n<blockquote expandable>{owns}</blockquote>"
|
||||
close: "❌ Close"
|
||||
apply: "✅ Apply"
|
||||
ru:
|
||||
my_id: "<emoji document_id=5361912768045792571>👑</emoji><b> Мой ID: </b><code>{id}</code>"
|
||||
chat_id: "<emoji document_id=5886436057091673541>💬</emoji> <b>ID чата:</b> <code>{id}</code>"
|
||||
user_id: "<emoji document_id=6035084557378654059>👤</emoji> <b>ID пользователя:</b> <code>{id}</code>"
|
||||
user_not_participant: "<emoji document_id=5019523782004441717>❌</emoji> <b>Пользователь не состоит в этом чате.</b>"
|
||||
admin_rights: "<blockquote expandable><emoji document_id=6023985764885338464>📜</emoji> {name} <b>Права в этом чате:\n\n{rights}</b>\n\n<emoji document_id=5287734473775918473>🔼</emoji><b> Повысил: {promoter_name}</b> [{promoter_id}]</blockquote>"
|
||||
not_an_admin: "<emoji document_id=5019523782004441717>❌</emoji><b> {user} не является админом.</b>"
|
||||
no_rights: "<emoji document_id=5019523782004441717>❌</emoji> <b>У меня недостаточно прав :(</b>"
|
||||
no_user: "<emoji document_id=5019523782004441717>❌</emoji> <b>Пользователь не найден.</b>"
|
||||
change_info: "Изменение информации"
|
||||
delete_messages: "Удаление сообщений"
|
||||
other: "Другое"
|
||||
ban_users: "Бан пользователей"
|
||||
invite_users: "Приглашение пользователей"
|
||||
pin_messages: "Закрепление сообщений"
|
||||
add_admins: "Добавление админов"
|
||||
manage_call: "Управление звонками"
|
||||
post_stories: "Публикация историй"
|
||||
edit_stories: "Редактирование историй"
|
||||
delete_stories: "Удаление историй"
|
||||
anonymous: "Анонимность"
|
||||
manage_topics: "Управление темами"
|
||||
post_messages: "Публикация сообщений"
|
||||
edit_messages: "Редактирование сообщений"
|
||||
view_messages: "Просмотр сообщений"
|
||||
send_messages: "Отправка сообщений"
|
||||
send_media: "Отправка медиа"
|
||||
send_stickers: "Отправка стикеров"
|
||||
send_gifs: "Отправка GIF"
|
||||
send_games: "Отправка игр"
|
||||
send_inline: "Использование инлайн-ботов"
|
||||
embed_links: "Встраивание ссылок"
|
||||
send_polls: "Отправка опросов"
|
||||
send_photos: "Отправка фото"
|
||||
send_videos: "Отправка видео"
|
||||
send_roundvideos: "Отправка круговых видео"
|
||||
send_audios: "Отправка аудио"
|
||||
send_voices: "Отправка голосовых"
|
||||
send_docs: "Отправка документов"
|
||||
send_plain: "Отправка текстовых сообщений"
|
||||
invalid_args: "<emoji document_id=5219776129669276751>❌</emoji> <b>Неверные аргументы.</b>"
|
||||
error: "<emoji document_id=5458497936763676259>😖</emoji><b> Что-то пошло не так. Проверьте логи.</b>"
|
||||
successful_delete: "<emoji document_id=5409029658794537988>✅</emoji> <b>Сущность успешно удалена</b>"
|
||||
no_deleted_accounts: "<emoji document_id=5238020759900668600>😶🌫️</emoji> <b>Удалённых аккаунтов здесь нет</b>"
|
||||
kicked_deleted_accounts: "<emoji document_id=5408832111773757273>🗑</emoji> <b>Удалённые аккаунты очищены из чата</b>"
|
||||
admins_in_chat: "<emoji document_id=5276229330131772747>👑</emoji> <b>Админы в <code>{title}</code> ({count}):</b>\n"
|
||||
no_admins_in_chat: "<emoji document_id=5458610095539645297>✖️</emoji> <b>В этом чате нет админов.</b>"
|
||||
bot_list: "<blockquote expandable><emoji document_id=5355051922862653659>🤖</emoji><b> Боты ({count}):</b>\n{bots}</blockquote>"
|
||||
no_bots_in_chat: "<emoji document_id=5458610095539645297>✖️</emoji> <b>В этом чате нет ботов.</b>"
|
||||
user_list: "<blockquote expandable><emoji document_id=5408846628763217930>👤</emoji><b> Пользователи ({count}):</b>\n{users}</blockquote>"
|
||||
no_user_in_chat: "<emoji document_id=5458610095539645297>✖️</emoji> <b>В этом чате нет пользователей.</b>"
|
||||
user_is_banned: "<emoji document_id=5348402067947929537>🚫</emoji> <b>{name} [<code>{id}</code>] забанен на{time_info}.</b>"
|
||||
user_is_unbanned: "<emoji document_id=5355277430120523169>👋</emoji> <b>{name} [<code>{id}</code>] разбанен.</b>"
|
||||
user_is_kicked: "<emoji document_id=5983033346207256798>🚪</emoji> <b><code>{name}</code> [<code>{id}</code>] кикнут.</b>"
|
||||
user_is_muted: "<emoji document_id=5409380965644514142>🔕</emoji> <b>{name} [<code>{id}</code>] замьючен на{time_info}.</b>"
|
||||
reason: "<i>Причина: {reason}</i>"
|
||||
forever: "всегда"
|
||||
user_is_unmuted: "<emoji document_id=5409331062419502443>🔉</emoji> <b>{name} [<code>{id}</code>] размьючен.</b>"
|
||||
title_changed: "<b>{type_of} название успешно изменено с <code>{old_title}</code> на <code>{new_title}</code>.</b>"
|
||||
channel_created: "<emoji document_id=6296367896398399651>✅</emoji> <b>Канал <code>{title}</code> создан.\n</b><emoji document_id=5237918475254526196>🔗</emoji><b> Пригласительная ссылка: {link}</b>"
|
||||
group_created: "<emoji document_id=6296367896398399651>✅</emoji> <b>Группа <code>{title}</code> создана.\n</b><emoji document_id=5237918475254526196>🔗</emoji><b> Пригласительная ссылка: {link}</b>"
|
||||
user_blocked: "<emoji document_id=5019523782004441717>❌</emoji> <b><a href='tg://user?id={user_id}'>{user}</a> заблокирован.</b>"
|
||||
user_privacy_restricted: "<emoji document_id=5019523782004441717>❌</emoji> <b><a href='tg://user?id={user_id}'>{user}</a> ограничил это действие настройками приватности.</b>"
|
||||
user_not_mutual_contact: "<emoji document_id=5019523782004441717>❌</emoji> <b><a href='tg://user?id={user_id}'>{user}</a> не является взаимным контактом.</b>"
|
||||
user_kicked: "<emoji document_id=5019523782004441717>❌</emoji> <b><a href='tg://user?id={user_id}'>{user}</a> исключён из чата.</b>"
|
||||
user_invited: "<emoji document_id=5409029658794537988>✅</emoji> <b>Пользователь <a href='tg://user?id={id}'>{user}</a> приглашён в чат.</b>"
|
||||
user_not_invited: "<emoji document_id=5019523782004441717>❌</emoji> <b>Не удалось пригласить пользователя.</b>"
|
||||
admin_list: "<blockquote expandable><emoji document_id=5361912768045792571>👑</emoji> <b>Создатель: <a href='tg://user?id={id}'>{name}</a>\n\nАдмины ({admins_count}):</b>\n{admins}</blockquote>"
|
||||
dnd: "<emoji document_id=5384262794306669858>🔕</emoji> <b>Чат заглушён и архивирован</b>"
|
||||
dnd_failed: "<emoji document_id=5312383351217201533>⚠️</emoji> <b>Не удалось заглушить и архивировать чат</b>"
|
||||
pinned: "<emoji document_id=6296367896398399651>✅</emoji> <b>Сообщение закреплено</b>"
|
||||
pin_failed: "<emoji document_id=5458610095539645297>✖️</emoji><b> Не удалось закрепить сообщение</b>"
|
||||
unpinned: "<emoji document_id=6296367896398399651>✅</emoji> <b>Сообщение откреплено</b>"
|
||||
unpin_failed: "<emoji document_id=5458610095539645297>✖️</emoji><b> Не удалось открепить сообщение</b>"
|
||||
type_group: "Группа"
|
||||
type_channel: "Канал"
|
||||
type_unknown: "Неизвестно"
|
||||
yes: "<emoji document_id=5408909562919007848>✅</emoji> Да"
|
||||
no: "<emoji document_id=5361566877149578396>✖️</emoji> Нет"
|
||||
chatinfo: "<blockquote><emoji document_id=5983036958274752500>🔒</emoji><b> Тип: {type_of}\n</b><emoji document_id=5985457743576698865>#️⃣</emoji><b> ID чата: </b><code>{id}</code><b>\n</b><emoji document_id=5408849420491962048>🔥</emoji><b> Название: {title}\n</b><emoji document_id=5258328383183396223>📖</emoji><b> Форум:</b> {is_forum}</blockquote>\n<blockquote><emoji document_id=5870676941614354370>🖋</emoji><b> Описание: {about}</blockquote>\n</b><blockquote><emoji document_id=5805553606635559688>👑</emoji><b> Кол-во админов: {admins_count}\n</b><emoji document_id=5433648711982921307>✅</emoji><b> Онлайн: {online_count}\n</b><emoji document_id=6024039683904772353>👤</emoji><b> Кол-во участников: {participants_count}\n</b><emoji document_id=5816617137447376501>🚫</emoji><b> Кикнутых: {kicked_count}\n</b><emoji document_id=5431560533243346887>🔀</emoji><b> Запросов в ожидании: {requests_pending}</blockquote>\n</b><blockquote><emoji document_id=5408910404732595664>🕐</emoji><b> Слоумод: {slowmode_seconds}\n</b><emoji document_id=6019279794988915337>📞</emoji><b> Звонок: {call}\n</b><emoji document_id=5408832111773757273>🗑</emoji><b> Период TTL: {ttl_period}\n</b><emoji document_id=5408846628763217930>👤</emoji><b> Недавние запросы: {recent_requesters}</blockquote>\n</b><blockquote><emoji document_id=6021690418398239007>👥</emoji><b> Связанный чат: {linked_chat_id}\n</b><emoji document_id=6019328362479097179>🛡</emoji><b> Антиспам: {antispam}\n</b><emoji document_id=6024008227564296298>👁</emoji><b> Скрытые участники: {participants_hidden}</blockquote>\n</b><emoji document_id=6028171274939797252>🔗</emoji><b> Ссылка: {link}</b>"
|
||||
requests_checked: "<emoji document_id=5409029658794537988>✅</emoji> <b>Рассмотрены заявки от {entities}</b>"
|
||||
promoted: "<emoji document_id=5458614983212427372>👑</emoji> <b><a href='tg://user?id={id}'>{name}</a> назначен администратором!\n<emoji document_id=5409029658794537988>✅</emoji> Права: {rights}</b>"
|
||||
full_rights: "Полные права"
|
||||
promote: "<b>Выберите права для <a href='tg://user?id={id}'>{name}</a>!\nРанг: {rank}</b>"
|
||||
restrict: "<b>Ограничение <a href='tg://user?id={id}'>{name}</a> на{time}\n\n<i>Выберите, что нужно запретить. Опции, отмеченные зелёным, будут применены.</i></b>"
|
||||
restricted: "<emoji document_id=5208491751639106607>🚫</emoji> <b><a href='tg://user?id={id}'>{name}</a> был(а) ограничен(а) на{time}</b>"
|
||||
demoted: "<emoji document_id=5447183459602669338>🔽</emoji> <b><a href='tg://user?id={id}'>{name}</a> понижен</b>"
|
||||
userinfo: "<blockquote><emoji document_id=5985457743576698865>#️⃣</emoji><b> ID: {user_id}\n</b><emoji document_id=5408849420491962048>🔥</emoji><b> Имя: {first_name}\n</b><emoji document_id=5408849420491962048>🔥</emoji><b> Фамилия: {last_name}\n</b><emoji document_id=5208889774848361126>📞</emoji><b> Номер телефона: {phone}\n</b><emoji document_id=5364052602357044385>🐶</emoji><b> Имена пользователей: {usernames}</b></blockquote><b>\n</b><blockquote><emoji document_id=5985616786215669454>ℹ️</emoji><b> О себе: {about}</b></blockquote><b>\n</b><blockquote><emoji document_id=5408910404732595664>🕐</emoji><b> Рабочие часы: </b>{business_work_hours}\n<emoji document_id=5408892365869952851>❤️</emoji><b> Статус с эмодзи: {emoji_status}</b></blockquote><b>\n</b><blockquote><emoji document_id=5409331062419502443>🔉</emoji><b> Личный канал: {personal_channel}\n</b><emoji document_id=6024041612345088787>🎂</emoji><b> День рождения: {birthday}\n</b><emoji document_id=6037175527846975726>🎁</emoji><b> Количество подарков: {stargifts_count}</b></blockquote><b>\n</b><blockquote><emoji document_id=5208842667647061916>🚨</emoji><b> Количество общих чатов: {common_chats_count}\n</b><emoji document_id=5985401861757210746>👥</emoji><b> Общие чаты: {common_chats}</b></blockquote>"
|
||||
monday: "Понедельник"
|
||||
tuesday: "Вторник"
|
||||
wednesday: "Среда"
|
||||
thursday: "Четверг"
|
||||
friday: "Пятница"
|
||||
saturday: "Суббота"
|
||||
sunday: "Воскресенье"
|
||||
owns: "<emoji document_id=5458614983212427372>👑</emoji><b> Мои королевства [{num}]:</b>\n<blockquote expandable>{owns}</blockquote>"
|
||||
close: "❌ Закрыть"
|
||||
apply: "✅ Применить"
|
||||
Reference in New Issue
Block a user