Added and updated repositories 2026-01-06 01:11:05
@@ -23,10 +23,11 @@
|
|||||||
# meta pic: https://i.postimg.cc/Hx3Zm8rB/logo.png
|
# meta pic: https://i.postimg.cc/Hx3Zm8rB/logo.png
|
||||||
# meta banner: https://te.legra.ph/file/55fa6eebae860a359ac27.jpg
|
# meta banner: https://te.legra.ph/file/55fa6eebae860a359ac27.jpg
|
||||||
|
|
||||||
__version__ = (1, 2, 0)
|
__version__ = (1, 3, 2)
|
||||||
|
|
||||||
from .. import loader, utils
|
from .. import loader, utils # type: ignore
|
||||||
from telethon.tl.types import Message # type: ignore
|
from telethon.tl import types # type: ignore
|
||||||
|
from telethon.tl.types import Message, InputDocument # type: ignore
|
||||||
|
|
||||||
@loader.tds
|
@loader.tds
|
||||||
class SendMod(loader.Module):
|
class SendMod(loader.Module):
|
||||||
@@ -104,19 +105,68 @@ class SendMod(loader.Module):
|
|||||||
|
|
||||||
|
|
||||||
@loader.command(
|
@loader.command(
|
||||||
ru_doc="[text] - Написать сообщение в закрытую тему",
|
ru_doc="[text or reply(media/file/sticker) or coordinates (<lat>, <long>)] - Написать сообщение в закрытую тему",
|
||||||
uz_doc="[text] - Yopiq mavzuga xabar yozing",
|
uz_doc="[text or reply(media/file/sticker) or coordinates (<lat>, <long>)] - Yopiq mavzuga xabar yozing",
|
||||||
de_doc="[text] - Schreiben Sie eine Nachricht zu einem geschlossenen Thema",
|
de_doc="[text or reply(media/file/sticker) or coordinates (<lat>, <long>)] - Schreiben Sie eine Nachricht zu einem geschlossenen Thema",
|
||||||
es_doc="[text] - Escribir un mensaje a un tema cerrado",
|
es_doc="[text or reply(media/file/sticker) or coordinates (<lat>, <long>)] - Escribir un mensaje a un tema cerrado",
|
||||||
)
|
)
|
||||||
async def sendclosedtopic(self, message: Message):
|
async def sendclosedtopic(self, message: Message):
|
||||||
"""[text] - Write a message to a closed topic"""
|
"""[text or reply(media/file/sticker) or coordinates (<lat>, <long>)] - Write a message to a closed topic"""
|
||||||
if not utils.get_args_raw(message):
|
args = utils.get_args_raw(message)
|
||||||
await utils.answer(message, self.strings["error_send"])
|
message_text = args if args else ""
|
||||||
|
reply = await message.get_reply_message()
|
||||||
|
|
||||||
|
media = None
|
||||||
|
temp_file = None
|
||||||
|
|
||||||
|
if reply and reply.media:
|
||||||
|
doc = getattr(reply.media, "document", None)
|
||||||
|
|
||||||
|
if doc and any(a.__class__.__name__ == "DocumentAttributeSticker" for a in doc.attributes):
|
||||||
|
media = InputDocument(
|
||||||
|
id=doc.id,
|
||||||
|
access_hash=doc.access_hash,
|
||||||
|
file_reference=doc.file_reference
|
||||||
|
)
|
||||||
|
message_text = ""
|
||||||
|
|
||||||
|
elif doc and doc.mime_type == "image/webp":
|
||||||
|
temp_file = await reply.download_media()
|
||||||
|
media = temp_file
|
||||||
|
else:
|
||||||
|
media = reply.media
|
||||||
else:
|
else:
|
||||||
text2 = f"{utils.get_args_raw(message)}"
|
media = message.media
|
||||||
await message.delete()
|
|
||||||
await message.reply(text2)
|
if message_text and "," in message_text:
|
||||||
|
lat_str, long_str = message_text.split(",", 1)
|
||||||
|
try:
|
||||||
|
gps_x = float(lat_str.strip())
|
||||||
|
gps_y = float(long_str.strip())
|
||||||
|
if -90 <= gps_x <= 90 and -180 <= gps_y <= 180:
|
||||||
|
geo_point = types.InputGeoPoint(lat=gps_x, long=gps_y)
|
||||||
|
media = types.InputMediaGeoPoint(geo_point)
|
||||||
|
message_text = ""
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if not message_text and not media:
|
||||||
|
await utils.answer(message, self.strings["error_send_2"])
|
||||||
|
return
|
||||||
|
|
||||||
|
await message.delete()
|
||||||
|
await message.reply(
|
||||||
|
message_text,
|
||||||
|
file=media if media else None,
|
||||||
|
parse_mode="html"
|
||||||
|
)
|
||||||
|
|
||||||
|
if temp_file:
|
||||||
|
import os
|
||||||
|
try:
|
||||||
|
os.remove(temp_file)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
@loader.command(
|
@loader.command(
|
||||||
ru_doc="[@UserName] [text or replay] - Написать сообщение в личные сообщения",
|
ru_doc="[@UserName] [text or replay] - Написать сообщение в личные сообщения",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
__version__ = (1, 1, 0)
|
version = (1, 4, 0)
|
||||||
|
|
||||||
# meta developer: @RUIS_VlP, @RoKrz
|
# meta developer: @RUIS_VlP, @RoKrz
|
||||||
# requires: yt_dlp
|
# requires: yt_dlp
|
||||||
@@ -7,51 +7,190 @@ import yt_dlp
|
|||||||
import uuid
|
import uuid
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import tempfile
|
||||||
from .. import loader, utils
|
from .. import loader, utils
|
||||||
|
|
||||||
|
|
||||||
def extract_youtube_link(text):
|
def extract_video_link(text):
|
||||||
|
"""Извлекает видео с сайтов"""
|
||||||
if not text:
|
if not text:
|
||||||
return None
|
return None
|
||||||
match = re.search(r"(https?://)?(www\.)?(youtube\.com|youtu\.be)/[^\s]+", text)
|
video_sites_patterns = [
|
||||||
return match.group(0) if match else None
|
# YouTube
|
||||||
|
r"(https?://)?(www\.)?(youtube\.com|youtu\.be)/[^\s]+",
|
||||||
|
|
||||||
|
# Social Media
|
||||||
|
r"(https?://)?(www\.)?(tiktok\.com|vt\.tiktok\.com|vm\.tiktok\.com)/[^\s]+",
|
||||||
|
r"(https?://)?(www\.)?instagram\.com/(p|reel|tv)/[^\s]+",
|
||||||
|
r"(https?://)?(www\.)?(twitter\.com|x\.com)/[^\s]+/status/[^\s]+",
|
||||||
|
r"(https?://)?(www\.)?facebook\.com/[^\s]+/videos/[^\s]+",
|
||||||
|
r"(https?://)?(www\.)?reddit\.com/r/[^\s]+/comments/[^\s]+",
|
||||||
|
|
||||||
async def download_video(url):
|
# Video Platforms
|
||||||
|
r"(https?://)?(www\.)?vimeo\.com/[^\s]+",
|
||||||
|
r"(https?://)?(www\.)?dailymotion\.com/video/[^\s]+",
|
||||||
|
r"(https?://)?(www\.)?twitch\.tv/(videos/|clip/|[^/]+$)[^\s]*",
|
||||||
|
r"(https?://)?(www\.)?streamable\.com/[^\s]+",
|
||||||
|
|
||||||
|
# News & Media
|
||||||
|
r"(https?://)?(www\.)?bbc\.co\.uk/iplayer/[^\s]+",
|
||||||
|
r"(https?://)?(www\.)?cnn\.com/videos/[^\s]+",
|
||||||
|
r"(https?://)?(www\.)?reuters\.com/video/[^\s]+",
|
||||||
|
|
||||||
|
# Educational
|
||||||
|
r"(https?://)?(www\.)?coursera\.org/learn/[^\s]+",
|
||||||
|
r"(https?://)?(www\.)?udemy\.com/course/[^\s]+",
|
||||||
|
r"(https?://)?(www\.)?khanacademy\.org/[^\s]+",
|
||||||
|
|
||||||
|
# Russian platforms
|
||||||
|
r"(https?://)?(www\.)?rutube\.ru/video/[^\s]+",
|
||||||
|
r"(https?://)?(www\.)?vk\.com/(video|clip)[^\s]+",
|
||||||
|
r"(https?://)?(www\.)?ok\.ru/video/[^\s]+",
|
||||||
|
|
||||||
|
# Other popular platforms
|
||||||
|
r"(https?://)?(www\.)?pornhub\.com/view_video\.php\?viewkey=[^\s]+",
|
||||||
|
r"(https?://)?(www\.)?xvideos\.com/video[^\s]+",
|
||||||
|
r"(https?://)?(www\.)?soundcloud\.com/[^\s]+",
|
||||||
|
r"(https?://)?(www\.)?bandcamp\.com/track/[^\s]+",
|
||||||
|
r"(https?://)?(www\.)?mixcloud\.com/[^\s]+",
|
||||||
|
|
||||||
|
# Live streaming
|
||||||
|
r"(https?://)?(www\.)?periscope\.tv/[^\s]+",
|
||||||
|
r"(https?://)?(www\.)?ustream\.tv/[^\s]+",
|
||||||
|
|
||||||
|
# International
|
||||||
|
r"(https?://)?(www\.)?bilibili\.com/video/[^\s]+",
|
||||||
|
r"(https?://)?(www\.)?niconico\.jp/watch/[^\s]+",
|
||||||
|
r"(https?://)?(www\.)?youku\.com/v_show/[^\s]+",
|
||||||
|
|
||||||
|
# Generic fallback for other video URLs
|
||||||
|
r"https?://[^\s]+\.(mp4|webm|avi|mkv|mov|flv|m4v)",
|
||||||
|
]
|
||||||
|
for pattern in video_sites_patterns:
|
||||||
|
match = re.search(pattern, text, re.IGNORECASE)
|
||||||
|
if match:
|
||||||
|
return match.group(0)
|
||||||
|
general_url_pattern = r"https?://[^\s]+"
|
||||||
|
match = re.search(general_url_pattern, text)
|
||||||
|
if match:
|
||||||
|
url = match.group(0)
|
||||||
|
excluded_domains = [
|
||||||
|
'google.com', 'yandex.ru', 'wikipedia.org', 'github.com',
|
||||||
|
'stackoverflow.com', 'reddit.com/r/', 'amazon.com'
|
||||||
|
]
|
||||||
|
if not any(domain in url.lower() for domain in excluded_domains):
|
||||||
|
return url
|
||||||
|
return None
|
||||||
|
async def download_video(url, cookies_text=None, youtube_client="default", custom_user_agent=None):
|
||||||
output_dir = utils.get_base_dir()
|
output_dir = utils.get_base_dir()
|
||||||
random_uuid = str(uuid.uuid4())
|
random_uuid = str(uuid.uuid4())
|
||||||
os.makedirs(output_dir, exist_ok=True)
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
ydl_opts = {
|
formats_to_try = [
|
||||||
'format': 'best',
|
'best[ext=mp4]',
|
||||||
'outtmpl': os.path.join(output_dir, f'{random_uuid}.%(ext)s'),
|
'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]',
|
||||||
'noplaylist': True,
|
'bestvideo+bestaudio/best',
|
||||||
}
|
'best',
|
||||||
|
'best*',
|
||||||
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
'bestvideo+bestaudio',
|
||||||
info_dict = ydl.extract_info(url, download=True)
|
'best[height<=1080]',
|
||||||
video_ext = info_dict.get('ext', None)
|
'best[height<=720]',
|
||||||
file_path = os.path.join(output_dir, f"{random_uuid}.{video_ext}")
|
'worst',
|
||||||
title = info_dict.get('title', None)
|
'worst*',
|
||||||
|
]
|
||||||
return file_path, title
|
cookies_file = None
|
||||||
|
if cookies_text and cookies_text.strip():
|
||||||
|
cleaned_cookies = cookies_text.strip()
|
||||||
|
if cleaned_cookies.startswith('"') or cleaned_cookies.startswith("'"):
|
||||||
|
cleaned_cookies = cleaned_cookies[1:]
|
||||||
|
if cleaned_cookies.endswith('"') or cleaned_cookies.endswith("'"):
|
||||||
|
cleaned_cookies = cleaned_cookies[:-1]
|
||||||
|
|
||||||
|
cookies_file = tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.txt', encoding='utf-8')
|
||||||
|
cookies_file.write(cleaned_cookies)
|
||||||
|
cookies_file.close()
|
||||||
|
try:
|
||||||
|
is_youtube = 'youtube.com' in url.lower() or 'youtu.be' in url.lower()
|
||||||
|
for format_option in formats_to_try:
|
||||||
|
ydl_opts = {
|
||||||
|
'format': format_option,
|
||||||
|
'outtmpl': os.path.join(output_dir, f'{random_uuid}.%(ext)s'),
|
||||||
|
'noplaylist': True,
|
||||||
|
'merge_output_format': 'mp4',
|
||||||
|
}
|
||||||
|
if cookies_file:
|
||||||
|
ydl_opts['cookiefile'] = cookies_file.name
|
||||||
|
if custom_user_agent:
|
||||||
|
ydl_opts['http_headers'] = {'User-Agent': custom_user_agent}
|
||||||
|
if is_youtube and youtube_client != "default":
|
||||||
|
ydl_opts['extractor_args'] = {'youtube': {'player_client': [youtube_client]}}
|
||||||
|
try:
|
||||||
|
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
||||||
|
info_dict = ydl.extract_info(url, download=True)
|
||||||
|
video_ext = info_dict.get('ext', None)
|
||||||
|
file_path = os.path.join(output_dir, f"{random_uuid}.{video_ext}")
|
||||||
|
title = info_dict.get('title', None)
|
||||||
|
channel = info_dict.get('uploader', None)
|
||||||
|
return file_path, title, channel
|
||||||
|
except Exception as e:
|
||||||
|
if "Requested format is not available" in str(e) or "No video formats found" in str(e):
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
raise e
|
||||||
|
raise Exception("Не удалось скачать видео ни в одном формате")
|
||||||
|
|
||||||
|
finally:
|
||||||
|
if cookies_file:
|
||||||
|
try:
|
||||||
|
os.unlink(cookies_file.name)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
def convert_markdown_to_html(template: str, link: str) -> str:
|
def convert_markdown_to_html(template: str, link: str) -> str:
|
||||||
return re.sub(r'\[([^\]]+)\]\(([^)]+)\)', r'<a href="\2">\1</a>', template).replace("{link}", link)
|
return re.sub(r'\[([^\]]+)\]\(([^)]+)\)', r'<a href="\2">\1</a>', template).replace("{link}", link)
|
||||||
|
|
||||||
|
|
||||||
@loader.tds
|
@loader.tds
|
||||||
class YouTube_DLDMod(loader.Module):
|
class YouTube_DLDMod(loader.Module):
|
||||||
"""Помогает скачивать видео с YouTube"""
|
"""Помогает скачивать видео с YouTube, TikTok и др."""
|
||||||
|
|
||||||
strings = {
|
strings = {
|
||||||
"name": "YouTube-DLD",
|
"name": "YouTube-DLD",
|
||||||
"no_link": "❌ <b>Пожалуйста, укажите ссылку на YouTube либо ответьте на сообщение с ней.</b>",
|
"no_link": "❌ <b>Пожалуйста, укажите ссылку на видео либо ответьте на сообщение с ней.</b>",
|
||||||
"default_downloading": "📥 <b>Начинаю загрузку видео.</b>\n\nℹ️ <code>Это может занять до 5 минут, в зависимости от длины и качества видео.</code>",
|
"default_downloading": "📥 <b>Начинаю загрузку видео.</b>\n\nℹ️ <code>Это может занять до 5 минут, в зависимости от длины и качества видео.</code>",
|
||||||
"default_error": "❌ <b>Ошибка!</b>\n\n<code>{}</code>",
|
"default_error": "❌ <b>Ошибка!</b>\n\n<code>{}</code>",
|
||||||
"default_response": "🎥 Вот [ваше видео]({link})!\n\n<code>{title}</code>",
|
"default_response": "🎥 Вот [ваше видео]({link})!\n\n<code>{title}</code>",
|
||||||
}
|
"default_channel": "📺 Канал: <code>{channel}</code>",
|
||||||
|
"cookies_error": "🍪 <b>YouTube требует аутентификацию!</b>\n\n❌ Ошибка: <code>Sign in to confirm you're not a bot</code>\n\n<b>Возможные причины:</b>\n▫️ YouTube детектит запросы без аутентификации\n▫️ IP сервера может быть заблокирован YouTube\n▫️ Видео требует подтверждения возраста/входа\n\n<b>Решения (попробуй по порядку):</b>\n\n<b>1️⃣ Смени YouTube клиент:</b>\n• Открой <code>.cfg YouTube-DLD</code>\n• Попробуй разные значения <code>youtube_client</code>:\n - <code>mweb</code> (мобильная веб-версия, часто работает)\n - <code>android</code> (Android приложение, сработало на сервере во Франции)\n - <code>ios</code> (iOS приложение)\n - <code>tv_embedded</code> (встроенный ТВ плеер)\n\n<b>2️⃣ Добавь куки (для пользователей из проблемных регионов):</b>\n• Открой НОВОЕ приватное окно (в браузере ctrl+shift+N) и залогинься на YouTube\n• Перейди на https://www.youtube.com/robots.txt в ТОЙ же вкладке\n• Cookie-Editor (расширение) → Export → Netscape format\n• СРАЗУ закрой приватное окно\n• Вставь куки в <code>youtube_cookies</code> (БЕЗ кавычек)",
|
||||||
|
"supported_sites": """🎥 <b>Поддерживаемые сайты:</b>
|
||||||
|
|
||||||
|
🔴 <b>YouTube</b> — youtube.com, youtu.be
|
||||||
|
🎵 <b>TikTok</b> — tiktok.com, vt.tiktok.com, vm.tiktok.com
|
||||||
|
📸 <b>Instagram</b> — instagram.com (посты, reels, IGTV)
|
||||||
|
🐦 <b>X (Twitter)</b> — x.com, twitter.com
|
||||||
|
👥 <b>Facebook</b> — facebook.com (видео)
|
||||||
|
🎬 <b>Vimeo</b> — vimeo.com
|
||||||
|
📺 <b>Twitch</b> — twitch.tv (стримы, клипы, VOD)
|
||||||
|
🤖 <b>Reddit</b> — reddit.com (видео из постов)
|
||||||
|
⚡ <b>Dailymotion</b> — dailymotion.com
|
||||||
|
|
||||||
|
<b>🇷🇺 Российские:</b>
|
||||||
|
▫️ <b>RuTube</b> — rutube.ru
|
||||||
|
▫️ <b>ВКонтакте</b> — vk.com (видео)
|
||||||
|
▫️ <b>Одноклассники</b> — ok.ru
|
||||||
|
|
||||||
|
<b>📚 Образовательные:</b>
|
||||||
|
▫️ <b>Coursera</b> — coursera.org
|
||||||
|
▫️ <b>Udemy</b> — udemy.com
|
||||||
|
▫️ <b>Khan Academy</b> — khanacademy.org
|
||||||
|
|
||||||
|
<b>🌍 Международные:</b>
|
||||||
|
▫️ <b>Bilibili</b> — bilibili.com
|
||||||
|
▫️ <b>NicoNico</b> — niconico.jp
|
||||||
|
▫️ <b>BBC iPlayer</b> — bbc.co.uk/iplayer
|
||||||
|
|
||||||
|
<b>🎵 Аудио:</b>
|
||||||
|
▫️ <b>SoundCloud</b> — soundcloud.com
|
||||||
|
▫️ <b>Bandcamp</b> — bandcamp.com
|
||||||
|
▫️ <b>Mixcloud</b> — mixcloud.com
|
||||||
|
|
||||||
|
<i>И многие другие платформы...</i>"""
|
||||||
|
}
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.config = loader.ModuleConfig(
|
self.config = loader.ModuleConfig(
|
||||||
loader.ConfigValue(
|
loader.ConfigValue(
|
||||||
@@ -75,32 +214,84 @@ class YouTube_DLDMod(loader.Module):
|
|||||||
self.strings["default_response"],
|
self.strings["default_response"],
|
||||||
"Ответ после загрузки. (используй {link} для ссылки и {title} для названия видео)"
|
"Ответ после загрузки. (используй {link} для ссылки и {title} для названия видео)"
|
||||||
),
|
),
|
||||||
|
loader.ConfigValue(
|
||||||
|
"show_channel",
|
||||||
|
True,
|
||||||
|
"Показывать название канала?",
|
||||||
|
validator=loader.validators.Boolean(),
|
||||||
|
),
|
||||||
|
loader.ConfigValue(
|
||||||
|
"channel_text",
|
||||||
|
self.strings["default_channel"],
|
||||||
|
"Текст для отображения канала. (используй {channel} для имени канала)"
|
||||||
|
),
|
||||||
|
loader.ConfigValue(
|
||||||
|
"youtube_cookies",
|
||||||
|
"",
|
||||||
|
"🍪 Куки YouTube в формате Netscape (опционально)\n\n"
|
||||||
|
"⚠️ ВНИМАНИЕ: Риск бана аккаунта! Используй тестовый аккаунт.\n\n"
|
||||||
|
"Как получить:\n"
|
||||||
|
"1. НОВОЕ приватное окно (Ctrl+Shift+N) → залогинься на YouTube\n"
|
||||||
|
"2. Перейди на https://www.youtube.com/robots.txt в той же вкладке\n"
|
||||||
|
"3. Cookie-Editor (расширение) → Export → Netscape format\n"
|
||||||
|
"4. СРАЗУ закрой приватное окно\n"
|
||||||
|
"5. Вставь текст сюда (БЕЗ кавычек)",
|
||||||
|
validator=loader.validators.String(),
|
||||||
|
),
|
||||||
|
loader.ConfigValue(
|
||||||
|
"youtube_client",
|
||||||
|
"mweb",
|
||||||
|
"📱 YouTube клиент для обхода блокировок\n\n"
|
||||||
|
"Доступные варианты:\n"
|
||||||
|
"• default - стандартный (может не работать)\n"
|
||||||
|
"• mweb - мобильная веб-версия\n"
|
||||||
|
"• android - Android приложение (рекомендуется)\n"
|
||||||
|
"• ios - iOS приложение\n"
|
||||||
|
"• tv_embedded - встроенный ТВ плеер\n\n"
|
||||||
|
"Если видео не скачивается, попробуй другой клиент!",
|
||||||
|
validator=loader.validators.Choice(["default", "mweb", "android", "ios", "tv_embedded"]),
|
||||||
|
),
|
||||||
|
loader.ConfigValue(
|
||||||
|
"custom_user_agent",
|
||||||
|
"",
|
||||||
|
"🌐 Кастомный User-Agent (опционально)\n\n"
|
||||||
|
"Можно указать User-Agent браузера для обхода некоторых блокировок.\n"
|
||||||
|
"Например:\n"
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36\n\n"
|
||||||
|
"Оставь пустым для использования стандартного.",
|
||||||
|
validator=loader.validators.String(),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@loader.command()
|
@loader.command()
|
||||||
async def dlvideo(self, message):
|
async def dvlist(self, message):
|
||||||
"""<ссылка> или ответ на сообщение со ссылкой — скачивает видео с YouTube"""
|
"""Показать список всех поддерживаемых сайтов"""
|
||||||
|
await utils.answer(message, self.strings["supported_sites"])
|
||||||
|
|
||||||
|
@loader.command()
|
||||||
|
async def dlvideo(self, message):
|
||||||
|
"""<ссылка> или ответ на сообщение со ссылкой — скачивает видео с поддерживаемых платформ"""
|
||||||
args = utils.get_args_raw(message)
|
args = utils.get_args_raw(message)
|
||||||
reply = await message.get_reply_message()
|
reply = await message.get_reply_message()
|
||||||
|
link = extract_video_link(args) if args else None
|
||||||
link = extract_youtube_link(args) if args else None
|
|
||||||
if not link and reply:
|
if not link and reply:
|
||||||
link = extract_youtube_link(reply.raw_text)
|
link = extract_video_link(reply.raw_text)
|
||||||
|
|
||||||
if not link:
|
if not link:
|
||||||
await utils.answer(message, self.strings["no_link"])
|
await utils.answer(message, self.strings["no_link"])
|
||||||
return
|
return
|
||||||
|
|
||||||
await utils.answer(message, self.config["downloading_text"])
|
await utils.answer(message, self.config["downloading_text"])
|
||||||
|
|
||||||
try:
|
try:
|
||||||
video, title = await download_video(link)
|
cookies_text = self.config["youtube_cookies"].strip() if self.config["youtube_cookies"] else None
|
||||||
|
youtube_client = self.config["youtube_client"]
|
||||||
|
user_agent = self.config["custom_user_agent"].strip() if self.config["custom_user_agent"] else None
|
||||||
|
video, title, channel = await download_video(link, cookies_text, youtube_client, user_agent)
|
||||||
if self.config["show_link"]:
|
if self.config["show_link"]:
|
||||||
caption_template = self.config["response_text"]
|
caption_template = self.config["response_text"]
|
||||||
caption = convert_markdown_to_html(caption_template, link)
|
caption = convert_markdown_to_html(caption_template, link)
|
||||||
caption = caption.replace("{title}", title or "")
|
caption = caption.replace("{title}", title or "")
|
||||||
|
if self.config["show_channel"] and channel:
|
||||||
|
channel_text = self.config["channel_text"].replace("{channel}", channel)
|
||||||
|
caption += f"\n\n{channel_text}"
|
||||||
else:
|
else:
|
||||||
caption = title or "Готово!"
|
caption = title or "Готово!"
|
||||||
|
|
||||||
@@ -108,9 +299,10 @@ class YouTube_DLDMod(loader.Module):
|
|||||||
message,
|
message,
|
||||||
video,
|
video,
|
||||||
caption=caption,
|
caption=caption,
|
||||||
parse_mode="HTML"
|
parse_mode="HTML",
|
||||||
|
reply_to=reply or message,
|
||||||
|
silent=True
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await message.delete()
|
await message.delete()
|
||||||
except:
|
except:
|
||||||
@@ -120,9 +312,14 @@ class YouTube_DLDMod(loader.Module):
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error_msg = self.config["error_text"].format(e)
|
error_str = str(e)
|
||||||
await utils.answer(message, error_msg)
|
if "Sign in to confirm you're not a bot" in error_str or "Use --cookies" in error_str:
|
||||||
|
await utils.answer(message, self.strings["cookies_error"])
|
||||||
|
else:
|
||||||
|
error_msg = self.config["error_text"].format(e)
|
||||||
|
await utils.answer(message, error_msg)
|
||||||
try:
|
try:
|
||||||
os.remove(video)
|
if 'video' in locals():
|
||||||
|
os.remove(video)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|||||||
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
@@ -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
|
passwordgen
|
||||||
send
|
send
|
||||||
lastfm
|
lastfm
|
||||||
|
dbmod
|
||||||
|
chatmodule
|
||||||
|
stats
|
||||||
|
tagwatcher
|
||||||
|
hardspam
|
||||||
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
@@ -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
@@ -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
@@ -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
@@ -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: "✅ Применить"
|
||||||
BIN
fajox1/famods/.DS_Store
vendored
Normal file
BIN
fajox1/famods/assets/.DS_Store
vendored
Normal file
BIN
fajox1/famods/assets/banners/.DS_Store
vendored
Normal file
BIN
fajox1/famods/assets/banners/ptichki.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
fajox1/famods/assets/birds/.DS_Store
vendored
Normal file
9
fajox1/famods/assets/birds/birds.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[
|
||||||
|
"blue",
|
||||||
|
"green",
|
||||||
|
"orange",
|
||||||
|
"pink",
|
||||||
|
"purple",
|
||||||
|
"white",
|
||||||
|
"yellow"
|
||||||
|
]
|
||||||
BIN
fajox1/famods/assets/birds/blue.png
Normal file
|
After Width: | Height: | Size: 162 KiB |
BIN
fajox1/famods/assets/birds/green.png
Normal file
|
After Width: | Height: | Size: 156 KiB |
BIN
fajox1/famods/assets/birds/orange.png
Normal file
|
After Width: | Height: | Size: 149 KiB |
BIN
fajox1/famods/assets/birds/pink.png
Normal file
|
After Width: | Height: | Size: 168 KiB |
BIN
fajox1/famods/assets/birds/purple.png
Normal file
|
After Width: | Height: | Size: 158 KiB |
BIN
fajox1/famods/assets/birds/white.png
Normal file
|
After Width: | Height: | Size: 147 KiB |
BIN
fajox1/famods/assets/birds/yellow.png
Normal file
|
After Width: | Height: | Size: 166 KiB |
BIN
fajox1/famods/assets/impact.ttf
Normal file
@@ -43,3 +43,4 @@ evalaliases
|
|||||||
spotify4ik
|
spotify4ik
|
||||||
picme
|
picme
|
||||||
hetsu
|
hetsu
|
||||||
|
ptichki
|
||||||
156
fajox1/famods/ptichki.py
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
# █▀▀ ▄▀█ █▀▄▀█ █▀█ █▀▄ █▀
|
||||||
|
# █▀░ █▀█ █░▀░█ █▄█ █▄▀ ▄█
|
||||||
|
|
||||||
|
# https://t.me/famods
|
||||||
|
|
||||||
|
# 🔒 Licensed under the GNU AGPLv3
|
||||||
|
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------------
|
||||||
|
# Name: Ptichki
|
||||||
|
# Description: Генератор птиц
|
||||||
|
# meta developer: @FAmods
|
||||||
|
# meta banner: https://github.com/FajoX1/FAmods/blob/main/assets/banners/ptichki.png?raw=true
|
||||||
|
# requires: aiohttp pillow
|
||||||
|
# ---------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
import json
|
||||||
|
import random
|
||||||
|
import aiohttp
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from io import BytesIO
|
||||||
|
from PIL import Image, ImageDraw, ImageFont
|
||||||
|
|
||||||
|
from .. import loader, utils
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@loader.tds
|
||||||
|
class Ptichki(loader.Module):
|
||||||
|
"""Генератор птиц"""
|
||||||
|
|
||||||
|
strings = {
|
||||||
|
"name": "Ptichki",
|
||||||
|
|
||||||
|
"no_args": "<emoji document_id=5393175345367123686>🦅</emoji> <b>Нужно </b><code>{}{} {}</code>",
|
||||||
|
|
||||||
|
"generation": "<emoji document_id=5393165630151103839>🦅</emoji> <i>Генерирую птичку...</i>",
|
||||||
|
}
|
||||||
|
|
||||||
|
async def client_ready(self, client, db):
|
||||||
|
self.db = db
|
||||||
|
self._client = client
|
||||||
|
|
||||||
|
self.assets_link = "https://famods.fajox.one/assets"
|
||||||
|
|
||||||
|
self.font_url = f"{self.assets_link}/impact.ttf"
|
||||||
|
self.birds_url = f"{self.assets_link}/birds/birds.json"
|
||||||
|
|
||||||
|
async def fetch_bytes(self, url: str) -> bytes:
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
async with session.get(url) as resp:
|
||||||
|
resp.raise_for_status()
|
||||||
|
return await resp.read()
|
||||||
|
|
||||||
|
async def get_bird_url(self) -> str:
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
async with session.get(self.birds_url) as resp:
|
||||||
|
birds_list = json.loads(await resp.text())
|
||||||
|
|
||||||
|
return f"{self.assets_link}/birds/{random.choice(birds_list)}.png"
|
||||||
|
|
||||||
|
async def generate_bird(self, text: str, format: str) -> bytes:
|
||||||
|
|
||||||
|
text = text.upper()
|
||||||
|
|
||||||
|
img_bytes = await self.fetch_bytes(
|
||||||
|
await self.get_bird_url()
|
||||||
|
)
|
||||||
|
img = Image.open(BytesIO(img_bytes)).convert("RGBA")
|
||||||
|
img.thumbnail((512, 512))
|
||||||
|
width, height = img.size
|
||||||
|
draw = ImageDraw.Draw(img)
|
||||||
|
|
||||||
|
font_bytes = await self.fetch_bytes(self.font_url)
|
||||||
|
font_size = 55
|
||||||
|
min_font_size = 12
|
||||||
|
max_width_fraction = 0.9
|
||||||
|
|
||||||
|
font = ImageFont.truetype(BytesIO(font_bytes), font_size)
|
||||||
|
text_width = font.getlength(text)
|
||||||
|
|
||||||
|
if text_width > max_width_fraction * width:
|
||||||
|
scale = (max_width_fraction * width) / text_width
|
||||||
|
font_size = max(int(font_size * scale), min_font_size)
|
||||||
|
font = ImageFont.truetype(BytesIO(font_bytes), font_size)
|
||||||
|
|
||||||
|
bbox = draw.textbbox((0, 0), text, font=font)
|
||||||
|
text_width = bbox[2] - bbox[0]
|
||||||
|
text_height = bbox[3] - bbox[1]
|
||||||
|
x = (width - text_width) / 2
|
||||||
|
y = height - text_height - (height * 0.05)
|
||||||
|
|
||||||
|
draw.text(
|
||||||
|
(x, y),
|
||||||
|
text,
|
||||||
|
font=font,
|
||||||
|
fill="white",
|
||||||
|
stroke_width=2,
|
||||||
|
stroke_fill="black"
|
||||||
|
)
|
||||||
|
|
||||||
|
output = BytesIO()
|
||||||
|
img.save(output, format=format.upper())
|
||||||
|
output.seek(0)
|
||||||
|
output.name = f"ptitchka.{format.lower()}"
|
||||||
|
|
||||||
|
return output
|
||||||
|
|
||||||
|
@loader.command()
|
||||||
|
async def ptichka(self, message):
|
||||||
|
"""[текст] - Сгенерировать стикер с птицей"""
|
||||||
|
|
||||||
|
text = utils.get_args_raw(message)
|
||||||
|
if not text:
|
||||||
|
return await utils.answer(
|
||||||
|
message,
|
||||||
|
self.strings["no_args"].format(
|
||||||
|
self.get_prefix(), "ptichka", "[текст]"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
m = await utils.answer(message, self.strings['generation'])
|
||||||
|
|
||||||
|
await self.client.send_file(
|
||||||
|
message.peer_id,
|
||||||
|
mime_type="image/webp",
|
||||||
|
file=await self.generate_bird(text, format="webp"),
|
||||||
|
reply_to=getattr(message.reply_to, "reply_to_msg_id", None),
|
||||||
|
)
|
||||||
|
|
||||||
|
return await m.delete()
|
||||||
|
|
||||||
|
@loader.command()
|
||||||
|
async def ptichka_img(self, message):
|
||||||
|
"""[текст] - Сгенерировать фото с птицей"""
|
||||||
|
|
||||||
|
text = utils.get_args_raw(message)
|
||||||
|
if not text:
|
||||||
|
return await utils.answer(
|
||||||
|
message,
|
||||||
|
self.strings["no_args"].format(
|
||||||
|
self.get_prefix(), "ptichka_img", "[текст]"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
m = await utils.answer(message, self.strings['generation'])
|
||||||
|
|
||||||
|
await self.client.send_file(
|
||||||
|
message.peer_id,
|
||||||
|
mime_type="image/png",
|
||||||
|
file=await self.generate_bird(text, format="png"),
|
||||||
|
reply_to=getattr(message.reply_to, "reply_to_msg_id", None),
|
||||||
|
)
|
||||||
|
|
||||||
|
return await m.delete()
|
||||||
@@ -20,4 +20,5 @@ createavatarspack
|
|||||||
multiunloadmodule
|
multiunloadmodule
|
||||||
tagall2.0
|
tagall2.0
|
||||||
point
|
point
|
||||||
deviceinfo
|
deviceinfo
|
||||||
|
mpi
|
||||||
229
fiksofficial/python-modules/mpi.py
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
# ______ ___ ___ _ _
|
||||||
|
# ____ | ___ \ | \/ | | | | |
|
||||||
|
# / __ \| |_/ / _| . . | ___ __| |_ _| | ___
|
||||||
|
# / / _` | __/ | | | |\/| |/ _ \ / _` | | | | |/ _ \
|
||||||
|
# | | (_| | | | |_| | | | | (_) | (_| | |_| | | __/
|
||||||
|
# \ \__,_\_| \__, \_| |_/\___/ \__,_|\__,_|_|\___|
|
||||||
|
# \____/ __/ |
|
||||||
|
# |___/
|
||||||
|
|
||||||
|
# На модуль распространяется лицензия "GNU General Public License v3.0"
|
||||||
|
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
||||||
|
|
||||||
|
# meta developer: @pymodule
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
|
import base64
|
||||||
|
import json
|
||||||
|
from datetime import datetime
|
||||||
|
from .. import loader, utils
|
||||||
|
|
||||||
|
async def download_image(session: aiohttp.ClientSession, url: str):
|
||||||
|
try:
|
||||||
|
async with session.get(url, timeout=35) as resp:
|
||||||
|
if resp.status == 200:
|
||||||
|
return await resp.read()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
@loader.tds
|
||||||
|
class MinecraftPlayerInfo(loader.Module):
|
||||||
|
"""A module for obtaining information about a Minecraft player by nickname"""
|
||||||
|
strings = {
|
||||||
|
"name": "MinecraftPlayerInfo",
|
||||||
|
"no_args": "<b>❌ Specify the player's nickname</b>",
|
||||||
|
"not_found": "<b>❌ Player with this nickname not found</b>",
|
||||||
|
"loading": "<b>🔄 Loading information...</b>",
|
||||||
|
"no_media": "<b>❌ Failed to load any images</b>",
|
||||||
|
"partial_media": "<i>⚠️ Some images failed to load</i>\n\n",
|
||||||
|
"no_history": "No nickname history",
|
||||||
|
"model_steve": "Classic (Steve)",
|
||||||
|
"model_alex": "Slim (Alex)",
|
||||||
|
"cape_yes": "Yes ✅",
|
||||||
|
"cape_no": "No ❌",
|
||||||
|
"cape_failed": " (render failed to load)",
|
||||||
|
"history_current": "— current",
|
||||||
|
"history_changed": "— changed {}",
|
||||||
|
"history_original": "— original",
|
||||||
|
"info": (
|
||||||
|
"<b>🔍 Minecraft Player Information</b>\n\n"
|
||||||
|
"<b>Nickname:</b> <code>{name}</code>\n"
|
||||||
|
"<b>UUID:</b> <code>{uuid_dashed}</code>\n"
|
||||||
|
"<b>Skin Model:</b> {model}\n"
|
||||||
|
"<b>Cape:</b> {cape}\n\n"
|
||||||
|
"<b>Nickname History:</b>\n{history}\n\n"
|
||||||
|
"<a href=\"https://namemc.com/profile/{uuid_raw}\">🔗 Full profile on NameMC</a>"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
strings_ru = {
|
||||||
|
"_cls_doc": "Модуль для получения информации о игроке Minecraft по никнейму",
|
||||||
|
"no_args": "<b>❌ Укажите никнейм игрока</b>",
|
||||||
|
"not_found": "<b>❌ Игрок с таким никнеймом не найден</b>",
|
||||||
|
"loading": "<b>🔄 Загружаю информацию...</b>",
|
||||||
|
"no_media": "<b>❌ Не удалось загрузить ни одного изображения</b>",
|
||||||
|
"partial_media": "<i>⚠️ Некоторые изображения не загрузились</i>\n\n",
|
||||||
|
"no_history": "Нет истории изменений",
|
||||||
|
"model_steve": "Classic (Steve)",
|
||||||
|
"model_alex": "Slim (Alex)",
|
||||||
|
"cape_yes": "Есть ✅",
|
||||||
|
"cape_no": "Нет ❌",
|
||||||
|
"cape_failed": " (рендер не загрузился)",
|
||||||
|
"history_current": "— текущий",
|
||||||
|
"history_changed": "— изменён {}",
|
||||||
|
"history_original": "— оригинальный",
|
||||||
|
"info": (
|
||||||
|
"<b>🔍 Информация о игроке Minecraft</b>\n\n"
|
||||||
|
"<b>Никнейм:</b> <code>{name}</code>\n"
|
||||||
|
"<b>UUID:</b> <code>{uuid_dashed}</code>\n"
|
||||||
|
"<b>Модель скина:</b> {model}\n"
|
||||||
|
"<b>Плащ:</b> {cape}\n\n"
|
||||||
|
"<b>История никнеймов:</b>\n{history}\n\n"
|
||||||
|
"<a href=\"https://namemc.com/profile/{uuid_raw}\">🔗 Полный профиль на NameMC</a>"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async def client_ready(self, client, db):
|
||||||
|
self.client = client
|
||||||
|
|
||||||
|
@loader.command(ru_doc="<никнейм> — отображает информацию об игроке Minecraft (3D-рендеринг, история, плащ)")
|
||||||
|
async def mcplayer(self, message):
|
||||||
|
"""<nickname> — show Minecraft player info (3D renders, history, cape)"""
|
||||||
|
args = utils.get_args_raw(message)
|
||||||
|
if not args:
|
||||||
|
return await utils.answer(message, self.strings("no_args"))
|
||||||
|
|
||||||
|
loading_msg = await utils.answer(message, self.strings("loading"))
|
||||||
|
|
||||||
|
nick = args.strip()
|
||||||
|
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
async with session.get(f"https://api.mojang.com/users/profiles/minecraft/{nick}") as resp:
|
||||||
|
if resp.status == 204 or resp.status != 200:
|
||||||
|
await loading_msg.edit(self.strings("not_found"))
|
||||||
|
return
|
||||||
|
data = await resp.json()
|
||||||
|
|
||||||
|
name = data["name"]
|
||||||
|
uuid = data["id"]
|
||||||
|
uuid_dashed = f"{uuid[:8]}-{uuid[8:12]}-{uuid[12:16]}-{uuid[16:20]}-{uuid[20:]}"
|
||||||
|
|
||||||
|
async with session.get(f"https://api.mojang.com/user/profiles/{uuid}/names") as resp:
|
||||||
|
names = await resp.json() if resp.status == 200 else [{"name": name}]
|
||||||
|
|
||||||
|
history_lines = []
|
||||||
|
for i, entry in enumerate(names):
|
||||||
|
uname = entry["name"]
|
||||||
|
if i == len(names) - 1:
|
||||||
|
history_lines.append(f"• <b>{uname}</b> {self.strings('history_current')}")
|
||||||
|
elif "changedToAt" in entry:
|
||||||
|
changed = datetime.utcfromtimestamp(entry["changedToAt"] / 1000).strftime("%d.%m.%Y")
|
||||||
|
history_lines.append(f"• {uname} {self.strings('history_changed').format(changed)}")
|
||||||
|
else:
|
||||||
|
history_lines.append(f"• {uname} {self.strings('history_original')}")
|
||||||
|
|
||||||
|
history = "\n".join(history_lines) or self.strings("no_history")
|
||||||
|
|
||||||
|
async with session.get(f"https://sessionserver.mojang.com/session/minecraft/profile/{uuid}?unsigned=false") as resp:
|
||||||
|
profile = await resp.json() if resp.status == 200 else {}
|
||||||
|
|
||||||
|
cape_url = None
|
||||||
|
model = self.strings("model_steve")
|
||||||
|
if profile.get("properties"):
|
||||||
|
for prop in profile["properties"]:
|
||||||
|
if prop["name"] == "textures":
|
||||||
|
textures_b64 = prop["value"]
|
||||||
|
textures_json = json.loads(base64.b64decode(textures_b64).decode("utf-8"))
|
||||||
|
textures = textures_json.get("textures", {})
|
||||||
|
skin_data = textures.get("SKIN", {})
|
||||||
|
cape_url = textures.get("CAPE", {}).get("url")
|
||||||
|
if "metadata" in skin_data and skin_data["metadata"].get("model") == "slim":
|
||||||
|
model = self.strings("model_alex")
|
||||||
|
|
||||||
|
has_cape = bool(cape_url)
|
||||||
|
cape_text = self.strings("cape_yes") if has_cape else self.strings("cape_no")
|
||||||
|
|
||||||
|
body_urls = [
|
||||||
|
f"https://crafthead.net/body/{uuid}.png",
|
||||||
|
f"https://api.mineatar.io/body/{uuid}?scale=12",
|
||||||
|
f"https://mc-heads.net/body/{uuid}/500",
|
||||||
|
f"https://cravatar.eu/helmbody/{uuid}/500.png",
|
||||||
|
f"https://minotar.net/body/{uuid}/500.png",
|
||||||
|
]
|
||||||
|
|
||||||
|
head_urls = [
|
||||||
|
f"https://crafthead.net/avatar/{uuid}.png",
|
||||||
|
f"https://api.mineatar.io/head/{uuid}?scale=12",
|
||||||
|
f"https://mc-heads.net/avatar/{uuid}/500",
|
||||||
|
f"https://cravatar.eu/head/{uuid}/500.png",
|
||||||
|
f"https://minotar.net/avatar/{uuid}/500.png",
|
||||||
|
]
|
||||||
|
|
||||||
|
body_bytes = None
|
||||||
|
for url in body_urls:
|
||||||
|
body_bytes = await download_image(session, url)
|
||||||
|
if body_bytes:
|
||||||
|
break
|
||||||
|
|
||||||
|
head_bytes = None
|
||||||
|
for url in head_urls:
|
||||||
|
head_bytes = await download_image(session, url)
|
||||||
|
if head_bytes:
|
||||||
|
break
|
||||||
|
|
||||||
|
cape_bytes = await download_image(session, cape_url) if cape_url else None
|
||||||
|
if not cape_bytes and has_cape:
|
||||||
|
cape_fallbacks = [
|
||||||
|
f"https://crafthead.net/cape/{uuid}.png",
|
||||||
|
f"https://api.mineatar.io/cape/{uuid}.png",
|
||||||
|
f"https://mc-heads.net/cape/{uuid}",
|
||||||
|
]
|
||||||
|
for url in cape_fallbacks:
|
||||||
|
cape_bytes = await download_image(session, url)
|
||||||
|
if cape_bytes:
|
||||||
|
break
|
||||||
|
if not cape_bytes:
|
||||||
|
cape_text += self.strings("cape_failed")
|
||||||
|
|
||||||
|
uploaded_media = []
|
||||||
|
|
||||||
|
if body_bytes:
|
||||||
|
uploaded = await self.client.upload_file(body_bytes, file_name=f"{name}_body.png")
|
||||||
|
uploaded_media.append(uploaded)
|
||||||
|
|
||||||
|
if head_bytes:
|
||||||
|
uploaded = await self.client.upload_file(head_bytes, file_name=f"{name}_head.png")
|
||||||
|
uploaded_media.append(uploaded)
|
||||||
|
|
||||||
|
if cape_bytes:
|
||||||
|
uploaded = await self.client.upload_file(cape_bytes, file_name=f"{name}_cape.png")
|
||||||
|
uploaded_media.append(uploaded)
|
||||||
|
|
||||||
|
if not uploaded_media:
|
||||||
|
await loading_msg.edit(self.strings("no_media"))
|
||||||
|
return
|
||||||
|
|
||||||
|
caption = self.strings("info").format(
|
||||||
|
name=name,
|
||||||
|
uuid_dashed=uuid_dashed,
|
||||||
|
uuid_raw=uuid,
|
||||||
|
model=model,
|
||||||
|
cape=cape_text,
|
||||||
|
history=history
|
||||||
|
)
|
||||||
|
|
||||||
|
if not (body_bytes and head_bytes):
|
||||||
|
caption = self.strings("partial_media") + caption
|
||||||
|
|
||||||
|
await self.client.send_file(
|
||||||
|
message.peer_id,
|
||||||
|
file=uploaded_media,
|
||||||
|
caption=caption,
|
||||||
|
parse_mode="html",
|
||||||
|
reply_to=message.reply_to_msg_id or message.id
|
||||||
|
)
|
||||||
|
|
||||||
|
await loading_msg.delete()
|
||||||
|
if message.out:
|
||||||
|
await message.delete()
|
||||||
@@ -26,241 +26,6 @@ from .. import loader, utils
|
|||||||
__version__ = (2, 0, 0)
|
__version__ = (2, 0, 0)
|
||||||
|
|
||||||
|
|
||||||
FLAGS = {
|
|
||||||
"ad": "🇦🇩", # Андорра
|
|
||||||
"ae": "🇦🇪", # ОАЭ
|
|
||||||
"af": "🇦🇫", # Афганистан
|
|
||||||
"ag": "🇦🇬", # Антигуа и Барбуда
|
|
||||||
"ai": "🇦🇮", # Ангилья
|
|
||||||
"al": "🇦🇱", # Албания
|
|
||||||
"am": "🇦🇲", # Армения
|
|
||||||
"ao": "🇦🇴", # Ангола
|
|
||||||
"aq": "🇦🇶", # Антарктика
|
|
||||||
"ar": "🇦🇷", # Аргентина
|
|
||||||
"at": "🇦🇹", # Австрия
|
|
||||||
"au": "🇦🇺", # Австралия
|
|
||||||
"aw": "🇦🇼", # Аруба
|
|
||||||
"ax": "🇦🇽", # Аландские острова
|
|
||||||
"az": "🇦🇿", # Азербайджан
|
|
||||||
"ba": "🇧🇦", # Босния и Герцеговина
|
|
||||||
"bb": "🇧🇧", # Барбадос
|
|
||||||
"bd": "🇧🇩", # Бангладеш
|
|
||||||
"be": "🇧🇪", # Бельгия
|
|
||||||
"bf": "🇧🇫", # Буркина-Фасо
|
|
||||||
"bg": "🇧🇬", # Болгария
|
|
||||||
"bh": "🇧🇭", # Бахрейн
|
|
||||||
"bi": "🇧🇮", # Бурунди
|
|
||||||
"bj": "🇧🇯", # Бенин
|
|
||||||
"bl": "🇧🇱", # Сен-Бартельми
|
|
||||||
"bm": "🇧🇲", # Бермудские острова
|
|
||||||
"bn": "🇧🇳", # Бруней
|
|
||||||
"bo": "🇧🇴", # Боливия
|
|
||||||
"bq": "🇧🇶", # Бонэйр, Синт-Эстатиус и Саба
|
|
||||||
"br": "🇧🇷", # Бразилия
|
|
||||||
"bs": "🇧🇸", # Багамы
|
|
||||||
"bt": "🇧🇹", # Бутан
|
|
||||||
"bv": "🇧🇻", # остров Буве
|
|
||||||
"bw": "🇧🇼", # Ботсвана
|
|
||||||
"by": "🇧🇾", # Беларусь
|
|
||||||
"bz": "🇧🇿", # Белиз
|
|
||||||
"ca": "🇨🇦", # Канада
|
|
||||||
"cc": "🇨🇨", # Кокосовые (Килинг) острова
|
|
||||||
"cd": "🇨🇩", # Конго - Киншаса
|
|
||||||
"cf": "🇨🇫", # Центральноафриканская Республика
|
|
||||||
"cg": "🇨🇬", # Конго - Браззавиль
|
|
||||||
"ch": "🇨🇭", # Швейцария
|
|
||||||
"ci": "🇨🇮", # Кот-д’Ивуар
|
|
||||||
"ck": "🇨🇰", # Острова Кука
|
|
||||||
"cl": "🇨🇱", # Чили
|
|
||||||
"cm": "🇨🇲", # Камерун
|
|
||||||
"cn": "🇨🇳", # Китай
|
|
||||||
"co": "🇨🇴", # Колумбия
|
|
||||||
"cr": "🇨🇷", # Коста-Рика
|
|
||||||
"cu": "🇨🇺", # Куба
|
|
||||||
"cv": "🇨🇻", # Кабо-Верде
|
|
||||||
"cw": "🇨🇼", # Кюрасао
|
|
||||||
"cx": "🇨🇽", # остров Рождества
|
|
||||||
"cy": "🇨🇾", # Кипр
|
|
||||||
"cz": "🇨🇿", # Чехия
|
|
||||||
"de": "🇩🇪", # Германия
|
|
||||||
"dj": "🇩🇯", # Джибути
|
|
||||||
"dk": "🇩🇰", # Дания
|
|
||||||
"dm": "🇩🇲", # Доминика
|
|
||||||
"do": "🇩🇴", # Доминиканская Республика
|
|
||||||
"dz": "🇩🇿", # Алжир
|
|
||||||
"ec": "🇪🇨", # Эквадор
|
|
||||||
"ee": "🇪🇪", # Эстония
|
|
||||||
"eg": "🇪🇬", # Египет
|
|
||||||
"eh": "🇪🇭", # Западная Сахара
|
|
||||||
"er": "🇪🇷", # Эритрея
|
|
||||||
"es": "🇪🇸", # Испания
|
|
||||||
"et": "🇪🇹", # Эфиопия
|
|
||||||
"fi": "🇫🇮", # Финляндия
|
|
||||||
"fj": "🇫🇯", # Фиджи
|
|
||||||
"fk": "🇫🇰", # Фолклендские острова
|
|
||||||
"fm": "🇫🇲", # Микронезия
|
|
||||||
"fo": "🇫🇴", # Фарерские острова
|
|
||||||
"fr": "🇫🇷", # Франция
|
|
||||||
"ga": "🇬🇦", # Габон
|
|
||||||
"gb": "🇬🇧", # Великобритания
|
|
||||||
"gd": "🇬🇩", # Гренада
|
|
||||||
"ge": "🇬🇪", # Грузия
|
|
||||||
"gf": "🇬🇫", # Французская Гвиана
|
|
||||||
"gg": "🇬🇬", # Гернси
|
|
||||||
"gh": "🇬🇭", # Гана
|
|
||||||
"gi": "🇬🇮", # Гибралтар
|
|
||||||
"gl": "🇬🇱", # Гренландия
|
|
||||||
"gm": "🇬🇲", # Гамбия
|
|
||||||
"gn": "🇬🇳", # Гвинея
|
|
||||||
"gp": "🇬🇵", # Гваделупа
|
|
||||||
"gq": "🇬🇶", # Экваториальная Гвинея
|
|
||||||
"gr": "🇬🇷", # Греция
|
|
||||||
"gs": "🇬🇸", # Южная Георгия и Южные Сандвичевы острова
|
|
||||||
"gt": "🇬🇹", # Гватемала
|
|
||||||
"gu": "🇬🇺", # Гуам
|
|
||||||
"gw": "🇬🇼", # Гвинея-Бисау
|
|
||||||
"gy": "🇬🇾", # Гайана
|
|
||||||
"hk": "🇭🇰", # Гонконг
|
|
||||||
"hm": "🇭🇲", # остров Херд и острова Макдональд
|
|
||||||
"hn": "🇭🇳", # Гондурас
|
|
||||||
"hr": "🇭🇷", # Хорватия
|
|
||||||
"ht": "🇭🇹", # Гаити
|
|
||||||
"hu": "🇭🇺", # Венгрия
|
|
||||||
"id": "🇮🇩", # Индонезия
|
|
||||||
"ie": "🇮🇪", # Ирландия
|
|
||||||
"il": "🇮🇱", # Израиль
|
|
||||||
"im": "🇮🇲", # остров Мэн
|
|
||||||
"in": "🇮🇳", # Индия
|
|
||||||
"io": "🇮🇴", # Британская территория в Индийском океане
|
|
||||||
"iq": "🇮🇶", # Ирак
|
|
||||||
"ir": "🇮🇷", # Иран
|
|
||||||
"is": "🇮🇸", # Исландия
|
|
||||||
"it": "🇮🇹", # Италия
|
|
||||||
"je": "🇯🇪", # Джерси
|
|
||||||
"jm": "🇯🇲", # Ямайка
|
|
||||||
"jo": "🇯🇴", # Иордания
|
|
||||||
"jp": "🇯🇵", # Япония
|
|
||||||
"ke": "🇰🇪", # Кения
|
|
||||||
"kg": "🇰🇬", # Киргизия
|
|
||||||
"kh": "🇰🇭", # Камбоджа
|
|
||||||
"ki": "🇰🇮", # Кирибати
|
|
||||||
"km": "🇰🇲", # Коморы
|
|
||||||
"kn": "🇰🇳", # Сент-Китс и Невис
|
|
||||||
"kp": "🇰🇵", # Корейская Народно-Демократическая Республика
|
|
||||||
"kr": "🇰🇷", # Республика Корея
|
|
||||||
"kw": "🇰🇼", # Кувейт
|
|
||||||
"ky": "🇰🇾", # Каймановы острова
|
|
||||||
"kz": "🇰🇿", # Казахстан
|
|
||||||
"la": "🇱🇦", # Лаос
|
|
||||||
"lb": "🇱🇧", # Ливан
|
|
||||||
"lc": "🇱🇨", # Сент-Люсия
|
|
||||||
"li": "🇱🇮", # Лихтенштейн
|
|
||||||
"lk": "🇱🇰", # Шри-Ланка
|
|
||||||
"lr": "🇱🇷", # Либерия
|
|
||||||
"ls": "🇱🇸", # Лесото
|
|
||||||
"lt": "🇱🇹", # Литва
|
|
||||||
"lu": "🇱🇺", # Люксембург
|
|
||||||
"lv": "🇱🇻", # Латвия
|
|
||||||
"ly": "🇱🇾", # Ливия
|
|
||||||
"my": "🇲🇾",
|
|
||||||
"md": "🇲🇩",
|
|
||||||
"mv": "🇲🇻",
|
|
||||||
"mw": "🇲🇼",
|
|
||||||
"mx": "🇲🇽",
|
|
||||||
"my": "🇲🇾",
|
|
||||||
"mz": "🇲🇿",
|
|
||||||
"na": "🇳🇦",
|
|
||||||
"nc": "🇳🇨",
|
|
||||||
"ne": "🇳🇪",
|
|
||||||
"nf": "🇳🇫",
|
|
||||||
"ng": "🇳🇬",
|
|
||||||
"ni": "🇳🇮",
|
|
||||||
"nl": "🇳🇱",
|
|
||||||
"no": "🇳🇴",
|
|
||||||
"np": "🇳🇵",
|
|
||||||
"nr": "🇳🇷",
|
|
||||||
"nu": "🇳🇺",
|
|
||||||
"nz": "🇳🇿",
|
|
||||||
"om": "🇴🇲",
|
|
||||||
"pa": "🇵🇦",
|
|
||||||
"pe": "🇵🇪",
|
|
||||||
"pf": "🇵🇫",
|
|
||||||
"pg": "🇵🇬",
|
|
||||||
"ph": "🇵🇭",
|
|
||||||
"pk": "🇵🇰",
|
|
||||||
"pl": "🇵🇱",
|
|
||||||
"pm": "🇵🇲",
|
|
||||||
"pn": "🇵🇳",
|
|
||||||
"pr": "🇵🇷",
|
|
||||||
"ps": "🇵🇸",
|
|
||||||
"pt": "🇵🇹",
|
|
||||||
"pw": "🇵🇼",
|
|
||||||
"py": "🇵🇾",
|
|
||||||
"qa": "🇶🇦",
|
|
||||||
"re": "🇷🇪",
|
|
||||||
"ro": "🇷🇴",
|
|
||||||
"rs": "🇷🇸",
|
|
||||||
"ru": "🇷🇺",
|
|
||||||
"rw": "🇷🇼",
|
|
||||||
"sa": "🇸🇦",
|
|
||||||
"sb": "🇸🇧",
|
|
||||||
"sc": "🇸🇨",
|
|
||||||
"sd": "🇸🇩",
|
|
||||||
"se": "🇸🇪",
|
|
||||||
"sg": "🇸🇬",
|
|
||||||
"sh": "🇸🇭",
|
|
||||||
"si": "🇸🇮",
|
|
||||||
"sj": "🇸🇯",
|
|
||||||
"sk": "🇸🇰",
|
|
||||||
"sl": "🇸🇱",
|
|
||||||
"sm": "🇸🇲",
|
|
||||||
"sn": "🇸🇳",
|
|
||||||
"so": "🇸🇴",
|
|
||||||
"sr": "🇸🇷",
|
|
||||||
"ss": "🇸🇸",
|
|
||||||
"st": "🇸🇹",
|
|
||||||
"sv": "🇸🇻",
|
|
||||||
"sx": "🇸🇽",
|
|
||||||
"sy": "🇸🇾",
|
|
||||||
"sz": "🇸🇿",
|
|
||||||
"tc": "🇹🇨",
|
|
||||||
"td": "🇹🇩",
|
|
||||||
"tf": "🇹🇫",
|
|
||||||
"tg": "🇹🇬",
|
|
||||||
"th": "🇹🇭",
|
|
||||||
"tj": "🇹🇯",
|
|
||||||
"tk": "🇹🇰",
|
|
||||||
"tl": "🇹🇱",
|
|
||||||
"tm": "🇹🇲",
|
|
||||||
"tn": "🇹🇳",
|
|
||||||
"to": "🇹🇴",
|
|
||||||
"tr": "🇹🇷",
|
|
||||||
"tt": "🇹🇹",
|
|
||||||
"tv": "🇹🇻",
|
|
||||||
"tw": "🇹🇼",
|
|
||||||
"tz": "🇹🇿",
|
|
||||||
"ua": "🇺🇦",
|
|
||||||
"ug": "🇺🇬",
|
|
||||||
"um": "🇺🇲",
|
|
||||||
"us": "🇺🇸",
|
|
||||||
"va": "🇻🇦",
|
|
||||||
"vc": "🇻🇨",
|
|
||||||
"ve": "🇻🇪",
|
|
||||||
"vg": "🇻🇬",
|
|
||||||
"vi": "🇻🇮",
|
|
||||||
"vn": "🇻🇳",
|
|
||||||
"vu": "🇻🇺",
|
|
||||||
"wf": "🇼🇫",
|
|
||||||
"ws": "🇼🇸",
|
|
||||||
"xk": "🇽🇰",
|
|
||||||
"ye": "🇾🇪",
|
|
||||||
"yt": "🇾🇹",
|
|
||||||
"za": "🇿🇦",
|
|
||||||
"zm": "🇿🇲",
|
|
||||||
"zw": "🇿🇼",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class Error(enum.Enum):
|
class Error(enum.Enum):
|
||||||
critical = 500
|
critical = 500
|
||||||
not_found = 404
|
not_found = 404
|
||||||
@@ -381,7 +146,9 @@ class HostAPI(API):
|
|||||||
) -> Dict:
|
) -> Dict:
|
||||||
route = f"{self._url}/{tg_id}/logs/{lines}"
|
route = f"{self._url}/{tg_id}/logs/{lines}"
|
||||||
return await self._request(route, method="GET", headers=self.auth_header)
|
return await self._request(route, method="GET", headers=self.auth_header)
|
||||||
|
|
||||||
|
def get_flag(country_code: str = None) -> str:
|
||||||
|
return ''.join( chr(ord(c.upper()) + 127397) for c in country_code)
|
||||||
|
|
||||||
@loader.tds
|
@loader.tds
|
||||||
class HHMod(loader.Module):
|
class HHMod(loader.Module):
|
||||||
@@ -389,16 +156,19 @@ class HHMod(loader.Module):
|
|||||||
|
|
||||||
strings = {
|
strings = {
|
||||||
"name": "HH",
|
"name": "HH",
|
||||||
"info": (
|
|
||||||
|
"_cfg_doc_hinfo_message": "Custom message text in hinfo. May contain keywords: {id}, {status}, {server}, {days_end}, {cpu_percent}, {ram_usage}, {warns}.",
|
||||||
|
"_cfg_doc_hinfo_banner_url": "Link to banner image or None.",
|
||||||
|
|
||||||
|
"default_info": (
|
||||||
"<emoji document_id=5413334818047940135>👤</emoji> <b>Info for</b> <code>{id}</code>\n\n"
|
"<emoji document_id=5413334818047940135>👤</emoji> <b>Info for</b> <code>{id}</code>\n\n"
|
||||||
"<emoji document_id=5418136591484865679>📶</emoji> <b>Status:</b> {status}\n"
|
"<emoji document_id=5418136591484865679>📶</emoji> <b>Status:</b> {status}\n"
|
||||||
"<emoji document_id=5415992848753379520>⚙️</emoji> <b>Server:</b> {server}\n"
|
"<emoji document_id=5415992848753379520>⚙️</emoji> <b>Server:</b> {server}\n"
|
||||||
"<emoji document_id=5416042764863293485>❤️</emoji> <b>The subscription expires after</b> <code>{days_end} days</code>\n"
|
"<emoji document_id=5416042764863293485>❤️</emoji> <b>The subscription expires after</b> <code>{days_end} days</code>\n"
|
||||||
"{stats}\n"
|
"<emoji document_id=5413394354884596702>💾</emoji> <b>Used now:</b> <code>{cpu_percent}%</code> CPU, <code>{ram_usage}MB</code> RAM\n\n"
|
||||||
"{warns}"
|
"{warns}"
|
||||||
),
|
),
|
||||||
"logs": "<emoji document_id=5411608069396254249>📄</emoji> All docker container logs from the userbot\n\n<i>In t.me/hikkahost_bot/hhapp logs more readable</i>",
|
"logs": "<emoji document_id=5411608069396254249>📄</emoji> All docker container logs from the userbot\n\n<i>In t.me/hikkahost_bot/hhapp logs more readable</i>",
|
||||||
"stats": "<emoji document_id=5413394354884596702>💾</emoji> <b>Used now:</b> <code>{cpu_percent}%</code> CPU, <code>{memory}MB</code> RAM\n",
|
|
||||||
"loading_info": "<emoji document_id=5416094132672156295>⌛️</emoji> Loading...",
|
"loading_info": "<emoji document_id=5416094132672156295>⌛️</emoji> Loading...",
|
||||||
"no_apikey": "<emoji document_id=5411402525146370107>🚫</emoji> Not specified API Key, need get token:\n\n1. Go to the @hikkahost_bot\n2. Send /token\n3. Paste token to .config HH",
|
"no_apikey": "<emoji document_id=5411402525146370107>🚫</emoji> Not specified API Key, need get token:\n\n1. Go to the @hikkahost_bot\n2. Send /token\n3. Paste token to .config HH",
|
||||||
"warn_sub_left": "<emoji document_id=5411402525146370107>🚫</emoji> <i>There are less than 5 days left until the end of the subscription</i>\n",
|
"warn_sub_left": "<emoji document_id=5411402525146370107>🚫</emoji> <i>There are less than 5 days left until the end of the subscription</i>\n",
|
||||||
@@ -413,16 +183,15 @@ class HHMod(loader.Module):
|
|||||||
|
|
||||||
strings_ru = {
|
strings_ru = {
|
||||||
"name": "HH",
|
"name": "HH",
|
||||||
"info": (
|
# "default_info": (
|
||||||
"<emoji document_id=5413334818047940135>👤</emoji> <b>Информация о</b> <code>{id}</code>\n\n"
|
# "<emoji document_id=5413334818047940135>👤</emoji> <b>Информация о</b> <code>{id}</code>\n\n"
|
||||||
"<emoji document_id=5418136591484865679>📶</emoji> <b>Статус:</b> {status}\n"
|
# "<emoji document_id=5418136591484865679>📶</emoji> <b>Статус:</b> {status}\n"
|
||||||
"<emoji document_id=5415992848753379520>⚙️</emoji> <b>Сервер:</b> {server}\n"
|
# "<emoji document_id=5415992848753379520>⚙️</emoji> <b>Сервер:</b> {server}\n"
|
||||||
"<emoji document_id=5416042764863293485>❤️</emoji> <b>Подписка истечёт через</b> <code>{days_end} дней</code>\n"
|
# "<emoji document_id=5416042764863293485>❤️</emoji> <b>Подписка истечёт через</b> <code>{days_end} дней</code>\n"
|
||||||
"{stats}\n"
|
# "<emoji document_id=5413394354884596702>💾</emoji> <b>Используется:</b> <code>{cpu_percent}%</code> CPU, <code>{ram_usage}MB</code> RAM\n\n"
|
||||||
"{warns}"
|
# "{warns}"
|
||||||
),
|
# ),
|
||||||
"logs": "<emoji document_id=5411608069396254249>📄</emoji> Все логи docker контейнера от hikka\n\n<i>В t.me/hikkahost_bot/hhapp логи более читабельны</i>",
|
"logs": "<emoji document_id=5411608069396254249>📄</emoji> Все логи docker контейнера от hikka\n\n<i>В t.me/hikkahost_bot/hhapp логи более читабельны</i>",
|
||||||
"stats": "<emoji document_id=5413394354884596702>💾</emoji> <b>Используется:</b> <code>{cpu_percent}%</code> CPU, <code>{memory}MB</code> RAM\n",
|
|
||||||
"loading_info": "<emoji document_id=5416094132672156295>⌛️</emoji> Загрузка...",
|
"loading_info": "<emoji document_id=5416094132672156295>⌛️</emoji> Загрузка...",
|
||||||
"no_apikey": "<emoji document_id=5411402525146370107>🚫</emoji> Не задан ключ API, нужно получить токен:\n\n1. Зайдите в @hikkahost_bot\n2. Отправьте /token\n3. Запишите токен в .config HH",
|
"no_apikey": "<emoji document_id=5411402525146370107>🚫</emoji> Не задан ключ API, нужно получить токен:\n\n1. Зайдите в @hikkahost_bot\n2. Отправьте /token\n3. Запишите токен в .config HH",
|
||||||
"warn_sub_left": "<emoji document_id=5411402525146370107>🚫</emoji> <i>Менее чем через 5 дней подписка истечёт</i>\n",
|
"warn_sub_left": "<emoji document_id=5411402525146370107>🚫</emoji> <i>Менее чем через 5 дней подписка истечёт</i>\n",
|
||||||
@@ -443,6 +212,16 @@ class HHMod(loader.Module):
|
|||||||
None,
|
None,
|
||||||
validator=loader.validators.Hidden(),
|
validator=loader.validators.Hidden(),
|
||||||
),
|
),
|
||||||
|
loader.ConfigValue(
|
||||||
|
"hinfo_message",
|
||||||
|
self.strings["default_info"],
|
||||||
|
self.strings['_cfg_doc_hinfo_message']
|
||||||
|
),
|
||||||
|
loader.ConfigValue(
|
||||||
|
"hinfo_banner_url",
|
||||||
|
"https://github.com/hikkahost/.github/blob/main/banners/main.jpg?raw=true",
|
||||||
|
self.strings['_cfg_doc_hinfo_banner_url']
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
async def client_ready(self, client, db):
|
async def client_ready(self, client, db):
|
||||||
@@ -459,7 +238,7 @@ class HHMod(loader.Module):
|
|||||||
self._db = db
|
self._db = db
|
||||||
self.me = await client.get_me()
|
self.me = await client.get_me()
|
||||||
self.bot = "@hikkahost_bot"
|
self.bot = "@hikkahost_bot"
|
||||||
|
|
||||||
@loader.command(
|
@loader.command(
|
||||||
en_doc=" - ub status",
|
en_doc=" - ub status",
|
||||||
)
|
)
|
||||||
@@ -485,19 +264,17 @@ class HHMod(loader.Module):
|
|||||||
stats = (await api.get_stats(user_id))["stats"]
|
stats = (await api.get_stats(user_id))["stats"]
|
||||||
working = True if status["status"] == "running" else False
|
working = True if status["status"] == "running" else False
|
||||||
|
|
||||||
|
load = {}
|
||||||
|
|
||||||
if working:
|
if working:
|
||||||
cpu_stats = stats["cpu_stats"]
|
cpu_stats = stats["cpu_stats"]
|
||||||
cpu_total_usage = cpu_stats['cpu_usage']['total_usage']
|
cpu_total_usage = cpu_stats['cpu_usage']['total_usage']
|
||||||
system_cpu_usage = cpu_stats['system_cpu_usage']
|
system_cpu_usage = cpu_stats['system_cpu_usage']
|
||||||
|
|
||||||
ram_usage = round(stats["memory_stats"]["usage"] / (1024 * 1024), 2)
|
load = {
|
||||||
cpu_percent = round((cpu_total_usage / system_cpu_usage) * 100.0, 2)
|
"ram_usage": round(stats["memory_stats"]["usage"] / (1024 * 1024), 2),
|
||||||
|
"cpu_percent": round((cpu_total_usage / system_cpu_usage) * 100.0, 2)
|
||||||
stats = self.strings["stats"].format(
|
}
|
||||||
cpu_percent=cpu_percent, memory=ram_usage
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
stats = ""
|
|
||||||
|
|
||||||
end_date = host.end_date.replace(tzinfo=timezone.utc)
|
end_date = host.end_date.replace(tzinfo=timezone.utc)
|
||||||
warns = ""
|
warns = ""
|
||||||
@@ -510,20 +287,22 @@ class HHMod(loader.Module):
|
|||||||
|
|
||||||
server = servers_dict.get(host.server_id)
|
server = servers_dict.get(host.server_id)
|
||||||
server = self.strings["server"].format(
|
server = self.strings["server"].format(
|
||||||
flag=FLAGS[server["country_code"]],
|
flag=get_flag(server["country_code"]),
|
||||||
name=server["name"],
|
name=server["name"],
|
||||||
)
|
)
|
||||||
|
|
||||||
await utils.answer(
|
await utils.answer(
|
||||||
message,
|
message,
|
||||||
self.strings["info"].format(
|
self.config["hinfo_message"].format(
|
||||||
id=user_id,
|
id=user_id,
|
||||||
warns=warns,
|
warns=warns,
|
||||||
stats=stats,
|
|
||||||
server=server,
|
server=server,
|
||||||
days_end=days_end,
|
days_end=days_end,
|
||||||
status=self.strings["statuses"][status["status"]],
|
status=self.strings["statuses"][status["status"]],
|
||||||
|
ram_usage=load.get("ram_usage", "0.00"),
|
||||||
|
cpu_percent=load.get("cpu_percent", "0.00")
|
||||||
),
|
),
|
||||||
|
file=self.config['hinfo_banner_url']
|
||||||
)
|
)
|
||||||
|
|
||||||
@loader.command(
|
@loader.command(
|
||||||
|
|||||||