Compare commits

..

4 Commits

Author SHA1 Message Date
github-actions[bot]
e2ff44c2e0 Updated modules.json after parse 2026-06-10 02:45:18 2026-06-10 02:45:18 +00:00
github-actions[bot]
bfeb11ff53 Added and updated repositories 2026-06-10 02:44:47 2026-06-10 02:44:47 +00:00
6b6afb7493 fix: Ported fix from stable, in python version below 3.12 you can't use " with f-strings 2026-06-08 09:46:37 +03:00
Macsim
2ed246b9ad Merge pull request #301 from MuRuLOSE/update-submodules_d279789b37a939b3d9ececce6b4d0e1992293c23
Update of repositories 2026-05-31 02:48:09
2026-06-01 00:23:40 +03:00
4 changed files with 440 additions and 119 deletions

View File

@@ -760,7 +760,7 @@ class Limoka(loader.Module):
), ),
}, },
{ {
"text": f"{self.strings["body_page"]} {page_body + 1}/{len(body_pages)}", "text": f"{self.strings['body_page']} {page_body + 1}/{len(body_pages)}",
"callback": self._inline_void, "callback": self._inline_void,
}, },
{ {

View File

@@ -26,7 +26,6 @@ from .. import loader, utils
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class Banners: class Banners:
def __init__( def __init__(
self, self,
@@ -324,6 +323,16 @@ class YaMusicMod(loader.Module):
"name": "YaMusic" "name": "YaMusic"
} }
duration_placeholder = {
"start_duration": "<tg-emoji emoji-id=5262663495538742892>☀️</tg-emoji><tg-emoji emoji-id=5260381609479153468>☀️</tg-emoji>",
"start_full_duration": "<tg-emoji emoji-id=5262663495538742892>☀️</tg-emoji><tg-emoji emoji-id=5260609582048254485>☀️</tg-emoji>",
"closed_duration": "<tg-emoji emoji-id=5260467667738859177>☀️</tg-emoji>",
"empty_mid": "<tg-emoji emoji-id=5260415715814448198>☀️</tg-emoji>",
"empty_closed_duration_duration": "<tg-emoji emoji-id=5260239235608255208>☀️</tg-emoji>",
"end_duration_full": "<tg-emoji emoji-id=5260467667738859177>☀️</tg-emoji>",
"empty_closed_duration": "<tg-emoji emoji-id=5260239235608255208>☀️</tg-emoji>",
}
def __init__(self): def __init__(self):
self.config = loader.ModuleConfig( self.config = loader.ModuleConfig(
loader.ConfigValue( loader.ConfigValue(
@@ -553,67 +562,19 @@ class YaMusicMod(loader.Module):
return "0%" return "0%"
percent = (progress / duration) * 100 percent = (progress / duration) * 100
fill_logic = int(percent // 16.66)
s_less_10 = ( bar = self.duration_placeholder["start_full_duration"] if fill_logic >= 1 else self.duration_placeholder["start_duration"]
"<emoji document_id=5454137780454067986></emoji>" for i in range(2, 6):
"<emoji document_id=6158923355173949539>⭐</emoji>" if fill_logic >= i:
"<emoji document_id=6159012102083188132>⭐</emoji>" bar += self.duration_placeholder["closed_duration"]
"<emoji document_id=6159012102083188132>⭐</emoji>" else:
"<emoji document_id=6158753257289158944>⭐</emoji>" bar += self.duration_placeholder["empty_mid"]
"<emoji document_id=6156700344526049665>⭐</emoji>" if fill_logic >= 6:
) bar += self.duration_placeholder["end_duration_full"]
s_10_to_20 = (
"<emoji document_id=5454137780454067986></emoji>"
"<emoji document_id=6159095673556840262>⭐</emoji>"
"<emoji document_id=6159012102083188132>⭐</emoji>"
"<emoji document_id=6156933677214341691>⭐</emoji>"
"<emoji document_id=6158753257289158944>⭐</emoji>"
"<emoji document_id=6156700344526049665>⭐</emoji>"
)
s_30_to_40 = (
"<emoji document_id=5454137780454067986></emoji>"
"<emoji document_id=5454397458471750662></emoji>"
"<emoji document_id=5454397458471750662></emoji>"
"<emoji document_id=6158923355173949539>⭐</emoji>"
"<emoji document_id=6159012102083188132>⭐</emoji>"
"<emoji document_id=6156700344526049665>⭐</emoji>"
)
s_over_50 = (
"<emoji document_id=5454137780454067986></emoji>"
"<emoji document_id=5454397458471750662></emoji>"
"<emoji document_id=5454397458471750662></emoji>"
"<emoji document_id=5454397458471750662></emoji>"
"<emoji document_id=6156933677214341691>⭐</emoji>"
"<emoji document_id=6156700344526049665>⭐</emoji>"
)
s_over_80 = (
"<emoji document_id=5454137780454067986></emoji>"
"<emoji document_id=5454397458471750662></emoji>"
"<emoji document_id=5454397458471750662></emoji>"
"<emoji document_id=5454397458471750662></emoji>"
"<emoji document_id=5454397458471750662></emoji>"
"<emoji document_id=6156700344526049665>⭐</emoji>"
)
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: else:
return s_over_80 bar += self.duration_placeholder["empty_closed_duration"]
return bar
except Exception as e: except Exception as e:
return f"Error: {e}" return f"Error: {e}"

View File

@@ -18,18 +18,199 @@
"class_cmd_names": {} "class_cmd_names": {}
}, },
"LimokaLegacy.py": { "LimokaLegacy.py": {
"name": "LimokaLegacy", "name": "Limoka",
"description": "", "description": "Modules are now in one place with easy searching!",
"cls_doc": {}, "cls_doc": {
"ru": "Модули теперь в одном месте с простым и удобным поиском!"
},
"meta": { "meta": {
"pic": null, "pic": null,
"banner": null, "banner": null,
"developer": "@limokanews" "developer": "@limokanews"
}, },
"commands": [], "commands": [
"new_commands": [], {
"limoka": "[query / nothing] - Search modules | (RU) [запрос / ничего] — Поиск модулей"
},
{
"lshistory": "[clear] - Show or clear search history | (RU) [clear] — Показать или очистить историю поиска"
}
],
"new_commands": [
{
"name": "limoka",
"original_name": "limokacmd",
"description": {
"default": "[query / nothing] - Search modules",
"ru": "[запрос / ничего] — Поиск модулей"
},
"cmd_names": {},
"aliases": [],
"usage": null,
"inline": false,
"is_inline_handler": false,
"decorators": []
},
{
"name": "lshistory",
"original_name": "lshistorycmd",
"description": {
"default": "[clear] - Show or clear search history",
"ru": "[clear] — Показать или очистить историю поиска"
},
"cmd_names": {},
"aliases": [],
"usage": null,
"inline": false,
"is_inline_handler": false,
"decorators": []
}
],
"inline_handlers": [], "inline_handlers": [],
"strings": {}, "strings": {
"name": "Limoka",
"wait": "<blockquote>Just wait\n<emoji document_id=5404630946563515782>🔍</emoji> A search is underway among {count} modules for the query: <code>{query}</code>\n<i>{fact}</i></blockquote>",
"found_header": "<blockquote><emoji document_id=5413334818047940135>🔍</emoji> Found module <b>{name}</b> by query: <b>{query}</b>\n\n<b><emoji document_id=5418376169055602355></emoji> Description:</b> {description}\n<b><emoji document_id=5418299289141004396>🧑‍💻</emoji> Developer:</b> {username}\n\n<b><emoji document_id=5418376169055602355>🏷</emoji> Tags:</b> {tags}\n\n</blockquote>",
"found_body": "{commands}",
"found_footer": "<blockquote>\n<emoji document_id=5411143117711624172>🪄</emoji> <code>{prefix}dlm {url}{module_path}</code></blockquote>",
"caption_short": "<blockquote><emoji document_id=5413334818047940135>🔍</emoji> <b>{safe_name}</b>\n<b><emoji document_id=5418376169055602355></emoji> Description:</b> {safe_desc}\n<b><emoji document_id=5418299289141004396>🧑‍💻</emoji> Dev:</b> {dev_username}\n<emoji document_id=5411143117711624172>🪄</emoji> <code>{prefix}dlm {module_path}</code></blockquote>",
"command_template": "{emoji} <code>{prefix}{command}</code> — {description}\n",
"inline_handler_template": "{inline_bot} {command} — {description}\n",
"emojis": {
"1": "<emoji document_id=5416037945909987712>1⃣</emoji>",
"2": "<emoji document_id=5413855071731470617>2⃣</emoji>",
"3": "<emoji document_id=5416068826724850291>3⃣</emoji>",
"4": "<emoji document_id=5415843998071803071>4⃣</emoji>",
"5": "<emoji document_id=5415684843763686989>5⃣</emoji>",
"6": "<emoji document_id=5415975458430796879>6⃣</emoji>",
"7": "<emoji document_id=5415769763857060166>7⃣</emoji>",
"8": "<emoji document_id=5416006506749383505>8⃣</emoji>",
"9": "<emoji document_id=5415963015910544694>9⃣</emoji>"
},
"404": "<blockquote><emoji document_id=5210952531676504517>❌</emoji> <b>Not found by query: <i>{query}</i></b></blockquote>",
"noargs": "<blockquote><emoji document_id=5210952531676504517>❌</emoji> <b>No args</b></blockquote>",
"?": "<blockquote><emoji document_id=5951895176908640647>🔎</emoji> Request too short / not found</blockquote>",
"no_info": "<blockquote>No information</blockquote>",
"facts": [
"<blockquote><emoji document_id=5472193350520021357>🛡</emoji> The limoka catalog is carefully moderated!</blockquote>",
"<blockquote><emoji document_id=5940434198413184876>🚀</emoji> Limoka performance allows you to search for modules quickly!</blockquote>"
],
"inline404": "<blockquote>Not found</blockquote>",
"inline?": "<blockquote>Request too short / not found</blockquote>",
"inlinenoargs": "<blockquote>Please, enter query</blockquote>",
"history": "<blockquote><emoji document_id=5879939498149679716>🔎</emoji> <b>Your search history:</b>\n{history}</blockquote>",
"filter_menu": "Choose filters",
"filter_cat": "📑 Filter by Category",
"apply_filters": "✅ Apply Filters",
"clear_filters": "🗑 Clear Filters",
"back_to_results": "🔙 Back to Results",
"empty_history": "<blockquote><emoji document_id=5879939498149679716>🔎</emoji> <b>Your search history is empty!</b></blockquote>",
"enter_query": "<blockquote>🔍 Enter new search query:</blockquote>",
"global_search": "<blockquote><emoji document_id=5413334818047940135>🔍</emoji> Global search for <b>{query}</b> — found <b>{count}</b> modules</blockquote>",
"change_query": "🔍 Change query",
"no_modules": "<blockquote>No modules available.</blockquote>",
"filter_title": "🏷 Filters",
"category_title": "📂 Categories",
"selected_categories": "<blockquote>✅ Selected categories: {categories}</blockquote>",
"no_categories": "<blockquote>No categories found in the module database</blockquote>",
"select_category": "<blockquote>Select categories for query: <code>{query}</code>\n(You can select multiple)</blockquote>",
"back": "🔙 Back",
"category": "📁 {category}",
"no_category": "<blockquote>No category</blockquote>",
"global_button": "🌍 Results",
"filtered_button": "🏷️ Filtered search",
"inline_search": "🔍 Search in Limoka",
"inline_no_results": "<blockquote>❌ No modules found</blockquote>",
"inline_error": "<blockquote>❌ Search error occurred</blockquote>",
"inline_short_query": "<blockquote>❌ Query too short (min 2 chars)</blockquote>",
"inline_switch_pm": "💬 Open in chat",
"inline_switch_pm_text": "🔍 Results for: {query}",
"inline_start_message": "<blockquote><emoji document_id=5413334818047940135>🔍</emoji> <b>Limoka Search</b>\nType module name or keyword</blockquote>",
"first_page": "<blockquote>This is the first page!</blockquote>",
"last_page": "<blockquote>This is the last page!</blockquote>",
"display_error": "<blockquote>Error displaying module. Please try again.</blockquote>",
"error_occurred": "<blockquote>An error occurred. Please try again.</blockquote>",
"start_search_form": "<blockquote><emoji document_id=5413334818047940135>🔍</emoji> <b>Limoka Search</b>\nEnter your query to search for modules:</blockquote>",
"global_search_form": "<blockquote><emoji document_id=5413334818047940135>🔍</emoji> <b>Global Search</b>\nEnter your query to search ALL modules without filters:</blockquote>",
"history_cleared": "<blockquote><emoji document_id=5427009710268689068>🧹</emoji> <b>Search history cleared!</b></blockquote>",
"invalid_history_arg": "<blockquote><emoji document_id=5210952531676504517>❌</emoji> <b>Invalid argument for history command. Use:</b>\n<code>.lshistory</code> - show history\n<code>.lshistory clear</code> - clear history</blockquote>",
"close": "❌ Close",
"watcher_no_tag": "<blockquote>❌ Invalid message format. No #limoka tag found.</blockquote>",
"watcher_invalid_format": "<blockquote>❌ Invalid format. Expected: #limoka:path:signature</blockquote>",
"watcher_signature_invalid": "<blockquote>❌ Signature invalid! Installation aborted.</blockquote>",
"watcher_loader_missing": "<blockquote>❌ Loader module not found.</blockquote>",
"watcher_module_not_found": "<blockquote>❌ Module not found in Limoka database: <code>{path}</code></blockquote>",
"watcher_critical": "<blockquote>❌ Critical error: {error}</blockquote>",
"tags": {
"herokutrusted": "Heroku Trusted",
"hikkatrusted": "Hikka Trusted",
"nonactive": "Non-Active Repository",
"nonlongermaintained": "No Longer Maintained Repository",
"newbie": "Newbie"
},
"indexing_in_progress": "<blockquote>⚠️ Database is busy, try again later. If issue persists, try removing limoka_index in the userbot's root folder. If error persists again, report to developers</blockquote>",
"body_page": "Commands",
"name_ru": "Limoka",
"wait_ru": "<blockquote>Подождите\n<emoji document_id=5404630946563515782>🔍</emoji> Идёт поиск среди {count} модулей по запросу: <code>{query}</code>\n<i>{fact}</i></blockquote>",
"found_header_ru": "<blockquote><emoji document_id=5413334818047940135>🔍</emoji> Найден модуль <b>{name}</b> по запросу: <b>{query}</b></blockquote>\n\n<blockquote><b><emoji document_id=5418376169055602355></emoji> Описание:</b> {description}</blockquote>\n<blockquote><b><emoji document_id=5418299289141004396>🧑‍💻</emoji> Разработчик:</b> {username}</blockquote>\n\n<blockquote><b><emoji document_id=5418376169055602355>🏷</emoji> Теги:</b> {tags}</blockquote>\n\n",
"found_body_ru": "{commands}",
"found_footer_ru": "\n<blockquote><emoji document_id=5411143117711624172>🪄</emoji> <code>{prefix}dlm {url}{module_path}</code></blockquote>",
"caption_short_ru": "<blockquote><emoji document_id=5413334818047940135>🔍</emoji> <b>{safe_name}</b>\n<b><emoji document_id=5418376169055602355></emoji> Описание:</b> {safe_desc}\n<b><emoji document_id=5418299289141004396>🧑‍💻</emoji> Разработчик:</b> {dev_username}\n<emoji document_id=5411143117711624172>🪄</emoji> <code>{prefix}dlm {module_path}</code></blockquote>",
"command_template_ru": "<blockquote>{emoji} <code>{prefix}{command}</code> — {description}</blockquote>\n",
"inline_handler_template_ru": "{inline_bot} {command} — {description}\n",
"404_ru": "<blockquote><emoji document_id=5210952531676504517>❌</emoji> <b>Не найдено по запросу: <i>{query}</i></b></blockquote>",
"noargs_ru": "<blockquote><emoji document_id=5210952531676504517>❌</emoji> <b>Нет аргументов</b></blockquote>",
"?_ru": "<blockquote><emoji document_id=5951895176908640647>🔎</emoji> Запрос слишком короткий / не найден</blockquote>",
"no_info_ru": "<blockquote>Нет информации</blockquote>",
"inline404_ru": "<blockquote>Не найдено</blockquote>",
"inline?_ru": "<blockquote>Запрос слишком короткий / не найден</blockquote>",
"inlinenoargs_ru": "<blockquote>Введите запрос</blockquote>",
"history_ru": "<blockquote><emoji document_id=5879939498149679716>🔎</emoji> <b>История поиска:</b>\n{history}</blockquote>",
"filter_menu_ru": "Выберите фильтры",
"filter_cat_ru": "📑 Фильтр по категориям",
"apply_filters_ru": "✅ Применить фильтры",
"clear_filters_ru": "🗑 Очистить фильтры",
"back_to_results_ru": "🔙 Вернуться к результатам",
"empty_history_ru": "<blockquote><emoji document_id=5879939498149679716>🔎</emoji> <b>История поиска пуста!</b></blockquote>",
"enter_query_ru": "<blockquote>🔍 Введите новый поисковый запрос:</blockquote>",
"global_search_ru": "<blockquote><emoji document_id=5413334818047940135>🔍</emoji> Глобальный поиск по <b>{query}</b> — найдено <b>{count}</b> модулей</blockquote>",
"change_query_ru": "🔍 Изменить запрос",
"no_modules_ru": "<blockquote>Модули недоступны.</blockquote>",
"filter_title_ru": "🏷 Фильтры",
"category_title_ru": "📂 Категории",
"selected_categories_ru": "<blockquote>✅ Выбранные категории: {categories}</blockquote>",
"no_categories_ru": "<blockquote>Категории не найдены в базе модулей</blockquote>",
"select_category_ru": "<blockquote>Выберите категории для запроса: <code>{query}</code>\n(Можно выбрать несколько)</blockquote>",
"back_ru": "🔙 Назад",
"category_ru": "📁 {category}",
"no_category_ru": "<blockquote>Без категории</blockquote>",
"global_button_ru": "🌍 Результаты",
"filtered_button_ru": "🏷️ Поиск с фильтрами",
"inline_search_ru": "🔍 Поиск в Limoka",
"inline_no_results_ru": "<blockquote>❌ Модули не найдены</blockquote>",
"inline_error_ru": "<blockquote>❌ Ошибка поиска</blockquote>",
"inline_short_query_ru": "<blockquote>❌ Запрос слишком короткий (мин. 2 символа)</blockquote>",
"inline_switch_pm_ru": "💬 Открыть в чате",
"inline_switch_pm_text_ru": "🔍 Результаты для: {query}",
"inline_start_message_ru": "<blockquote><emoji document_id=5413334818047940135>🔍</emoji> <b>Limoka Поиск</b>\nВведите название модуля или ключевое слово</blockquote>",
"first_page_ru": "<blockquote>Это первая страница!</blockquote>",
"last_page_ru": "<blockquote>Это последняя страница!</blockquote>",
"display_error_ru": "<blockquote>Ошибка отображения модуля. Пожалуйста, попробуйте еще раз.</blockquote>",
"error_occurred_ru": "<blockquote>Произошла ошибка. Пожалуйста, попробуйте еще раз.</blockquote>",
"start_search_form_ru": "<blockquote><emoji document_id=5413334818047940135>🔍</emoji> <b>Limoka Поиск</b>\nВведите ваш запрос для поиска модулей:</blockquote>",
"global_search_form_ru": "<blockquote><emoji document_id=5413334818047940135>🔍</emoji> <b>Глобальный Поиск</b>\nВведите запрос для поиска ВСЕХ модулей без фильтров:</blockquote>",
"history_cleared_ru": "<blockquote><emoji document_id=5427009710268689068>🧹</emoji> <b>История поиска очищена!</b></blockquote>",
"invalid_history_arg_ru": "<blockquote><emoji document_id=5210952531676504517>❌</emoji> <b>Неверный аргумент для команды истории. Используйте:</b>\n<code>.lshistory</code> - показать историю\n<code>.lshistory clear</code> - очистить историю</blockquote>",
"close_ru": "❌ Закрыть",
"watcher_no_tag_ru": "<blockquote>❌ Неверный формат сообщения. Тег #limoka не найден.</blockquote>",
"watcher_invalid_format_ru": "<blockquote>❌ Неверный формат. Ожидается: #limoka:path:signature</blockquote>",
"watcher_signature_invalid_ru": "<blockquote>❌ Неверная подпись! Установка отменена.</blockquote>",
"watcher_loader_missing_ru": "<blockquote>❌ Модуль загрузчика не найден.</blockquote>",
"watcher_module_not_found_ru": "<blockquote>❌ Модуль не найден в базе Limoka: <code>{path}</code></blockquote>",
"watcher_critical_ru": "<blockquote>❌ Критическая ошибка: {error}</blockquote>",
"indexing_in_progress_ru": "<blockquote>⚠️ База данных занята, попробуйте снова через несколько секунд. Если ошибка сохраняется, попробуйте удалить limoka_index в корневой папке юзербота. Если ошибка сохраняется снова, сообщите разработчикам</blockquote>",
"body_page_ru": "Команды"
},
"has_on_load": false, "has_on_load": false,
"has_on_unload": false, "has_on_unload": false,
"class_cmd_names": {} "class_cmd_names": {}
@@ -44616,6 +44797,12 @@
{ {
"sderepeat": "- ✋ Stop repeat | (RU) - ✋ Остановить повтор" "sderepeat": "- ✋ Stop repeat | (RU) - ✋ Остановить повтор"
}, },
{
"sshuffle": "- 🔀 Enable shuffle | (RU) - 🔀 Включить перемешивание"
},
{
"sdeshuffle": "- 🔀 Disable shuffle | (RU) - 🔀 Отключить перемешивание"
},
{ {
"snext": "- 👉 Next track | (RU) - 👉 Следующий трек" "snext": "- 👉 Next track | (RU) - 👉 Следующий трек"
}, },
@@ -44656,7 +44843,10 @@
"snowt": "| .snt - 🎧 Download current track. | (RU) | .snt - 🎧 Скачать играющий трек" "snowt": "| .snt - 🎧 Download current track. | (RU) | .snt - 🎧 Скачать играющий трек"
}, },
{ {
"ssearch": "| .sq - 🔍 Search for tracks. | (RU) | .sq - 🔍 Поиск треков." "sq": "- 🔍 Search for tracks. | (RU) - 🔍 Поиск треков."
},
{
"ssearch": "- 🔍 Search for tracks. | (RU) - 🔍 Поиск треков."
} }
], ],
"new_commands": [ "new_commands": [
@@ -44826,6 +45016,34 @@
"is_inline_handler": false, "is_inline_handler": false,
"decorators": [] "decorators": []
}, },
{
"name": "sshuffle",
"original_name": "sshufflecmd",
"description": {
"default": "- 🔀 Enable shuffle",
"ru": "- 🔀 Включить перемешивание"
},
"cmd_names": {},
"aliases": [],
"usage": null,
"inline": false,
"is_inline_handler": false,
"decorators": []
},
{
"name": "sdeshuffle",
"original_name": "sdeshufflecmd",
"description": {
"default": "- 🔀 Disable shuffle",
"ru": "- 🔀 Отключить перемешивание"
},
"cmd_names": {},
"aliases": [],
"usage": null,
"inline": false,
"is_inline_handler": false,
"decorators": []
},
{ {
"name": "snext", "name": "snext",
"original_name": "snextcmd", "original_name": "snextcmd",
@@ -45008,12 +45226,26 @@
"is_inline_handler": false, "is_inline_handler": false,
"decorators": [] "decorators": []
}, },
{
"name": "sq",
"original_name": "sqcmd",
"description": {
"default": "- 🔍 Search for tracks.",
"ru": "- 🔍 Поиск треков."
},
"cmd_names": {},
"aliases": [],
"usage": null,
"inline": false,
"is_inline_handler": false,
"decorators": []
},
{ {
"name": "ssearch", "name": "ssearch",
"original_name": "ssearchcmd", "original_name": "ssearchcmd",
"description": { "description": {
"default": "| .sq - 🔍 Search for tracks.", "default": "- 🔍 Search for tracks.",
"ru": "| .sq - 🔍 Поиск треков." "ru": "- 🔍 Поиск треков."
}, },
"cmd_names": {}, "cmd_names": {},
"aliases": [], "aliases": [],
@@ -45073,7 +45305,6 @@
"no_devices_found": "<tg-emoji emoji-id=5778527486270770928>❌</tg-emoji> <b>No devices found.</b>", "no_devices_found": "<tg-emoji emoji-id=5778527486270770928>❌</tg-emoji> <b>No devices found.</b>",
"device_changed": "<tg-emoji emoji-id=5776375003280838798>✅</tg-emoji> <b>Playback transferred to {}.</b>", "device_changed": "<tg-emoji emoji-id=5776375003280838798>✅</tg-emoji> <b>Playback transferred to {}.</b>",
"autobio": "<tg-emoji emoji-id=6319076999105087378>🎧</tg-emoji> <b>Spotify autobio {}</b>", "autobio": "<tg-emoji emoji-id=6319076999105087378>🎧</tg-emoji> <b>Spotify autobio {}</b>",
"no_ytdlp": "<tg-emoji emoji-id=5778527486270770928>❌</tg-emoji> <b>yt-dlp not found... Check config or install yt-dlp (<code>{}terminal pip install yt-dlp</code>)</b>",
"snowt_failed": "\n\n<tg-emoji emoji-id=5778527486270770928>❌</tg-emoji> <b>Download failed</b>", "snowt_failed": "\n\n<tg-emoji emoji-id=5778527486270770928>❌</tg-emoji> <b>Download failed</b>",
"uploading_banner": "\n\n<tg-emoji emoji-id=5841359499146825803>🕔</tg-emoji> <i>Uploading banner...</i>", "uploading_banner": "\n\n<tg-emoji emoji-id=5841359499146825803>🕔</tg-emoji> <i>Uploading banner...</i>",
"downloading_track": "\n\n<tg-emoji emoji-id=5841359499146825803>🕔</tg-emoji> <i>Downloading track...</i>", "downloading_track": "\n\n<tg-emoji emoji-id=5841359499146825803>🕔</tg-emoji> <i>Downloading track...</i>",
@@ -45087,6 +45318,8 @@
"playlist_deleted": "<tg-emoji emoji-id=5776375003280838798>✅</tg-emoji> <b>Playlist {} deleted.</b>", "playlist_deleted": "<tg-emoji emoji-id=5776375003280838798>✅</tg-emoji> <b>Playlist {} deleted.</b>",
"no_playlist_name": "<tg-emoji emoji-id=5778527486270770928>❌</tg-emoji> <b>Please specify a playlist name.</b>", "no_playlist_name": "<tg-emoji emoji-id=5778527486270770928>❌</tg-emoji> <b>Please specify a playlist name.</b>",
"device_select": "<tg-emoji emoji-id=5956561916573782596>📄</tg-emoji> <b>Select playback device:</b>", "device_select": "<tg-emoji emoji-id=5956561916573782596>📄</tg-emoji> <b>Select playback device:</b>",
"on-shuffle": "<tg-emoji emoji-id=5267246517701352801>🔀</tg-emoji> <b>Shuffle enabled.</b>",
"off-shuffle": "<tg-emoji emoji-id=5265105218806259720>🔀</tg-emoji> <b>Shuffle disabled.</b>",
"need_auth_ru": "<tg-emoji emoji-id=5778527486270770928>❌</tg-emoji> <b>Выполни </b><code>.sauth</code><b> перед выполнением этого действия.</b>", "need_auth_ru": "<tg-emoji emoji-id=5778527486270770928>❌</tg-emoji> <b>Выполни </b><code>.sauth</code><b> перед выполнением этого действия.</b>",
"err_ru": "<tg-emoji emoji-id=5778527486270770928>❌</tg-emoji> <b>Произошла ошибка.</b>\n<code>{}</code>", "err_ru": "<tg-emoji emoji-id=5778527486270770928>❌</tg-emoji> <b>Произошла ошибка.</b>\n<code>{}</code>",
"on-repeat_ru": "<tg-emoji emoji-id=5258420634785947640>🔄</tg-emoji> <b>Включен повтор трека.</b>", "on-repeat_ru": "<tg-emoji emoji-id=5258420634785947640>🔄</tg-emoji> <b>Включен повтор трека.</b>",
@@ -45119,7 +45352,6 @@
"no_devices_found_ru": "<tg-emoji emoji-id=5778527486270770928>❌</tg-emoji> <b>Устройства не найдены.</b>", "no_devices_found_ru": "<tg-emoji emoji-id=5778527486270770928>❌</tg-emoji> <b>Устройства не найдены.</b>",
"device_changed_ru": "<tg-emoji emoji-id=5776375003280838798>✅</tg-emoji> <b>Воспроизведение переключено на {}.</b>", "device_changed_ru": "<tg-emoji emoji-id=5776375003280838798>✅</tg-emoji> <b>Воспроизведение переключено на {}.</b>",
"autobio_ru": "<tg-emoji emoji-id=6319076999105087378>🎧</tg-emoji> <b>Обновление био включено {}</b>", "autobio_ru": "<tg-emoji emoji-id=6319076999105087378>🎧</tg-emoji> <b>Обновление био включено {}</b>",
"no_ytdlp_ru": "<tg-emoji emoji-id=5778527486270770928>❌</tg-emoji> <b>yt-dlp не найден... Проверьте конфиг или установите yt-dlp (<code>{}terminal pip install yt-dlp</code>)</b>",
"snowt_failed_ru": "\n\n<tg-emoji emoji-id=5778527486270770928>❌</tg-emoji> <b>Ошибка скачивания.</b>", "snowt_failed_ru": "\n\n<tg-emoji emoji-id=5778527486270770928>❌</tg-emoji> <b>Ошибка скачивания.</b>",
"uploading_banner_ru": "\n\n<tg-emoji emoji-id=5841359499146825803>🕔</tg-emoji> <i>Загрузка баннера...</i>", "uploading_banner_ru": "\n\n<tg-emoji emoji-id=5841359499146825803>🕔</tg-emoji> <i>Загрузка баннера...</i>",
"downloading_track_ru": "\n\n<tg-emoji emoji-id=5841359499146825803>🕔</tg-emoji> <i>Скачивание трека...</i>", "downloading_track_ru": "\n\n<tg-emoji emoji-id=5841359499146825803>🕔</tg-emoji> <i>Скачивание трека...</i>",
@@ -45132,7 +45364,9 @@
"playlist_created_ru": "<tg-emoji emoji-id=5776375003280838798>✅</tg-emoji> <b>Плейлист {} создан.</b>", "playlist_created_ru": "<tg-emoji emoji-id=5776375003280838798>✅</tg-emoji> <b>Плейлист {} создан.</b>",
"playlist_deleted_ru": "<tg-emoji emoji-id=5776375003280838798>✅</tg-emoji> <b>Плейлист {} удален.</b>", "playlist_deleted_ru": "<tg-emoji emoji-id=5776375003280838798>✅</tg-emoji> <b>Плейлист {} удален.</b>",
"no_playlist_name_ru": "<tg-emoji emoji-id=5778527486270770928>❌</tg-emoji> <b>Пожалуйста, укажите название плейлиста.</b>", "no_playlist_name_ru": "<tg-emoji emoji-id=5778527486270770928>❌</tg-emoji> <b>Пожалуйста, укажите название плейлиста.</b>",
"device_select_ru": "<tg-emoji emoji-id=5956561916573782596>📄</tg-emoji> <b>Выберите устройство для воспроизведения:</b>" "device_select_ru": "<tg-emoji emoji-id=5956561916573782596>📄</tg-emoji> <b>Выберите устройство для воспроизведения:</b>",
"on-shuffle_ru": "<tg-emoji emoji-id=5267246517701352801>🔀</tg-emoji> <b>Перемешивание включено.</b>",
"off-shuffle_ru": "<tg-emoji emoji-id=5265105218806259720>🔀</tg-emoji> <b>Перемешивание отключено.</b>"
}, },
"has_on_load": false, "has_on_load": false,
"has_on_unload": false, "has_on_unload": false,
@@ -79042,6 +79276,6 @@
}, },
"meta": { "meta": {
"total_modules": 998, "total_modules": 998,
"generated_at": "2026-05-31T02:47:45.912076" "generated_at": "2026-06-10T02:45:17.844204"
} }
} }

