diff --git a/coddrago/modules/YaMusic.py b/coddrago/modules/YaMusic.py
index 18b514d..2279cfe 100644
--- a/coddrago/modules/YaMusic.py
+++ b/coddrago/modules/YaMusic.py
@@ -1,8 +1,8 @@
__version__ = (3, 1, 1)
# meta banner: https://raw.githubusercontent.com/kamekuro/hikka-mods/main/banners/yamusic.png
# packurl: https://raw.githubusercontent.com/coddrago/assets/refs/heads/main/modules/yamusic.yml
-# meta pic: https://raw.githubusercontent.com/kamekuro/hikka-mods/main/icons/yamusic.png
-# meta developer: @codrago
+# meta banner: https://raw.githubusercontent.com/coddrago/modules/refs/heads/main/banner.png
+# meta developer: @codrago_m
# old meta dev: @kamekuro xuesos
# scope: heroku_only
# scope: heroku_min 1.7.2
@@ -41,6 +41,7 @@ class Banners:
meta_info: str = "Music",
is_liked: bool = False,
repeat_mode: str = "NONE",
+ blur: int = 0,
):
self.title = title
self.artists = artists
@@ -52,6 +53,7 @@ class Banners:
self.meta_info = meta_info
self.is_liked = is_liked
self.repeat_mode = repeat_mode
+ self.blur = blur
def ultra(self) -> io.BytesIO:
WIDTH, HEIGHT = 2560, 1220
@@ -96,7 +98,9 @@ class Banners:
background = background.crop((0, offset, bg_w, offset + new_h))
background = background.resize((WIDTH, HEIGHT), Image.Resampling.LANCZOS)
- background = background.filter(ImageFilter.GaussianBlur(radius=0))
+
+ if self.blur > 0:
+ background = background.filter(ImageFilter.GaussianBlur(radius=self.blur))
dark_overlay = Image.new("RGBA", (WIDTH, HEIGHT), (0, 0, 0, 180))
background = Image.alpha_composite(background, dark_overlay)
@@ -296,30 +300,6 @@ class Banners:
(heart_x, icon_y_center + heart_size + 5),
]
- if self.is_liked:
- draw.ellipse(c1_box, fill="red", outline="red")
- draw.ellipse(c2_box, fill="red", outline="red")
- draw.polygon(tri_points, fill="red", outline="red")
- else:
- draw.ellipse(c1_box, fill=None, outline="red", width=3)
- draw.ellipse(c2_box, fill=None, outline="red", width=3)
- draw.line(
- [
- (heart_x - c_r * 2 + 1, icon_y_center),
- (heart_x, icon_y_center + heart_size + 5),
- ],
- fill="red",
- width=3,
- )
- draw.line(
- [
- (heart_x + c_r * 2 - 1, icon_y_center),
- (heart_x, icon_y_center + heart_size + 5),
- ],
- fill="red",
- width=3,
- )
-
by = io.BytesIO()
background.save(by, format="PNG")
by.seek(0)
@@ -378,8 +358,13 @@ class YaMusicMod(loader.Module):
option="banner_version",
default="ultra",
doc=lambda: self.strings["_cfg"]["banner_version"],
- validator=loader.validators.Choice(["old", "new", "ultra"]),
- ),)
+ validator=loader.validators.Choice(["ultra"]),
+ ),
+ loader.ConfigValue(
+ option="blur",
+ default=0,
+ ),
+ )
self.ym_client = None
self.device_id = "".join(random.choices(string.ascii_lowercase, k=16))
@@ -392,6 +377,7 @@ class YaMusicMod(loader.Module):
#"now_play", self._now_play_placeholder, "placeholder for nowplay music"
# Heroku 2.0.0 feature
#)
+ #utils.register_placeholder("duration", self._duration_placeholder, "progress bar")
if not self.get("guide_sent", False):
await self.inline.bot.send_message(self._tg_id, self.strings("iguide"))
@@ -437,7 +423,7 @@ class YaMusicMod(loader.Module):
me = await self._client.get_me()
self._premium = me.premium if hasattr(me, "premium") else False
- @loader.loop(30)
+ @loader.loop(15)
async def autobio(self):
if not self.config["token"]:
self.autobio.stop()
@@ -547,6 +533,88 @@ class YaMusicMod(loader.Module):
),
)
+
+ async def _duration_placeholder(self):
+ """Placeholder for {duration} with custom emoji bar"""
+ if not self.config["token"]:
+ return "No Token"
+
+ try:
+ now = await self.__get_now_playing()
+ if not now or now.get("paused"):
+ return "Not Playing"
+
+ duration = now.get("duration_ms", 0)
+ progress = now.get("progress_ms", 0)
+
+ if duration == 0:
+ return "0%"
+
+ percent = (progress / duration) * 100
+
+ s_less_10 = (
+ "➖"
+ "⭐"
+ "⭐"
+ "⭐"
+ "⭐"
+ "⭐"
+ )
+
+ s_10_to_20 = (
+ "➖"
+ "⭐"
+ "⭐"
+ "⭐"
+ "⭐"
+ "⭐"
+ )
+
+ s_30_to_40 = (
+ "➖"
+ "➖"
+ "➖"
+ "⭐"
+ "⭐"
+ "⭐"
+ )
+
+ s_over_50 = (
+ "➖"
+ "➖"
+ "➖"
+ "➖"
+ "⭐"
+ "⭐"
+ )
+
+ s_over_80 = (
+ "➖"
+ "➖"
+ "➖"
+ "➖"
+ "➖"
+ "⭐"
+ )
+
+ if percent < 10:
+ return s_less_10
+ elif percent < 20:
+ return s_10_to_20
+ elif percent < 30:
+ return s_10_to_20
+ elif percent < 40:
+ return s_30_to_40
+ elif percent < 50:
+ return s_30_to_40
+ elif percent < 80:
+ return s_over_50
+ else:
+ return s_over_80
+
+ except Exception as e:
+ return f"Error: {e}"
+
async def _download_bytes(self, url: str) -> typing.Optional[bytes]:
try:
async with aiohttp.ClientSession() as session:
@@ -686,8 +754,10 @@ class YaMusicMod(loader.Module):
meta_info=meta_info,
is_liked=is_liked,
repeat_mode=repeat_mode,
+ blur=self.config["blur"],
)
+
file = await utils.run_sync(
getattr(banners, self.config["banner_version"], banners.ultra)
)
diff --git a/coddrago/modules/full.txt b/coddrago/modules/full.txt
index 59451ea..d889594 100644
--- a/coddrago/modules/full.txt
+++ b/coddrago/modules/full.txt
@@ -17,10 +17,9 @@ figlet
promoclaimer
passwordgen
send
-lastfm
dbmod
chatmodule
stats
tagwatcher
hardspam
-YaMusic
\ No newline at end of file
+YaMusic
diff --git a/coddrago/modules/lastfm.py b/coddrago/modules/lastfm.py
deleted file mode 100644
index efd7334..0000000
--- a/coddrago/modules/lastfm.py
+++ /dev/null
@@ -1,124 +0,0 @@
-# ---------------------------------------------------------------------------------
-#░█▀▄░▄▀▀▄░█▀▄░█▀▀▄░█▀▀▄░█▀▀▀░▄▀▀▄░░░█▀▄▀█
-#░█░░░█░░█░█░█░█▄▄▀░█▄▄█░█░▀▄░█░░█░░░█░▀░█
-#░▀▀▀░░▀▀░░▀▀░░▀░▀▀░▀░░▀░▀▀▀▀░░▀▀░░░░▀░░▒▀
-# Name: LastFM
-# Description: Module for music from different services
-# Author: @codrago_m
-# ---------------------------------------------------------------------------------
-# 🔒 Licensed under the GNU AGPLv3
-# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
-# ---------------------------------------------------------------------------------
-# Author: @codrago
-# Commands: nowplay
-# scope: heroku_only
-# meta developer: @codrago_m
-# meta banner: https://raw.githubusercontent.com/coddrago/modules/refs/heads/main/banner.png
-# meta pic: https://envs.sh/Hob.webp
-# ---------------------------------------------------------------------------------
-
-from .. import loader, utils
-from herokutl import events
-import requests
-import asyncio
-
-
-@loader.tds
-class lastfmmod(loader.Module):
- """Module for music from different services"""
- def __init__(self):
- self.config = loader.ModuleConfig(
- loader.ConfigValue(
- "username_lastfm",
- None,
- lambda: self.strings["_doc_username_lastfm"],
- ),
- loader.ConfigValue(
- "text",
- "🎧 now playing...\n"
- "🎶 playlist: {song_album}\n"
- "🎵 track: {song_name}\n"
- "🎤 artist: {song_artist}",
- lambda: self.strings["_doc_text"],
- ),
- )
-
- strings = {
- "name": "LastFm",
- "loading":"⌨️ Loading song...",
- "bot_no_result": "❌ Nothing found.\nTitle: {song_name}\nAuthor: {song_artist}\nAlbum:{song_album}",
- "_doc_text": "The text that will be written next to the file",
- "_doc_username_lastfm": "Your username from last.fm",
- "nick_error": "❌ Put your nickname from last.fm",
- "tutorial": "Go to last.fm and register.\nBE SURE to remember the username and password, they will come in handy later.\nLet's look at the VK version\nAfter that, go to the @vkxci channel, download VK X and log in to your VK account, then go to settings and click «Integrations», select Last FM.\nEnter the username and password.\nThen you're almost done!\nWrite {prefix}fcfg lastfm username_lastfm {username}\nUse the {prefix}nowplay command and enjoy life!",
- }
-
- strings_ru = {
- "name": "LastFm",
- "loading": "⌨️ Загрузка трека...",
- "bot_no_result": "❌ Ничего не найдено.\nНазвание: {song_name}\nИсполнитель: {song_artist}\nАльбом: {song_album}",
- "_doc_text": "Текст, который будет написан рядом с файлом",
- "_doc_username_lastfm": "Ваш username с last.fm",
- "nick_error": "❌ Укажите ваш никнейм с last.fm",
- "tutorial": "Зайдите на last.fm и зарегистрируйтесь.\nОБЯЗАТЕЛЬНО запомните логин и пароль, они пригодятся позже.\nРассмотрим вариант для VK\nПосле этого зайдите в канал @vkxci, скачайте VK X и авторизуйтесь в своём аккаунте VK, затем зайдите в настройки и нажмите «Интеграции», выберите Last FM.\nВведите логин и пароль.\nЗатем вы почти закончили!\nНапишите {prefix}fcfg lastfm username_lastfm {username}\nИспользуйте команду {prefix}nowplay и наслаждайтесь жизнью!",
- }
-
- @loader.command(alias="np")
- async def nowplay(self, message):
- """| send playing track"""
-
- lastfm_username = self.config["username_lastfm"]
- API_KEY = "460cda35be2fbf4f28e8ea7a38580730" # Облегчение жизни школьникам
-
- if not lastfm_username:
- response_text = self.strings["nick_error"]
- await self.invoke("config", "lastfm", message=message)
- await utils.answer(message, response_text)
- else:
- try:
- current_track_url = f'http://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&nowplaying=true&user={lastfm_username}&api_key={API_KEY}&format=json'
- response = requests.get(current_track_url)
- data = response.json()
-
- if 'recenttracks' in data and 'track' in data['recenttracks'] and data['recenttracks']['track']:
- nowplaying_track = None
- for track in data['recenttracks']['track']:
- if '@attr' in track and 'nowplaying' in track['@attr']:
- nowplaying_track = track
- break
-
- if nowplaying_track:
- song_name = nowplaying_track.get('name', 'Unknown song')
- song_artist = nowplaying_track.get('artist', {}).get('#text', 'Unknown Artist')
- if nowplaying_track.get('album', {}).get('#text') == nowplaying_track.get('name'):
- song_album = "single"
- else:
- song_album = nowplaying_track.get('album', {}).get('#text', 'Unknown Album')
- response_text = f"/search {song_name} - {song_artist}"
-
- try:
- async with message.client.conversation("@LyaDownbot") as conv:
- await conv.send_message(response_text)
- while True:
- response_bot = await conv.get_response()
- if "Не удалось найти трек" in response_bot.text:
- await utils.answer(message, self.strings["bot_no_result"])
- return
-
- if "Ищем треки..." in response_bot.text:
- await utils.answer(message, self.strings["loading"])
-
- if response_bot.media:
- await message.client.send_file(message.chat_id, response_bot.media, caption = self.config["text"].format(song_artist=song_artist, song_album=song_album, song_name=song_name))
- await message.delete()
- return
- except Exception as e:
- await utils.answer(message, f"
{e}
")
- except Exception as e:
- await utils.answer(message, f"{e}
")
-
- @loader.command()
- async def tutorl(self, message):
- """| tutorial"""
-
- await utils.answer(message, self.strings['tutorial'].format(prefix = self.get_prefix(), username="{username}"))
diff --git a/coddrago/modules/tagwatcher.py b/coddrago/modules/tagwatcher.py
index 294a559..13392ff 100644
--- a/coddrago/modules/tagwatcher.py
+++ b/coddrago/modules/tagwatcher.py
@@ -1,10 +1,13 @@
# meta developer: @codrago_m
# scope: heroku_min 2.0.0
+
import logging
-from .. import utils, loader, main
+
from telethon.tl.functions.messages import MarkDialogUnreadRequest
+from .. import loader, main, utils
+
logger = logging.getLogger("TagWatcher")
@@ -124,6 +127,7 @@ class TagWatcher(loader.Module):
description="Here will be notifications about mentions in chats.",
icon_emoji_id=5409025823388741707,
)
+
self.xdlib = await self.import_lib(
"https://raw.githubusercontent.com/coddrago/modules/refs/heads/main/libs/xdlib.py",
suspend_on_error=True,
diff --git a/mead0wsss/mead0wsMods/SenderGifts.py b/mead0wsss/mead0wsMods/SenderGifts.py
index 74b6d22..9ea5a29 100644
--- a/mead0wsss/mead0wsMods/SenderGifts.py
+++ b/mead0wsss/mead0wsMods/SenderGifts.py
@@ -1,5 +1,5 @@
# -- version --
-__version__ = (1, 2, 1)
+__version__ = (1, 2, 2)
# -- version --
@@ -9,7 +9,7 @@ __version__ = (1, 2, 1)
# ██║╚██╔╝██║██╔══╝░░██╔══██║██║░░██║██║░░██║░░████╔═████║░░╚═══██╗░╚═══██╗
# ██║░╚═╝░██║███████╗██║░░██║██████╔╝╚█████╔╝░░╚██╔╝░╚██╔╝░██████╔╝██████╔╝
# ╚═╝░░░░░╚═╝╚══════╝╚═╝░░╚═╝╚═════╝░░╚════╝░░░░╚═╝░░░╚═╝░░╚═════╝░╚═════╝░
-# © Copyright 2025
+# © Copyright 2026
# ✈ https://t.me/mead0wssMods
@@ -33,14 +33,17 @@ class SenderGifts(loader.Module):
"checking_user": "🔍 Проверка пользователя...",
"checking_balance": "🔍 Проверка баланса...",
"user_not_found": "❌ Пользователь не найден",
- "gift_menu": "🎁 Выберите категорию подарков.\n\n👤 Пользователь: {}\n📄 Текст: {}\n⭐ Баланс: {} звезд",
- "category_menu": "🎁 Подарки за {} ⭐\n\n👤 Пользователь: {}\n📄 Текст: {}",
+ "gift_menu": "🎁 Выберите категорию подарков.\n\n👤 Пользователь: {}\n📂 Текст: {}\n⭐️ Баланс: {} звезд",
+ "category_menu": "🎁 Подарки за {} ⭐\n\n👤 Пользователь: {}\n📂 Текст: {}",
+ "privacy_menu": "🎁 Выбран подарок: {}\n\nКак отправить подарок?",
"sending_gift": "🛫 Отправка подарка...",
"gift_sent": "✅ Подарок успешно отправлен!",
"not_enough_stars": "❌ Недостаточно звезд для отправки подарка {}!",
"min_stars_error": "❌ Недостаточно звезд для отправки минимального подарка!",
"no_available_gifts": "❌ Нет доступных подарков для вашего баланса",
"balance_error": "❌ Ошибка при проверке баланса",
+ "btn_public": "📢 Публично",
+ "btn_anon": "🕵️ Анонимно",
}
gift_categories = {
@@ -57,6 +60,7 @@ class SenderGifts(loader.Module):
{"id": 5170314324215857265, "emoji": "💐", "name": "Цветы"},
{"id": 5170564780938756245, "emoji": "🚀", "name": "Ракета"},
{"id": 5922558454332916696, "emoji": "🎄", "name": "Ёлка"},
+ {"id": 5956217000635139069, "emoji": "🧸", "name": "Новогодний мишка"}
],
100: [
{"id": 5168043875654172773, "emoji": "🏆", "name": "Кубок"},
@@ -135,9 +139,11 @@ class SenderGifts(loader.Module):
if row:
buttons.append(row)
+
+ helper_msg = await self.inline.form("🪐", balance_msg)
await utils.answer(
- balance_msg,
+ helper_msg,
self.strings["gift_menu"].format(
f"@{user.username}" if user.username else user.first_name,
text if text else "-",
@@ -153,8 +159,8 @@ class SenderGifts(loader.Module):
for gift in gifts:
row.append({
"text": gift["emoji"],
- "callback": self._send_gift,
- "args": (user_id, gift["id"], text, gift["emoji"], msg_id, balance),
+ "callback": self._select_privacy,
+ "args": (user_id, gift["id"], text, gift["emoji"], msg_id, balance, price),
})
if len(row) == 3:
buttons.append(row)
@@ -183,6 +189,34 @@ class SenderGifts(loader.Module):
reply_markup=buttons
)
+ async def _select_privacy(self, call, user_id, gift_id, text, gift_emoji, msg_id, balance, price):
+ buttons = [
+ [
+ {
+ "text": self.strings["btn_public"],
+ "callback": self._send_gift,
+ "args": (user_id, gift_id, text, gift_emoji, msg_id, balance, False) # hide_name=False публично
+ },
+ {
+ "text": self.strings["btn_anon"],
+ "callback": self._send_gift,
+ "args": (user_id, gift_id, text, gift_emoji, msg_id, balance, True) # hide_name=True анонимно
+ }
+ ],
+ [
+ {
+ "text": "⬅️ Назад",
+ "callback": self._show_category,
+ "args": (user_id, price, text, balance, msg_id)
+ }
+ ]
+ ]
+
+ await call.edit(
+ self.strings["privacy_menu"].format(gift_emoji),
+ reply_markup=buttons
+ )
+
async def _back_to_categories(self, call, user_id, text, balance, msg_id):
try:
user = await self.client.get_entity(user_id)
@@ -216,7 +250,7 @@ class SenderGifts(loader.Module):
reply_markup=buttons
)
- async def _send_gift(self, call, user_id, gift_id, text, gift_emoji, msg_id, balance):
+ async def _send_gift(self, call, user_id, gift_id, text, gift_emoji, msg_id, balance, hide_name):
try:
await call.edit(
self.strings["sending_gift"],
@@ -227,11 +261,11 @@ class SenderGifts(loader.Module):
self.client.parse_mode,
)
text, entities = parse_mode.parse(text)
-
user = await self.client.get_input_entity(user_id)
inv = InputInvoiceStarGift(
user,
gift_id,
+ hide_name=hide_name,
message=TextWithEntities(text, entities) if text else TextWithEntities("", [])
)
form = await self.client(GetPaymentFormRequest(inv))
diff --git a/modules.json b/modules.json
index f0d04de..e1acf11 100644
--- a/modules.json
+++ b/modules.json
@@ -38599,7 +38599,7 @@
"sgamerps": "- Start the game «Rock, Paper, Scissors» | (RU) - Начать игру «Камень, ножницы, бумага» | (UZ) - «Tosh, qog'oz, qaychi» o'yinining boshlanishi | (DE) - Beginn des Spiels «Stein, Papier, Schere» | (ES) - El comienzo del juego «Piedra, papel o tijera»"
},
{
- "cleargames": "- Complete all running games. | (RU) | (UZ) - Barcha faol o'yinlarni tugatish. | (DE) - Alle aktiven Spiele beenden. | (ES) - Completar todos los juegos en curso."
+ "cleargames": "- Complete all running games. | (RU) | (UZ) - Barcha faol o'yinlarni tugatish. | (DE) - Alle aktiven Spiele beenden. | (ES) - Completar todos los juegos en curso."
}
],
"new_commands": [
@@ -38625,7 +38625,7 @@
"original_name": "cleargames",
"description": {
"default": "- Complete all running games.",
- "ru": "",
+ "ru": "",
"uz": "- Barcha faol o'yinlarni tugatish.",
"de": "- Alle aktiven Spiele beenden.",
"es": "- Completar todos los juegos en curso."
@@ -60085,14 +60085,17 @@
"checking_user": "🔍 Проверка пользователя...",
"checking_balance": "🔍 Проверка баланса...",
"user_not_found": "❌ Пользователь не найден",
- "gift_menu": "🎁 Выберите категорию подарков.\n\n👤 Пользователь: {}\n📄 Текст: {}\n⭐ Баланс: {} звезд",
- "category_menu": "🎁 Подарки за {} ⭐\n\n👤 Пользователь: {}\n📄 Текст: {}",
+ "gift_menu": "🎁 Выберите категорию подарков.\n\n👤 Пользователь: {}\n📂 Текст: {}\n⭐️ Баланс: {} звезд",
+ "category_menu": "🎁 Подарки за {} ⭐\n\n👤 Пользователь: {}\n📂 Текст: {}",
+ "privacy_menu": "🎁 Выбран подарок: {}\n\nКак отправить подарок?",
"sending_gift": "🛫 Отправка подарка...",
"gift_sent": "✅ Подарок успешно отправлен!",
"not_enough_stars": "❌ Недостаточно звезд для отправки подарка {}!",
"min_stars_error": "❌ Недостаточно звезд для отправки минимального подарка!",
"no_available_gifts": "❌ Нет доступных подарков для вашего баланса",
- "balance_error": "❌ Ошибка при проверке баланса"
+ "balance_error": "❌ Ошибка при проверке баланса",
+ "btn_public": "📢 Публично",
+ "btn_anon": "🕵️ Анонимно"
},
"has_on_load": false,
"has_on_unload": false,
@@ -72469,72 +72472,6 @@
"has_on_unload": false,
"class_cmd_names": {}
},
- "coddrago/modules/lastfm.py": {
- "name": "lastfmmod",
- "description": "Module for music from different services",
- "cls_doc": {},
- "meta": {
- "pic": "https://envs.sh/Hob.webp",
- "banner": "https://raw.githubusercontent.com/coddrago/modules/refs/heads/main/banner.png",
- "developer": "@codrago_m"
- },
- "commands": [
- {
- "nowplay": "| send playing track"
- },
- {
- "tutorl": "| tutorial"
- }
- ],
- "new_commands": [
- {
- "name": "nowplay",
- "original_name": "nowplay",
- "description": {
- "default": "| send playing track"
- },
- "cmd_names": {},
- "aliases": [],
- "usage": null,
- "inline": false,
- "is_inline_handler": false,
- "decorators": []
- },
- {
- "name": "tutorl",
- "original_name": "tutorl",
- "description": {
- "default": "| tutorial"
- },
- "cmd_names": {},
- "aliases": [],
- "usage": null,
- "inline": false,
- "is_inline_handler": false,
- "decorators": []
- }
- ],
- "inline_handlers": [],
- "strings": {
- "name": "LastFm",
- "loading": "⌨️ Loading song...",
- "bot_no_result": "❌ Nothing found.\nTitle: {song_name}\nAuthor: {song_artist}\nAlbum:{song_album}",
- "_doc_text": "The text that will be written next to the file",
- "_doc_username_lastfm": "Your username from last.fm",
- "nick_error": "❌ Put your nickname from last.fm",
- "tutorial": "Go to last.fm and register.\nBE SURE to remember the username and password, they will come in handy later.\nLet's look at the VK version\nAfter that, go to the @vkxci channel, download VK X and log in to your VK account, then go to settings and click «Integrations», select Last FM.\nEnter the username and password.\nThen you're almost done!\nWrite {prefix}fcfg lastfm username_lastfm {username}\nUse the {prefix}nowplay command and enjoy life!",
- "name_ru": "LastFm",
- "loading_ru": "⌨️ Загрузка трека...",
- "bot_no_result_ru": "❌ Ничего не найдено.\nНазвание: {song_name}\nИсполнитель: {song_artist}\nАльбом: {song_album}",
- "_doc_text_ru": "Текст, который будет написан рядом с файлом",
- "_doc_username_lastfm_ru": "Ваш username с last.fm",
- "nick_error_ru": "❌ Укажите ваш никнейм с last.fm",
- "tutorial_ru": "Зайдите на last.fm и зарегистрируйтесь.\nОБЯЗАТЕЛЬНО запомните логин и пароль, они пригодятся позже.\nРассмотрим вариант для VK\nПосле этого зайдите в канал @vkxci, скачайте VK X и авторизуйтесь в своём аккаунте VK, затем зайдите в настройки и нажмите «Интеграции», выберите Last FM.\nВведите логин и пароль.\nЗатем вы почти закончили!\nНапишите {prefix}fcfg lastfm username_lastfm {username}\nИспользуйте команду {prefix}nowplay и наслаждайтесь жизнью!"
- },
- "has_on_load": false,
- "has_on_unload": false,
- "class_cmd_names": {}
- },
"coddrago/modules/randomizer.py": {
"name": "Randomizer",
"description": "Random - it's life!",
@@ -73317,9 +73254,9 @@
"description": "",
"cls_doc": {},
"meta": {
- "pic": "https://raw.githubusercontent.com/kamekuro/hikka-mods/main/icons/yamusic.png",
- "banner": "https://raw.githubusercontent.com/kamekuro/hikka-mods/main/banners/yamusic.png",
- "developer": "@codrago"
+ "pic": null,
+ "banner": "https://raw.githubusercontent.com/coddrago/modules/refs/heads/main/banner.png",
+ "developer": "@codrago_m"
},
"commands": [],
"new_commands": [],
@@ -78847,6 +78784,880 @@
"has_on_unload": false,
"class_cmd_names": {}
},
+ "radiocycle/Modules/randomanimepic.py": {
+ "name": "RandomAnimePicMod",
+ "description": "",
+ "cls_doc": {},
+ "meta": {
+ "pic": null,
+ "banner": null,
+ "developer": "@ke_mods"
+ },
+ "commands": [
+ {
+ "rapic": "- fetch random anime-pic 👀 | (RU) - получить рандомную аниме-картинку 👀"
+ }
+ ],
+ "new_commands": [
+ {
+ "name": "rapic",
+ "original_name": "rapiccmd",
+ "description": {
+ "default": "- fetch random anime-pic 👀",
+ "ru": "- получить рандомную аниме-картинку 👀"
+ },
+ "cmd_names": {},
+ "aliases": [],
+ "usage": null,
+ "inline": false,
+ "is_inline_handler": false,
+ "decorators": []
+ }
+ ],
+ "inline_handlers": [],
+ "strings": {
+ "name": "RandomAnimePic",
+ "img": "✅ Your anime pic\n🔗 URL: {}",
+ "loading": "✨ Loading image...",
+ "error": "🚫 An unexpected error occurred...",
+ "img_ru": "✅ Ваша аниме-картинка\n🔗 Ссылка: {}",
+ "loading_ru": "✨ Загрузка изображения...",
+ "error_ru": "🚫 Произошла непредвиденная ошибка..."
+ },
+ "has_on_load": false,
+ "has_on_unload": false,
+ "class_cmd_names": {}
+ },
+ "radiocycle/Modules/UnbanAll.py": {
+ "name": "UnbanAllMod",
+ "description": "",
+ "cls_doc": {},
+ "meta": {
+ "pic": null,
+ "banner": null,
+ "developer": "@ke_mods"
+ },
+ "commands": [
+ {
+ "unbanall": "- Unban all banned users | (RU) - Разбанить всех забаненных пользователей"
+ }
+ ],
+ "new_commands": [
+ {
+ "name": "unbanall",
+ "original_name": "unbanallcmd",
+ "description": {
+ "default": "- Unban all banned users",
+ "ru": "- Разбанить всех забаненных пользователей"
+ },
+ "cmd_names": {},
+ "aliases": [],
+ "usage": null,
+ "inline": false,
+ "is_inline_handler": false,
+ "decorators": []
+ }
+ ],
+ "inline_handlers": [],
+ "strings": {
+ "name": "UnbanAll",
+ "no_rights": "❌ I don't have administrator rights to remove restrictions.",
+ "success": "✅ All banned chat members have been unbanned.",
+ "unban_in_process": "👀 Unbanning users...",
+ "no_banned": "ℹ️ There are no banned members in this chat.",
+ "error_occured": "💢 An error occurred while unbanning user {}:\n{}",
+ "no_rights_ru": "❌ У меня нет прав администратора для снятия ограничений.",
+ "success_ru": "✅ Все забаненные участники чата были разблокированы.",
+ "unban_in_process_ru": "👀 Разбаниваю пользователей...",
+ "no_banned_ru": "ℹ️ В этом чате нет забаненных участников.",
+ "error_occured_ru": "💢 Произошла ошибка при разблокировке пользователя {}:\n{}"
+ },
+ "has_on_load": false,
+ "has_on_unload": false,
+ "class_cmd_names": {}
+ },
+ "radiocycle/Modules/SpotifyMod.py": {
+ "name": "SpotifyMod",
+ "description": "Card with the currently playing track on Spotify.",
+ "cls_doc": {
+ "ru": "Карточка с играющим треком в Spotify.",
+ "jp": "Spotify からのメッセージ"
+ },
+ "meta": {
+ "pic": null,
+ "banner": null,
+ "developer": "@ke_mods"
+ },
+ "commands": [
+ {
+ "splaylistadd": "- ➕ Add current track to playlist (use number from .splaylists) | (RU) - ➕ Добавить текущий трек в плейлист (используйте номер из .splaylists)"
+ },
+ {
+ "splaylistrem": "- ➖ Remove current track from playlist (use number from .splaylists) | (RU) - ➖ Удалить текущий трек из плейлиста (используйте номер из .splaylists)"
+ },
+ {
+ "splaylistcreate": "- 🆕 Create a new playlist | (RU) - 🆕 Создать новый плейлист"
+ },
+ {
+ "splaylistdelete": "- 🗑 Delete playlist (use number from .splaylists) | (RU) - 🗑 Удалить плейлист (используйте номер из .splaylists)"
+ },
+ {
+ "splaylists": "- 📃 Get all playlists | (RU) - 📃 Получить все плейлисты"
+ },
+ {
+ "sbio": "- ℹ️ Toggle bio playback streaming | (RU) - ℹ️ Переключить стриминг воспроизведения в био"
+ },
+ {
+ "svolume": "- 🔊 Change playback volume. .svolume <0-100> | (RU) - 🔊 Изменить громкость. .svolume <0-100>"
+ },
+ {
+ "sdevice": "- 🎵 Set preferred playback device. Usage: .sdevice or .sdevice to list devices | (RU) - 🎵 Выбрать устройство для воспроизведения. Например: .sdevice \n- 📝 Показать список устройств: .sdevice"
+ },
+ {
+ "srepeat": "- 💫 Repeat | (RU) - 💫 Включить повтор трека"
+ },
+ {
+ "sderepeat": "- ✋ Stop repeat | (RU) - ✋ Остановить повтор"
+ },
+ {
+ "snext": "- 👉 Next track | (RU) - 👉 Следующий трек"
+ },
+ {
+ "sresume": "- 🤚 Resume | (RU) - 🤚 Продолжить воспроизведение"
+ },
+ {
+ "spause": "- 🤚 Pause | (RU) - 🤚 Пауза"
+ },
+ {
+ "sback": "- ⏮ Previous track | (RU) - ⏮ Предыдущий трек"
+ },
+ {
+ "sbegin": "- ⏪ Restart track | (RU) - ⏪ Перезапустить трек"
+ },
+ {
+ "slike": "- ❤️ Like current track | (RU) - ❤️ Лайкнуть играющий трек"
+ },
+ {
+ "sunlike": "- 💔 Unlike current track | (RU) - 💔 Убрать лайк с играющего трека"
+ },
+ {
+ "sauth": "- Get authorization link | (RU) - Получить ссылку для авторизации"
+ },
+ {
+ "scode": "- Paste authorization code | (RU) - Вставить код авторизации"
+ },
+ {
+ "unauth": "- Log out of account | (RU) - Выйти из аккаунта"
+ },
+ {
+ "stokrefresh": "- Refresh authorization token | (RU) - Обновить токен авторизации"
+ },
+ {
+ "snow": "- 🎧 View current track card. | (RU) - 🎧 Показать карточку играющего трека"
+ },
+ {
+ "snowt": "- 🎧 Download current track. | (RU) - 🎧 Скачать играющий трек"
+ },
+ {
+ "ssearch": "🔍 Search for tracks. Usage: .ssearch or .ssearch to download | (RU) - 🔍 Поиск треков. Например: .ssearch Imagine Dragons Believer\n- 🎧 Скачать трек: .ssearch 1 (где 1 — номер трека из списка)"
+ },
+ {
+ "ssearchreset": "- 🔄 Reset track search results | (RU) - 🔄 Сброс результатов поиска по трекам"
+ }
+ ],
+ "new_commands": [
+ {
+ "name": "splaylistadd",
+ "original_name": "splaylistadd",
+ "description": {
+ "default": "- ➕ Add current track to playlist (use number from .splaylists)",
+ "ru": "- ➕ Добавить текущий трек в плейлист (используйте номер из .splaylists)"
+ },
+ "cmd_names": {},
+ "aliases": [],
+ "usage": null,
+ "inline": false,
+ "is_inline_handler": false,
+ "decorators": []
+ },
+ {
+ "name": "splaylistrem",
+ "original_name": "splaylistrem",
+ "description": {
+ "default": "- ➖ Remove current track from playlist (use number from .splaylists)",
+ "ru": "- ➖ Удалить текущий трек из плейлиста (используйте номер из .splaylists)"
+ },
+ "cmd_names": {},
+ "aliases": [],
+ "usage": null,
+ "inline": false,
+ "is_inline_handler": false,
+ "decorators": []
+ },
+ {
+ "name": "splaylistcreate",
+ "original_name": "splaylistcreate",
+ "description": {
+ "default": "- 🆕 Create a new playlist",
+ "ru": "- 🆕 Создать новый плейлист"
+ },
+ "cmd_names": {},
+ "aliases": [],
+ "usage": null,
+ "inline": false,
+ "is_inline_handler": false,
+ "decorators": []
+ },
+ {
+ "name": "splaylistdelete",
+ "original_name": "splaylistdelete",
+ "description": {
+ "default": "- 🗑 Delete playlist (use number from .splaylists)",
+ "ru": "- 🗑 Удалить плейлист (используйте номер из .splaylists)"
+ },
+ "cmd_names": {},
+ "aliases": [],
+ "usage": null,
+ "inline": false,
+ "is_inline_handler": false,
+ "decorators": []
+ },
+ {
+ "name": "splaylists",
+ "original_name": "splaylists",
+ "description": {
+ "default": "- 📃 Get all playlists",
+ "ru": "- 📃 Получить все плейлисты"
+ },
+ "cmd_names": {},
+ "aliases": [],
+ "usage": null,
+ "inline": false,
+ "is_inline_handler": false,
+ "decorators": []
+ },
+ {
+ "name": "sbio",
+ "original_name": "sbiocmd",
+ "description": {
+ "default": "- ℹ️ Toggle bio playback streaming",
+ "ru": "- ℹ️ Переключить стриминг воспроизведения в био"
+ },
+ "cmd_names": {},
+ "aliases": [],
+ "usage": null,
+ "inline": false,
+ "is_inline_handler": false,
+ "decorators": []
+ },
+ {
+ "name": "svolume",
+ "original_name": "svolume",
+ "description": {
+ "default": "- 🔊 Change playback volume. .svolume <0-100>",
+ "ru": "- 🔊 Изменить громкость. .svolume <0-100>"
+ },
+ "cmd_names": {},
+ "aliases": [],
+ "usage": null,
+ "inline": false,
+ "is_inline_handler": false,
+ "decorators": []
+ },
+ {
+ "name": "sdevice",
+ "original_name": "sdevicecmd",
+ "description": {
+ "default": "- 🎵 Set preferred playback device. Usage: .sdevice or .sdevice to list devices",
+ "ru": "- 🎵 Выбрать устройство для воспроизведения. Например: .sdevice \n- 📝 Показать список устройств: .sdevice"
+ },
+ "cmd_names": {},
+ "aliases": [],
+ "usage": null,
+ "inline": false,
+ "is_inline_handler": false,
+ "decorators": []
+ },
+ {
+ "name": "srepeat",
+ "original_name": "srepeatcmd",
+ "description": {
+ "default": "- 💫 Repeat",
+ "ru": "- 💫 Включить повтор трека"
+ },
+ "cmd_names": {},
+ "aliases": [],
+ "usage": null,
+ "inline": false,
+ "is_inline_handler": false,
+ "decorators": []
+ },
+ {
+ "name": "sderepeat",
+ "original_name": "sderepeatcmd",
+ "description": {
+ "default": "- ✋ Stop repeat",
+ "ru": "- ✋ Остановить повтор"
+ },
+ "cmd_names": {},
+ "aliases": [],
+ "usage": null,
+ "inline": false,
+ "is_inline_handler": false,
+ "decorators": []
+ },
+ {
+ "name": "snext",
+ "original_name": "snextcmd",
+ "description": {
+ "default": "- 👉 Next track",
+ "ru": "- 👉 Следующий трек"
+ },
+ "cmd_names": {},
+ "aliases": [],
+ "usage": null,
+ "inline": false,
+ "is_inline_handler": false,
+ "decorators": []
+ },
+ {
+ "name": "sresume",
+ "original_name": "sresumecmd",
+ "description": {
+ "default": "- 🤚 Resume",
+ "ru": "- 🤚 Продолжить воспроизведение"
+ },
+ "cmd_names": {},
+ "aliases": [],
+ "usage": null,
+ "inline": false,
+ "is_inline_handler": false,
+ "decorators": []
+ },
+ {
+ "name": "spause",
+ "original_name": "spausecmd",
+ "description": {
+ "default": "- 🤚 Pause",
+ "ru": "- 🤚 Пауза"
+ },
+ "cmd_names": {},
+ "aliases": [],
+ "usage": null,
+ "inline": false,
+ "is_inline_handler": false,
+ "decorators": []
+ },
+ {
+ "name": "sback",
+ "original_name": "sbackcmd",
+ "description": {
+ "default": "- ⏮ Previous track",
+ "ru": "- ⏮ Предыдущий трек"
+ },
+ "cmd_names": {},
+ "aliases": [],
+ "usage": null,
+ "inline": false,
+ "is_inline_handler": false,
+ "decorators": []
+ },
+ {
+ "name": "sbegin",
+ "original_name": "sbegincmd",
+ "description": {
+ "default": "- ⏪ Restart track",
+ "ru": "- ⏪ Перезапустить трек"
+ },
+ "cmd_names": {},
+ "aliases": [],
+ "usage": null,
+ "inline": false,
+ "is_inline_handler": false,
+ "decorators": []
+ },
+ {
+ "name": "slike",
+ "original_name": "slikecmd",
+ "description": {
+ "default": "- ❤️ Like current track",
+ "ru": "- ❤️ Лайкнуть играющий трек"
+ },
+ "cmd_names": {},
+ "aliases": [],
+ "usage": null,
+ "inline": false,
+ "is_inline_handler": false,
+ "decorators": []
+ },
+ {
+ "name": "sunlike",
+ "original_name": "sunlikecmd",
+ "description": {
+ "default": "- 💔 Unlike current track",
+ "ru": "- 💔 Убрать лайк с играющего трека"
+ },
+ "cmd_names": {},
+ "aliases": [],
+ "usage": null,
+ "inline": false,
+ "is_inline_handler": false,
+ "decorators": []
+ },
+ {
+ "name": "sauth",
+ "original_name": "sauthcmd",
+ "description": {
+ "default": "- Get authorization link",
+ "ru": "- Получить ссылку для авторизации"
+ },
+ "cmd_names": {},
+ "aliases": [],
+ "usage": null,
+ "inline": false,
+ "is_inline_handler": false,
+ "decorators": []
+ },
+ {
+ "name": "scode",
+ "original_name": "scodecmd",
+ "description": {
+ "default": "- Paste authorization code",
+ "ru": "- Вставить код авторизации"
+ },
+ "cmd_names": {},
+ "aliases": [],
+ "usage": null,
+ "inline": false,
+ "is_inline_handler": false,
+ "decorators": []
+ },
+ {
+ "name": "unauth",
+ "original_name": "unauthcmd",
+ "description": {
+ "default": "- Log out of account",
+ "ru": "- Выйти из аккаунта"
+ },
+ "cmd_names": {},
+ "aliases": [],
+ "usage": null,
+ "inline": false,
+ "is_inline_handler": false,
+ "decorators": []
+ },
+ {
+ "name": "stokrefresh",
+ "original_name": "stokrefreshcmd",
+ "description": {
+ "default": "- Refresh authorization token",
+ "ru": "- Обновить токен авторизации"
+ },
+ "cmd_names": {},
+ "aliases": [],
+ "usage": null,
+ "inline": false,
+ "is_inline_handler": false,
+ "decorators": []
+ },
+ {
+ "name": "snow",
+ "original_name": "snowcmd",
+ "description": {
+ "default": "- 🎧 View current track card.",
+ "ru": "- 🎧 Показать карточку играющего трека"
+ },
+ "cmd_names": {},
+ "aliases": [],
+ "usage": null,
+ "inline": false,
+ "is_inline_handler": false,
+ "decorators": []
+ },
+ {
+ "name": "snowt",
+ "original_name": "snowtcmd",
+ "description": {
+ "default": "- 🎧 Download current track.",
+ "ru": "- 🎧 Скачать играющий трек"
+ },
+ "cmd_names": {},
+ "aliases": [],
+ "usage": null,
+ "inline": false,
+ "is_inline_handler": false,
+ "decorators": []
+ },
+ {
+ "name": "ssearch",
+ "original_name": "ssearchcmd",
+ "description": {
+ "default": "🔍 Search for tracks. Usage: .ssearch or .ssearch to download",
+ "ru": "- 🔍 Поиск треков. Например: .ssearch Imagine Dragons Believer\n- 🎧 Скачать трек: .ssearch 1 (где 1 — номер трека из списка)"
+ },
+ "cmd_names": {},
+ "aliases": [],
+ "usage": null,
+ "inline": false,
+ "is_inline_handler": false,
+ "decorators": []
+ },
+ {
+ "name": "ssearchreset",
+ "original_name": "ssearchresetcmd",
+ "description": {
+ "default": "- 🔄 Reset track search results",
+ "ru": "- 🔄 Сброс результатов поиска по трекам"
+ },
+ "cmd_names": {},
+ "aliases": [],
+ "usage": null,
+ "inline": false,
+ "is_inline_handler": false,
+ "decorators": []
+ }
+ ],
+ "inline_handlers": [],
+ "strings": {
+ "name": "SpotifyMod",
+ "need_auth": "❌ Please execute .sauth before performing this action.",
+ "on-repeat": "🔄 Set on-repeat.",
+ "off-repeat": "🔄 Stopped track repeat.",
+ "skipped": "➡️ Skipped track.",
+ "playing": "▶️ Playing...",
+ "back": "⬅️ Switched to previous track",
+ "paused": "❌ Pause",
+ "restarted": "✅️ Playing track from the beginning",
+ "liked": "❤️ Liked current playback",
+ "unlike": "❌ Unliked current playback",
+ "err": "❌ An error occurred.\n{}",
+ "already_authed": "❌ Already authorized",
+ "authed": "✅ Authentication successful",
+ "deauth": "🚪 Successfully logged out of account",
+ "auth": "🔗 Follow this link, allow access, then enter .scode https://... with the link you received.",
+ "no_music": "❌ No music is playing!",
+ "dl_err": "❌ Failed to download track.",
+ "volume_changed": "🔊 Volume changed to {}%.",
+ "volume_invalid": "❌ Volume level must be a number between 0 and 100.",
+ "volume_err": "❌ An error occurred while changing volume.",
+ "no_volume_arg": "❌ Please specify a volume level between 0 and 100.",
+ "searching_tracks": "🕔 Searching for tracks matching {}...",
+ "no_search_query": "❌ Please specify a search query.",
+ "no_tracks_found": "❌ No tracks found for {}.",
+ "search_results": "✅ Search results for {}:\n\n{}",
+ "downloading_search_track": "🕔 Downloading {}...",
+ "download_success": "✅ Successfully downloaded {} - {}",
+ "invalid_track_number": "❌ Invalid track number. Please search first or provide a valid number from the list.",
+ "device_list": "📄 Available devices:\n{}",
+ "no_devices_found": "❌ No devices found.",
+ "device_changed": "✅ Playback transferred to {}.",
+ "invalid_device_id": "❌ Invalid device ID. Use .sdevice to see available devices.",
+ "search_results_cleared": "✅ Search results cleared",
+ "autobio": "🎧 Spotify autobio {}",
+ "no_ytdlp": "❌ yt-dlp not found... Check config or install yt-dlp ({}terminal pip install yt-dlp)",
+ "snowt_failed": "\n\n❌ Download failed",
+ "uploading_banner": "\n\n🕔 Uploading banner...",
+ "downloading_track": "\n\n🕔 Downloading track...",
+ "no_playlists": "❌ No playlists found.",
+ "playlists_list": "📄 Your playlists:\n\n{}",
+ "added_to_playlist": "✅ Added {} to {}",
+ "removed_from_playlist": "✅ Removed {} from {}",
+ "invalid_playlist_index": "❌ Invalid playlist number.",
+ "no_cached_playlists": "❌ Use .splaylists first.",
+ "playlist_created": "✅ Playlist {} created.",
+ "playlist_deleted": "✅ Playlist {} deleted.",
+ "no_playlist_name": "❌ Please specify a playlist name.",
+ "need_auth_ru": "❌ Выполни .sauth перед выполнением этого действия.",
+ "err_ru": "❌ Произошла ошибка.\n{}",
+ "on-repeat_ru": "🔄 Включен повтор трека.",
+ "off-repeat_ru": "🔄 Повтор трека отключён.",
+ "skipped_ru": "➡️ Трек пропущен.",
+ "playing_ru": "▶️ Играет...",
+ "back_ru": "⬅️ Переключено на предыдущий трек",
+ "paused_ru": "❌ Пауза",
+ "restarted_ru": "✅️ Воспроизведение трека с начала...",
+ "liked_ru": "❤️ Текущий трек добавлен в избранное",
+ "unlike_ru": "❌ Убрал лайк с текущего трека",
+ "already_authed_ru": "❌ Уже авторизован",
+ "authed_ru": "✅ Успешная аутентификация",
+ "deauth_ru": "🚪 Успешный выход из аккаунта",
+ "auth_ru": "🔗 Пройдите по этой ссылке, разрешите вход, затем введите .scode https://... с ссылкой которую вы получили.",
+ "no_music_ru": "❌ Музыка не играет!",
+ "dl_err_ru": "❌ Не удалось скачать трек.",
+ "volume_changed_ru": "🔊 Громкость изменена на {}%.",
+ "volume_invalid_ru": "❌ Уровень громкости должен быть числом от 0 до 100.",
+ "volume_err_ru": "❌ Произошла ошибка при изменении громкости.",
+ "no_volume_arg_ru": "❌ Пожалуйста, укажите уровень громкости от 0 до 100.",
+ "searching_tracks_ru": "🕔 Идет поиск треков по запросу {}...",
+ "no_search_query_ru": "❌ Пожалуйста, укажите поисковый запрос.",
+ "no_tracks_found_ru": "❌ По запросу '{}' ничего не найдено.",
+ "search_results_ru": "✅ Результаты поиска по запросу {}:\n\n{}",
+ "downloading_search_track_ru": "🕔 Скачиваю {}...",
+ "download_success_ru": "✅ Трек {} - {} успешно скачан.",
+ "invalid_track_number_ru": "❌ Некорректный номер трека. Сначала выполните поиск или укажите правильный номер из списка.",
+ "device_list_ru": "📄 Доступные устройства:\n{}",
+ "no_devices_found_ru": "❌ Устройства не найдены.",
+ "device_changed_ru": "✅ Воспроизведение переключено на {}.",
+ "invalid_device_id_ru": "❌ Некорректный ID устройства. Используйте .sdevice , чтобы увидеть доступные устройства.",
+ "search_results_cleared_ru": "✅ Результаты поиска очищены",
+ "autobio_ru": "🎧 Обновление био включено {}",
+ "no_ytdlp_ru": "❌ yt-dlp не найден... Проверьте конфиг или установите yt-dlp ({}terminal pip install yt-dlp)",
+ "snowt_failed_ru": "\n\n❌ Ошибка скачивания.",
+ "uploading_banner_ru": "\n\n🕔 Загрузка баннера...",
+ "downloading_track_ru": "\n\n🕔 Скачивание трека...",
+ "no_playlists_ru": "❌ Плейлисты не найдены.",
+ "playlists_list_ru": "📄 Ваши плейлисты:\n\n{}",
+ "added_to_playlist_ru": "✅ Трек {} добавлен в {}",
+ "removed_from_playlist_ru": "✅ Трек {} удален из {}",
+ "invalid_playlist_index_ru": "❌ Неверный номер плейлиста.",
+ "no_cached_playlists_ru": "❌ Сначала используйте .splaylists.",
+ "playlist_created_ru": "✅ Плейлист {} создан.",
+ "playlist_deleted_ru": "✅ Плейлист {} удален.",
+ "no_playlist_name_ru": "❌ Пожалуйста, укажите название плейлиста.",
+ "need_auth_jp": "❌ この操作を行う前に .sauth を実行してください。",
+ "on-repeat_jp": "🔄 リピート再生を設定しました。",
+ "off-repeat_jp": "🔄 リピート再生を解除しました。",
+ "skipped_jp": "➡️ スキップしました。",
+ "playing_jp": "▶️ 再生中...",
+ "back_jp": "⬅️ 前のトラックに戻りました。",
+ "paused_jp": "❌ 一時停止",
+ "restarted_jp": "✅️ 最初から再生します。",
+ "liked_jp": "❤️ お気に入りに追加しました。",
+ "unlike_jp": "❌ お気に入りから削除しました。",
+ "err_jp": "❌ エラーが発生しました。\n{}",
+ "already_authed_jp": "❌ 既に認証されています。",
+ "authed_jp": "✅ 認証に成功しました。",
+ "deauth_jp": "🚪 ログアウトしました。",
+ "auth_jp": "🔗 リンクをクリックしてアクセスを許可し、取得したURLを使って .scode https://... を入力してください。",
+ "no_music_jp": "❌ 音楽は再生されていません!",
+ "dl_err_jp": "❌ トラックのダウンロードに失敗しました。",
+ "volume_changed_jp": "🔊 音量を {}% に変更しました。",
+ "volume_invalid_jp": "❌ 音量は0から100の数字で指定してください。",
+ "volume_err_jp": "❌ 音量の変更中にエラーが発生しました。",
+ "no_volume_arg_jp": "❌ 0から100の間で音量を指定してください。",
+ "searching_tracks_jp": "🕔 {} を検索中...",
+ "no_search_query_jp": "❌ 検索キーワードを指定してください。",
+ "no_tracks_found_jp": "❌ {} は見つかりませんでした。",
+ "search_results_jp": "✅ {} の検索結果:\n\n{}",
+ "downloading_search_track_jp": "🕔 {} をダウンロード中...",
+ "download_success_jp": "✅ {} - {} のダウンロードに成功しました。",
+ "invalid_track_number_jp": "❌ トラック番号が無効です。 先に検索するか、リストから有効な番号を指定してください。",
+ "device_list_jp": "📄 利用可能なデバイス:\n{}",
+ "no_devices_found_jp": "❌ デバイスが見つかりません。",
+ "device_changed_jp": "✅ 再生デバイスを {} に切り替えました。",
+ "invalid_device_id_jp": "❌ デバイスIDが無効です。 .sdevice で利用可能なデバイスを確認してください。",
+ "search_results_cleared_jp": "✅ 検索結果をクリアしました。",
+ "autobio_jp": "🎧 Spotify AutoBio: {}",
+ "no_ytdlp_jp": "❌ yt-dlpが見つかりません... 設定を確認するか、インストールしてください ({}terminal pip install yt-dlp)",
+ "snowt_failed_jp": "\n\n❌ ダウンロードに失敗しました。",
+ "uploading_banner_jp": "\n\n🕔 バナーをアップロード中...",
+ "downloading_track_jp": "\n\n🕔 トラックをダウンロード中...",
+ "no_playlists_jp": "❌ プレイリストが見つかりません。",
+ "playlists_list_jp": "📄 あなたのプレイリスト:\n\n{}",
+ "added_to_playlist_jp": "✅ {} を {} に追加しました。",
+ "removed_from_playlist_jp": "✅ {} を {} から削除しました。",
+ "invalid_playlist_index_jp": "❌ プレイリスト番号が無効です。",
+ "no_cached_playlists_jp": "❌ 先に .splaylists を使用してください。",
+ "playlist_created_jp": "✅ プレイリスト {} を作成しました。",
+ "playlist_deleted_jp": "✅ プレイリスト {} を削除しました。",
+ "no_playlist_name_jp": "❌ プレイリスト名を指定してください。"
+ },
+ "has_on_load": false,
+ "has_on_unload": false,
+ "class_cmd_names": {}
+ },
+ "radiocycle/Modules/voicetotext.py": {
+ "name": "VoiceToTextMod",
+ "description": "",
+ "cls_doc": {},
+ "meta": {
+ "pic": null,
+ "banner": null,
+ "developer": "@ke_mods"
+ },
+ "commands": [
+ {
+ "vtt": "- recognizes text from voice or video messages. | (RU) - распознает текст из голосового или видеосообщения."
+ }
+ ],
+ "new_commands": [
+ {
+ "name": "vtt",
+ "original_name": "vttcmd",
+ "description": {
+ "default": "- recognizes text from voice or video messages.",
+ "ru": "- распознает текст из голосового или видеосообщения."
+ },
+ "cmd_names": {},
+ "aliases": [],
+ "usage": null,
+ "inline": false,
+ "is_inline_handler": false,
+ "decorators": []
+ }
+ ],
+ "inline_handlers": [],
+ "strings": {
+ "name": "VoiceToText",
+ "process_text": "✨ Recognizing the message text...",
+ "vtt_success": "🔥 Recognized text:\n{}
",
+ "vtt_failure": "🚫 Failed to recognize the message.",
+ "vtt_request_error": "🚫 Error when contacting the recognition service:\n{}",
+ "vtt_invalid": "🚫 Please reply to a voice or video message with the command {}vtt",
+ "vtt_successful": "✅ Text recognized successfully",
+ "process_text_ru": "✨ Распознаю текст сообщения...",
+ "vtt_success_ru": "🔥 Распознанный текст:\n{}
",
+ "vtt_failure_ru": "🚫 Не удалось распознать сообщение.",
+ "vtt_request_error_ru": "🚫 Ошибка при обращении к сервису распознавания:\n{}",
+ "vtt_invalid_ru": "🚫 Пожалуйста, ответьте на голосовое или видеосообщение командой {}vtt",
+ "vtt_successful_ru": "✅ Текст успешно распознан"
+ },
+ "has_on_load": false,
+ "has_on_unload": false,
+ "class_cmd_names": {}
+ },
+ "radiocycle/Modules/Neofetch.py": {
+ "name": "NeofetchMod",
+ "description": "",
+ "cls_doc": {},
+ "meta": {
+ "pic": null,
+ "banner": null,
+ "developer": "@ke_mods"
+ },
+ "commands": [
+ {
+ "neofetch": "- run neofetch command | (RU) - запустить команду neofetch | (UA) - запустити команду neofetch"
+ }
+ ],
+ "new_commands": [
+ {
+ "name": "neofetch",
+ "original_name": "neofetchcmd",
+ "description": {
+ "default": "- run neofetch command",
+ "ru": "- запустить команду neofetch",
+ "ua": "- запустити команду neofetch"
+ },
+ "cmd_names": {},
+ "aliases": [],
+ "usage": null,
+ "inline": false,
+ "is_inline_handler": false,
+ "decorators": []
+ }
+ ],
+ "inline_handlers": [],
+ "strings": {
+ "name": "Neofetch",
+ "not_installed": "Please, install Neofetch package",
+ "not_installed_ru": "Пожалуйста, установите пакет Neofetch",
+ "not_installed_ua": "Будь ласка, встановіть пакет Neofetch"
+ },
+ "has_on_load": false,
+ "has_on_unload": false,
+ "class_cmd_names": {}
+ },
+ "radiocycle/Modules/PicToStories.py": {
+ "name": "PicToStoriesMod",
+ "description": "Grid for stories",
+ "cls_doc": {},
+ "meta": {
+ "pic": null,
+ "banner": null,
+ "developer": "@ke_mods"
+ },
+ "commands": [
+ {
+ "pts": " [album name] - make grid | (RU) <реплай на фото> [название альбома] - сделать сетку"
+ }
+ ],
+ "new_commands": [
+ {
+ "name": "pts",
+ "original_name": "ptscmd",
+ "description": {
+ "default": " [album name] - make grid",
+ "ru": "<реплай на фото> [название альбома] - сделать сетку"
+ },
+ "cmd_names": {},
+ "aliases": [],
+ "usage": null,
+ "inline": false,
+ "is_inline_handler": false,
+ "decorators": []
+ }
+ ],
+ "inline_handlers": [],
+ "strings": {
+ "name": "PicToStories",
+ "no_rep": "❗️ Reply to photo!",
+ "work": "🕔 Processing...",
+ "done": "✅ Done! Check your profile.",
+ "err": "❌ Error: {}",
+ "no_rep_ru": "❗️ Реплай на фото!",
+ "work_ru": "🕔 Обрабатываю...",
+ "done_ru": "✅ Готово! Проверяй профиль.",
+ "err_ru": "❌ Ошибка: {}"
+ },
+ "has_on_load": false,
+ "has_on_unload": false,
+ "class_cmd_names": {}
+ },
+ "radiocycle/Modules/LastFm.py": {
+ "name": "lastfmmod",
+ "description": "Module for music from different services",
+ "cls_doc": {},
+ "meta": {
+ "pic": null,
+ "banner": null,
+ "developer": "@ke_mods"
+ },
+ "commands": [
+ {
+ "nowplay": "| send playing track info"
+ }
+ ],
+ "new_commands": [
+ {
+ "name": "nowplay",
+ "original_name": "nowplay",
+ "description": {
+ "default": "| send playing track info"
+ },
+ "cmd_names": {},
+ "aliases": [],
+ "usage": null,
+ "inline": false,
+ "is_inline_handler": false,
+ "decorators": []
+ }
+ ],
+ "inline_handlers": [],
+ "strings": {
+ "name": "LastFm",
+ "no_track": "❌ No track is currently playing",
+ "_doc_text": "The text that will be written next to the file",
+ "_doc_username": "Your username from last.fm",
+ "nick_error": "❌ Put your nickname from last.fm",
+ "uploading": "🕔 Uploading banner...",
+ "name_ru": "LastFm",
+ "no_track_ru": "❌ Сейчас ничего не играет",
+ "_doc_text_ru": "Текст, который будет написан рядом с файлом",
+ "_doc_username_ru": "Ваш username с last.fm",
+ "nick_error_ru": "❌ Укажите ваш никнейм с last.fm",
+ "uploading_ru": "🕔 Загрузка баннера...",
+ "name_jp": "LastFm",
+ "no_track_jp": "❌ 現在再生中のトラックはありません",
+ "_doc_text_jp": "ファイルの横に表示されるテキスト",
+ "_doc_username_jp": "Last.fmのユーザー名",
+ "nick_error_jp": "❌ Last.fmのニックネームを入力してください",
+ "uploading_jp": "🕔 バナーをアップロード中..."
+ },
+ "has_on_load": false,
+ "has_on_unload": false,
+ "class_cmd_names": {}
+ },
"CakesTwix/Hikka-Modules/ImageBoardSender.py": {
"name": "BooruModel",
"description": "",
@@ -80995,7 +81806,7 @@
}
},
"meta": {
- "total_modules": 1011,
- "generated_at": "2026-01-30T11:59:36.555357"
+ "total_modules": 1017,
+ "generated_at": "2026-02-05T01:23:15.214009"
}
}
\ No newline at end of file
diff --git a/radiocycle/Modules/LastFm.py b/radiocycle/Modules/LastFm.py
new file mode 100644
index 0000000..7069ef3
--- /dev/null
+++ b/radiocycle/Modules/LastFm.py
@@ -0,0 +1,214 @@
+# =======================================
+# _ __ __ __ _
+# | |/ /___ | \/ | ___ __| |___
+# | ' // _ \ | |\/| |/ _ \ / _` / __|
+# | . \ __/ | | | | (_) | (_| \__ \
+# |_|\_\___| |_| |_|\___/ \__,_|___/
+# @ke_mods
+# =======================================
+#
+# LICENSE: CC BY-ND 4.0 (Attribution-NoDerivatives 4.0 International)
+# --------------------------------------
+# https://creativecommons.org/licenses/by-nd/4.0/legalcode
+# =======================================
+
+# meta developer: @ke_mods
+
+from .. import loader, utils
+import requests
+import io
+import textwrap
+from PIL import Image, ImageDraw, ImageEnhance, ImageFilter, ImageFont, ImageOps
+
+class Banners:
+ def __init__(
+ self,
+ title: str,
+ artists: list,
+ track_cover: bytes,
+ font
+ ):
+ self.title = title
+ self.artists = ", ".join(artists) if isinstance(artists, list) else artists
+ self.track_cover = track_cover
+ self.font_url = font
+
+ def _get_font(self, size, font_bytes):
+ return ImageFont.truetype(io.BytesIO(font_bytes), size)
+
+ def _prepare_cover(self, size, radius):
+ cover = Image.open(io.BytesIO(self.track_cover)).convert("RGBA")
+ cover = cover.resize((size, size), Image.Resampling.LANCZOS)
+
+ mask = Image.new("L", (size, size), 0)
+ draw = ImageDraw.Draw(mask)
+ draw.rounded_rectangle((0, 0, size, size), radius=radius, fill=255)
+
+ output = Image.new("RGBA", (size, size), (0, 0, 0, 0))
+ output.paste(cover, (0, 0), mask=mask)
+ return output
+
+ def _prepare_background(self, w, h):
+ bg = Image.open(io.BytesIO(self.track_cover)).convert("RGBA")
+ bg = bg.resize((w, h), Image.Resampling.BICUBIC)
+ bg = bg.filter(ImageFilter.GaussianBlur(radius=20))
+ bg = ImageEnhance.Brightness(bg).enhance(0.4)
+ return bg
+
+ def horizontal(self):
+ W, H = 1500, 600
+ padding = 60
+ cover_size = 480
+
+ font_bytes = requests.get(self.font_url).content
+ title_font = self._get_font(55, font_bytes)
+ artist_font = self._get_font(45, font_bytes)
+ lfm_font = self._get_font(35, font_bytes)
+
+ img = self._prepare_background(W, H)
+ draw = ImageDraw.Draw(img)
+
+ cover = self._prepare_cover(cover_size, 30)
+ img.paste(cover, (padding, (H - cover_size) // 2), cover)
+
+ text_x = padding + cover_size + 60
+ text_y_start = 100
+ text_width_limit = W - text_x - padding
+
+ display_title = self.title
+ while title_font.getlength(display_title) > text_width_limit and len(display_title) > 0:
+ display_title = display_title[:-1]
+ if len(display_title) < len(self.title): display_title += "…"
+
+ display_artist = self.artists
+ while artist_font.getlength(display_artist) > text_width_limit and len(display_artist) > 0:
+ display_artist = display_artist[:-1]
+ if len(display_artist) < len(self.artists): display_artist += "…"
+
+ draw.text((text_x, text_y_start), display_title, font=title_font, fill="white")
+ draw.text((text_x, text_y_start + 70), display_artist, font=artist_font, fill="#B3B3B3")
+
+ bar_y = 480
+ draw.text((text_x, bar_y), "last.fm", font=lfm_font, fill="white")
+
+ by = io.BytesIO()
+ img.save(by, format="PNG")
+ by.seek(0)
+ by.name = "banner.png"
+ return by
+
+ def vertical(self):
+ W, H = 1000, 1500
+ padding = 80
+ cover_size = 800
+
+ font_bytes = requests.get(self.font_url).content
+ title_font = self._get_font(60, font_bytes)
+ artist_font = self._get_font(45, font_bytes)
+ lfm_font = self._get_font(35, font_bytes)
+
+ img = self._prepare_background(W, H)
+ draw = ImageDraw.Draw(img)
+
+ cover = self._prepare_cover(cover_size, 40)
+ cover_x = (W - cover_size) // 2
+ cover_y = 120
+ img.paste(cover, (cover_x, cover_y), cover)
+
+ text_area_y = cover_y + cover_size + 120
+ text_width_limit = W - (padding * 2)
+
+ display_title = self.title
+ while title_font.getlength(display_title) > text_width_limit and len(display_title) > 0:
+ display_title = display_title[:-1]
+ if len(display_title) < len(self.title): display_title += "…"
+
+ display_artist = self.artists
+ while artist_font.getlength(display_artist) > text_width_limit and len(display_artist) > 0:
+ display_artist = display_artist[:-1]
+ if len(display_artist) < len(self.artists): display_artist += "…"
+
+ title_w = title_font.getlength(display_title)
+ draw.text(((W - title_w) / 2, text_area_y), display_title, font=title_font, fill="white")
+
+ artist_w = artist_font.getlength(display_artist)
+ draw.text(((W - artist_w) / 2, text_area_y + 75), display_artist, font=artist_font, fill="#B3B3B3")
+
+ bar_y = text_area_y + 260
+
+ lfm_w = lfm_font.getlength("last.fm")
+ draw.text(((W - lfm_w) / 2, bar_y), "last.fm", font=lfm_font, fill="white")
+
+ by = io.BytesIO()
+ img.save(by, format="PNG")
+ by.seek(0)
+ by.name = "banner.png"
+ return by
+
+@loader.tds
+class lastfmmod(loader.Module):
+ """Module for music from different services"""
+
+ strings = {
+ "name": "LastFm",
+ "no_track": "❌ No track is currently playing",
+ "_doc_text": "The text that will be written next to the file",
+ "_doc_username": "Your username from last.fm",
+ "nick_error": "❌ Put your nickname from last.fm",
+ "uploading": "🕔 Uploading banner...",
+ }
+ strings_ru = {
+ "name": "LastFm",
+ "no_track": "❌ Сейчас ничего не играет",
+ "_doc_text": "Текст, который будет написан рядом с файлом",
+ "_doc_username": "Ваш username с last.fm",
+ "nick_error": "❌ Укажите ваш никнейм с last.fm",
+ "uploading": "🕔 Загрузка баннера...",
+ }
+ strings_jp = {
+ "name": "LastFm",
+ "no_track": "❌ 現在再生中のトラックはありません",
+ "_doc_text": "ファイルの横に表示されるテキスト",
+ "_doc_username": "Last.fmのユーザー名",
+ "nick_error": "❌ Last.fmのニックネームを入力してください",
+ "uploading": "🕔 バナーをアップロード中...",
+ }
+
+ def __init__(self):
+ self.config = loader.ModuleConfig(
+ loader.ConfigValue("username", None, lambda: self.strings["_doc_username"]),
+ loader.ConfigValue("custom_text", "🤩 {song_name} — {song_artist}", lambda: self.strings["_doc_text"]),
+ loader.ConfigValue("font", "https://raw.githubusercontent.com/kamekuro/assets/master/fonts/Onest-Bold.ttf", "Custom font URL (ttf)"),
+ loader.ConfigValue("banner_version", "horizontal", lambda: "Banner version", validator=loader.validators.Choice(["horizontal", "vertical"])),
+ )
+
+ @loader.command(alias="np")
+ async def nowplay(self, message):
+ """| send playing track info"""
+ user = self.config["username"]
+ if not user:
+ await self.invoke("config", "lastfm", message=message)
+ return await utils.answer(message, self.strings["nick_error"])
+
+ try:
+ url = f'http://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&nowplaying=true&user={user}&api_key=460cda35be2fbf4f28e8ea7a38580730&format=json'
+ data = requests.get(url).json()
+ track = next((t for t in data.get('recenttracks', {}).get('track', []) if t.get('@attr', {}).get('nowplaying')), None)
+ if not track:
+ return await utils.answer(message, self.strings["no_track"])
+ name = track.get('name', 'Unknown')
+ artist = track.get('artist', {}).get('#text', 'Unknown')
+ caption = self.config["custom_text"].format(song_artist=artist, song_name=name)
+ imgs = track.get('image', [])
+ cov_url = next((i['#text'] for i in imgs if i['size'] == 'extralarge'), imgs[-1]['#text'] if imgs else None)
+
+ if not cov_url:
+ return await utils.answer(message, caption)
+ msg = await utils.answer(message, self.strings["uploading"])
+ cov_bytes = await utils.run_sync(requests.get, cov_url)
+ banners = Banners(name, artist, cov_bytes.content, self.config["font"])
+ file = await utils.run_sync(getattr(banners, self.config["banner_version"]))
+ await utils.answer(msg, caption, file=file)
+
+ except Exception as e:
+ await utils.answer(message, f"{e}
")
diff --git a/radiocycle/Modules/Neofetch.py b/radiocycle/Modules/Neofetch.py
new file mode 100644
index 0000000..3301c1e
--- /dev/null
+++ b/radiocycle/Modules/Neofetch.py
@@ -0,0 +1,48 @@
+# =======================================
+# _ __ __ __ _
+# | |/ /___ | \/ | ___ __| |___
+# | ' // _ \ | |\/| |/ _ \ / _` / __|
+# | . \ __/ | | | | (_) | (_| \__ \
+# |_|\_\___| |_| |_|\___/ \__,_|___/
+# @ke_mods
+# =======================================
+#
+# LICENSE: CC BY-ND 4.0 (Attribution-NoDerivatives 4.0 International)
+# --------------------------------------
+# https://creativecommons.org/licenses/by-nd/4.0/legalcode
+# =======================================
+
+# meta developer: @ke_mods
+
+import subprocess
+from .. import loader, utils
+
+@loader.tds
+class NeofetchMod(loader.Module):
+ strings = {
+ "name": "Neofetch",
+ "not_installed": "Please, install Neofetch package",
+ }
+
+ strings_ru = {
+ "not_installed": "Пожалуйста, установите пакет Neofetch",
+ }
+
+ strings_ua = {
+ "not_installed": "Будь ласка, встановіть пакет Neofetch",
+ }
+
+ @loader.command(
+ ru_doc="- запустить команду neofetch",
+ ua_doc="- запустити команду neofetch",
+ )
+ async def neofetchcmd(self, message):
+ """- run neofetch command"""
+ try:
+ result = subprocess.run(["neofetch", "--stdout"], capture_output=True, text=True)
+ output = result.stdout
+ await utils.answer(message, f"{utils.escape_html(output)}")
+
+ except FileNotFoundError:
+ await utils.answer(message, self.strings("not_installed"))
+
diff --git a/radiocycle/Modules/PicToStories.py b/radiocycle/Modules/PicToStories.py
new file mode 100644
index 0000000..84bbd9a
--- /dev/null
+++ b/radiocycle/Modules/PicToStories.py
@@ -0,0 +1,211 @@
+# =======================================
+# _ __ __ __ _
+# | |/ /___ | \/ | ___ __| |___
+# | ' // _ \ | |\/| |/ _ \ / _` / __|
+# | . \ __/ | | | | (_) | (_| \__ \
+# |_|\_\___| |_| |_|\___/ \__,_|___/
+# @ke_mods
+# =======================================
+#
+# LICENSE: CC BY-ND 4.0 (Attribution-NoDerivatives 4.0 International)
+# --------------------------------------
+# https://creativecommons.org/licenses/by-nd/4.0/legalcode
+# =======================================
+
+# meta developer: @ke_mods
+# requires: pillow
+
+import io
+import asyncio
+
+from telethon import functions, types
+from PIL import Image
+
+from .. import loader, utils
+
+
+@loader.tds
+class PicToStoriesMod(loader.Module):
+ """Grid for stories"""
+
+ strings = {
+ "name": "PicToStories",
+ "no_rep": (
+ "❗️ "
+ "Reply to photo!"
+ ),
+ "work": (
+ "🕔 "
+ "Processing..."
+ ),
+ "done": (
+ "✅ "
+ "Done! Check your profile."
+ ),
+ "err": (
+ "❌ "
+ "Error: {}"
+ ),
+ }
+
+ strings_ru = {
+ "no_rep": (
+ "❗️ "
+ "Реплай на фото!"
+ ),
+ "work": (
+ "🕔 "
+ "Обрабатываю..."
+ ),
+ "done": (
+ "✅ "
+ "Готово! Проверяй профиль."
+ ),
+ "err": (
+ "❌ "
+ "Ошибка: {}"
+ ),
+ }
+
+ def __init__(self):
+ self.config = loader.ModuleConfig(
+ loader.ConfigValue(
+ "period",
+ 48,
+ lambda: "Visibility period in hours",
+ validator=loader.validators.Integer(),
+ ),
+ loader.ConfigValue(
+ "blacklist",
+ [],
+ lambda: "Blacklisted user IDs",
+ validator=loader.validators.Series(loader.validators.Integer()),
+ ),
+ loader.ConfigValue(
+ "cooldown",
+ 0,
+ lambda: "Cooldown between stories in seconds",
+ validator=loader.validators.Integer(minimum=0),
+ ),
+ )
+
+ @loader.command(ru_doc="<реплай на фото> [название альбома] - сделать сетку")
+ async def ptscmd(self, message):
+ """ [album name] - make grid"""
+ args = utils.get_args_raw(message)
+ reply = await message.get_reply_message()
+ if not reply or not reply.media:
+ await utils.answer(message, self.strings("no_rep"))
+ return
+
+ try:
+ image_bytes = await reply.download_media(file=bytes)
+ img = Image.open(io.BytesIO(image_bytes))
+ except Exception as e:
+ await utils.answer(message, self.strings("err").format(e))
+ return
+
+ await utils.answer(message, self.strings("work"))
+
+ w, h = img.size
+ curr_ratio = w / h
+ variants = [
+ (5 / 4, 2),
+ (4 / 5, 3),
+ (3 / 5, 4),
+ (9 / 16, 5)
+ ]
+ best_ratio, rows = min(variants, key=lambda x: abs(curr_ratio - x[0]))
+
+ new_h = int(w / best_ratio)
+ img = img.resize((w, new_h), Image.LANCZOS)
+ w, h = img.size
+
+ parts = []
+ pw, ph = w // 3, h // rows
+ for r in range(rows):
+ for c in range(3):
+ x, y = c * pw, r * ph
+ parts.append(img.crop((x, y, x + pw, y + ph)))
+
+ parts.reverse()
+
+ privacy = [types.InputPrivacyValueAllowAll()]
+ if self.config["blacklist"]:
+ entities = []
+ for uid in self.config["blacklist"]:
+ try:
+ entities.append(await self.client.get_input_entity(uid))
+ except Exception:
+ continue
+ if entities:
+ privacy.append(types.InputPrivacyValueDisallowUsers(users=entities))
+
+ story_ids = []
+ for i, p in enumerate(parts):
+ out = io.BytesIO()
+ p.save(out, "JPEG", quality=95)
+ out.seek(0)
+
+ uploaded_file = await self.client.upload_file(out, file_name="s.jpg")
+ res = await self.client(
+ functions.stories.SendStoryRequest(
+ peer=types.InputPeerSelf(),
+ media=types.InputMediaUploadedPhoto(uploaded_file),
+ privacy_rules=privacy,
+ period=self.config["period"] * 3600,
+ )
+ )
+
+ sid = next(
+ (
+ u.story_id if hasattr(u, "story_id") else u.id
+ for u in res.updates
+ if hasattr(u, "story_id") or hasattr(u, "id")
+ ),
+ None,
+ )
+
+ if sid:
+ story_ids.append(sid)
+
+ if self.config["cooldown"] > 0 and i < len(parts) - 1:
+ await asyncio.sleep(self.config["cooldown"])
+
+ if not story_ids:
+ return
+
+ if args:
+ all_albums = await self.client(
+ functions.stories.GetAlbumsRequest(peer=types.InputPeerSelf(), hash=0)
+ )
+
+ target = next(
+ (a for a in all_albums.albums if getattr(a, 'title', '') == args),
+ None
+ )
+
+ if target:
+ await self.client(
+ functions.stories.UpdateAlbumRequest(
+ peer=types.InputPeerSelf(),
+ album_id=target.album_id,
+ add_stories=story_ids,
+ )
+ )
+ else:
+ await self.client(
+ functions.stories.CreateAlbumRequest(
+ peer=types.InputPeerSelf(),
+ stories=story_ids,
+ title=args,
+ )
+ )
+ else:
+ await self.client(
+ functions.stories.TogglePinnedRequest(
+ peer=types.InputPeerSelf(), id=story_ids, pinned=True
+ )
+ )
+
+ await utils.answer(message, self.strings("done"))
\ No newline at end of file
diff --git a/radiocycle/Modules/SpotifyMod.py b/radiocycle/Modules/SpotifyMod.py
new file mode 100644
index 0000000..7834a05
--- /dev/null
+++ b/radiocycle/Modules/SpotifyMod.py
@@ -0,0 +1 @@
+# License Violation. Original module has GPL-v3 but author of nowhere tried to change it to CC BY-ND 4.0
diff --git a/radiocycle/Modules/UnbanAll.py b/radiocycle/Modules/UnbanAll.py
new file mode 100644
index 0000000..922fc3d
--- /dev/null
+++ b/radiocycle/Modules/UnbanAll.py
@@ -0,0 +1,74 @@
+# =======================================
+# _ __ __ __ _
+# | |/ /___ | \/ | ___ __| |___
+# | ' // _ \ | |\/| |/ _ \ / _` / __|
+# | . \ __/ | | | | (_) | (_| \__ \
+# |_|\_\___| |_| |_|\___/ \__,_|___/
+# @ke_mods
+# =======================================
+#
+# LICENSE: CC BY-ND 4.0 (Attribution-NoDerivatives 4.0 International)
+# --------------------------------------
+# https://creativecommons.org/licenses/by-nd/4.0/legalcode
+# =======================================
+
+# meta developer: @ke_mods
+
+from .. import loader, utils
+from telethon.tl.types import ChatBannedRights
+from telethon.tl.functions.channels import EditBannedRequest
+from telethon.tl.types import ChannelParticipantsKicked
+
+@loader.tds
+class UnbanAllMod(loader.Module):
+ strings = {
+ "name": "UnbanAll",
+ "no_rights": "❌ I don't have administrator rights to remove restrictions.",
+ "success": "✅ All banned chat members have been unbanned.",
+ "unban_in_process": "👀 Unbanning users...",
+ "no_banned": "ℹ️ There are no banned members in this chat.",
+ "error_occured": "💢 An error occurred while unbanning user {}:\n{}",
+ }
+ strings_ru = {
+ "no_rights": "❌ У меня нет прав администратора для снятия ограничений.",
+ "success": "✅ Все забаненные участники чата были разблокированы.",
+ "unban_in_process": "👀 Разбаниваю пользователей...",
+ "no_banned": "ℹ️ В этом чате нет забаненных участников.",
+ "error_occured": "💢 Произошла ошибка при разблокировке пользователя {}:\n{}",
+ }
+
+ @loader.command(ru_doc="- Разбанить всех забаненных пользователей")
+ async def unbanallcmd(self, message):
+ """- Unban all banned users"""
+ chat = await message.get_chat()
+
+ if not chat.admin_rights and not chat.creator:
+ await utils.answer(message, self.strings("no_rights"))
+ return
+
+ await utils.answer(message, self.strings("unban_in_process"))
+
+ no_banned = True
+
+ async for user in self.client.iter_participants(
+ message.chat_id, filter=ChannelParticipantsKicked
+ ):
+
+ no_banned = False
+
+ try:
+ await self.client(EditBannedRequest(
+ message.chat_id,
+ user.id,
+ ChatBannedRights(until_date=0)
+ ))
+
+ except Exception as e:
+ await utils.answer(message, self.strings("error_occured").format(user.id, e))
+ pass
+
+ if no_banned:
+ await utils.answer(message, self.strings("no_banned"))
+ return
+
+ await utils.answer(message, self.strings("success"))
diff --git a/radiocycle/Modules/full.txt b/radiocycle/Modules/full.txt
new file mode 100644
index 0000000..699e87a
--- /dev/null
+++ b/radiocycle/Modules/full.txt
@@ -0,0 +1,7 @@
+Neofetch
+randomanimepic
+SpotifyMod
+UnbanAll
+voicetotext
+LastFm
+PicToStories
diff --git a/radiocycle/Modules/randomanimepic.py b/radiocycle/Modules/randomanimepic.py
new file mode 100644
index 0000000..aa7766b
--- /dev/null
+++ b/radiocycle/Modules/randomanimepic.py
@@ -0,0 +1,65 @@
+# =======================================
+# _ __ __ __ _
+# | |/ /___ | \/ | ___ __| |___
+# | ' // _ \ | |\/| |/ _ \ / _` / __|
+# | . \ __/ | | | | (_) | (_| \__ \
+# |_|\_\___| |_| |_|\___/ \__,_|___/
+# @ke_mods
+# =======================================
+#
+# LICENSE: CC BY-ND 4.0 (Attribution-NoDerivatives 4.0 International)
+# --------------------------------------
+# https://creativecommons.org/licenses/by-nd/4.0/legalcode
+# =======================================
+
+# meta developer: @ke_mods
+
+import requests
+import asyncio
+import logging
+import traceback
+from logging import basicConfig
+from .. import loader, utils
+
+basicConfig(level=logging.INFO)
+logger = logging.getLogger(__name__)
+
+@loader.tds
+class RandomAnimePicMod(loader.Module):
+ strings = {
+ "name": "RandomAnimePic",
+ "img": "✅ Your anime pic\n🔗 URL: {}",
+ "loading": "✨ Loading image...",
+ "error": "🚫 An unexpected error occurred...",
+ }
+
+ strings_ru = {
+ "img": "✅ Ваша аниме-картинка\n🔗 Ссылка: {}",
+ "loading": "✨ Загрузка изображения...",
+ "error": "🚫 Произошла непредвиденная ошибка...",
+ }
+
+ @loader.command(
+ ru_doc="- получить рандомную аниме-картинку 👀"
+ )
+ async def rapiccmd(self, message):
+ """- fetch random anime-pic 👀"""
+
+ await utils.answer(message, self.strings("loading"))
+
+ try:
+ res = requests.get("https://api.nekosia.cat/api/v1/images/cute?count=1")
+ res.raise_for_status()
+ data = res.json()
+ image_url = data['image']['original']['url']
+
+ await asyncio.sleep(2)
+
+ await utils.answer(message, self.strings("img").format(image_url), file=image_url, reply_to=message.reply_to_msg_id)
+
+ except Exception:
+ logger.error("Error fetching random anime pic: %s", traceback.format_exc())
+
+ await utils.answer(message, self.strings("error"))
+
+ await asyncio.sleep(5)
diff --git a/radiocycle/Modules/voicetotext.py b/radiocycle/Modules/voicetotext.py
new file mode 100644
index 0000000..0e4cfd5
--- /dev/null
+++ b/radiocycle/Modules/voicetotext.py
@@ -0,0 +1,77 @@
+# =======================================
+# _ __ __ __ _
+# | |/ /___ | \/ | ___ __| |___
+# | ' // _ \ | |\/| |/ _ \ / _` / __|
+# | . \ __/ | | | | (_) | (_| \__ \
+# |_|\_\___| |_| |_|\___/ \__,_|___/
+# @ke_mods
+# =======================================
+#
+# LICENSE: CC BY-ND 4.0 (Attribution-NoDerivatives 4.0 International)
+# --------------------------------------
+# https://creativecommons.org/licenses/by-nd/4.0/legalcode
+# =======================================
+
+# meta developer: @ke_mods
+# scope: ffmpeg
+# requires: pydub SpeechRecognition
+
+from .. import loader, utils
+import os
+import speech_recognition as sr
+from pydub import AudioSegment
+
+@loader.tds
+class VoiceToTextMod(loader.Module):
+ strings = {
+ "name": "VoiceToText",
+ "process_text": "✨ Recognizing the message text...",
+ "vtt_success": "🔥 Recognized text:\n{}
",
+ "vtt_failure": "🚫 Failed to recognize the message.",
+ "vtt_request_error": "🚫 Error when contacting the recognition service:\n{}",
+ "vtt_invalid": "🚫 Please reply to a voice or video message with the command {}vtt",
+ "vtt_successful": "✅ Text recognized successfully",
+ }
+
+ strings_ru = {
+ "process_text": "✨ Распознаю текст сообщения...",
+ "vtt_success": "🔥 Распознанный текст:\n{}
",
+ "vtt_failure": "🚫 Не удалось распознать сообщение.",
+ "vtt_request_error": "🚫 Ошибка при обращении к сервису распознавания:\n{}",
+ "vtt_invalid": "🚫 Пожалуйста, ответьте на голосовое или видеосообщение командой {}vtt",
+ "vtt_successful": "✅ Текст успешно распознан",
+ }
+
+ @loader.command(
+ ru_doc="- распознает текст из голосового или видеосообщения.",
+ )
+ async def vttcmd(self, message):
+ """- recognizes text from voice or video messages."""
+ reply = await message.get_reply_message()
+
+ if not reply or not (reply.voice or reply.video_note):
+ await utils.answer(message, self.strings["vtt_invalid"].format(self.get_prefix()))
+ return
+
+ msg = await utils.answer(
+ message, self.strings["process_text"], reply_to=message.id
+ )
+
+ media_file = await reply.download_media()
+ wav_file = media_file.replace('.mp4', '.wav') if reply.video_note else media_file.replace('.oga', '.wav')
+
+ try:
+ AudioSegment.from_file(media_file).export(wav_file, format='wav')
+ recognizer = sr.Recognizer()
+ with sr.AudioFile(wav_file) as source:
+ audio_data = recognizer.record(source)
+ try:
+ text = recognizer.recognize_google(audio_data, language='ru-RU')
+ await utils.answer(msg, self.strings["vtt_success"].format(text))
+ except sr.UnknownValueError:
+ await utils.answer(msg, self.strings["vtt_failure"])
+ except sr.RequestError as e:
+ await utils.answer(msg, self.strings["vtt_request_error"].format(e))
+ finally:
+ os.remove(media_file)
+ os.remove(wav_file)
diff --git a/yummy1gay/limoka/yg_quotes.py b/yummy1gay/limoka/yg_quotes.py
index b2f6231..28f89ac 100644
--- a/yummy1gay/limoka/yg_quotes.py
+++ b/yummy1gay/limoka/yg_quotes.py
@@ -1,396 +1,409 @@
-__version__ = (1, 1, 1, 1)
-
-# This file is a part of Hikka Userbot!
-# This product includes software developed by t.me/Fl1yd and t.me/spypm.
-# Based on the "SQuotes" module.
-
-# 🌐 https://github.com/hikariatama/Hikka
-
-# You CAN edit this file without direct permission from the author.
-# You can redistribute this file with any modifications.
-
-# thx to t.me/LyoSU for github.com/LyoSU/quote-api
-
-# meta developer: @yg_modules
-# scope: hikka_only
-# scope: hikka_min 1.6.3
-
-# █▄█ █░█ █▀▄▀█ █▀▄▀█ █▄█ █▀▄▀█ █▀█ █▀▄ █▀
-# ░█░ █▄█ █░▀░█ █░▀░█ ░█░ █░▀░█ █▄█ █▄▀ ▄█
-
-import base64, io, requests, telethon
-from time import gmtime
-from typing import List, Optional, Tuple, Union
-from PIL import Image, ImageDraw
-from telethon.tl import types
-from telethon.extensions import html
-from telethon.tl.patched import Message
-
-from .. import loader, utils
-
-class Dick:
- @staticmethod
- def ents(es: types.TypeMessageEntity) -> List[dict]:
- out: List[dict] = []
- if not es: return out
- for e in es:
- try:
- d = e.to_dict(); t = d.pop("_","").replace("MessageEntity","").lower()
- if not t: continue
- mt = {"bold": "bold","italic": "italic","underline": "underline","strikethrough": "strikethrough",
- "code": "code","pre": "pre","texturl": "text_link","url": "url","email": "email",
- "phone": "phone_number","mention": "mention",
- "mentionname": "text_mention","hashtag": "hashtag","cashtag": "cashtag",
- "botcommand": "bot_command","spoiler": "spoiler","customemoji": "custom_emoji"}.get(t,t)
- it = {"type": mt,"offset": d.get("offset",0),"length": d.get("length",0)}
- if t=="texturl": it["url"]=d.get("url","")
- elif t=="mentionname": it["user"]={"id": d.get("user_id",0)}
- elif t=="customemoji": it["custom_emoji_id"]=str(d.get("document_id",""))
- elif t=="pre": it["language"]=d.get("language","")
- out.append(it)
- except Exception: continue
- return out
-
- @staticmethod
- def dur(s: Union[int,float]) -> str:
- t=gmtime(s); return (f"{t.tm_hour:02d}:" if t.tm_hour>0 else "")+f"{t.tm_min:02d}:{t.tm_sec:02d}"
-
- @staticmethod
- def desc(m: Message, rep: bool=False) -> str:
- return (
- "📷 Фото" if m.photo and rep else
- (m.file.emoji+" Стикер") if m.sticker and rep else
- "📹 Видеосообщение" if m.video_note and rep else
- "📹 Видео" if m.video and rep else
- "🖼 GIF" if m.gif else
- "📊 Опрос" if m.poll else
- "📍 Местоположение" if m.geo else
- "👤 Контакт" if m.contact else
- (f"🎵 Голосовое сообщение: {Dick.dur(m.voice.attributes[0].duration)}" if m.voice else
- (f"🎧 Музыка: {Dick.dur(m.audio.attributes[0].duration)} | {m.audio.attributes[0].performer} - {m.audio.attributes[0].title}" if m.audio else
- (f"💾 Файл: {m.file.name}" if isinstance(m.media, types.MessageMediaDocument) and not Dick.pick(m) else
- (f"{m.media.emoticon} Кость: {m.media.value}" if isinstance(m.media, types.MessageMediaDice) else
- (f"Сервисное сообщение: {m.action.to_dict().get('_')}" if isinstance(m, types.MessageService) else "")))))) #)))
-
- @staticmethod
- def split(name: Optional[str]) -> Tuple[str,str]:
- if not name: return "",""
- p=name.split(); return (p[0], " ".join(p[1:]) if len(p)>1 else "")
-
- @staticmethod
- def pick(m: Message):
- if m and m.media:
- return m.photo or m.sticker or m.video or m.video_note or m.gif or m.web_preview
- return None
-
- @staticmethod
- def wf(b: Optional[bytes]) -> List[int]:
- if not b: return []
- n=(len(b)*8)//5
- if not n: return []
- out: List[int]=[]
- last=n-1
- for i in range(last):
- j=i*5; bi,sh=j//8,j%8
- v=int.from_bytes(b[bi:bi+2],"little") if bi+1>sh)&0b11111)
- j=last*5; bi,sh=j//8,j%8
- v=int.from_bytes(b[bi:bi+2],"little") if bi+1>sh)&0b11111)
- return out
-
- @staticmethod
- async def img(b: bytes, circle: bool=False) -> Optional[str]:
- try:
- im=Image.open(io.BytesIO(b))
- if im.mode!="RGBA": im=im.convert("RGBA")
- if circle:
- size=min(im.size)
- mask=Image.new("L",(size,size),0); ImageDraw.Draw(mask).ellipse((0,0,size,size),fill=255)
- sq=Image.new("RGBA",(size,size),(0,0,0,0))
- off=((size-im.width)//2,(size-im.height)//2); sq.paste(im,off)
- im=Image.composite(sq,Image.new("RGBA",(size,size),(0,0,0,0)),mask)
- o=io.BytesIO(); im.save(o,format="PNG")
- return f"data:image/png;base64,{base64.b64encode(o.getvalue()).decode()}"
- except Exception:
- return None
-
- @staticmethod
- async def stc(b: bytes) -> Optional[str]:
- try:
- im=Image.open(io.BytesIO(b))
- if im.mode not in ("RGBA","LA"): im=im.convert("RGBA")
- elif im.mode=="LA": im=im.convert("RGBA")
- o=io.BytesIO(); im.save(o,format="PNG")
- return f"data:image/png;base64,{base64.b64encode(o.getvalue()).decode()}"
- except Exception:
- return None
-
- @staticmethod
- async def proc(cli, obj, m: Message) -> Optional[dict]:
- try:
- if m.voice:
- for a in m.voice.attributes or []:
- if getattr(a,"voice",False) and hasattr(a,"waveform"):
- return {"voice":{"waveform":Dick.wf(a.waveform)}}
- b: bytes = await cli.download_media(obj, bytes, thumb=-1)
- if not b: return None
- if m.sticker:
- u=await Dick.stc(b); return {"url": u} if u else None
- u=await Dick.img(b, circle=bool(m.video_note))
- return {"url": u} if u else None
- except Exception:
- return None
-
- @staticmethod
- async def ava(cli, uid: int) -> Optional[str]:
- try:
- b=await cli.download_profile_photo(uid, bytes)
- if b: return f"data:image/jpeg;base64,{base64.b64encode(b).decode()}"
- except Exception: pass
- return None
-
- @staticmethod
- async def post(url: str, data: dict):
- try:
- return await utils.run_sync(requests.post, url, json=data, timeout=30)
- except Exception:
- return None
-
-@loader.tds
-class Quotes(loader.Module):
- """Модуль для создания цитат из сообщений"""
-
- strings = {"name": "yg_quotes",
- "no_reply": "🏳️🌈 Нет реплая на сообщение",
- "processing": "🏳️🌈 Обработка…",
- "api_processing": "🏳️🌈 Ожидание ответа API…",
- "api_error": "🏳️🌈 Ошибка API: {}",
- "loading_media": "🏳️🌈 Отправка…",
- "no_args_or_reply": "🏳️🌈 Нет аргументов или реплая",
- "args_error": "🏳️🌈 Ошибка разбора аргументов. Запрос: {}",
- "too_many_messages": "🏳️🌈 Слишком много сообщений. Максимум: {}"}
-
- def __init__(self):
- self.config=loader.ModuleConfig(
- loader.ConfigValue("type","quote",
- lambda:"Тип цитаты",
- validator=loader.validators.Choice(["quote", "stories"])),
- loader.ConfigValue("bg_color","#162330",
- lambda:"Цвет фона цитаты (например, #1a1a1a или red)"),
- loader.ConfigValue("width",512,
- lambda:"Ширина цитаты (px)",
- validator=loader.validators.Integer(minimum=200,maximum=2000)),
- loader.ConfigValue("height",768,
- lambda:"Высота цитаты (px)",
- validator=loader.validators.Integer(minimum=200,maximum=2000)),
- loader.ConfigValue("scale",2,
- lambda:"Масштаб рендера",
- validator=loader.validators.Choice([1, 2, 3])),
- loader.ConfigValue("emoji_brand","apple",
- lambda:"Стиль эмодзи (apple, google, twitter и т.д.)"),
- loader.ConfigValue("max_messages",15,
- lambda:"Максимальное число сообщений в цитате",
- validator=loader.validators.Integer(minimum=1,maximum=50)),
- loader.ConfigValue("endpoint","https://kok.gay/gayotes/generate",
- lambda:"URL API-эндпоинта (можешь поднять локально - github.com/yummy1gay/quote-api)",
- validator=loader.validators.Link()))
-
- async def client_ready(self, client, db):
- self.client=client; self.db=db
-
- async def qcmd(self, m: Message):
- """
- Обычные цитаты:
- • .q — процитировать одно сообщение из реплая
- • .q 2 — процитировать 2 сообщения
- • .q 3 #2d2d2d — 3 сообщения на тёмном фоне
- • .q pink — фон по имени цвета
- • .q !file — отправить как файл (PNG)
- """
- try:
- args=utils.get_args(m); rep=await m.get_reply_message()
- if not rep: return await utils.answer(m,self.strings["no_reply"])
- st=await utils.answer(m,self.strings["processing"])
- doc="!file" in args
- n=next((int(a) for a in args if a.isdigit() and int(a)>0),1)
- bg=next((a for a in args if a!="!file" and not a.isdigit()), self.config["bg_color"])
- if n>self.config["max_messages"]:
- return await utils.answer(st,self.strings["too_many_messages"].format(self.config["max_messages"]))
-
- js=await self.parse(m,n)
- if not js: return await utils.answer(st,self.strings["api_error"].format("Не удалось собрать сообщения"))
-
- pay={"backgroundColor":bg,"width":self.config["width"],"height":self.config["height"],
- "scale":self.config["scale"],"emojiBrand":self.config["emoji_brand"],"messages":js,
- "format": "webp" if not doc else "png", "type": self.config["type"]}
-
- await utils.answer(st,self.strings["api_processing"])
- r=await Dick.post(f"{self.config['endpoint']}.webp",pay)
- if not r or r.status_code!=200:
- try: err=r.json().get("error",f"HTTP {r.status_code}") if r else "Нетворк еррорь"
- except Exception: err=f"HTTP {r.status_code}" if r else "Нетворк еррорь"
- return await utils.answer(st,self.strings["api_error"].format(err))
-
- buf=io.BytesIO(r.content); buf.name="YgQuote"+(".png" if doc else ".webp")
- await utils.answer(st,buf,force_document=doc)
- except Exception as e:
- return await utils.answer(m,f"🏳️🌈 Ошибка: {e}")
-
- async def fqcmd(self, m: Message):
- """
- Фейковые цитаты:
- • .fq <@ или ID> <текст> — цитата от пользователя
- • .fq <текст> — цитата от автора реплая
- • .fq <@/ID> <текст> -r <@/ID> <текст> — с ответом
- • .fq user1 текст; user2 текст — несколько сообщений
- """
- try:
- raw=utils.get_args_html(m); rep=await m.get_reply_message()
- if not (raw or rep): return await utils.answer(m,self.strings["no_args_or_reply"])
- st= await utils.answer(m,self.strings["processing"])
- try: js=await self.fake(raw,rep)
- except (IndexError,ValueError): return await utils.answer(st,self.strings["args_error"].format(m.text))
- if len(js)>self.config["max_messages"]:
- return await utils.answer(st,self.strings["too_many_messages"].format(self.config["max_messages"]))
-
- dickk={"backgroundColor":self.config["bg_color"],"width":self.config["width"],"height":self.config["height"],
- "scale":self.config["scale"],"emojiBrand":self.config["emoji_brand"],"messages":js,
- "format": "webp","type":self.config["type"]}
-
- await utils.answer(st,self.strings["api_processing"])
- r=await Dick.post(f"{self.config['endpoint']}.webp",dickk)
- if not r or r.status_code!=200:
- try: err=r.json().get("error",f"HTTP {r.status_code}") if r else "Нетворк еррорь"
- except Exception: err=f"HTTP {r.status_code}" if r else "Нетворк еррорь"
- return await utils.answer(st,self.strings["api_error"].format(err))
-
- buf=io.BytesIO(r.content); buf.name="YgQuote.webp"
- await utils.answer(st,buf)
- except Exception as e:
- return await utils.answer(m,f"🏳️🌈 Ошибка: {e}")
-
- async def parse(self, trg: Message, n: int) -> Optional[List[dict]]:
- try:
- rep= await trg.get_reply_message()
- lst: List[Message]=[mm async for mm in self.client.iter_messages(trg.chat_id,limit=n,reverse=True,add_offset=1,offset_id=rep.id if rep else None)]
- except Exception:
- return None
-
- out: List[dict]=[]
- for mm in lst:
- try:
- u=await self.who(mm)
- if not u: continue
- name=telethon.utils.get_display_name(u); f,l=Dick.split(name)
- ava=await Dick.ava(self.client,getattr(u,"id",0)) if getattr(u,"id",None) else None
-
- rb=None
- try:
- r=await mm.get_reply_message()
- if r:
- rname=telethon.utils.get_display_name(r.sender)
- rtxt=Dick.desc(r,True)
- if r.raw_text: rtxt=(rtxt+". "+r.raw_text) if rtxt else r.raw_text
- rb={"name":rname,"text":rtxt or "","entities":Dick.ents(r.entities),
- "chatId": r.sender.id if r.sender else mm.chat_id,"from":{"name":rname}}
- except Exception: rb=None
-
- med=None; obj=Dick.pick(mm)
- if obj: med=await Dick.proc(self.client,obj,mm)
-
- txt=mm.raw_text or ""; ad=Dick.desc(mm)
- if ad: txt=f"{txt}\n\n{ad}" if txt else ad
-
- item={"from":{"id":getattr(u,"id", 0),"first_name":getattr(u,"first_name","") or f,"last_name":getattr(u,"last_name","") or l,
- "username":getattr(u,"username",None),"name":name,"photo":{"url":ava} if ava else {}},
- "text":txt,"entities":Dick.ents(mm.entities),"avatar":True}
-
- try:
- if mm.voice:
- a = next((a for a in mm.voice.attributes or []
- if getattr(a, "voice", False) and hasattr(a, "waveform")), None)
- if a: item["voice"] = {"waveform": Dick.wf(a.waveform)}
- except Exception: pass
-
- if med: item["voice" if "voice" in med else "media"] = med.get("voice", med)
-
- es=getattr(u,"emoji_status",None)
- if getattr(es,"document_id",None): item["from"]["emoji_status"]=str(es.document_id)
- if rb: item["replyMessage"]=rb
- out.append(item)
- except Exception: continue
- return out
-
- async def who(self, m: Message):
- try:
- if m.fwd_from:
- if m.fwd_from.from_id:
- pid=m.fwd_from.from_id
- uid=pid.channel_id if isinstance(pid, types.PeerChannel) else pid.user_id
- try: return await self.client.get_entity(uid)
- except Exception: return m.sender
- if m.fwd_from.from_name:
- return types.User(
- id=hash(m.fwd_from.from_name)%2147483647, first_name=m.fwd_from.from_name,
- username=None, phone=None, bot=False, verified=False, restricted=False,
- scam=False, fake=False, premium=False)
- return m.sender
- except Exception:
- return m.sender
-
- async def fake(self, args: str, rep: Optional[Message]) -> List[dict]:
- async def tok(ch: str):
- p=ch.split()
- if not p: return None,""
- who=p[0]; tx=ch.split(maxsplit=1)[1] if len(p)>1 else ""
- try:
- u=await self.client.get_entity(int(who) if who.isdigit() else who)
- return u,tx
- except Exception:
- return None,tx
-
- if rep and not args:
- u=rep.sender; name=telethon.utils.get_display_name(u); f,l=Dick.split(name)
- ava=await Dick.ava(self.client,u.id) if getattr(u,"id",None) else None
- msg={"from":{"id":u.id,"first_name":getattr(u,"first_name","") or f,"last_name":getattr(u,"last_name","") or l,
- "username":getattr(u,"username",None),"name":name,"photo":{"url":ava} if ava else {}},
- "text":"","entities":[], "avatar":True}
- es=getattr(u,"emoji_status",None)
- if getattr(es,"document_id", None): msg["from"]["emoji_status"]=str(es.document_id)
- return [msg]
-
- if rep and args:
- u=rep.sender
- return await self.fake(f"{getattr(u,'id','')} {args}", None)
-
- out: List[dict]=[]
- for part in args.split("; "):
- try:
- rb=None
- if " -r " in part:
- a,b=part.split(" -r ",1); u1,t1=await tok(a); u2,t2=await tok(b)
- else:
- u1,t1=await tok(part); u2,t2=None,None
- if not u1: continue
-
- txt1, ents1 = html.parse(t1) if t1 else ("", [])
-
- name=telethon.utils.get_display_name(u1); f,l=Dick.split(name)
- ava=await Dick.ava(self.client,u1.id)
-
- if u2:
- txt2, ents2 = html.parse(t2) if t2 else ("", [])
- name2=telethon.utils.get_display_name(u2); ava2=await Dick.ava(self.client,u2.id)
- rb={"name":name2,"text":txt2,"entities":Dick.ents(ents2),"chatId":u2.id,"from":{"name":name2,"photo":{"url":ava2} if ava2 else {}}}
-
- msg={"from":{"id":u1.id,"first_name":getattr(u1,"first_name","") or f,"last_name":getattr(u1,"last_name","") or l,
- "username":getattr(u1,"username",None),"name":name,"photo":{"url":ava} if ava else {}},
- "text":txt1,"entities":Dick.ents(ents1), "avatar":True}
-
- es=getattr(u1,"emoji_status",None)
- if getattr(es,"document_id",None): msg["from"]["emoji_status"]=str(es.document_id)
- if rb: msg["replyMessage"]=rb
- out.append(msg)
- except Exception: continue
+__version__ = (1, 2, 0, 0)
+
+# This file is a part of Hikka Userbot!
+# This product includes software developed by t.me/Fl1yd and t.me/spypm.
+# Based on the "SQuotes" module.
+
+# 🌐 https://github.com/hikariatama/Hikka
+
+# You CAN edit this file without direct permission from the author.
+# You can redistribute this file with any modifications.
+
+# thx to t.me/LyoSU for github.com/LyoSU/quote-api
+
+# meta developer: @yg_modules
+# scope: hikka_only
+# scope: hikka_min 1.6.3
+
+# Changelog v1.2:
+# - Added: Proxy for users from RF
+# - Fixed: Correct reply author resolving for forwarded messages
+
+# █▄█ █░█ █▀▄▀█ █▀▄▀█ █▄█ █▀▄▀█ █▀█ █▀▄ █▀
+# ░█░ █▄█ █░▀░█ █░▀░█ ░█░ █░▀░█ █▄█ █▄▀ ▄█
+
+import base64, io, requests, telethon
+from time import gmtime
+from typing import List, Optional, Tuple, Union
+from PIL import Image, ImageDraw
+from telethon.tl import types
+from telethon.extensions import html
+from telethon.tl.patched import Message
+
+from .. import loader, utils
+
+class Dick:
+ @staticmethod
+ def ents(es: types.TypeMessageEntity) -> List[dict]:
+ out: List[dict] = []
+ if not es: return out
+ for e in es:
+ try:
+ d = e.to_dict(); t = d.pop("_","").replace("MessageEntity","").lower()
+ if not t: continue
+ mt = {"bold": "bold","italic": "italic","underline": "underline","strikethrough": "strikethrough",
+ "code": "code","pre": "pre","texturl": "text_link","url": "url","email": "email",
+ "phone": "phone_number","mention": "mention",
+ "mentionname": "text_mention","hashtag": "hashtag","cashtag": "cashtag",
+ "botcommand": "bot_command","spoiler": "spoiler","customemoji": "custom_emoji"}.get(t,t)
+ it = {"type": mt,"offset": d.get("offset",0),"length": d.get("length",0)}
+ if t=="texturl": it["url"]=d.get("url","")
+ elif t=="mentionname": it["user"]={"id": d.get("user_id",0)}
+ elif t=="customemoji": it["custom_emoji_id"]=str(d.get("document_id",""))
+ elif t=="pre": it["language"]=d.get("language","")
+ out.append(it)
+ except Exception: continue
+ return out
+
+ @staticmethod
+ def dur(s: Union[int,float]) -> str:
+ t=gmtime(s); return (f"{t.tm_hour:02d}:" if t.tm_hour>0 else "")+f"{t.tm_min:02d}:{t.tm_sec:02d}"
+
+ @staticmethod
+ def desc(m: Message, rep: bool=False) -> str:
+ return (
+ "📷 Фото" if m.photo and rep else
+ (m.file.emoji+" Стикер") if m.sticker and rep else
+ "📹 Видеосообщение" if m.video_note and rep else
+ "📹 Видео" if m.video and rep else
+ "🖼 GIF" if m.gif else
+ "📊 Опрос" if m.poll else
+ "📍 Местоположение" if m.geo else
+ "👤 Контакт" if m.contact else
+ (f"🎵 Голосовое сообщение: {Dick.dur(m.voice.attributes[0].duration)}" if m.voice else
+ (f"🎧 Музыка: {Dick.dur(m.audio.attributes[0].duration)} | {m.audio.attributes[0].performer} - {m.audio.attributes[0].title}" if m.audio else
+ (f"💾 Файл: {m.file.name}" if isinstance(m.media, types.MessageMediaDocument) and not Dick.pick(m) else
+ (f"{m.media.emoticon} Кость: {m.media.value}" if isinstance(m.media, types.MessageMediaDice) else
+ (f"Сервисное сообщение: {m.action.to_dict().get('_')}" if isinstance(m, types.MessageService) else "")))))) #)))
+
+ @staticmethod
+ def split(name: Optional[str]) -> Tuple[str,str]:
+ if not name: return "",""
+ p=name.split(); return (p[0], " ".join(p[1:]) if len(p)>1 else "")
+
+ @staticmethod
+ def pick(m: Message):
+ if m and m.media:
+ return m.photo or m.sticker or m.video or m.video_note or m.gif or m.web_preview
+ return None
+
+ @staticmethod
+ def wf(b: Optional[bytes]) -> List[int]:
+ if not b: return []
+ n=(len(b)*8)//5
+ if not n: return []
+ out: List[int]=[]
+ last=n-1
+ for i in range(last):
+ j=i*5; bi,sh=j//8,j%8
+ v=int.from_bytes(b[bi:bi+2],"little") if bi+1>sh)&0b11111)
+ j=last*5; bi,sh=j//8,j%8
+ v=int.from_bytes(b[bi:bi+2],"little") if bi+1>sh)&0b11111)
+ return out
+
+ @staticmethod
+ async def img(b: bytes, circle: bool=False) -> Optional[str]:
+ try:
+ im=Image.open(io.BytesIO(b))
+ if im.mode!="RGBA": im=im.convert("RGBA")
+ if circle:
+ size=min(im.size)
+ mask=Image.new("L",(size,size),0); ImageDraw.Draw(mask).ellipse((0,0,size,size),fill=255)
+ sq=Image.new("RGBA",(size,size),(0,0,0,0))
+ off=((size-im.width)//2,(size-im.height)//2); sq.paste(im,off)
+ im=Image.composite(sq,Image.new("RGBA",(size,size),(0,0,0,0)),mask)
+ o=io.BytesIO(); im.save(o,format="PNG")
+ return f"data:image/png;base64,{base64.b64encode(o.getvalue()).decode()}"
+ except Exception:
+ return None
+
+ @staticmethod
+ async def stc(b: bytes) -> Optional[str]:
+ try:
+ im=Image.open(io.BytesIO(b))
+ if im.mode not in ("RGBA","LA"): im=im.convert("RGBA")
+ elif im.mode=="LA": im=im.convert("RGBA")
+ o=io.BytesIO(); im.save(o,format="PNG")
+ return f"data:image/png;base64,{base64.b64encode(o.getvalue()).decode()}"
+ except Exception:
+ return None
+
+ @staticmethod
+ async def proc(cli, obj, m: Message) -> Optional[dict]:
+ try:
+ if m.voice:
+ for a in m.voice.attributes or []:
+ if getattr(a,"voice",False) and hasattr(a,"waveform"):
+ return {"voice":{"waveform":Dick.wf(a.waveform)}}
+ b: bytes = await cli.download_media(obj, bytes, thumb=-1)
+ if not b: return None
+ if m.sticker:
+ u=await Dick.stc(b); return {"url": u} if u else None
+ u=await Dick.img(b, circle=bool(m.video_note))
+ return {"url": u} if u else None
+ except Exception:
+ return None
+
+ @staticmethod
+ async def ava(cli, uid: int) -> Optional[str]:
+ try:
+ b=await cli.download_profile_photo(uid, bytes)
+ if b: return f"data:image/jpeg;base64,{base64.b64encode(b).decode()}"
+ except Exception: pass
+ return None
+
+ @staticmethod
+ async def post(url: str, data: dict):
+ try:
+ return await utils.run_sync(requests.post, url, json=data, timeout=30)
+ except Exception:
+ return None
+
+@loader.tds
+class Quotes(loader.Module):
+ """Модуль для создания цитат из сообщений"""
+
+ strings = {"name": "yg_quotes",
+ "no_reply": "🏳️🌈 Нет реплая на сообщение",
+ "processing": "🏳️🌈 Обработка…",
+ "api_processing": "🏳️🌈 Ожидание ответа API…",
+ "api_error": "🏳️🌈 Ошибка API: {}",
+ "loading_media": "🏳️🌈 Отправка…",
+ "no_args_or_reply": "🏳️🌈 Нет аргументов или реплая",
+ "args_error": "🏳️🌈 Ошибка разбора аргументов. Запрос: {}",
+ "too_many_messages": "🏳️🌈 Слишком много сообщений. Максимум: {}"}
+
+ def __init__(self):
+ self.config=loader.ModuleConfig(
+ loader.ConfigValue("type","quote",
+ lambda:"Тип цитаты",
+ validator=loader.validators.Choice(["quote", "stories"])),
+ loader.ConfigValue("bg_color","#162330",
+ lambda:"Цвет фона цитаты (например, #1a1a1a или red)"),
+ loader.ConfigValue("width",512,
+ lambda:"Ширина цитаты (px)",
+ validator=loader.validators.Integer(minimum=200,maximum=2000)),
+ loader.ConfigValue("height",768,
+ lambda:"Высота цитаты (px)",
+ validator=loader.validators.Integer(minimum=200,maximum=2000)),
+ loader.ConfigValue("scale",2,
+ lambda:"Масштаб рендера",
+ validator=loader.validators.Choice([1, 2, 3])),
+ loader.ConfigValue("emoji_brand","apple",
+ lambda:"Стиль эмодзи (apple, google, twitter и т.д.)"),
+ loader.ConfigValue("max_messages",15,
+ lambda:"Максимальное число сообщений в цитате",
+ validator=loader.validators.Integer(minimum=1,maximum=50)),
+ loader.ConfigValue("endpoint","https://kok.gay/gayotes/generate",
+ lambda:"URL API-эндпоинта (можешь поднять локально - github.com/yummy1gay/quote-api)",
+ validator=loader.validators.Link()),
+ loader.ConfigValue("use_rf_proxy", False,
+ lambda:'Включает прокси для РФ, если основной эндпоинт возвращает ошибку "Нетворк еррорь", и при этом сервер с юзерботом находится в России или ты сам сидишь в России с ограниченным доступом к зарубежным ресурсам (Termux / UserLAnd)',
+ validator=loader.validators.Boolean()),
+ loader.ConfigValue("rf_endpoint", "https://ru.kok.gay/gayotes/generate",
+ lambda:"URL API-эндпоинта для РФ",
+ validator=loader.validators.Link()))
+
+ async def client_ready(self, client, db):
+ self.client=client; self.db=db
+
+ async def qcmd(self, m: Message):
+ """
+ Обычные цитаты:
+ • .q — процитировать одно сообщение из реплая
+ • .q 2 — процитировать 2 сообщения
+ • .q 3 #2d2d2d — 3 сообщения на тёмном фоне
+ • .q pink — фон по имени цвета
+ • .q !file — отправить как файл (PNG)
+ """
+ try:
+ args=utils.get_args(m); rep=await m.get_reply_message()
+ if not rep: return await utils.answer(m,self.strings["no_reply"])
+ st=await utils.answer(m,self.strings["processing"])
+ doc="!file" in args
+ n=next((int(a) for a in args if a.isdigit() and int(a)>0),1)
+ bg=next((a for a in args if a!="!file" and not a.isdigit()), self.config["bg_color"])
+ if n>self.config["max_messages"]:
+ return await utils.answer(st,self.strings["too_many_messages"].format(self.config["max_messages"]))
+
+ js=await self.parse(m,n)
+ if not js: return await utils.answer(st,self.strings["api_error"].format("Не удалось собрать сообщения"))
+
+ pay={"backgroundColor":bg,"width":self.config["width"],"height":self.config["height"],
+ "scale":self.config["scale"],"emojiBrand":self.config["emoji_brand"],"messages":js,
+ "format": "webp" if not doc else "png", "type": self.config["type"]}
+
+ await utils.answer(st,self.strings["api_processing"])
+ endpoint=self.config['rf_endpoint'] if self.config['use_rf_proxy'] else self.config['endpoint']
+ r=await Dick.post(f"{endpoint}.webp",pay)
+ if not r or r.status_code!=200:
+ try: err=r.json().get("error",f"HTTP {r.status_code}") if r else "Нетворк еррорь (попробуй включить use_rf_proxy в конфиге)"
+ except Exception: err=f"HTTP {r.status_code}" if r else "Нетворк еррорь (попробуй включить use_rf_proxy в конфиге)"
+ return await utils.answer(st,self.strings["api_error"].format(err))
+
+ buf=io.BytesIO(r.content); buf.name="YgQuote"+(".png" if doc else ".webp")
+ await utils.answer(st,buf,force_document=doc)
+ except Exception as e:
+ return await utils.answer(m,f"🏳️🌈 Ошибка: {e}")
+
+ async def fqcmd(self, m: Message):
+ """
+ Фейковые цитаты:
+ • .fq <@ или ID> <текст> — цитата от пользователя
+ • .fq <текст> — цитата от автора реплая
+ • .fq <@/ID> <текст> -r <@/ID> <текст> — с ответом
+ • .fq user1 текст; user2 текст — несколько сообщений
+ """
+ try:
+ raw=utils.get_args_html(m); rep=await m.get_reply_message()
+ if not (raw or rep): return await utils.answer(m,self.strings["no_args_or_reply"])
+ st= await utils.answer(m,self.strings["processing"])
+ try: js=await self.fake(raw,rep)
+ except (IndexError,ValueError): return await utils.answer(st,self.strings["args_error"].format(m.text))
+ if len(js)>self.config["max_messages"]:
+ return await utils.answer(st,self.strings["too_many_messages"].format(self.config["max_messages"]))
+
+ dickk={"backgroundColor":self.config["bg_color"],"width":self.config["width"],"height":self.config["height"],
+ "scale":self.config["scale"],"emojiBrand":self.config["emoji_brand"],"messages":js,
+ "format": "webp","type":self.config["type"]}
+
+ await utils.answer(st,self.strings["api_processing"])
+ endpoint=self.config['rf_endpoint'] if self.config['use_rf_proxy'] else self.config['endpoint']
+ r=await Dick.post(f"{endpoint}.webp",dickk)
+ if not r or r.status_code!=200:
+ try: err=r.json().get("error",f"HTTP {r.status_code}") if r else "Нетворк еррорь (попробуй включить use_rf_proxy в конфиге)"
+ except Exception: err=f"HTTP {r.status_code}" if r else "Нетворк еррорь (попробуй включить use_rf_proxy в конфиге)"
+ return await utils.answer(st,self.strings["api_error"].format(err))
+
+ buf=io.BytesIO(r.content); buf.name="YgQuote.webp"
+ await utils.answer(st,buf)
+ except Exception as e:
+ return await utils.answer(m,f"🏳️🌈 Ошибка: {e}")
+
+ async def parse(self, trg: Message, n: int) -> Optional[List[dict]]:
+ try:
+ rep= await trg.get_reply_message()
+ lst: List[Message]=[mm async for mm in self.client.iter_messages(trg.chat_id,limit=n,reverse=True,add_offset=1,offset_id=rep.id if rep else None)]
+ except Exception:
+ return None
+
+ out: List[dict]=[]
+ for mm in lst:
+ try:
+ u=await self.who(mm)
+ if not u: continue
+ name=telethon.utils.get_display_name(u); f,l=Dick.split(name)
+ ava=await Dick.ava(self.client,getattr(u,"id",0)) if getattr(u,"id",None) else None
+
+ rb=None
+ try:
+ r=await mm.get_reply_message()
+ if r:
+ ruser = await self.who(r)
+ rname=telethon.utils.get_display_name(ruser)
+ rtxt=Dick.desc(r,True)
+ if r.raw_text: rtxt=(rtxt+". "+r.raw_text) if rtxt else r.raw_text
+ rb={"name":rname,"text":rtxt or "","entities":Dick.ents(r.entities),
+ "chatId": r.sender.id if r.sender else mm.chat_id,"from":{"name":rname}}
+ except Exception: rb=None
+
+ med=None; obj=Dick.pick(mm)
+ if obj: med=await Dick.proc(self.client,obj,mm)
+
+ txt=mm.raw_text or ""; ad=Dick.desc(mm)
+ if ad: txt=f"{txt}\n\n{ad}" if txt else ad
+
+ item={"from":{"id":getattr(u,"id", 0),"first_name":getattr(u,"first_name","") or f,"last_name":getattr(u,"last_name","") or l,
+ "username":getattr(u,"username",None),"name":name,"photo":{"url":ava} if ava else {}},
+ "text":txt,"entities":Dick.ents(mm.entities),"avatar":True}
+
+ try:
+ if mm.voice:
+ a = next((a for a in mm.voice.attributes or []
+ if getattr(a, "voice", False) and hasattr(a, "waveform")), None)
+ if a: item["voice"] = {"waveform": Dick.wf(a.waveform)}
+ except Exception: pass
+
+ if med: item["voice" if "voice" in med else "media"] = med.get("voice", med)
+
+ es=getattr(u,"emoji_status",None)
+ if getattr(es,"document_id",None): item["from"]["emoji_status"]=str(es.document_id)
+ if rb: item["replyMessage"]=rb
+ out.append(item)
+ except Exception: continue
+ return out
+
+ async def who(self, m: Message):
+ try:
+ if m.fwd_from:
+ if m.fwd_from.from_id:
+ pid=m.fwd_from.from_id
+ uid=pid.channel_id if isinstance(pid, types.PeerChannel) else pid.user_id
+ try: return await self.client.get_entity(uid)
+ except Exception: return m.sender
+ if m.fwd_from.from_name:
+ return types.User(
+ id=hash(m.fwd_from.from_name)%2147483647, first_name=m.fwd_from.from_name,
+ username=None, phone=None, bot=False, verified=False, restricted=False,
+ scam=False, fake=False, premium=False)
+ return m.sender
+ except Exception:
+ return m.sender
+
+ async def fake(self, args: str, rep: Optional[Message]) -> List[dict]:
+ async def tok(ch: str):
+ p=ch.split()
+ if not p: return None,""
+ who=p[0]; tx=ch.split(maxsplit=1)[1] if len(p)>1 else ""
+ try:
+ u=await self.client.get_entity(int(who) if who.isdigit() else who)
+ return u,tx
+ except Exception:
+ return None,tx
+
+ if rep and not args:
+ u=rep.sender; name=telethon.utils.get_display_name(u); f,l=Dick.split(name)
+ ava=await Dick.ava(self.client,u.id) if getattr(u,"id",None) else None
+ msg={"from":{"id":u.id,"first_name":getattr(u,"first_name","") or f,"last_name":getattr(u,"last_name","") or l,
+ "username":getattr(u,"username",None),"name":name,"photo":{"url":ava} if ava else {}},
+ "text":"","entities":[], "avatar":True}
+ es=getattr(u,"emoji_status",None)
+ if getattr(es,"document_id", None): msg["from"]["emoji_status"]=str(es.document_id)
+ return [msg]
+
+ if rep and args:
+ u=rep.sender
+ return await self.fake(f"{getattr(u,'id','')} {args}", None)
+
+ out: List[dict]=[]
+ for part in args.split("; "):
+ try:
+ rb=None
+ if " -r " in part:
+ a,b=part.split(" -r ",1); u1,t1=await tok(a); u2,t2=await tok(b)
+ else:
+ u1,t1=await tok(part); u2,t2=None,None
+ if not u1: continue
+
+ txt1, ents1 = html.parse(t1) if t1 else ("", [])
+
+ name=telethon.utils.get_display_name(u1); f,l=Dick.split(name)
+ ava=await Dick.ava(self.client,u1.id)
+
+ if u2:
+ txt2, ents2 = html.parse(t2) if t2 else ("", [])
+ name2=telethon.utils.get_display_name(u2); ava2=await Dick.ava(self.client,u2.id)
+ rb={"name":name2,"text":txt2,"entities":Dick.ents(ents2),"chatId":u2.id,"from":{"name":name2,"photo":{"url":ava2} if ava2 else {}}}
+
+ msg={"from":{"id":u1.id,"first_name":getattr(u1,"first_name","") or f,"last_name":getattr(u1,"last_name","") or l,
+ "username":getattr(u1,"username",None),"name":name,"photo":{"url":ava} if ava else {}},
+ "text":txt1,"entities":Dick.ents(ents1), "avatar":True}
+
+ es=getattr(u1,"emoji_status",None)
+ if getattr(es,"document_id",None): msg["from"]["emoji_status"]=str(es.document_id)
+ if rb: msg["replyMessage"]=rb
+ out.append(msg)
+ except Exception: continue
return out
\ No newline at end of file