Merge pull request #146 from MuRuLOSE/update-submodules_4f81ff0547062cbdb7b83bce694b3c2120e21bff
Update of repositories 2026-01-06 01:12:33
@@ -23,10 +23,11 @@
|
||||
# meta pic: https://i.postimg.cc/Hx3Zm8rB/logo.png
|
||||
# meta banner: https://te.legra.ph/file/55fa6eebae860a359ac27.jpg
|
||||
|
||||
__version__ = (1, 2, 0)
|
||||
__version__ = (1, 3, 2)
|
||||
|
||||
from .. import loader, utils
|
||||
from telethon.tl.types import Message # type: ignore
|
||||
from .. import loader, utils # type: ignore
|
||||
from telethon.tl import types # type: ignore
|
||||
from telethon.tl.types import Message, InputDocument # type: ignore
|
||||
|
||||
@loader.tds
|
||||
class SendMod(loader.Module):
|
||||
@@ -104,19 +105,68 @@ class SendMod(loader.Module):
|
||||
|
||||
|
||||
@loader.command(
|
||||
ru_doc="[text] - Написать сообщение в закрытую тему",
|
||||
uz_doc="[text] - Yopiq mavzuga xabar yozing",
|
||||
de_doc="[text] - Schreiben Sie eine Nachricht zu einem geschlossenen Thema",
|
||||
es_doc="[text] - Escribir un mensaje a un tema cerrado",
|
||||
ru_doc="[text or reply(media/file/sticker) or coordinates (<lat>, <long>)] - Написать сообщение в закрытую тему",
|
||||
uz_doc="[text or reply(media/file/sticker) or coordinates (<lat>, <long>)] - Yopiq mavzuga xabar yozing",
|
||||
de_doc="[text or reply(media/file/sticker) or coordinates (<lat>, <long>)] - Schreiben Sie eine Nachricht zu einem geschlossenen Thema",
|
||||
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):
|
||||
"""[text] - Write a message to a closed topic"""
|
||||
if not utils.get_args_raw(message):
|
||||
await utils.answer(message, self.strings["error_send"])
|
||||
async def sendclosedtopic(self, message: Message):
|
||||
"""[text or reply(media/file/sticker) or coordinates (<lat>, <long>)] - Write a message to a closed topic"""
|
||||
args = utils.get_args_raw(message)
|
||||
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:
|
||||
text2 = f"{utils.get_args_raw(message)}"
|
||||
await message.delete()
|
||||
await message.reply(text2)
|
||||
media = message.media
|
||||
|
||||
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(
|
||||
ru_doc="[@UserName] [text or replay] - Написать сообщение в личные сообщения",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
__version__ = (1, 1, 0)
|
||||
version = (1, 4, 0)
|
||||
|
||||
# meta developer: @RUIS_VlP, @RoKrz
|
||||
# requires: yt_dlp
|
||||
@@ -7,51 +7,190 @@ import yt_dlp
|
||||
import uuid
|
||||
import os
|
||||
import re
|
||||
import tempfile
|
||||
from .. import loader, utils
|
||||
|
||||
|
||||
def extract_youtube_link(text):
|
||||
def extract_video_link(text):
|
||||
"""Извлекает видео с сайтов"""
|
||||
if not text:
|
||||
return None
|
||||
match = re.search(r"(https?://)?(www\.)?(youtube\.com|youtu\.be)/[^\s]+", text)
|
||||
return match.group(0) if match else None
|
||||
video_sites_patterns = [
|
||||
# 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()
|
||||
random_uuid = str(uuid.uuid4())
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
ydl_opts = {
|
||||
'format': 'best',
|
||||
'outtmpl': os.path.join(output_dir, f'{random_uuid}.%(ext)s'),
|
||||
'noplaylist': True,
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
return file_path, title
|
||||
formats_to_try = [
|
||||
'best[ext=mp4]',
|
||||
'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]',
|
||||
'bestvideo+bestaudio/best',
|
||||
'best',
|
||||
'best*',
|
||||
'bestvideo+bestaudio',
|
||||
'best[height<=1080]',
|
||||
'best[height<=720]',
|
||||
'worst',
|
||||
'worst*',
|
||||
]
|
||||
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:
|
||||
return re.sub(r'\[([^\]]+)\]\(([^)]+)\)', r'<a href="\2">\1</a>', template).replace("{link}", link)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class YouTube_DLDMod(loader.Module):
|
||||
"""Помогает скачивать видео с YouTube"""
|
||||
|
||||
"""Помогает скачивать видео с YouTube, TikTok и др."""
|
||||
strings = {
|
||||
"name": "YouTube-DLD",
|
||||
"no_link": "❌ <b>Пожалуйста, укажите ссылку на YouTube либо ответьте на сообщение с ней.</b>",
|
||||
"no_link": "❌ <b>Пожалуйста, укажите ссылку на видео либо ответьте на сообщение с ней.</b>",
|
||||
"default_downloading": "📥 <b>Начинаю загрузку видео.</b>\n\nℹ️ <code>Это может занять до 5 минут, в зависимости от длины и качества видео.</code>",
|
||||
"default_error": "❌ <b>Ошибка!</b>\n\n<code>{}</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):
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
@@ -75,32 +214,84 @@ class YouTube_DLDMod(loader.Module):
|
||||
self.strings["default_response"],
|
||||
"Ответ после загрузки. (используй {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()
|
||||
async def dlvideo(self, message):
|
||||
"""<ссылка> или ответ на сообщение со ссылкой — скачивает видео с YouTube"""
|
||||
async def dvlist(self, message):
|
||||
"""Показать список всех поддерживаемых сайтов"""
|
||||
await utils.answer(message, self.strings["supported_sites"])
|
||||
|
||||
@loader.command()
|
||||
async def dlvideo(self, message):
|
||||
"""<ссылка> или ответ на сообщение со ссылкой — скачивает видео с поддерживаемых платформ"""
|
||||
args = utils.get_args_raw(message)
|
||||
reply = await message.get_reply_message()
|
||||
|
||||
link = extract_youtube_link(args) if args else None
|
||||
link = extract_video_link(args) if args else None
|
||||
if not link and reply:
|
||||
link = extract_youtube_link(reply.raw_text)
|
||||
|
||||
link = extract_video_link(reply.raw_text)
|
||||
if not link:
|
||||
await utils.answer(message, self.strings["no_link"])
|
||||
return
|
||||
|
||||
await utils.answer(message, self.config["downloading_text"])
|
||||
|
||||
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"]:
|
||||
caption_template = self.config["response_text"]
|
||||
caption = convert_markdown_to_html(caption_template, link)
|
||||
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:
|
||||
caption = title or "Готово!"
|
||||
|
||||
@@ -108,9 +299,10 @@ class YouTube_DLDMod(loader.Module):
|
||||
message,
|
||||
video,
|
||||
caption=caption,
|
||||
parse_mode="HTML"
|
||||
parse_mode="HTML",
|
||||
reply_to=reply or message,
|
||||
silent=True
|
||||
)
|
||||
|
||||
try:
|
||||
await message.delete()
|
||||
except:
|
||||
@@ -120,9 +312,14 @@ class YouTube_DLDMod(loader.Module):
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
error_msg = self.config["error_text"].format(e)
|
||||
await utils.answer(message, error_msg)
|
||||
error_str = str(e)
|
||||
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:
|
||||
os.remove(video)
|
||||
if 'video' in locals():
|
||||
os.remove(video)
|
||||
except:
|
||||
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
|
||||
send
|
||||
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
|
||||
picme
|
||||
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
|
||||
tagall2.0
|
||||
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()
|
||||
451
modules.json
@@ -12209,6 +12209,43 @@
|
||||
"Tools"
|
||||
]
|
||||
},
|
||||
"fajox1/famods/ptichki.py": {
|
||||
"name": "Ptichki",
|
||||
"description": "Генератор птиц",
|
||||
"meta": {
|
||||
"pic": null,
|
||||
"banner": "https://github.com/FajoX1/FAmods/blob/main/assets/banners/ptichki.png?raw=true",
|
||||
"developer": "@FAmods"
|
||||
},
|
||||
"commands": [
|
||||
{
|
||||
"ptichka": "[текст] - Сгенерировать стикер с птицей"
|
||||
},
|
||||
{
|
||||
"ptichka_img": "[текст] - Сгенерировать фото с птицей"
|
||||
}
|
||||
],
|
||||
"new_commands": [
|
||||
{
|
||||
"ptichka": {
|
||||
"ru_doc": null,
|
||||
"en_doc": null,
|
||||
"doc": "[текст] - Сгенерировать стикер с птицей"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ptichka_img": {
|
||||
"ru_doc": null,
|
||||
"en_doc": null,
|
||||
"doc": "[текст] - Сгенерировать фото с птицей"
|
||||
}
|
||||
}
|
||||
],
|
||||
"category": [
|
||||
"Tools",
|
||||
"Chat"
|
||||
]
|
||||
},
|
||||
"fajox1/famods/fun.py": {
|
||||
"name": "Fun",
|
||||
"description": "Module for fun...",
|
||||
@@ -16278,6 +16315,33 @@
|
||||
"Tools"
|
||||
]
|
||||
},
|
||||
"fiksofficial/python-modules/mpi.py": {
|
||||
"name": "MinecraftPlayerInfo",
|
||||
"description": "A module for obtaining information about a Minecraft player by nickname",
|
||||
"meta": {
|
||||
"pic": null,
|
||||
"banner": null,
|
||||
"developer": "@pymodule"
|
||||
},
|
||||
"commands": [
|
||||
{
|
||||
"mcplayer": "<nickname> — show Minecraft player info (3D renders, history, cape) <никнейм> — отображает информацию об игроке Minecraft (3D-рендеринг, история, плащ)"
|
||||
}
|
||||
],
|
||||
"new_commands": [
|
||||
{
|
||||
"mcplayer": {
|
||||
"ru_doc": "<никнейм> — отображает информацию об игроке Minecraft (3D-рендеринг, история, плащ)",
|
||||
"en_doc": null,
|
||||
"doc": "<nickname> — show Minecraft player info (3D renders, history, cape)"
|
||||
}
|
||||
}
|
||||
],
|
||||
"category": [
|
||||
"Tools",
|
||||
"Admin"
|
||||
]
|
||||
},
|
||||
"fiksofficial/python-modules/tagall2.0.py": {
|
||||
"name": "TagAllMod",
|
||||
"description": "TagAll 2.0 — smart mention of chat participants: .tagall {all/admins/online/active} {text}",
|
||||
@@ -17009,7 +17073,7 @@
|
||||
},
|
||||
"Ruslan-Isaev/modules/youtube-loader.py": {
|
||||
"name": "YouTube_DLDMod",
|
||||
"description": "Помогает скачивать видео с YouTube",
|
||||
"description": "Помогает скачивать видео с YouTube, TikTok и др.",
|
||||
"meta": {
|
||||
"pic": null,
|
||||
"banner": null,
|
||||
@@ -17017,15 +17081,25 @@
|
||||
},
|
||||
"commands": [
|
||||
{
|
||||
"dlvideo": "<ссылка> или ответ на сообщение со ссылкой — скачивает видео с YouTube"
|
||||
"dvlist": "Показать список всех поддерживаемых сайтов"
|
||||
},
|
||||
{
|
||||
"dlvideo": "<ссылка> или ответ на сообщение со ссылкой — скачивает видео с поддерживаемых платформ"
|
||||
}
|
||||
],
|
||||
"new_commands": [
|
||||
{
|
||||
"dvlist": {
|
||||
"ru_doc": null,
|
||||
"en_doc": null,
|
||||
"doc": "Показать список всех поддерживаемых сайтов"
|
||||
}
|
||||
},
|
||||
{
|
||||
"dlvideo": {
|
||||
"ru_doc": null,
|
||||
"en_doc": null,
|
||||
"doc": "<ссылка> или ответ на сообщение со ссылкой — скачивает видео с YouTube"
|
||||
"doc": "<ссылка> или ответ на сообщение со ссылкой — скачивает видео с поддерживаемых платформ"
|
||||
}
|
||||
}
|
||||
],
|
||||
@@ -22136,7 +22210,7 @@
|
||||
"send": "[text] - Write a message [text] - Написать сообщение"
|
||||
},
|
||||
{
|
||||
"sendclosedtopic": "[text] - Write a message to a closed topic [text] - Написать сообщение в закрытую тему"
|
||||
"sendclosedtopic": "[text or reply(media/file/sticker) or coordinates (<lat>, <long>)] - Write a message to a closed topic [text or reply(media/file/sticker) or coordinates (<lat>, <long>)] - Написать сообщение в закрытую тему"
|
||||
},
|
||||
{
|
||||
"sendpm": "[@UserName] [text or replay] - Write a message to personal messages [@UserName] [text or replay] - Написать сообщение в личные сообщения"
|
||||
@@ -22155,9 +22229,9 @@
|
||||
},
|
||||
{
|
||||
"sendclosedtopic": {
|
||||
"ru_doc": "[text] - Написать сообщение в закрытую тему",
|
||||
"ru_doc": "[text or reply(media/file/sticker) or coordinates (<lat>, <long>)] - Написать сообщение в закрытую тему",
|
||||
"en_doc": null,
|
||||
"doc": "[text] - Write a message to a closed topic"
|
||||
"doc": "[text or reply(media/file/sticker) or coordinates (<lat>, <long>)] - Write a message to a closed topic"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -39244,6 +39318,33 @@
|
||||
"Tools"
|
||||
]
|
||||
},
|
||||
"coddrago/modules/hardspam.py": {
|
||||
"name": "HardSpam",
|
||||
"description": null,
|
||||
"meta": {
|
||||
"pic": null,
|
||||
"banner": null,
|
||||
"developer": "@codrago_m"
|
||||
},
|
||||
"commands": [
|
||||
{
|
||||
"hspamcmd": "[-c|--clean] n text - Send n number of messages at the same time [-c|--clean] n text - Отправить n кол-во сообщений одновременно"
|
||||
}
|
||||
],
|
||||
"new_commands": [
|
||||
{
|
||||
"hspam": {
|
||||
"ru_doc": "[-c|--clean] n text - Отправить n кол-во сообщений одновременно",
|
||||
"en_doc": null,
|
||||
"doc": "[-c|--clean] n text - Send n number of messages at the same time"
|
||||
}
|
||||
}
|
||||
],
|
||||
"category": [
|
||||
"Tools",
|
||||
"Chat"
|
||||
]
|
||||
},
|
||||
"coddrago/modules/DoxTool.py": {
|
||||
"name": "dox",
|
||||
"description": "Maybe... doxing tool?",
|
||||
@@ -39446,6 +39547,33 @@
|
||||
"Tools"
|
||||
]
|
||||
},
|
||||
"coddrago/modules/stats.py": {
|
||||
"name": "Stats",
|
||||
"description": "Показывает статистику твоего аккаунта",
|
||||
"meta": {
|
||||
"pic": null,
|
||||
"banner": null,
|
||||
"developer": "@codrago_m"
|
||||
},
|
||||
"commands": [
|
||||
{
|
||||
"stats": "Получить статистику"
|
||||
}
|
||||
],
|
||||
"new_commands": [
|
||||
{
|
||||
"stats": {
|
||||
"ru_doc": null,
|
||||
"en_doc": null,
|
||||
"doc": "Получить статистику"
|
||||
}
|
||||
}
|
||||
],
|
||||
"category": [
|
||||
"Tools",
|
||||
"Fun"
|
||||
]
|
||||
},
|
||||
"coddrago/modules/modlist.py": {
|
||||
"name": "ModulesList",
|
||||
"description": "Модуль для быстрого доступа к каналам с модулями",
|
||||
@@ -39493,6 +39621,33 @@
|
||||
"Tools"
|
||||
]
|
||||
},
|
||||
"coddrago/modules/tagwatcher.py": {
|
||||
"name": "TagWatcher",
|
||||
"description": "Informs when you are tagged in chats and automatically reads pm.",
|
||||
"meta": {
|
||||
"pic": null,
|
||||
"banner": null,
|
||||
"developer": "@codrago_m"
|
||||
},
|
||||
"commands": [
|
||||
{
|
||||
"tagwatcher": "Enable/Disable TagWatcher. Вкл/выкл TagWatcher."
|
||||
}
|
||||
],
|
||||
"new_commands": [
|
||||
{
|
||||
"tagwatcher": {
|
||||
"ru_doc": "Вкл/выкл TagWatcher.",
|
||||
"en_doc": null,
|
||||
"doc": "Enable/Disable TagWatcher."
|
||||
}
|
||||
}
|
||||
],
|
||||
"category": [
|
||||
"Automation",
|
||||
"Chat"
|
||||
]
|
||||
},
|
||||
"coddrago/modules/id.py": {
|
||||
"name": "ID",
|
||||
"description": "ID of all!",
|
||||
@@ -39567,6 +39722,290 @@
|
||||
"Media"
|
||||
]
|
||||
},
|
||||
"coddrago/modules/dbmod.py": {
|
||||
"name": "DBMod",
|
||||
"description": null,
|
||||
"meta": {
|
||||
"pic": null,
|
||||
"banner": null,
|
||||
"developer": "@codrago_m"
|
||||
},
|
||||
"commands": [
|
||||
{
|
||||
"mydb": "Viewing the database Просмотр базы данных"
|
||||
}
|
||||
],
|
||||
"new_commands": [
|
||||
{
|
||||
"mydb": {
|
||||
"ru_doc": "Просмотр базы данных",
|
||||
"en_doc": null,
|
||||
"doc": "Viewing the database"
|
||||
}
|
||||
}
|
||||
],
|
||||
"category": [
|
||||
"Tools",
|
||||
"Info"
|
||||
]
|
||||
},
|
||||
"coddrago/modules/chatmodule.py": {
|
||||
"name": "ChatModuleMod",
|
||||
"description": null,
|
||||
"meta": {
|
||||
"pic": null,
|
||||
"banner": null,
|
||||
"developer": "@codrago_m"
|
||||
},
|
||||
"commands": [
|
||||
{
|
||||
"id": "[reply] - Get the ID [reply] - Узнать ID"
|
||||
},
|
||||
{
|
||||
"rights": "[reply/-u username/id] - Check user's admin rights [reply/-u username/id] - Посмотреть права администратора пользователя"
|
||||
},
|
||||
{
|
||||
"leave": "Leave chat Покинуть чат"
|
||||
},
|
||||
{
|
||||
"d": "[a[1-100] b[1-100]] | [reply] - Delete messages [a[1-100] b[1-100]] | [reply] Удалить сообщения"
|
||||
},
|
||||
{
|
||||
"pin": "[reply] - Pin a message [reply] - Закрепить сообщение"
|
||||
},
|
||||
{
|
||||
"unpin": "Unpin a message Открепить сообщение"
|
||||
},
|
||||
{
|
||||
"dgc": "[-c id] Delete chat/channel [-c id] Удаляет группу/канал"
|
||||
},
|
||||
{
|
||||
"flush": "Removes deleted accounts from the chat/channel Очищает группу/канал от удаленных аккаунтов"
|
||||
},
|
||||
{
|
||||
"admins": "Shows the admins in the chat/channel Показывает админов в группе/канале"
|
||||
},
|
||||
{
|
||||
"bots": "Shows the bots in the chat/channel Показывает ботов в группе/канале"
|
||||
},
|
||||
{
|
||||
"users": "Shows the users in the chat/channel Показывает простых участников чата/канала"
|
||||
},
|
||||
{
|
||||
"ban": "[-u] [-t] [-r] Ban a participant temporarily or permanently [-u] [-t] [-r] Забанить участника"
|
||||
},
|
||||
{
|
||||
"unban": "[-u] Unban a user Разбанить пользователя"
|
||||
},
|
||||
{
|
||||
"kick": "[-u] [-r] Kick a participant [-u] [-r] Кикнуть участника"
|
||||
},
|
||||
{
|
||||
"mute": "[-u] [-t] [-r] Mute a participant temporarily or permanently [-u] [-t] [-r] Замутить участника"
|
||||
},
|
||||
{
|
||||
"unmute": "Unmute a participant Размутить участника"
|
||||
},
|
||||
{
|
||||
"create": "[-g|--group name] [-c|--channel name] - Create group/channel [-g|--group name] [-c|--channel name] - Создать группу/канал"
|
||||
},
|
||||
{
|
||||
"dnd": "Mutes and archives the current chat Отключает звук и архивирует чат"
|
||||
},
|
||||
{
|
||||
"invite": "-u username/id - Invite a user to the chat (use -b to invite the inline bot) -u username/id - Пригласить пользователя в чат (-b пригласить инлайн бота)"
|
||||
},
|
||||
{
|
||||
"inspect": "[-i] Get the info about the entity [-i] Получить информацию о сущности"
|
||||
},
|
||||
{
|
||||
"requests": "[-a] [-d] Manage join requests [-a] [-d] Управлять заявками на вступление"
|
||||
},
|
||||
{
|
||||
"owns": "Get all your chats/channels Получить все свои чаты/каналы"
|
||||
},
|
||||
{
|
||||
"promote": "[-r] [-u] [-f] - Promote a participant [-r] [-u] [-f] - Выдать админку участнику"
|
||||
},
|
||||
{
|
||||
"restrict": "[-t] [-u] - Restrict a participant [-t] [-u] - Ограничить участника"
|
||||
}
|
||||
],
|
||||
"new_commands": [
|
||||
{
|
||||
"id": {
|
||||
"ru_doc": "[reply] - Узнать ID",
|
||||
"en_doc": null,
|
||||
"doc": "[reply] - Get the ID"
|
||||
}
|
||||
},
|
||||
{
|
||||
"rights": {
|
||||
"ru_doc": "[reply/-u username/id] - Посмотреть права администратора пользователя",
|
||||
"en_doc": null,
|
||||
"doc": "[reply/-u username/id] - Check user's admin rights"
|
||||
}
|
||||
},
|
||||
{
|
||||
"leave": {
|
||||
"ru_doc": "Покинуть чат",
|
||||
"en_doc": null,
|
||||
"doc": "Leave chat"
|
||||
}
|
||||
},
|
||||
{
|
||||
"d": {
|
||||
"ru_doc": "[a[1-100] b[1-100]] | [reply] Удалить сообщения",
|
||||
"en_doc": null,
|
||||
"doc": "[a[1-100] b[1-100]] | [reply] - Delete messages"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pin": {
|
||||
"ru_doc": "[reply] - Закрепить сообщение",
|
||||
"en_doc": null,
|
||||
"doc": "[reply] - Pin a message"
|
||||
}
|
||||
},
|
||||
{
|
||||
"unpin": {
|
||||
"ru_doc": "Открепить сообщение",
|
||||
"en_doc": null,
|
||||
"doc": "Unpin a message"
|
||||
}
|
||||
},
|
||||
{
|
||||
"dgc": {
|
||||
"ru_doc": "[-c id] Удаляет группу/канал",
|
||||
"en_doc": null,
|
||||
"doc": "[-c id] Delete chat/channel"
|
||||
}
|
||||
},
|
||||
{
|
||||
"flush": {
|
||||
"ru_doc": "Очищает группу/канал от удаленных аккаунтов",
|
||||
"en_doc": null,
|
||||
"doc": "Removes deleted accounts from the chat/channel"
|
||||
}
|
||||
},
|
||||
{
|
||||
"admins": {
|
||||
"ru_doc": "Показывает админов в группе/канале",
|
||||
"en_doc": null,
|
||||
"doc": "Shows the admins in the chat/channel"
|
||||
}
|
||||
},
|
||||
{
|
||||
"bots": {
|
||||
"ru_doc": "Показывает ботов в группе/канале",
|
||||
"en_doc": null,
|
||||
"doc": "Shows the bots in the chat/channel"
|
||||
}
|
||||
},
|
||||
{
|
||||
"users": {
|
||||
"ru_doc": "Показывает простых участников чата/канала",
|
||||
"en_doc": null,
|
||||
"doc": "Shows the users in the chat/channel"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ban": {
|
||||
"ru_doc": "[-u] [-t] [-r] Забанить участника",
|
||||
"en_doc": null,
|
||||
"doc": "[-u] [-t] [-r] Ban a participant temporarily or permanently"
|
||||
}
|
||||
},
|
||||
{
|
||||
"unban": {
|
||||
"ru_doc": "Разбанить пользователя",
|
||||
"en_doc": null,
|
||||
"doc": "[-u] Unban a user"
|
||||
}
|
||||
},
|
||||
{
|
||||
"kick": {
|
||||
"ru_doc": "[-u] [-r] Кикнуть участника",
|
||||
"en_doc": null,
|
||||
"doc": "[-u] [-r] Kick a participant"
|
||||
}
|
||||
},
|
||||
{
|
||||
"mute": {
|
||||
"ru_doc": "[-u] [-t] [-r] Замутить участника",
|
||||
"en_doc": null,
|
||||
"doc": "[-u] [-t] [-r] Mute a participant temporarily or permanently"
|
||||
}
|
||||
},
|
||||
{
|
||||
"unmute": {
|
||||
"ru_doc": "Размутить участника",
|
||||
"en_doc": null,
|
||||
"doc": "Unmute a participant"
|
||||
}
|
||||
},
|
||||
{
|
||||
"create": {
|
||||
"ru_doc": "[-g|--group name] [-c|--channel name] - Создать группу/канал",
|
||||
"en_doc": null,
|
||||
"doc": "[-g|--group name] [-c|--channel name] - Create group/channel"
|
||||
}
|
||||
},
|
||||
{
|
||||
"dnd": {
|
||||
"ru_doc": "Отключает звук и архивирует чат",
|
||||
"en_doc": null,
|
||||
"doc": "Mutes and archives the current chat"
|
||||
}
|
||||
},
|
||||
{
|
||||
"invite": {
|
||||
"ru_doc": "-u username/id - Пригласить пользователя в чат (-b пригласить инлайн бота)",
|
||||
"en_doc": null,
|
||||
"doc": "-u username/id - Invite a user to the chat (use -b to invite the inline bot)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"inspect": {
|
||||
"ru_doc": "[-i] Получить информацию о сущности",
|
||||
"en_doc": null,
|
||||
"doc": "[-i] Get the info about the entity"
|
||||
}
|
||||
},
|
||||
{
|
||||
"requests": {
|
||||
"ru_doc": "[-a] [-d] Управлять заявками на вступление",
|
||||
"en_doc": null,
|
||||
"doc": "[-a] [-d] Manage join requests"
|
||||
}
|
||||
},
|
||||
{
|
||||
"owns": {
|
||||
"ru_doc": "Получить все свои чаты/каналы",
|
||||
"en_doc": null,
|
||||
"doc": "Get all your chats/channels"
|
||||
}
|
||||
},
|
||||
{
|
||||
"promote": {
|
||||
"ru_doc": "[-r] [-u] [-f] - Выдать админку участнику",
|
||||
"en_doc": null,
|
||||
"doc": "[-r] [-u] [-f] - Promote a participant"
|
||||
}
|
||||
},
|
||||
{
|
||||
"restrict": {
|
||||
"ru_doc": "[-t] [-u] - Ограничить участника",
|
||||
"en_doc": null,
|
||||
"doc": "[-t] [-u] - Restrict a participant"
|
||||
}
|
||||
}
|
||||
],
|
||||
"category": [
|
||||
"Chat",
|
||||
"Tools"
|
||||
]
|
||||
},
|
||||
"coddrago/modules/compliments.py": {
|
||||
"name": "Compliments",
|
||||
"description": "Compliments for your partner",
|
||||
|
||||
@@ -26,241 +26,6 @@ from .. import loader, utils
|
||||
__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):
|
||||
critical = 500
|
||||
not_found = 404
|
||||
@@ -381,7 +146,9 @@ class HostAPI(API):
|
||||
) -> Dict:
|
||||
route = f"{self._url}/{tg_id}/logs/{lines}"
|
||||
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
|
||||
class HHMod(loader.Module):
|
||||
@@ -389,16 +156,19 @@ class HHMod(loader.Module):
|
||||
|
||||
strings = {
|
||||
"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=5418136591484865679>📶</emoji> <b>Status:</b> {status}\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"
|
||||
"{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}"
|
||||
),
|
||||
"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...",
|
||||
"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",
|
||||
@@ -413,16 +183,15 @@ class HHMod(loader.Module):
|
||||
|
||||
strings_ru = {
|
||||
"name": "HH",
|
||||
"info": (
|
||||
"<emoji document_id=5413334818047940135>👤</emoji> <b>Информация о</b> <code>{id}</code>\n\n"
|
||||
"<emoji document_id=5418136591484865679>📶</emoji> <b>Статус:</b> {status}\n"
|
||||
"<emoji document_id=5415992848753379520>⚙️</emoji> <b>Сервер:</b> {server}\n"
|
||||
"<emoji document_id=5416042764863293485>❤️</emoji> <b>Подписка истечёт через</b> <code>{days_end} дней</code>\n"
|
||||
"{stats}\n"
|
||||
"{warns}"
|
||||
),
|
||||
# "default_info": (
|
||||
# "<emoji document_id=5413334818047940135>👤</emoji> <b>Информация о</b> <code>{id}</code>\n\n"
|
||||
# "<emoji document_id=5418136591484865679>📶</emoji> <b>Статус:</b> {status}\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=5413394354884596702>💾</emoji> <b>Используется:</b> <code>{cpu_percent}%</code> CPU, <code>{ram_usage}MB</code> RAM\n\n"
|
||||
# "{warns}"
|
||||
# ),
|
||||
"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> Загрузка...",
|
||||
"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",
|
||||
@@ -443,6 +212,16 @@ class HHMod(loader.Module):
|
||||
None,
|
||||
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):
|
||||
@@ -459,7 +238,7 @@ class HHMod(loader.Module):
|
||||
self._db = db
|
||||
self.me = await client.get_me()
|
||||
self.bot = "@hikkahost_bot"
|
||||
|
||||
|
||||
@loader.command(
|
||||
en_doc=" - ub status",
|
||||
)
|
||||
@@ -485,19 +264,17 @@ class HHMod(loader.Module):
|
||||
stats = (await api.get_stats(user_id))["stats"]
|
||||
working = True if status["status"] == "running" else False
|
||||
|
||||
load = {}
|
||||
|
||||
if working:
|
||||
cpu_stats = stats["cpu_stats"]
|
||||
cpu_total_usage = cpu_stats['cpu_usage']['total_usage']
|
||||
system_cpu_usage = cpu_stats['system_cpu_usage']
|
||||
|
||||
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 = ""
|
||||
load = {
|
||||
"ram_usage": round(stats["memory_stats"]["usage"] / (1024 * 1024), 2),
|
||||
"cpu_percent": round((cpu_total_usage / system_cpu_usage) * 100.0, 2)
|
||||
}
|
||||
|
||||
end_date = host.end_date.replace(tzinfo=timezone.utc)
|
||||
warns = ""
|
||||
@@ -510,20 +287,22 @@ class HHMod(loader.Module):
|
||||
|
||||
server = servers_dict.get(host.server_id)
|
||||
server = self.strings["server"].format(
|
||||
flag=FLAGS[server["country_code"]],
|
||||
flag=get_flag(server["country_code"]),
|
||||
name=server["name"],
|
||||
)
|
||||
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings["info"].format(
|
||||
self.config["hinfo_message"].format(
|
||||
id=user_id,
|
||||
warns=warns,
|
||||
stats=stats,
|
||||
server=server,
|
||||
days_end=days_end,
|
||||
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(
|
||||
|
||||