View File

@@ -17,10 +17,10 @@
# ======================================= # =======================================
# #
# meta developer: @ke_mods # meta developer: @ke_mods
# requires: telethon spotipy pillow requests yt-dlp curl_cffi # requires: telethon spotipy pillow requests httpx
# scope: ffmpeg # scope: ffmpeg
__version__ = (1, 0) __version__ = (1, 0, 2)
import asyncio import asyncio
import contextlib import contextlib
@@ -35,6 +35,7 @@ import os
from types import FunctionType from types import FunctionType
import random import random
import httpx
import requests import requests
import spotipy import spotipy
from PIL import Image, ImageDraw, ImageEnhance, ImageFilter, ImageFont, ImageOps from PIL import Image, ImageDraw, ImageEnhance, ImageFilter, ImageFont, ImageOps
@@ -48,6 +49,14 @@ from .. import loader, utils
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
logging.getLogger("spotipy").setLevel(logging.CRITICAL) logging.getLogger("spotipy").setLevel(logging.CRITICAL)
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36",
"Accept": "application/json",
"Content-Type": "application/json",
"Origin": "https://spotmate.online",
"Referer": "https://spotmate.online/en1",
}
class Banners: class Banners:
def __init__( def __init__(
self, self,
@@ -517,7 +526,6 @@ class SpotifyMod(loader.Module):
"autobio": ( "autobio": (
"<tg-emoji emoji-id=6319076999105087378>🎧</tg-emoji> <b>Spotify autobio {}</b>" "<tg-emoji emoji-id=6319076999105087378>🎧</tg-emoji> <b>Spotify autobio {}</b>"
), ),
"no_ytdlp": "<tg-emoji emoji-id=5778527486270770928>❌</tg-emoji> <b>yt-dlp not found... Check config or install yt-dlp (<code>{}terminal pip install yt-dlp</code>)</b>",
"snowt_failed": "\n\n<tg-emoji emoji-id=5778527486270770928>❌</tg-emoji> <b>Download failed</b>", "snowt_failed": "\n\n<tg-emoji emoji-id=5778527486270770928>❌</tg-emoji> <b>Download failed</b>",
"uploading_banner": "\n\n<tg-emoji emoji-id=5841359499146825803>🕔</tg-emoji> <i>Uploading banner...</i>", "uploading_banner": "\n\n<tg-emoji emoji-id=5841359499146825803>🕔</tg-emoji> <i>Uploading banner...</i>",
"downloading_track": "\n\n<tg-emoji emoji-id=5841359499146825803>🕔</tg-emoji> <i>Downloading track...</i>", "downloading_track": "\n\n<tg-emoji emoji-id=5841359499146825803>🕔</tg-emoji> <i>Downloading track...</i>",
@@ -531,6 +539,12 @@ class SpotifyMod(loader.Module):
"playlist_deleted": "<tg-emoji emoji-id=5776375003280838798>✅</tg-emoji> <b>Playlist {} deleted.</b>", "playlist_deleted": "<tg-emoji emoji-id=5776375003280838798>✅</tg-emoji> <b>Playlist {} deleted.</b>",
"no_playlist_name": "<tg-emoji emoji-id=5778527486270770928>❌</tg-emoji> <b>Please specify a playlist name.</b>", "no_playlist_name": "<tg-emoji emoji-id=5778527486270770928>❌</tg-emoji> <b>Please specify a playlist name.</b>",
"device_select": "<tg-emoji emoji-id=5956561916573782596>📄</tg-emoji> <b>Select playback device:</b>", "device_select": "<tg-emoji emoji-id=5956561916573782596>📄</tg-emoji> <b>Select playback device:</b>",
"on-shuffle": (
"<tg-emoji emoji-id=5267246517701352801>🔀</tg-emoji> <b>Shuffle enabled.</b>"
),
"off-shuffle": (
"<tg-emoji emoji-id=5265105218806259720>🔀</tg-emoji> <b>Shuffle disabled.</b>"
),
} }
strings_ru = { strings_ru = {
@@ -641,7 +655,6 @@ class SpotifyMod(loader.Module):
"<tg-emoji emoji-id=6319076999105087378>🎧</tg-emoji> <b>Обновление био" "<tg-emoji emoji-id=6319076999105087378>🎧</tg-emoji> <b>Обновление био"
" включено {}</b>" " включено {}</b>"
), ),
"no_ytdlp": "<tg-emoji emoji-id=5778527486270770928>❌</tg-emoji> <b>yt-dlp не найден... Проверьте конфиг или установите yt-dlp (<code>{}terminal pip install yt-dlp</code>)</b>",
"snowt_failed": "\n\n<tg-emoji emoji-id=5778527486270770928>❌</tg-emoji> <b>Ошибка скачивания.</b>", "snowt_failed": "\n\n<tg-emoji emoji-id=5778527486270770928>❌</tg-emoji> <b>Ошибка скачивания.</b>",
"uploading_banner": "\n\n<tg-emoji emoji-id=5841359499146825803>🕔</tg-emoji> <i>Загрузка баннера...</i>", "uploading_banner": "\n\n<tg-emoji emoji-id=5841359499146825803>🕔</tg-emoji> <i>Загрузка баннера...</i>",
"downloading_track": "\n\n<tg-emoji emoji-id=5841359499146825803>🕔</tg-emoji> <i>Скачивание трека...</i>", "downloading_track": "\n\n<tg-emoji emoji-id=5841359499146825803>🕔</tg-emoji> <i>Скачивание трека...</i>",
@@ -655,6 +668,12 @@ class SpotifyMod(loader.Module):
"playlist_deleted": "<tg-emoji emoji-id=5776375003280838798>✅</tg-emoji> <b>Плейлист {} удален.</b>", "playlist_deleted": "<tg-emoji emoji-id=5776375003280838798>✅</tg-emoji> <b>Плейлист {} удален.</b>",
"no_playlist_name": "<tg-emoji emoji-id=5778527486270770928>❌</tg-emoji> <b>Пожалуйста, укажите название плейлиста.</b>", "no_playlist_name": "<tg-emoji emoji-id=5778527486270770928>❌</tg-emoji> <b>Пожалуйста, укажите название плейлиста.</b>",
"device_select": "<tg-emoji emoji-id=5956561916573782596>📄</tg-emoji> <b>Выберите устройство для воспроизведения:</b>", "device_select": "<tg-emoji emoji-id=5956561916573782596>📄</tg-emoji> <b>Выберите устройство для воспроизведения:</b>",
"on-shuffle": (
"<tg-emoji emoji-id=5267246517701352801>🔀</tg-emoji> <b>Перемешивание включено.</b>"
),
"off-shuffle": (
"<tg-emoji emoji-id=5265105218806259720>🔀</tg-emoji> <b>Перемешивание отключено.</b>"
),
} }
def __init__(self): def __init__(self):
@@ -700,16 +719,10 @@ class SpotifyMod(loader.Module):
lambda: "Template for Spotify AutoBio, supports {artist}, {title}", lambda: "Template for Spotify AutoBio, supports {artist}, {title}",
), ),
loader.ConfigValue( loader.ConfigValue(
"ytdlp_path", "TimeOut",
"", 60,
"Path to ytdlp binary", "Response timeout in seconds | Время ожидания ответа в секундах",
validator=loader.validators.String(), validator=loader.validators.Integer(minimum=30),
),
loader.ConfigValue(
"cookies_path",
"",
"Path to your cookies for yt-dlp",
validator=loader.validators.String(),
), ),
loader.ConfigValue( loader.ConfigValue(
"banner_version", "banner_version",
@@ -944,36 +957,102 @@ class SpotifyMod(loader.Module):
success = False success = False
try: try:
squery = query.replace('"', '').replace("'", "") track_url = (query or "").strip().split("?")[0]
cookies = self.config["cookies_path"] if "spotify:track:" in track_url:
ytdlp_flags = '-x --audio-format mp3 --audio-quality 0 --add-metadata --format "bestaudio/best" --no-playlist' track_url = f"https://open.spotify.com/track/{track_url.split(':')[-1]}"
cookies_flag = f"--cookies {cookies} " if cookies else ""
cmd = (
f'{self.config["ytdlp_path"]} {ytdlp_flags} {cookies_flag}'
f'-o "{dl_dir}/%(title)s [%(id)s].%(ext)s" '
f'"ytsearch1:{squery}"'
)
proc = await asyncio.create_subprocess_shell( if "track/" not in track_url:
cmd, results = await asyncio.to_thread(
stdout=asyncio.subprocess.PIPE, self.sp.search,
stderr=asyncio.subprocess.PIPE, q=query,
) limit=1,
_, stderr = await proc.communicate() type="track",
)
items = (results or {}).get("tracks", {}).get("items", [])
if not items:
logger.error("SpotifyMod: Spotify track not found for %r", log_context or query)
await send_text(self.strings["snowt_failed"])
return False
if proc.returncode: track_data = items[0]
err_text = stderr.decode(errors="ignore").strip() if stderr else "yt-dlp failed" track_url = track_data.get("external_urls", {}).get("spotify") or f"https://open.spotify.com/track/{track_data['id']}"
logger.error("SpotifyMod: yt-dlp code %s for %r: %s", proc.returncode, log_context or query, err_text[-400:])
files = [f for f in os.listdir(dl_dir) if f.endswith(".mp3")] async with httpx.AsyncClient(follow_redirects=True) as client:
if files: csrf = await self.get_session(client)
success = await send_file(os.path.join(dl_dir, files[0])) hdrs = {**headers, "X-CSRF-TOKEN": csrf}
info_res = await client.post(
"https://spotmate.online/getTrackData",
headers=hdrs,
json={"spotify_url": track_url},
timeout=self.config["TimeOut"],
)
info = info_res.json()
if info.get("type") != "track":
logger.error("SpotifyMod: spotmate returned no track for %r", log_context or query)
await send_text(self.strings["snowt_failed"])
return False
track_id = info.get("id", track_url.split("/")[-1])
conv_res = await client.post(
"https://spotmate.online/convert",
headers=hdrs,
json={"urls": track_url},
timeout=self.config["TimeOut"],
)
conv = conv_res.json()
download_url = conv.get("url") or conv.get("download_url")
task_id = conv.get("task_id") or conv.get("taskId")
if not download_url and task_id:
for _ in range(40):
await asyncio.sleep(4.5)
task_res = await client.get(
f"https://spotmate.online/tasks/{task_id}",
headers={**hdrs, "Accept": "application/json"},
timeout=self.config["TimeOut"],
)
task = task_res.json()
if task.get("error"):
logger.error("SpotifyMod: task error for %r", log_context or query)
await send_text(self.strings["dl_err"])
return False
data = task.get("data") or task.get("result") or {}
status = str(data.get("status") or data.get("state") or "").lower()
if status == "finished":
download_url = (
data.get("url")
or data.get("download_url")
or (data.get("result") or {}).get("url")
or (data.get("result") or {}).get("download_url")
)
break
if status in ("failed", "error", "expired", "cancelled"):
logger.error("SpotifyMod: task failed for %r", log_context or query)
await send_text(self.strings["dl_err"])
return False
if not download_url:
logger.error("SpotifyMod: download timeout for %r", log_context or query)
await send_text(self.strings["snowt_failed"])
return False
file_res = await client.get(
download_url,
headers={"User-Agent": headers["User-Agent"], "Referer": "https://spotmate.online/en1"},
timeout=self.config["TimeOut"],
)
file_path = os.path.join(dl_dir, f"{track_id}.mp3")
with open(file_path, "wb") as f:
f.write(file_res.content)
success = await send_file(file_path)
if not success: if not success:
logger.error("SpotifyMod: failed to send %r (target=%s)", log_context or query, type(target).__name__) logger.error("SpotifyMod: failed to send %r (target=%s)", log_context or query, type(target).__name__)
await send_text(self.strings["dl_err"]) await send_text(self.strings["dl_err"])
else:
logger.error("SpotifyMod: yt-dlp produced no mp3 for %r", log_context or query)
await send_text(self.strings["snowt_failed"])
except Exception as e: except Exception as e:
logger.error("Download track error (%s): %s", log_context or "no context", e, exc_info=True) logger.error("Download track error (%s): %s", log_context or "no context", e, exc_info=True)
@@ -986,6 +1065,20 @@ class SpotifyMod(loader.Module):
return success return success
async def get_session(self, client: httpx.AsyncClient) -> str:
res = await client.get(
"https://spotmate.online/en1",
headers={
"User-Agent": headers["User-Agent"],
"Accept": "text/html",
},
timeout=self.config["TimeOut"],
)
match = re.search(r'csrf-token[^>]*content="([^"]+)"', res.text)
if not match:
raise ValueError("CSRF token not found")
return match.group(1)
def _short_text(self, text: str, limit: int = 60) -> str: def _short_text(self, text: str, limit: int = 60) -> str:
text = " ".join(text.split()) text = " ".join(text.split())
if len(text) <= limit: if len(text) <= limit:
@@ -1126,7 +1219,13 @@ class SpotifyMod(loader.Module):
tracks = results["tracks"]["items"] tracks = results["tracks"]["items"]
store_id = id(tracks) store_id = id(tracks)
self._sp_store[store_id] = [(t.get("name", "Unknown"), ", ".join(a.get("name", "") for a in t.get("artists", []) if a.get("name")) or "Unknown Artist") for t in tracks] self._sp_store[store_id] = [
(
t.get("name", "Unknown"),
", ".join(a.get("name", "") for a in t.get("artists", []) if a.get("name")) or "Unknown Artist",
)
for t in tracks
]
entries = [] entries = []
for i, track in enumerate(tracks): for i, track in enumerate(tracks):
@@ -1154,7 +1253,7 @@ class SpotifyMod(loader.Module):
async def ssearch(self, query): async def ssearch(self, query):
"""<query> - search Spotify track""" """<query> - search Spotify track"""
return await self._inline_search_tracks(query) return await self._inline_search_tracks(query)
@error_handler @error_handler
@tokenized @tokenized
@loader.command( @loader.command(
@@ -1427,6 +1526,26 @@ class SpotifyMod(loader.Module):
self.sp.repeat("context") self.sp.repeat("context")
await utils.answer(message, self.strings["off-repeat"]) await utils.answer(message, self.strings["off-repeat"])
@error_handler
@tokenized
@loader.command(
ru_doc="- 🔀 Включить перемешивание"
)
async def sshufflecmd(self, message: Message):
"""- 🔀 Enable shuffle"""
self.sp.shuffle(True)
await utils.answer(message, self.strings["on-shuffle"])
@error_handler
@tokenized
@loader.command(
ru_doc="- 🔀 Отключить перемешивание"
)
async def sdeshufflecmd(self, message: Message):
"""- 🔀 Disable shuffle"""
self.sp.shuffle(False)
await utils.answer(message, self.strings["off-shuffle"])
@error_handler @error_handler
@tokenized @tokenized
@loader.command( @loader.command(
@@ -1730,11 +1849,10 @@ class SpotifyMod(loader.Module):
@error_handler @error_handler
@tokenized @tokenized
@loader.command( @loader.command(
ru_doc="| .sq - 🔍 Поиск треков.", ru_doc="- 🔍 Поиск треков."
alias="sq"
) )
async def ssearchcmd(self, message: Message): async def sqcmd(self, message: Message):
"""| .sq - 🔍 Search for tracks.""" """- 🔍 Search for tracks."""
args = utils.get_args_raw(message) args = utils.get_args_raw(message)
if not args: if not args:
await utils.answer(message, self.strings["no_search_query"]) await utils.answer(message, self.strings["no_search_query"])
@@ -1800,6 +1918,13 @@ class SpotifyMod(loader.Module):
), ),
) )
@error_handler
@tokenized
@loader.command(ru_doc="- 🔍 Поиск треков.")
async def ssearchcmd(self, message: Message):
"""- 🔍 Search for tracks."""
await self.sqcmd(message)
async def watcher(self, message: Message): async def watcher(self, message: Message):
"""Watcher is used to update token""" """Watcher is used to update token"""
if not self.sp: if not self.sp:
@@ -1855,4 +1980,5 @@ class SpotifyMod(loader.Module):
refresh_token = await self.invoke("stokrefresh", "", self.inline.bot.id) refresh_token = await self.invoke("stokrefresh", "", self.inline.bot.id)
await refresh_token.delete() await refresh_token.delete()
else: else:
self.set("NextRefresh", time.time() + 300) self.set("NextRefresh", time.time() + 300)
# слендермен