From f0d2a281056537b5d84da9bc3fbdf08a3e01b7a9 Mon Sep 17 00:00:00 2001 From: John Doe Date: Fri, 24 Apr 2026 20:10:52 +0300 Subject: [PATCH] removed H.modules feat: added force index update Co-authored-by: Copilot --- Limoka.py | 34 +- archquise/H.Modules/.DS_Store | Bin 10244 -> 0 bytes .../H.Modules/.github/workflows/genpage.yml | 59 - archquise/H.Modules/.gitignore | 7 - archquise/H.Modules/ASCIIArt.py | 136 - archquise/H.Modules/AccountData.py | 134 - archquise/H.Modules/AnimeQuotes.py | 100 - archquise/H.Modules/Article.py | 76 - archquise/H.Modules/AutofarmCookies.py | 205 - archquise/H.Modules/BirthdayTime.py | 252 - archquise/H.Modules/CNAME | 1 - archquise/H.Modules/CheckSpamBan.py | 54 - archquise/H.Modules/CryptoCurrency.py | 126 - archquise/H.Modules/EmojiStickerBlocker.py | 360 - archquise/H.Modules/FakeActions.py | 90 - archquise/H.Modules/FakeWallet.py | 179 - archquise/H.Modules/GigaChat.py | 124 - archquise/H.Modules/H.py | 44 - archquise/H.Modules/HAFK.py | 306 - archquise/H.Modules/HInstall.py | 107 - archquise/H.Modules/InfoBannersManager.py | 108 - archquise/H.Modules/InlineButton.py | 88 - archquise/H.Modules/InlineCoin.py | 92 - archquise/H.Modules/InlineHelper.py | 308 - archquise/H.Modules/KBSwapper.py | 138 - archquise/H.Modules/LICENSE | 17 - archquise/H.Modules/Memes.py | 112 - archquise/H.Modules/MessageMonitor.py | 305 - archquise/H.Modules/MooFarmRC1.py | 1812 ----- archquise/H.Modules/Music.py | 126 - archquise/H.Modules/README.md | 184 - archquise/H.Modules/ReplaceVowels.py | 81 - archquise/H.Modules/SMArchiver.py | 153 - archquise/H.Modules/TaskManager.py | 386 - archquise/H.Modules/TelegramStatusCodes.py | 200 - archquise/H.Modules/TelegraphComic.py | 536 -- archquise/H.Modules/Text2File.py | 80 - archquise/H.Modules/TikTokDownloader.py | 282 - archquise/H.Modules/TimedEmojiStatus.py | 538 -- archquise/H.Modules/UserbotAvast.py | 963 --- archquise/H.Modules/Video2GIF.py | 132 - archquise/H.Modules/VirusTotal.py | 240 - archquise/H.Modules/VoiceDL.py | 118 - archquise/H.Modules/Weather.py | 333 - archquise/H.Modules/_site/q | 1 - archquise/H.Modules/aiogram3/hikarichat.py | 6206 ----------------- archquise/H.Modules/animals.py | 100 - archquise/H.Modules/caliases.py | 223 - archquise/H.Modules/coddrago/DelMessTools.py | 369 - archquise/H.Modules/full.txt | 54 - archquise/H.Modules/globalrestrict.py | 684 -- archquise/H.Modules/hikkahost.py | 326 - archquise/H.Modules/index.html | 68 - archquise/H.Modules/jacques.py | 130 - archquise/H.Modules/mediatools.py | 770 -- archquise/H.Modules/novoice.py | 160 - archquise/H.Modules/nsfwart.py | 135 - archquise/H.Modules/numbersapi.py | 167 - archquise/H.Modules/passgen.py | 105 - archquise/H.Modules/privacy.py | 350 - archquise/H.Modules/profile.py | 180 - archquise/H.Modules/sdsaver.py | 116 - archquise/H.Modules/search.py | 188 - archquise/H.Modules/silenttr.py | 419 -- archquise/H.Modules/soundcloud.py | 875 --- 65 files changed, 28 insertions(+), 21324 deletions(-) delete mode 100644 archquise/H.Modules/.DS_Store delete mode 100644 archquise/H.Modules/.github/workflows/genpage.yml delete mode 100644 archquise/H.Modules/.gitignore delete mode 100644 archquise/H.Modules/ASCIIArt.py delete mode 100644 archquise/H.Modules/AccountData.py delete mode 100644 archquise/H.Modules/AnimeQuotes.py delete mode 100644 archquise/H.Modules/Article.py delete mode 100644 archquise/H.Modules/AutofarmCookies.py delete mode 100644 archquise/H.Modules/BirthdayTime.py delete mode 100644 archquise/H.Modules/CNAME delete mode 100644 archquise/H.Modules/CheckSpamBan.py delete mode 100644 archquise/H.Modules/CryptoCurrency.py delete mode 100644 archquise/H.Modules/EmojiStickerBlocker.py delete mode 100644 archquise/H.Modules/FakeActions.py delete mode 100644 archquise/H.Modules/FakeWallet.py delete mode 100644 archquise/H.Modules/GigaChat.py delete mode 100644 archquise/H.Modules/H.py delete mode 100644 archquise/H.Modules/HAFK.py delete mode 100644 archquise/H.Modules/HInstall.py delete mode 100644 archquise/H.Modules/InfoBannersManager.py delete mode 100644 archquise/H.Modules/InlineButton.py delete mode 100644 archquise/H.Modules/InlineCoin.py delete mode 100644 archquise/H.Modules/InlineHelper.py delete mode 100644 archquise/H.Modules/KBSwapper.py delete mode 100644 archquise/H.Modules/LICENSE delete mode 100644 archquise/H.Modules/Memes.py delete mode 100644 archquise/H.Modules/MessageMonitor.py delete mode 100644 archquise/H.Modules/MooFarmRC1.py delete mode 100644 archquise/H.Modules/Music.py delete mode 100644 archquise/H.Modules/README.md delete mode 100644 archquise/H.Modules/ReplaceVowels.py delete mode 100644 archquise/H.Modules/SMArchiver.py delete mode 100644 archquise/H.Modules/TaskManager.py delete mode 100644 archquise/H.Modules/TelegramStatusCodes.py delete mode 100644 archquise/H.Modules/TelegraphComic.py delete mode 100644 archquise/H.Modules/Text2File.py delete mode 100644 archquise/H.Modules/TikTokDownloader.py delete mode 100644 archquise/H.Modules/TimedEmojiStatus.py delete mode 100644 archquise/H.Modules/UserbotAvast.py delete mode 100644 archquise/H.Modules/Video2GIF.py delete mode 100644 archquise/H.Modules/VirusTotal.py delete mode 100644 archquise/H.Modules/VoiceDL.py delete mode 100644 archquise/H.Modules/Weather.py delete mode 100644 archquise/H.Modules/_site/q delete mode 100644 archquise/H.Modules/aiogram3/hikarichat.py delete mode 100644 archquise/H.Modules/animals.py delete mode 100644 archquise/H.Modules/caliases.py delete mode 100644 archquise/H.Modules/coddrago/DelMessTools.py delete mode 100644 archquise/H.Modules/full.txt delete mode 100644 archquise/H.Modules/globalrestrict.py delete mode 100644 archquise/H.Modules/hikkahost.py delete mode 100644 archquise/H.Modules/index.html delete mode 100644 archquise/H.Modules/jacques.py delete mode 100644 archquise/H.Modules/mediatools.py delete mode 100644 archquise/H.Modules/novoice.py delete mode 100644 archquise/H.Modules/nsfwart.py delete mode 100644 archquise/H.Modules/numbersapi.py delete mode 100644 archquise/H.Modules/passgen.py delete mode 100644 archquise/H.Modules/privacy.py delete mode 100644 archquise/H.Modules/profile.py delete mode 100644 archquise/H.Modules/sdsaver.py delete mode 100644 archquise/H.Modules/search.py delete mode 100644 archquise/H.Modules/silenttr.py delete mode 100644 archquise/H.Modules/soundcloud.py diff --git a/Limoka.py b/Limoka.py index 62403b6..9d22e7e 100644 --- a/Limoka.py +++ b/Limoka.py @@ -489,8 +489,12 @@ class Limoka(loader.Module): "New Limoka Version {version} already available. Please update for better performance, bug fixes, and new features.\n" "Press the button below to update the module." ), - "no_updates_available": "No updates available. You are using the latest version of Limoka.", - "module_update_available": "Notification about module update has been sent, check @{bot}.", + "no_updates_available": "
❌ No updates available. You are using the latest version of Limoka.
", + "module_update_available": "
🔔 Notification about module update has been sent, check @{bot}.
", + "index_update_started": "
🔄 Limoka module index update has started. This may take a few minutes. Please wait...
", + "index_update_failed": "
❌ Failed to update Limoka module index. Please try again later. If the error persists, report to developers
", + "index_update_success": "
✅ Limoka module index updated successfully!
", + "update_check_started": "
🔍 Checking for Limoka updates...
", } strings_ru = { "name": "Limoka", @@ -612,8 +616,12 @@ class Limoka(loader.Module): "Новая версия Limoka {version} уже доступна. Пожалуйста, обновитесь для лучшей производительности, исправления багов и новых функций.\n" "Нажмите кнопку ниже, чтобы обновить модуль." ), - "no_updates_available": "Нет доступных обновлений. У вас установлена последняя версия Limoka.", - "module_update_available": "Уведомление об обновлении модуля было отправлено, проверьте @{bot}.", + "no_updates_available": "
❌ Нет доступных обновлений. У вас установлена последняя версия Limoka.
", + "module_update_available": "
🔔 Уведомление об обновлении модуля было отправлено, проверьте @{bot}.
", + "index_update_started": "
🔄 Обновление индекса модулей Limoka началось. Это может занять несколько минут. Пожалуйста, подождите...
", + "index_update_failed": "
❌ Не удалось обновить индекс модулей Limoka. Пожалуйста, попробуйте снова позже. Если ошибка сохраняется, сообщите разработчикам
", + "index_update_success": "
✅ Индекс модулей Limoka успешно обновлен!
", + "update_check_started": "
🔍 Проверка обновлений Limoka...
", "_cls_doc": "Модули теперь в одном месте с простым и удобным поиском!", } @@ -1724,9 +1732,23 @@ class Limoka(loader.Module): message, module_info, module_path, display_session, 0 ) - @loader.command(ru_doc="Проверить наличие обновлений модуля") + @loader.command(ru_doc="— Обновить индекс ") + async def updateindex(self, message: Message): + """— Update search index""" + await utils.answer(message, self.strings["index_update_started"]) + try: + self._update_index() + except Exception as e: + logger.exception(f"Error updating index: {e}") + await utils.answer(message, self.strings["index_update_failed"]) + else: + await utils.answer(message, self.strings["index_update_success"]) + + @loader.command(ru_doc="— Проверить наличие обновлений модуля") async def limokaupdatecmd(self, message: Message): - """Check for module updates""" + """— Check for module updates""" + await utils.answer(message, self.strings["checking_for_updates"]) + is_update_available = await self.check_for_module_update() if is_update_available: await utils.answer(message, self.strings["module_update_available"].format(bot=self._self_bot_username)) diff --git a/archquise/H.Modules/.DS_Store b/archquise/H.Modules/.DS_Store deleted file mode 100644 index cf853138b627cb82170b66bb8acca3a9ae635770..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10244 zcmeI1&vF|@5XO6Dr($Huf1t=&1spiJ2%M>MY%7TiLKRWKN3dljR=n04$x7mzpMjit z33vgjcmy7S2Z7($yGhN+v*ARbs9n|WXjlDBcYo8}GqWJXjM->r_I2djrQ|mc$61M*JPUq4Fe4W4Fe4W4Fe4W{|yG1vvsVjmThxw7-$%1 z7O+rH?RM2(m93W!F1`hzUFNYLd}klvm|oRxSM62VI+Q-k-9uJEvucZxm2iIV zNt|}OYOl(+gj-g^EvqzV)ea?d?cjPYZrOI(HrIxMhJou0XzzZ*zO$K)?IPO0E0K0M zhkyFf|CR4+cE^T%3Y*&m`8_-2dT3jAZkNH{U)Y})uq()oK|Zl5-_Go_SU&p*`K>@# z7hMw&UveJV2-JmLrZiV7s=@r$nbv;xI(Li|iatg+0c%DIV_fy|7bs*I4Vk{uF3l$Ck0% zdt%4*a%qp8tEAn|ZD_$d8c)zQMCX7}*GTSgous^WmP7On&|l%h1MA15-x8;x*i*b4RHJiB4^t#_p34K>)$ktmb<_DCBA<{J zN0IcJ-GveLu53|U6xdxGhY_~>8hbz=xyRE*N~;))keeV2~_X} z2^d?!%em{^!s-`x5s#5FIt%4j?H;~8p?}4MDnUX^_Q3*b6NLCba{n}@Gmuky5?84n z#BZo~+3u(06m5~*P2}#t$`?s#@fY^m!G|%Uc8Xmm*e9zJ)`+#%hXHGCP!nhU1@b4L z&Md#qT!eX_te?PkL2t^PiI3TgywKet4An%Y*o=Bw_nvaMVMK4L4yq5yd{m@H=x&5o zEs-w?uf=VIjP>5f-ke;mv0AYjp*PU05ucHns?+D7M|fYJlVT@!i~l@X?ko;jYB>jg1$j>Z%cHX~#k$wNGvdG6Kb zz|t(>Pnan_Jw`{}yf5ZL%sa5$rz-8iU?0oQ>^QC!&RvMu05L*Sj(y_pTQokWt%)l| z8Mh{@$tGL+Bj97zBSK7QZK#@_(B`oeqPk?&1D`3U zXdM$JHTs;H5V^+6MPqgf{_pChSP8MWz>32W<6W*twMJ0RDy~%tHIJq-e?oY5T?h?I}wJKgG zz2YF%3oJ&xtlKwmr13gJ{{^G?%txd)TO)LbSRT901svR?%HB;yjF^jXUB^v*q{M>8 zEl(j8q07Go+_gP>${MssT~GL^_*}po;GtO2x;Giq&Tgn@%?|KPs{)PAjG1S|sx=u+ z%GXFA3*qKy7v%2_(T%^3=SHvNKdzpAqE-jLZ&&SA**cu(4yP^O=Kte=2Jn7TtBPj- W-|YVv-&<=f*LoSO8ULsI|GxmsFR{u1 diff --git a/archquise/H.Modules/.github/workflows/genpage.yml b/archquise/H.Modules/.github/workflows/genpage.yml deleted file mode 100644 index 9631103..0000000 --- a/archquise/H.Modules/.github/workflows/genpage.yml +++ /dev/null @@ -1,59 +0,0 @@ -name: Generate Index Page - -on: - push: - branches: - - main - -permissions: - contents: write - pages: write - id-token: write - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: '3.x' - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - - name: Generate index.html - run: | - cat < _site/index.html - - - - - - H:Mods - - - -
-

H:Mods modules

-
    - $(for file in *.py; do echo "
  • $file
  • "; done) -
-
- - - EOF - - name: Upload artifact - uses: actions/upload-pages-artifact@v3 - - - name: Deploy to GitHub Pages - uses: JamesIves/github-pages-deploy-action@v4 - with: - branch: gh-pages - folder: . diff --git a/archquise/H.Modules/.gitignore b/archquise/H.Modules/.gitignore deleted file mode 100644 index 4fb1dc3..0000000 --- a/archquise/H.Modules/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -full.py -autocleaner.py -silent.py - -# Ruff Format -.ruff_cache/ -.idea diff --git a/archquise/H.Modules/ASCIIArt.py b/archquise/H.Modules/ASCIIArt.py deleted file mode 100644 index 96f992c..0000000 --- a/archquise/H.Modules/ASCIIArt.py +++ /dev/null @@ -1,136 +0,0 @@ -# Proprietary License Agreement - -# Copyright (c) 2024-29 CodWiz - -# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions: - -# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author. - -# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author. - -# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software. - -# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software. - -# 5. By using the Software, you agree to be bound by the terms and conditions of this license. - -# For any inquiries or requests for permissions, please contact codwiz@yandex.ru. - -# --------------------------------------------------------------------------------- -# Name: ASCIIArt -# Description: Converting images to ASCII art -# Author: @hikka_mods -# --------------------------------------------------------------------------------- -# meta developer: @hikka_mods -# scope: ASCIIArt -# scope: ASCIIArt 0.0.1 -# requires: pillow -# --------------------------------------------------------------------------------- - -import logging -import os -import tempfile - -from PIL import Image - -from .. import loader, utils - -logger = logging.getLogger(__name__) - -@loader.tds -class ASCIIArtMod(loader.Module): - """Converting images to ASCII art""" - - strings = { - "name": "ASCIIArt", - "no_media_reply": "Please reply to the image!", - "loading": " Converting an image to ASCII...", - "error": "👎 Error when converting an image.", - "done": " Here is your ASCII art:", - } - - strings_ru = { - "no_media_reply": "Пожалуйста, ответьте на изображение!", - "loading": " Конвертирую изображение в ASCII...", - "error": "👎 Ошибка при конвертации изображения.", - "done": " Вот ваш ASCII-арт:", - } - - @loader.command( - ru_doc="<реплай на изображение> сделать ascii art", - en_doc=" make ascii art", - ) - async def cascii(self, message): - reply = await message.get_reply_message() - if not self._is_image(reply): - await utils.answer(message, self.strings("no_media_reply")) - return - - await utils.answer(message, self.strings("loading")) - ascii_art = await self._generate_ascii_art(reply) - - if ascii_art: - await self._send_ascii_file(message, ascii_art) - await message.delete() - else: - await utils.answer(message, self.strings("error")) - - def _is_image(self, reply): - """Проверка, является ли ответ изображением""" - return reply and ( - reply.photo - or (reply.document and reply.file.mime_type.startswith("image/")) - ) - - async def _generate_ascii_art(self, reply): - """Генерирует ASCII-арт из изображения""" - try: - image_path = await reply.download_media(tempfile.gettempdir()) - if not image_path: - return None - with Image.open(image_path) as img: - img = img.convert("L") - img = img.resize(self._get_new_dimensions(img), Image.NEAREST) - - chars = "@#S%?*+;:,. " - pixels = img.getdata() - - ascii_str = "".join(chars[pixel // 25] for pixel in pixels) - return "\n".join( - ascii_str[i : i + img.width] - for i in range(0, len(ascii_str), img.width) - ) - - except Exception as e: - logger.error(f"Error generating ASCII art: {e}") - return None - finally: - if image_path and os.path.exists(image_path): - os.remove(image_path) - - def _get_new_dimensions(self, img): - """Получаем новые размеры для изображения""" - new_width = 100 - aspect_ratio = img.height / img.width - new_height = int(aspect_ratio * new_width * 0.55) - return new_width, new_height - - async def _send_ascii_file(self, message, ascii_art): - """Сохраняет ASCII-арт во временный файл и отправляет его""" - try: - with tempfile.NamedTemporaryFile( - mode="w", encoding="utf-8", suffix=".txt", delete=False - ) as tmp_file: - tmp_file_path = tmp_file.name - tmp_file.write(ascii_art) - - await message.client.send_file( - message.chat_id, - tmp_file_path, - caption=self.strings("done"), - force_document=True, - reply_to=getattr(message, "reply_to_msg_id", None), - ) - finally: - if tmp_file_path and os.path.exists(tmp_file_path): - os.remove(tmp_file_path) diff --git a/archquise/H.Modules/AccountData.py b/archquise/H.Modules/AccountData.py deleted file mode 100644 index 385a977..0000000 --- a/archquise/H.Modules/AccountData.py +++ /dev/null @@ -1,134 +0,0 @@ -# Proprietary License Agreement - -# Copyright (c) 2024-29 CodWiz - -# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions: - -# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author. - -# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author. - -# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software. - -# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software. - -# 5. By using the Software, you agree to be bound by the terms and conditions of this license. - -# For any inquiries or requests for permissions, please contact codwiz@yandex.ru. - -# --------------------------------------------------------------------------------- -# Name: AccountData -# Description: Find out the approximate date of registration of the telegram account -# Author: @hikka_mods -# --------------------------------------------------------------------------------- -# meta developer: @hikka_mods -# scope: Api AccountData -# scope: Api AccountData 0.0.1 -# --------------------------------------------------------------------------------- - -import asyncio -import logging -from datetime import datetime -from typing import Optional - -import aiohttp - -from .. import loader, utils - -logger = logging.getLogger(__name__) - - -@loader.tds -class AccountData(loader.Module): - """Find out the approximate date of registration of the telegram account""" - - def __init__(self): - self.config = loader.ModuleConfig( - loader.ConfigValue( - "api_token", - "7518491974:1ea2284eec9dc40a9838cfbcb48a2b36", - "API token for datereg.pro", - validator=loader.validators.Hidden(), - ) - ) - self._session: Optional[aiohttp.ClientSession] = None - - async def _get_session(self) -> aiohttp.ClientSession: - if self._session is None or self._session.closed: - self._session = aiohttp.ClientSession( - timeout=aiohttp.ClientTimeout(total=15) - ) - return self._session - - async def on_unload(self): - if self._session and not self._session.closed: - await self._session.close() - - strings = { - "name": "AccountData", - "_cls_doc": "Find out the approximate date of registration of the telegram account", - "date_text": "⏰️ Date of registration of this account: {data} (Accuracy: {accuracy}%)", - "date_text_ps": " Tip: To increase accuracy, the person whose registration date is being checked can write any message to @mewpl2.\n\nDon't worry, this account is not run by a person, but by a userbot just like yours, which will check the registration date using Telegram's built-in tool.", - "no_reply": "💬 You did not reply to the user's message", - } - - strings_ru = { - "date_text": "⏰️ Дата регистрации этого аккаунта: {data} (Точность: {accuracy}%)", - "_cls_doc": "Узнайте примерную дату регистрации Telegram-аккаунта", - "date_text_ps": " Совет: Для повышения точности, человек, дата регистрации которого проверяется, может написать любое сообщение @mewpl2.\n\nНе бойтесь, на этом аккаунте сидит не человек, а такой же юзербот, как и у вас, который проверит дату регистрации при помощи встроенного инструмента Telegram.", - "no_reply": "💬 Вы не ответили на сообщение пользователя", - } - - async def get_creation_date(self, user_id: int) -> dict: - api_token = self.config["api_token"] - if not api_token: - return {"error": "API token not configured"} - - url = "https://api.datereg.pro/api/v1/users/getCreationDateFast" - params = {"token": api_token, "user_id": user_id} - - session = await self._get_session() - try: - async with session.get(url, params=params) as response: - if response.status == 200: - json_response = await response.json() - if json_response["success"]: - return { - "creation_date": json_response["creation_date"], - "accuracy_percent": json_response["accuracy_percent"], - } - else: - return {"error": json_response["error"]["message"]} - else: - return {"error": f"HTTP {response.status}"} - except asyncio.TimeoutError: - return {"error": "Request timed out"} - except Exception as e: - return {"error": str(e)} - - @loader.command( - ru_doc="Узнать примерную дату регистрации Telergam-аккаунта", - en_doc="Find out the approximate date of registration of the telegram account", - ) - async def accdata(self, message): - if reply := await message.get_reply_message(): - result = await self.get_creation_date(user_id=reply.sender.id) - - if "error" in result or not result.get("creation_date"): - error_msg = result.get("error", "Unknown error occurred") - await utils.answer(message, f"Ошибка: {error_msg}") - return - - try: - month, year = map(int, result["creation_date"].split(".")) - date_object = datetime(year, month, 1) - formatted = date_object.strftime("%B %Y") - - await utils.answer( - message, - f"{self.strings('date_text').format(data=formatted, accuracy=result['accuracy_percent'])}\n\n{self.strings('date_text_ps')}", - ) - except (ValueError, KeyError) as e: - await utils.answer(message, f"Ошибка обработки данных: {str(e)}") - else: - await utils.answer(message, self.strings("no_reply")) diff --git a/archquise/H.Modules/AnimeQuotes.py b/archquise/H.Modules/AnimeQuotes.py deleted file mode 100644 index 4d98c64..0000000 --- a/archquise/H.Modules/AnimeQuotes.py +++ /dev/null @@ -1,100 +0,0 @@ -# Proprietary License Agreement - -# Copyright (c) 2024-29 CodWiz - -# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions: - -# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author. - -# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author. - -# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software. - -# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software. - -# 5. By using the Software, you agree to be bound by the terms and conditions of this license. - -# For any inquiries or requests for permissions, please contact codwiz@yandex.ru. - -# --------------------------------------------------------------------------------- -# Name: AnimeQuotes -# Description: A module for sending random quotes from anime -# Author: @hikka_mods -# --------------------------------------------------------------------------------- -# meta developer: @hikka_mods -# scope: AnimeQuotes -# scope: AnimeQuotes 0.0.1 -# requires: requests -# --------------------------------------------------------------------------------- - -import logging -from typing import Optional - -import aiohttp - -from .. import loader, utils - -logger = logging.getLogger(__name__) - - -@loader.tds -class AnimeQuotesMod(loader.Module): - """A module for sending random quotes from anime""" - - strings = { - "name": "AnimeQuotes", - "quote_template": ( - 'Quote: "{quote}"\n\n' - "Character: {character}\n" - "Anime: {anime}" - ), - "error": "Couldn't get a quote. Try again later!", - } - - strings_ru = { - "quote_template": ( - 'Цитата: "{quote}"\n\n' - "Персонаж: {character}\n" - "Аниме: {anime}" - ), - "error": "Не удалось получить цитату. Попробуйте позже!", - } - - def __init__(self): - self._session: Optional[aiohttp.ClientSession] = None - - async def _get_session(self) -> aiohttp.ClientSession: - if self._session is None or self._session.closed: - self._session = aiohttp.ClientSession( - timeout=aiohttp.ClientTimeout(total=15) - ) - return self._session - - async def on_unload(self): - if self._session and not self._session.closed: - await self._session.close() - - @loader.command( - ru_doc="Получить случайную цитату из аниме", - en_doc="Get a random quote from the anime", - ) - async def quote(self, message): - url = "https://api.animechan.io/v1/quotes/random" - - try: - session = await self._get_session() - async with session.get(url) as response: - response.raise_for_status() - data = await response.json() - - quote_content = data["data"]["content"] - character_name = data["data"]["character"]["name"] - anime_name = data["data"]["anime"]["name"] - - quote = self.strings("quote_template").format( - quote=quote_content, character=character_name, anime=anime_name - ) - await utils.answer(message, quote) - - except aiohttp.ClientError: - await utils.answer(message, self.strings("error")) diff --git a/archquise/H.Modules/Article.py b/archquise/H.Modules/Article.py deleted file mode 100644 index 67a81d0..0000000 --- a/archquise/H.Modules/Article.py +++ /dev/null @@ -1,76 +0,0 @@ -# Proprietary License Agreement - -# Copyright (c) 2024-29 CodWiz - -# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions: - -# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author. - -# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author. - -# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software. - -# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software. - -# 5. By using the Software, you agree to be bound by the terms and conditions of this license. - -# For any inquiries or requests for permissions, please contact codwiz@yandex.ru. - -# --------------------------------------------------------------------------------- -# Name: Article -# Description: Displays your article Criminal Code of the Russian Federation -# Author: @hikka_mods -# --------------------------------------------------------------------------------- -# meta developer: @hikka_mods -# scope: Article -# scope: Article 0.0.1 -# requires: requests -# --------------------------------------------------------------------------------- - -import json -import logging -import random -from typing import Dict - -import requests - -from .. import loader, utils - -logger = logging.getLogger(__name__) - -@loader.tds -class ArticleMod(loader.Module): - """Displays your article Criminal Code of the Russian Federation""" - - strings = { - "name": "Article", - "article": "📖 Your article of the Criminal Code of the Russian Federation:\n\n
Number {}\n\n{}
", - } - - strings_ru = { - "article": "📖 Твоя статья УК РФ:\n\n
Номер {}\n\n{}
", - } - - @loader.command( - ru_doc="Отображается ваша статья Уголовного кодекса Российской Федерации", - en_doc="Displays your article Criminal Code of the Russian Federation", - ) - async def arccmd(self, message): - if values := self._load_values(): - random_key = random.choice(list(values.keys())) - random_value = values[random_key] - await utils.answer( - message, self.strings("article").format(random_key, random_value) - ) - - def _load_values(self) -> Dict[str, str]: - url = "https://raw.githubusercontent.com/Codwizer/ReModules/main/assets/zakon.json" - try: - response = requests.get(url) - if response.ok: - data = json.loads(response.text) - return data - except (requests.RequestException, json.JSONDecodeError): - pass - - return {} diff --git a/archquise/H.Modules/AutofarmCookies.py b/archquise/H.Modules/AutofarmCookies.py deleted file mode 100644 index 44735e2..0000000 --- a/archquise/H.Modules/AutofarmCookies.py +++ /dev/null @@ -1,205 +0,0 @@ -# Proprietary License Agreement - -# Copyright (c) 2024-29 CodWiz - -# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions: - -# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author. - -# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author. - -# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software. - -# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software. - -# 5. By using the Software, you agree to be bound by the terms and conditions of this license. - -# For any inquiries or requests for permissions, please contact codwiz@yandex.ru. - -# --------------------------------------------------------------------------------- -# Name: AutofarmCookies -# Description: Autofarm in the bot @cookies_game_bot -# Author: @hikka_mods -# --------------------------------------------------------------------------------- -# meta developer: @hikka_mods -# scope: AutofarmCookies -# scope: AutofarmCookies 0.0.1 -# --------------------------------------------------------------------------------- - -import logging -import random -from datetime import timedelta - -from telethon import functions -from telethon.tl.custom import Message - -from .. import loader, utils - -__version__ = (1, 0, 0) -logger = logging.getLogger(__name__) - -@loader.tds -class AutofarmCookiesMod(loader.Module): - """Autofarm in the bot @cookies_game_bot""" - - strings = { - "name": "AutofarmCookies", - "farmon": ( - "The deferred task has been created, autofarming has been started, everything will start in 10 minutes" - " seconds..." - ), - "farmon_already": "It has already been launched :)", - "farmoff": "The autopharm is stopped\nSelected: %coins% Cookies", - "farm": "I typed: %coins% Cookies", - } - - strings_ru = { - "farmon": ( - "Отложенная задача создана, автофарминг запущен, всё начнётся через 10" - " секунд..." - ), - "farmon_already": "Уже запущено :)", - "farmoff": "Автофарм остановлен.\nНвброно: %coins% Cookies", - "farm": "Я набрал: %coins% Cookies", - } - - def __init__(self): - self.name = self.strings["name"] - - async def client_ready(self, client, db): - self.client = client - self.db = db - self.myid = (await client.get_me()).id - self.cookies = "@cookies_game_bot" - - @loader.command( - ru_doc="Запустить автофарминг", - en_doc="Launch auto-farming", - ) - async def cookon(self, message): - status = self.db.get(self.name, "status", False) - if status: - return await message.edit(self.strings["farmon_already"]) - self.db.set(self.name, "status", True) - await self.client.send_message( - self.cookies, "/cookie", schedule=timedelta(seconds=10) - ) - await message.edit(self.strings["farmon"]) - - @loader.command( - ru_doc="Остановить автофарминг", - en_doc="Stop auto-farming", - ) - async def cookoff(self, message): - self.db.set(self.name, "status", False) - coins = self.db.get(self.name, "coins", 0) - if coins: - self.db.set(self.name, "coins", 0) - await message.edit(self.strings["farmoff"].replace("%coins%", str(coins))) - - @loader.command( - ru_doc="Вывод кол-ва коинов, добытых этим модулем", - en_doc="Output of the number of coins mined by this module", - ) - async def cookies(self, message): - coins = self.db.get(self.name, "coins", 0) - await message.edit(self.strings["farm"].replace("%coins%", str(coins))) - - async def watcher(self, event): - if not isinstance(event, Message): # noqa: F821 - return - chat = utils.get_chat_id(event) - if chat != self.cookies: - return - status = self.db.get(self.name, "status", False) - if not status: - return - if event.raw_text == "/cookie": - return await self.client.send_message( - self.cookies, "/cookie", schedule=timedelta(hours=2) - ) - if event.sender_id != self.cookies: - return - if "🙅‍♂️!" in event.raw_text: - args = [int(x) for x in event.raw_text.split() if x.isnumeric()] - randelta = random.randint(20, 60) - if len(args) == 4: - delta = timedelta( - hours=args[1], minutes=args[2], seconds=args[3] + randelta - ) - elif len(args) == 3: - delta = timedelta(minutes=args[1], seconds=args[2] + randelta) - elif len(args) == 2: - delta = timedelta(seconds=args[1] + randelta) - else: - return - sch = ( - await self.client( - functions.messages.GetScheduledHistoryRequest(self.cookies, 1488) - ) - ).messages - await self.client( - functions.messages.DeleteScheduledMessagesRequest( - self.cookies, id=[x.id for x in sch] - ) - ) - return await self.client.send_message( - self.cookies, "/cookie", schedule=delta - ) - if "✨" in event.raw_text: - args = event.raw_text.split() - for x in args: - if x[0] == "+": - return self.db.set( - self.name, - "coins", - self.db.get(self.name, "coins", 0) + int(x[1:]), - ) - - async def message_q( - self, - text: str, - user_id: int, - mark_read: bool = False, - delete: bool = False, - ): - async with self.client.conversation(user_id) as conv: - msg = await conv.send_message(text) - response = await conv.get_response() - if mark_read: - await conv.mark_read() - - if delete: - await msg.delete() - await response.delete() - - return response - - @loader.command( - ru_doc="Показывает ваш мешок", - en_doc="Shows your bag", - ) - async def me(self, message): - bot = "@cookies_game_bot" - bags = await self.message_q( - "/me", - bot, - delete=True, - ) - - args = utils.get_args_raw(message) - - if not args: - await utils.answer(message, bags.text) - - @loader.command( - ru_doc="Помощь по модулю AutofarmCookies", - en_doc="Help with the AutofarmCookies module", - ) - async def ckies(self, message): - chelp = """ - 🍀| Помощь по командам: - .cookon - Включает авто-фарм. - .cookoff - Выключает авто-фарм. - .me - Показывает ваш мешок""" - await utils.answer(message, chelp) diff --git a/archquise/H.Modules/BirthdayTime.py b/archquise/H.Modules/BirthdayTime.py deleted file mode 100644 index 8d40b36..0000000 --- a/archquise/H.Modules/BirthdayTime.py +++ /dev/null @@ -1,252 +0,0 @@ -# Proprietary License Agreement - -# Copyright (c) 2024-29 CodWiz - -# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions: - -# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author. - -# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author. - -# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software. - -# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software. - -# 5. By using the Software, you agree to be bound by the terms and conditions of this license. - -# For any inquiries or requests for permissions, please contact codwiz@yandex.ru. - -# --------------------------------------------------------------------------------- -# Name: BirthdayTime -# Description: Counting down to your birthday -# Author: @hikka_mods -# --------------------------------------------------------------------------------- -# meta developer: @hikka_mods -# scope: BirthdayTime -# scope: Api BirthdayTime 0.0.1 -# --------------------------------------------------------------------------------- - -import asyncio -import calendar -import logging -import random -from datetime import datetime - -from telethon.errors.rpcerrorlist import UserPrivacyRestrictedError -from telethon.tl.functions.account import UpdateProfileRequest -from telethon.tl.functions.users import GetFullUserRequest - -from .. import loader, utils - -logger = logging.getLogger(__name__) - -D_MSG = [ - "Ждешь его?", - "Осталось немного)", - "Дни пролетят, даже не заметишь", - "Уже знаешь что хочешь получить в подарок?)", - "Сколько исполняется?", - "Жду не дождусь уже", -] - - -@loader.tds -class DaysToMyBirthday(loader.Module): - """Counting down to your birthday""" - - strings = { - "name": "BirthdayTime", - "date_error": "❗️ Your birthdate is not specified in the config, please correct this :)", - "msg": ( - "🎉 " - "There are {} days, {} hours, {} minutes, and {} seconds left until your birthday. \n" - "💙 {}" - ), - "conf": "Open config...", - "name_changed": "Name updated!", - "name_not_changed": "Name was not updated.", - "name_privacy_error": "Unable to change name due to privacy settings.", - "error": "An error occurred. Please check the logs.", - } - - strings_ru = { - "date_error": "❗️ В конфиге не указан день вашего рождения, пожалуйста, исправь это :)", - "msg": ( - "🎉 " - "До вашего дня рождения осталось {} дней, {} часов, {} " - "минут, {} секунд. \n" - "💙 {}" - ), - "conf": "Открываю конфиг...", - "btname_yes": ( - "😶 Хорошо, теперь я " - "буду изменять ваше имя в зависимости от количества дней до дня рождения" - ), - "btname_no": "😶Хорошо, я больше не буду изменять ваше имя", - "name_changed": "Имя обновлено!", - "name_not_changed": "Имя не было обновлено.", - "name_privacy_error": "Не удалось изменить имя из-за настроек приватности.", - "error": "Произошла ошибка. Пожалуйста, проверьте логи.", - } - - def __init__(self): - self.config = loader.ModuleConfig( - loader.ConfigValue( - "birthday_date", - None, - lambda: "Дата вашего рождения. Указывать только день", - validator=loader.validators.Integer(), - ), - loader.ConfigValue( - "birthday_month", - None, - "Месяц вашего рождения", - validator=loader.validators.Choice( - [ - "January", - "February", - "March", - "April", - "May", - "June", - "July", - "August", - "September", - "October", - "November", - "December", - ] - ), - ), - ) - self._task = None - - async def client_ready(self): - if self._task: - self._task.cancel() - - self._task = asyncio.create_task(self.checker()) - - async def checker(self): - while True: - if not self.db.get(__name__, "change_name", False): - await asyncio.sleep(60) - continue - try: - now = datetime.now() - day = self.config["birthday_date"] - monthy = self.config["birthday_month"] - month = list(calendar.month_name).index(monthy) - birthday = datetime(now.year, month, day) - - if now.month > month or (now.month == month and now.day > day): - birthday = datetime(now.year + 1, month, day) - - time_to_birthday = abs(birthday - now) - days = time_to_birthday.days - - user = await self.client(GetFullUserRequest(self.client.hikka_me.id)) - if not user or not user.users: - await asyncio.sleep(60) - continue - - name = user.users[0].last_name or "" - - ln = f"{self.db.get(__name__, 'last_name', '')} • {days} d." - if name == ln: - await asyncio.sleep(60) - continue - else: - await self.client(UpdateProfileRequest(last_name=ln)) - self.db.set(__name__, "last_name", name) - except UserPrivacyRestrictedError: - self.db.set(__name__, "change_name", False) - logger.error("Error: Can't change name due to privacy settings.") - except Exception as e: - logger.error(f"Error in checker: {e}") - finally: - await asyncio.sleep(60) - - @loader.command( - ru_doc="Включить таймер дней в ник (нестабильно)", - en_doc="Enable timer of days in nickname (unstable)", - ) - async def btnameon(self, message): - try: - user = await self.client(GetFullUserRequest(self.client.hikka_me.id)) - name = user.users[0].last_name or "" - except Exception as e: - logger.error(f"Error getting user info: {e}") - await utils.answer(message, self.strings("error")) - return - - self.db.set(__name__, "last_name", name) - self.db.set(__name__, "change_name", True) - await utils.answer(message, self.strings("btname_yes")) - - @loader.command( - ru_doc="Выключить таймер дней в ник", - en_doc="Disable timer of days in nickname", - ) - async def btnameoff(self, message): - change_name = self.db.get(__name__, "change_name", False) - - if not change_name: - await utils.answer(message, self.strings("btname_no")) - return - - self.db.set(__name__, "change_name", False) - await utils.answer(message, self.strings("btname_no")) - try: - await self.client( - UpdateProfileRequest(last_name=self.db.get(__name__, "last_name")) - ) - await utils.answer(message, self.strings("name_not_changed")) - except UserPrivacyRestrictedError: - await utils.answer(message, self.strings("name_privacy_error")) - except Exception as e: - logger.error(f"Error removing name: {e}") - await utils.answer(message, self.strings("error")) - - @loader.command( - ru_doc="Вывести таймер", - en_doc="Display the timer", - ) - async def bt(self, message): - if ( - self.config["birthday_date"] is None - or self.config["birthday_month"] is None - ): - await utils.answer(message, self.strings("date_error")) - msg = await self.client.send_message(message.chat_id, self.strings("conf")) - await self.allmodules.commands["config"]( - await utils.answer(msg, f"{self.get_prefix()}config BirthdayTime") - ) - return - - try: - now = datetime.now() - day = self.config["birthday_date"] - monthy = self.config["birthday_month"] - month = list(calendar.month_name).index(monthy) - birthday = datetime(now.year, month, day) - - if now.month > month or (now.month == month and now.day > day): - birthday = datetime(now.year + 1, month, day) - - time_to_birthday = abs(birthday - now) - - await utils.answer( - message, - self.strings("msg").format( - time_to_birthday.days, - (time_to_birthday.seconds // 3600), - (time_to_birthday.seconds // 60 % 60), - (time_to_birthday.seconds % 60), - random.choice(D_MSG), - ), - ) - - except Exception as e: - logger.error(f"Error in bt command: {e}") - await utils.answer(message, self.strings("error")) diff --git a/archquise/H.Modules/CNAME b/archquise/H.Modules/CNAME deleted file mode 100644 index e727422..0000000 --- a/archquise/H.Modules/CNAME +++ /dev/null @@ -1 +0,0 @@ -mods.archquise.ru \ No newline at end of file diff --git a/archquise/H.Modules/CheckSpamBan.py b/archquise/H.Modules/CheckSpamBan.py deleted file mode 100644 index 9a9c347..0000000 --- a/archquise/H.Modules/CheckSpamBan.py +++ /dev/null @@ -1,54 +0,0 @@ -# Proprietary License Agreement - -# Copyright (c) 2024-29 CodWiz - -# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions: - -# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author. - -# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author. - -# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software. - -# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software. - -# 5. By using the Software, you agree to be bound by the terms and conditions of this license. - -# For any inquiries or requests for permissions, please contact codwiz@yandex.ru. - -# --------------------------------------------------------------------------------- -# Name: CheckSpamBan -# Description: Check spam ban for your account. -# Author: @hikka_mods -# --------------------------------------------------------------------------------- -# meta developer: @hikka_mods -# scope: CheckSpamBan -# scope: CheckSpamBan 0.0.1 -# --------------------------------------------------------------------------------- - -import logging - -from .. import loader, utils - -logger = logging.getLogger(__name__) - - -@loader.tds -class SpamBanCheckMod(loader.Module): - """Checks spam ban for your account.""" - - strings = { - "name": "CheckSpamBan", - } - - @loader.command( - ru_doc="Проверяет вашу учетную запись на спам-бан с помощью бота @SpamBot", - en_doc="Checks your account for spam ban via @SpamBot bot", - ) - async def spambot(self, message): - async with self.client.conversation(178220800) as conv: - user_message = await conv.send_message("/start") - await user_message.delete() - spam_message = await conv.get_response() - await utils.answer(message, spam_message.text) - await spam_message.delete() diff --git a/archquise/H.Modules/CryptoCurrency.py b/archquise/H.Modules/CryptoCurrency.py deleted file mode 100644 index bff8059..0000000 --- a/archquise/H.Modules/CryptoCurrency.py +++ /dev/null @@ -1,126 +0,0 @@ -# Proprietary License Agreement - -# Copyright (c) 2024-29 CodWiz - -# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions: - -# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author. - -# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author. - -# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software. - -# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software. - -# 5. By using the Software, you agree to be bound by the terms and conditions of this license. - -# For any inquiries or requests for permissions, please contact codwiz@yandex.ru. - -# --------------------------------------------------------------------------------- -# Name: CryptoCurrency -# Description: Module for displaying current cryptocurrency exchange rates. -# Author: @hikka_mods -# --------------------------------------------------------------------------------- -# meta developer: @hikka_mods -# scope: Api CryptoCurrency -# scope: Api CryptoCurrency 0.0.1 -# --------------------------------------------------------------------------------- - -import logging -from typing import Optional - -import aiohttp - -from .. import loader, utils - -logger = logging.getLogger(__name__) - - -@loader.tds -class CryptoCurrencyMod(loader.Module): - """Module for displaying current cryptocurrency exchange rates.""" - - strings = { - "name": "CryptoCurrency", - "query_missing": "Please specify a cryptocurrency ticker or name.", - "coin_not_found": "Cryptocurrency '{query}' not found.", - } - - strings_ru = { - "query_missing": "Пожалуйста, укажите тикер или название криптовалюты.", - "coin_not_found": "Криптовалюта '{query}' не найдена.", - } - - def __init__(self): - self._session: Optional[aiohttp.ClientSession] = None - - async def _get_session(self) -> aiohttp.ClientSession: - if self._session is None or self._session.closed: - self._session = aiohttp.ClientSession( - timeout=aiohttp.ClientTimeout(total=15) - ) - return self._session - - async def on_unload(self): - if self._session and not self._session.closed: - await self._session.close() - - async def fetch_json(self, url): - """Fetch JSON data from a given URL.""" - session = await self._get_session() - async with session.get(url) as response: - response.raise_for_status() - return await response.json() - - async def get_exchange_rates(self): - """Get exchange rates for RUB and EUR based on USD.""" - data = await self.fetch_json("https://open.er-api.com/v6/latest/USD") - return data["rates"]["RUB"], data["rates"]["EUR"] - - async def find_coin(self, query): - """Find a cryptocurrency by its name or symbol.""" - data = await self.fetch_json( - "https://api.coinlore.net/api/tickers/?start=0&limit=100" - ) - return next( - ( - item - for item in data["data"] - if query.lower() in item["name"].lower() - or query.lower() in item["symbol"].lower() - ), - None, - ) - - @loader.command( - ru_doc="Отображает текущий курс криптовалюты в рублях, долларах США и евро", - en_doc="Displays the current cryptocurrency rate in RUB, USD, and EUR", - ) - async def crypto(self, message): - query = utils.get_args_raw(message) - if not query: - return await utils.answer(message, self.strings("query_missing")) - - coin = await self.find_coin(query) - if not coin: - return await utils.answer( - message, self.strings("coin_not_found").format(query=query) - ) - - price_usd = float(coin["price_usd"]) - usd_rub_rate, usd_eur_rate = await self.get_exchange_rates() - - price_rub = price_usd * usd_rub_rate - price_eur = price_usd * usd_eur_rate - - response = self.format_response(coin, price_usd, price_rub, price_eur) - await utils.answer(message, response) - - def format_response(self, coin, price_usd, price_rub, price_eur): - """Format the response message with cryptocurrency information.""" - return ( - f"💰 {coin['name']} ({coin['symbol']})\n" - f"USD: ${price_usd:.2f}\n" - f"RUB: ₽{price_rub:.2f}\n" - f"EUR: €{price_eur:.2f}\n" - ) diff --git a/archquise/H.Modules/EmojiStickerBlocker.py b/archquise/H.Modules/EmojiStickerBlocker.py deleted file mode 100644 index a07eca7..0000000 --- a/archquise/H.Modules/EmojiStickerBlocker.py +++ /dev/null @@ -1,360 +0,0 @@ -# Proprietary License Agreement - -# Copyright (c) 2024-29 CodWiz - -# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions: - -# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author. - -# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author. - -# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software. - -# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software. - -# 5. By using the Software, you agree to be bound by the terms and conditions of this license. - -# For any inquiries or requests for permissions, please contact codwiz@yandex.ru. - -# --------------------------------------------------------------------------------- -# Name: EmojiStickerBlocker -# Description: Block emojis, stickers and sticker packs -# Author: @hikka_mods -# --------------------------------------------------------------------------------- -# meta developer: @hikka_mods -# scope: EmojiStickerBlocker -# scope: EmojiStickerBlocker0.0.1 -# --------------------------------------------------------------------------------- - -import logging -import re -from typing import Optional, Set - -from telethon.errors import FloodWaitError, MessageDeleteForbiddenError -from telethon.tl.types import Message, MessageMediaDocument - -from .. import loader, utils - -logger = logging.getLogger(__name__) - - -@loader.tds -class EmojiStickerBlocker(loader.Module): - """Block emojis, stickers and sticker packs with enhanced functionality""" - - strings = { - "name": "EmojiStickerBlocker", - "no_permission": " Need delete messages permission", - "pack_blocked": " Pack blocked", - "pack_not_found": " Pack not found", - "sticker_blocked": " Sticker blocked", - "emoji_blocked": " Emoji blocked", - "pack_unblocked": " Pack unblocked", - "item_unblocked": " Item unblocked", - "not_found": " Not in blocklist", - "no_reply": " Reply to a sticker or emoji", - "no_args": " Specify pack link or name", - "list_packs": "📦 Blocked packs: {}", - "list_stickers": "🖼 Blocked stickers: {}", - "list_emojis": "😀 Blocked emojis: {}", - "all_cleared": "✅ All blocks cleared", - } - - strings_ru = { - "no_permission": " Нужны права на удаление сообщений", - "pack_blocked": " Пак заблокирован", - "pack_not_found": " Пак не найден", - "sticker_blocked": " Стикер заблокирован", - "emoji_blocked": " Эмодзи заблокирован", - "pack_unblocked": " Пак разблокирован", - "item_unblocked": " Элемент разблокирован", - "not_found": " Не найден в блоклисте", - "no_reply": " Ответьте на стикер или эмодзи", - "no_args": " Укажите ссылку или название пака", - "list_packs": "📦 Заблокированные паки: {}", - "list_stickers": "🖼 Заблокированные стикеры: {}", - "list_emojis": "😀 Заблокированные эмодзи: {}", - "all_cleared": "✅ Все блоки очищены", - } - - def __init__(self): - self.blocked_packs: Set[str] = set() - self.blocked_stickers: Set[str] = set() - self.blocked_emojis: Set[str] = set() - self._client = None - self._db = None - - async def client_ready(self, client, db): - self._client = client - self._db = db - self._load_blocklists() - - def _load_blocklists(self): - self.blocked_packs = set(self._db.get(__name__, "blocked_packs", [])) - self.blocked_stickers = set(self._db.get(__name__, "blocked_stickers", [])) - self.blocked_emojis = set(self._db.get(__name__, "blocked_emojis", [])) - - def _save_blocklists(self): - self._db.set(__name__, "blocked_packs", list(self.blocked_packs)) - self._db.set(__name__, "blocked_stickers", list(self.blocked_stickers)) - self._db.set(__name__, "blocked_emojis", list(self.blocked_emojis)) - - def _extract_pack_name(self, message: Message) -> Optional[str]: - """Extract pack name from sticker or emoji""" - if not message.media: - return None - - if message.sticker: - if hasattr(message.sticker, "set_name") and message.sticker.set_name: - return message.sticker.set_name.lower() - - if isinstance(message.media, MessageMediaDocument): - if hasattr(message.media, "document") and hasattr( - message.media.document, "attributes" - ): - for attr in message.media.document.attributes: - if ( - hasattr(attr, "stickerset") - and hasattr(attr.stickerset, "title") - and attr.stickerset.title - ): - return attr.stickerset.title.lower() - - return None - - def _extract_emoji_text(self, message: Message) -> Optional[str]: - """Extract emoji text from message""" - if not message.message: - return None - - emoji_pattern = re.compile( - r"[\U0001F600-\U0001F64F\U0001F300-\U0001F5FF\U0001F680-\U0001F6FF\U0001F1E0-\U0001F1FF\U00002702-\U000027B0\U000024C2-\U0001F251]" - ) - emojis = emoji_pattern.findall(message.message) - - if emojis: - return emojis[0] - return None - - async def _delete_message(self, message: Message) -> bool: - """Delete message with error handling""" - try: - await self._client.delete_messages(message.to_id, [message.id]) - return True - except MessageDeleteForbiddenError: - logger.warning("No permission to delete message") - return False - except FloodWaitError as e: - logger.warning(f"Flood wait when deleting message: {e.seconds}s") - return False - except Exception as e: - logger.error(f"Error deleting message: {e}") - return False - - async def _should_block_message(self, message: Message) -> tuple[bool, str]: - """Check if message should be blocked and return reason""" - try: - pack_name = self._extract_pack_name(message) - emoji_text = self._extract_emoji_text(message) - - if pack_name and pack_name in self.blocked_packs: - return True, f"pack: {pack_name}" - - if message.sticker: - sticker_id = str(message.sticker.id) - if sticker_id in self.blocked_stickers: - return True, f"sticker: {sticker_id}" - - if emoji_text and emoji_text in self.blocked_emojis: - return True, f"emoji: {emoji_text}" - - except Exception as e: - logger.error(f"Error checking message: {e}") - - return False, "" - - @loader.command( - ru_doc="[link/название пака] — блокирует эмодзипак/стикерпак в личных сообщениях", - en_doc="[link/pack name] — block emoji pack/sticker pack in private messages", - ) - async def packblock(self, message: Message): - """Block emoji pack/sticker pack""" - args = utils.get_args_raw(message) - if not args: - return await utils.answer(message, self.strings["no_args"]) - - pack_name = args.lower().strip() - - if pack_name in self.blocked_packs: - return await utils.answer(message, self.strings["not_found"]) - - self.blocked_packs.add(pack_name) - self._save_blocklists() - - await utils.answer(message, self.strings["pack_blocked"]) - - @loader.command( - ru_doc="[reply] — блокирует определенный стикер", - en_doc="[reply] — block specific sticker", - ) - async def stickblock(self, message: Message): - """Block sticker from reply""" - if not message.is_reply: - return await utils.answer(message, self.strings["no_reply"]) - - reply_msg = await message.get_reply_message() - if not reply_msg or not reply_msg.sticker: - return await utils.answer(message, self.strings["no_reply"]) - - sticker_id = str(reply_msg.sticker.id) - - if sticker_id in self.blocked_stickers: - return await utils.answer(message, self.strings["not_found"]) - - self.blocked_stickers.add(sticker_id) - self._save_blocklists() - - await utils.answer(message, self.strings["sticker_blocked"]) - - @loader.command( - ru_doc="[reply/enter] — блокирует определенное эмодзи", - en_doc="[reply/enter] — block specific emoji", - ) - async def emojiblock(self, message: Message): - """Block emoji from reply or input""" - args = utils.get_args_raw(message) - emoji_text = None - - if args: - emoji_text = args.strip() - if not emoji_text: - return await utils.answer(message, self.strings["no_args"]) - else: - if not message.is_reply: - return await utils.answer(message, self.strings["no_reply"]) - - reply_msg = await message.get_reply_message() - if not reply_msg: - return await utils.answer(message, self.strings["no_reply"]) - - emoji_text = self._extract_emoji_text(reply_msg) - if not emoji_text: - return await utils.answer(message, self.strings["no_reply"]) - - if emoji_text in self.blocked_emojis: - return await utils.answer(message, self.strings["not_found"]) - - self.blocked_emojis.add(emoji_text) - self._save_blocklists() - - await utils.answer(message, self.strings["emoji_blocked"]) - - @loader.command( - ru_doc="— снимает блокировку с эмодзипака/стикерпака", - en_doc="— unblock emoji pack/sticker pack", - ) - async def ublpack(self, message: Message): - """Unblock emoji pack/sticker pack""" - args = utils.get_args_raw(message) - if not args: - return await utils.answer(message, self.strings["no_args"]) - - pack_name = args.lower().strip() - - if pack_name in self.blocked_packs: - self.blocked_packs.remove(pack_name) - self._save_blocklists() - await utils.answer(message, self.strings["pack_unblocked"]) - else: - await utils.answer(message, self.strings["not_found"]) - - @loader.command( - ru_doc="[reply/enter] — снимает блокировку с определенного эмодзи/стикера", - en_doc="[reply/enter] — unblock specific emoji/sticker", - ) - async def ublthis(self, message: Message): - """Unblock emoji/sticker from reply or input""" - args = utils.get_args_raw(message) - - if args: - item = args.strip() - if not item: - return await utils.answer(message, self.strings["no_args"]) - else: - if not message.is_reply: - return await utils.answer(message, self.strings["no_reply"]) - - reply_msg = await message.get_reply_message() - if not reply_msg: - return await utils.answer(message, self.strings["no_reply"]) - - if reply_msg.sticker: - item = str(reply_msg.sticker.id) - else: - item = self._extract_emoji_text(reply_msg) - - if not item: - return await utils.answer(message, self.strings["no_reply"]) - - unblocked = False - if item in self.blocked_stickers: - self.blocked_stickers.remove(item) - unblocked = True - if item in self.blocked_emojis: - self.blocked_emojis.remove(item) - unblocked = True - - if unblocked: - self._save_blocklists() - await utils.answer(message, self.strings["item_unblocked"]) - else: - await utils.answer(message, self.strings["not_found"]) - - @loader.command( - ru_doc="— показать список заблокированных паков/стикеров/эмодзи", - en_doc="— show list of blocked packs/stickers/emojis", - ) - async def blocklist(self, message: Message): - """Show blocklist""" - packs_list = ", ".join(self.blocked_packs) if self.blocked_packs else "нет" - stickers_list = ( - ", ".join(self.blocked_stickers) if self.blocked_stickers else "нет" - ) - emojis_list = ", ".join(self.blocked_emojis) if self.blocked_emojis else "нет" - - result = [] - if packs_list: - result.append(self.strings["list_packs"].format(packs_list)) - if stickers_list: - result.append(self.strings["list_stickers"].format(stickers_list)) - if emojis_list: - result.append(self.strings["list_emojis"].format(emojis_list)) - - if result: - await utils.answer(message, "\n".join(result)) - else: - await utils.answer(message, self.strings["all_cleared"]) - - @loader.command(ru_doc="— очистить все блокировки", en_doc="— clear all blocks") - async def clearblocks(self, message: Message): - """Clear all blocks""" - self.blocked_packs.clear() - self.blocked_stickers.clear() - self.blocked_emojis.clear() - self._save_blocklists() - - await utils.answer(message, self.strings["all_cleared"]) - - async def watcher(self, message: Message): - """Monitor messages and block unwanted content""" - if not self._client or not self._db: - return - - if message.is_group or message.is_channel: - return - - should_block, reason = await self._should_block_message(message) - - if should_block: - logger.info(f"Blocking message: {reason}") - await self._delete_message(message) diff --git a/archquise/H.Modules/FakeActions.py b/archquise/H.Modules/FakeActions.py deleted file mode 100644 index e2a24e0..0000000 --- a/archquise/H.Modules/FakeActions.py +++ /dev/null @@ -1,90 +0,0 @@ -# Proprietary License Agreement - -# Copyright (c) 2024-29 CodWiz - -# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions: - -# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author. - -# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author. - -# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software. - -# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software. - -# 5. By using the Software, you agree to be bound by the terms and conditions of this license. - -# For any inquiries or requests for permissions, please contact codwiz@yandex.ru. - -# --------------------------------------------------------------------------------- -# Name: FakeActions -# Description: Module for simulating various actions in chat -# Author: @hikka_mods -# --------------------------------------------------------------------------------- -# meta developer: @hikka_mods -# scope: Api FakeActions -# scope: Api FakeActions 0.0.1 -# --------------------------------------------------------------------------------- - -import asyncio -import logging - -from .. import loader, utils - -logger = logging.getLogger(__name__) - -@loader.tds -class FakeActionsMod(loader.Module): - """Module for simulating various actions in chat""" - - strings = {"name": "FakeActions"} - - def __init__(self): - self.config = loader.ModuleConfig( - "DEFAULT_DURATION", 5, "Default duration for actions in seconds" - ) - - async def ftcmd(self, message): - """ - Simulates typing in chat for the specified number of seconds.""" - await self._simulate_action_command(message, "typing") - - async def ffcmd(self, message): - """ - Simulates sending a file.""" - await self._simulate_action_command(message, "document") - - async def fgcmd(self, message): - """ - Simulates recording a voice message.""" - await self._simulate_action_command(message, "record-audio") - - async def fvgcmd(self, message): - """ - Simulates recording a video message.""" - await self._simulate_action_command(message, "record-round") - - async def fpgcmd(self, message): - """ - Simulates playing a game.""" - await self._simulate_action_command(message, "game") - - async def _simulate_action_command(self, message, action): - """General function for handling action simulation commands.""" - duration = self._parse_duration(message) - if duration is None: - await utils.answer( - message, - f"Usage: {self.get_prefix()}{message.raw_text.split()[0][1:]} ", - ) - return - - await message.delete() - await self._simulate_action(message, action, duration) - - def _parse_duration(self, message): - """Parse the duration from the message.""" - args = message.raw_text.split() - if len(args) == 2 and args[1].isdigit(): - return int(args[1]) - return self.config["DEFAULT_DURATION"] - - async def _simulate_action(self, message, action, duration): - """Simulate the specified action in chat.""" - async with message.client.action(message.chat_id, action): - await asyncio.sleep(duration) diff --git a/archquise/H.Modules/FakeWallet.py b/archquise/H.Modules/FakeWallet.py deleted file mode 100644 index 289811a..0000000 --- a/archquise/H.Modules/FakeWallet.py +++ /dev/null @@ -1,179 +0,0 @@ -# Proprietary License Agreement - -# Copyright (c) 2024-29 CodWiz - -# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions: - -# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author. - -# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author. - -# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software. - -# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software. - -# 5. By using the Software, you agree to be bound by the terms and conditions of this license. - -# For any inquiries or requests for permissions, please contact codwiz@yandex.ru. - -# --------------------------------------------------------------------------------- -# Name: FakeWallet -# Description: Fun joke - fake crypto wallet. You can change cryptocurrency values ​​using .cfg FakeWallet. -# Author: @hikka_mods -# --------------------------------------------------------------------------------- -# ----------------------------------------------------------------------------------- -# meta developer: @hikka_mods -# scope: hikka_only -# scope: hikka_min 1.4.2 -# ----------------------------------------------------------------------------------- - -import logging - -from .. import loader, utils - -logger = logging.getLogger(__name__) - -@loader.tds -class FakeWallet(loader.Module): - """Fun joke - fake crypto wallet. You can change cryptocurrency values ​​using .cfg FakeWallet.""" - - def __init__(self): - self.config = loader.ModuleConfig( - loader.ConfigValue( - "Toncoin", - 0, - lambda: self.strings("ton"), - validator=loader.validators.Integer(), - ), - loader.ConfigValue( - "Tether", - 0, - lambda: self.strings("tether"), - validator=loader.validators.Integer(), - ), - loader.ConfigValue( - "Bitcoin", - 0, - lambda: self.strings("btc"), - validator=loader.validators.Integer(), - ), - loader.ConfigValue( - "Etherium", - 0, - lambda: self.strings("ether"), - validator=loader.validators.Integer(), - ), - loader.ConfigValue( - "Binance", - 0, - lambda: self.strings("binc"), - validator=loader.validators.Integer(), - ), - loader.ConfigValue( - "Tron", - 0, - lambda: self.strings("tron"), - validator=loader.validators.Integer(), - ), - loader.ConfigValue( - "USDT", - 0, - lambda: self.strings("usdt"), - validator=loader.validators.Integer(), - ), - loader.ConfigValue( - "Gram", - 0, - lambda: self.strings("gram"), - validator=loader.validators.Integer(), - ), - loader.ConfigValue( - "Litecoin", - 0, - lambda: self.strings("lite"), - validator=loader.validators.Integer(), - ), - ) - - strings = { - "name": "FakeWallet", - "crypto": "Enter a value for your cryptovalute", - "wallet": "👛 Wallet\n\n" - "☺️ Toncoin: {} TON\n\n" - "☺️ Tether: {} USDT\n\n" - "☺️ Bitcoin: {} BTC\n\n" - "☺️ Etherium: {} ETH\n\n" - "☺️ Binance coin: {} BNB\n\n" - "☺️ TRON: {} TRX\n\n" - "☺️ USD Coin: {} USDC\n\n" - "☺️ Gram: {} GRAM\n\n" - "☺️ Litecoin: {} LTC", - "ton": "Enter a value for Toncoin", - "teth": "Enter a value for Tethcoin", - "btc": "Enter a value for Bitcoin", - "ether": "Enter a value for Etherium", - "binc": "Enter a value for Binance coin", - "tron": "Enter a value for Tron", - "usdt": "Enter a value for USDT coin", - "gram": "Enter a value for Gramcoin", - "lite": "Enter a value for Litecoin", - "info": "🫥Attention!\n\n" - "☝️This module is strictly prohibited from being used for the purposes of scam, fraud and advertising.\n\n" - "🗣The module is provided solely for entertainment purposes, and any violation of the Rules for using the module, if detected, will be subject to appropriate punishment", - } - - strings_ru = { - "wallet": "👛 Кошелёк\n\n" - "☺️ Toncoin: {} TON\n\n" - "☺️ Tether: {} USDT\n\n" - "☺️ Bitcoin: {} BTC\n\n" - "☺️ Etherium: {} ETH\n\n" - "☺️ Binance coin: {} BNB\n\n" - "☺️ TRON: {} TRX\n\n" - "☺️ USD Coin: {} USDC\n\n" - "☺️ Gram: {} GRAM\n\n" - "☺️ Litecoin: {} LTC", - "ton": "Введите количество валюты для Toncoin", - "teth": "Введите количество валюты для Tethcoin", - "btc": "Введите количество валюты для Bitcoin", - "ether": "Введите количество валюты для Etherium", - "binc": "Введите количество валюты для Binance coin", - "tron": "Введите количество валюты для Tron", - "usdt": "Введите количество валюты для USDT coin", - "gram": "Введите количество валюты для Gramcoin", - "lite": "Введите количество валюты для Litecoin", - "info": "🫥 Внимание!\n\n" - "☝️ Использование этого модуля в целях скама, обмана и рекламы строго запрещено.\n\n" - "🗣 Модуль предоставлен исключительно в развлекательных целях, и любое нарушение Правил использования модуля, если его обнаружат, будет подлежать соответствующему наказанию.", - } - - @loader.command( - ru_doc="Чтобы заполучить поддельный кошелек", - en_doc="To get a fake wallet", - ) - @loader.command() - async def fwalletcmd(self, message): - ton = self.config["Toncoin"] - teth = self.config["Tether"] - btc = self.config["Bitcoin"] - ether = self.config["Etherium"] - binc = self.config["Binance"] - tron = self.config["Tron"] - usdt = self.config["USDT"] - gram = self.config["Gram"] - lite = self.config["Litecoin"] - - await utils.answer( - message, - self.strings("wallet").format( - ton, teth, btc, ether, binc, tron, usdt, gram, lite - ), - ) - - @loader.command( - ru_doc="Информация о FakeModule", - en_doc="Info about FakeModule", - ) - @loader.command() - async def fwinfocmd(self, message): - await utils.answer(message, self.strings("info")) diff --git a/archquise/H.Modules/GigaChat.py b/archquise/H.Modules/GigaChat.py deleted file mode 100644 index 2ba5d25..0000000 --- a/archquise/H.Modules/GigaChat.py +++ /dev/null @@ -1,124 +0,0 @@ -# Proprietary License Agreement - -# Copyright (c) 2024-29 CodWiz - -# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions: - -# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author. - -# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author. - -# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software. - -# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software. - -# 5. By using the Software, you agree to be bound by the terms and conditions of this license. - -# For any inquiries or requests for permissions, please contact codwiz@yandex.ru. - -# --------------------------------------------------------------------------------- -# Name: GigaChat -# Description: Module for using GigaChat -# Author: @hikka_mods -# --------------------------------------------------------------------------------- -# meta developer: @hikka_mods -# scope: Api GigaChat -# scope: Api GigaChat 0.0.1 -# --------------------------------------------------------------------------------- - -import logging - -from .. import loader, utils - -logger = logging.getLogger(__name__) - -@loader.tds -class GigaChatMod(loader.Module): - """Module for using GigaChat""" - - strings = { - "name": "GigaChat", - "api_key_missing": "Please set the API key in the module configuration.", - "query_missing": "Please enter a query after the command.", - "response_error": "Failed to get a response from GigaChat.", - "error_occurred": "An error occurred: {}", - "formatted_response": ( - " Query: {}\n" - "🤖 GigaChat: {}" - ), - "giga_model": "List of GigaChat models:\n{}", - } - - strings_ru = { - "api_key_missing": "Пожалуйста, установите API ключ в конфигурации модуля.", - "query_missing": "Пожалуйста, введите запрос после команды.", - "response_error": "Не удалось получить ответ от GigaChat.", - "error_occurred": "Произошла ошибка: {}", - "formatted_response": ( - " Запрос: {}\n" - "🤖 GigaChat: {}" - ), - "giga_model": "Список моделей GigaChat:\n{}", - } - - async def client_ready(self, client, db): - self.hmodslib = await self.import_lib( - "https://files.archquise.ru/HModsLibrary.py" - ) - - def __init__(self): - self.config = loader.ModuleConfig( - loader.ConfigValue( - "GIGACHAT_API_KEY", - None, - "Введите ваш API ключ для GigaChat, Чтобы получить ключ API, перейдите сюда: https://developers.sber.ru/studio/workspaces", - validator=loader.validators.Hidden(), - ), - loader.ConfigValue( - "GIGACHAT_MODEL", - "GigaChat", - "Введите модель, ее можно получить при команде .gigamodel", - ), - ) - - @loader.command( - ru_doc="Получите исчерпывающий ответ на свой вопрос", - en_doc="Get GigaResponse to your question", - ) - async def giga(self, message): - api_key = self.config["GIGACHAT_API_KEY"] - if not api_key: - return await utils.answer(message, self.strings("api_key_missing")) - - query = utils.get_args_raw(message) - if not query: - return await utils.answer(message, self.strings("query_missing")) - - try: - response = await self.hmodslib.get_giga_response(api_key, query) - if response: - await utils.answer( - message, self.strings("formatted_response").format(query, response) - ) - else: - await utils.answer(message, self.strings("response_error")) - except Exception as e: - await utils.answer(message, self.strings("error_occurred").format(str(e))) - - @loader.command( - ru_doc="Получить список моделей", - en_doc="Get a list of models", - ) - async def gigamodel(self, message): - api_key = self.config["GIGACHAT_API_KEY"] - if not api_key: - return await utils.answer(message, self.strings("api_key_missing")) - - try: - response = await self.hmodslib.get_giga_models(api_key) - if response: - await utils.answer(message, self.strings("giga_model").format(response)) - else: - await utils.answer(message, self.strings("response_error")) - except Exception as e: - await utils.answer(message, self.strings("error_occurred").format(str(e))) diff --git a/archquise/H.Modules/H.py b/archquise/H.Modules/H.py deleted file mode 100644 index 8a65d07..0000000 --- a/archquise/H.Modules/H.py +++ /dev/null @@ -1,44 +0,0 @@ -# Proprietary License Agreement - -# Copyright (c) 2024-29 CodWiz - -# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions: - -# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author. - -# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author. - -# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software. - -# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software. - -# 5. By using the Software, you agree to be bound by the terms and conditions of this license. - -# For any inquiries or requests for permissions, please contact codwiz@yandex.ru. - -# --------------------------------------------------------------------------------- -# Name: H -# Description: H. -# Author: @hikka_mods -# --------------------------------------------------------------------------------- -# meta developer: @hikka_mods -# scope: H -# scope: H 0.0.1 -# --------------------------------------------------------------------------------- - -from .. import loader, utils - - -@loader.tds -class H(loader.Module): - """H""" - - strings = {"name": "H", "h": "H"} - strings_ru = {"h": "H"} - - @loader.command( - ru_doc="H", - ) - async def h(self, message): - """H""" - await utils.answer(message, self.strings("h")) diff --git a/archquise/H.Modules/HAFK.py b/archquise/H.Modules/HAFK.py deleted file mode 100644 index fb5b020..0000000 --- a/archquise/H.Modules/HAFK.py +++ /dev/null @@ -1,306 +0,0 @@ -# Proprietary License Agreement - -# Copyright (c) 2024-29 CodWiz - -# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions: - -# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author. - -# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author. - -# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software. - -# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software. - -# 5. By using the Software, you agree to be bound by the terms and conditions of this license. - -# For any inquiries or requests for permissions, please contact codwiz@yandex.ru. - -# --------------------------------------------------------------------------------- -# Name: HAFK -# Description: Your personal assistant while you are in AFK mode -# Author: @hikka_mods -# --------------------------------------------------------------------------------- -# meta developer: @hikka_mods -# scope: HAFK -# scope: HAFK 0.0.1 -# --------------------------------------------------------------------------------- - -import asyncio -import datetime -import logging -import time - -from telethon import types -from telethon.utils import get_peer_id - -from .. import loader, utils - -logger = logging.getLogger(__name__) - - -@loader.tds -class HAFK(loader.Module): - strings = { - "name": "HAFK", - "afk_on": "🫶 AFK mode is on!", - "afk_on_reason": "🫶 AFK mode is on!\n\nReason: {}", - "afk_here_on": "🫶 AFK mode is on in this chat!", - "afk_here_on_reason": "🫶 AFK mode is on in this chat!\n\nReason: {}", - "afk_off": "🤌 AFK mode is off!", - "afk_off_time": "🤌 AFK mode is off!\n\nYou were AFK for: {}", - "afk_off_here_time": "🤌 AFK mode is off in this chat!\n\nYou were AFK for: {}", - "already_afk": " You are already in AFK mode!", - "already_afk_here": " You are already in AFK mode in this chat!", - "not_afk": "😐 AFK mode is already off.", - "not_afk_here": "😐 AFK mode is already off in this chat.", - "afk_message": "🫤 I'm currently not accepting messages!\nReason: {}\n\nInactive mode has been on for: {}", - "afk_message_reason": "🫤 I'm currently not accepting messages!\nReason: {}\n\nInactive mode has been on for: {}", - "afk_set_failed": "Failed to set AFK status. Check logs.", - "added_excluded_chat": "Chat {} added to excluded chats.", - "removed_excluded_chat": "Chat {} removed from excluded chats.", - "excluded_chats_list": "Excluded chats:\n{}", - "no_excluded_chats": "No chats are excluded.", - "invalid_chat_id": "Invalid chat ID.", - "already_excluded": "Chat {} is already excluded.", - "not_excluded": "Chat {} is not excluded.", - } - - strings_ru = { - "afk_on": "🫶 AFK-режим включен!", - "afk_on_reason": "🫶 AFK-режим включен!\n\nПричина: {}", - "afk_here_on": "🫶 AFK-режим включен в этом чате!", - "afk_here_on_reason": "🫶 AFK-режим включен в этом чате!\n\nПричина: {}", - "afk_off": "🤌 AFK-режим отключен!", - "afk_off_time": "🤌 AFK-режим отключен!\n\nВы были AFK: {}", - "afk_off_here_time": "🤌 AFK-режим отключен в этом чате!\n\nВы были AFK: {}", - "already_afk": " Вы уже находитесь в AFK-режиме!", - "already_afk_here": " Вы уже находитесь в AFK-режиме в этом чате!", - "not_afk": "😐 AFK-режим уже отключён.", - "not_afk_here": "😐 AFK-режим уже отключен в этом чате.", - "afk_message": "🫤 На данный момент я не принимаю сообщения!\nПричина: {}\n\nС момента включения режима неактивности: {}", - "afk_message_reason": "🫤 На данный момент я не принимаю сообщения!\nПричина: {}\n\nС момента включения режима неактивности: {}", - "afk_set_failed": "Не удалось установить AFK-статус. Проверьте логи.", - "added_excluded_chat": "Чат {} добавлен в список исключений.", - "removed_excluded_chat": "Чат {} удален из списка исключений.", - "excluded_chats_list": "Список исключенных чатов:\n{}", - "no_excluded_chats": "Нет исключенных чатов.", - "invalid_chat_id": "Неверный ID чата.", - "already_excluded": "Чат {} уже исключен.", - "not_excluded": "Чат {} не исключен.", - } - - DEFAULT_AFK_TIMEOUT = 60 - DEFAULT_DELETE_TIMEOUT = 5 - - def __init__(self): - self.config = loader.ModuleConfig( - loader.ConfigValue( - "excluded_chats", - [], - lambda: "List of chat IDs where AFK mode will not be activated", - validator=loader.validators.Series( - validator=loader.validators.Integer() - ), - ) - ) - - async def client_ready(self, client, db): - self.client = client - self.db = db - self._me = await client.get_me() - self._ratelimit_cache = {} - - self.global_afk = self.db.get(__name__, "afk", False) - self.global_afk_reason = self.db.get(__name__, "afk_reason", None) - self.global_gone_time = self.db.get(__name__, "gone_afk", None) - - logger.debug( - f"Initial global AFK state: afk={self.global_afk}, reason={self.global_afk_reason}, gone_time={self.global_gone_time}" - ) - - @loader.command( - ru_doc="[reason / none] – Установить режим AFK", - en_doc="[reason / none] – Set AFK mode globally", - ) - async def afk(self, message): - await self._afk_toggle(message, global_afk=True) - - @loader.command( - ru_doc="[reason / none] – Установить режим AFK только в этом чате.", - en_doc="[reason / none] – Set AFK mode in current chat only.", - ) - async def afkhere(self, message): - await self._afk_toggle(message, global_afk=False) - - async def _afk_toggle(self, message, global_afk: bool): - chat_id = utils.get_chat_id(message) - already_afk_string = "already_afk" if global_afk else "already_afk_here" - afk_on_string = "afk_on" if global_afk else "afk_here_on" - afk_on_reason_string = "afk_on_reason" if global_afk else "afk_here_on_reason" - - if self._is_afk_enabled(chat_id, global_afk): - await utils.answer(message, self.strings(already_afk_string, message)) - return - - reason = utils.get_args_raw(message) or None - success = self._set_afk( - True, reason=reason, chat_id=chat_id if not global_afk else None - ) - - if not success: - await utils.answer(message, self.strings("afk_set_failed", message)) - return - - await utils.answer( - message, - self.strings(afk_on_reason_string, message).format(reason) - if reason - else self.strings(afk_on_string, message), - ) - - @loader.command( - ru_doc="Выйти из режима AFK", - en_doc="Exit AFK mode", - ) - async def unafk(self, message): - await self._unafk_toggle(message, global_afk=True) - - @loader.command( - ru_doc="Выйти из режима AFK в этом чате", - en_doc="Exit AFK mode in this chat", - ) - async def unafkhere(self, message): - await self._unafk_toggle(message, global_afk=False) - - async def _unafk_toggle(self, message, global_afk: bool): - chat_id = utils.get_chat_id(message) - not_afk_string = "not_afk" if global_afk else "not_afk_here" - afk_off_time_string = "afk_off_time" if global_afk else "afk_off_here_time" - - if not self._is_afk_enabled(chat_id, global_afk): - await utils.answer(message, self.strings(not_afk_string, message)) - return - - total_gone_time = self._calculate_total_afk_time( - datetime.datetime.now().replace(microsecond=0), - chat_id=chat_id if not global_afk else None, - ) - - self._set_afk(False, chat_id=chat_id if not global_afk else None) - - await self.allmodules.log("unafk" if global_afk else "unafkhere") - await utils.answer( - message, self.strings(afk_off_time_string, message).format(total_gone_time) - ) - - async def watcher(self, message): - if not isinstance(message, types.Message): - return - - chat_id = get_peer_id(message.peer_id) - user_id = getattr(message.to_id, "user_id", None) - is_mentioned = message.mentioned or user_id == self._me.id - is_private = isinstance(message.to_id, types.PeerUser) - - if not (is_mentioned or is_private) or chat_id in self.config["excluded_chats"]: - return - - reason = None - gone_time = None - - if self._is_afk_enabled(chat_id, False): - reason = self.db.get(__name__, f"afk_here_{chat_id}_reason", None) - gone_time = self.db.get(__name__, f"gone_afk_here_{chat_id}", None) - elif self.global_afk: - reason = self.global_afk_reason - gone_time = self.global_gone_time - else: - return - - if gone_time is None: - logger.warning(f"No 'gone' time found for chat {chat_id}. Cannot send AFK.") - return - - afk_message = await self._send_afk_message(message, reason, gone_time) - if afk_message: - await self._delete_message(afk_message, self.DEFAULT_DELETE_TIMEOUT) - - def _set_afk(self, value: bool, reason: str = None, chat_id: int = None) -> bool: - try: - key_prefix = f"afk_here_{chat_id}" if chat_id else "afk" - self.db.set(__name__, key_prefix, value) - self.db.set(__name__, f"{key_prefix}_reason", reason) - - gone_key = f"gone_{key_prefix}" - gone_time = time.time() if value else None - self.db.set(__name__, gone_key, gone_time) - logger.debug(f"AFK status updated. {gone_key} set to {gone_time}") - - if chat_id is None: - self.global_afk = value - self.global_afk_reason = reason - self.global_gone_time = gone_time - logger.debug("Updated global AFK vars") - - return True - except Exception as e: - logger.exception(f"Error setting AFK status: {e}") - return False - - def _calculate_total_afk_time( - self, now: datetime.datetime, chat_id: int = None - ) -> datetime.timedelta: - key_prefix = f"afk_here_{chat_id}" if chat_id else "afk" - gone_time = self.db.get(__name__, f"gone_{key_prefix}") - - if gone_time is None: - return datetime.timedelta(seconds=0) - - try: - gone = datetime.datetime.fromtimestamp(gone_time).replace(microsecond=0) - return now - gone - except Exception as e: - logger.error(f"Error calculating AFK time: {e}") - return datetime.timedelta(seconds=0) - - async def _send_afk_message(self, message, reason, gone_time): - user = await utils.get_user(message) - if user.is_self or user.bot or user.verified: - logger.debug("User is self, bot, or verified. Not sending AFK message.") - return None - - now = datetime.datetime.now().replace(microsecond=0) - try: - gone = datetime.datetime.fromtimestamp(gone_time).replace(microsecond=0) - except (TypeError, ValueError) as e: - logger.error(f"Error converting timestamp: {e}") - return None - - time_string = str(now - gone) - ret = ( - self.strings("afk_message_reason", message).format(reason, time_string) - if reason - else self.strings("afk_message", message).format(time_string) - ) - - try: - reply = await utils.answer(message, ret, reply_to=message) - return reply - except Exception as e: - logger.exception(f"Error sending AFK message: {e}") - return None - - def _is_afk_enabled(self, chat_id: int = None, global_afk: bool = False) -> bool: - return ( - self.global_afk - if global_afk - else self.db.get(__name__, f"afk_here_{chat_id}", False) - ) - - async def _delete_message(self, message, delay): - await asyncio.sleep(delay) - try: - await self.client.delete_messages(message.chat_id, message.id) - except Exception as e: - logger.exception(f"Error deleting message: {e}") diff --git a/archquise/H.Modules/HInstall.py b/archquise/H.Modules/HInstall.py deleted file mode 100644 index 58a8fc5..0000000 --- a/archquise/H.Modules/HInstall.py +++ /dev/null @@ -1,107 +0,0 @@ -# 🔐 Licensed under the GNU AGPLv3. -# --------------------------------------------------------------------------------- -# Name: HInstall -# Description: Provides H:Mods modules installation trough buttons -# Author: @hikka_mods -# --------------------------------------------------------------------------------- -# meta developer: @hikka_mods -# requires: PyCryptodome -# --------------------------------------------------------------------------------- -# ################################################################################# -# ########## This module is based on @hikariatama 's hikkamods_socket!! ########### -# ################################################################################# - - -__version__ = (1, 0, 0) - -import base64 -import logging - -from Crypto.PublicKey import RSA -from Crypto.Hash import SHA256 -from Crypto.Signature import pkcs1_15 - -from telethon.tl.types import Message -from telethon import functions, types -from typing import Optional - -from .. import loader, utils - -logger = logging.getLogger(__name__) - -pubkey_data = """ ------BEGIN PUBLIC KEY----- -MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvekpGqKiD2HZwY/J7jZv -PwGRobAS2TaC9HU5LUNRDg90jA/r8xgoFhlCBJocq8+XvJIWpgmIEYWJCz0KpCXu -Meu42bAXvLqniDOqnOt8FjXFapGZvEMLen1CLCRr1OQhVNpRlPjjWo7PM+YpUnbw -giqEZ9nA5DQ5Gi0vsSHXAnBa+ZIsxaY3EwosHMvUUhnnijcbBpkyYRJ8atvsT9AX -cNS+NjDE4Kj8jSnArQ1D1Ct1pcZEXD6DUk2k3HAD4OlZS5nY5IFchWEcpLT/Fjbt -BzGBZCJZ+rp8qR1tCVvVTV3itACc8O0Pirmptkrxb3A4pC0S8oxYBFQcnZAlIiw3 -uX36O90AkRwbsdnsp2JVg5AAPUYvdsMoCGG+cSGZC73arqcrvn0VFo7EhsYq/1Ds -CevorFI4TiLVbSlFSVnX5baqmTj+XNhgaWWmiY/+mhErzsWtpCOHYFitf1xqp3zD -9O2Vs7lQIxMsHFISAEhn8BqQxvlwslfcjmbuJxkYriqAHXQGS3IZDXhEZXwouOUV -HGN2YD5aLK0L8OuTNY5cf1TN8C5xgVZoEodAKqAva/i1v/F6IQk3iEo0ncgypeyg -NM1TUudkQ+f1wXqLj2YaVKqRdKswl9vgYpUCHjGZfN+WYT4DbOMrJm1OFeen6geo -xqON1/xeRBgkE3tna3RuhmUCAwEAAQ== ------END PUBLIC KEY----- -""" - -pubkey = RSA.importKey(pubkey_data.strip()) - - -@loader.tds -class HInstallMod(loader.Module): - """Provides H:Mods modules installation trough buttons""" - - strings = { - "name": "HInstall", - "_cls_doc": "Provides H:Mods modules installation trough buttons", - "module_downloaded": "Module downloaded!" - } - - strings_ru = { - "_cls_doc": "Позволяет устанавливать модули от H:Mods через кнопки", - "module_downloaded": "Модуль загружен!" - } - - async def on_dlmod(self, client, db): - ent = await self.client(functions.users.GetFullUserRequest('@hinstall_bot')) - if ent.full_user.blocked: - await self.client(functions.contacts.UnblockRequest('@hinstall_bot')) - await self.client.send_message('@hinstall_bot', '/start') - await self.client.delete_dialog('@hinstall_bot') - - - async def _load_module(self, url: str, message: Optional[Message] = None): - loader_m = self.lookup("loader") - - await loader_m.download_and_install(url, None) - - if getattr(loader_m, "_fully_loaded", getattr(loader_m, "fully_loaded", False)): - getattr( - loader_m, - "_update_modules_in_db", - getattr(loader_m, "update_modules_in_db", lambda: None), - )() - - - async def watcher(self, message: Message): - if not isinstance(message, Message): - return - if message.sender_id == 8104671142 and message.raw_text.startswith("#install"): - await message.delete() - fileref = ( - message.raw_text.split("#install:")[1].strip().splitlines()[0].strip() - ) - sig = base64.b64decode(message.raw_text.splitlines()[1].strip().encode()) - try: - h = SHA256.new(fileref.encode("utf-8")) - pkcs1_15.new(pubkey).verify(h, sig) - except (ValueError, TypeError): - logger.error(f"Got message with non-verified signature ({fileref=})") - return - await self._load_module(f"https://raw.githubusercontent.com/archquise/H.Modules/refs/heads/main/{fileref}", message) - await self.client.send_message('@hinstall_bot', self.strings['module_downloaded']) - - - diff --git a/archquise/H.Modules/InfoBannersManager.py b/archquise/H.Modules/InfoBannersManager.py deleted file mode 100644 index fd47de8..0000000 --- a/archquise/H.Modules/InfoBannersManager.py +++ /dev/null @@ -1,108 +0,0 @@ -# Proprietary License Agreement - -# Copyright (c) 2024-29 CodWiz - -# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions: - -# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author. - -# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author. - -# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software. - -# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software. - -# 5. By using the Software, you agree to be bound by the terms and conditions of this license. - -# For any inquiries or requests for permissions, please contact archquise@gmail.com. - -# --------------------------------------------------------------------------------- -# Name: InfoBannersManager -# Description: Автоматически меняет баннеры на случайные из выбранного списка через заданный промежуток времени -# Author: @hikka_mods -# --------------------------------------------------------------------------------- -# meta developer: @hikka_mods - -import logging -import random - -from .. import loader - -logger = logging.getLogger(__name__) - - -@loader.tds -class InfoBannersManagerMod(loader.Module): - """Автоматически меняет баннеры на случайные из выбранного списка через заданный промежуток времени""" - - strings = {"name": "InfoBannersManager"} - - def __init__(self): - self.config = loader.ModuleConfig( - loader.ConfigValue( - "enabled", - False, - "Включить автоматическую смену баннеров", - validator=loader.validators.Boolean(), - ), - loader.ConfigValue( - "delay", - 60, - "Задержка между изменениями баннеров в секундах", - validator=loader.validators.Integer(minimum=1), - ), - loader.ConfigValue( - "bannerslist", - None, - "Список ссылок на баннеры", - validator=loader.validators.Series(validator=loader.validators.Link()), - ), - ) - - async def banner_changer(self): - """Change banner periodically""" - try: - if not self.config["bannerslist"]: - logger.warning("Banners list is empty!") - return - - banner = random.choice(self.config["bannerslist"]) - instance = self.lookup("HerokuInfo") - if not instance: - instance = self.lookup("HikkaInfo") - - if instance: - instance.config["banner_url"] = banner - logger.info(f"Banner changed to: {banner}") - else: - logger.warning("Info module not found!") - - except Exception as e: - logger.exception(f"Error changing banner: {e}") - - @loader.loop(interval=60, autostart=False) - async def banner_loop(self): - """Main banner changing loop""" - if not self.config["enabled"]: - return - - await self.banner_changer() - - # Update interval from config - self.banner_loop.set_interval(self.config["delay"]) - - async def client_ready(self): - """Initialize the banner changer loop""" - if self.config["enabled"]: - self.banner_loop.start() - - def on_config_update(self, config_key, new_value): - """Handle config updates""" - if config_key == "enabled": - if new_value: - self.banner_loop.start() - else: - self.banner_loop.stop() - elif config_key == "delay": - # Update interval immediately - self.banner_loop.set_interval(new_value) diff --git a/archquise/H.Modules/InlineButton.py b/archquise/H.Modules/InlineButton.py deleted file mode 100644 index 1b965f8..0000000 --- a/archquise/H.Modules/InlineButton.py +++ /dev/null @@ -1,88 +0,0 @@ -# Proprietary License Agreement - -# Copyright (c) 2024-29 CodWiz - -# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions: - -# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author. - -# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author. - -# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software. - -# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software. - -# 5. By using the Software, you agree to be bound by the terms and conditions of this license. - -# For any inquiries or requests for permissions, please contact codwiz@yandex.ru. - -# --------------------------------------------------------------------------------- -# Name: InlineButton -# Description: Create inline button -# Author: @hikka_mods -# --------------------------------------------------------------------------------- -# meta developer: @hikka_mods -# scope: InlineButton -# scope: InlineButton 0.0.1 -# --------------------------------------------------------------------------------- - -import logging - -from .. import loader, utils -from ..inline.types import InlineQuery - -logger = logging.getLogger(__name__) - - -@loader.tds -class InlineButtonMod(loader.Module): - """Create inline buttons with enhanced functionality""" - - strings = { - "name": "InlineButton", - "titles": "🔘 Create message with Inline Button", - "error_title": " Error", - "error_description": " Invalid input format. Please provide exactly three comma-separated values: message, name, url.", - "error_message": " Make sure your input is formatted as: message, name, url.", - "button_created": " Button created successfully!", - "no_args": " Please provide arguments: message, name, url.", - } - - strings_ru = { - "titles": "🔘 Создать сообщение с Inline Кнопкой", - "error_title": " Ошибка", - "error_description": " Неверный формат ввода. Пожалуйста, укажите ровно три значения, разделенных запятыми: сообщение, имя, url.", - "error_message": " Убедитесь, что ваш ввод имеет следующий формат: сообщение, имя, url.", - "button_created": " Кнопка успешно создана!", - "no_args": " Укажите аргументы: сообщение, имя, url.", - } - - @loader.command( - ru_doc="Создать inline кнопку\nНапример: @username_bot crinl Текст сообщения, Текст кнопки, Ссылка в кнопке", - en_doc="Create an inline button\nexample: @username_bot crinl Message text, Button text, Link in the button", - ) - async def crinl_inline_handler(self, query: InlineQuery): - args = utils.get_args_raw(query.query) - - if not args: - return { - "title": self.strings("error_title"), - "description": self.strings("error_description"), - "message": self.strings("no_args"), - } - - args_list = [arg.strip() for arg in args.split(",")] - - if len(args_list) != 3: - return { - "title": self.strings("error_title"), - "description": self.strings("error_description"), - "message": self.strings("error_message"), - } - - message, name, url = args_list - return True, { - "message": message, - "reply_markup": [{"text": name, "url": url}], - "description": self.strings("button_created"), - } diff --git a/archquise/H.Modules/InlineCoin.py b/archquise/H.Modules/InlineCoin.py deleted file mode 100644 index 773702f..0000000 --- a/archquise/H.Modules/InlineCoin.py +++ /dev/null @@ -1,92 +0,0 @@ -# Proprietary License Agreement - -# Copyright (c) 2024-29 CodWiz - -# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions: - -# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author. - -# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author. - -# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software. - -# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software. - -# 5. By using the Software, you agree to be bound by the terms and conditions of this license. - -# For any inquiries or requests for permissions, please contact codwiz@yandex.ru. - -# --------------------------------------------------------------------------------- -# Name: InlineCoin -# Description: Mini game heads or tails. -# Author: @hikka_mods -# --------------------------------------------------------------------------------- -# meta developer: @hikka_mods -# scope: InlineCoin -# scope: InlineCoin 0.0.1 -# --------------------------------------------------------------------------------- - -import logging -import random -from typing import Dict - -from .. import loader -from ..inline.types import InlineQuery - -logger = logging.getLogger(__name__) - - -@loader.tds -class CoinFlipMod(loader.Module): - """Mini coin flip game""" - - strings = { - "name": "InlineCoin", - "titles": "🪙 Heads or Tails?", - "description": "🎲 Let's find out!", - "heads": "🦅 An eagle fell out!", - "tails": "🪙 Tails fell out!", - "edge": "🙀 Miraculously, the coin remained on its edge!", - "no_args": " Please provide a command to flip.", - "error_general": " An error occurred: {error}", - } - - strings_ru = { - "titles": "🪙 Орёл или решка?", - "description": "🎲 Давай узнаем!", - "heads": "🦅 Выпал орёл!", - "tails": "🪙 Выпала решка!", - "edge": "🙀 Чудо, монетка осталась на ребре!", - "no_args": " Укажите команду для подбрасывания монетки.", - "error_general": " Произошла ошибка: {error}", - } - - def get_coin_flip_result(self) -> Dict[str, str]: - """Get coin flip result with better formatting""" - return { - "title": self.strings["titles"], - "description": self.strings["description"], - "message": f"{random.choice([self.strings['heads'], self.strings['tails']])}", - "thumb": "https://github.com/Codwizer/ReModules/blob/main/assets/images.png", - } - - @loader.command( - ru_doc="Подбросить монетку", - en_doc="Flip a coin", - ) - async def coin_inline_handler(self, query: InlineQuery): - """Handle coin flip inline query""" - if not query.args: - return { - "title": self.strings["titles"], - "description": self.strings["no_args"], - "message": self.strings["no_args"], - } - - result = self.get_coin_flip_result() - return { - "title": self.strings["titles"], - "description": self.strings["description"], - "message": result["message"], - "thumb": result["thumb"], - } diff --git a/archquise/H.Modules/InlineHelper.py b/archquise/H.Modules/InlineHelper.py deleted file mode 100644 index a2d59d9..0000000 --- a/archquise/H.Modules/InlineHelper.py +++ /dev/null @@ -1,308 +0,0 @@ -# Proprietary License Agreement - -# Copyright (c) 2024-29 CodWiz - -# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions: - -# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author. - -# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author. - -# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software. - -# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software. - -# 5. By using the Software, you agree to be bound by the terms and conditions of this license. - -# For any inquiries or requests for permissions, please contact codwiz@yandex.ru. - -# --------------------------------------------------------------------------------- -# Name: InlineHelper -# Description: Basic management of the UB in case only the inline works -# Author: @hikka_mods -# --------------------------------------------------------------------------------- -# meta developer: @hikka_mods -# scope: InlineHelper -# scope: InlineHelper 0.0.1 -# --------------------------------------------------------------------------------- - -import asyncio -import logging -import shlex -import sys - -from .. import loader, main, utils -from ..inline.types import InlineQuery - -logger = logging.getLogger(__name__) - - -@loader.tds -class InlineHelperMod(loader.Module): - """Basic management of the UB in case only the inline works""" - - strings = { - "name": "InlineHelper", - "call_restart": "🔄 Restarting...", - "call_update": "🔄 Updating...", - "res_prefix": " Prefix successfully reset to default", - "restart_inline_handler_title": "🔄 Restart Userbot", - "restart_inline_handler_description": "Restart your userbot via inline", - "restart_inline_handler_message": "🔄 Restart", - "update_inline_handler_title": "🔄 Update Userbot", - "update_inline_handler_description": "Update your userbot via inline", - "update_inline_handler_message": "🔄 Update", - "terminal_inline_handler_title": "💻 Command Executed", - "terminal_inline_handler_description": "Command executed successfully", - "terminal_inline_handler_message": "Command {text} executed successfully in terminal", - "modules_inline_handler_title": "📦 Modules", - "modules_inline_handler_description": "List all installed modules", - "modules_inline_handler_result": "📦 All installed modules:\n\n", - "resetprefix_inline_handler_title": "⚠️ Reset Prefix", - "resetprefix_inline_handler_description": "Reset your prefix back to default (be careful!)", - "resetprefix_inline_handler_message": "Are you sure you want to reset your prefix to default dot?", - "resetprefix_inline_handler_reply_text_yes": "Yes, reset it", - "resetprefix_inline_handler_reply_text_no": "No, cancel", - "error_no_module": " Module not found: {module}", - "error_command_failed": " Command execution failed: {error}", - "error_git_failed": " Git operation failed: {error}", - } - - strings_ru = { - "call_restart": "🔄 Перезагружаю...", - "call_update": "🔄 Обновляю...", - "res_prefix": " Префикс успешно сброшен по умолчанию", - "restart_inline_handler_title": "🔄 Перезагрузить юзербота", - "restart_inline_handler_description": "Перезагрузить юзербота через инлайн", - "restart_inline_handler_message": "🔄 Перезагрузка", - "update_inline_handler_title": "🔄 Обновить юзербота", - "update_inline_handler_description": "Обновить юзербота через инлайн", - "update_inline_handler_message": "🔄 Обновить", - "terminal_inline_handler_title": "💻 Команда выполнена!", - "terminal_inline_handler_description": "Команда успешно выполнена.", - "terminal_inline_handler_message": "Команда {text} была успешно выполнена в терминале", - "modules_inline_handler_title": "📦 Модули", - "modules_inline_handler_description": "Вывести список установленных модулей", - "modules_inline_handler_result": "📦 Все установленные модули:\n\n", - "resetprefix_inline_handler_title": "⚠️ Сбросить префикс", - "resetprefix_inline_handler_description": "Сбросить префикс по умолчанию (осторожно!)", - "resetprefix_inline_handler_message": "Вы действительно хотите сбросить ваш префикс и установить стандартную точку?", - "resetprefix_inline_handler_reply_text_yes": "Да, сбросить", - "resetprefix_inline_handler_reply_text_no": "Нет, отменить", - "error_no_module": " Модуль не найден: {module}", - "error_command_failed": " Ошибка выполнения команды: {error}", - "error_git_failed": " Ошибка git операции: {error}", - } - - def __init__(self): - self.client = None - self.db = None - self._base_dir = utils.get_base_dir() - - async def client_ready(self, client, db): - self.client = client - self.db = db - - async def restart(self, call): - """Restart callback""" - logger.info("InlineHelper: Restarting userbot...") - try: - await call.edit(self.strings["call_restart"]) - - await asyncio.create_subprocess_exec( - [ - sys.executable, - "-c", - f"cd {self._base_dir} && git reset --hard HEAD && git pull", - ], - cwd=self._base_dir, - ) - await call.edit(self.strings["call_update"]) - await asyncio.sleep(2) - await asyncio.create_subprocess_exec( - [sys.executable, "-c", f"cd {self._base_dir} && git pull"], - cwd=self._base_dir, - ) - await call.edit(self.strings["res_prefix"]) - except Exception as e: - logger.error(f"Restart failed: {e}") - await call.edit(self.strings["error_git_failed"].format(error=str(e))) - - async def update(self, call): - """Update callback""" - logger.info("InlineHelper: Updating userbot...") - try: - await call.edit(self.strings["call_update"]) - - await asyncio.create_subprocess_exec( - [ - sys.executable, - "-c", - f"cd {self._base_dir} && git reset --hard HEAD && git pull", - ], - cwd=self._base_dir, - ) - await call.edit(self.strings["res_prefix"]) - except Exception as e: - logger.error(f"Update failed: {e}") - await call.edit(self.strings["error_git_failed"].format(error=str(e))) - - async def reset_prefix(self, call): - """Reset prefix callback""" - try: - self.db.set(main.__name__, "command_prefix", ".") - await call.edit(self.strings["res_prefix"]) - except Exception as e: - logger.error(f"Reset prefix failed: {e}") - await call.edit(self.strings["error_command_failed"].format(error=str(e))) - - @loader.inline_handler( - ru_doc="Перезагрузить юзербота", - en_doc="Reboot the userbot", - ) - async def restart_inline_handler(self, _: InlineQuery): - return { - "title": self.strings("restart_inline_handler_title"), - "description": self.strings("restart_inline_handler_description"), - "message": self.strings("restart_inline_handler_message"), - "reply_markup": [ - { - "text": self.strings("restart_inline_handler_reply_text"), - "callback": self.restart, - } - ], - } - - @loader.inline_handler( - ru_doc="Обновить юзербота", - en_doc="Update the userbot", - ) - async def update_inline_handler(self, _: InlineQuery): - return { - "title": self.strings("update_inline_handler_title"), - "description": self.strings("update_inline_handler_description"), - "message": self.strings("update_inline_handler_message"), - "reply_markup": [ - { - "text": self.strings("update_inline_handler_reply_text"), - "callback": self.update, - } - ], - } - - @loader.inline_handler( - ru_doc="Выполнить команду в терминале (лучше сразу подготовить команду и просто вставить)", - en_doc="Execute the command in the terminal (it is better to prepare the command immediately and just paste it)", - ) - async def terminal_inline_handler(self, query: InlineQuery): - """Execute terminal command safely""" - if not query.args: - return { - "title": self.strings["terminal_inline_handler_title"], - "description": self.strings["terminal_inline_handler_description"], - "message": self.strings["terminal_inline_handler_message"].format( - text="No command provided" - ), - } - - command_text = query.args.strip() - if not command_text: - return { - "title": self.strings["terminal_inline_handler_title"], - "description": self.strings["terminal_inline_handler_description"], - "message": self.strings["terminal_inline_handler_message"].format( - text="No command provided" - ), - } - - if any(char in command_text for char in ["&", "|", ";", "`", "$"]): - return { - "title": self.strings["terminal_inline_handler_title"], - "description": self.strings["terminal_inline_handler_description"], - "message": self.strings["error_command_failed"].format( - error="Invalid characters in command" - ), - } - - try: - args = shlex.split(command_text) - process = await asyncio.create_subprocess_exec( - args, - stdin=asyncio.subprocess.PIPE, - stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE, - cwd=self._base_dir, - text=True, - ) - - stdout, stderr = await process.communicate() - stdout.decode().strip() if stdout else "" - error = stderr.decode().strip() if stderr else "" - - if error: - return { - "title": self.strings["terminal_inline_handler_title"], - "description": self.strings["terminal_inline_handler_description"], - "message": self.strings["error_command_failed"].format(error=error), - } - - return { - "title": self.strings["terminal_inline_handler_title"], - "description": self.strings["terminal_inline_handler_description"], - "message": self.strings["terminal_inline_handler_message"].format( - text=command_text - ), - } - except Exception as e: - return { - "title": self.strings["terminal_inline_handler_title"], - "description": self.strings["terminal_inline_handler_description"], - "message": self.strings["error_command_failed"].format(error=str(e)), - } - - @loader.inline_handler( - ru_doc="Вывести список установленных модулей через инлайн", - en_doc="Display a list of installed modules via the inline", - ) - async def modules_inline_handler(self, query: InlineQuery): - """List all installed modules""" - try: - result = self.strings["modules_inline_handler_result"] - - for mod in self.allmodules.modules: - try: - name = mod.strings["name"] - except KeyError: - name = mod.__class__.__name__ - result += f"• {name}\n" - - except Exception as e: - logger.error(f"Error listing modules: {e}") - result = f"Error listing modules: {str(e)}" - - return { - "title": self.strings["modules_inline_handler_title"], - "description": self.strings["modules_inline_handler_description"], - "message": result, - } - - @loader.inline_handler( - ru_doc="Сбросить префикс (осторожнее, сбрасывает ваш префикс на . )", - en_doc="Reset the prefix (be careful, resets your prefix to . )", - ) - async def resetprefix_inline_handler(self, _: InlineQuery): - return { - "title": self.strings("resetprefix_inline_handler_title"), - "description": self.strings("resetprefix_inline_handler_description"), - "message": self.strings("resetprefix_inline_handler_message"), - "reply_markup": [ - { - "text": self.strings("resetprefix_inline_handler_reply_text_yes"), - "callback": self.reset_prefix, - }, - { - "text": self.strings("resetprefix_inline_handler_reply_text_no"), - "action": "close", - }, - ], - } diff --git a/archquise/H.Modules/KBSwapper.py b/archquise/H.Modules/KBSwapper.py deleted file mode 100644 index b91684f..0000000 --- a/archquise/H.Modules/KBSwapper.py +++ /dev/null @@ -1,138 +0,0 @@ -# Proprietary License Agreement - -# Copyright (c) 2024-29 CodWiz - -# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions: - -# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author. - -# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author. - -# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software. - -# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software. - -# 5. By using the Software, you agree to be bound by the terms and conditions of this license. - -# For any inquiries or requests for permissions, please contact codwiz@yandex.ru. - -# --------------------------------------------------------------------------------- -# Name: KBSwapper -# Description: KBSwapper is a module for changing the keyboard layout -# Author: @hikka_mods -# --------------------------------------------------------------------------------- -# meta developer: @hikka_mods -# scope: KBSwapper -# scope: KBSwapper 0.0.1 -# --------------------------------------------------------------------------------- - -import logging -import string - -from .. import loader, utils - -logger = logging.getLogger(__name__) - -EN_TO_RU = str.maketrans( - "qwertyuiop[]asdfghjkl;'zxcvbnm,./`" + 'QWERTYUIOP{}ASDFGHJKL:"ZXCVBNM<>?~', - "йцукенгшщзхъфывапролджэячсмитьбю.ё" + "ЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮ,Ё", -) - -RU_TO_EN = str.maketrans( - "йцукенгшщзхъфывапролджэячсмитьбю.ё" + "ЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮ,Ё", - "qwertyuiop[]asdfghjkl;'zxcvbnm,./`" + 'QWERTYUIOP{}ASDFGHJKL:"ZXCVBNM<>?~', -) - - -@loader.tds -class KBSwapperMod(loader.Module): - """KBSwapper is a module for changing the keyboard layout""" - - strings = { - "name": "KBSwapper", - "no_reply": " Please reply to a message.", - "no_text": " The replied message does not contain text.", - "original_message": "➡️ Original message:\n{original}", - "fixed_message": " Fixed message:\n{fixed}", - "error": " An error occurred while processing the message.", - } - strings_ru = { - "no_reply": " Пожалуйста, ответьте на сообщение.", - "no_text": " Отвеченное сообщение не содержит текста.", - "original_message": "➡️ Оригинальное сообщение:\n{original}", - "fixed_message": " Исправленное сообщение:\n{fixed}", - "error": " Произошла ошибка при обработке сообщения.", - } - - @loader.command( - ru_doc="При ответе на своё сообщение меняет раскладку путем редактирования, на чужое — в отдельном сообщении.", - en_doc="Change keyboard layout for the replied message.", - ) - async def swap(self, message): - reply = await message.get_reply_message() - if not reply: - await utils.answer(message, self.strings("no_reply")) - return - - original_text = reply.text - if not original_text or original_text.isspace(): - await utils.answer(message, self.strings("no_text")) - return - - try: - trimmed_text = original_text.strip() - - has_russian = any( - char - in "йцукенгшщзхъфывапролджэячсмитьбюёЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮЁ" - for char in trimmed_text - ) - has_english = any(char in string.ascii_letters for char in trimmed_text) - - logger.debug( - f"Text analysis - Russian: {has_russian}, English: {has_english}, Text: {trimmed_text[:50]}..." - ) - - if has_russian and not has_english: - fixed_text = original_text.translate(RU_TO_EN) - logger.debug("Detected Russian text, translating to English") - elif has_english and not has_russian: - fixed_text = original_text.translate(EN_TO_RU) - logger.debug("Detected English text, translating to Russian") - else: - first_char = ( - trimmed_text[0].lower() - if trimmed_text - else original_text[0].lower() - ) - logger.debug( - f"Mixed/other characters detected, first char: {first_char}" - ) - if first_char in string.ascii_lowercase: - fixed_text = original_text.translate(EN_TO_RU) - logger.debug("Using first char detection: English to Russian") - elif first_char in "йцукенгшщзхъфывапролджэячсмитьбюё": - fixed_text = original_text.translate(RU_TO_EN) - logger.debug("Using first char detection: Russian to English") - else: - fixed_text = original_text - logger.debug("No recognizable letters, returning as is") - - if fixed_text != original_text: - logger.debug( - f"Text changed: {original_text[:30]}... → {fixed_text[:30]}..." - ) - else: - logger.debug("Text unchanged") - - if message.sender_id == reply.sender_id: - await reply.edit(fixed_text) - else: - await utils.answer( - message, - f"{self.strings('original_message').format(original=original_text)}\n" - f"{self.strings('fixed_message').format(fixed=fixed_text)}", - ) - except Exception as e: - logger.error(f"Error during swap: {e}") - await utils.answer(message, self.strings("error")) diff --git a/archquise/H.Modules/LICENSE b/archquise/H.Modules/LICENSE deleted file mode 100644 index 5f63eac..0000000 --- a/archquise/H.Modules/LICENSE +++ /dev/null @@ -1,17 +0,0 @@ -Proprietary License Agreement - -Copyright (c) 2024-29 Archquise - -Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions: - -1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author. - -2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author. - -3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software. - -4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software. - -5. By using the Software, you agree to be bound by the terms and conditions of this license. - -For any inquiries or requests for permissions, please contact archquise@gmail.com. diff --git a/archquise/H.Modules/Memes.py b/archquise/H.Modules/Memes.py deleted file mode 100644 index 1f5e9a2..0000000 --- a/archquise/H.Modules/Memes.py +++ /dev/null @@ -1,112 +0,0 @@ -# Proprietary License Agreement - -# Copyright (c) 2024-29 CodWiz - -# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions: - -# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author. - -# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author. - -# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software. - -# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software. - -# 5. By using the Software, you agree to be bound by the terms and conditions of this license. - -# For any inquiries or requests for permissions, please contact codwiz@yandex.ru. - -# --------------------------------------------------------------------------------- -# Name: Meme -# Description: Random memes -# Author: @hikka_mods -# Commands: -# --------------------------------------------------------------------------------- -# meta developer: @hikka_mods -# scope: Meme -# scope: Meme 0.0.1 -# --------------------------------------------------------------------------------- - -import logging -import random # noqa: F401 - -import aiohttp # noqa: F401 -from bs4 import BeautifulSoup # noqa: F401 - -from .. import loader - -logger = logging.getLogger(__name__) - -@loader.tds -class MemesMod(loader.Module): - """Random memes""" - - strings = { - "name": "Memes", - "done": "☄️ Catch the meme", - "still": "🔄 Update", - "dell": "❌ Close", - } - - strings_ru = { - "done": "☄️ Лови мем", - "still": "🔄 Обновить", - "dell": "❌ Закрыть", - } - - async def client_ready(self, client, db): - self.hmodslib = await self.import_lib( - "https://files.archquise.ru/HModsLibrary.py" - ) - - @loader.command( - ru_doc="", - en_doc="", - ) - async def memescmd(self, message): - img = await self.hmodslib.get_random_image() - await self.inline.form( - text=self.strings("done"), - photo=img, - message=message, - reply_markup=[ - [ - { - "text": self.strings("still"), - "callback": self.ladno, - } - ], - [ - { - "text": self.strings("dell"), - "callback": self.dell, - } - ], - ], - silent=True, - ) - - async def ladno(self, call): - img = await self.hmodslib.get_random_image() - await call.edit( - text=self.strings("done"), - photo=img, - reply_markup=[ - [ - { - "text": self.strings("still"), - "callback": self.ladno, - } - ], - [ - { - "text": self.strings("dell"), - "callback": self.dell, - } - ], - ], - ) - - async def dell(self, call): - """Callback button""" - await call.delete() diff --git a/archquise/H.Modules/MessageMonitor.py b/archquise/H.Modules/MessageMonitor.py deleted file mode 100644 index 8a400f3..0000000 --- a/archquise/H.Modules/MessageMonitor.py +++ /dev/null @@ -1,305 +0,0 @@ -# Proprietary License Agreement - -# Copyright (c) 2024-29 CodWiz - -# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions: - -# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author. - -# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author. - -# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software. - -# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software. - -# 5. By using the Software, you agree to be bound by the terms and conditions of this license. - -# For any inquiries or requests for permissions, please contact codwiz@yandex.ru. - -# --------------------------------------------------------------------------------- -# Name: MessageMonitor -# Description: Monitor messages for trigger words in all chats. -# Author: @hikka_mods -# --------------------------------------------------------------------------------- -# meta developer: @hikka_mods -# scope: MessageMonitor -# scope: MessageMonitor 0.0.1 -# --------------------------------------------------------------------------------- - -import logging -import re -from typing import List, Optional - -from telethon.types import Message - -from .. import loader, utils - -logger = logging.getLogger(__name__) - - -@loader.tds -class MessageMonitor(loader.Module): - """ - Monitor messages for trigger words in all chats. - """ - - strings = { - "name": "MessageMonitor", - "triggers_set": " Trigger words have been set: {}", - "triggers_not_set": " Trigger words have not been set", - "target_set": " Target chat for notifications has been set", - "target_not_set": " Target chat for notifications has not been set", - "monitoring_started": "🌎 Monitoring has started", - "monitoring_stopped": " Monitoring has stopped", - "monitoring_status": "🌎 Monitoring {}", - "triggers_example": " Example: .triggers word1 word2", - "monitoring_status_on": " enabled", - "monitoring_status_off": " disabled", - "ignore_set": " Ignored chats have been set: {}", - "ignore_none": " Ignored chats have not been set", - "ignore_example": " Example: .ignore 123456789 -987654321 (chat IDs)", - "no_reply": " Reply to a message in the desired chat or specify its ID", - "monitoring_msg": ( - "🚨 Trigger word detected! 🚨\n\n" - "Chat: {}\n" - "User: {}\n" - "Link: {}\n\n" - "Message:\n{}" - ), - } - - strings_ru = { - "triggers_set": " Триггерные слова установлены: {}", - "triggers_not_set": " Триггерные слова не установлены", - "target_set": " Целевой чат для уведомлений установлен", - "target_not_set": " Целевой чат для уведомлений не установлен", - "monitoring_started": "🌎 Мониторинг запущен", - "monitoring_stopped": " Мониторинг остановлен", - "monitoring_status": "🌎 Мониторинг {}", - "triggers_example": " Пример: .triggers слово1 слово2", - "monitoring_status_on": " включен", - "monitoring_status_off": " выключен", - "ignore_set": " Игнорируемые чаты установлены: {}", - "ignore_none": " Игнорируемые чаты не установлены", - "ignore_example": " Пример: .ignore 123456789 -987654321 (ID чатов)", - "no_reply": " Ответьте на сообщение в нужном чате или укажите его ID", - "monitoring_msg": ( - "🚨 Обнаружено триггерное слово! 🚨\n\n" - "Чат: {}\n" - "Пользователь: {}\n" - "Ссылка: {}\n\n" - "Сообщение:\n{}" - ), - } - - def __init__(self): - self.config = loader.ModuleConfig( - loader.ConfigValue( - "triggers", - [], - "List of trigger words to monitor", - validator=loader.validators.Series(), - ), - loader.ConfigValue( - "target_chat", - None, - "Target chat ID for notifications", - validator=loader.validators.Integer(), - ), - loader.ConfigValue( - "ignore_chats", - [], - "List of chat IDs to ignore", - validator=loader.validators.Series(), - ), - ) - self._triggers: List[str] = [] - self._target_chat: Optional[int] = None - self._ignore_chats: List[int] = [] - self._compiled_patterns: List[re.Pattern] = [] - - async def client_ready(self, client, db): - """Initialize module when client is ready""" - await self._update_config() - self.client = client - - async def _update_config(self): - """Update internal configuration and compile regex patterns""" - self._triggers = [trigger.lower() for trigger in self.config["triggers"]] - self._target_chat = self.config["target_chat"] - self._ignore_chats = [ - int(chat_id) - for chat_id in self.config["ignore_chats"] - if str(chat_id).lstrip("-").isdigit() - ] - - self._compiled_patterns = [ - re.compile(r"\b" + re.escape(trigger) + r"\b", re.IGNORECASE) - for trigger in self._triggers - ] - - @loader.command( - ru_doc="Показать статус мониторинга", - en_doc="Show monitoring status", - ) - async def status(self, message: Message): - """Show current monitoring status""" - status_text = ( - self.strings["monitoring_status_on"] - if self._target_chat and self._triggers - else self.strings["monitoring_status_off"] - ) - await utils.answer( - message, self.strings["monitoring_status"].format(status_text) - ) - - @loader.command( - ru_doc="Установить триггерные слова. Пример: .triggers слово1 слово2", - en_doc="Set trigger words. Example: .triggers word1 word2", - ) - async def triggers(self, message: Message): - """Set trigger words""" - args = utils.get_args(message) - if not args: - await utils.answer(message, self.strings["triggers_example"]) - return - - self._triggers = [arg.lower() for arg in args] - self.config["triggers"] = self._triggers - await self._update_config() - await utils.answer( - message, self.strings["triggers_set"].format(", ".join(self._triggers)) - ) - - @loader.command( - ru_doc="Установить целевой чат для уведомлений. Ответьте на сообщение или укажите ID", - en_doc="Set target chat for notifications. Reply to a message or provide its ID", - ) - async def settarget(self, message: Message): - """Set target chat""" - args = utils.get_args_raw(message) - chat_id = None - - if getattr(message, "is_reply", False): - reply_message = await message.get_reply_message() - if reply_message and hasattr(reply_message, "chat_id"): - chat_id = reply_message.chat_id - elif args and (args.isdigit() or (args.startswith("-") and args[1:].isdigit())): - chat_id = int(args) - - if chat_id: - self.config["target_chat"] = chat_id - self._target_chat = chat_id - await utils.answer(message, self.strings["target_set"]) - else: - await utils.answer(message, self.strings["no_reply"]) - - @loader.command( - ru_doc="Установить игнорируемые чаты. Укажите ID чатов через пробел.", - en_doc="Set ignored chats. Provide chat IDs separated by space.", - ) - async def ignore(self, message: Message): - """Set ignored chats""" - args = utils.get_args(message) - if not args: - await utils.answer(message, self.strings["ignore_example"]) - return - - valid_ids = [] - for arg in args: - if arg.isdigit() or (arg.startswith("-") and arg[1:].isdigit()): - valid_ids.append(int(arg)) - - self.config["ignore_chats"] = valid_ids - await self._update_config() - - if valid_ids: - await utils.answer( - message, - self.strings["ignore_set"].format(", ".join(map(str, valid_ids))), - ) - else: - await utils.answer(message, self.strings["ignore_none"]) - - @loader.watcher(out=False, only_messages=True) - async def message_watcher(self, message: Message): - """Watch for messages containing trigger words""" - if not self._target_chat or not self._triggers: - return - - chat_id = getattr(message, "chat_id", None) - if chat_id and chat_id in self._ignore_chats: - logger.debug(f"Message in ignored chat: {chat_id}. Skipping monitoring.") - return - - text = getattr(message, "text", "") - if not text: - return - - found_triggers = [ - trigger - for pattern, trigger in zip(self._compiled_patterns, self._triggers) - if pattern.search(text) - ] - - if not found_triggers: - return - - try: - chat = await message.get_chat() - chat_title = getattr( - chat, - "title", - "Личные сообщения" - if getattr(message, "is_private", False) - else f"Чат с ID {chat_id}", - ) - - sender = await message.get_sender() - if sender: - sender_name = sender.first_name - if getattr(sender, "last_name", None): - sender_name += f" {sender.last_name}" - if not sender_name: - sender_name = getattr( - sender, "username", "Неизвестный пользователь" - ) - else: - sender_name = "Неизвестный пользователь" - - link = await self._get_message_link(message, sender) - - await self.client.send_message( - self._target_chat, - self.strings["monitoring_msg"].format( - chat_title, - chat_id, - sender_name, - link, - text, - ), - parse_mode="HTML", - ) - logger.debug( - f"Sent notification about trigger word(s) {found_triggers} to chat {self._target_chat}" - ) - except Exception as e: - logger.error(f"Error processing message: {e}") - - async def _get_message_link(self, message: Message, sender) -> str: - """Generate message link based on message type""" - message_id = message.id - - if getattr(message, "to_id", None): - to_id_obj = getattr(message, "to_id") - if getattr(to_id_obj, "channel_id", None): - return f"https://t.me/c/{to_id_obj.channel_id}/{message_id}" - - if ( - getattr(message, "is_private", False) - and sender - and getattr(sender, "username", None) - ): - return f"https://t.me/{sender.username}/{message_id}" - - return f"https://t.me/c/{message_id}" diff --git a/archquise/H.Modules/MooFarmRC1.py b/archquise/H.Modules/MooFarmRC1.py deleted file mode 100644 index 6536509..0000000 --- a/archquise/H.Modules/MooFarmRC1.py +++ /dev/null @@ -1,1812 +0,0 @@ -# Proprietary License Agreement - -# Copyright (c) 2024-29 CodWiz - -# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions: - -# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author. - -# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author. - -# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software. - -# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software. - -# 5. By using the Software, you agree to be bound by the terms and conditions of this license. - -# For any inquiries or requests for permissions, please contact codwiz@yandex.ru. - -# --------------------------------------------------------------------------------- -# Name: MooFarmRC1 -# Description: Модуль для автофарма в "Коровке"! -# Author: @hikka_mods and @Frost_Shard -# --------------------------------------------------------------------------------- -# meta developer: @hikka_mods and @Frost_Shard -# scope: MooFarmRC1 -# scope: MooFarmRC1 0.0.1 -# requires: aioredis -# --------------------------------------------------------------------------------- - -import asyncio -import base64 -import json -import re - -import aioredis -from telethon import events -from telethon.tl.types import InputDocument, Message - -from .. import loader, utils -from ..inline.types import InlineCall - -__version__ = (0, 1, 4, 10) - - -class DebugLogger: - def __init__(self, client, config): - self.client = client - self.config = config - - async def log(self, text: str, category: str): - """Основной метод логирования.""" - if not self.config["config_debug_msg"]: - return - - allowed_categories = self.config["config_debug_diff_msg"] - if category not in allowed_categories: - return - - await self.client.send_message( - self.config["config_bot_used_chat_id"], - f"[{category.upper()}] {text}", - ) - - async def eat(self, text: str): - """Логирование для еды.""" - await self.log(text, "Eating") - - async def eat_state(self, text: str): - """Логирование для состояния еды.""" - await self.log(text, "Eating_state") - - async def eat_click(self, text: str): - """Логирование для автокликера еды.""" - await self.log(text, "Eating_click") - - async def craft(self, text: str): - """Логирование для крафта.""" - await self.log(text, "Crafting") - - async def craft_state(self, text: str): - """Логирование для состояния крафта.""" - await self.log(text, "Crafting_state") - - async def craft_click(self, text: str): - """Логирование для автокликера еды.""" - await self.log(text, "Crafting_click") - - async def forest(self, text: str): - """Логирование для Автолеса.""" - await self.log(text, "Forest") - - async def forest_state(self, text: str): - """Логирование для леса.""" - await self.log(text, "Forest_state") - - async def forest_click(self, text: str): - """Логирование для лесного автокликера.""" - await self.log(text, "Forest_click") - - async def forest_npc(self, text: str): - """Логирование для лесных нпц.""" - await self.log(text, "Forest_npc") - - async def general(self, text: str): - """Общий лог.""" - await self.log(text, "General") - - async def redis(self, text: str): - """Общий лог для базы данных.""" - await self.log(text, "Redis") - - async def state(self, text: str): - """Общий лог состояний.""" - await self.log(text, "State") - - -@loader.tds -class AutoFarmbotMod(loader.Module): - """ - Модуль для автофарма в "Коровке"! - В конфиге настройте: сhat_id и bot_id -> - Синхронизируйте скин в меню -> - Зарегистрируйтесь на Redis.io и ссылку добавьте в конфиг - - """ - - # NOTE: Автокрафт и Автолес готовы на 95%, автохавка на 45% - strings = { - "name": "AutoFarmbot", - # Inline keys - "auto_eating": "🌸 Автоеда", - "auto_milk": "🥛 АвтоДойка", - "auto_forest": "🌳 АвтоЛес", - "auto_craft": "🧤 АвтоКрафт", - "settings": "🛠️ Настройки", - "close_btn": "📂 Закрыть меню", - "back_btn": "🔙 Назад", - "bot_forest_back": "🥺 забрать лут", - "bot_forest_go": "🌲 гулять", - "bot_skin_menu_key": "⭐ Настройки скина", - "on": "✅ Включено", - "on_btn": "✅", - "off": "❌ Выключено", - "off_btn": "❌", - # main menu - "moo_menu": "🐮 Меню управления автофармом:\n\n", - "auto_forest_menu": "🌳 АвтоЛес - Функции автоматического хождения в лес\n", - "auto_eating_menu": "🌸 Автоеда - Функции автоматического кормления коровки\n", - "auto_craft_menu": "🧤 АвтоКрафт - Автоматический крафт на верстаке\n", - "settings_menu": "️Настройки - Остальные настройки модуля\n", - "skin_menu": "⭐ Скин - Все настройки связанные с показом скина\n", - # Auto forest menu - "npc_not_skipped": "Никто не пропускается\n", - "npc_menu": "🌲 Настройки автолеса:\n\n", - "npc_menu_autoforest": "🌲 Автолес:", - "npc_menu_autoforest_btn": "🌲 Автолес:", - "npc_menu_skip_status": "🦔 АвтоСкип НПЦ:", - "npc_menu_skip_status_btn": "🦔 АвтоСкип НПЦ:", - "npc_menu_skip": "🛠️ Меню Автоскипа: - Выберите НПЦ для скипа.", - "npc_menu_skip_now": "\n📋 Сейчас скипаются:\n", - "npc_autoskip": "🛠️ Меню Автоскипа", - # skin menu - "skin_menu_main_txt": "🌫️ Меню настройки скина\n\n", - "skin_menu_show_skin_btn": "🌟 Показывать скин", - "skin_menu_sync_skin_btn": "🌟 Синхронизировать скин", - "skin_menu_txt": "🧩 Скин:", - "skin_menu_show_txt": "👁️ Показывать:", - # eat menu - "auto_eat_main_menu_txt": "🍽 Настройки автоеды:\n\n", - "auto_eating_main_menu_txt": "Автоеда:", - "auto_eating_inforest_main_menu_txt": "В лесу:", - "auto_eating_item": "🍲 Предмет:", - "auto_eating_item_count": "🔢 Кол-во:", - "auto_eating_lvl": "🧬 Уровень еды:", - "auto_eating_inline_count": "✍️ Введите количество для авто-кормёжки:", - "auto_eating_inline_lvl": "✍️ Введите % еды авто-кормёжки:", - "auto_eating_inline_item": "✍️ Введите еду для авто-кормёжки:", - # forest inline skip menu - "skip_menu_main_txt": "🧪 Настройка скипа лесных жителей:", - "skip_menu_main_on": "❌ - Не пропускать", - "skip_menu_main_off": "✅ - Пропускать", - "skip_menu_main_skipped": "✅ пропускаем", - # craft inline menu - "craft_menu_main_txt": "⚒ Настройки автокрафта:", - "craft_menu_main_craft": "Автокрафт:", - "craft_menu_main_craft_item": "🛠 Предмет:", - "craft_menu_main_craft_count": "🔢 Кол-во:", - "craft_menu_main_craft_item_inline": "✍️ Введите название предмета для авто-крафта:", - "craft_menu_main_craft_count_inline": "✍️ Введите количество для авто-крафта:", - # misc inline menu - "misc_menu_main_txt": "⚙️ Прочие настройки:", - "misc_menu_main_debug": "Отладка:", - "misc_menu_main_deletemsg": "Удалять в боте:", - "misc_menu_main_logs_chat": "📤 Куда слать логи:", - "misc_menu_main_logs_chat_inline": "✍️ Введите чат для логов:", - "misc_menu_main_chat_id": "ID чата:", - "misc_menu_main_chat_id_inline": "✍️ Введите чат для работы бота:", - "misc_menu_main_bots_id": "ID бота(ов):", - "misc_menu_main_bots_id_inline": "✍️ Введите ID бота для работы:", - "misc_menu_main_debug_btn_menu": "🧪 Конфиги отладки", - # debug inline menu - "debug_menu_main_txt": "🧪 Конфиг Debug Diff Msg:", - # Debug message - "Debug_Events_msg_set": "[EVENTS] Установил хендлеры.", - "Debug_Events_msg_del": "[EVENTS] Удалил хендлеры.", - "Debug_craft_take_ok": "[CRAFT] Забрал скрафченные предметы!", - "Debug_craft_start_ok": "[CRAFT] Открыл список для крафта!", - "Debug_craft_finall_ok": "[CRAFT] Нажал кнопку крафта предмета и отправил количество на крафт!", - "Debug_craft_job_ok": "[REDIS] Найдено сообщение с крафтом, обновил таймер!", - "Debug_Events_msg_forest_set": "[EVENTS] Устанавливаю обработчики для леса!", - "Debug_Events_msg_forest_del": "[EVENTS] Удаляю обработчики для леса!", - "Debug_forest_cow_takeloot_msg": "[FOREST] Коровка вернулась, обрабатываем!", - "Debug_forest_cow_takeloot_ok": "[FOREST] Забрал лут!", - "Debug_forest_cow_go_msg": "[FOREST] Коровка не в лесу, обрабатываем!", - "Debug_forest_cow_go_ok": "[FOREST] Отправил коровку в лес!", - "Debug_forest_job_go_update": "[REDIS] Обновил таймер коровки в лесу!", - "Debug_forest_npc_chick_msg": "[NPC] Сообщение с цыпой найдено, начинаю обработку!", - "Debug_forest_npc_chick_ok": "[NPC] Цыпа обработана, продолжаем!", - "Debug_forest_npc_ejik_msg": "[NPC] Сообщение с ежиком найдено, начинаю обработку!", - "Debug_forest_npc_ejik_ok": "[NPC] Ежиха обработана, продолжаем!", - "Debug_forest_npc_djun_msg": "[NPC] Сообщение с попугаем найдено, начинаю обработку!", - "Debug_forest_npc_djun_ok": "[NPC] Попугай обработан, продолжаем!", - "Debug_forest_npc_bear_msg": "[NPC] Сообщение с медведем найдено, начинаю обработку!", - "Debug_forest_npc_bear_ok": "[NPC] Медведь обработан, продолжаем!", - "Debug_forest_npc_jabomraz_msg": "[NPC] Сообщение с жабомразью найдено, начинаю обработку!", - "Debug_forest_npc_jabomraz_ok": "[NPC] Жабомразь обработан, продолжаем!", - "Debug_forest_npc_edinorog_msg": "[NPC] Сообщение с единорогом найдено, начинаю обработку!", - "Debug_forest_npc_edinorog_ok": "[NPC] Единорожка обработана, продолжаем!", - "Debug_forest_npc_belka_msg": "[NPC] Сообщение с белкой найдено, начинаю обработку!", - "Debug_forest_npc_belka_ok": "[NPC] Белочка обработана, продолжаем!", - # skins - "config_bot_skin_show": "Показывать скин при открытии меню?\n True - Показывать,\n False - Не показывать.", - "config_bot_skin_strings_id": "ID скина", - "config_bot_skin_strings_hash": "Hash скина", - "config_bot_skin_strings_bytes": "Bytes скина", - # npc - "config_bot_autoforest_npcs": "В списке - пропускаем.", - "npc_jabomraz": "🐸 Жабомразь", - "npc_chick": "🐤 цыпа", - "npc_ejik": "💕🦔 Винди", - "npc_djun": "🦜 Джун", - "npc_djun_farm": "🦜 Ферма Джуна", - "npc_bear": "🐻 Тэдди", - "npc_edinorog": "🦄 Единорожка", - "npc_belka": "🐿 Белочка", - # Config message - "config_bot_auto_forest_btn": "🌳Выгулять Коровку?", - "config_bot_auto_forest": "🌳 Выгуливать коровку?\n True - Выгуливать,\n False - Не выгуливать.", - "config_bot_auto_forest_skip_npc_btn": "🦄 Скипать Нпц?", - "config_bot_auto_forest_skip_npc": "Скипать Нпц?\n True - Скипать,\n False - Не скипать.", - "config_bot_auto_craft": "Крафтить предметы?\n True - Крафтить,\n False - Не крафтить.", - "config_bot_auto_craft_count": "Cколько предметов крафтить (за раз)?\n 1-100.", - "config_bot_auto_craft_item_name": "Впишите сюда итем, который автокрафт будет крафтить\n" - "Пример: масло, куки", - "config_debug_diff_msg": "Выберите раздел для логов\n" - "Redis - База данных,\n" - "Forest - Автолес,\n" - "Eating - Автохавка,\n" - "Crafting - Автокраф,\n" - "State - Хендлеры\n" - "General - Общие\n", - "config_bot_auto_eat": "Кормить коровку?\n True - Кормить,\n False - Не кормить.", - "config_bot_auto_eating": "Кормить коровку перед забиранием лута(с леса)?\n True - Кормить,\n False - Не кормить.", - "config_bot_eat_use_count": "Сколько раз использовать еду?\n Указывать строго числа 0-9.", - "config_bot_eat_use_item": "Чем кормить коровку?\n травка, брокколи, молоко+, холли-суп, милк-шейк", - "config_bot_eat_lvl": "Со скольки процентов сытости начинать кормить?\n0-99", - "config_redis_cloud_link": "Ссылка для подключения к хранилищу Redis\n" - "Ссылку брать на Redis.io", - "config_debug_msg": "Сервисные сообщения модуля, нужны только для проверки работоспособности отдельных функций.\n" - "Переключает сообщения с пометками [NPC], [DEBUG] и т.д.\n" - "True - Включено,\n" - "False - Выключено", - "config_bot_send_logs": "Куда отправлять логи?\n" - "False - Выключить логи,\n" - "me - Себе(в избранное),\n" - "default - дефолтный чат логов модуля,\n" - "ID - любой чат, который Вы укажите.\n", - "config_bot_deletemsg_inbot": "Удалять сообщения(свои) в боте после отправки?\n" - "True< - Удалять,\n" - "False - Не удалять.\n", - "config_bot_used_bot": "username или id бота, который будет использоваться для работы модуля,\nУкажите что-то одно:\n" - "💗 default - @moolokobot id: 1606812809 - Стандартный,\n" - "Любое другое значение из разрешенных:\n" - "💗 @moolokobot id: 1606812809 - Основной, лагает,\n" - "💙 @mooloko1bot id: 6467105350 - Дополнительный, лагает,\n" - "💜 @mooloko2bot id: 6396922937 - Второй дополнительный, лагает,\n" - "🦄 @ultramoobot id: 5641915741 - Ультра, не лагает, работает только по подписке,\n" - "🇺🇦 @uamoobot id: 6770881933 - Украинский, не лагает,\n", - "config_bot_used_chat_id": "Если хотите чтобы модуль работал не только в боте, но и в чате, укажите Chat_id", - } - - def __init__(self): - self.config = loader.ModuleConfig( - loader.ConfigValue( - "config_debug_diff_msg", - [], - self.strings["config_debug_diff_msg"], - validator=loader.validators.MultiChoice( - [ - "Forest", - "Forest_click", - "Forest_npc", - "Forest_state", - "Eating", - "Eating_click", - "Eating_state", - "Crafting", - "Crafting_click", - "Craft_state", - "Redis", - "State", - "General", - ] - ), - ), - loader.ConfigValue( - "config_debug_msg", - False, - self.strings["config_debug_msg"], - validator=loader.validators.Boolean(), - ), - loader.ConfigValue( - "config_bot_deletemsg_inbot", - False, - self.strings["config_bot_deletemsg_inbot"], - validator=loader.validators.Boolean(), - ), - loader.ConfigValue( - "config_bot_send_logs", - "me", - lambda: self.strings["config_bot_send_logs"], - ), - loader.ConfigValue( - "config_redis_cloud_link", - "redis://default:S50OBWLodXYQHHeLwjWOB9xCxfGyF22H@redis-16447.c246.us-east-1-4.ec2.redns.redis-cloud.com:16447", - lambda: self.strings["config_redis_cloud_link"], - validator=loader.validators.Hidden(), - ), - loader.ConfigValue( - "config_bot_used_bot", - [], - lambda: self.strings["config_bot_used_bot"], - # validator=loader.validators.Integer(minimum=0), - validator=loader.validators.MultiChoice( - [ - "1606812809", - "6467105350", - "6396922937", - "5641915741", - "6770881933", - ] - ), - ), - loader.ConfigValue( - "config_bot_used_chat_id", - "-1001606812809", - lambda: self.strings["config_bot_used_chat_id"], - validator=loader.validators.Integer(minimum=-100999999999999999999), - ), - loader.ConfigValue( - "config_bot_auto_eat", - "False", - lambda: self.strings["config_bot_auto_eat"], - validator=loader.validators.Boolean(), - ), - loader.ConfigValue( - "config_bot_auto_eating_forest", - "False", - lambda: self.strings["config_bot_auto_eating_forest"], - validator=loader.validators.Boolean(), - ), - loader.ConfigValue( - "config_bot_eat_use_count", - "1", - lambda: self.strings["config_bot_eat_use_count"], - validator=loader.validators.Integer(minimum=0), - ), - loader.ConfigValue( - "config_bot_eat_use_item", - "брокколи", - lambda: self.strings["config_bot_eat_use_item"], - validator=loader.validators.String(), - ), - loader.ConfigValue( - "config_bot_eat_lvl", - "50", - lambda: self.strings["config_bot_eat_lvl"], - validator=loader.validators.Integer(minimum=0), - ), - loader.ConfigValue( - "config_bot_auto_craft", - "False", - lambda: self.strings["config_bot_auto_craft"], - validator=loader.validators.Boolean(), - ), - loader.ConfigValue( - "config_bot_auto_craft_count", - "50", - lambda: self.strings["config_bot_auto_craft_count"], - validator=loader.validators.Integer(minimum=0), - ), - loader.ConfigValue( - "config_bot_auto_craft_item_name", - "масло", - lambda: self.strings["config_bot_auto_craft_item_name"], - validator=loader.validators.String(), - ), - loader.ConfigValue( - "config_bot_auto_forest", - "False", - lambda: self.strings["config_bot_auto_forest"], - validator=loader.validators.Boolean(), - ), - loader.ConfigValue( - "config_bot_auto_forest_skip_npc", - "True", - lambda: self.strings["config_bot_auto_forest_skip_npc"], - validator=loader.validators.Boolean(), - ), - loader.ConfigValue( - "config_bot_autoforest_npcs", - [], - lambda: self.strings["config_bot_autoforest_npcs"], - validator=loader.validators.MultiChoice( - [ - "npc_belka", - "npc_jabomraz", - "npc_edinorog", - "npc_djun", - "npc_djun_farm", - "npc_chick", - "npc_bear", - "npc_ejik", - ] - ), - ), - loader.ConfigValue( - "config_bot_skin_show", - "False", - lambda: self.strings["config_bot_skin_show"], - validator=loader.validators.Boolean(), - ), - loader.ConfigValue( - "config_bot_skin_strings_id", - "5395592741939849449", - lambda: self.strings["config_bot_skin_strings_id"], - ), - loader.ConfigValue( - "config_bot_skin_strings_hash", - "-7011006528981204019", - lambda: self.strings["config_bot_skin_strings_hash"], - ), - loader.ConfigValue( - "config_bot_skin_strings_bytes", - "AQAAClpn9Gn8lOq0lTYlXzF9lctkuIA3lNI=", - lambda: self.strings["config_bot_skin_strings_bytes"], - ), - ) - - async def client_ready(self, client, *_): - """ - Основная иницализация обьектов - :param client: - :param _: - :return: - """ - self.client = client - self.tg_id = (await client.get_me()).id - self.db = 0 - self.redis = await aioredis.from_url( - self.config["config_redis_cloud_link"], - encoding="utf-8", - decode_responses=True, - db=self.db, - ) - - self.pubsub = self.redis.pubsub() - self.debug = DebugLogger(self.client, self.config) - await self.redis.config_set("notify-keyspace-events", "Ex") - await self.pubsub.subscribe(f"__keyevent@{self.db}__:expired") - - @loader.command() - async def fmoo(self, message: Message): - """ - Инлайн-меню управления автофармом - """ - chat_id = utils.get_chat_id(message) - - if self.config["config_bot_skin_show"]: - sticker = InputDocument( - id=self.config["config_bot_skin_strings_id"], - access_hash=self.config["config_bot_skin_strings_hash"], - file_reference=base64.b64decode( - self.config["config_bot_skin_strings_bytes"] - ), - ) - - await self.client.send_file(chat_id, sticker) - - msg, buttons = await self._moobot_info() - await self.inline.form(message=message, text=msg, reply_markup=buttons) - - async def _moobot_info(self): - """ - Inline Главное меню - :return: - """ - msg = ( - f"{self.strings['moo_menu']}" - f"\t\t{self.strings['auto_forest_menu']}" - f"\t\t{self.strings['auto_eating_menu']}" - f"\t\t{self.strings['auto_craft_menu']}" - f"\t\t{self.strings['settings_menu']}" - f"\t\t{self.strings['skin_menu']}" - ) - markup = [ - [ - { - "text": self.strings["auto_forest"], - "callback": self.inline_forest_menu, - "args": (), - }, - { - "text": self.strings["auto_eating"], - "callback": self.inline_eating_menu, - "args": (), - }, - ], - [ - { - "text": self.strings["auto_craft"], - "callback": self.inline_craft_menu, - "args": (), - }, - { - "text": self.strings["settings"], - "callback": self.inline_misc_menu, - "args": (), - }, - ], - [ - { - "text": self.strings["bot_skin_menu_key"], - "callback": self.inline_skin_menu, - "args": (), - } - ], - [ - { - "text": self.strings["close_btn"], - "callback": self.close_button, - "args": (), - } - ], - ] - return msg, markup - - async def moobot_info(self, call: InlineCall): - """ - Inline Главное меню - :param call: - :return: - """ - msg = ( - f"{self.strings['moo_menu']}" - f"\t\t{self.strings['auto_forest_menu']}" - f"\t\t{self.strings['auto_eating_menu']}" - f"\t\t{self.strings['auto_craft_menu']}" - f"\t\t{self.strings['settings_menu']}" - f"\t\t{self.strings['skin_menu']}" - ) - markup = [ - [ - { - "text": self.strings["auto_forest"], - "callback": self.inline_forest_menu, - "args": (call,), - }, - { - "text": self.strings["auto_eating"], - "callback": self.inline_eating_menu, - "args": (call,), - }, - ], - [ - { - "text": self.strings["auto_craft"], - "callback": self.inline_craft_menu, - "args": (call,), - }, - { - "text": self.strings["settings"], - "callback": self.inline_misc_menu, - "args": (call,), - }, - ], - [ - { - "text": self.strings["bot_skin_menu_key"], - "callback": self.inline_skin_menu, - "args": (call,), - } - ], - [ - { - "text": self.strings["close_btn"], - "callback": self.close_button, - "args": (), - } - ], - ] - await call.edit(msg, reply_markup=markup) - - async def inline_forest_menu(self, call: InlineCall): - autoforest = ( - f"{self.strings['on']}" - if self.config["config_bot_auto_forest"] - else f"{self.strings['off']}" - ) - autonpc = ( - f"{self.strings['on']}" - if self.config["config_bot_auto_forest_skip_npc"] - else f"{self.strings['off']}" - ) - - value = self.config["config_bot_autoforest_npcs"] - categories = [ - "npc_belka", - "npc_jabomraz", - "npc_edinorog", - "npc_djun", - "npc_djun_farm", - "npc_chick", - "npc_bear", - "npc_ejik", - ] - - skipped_npcs = [ - self.strings.get(cat, cat) for cat in categories if cat in value - ] - skipped_text = ( - "\n".join(f"{self.strings['on']} {npc}" for npc in skipped_npcs) - if skipped_npcs - else f"{self.strings['npc_not_skipped']}" - ) - - msg = ( - f"{self.strings['npc_menu']}" - f"{self.strings['npc_menu_autoforest']} - {autoforest}\n" - f"{self.strings['npc_menu_skip_status']} - {autonpc}\n" - f"{self.strings['npc_menu_skip']}" - f"{self.strings['npc_menu_skip_now']}" + skipped_text - ) - - markup = [ - [ - { - "text": f"{self.strings['npc_menu_autoforest_btn']} {self.strings['on_btn'] if self.config['config_bot_auto_forest'] else self.strings['off_btn']}", - "callback": self.toggle_config_and_refresh, - "args": ("config_bot_auto_forest", self.inline_forest_menu), - }, - { - "text": f"{self.strings['npc_menu_skip_status_btn']} {self.strings['on_btn'] if self.config['config_bot_auto_forest_skip_npc'] else self.strings['off_btn']}", - "callback": self.toggle_config_and_refresh, - "args": ( - "config_bot_auto_forest_skip_npc", - self.inline_forest_menu, - ), - }, - ], - [ - { - "text": self.strings["npc_autoskip"], - "callback": self.inline_forest_skip_menu, - "args": (), - } - ], - [ - { - "text": self.strings["back_btn"], - "callback": self.back_button, - "args": (), - }, - { - "text": self.strings["close_btn"], - "callback": self.close_button, - "args": (), - }, - ], - ] - await call.edit(msg, reply_markup=markup) - - async def inline_skin_menu(self, call: InlineCall): - skin_synced = all( - [ - self.config.get("config_bot_skin_strings_id"), - self.config.get("config_bot_skin_strings_hash"), - self.config.get("config_bot_skin_strings_bytes"), - ] - ) - skin_show = self.config.get("config_bot_skin_show", False) - - msg = ( - "" - f"{self.strings['skin_menu_txt']} {self.strings['on'] if skin_synced else self.strings['off']}\n" - f" {self.strings['skin_menu_show_txt']} {self.strings['on_btn'] if skin_show else self.strings['off_btn']}" - ) - - markup = [ - [ - { - "text": f"{self.strings['skin_menu_show_skin_btn']} {self.strings['on_btn'] if skin_show else self.strings['off_btn']}", - "callback": self.toggle_config_and_refresh, - "args": ("config_bot_skin_show", self.inline_skin_menu), - } - ], - [ - { - "text": self.strings["skin_menu_sync_skin_btn"], - "callback": self.button_sync_skin, - "args": (), - } - ], - [ - { - "text": self.strings["back_btn"], - "callback": self.back_button, - "args": (), - }, - { - "text": self.strings["close_btn"], - "callback": self.close_button, - "args": (), - }, - ], - ] - - await call.edit(msg, reply_markup=markup) - - async def inline_forest_skip_menu(self, call: InlineCall): - value = self.config["config_bot_autoforest_npcs"] - categories = [ - "npc_belka", - "npc_jabomraz", - "npc_edinorog", - "npc_djun", - "npc_djun_farm", - "npc_chick", - "npc_bear", - "npc_ejik", - ] - - msg = ( - f"{self.strings['skip_menu_main_txt']}\n\n" - f"\t\t{self.strings['skip_menu_main_on']}\n" - f"\t\t{self.strings['skip_menu_main_off']}\n\n" - ) - - for cat in categories: - if cat in value: - display_name = self.strings.get(cat, cat) - msg += f"{display_name} {self.strings['skip_menu_main_skipped']}\n" - - markup = [] - row = [] - for i, cat in enumerate(categories): - display_name = self.strings.get(cat, cat) - mark = ( - f"{self.strings['on_btn']}" - if cat in value - else f"{self.strings['off_btn']}" - ) - - row.append( - { - "text": f"{display_name}: {mark}", - "callback": self.toggle_multi_choice, - "args": ( - "config_bot_autoforest_npcs", - cat, - self.inline_forest_skip_menu, - ), - } - ) - - if len(row) == 2 or i == len(categories) - 1: - markup.append(row) - row = [] - - markup.append( - [ - { - "text": self.strings["back_btn"], - "callback": self.back_forest_button, - "args": (), - }, - { - "text": self.strings["close_btn"], - "callback": self.close_button, - "args": (), - }, - ] - ) - - await call.edit(msg, reply_markup=markup) - - async def inline_eating_menu(self, call: InlineCall): - auto_eat = ( - f"{self.strings['on']}" - if self.config["config_bot_auto_eat"] - else f"{self.strings['off']}" - ) - eat_forest = ( - f"{self.strings['on']}" - if self.config["config_bot_auto_eating_forest"] - else f"{self.strings['off']}" - ) - item = self.config["config_bot_eat_use_item"] - count = self.config["config_bot_eat_use_count"] - lvl = self.config["config_bot_eat_lvl"] - - msg = ( - f"{self.strings['auto_eat_main_menu_txt']}" - f"\t\t{self.strings['auto_eating_main_menu_txt']} - {auto_eat}\n\n" - f"\t\t{self.strings['auto_eating_inforest_main_menu_txt']} - {eat_forest}\n\n" - f"\t\t{self.strings['auto_eating_item']} - {item}\n\n" - f"\t\t{self.strings['auto_eating_item_count']} - {count}\n\n" - f"\t\t{self.strings['auto_eating_lvl']} - {lvl}%\n" - ) - - markup = [ - [ - { - "text": f"{self.strings['auto_eating_main_menu_txt']} {self.strings['on_btn'] if self.config['config_bot_auto_eat'] else self.strings['off_btn']}", - "callback": self.toggle_config_and_refresh, - "args": ("config_bot_auto_eat", self.inline_eating_menu), - }, - { - "text": f"{self.strings['auto_eating_inforest_main_menu_txt']} {self.strings['on_btn'] if self.config['config_bot_auto_eating_forest'] else self.strings['off_btn']}", - "callback": self.toggle_config_and_refresh, - "args": ("config_bot_auto_eating_forest", self.inline_eating_menu), - }, - ], - [ - { - "text": f"{self.strings['auto_eating_item_count']} {count}", - "input": self.strings["auto_eating_inline_count"], - "handler": self.ask_config_value_handler, - "args": ("config_bot_eat_use_count",), - }, - { - "text": f"{self.strings['auto_eating_lvl']} - {lvl}%", - "input": self.strings["auto_eating_inline_lvl"], - "handler": self.ask_config_value_handler, - "args": ("config_bot_eat_lvl",), - }, - ], - [ - { - "text": f"{self.strings['auto_eating_item']} {item}", - "input": self.strings["auto_eating_inline_item"], - "handler": self.ask_config_value_handler, - "args": ("config_bot_eat_use_item",), - }, - ], - [ - { - "text": self.strings["back_btn"], - "callback": self.back_button, - "args": (), - }, - { - "text": self.strings["close_btn"], - "callback": self.close_button, - "args": (), - }, - ], - ] - await call.edit(msg, reply_markup=markup) - - async def inline_craft_menu(self, call: InlineCall): - auto_craft = f"{self.strings['on'] if self.config['config_bot_auto_craft'] else self.strings['off']}" - item = self.config["config_bot_auto_craft_item_name"] - count = self.config["config_bot_auto_craft_count"] - - msg = ( - f"{self.strings['craft_menu_main_txt']}\n\n" - f"{self.strings['craft_menu_main_craft']} - {auto_craft}\n" - f"{self.strings['craft_menu_main_craft_item']} - {item}\n" - f"{self.strings['craft_menu_main_craft_count']} - {count}" - ) - - markup = [ - [ - { - "text": f"{self.strings['craft_menu_main_craft']} {self.strings['on_btn'] if self.config['config_bot_auto_craft'] else self.strings['off_btn']}", - "callback": self.toggle_config_and_refresh, - "args": ("config_bot_auto_craft", self.inline_craft_menu), - }, - ], - [ - { - "text": f"{self.strings['craft_menu_main_craft_item']} {item}", - "input": self.strings["craft_menu_main_craft_item_inline"], - "handler": self.ask_config_value_handler, - "args": ("config_bot_auto_craft_item_name", self.inline_craft_menu), - } - ], - [ - { - "text": f"{self.strings['craft_menu_main_craft_count']} {count}", - "input": self.strings["craft_menu_main_craft_count_inline"], - "handler": self.ask_config_value_handler, - "args": ("config_bot_auto_craft_count", self.inline_craft_menu), - }, - ], - [ - { - "text": self.strings["back_btn"], - "callback": self.back_button, - "args": (), - }, - { - "text": self.strings["close_btn"], - "callback": self.close_button, - "args": (), - }, - ], - ] - await call.edit(msg, reply_markup=markup) - - async def inline_misc_menu(self, call: InlineCall): - msg = f"{self.strings['misc_menu_main_txt']}" - markup = [ - [ - { - "text": f"{self.strings['misc_menu_main_debug']} {self.strings['on_btn'] if self.config['config_debug_msg'] else self.strings['off_btn']}", - "callback": self.toggle_config_and_refresh, - "args": ("config_debug_msg", self.inline_misc_menu), - }, - ], - [ - { - "text": f"{self.strings['misc_menu_main_deletemsg']} {self.strings['on_btn'] if self.config['config_bot_deletemsg_inbot'] else self.strings['off_btn']}", - "callback": self.toggle_config_and_refresh, - "args": ("config_bot_deletemsg_inbot", self.inline_misc_menu), - }, - ], - [ - { - "text": f"{self.strings['misc_menu_main_logs_chat']} {self.config['config_bot_send_logs']}", - "input": self.strings["misc_menu_main_logs_chat_inline"], - "handler": self.ask_config_value_handler, - "args": ("config_bot_send_logs", self.inline_misc_menu), - }, - ], - [ - { - "text": f"{self.strings['misc_menu_main_chat_id']} {self.config['config_bot_used_chat_id']}", - "input": self.strings["misc_menu_main_chat_id_inline"], - "handler": self.ask_config_value_handler, - "args": ("config_bot_used_chat_id", self.inline_misc_menu), - }, - ], - [ - { - "text": f"{self.strings['misc_menu_main_bots_id']} {', '.join(self.config['config_bot_used_bot']) or 'Нет'}", - "callback": self.inline_bot_select_menu, - "args": (call,), - }, - ], - [ - { - "text": self.strings["misc_menu_main_debug_btn_menu"], - "callback": self.inline_debug_menu, - "args": (), - }, - ], - [ - { - "text": self.strings["back_btn"], - "callback": self.back_button, - "args": (), - }, - { - "text": self.strings["close_btn"], - "callback": self.close_button, - "args": (), - }, - ], - ] - await call.edit(msg, reply_markup=markup) - - async def inline_bot_select_menu(self, call: InlineCall, *args): - msg = "🤖 Выберите основного бота:\nТекущий: " - current = ", ".join(self.config["config_bot_used_bot"]) or "❌ Не выбран" - - msg += f"{current}" - - bots = [ - ("1606812809", "💗 @moolokobot"), - ("6467105350", "💙 @mooloko1bot"), - ("6396922937", "💜 @mooloko2bot"), - ("5641915741", "🦄 @ultramoobot"), - ("6770881933", "🇺🇦 @uamoobot"), - ] - - markup = [ - [ - { - "text": f"{name} {'✅' if bot_id in self.config['config_bot_used_bot'] else '❌'}", - "callback": self.set_config_bot_used_bot, - "args": (bot_id, self.inline_bot_select_menu), - } - ] - for bot_id, name in bots - ] - - markup.append( - [ - { - "text": "🗑 Очистить", - "callback": self.clear_config_bot_used_bot, - "args": (self.inline_bot_select_menu,), - }, - {"text": "🔙 Назад", "callback": self.inline_misc_menu, "args": ()}, - ] - ) - - await call.edit(msg, reply_markup=markup) - - async def set_config_bot_used_bot( - self, call: InlineCall, bot_id: str, refresh_callback, *args - ): - self.config["config_bot_used_bot"] = [bot_id] - await refresh_callback(call) - - async def clear_config_bot_used_bot( - self, call: InlineCall, refresh_callback, *args - ): - self.config["config_bot_used_bot"] = [] - await refresh_callback(call) - - async def inline_debug_menu(self, call: InlineCall): - msg = f"{self.strings['debug_menu_main_txt']}" - value = self.config["config_debug_diff_msg"] - categories = [ - "Forest", - "Forest_click", - "Forest_npc", - "Forest_state", - "Eating", - "Eating_click", - "Eating_state", - "Crafting", - "Crafting_click", - "Craft_state", - "Redis", - "State", - "General", - ] - markup = [] - row = [] - for i, cat in enumerate(categories): - row.append( - { - "text": f"{cat}: {self.strings['on_btn'] if cat in value else self.strings['off_btn']}", - "callback": self.toggle_multi_choice, - "args": ("config_debug_diff_msg", cat, self.inline_debug_menu), - } - ) - if len(row) == 2 or i == len(categories) - 1: - markup.append(row) - row = [] - - markup.append( - [ - { - "text": self.strings["back_btn"], - "callback": self.inline_misc_menu, - "args": (), - }, - { - "text": self.strings["close_btn"], - "callback": self.close_button, - "args": (), - }, - ] - ) - await call.edit(msg, reply_markup=markup) - - async def toggle_config_and_refresh(self, call: InlineCall, key, refresh_func): - self.config[key] = not self.config[key] - await refresh_func(call) - - async def ask_config_value_handler( - self, call: InlineCall, value: str, key: str, back_func - ): - self.config[key] = value - await back_func(call) - - async def toggle_multi_choice( - self, call: InlineCall, config_key: str, value: str, redraw_callback - ): - current = list(self.config[config_key]) - if value in current: - current.remove(value) - else: - current.append(value) - - try: - self.config[config_key] = current - except Exception: - await call.answer("❌ Ошибка валидации") - return - - await redraw_callback(call) - - async def syncskin_inline(self, call: InlineCall): - await call.answer("🔄 Синхронизация началась...") - - chat_id = self.get_chat_id - self.config["config_bot_used_bot"] - - msg = await self.client.send_message(chat_id, "/cow") - start_id = msg.id - - for _ in range(15): - await asyncio.sleep(1) - - messages = await self.client.get_messages(chat_id, limit=10) - for m in messages: - if m.id > start_id and m.sticker: - sticker = m.media.document - - self.config["config_bot_skin_strings_id"] = sticker.id - self.config["config_bot_skin_strings_hash"] = sticker.access_hash - file_reference_b64 = base64.b64encode( - sticker.file_reference - ).decode() - - self.config["config_bot_skin_strings_bytes"] = file_reference_b64 - - return await call.answer("✅ Скин синхронизирован!") - - await call.answer("⚠️ Стикер не получен — бот молчит?") - - async def button_sync_skin(self, call: InlineCall): - await self.syncskin_inline(call) - - async def back_forest_button(self, call: InlineCall): - """Вернуться обратно""" - await call.answer("OK") - await self.inline_forest_menu(call) - - async def back_button(self, call: InlineCall): - """Вернуться обратно""" - await call.answer("OK") - msg, markup = await self._moobot_info() - await call.edit(msg, reply_markup=markup) - - @staticmethod - async def close_button(call: InlineCall): - await call.answer("Закрываю...") - await call.delete() - - @property - def get_chat_id(self): - """ - Проверяет наличие chat_id и bot_id в конфиге. - Возвращает chat_id, если он есть, иначе bot_id. - """ - bot_id = self.config["config_bot_used_bot"] - chat_id = self.config["config_bot_used_chat_id"] - - if chat_id and chat_id != "-100": - return int(chat_id) - - if bot_id: - return int(bot_id) - - @loader.command() - async def auto_eating(self, message): - """Автоматически кормит персонажа, если уровень еды ниже 70%""" - # TODO: Прикрутить к инлайн-хендлеру - if not self.config["config_bot_auto_eat"]: - return - chat_id = self.get_chat_id - - await self.debug.eat_state(self.strings["Debug_Events_msg_set"]) - self.client.add_event_handler(self.eating_handler, events.NewMessage) - self.client.add_event_handler(self.eating_handler, events.MessageEdited) - - msg = await self.client.send_message(chat_id, "/cow") - await self.save_forest_msg(chat_id, "eating_msg", msg) - - async def eating_handler(self, event): - chat_id = self.get_chat_id - food = self.config["config_bot_eat_lvl"] - if event.chat_id != chat_id: - return - - if not event.is_reply: - return - eating_msg = await self.get_forest_msg(chat_id, "eating_msg") - reply_msg = await event.get_reply_message() - - if not reply_msg or reply_msg.id != eating_msg["id"]: - return - - text = event.raw_text - await self.debug.eat(f"[DEBUG] Получен текст: {text}") - - match = re.search(r"🌿\s*хавчик\s*(\d+)%", text) - - if match: - food_level = int(match.group(1)) - await self.debug.eat(f"[DEBUG] Найден уровень еды: {food_level}%") - - if food_level <= food: - await self.save_forest_msg(chat_id, "food", event) - await self.debug.eat(f"[ACTION] Еда {food_level}%, запускаю кормление") - await self.eating() - else: - await self.debug.eat(f"[INFO] Еды {food_level}%, кормить не надо") - elif "🌿 голодает" in text: - await self.save_forest_msg(chat_id, "food", event) - await self.debug.eat("[ACTION] Обнаружено голодание! Запускаю кормление") - await self.eating() - - async def eating(self): - """ - Ищет кнопку 'Брокколи' и использует её eat_use_count раз - """ - use_count = 0 - user_id = self.tg_id - chat_id = self.get_chat_id - eat_use_count = self.config["config_bot_eat_use_count"] - eat_use_item = self.config["config_bot_eat_use_item"] - msg_data = await self.get_forest_msg(chat_id, "food") - msg = await self.client.get_messages(chat_id, ids=msg_data["id"]) - - if not msg.buttons: - return await self.debug.eat_click("[EATING] Кнопки не найдены.") - - for _ in range(eat_use_count): - for row in msg.buttons: - for button in row: - if button.data.decode() == f"check_items {user_id}": - await msg.click(msg._buttons_flat.index(button)) - await self.debug.eat_click( - "[EATING] Нажата кнопка 'check_items'" - ) - await asyncio.sleep(2) - - msg = await self.client.get_messages( - chat_id, ids=msg_data["id"] - ) - break - - for row in msg.buttons: - for button in row: - if button.data.decode() == f"itemuse {user_id} {eat_use_item}": - await msg.click(msg._buttons_flat.index(button)) - use_count += 1 - await self.debug.eat_click( - f"[EATING] Используем брокколи ({use_count}/{eat_use_count})" - ) - await asyncio.sleep(3) - if use_count >= eat_use_count: - return await self.debug.eat_click( - "[EATING] Достигнут лимит использования брокколи. Завершаем." - ) - - @loader.command() - async def auto_craft_txt(self, message): - """ - Команда для автоматической работы авто-крафта - """ - # Todo: Прикрутить это все к инлайн-хендлеру - if not self.config["config_bot_auto_craft"]: - return - - chat_id = self.get_chat_id - - await self.debug.craft_state(self.strings["Debug_Events_msg_set"]) - self.client.add_event_handler(self.craft_handler, events.NewMessage) - self.client.add_event_handler(self.craft_handler, events.MessageEdited) - - msg = await self.client.send_message(chat_id, "/craft") - await self.save_forest_msg(chat_id, "craft_msg", msg) - - await self.auto_forest_jobs(20, "del_auto_craft_handlers") - - async def craft_handler(self, event): - chat_id = self.get_chat_id - if event.chat_id != chat_id: - return - text = event.raw_text - - if "мин." in text: - wait_time_match = re.search( - r"(?:(\d+)\s*(?:час(?:а|ов)?|⏱))?\s*(\d+)\s*мин\.", text - ) - if wait_time_match: - hours = int(wait_time_match.group(1)) if wait_time_match.group(1) else 0 - minutes = ( - int(wait_time_match.group(2)) if wait_time_match.group(2) else 0 - ) - wait_time = (hours * 60 + minutes) * 60 - wait_time += 2 * 60 - await self.auto_forest_jobs(wait_time, "crafting") - await self.debug.craft(self.strings["Debug_craft_job_ok"]) - - if not event.is_reply: - return - craft_msg = await self.get_forest_msg(chat_id, "craft_msg") - reply_msg = await event.get_reply_message() - - if not reply_msg or reply_msg.id != craft_msg["id"]: - return - - if "Твой верстак" in text: - if "готово" in text: - await self.save_forest_msg(chat_id, "craft_take", event) - await self.craft_take() - - elif "пусто" in text: - await self.save_forest_msg(chat_id, "craft_check", event) - await self.craft_start() - - elif "•50" in text: - wait_time_match = re.search( - r"(?:(\d+)\s*(?:час(?:а|ов)?|⏱))?\s*(\d+)\s*мин\.", text - ) - if wait_time_match: - hours = ( - int(wait_time_match.group(1)) if wait_time_match.group(1) else 0 - ) - minutes = ( - int(wait_time_match.group(2)) if wait_time_match.group(2) else 0 - ) - wait_time = (hours * 60 + minutes) * 60 - wait_time += 2 * 60 - await self.auto_forest_jobs(wait_time, "crafting") - await self.debug.craft(self.strings["Debug_craft_job_ok"]) - - elif "Что будем крафтить" in text: - await self.save_forest_msg(chat_id, "craft_finall", event) - await self.craft_finall() - - async def craft_take(self): - """ - Ищет кнопку 'Забрать' и забирает предеты. - """ - user_id = self.tg_id - chat_id = self.get_chat_id - msg_data = await self.get_forest_msg(chat_id, "craft_take") - if msg_data: - msg = await self.client.get_messages(chat_id, ids=msg_data["id"]) - if msg.buttons: - for row in msg.buttons: - for button in row: - if button.data.decode() == f"craft {user_id} takeout": - await msg.click(msg._buttons_flat.index(button)) - await self.debug.craft_click( - self.strings["Debug_craft_take_ok"] - ) - await asyncio.sleep(3) - - async def craft_start(self): - """ - Ищет кнопку 'Скрафтить' и вызываем следующее меню - """ - chat_id = self.get_chat_id - user_id = self.tg_id - msg_data = await self.get_forest_msg(chat_id, "craft_check") - if msg_data: - msg = await self.client.get_messages(chat_id, ids=msg_data["id"]) - if msg.buttons: - for row in msg.buttons: - for button in row: - if button.data.decode() == f"craft {user_id} check": - await msg.click(msg._buttons_flat.index(button)) - await self.debug.craft_click( - self.strings["Debug_craft_start_ok"] - ) - await asyncio.sleep(3) - - async def craft_finall(self): - """ - Ищет кнопку 'Скрафтить' и вызываем следующее меню - """ - chat_id = self.get_chat_id - user_id = self.tg_id - item_name = self.config["config_bot_auto_craft_item_name"] - msg_data = await self.get_forest_msg(chat_id, "craft_finall") - if msg_data: - msg = await self.client.get_messages(chat_id, ids=msg_data["id"]) - if msg.buttons: - for row in msg.buttons: - for button in row: - if button.data.decode().endswith( - f"{user_id} f-craft {item_name}" - ): - await msg.click(msg._buttons_flat.index(button)) - await asyncio.sleep(2) - await msg.reply("50") - await self.debug.craft_click( - self.strings["Debug_craft_finall_ok"] - ) - - @loader.command() - async def auto_forest_txt(self, message): - """ - Команда для автоматической работы авто-леса - """ - if not self.config["config_bot_auto_forest"]: - return - - chat_id = self.get_chat_id - - if self.config["config_bot_auto_eating_forest"]: - await self.auto_eating(message) - - self.client.add_event_handler(self.forest_handler, events.NewMessage) - self.client.add_event_handler(self.forest_handler, events.MessageEdited) - await self.debug.forest_state(self.strings["Debug_Events_msg_set"]) - - msg = await self.client.send_message(chat_id, "/forest") - await self.save_forest_msg(chat_id, "forest_msg", msg) - - await self.auto_forest_jobs(20, "del_forest_handlers") - - async def forest_handler(self, event): - chat_id = self.get_chat_id - if event.chat_id != chat_id: - return - - if not event.is_reply: - return - forest_msg = await self.get_forest_msg(chat_id, "mymsg") - - reply_msg = await event.get_reply_message() - if not reply_msg and reply_msg.id != forest_msg["id"]: - return - - text = event.raw_text - - if "Твоя коровка гуляет" in text: - wait_time_match = re.search( - r"через (?:(\d+) час(?:а|ов)? )?(\d+) минут", text - ) - if wait_time_match: - hours = int(wait_time_match.group(1)) if wait_time_match.group(1) else 0 - minutes = int(wait_time_match.group(2)) - wait_time = (hours * 60 + minutes) * 60 - wait_time += 2 * 60 - await self.auto_forest_jobs(wait_time, "takeloot") - await self.debug.redis(self.strings["Debug_forest_job_go_update"]) - - elif "🐤 цыпа" in text: - if "npc_chick" in self.config["config_bot_autoforest_npcs"]: - return - await self.save_forest_msg(chat_id, "chick", event) - await self.debug.forest_npc(self.strings["Debug_forest_npc_chick_msg"]) - await self.npc_chick() - - elif "💕🦔 Винди" in text: - if "npc_ejik" in self.config["config_bot_autoforest_npcs"]: - return - await self.save_forest_msg(chat_id, "ejik", event) - await self.debug.forest_npc(self.strings["Debug_forest_npc_ejik_msg"]) - await self.npc_ejik() - - elif "🦜 Джун" in text: - if "npc_djun" in self.config["config_bot_autoforest_npcs"]: - return - await self.save_forest_msg(chat_id, "djun", event) - await self.debug.forest_npc(self.strings["Debug_forest_npc_djun_msg"]) - await self.npc_djun() - - elif "🦜 Ферма Джуна" in text: - if "npc_djun_farm" in self.config["config_bot_autoforest_npcs"]: - return - await self.save_forest_msg(chat_id, "djun", event) - await self.debug.forest_npc(self.strings["Debug_forest_npc_djun_msg"]) - await self.npc_djun() - - elif "🐻 Тэдди" in text: - if "npc_bear" in self.config["config_bot_autoforest_npcs"]: - return - await self.save_forest_msg(chat_id, "bear", event) - await self.debug.forest_npc(self.strings["Debug_forest_npc_bear_msg"]) - await self.npc_bear() - - elif "🐸 Жабомразь" in text: - if "npc_jabomraz" in self.config["config_bot_autoforest_npcs"]: - return - await self.save_forest_msg(chat_id, "jabomraz", event) - await self.debug.forest_npc(self.strings["Debug_forest_npc_jabomraz_msg"]) - await self.npc_jabomraz() - - elif "🦄 Единорожка" in text: - if "npc_edinorog" in self.config["config_bot_autoforest_npcs"]: - return - await self.save_forest_msg(chat_id, "edinorog", event) - await self.debug.forest_npc(self.strings["Debug_forest_npc_edinorog_msg"]) - await self.npc_edinorog() - - elif "🐿 Белочка" in text: - if "npc_belka" in self.config["config_bot_autoforest_npcs"]: - return - await self.save_forest_msg(chat_id, "belka", event) - await self.debug.forest_npc(self.strings["Debug_forest_npc_belka_msg"]) - await self.npc_belka() - - elif any( - trigger in text - for trigger in ["Отправь коровку погулять", "не кушает травку"] - ): - await self.save_forest_msg(chat_id, "go", event) - await self.debug.forest_npc(self.strings["Debug_forest_cow_go_msg"]) - await self.auto_forest_go() - - elif any( - trigger in text - for trigger in [ - "коровка вернулась", - "Коровка пришла", - "пришла домой", - "прискакала", - "Проверишь лут", - "Коровочка вернулась", - "вернулась из леса", - "коровка принесла", - ] - ): - await self.save_forest_msg(chat_id, "go", event) - await self.debug.forest_npc(self.strings["Debug_forest_cow_takeloot_msg"]) - await self.auto_forest_takeloot() - - async def auto_forest_go(self): - """ - Ищет кнопку 'Гулять' и отправляет коровку на прогулку. - """ - chat_id = self.get_chat_id - user_id = self.tg_id - msg_data = await self.get_forest_msg(chat_id, "go") - if msg_data: - msg = await self.client.get_messages(chat_id, ids=msg_data["id"]) - if msg.buttons: - for row in msg.buttons: - for button in row: - if button.data.decode() == f"forest {user_id} go": - await msg.click(msg._buttons_flat.index(button)) - await self.redis.delete(f"forest_msg:{chat_id}:go") - await self.debug.forest_click( - self.strings["Debug_forest_cow_go_ok"] - ) - - async def auto_forest_takeloot(self): - """ - После прогулки проверяет, можно ли забрать лут. - """ - chat_id = self.get_chat_id - user_id = self.tg_id - msg_data = await self.get_forest_msg(chat_id, "go") - if msg_data: - msg = await self.client.get_messages(chat_id, ids=msg_data["id"]) - if msg.buttons: - for row in msg.buttons: - for button in row: - if button.data.decode() == f"forest {user_id} takeloot": - await msg.click(msg._buttons_flat.index(button)) - await self.auto_forest_go() - await self.debug.forest_click( - self.strings["Debug_forest_cow_takeloot_ok"] - ) - - async def save_forest_msg(self, chat_id, action, msg): - """ - Сохраняем сообщение с уникальным ключом в Redis - """ - key = f"forest_msg:{chat_id}:{action}" - data = {"id": msg.id, "text": msg.raw_text} - await self.redis.set(key, json.dumps(data), ex=30) - await self.debug.redis( - f"[REDIS] Сохранил данные в временное хранилище!\nДанные: {data}" - ) - - async def get_forest_msg(self, chat_id, action): - """ - Получаем сообщение по уникальному ключу - """ - key = f"forest_msg:{chat_id}:{action}" - data = await self.redis.get(key) - if data: - await self.debug.redis( - f"[REDIS] Получил данные из хранилища!\nКлюч: {key}\nДанные: {data}" - ) - return json.loads(data) - return None - - @loader.loop(interval=1, autostart=True) - async def listen_to_expired_keys(self): - """ - Своеобразный слушатель для истекших TTL ключей редиса. - Если ключ есть - отправляем в self.handle_expired_key() - :return: - """ - async for message in self.pubsub.listen(): - if message["type"] == "message": - key = message["data"] - await self.handle_expired_key(key) - - async def handle_expired_key(self, key): - """ - Обработчик истекших ключей, чтоб не путать - все ключи подписываются. - Ключ:Пользователь:Действие - :param key: - :return: - """ - parts = key.split(":") - if len(parts) < 3: - return - - user_id = parts[1] - action = parts[2] - - if str(user_id) != str(self.tg_id): - return - - if action == "takeloot": - await self.auto_forest_txt(None) - - elif action == "crafting": - await self.auto_craft_txt(None) - - elif action == "del_forest_handlers": - self.client.remove_event_handler(self.forest_handler, events.NewMessage) - self.client.remove_event_handler(self.forest_handler, events.MessageEdited) - await self.debug.forest_state(self.strings["Debug_Events_msg_del"]) - - elif action == "del_auto_craft_handlers": - self.client.remove_event_handler(self.craft_handler, events.NewMessage) - self.client.remove_event_handler(self.craft_handler, events.MessageEdited) - await self.debug.craft_state(self.strings["Debug_Events_msg_del"]) - - elif action == "del_auto_eat_handlers": - self.client.remove_event_handler(self.eating_handler, events.NewMessage) - self.client.remove_event_handler(self.eating_handler, events.MessageEdited) - await self.debug.eat_state(self.strings["Debug_Events_msg_del"]) - - async def auto_forest_jobs(self, wait_time: int, action: str): - """ - Сюда отправляются время и задание, мы его пакуем в ключ и отправляем с TTL на хранение в Redis. - :param wait_time: - :param action: - :return: - """ - self.config["config_bot_used_chat_id"] - user_id = self.tg_id - key = f"forest_task:{user_id}:{action}" - await self.redis.set(key, "pending", ex=wait_time) - await self.debug.redis( - f"[DEBUG] Таймер на {wait_time // 60} минут до {action} поставлен." - ) - - async def npc_ejik(self): - """ - Обрабатывает появление НПЦ Ежиха. - """ - chat_id = self.get_chat_id - user_id = self.tg_id - msg_data = await self.get_forest_msg(chat_id, "ejik") - if msg_data: - msg = await self.client.get_messages(chat_id, ids=msg_data["id"]) - if msg.buttons: - for row in msg.buttons: - for button in row: - if button.data.decode() == f"npc_inter {user_id} wind leave": - await msg.click(msg._buttons_flat.index(button)) - await self.debug.forest_npc( - self.strings["Debug_forest_npc_ejik_ok"] - ) - - async def npc_bear(self): - """ - Обрабатывает появление НПЦ Медведя. - """ - chat_id = self.get_chat_id - user_id = self.tg_id - msg_data = await self.get_forest_msg(chat_id, "bear") - if msg_data: - msg = await self.client.get_messages(chat_id, ids=msg_data["id"]) - if msg.buttons: - for row in msg.buttons: - for button in row: - if button.data.decode() == f"npctrade {user_id} Тэдди no": - await msg.click(msg._buttons_flat.index(button)) - await self.debug.forest_npc( - self.strings["Debug_forest_npc_bear_ok"] - ) - - async def npc_belka(self): - """ - Обрабатывает появление НПЦ Белку. - """ - chat_id = self.get_chat_id - user_id = self.tg_id - msg_data = await self.get_forest_msg(chat_id, "belka") - if msg_data: - msg = await self.client.get_messages(chat_id, ids=msg_data["id"]) - if msg.buttons: - for row in msg.buttons: - for button in row: - if button.data.decode() == f"npctrade {user_id} Белочка no": - await msg.click(msg._buttons_flat.index(button)) - await self.debug.forest_npc( - self.strings["Debug_forest_npc_belka_ok"] - ) - - async def npc_djun_farm(self): - """ - Обрабатывает появление НПЦ Фермы Попугая. - """ - chat_id = self.get_chat_id - user_id = self.tg_id - msg_data = await self.get_forest_msg(chat_id, "djun_farm") - if msg_data: - msg = await self.client.get_messages(chat_id, ids=msg_data["id"]) - if msg.buttons: - for row in msg.buttons: - for button in row: - if button.data.decode() == f"npc_inter {user_id} goaway home": - await msg.click(msg._buttons_flat.index(button)) - await self.debug.forest_npc( - self.strings["Debug_forest_npc_djun_ok"] - ) - - async def npc_djun(self): - """ - Обрабатывает появление НПЦ Попугая. - """ - chat_id = self.get_chat_id - user_id = self.tg_id - msg_data = await self.get_forest_msg(chat_id, "djun") - if msg_data: - msg = await self.client.get_messages(chat_id, ids=msg_data["id"]) - if msg.buttons: - for row in msg.buttons: - for button in row: - if button.data.decode() == f"npc_inter {user_id} djun no": - await msg.click(msg._buttons_flat.index(button)) - await self.debug.forest_npc( - self.strings["Debug_forest_npc_djun_ok"] - ) - - async def npc_edinorog(self): - """ - Обрабатывает появление НПЦ Единорожка. - """ - chat_id = self.get_chat_id - user_id = self.tg_id - msg_data = await self.get_forest_msg(chat_id, "edinorog") - if msg_data: - msg = await self.client.get_messages(chat_id, ids=msg_data["id"]) - if msg.buttons: - for row in msg.buttons: - for button in row: - if button.data.decode() == f"npctrade {user_id} Единорожка no": - await msg.click(msg._buttons_flat.index(button)) - await self.debug.forest_npc( - self.strings["Debug_forest_npc_edinorog_ok"] - ) - - async def npc_jabomraz(self): - """ - Обрабатывает появление НПЦ Жабомразь. - """ - chat_id = self.get_chat_id - user_id = self.tg_id - msg_data = await self.get_forest_msg(chat_id, "jabomraz") - if msg_data: - msg = await self.client.get_messages(chat_id, ids=msg_data["id"]) - if msg.buttons: - for row in msg.buttons: - for button in row: - if button.data.decode() == f"npctrade {user_id} Жабомразь no": - await msg.click(msg._buttons_flat.index(button)) - await self.debug.forest_npc( - self.strings["Debug_forest_npc_jabomraz_ok"] - ) - - async def npc_chick(self): - """ - Обрабатывает появление НПЦ Цыпа. - """ - chat_id = self.get_chat_id - user_id = self.tg_id - msg_data = await self.get_forest_msg(chat_id, "chick") - if msg_data: - msg = await self.client.get_messages(chat_id, ids=msg_data["id"]) - if msg.buttons: - for row in msg.buttons: - for button in row: - if button.data.decode() == f"npc_inter {user_id} chick catch": - await msg.click(msg._buttons_flat.index(button)) - await self.debug.forest_npc( - self.strings["Debug_forest_npc_chick_ok"] - ) diff --git a/archquise/H.Modules/Music.py b/archquise/H.Modules/Music.py deleted file mode 100644 index 28eda94..0000000 --- a/archquise/H.Modules/Music.py +++ /dev/null @@ -1,126 +0,0 @@ -# Proprietary License Agreement - -# Copyright (c) 2024-29 CodWiz - -# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions: - -# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author. - -# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author. - -# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software. - -# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software. - -# 5. By using the Software, you agree to be bound by the terms and conditions of this license. - -# For any inquiries or requests for permissions, please contact codwiz@yandex.ru. - -# --------------------------------------------------------------------------------- -# Name: Music -# Description: Searches for music using Telegram music bots -# Author: @hikka_mods -# meta developer: @hikka_mods -# scope: Music -# scope: Music 0.0.2 -# --------------------------------------------------------------------------------- - -# Thanks to @murpizz for the search code yandex - -import logging - -from telethon.errors.rpcerrorlist import ( - BotMethodInvalidError, - FloodWaitError, - MessageNotModifiedError, -) -from telethon.tl.types import Message - -from .. import loader, utils - -logger = logging.getLogger(__name__) - - -@loader.tds -class MusicMod(loader.Module): - strings = { - "name": "Music", - "no_query": "🤷‍♂ Provide a search query!", - "searching": "⌨️ Searching...", - "found": "🗣 Possible match:", - "not_found": "😫 Track not found: {}", - "usage": "Usage: .music [track name]", - "error": "⚠️ Error: {}", - "no_results": "😫 No results: {}", - "flood_wait": " Wait {}s (Telegram limits)", - "bot_error": "🤖 Bot error: {}", - "no_audio": "🎵 No audio", - "generic_result": "ℹ️ Non-media result. Check the bot's chat", - "yafind_searching": "🔎 Searching Yandex.Music...", - "yafind_not_found": "🚫 Track not found on Yandex.Music", - "yafind_error": "🚫 Error (Yandex): {}", - } - - strings_ru = { - "name": "Music", - "no_query": "🤷‍♂ Укажите запрос!", - "searching": "⌨️ Поиск...", - "found": "🗣 Возможно, это оно:", - "not_found": "😫 Трек не найден: {}", - "usage": "Использование: .music [название трека]", - "error": "⚠️ Ошибка: {}", - "no_results": "😫 Нет результатов: {}", - "flood_wait": " Подождите {}с (лимиты Telegram)", - "bot_error": "🤖 Ошибка бота: {}", - "no_audio": "🎵 Нет аудио", - "generic_result": "ℹ️ Немедийный результат. Проверьте чат с ботом", - "yafind_searching": "🔎 Поиск в Яндекс.Музыке...", - "yafind_not_found": "🚫 Трек не найден в Яндекс.Музыке", - "yafind_error": "🚫 Ошибка (Яндекс): {}", - } - - def __init__(self): - self.murglar_bot = "@murglar_bot" - - @loader.command( - ru_doc="Найти трек в Yandex.Music: `.music {название}`", - en_doc="Find a track in Yandex.Music: `.music yandex {name}`", - ) - async def music(self, message): - args = utils.get_args(message) - - if not args: - if reply := await message.get_reply_message(): - await self._yafind(message, reply.raw_text.strip()) - else: - await utils.answer(message, self.strings("usage", message)) - return - - await self._yafind(message, query=args) - - async def _yafind(self, message: Message, query: str): - if not query: - return await utils.answer(message, self.strings("no_query", message)) - - await utils.answer(message, self.strings("yafind_searching", message)) - - try: - results = await message.client.inline_query( - self.murglar_bot, f"s:ynd {query}" - ) - - if not results: - return await utils.answer( - message, self.strings("yafind_not_found", message) - ) - - await results[0].click( - entity=message.chat_id, - hide_via=True, - reply_to=message.reply_to_msg_id if message.reply_to_msg_id else None, - ) - await message.delete() - - except Exception as e: - logger.exception("Yandex search error:") - await utils.answer(message, self.strings("yafind_error", message).format(e)) diff --git a/archquise/H.Modules/README.md b/archquise/H.Modules/README.md deleted file mode 100644 index 1df8f42..0000000 --- a/archquise/H.Modules/README.md +++ /dev/null @@ -1,184 +0,0 @@ -
- - - H:Mods Logo - -
- -

- - - -

- -

- - Hikka - - - Heroku - -

- -

- - Telegram Channel - - - GitHub - - - Stars - -

- ---- - -## 🚀 Quick Start - -### 📦 Repository Installation (Recommended) - -The easiest way to install and manage all modules: - -```bash -.addrepo https://github.com/archquise/H.Modules/raw/main -``` - -After adding the repository, install any module: - -```bash -.dlm -``` - -### 🎯 Direct Installation - -Install a specific module directly: - -```bash -.dlm https://raw.githubusercontent.com/archquise/H.Modules/main/.py -``` - ---- - -## 🛠️ Installation Guide - -### Step 1: Add Repository - -```bash -.addrepo https://github.com/archquise/H.Modules/raw/main -``` - -### Step 2: Install Modules - -```bash -# Install specific module -.dlm TelegraphComic - - -# Install from direct URL -.dlm https://raw.githubusercontent.com/archquise/H.Modules/main/TelegraphComic.py -``` - -### Step 4: Configure Modules - -Most modules have configurable settings: - -```bash -# View module configuration -.config TelegraphComic - -# Update configuration -.config TelegraphComic upload_service catbox -``` - -## 🐛 Troubleshooting - -### Common Issues - -
- ❌ Module Installation Failed - -**Solution:** - -1. Check repository URL is correct -2. Ensure you have internet connection -3. Try restarting Hikka/Heroku -4. Use direct installation method - -```bash -.dlm https://raw.githubusercontent.com/archquise/H.Modules/main/.py -``` - -
- -
- ⚠️ Module Not Working - -**Solution:** -2. Check configuration: `.cfg ` -3. Check dependencies are installed -4. Update the module: `.dlm ` - -
- -
- 🔧 Configuration Issues - -**Solution:** - -1. Reset to defaults: `.cfg reset` -2. Check syntax: `.cfg ` -3. View help: `.help ` - -
- ---- - -## 📜 License - -
- -**🔒 Proprietary License** - -This project is licensed under a proprietary license. By using this software, you agree to the following terms: - -
- -### 📋 License Terms - -> **✅ What You CAN Do:** - -> - Use the software for personal and non-commercial purposes -> - Install and use modules in Hikka/Heroku -> - Modify configuration settings -> - Report issues and suggest improvements - -> **❌ What You CANNOT Do:** - -> - Modify, alter, or change the software without explicit permission -> - Redistribute the software in original or modified form -> - Use for commercial purposes without permission -> - Remove copyright notices or attribution - -### 📧 Contact - -For inquiries or permission requests: - -- **Email:** `archquise@gmail.com` -- **Telegram:** [@hikka_mods](https://t.me/hikka_mods) - ---- - -
- - Footer - - -

- Built with ❤️ for the Hikka/Heroku community -

- -

- ⬆️ Back to Top -

-
diff --git a/archquise/H.Modules/ReplaceVowels.py b/archquise/H.Modules/ReplaceVowels.py deleted file mode 100644 index f1f2d60..0000000 --- a/archquise/H.Modules/ReplaceVowels.py +++ /dev/null @@ -1,81 +0,0 @@ -# Proprietary License Agreement - -# Copyright (c) 2024-29 CodWiz - -# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions: - -# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author. - -# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author. - -# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software. - -# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software. - -# 5. By using the Software, you agree to be bound by the terms and conditions of this license. - -# For any inquiries or requests for permissions, please contact codwiz@yandex.ru. - -# --------------------------------------------------------------------------------- -# Name: VowelReplacer -# Description: Replaces vowel letters with ё -# Author: @hikka_mods -# --------------------------------------------------------------------------------- -# meta developer: @hikka_mods -# scope: VowelReplacer -# scope: VowelReplacer 0.0.1 -# --------------------------------------------------------------------------------- - -import logging - -from telethon.tl.types import Message - -from .. import loader, utils - -logger = logging.getLogger(__name__) - -@loader.tds -class VowelReplacer(loader.Module): - """Replaces vowel letters with ё""" - - strings = { - "name": "Vowel Replacer", - "on": "✅ Vowel substitution for ё has been successfully enabled.", - "off": "🚫 Vowel substitution for ё is disabled.", - } - - strings_ru = { - "on": "✅ Замена гласных на ё успешно включена.", - "off": "🚫 Замена гласных на ё отключена.", - } - - async def client_ready(self, client, db): - self.db = db - self._client = client - self.enabled = self.db.get("vowel_replacer", "enabled", False) - - @loader.command( - ru_doc="Включить или отключить замену гласных на ё.", - en_doc="Enable or disable vowel substitution for ё.", - ) - async def vowelreplace(self, message): - self.enabled = not self.enabled - self.db.set("vowel_replacer", "enabled", self.enabled) - - if self.enabled: - response = self.strings("on") - else: - response = self.strings("off") - - await utils.answer(message, response) - - async def watcher(self, message: Message): - """Автоматическая замена гласных на ё при получении собственного сообщения.""" - if self.enabled and message.out: - vowels = "аеёиоуыэюяАЕЁИОУЫЭЮЯ" - message_text = message.text - replaced_text = "".join( - "ё" if char in vowels else char for char in message_text - ) - - await message.edit(replaced_text) diff --git a/archquise/H.Modules/SMArchiver.py b/archquise/H.Modules/SMArchiver.py deleted file mode 100644 index fec3111..0000000 --- a/archquise/H.Modules/SMArchiver.py +++ /dev/null @@ -1,153 +0,0 @@ -# Proprietary License Agreement - -# Copyright (c) 2024-29 CodWiz - -# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions: - -# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author. - -# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author. - -# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software. - -# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software. - -# 5. By using the Software, you agree to be bound by the terms and conditions of this license. - -# For any inquiries or requests for permissions, please contact codwiz@yandex.ru. - -# --------------------------------------------------------------------------------- -# Name: SMArchiver -# Description: unloads all messages from Favorites -# Author: @hikka_mods -# --------------------------------------------------------------------------------- -# meta developer: @hikka_mods -# scope: SMArchiver -# scope: SMArchiver 0.0.1 -# requires: zipfile -# --------------------------------------------------------------------------------- - -import logging -import os -import zipfile -from datetime import datetime - -from .. import loader, utils - -logger = logging.getLogger(__name__) - - -@loader.tds -class SMArchiver(loader.Module): - """unloads all messages from Favorites""" - - strings = { - "name": "SMArchiver", - "archive_created": "🎉 Archive with messages has been successfully created: {filename}", - "no_messages": "⚠️ There are no messages in Saved Messages.", - "error": "❌ An error occurred: {error}", - "processing": "🛠️ Processing messages... Please wait.\n\nP.S: Be careful, if you have a lot of messages, you may get flooding, and if you have a lot of heavy files, the download will be slower than usual.", - } - - strings_ru = { - "archive_created": "🎉 Архив с сообщениями успешно создан: {filename}", - "no_messages": "⚠️ В Сохраненных сообщениях нет сообщений.", - "error": "❌ Произошла ошибка: {error}", - "processing": "🛠️ Обработка сообщений... Пожалуйста, подождите.\n\nP.S: Будьте осторожны, если у вас много сообщений, вы можете получить флуд, а если у вас много тяжелых файлов, загрузка будет медленнее обычного.", - } - - @loader.command( - ru_doc="выгружает все сообщения из Избранного / Saved Messages и собирает их в одном архиве.", - en_doc="downloads all messages from Favorites / Saved Messages and collects them in one archive.", - ) - async def smdump(self, message): - await utils.answer(message, self.strings["processing"]) - saved_messages = await message.client.get_messages("me", limit=None) - - if not saved_messages: - await utils.answer(message, self.strings["no_messages"]) - return - - archive_path = await self.create_archive(saved_messages) - - try: - await message.client.send_file( - message.chat_id, - archive_path, - caption=self.strings["archive_created"].format( - filename=os.path.basename(archive_path) - ), - ) - except Exception as e: - await utils.answer(message, self.strings["error"].format(error=str(e))) - finally: - self.cleanup(archive_path) - - async def create_archive(self, saved_messages): - current_month = datetime.now().strftime("%B %Y") - archive_path = "saved_messages.zip" - - with zipfile.ZipFile(archive_path, "w") as archive: - self.initialize_archive_structure(archive, current_month) - for msg in saved_messages: - await self.add_message_to_archive(msg, archive, current_month) - - return archive_path - - def initialize_archive_structure(self, archive, current_month): - month_folder = f"{current_month}/" - archive.writestr(month_folder, "") - message_folders = { - "Text Messages": f"{month_folder}Text Messages/", - "Voice Messages": f"{month_folder}Voice Messages/", - "Video Messages": f"{month_folder}Video Messages/", - "Videos": f"{month_folder}Videos/", - "Audios": f"{month_folder}Audios/", - "GIFs": f"{month_folder}GIFs/", - "Files": f"{month_folder}Files/", - } - - for folder in message_folders.values(): - archive.writestr(folder, "") - - async def add_message_to_archive(self, msg, archive, current_month): - """Обрабатывает отдельное сообщение и добавляет его в архив.""" - if msg.message: - await self.add_text_message_to_archive(msg, archive, current_month) - - if msg.media: - await self.add_media_to_archive(msg, archive, current_month) - - async def add_text_message_to_archive(self, msg, archive, current_month): - timestamp = datetime.fromtimestamp(msg.date.timestamp()).strftime( - "%Y%m%d_%H%M%S" - ) - safe_name = f"message_{timestamp}.txt" - archive.writestr( - os.path.join(f"{current_month}/Text Messages/", safe_name), msg.message - ) - - async def add_media_to_archive(self, msg, archive, current_month): - media_file = await msg.client.download_media(msg.media) - if media_file: - mime_type = ( - msg.media.document.mime_type if hasattr(msg.media, "document") else None - ) - folder = self.get_media_folder(mime_type, current_month) - archive.write( - media_file, os.path.join(folder, os.path.basename(media_file)) - ) - - def get_media_folder(self, mime_type, current_month): - if mime_type: - if mime_type.startswith("audio/"): - return f"{current_month}/Audios/" - elif mime_type.startswith("video/"): - return f"{current_month}/Videos/" - elif mime_type.startswith("image/gif"): - return f"{current_month}/GIFs/" - return f"{current_month}/Files/" - - def cleanup(self, archive_path): - if os.path.exists(archive_path): - os.remove(archive_path) diff --git a/archquise/H.Modules/TaskManager.py b/archquise/H.Modules/TaskManager.py deleted file mode 100644 index 47f39fc..0000000 --- a/archquise/H.Modules/TaskManager.py +++ /dev/null @@ -1,386 +0,0 @@ -# Proprietary License Agreement - -# Copyright (c) 2024-29 CodWiz - -# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions: - -# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author. - -# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author. - -# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software. - -# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software. - -# 5. By using the Software, you agree to be bound by the terms and conditions of this license. - -# For any inquiries or requests for permissions, please contact codwiz@yandex.ru. - -# --------------------------------------------------------------------------------- -# Name: TaskManager -# Description: Manages tasks with Telegram commands and inline keyboards. -# Author: @hikka_mods -# --------------------------------------------------------------------------------- -# meta developer: @hikka_mods -# scope: TaskManager -# scope: TaskManager 0.0.1 -# --------------------------------------------------------------------------------- - -import asyncio -import datetime -import json -import logging -from dataclasses import dataclass, field -from pathlib import Path -from typing import Dict, List, Optional - -from .. import loader, utils - -logger = logging.getLogger(__name__) - - -@dataclass -class Task: - """Represents a task.""" - - description: str - due_date: Optional[datetime.datetime] = None - completed: bool = False - created_at: datetime.datetime = field(default_factory=datetime.datetime.now) - id: str = field(default_factory=lambda: f"{datetime.datetime.now().timestamp()}") - - def to_dict(self) -> dict: - """Convert task to dictionary for JSON serialization.""" - return { - "id": self.id, - "description": self.description, - "due_date": self.due_date.isoformat() if self.due_date else None, - "completed": self.completed, - "created_at": self.created_at.isoformat(), - } - - @classmethod - def from_dict(cls, data: dict) -> "Task": - """Create task from dictionary.""" - return cls( - id=data.get("id", f"{datetime.datetime.now().timestamp()}"), - description=data["description"], - due_date=datetime.datetime.fromisoformat(data["due_date"]) - if data.get("due_date") - else None, - completed=data["completed"], - created_at=datetime.datetime.fromisoformat(data["created_at"]), - ) - - -class TaskManager: - """Manages tasks, storing them in a JSON file.""" - - def __init__(self, data_file: str): - self.data_file = Path(data_file) - self.tasks: Dict[int, List[Task]] = {} - self._lock = asyncio.Lock() - self.load_data() - - def load_data(self): - """Loads task data from the JSON file.""" - if not self.data_file.exists(): - self.tasks = {} - logger.info("Task data file not found. Starting empty.") - return - - try: - with open(self.data_file, "r", encoding="utf-8") as f: - data = json.load(f) - self.tasks = { - int(user_id): [Task.from_dict(task) for task in task_list] - for user_id, task_list in data.items() - } - except (json.JSONDecodeError, KeyError, ValueError) as e: - logger.warning(f"Failed to load task data: {e}. Starting empty.") - self.tasks = {} - except Exception as e: - logger.error(f"Unexpected error loading task data: {e}") - self.tasks = {} - - async def save_data(self): - """Saves task data to the JSON file.""" - async with self._lock: - try: - self.data_file.parent.mkdir(parents=True, exist_ok=True) - data = { - str(user_id): [task.to_dict() for task in task_list] - for user_id, task_list in self.tasks.items() - } - with open(self.data_file, "w", encoding="utf-8") as f: - json.dump(data, f, indent=2, ensure_ascii=False) - except IOError as e: - logger.error(f"Failed to save task data: {e}") - except Exception as e: - logger.error(f"Unexpected error saving task data: {e}") - - async def add_task(self, user_id: int, task: Task): - self.tasks.setdefault(user_id, []).append(task) - await self.save_data() - - async def remove_task(self, user_id: int, index: int) -> bool: - if user_id in self.tasks and 0 <= index < len(self.tasks[user_id]): - del self.tasks[user_id][index] - await self.save_data() - return True - logger.warning(f"Invalid index for removal: {index}, user: {user_id}") - return False - - async def complete_task(self, user_id: int, index: int) -> bool: - if user_id in self.tasks and 0 <= index < len(self.tasks[user_id]): - self.tasks[user_id][index].completed = True - await self.save_data() - return True - logger.warning(f"Invalid index for completion: {index}, user: {user_id}") - return False - - def get_tasks(self, user_id: int, include_completed: bool = True) -> List[Task]: - tasks = self.tasks.get(user_id, []) - if not include_completed: - tasks = [task for task in tasks if not task.completed] - return tasks - - async def clear_tasks(self, user_id: int) -> bool: - if user_id in self.tasks: - self.tasks[user_id] = [] - await self.save_data() - return True - logger.info(f"No tasks to clear for user: {user_id}") - return False - - def get_task(self, user_id: int, index: int) -> Optional[Task]: - if user_id in self.tasks and 0 <= index < len(self.tasks[user_id]): - return self.tasks[user_id][index] - return None - - def get_overdue_tasks(self, user_id: int) -> List[Task]: - """Get overdue tasks for a user.""" - now = datetime.datetime.now() - return [ - task - for task in self.get_tasks(user_id) - if task.due_date and task.due_date < now and not task.completed - ] - - async def update_task(self, user_id: int, index: int, **kwargs) -> bool: - """Update task properties.""" - task = self.get_task(user_id, index) - if not task: - return False - - for key, value in kwargs.items(): - if hasattr(task, key): - setattr(task, key, value) - - await self.save_data() - return True - - -@loader.tds -class TaskManagerModule(loader.Module): - """Manages tasks with Telegram commands and inline keyboards.""" - - strings = { - "name": "TaskManager", - "task_added": " Task added.", - "task_removed": " Task removed.", - "task_completed": " Task completed.", - "task_not_found": " Task not found.", - "no_tasks": "📄 No active tasks.", - "task_list": "📄 Your tasks:\n{}", - "invalid_index": " Invalid index. Provide valid integer.", - "description_required": "❗️ Provide task description.", - "clear_confirmation": "⚠️ Delete all tasks?", - "tasks_cleared": "✅ All tasks deleted.", - "due_date_format": " Invalid date. Use YYYY-MM-DD HH:MM.", - "task_info": " Task: {description}\n📅 Due: {due_date}\n✔️ Completed: {completed}\n🎛 Created: {created_at}", - "confirm_clear": "Confirm", - "cancel_clear": "Cancel", - "clear_cancelled": "❌ Deletion cancelled.", - "index_required": "⚠️ Provide task index.", - "clear_confirmation_text": "Are you sure you want to clear all tasks?", - "confirm": "Confirm", - "cancel": "Cancel", - } - - strings_ru = { - "task_added": " Задача добавлена.", - "task_removed": " Задача удалена.", - "task_completed": " Задача выполнена.", - "task_not_found": " Задача не найдена.", - "no_tasks": "📄 Нет активных задач.", - "task_list": "📄 Ваши задачи:\n{}", - "invalid_index": " Неверный индекс. Укажите целое число.", - "description_required": "❗️ Укажите описание задачи.", - "clear_confirmation": "⚠️ Удалить все задачи?", - "tasks_cleared": "✅ Все задачи удалены.", - "due_date_format": " Неверный формат даты. Используйте ГГГГ-ММ-ДД ЧЧ:ММ.", - "task_info": " Задача: {description}\n📅 Срок: {due_date}\n✔️ Выполнена: {completed}\n🎛 Создана: {created_at}", - "confirm_clear": "Подтвердить", - "cancel_clear": "Отменить", - "clear_cancelled": "❌ Удаление отменено.", - "index_required": "⚠️ Укажите индекс задачи.", - "clear_confirmation_text": "Вы уверены, что хотите удалить все задачи?", - "confirm": "Подтвердить", - "cancel": "Отменить", - } - - def __init__(self): - self.task_manager: Optional[TaskManager] = None - - async def client_ready(self, client, db): - data_dir = Path.cwd() / "data" - data_dir.mkdir(exist_ok=True) - self.task_manager = TaskManager(str(data_dir / "tasks.json")) - - @loader.command( - ru_doc="Добавить задачу:\n.taskadd <описание> | <дата (необязательно)>", - en_doc="Add task:\n.taskadd | ", - ) - async def taskadd(self, message): - args = utils.get_args_raw(message) - if not args: - await utils.answer(message, self.strings("description_required")) - return - - try: - description, due_date_str = ( - args.split("|", 1) if "|" in args else (args, None) - ) - description = description.strip() - due_date_str = due_date_str.strip() if due_date_str else None - due_date = ( - datetime.datetime.fromisoformat(due_date_str) if due_date_str else None - ) - except ValueError: - await utils.answer(message, self.strings("due_date_format")) - return - except Exception as e: - logger.error(f"Error adding task: {e}") - await utils.answer( - message, f" Error: {e}" - ) - return - - task = Task(description=description, due_date=due_date) - await self.task_manager.add_task(message.sender_id, task) - await utils.answer(message, self.strings("task_added")) - - @loader.command(ru_doc="[index] - удалить задачу", en_doc="[index] - remove task") - async def taskremove(self, message): - args = utils.get_args_raw(message) - if not args: - await utils.answer(message, self.strings("index_required")) - return - - try: - index = int(args) - 1 - except ValueError: - await utils.answer(message, self.strings("invalid_index")) - return - - if await self.task_manager.remove_task(message.sender_id, index): - await utils.answer(message, self.strings("task_removed")) - else: - await utils.answer(message, self.strings("task_not_found")) - - @loader.command( - ru_doc="[index] - Завершите задачу", en_doc="[index] - Complete task" - ) - async def taskcomplete(self, message): - args = utils.get_args_raw(message) - if not args: - await utils.answer(message, self.strings("index_required")) - return - - try: - index = int(args) - 1 - except ValueError: - await utils.answer(message, self.strings("invalid_index")) - return - - if await self.task_manager.complete_task(message.sender_id, index): - await utils.answer(message, self.strings("task_completed")) - else: - await utils.answer(message, self.strings("task_not_found")) - - @loader.command(ru_doc="Список задач", en_doc="List tasks") - async def tasklist(self, message): - tasks = self.task_manager.get_tasks(message.sender_id) - - if not tasks: - await utils.answer(message, self.strings("no_tasks")) - return - - task_list_str = "\n".join( - [ - f" {i + 1}. {'' if task.completed else ''} {task.description} (Due: {task.due_date.strftime('%Y-%m-%d %H:%M') if task.due_date else 'None'})" - for i, task in enumerate(tasks) - ] - ) - await utils.answer(message, self.strings("task_list").format(task_list_str)) - - @loader.command( - ru_doc="[index] - Посмотреть информацию о задаче", - en_doc="[index] - Show task info", - ) - async def taskinfo(self, message): - args = utils.get_args_raw(message) - if not args: - await utils.answer(message, self.strings("index_required")) - return - try: - index = int(args) - 1 - except ValueError: - await utils.answer(message, self.strings("invalid_index")) - return - - task = self.task_manager.get_task(message.sender_id, index) - if not task: - await utils.answer(message, self.strings("task_not_found")) - return - - due_date_str = ( - task.due_date.strftime("%Y-%m-%d %H:%M") if task.due_date else "None" - ) - created_at_str = task.created_at.strftime("%Y-%m-%d %H:%M") - - await utils.answer( - message, - self.strings("task_info").format( - description=task.description, - due_date=due_date_str, - completed="Yes" if task.completed else "No", - created_at=created_at_str, - ), - ) - - @loader.command(ru_doc="Удалить все задачи", en_doc="Clear all tasks") - async def taskclear(self, message): - await self.inline.form( - text=self.strings("clear_confirmation_text"), - message=message, - reply_markup=[ - [ - {"text": self.strings("confirm"), "callback": self.clear_confirm}, - {"text": self.strings("cancel"), "callback": self.clear_cancel}, - ] - ], - silent=True, - ) - - async def clear_confirm(self, call): - """Callback for confirming task clearing.""" - if await self.task_manager.clear_tasks(call.from_user.id): - await call.edit(self.strings("tasks_cleared")) - else: - await call.edit(self.strings("no_tasks")) - - async def clear_cancel(self, call): - """Callback for canceling task clearing.""" - await call.edit(self.strings("clear_cancelled")) diff --git a/archquise/H.Modules/TelegramStatusCodes.py b/archquise/H.Modules/TelegramStatusCodes.py deleted file mode 100644 index 1b08f6e..0000000 --- a/archquise/H.Modules/TelegramStatusCodes.py +++ /dev/null @@ -1,200 +0,0 @@ -# Proprietary License Agreement - -# Copyright (c) 2024-29 CodWiz - -# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions: - -# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author. - -# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author. - -# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software. - -# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software. - -# 5. By using the Software, you agree to be bound by the terms and conditions of this license. - -# For any inquiries or requests for permissions, please contact codwiz@yandex.ru. - -# --------------------------------------------------------------------------------- -# Name: TelegramStatusCodes -# Description: Dictionary of telegram status codes -# Author: @hikka_mods -# --------------------------------------------------------------------------------- -# meta developer: @hikka_mods -# scope: Api TelegramStatusCodes -# scope: Api TelegramStatusCodes 0.0.1 -# --------------------------------------------------------------------------------- - -import logging - -from .. import loader, utils - -logger = logging.getLogger(__name__) - -responses = { - 300: ( - "⛔ SEE_OTHER", - "The request must be repeated, but directed to a different data center.", - ), - 400: ( - "⛔ BAD_REQUEST", - "The query contains errors. In the event that a request was created using a form and contains user generated data, the user should be notified that the data must be corrected before the query is repeated.", - ), - 401: ( - "⛔ UNAUTHORIZED", - "There was an unauthorized attempt to use functionality available only to authorized users.", - ), - 403: ( - "⛔ FORBIDDEN", - "Privacy violation. For example, an attempt to write a message to someone who has blacklisted the current user.", - ), - 404: ( - "⛔ NOT_FOUND", - "An attempt to invoke a non-existent object, such as a method", - ), - 406: ( - "⛔ NOT_ACCEPTABLE", - """ -Similar to 400 BAD_REQUESTS, but the app must display the error to the user a bit differently. -Do not display any visible error to the user when receiving the rpc_error constructor: instead, wait for an updateServiceNotification update, and handle it as usual. -Basically, an pop-up update will be emitted independently (ie NOT as an Updates constructor inside rpc_result but as a normal update) immediately after emission of a 406 rpc_error: the update will contain the actual localized error message to show to the user with a UI popup. - -An exception to this is the AUTH_KEY_DUPLICATED error, which is only emitted if any of the non-media DC detects that an authorized session is sending requests in parallel from two separate TCP connections, from the same or different IP addresses. -Note that parallel connections are still allowed and actually recommended for media DCs. -Also note that by session we mean a logged-in session identified by an authorization constructor, fetchable using account.getAuthorizations, not an MTProto session. - -If the client receives an AUTH_KEY_DUPLICATED error, the session was already invalidated by the server and the user must generate a new auth key and login again.""", - ), - 420: ( - "⛔ FLOOD", - "The maximum allowed number of attempts to invoke the given method with the given input parameters has been exceeded. For example, in an attempt to request a large number of text messages (SMS) for the same phone number.", - ), - 500: ( - "⛔ INTERNAL", - """An internal server error occurred while a request was being processed; for example, there was a disruption while accessing a database or file storage. - -If a client receives a 500 error, or you believe this error should not have occurred, please collect as much information as possible about the query and error and send it to the developers""", - ), -} - -responses_ru = { - 300: ( - "⛔ SEE_OTHER", - "Запрос должен быть повторен, но направлен в другой дата-центр.", - ), - 400: ( - "⛔ BAD_REQUEST", - "Запрос содержит ошибки. В случае, если запрос был создан с помощью формы и содержит данные, введенные пользователем, пользователю следует сообщить, что данные должны быть исправлены перед повторным выполнением запроса.", - ), - 401: ( - "⛔ UNAUTHORIZED", - "Была совершена неавторизованная попытка использовать функциональность, доступную только авторизованным пользователям.", - ), - 403: ( - "⛔ FORBIDDEN", - "Нарушение конфиденциальности. Например, попытка написать сообщение пользователю, который добавил текущего пользователя в черный список.", - ), - 404: ( - "⛔ NOT_FOUND", - "Попытка обращения к несуществующему объекту, например, к методу.", - ), - 406: ( - "⛔ NOT_ACCEPTABLE", - """ -Аналогично 400 BAD_REQUESTS, но приложение должно отображать ошибку пользователю немного иначе. -Не показывайте пользователю видимую ошибку при получении конструктора rpc_error: вместо этого дождитесь обновления updateServiceNotification и обработайте его как обычно. -По сути, обновление-всплывающее окно updateServiceNotification будет отправлено независимо (т.е. НЕ как конструктор Updates внутри rpc_result, а как обычное обновление) сразу после выдачи 406 rpc_error: обновление будет содержать актуальное локализованное сообщение об ошибке для показа пользователю в интерфейсе. - -Исключением является ошибка AUTH_KEY_DUPLICATED, которая возникает только в том случае, если любой из не-медиа DC обнаруживает, что авторизованная сессия отправляет запросы параллельно из двух отдельных TCP-соединений с одного или разных IP-адресов. -Обратите внимание, что параллельные соединения по-прежнему разрешены и фактически рекомендуются для медиа-DC. -Также обратите внимание, что под сессией понимается авторизованная сессия, идентифицируемая конструктором authorization, которую можно получить с помощью account.getAuthorizations, а не сессия MTProto. - -Если клиент получает ошибку AUTH_KEY_DUPLICATED, сессия уже была аннулирована сервером, и пользователю необходимо сгенерировать новый ключ авторизации и войти снова.""", - ), - 420: ( - "⛔ FLOOD", - "Превышено максимально допустимое количество попыток вызова данного метода с указанными входными параметрами. Например, при попытке запросить большое количество текстовых сообщений (SMS) для одного и того же номера телефона.", - ), - 500: ( - "⛔ INTERNAL", - """Произошла внутренняя ошибка сервера во время обработки запроса; например, произошел сбой при доступе к базе данных или файловому хранилищу. - -Если клиент получает ошибку 500 или вы считаете, что эта ошибка не должна была возникнуть, пожалуйста, соберите как можно больше информации о запросе и ошибке и отправьте ее разработчикам.""", - ), -} - - -@loader.tds -class TelegramStatusCodes(loader.Module): - """Dictionary of telegram status codes""" - - strings = { - "name": "TelegramStatusCodes", - "args_incorrect": "Incorrect args", - "not_found": "Code not found", - "syntax_error": "Args are mandatory", - "scode": "{} {}\n⚜️ Code Description: {}", - } - - strings_ru = { - "args_incorrect": "Неверные аргументы", - "not_found": "Код не найден", - "syntax_error": "Аргументы обязательны", - "_cmd_doc_httpsc": "<код> - Получить информацию о статус-коде", - "_cmd_doc_httpscs": "Показать все доступные коды", - "_cls_doc": "Словарь статус-кодов Telegram", - "scode": "{} {}\n⚜️ Описание статус-кода: {}", - } - - async def client_ready(self, client, db): - self.ub_lang = self._db.get("hikka.translations", "lang") - if not self.ub_lang: - self.ub_lang = self._db.get("heroku.translations", "lang") - - @loader.unrestricted - @loader.command( - ru_doc="<код состояния> - Получение информации о статус-коде", - en_doc=" - Get status code info", - ) - async def tgccmd(self, message): - - args = utils.get_args(message) - if not args: - await utils.answer(message, self.strings("syntax_error", message)) - return - - try: - if int(args[0]) not in responses: - await utils.answer(message, self.strings("not_found", message)) - except ValueError: - await utils.answer(message, self.strings("args_incorrect", message)) - - if self.ub_lang != "ru": - await utils.answer( - message, - self.strings("scode", message).format( - responses[int(args[0])][0], args[0], responses[int(args[0])][1] - ), - ) - else: - await utils.answer( - message, - self.strings("scode", message).format( - responses[int(args[0])][0], args[0], responses_ru[int(args[0])][1] - ), - ) - - - @loader.unrestricted - @loader.command( - ru_doc="Получите все статус-коды Telegram", - en_doc="Get all Telegram status codes", - ) - async def tgcscmd(self, message): - await utils.answer( - message, - "\n".join( - [f"{str(sc)}: {text}" for sc, (text, _) in responses.items()] - ), - ) diff --git a/archquise/H.Modules/TelegraphComic.py b/archquise/H.Modules/TelegraphComic.py deleted file mode 100644 index 3f470f9..0000000 --- a/archquise/H.Modules/TelegraphComic.py +++ /dev/null @@ -1,536 +0,0 @@ -# Proprietary License Agreement - -# Copyright (c) 2026-2029 CodWiz - -# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions: - -# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author. - -# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author. - -# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software. - -# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software. - -# 5. By using the Software, you agree to be bound by the terms and conditions of this license. - -# For any inquiries or requests for permissions, please contact codwiz@yandex.ru. - -# --------------------------------------------------------------------------------- -# Name: TelegraphComics -# Description: Create comics on Telegraph from ZIP/RAR archives -# Author: @hikka_mods -# --------------------------------------------------------------------------------- -# meta developer: @hikka_mods -# requires: aiohttp, zipfile, telegraph -# --------------------------------------------------------------------------------- - -import asyncio -import logging -import os -import tempfile -from typing import List, Optional -import zipfile - -import aiohttp -from telethon.types import MessageMediaDocument, Message - -from telegraph import Telegraph - -from .. import loader, utils - -logger = logging.getLogger(__name__) - - -@loader.tds -class TelegraphComicMod(loader.Module): - """Create comics on Telegraph from ZIP/CBZ/RAR archives""" - - strings = { - "name": "TelegraphComic", - "invalid_args": " Invalid arguments. Usage: .telegraphcomics | <cover_url> (optional)", - "no_reply": "<emoji document_id=5388785832956016892>❌</emoji> Reply to a message with ZIP/CBZ/RAR file", - "unsupported_format": "<emoji document_id=5388785832956016892>❌</emoji> Unsupported file format. Only ZIP/CBZ/RAR files are supported", - "processing": "<emoji document_id=5256094480498436162>⏳</emoji> Processing archive...", - "uploading": "<emoji document_id=5854762571659218443>⏳</emoji> Uploading images...", - "creating_article": "<emoji document_id=5854762571659218443>⏳</emoji> Creating Telegraph article...", - "archive_extracted": "<emoji document_id=5854762571659218443>📦</emoji> Archive successfully extracted: <emoji document_id=5208422125924275090>✅</emoji>", - "upload_files": "<emoji document_id=5854762571659218443>📦</emoji> Upload image files:", - "creating_telegraph": "<emoji document_id=5854762571659218443>📝</emoji> Creating Telegraph article:", - "success": '<emoji document_id=5208422125924275090>✅</emoji> <b>Telegraph article created!</b>\n\n<emoji document_id=5256094480498436162>📦</emoji> Archive successfully extracted: <emoji document_id=5208422125924275090>✅</emoji>\n\n<emoji document_id=5256094480498436162>📦</emoji> Upload image files:\n{upload_status}\n\n<emoji document_id=5256230583717079814>📝</emoji> Creating Telegraph article:\n{article_status}\n\n<emoji document_id=5271604874419647061>🔗</emoji> <a href="{url}">{url}</a>', - "error": "<emoji document_id=5854929766146118183>❌</emoji> <b>Error:</b> {}", - "_cls_doc": "Create comics on Telegraph from ZIP/CBZ/RAR archives", - } - - strings_ru = { - "_cls_doc": "Создание комиксов на Telegraph из ZIP/CBZ/RAR архивов", - "invalid_args": "<emoji document_id=5388785832956016892>❌</emoji> Неверные аргументы. Использование: .telegraphcomics <название> | <ссылка_на_обложку>(необязательно)", - "no_reply": "<emoji document_id=5388785832956016892>❌</emoji> Ответьте на сообщение с ZIP/CBZ/RAR файлом", - "unsupported_format": "<emoji document_id=5388785832956016892>❌</emoji> Неподдерживаемый формат. Только ZIP/CBZ/RAR файлы", - "processing": "<emoji document_id=5256094480498436162>⏳</emoji> Обработка архива...", - "uploading": "<emoji document_id=5256094480498436162>⏳</emoji> Загрузка изображений...", - "creating_article": "<emoji document_id=5854762571659218443>⏳</emoji> Создание Telegraph статьи...", - "archive_extracted": "<emoji document_id=5256094480498436162>📦</emoji> Архив успешно распакован: <emoji document_id=5208422125924275090>✅</emoji>", - "upload_files": "<emoji document_id=5256094480498436162>📦</emoji> Загрузка файлов изображений:", - "creating_telegraph": "<emoji document_id=5854762571659218443>📝</emoji> Создание Telegraph статьи:", - "success": '<emoji document_id=5208422125924275090>✅</emoji> <b>Telegraph статья создана!</b>\n\n<emoji document_id=5256094480498436162>📦</emoji> Архив успешно распакован: <emoji document_id=5208422125924275090>✅</emoji>\n\n<emoji document_id=5256094480498436162>📦</emoji> Загрузка файлов изображений:\n{upload_status}\n\n<emoji document_id=5256230583717079814>📝</emoji> Создание Telegraph статьи:\n{article_status}\n\n<emoji document_id=5271604874419647061>🔗</emoji> <a href="{url}">{url}</a>', - "error": "<emoji document_id=5388785832956016892>❌</emoji> <b>Ошибка:</b> {}", - "available_services": "Доступные сервисы: catbox, bashupload, kappa, x0, tmpfiles, pomf", - "current_service": "Текущий сервис: {}", - "invalid_service": "❌ Неизвестный сервис: {}\n\n{}", - } - - def __init__(self): - self.config = loader.ModuleConfig( - loader.ConfigValue( - "upload_service", - "catbox", - "Upload service to use", - validator=loader.validators.Choice( - ["catbox", "bashupload", "kappa", "x0", "tmpfiles", "pomf"] - ), - ), - loader.ConfigValue( - "short_name", - "HikkaMods", - "short name for the article", - validator=loader.validators.String(), - ), - loader.ConfigValue( - "author_name", - "HikkaMods", - "nickname of the author of the article", - validator=loader.validators.String(), - ), - loader.ConfigValue( - "author_url", - "https://t.me/hikka_mods", - "link to author", - validator=loader.validators.String(), - ), - ) - - async def client_ready(self, client, db): - self.client = client - self.db = db - self.telegraph = Telegraph() - self.telegraph.create_account( - short_name=self.config["short_name"], - author_name=self.config["author_name"], - author_url=self.config["author_url"], - ) - - async def _upload_file_to_service( - self, - session: aiohttp.ClientSession, - url: str, - file_path: str, - field_name: str, - **extra_fields, - ) -> Optional[str]: - """Generic file upload method""" - try: - with open(file_path, "rb") as f: - data = aiohttp.FormData() - data.add_field(field_name, f, filename=os.path.basename(file_path)) - - for key, value in extra_fields.items(): - data.add_field(key, value) - - async with session.post(url, data=data) as response: - if response.status == 200: - result = await response.text() - return result.strip() if result else None - else: - logger.info( - f"Upload failed with status {response.status}: {await response.text()}" - ) - except Exception as e: - logger.info(f"Error uploading to {url}: {e}") - return None - - async def upload_to_catbox(self, file_path: str) -> Optional[str]: - """Upload file to catbox.moe""" - async with aiohttp.ClientSession() as session: - result = await self._upload_file_to_service( - session, - "https://catbox.moe/user/api.php", - file_path, - "fileToUpload", - reqtype="fileupload", - ) - return ( - result - if result and result.startswith("https://files.catbox.moe/") - else None - ) - - async def upload_to_bashupload(self, file_path: str) -> Optional[str]: - """Upload file to bashupload.com""" - async with aiohttp.ClientSession() as session: - try: - with open(file_path, "rb") as f: - data = aiohttp.FormData() - data.add_field("file", f, filename=os.path.basename(file_path)) - - async with session.post( - "https://bashupload.com", data=data - ) as response: - if response.status == 200: - result = await response.text() - - lines = result.strip().split("\n") - for line in lines: - if line.startswith("https://"): - return line - - if "wget" in result: - urls = [ - line - for line in result.split("\n") - if "wget" in line - ] - if urls: - parts = urls[0].split() - for part in parts: - if part.startswith("https://"): - return part - except Exception as e: - logger.info(f"Error uploading to bashupload: {e}") - return None - - async def upload_to_kappa(self, file_path: str) -> Optional[str]: - """Upload file to kappa.lol""" - async with aiohttp.ClientSession() as session: - try: - with open(file_path, "rb") as f: - data = aiohttp.FormData() - data.add_field("file", f, filename=os.path.basename(file_path)) - - async with session.post( - "https://kappa.lol/api/upload", data=data - ) as response: - if response.status == 200: - result = await response.json() - if result and "id" in result: - return f"https://kappa.lol/{result['id']}" - except Exception as e: - logger.info(f"Error uploading to kappa: {e}") - return None - - async def upload_to_x0(self, file_path: str) -> Optional[str]: - """Upload file to x0.at""" - async with aiohttp.ClientSession() as session: - try: - with open(file_path, "rb") as f: - data = aiohttp.FormData() - data.add_field("file", f, filename=os.path.basename(file_path)) - - async with session.post("https://x0.at", data=data) as response: - if response.status == 200: - result = await response.text() - return ( - result.strip() - if result and "https://" in result - else None - ) - except Exception as e: - logger.info(f"Error uploading to x0: {e}") - return None - - async def upload_to_tmpfiles(self, file_path: str) -> Optional[str]: - """Upload file to tmpfiles.org""" - async with aiohttp.ClientSession() as session: - try: - with open(file_path, "rb") as f: - data = aiohttp.FormData() - data.add_field("file", f, filename=os.path.basename(file_path)) - - async with session.post( - "https://tmpfiles.org/api/v1/upload", data=data - ) as response: - if response.status == 200: - result = await response.json() - if result and "data" in result and "url" in result["data"]: - return result["data"]["url"] - except Exception as e: - logger.info(f"Error uploading to tmpfiles: {e}") - return None - - async def upload_to_pomf(self, file_path: str) -> Optional[str]: - """Upload file to pomf.lain.la""" - async with aiohttp.ClientSession() as session: - try: - with open(file_path, "rb") as f: - data = aiohttp.FormData() - data.add_field("files[]", f, filename=os.path.basename(file_path)) - - async with session.post( - "https://pomf.lain.la/upload.php", data=data - ) as response: - if response.status == 200: - result = await response.json() - if result and "files" in result and result["files"]: - return result["files"][0].get("url") - except Exception as e: - logger.info(f"Error uploading to pomf: {e}") - return None - - async def upload_file(self, file_path: str) -> Optional[str]: - """Upload file to selected service""" - service_name = self.config["upload_service"] - - service_map = { - "catbox": self.upload_to_catbox, - "bashupload": self.upload_to_bashupload, - "kappa": self.upload_to_kappa, - "x0": self.upload_to_x0, - "tmpfiles": self.upload_to_tmpfiles, - "pomf": self.upload_to_pomf, - } - - service_func = service_map.get(service_name) - if not service_func: - return await self.upload_to_catbox(file_path) - - try: - result = await service_func(file_path) - return result - except Exception as e: - logger.error(f"Upload to {service_name} failed: {e}") - return None - - async def extract_zip_archive(self, zip_path: str, extract_dir: str) -> List[str]: - """Extract ZIP archive and return sorted list of image files""" - image_extensions = {".png", ".jpg", ".jpeg", ".gif", ".bmp", ".webp", ".avif"} - image_files = [] - - try: - with zipfile.ZipFile(zip_path, "r") as zip_ref: - zip_ref.extractall(extract_dir) - - for root, _, files in os.walk(extract_dir): - for file in files: - if os.path.splitext(file)[1].lower() in image_extensions: - image_files.append(os.path.join(root, file)) - - image_files.sort(key=lambda x: os.path.basename(x).lower()) - - except Exception as e: - logger.info(f"Error extracting ZIP archive: {e}") - - return image_files - - async def create_telegraph_article( - self, title: str, image_urls: List[str], cover_url: Optional[str] = None - ) -> Optional[str]: - """Create Telegraph article with images""" - try: - if cover_url: - content = f'<img src="{cover_url}"/><br>' - content += "<br>".join(f'<img src="{url}"/>' for url in image_urls) - else: - content = "<br>".join(f'<img src="{url}"/>' for url in image_urls) - - response = await asyncio.to_thread( - lambda: self.telegraph.create_page( - title=title, - html_content=content, - author_name=self.config["author_name"], - author_url=self.config["author_url"], - ) - ) - - return response["url"] - - except Exception as e: - logger.info(f"Error creating Telegraph article: {e}") - return None - - async def _process_cover_url(self, cover_url: str) -> Optional[str]: - """Process cover URL - handle Telegram message links and direct URLs""" - if not cover_url: - return None - - cover_url = cover_url.strip() - - if "t.me/" in cover_url and "/" in cover_url.split("t.me/")[1]: - try: - parts = cover_url.split("/") - if len(parts) >= 4: - chat_username = parts[-3] - message_id = int(parts[-1]) - - message = await self.client.get_messages( - chat_username, ids=message_id - ) - if message and message.media: - media_path = await message.download_media() - if media_path: - uploaded_url = await self.upload_file(media_path) - os.remove(media_path) - return uploaded_url - except Exception as e: - logger.info(f"Error processing Telegram cover link: {e}") - return cover_url - - return cover_url - - async def _process_comics_request(self, message, create_func) -> None: - """Common logic for processing comics requests""" - args = utils.get_args_raw(message) - reply = await message.get_reply_message() - - if not args or not reply: - await utils.answer(message, self.strings["invalid_args"]) - return - - if not isinstance(reply.media, MessageMediaDocument): - await utils.answer(message, self.strings["no_reply"]) - return - - if "|" in args: - title, cover_url = args.split("|", 1) - else: - title = args - cover_url = None - - title = title.strip() - cover_url = ( - await self._process_cover_url(cover_url.strip()) if cover_url else None - ) - - await utils.answer(message, self.strings["processing"]) - - file_path = await reply.download_media() - if not file_path: - await utils.answer( - message, self.strings["error"].format("Failed to download file") - ) - return - - try: - if not (file_path.lower().endswith((".zip", ".cbz"))): - await utils.answer(message, self.strings["unsupported_format"]) - return - - with tempfile.TemporaryDirectory() as temp_dir: - archive_path = file_path - if file_path.lower().endswith(".cbz"): - import shutil - - zip_path = file_path[:-4] + ".zip" - shutil.copy2(file_path, zip_path) - archive_path = zip_path - - image_files = await self.extract_zip_archive(archive_path, temp_dir) - - if archive_path != file_path and os.path.exists(archive_path): - os.remove(archive_path) - if not image_files: - await utils.answer( - message, - self.strings["error"].format("No images found in archive"), - ) - return - - await utils.answer(message, self.strings["archive_extracted"]) - - await utils.answer(message, self.strings["uploading"]) - - upload_tasks = [self.upload_file(img_file) for img_file in image_files] - upload_results = await asyncio.gather( - *upload_tasks, return_exceptions=True - ) - image_urls = [] - failed_uploads = 0 - upload_errors = [] - upload_status_lines = [] - - for i, (img_file, result) in enumerate( - zip(image_files, upload_results) - ): - filename = os.path.basename(img_file) - if isinstance(result, Exception): - error_str = str(result) - logger.info(f"Upload failed: {error_str}") - failed_uploads += 1 - upload_errors.append(error_str) - upload_status_lines.append( - f"{filename} - <emoji document_id=5388785832956016892>❌</emoji>" - ) - elif result and "https://" in result: - image_urls.append(result) - upload_status_lines.append( - f"{filename} - <emoji document_id=5208422125924275090>✅</emoji>" - ) - else: - failed_uploads += 1 - upload_errors.append("Invalid response from upload service") - upload_status_lines.append( - f"{filename} - <emoji document_id=5388785832956016892>❌</emoji>" - ) - - if not image_urls: - error_details = [] - error_details.append(f"Failed uploads: {failed_uploads}") - - if upload_errors: - unique_errors = list(set(upload_errors))[:3] - error_details.append("Errors: " + "; ".join(unique_errors)) - - error_msg = " | ".join(error_details) - await utils.answer( - message, - self.strings["error"].format(error_msg), - ) - return - - upload_status = ( - self.strings["upload_files"] + "\n" + "\n".join(upload_status_lines) - ) - await utils.answer(message, upload_status) - - await utils.answer(message, self.strings["creating_article"]) - - article_url = await create_func(title, image_urls, cover_url) - if article_url: - article_status_lines = [] - for i, (img_file, url) in enumerate(zip(image_files, image_urls)): - filename = os.path.basename(img_file) - article_status_lines.append( - f"{filename} - <emoji document_id=5208422125924275090>✅</emoji>" - ) - - upload_status = "\n".join(upload_status_lines) - article_status = "\n".join(article_status_lines) - - await utils.answer( - message, - self.strings["success"].format( - upload_status=upload_status, - article_status=article_status, - url=article_url, - ), - ) - - else: - await utils.answer( - message, - self.strings["error"].format("Failed to create article"), - ) - except Exception as e: - await utils.answer( - message, - self.strings["error"].format(f"Processing error: {e}"), - ) - finally: - if os.path.exists(file_path): - os.remove(file_path) - - @loader.command( - ru_doc="Создать комикс на Telegraph из ZIP/CBZ/RAR архива\nАргументы: <название> | <ссылка_на_обложку>(необязательно)\nИспользование: .telegraphcomics <title> | <cover_url>(optional)", - en_doc="Create Telegraph comic from ZIP/CBZ/RAR archive\nArguments: <title> | <cover_url>(optional)\nUsage: .telegraphcomics <title> | <cover_url>(optional)", - ) - async def telegraphcomicscmd(self, message): - await self._process_comics_request(message, self.create_telegraph_article) diff --git a/archquise/H.Modules/Text2File.py b/archquise/H.Modules/Text2File.py deleted file mode 100644 index a0638be..0000000 --- a/archquise/H.Modules/Text2File.py +++ /dev/null @@ -1,80 +0,0 @@ -# Proprietary License Agreement - -# Copyright (c) 2024-29 CodWiz - -# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions: - -# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author. - -# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author. - -# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software. - -# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software. - -# 5. By using the Software, you agree to be bound by the terms and conditions of this license. - -# For any inquiries or requests for permissions, please contact codwiz@yandex.ru. - -# --------------------------------------------------------------------------------- -# Name: Text2File -# Description: Module for convertation your text to file -# Author: @hikka_mods -# --------------------------------------------------------------------------------- -# meta developer: @hikka_mods -# scope: Text2File -# scope: Text2File 0.0.1 -# --------------------------------------------------------------------------------- - -import io -import logging - -from .. import loader, utils - -logger = logging.getLogger(__name__) - - -@loader.tds -class Text2File(loader.Module): - """Module for convertation your text to file""" - - strings = { - "name": "Text2File", - "no_args": "Don't have any args! Use .ttf text/code", - "cfg_name": "You can change the extension and file name", - } - - strings_ru = { - "no_args": "Недостаточно аргументов! Используйте: .ttf текст/код", - "cfg_name": "Вы можете выбрать расширение и название для файла", - } - - def __init__(self): - self.config = loader.ModuleConfig( - loader.ConfigValue( - "name", - "file.txt", - lambda: self.strings("cfg_name"), - ), - ) - - @loader.command( - ru_doc="Создать файл с вашим текстом или кодом", - en_doc="Create a file with your text or code", - ) - async def ttfcmd(self, message): - args = utils.get_args_raw(message) - if not args: - await utils.answer(message, self.strings("no_args")) - return - - text = args - by = io.BytesIO(text.encode("utf-8")) - by.name = self.config["name"] - - await utils.send_file( - message.chat_id, - by, - caption=None, - reply_to=message.reply_to_msg_id, - ) diff --git a/archquise/H.Modules/TikTokDownloader.py b/archquise/H.Modules/TikTokDownloader.py deleted file mode 100644 index fde8ffd..0000000 --- a/archquise/H.Modules/TikTokDownloader.py +++ /dev/null @@ -1,282 +0,0 @@ -# Proprietary License Agreement - -# Copyright (c) 2024-29 CodWiz - -# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions: - -# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author. - -# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author. - -# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software. - -# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software. - -# 5. By using the Software, you agree to be bound by the terms and conditions of this license. - -# For any inquiries or requests for permissions, please contact codwiz@yandex.ru. - -# --------------------------------------------------------------------------------- -# Name: TikTokDownloader -# Description: A module for downloading videos and photos from TikTok without watermark -# Author: @hikka_mods -# --------------------------------------------------------------------------------- -# meta developer: @hikka_mods -# scope: Api TikTokDownloader -# scope: Api TikTokDownloader 0.0.1 -# --------------------------------------------------------------------------------- - -import asyncio -from dataclasses import dataclass -import logging -import os -import re -from typing import List, Optional, Union -from urllib.parse import urljoin - -import aiohttp -from tqdm import tqdm - -from .. import loader, utils - -logger = logging.getLogger(__name__) - - -@dataclass -class data: - dir_name: str - media: Union[str, List[str]] - type: str - - -class TikTok: - def __init__(self, host: Optional[str] = None): - self.headers = { - "User-Agent": ( - "Mozilla/5.0 (iPad; U; CPU OS 3_2 like Mac OS X; en-us) " - "AppleWebKit/531.21.10 (KHTML, like Gecko) Version/4.0.4 " - "Mobile/7B334b Safari/531.21.10" - ) - } - self.host = host or "https://www.tikwm.com/" - self.session = aiohttp.ClientSession() - - self.data_endpoint = "api" - self.search_videos_keyword_endpoint = "api/feed/search" - self.search_videos_hashtag_endpoint = "api/challenge/search" - - self.link = None - self.result = None - - self.logger = logging.getLogger("damirtag-TikTok") - handler = logging.StreamHandler() - formatter = logging.Formatter( - "[damirtag-TikTok:%(funcName)s]: %(levelname)s - %(message)s" - ) - handler.setFormatter(formatter) - self.logger.addHandler(handler) - self.logger.setLevel(logging.INFO) - - async def close_session(self): - await self.session.close() - - async def __ensure_data(self, link: str): - if self.link != link: - self.link = link - self.result = await self._fetch_data(link) - self.logger.info("Successfully ensured data from the link") - - async def __get_images(self, download_dir: Optional[str] = None): - download_dir = download_dir or self.result["id"] - os.makedirs(download_dir, exist_ok=True) - - tasks = [ - self._download_file(url, os.path.join(download_dir, f"image_{i + 1}.jpg")) - for i, url in enumerate(self.result["images"]) - ] - await asyncio.gather(*tasks) - - self.logger.info(f"Images - Downloaded and saved photos to {download_dir}") - - return data( - dir_name=download_dir, - media=[ - os.path.join(download_dir, f"image_{i + 1}.jpg") - for i in range(len(self.result["images"])) - ], - type="images", - ) - - async def __get_video(self, video_filename: Optional[str] = None, hd: bool = False): - video_url = self.result["hdplay"] if hd else self.result["play"] - video_filename = video_filename or f"{self.result['id']}.mp4" - - async with self.session.get(video_url) as response: - response.raise_for_status() - total_size = int(response.headers.get("content-length", 0)) - with open(video_filename, "wb") as file: - with tqdm( - total=total_size, unit="B", unit_scale=True, desc=video_filename - ) as pbar: - async for chunk in response.content.iter_any(): - file.write(chunk) - pbar.update(len(chunk)) - - self.logger.info(f"Video - Downloaded and saved video as {video_filename}") - - return data( - dir_name=os.path.dirname(video_filename), media=video_filename, type="video" - ) - - async def _fetch_data(self, link: str) -> dict: - url = self.get_url(link) - params = {"url": url, "hd": 1} - return await self._make_request(self.data_endpoint, params=params) - - async def _download_file(self, url: str, path: str): - async with self.session.get(url) as response: - response.raise_for_status() - with open(path, "wb") as file: - while chunk := await response.content.read(1024): - file.write(chunk) - - async def download_sound( - self, - link: str, - audio_filename: Optional[str] = None, - audio_ext: Optional[str] = ".mp3", - ): - await self.__ensure_data(link) - - if not audio_filename: - audio_filename = f"{self.result['music_info']['title']}{audio_ext}" - else: - audio_filename += audio_ext - - await self._download_file(self.result["music_info"]["play"], audio_filename) - self.logger.info(f"Sound - Downloaded and saved sound as {audio_filename}") - return audio_filename - - async def download( - self, link: str, video_filename: Optional[str] = None, hd: bool = True - ): - await self.__ensure_data(link) - - if "images" in self.result: - return await self.__get_images(video_filename) - - if "hdplay" in self.result or "play" in self.result: - return await self.__get_video(video_filename, hd) - - self.logger.error("No downloadable content found in the provided link.") - raise Exception("No downloadable content found in the provided link.") - - async def _make_request(self, endpoint: str, params: dict) -> dict: - async with self.session.get( - urljoin(self.host, endpoint), params=params, headers=self.headers - ) as response: - response.raise_for_status() - data = await response.json() - return data.get("data", {}) - - @staticmethod - def get_url(text: str) -> Optional[str]: - urls = re.findall(r"http[s]?://[^\s]+", text) - return urls[0] if urls else None - - @staticmethod - def _get_video_link(unique_id: str, aweme_id: str) -> str: - return f"https://www.tiktok.com/@{unique_id}/video/{aweme_id}" - - @staticmethod - def _get_uploader_link(unique_id: str) -> str: - return f"https://www.tiktok.com/@{unique_id}" - - -@loader.tds -class TikTokDownloader(loader.Module): - """TikTok Downloader module""" - - strings = { - "name": "TikTokDownloader", - "downloading": "<emoji document_id=5436024756610546212>⚡</emoji> <b>Downloading…</b>", - "success_photo": "<emoji document_id=5436246187944460315>❤️</emoji> <b>The photo(s) has/have been successfully downloaded!</b>!", - "success_video": "<emoji document_id=5436246187944460315>❤️</emoji> <b>The video has been successfully downloaded!</b>", - "success_sound": "<emoji document_id=5436246187944460315>❤️</emoji> <b>The sound has been successfully downloaded!</b>", - "error": "Error occurred while downloading.\n{}", - } - - strings_ru = { - "downloading": "<emoji document_id=5436024756610546212>⚡</emoji> <b>Загружаем…</b>", - "success_photo": "<emoji document_id=5436246187944460315>❤️</emoji> <b>Фотография(-и) была(-и) успешно загружены!</b>!", - "success_video": "<emoji document_id=5436246187944460315>❤️</emoji> <b>Видео было успешно загружено!</b>", - "success_sound": "<emoji document_id=5436246187944460315>❤️</emoji> <b>Звук был успешно загружен!</b>", - "error": "Во время загрузки произошла ошибка.\n{}", - } - - @loader.command( - ru_doc="Скачать звук с TikTok", - en_doc="Download sound from TikTok", - ) - async def ttsound(self, message): - args = utils.get_args(message) - if not args: - await utils.answer(message, "Please provide a TikTok URL.") - return - - url = args[0] - await utils.answer(message, self.strings("downloading")) - - tiktok_downloader = TikTok() - - try: - download_result = await tiktok_downloader.download_sound(url) - await message.client.send_file( - message.to_id, download_result, caption=self.strings("success_sound") - ) - await message.delete() - except Exception as e: - await utils.answer( - message, - f"{self.strings('error').format(e)}\n Убедитесь, что ссылка ведет именно на видео или фото с нужным звуком, прямая ссылка на звук не сработает!", - ) - finally: - await tiktok_downloader.close_session() - - @loader.command( - ru_doc="Скачать видео или фото с TikTok", - en_doc="Download videos or photos from TikTok", - ) - async def tt(self, message): - args = utils.get_args(message) - if not args: - await utils.answer(message, "Please provide a TikTok URL.") - return - - url = args[0] - await utils.answer(message, self.strings("downloading")) - - tiktok_downloader = TikTok() - - try: - download_result = await tiktok_downloader.download(url) - - if download_result.type == "video": - await message.client.send_file( - message.to_id, - download_result.media, - caption=self.strings("success_video"), - ) - await message.delete() - elif download_result.type == "images": - await message.client.send_file( - message.to_id, - download_result.media, - caption=self.strings("success_photo"), - ) - await message.delete() - - except Exception as e: - await utils.answer(message, self.strings("error").format(e)) - finally: - await tiktok_downloader.close_session() diff --git a/archquise/H.Modules/TimedEmojiStatus.py b/archquise/H.Modules/TimedEmojiStatus.py deleted file mode 100644 index c7ad082..0000000 --- a/archquise/H.Modules/TimedEmojiStatus.py +++ /dev/null @@ -1,538 +0,0 @@ -# Proprietary License Agreement - -# Copyright (c) 2024-29 CodWiz - -# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions: - -# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author. - -# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author. - -# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software. - -# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software. - -# 5. By using the Software, you agree to be bound by the terms and conditions of this license. - -# For any inquiries or requests for permissions, please contact codwiz@yandex.ru. - -# --------------------------------------------------------------------------------- -# Name: TimedEmojiStatus -# Description: Temporary emoji status with auto-revert -# Author: @hikka_mods -# --------------------------------------------------------------------------------- -# meta developer: @hikka_mods -# scope: TimedEmojiStatus -# scope: TimedEmojiStatus 0.0.1 -# --------------------------------------------------------------------------------- - -import asyncio -import logging -import re -import time -from datetime import datetime, timedelta -from typing import Dict, Optional - -from telethon.tl.functions.account import UpdateEmojiStatusRequest -from telethon.tl.types import EmojiStatus, MessageEntityCustomEmoji, Message - -from .. import loader, utils - -logger = logging.getLogger(__name__) - - -@loader.tds -class TimedEmojiStatusMod(loader.Module): - """Temporary emoji status with auto-revert using scheduler""" - - strings = { - "name": "TimedEmojiStatus", - "no_emoji": "<emoji document_id=5337117114392127164>❌</emoji> <b>Specify emoji or emoji document_id</b>", - "no_time": "<emoji document_id=5337117114392127164>❌</emoji> <b>Specify time (ex: 1h, 30m, 2d)</b>", - "invalid_time": "<emoji document_id=5337117114392127164>❌</emoji> <b>Invalid time format (ex: 30m, 2h, 1d, 1w)</b>", - "status_set": "<emoji document_id=5336965905773504919>✅</emoji> <b>Status set:</b>\n<b>Current:</b> {}\n<b>Final:</b> {}\n<b>For:</b> {} ({})", - "status_updated": "<emoji document_id=5336965905773504919>✅</emoji> <b>Status updated: {}</b>", - "no_status": "<emoji document_id=5337117114392127164>❌</emoji> <b>No active status</b>", - "status_removed": "<emoji document_id=5336965905773504919>✅</emoji> <b>Status removed</b>", - "current_status": "<emoji document_id=5348186233610711303>📊</emoji> <b>Active status:</b>\n<b>Current:</b> {}\n<b>Final:</b> {}\n<b>Until:</b> {} ({})", - "no_premium": "<emoji document_id=5337117114392127164>❌</emoji> <b>Premium required for emoji status</b>", - "error": "<emoji document_id=5337117114392127164>❌</emoji> <b>Error: {}</b>", - } - - strings_ru = { - "no_emoji": "<emoji document_id=5337117114392127164>❌</emoji> <b>Укажите эмодзи или document_id</b>", - "no_time": "<emoji document_id=5337117114392127164>❌</emoji> <b>Укажите время (напр: 1h, 30m, 2d)</b>", - "invalid_time": "<emoji document_id=5337117114392127164>❌</emoji> <b>Неверный формат времени (напр: 30m, 2h, 1d, 1w)</b>", - "status_set": "<emoji document_id=5336965905773504919>✅</emoji> <b>Статус установлен:</b>\n<b>Текущий:</b> {}\n<b>Финальный:</b> {}\n<b>На:</b> {} ({})", - "status_updated": "<emoji document_id=5336965905773504919>✅</emoji> <b>Статус обновлён: {}</b>", - "no_status": "<emoji document_id=5337117114392127164>❌</emoji> <b>Нет активного статуса</b>", - "status_removed": "<emoji document_id=5336965905773504919>✅</emoji> <b>Статус удалён</b>", - "current_status": "<emoji document_id=5348186233610711303>📊</emoji> <b>Активный статус:</b>\n<b>Текущий:</b> {}\n<b>Финальный:</b> {}\n<b>До:</b> {} ({})", - "no_premium": "<emoji document_id=5337117114392127164>❌</emoji> <b>Требуется Premium для эмодзи статуса</b>", - "error": "<emoji document_id=5337117114392127164>❌</emoji> <b>Ошибка: {}</b>", - } - - def __init__(self): - self.status_data: Dict[int, Dict] = {} - self.scheduler_tasks: Dict[int, asyncio.Task] = {} - - async def client_ready(self, client, db): - self._client = client - self._db = db - - if not self._client.hikka_me.premium: - logger.warning("Premium required for emoji status functionality") - - await self._restore_active_statuses() - - async def _restore_active_statuses(self): - """Restore and reschedule active statuses after restart""" - saved = self._db.get(__name__, "statuses", {}) - current_time = time.time() - - for user_id, data in saved.items(): - end_time = data.get("end_time", 0) - if end_time > current_time: - remaining_time = end_time - current_time - logger.info( - f"Restoring status for user {user_id}, remaining: {remaining_time}s" - ) - - task = asyncio.create_task( - self._schedule_revert_sleep(user_id, remaining_time) - ) - self.scheduler_tasks[user_id] = task - - self.status_data[user_id] = data - else: - logger.info(f"Removing expired status for user {user_id}") - del saved[user_id] - - if saved != self._db.get(__name__, "statuses", {}): - self._db.set(__name__, "statuses", saved) - - def _parse_time(self, time_str: str) -> Optional[timedelta]: - """Parse time string like 1h30m, 2d, 1w, 1mth""" - pattern = r"(\d+)([smhdwmth]+)" - matches = re.findall(pattern, time_str.lower()) - - if not matches: - return None - - total_seconds = 0 - for value, unit in matches: - value = int(value) - if unit == "s": - total_seconds += value - elif unit == "m": - total_seconds += value * 60 - elif unit == "h": - total_seconds += value * 3600 - elif unit == "d": - total_seconds += value * 86400 - elif unit == "w": - total_seconds += value * 604800 - elif unit in ["mth", "month"]: - total_seconds += value * 2592000 # 30 days - - return timedelta(seconds=total_seconds) - - def _format_time(self, td: timedelta) -> str: - """Format timedelta to human readable string""" - total_days = td.days - months = total_days // 30 - remaining_days = total_days % 30 - - if months > 0: - if remaining_days > 0: - return f"{months}mth {remaining_days}d" - return f"{months}mth" - elif total_days > 0: - return f"{total_days}d {td.seconds // 3600}h" - elif td.seconds >= 3600: - return f"{td.seconds // 3600}h {(td.seconds % 3600) // 60}m" - else: - return f"{td.seconds // 60}m" - - def _extract_document_id(self, emoji_input: str) -> Optional[int]: - """Extract document_id from emoji string""" - - pattern = r"<emoji\s+document_id=(\d+)>.*?</emoji>" - match = re.search(pattern, emoji_input) - if match: - return int(match.group(1)) - - if emoji_input.isdigit(): - return int(emoji_input) - - return None - - def _extract_document_id_from_entities(self, message: Message) -> Optional[int]: - """Extract document_id from message entities""" - if not message.entities: - return None - - for entity in message.entities: - if isinstance(entity, MessageEntityCustomEmoji): - return entity.document_id - return None - - def _safe_emoji_display( - self, emoji_str: str, document_id: Optional[int] = None - ) -> str: - """Safely display emoji without causing errors""" - if not emoji_str: - return "❌" - - if document_id: - return f"[Custom Emoji ID: {document_id}]" - - if emoji_str.isdigit(): - return f"[Custom Emoji ID: {emoji_str}]" - - if "<emoji document_id=" in emoji_str: - - import re - match = re.search(r'document_id=(\d+)', emoji_str) - if match: - return f"[Custom Emoji ID: {match.group(1)}]" - return "[Custom Emoji]" - - if len(emoji_str) == 1 or ( - len(emoji_str) <= 4 and all(ord(c) >= 0x1F000 for c in emoji_str) - ): - return emoji_str - - return emoji_str[:10] + "..." if len(emoji_str) > 10 else emoji_str - - async def _set_emoji_status( - self, emoji_input: str, until: datetime | None = None, message: Message = None - ) -> tuple[bool, Optional[int]]: - """Set emoji status (requires Premium). Returns (success, document_id)""" - try: - logger.info(f"Setting emoji status for: {emoji_input}") - - if not self._client.hikka_me.premium: - logger.warning("Premium required for emoji status") - return False, None - - if not emoji_input: - logger.info("Removing emoji status") - await self._client(UpdateEmojiStatusRequest(emoji_status=None)) - return True, None - - document_id = None - - if message: - document_id = self._extract_document_id_from_entities(message) - if document_id: - logger.info( - f"Found document_id from message entities: {document_id}" - ) - - if not document_id: - document_id = self._extract_document_id(emoji_input) - if document_id: - logger.info(f"Extracted document_id from text: {document_id}") - - if not document_id: - try: - logger.info("Trying to get document_id from test message") - test_msg = await self._client.send_message("me", emoji_input) - document_id = self._extract_document_id_from_entities(test_msg) - await self._client.delete_messages("me", [test_msg.id]) - - if document_id: - logger.info( - f"Found document_id from test message: {document_id}" - ) - else: - logger.warning("No document_id found in test message") - - except Exception as e: - logger.error(f"Error getting document_id from test message: {e}") - - if document_id: - try: - emoji_status = EmojiStatus(document_id=document_id, until=until) - await self._client( - UpdateEmojiStatusRequest(emoji_status=emoji_status) - ) - logger.info( - f"Status set successfully with document_id: {document_id}" - ) - return True, document_id - except Exception as e: - logger.error(f"Error setting status: {e}") - if "PREMIUM" in str(e).upper(): - return False, None - return False, None - - logger.warning("No document_id found, all methods failed") - return False, None - - except Exception as e: - logger.error(f"General error setting emoji status: {e}") - return False, None - - async def _revert_status(self, user_id: int): - """Revert status to final emoji or remove""" - logger.info(f"Starting revert status for user {user_id}") - - if user_id in self.scheduler_tasks: - del self.scheduler_tasks[user_id] - - if user_id in self.status_data: - data = self.status_data[user_id] - final_emoji = data.get("final_emoji", "") - final_doc_id = data.get("final_doc_id") - - logger.info( - f"Reverting status for user {user_id} to: '{final_emoji}' (saved doc_id: {final_doc_id})" - ) - - try: - if final_emoji and final_doc_id: - logger.info( - f"Setting final emoji using saved document_id: {final_doc_id}" - ) - try: - emoji_status = EmojiStatus(document_id=final_doc_id) - await self._client( - UpdateEmojiStatusRequest(emoji_status=emoji_status) - ) - logger.info( - f"Successfully set final emoji with document_id: {final_doc_id}" - ) - except Exception as e: - logger.error(f"Error setting final emoji with document_id: {e}") - - success, _ = await self._set_emoji_status(final_emoji) - if not success: - await self._set_emoji_status("") - elif final_emoji: - logger.info(f"Attempting to set final emoji: '{final_emoji}'") - success, final_doc_id = await self._set_emoji_status(final_emoji) - if success: - logger.info( - f"Successfully reverted to final emoji: '{final_emoji}' (doc_id: {final_doc_id})" - ) - else: - logger.warning( - f"Failed to set final emoji '{final_emoji}', removing status instead" - ) - await self._set_emoji_status("") - else: - logger.info("No final emoji specified, removing status") - await self._set_emoji_status("") - except Exception as e: - logger.error(f"Error reverting status: {e}") - - try: - await self._set_emoji_status("") - except Exception as e2: - logger.error(f"Error removing status: {e2}") - - logger.info(f"Removing status data for user {user_id}") - del self.status_data[user_id] - - saved = self._db.get(__name__, "statuses", {}) - if user_id in saved: - logger.info(f"Removing saved status for user {user_id}") - del saved[user_id] - self._db.set(__name__, "statuses", saved) - - logger.info(f"Revert status completed for user {user_id}") - - async def _schedule_revert_sleep(self, user_id: int, delay: float): - """Schedule status revert using asyncio.sleep""" - try: - logger.info(f"Scheduling revert for user {user_id} in {delay} seconds") - await asyncio.sleep(delay) - await self._revert_status(user_id) - except asyncio.CancelledError: - logger.info(f"Revert task cancelled for user {user_id}") - except Exception as e: - logger.error(f"Error in scheduled revert for user {user_id}: {e}") - - async def _schedule_revert(self, user_id: int, data: Dict): - """Schedule status revert""" - end_time = data.get("end_time", 0) - delay = max(0, end_time - time.time()) - - self.status_data[user_id] = data - - await self._schedule_revert_sleep(user_id, delay) - - @loader.command( - ru_doc="<время> <эмодзи/document_id> [финальный_эмодзи/document_id] - установить временный статус", - en_doc="<time> <emoji/document_id> [final_emoji/document_id] - set temporary status", - ) - async def setmoji(self, message: Message): - """Set timed emoji status""" - args = utils.get_args_raw(message) - - if not args: - return await utils.answer(message, self.strings["no_time"]) - - parts = args.split(maxsplit=2) - if len(parts) < 2: - return await utils.answer(message, self.strings["no_emoji"]) - - time_str, initial_emoji = parts[0], parts[1] - final_emoji = parts[2] if len(parts) > 2 else "" - - td = self._parse_time(time_str) - if not td: - return await utils.answer(message, self.strings["invalid_time"]) - - if message.sender_id in self.scheduler_tasks: - self.scheduler_tasks[message.sender_id].cancel() - del self.scheduler_tasks[message.sender_id] - - try: - success, initial_doc_id = await self._set_emoji_status( - initial_emoji, message=message - ) - if not success: - return await utils.answer(message, self.strings["no_premium"]) - except Exception as e: - return await utils.answer(message, self.strings["error"].format(str(e))) - - final_doc_id = None - if final_emoji: - try: - final_doc_id = self._extract_document_id(final_emoji) - if not final_doc_id: - if message and len(parts) > 2: - emoji_entities = [ - e - for e in message.entities - if isinstance(e, MessageEntityCustomEmoji) - ] - if len(emoji_entities) >= 2: - final_doc_id = emoji_entities[1].document_id - - if not final_doc_id: - try: - test_msg = await self._client.send_message("me", final_emoji) - final_doc_id = self._extract_document_id_from_entities(test_msg) - await self._client.delete_messages("me", [test_msg.id]) - except Exception as e: - logger.warning( - f"Could not get document_id for final emoji: {e}" - ) - - if final_doc_id: - logger.info(f"Final emoji document_id: {final_doc_id}") - else: - logger.warning( - f"Could not resolve document_id for final emoji: {final_emoji}" - ) - - except Exception as e: - logger.warning(f"Error getting final emoji document_id: {e}") - - end_time = time.time() + td.total_seconds() - user_id = message.sender_id - - data = { - "initial_emoji": initial_emoji, - "final_emoji": final_emoji, - "initial_doc_id": initial_doc_id, - "final_doc_id": final_doc_id, - "end_time": end_time, - "set_time": time.time(), - } - - self.status_data[user_id] = data - - saved = self._db.get(__name__, "statuses", {}) - saved[user_id] = data - self._db.set(__name__, "statuses", saved) - - task = asyncio.create_task( - self._schedule_revert_sleep(user_id, td.total_seconds()) - ) - self.scheduler_tasks[user_id] = task - - end_dt = datetime.fromtimestamp(end_time) - time_str = self._format_time(td) - - logger.info( - f"Display formatting - initial: '{initial_emoji}' (doc_id: {initial_doc_id}), final: '{final_emoji}' (doc_id: {final_doc_id})" - ) - current_display = self._safe_emoji_display(initial_emoji, initial_doc_id) - final_display = ( - self._safe_emoji_display(final_emoji, final_doc_id) - if final_emoji - else "❌ (удалить)" - ) - - logger.info( - f"Display results - current: '{current_display}', final: '{final_display}'" - ) - - await utils.answer( - message, - self.strings["status_set"].format( - current_display, final_display, time_str, f"{end_dt:%H:%M:%S}" - ), - ) - - @loader.command(ru_doc="Показать текущий статус", en_doc="Show current status") - async def showmoji(self, message: Message): - """Show current emoji status""" - user_id = message.sender_id - - if user_id not in self.status_data: - return await utils.answer(message, self.strings["no_status"]) - - data = self.status_data[user_id] - end_time = data.get("end_time", 0) - initial_emoji = data.get("initial_emoji", "") - final_emoji = data.get("final_emoji", "") - initial_doc_id = data.get("initial_doc_id") - final_doc_id = data.get("final_doc_id") - - if end_time <= time.time(): - return await utils.answer(message, self.strings["no_status"]) - - end_dt = datetime.fromtimestamp(end_time) - remaining = timedelta(seconds=end_time - time.time()) - remaining_str = self._format_time(remaining) - - current_display = self._safe_emoji_display(initial_emoji, initial_doc_id) - final_display = ( - self._safe_emoji_display(final_emoji, final_doc_id) - if final_emoji - else "❌ (удалить)" - ) - - await utils.answer( - message, - self.strings["current_status"].format( - current_display, final_display, f"{end_dt:%H:%M:%S}", remaining_str - ), - ) - - @loader.command(ru_doc="Удалить статус", en_doc="Remove status") - async def removemoji(self, message: Message): - """Remove emoji status""" - user_id = message.sender_id - - if user_id not in self.status_data: - return await utils.answer(message, self.strings["no_status"]) - - if user_id in self.scheduler_tasks: - self.scheduler_tasks[user_id].cancel() - del self.scheduler_tasks[user_id] - - await self._revert_status(user_id) - await utils.answer(message, self.strings["status_removed"]) - - async def on_unload(self): - """Cancel all scheduled tasks on unload""" - for task in self.scheduler_tasks.values(): - task.cancel() - self.scheduler_tasks.clear() diff --git a/archquise/H.Modules/UserbotAvast.py b/archquise/H.Modules/UserbotAvast.py deleted file mode 100644 index 0d87c98..0000000 --- a/archquise/H.Modules/UserbotAvast.py +++ /dev/null @@ -1,963 +0,0 @@ -# Proprietary License Agreement - -# Copyright (c) 2024-29 CodWiz - -# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions: - -# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author. - -# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author. - -# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software. - -# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software. - -# 5. By using the Software, you agree to be bound by the terms and conditions of this license. - -# For any inquiries or requests for permissions, please contact codwiz@yandex.ru. - -# --------------------------------------------------------------------------------- -# Name: UserbotAvast -# Description: A module for checking modules for security. -# Author: @hikka_mods -# --------------------------------------------------------------------------------- -# meta developer: @hikka_mods -# scope: UserbotAvast -# scope: UserbotAvast 0.0.1 -# --------------------------------------------------------------------------------- - -import ast -import base64 -import logging -import re -import zlib - -import requests - -from .. import loader, utils - -logger = logging.getLogger(__name__) - -try: - import g4f - - G4F_AVAILABLE = True -except ImportError: - G4F_AVAILABLE = False - logger.warning("g4f is not installed. AI analysis will be disabled.") - - -class SecurityAnalyzer: - """ - Продвинутый анализатор безопасности Python-кода с эвристическим анализом. - """ - - SECURITY_KEYWORDS = { - "critical": [ - { - "keyword": "DeleteAccountRequest", - "description": "Удаление аккаунта", - "relevance": "Высокая", - }, - { - "keyword": "ResetAuthorizationRequest", - "description": "Сброс авторизации", - "relevance": "Высокая", - }, - { - "keyword": "client.export_session_string", - "description": "Экспорт сессии", - "relevance": "Высокая", - }, - { - "keyword": "edit_2fa", - "description": "Изменение 2FA", - "relevance": "Высокая", - }, - { - "keyword": "os.system", - "description": "Системные команды", - "relevance": "Высокая", - }, - { - "keyword": "subprocess.Popen", - "description": "Внешние процессы", - "relevance": "Высокая", - }, - { - "keyword": "eval", - "description": "Выполнение кода (eval)", - "relevance": "Критическая", - }, - { - "keyword": "exec", - "description": "Выполнение кода (exec)", - "relevance": "Критическая", - }, - { - "keyword": "MessagePacker.append", - "description": "Патч MessagePacker", - "relevance": "Средняя", - }, - { - "keyword": "MessagePacker.extend", - "description": "Патч MessagePacker", - "relevance": "Средняя", - }, - { - "keyword": "Scrypt", - "description": "Класс Scrypt (подозрительно)", - "relevance": "Высокая", - }, - { - "keyword": "socket.socket", - "description": "Создание сокета", - "relevance": "Высокая", - }, - { - "keyword": "shell=True", - "description": "Использование shell=True в subprocess", - "relevance": "Критическая", - }, - { - "keyword": "codecs.decode", - "description": "Декодирование с использованием codecs", - "relevance": "Средняя", - }, - { - "keyword": "pickle.loads", - "description": "Десериализация (pickle)", - "relevance": "Высокая", - }, - { - "keyword": "marshal.loads", - "description": "Десериализация (marshal)", - "relevance": "Высокая", - }, - { - "keyword": "__import__", - "description": "Динамический импорт", - "relevance": "Средняя", - }, - { - "keyword": "ctypes.CDLL", - "description": "Загрузка динамической библиотеки", - "relevance": "Высокая", - }, - { - "keyword": "create_connection", - "description": "Установка соединения", - "relevance": "Высокая", - }, - { - "keyword": "http.server", - "description": "Запуск веб-сервера", - "relevance": "Средняя", - }, - { - "keyword": "asyncio.create_subprocess_shell", - "description": "Асинхронный запуск процесса через shell", - "relevance": "Критическая", - }, - ], - "warning": [ - { - "keyword": "requests", - "description": "HTTP-запросы", - "relevance": "Средняя", - }, - { - "keyword": "aiohttp", - "description": "Асинхронные HTTP-запросы", - "relevance": "Средняя", - }, - { - "keyword": "os.remove", - "description": "Удаление файлов", - "relevance": "Средняя", - }, - { - "keyword": "os.mkdir", - "description": "Создание каталогов", - "relevance": "Низкая", - }, - { - "keyword": "json.loads", - "description": "Парсинг JSON", - "relevance": "Низкая", - }, - { - "keyword": "open(..., 'w')", - "description": "Открытие файла на запись", - "relevance": "Средняя", - }, - { - "keyword": "open(..., 'a')", - "description": "Открытие файла на добавление", - "relevance": "Средняя", - }, - { - "keyword": "telnetlib.Telnet", - "description": "Telnet соединение", - "relevance": "Средняя", - }, - { - "keyword": "ftplib.FTP", - "description": "FTP соединение", - "relevance": "Средняя", - }, - { - "keyword": "shutil.move", - "description": "Перемещение файлов", - "relevance": "Средняя", - }, - { - "keyword": "shutil.copy", - "description": "Копирование файлов", - "relevance": "Средняя", - }, - { - "keyword": "threading.Thread", - "description": "Создание потока", - "relevance": "Низкая", - }, - { - "keyword": "multiprocessing.Process", - "description": "Создание процесса", - "relevance": "Низкая", - }, - { - "keyword": "queue.Queue", - "description": "Использование очереди", - "relevance": "Низкая", - }, - { - "keyword": "subprocess.check_output", - "description": "Запуск процесса с захватом вывода", - "relevance": "Средняя", - }, - { - "keyword": "subprocess.run", - "description": "Запуск процесса", - "relevance": "Средняя", - }, - { - "keyword": "codecs.encode", - "description": "Кодирование с использованием codecs", - "relevance": "Средняя", - }, - ], - "info": [ - { - "keyword": "telethon", - "description": "Использование Telethon", - "relevance": "Низкая", - }, - { - "keyword": "pyrogram", - "description": "Использование Pyrogram", - "relevance": "Низкая", - }, - { - "keyword": "import", - "description": "Импорт модулей", - "relevance": "Низкая", - }, - { - "keyword": "print", - "description": "Вывод в консоль", - "relevance": "Низкая", - }, - { - "keyword": "logging.info", - "description": "Логирование", - "relevance": "Низкая", - }, - ], - } - - def __init__(self, ai_enabled: bool = False): - """Инициализация анализатора.""" - self.results = {"critical": [], "warning": [], "info": []} - self.reported_issues = set() - self.code_lines = [] - self.is_decoded = False - self.ai_enabled = ai_enabled - - def reset(self): - """Сброс результатов анализа.""" - self.results = {"critical": [], "warning": [], "info": []} - self.reported_issues = set() - self.code_lines = [] - self.is_decoded = False - - async def analyze(self, code: str, strings: dict) -> str: - """ - Выполняет анализ предоставленного Python-кода. - - Args: - code: Python-код для анализа. - strings: Словарь строк для локализации. - - Returns: - Форматированный отчет об анализе. - """ - self.reset() - original_code = code - - try: - code = self._try_decode(code) - self.is_decoded = True - except Exception: - logger.warning("Не удалось расшифровать код, анализ как есть.") - - self.code_lines = code.splitlines() - try: - tree = ast.parse(code) - self._visit_tree(tree) - self._heuristic_analysis(code, tree) - - if self.ai_enabled and G4F_AVAILABLE: - ai_analysis_result = await self._ai_analysis(code) - if ai_analysis_result: - self.results["critical"].append( - { - "keyword": "AI Analysis", - "description": ai_analysis_result, - "relevance": "Критическая", - "line": 0, - "col": 0, - } - ) - - except SyntaxError as e: - logger.error(f"Ошибка синтаксиса в коде: {e}") - return strings["syntax_error"].format(error=e) - except Exception as e: - logger.exception("Unexpected error during analysis") - return strings["syntax_error"].format(error=str(e)) - - return self._format_report(strings, original_code) - - async def _ai_analysis(self, code: str) -> str or None: - """ - Использует g4f для анализа кода и выявления потенциальных угроз. - """ - try: - prompt = f"Проанализируйте следующий Python-код на предмет потенциальных угроз безопасности, уязвимостей и вредоносных действий. Предоставьте подробное объяснение, если что-то будет обнаружено:\n\n{code}" - response = await utils.run_sync( - g4f.ChatCompletion.create, - model=g4f.models.default, - messages=[{"role": "user", "content": prompt}], - ) - return str(response) - except Exception as e: - logger.error(f"Ошибка при анализе с помощью g4f: {e}") - return None - - def _try_decode(self, code): - """Попытка расшифровать base64 + zlib код.""" - if re.search(r"__import__\('zlib'\).decompress\(", code) and re.search( - r"__import__\('base64'\).b64decode\(", code - ): - try: - match = re.search(r"b'([A-Za-z0-9+/=]+)'", code) - if match: - encoded_string = match.group(1) - decoded_code = self._decode_base64_zlib(encoded_string) - logger.info("Код успешно расшифрован.") - return decoded_code - except Exception as e: - logger.error(f"Ошибка при расшифровке кода: {e}") - raise - return code - - def _decode_base64_zlib(self, encoded_string): - """Расшифровывает base64 + zlib код.""" - try: - decoded_bytes = base64.b64decode(encoded_string) - decompressed_bytes = zlib.decompress(decoded_bytes) - return decompressed_bytes.decode("utf-8") - except Exception as e: - logger.error(f"Ошибка при расшифровке base64+zlib: {e}") - raise - - def _get_line_from_code(self, lineno): - """Получает строку кода по номеру строки.""" - try: - return self.code_lines[lineno - 1] - except IndexError: - return "" - - def _visit_tree(self, tree): - """Рекурсивно обходит AST-дерево.""" - for node in ast.walk(tree): - self._analyze_node(node) - - def _analyze_node(self, node): - """Анализирует отдельный узел AST.""" - if isinstance(node, ast.Name): - self._check_keyword(node.id, node) - elif isinstance(node, ast.Call): - self._check_call(node) - elif isinstance(node, (ast.Import, ast.ImportFrom)): - self._check_import(node) - elif isinstance(node, ast.FunctionDef): - self._check_function_def(node) - elif isinstance(node, ast.ClassDef): - self._check_class_def(node) - elif isinstance(node, ast.Assign): - self._check_assign(node) - - def _check_keyword(self, keyword, node): - """Проверяет ключевые слова.""" - for severity, keywords in self.SECURITY_KEYWORDS.items(): - for item in keywords: - if item["keyword"] == keyword: - issue_key = ( - item["keyword"], - node.lineno, - node.col_offset, - ) - if issue_key not in self.reported_issues: - self.results[severity].append( - { - "keyword": item["keyword"], - "description": item["description"], - "relevance": item["relevance"], - "line": node.lineno, - "col": node.col_offset, - } - ) - self.reported_issues.add(issue_key) - - def _check_call(self, node): - """Анализирует вызовы функций.""" - if isinstance(node.func, ast.Name): - self._check_keyword(node.func.id, node) - elif isinstance(node.func, ast.Attribute): - full_attr = "" - if isinstance(node.func.value, ast.Name): - full_attr = node.func.value.id + "." + node.func.attr - self._check_keyword(full_attr, node) - else: - self._check_keyword(node.func.attr, node) - elif isinstance(node.func, ast.Subscript): - if isinstance(node.func.value, ast.Attribute): - full_attr = "" - if isinstance(node.func.value.value, ast.Name): - full_attr = node.func.value.value.id + "." + node.func.value.attr - self._check_keyword(full_attr, node) - - def _check_function_def(self, node): - self._check_keyword(node.name, node) - - def _check_class_def(self, node): - self._check_keyword(node.name, node) - - def _check_import(self, node): - """Анализирует импорты.""" - if isinstance(node, ast.Import): - for alias in node.names: - self._check_keyword(alias.name, node) - elif isinstance(node, ast.ImportFrom): - self._check_keyword(node.module, node) - for alias in node.names: - self._check_keyword(alias.name, node) - - def _check_assign(self, node): - """Анализирует присваивания.""" - for target in node.targets: - if isinstance(target, ast.Name): - self._check_keyword(target.id, node) - - def _heuristic_analysis(self, code: str, tree: ast.AST): - """ - Эвристический анализ для обнаружения подозрительного кода. - """ - self._check_obfuscation(code, tree) - self._check_dynamic_code_generation(code, tree) - self._check_url_patterns(code) - self._check_api_abuse(tree) - self._check_reverse_shell(code) - self._check_file_operations(code) - - def _check_obfuscation(self, code: str, tree: ast.AST): - """Обнаружение обфускации кода.""" - if len(re.findall(r"[A-Za-z0-9+/]{30,}", code)) > 2: - issue_key = ("Base64", 1, 1) - if issue_key not in self.reported_issues: - self.results["warning"].append( - { - "keyword": "Base64", - "description": "Подозрительные строки Base64", - "relevance": "Средняя", - "line": 1, - "col": 1, - } - ) - self.reported_issues.add(issue_key) - - if "zlib.decompress" in code: - issue_key = ("zlib.decompress", 1, 1) - if issue_key not in self.reported_issues: - self.results["warning"].append( - { - "keyword": "zlib.decompress", - "description": "Использование zlib декомпрессии", - "relevance": "Средняя", - "line": 1, - "col": 1, - } - ) - self.reported_issues.add(issue_key) - - for node in ast.walk(tree): - if isinstance(node, (ast.Call)): - if isinstance(node.func, ast.Name) and node.func.id in ("eval", "exec"): - if len(node.args) > 0 and isinstance(node.args[0], ast.Str): - obfuscated_string = node.args[0].s - if ( - len(re.findall(r"[A-Za-z0-9+/]{30,}", obfuscated_string)) - > 0 - ): - issue_key = ( - "eval/exec+Base64", - node.lineno, - node.col_offset, - ) - if issue_key not in self.reported_issues: - self.results["critical"].append( - { - "keyword": "eval/exec+Base64", - "description": "eval/exec с обфускацией Base64", - "relevance": "Критическая", - "line": node.lineno, - "col": node.col_offset, - } - ) - self.reported_issues.add(issue_key) - elif isinstance(node.func, ast.Name) and node.func.id in ( - "eval", - "exec", - ): - if len(node.args) > 0 and isinstance(node.args[0], ast.Name): - issue_key = ("eval/exec+Variable", node.lineno, node.col_offset) - if issue_key not in self.reported_issues: - self.results["critical"].append( - { - "keyword": "eval/exec+Variable", - "description": "eval/exec с переменной", - "relevance": "Критическая", - "line": node.lineno, - "col": node.col_offset, - } - ) - self.reported_issues.add(issue_key) - - hash_functions = ["md5", "sha1", "sha256", "sha512"] - for hash_func in hash_functions: - if f"hashlib.{hash_func}" in code: - issue_key = (f"hashlib.{hash_func}", 1, 1) - if issue_key not in self.reported_issues: - self.results["info"].append( - { - "keyword": f"hashlib.{hash_func}", - "description": f"Использование {hash_func} хеширования", - "relevance": "Низкая", - "line": 1, - "col": 1, - } - ) - self.reported_issues.add(issue_key) - - if any( - x in code - for x in [ - "hashlib.md5(password.encode()).hexdigest()", - "hashlib.sha256(password.encode()).hexdigest()", - ] - ): - issue_key = ("Weak Hashing", 1, 1) - if issue_key not in self.reported_issues: - self.results["warning"].append( - { - "keyword": "Weak Hashing", - "description": "Использование хеширования без соли", - "relevance": "Средняя", - "line": 1, - "col": 1, - } - ) - self.reported_issues.add(issue_key) - - def _check_dynamic_code_generation(self, code, tree: ast.AST): - """Обнаружение динамической генерации кода.""" - if "compile(" in code: - issue_key = ("compile", 1, 1) - if issue_key not in self.reported_issues: - self.results["warning"].append( - { - "keyword": "compile", - "description": "Использование compile() для генерации кода", - "relevance": "Средняя", - "line": 1, - "col": 1, - } - ) - self.reported_issues.add(issue_key) - - for node in ast.walk(tree): - if ( - isinstance(node, ast.Call) - and isinstance(node.func, ast.Name) - and node.func.id == "type" - ): - if ( - len(node.args) == 3 - and isinstance(node.args[0], ast.Str) - and isinstance(node.args[1], ast.Tuple) - and isinstance(node.args[2], ast.Dict) - ): - issue_key = ("type() class", node.lineno, node.col_offset) - if issue_key not in self.reported_issues: - self.results["warning"].append( - { - "keyword": "type() class", - "description": "Динамическое создание классов через type()", - "relevance": "Средняя", - "line": node.lineno, - "col": node.col_offset, - } - ) - self.reported_issues.add(issue_key) - - def _check_url_patterns(self, code: str): - """Обнаружение подозрительных URL-паттернов.""" - short_url_domains = [ - "bit.ly", - "goo.gl", - "t.co", - "tinyurl.com", - "is.gd", - "ow.ly", - "github.com", - "raw.githubusercontent.com", - ] - for domain in short_url_domains: - if domain in code: - issue_key = (f"Short URL ({domain})", 1, 1) - if issue_key not in self.reported_issues: - line = self._get_line_from_code(1) - match = re.search(r"(https?://\S+)", line) - url = ( - match.group(1) - if match - else f"Не удалось извлечь URL ({domain})" - ) - self.results["warning"].append( - { - "keyword": f"Short URL ({domain})", - "description": f"Обнаружен сокращенный URL: {url}", - "relevance": "Низкая", - "line": 1, - "col": 1, - } - ) - self.reported_issues.add(issue_key) - - webhook_patterns = ["discord.com/api/webhooks", "api.telegram.org/bot"] - for pattern in webhook_patterns: - if pattern in code: - issue_key = (f"Webhook ({pattern})", 1, 1) - if issue_key not in self.reported_issues: - line = self._get_line_from_code(1) - match = re.search(pattern, line) - url = ( - match.group(0) - if match - else f"Не удалось извлечь Webhook ({pattern})" - ) - - self.results["critical"].append( - { - "keyword": f"Webhook ({pattern})", - "description": f"Обнаружен Webhook: {url}", - "relevance": "Критическая", - "line": 1, - "col": 1, - } - ) - self.reported_issues.add(issue_key) - - def _check_api_abuse(self, tree: ast.AST): - """Обнаружение потенциального злоупотребления Telegram API.""" - send_methods = ["send_message", "send_file", "send_photo"] - for node in ast.walk(tree): - if isinstance(node, ast.For): - for send_method in send_methods: - if send_method in ast.unparse(node): - issue_key = ( - f"Mass {send_method}", - node.lineno, - node.col_offset, - ) - if issue_key not in self.reported_issues: - self.results["warning"].append( - { - "keyword": f"Mass {send_method}", - "description": f"Подозрение на массовую рассылку ({send_method})", - "relevance": "Средняя", - "line": node.lineno, - "col": node.col_offset, - } - ) - self.reported_issues.add(issue_key) - - if "time.sleep(" in ast.unparse(tree): - sleep_calls = re.findall(r"time\.sleep\((.*?)\)", ast.unparse(tree)) - for sleep_time in sleep_calls: - try: - sleep_value = float(sleep_time) - if sleep_value < 1: - issue_key = ("Short Sleep Time", 1, 1) - if issue_key not in self.reported_issues: - self.results["warning"].append( - { - "keyword": "Short Sleep Time", - "description": "Обнаружена короткая задержка (менее 1 секунды)", - "relevance": "Средняя", - "line": 1, - "col": 1, - } - ) - self.reported_issues.add(issue_key) - except ValueError: - pass - - def _check_reverse_shell(self, code: str): - """Обнаружение попыток создания обратного шелла.""" - try: - reverse_shell_patterns = [ - r"socket\.socket\(\s*socket\.AF_INET", - r"os\.dup2\(", - r"subprocess\.Popen\(\s*\[.+?\]\s*,\s*shell=True", - r"/bin/bash -i", - r"/bin/sh -i", - r"nc -e /bin/bash", - r"nc -e /bin/sh", - r"> /dev/tcp/", - r"python -c 'import socket,subprocess,os;s=socket.socket", - r"python3 -c 'import socket,subprocess,os;s=socket.socket", - ] - for pattern in reverse_shell_patterns: - if re.search(pattern, code): - issue_key = ("Reverse Shell", 1, 1) - if issue_key not in self.reported_issues: - self.results["critical"].append( - { - "keyword": "Reverse Shell", - "description": "Обнаружена попытка создания обратного шелла", - "relevance": "Критическая", - "line": 1, - "col": 1, - } - ) - self.reported_issues.add(issue_key) - except Exception as e: - logger.error(f"Error in _check_reverse_shell: {e}") - - def _check_file_operations(self, code: str): - """Обнаружение потенциально опасных операций с файлами.""" - dangerous_file_paths = [ - "/etc/passwd", - "/etc/shadow", - "/etc/hosts", - "/etc/sudoers", - ] - for file_path in dangerous_file_paths: - if file_path in code: - issue_key = ("File Override", 1, 1) - if issue_key not in self.reported_issues: - self.results["critical"].append( - { - "keyword": "File Override", - "description": f"Попытка записи в критический файл: {file_path}", - "relevance": "Критическая", - "line": 1, - "col": 1, - } - ) - self.reported_issues.add(issue_key) - - if "shutil.rmtree" in code: - issue_key = ("Recursive Delete", 1, 1) - if issue_key not in self.reported_issues: - self.results["warning"].append( - { - "keyword": "Recursive Delete", - "description": "Обнаружено рекурсивное удаление каталога", - "relevance": "Средняя", - "line": 1, - "col": 1, - } - ) - self.reported_issues.add(issue_key) - - executable_extensions = [".py", ".sh", ".bat", ".exe"] - for ext in executable_extensions: - if f"open(..., '{ext}'" in code or f"open(... + '{ext}'" in code: - issue_key = ("Executable File Creation", 1, 1) - if issue_key not in self.reported_issues: - self.results["warning"].append( - { - "keyword": "Executable File Creation", - "description": f"Обнаружено создание файла с расширением {ext}", - "relevance": "Средняя", - "line": 1, - "col": 1, - } - ) - self.reported_issues.add(issue_key) - - def _format_report(self, strings: dict, original_code: str) -> str: - """Форматирует отчет об анализе.""" - report = strings["report_header"] - - if self.is_decoded: - report += "<b>⚠️ Код был расшифрован перед анализом.</b>\n\n" - else: - report += "<b>⚠️ Анализ проводился над исходным кодом, расшифровка не удалась.</b>\n\n" - - total_issues = 0 - for severity, issues in self.results.items(): - if issues: - report += strings[f"{severity}_header"] - total_issues += len(issues) - for issue in issues: - report += strings["issue_format"].format( - keyword=issue["keyword"], - description=issue["description"], - relevance=issue["relevance"], - line=issue["line"], - col=issue["col"], - ) - report += "\n" - - if total_issues == 0: - report += strings["no_issues"] - else: - report += strings["report_footer"].format(count=total_issues) - - return report - - -@loader.tds -class UserbotAvast(loader.Module): - """A module for checking modules for security.""" - - strings = { - "name": "UserbotAvast", - "cfg_ai_enabled": "Включить анализ с помощью AI (g4f)", - "cfg_lingva_url": "Анализирует Python-код модуля на предмет потенциальных угроз безопасности, включая обфускацию и эвристические признаки.", - "report_header": "<b>🛡️ Отчет об анализе безопасности модуля:</b>\n\n", - "critical_header": "<b>🔴 Критические угрозы:</b>\n", - "warning_header": "<b>🟠 Предупреждения:</b>\n", - "info_header": "<b>🔵 Информация:</b>\n", - "issue_format": " - ⚠️ <code>{keyword}</code>: {description} (Важность: {relevance}, Строка: {line}, Позиция: {col})\n", - "no_issues": "✅ Не обнаружено проблем безопасности.\n", - "report_footer": "\nВсего обнаружено {count} проблем.\n", - "syntax_error": "❌ Ошибка синтаксиса в коде: {error}\n", - "loading": "⏳ Запуск анализатора безопасности...", - "no_module": "⚠️ Не удалось получить код модуля. Убедитесь, что ссылка верна или прикрепите файл к сообщению.", - "decoding_error": "⚠️ Обнаружен зашифрованный код, но не удалось его расшифровать.", - } - - strings_ru = { - "cfg_ai_enabled": "Включить анализ с помощью AI (g4f)", - "cfg_lingva_url": "Анализирует Python-код модуля на предмет потенциальных угроз безопасности, включая обфускацию и эвристические признаки.", - "report_header": "<b>🛡️ Отчет об анализе безопасности модуля:</b>\n\n", - "critical_header": "<b>🔴 Критические угрозы:</b>\n", - "warning_header": "<b>🟠 Предупреждения:</b>\n", - "info_header": "<b>🔵 Информация:</b>\n", - "issue_format": " - ⚠️ <code>{keyword}</code>: {description} (Важность: {relevance}, Строка: {line}, Позиция: {col})\n", - "no_issues": "✅ Не обнаружено проблем безопасности.\n", - "report_footer": "\nВсего обнаружено {count} проблем.\n", - "syntax_error": "❌ Ошибка синтаксиса в коде: {error}\n", - "loading": "⏳ Запуск анализатора безопасности...", - "no_module": "⚠️ Не удалось получить код модуля. Убедитесь, что ссылка верна или прикрепите файл к сообщению.", - "decoding_error": "⚠️ Обнаружен зашифрованный код, но не удалось его расшифровать.", - } - - def __init__(self): - self.config = loader.ModuleConfig( - loader.ConfigValue( - "ai_enabled", - False, - lambda: self.strings["cfg_ai_enabled"], - validator=loader.validators.Boolean(), - ), - ) - - async def client_ready(self, client, db): - """Вызывается при готовности клиента.""" - self.client = client - self.db = db - self.security_analyzer = SecurityAnalyzer(self.config["ai_enabled"]) - - @loader.unrestricted - @loader.ratelimit - async def checkmodcmd(self, message): - """ - [module_link] или [reply file] или [send file] - выполняет проверку модуля на безопасность. - """ - await utils.answer(message, self.strings["loading"]) - args = utils.get_args_raw(message) - code = None - - if args: - code = await self._get_code_from_url(args) - - if not code: - code = await self._get_code_from_message(message) - - if not code: - await utils.answer(message, self.strings["no_module"]) - return - - try: - result = await self.security_analyzer.analyze(code, self.strings) - await utils.answer(message, result) - except Exception as e: - logger.exception("Error during analysis") - await utils.answer(message, f"An error occurred during analysis: {e}") - - async def _get_code_from_url(self, url: str) -> str or None: - """Получает код модуля по URL.""" - try: - response = await utils.run_sync(requests.get, url) - response.raise_for_status() - return response.text - - except requests.exceptions.RequestException as e: - logger.error(f"Ошибка при получении кода из URL: {e}") - return None - - async def _get_code_from_message(self, message) -> str or None: - """Получает код модуля из прикрепленного файла или ответа на сообщение.""" - try: - if message.media: - code = (await self.client.download_file(message.media, bytes)).decode( - "utf-8" - ) - return code - - reply = await message.get_reply_message() - if reply and reply.media: - code = (await self.client.download_file(reply.media, bytes)).decode( - "utf-8" - ) - return code - except Exception as e: - logger.error(f"Ошибка при получении кода из сообщения: {e}") - return None diff --git a/archquise/H.Modules/Video2GIF.py b/archquise/H.Modules/Video2GIF.py deleted file mode 100644 index f13da6b..0000000 --- a/archquise/H.Modules/Video2GIF.py +++ /dev/null @@ -1,132 +0,0 @@ -# Proprietary License Agreement - -# Copyright (c) 2024-29 CodWiz - -# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions: - -# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author. - -# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author. - -# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software. - -# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software. - -# 5. By using the Software, you agree to be bound by the terms and conditions of this license. - -# For any inquiries or requests for permissions, please contact codwiz@yandex.ru. - -# --------------------------------------------------------------------------------- -# Name: Video2GIF -# Description: Converts video to GIF -# Author: @hikka_mods -# --------------------------------------------------------------------------------- -# meta developer: @hikka_mods -# scope: Video2GIF -# scope: Video2GIF 0.0.1 -# --------------------------------------------------------------------------------- - -import asyncio -import logging -import os -import shutil -import tempfile - -from .. import loader, utils - -logger = logging.getLogger(__name__) - - -@loader.tds -class Video2GIFMod(loader.Module): - """Convert video to high quality GIF""" - - strings = { - "name": "Video2GIF", - "success": "✅ GIF created", - "error": "❌ Conversion failed", - "no_video": "❌ Reply to a video", - "no_ffmpeg": "❌ FFmpeg not installed. Install: apt install ffmpeg", - "processing": "🔄 Processing video...", - "compressing": "📦 Optimizing GIF...", - } - - strings_ru = { - "success": "✅ GIF создан", - "error": "❌ Ошибка конвертации", - "no_video": "❌ Ответьте на видео", - "no_ffmpeg": "❌ FFmpeg не установлен. Установите: apt install ffmpeg", - "processing": "🔄 Обрабатываю видео...", - "compressing": "📦 Оптимизирую GIF...", - } - - def __init__(self): - self._ffmpeg_check = None - - async def client_ready(self, client, db): - self._client = client - self._db = db - self._check_ffmpeg() - - def _check_ffmpeg(self): - self._ffmpeg_check = shutil.which("ffmpeg") is not None - - @loader.command( - ru_doc="[ответ] [fps] [ширина] - конвертировать видео в GIF", - en_doc="[reply] [fps] [width] - convert video to GIF", - ) - async def gifc(self, message): - """Convert video to GIF""" - if not self._ffmpeg_check: - return await utils.answer(message, self.strings["no_ffmpeg"]) - - reply = await message.get_reply_message() - if not reply or not reply.video: - return await utils.answer(message, self.strings["no_video"]) - - args = utils.get_args_raw(message).split() - fps = 15 if len(args) < 1 else min(int(args[0]), 30) - width = 480 if len(args) < 2 else min(int(args[1]), 1024) - - msg = await utils.answer(message, self.strings["processing"]) - - try: - gif_path = await self._convert_to_gif(reply, fps, width) - - await self._client.send_file( - message.chat_id, - gif_path, - caption=self.strings["success"], - reply_to=reply.id, - ) - - os.remove(gif_path) - await msg.delete() - - except Exception: - await utils.answer(message, self.strings["error"]) - - async def _convert_to_gif(self, reply, fps: int, width: int) -> str: - """Convert video to optimized GIF""" - with tempfile.TemporaryDirectory() as tmpdir: - video_path = os.path.join(tmpdir, "video.mp4") - gif_path = os.path.join(tmpdir, "output.gif") - - await reply.download_media(video_path) - - cmd = [ - "ffmpeg", - "-i", - video_path, - "-vf", - f"fps={fps},scale={width}:-1:flags=lanczos", - "-lavfi", - "[0:v]split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse", - "-y", - gif_path, - ] - - proc = await asyncio.create_subprocess_exec(*cmd) - await proc.communicate() - - return gif_path diff --git a/archquise/H.Modules/VirusTotal.py b/archquise/H.Modules/VirusTotal.py deleted file mode 100644 index 4f44ce6..0000000 --- a/archquise/H.Modules/VirusTotal.py +++ /dev/null @@ -1,240 +0,0 @@ -# Proprietary License Agreement - -# Copyright (c) 2024-29 CodWiz - -# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions: - -# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author. - -# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author. - -# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of or in connection with the Software or the use or other dealings in the Software. - -# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software. - -# 5. By using the Software, you agree to be bound by the terms and conditions of this license. -# For any inquiries or requests for permissions, please contact codwiz@yandex.ru. - -# --------------------------------------------------------------------------------- -# Name: VirusTotal -# Description: Checks files for viruses using VirusTotal -# Author: @hikka_mods -# --------------------------------------------------------------------------------- -# meta developer: @hikka_mods -# scope: Api VirusTotal -# scope: Api VirusTotal 0.0.1 -# requires: json aiohttp tempfile -# --------------------------------------------------------------------------------- - -import asyncio -import logging -import os -import tempfile -from typing import Any, Dict, Optional - -import aiohttp - -from .. import loader, utils - -logger = logging.getLogger(__name__) - - -@loader.tds -class VirusTotalMod(loader.Module): - """Professional file scanning with VirusTotal""" - - strings = { - "name": "VirusTotal", - "no_file": "🚫 Reply to a file", - "downloading": "📥 Downloading file...", - "uploading": "📤 Uploading to VirusTotal...", - "scanning": "🔍 Scanning in progress...", - "waiting": "⏳ Waiting for analysis...", - "no_key": "🚫 Set VirusTotal API key in config", - "error": "❌ Error during scan", - "size_limit": "📁 File exceeds 32MB limit", - "timeout": "⏰ Scan timeout", - "clean": "✅ File is clean", - "suspicious": "⚠️ Suspicious file", - "malicious": "⛔ Malicious file", - "view_report": "📊 View full report", - "close": "❌ Close", - "engines": "Scan engines", - "detections": "Detections", - "status": "Status", - "completed": "Completed", - "queued": "Queued", - "scan_date": "Scan date", - } - - strings_ru = { - "no_file": "🚫 Ответьте на файл", - "downloading": "📥 Скачиваю файл...", - "uploading": "📤 Загружаю на VirusTotal...", - "scanning": "🔍 Сканирую...", - "waiting": "⏳ Жду анализа...", - "no_key": "🚫 Укажите API ключ в конфиге", - "error": "❌ Ошибка при сканировании", - "size_limit": "📁 Файл больше 32МБ", - "timeout": "⏰ Таймаут сканирования", - "clean": "✅ Файл чистый", - "suspicious": "⚠️ Подозрительный файл", - "malicious": "⛔ Вредоносный файл", - "view_report": "📊 Полный отчёт", - "close": "❌ Закрыть", - "engines": "Антивирусов", - "detections": "Обнаружено", - "status": "Статус", - "completed": "Завершён", - "queued": "В очереди", - "scan_date": "Дата сканирования", - } - - def __init__(self): - self.config = loader.ModuleConfig( - loader.ConfigValue( - "api_key", - None, - "VirusTotal API key from https://virustotal.com", - validator=loader.validators.Hidden(), - ) - ) - self.session: Optional[aiohttp.ClientSession] = None - self.MAX_SIZE = 32 * 1024 * 1024 # 32MB - self.TIMEOUT = 120 # seconds - - async def client_ready(self, client, db): - self._client = client - self._db = db - - async def on_unload(self): - if self.session: - await self.session.close() - - def _get_session(self) -> aiohttp.ClientSession: - """Get or create aiohttp session with API key""" - if not self.session: - headers = {"x-apikey": self.config["api_key"]} - self.session = aiohttp.ClientSession(headers=headers) - return self.session - - @loader.command( - ru_doc="[ответ] - просканировать файл через VirusTotal", - en_doc="[reply] - scan file with VirusTotal", - ) - async def vt(self, message): - """Scan file with VirusTotal""" - api_key = self.config["api_key"] - if not api_key: - return await utils.answer(message, self.strings["no_key"]) - - reply = await message.get_reply_message() - if not reply or not reply.document: - return await utils.answer(message, self.strings["no_file"]) - - async with self._get_session() as session: - try: - msg = await utils.answer(message, self.strings["downloading"]) - - with tempfile.TemporaryDirectory() as tmpdir: - file_path = os.path.join(tmpdir, reply.file.name) - await reply.download_media(file_path) - - file_size = os.path.getsize(file_path) - if file_size > self.MAX_SIZE: - return await msg.edit(self.strings["size_limit"]) - - await msg.edit(self.strings["uploading"]) - analysis_id = await self._upload_file(session, file_path) - - await msg.edit(self.strings["waiting"]) - result = await self._wait_for_analysis(session, analysis_id) - - await self._show_results(msg, analysis_id, result) - - except asyncio.TimeoutError: - await utils.answer(message, self.strings["timeout"]) - except Exception as e: - error_text = f"{self.strings['error']}: {str(e)[:100]}" - await utils.answer(message, error_text) - - async def _upload_file(self, session: aiohttp.ClientSession, path: str) -> str: - """Upload file to VirusTotal and return analysis ID""" - with open(path, "rb") as f: - form = aiohttp.FormData() - form.add_field("file", f, filename=os.path.basename(path)) - - async with session.post( - "https://www.virustotal.com/api/v3/files", data=form - ) as response: - response.raise_for_status() - data = await response.json() - return data["data"]["id"] - - async def _wait_for_analysis( - self, session: aiohttp.ClientSession, analysis_id: str - ) -> Dict[str, Any]: - """Poll analysis results until completion""" - url = f"https://www.virustotal.com/api/v3/analyses/{analysis_id}" - - for _ in range(20): - async with session.get(url) as response: - response.raise_for_status() - data = await response.json() - - status = data["data"]["attributes"]["status"] - if status == "completed": - return data - - await asyncio.sleep(3) - - raise asyncio.TimeoutError() - - async def _show_results(self, message, analysis_id: str, result: Dict[str, Any]): - """Display scan results in inline form""" - stats = result["data"]["attributes"]["stats"] - date = result["data"]["attributes"]["date"] - - malicious = stats.get("malicious", 0) - suspicious = stats.get("suspicious", 0) - undetected = stats.get("undetected", 0) - harmless = stats.get("harmless", 0) - total = malicious + suspicious + undetected + harmless - - if malicious > 0: - verdict = self.strings["malicious"] - emoji = "⛔" - elif suspicious > 0: - verdict = self.strings["suspicious"] - emoji = "⚠️" - else: - verdict = self.strings["clean"] - emoji = "✅" - - from datetime import datetime - - scan_date = datetime.fromtimestamp(date).strftime("%Y-%m-%d %H:%M:%S") - - text = ( - f"{emoji} <b>VirusTotal Scan Results</b>\n\n" - f"<b>{self.strings['status']}:</b> {verdict}\n" - f"<b>{self.strings['detections']}:</b> {malicious}\n" - f"<b>{self.strings['engines']}:</b> {total}\n" - f"<b>{self.strings['scan_date']}:</b> {scan_date}\n\n" - f"<code>Malicious: {malicious}/{total}</code>\n" - f"<code>Suspicious: {suspicious}/{total}</code>\n" - f"<code>Harmless: {harmless}/{total}</code>\n" - f"<code>Undetected: {undetected}/{total}</code>" - ) - - vt_url = f"https://www.virustotal.com/gui/file-analysis/{analysis_id}" - - await self.inline.form( - text=text, - message=message, - reply_markup=[ - [{"text": f"🔗 {self.strings['view_report']}", "url": vt_url}], - [{"text": self.strings["close"], "action": "close"}], - ], - ttl=300, # 5 minutes timeout - ) diff --git a/archquise/H.Modules/VoiceDL.py b/archquise/H.Modules/VoiceDL.py deleted file mode 100644 index 167c0b2..0000000 --- a/archquise/H.Modules/VoiceDL.py +++ /dev/null @@ -1,118 +0,0 @@ -# Proprietary License Agreement - -# Copyright (c) 2024-29 CodWiz - -# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions: - -# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author. - -# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author. - -# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software. - -# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software. - -# 5. By using the Software, you agree to be bound by the terms and conditions of this license. - -# For any inquiries or requests for permissions, please contact codwiz@yandex.ru. - -# --------------------------------------------------------------------------------- -# Name: VoiceDL -# Description: Voice Downloader module -# Author: @hikka_mods -# --------------------------------------------------------------------------------- -# meta developer: @hikka_mods -# scope: VoiceDL -# scope: VoiceDL 0.0.1 -# requires: tempfile -# --------------------------------------------------------------------------------- - -import asyncio -import logging -import os -import shutil -import tempfile - -from .. import loader, utils - -logger = logging.getLogger(__name__) - - -@loader.tds -class VoiceDLMod(loader.Module): - """Download voice messages as MP3""" - - strings = { - "name": "VoiceDL", - "success": "✅ Voice downloaded as MP3", - "error": "❌ Error downloading voice", - "no_voice": "❌ Reply to a voice message", - "no_ffmpeg": "❌ FFmpeg not found. Install: apt install ffmpeg", - } - - strings_ru = { - "success": "✅ Голосовое скачано как MP3", - "error": "❌ Ошибка скачивания", - "no_voice": "❌ Ответьте на голосовое", - "no_ffmpeg": "❌ FFmpeg не установлен. Установите: apt install ffmpeg", - } - - def __init__(self): - self._ffmpeg_check = None - - async def client_ready(self, client, db): - self._client = client - self._db = db - self._check_ffmpeg() - - def _check_ffmpeg(self): - self._ffmpeg_check = shutil.which("ffmpeg") is not None - - @loader.command( - ru_doc="[ответ] - скачать голосовое как MP3", - en_doc="[reply] - download voice as MP3", - ) - async def voicedl(self, message): - if not self._ffmpeg_check: - return await utils.answer(message, self.strings["no_ffmpeg"]) - - reply = await message.get_reply_message() - if not reply or not reply.voice: - return await utils.answer(message, self.strings["no_voice"]) - - await self._process_voice(message, reply) - - async def _process_voice(self, message, reply): - with tempfile.TemporaryDirectory() as tmpdir: - try: - ogg_path = os.path.join(tmpdir, "voice.ogg") - mp3_path = os.path.join(tmpdir, "voice.mp3") - - await reply.download_media(file=ogg_path) - - proc = await asyncio.create_subprocess_exec( - "ffmpeg", - "-i", - ogg_path, - "-codec:a", - "libmp3lame", - "-q:a", - "2", - mp3_path, - stdout=asyncio.subprocess.DEVNULL, - stderr=asyncio.subprocess.DEVNULL, - ) - await proc.communicate() - - if proc.returncode != 0: - raise Exception("FFmpeg error") - - await message.client.send_file( - message.chat.id, - mp3_path, - caption=self.strings["success"], - reply_to=reply.id, - ) - - except Exception: - await utils.answer(message, self.strings["error"]) diff --git a/archquise/H.Modules/Weather.py b/archquise/H.Modules/Weather.py deleted file mode 100644 index 7bb7103..0000000 --- a/archquise/H.Modules/Weather.py +++ /dev/null @@ -1,333 +0,0 @@ -# Proprietary License Agreement - -# Copyright (c) 2024-29 CodWiz - -# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions: - -# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author. - -# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author. - -# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software. - -# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software. - -# 5. By using the Software, you agree to be bound by the terms and conditions of this license. - -# For any inquiries or requests for permissions, please contact codwiz@yandex.ru. - -# --------------------------------------------------------------------------------- -# Name: Weather -# Description: Advanced weather module with detailed information -# Author: @hikka_mods -# --------------------------------------------------------------------------------- -# meta developer: @hikka_mods -# scope: api Weather -# scope: api Weather 0.0.1 -# --------------------------------------------------------------------------------- - -import logging -from dataclasses import dataclass -from datetime import datetime -from typing import Dict, List, Union - -import requests - -from .. import loader, utils - -logger = logging.getLogger(__name__) - -DEFAULT_FORECAST_DAYS = 3 -DEFAULT_HOURLY_INDEX = 4 -WEATHER_API_URL = "https://wttr.in/{city}?format=j1&lang=en" - - -@dataclass -class WeatherCondition: - """Represents a weather condition with its emoji.""" - - condition: str - emoji: str - - -@dataclass -class WindDirection: - """Represents a wind direction with its description.""" - - direction: str - description: str - - -@dataclass -class ForecastDay: - """Represents a single day's weather forecast.""" - - date: str - emoji: str - condition: str - temp_min: str - temp_max: str - wind_speed: str - wind_direction: str - - -WEATHER_EMOJI: List[WeatherCondition] = [ - WeatherCondition("clear", "<emoji document_id=5402477260982731644>☀️</emoji>"), - WeatherCondition("sunny", "<emoji document_id=5402477260982731644>☀️</emoji>"), - WeatherCondition( - "partly cloudy", "<emoji document_id=5350424168615649565>⛅️</emoji>" - ), - WeatherCondition("cloudy", "☁️<emoji document_id=5208563370218762357>☁️</emoji>"), - WeatherCondition("overcast", "<emoji document_id=5208563370218762357>☁️</emoji>"), - WeatherCondition("mist", "<emoji document_id=5449510395574229527>😶‍🌫️</emoji>"), - WeatherCondition("fog", "<emoji document_id=5449510395574229527>😶‍🌫️</emoji>"), - WeatherCondition("light rain", "<emoji document_id=5283097055852503586>🌦</emoji>"), - WeatherCondition("rain", "<emoji document_id=5283243028905994049>🌧</emoji>"), - WeatherCondition("heavy rain", "<emoji document_id=5282939632416206153>⛈</emoji>"), - WeatherCondition( - "thunderstorm", "<emoji document_id=5282939632416206153>⛈</emoji>" - ), - WeatherCondition("snow", "<emoji document_id=5282833267551117457>🌨</emoji>"), - WeatherCondition("heavy snow", "<emoji document_id=5449449325434266744>❄️</emoji>"), - WeatherCondition("sleet", "<emoji document_id=5282833267551117457>🌨</emoji>"), - WeatherCondition("wind", "💨"), -] - -WIND_DIRECTIONS: List[WindDirection] = [ - WindDirection("N", "⬆️ North"), - WindDirection("NE", "↗️ Northeast"), - WindDirection("E", "➡️ East"), - WindDirection("SE", "↘️ Southeast"), - WindDirection("S", "⬇️ South"), - WindDirection("SW", "↙️ Southwest"), - WindDirection("W", "⬅️ West"), - WindDirection("NW", "↖️ Northwest"), -] - -WIND_DIRECTIONS_RU: List[WindDirection] = [ - WindDirection("N", "⬆️ Северный"), - WindDirection("NE", "↗️ Северо-восточный"), - WindDirection("E", "➡️ Восточный"), - WindDirection("SE", "↘️ Юго-восточный"), - WindDirection("S", "⬇️ Южный"), - WindDirection("SW", "↙️ Юго-западный"), - WindDirection("W", "⬅️ Западный"), - WindDirection("NW", "↖️ Северо-западный"), -] - - -@loader.tds -class Weather(loader.Module): - """Advanced weather module with detailed information""" - - strings = { - "name": "Weather", - "no_city": "🚫 <b>Please specify a city</b>", - "invalid_city": "🚫 <b>City not found</b>", - "loading": "🔄 <b>Fetching weather data for {}</b>...", - "error": "<emoji document_id=5980953710157632545>❌</emoji> <b>Error retrieving weather data</b>", - "default_city": "<emoji document_id=5980930633298350051>✅</emoji> Default city set to: <code>{city}</code>", - "weather_text": """<b>{emoji} Weather: {location}</b> - -<b>📊 Current conditions:</b> -├ 🌡 Temperature: <code>{temp}°C</code> -├– <i>Feels like:</i> <code>{feels_like}°C</code> -├ 💧 Humidity: <code>{humidity}%</code> -├ 💨 Wind: <code>{wind_speed} km/h</code> {wind_direction} -├ 🌪 Pressure: <code>{pressure} mmHg</code> -├ 👁 Visibility: <code>{visibility} km</code> -└ ☁️ Cloudiness: <code>{clouds}</code> - -<b>🌅 Time:</b> -├ 🌅 Sunrise: <code>{sunrise}</code> -├ 🌇 Sunset: <code>{sunset}</code> -└ ⏱ Local time: <code>{local_time}</code> - -<b>📅 Forecast for {forecast_days} days:</b> -{forecast} -⏰ Updated: <code>{updated}</code>""", - "forecast_day": """<b>{date}</b> {emoji} -├ 🌡 Temperature: {temp_min}°C ... {temp_max}°C -└ 💨 Wind: {wind_speed} km/h {wind_direction} - -""", - } - - strings_ru = { - "no_city": "🚫 <b>Пожалуйста, укажите город</b>", - "invalid_city": "🚫 <b>Город не найден</b>", - "loading": "🔄 <b>Получаю метеоданные для {}</b>...", - "default_city": "<emoji document_id=5980930633298350051>✅</emoji> Город по умолчанию установлен: <code>{city}</code>", - "error": "<emoji document_id=5980953710157632545>❌</emoji> <b>Ошибка при получении данных о погоде</b>", - "weather_text": """<b>{emoji} Погода: {location}</b> - -<b>📊 Текущие условия:</b> -├ 🌡 Температура: <code>{temp}°C</code> -├– <i>Ощущается как:</i> <code>{feels_like}°C</code> -├ 💧 Влажность: <code>{humidity}%</code> -├ 💨 Ветер: <code>{wind_speed} км/ч</code> {wind_direction} -├ 🌪 Давление: <code>{pressure} мм.рт.ст</code> -├ 👁 Видимость: <code>{visibility} км</code> -└ ☁️ Облачность: <code>{clouds}</code> - -<b>🌅 Время:</b> -├ 🌅 Восход: <code>{sunrise}</code> -├ 🌇 Закат: <code>{sunset}</code> -└ ⏱ Местное время: <code>{local_time}</code> - -<b>📅 Прогноз на {forecast_days} дня:</b> -{forecast} -⏰ Обновлено: <code>{updated}</code>""", - "forecast_day": """<b>{date}</b> {emoji} -├ 🌡 Температура: {temp_min}°C ... {temp_max}°C -└ 💨 Ветер: {wind_speed} км/ч {wind_direction} - -""", - } - - def __init__(self): - self.config = loader.ModuleConfig( - loader.ConfigValue( - "default_city", - None, - lambda: "Default city for weather command", - ), - loader.ConfigValue( - "language", - "ru", - lambda: "Language for weather output (en/ru)", - ), - ) - - def get_weather_emoji(self, condition: str) -> str: - """Get emoji for weather conditions""" - condition = condition.lower() - for item in WEATHER_EMOJI: - if item.condition in condition: - return item.emoji - return "🌡" - - def get_wind_direction(self, direction: str) -> str: - """Get wind direction description""" - lang = self.config["language"] - directions = WIND_DIRECTIONS_RU if lang == "ru" else WIND_DIRECTIONS - for item in directions: - if item.direction == direction.upper(): - return item.description - return direction - - async def get_weather_data(self, city: str) -> Union[Dict, None]: - """Get weather data from wttr.in""" - lang = self.config["language"] - url = WEATHER_API_URL.format(city=city) - if lang == "ru": - url = f"https://wttr.in/{city}?format=j1&lang=ru" - try: - response = await utils.run_sync(requests.get, url) - response.raise_for_status() - return response.json() - except requests.exceptions.RequestException as e: - logger.error(f"Failed to fetch weather data for {city}: {e}") - return None - except Exception as e: - logger.exception(f"Error fetching weather data: {e}") - return None - - def format_forecast(self, forecast_data: list) -> str: - """Format weather forecast for multiple days.""" - forecast_text = "" - for day in forecast_data: - hourly = day["hourly"][DEFAULT_HOURLY_INDEX] - forecast_day = ForecastDay( - date=day["date"], - emoji=self.get_weather_emoji(hourly["weatherDesc"][0]["value"]), - condition=hourly["weatherDesc"][0]["value"], - temp_min=day["mintempC"], - temp_max=day["maxtempC"], - wind_speed=hourly["windspeedKmph"], - wind_direction=self.get_wind_direction(hourly["winddir16Point"]), - ) - - forecast_text += self.strings("forecast_day").format( - date=forecast_day.date, - emoji=forecast_day.emoji, - condition=forecast_day.condition, - temp_min=forecast_day.temp_min, - temp_max=forecast_day.temp_max, - wind_speed=forecast_day.wind_speed, - wind_direction=forecast_day.wind_direction, - ) - return forecast_text - - async def process_weather_data(self, weather_data: Dict) -> str: - """Process weather data and format the text.""" - current = weather_data["current_condition"][0] - forecast = weather_data["weather"] - location = ( - f"{weather_data['nearest_area'][0]['areaName'][0]['value']}, " - f"{weather_data['nearest_area'][0]['country'][0]['value']}" - ) - - forecast_text = self.format_forecast(forecast[:DEFAULT_FORECAST_DAYS]) - - return self.strings("weather_text").format( - location=location, - emoji=self.get_weather_emoji(current["weatherDesc"][0]["value"]), - temp=current["temp_C"], - feels_like=current["FeelsLikeC"], - humidity=current["humidity"], - wind_speed=current["windspeedKmph"], - wind_direction=self.get_wind_direction(current["winddir16Point"]), - pressure=current["pressure"], - visibility=current["visibility"], - clouds=current["weatherDesc"][0]["value"], - sunrise=forecast[0]["astronomy"][0]["sunrise"], - sunset=forecast[0]["astronomy"][0]["sunset"], - local_time=current["observation_time"], - forecast=forecast_text, - forecast_days=DEFAULT_FORECAST_DAYS, - updated=datetime.now().strftime("%Y-%m-%d %H:%M:%S"), - ) - - @loader.command( - ru_doc="Узнайте погоду для указанного города", - en_doc="Get the weather for the specified city", - ) - async def weather(self, message): - city = utils.get_args_raw(message) or self.config["default_city"] - if not city: - await utils.answer(message, self.strings("no_city")) - return - - await utils.answer(message, self.strings("loading").format(city)) - - weather_data = await self.get_weather_data(city) - if not weather_data: - await utils.answer(message, self.strings("error")) - return - - try: - weather_text = await self.process_weather_data(weather_data) - await utils.answer(message, weather_text) - - except Exception as e: - logger.exception(f"Error processing weather data: {e}") - await utils.answer(message, self.strings("error")) - - @loader.command( - ru_doc="Установите город по умолчанию для определения погоды", - en_doc="Set the default city for weather", - ) - async def weatherset(self, message): - city = utils.get_args_raw(message) - if not city: - await utils.answer(message, self.strings("no_city")) - return - - weather_data = await self.get_weather_data(city) - if not weather_data: - await utils.answer(message, self.strings("invalid_city")) - return - - self.config["default_city"] = city - await utils.answer(message, self.strings("default_city").format(city=city)) diff --git a/archquise/H.Modules/_site/q b/archquise/H.Modules/_site/q deleted file mode 100644 index 8b13789..0000000 --- a/archquise/H.Modules/_site/q +++ /dev/null @@ -1 +0,0 @@ - diff --git a/archquise/H.Modules/aiogram3/hikarichat.py b/archquise/H.Modules/aiogram3/hikarichat.py deleted file mode 100644 index acb1b13..0000000 --- a/archquise/H.Modules/aiogram3/hikarichat.py +++ /dev/null @@ -1,6206 +0,0 @@ -__version__ = (13, 0, 3) - -# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀ -# █▀█ █ █ █ █▀█ █▀▄ █ -# © Copyright 2022 -# https://t.me/hikariatama -# -# 🔒 Licensed under the GNU AGPLv3 -# 🌐 https://www.gnu.org/licenses/agpl-3.0.html - -# meta pic: https://static.dan.tatar/hikarichat_icon.png -# meta banner: https://mods.hikariatama.ru/badges/hikarichat.jpg -# meta desc: Chat administrator toolkit, now with powerful free version -# meta developer: @hikarimods - -# scope: disable_onload_docs -# scope: inline -# scope: hikka_min 1.7.0 -# requires: aiohttp websockets - -import abc -import asyncio -import contextlib -import functools -import imghdr -import io -import json -import logging -import random -import re -import time -import typing -from math import ceil -from types import FunctionType - -import aiohttp -import requests -import websockets -from aiogram.types import CallbackQuery, ChatPermissions -from aiogram.exceptions import TelegramAPIError -from telethon.errors import ChatAdminRequiredError, UserAdminInvalidError -from telethon.errors.rpcerrorlist import WebpageCurlFailedError -from telethon.tl.functions.channels import ( - EditAdminRequest, - EditBannedRequest, - GetFullChannelRequest, - GetParticipantRequest, - InviteToChannelRequest, -) -from telethon.tl.functions.messages import EditChatDefaultBannedRightsRequest -from telethon.tl.types import ( - Channel, - ChannelParticipantCreator, - Chat, - ChatAdminRights, - ChatBannedRights, - DocumentAttributeAnimated, - Message, - MessageEntitySpoiler, - MessageMediaUnsupported, - User, - UserStatusOnline, -) - -from .. import loader, utils -from ..inline.types import InlineCall, InlineMessage - -try: - from PIL import Image, ImageDraw, ImageFont -except ImportError: - PIL_AVAILABLE = False -else: - PIL_AVAILABLE = True - -logger = logging.getLogger(__name__) - -version = f"v{__version__[0]}.{__version__[1]}.{__version__[2]}stable" -ver = f"<i>HikariChat {version}</i>" - -FLOOD_TIMEOUT = 0.8 -FLOOD_TRESHOLD = 4 - - -PROTECTS = { - "antinsfw": "🔞 AntiNSFW", - "antiarab": "🇵🇸 AntiArab", - "antitagall": "🐵 AntiTagAll", - "antihelp": "🐺 AntiHelp", - "antiflood": "⏱ AntiFlood", - "antichannel": "📯 AntiChannel", - "antispoiler": "👻 AntiSpoiler", - "report": "📣 Report", - "antiexplicit": "🤬 AntiExplicit", - "antiservice": "⚙️ AntiService", - "antigif": "🎑 AntiGIF", - "antizalgo": "🌀 AntiZALGO", - "antistick": "🎨 AntiStick", - "antilagsticks": "⚰️ AntiLagSticks", - "cas": "🛡 CAS", - "bnd": "💬 BND", - "antiraid": "🚪 AntiRaid", - "banninja": "🥷 BanNinja", - "welcome": "👋 Welcome", - "captcha": "🚥 Captcha", -} - - -def fit(line: str, max_size: int) -> str: - if len(line) >= max_size: - return line - - offsets_sum = max_size - len(line) - - return f"{' ' * ceil(offsets_sum / 2 - 1)}{line}{' ' * int(offsets_sum / 2 - 1)}" - - -def gen_table(t: typing.List[typing.List[str]]) -> bytes: - table = "" - header = t[0] - rows_sizes = [len(i) + 2 for i in header] - for row in t[1:]: - rows_sizes = [max(len(j) + 2, rows_sizes[i]) for i, j in enumerate(row)] - - rows_lines = ["━" * i for i in rows_sizes] - - table += f"┏{('┯'.join(rows_lines))}┓\n" - - for line in t: - table += f"┃⁣⁣ {' ┃⁣⁣ '.join([fit(row, rows_sizes[k]) for k, row in enumerate(line)])} ┃⁣⁣\n" - table += "┠" - - for row in rows_sizes: - table += f"{'─' * row}┼" - - table = table[:-1] + "┫\n" - - return "\n".join(table.splitlines()[:-1]) + "\n" + f"┗{('┷'.join(rows_lines))}┛\n" - - -def get_first_name(user: typing.Union[User, Channel]) -> str: - """Returns first name of user or channel title""" - return utils.escape_html( - user.first_name if isinstance(user, User) else user.title - ).strip() - - -def get_full_name(user: typing.Union[User, Channel]) -> str: - return utils.escape_html( - user.title - if isinstance(user, Channel) - else ( - f"{user.first_name} " - + (user.last_name if getattr(user, "last_name", False) else "") - ) - ).strip() - - -BANNED_RIGHTS = { - "view_messages": False, - "send_messages": False, - "send_media": False, - "send_stickers": False, - "send_gifs": False, - "send_games": False, - "send_inline": False, - "send_polls": False, - "change_info": False, - "invite_users": False, -} - - -class HikariChatAPI: - def __init__(self): - self._bot = "@hikka_userbot" - - self._queue = [] - self.feds = {} - self.chats = {} - self.variables = {} - self.init_done = asyncio.Event() - self._show_warning = True - self._connected = False - self._inited = False - self._local = False - - async def init( - self, - client: "CustomTelegramClient", # type: ignore - db: "Database", # type: ignore - module: loader.Module, - ): - """Entry point""" - self._client = client - self._db = db - self.module = module - - if not self.module.get("token"): - await self._get_token() - - self._task = asyncio.ensure_future(self._connect()) - await self.init_done.wait() - - async def _wss(self): - async with websockets.connect( - f"wss://hikarichat.hikariatama.ru/ws/{self.module.get('token')}" - ) as wss: - init = json.loads(await wss.recv()) - - logger.debug(f"HikariChat connection debug info {init}") - - if init["event"] == "startup": - self.variables = init["variables"] - elif init["event"] == "license_violation": - await wss.close() - raise Exception("local") - - self.init_done.set() - - logger.debug("HikariChat connected") - self._show_warning = True - self._connected = True - self._inited = True - - while True: - ans = json.loads(await wss.recv()) - - if ans["event"] == "update_info": - self.chats = ans["chats"] - self.feds = ans["feds"] - - await wss.send(json.dumps({"ok": True, "queue": self._queue})) - self._queue = [] - for chat in self.chats: - if str(chat) not in self.module._linked_channels: - channel = ( - await self._client(GetFullChannelRequest(int(chat))) - ).full_chat.linked_chat_id - self.module._linked_channels[str(chat)] = channel or False - - if ans["event"] == "queue_status": - await self._client.edit_message( - ans["chat_id"], - ans["message_id"], - ans["text"], - ) - - async def _connect(self): - while True: - try: - await self._wss() - except Exception: - logger.debug("HikariChat disconnection traceback", exc_info=True) - - if not self._inited: - self._local = True - self.variables = json.loads( - ( - await utils.run_sync( - requests.get, - "https://gist.githubusercontent.com/hikariatama/31a8246c9c6ad0b451324969d6ff2940/raw/608509efd7fee6fa876227e1c8c3c7dc0a952892/variables.json", - ) - ).text - ) - self._feds = self.module.get("feds", {}) - delattr(self, "feds") - self.chats = self.module.get("chats", {}) - self._processor_task = asyncio.ensure_future( - self._queue_processor() - ) - self.init_done.set() - self._task.cancel() - return - - self._connected = False - if self._show_warning: - logger.debug("HikariChat disconnected, retry in 5 sec") - self._show_warning = False - - await asyncio.sleep(5) - - def request(self, payload: dict, message: typing.Optional[Message] = None): - if isinstance(message, Message): - payload = { - **payload, - **{ - "chat_id": utils.get_chat_id(message), - "message_id": message.id, - }, - } - - self._queue += [payload] - - def should_protect(self, chat_id: typing.Union[str, int], protection: str) -> bool: - return ( - str(chat_id) in self.chats - and protection in self.chats[str(chat_id)] - and str(self.chats[str(chat_id)][protection][1]) == str(self.module._tg_id) - ) - - async def nsfw(self, photo: bytes) -> str: - if not self.module.get("token"): - logger.warning("Token is not sent, NSFW check forbidden") - return "sfw" - - async with aiohttp.ClientSession() as session: - async with session.request( - "POST", - "https://hikarichat.hikariatama.ru/check_nsfw", - headers={"Authorization": f"Bearer {self.module.get('token')}"}, - data={"file": photo}, - ) as resp: - r = await resp.text() - - try: - r = json.loads(r) - except Exception: - logger.exception("Failed to check NSFW") - return "sfw" - - if "error" in r and "Rate limit" in r["error"]: - logger.warning("NSFW checker ratelimit exceeded") - return "sfw" - - if "success" not in r: - logger.error(f"API error {json.dumps(r, indent=4)}") - return "sfw" - - return r["verdict"] - - async def _get_token(self): - async with self._client.conversation(self._bot) as conv: - m = await conv.send_message("/token") - r = await conv.get_response() - token = r.raw_text - await m.delete() - await r.delete() - - if not token.startswith("kirito_") and not token.startswith("asuna_"): - raise loader.LoadError("Can't get token") - - self.module.set("token", token) - - await self._client.delete_dialog(self._bot) - - def __getattr__(self, attribute: str): - if self._local and attribute == "feds": - return {fed["shortname"]: fed for fed in self._feds.values()} - - raise AttributeError - - async def _queue_processor(self): - while True: - if not self._queue: - await asyncio.sleep(1) - continue - - ERROR = ( - "<emoji document_id=5300759756669984376>🚫</emoji> <b>API Error:" - " </b><code>{}</code>" - ) - - async def assert_arguments(args: set, item: dict) -> bool: - if any(i not in item.get("args", {}) for i in args): - if "chat_id" in item: - await self._client.edit_message( - item["chat_id"], - item["message_id"], - ( - "<emoji document_id=5300759756669984376>🚫</emoji>" - " <b>Bad API arguments, PROKAZNIK!</b>" - ), - ) - return False - - return True - - async def error(msg: str, item: dict): - if "chat_id" in item: - await self._client.edit_message( - item["chat_id"], - item["message_id"], - ERROR.format(msg), - ) - - feds_copy = self._feds.copy() - chats_copy = self.chats.copy() - - item = self._queue.pop(0) - u = str(self._client._tg_id) - - if item["action"] == "create federation": - if not await assert_arguments({"shortname", "name"}, item): - continue - - t = "fed_" + "".join( - [ - random.choice(list("abcdefghijklmnopqrstuvwyz1234567890")) - for _ in range(32) - ] - ) - - self._feds[t] = { - "shortname": item["args"]["shortname"], - "name": item["args"]["name"], - "chats": [], - "warns": {}, - "admins": [u], - "owner": u, - "fdef": [], - "notes": {}, - "uid": t, - } - - if item["action"] == "add chat to federation": - if not await assert_arguments({"uid", "cid"}, item): - continue - - if item["args"]["uid"] not in self._feds: - await error("Federation doesn't exist", item) - continue - - if str(item["args"]["cid"]) in self._feds[item["args"]["uid"]]["chats"]: - await error("Chat is already in this federation", item) - continue - - self._feds[item["args"]["uid"]]["chats"] += [str(item["args"]["cid"])] - - if item["action"] == "remove chat from federation": - if not await assert_arguments({"uid", "cid"}, item): - continue - - if item["args"]["uid"] not in self._feds: - await error("Federation doesn't exist", item) - continue - - if ( - str(item["args"]["cid"]) - not in self._feds[item["args"]["uid"]]["chats"] - ): - await error("Chat is not in this federation", item) - continue - - self._feds[item["args"]["uid"]]["chats"].remove( - str(item["args"]["cid"]) - ) - - if item["action"] == "update protections": - if not await assert_arguments({"protection", "state", "chat"}, item): - continue - - chat, protection, state = ( - str(item["args"]["chat"]), - item["args"]["protection"], - item["args"]["state"], - ) - - if protection not in self.variables["protections"] + ["welcome"]: - await error("Unknown protection type", item) - continue - - if ( - protection in self.variables["argumented_protects"] - and state not in self.variables["protect_actions"] - or protection not in self.variables["argumented_protects"] - and protection in self.variables["protections"] - and state not in {"on", "off"} - ): - await error("Protection state invalid", item) - continue - - if chat not in self.chats: - self.chats[chat] = {} - - if state == "off": - if protection in self.chats[chat]: - del self.chats[chat][protection] - else: - self.chats[chat][protection] = [state, u] - - if item["action"] == "delete federation": - if not await assert_arguments({"uid"}, item): - continue - - uid = item["args"]["uid"] - - if uid not in self._feds: - await error("Federation doesn't exist", item) - continue - - del self._feds[uid] - - if item["action"] == "rename federation": - if not await assert_arguments({"uid", "name"}, item): - continue - - uid, name = item["args"]["uid"], item["args"]["name"] - - if uid not in self._feds: - await error("Federation doesn't exist", item) - continue - - self._feds[uid]["name"] = name - - if item["action"] == "protect user": - if not await assert_arguments({"uid", "user"}, item): - continue - - uid, user = item["args"]["uid"], item["args"]["user"] - user = str(user) - - if not user.isdigit(): - await error("Unexpected format for user", item) - continue - - if uid not in self._feds: - await error("Federation doesn't exist", item) - continue - - if user in self._feds[uid]["fdef"]: - self._feds[uid]["fdef"].remove(user) - else: - self._feds[uid]["fdef"] += [user] - - if item["action"] == "warn user": - if not await assert_arguments({"uid", "user", "reason"}, item): - continue - - uid, user, reason = ( - item["args"]["uid"], - item["args"]["user"], - item["args"]["reason"], - ) - user = str(user) - - if not user.isdigit(): - await error("Unexpected format for user", item) - continue - - if uid not in self._feds: - await error("Federation doesn't exist", item) - continue - - if user not in self._feds[uid]["warns"]: - self._feds[uid]["warns"][user] = [] - - self._feds[uid]["warns"][user] += [reason] - - if item["action"] == "forgive user warn": - if not await assert_arguments({"uid", "user"}, item): - continue - - uid, user = item["args"]["uid"], item["args"]["user"] - user = str(user) - - if not user.isdigit(): - await error("Unexpected format for user", item) - continue - - if uid not in self._feds: - await error("Federation doesn't exist", item) - continue - - if ( - user not in self._feds[uid]["warns"] - or not self._feds[uid]["warns"][user] - ): - await error("This user has no warns yet", item) - continue - - del self._feds[uid]["warns"][user][-1] - - if item["action"] == "clear all user warns": - if not await assert_arguments({"uid", "user"}, item): - continue - - uid, user = item["args"]["uid"], item["args"]["user"] - user = str(user) - - if not user.isdigit(): - if not item["args"].get("silent", False): - await error("Unexpected format for user", item) - continue - - if uid not in self._feds: - if not item["args"].get("silent", False): - await error("Federation doesn't exist", item) - continue - - if not self._feds[uid].get("warns", {}).get(user): - if not item["args"].get("silent", False): - await error("This user has no warns yet", item) - continue - - del self._feds[uid]["warns"][user] - - if item["action"] == "clear federation warns": - if not await assert_arguments({"uid"}, item): - continue - - uid = item["args"]["uid"] - - if uid not in self._feds: - await error("Federation doesn't exist", item) - continue - - if not self._feds[uid].get("warns"): - await error("This federation has no warns yet", item) - continue - - del self._feds[uid]["warns"] - - if item["action"] == "new note": - if not await assert_arguments({"uid", "shortname", "note"}, item): - continue - - uid, shortname, note = ( - item["args"]["uid"], - item["args"]["shortname"], - item["args"]["note"], - ) - - if uid not in self._feds: - await error("Federation doesn't exist", item) - continue - - self._feds[uid]["notes"][shortname] = {"creator": u, "text": note} - - if item["action"] == "delete note": - if not await assert_arguments({"uid", "shortname"}, item): - continue - - uid, shortname = item["args"]["uid"], item["args"]["shortname"] - - if uid not in self._feds: - await error("Federation doesn't exist", item) - continue - - if shortname not in self._feds[uid]["notes"]: - await error(f"Note not found ({uid=}, {shortname=})", item) - continue - - del self._feds[uid]["notes"][shortname] - - if feds_copy != self._feds: - self.module.set("feds", self._feds) - - if chats_copy != self.chats: - self.module.set("chats", self.chats) - - -api = HikariChatAPI() - - -def reverse_dict(d: dict) -> dict: - return {val: key for key, val in d.items()} - - -@loader.tds -class HikariChatMod(loader.Module): - """ - Advanced chat admin toolkit - """ - - __metaclass__ = abc.ABCMeta - - strings = { - "name": "HikariChat", - "args": ( - "<emoji document_id=5300759756669984376>🚫</emoji> <b>Args are" - " incorrect</b>" - ), - "no_reason": "Not specified", - "antitagall_on": ( - "<emoji document_id=5785175271011259591>🐵</emoji> <b>AntiTagAll is now on" - " in this chat\nAction: {}</b>" - ), - "antitagall_off": ( - "<emoji document_id=5785175271011259591>🐵</emoji> <b>AntiTagAll is now off" - " in this chat</b>" - ), - "antiarab_on": ( - "<emoji document_id=6323257144745395640>🇵🇸</emoji> <b>AntiArab is now on in" - " this chat\nAction: {}</b>" - ), - "antiarab_off": ( - "<emoji document_id=6323257144745395640>🇵🇸</emoji> <b>AntiArab is now off" - " in this chat</b>" - ), - "antilagsticks_on": ( - "<emoji document_id=5469654973308476699>💣</emoji> <b>Destructive stickers" - " protection is now on in this chat</b>" - ), - "antilagsticks_off": ( - "<emoji document_id=5469654973308476699>💣</emoji> <b>Destructive stickers" - " protection is now off in this chat</b>" - ), - "antizalgo_on": ( - "<emoji document_id=5213293263083018856>🌀</emoji> <b>AntiZALGO is now" - " on in" - " this chat\nAction: {}</b>" - ), - "antizalgo_off": ( - "<emoji document_id=5213293263083018856>🌀</emoji> <b>AntiZALGO is now off" - " in this chat</b>" - ), - "antistick_on": ( - "<emoji document_id=5431456208487716895>🎨</emoji> <b>AntiStick is now" - " on in" - " this chat\nAction: {}</b>" - ), - "antistick_off": ( - "<emoji document_id=5431456208487716895>🎨</emoji> <b>AntiStick is now off" - " in this chat</b>" - ), - "antihelp_on": ( - "<emoji document_id=5467759840463953770>🐺</emoji> <b>AntiHelp is now on in" - " this chat</b>" - ), - "antihelp_off": ( - "<emoji document_id=5467759840463953770>🐺</emoji> <b>AntiHelp is now" - " off in" - " this chat</b>" - ), - "antiraid_on": ( - "<emoji document_id=6334359218593728345><emoji" - " document_id=6037460928423791421>🚪</emoji></emoji> <b>AntiRaid is now on" - " in this chat\nAction: {}</b>" - ), - "antiraid_off": ( - "<emoji document_id=6334359218593728345><emoji" - " document_id=6037460928423791421>🚪</emoji></emoji> <b>AntiRaid is now off" - " in this chat</b>" - ), - "bnd_on": ( - "<emoji document_id=5465300082628763143>💬</emoji> <b>Block-Non-Discussion" - " is now on in this chat\nAction: {}</b>" - ), - "bnd_off": ( - "<emoji document_id=5465300082628763143>💬</emoji> <b>Block-Non-Discussion" - " is now off in this chat</b>" - ), - "antiraid": ( - "<emoji document_id=6334359218593728345><emoji" - " document_id=6037460928423791421>🚪</emoji></emoji> <b>AntiRaid is On." - " I {}" - ' <a href="{}">{}</a> in chat {}</b>' - ), - "antichannel_on": ( - "<emoji document_id=5470094069289984325>📯</emoji> <b>AntiChannel is now on" - " in this chat</b>" - ), - "antichannel_off": ( - "<emoji document_id=5470094069289984325>📯</emoji> <b>AntiChannel is" - " now off" - " in this chat</b>" - ), - "report_on": ( - "<emoji document_id=5213203794619277246>📣</emoji> <b>Report is now on in" - " this chat</b>" - ), - "report_off": ( - "<emoji document_id=5213203794619277246>📣</emoji> <b>Report is now off in" - " this chat</b>" - ), - "antiflood_on": ( - "<emoji document_id=5384611567125928766>⏱</emoji> <b>AntiFlood is now on in" - " this chat\nAction: {}</b>" - ), - "antiflood_off": ( - "<emoji document_id=5384611567125928766>⏱</emoji> <b>AntiFlood is now off" - " in this chat</b>" - ), - "antispoiler_on": ( - "<emoji document_id=5798648862591684122>👻</emoji> <b>AntiSpoiler is now on" - " in this chat</b>" - ), - "antispoiler_off": ( - "<emoji document_id=5798648862591684122>👻</emoji> <b>AntiSpoiler is" - " now off" - " in this chat</b>" - ), - "antigif_on": ( - "<emoji document_id=6048825205730577727>🎑</emoji> <b>AntiGIF is now on in" - " this chat</b>" - ), - "antigif_off": ( - "<emoji document_id=6048825205730577727>🎑</emoji> <b>AntiGIF is now off in" - " this chat</b>" - ), - "antiservice_on": ( - "<emoji document_id=5787237370709413702>⚙️</emoji> <b>AntiService is now on" - " in this chat</b>" - ), - "antiservice_off": ( - "<emoji document_id=5787237370709413702>⚙️</emoji> <b>AntiService is now" - " off in this chat</b>" - ), - "banninja_on": ( - "<emoji document_id=6323575131239089635>🥷</emoji> <b>BanNinja is now on in" - " this chat</b>" - ), - "banninja_off": ( - "<emoji document_id=6323575131239089635>🥷</emoji> <b>BanNinja is now" - " off in" - " this chat</b>" - ), - "antiexplicit_on": ( - "<emoji document_id=5373123633415723713>🤬</emoji> <b>AntiExplicit is" - " now on" - " in this chat\nAction: {}</b>" - ), - "antiexplicit_off": ( - "<emoji document_id=5373123633415723713>🤬</emoji> <b>AntiExplicit is now" - " off in this chat</b>" - ), - "captcha_on": ( - "<emoji document_id=5213107179329953547>🚥</emoji> <b>Captcha is now on in" - " this chat\nAction: {}</b>" - ), - "captcha_off": ( - "<emoji document_id=5213107179329953547>🚥</emoji> <b>Captcha is now off in" - " this chat</b>" - ), - "cas_on": ( - "<emoji document_id=5300855732009180995>🛡</emoji> <b>CAS is now on in this" - " chat\nAction: {}</b>" - ), - "cas_off": ( - "<emoji document_id=5300855732009180995>🛡</emoji> <b>CAS is now off in this" - " chat</b>" - ), - "antinsfw_on": ( - "<emoji document_id=4976982981341086273>🔞</emoji> <b>AntiNSFW is now on in" - " this chat\nAction: {}</b>" - ), - "antinsfw_off": ( - "<emoji document_id=4976982981341086273>🔞</emoji> <b>AntiNSFW is now" - " off in" - " this chat</b>" - ), - "arabic_nickname": ( - '<emoji document_id=6323257144745395640>🇵🇸</emoji> <b><a href="{}">{}</a>' - " has hieroglyphics in his nickname.\n👊 Action: I {}</b>" - ), - "zalgo": ( - '<emoji document_id=5213293263083018856>🌀</emoji> <b><a href="{}">{}</a>' - " has ZALGO in his nickname.\n👊 Action: I {}</b>" - ), - "bnd": ( - '<emoji document_id=5465300082628763143>💬</emoji> <b><a href="{}">{}</a>' - " sent a message to channel comments without being chat member.\n👊 Action:" - " I {}</b>" - ), - "cas": ( - '<emoji document_id=5300855732009180995>🛡</emoji> <b><a href="{}">{}</a>' - " appears to be in Combat Anti Spam database.\n👊 Action: I {}</b>" - ), - "stick": ( - "<emoji document_id=5431456208487716895>🎨</emoji> <b><a" - ' href="{}">{}</a> is' - " flooding stickers.\n👊 Action: I {}</b>" - ), - "explicit": ( - '<emoji document_id=5373123633415723713>🤬</emoji> <b><a href="{}">{}</a>' - " sent explicit content.\n👊 Action: I {}</b>" - ), - "destructive_stick": ( - '<emoji document_id=5300759756669984376>🚫</emoji> <b><a href="{}">{}</a>' - " sent destructive sticker.\n👊 Action: I {}</b>" - ), - "nsfw_content": ( - '<emoji document_id=4976982981341086273>🔞</emoji> <b><a href="{}">{}</a>' - " sent NSFW content.\n👊 Action: I {}</b>" - ), - "flood": ( - '<emoji document_id=5384611567125928766>⏱</emoji> <b><a href="{}">{}</a> is' - " flooding.\n👊 Action: I {}</b>" - ), - "tagall": ( - '<emoji document_id=5785175271011259591>🐵</emoji> <b><a href="{}">{}</a>' - " used TagAll.\n👊 Action: I {}</b>" - ), - "fwarn": ( - "<emoji document_id=5193091781327068499>👮‍♀️</emoji><emoji" - ' document_id=5773781976905421370>💼</emoji> <b><a href="{}">{}</a></b> got' - " {}/{} federative warn\nReason: <b>{}</b>\n\n{}" - ), - "no_fed_warns": ( - "<emoji document_id=5193091781327068499>👮‍♀️</emoji> <b>This federation has" - " no warns yet</b>" - ), - "no_warns": ( - '<emoji document_id=5193091781327068499>👮‍♀️</emoji> <b><a href="{}">{}</a>' - " has no warns yet</b>" - ), - "warns": ( - '<emoji document_id=5193091781327068499>👮‍♀️</emoji> <b><a href="{}">{}</a>' - " has {}/{} warns</b>\n\n<i>{}</i>" - ), - "warns_adm_fed": ( - "<emoji document_id=5193091781327068499>👮‍♀️</emoji> <b>Warns in this" - " federation</b>:\n" - ), - "dwarn_fed": ( - "<emoji document_id=5193091781327068499>👮‍♀️</emoji> <b>Forgave last" - ' federative warn of <a href="tg://user?id={}">{}</a></b>' - ), - "clrwarns_fed": ( - "<emoji document_id=5193091781327068499>👮‍♀️</emoji> <b>Forgave all" - ' federative warns of <a href="tg://user?id={}">{}</a></b>' - ), - "warns_limit": ( - '<emoji document_id=5193091781327068499>👮‍♀️</emoji> <b><a href="{}">{}</a>' - " reached warns limit.\nAction: I {}</b>" - ), - "welcome": ( - "<emoji document_id=5472055112702629499>👋</emoji> <b>Now I will greet" - " people in this chat</b>\n{}" - ), - "unwelcome": ( - "<emoji document_id=5472055112702629499>👋</emoji> <b>Now I will not greet" - " people in this chat</b>" - ), - "chat404": "🔓 <b>I am not protecting this chat yet.</b>\n", - "protections": ( - "<b><emoji document_id=6323257144745395640>🇵🇸</emoji>" - " <code>.AntiArab</code> - Bans spammy arabs\n<b><emoji" - " document_id=5467759840463953770>🐺</emoji> <code>.AntiHelp</code> -" - " Removes frequent userbot commands\n<b><emoji" - " document_id=5785175271011259591>🐵</emoji> <code>.AntiTagAll</code> -" - " Restricts tagging all members\n<b><emoji" - " document_id=5472055112702629499>👋</emoji> <code>.Welcome</code> - Greets" - " new members\n<b><emoji document_id=6334359218593728345><emoji" - " document_id=6037460928423791421>🚪</emoji></emoji> <code>.AntiRaid</code>" - " - Bans all new members\n<b><emoji" - " document_id=5470094069289984325>📯</emoji> <code>.AntiChannel</code> -" - " Restricts writing on behalf of channels\n<b><emoji" - " document_id=5798648862591684122>👻</emoji> <code>.AntiSpoiler</code> -" - " Restricts spoilers\n<b><emoji document_id=6048825205730577727>🎑</emoji>" - " <code>.AntiGIF</code> - Restricts GIFs\n<b>🍓 <code>.AntiNSFW</code> -" - " Restricts NSFW photos and stickers\n<b><emoji" - " document_id=5384611567125928766>⏱</emoji> <code>.AntiFlood</code> -" - " Prevents flooding\n<b><emoji document_id=5373123633415723713>🤬</emoji>" - " <code>.AntiExplicit</code> - Restricts explicit content\n<b><emoji" - " document_id=5787237370709413702>⚙️</emoji> <code>.AntiService</code> -" - " Removes service messages\n<b><emoji" - " document_id=5213293263083018856>🌀</emoji> <code>.AntiZALGO</code> -" - " Penalty for users with ZALGO in nickname\n<b><emoji" - " document_id=5431456208487716895>🎨</emoji> <code>.AntiStick</code> -" - " Prevents stickers flood\n<b><emoji" - " document_id=5213107179329953547>🚥</emoji> <code>.Captcha</code> -" - " Requires every new participant to complete captcha\n<b><emoji" - " document_id=5215470334760721739>🛡</emoji> <code>.CAS</code> - Check every" - " new participant through Combat Anti Spam\n<b><emoji" - " document_id=5465300082628763143>💬</emoji> <code>.BND</code> - Restricts" - " messages from users, which are not a participants of chat" - " (comments)\n<b><emoji document_id=6323575131239089635>🥷</emoji>" - " <code>.BanNinja</code> - Automatic version of AntiRaid\n<b>⚰️" - " <code>.AntiLagSticks</code> - Bans laggy stickers\n<b>👾 Admin:" - " </b><code>.ban</code> <code>.kick</code>" - " <code>.mute</code>\n<code>.unban</code> <code>.unmute</code> <b>- Admin" - " tools</b>\n<b><emoji document_id=5193091781327068499>👮‍♀️</emoji>" - " Warns:</b> <code>.warn</code> <code>.warns</code>\n<code>.dwarn</code>" - " <code>.clrwarns</code> <b>- Warning system</b>\n<b><emoji" - " document_id=5773781976905421370>💼</emoji> Federations:</b>" - " <code>.fadd</code> <code>.frm</code>" - " <code>.newfed</code>\n<code>.namefed</code> <code>.fban</code>" - " <code>.rmfed</code> <code>.feds</code>\n<code>.fpromote</code>" - " <code>.fdemote</code>\n<code>.fdef</code> <code>.fdeflist</code> <b>-" - " Controlling multiple chats</b>\n<b>🗒 Notes:</b> <code>.fsave</code>" - " <code>.fstop</code> <code>.fnotes</code> <b>- Federative notes</b>" - ), - "not_admin": "🤷‍♂️ <b>I'm not admin here, or don't have enough rights</b>", - "mute": ( - '<emoji document_id=5372800046284674872>🤐</emoji> <b><a href="{}">{}</a>' - " was muted {}. Reason: </b><i>{}</i>\n\n{}" - ), - "mute_log": ( - '<emoji document_id=5372800046284674872>🤐</emoji> <b><a href="{}">{}</a>' - ' was muted {} in <a href="{}">{}</a>. Reason: </b><i>{}</i>\n\n{}' - ), - "ban": ( - '<emoji document_id=5247152118069992250>🔒</emoji> <b><a href="{}">{}</a>' - " was banned {}. Reason: </b><i>{}</i>\n\n{}" - ), - "ban_log": ( - '<emoji document_id=5247152118069992250>🔒</emoji> <b><a href="{}">{}</a>' - ' was banned {} in <a href="{}">{}</a>. Reason: </b><i>{}</i>\n\n{}' - ), - "kick": ( - '<emoji document_id=6037460928423791421>🚪</emoji> <b><a href="{}">{}</a>' - " was kicked. Reason: </b><i>{}</i>\n\n{}" - ), - "kick_log": ( - '<emoji document_id=6037460928423791421>🚪</emoji> <b><a href="{}">{}</a>' - ' was kicked in <a href="{}">{}</a>. Reason: </b><i>{}</i>\n\n{}' - ), - "unmuted": ( - '<emoji document_id=5436040291507247633>🎉</emoji> <b><a href="{}">{}</a>' - " was unmuted</b>" - ), - "unmuted_log": ( - '<emoji document_id=5436040291507247633>🎉</emoji> <b><a href="{}">{}</a>' - ' was unmuted in <a href="{}">{}</a></b>' - ), - "unban": ( - '<emoji document_id=5469791106591890404>🪄</emoji> <b><a href="{}">{}</a>' - " was unbanned</b>" - ), - "unban_log": ( - '<emoji document_id=5469791106591890404>🪄</emoji> <b><a href="{}">{}</a>' - ' was unbanned in <a href="{}">{}</a></b>' - ), - "defense": ( - "<emoji document_id=5300855732009180995>🛡</emoji> <b>Shield for <a" - ' href="{}">{}</a> is now {}</b>' - ), - "no_defense": ( - "<emoji document_id=5300855732009180995>🛡</emoji> <b>Federative defense" - " list is empty</b>" - ), - "defense_list": ( - "<emoji document_id=5300855732009180995>🛡</emoji> <b>Federative defense" - " list:</b>\n{}" - ), - "fadded": ( - "<emoji document_id=5773781976905421370>💼</emoji> <b>Current chat added to" - ' federation "{}"</b>' - ), - "newfed": ( - "<emoji document_id=5773781976905421370>💼</emoji> <b>Created federation" - ' "{}"</b>' - ), - "rmfed": ( - "<emoji document_id=5773781976905421370>💼</emoji> <b>Removed federation" - ' "{}"</b>' - ), - "fed404": ( - "<emoji document_id=5773781976905421370>💼</emoji> <b>Federation not" - " found</b>" - ), - "frem": ( - "<emoji document_id=5773781976905421370>💼</emoji> <b>Current chat removed" - ' from federation "{}"</b>' - ), - "f404": ( - "<emoji document_id=5773781976905421370>💼</emoji> <b>Current chat is" - " not in" - ' federation "{}"</b>' - ), - "fexists": ( - "<emoji document_id=5773781976905421370>💼</emoji> <b>Current chat is" - ' already in federation "{}"</b>' - ), - "fedexists": ( - "<emoji document_id=5773781976905421370>💼</emoji> <b>Federation exists</b>" - ), - "joinfed": ( - "<emoji document_id=5773781976905421370>💼</emoji> <b>Federation joined</b>" - ), - "namedfed": ( - "<emoji document_id=5773781976905421370>💼</emoji> <b>Federation renamed to" - " {}</b>" - ), - "nofed": ( - "<emoji document_id=5773781976905421370>💼</emoji> <b>Current chat is" - " not in" - " any federation</b>" - ), - "fban": ( - '<emoji document_id=5773781976905421370>💼</emoji> <b><a href="{}">{}</a>' - " was banned in federation {} {}\nReason: </b><i>{}</i>\n{}" - ), - "gban": ( - '<emoji document_id=5301059317753979286>🖕</emoji> <b><a href="{}">{}</a>' - " was gbanned.</b>\n<b>Reason: </b><i>{}</i>\n\n{}" - ), - "gbanning": ( - "<emoji document_id=5301059317753979286>🖕</emoji> <b>Gbanning <a" - ' href="{}">{}</a>...</b>' - ), - "gunban": ( - '<emoji document_id=6334872157947955302>🤗</emoji> <b><a href="{}">{}</a>' - " was gunbanned.</b>\n\n{}" - ), - "gunbanning": ( - "<emoji document_id=6334872157947955302>🤗</emoji> <b>Gunbanning <a" - ' href="{}">{}</a>...</b>' - ), - "in_n_chats": ( - "<emoji document_id=5379568936218009290>👎</emoji> <b>Banned in {}" - " chat(-s)</b>" - ), - "unbanned_in_n_chats": ( - "<emoji document_id=5461129450341014019>✋️</emoji> <b>Unbanned in {}" - " chat(-s)</b>" - ), - "fmute": ( - '<emoji document_id=5773781976905421370>💼</emoji> <b><a href="{}">{}</a>' - " muted in federation {} {}\nReason: </b><i>{}</i>\n{}" - ), - "funban": ( - '<emoji document_id=5773781976905421370>💼</emoji> <b><a href="{}">{}</a>' - " unbanned in federation </b><i>{}</i>\n" - ), - "funmute": ( - '<emoji document_id=5773781976905421370>💼</emoji> <b><a href="{}">{}</a>' - " unmuted in federation </b><i>{}</i>\n" - ), - "feds_header": ( - "<emoji document_id=5773781976905421370>💼</emoji> <b>Federations:</b>\n\n" - ), - "fed": ( - '<emoji document_id=5773781976905421370>💼</emoji> <b>Federation "{}"' - " info:</b>\n🔰 <b>Chats:</b>\n<b>{}</b>\n🔰 <b>Channels:</b>\n<b>{}</b>\n🔰" - " <b>Admins:</b>\n<b>{}</b>\n🔰 <b>Warns: {}</b>\n" - ), - "no_fed": ( - "<emoji document_id=5773781976905421370>💼</emoji> <b>This chat is not in" - " any federation</b>" - ), - "fpromoted": ( - '<emoji document_id=5773781976905421370>💼</emoji> <b><a href="{}">{}</a>' - " promoted in federation {}</b>" - ), - "fdemoted": ( - '<emoji document_id=5773781976905421370>💼</emoji> <b><a href="{}">{}</a>' - " demoted in federation {}</b>" - ), - "api_error": ( - "<emoji document_id=5300759756669984376>🚫</emoji> <b>api.hikariatama.ru" - " Error!</b>\n<code>{}</code>" - ), - "fsave_args": ( - "<emoji document_id=5773781976905421370>💼</emoji> <b>Usage: .fsave" - " shortname <reply></b>" - ), - "fstop_args": ( - "<emoji document_id=5773781976905421370>💼</emoji> <b>Usage: .fstop" - " shortname</b>" - ), - "fsave": ( - "<emoji document_id=5773781976905421370>💼</emoji> <b>Federative note" - " </b><code>{}</code><b> saved!</b>" - ), - "fstop": ( - "<emoji document_id=5773781976905421370>💼</emoji> <b>Federative note" - " </b><code>{}</code><b> removed!</b>" - ), - "fnotes": ( - "<emoji document_id=5773781976905421370>💼</emoji> <b>Federative" - " notes:</b>\n{}" - ), - "usage": "ℹ️ <b>Usage: .{} <on/off></b>", - "chat_only": "ℹ️ <b>This command is for chats only</b>", - "version": ( - "<emoji document_id=5440551785284510215>🎢</emoji> <b>{}</b>\n\n<emoji" - " document_id=5454182070156794055>🤘</emoji> <b>Author:" - " t.me/hikariatama</b>\n<emoji document_id=6325750691088303497>☺️</emoji>" - " <b>Downloaded from @hikarimods</b>\n<b>{}</b>" - ), - "error": ( - "<emoji document_id=6053166094816905153>💀</emoji> <b>HikariChat Issued" - " error</b>" - ), - "reported": ( - '<emoji document_id=5773781976905421370>💼</emoji> <b><a href="{}">{}</a>' - " reported this message to admins\nReason: </b><i>{}</i>" - ), - "no_federations": ( - "<emoji document_id=5773781976905421370>💼</emoji> <b>You have no active" - " federations</b>" - ), - "clrallwarns_fed": ( - "<emoji document_id=5193091781327068499>👮‍♀️</emoji> <b>Forgave all" - " federative warns of federation</b>" - ), - "cleaning": ( - "<emoji document_id=5215493819641895305>🫥</emoji> <b>Looking for Deleted" - " accounts...</b>" - ), - "deleted": ( - "<emoji document_id=5215493819641895305>🫥</emoji> <b>Removed {} Deleted" - " accounts</b>" - ), - "fcleaning": ( - "<emoji document_id=5215493819641895305>🫥</emoji> <b>Looking for Deleted" - " accounts in federation...</b>" - ), - "btn_unban": "🔓 Unban (ADM)", - "btn_unmute": "🔈 Unmute (ADM)", - "btn_unwarn": "♻️ De-Warn (ADM)", - "inline_unbanned": ( - '🔓 <b><a href="{}">{}</a> unbanned by <a href="{}">{}</a></b>' - ), - "inline_unmuted": ( - '🔈 <b><a href="{}">{}</a> unmuted by <a href="{}">{}</a></b>' - ), - "inline_unwarned": ( - '♻️ <b>Forgave last warn of <a href="{}">{}</a> by <a href="{}">{}</a></b>' - ), - "inline_funbanned": ( - '🔓 <b><a href="{}">{}</a> unbanned in federation by <a' - ' href="{}">{}</a></b>' - ), - "inline_funmuted": ( - '🔈 <b><a href="{}">{}</a> unmuted in federation by <a href="{}">{}</a></b>' - ), - "btn_funmute": "🔈 Fed Unmute (ADM)", - "btn_funban": "🔓 Fed Unban (ADM)", - "btn_mute": "🙊 Mute", - "btn_ban": "🔒 Ban", - "btn_fban": "💼 Fed Ban", - "btn_del": "🗑 Delete", - "inline_fbanned": ( - '<emoji document_id=5773781976905421370>💼</emoji> <b><a href="{}">{}</a>' - ' banned in federation by <a href="{}">{}</a></b>' - ), - "inline_muted": '🙊 <b><a href="{}">{}</a> muted by <a href="{}">{}</a></b>', - "inline_banned": ( - '<emoji document_id=5247152118069992250>🔒</emoji> <b><a href="{}">{}</a>' - ' banned by <a href="{}">{}</a></b>' - ), - "inline_deleted": '🗑 <b>Deleted by <a href="{}">{}</a></b>', - "sync": "🔄 <b>Syncing chats and feds with server in force mode...</b>", - "sync_complete": "😌 <b>Successfully synced</b>", - "rename_noargs": ( - "<emoji document_id=5300759756669984376>🚫</emoji> <b>Specify new" - " federation" - " name</b>" - ), - "rename_success": '😇 <b>Federation renamed to "</b><code>{}</code><b>"</b>', - "suffix_removed": "📼 <b>Punishment suffix removed</b>", - "suffix_updated": "📼 <b>New punishment suffix saved</b>\n\n{}", - "processing_myrights": "😌 <b>Processing chats</b>", - "logchat_removed": "📲 <b>Log chat disabled</b>", - "logchat_invalid": ( - "<emoji document_id=5300759756669984376>🚫</emoji> <b>Log chat invalid</b>" - ), - "logchat_set": "📲 <b>Log chat updated to </b><code>{}</code>", - "clnraid_args": ( - "<emoji document_id=6323575131239089635>🥷</emoji> <b>Example usage:" - " </b><code>.clnraid 10</code>" - ), - "clnraid_admin": ( - "<emoji document_id=6323575131239089635>🥷</emoji> <b>Error occured while" - " promoting cleaner. Please, ensure you have enough rights in chat</b>" - ), - "clnraid_started": ( - "<emoji document_id=6323575131239089635>🥷</emoji> <b>RaidCleaner is in" - " progress... Found {} users to kick...</b>" - ), - "clnraid_confirm": ( - "<emoji document_id=6323575131239089635>🥷</emoji> <b>Please, confirm that" - " you want to start RaidCleaner on {} users</b>" - ), - "clnraid_yes": "<emoji document_id=6323575131239089635>🥷</emoji> Start", - "clnraid_cancel": "🔻 Cancel", - "clnraid_stop": "🚨 Stop", - "clnraid_complete": ( - "<emoji document_id=6323575131239089635>🥷</emoji> <b>RaidCleaner complete!" - " Removed: {} user(-s)</b>" - ), - "clnraid_cancelled": ( - "<emoji document_id=6323575131239089635>🥷</emoji> <b>RaidCleaner" - " cancelled." - " Removed: {} user(-s)</b>" - ), - "smart_anti_raid_active": ( - "<emoji document_id=6323575131239089635>🥷</emoji> <b>BanNinja is working" - " hard to prevent intrusion to this chat.</b>\n\n{}<i>Deleted {}" - " bot(-s)</i>" - ), - "smart_anti_raid_off": "🚨 Stop", - "smart_anti_raid_stopped": ( - "<emoji document_id=6323575131239089635>🥷</emoji> <b>BanNinja Stopped</b>" - ), - "banninja_report": ( - "<emoji document_id=6323575131239089635>🥷</emoji> <b>BanNinja has done his" - " job.</b>\n<i>Deleted {} bot(-s)</i>\n\n🏹 <i>«BanNinja can handle any" - " size" - " of attack»</i> © <code>@hikariatama</code>" - ), - "forbid_messages": ( - "⚠️ <b>I've forbidden sending messages until attack is fully" - " released</b>\n\n" - ), - "confirm_rmfed": ( - "⚠️ <b>Warning! This operation can't be reverted! Are you sure, " - "you want to delete federation </b><code>{}</code><b>?</b>" - ), - "confirm_rmfed_btn": "🗑 Delete", - "decline_rmfed_btn": "🔻 Cancel", - "pil_unavailable": ( - "<emoji document_id=5300759756669984376>🚫</emoji> <b>Pillow package" - " unavailable</b>" - ), - "action": "<action>", - "configure": "Configure", - "toggle": "Toggle", - "no_protects": ( - "<emoji document_id=5300759756669984376>🚫</emoji> <b>This chat has no" - " active protections to show</b>" - ), - "from_where": ( - "<emoji document_id=5300759756669984376>🚫</emoji> <b>Reply to a message to" - " purge from</b>" - ), - "no_notes": ( - "<emoji document_id=5300759756669984376>🚫</emoji> <b>No notes found</b>" - ), - "complete_captcha": ( - "<emoji document_id=5213107179329953547>🚥</emoji> <b><a" - ' href="tg://user?id={}">{}</a>, please, complete captcha within 5' - " minutes</b>" - ), - "captcha_timeout": ( - '<emoji document_id=5213107179329953547>🚥</emoji> <b><a href="{}">{}</a>' - " have not completed captcha in time.\n👊 Action: I {}</b>" - ), - "captcha_failed": ( - '<emoji document_id=5213107179329953547>🚥</emoji> <b><a href="{}">{}</a>' - " failed captcha.\n👊 Action: I {}</b>" - ), - "fdef403": ( - "<emoji document_id=5300855732009180995>🛡</emoji> <b>You can't {} this" - " user, because he is under federative protection</b>" - ), - } - - strings_ru = { - "complete_captcha": ( - "<emoji document_id=5213107179329953547>🚥</emoji> <b><a" - ' href="tg://user?id={}">{}</a>, пожалуйста, пройди капчу в течение 5' - " минут</b>" - ), - "captcha_timeout": ( - "<emoji document_id=5213107179329953547>🚥</emoji> <b><a" - ' href="{}">{}</a> не' - " прошел капчу вовремя.\n👊 Действие: {}</b>" - ), - "captcha_failed": ( - "<emoji document_id=5213107179329953547>🚥</emoji> <b><a" - ' href="{}">{}</a> не' - " прошел капчу.\n👊 Действие: {}</b>" - ), - "cas_on": ( - "<emoji document_id=5300855732009180995>🛡</emoji> <b>CAS теперь включен в" - " этом чате\nДействие: {}</b>" - ), - "cas_off": ( - "<emoji document_id=5300855732009180995>🛡</emoji> <b>CAS теперь выключен в" - " этом чате</b>" - ), - "cas": ( - '<emoji document_id=5300855732009180995>🛡</emoji> <b><a href="{}">{}</a>' - " appears to be in Combat Anti Spam database.\n👊 Action: I {}</b>" - ), - "from_where": ( - "<emoji document_id=5300759756669984376>🚫</emoji> <b>Ответь на сообщение," - " начиная с которого надо удалить.</b>" - ), - "smart_anti_raid_active": ( - "<emoji document_id=6323575131239089635>🥷</emoji> <b>BanNinja работает в" - " поте лица, отбивая атаку на этот чат.</b>\n\n{}<i>Удалено {} бот(-ов)</i>" - ), - "forbid_messages": ( - "⚠️ <b>Я запретил отправку сообщений, пока атака не будет полностью" - " отражена</b>\n\n" - ), - "smart_anti_raid_off": "🚨 Остановить", - "smart_anti_raid_stopped": ( - "<emoji document_id=6323575131239089635>🥷</emoji> <b>BanNinja" - " остановлен</b>" - ), - "error": "😵 <b>Произошла ошибка HikariChat</b>", - "args": ( - "<emoji document_id=5300759756669984376>🚫</emoji> <b>Неверные" - " аргументы</b>" - ), - "no_reason": "Не указана", - "antitagall_on": ( - "<emoji document_id=5785175271011259591>🐵</emoji> <b>AntiTagAll теперь" - " включен в этом чате\nДействие: {}</b>" - ), - "antitagall_off": ( - "<emoji document_id=5785175271011259591>🐵</emoji> <b>AntiTagAll теперь" - " выключен в этом чате</b>" - ), - "antiarab_on": ( - "<emoji document_id=6323257144745395640>🇵🇸</emoji> <b>AntiArab теперь" - " включен в этом чате\nДействие: {}</b>" - ), - "antiarab_off": ( - "<emoji document_id=6323257144745395640>🇵🇸</emoji> <b>AntiArab теперь" - " выключен в этом чате</b>" - ), - "antizalgo_on": ( - "<emoji document_id=5213293263083018856>🌀</emoji> <b>AntiZALGO теперь" - " включен в этом чате\nДействие: {}</b>" - ), - "antizalgo_off": ( - "<emoji document_id=5213293263083018856>🌀</emoji> <b>AntiZALGO теперь" - " выключен в этом чате</b>" - ), - "antistick_on": ( - "<emoji document_id=5431456208487716895>🎨</emoji> <b>AntiStick теперь" - " включен в этом чате\nДействие: {}</b>" - ), - "antistick_off": ( - "<emoji document_id=5431456208487716895>🎨</emoji> <b>AntiStick теперь" - " выключен в этом чате</b>" - ), - "antihelp_on": ( - "<emoji document_id=5467759840463953770>🐺</emoji> <b>AntiHelp теперь" - " включен в этом чате</b>" - ), - "antihelp_off": ( - "<emoji document_id=5467759840463953770>🐺</emoji> <b>AntiHelp теперь" - " выключен в этом чате</b>" - ), - "antiraid_on": ( - "<emoji document_id=6334359218593728345><emoji" - " document_id=6037460928423791421>🚪</emoji></emoji> <b>AntiRaid теперь" - " включен в этом чате\nДействие: {}</b>" - ), - "antiraid_off": ( - "<emoji document_id=6334359218593728345><emoji" - " document_id=6037460928423791421>🚪</emoji></emoji> <b>AntiRaid теперь" - " выключен в этом чате</b>" - ), - "bnd_on": ( - "<emoji document_id=5465300082628763143>💬</emoji> <b>Block-Non-Discussion" - " теперь включен в этом чате\nДействие: {}</b>" - ), - "bnd_off": ( - "<emoji document_id=5465300082628763143>💬</emoji> <b>Block-Non-Discussion" - " теперь выключен в этом чате</b>" - ), - "antichannel_on": ( - "<emoji document_id=5470094069289984325>📯</emoji> <b>AntiChannel теперь" - " включен в этом чате</b>" - ), - "antichannel_off": ( - "<emoji document_id=5470094069289984325>📯</emoji> <b>AntiChannel теперь" - " выключен в этом чате</b>" - ), - "report_on": ( - "<emoji document_id=5213203794619277246>📣</emoji> <b>Report теперь включен" - " в этом чате</b>" - ), - "report_off": ( - "<emoji document_id=5213203794619277246>📣</emoji> <b>Report теперь" - " выключен" - " в этом чате</b>" - ), - "antiflood_on": ( - "<emoji document_id=5384611567125928766>⏱</emoji> <b>AntiFlood теперь" - " включен в этом чате\nДействие: {}</b>" - ), - "antiflood_off": ( - "<emoji document_id=5384611567125928766>⏱</emoji> <b>AntiFlood теперь" - " выключен в этом чате</b>" - ), - "antispoiler_on": ( - "<emoji document_id=5798648862591684122>👻</emoji> <b>AntiSpoiler теперь" - " включен в этом чате</b>" - ), - "antispoiler_off": ( - "<emoji document_id=5798648862591684122>👻</emoji> <b>AntiSpoiler теперь" - " выключен в этом чате</b>" - ), - "antigif_on": ( - "<emoji document_id=6048825205730577727>🎑</emoji> <b>AntiGIF теперь" - " включен" - " в этом чате</b>" - ), - "antigif_off": ( - "<emoji document_id=6048825205730577727>🎑</emoji> <b>AntiGIF теперь" - " выключен в этом чате</b>" - ), - "antiservice_on": ( - "<emoji document_id=5787237370709413702>⚙️</emoji> <b>AntiService теперь" - " включен в этом чате</b>" - ), - "antiservice_off": ( - "<emoji document_id=5787237370709413702>⚙️</emoji> <b>AntiService теперь" - " выключен в этом чате</b>" - ), - "banninja_on": ( - "<emoji document_id=6323575131239089635>🥷</emoji> <b>BanNinja теперь" - " включен в этом чате</b>" - ), - "banninja_off": ( - "<emoji document_id=6323575131239089635>🥷</emoji> <b>BanNinja теперь" - " выключен в этом чате</b>" - ), - "antiexplicit_on": ( - "<emoji document_id=5373123633415723713>🤬</emoji> <b>AntiExplicit теперь" - " включен в этом чате\nДействие: {}</b>" - ), - "antiexplicit_off": ( - "<emoji document_id=5373123633415723713>🤬</emoji> <b>AntiExplicit теперь" - " выключен в этом чате</b>" - ), - "antinsfw_on": ( - "<emoji document_id=4976982981341086273>🔞</emoji> <b>AntiNSFW теперь" - " включен в этом чате\nДействие: {}</b>" - ), - "antinsfw_off": ( - "<emoji document_id=4976982981341086273>🔞</emoji> <b>AntiNSFW теперь" - " выключен в этом чате</b>" - ), - "captcha_on": ( - "<emoji document_id=5213107179329953547>🚥</emoji> <b>Captcha теперь" - " включена в этом чате\nДействие: {}</b>" - ), - "captcha_off": ( - "<emoji document_id=5213107179329953547>🚥</emoji> <b>Captcha теперь" - " выключена в этом чате</b>" - ), - "no_fed_warns": ( - "<emoji document_id=5193091781327068499>👮‍♀️</emoji> <b>This federation has" - " no warns yet</b>" - ), - "warns_adm_fed": ( - "<emoji document_id=5193091781327068499>👮‍♀️</emoji> <b>Warns in this" - " federation</b>:\n" - ), - "welcome": ( - "<emoji document_id=5472055112702629499>👋</emoji> <b>Теперь я буду" - " приветствовать людей в этом чате</b>\n{}" - ), - "unwelcome": ( - "<emoji document_id=5472055112702629499>👋</emoji> <b>Я больше не буду" - " приветствовать людей в этом чате</b>" - ), - "chat404": "🔓 <b>Этот чат еще не защищен.</b>\n", - "not_admin": "🤷‍♂️ <b>Я здесь не админ, или у меня недостаточно прав</b>", - "no_defense": ( - "<emoji document_id=5300855732009180995>🛡</emoji> <b>Федеративный список" - " защиты пуст</b>" - ), - "defense_list": ( - "<emoji document_id=5300855732009180995>🛡</emoji> <b>Федеративный список" - " защиты:</b>\n{}" - ), - "fed404": ( - "<emoji document_id=5773781976905421370>💼</emoji> <b>Федерация не" - " найдена</b>" - ), - "fedexists": ( - "<emoji document_id=5773781976905421370>💼</emoji> <b>Федерация" - " существует</b>" - ), - "joinfed": ( - "<emoji document_id=5773781976905421370>💼</emoji> <b>Присоединился к" - " федерации</b>" - ), - "namedfed": ( - "<emoji document_id=5773781976905421370>💼</emoji> <b>Федерация" - " переименована в {}</b>" - ), - "nofed": ( - "<emoji document_id=5773781976905421370>💼</emoji> <b>Этот чат не находится" - " ни в одной из федераций</b>" - ), - "feds_header": ( - "<emoji document_id=5773781976905421370>💼</emoji> <b>Федерации:</b>\n\n" - ), - "no_fed": ( - "<emoji document_id=5773781976905421370>💼</emoji> <b>Этот чат не находится" - " ни в одной из федераций</b>" - ), - "api_error": ( - "<emoji document_id=5300759756669984376>🚫</emoji> <b>Ошибка" - " api.hikariatama.ru!</b>\n<code>{}</code>" - ), - "fsave_args": ( - "<emoji document_id=5773781976905421370>💼</emoji> <b>Пример: .fsave" - " shortname <reply></b>" - ), - "fstop_args": ( - "<emoji document_id=5773781976905421370>💼</emoji> <b>Пример: .fstop" - " shortname</b>" - ), - "fsave": ( - "<emoji document_id=5773781976905421370>💼</emoji> <b>Федеративная заметка" - " </b><code>{}</code><b> сохранена!</b>" - ), - "fstop": ( - "<emoji document_id=5773781976905421370>💼</emoji> <b>Федеративная заметка" - " </b><code>{}</code><b> удалена!</b>" - ), - "fnotes": ( - "<emoji document_id=5773781976905421370>💼</emoji> <b>Федеративные" - " заметки:</b>\n{}" - ), - "usage": "ℹ️ <b>Пример: .{} <on/off></b>", - "chat_only": "ℹ️ <b>Эта команда предназначена для чатов</b>", - "no_federations": ( - "<emoji document_id=5773781976905421370>💼</emoji> <b>Нет активных" - " федераций</b>" - ), - "clrallwarns_fed": ( - "<emoji document_id=5193091781327068499>👮‍♀️</emoji> <b>Прощены все" - " предупреждения в федерации</b>" - ), - "cleaning": ( - "<emoji document_id=5215493819641895305>🫥</emoji> <b>Поиск удаленных" - " аккаунтов...</b>" - ), - "deleted": ( - "<emoji document_id=5215493819641895305>🫥</emoji> <b>Удалено {} удаленных" - " аккаунтов</b>" - ), - "fcleaning": ( - "<emoji document_id=5215493819641895305>🫥</emoji> <b>Поиск удаленных" - " аккаунтов в федерации...</b>" - ), - "btn_unban": "🔓 Разбанить (админ)", - "btn_unmute": "🔈 Размутить (админ)", - "btn_unwarn": "♻️ Удалить предупреждение (админ)", - "btn_funmute": "🔈 Размутить в федерации (админ)", - "btn_funban": "🔓 Разбанить в федерации (админ)", - "btn_mute": "🙊 Мут", - "btn_ban": "🔒 Бан", - "btn_fban": "💼 Фед. бан", - "btn_del": "🗑 Удалить", - "sync": ( - "🔄 <b>Принудительная синхронизация федераций и чатов с сервером...</b>" - ), - "sync_complete": "😌 <b>Сихнронизирован</b>", - "rename_noargs": ( - "<emoji document_id=5300759756669984376>🚫</emoji> <b>Укажи имя" - " федерации</b>" - ), - "suffix_removed": "📼 <b>Суффикс предупреждения удален</b>", - "suffix_updated": "📼 <b>Установлен новый суффикс предупреждения</b>\n\n{}", - "processing_myrights": "😌 <b>Обработка чатов</b>", - "logchat_removed": "📲 <b>Логирование отключено</b>", - "logchat_invalid": ( - "<emoji document_id=5300759756669984376>🚫</emoji> <b>Неверный чат" - " логирования</b>" - ), - "logchat_set": "📲 <b>Чат логирования установлен на </b><code>{}</code>", - "clnraid_args": ( - "<emoji document_id=6323575131239089635>🥷</emoji> <b>Пример:" - " </b><code>.clnraid 10</code>" - ), - "clnraid_admin": ( - "<emoji document_id=6323575131239089635>🥷</emoji> <b>Ошибка выдачи прав" - " боту. Убедись, что у тебя достаточно прав</b>" - ), - "clnraid_started": ( - "<emoji document_id=6323575131239089635>🥷</emoji> <b>RaidCleaner" - " активен..." - " Найдено {} пользователей для бана...</b>" - ), - "clnraid_confirm": ( - "<emoji document_id=6323575131239089635>🥷</emoji> <b>Подтвердите запуск" - " RaidCleaner на {} пользователях</b>" - ), - "clnraid_yes": "<emoji document_id=6323575131239089635>🥷</emoji> Начать", - "banninja_report": ( - "<emoji document_id=6323575131239089635>🥷</emoji> <b>BanNinja закончил" - " работу.</b>\n<i>Удалено {} бот(-ов)</i>\n\n🏹 <i>«BanNinja can handle any" - " size of attack»</i> © <code>@hikariatama</code>" - ), - "clnraid_cancel": "🔻 Отмена", - "clnraid_stop": "🚨 Остановить", - "clnraid_complete": ( - "<emoji document_id=6323575131239089635>🥷</emoji> <b>RaidCleaner закончил" - " работу! Удалено: {} бот(-ов)</b>" - ), - "clnraid_cancelled": ( - "<emoji document_id=6323575131239089635>🥷</emoji> <b>RaidCleaner" - " остановлен. Удалено: {} бот(-ов)</b>" - ), - "confirm_rmfed_btn": "🗑 Удалить", - "decline_rmfed_btn": "🔻 Отмена", - "pil_unavailable": ( - "<emoji document_id=5300759756669984376>🚫</emoji> <b>Библиотека Pillow" - " недоступна</b>" - ), - "_cmd_doc_version": "Получить информацию о модуле", - "_cmd_doc_deleted": "Очистка удалнных аккаунтов в чате", - "_cmd_doc_fclean": "Очистка удаленных аккаунтов в федерации", - "_cmd_doc_newfed": "<shortname> <имя> - Создать новую федерацию", - "_cmd_doc_rmfed": "<shortname> - Удалить федерацию", - "_cmd_doc_fpromote": "<пользователь> - Выдать пользователю права в федерации", - "_cmd_doc_fdemote": ( - "<shortname> <пользователь> - Забрать у пользователя права в федерации" - ), - "_cmd_doc_fadd": "<федерация> - Добавить чат в федерацию", - "_cmd_doc_frm": "Удалить чат из федерации", - "_cmd_doc_fban": "<пользователь> [причина] - Забанить пользователя в федерации", - "_cmd_doc_punishsuff": "Установить новый суффикс наказания", - "_cmd_doc_sethclog": "Установить чат логирования", - "_cmd_doc_funban": ( - "<пользователь> [причина] - Разбанить пользователя в федерации" - ), - "_cmd_doc_fmute": ( - "<пользователь> [причина] - Замутить пользователя в федерации" - ), - "_cmd_doc_funmute": ( - "<пользователь> [причина] - Разбанить пользователя в федерации" - ), - "_cmd_doc_kick": "<пользователь> [причина] - Кикнуть пользователя", - "_cmd_doc_ban": "<пользователь> [причина] - Забанить пользователя", - "_cmd_doc_mute": "<пользователь> [время] [причина] - Замутить пользователя", - "_cmd_doc_unmute": "<пользователь> - Размутить пользователя", - "_cmd_doc_unban": "<пользователь> - Разбанить пользователя", - "_cmd_doc_protects": "Показать доступные защиты", - "_cmd_doc_feds": "Показать федерации", - "_cmd_doc_fed": "<shortname> - Информация о федерации", - "_cmd_doc_pchat": "Показать защиты в чате", - "_cmd_doc_warn": "<пользователь> - Предупредить пользователя", - "_cmd_doc_warns": ( - "[пользователь] - Показать предупреждения в чате \\ у пользователя" - ), - "_cmd_doc_delwarn": "<пользователь> - Простить последнее предупреждение", - "_cmd_doc_clrwarns": ( - "<пользователь> - Простить все предупреждения пользователя" - ), - "_cmd_doc_clrallwarns": "Простить все предупреждения в федерации", - "_cmd_doc_welcome": "<text> - Изменить текст приветствовия", - "_cmd_doc_fdef": ( - "<пользователь> - Включить\\выключить федеративную защиту пользователя" - ), - "_cmd_doc_fsave": "<note name> <reply> - Сохранить федеративную заметку", - "_cmd_doc_fstop": "<note name> - Удалить федеративную заметку", - "_cmd_doc_fnotes": "Показать федеративные заметки", - "_cmd_doc_fdeflist": "Показать федеративный список защиты", - "_cmd_doc_dmute": "Удалить и замутить", - "_cmd_doc_dban": "Удалить и забанить", - "_cmd_doc_dwarn": "Удалить и предупредить", - "_cmd_doc_fsync": "Принудительная синхронизация федераций и чатов с сервером", - "_cmd_doc_frename": "Переименовать федерацию", - "_cmd_doc_myrights": "Показать все права администратора во всех чатах", - "action": "<действие>", - "configure": "Настроить", - "toggle": "Включить\\выключить", - "fed": ( - "<emoji document_id=5773781976905421370>💼</emoji> <b>Федерация" - ' "{}":</b>\n🔰' - " <b>Чаты:</b>\n<b>{}</b>\n🔰 <b>Каналы:</b>\n<b>{}</b>\n🔰" - " <b>Админы:</b>\n<b>{}</b>\n🔰 <b>Предупреждения: {}</b>\n" - ), - "confirm_rmfed": ( - "⚠️ <b>Внимание! Это действие нельзя отменить! Ты уверен, что хочешь" - " удалить федерацию </b><code>{}</code><b>?</b>" - ), - "_cls_doc": "Must-have модуль администратора чата", - "no_notes": ( - "<emoji document_id=5300759756669984376>🚫</emoji> <b>Нет заметок</b>" - ), - } - - def __init__(self): - self._punish_queue = [] - self._raid_cleaners = [] - self._global_queue = [] - self._captcha_db = {} - self._captcha_messages = {} - self._ban_ninja = {} - self._ban_ninja_messages = [] - self._ban_ninja_forms = {} - self._ban_ninja_progress = {} - self._ban_ninja_tasks = {} - self._ban_ninja_default_rights = {} - self.flood_timeout = FLOOD_TIMEOUT - self.flood_threshold = FLOOD_TRESHOLD - self._my_protects = {} - self._linked_channels = {} - self._sticks_ratelimit = {} - self._flood_fw_protection = {} - self._ratelimit = {"notes": {}, "report": {}} - self._delete_soon = [] - self._gban_cache = {} - - self.config = loader.ModuleConfig( - loader.ConfigValue( - "silent", - False, - lambda: "Do not notify about protections actions", - validator=loader.validators.Boolean(), - ), - loader.ConfigValue( - "join_ratelimit", - 10, - lambda: ( - "How many users per minute need to join until BanNinja activates" - ), - validator=loader.validators.Integer(minimum=1), - ), - loader.ConfigValue( - "banninja_cooldown", - 300, - lambda: "How long is BanNinja supposed to be active in seconds", - validator=loader.validators.Integer(minimum=15), - ), - loader.ConfigValue( - "warns_limit", - 7, - lambda: "How many warns can be issued before ban", - validator=loader.validators.Integer(minimum=1), - ), - loader.ConfigValue( - "close_on_raid", - True, - lambda: "Close chat on raid with active BanNinja", - validator=loader.validators.Boolean(), - ), - ) - - def render_table(self, t: typing.List[typing.List[str]]) -> bytes: - table = gen_table(t) - - fnt = ImageFont.truetype(io.BytesIO(self.font), 20, encoding="utf-8") - - def get_t_size(text, fnt): - if "\n" not in text: - return fnt.getsize(text) - - w, h = 0, 0 - - for line in text.split("\n"): - line_size = fnt.getsize(line) - if line_size[0] > w: - w = line_size[0] - - h += line_size[1] - - w += 10 - h += 10 - return (w, h) - - t_size = get_t_size(table, fnt) - img = Image.new("RGB", t_size, (30, 30, 30)) - - d = ImageDraw.Draw(img) - d.text((5, 5), table, font=fnt, fill=(200, 200, 200)) - - imgByteArr = io.BytesIO() - img.save(imgByteArr, format="PNG") - imgByteArr = imgByteArr.getvalue() - - return imgByteArr - - async def on_unload(self): - with contextlib.suppress(Exception): - self.api._task.cancel() - - with contextlib.suppress(Exception): - self._pt_task.cancel() - - with contextlib.suppress(Exception): - self.api._processor_task.cancel() - - with contextlib.suppress(Exception): - for _, form in self._ban_ninja_forms.items(): - with contextlib.suppress(Exception): - await form.delete() - - def lookup(self, modname: str): - return next( - ( - mod - for mod in self.allmodules.modules - if mod.name.lower() == modname.lower() - ), - False, - ) - - async def check_admin( - self, - chat_id: typing.Union[Chat, Channel, int], - user_id: typing.Union[User, int], - ) -> bool: - """ - Checks if user is admin in target chat - """ - try: - return (await self._client.get_permissions(chat_id, user_id)).is_admin - # We could've ignored only ValueError to check - # entity for validity, but there are many errors - # possible to occur, so we ignore all of them, bc - # actually we don't give a fuck about 'em - except Exception: - return ( - user_id in self._client.dispatcher.security._owner - or user_id in self._client.dispatcher.security._sudo - ) - - def chat_command(function) -> FunctionType: - """ - Decorator to allow execution of certain commands in chat only - """ - - @functools.wraps(function) - async def wrapped(*args, **kwargs): - if len(args) < 2 or not isinstance(args[1], Message): - return await function(*args, **kwargs) - - if args[1].is_private: - await utils.answer(args[1], args[0].strings("chat_only")) - return - - return await function(*args, **kwargs) - - wrapped.__doc__ = function.__doc__ - wrapped.__module__ = function.__module__ - - return wrapped - - def error_handler(function) -> FunctionType: - """ - Decorator to handle functions' errors - """ - - @functools.wraps(function) - async def wrapped(*args, **kwargs): - try: - return await function(*args, **kwargs) - except Exception: - logger.exception("Exception caught in HikariChat") - - if function.__name__.startswith("p__"): - return - - if function.__name__ == "watcher": - return - - wrapped.__doc__ = function.__doc__ - wrapped.__module__ = function.__module__ - - return wrapped - - async def get_config(self, chat: typing.Union[str, int]) -> tuple: - info = self.api.chats[str(chat)] - cinfo = await self._client.get_entity(int(chat)) - - answer_message = ( - f"🪆 <b>HikariChat protection</b>\n<b>{get_full_name(cinfo)}</b>\n\n" - ) - - protections = { - key: value - for key, value in PROTECTS.items() - if key in self.api.variables["protections"] - } - - btns = [] - for protection, style in protections.items(): - answer_message += ( - f" <b>{style}</b>: {info[protection][0]}\n" - if protection in info - else "" - ) - style = style if protection in info else style[2:] - btns += [ - { - "text": style, - "callback": self._change_protection_state, - "args": (chat, protection), - } - ] - - fed = None - for info in self.api.feds.values(): - if str(chat) in info["chats"]: - fed = info - - answer_message += ( - f"\n<emoji document_id=5773781976905421370>💼</emoji> <b>{fed['name']}</b>" - if fed - else "" - ) - - btns = utils.chunks(btns, 3) + [[{"text": "❌ Close", "action": "close"}]] - - return {"text": answer_message, "reply_markup": btns} - - async def _inline_config(self, call: CallbackQuery, chat: typing.Union[str, int]): - await call.edit(**(await self.get_config(chat))) - - async def _change_protection_state( - self, - call: CallbackQuery, - chat: typing.Union[str, int], - protection: str, - state: typing.Optional[str] = None, - ): - if protection == "welcome": - await call.answer("Use .welcome to configure this option!", show_alert=True) - return - - if protection in self.api.variables["argumented_protects"]: - if state is None: - cinfo = await self._client.get_entity(int(chat)) - markup = utils.chunks( - [ - { - "text": "🔒 Ban", - "callback": self._change_protection_state, - "args": (chat, protection, "ban"), - }, - { - "text": "🙊 Mute", - "callback": self._change_protection_state, - "args": (chat, protection, "mute"), - }, - { - "text": "🤕 Warn", - "callback": self._change_protection_state, - "args": (chat, protection, "warn"), - }, - { - "text": "🚪 Kick", - "callback": self._change_protection_state, - "args": (chat, protection, "kick"), - }, - { - "text": "😶‍🌫️ Delmsg", - "callback": self._change_protection_state, - "args": (chat, protection, "delmsg"), - }, - { - "text": "🚫 Off", - "callback": self._change_protection_state, - "args": (chat, protection, "off"), - }, - ], - 3, - ) + [ - [ - { - "text": "🔙 Back", - "callback": self._inline_config, - "args": (chat,), - } - ] - ] - current_state = ( - "off" - if protection not in self.api.chats[str(chat)] - else self.api.chats[str(chat)][protection][0] - ) - await call.edit( - ( - f"🌁 <b>{get_full_name(cinfo)}</b>:" - f" <code>{PROTECTS[protection]}</code> (now: {current_state})" - ), - reply_markup=markup, - ) - else: - self.api.request( - { - "action": "update protections", - "args": { - "chat": chat, - "protection": protection, - "state": state, - }, - } - ) - await call.answer("Configuration value saved") - if state != "off": - self.api.chats[str(chat)][protection] = [state, str(self._tg_id)] - else: - del self.api.chats[str(chat)][protection] - - await self._inline_config(call, chat) - else: - current_state = protection in self.api.chats[str(chat)] - self.api.request( - { - "action": "update protections", - "args": { - "chat": chat, - "protection": protection, - "state": "off" if current_state else "on", - }, - } - ) - - await call.answer( - f"{PROTECTS[protection]} -> {'off' if current_state else 'on'}" - ) - - if current_state: - del self.api.chats[str(chat)][protection] - else: - self.api.chats[str(chat)][protection] = ["on", str(self._tg_id)] - await self._inline_config(call, chat) - - @error_handler - async def protect(self, message: Message, protection: str): - """ - Protection toggle handler - """ - args = utils.get_args_raw(message) - chat = utils.get_chat_id(message) - - await self._promote_bot(chat) - - if protection in self.api.variables["argumented_protects"]: - if args not in self.api.variables["protect_actions"] or args == "off": - args = "off" - await utils.answer(message, self.strings(f"{protection}_off")) - else: - await utils.answer( - message, - self.strings(f"{protection}_on").format(args), - ) - elif args == "on": - await utils.answer(message, self.strings(f"{protection}_on")) - elif args == "off": - await utils.answer( - message, - self.strings(f"{protection}_off").format(args), - ) - else: - await utils.answer(message, self.strings("usage").format(protection)) - return - - self.api.request( - { - "action": "update protections", - "args": {"protection": protection, "state": args, "chat": chat}, - }, - message, - ) - - def protection_template(self, protection: str) -> FunctionType: - """ - Template for protection toggler - For internal use only - """ - comments = self.api.variables["named_protects"] - func_name = f"{protection}cmd" - function = functools.partial(self.protect, protection=protection) - function.__module__ = self.__module__ - function.__name__ = func_name - function.__self__ = self - - args = ( - self.strings("action") - if protection in self.api.variables["argumented_protects"] - else "<on/off>" - ) - - action = ( - self.strings("configure") - if protection in self.api.variables["argumented_protects"] - else self.strings("toggle") - ) - - function.__doc__ = f"{args} - {action} {comments[protection]}" - return function - - @staticmethod - def convert_time(t: str) -> int: - """ - Tries to export time from text - """ - try: - if not str(t)[:-1].isdigit(): - return 0 - - if "d" in str(t): - t = int(t[:-1]) * 60 * 60 * 24 - - if "h" in str(t): - t = int(t[:-1]) * 60 * 60 - - if "m" in str(t): - t = int(t[:-1]) * 60 - - if "s" in str(t): - t = int(t[:-1]) - - t = int(re.sub(r"[^0-9]", "", str(t))) - except ValueError: - return 0 - - return t - - async def ban( - self, - chat: typing.Union[Chat, int], - user: typing.Union[User, Channel, int], - period: int = 0, - reason: str = None, - message: typing.Optional[Message] = None, - silent: bool = False, - ): - """Ban user in chat""" - if str(user).isdigit(): - user = int(user) - - if reason is None: - reason = self.strings("no_reason") - - try: - await self.inline.bot.kick_chat_member( - int(f"-100{getattr(chat, 'id', chat)}"), - int(getattr(user, "id", user)), - ) - except Exception: - logger.debug("Can't ban with bot", exc_info=True) - - await self._client.edit_permissions( - chat, - user, - until_date=(time.time() + period) if period else 0, - **BANNED_RIGHTS, - ) - - if silent: - return - - msg = self.strings("ban").format( - utils.get_link(user), - get_full_name(user), - f"for {period // 60} min(-s)" if period else "forever", - reason, - self.get("punish_suffix", ""), - ) - - if self._is_inline: - if self.get("logchat"): - if not isinstance(chat, (Chat, Channel)): - chat = await self._client.get_entity(chat) - - await self.inline.form( - message=self.get("logchat"), - text=self.strings("ban_log").format( - utils.get_link(user), - get_full_name(user), - f"for {period // 60} min(-s)" if period else "forever", - utils.get_link(chat), - get_full_name(chat), - reason, - "", - ), - reply_markup={ - "text": self.strings("btn_unban"), - "data": ( - f"ub/{chat.id if isinstance(chat, (Chat, Channel)) else chat}/{user.id}" - ), - }, - silent=True, - ) - - if isinstance(message, Message): - await utils.answer(message, msg) - else: - await self._client.send_message(chat.id, msg) - else: - await self.inline.form( - message=( - message - if isinstance(message, Message) - else getattr(chat, "id", chat) - ), - text=msg, - reply_markup={ - "text": self.strings("btn_unban"), - "data": ( - f"ub/{chat.id if isinstance(chat, (Chat, Channel)) else chat}/{user.id}" - ), - }, - silent=True, - ) - else: - await (utils.answer if message else self._client.send_message)( - message or chat.id, msg - ) - - async def mute( - self, - chat: typing.Union[Chat, int], - user: typing.Union[User, Channel, int], - period: int = 0, - reason: str = None, - message: typing.Optional[Message] = None, - silent: bool = False, - ): - """Mute user in chat""" - if str(user).isdigit(): - user = int(user) - - if reason is None: - reason = self.strings("no_reason") - - try: - await self.inline.bot.restrict_chat_member( - int(f"-100{getattr(chat, 'id', chat)}"), - int(getattr(user, "id", user)), - permissions=ChatPermissions(can_send_messages=False), - until_date=time.time() + period, - ) - except Exception: - logger.debug("Can't mute with bot", exc_info=True) - - await self._client.edit_permissions( - chat, - user, - until_date=time.time() + period, - send_messages=False, - ) - - if silent: - return - - msg = self.strings("mute").format( - utils.get_link(user), - get_full_name(user), - f"for {period // 60} min(-s)" if period else "forever", - reason, - self.get("punish_suffix", ""), - ) - - if self._is_inline: - if self.get("logchat"): - if not isinstance(chat, (Chat, Channel)): - chat = await self._client.get_entity(chat) - - await self.inline.form( - message=self.get("logchat"), - text=self.strings("mute_log").format( - utils.get_link(user), - get_full_name(user), - f"for {period // 60} min(-s)" if period else "forever", - utils.get_link(chat), - get_full_name(chat), - reason, - "", - ), - reply_markup={ - "text": self.strings("btn_unmute"), - "data": ( - f"um/{chat.id if isinstance(chat, (Chat, Channel)) else chat}/{user.id}" - ), - }, - silent=True, - ) - - if isinstance(message, Message): - await utils.answer(message, msg) - else: - await self._client.send_message(chat.id, msg) - else: - await self.inline.form( - message=( - message - if isinstance(message, Message) - else getattr(chat, "id", chat) - ), - text=msg, - reply_markup={ - "text": self.strings("btn_unmute"), - "data": ( - f"um/{chat.id if isinstance(chat, (Chat, Channel)) else chat}/{user.id}" - ), - }, - silent=True, - ) - else: - await (utils.answer if message else self._client.send_message)( - message or chat.id, msg - ) - - @loader.inline_everyone - async def actions_callback_handler(self, call: CallbackQuery): - """ - Handles unmute, unban, unwarn etc. button clicks - """ - if not re.match(r"[fbmudw]{1,3}\/[-0-9]+\/[-#0-9]+", call.data): - return - - action, chat, user = call.data.split("/") - - msg_id = None - - try: - user, msg_id = user.split("#") - msg_id = int(msg_id) - except Exception: - pass - - chat, user = int(chat), int(user) - - if not await self.check_admin(chat, call.from_user.id): - await call.answer("You are not admin") - return - - try: - user = await self._client.get_entity(user) - except Exception: - await call.answer("Unable to resolve entity") - return - - try: - adm = await self._client.get_entity(call.from_user.id) - except Exception: - await call.answer("Unable to resolve admin entity") - return - - p = ( - await self._client(GetParticipantRequest(chat, call.from_user.id)) - ).participant - - owner = isinstance(p, ChannelParticipantCreator) - - if action == "ub": - if not owner and not p.admin_rights.ban_users: - await call.answer("Not enough rights!") - return - - await self._client.edit_permissions( - chat, - user, - until_date=0, - **{right: True for right in BANNED_RIGHTS.keys()}, - ) - msg = self.strings("inline_unbanned").format( - utils.get_link(user), - get_full_name(user), - utils.get_link(adm), - get_full_name(adm), - ) - try: - await self.inline.bot.edit_message_text( - msg, - inline_message_id=call.inline_message_id, - parse_mode="HTML", - disable_web_page_preview=False, - ) - except Exception: - await self._client.send_message(chat, msg) - elif action == "um": - if not owner and not p.admin_rights.ban_users: - await call.answer("Not enough rights!") - return - - await self._client.edit_permissions( - chat, - user, - until_date=0, - send_messages=True, - ) - msg = self.strings("inline_unmuted").format( - utils.get_link(user), - get_full_name(user), - utils.get_link(adm), - get_full_name(adm), - ) - try: - await self.inline.bot.edit_message_text( - msg, - inline_message_id=call.inline_message_id, - parse_mode="HTML", - disable_web_page_preview=False, - ) - except Exception: - await self._client.send_message(chat, msg) - elif action == "dw": - if not owner and not p.admin_rights.ban_users: - await call.answer("Not enough rights!") - return - - fed = await self.find_fed(chat) - - self.api.request( - { - "action": "forgive user warn", - "args": {"uid": self.api.feds[fed]["uid"], "user": user.id}, - } - ) - - msg = self.strings("inline_unwarned").format( - utils.get_link(user), - get_full_name(user), - utils.get_link(adm), - get_full_name(adm), - ) - - try: - await self.inline.bot.edit_message_text( - msg, - inline_message_id=call.inline_message_id, - parse_mode="HTML", - disable_web_page_preview=False, - ) - except Exception: - await self._client.send_message(chat, msg) - elif action == "ufb": - if not owner and not p.admin_rights.ban_users: - await call.answer("Not enough rights!") - return - - m = await self._client.send_message( - chat, f"{self.get_prefix()}funban {user.id}" - ) - await self.funbancmd(m) - await m.delete() - msg = self.strings("inline_funbanned").format( - utils.get_link(user), - get_full_name(user), - utils.get_link(adm), - get_full_name(adm), - ) - try: - await self.inline.bot.edit_message_text( - msg, - inline_message_id=call.inline_message_id, - parse_mode="HTML", - disable_web_page_preview=False, - ) - except Exception: - await self._client.send_message(chat, msg) - elif action == "ufm": - if not owner and not p.admin_rights.ban_users: - await call.answer("Not enough rights!") - return - - m = await self._client.send_message( - chat, f"{self.get_prefix()}funmute {user.id}" - ) - await self.funmutecmd(m) - await m.delete() - msg = self.strings("inline_funmuted").format( - utils.get_link(user), - get_full_name(user), - utils.get_link(adm), - get_full_name(adm), - ) - try: - await self.inline.bot.edit_message_text( - msg, - inline_message_id=call.inline_message_id, - parse_mode="HTML", - disable_web_page_preview=False, - ) - except Exception: - await self._client.send_message(chat, msg) - elif action == "fb": - if not owner and not p.admin_rights.ban_users: - await call.answer("Not enough rights!") - return - - m = await self._client.send_message( - chat, f"{self.get_prefix()}fban {user.id}" - ) - await self.fbancmd(m) - await m.delete() - msg = self.strings("inline_fbanned").format( - utils.get_link(user), - get_full_name(user), - utils.get_link(adm), - get_full_name(adm), - ) - try: - await self.inline.bot.edit_message_text( - msg, - inline_message_id=call.inline_message_id, - parse_mode="HTML", - disable_web_page_preview=False, - ) - except Exception: - await self._client.send_message(chat, msg) - elif action == "m": - if not owner and not p.admin_rights.ban_users: - await call.answer("Not enough rights!") - return - - await self.mute(chat, user, 0, silent=True) - msg = self.strings("inline_muted").format( - utils.get_link(user), - get_full_name(user), - utils.get_link(adm), - get_full_name(adm), - ) - try: - await self.inline.bot.edit_message_text( - msg, - inline_message_id=call.inline_message_id, - parse_mode="HTML", - disable_web_page_preview=False, - ) - except Exception: - await self._client.send_message(chat, msg) - elif action == "d": - if not owner and not p.admin_rights.delete_messages: - await call.answer("Not enough rights!") - return - - msg = self.strings("inline_deleted").format( - utils.get_link(adm), - get_full_name(adm), - ) - - await self.inline.bot.edit_message_text( - msg, - inline_message_id=call.inline_message_id, - parse_mode="HTML", - disable_web_page_preview=False, - ) - else: - return - - if msg_id is not None: - await self._client.delete_messages(chat, message_ids=[msg_id]) - - async def args_parser( - self, - message: Message, - include_force: bool = False, - include_silent: bool = False, - ) -> tuple: - """Get args from message""" - args = " " + utils.get_args_raw(message) - if include_force and " -f" in args: - force = True - args = args.replace(" -f", "") - else: - force = False - - if include_silent and " -s" in args: - silent = True - args = args.replace(" -s", "") - else: - silent = False - - args = args.strip() - - reply = await message.get_reply_message() - - if reply and not args: - return ( - (await self._client.get_entity(reply.sender_id)), - 0, - utils.escape_html(self.strings("no_reason")).strip(), - *((force,) if include_force else []), - *((silent,) if include_silent else []), - ) - - try: - a = args.split()[0] - if str(a).isdigit(): - a = int(a) - user = await self._client.get_entity(a) - except Exception: - try: - user = await self._client.get_entity(reply.sender_id) - except Exception: - return False - - t = ([arg for arg in args.split() if self.convert_time(arg)] or ["0"])[0] - args = args.replace(t, "").replace(" ", " ") - t = self.convert_time(t) - - if not reply: - try: - args = " ".join(args.split()[1:]) - except Exception: - pass - - if time.time() + t >= 2208978000: # 01.01.2040 00:00:00 - t = 0 - - return ( - user, - t, - utils.escape_html(args or self.strings("no_reason")).strip(), - *((force,) if include_force else []), - *((silent,) if include_silent else []), - ) - - async def find_fed(self, message: typing.Union[Message, int]) -> str: - """Find if chat belongs to any federation""" - return next( - ( - federation - for federation, info in self.api.feds.items() - if str( - utils.get_chat_id(message) - if isinstance(message, Message) - else message - ) - in list(map(str, info["chats"])) - ), - None, - ) - - @error_handler - async def punish( - self, - chat_id: int, - user: typing.Union[int, Channel, User], - violation: str, - action: str, - user_name: str, - fulltime: bool = False, - message: Message = None, - ): - """ - Callback, called if the protection is triggered - Queue is being used to prevent spammy behavior - It is being processed in a loop `_punish_queue_handler` - """ - self._punish_queue += [ - [chat_id, user, violation, action, user_name, fulltime, message] - ] - - @error_handler - async def purgecmd(self, message: Message): - """[user(-s)] - Clean message history starting from replied one""" - if not message.is_reply: - await utils.answer(message, self.strings("from_where", message)) - return - - from_users = set() - args = utils.get_args(message) - - for arg in args: - try: - entity = await message.client.get_entity(arg) - - if isinstance(entity, User): - from_users.add(entity.id) - except ValueError: - pass - - messages = [] - - async for msg in self._client.iter_messages( - entity=message.peer_id, - min_id=message.reply_to_msg_id - 1, - reverse=True, - ): - logger.debug(msg) - if (not from_users or msg.sender_id in from_users) and ( - not getattr(message.reply_to, "forum_topic", False) - or msg.reply_to - and (msg.reply_to.reply_to_top_id or msg.reply_to.reply_to_msg_id) - == ( - message.reply_to.reply_to_top_id or message.reply_to.reply_to_msg_id - ) - ): - messages += [msg.id] - - if len(messages) >= 99: - await self._client.delete_messages(message.peer_id, messages) - messages.clear() - - if messages: - await self._client.delete_messages(message.peer_id, messages) - - async def delcmd(self, message): - """Delete the replied message""" - await self._client.delete_messages( - message.peer_id, - [ - ( - ( - await self._client.iter_messages( - message.peer_id, 1, max_id=message.id - ).__anext__() - ) - if not message.is_reply - else (await message.get_reply_message()) - ).id, - message.id, - ], - ) - - @loader.loop(interval=0.5, autostart=True) - async def _punish_queue_handler(self): - while self._punish_queue: - ( - chat_id, - user, - violation, - action, - user_name, - fulltime, - message, - ) = self._punish_queue.pop() - if str(chat_id) not in self._flood_fw_protection: - self._flood_fw_protection[str(chat_id)] = {} - - if ( - self._flood_fw_protection[str(chat_id)].get(str(user.id), 0) - >= time.time() - ): - continue - - comment = None - - if action == "ban": - comment = "banned him" - await self.ban( - chat_id, - user, - 0, - violation, - silent=str(chat_id) in self._ban_ninja or self.config["silent"], - ) - elif action == "fban": - comment = "f-banned him" - await self.fbancmd( - await self._client.send_message( - chat_id, - f"{self.get_prefix()}fban {user.id} {violation}", - ) - ) - elif action == "delmsg": - # Do nothing... - ... - elif action == "kick": - comment = "kicked him" - await self._client.kick_participant(chat_id, user) - elif action == "mute": - if fulltime: - comment = "muted him forever" - await self.mute( - chat_id, - user, - 0, - violation, - silent=str(chat_id) in self._ban_ninja or self.config["silent"], - ) - else: - comment = "muted him for 1 hour" - await self.mute( - chat_id, - user, - 60 * 60, - violation, - silent=str(chat_id) in self._ban_ninja or self.config["silent"], - ) - elif action == "warn": - comment = "warned him" - warn_msg = await self._client.send_message( - chat_id, f".warn {user.id} {violation}" - ) - await self.allmodules.commands["warn"](warn_msg) - await warn_msg.delete() - - if message is not None: - try: - await self.inline.bot.delete_message( - int(f"-100{chat_id}"), - message.id, - ) - except Exception: - with contextlib.suppress(Exception): - await message.delete() - - if not comment: - continue - - if str(chat_id) not in self._ban_ninja and not self.config["silent"]: - self._flood_fw_protection[str(chat_id)][str(user.id)] = round( - time.time() + 10 - ) - await self._client.send_message( - chat_id, - self.strings(violation).format( - utils.get_link(user), - user_name, - comment, - ), - ) - - @error_handler - async def versioncmd(self, message: Message): - """Get module info""" - await utils.answer( - message, - self.strings("version").format( - ver, - ( - "✅ Connected" - if self.api._connected - else ("🔁 Connecting..." if self.api._inited else "🗃 Local") - ), - ), - ) - - @error_handler - @chat_command - async def deletedcmd(self, message: Message): - """Remove deleted accounts from chat""" - chat = await message.get_chat() - - if not chat.admin_rights and not chat.creator: - await utils.answer(message, self.strings("not_admin")) - return - - kicked = 0 - - message = await utils.answer(message, self.strings("cleaning")) - - async for user in self._client.iter_participants(chat): - if user.deleted: - try: - await self._client.kick_participant(chat, user) - await self._client.edit_permissions( - chat, - user, - until_date=0, - **{right: True for right in BANNED_RIGHTS.keys()}, - ) - kicked += 1 - except Exception: - pass - - await utils.answer(message, self.strings("deleted").format(kicked)) - - @error_handler - @chat_command - async def fcleancmd(self, message: Message): - """Remove deleted accounts from federation""" - fed = await self.find_fed(message) - - if not fed: - await utils.answer(message, self.strings("no_fed")) - return - - chats = self.api.feds[fed]["chats"] - cleaned_in = [] - cleaned_in_c = [] - - message = await utils.answer(message, self.strings("fcleaning")) - - overall = 0 - - for c in chats: - try: - if str(c).isdigit(): - c = int(c) - chat = await self._client.get_entity(c) - except Exception: - continue - - if not chat.admin_rights and not chat.creator: - continue - - try: - kicked = 0 - async for user in self._client.iter_participants(chat): - if user.deleted: - try: - await self._client.kick_participant(chat, user) - await self._client.edit_permissions( - chat, - user, - until_date=0, - **{right: True for right in BANNED_RIGHTS.keys()}, - ) - kicked += 1 - except Exception: - pass - - overall += kicked - cleaned_in += [ - "👥 <a" - f' href="{utils.get_link(chat)}">{utils.escape_html(chat.title)}</a>' - f" - {kicked}" - ] - except UserAdminInvalidError: - pass - - if str(c) in self._linked_channels and self._linked_channels[str(c)]: - channel = await self._client.get_entity(self._linked_channels[str(c)]) - kicked = 0 - try: - async for user in self._client.iter_participants( - self._linked_channels[str(c)] - ): - if user.deleted: - try: - await self._client.kick_participant( - self._linked_channels[str(c)], - user, - ) - await self._client.edit_permissions( - self._linked_channels[str(c)], - user, - until_date=0, - **{right: True for right in BANNED_RIGHTS.keys()}, - ) - kicked += 1 - except Exception: - pass - - overall += kicked - cleaned_in_c += [ - "<emoji document_id=5213203794619277246>📣</emoji> <a" - f' href="{utils.get_link(channel)}">{utils.escape_html(channel.title)}</a>' - f" - {kicked}" - ] - except ChatAdminRequiredError: - pass - - await utils.answer( - message, - self.strings("deleted").format(overall) - + "\n\n<b>" - + "\n".join(cleaned_in) - + "</b>" - + "\n\n<b>" - + "\n".join(cleaned_in_c) - + "</b>", - ) - - @error_handler - @chat_command - async def newfedcmd(self, message: Message): - """<shortname> <name> - Create new federation""" - args = utils.get_args_raw(message) - if not args or args.count(" ") == 0: - await utils.answer(message, self.strings("args")) - return - - shortname, name = args.split(maxsplit=1) - if shortname in self.api.feds: - await utils.answer(message, self.strings("fedexists")) - return - - self.api.request( - { - "action": "create federation", - "args": {"shortname": shortname, "name": name}, - }, - message, - ) - - await utils.answer(message, self.strings("newfed").format(name)) - - async def inline__confirm_rmfed(self, call: CallbackQuery, args: str): - name = self.api.feds[args]["name"] - - self.api.request( - {"action": "delete federation", "args": {"uid": self.api.feds[args]["uid"]}} - ) - - await call.edit(self.strings("rmfed").format(name)) - - @error_handler - @chat_command - async def rmfedcmd(self, message: Message): - """<shortname> - Remove federation""" - args = utils.get_args_raw(message) - if not args: - await utils.answer(message, self.strings("args")) - return - - if args not in self.api.feds: - await utils.answer(message, self.strings("fed404")) - return - - await self.inline.form( - self.strings("confirm_rmfed").format( - utils.escape_html(self.api.feds[args]["name"]) - ), - message=message, - reply_markup=[ - { - "text": self.strings("confirm_rmfed_btn"), - "callback": self.inline__confirm_rmfed, - "args": (args,), - }, - { - "text": self.strings("decline_rmfed_btn"), - "action": "close", - }, - ], - silent=True, - ) - - @error_handler - @chat_command - async def fpromotecmd(self, message: Message): - """<user> - Promote user in federation""" - fed = await self.find_fed(message) - - if not fed: - await utils.answer(message, self.strings("no_fed")) - return - - reply = await message.get_reply_message() - args = utils.get_args_raw(message) - if not reply and not args: - await utils.answer(message, self.strings("args")) - return - - user = reply.sender_id if reply else args - try: - try: - if str(user).isdigit(): - user = int(user) - obj = await self._client.get_entity(user) - except Exception: - await utils.answer(message, self.strings("args")) - return - - name = get_full_name(obj) - except Exception: - await utils.answer(message, self.strings("args")) - return - - self.api.request( - { - "action": "promote user in federation", - "args": {"uid": self.api.feds[fed]["uid"], "user": obj.id}, - }, - message, - ) - - await utils.answer( - message, - self.strings("fpromoted").format( - utils.get_link(obj), - name, - self.api.feds[fed]["name"], - ), - ) - - @error_handler - @chat_command - async def fdemotecmd(self, message: Message): - """<shortname> <reply|user> - Demote user in federation""" - fed = await self.find_fed(message) - - if not fed: - await utils.answer(message, self.strings("no_fed")) - return - - reply = await message.get_reply_message() - args = utils.get_args_raw(message) - if not reply and not args: - await utils.answer(message, self.strings("args")) - return - - user = reply.sender_id if reply else args - try: - try: - if str(user).isdigit(): - user = int(user) - obj = await self._client.get_entity(user) - except Exception: - await utils.answer(message, self.strings("args")) - return - - user = obj.id - - name = get_full_name(obj) - except Exception: - logger.exception("Parsing entity exception") - name = "User" - - self.api.request( - { - "action": "demote user in federation", - "args": {"uid": self.api.feds[fed]["uid"], "user": obj.id}, - }, - message, - ) - - await utils.answer( - message, - self.strings("fdemoted").format( - user, - name, - self.api.feds[fed]["name"], - ), - ) - - @error_handler - @chat_command - async def faddcmd(self, message: Message): - """<fed name> - Add chat to federation""" - args = utils.get_args_raw(message) - if not args: - await utils.answer(message, self.strings("args")) - return - - if args not in self.api.feds: - await utils.answer(message, self.strings("fed404")) - return - - chat = utils.get_chat_id(message) - - self.api.request( - { - "action": "add chat to federation", - "args": {"uid": self.api.feds[args]["uid"], "cid": chat}, - }, - message, - ) - - await utils.answer( - message, - self.strings("fadded").format( - self.api.feds[args]["name"], - ), - ) - - @error_handler - @chat_command - async def frmcmd(self, message: Message): - """Remove chat from federation""" - fed = await self.find_fed(message) - if not fed: - await utils.answer(message, self.strings("fed404")) - return - - chat = utils.get_chat_id(message) - - self.api.request( - { - "action": "remove chat from federation", - "args": {"uid": self.api.feds[fed]["uid"], "cid": chat}, - }, - message, - ) - - await utils.answer( - message, - self.strings("frem").format( - self.api.feds[fed]["name"], - ), - ) - - @loader.command( - ru_doc=( - "<реплай | юзер> [причина] [-s] - Заблокировать пользователя во всех чатах," - " где ты админ" - ) - ) - async def gban(self, message: Message): - """<reply|user> [reason] [-s] - Ban user in all chats where you are admin""" - reply = await message.get_reply_message() - args = utils.get_args_raw(message) - if not reply and not args: - await utils.answer(message, self.strings("args")) - return - - a = await self.args_parser(message, include_silent=True) - - if not a: - await utils.answer(message, self.strings("args")) - return - - user, t, reason, silent = a - - message = await utils.answer( - message, - self.strings("gbanning").format( - utils.get_entity_url(user), - utils.escape_html(get_full_name(user)), - ), - ) - - if not self._gban_cache or self._gban_cache["exp"] < time.time(): - self._gban_cache = { - "exp": int(time.time()) + 10 * 60, - "chats": [ - chat.entity.id - async for chat in self._client.iter_dialogs() - if ( - ( - isinstance(chat.entity, Chat) - or ( - isinstance(chat.entity, Channel) - and getattr(chat.entity, "megagroup", False) - ) - ) - and chat.entity.admin_rights - and chat.entity.participants_count > 5 - and chat.entity.admin_rights.ban_users - ) - ], - } - - chats = "" - counter = 0 - - for chat in self._gban_cache["chats"]: - try: - await self.ban(chat, user, 0, reason, silent=True) - except Exception: - pass - else: - chats += '▫️ <b><a href="{}">{}</a></b>\n'.format( - utils.get_entity_url(await self._client.get_entity(chat, exp=0)), - utils.escape_html( - get_full_name(await self._client.get_entity(chat, exp=0)) - ), - ) - counter += 1 - - await utils.answer( - message, - self.strings("gban").format( - utils.get_entity_url(user), - utils.escape_html(get_full_name(user)), - reason, - self.strings("in_n_chats").format(counter) if silent else chats, - ), - ) - - @loader.command( - ru_doc=( - "<реплай | юзер> [причина] [-s] - Разблокировать пользователя во всех" - " чатах, где ты админ" - ) - ) - async def gunban(self, message: Message): - """<reply|user> [reason] [-s] - Unban user in all chats where you are admin""" - reply = await message.get_reply_message() - args = utils.get_args_raw(message) - if not reply and not args: - await utils.answer(message, self.strings("args")) - return - - a = await self.args_parser(message, include_silent=True) - - if not a: - await utils.answer(message, self.strings("args")) - return - - user, t, reason, silent = a - - message = await utils.answer( - message, - self.strings("gunbanning").format( - utils.get_entity_url(user), - utils.escape_html(get_full_name(user)), - ), - ) - - if not self._gban_cache or self._gban_cache["exp"] < time.time(): - self._gban_cache = { - "exp": int(time.time()) + 10 * 60, - "chats": [ - chat.entity.id - async for chat in self._client.iter_dialogs() - if ( - ( - isinstance(chat.entity, Chat) - or ( - isinstance(chat.entity, Channel) - and getattr(chat.entity, "megagroup", False) - ) - ) - and chat.entity.admin_rights - and chat.entity.participants_count > 5 - and chat.entity.admin_rights.ban_users - ) - ], - } - - chats = "" - counter = 0 - - for chat in self._gban_cache["chats"]: - try: - await self._client.edit_permissions( - chat, - user, - until_date=0, - **{right: True for right in BANNED_RIGHTS.keys()}, - ) - except Exception: - pass - else: - chats += '▫️ <b><a href="{}">{}</a></b>\n'.format( - utils.get_entity_url(await self._client.get_entity(chat, exp=0)), - utils.escape_html( - get_full_name(await self._client.get_entity(chat, exp=0)) - ), - ) - counter += 1 - - await utils.answer( - message, - self.strings("gunban").format( - utils.get_entity_url(user), - utils.escape_html(get_full_name(user)), - ( - self.strings("unbanned_in_n_chats").format(counter) - if silent - else chats - ), - ), - ) - - @error_handler - @chat_command - async def fbancmd(self, message: Message): - """<reply | user> [reason] - Ban user in federation""" - fed = await self.find_fed(message) - - if not fed: - await utils.answer(message, self.strings("no_fed")) - return - - a = await self.args_parser(message, include_force=True) - - if not a: - await utils.answer(message, self.strings("args")) - return - - user, t, reason, force = a - - if not force and user.id in list(map(int, self.api.feds[fed]["fdef"])): - await utils.answer(message, self.strings("fdef403").format("fban")) - return - - chats = self.api.feds[fed]["chats"] - - banned_in = [] - - for c in chats: - try: - if str(c).isdigit(): - c = int(c) - chat = await self._client.get_entity(c) - except Exception: - continue - - if not chat.admin_rights and not chat.creator: - continue - - try: - await self.ban(chat, user, t, reason, message, silent=True) - banned_in += [ - f'<a href="{utils.get_link(chat)}">{get_full_name(chat)}</a>' - ] - except Exception: - pass - - msg = ( - self.strings("fban").format( - utils.get_link(user), - get_first_name(user), - self.api.feds[fed]["name"], - f"for {t // 60} min(-s)" if t else "forever", - reason, - self.get("punish_suffix", ""), - ) - + "\n\n<b>" - + "\n".join(banned_in) - + "</b>" - ) - - if self._is_inline: - punishment_info = { - "reply_markup": { - "text": self.strings("btn_funban"), - "data": f"ufb/{utils.get_chat_id(message)}/{user.id}", - }, - } - - if self.get("logchat"): - await utils.answer(message, msg) - await self.inline.form( - text=self.strings("fban").format( - utils.get_link(user), - get_first_name(user), - self.api.feds[fed]["name"], - f"for {t // 60} min(-s)" if t else "forever", - reason, - "", - ) - + "<b>" - + "\n".join(banned_in) - + "</b>", - message=self.get("logchat"), - **punishment_info, - silent=True, - ) - else: - await self.inline.form( - text=msg, message=message, **punishment_info, silent=True - ) - else: - await utils.answer(message, msg) - - self.api.request( - { - "action": "clear all user warns", - "args": { - "uid": self.api.feds[fed]["uid"], - "user": user.id, - "silent": True, - }, - }, - message, - ) - - reply = await message.get_reply_message() - if reply: - await reply.delete() - - @error_handler - @chat_command - async def punishsuffcmd(self, message: Message): - """Set new punishment suffix""" - if not utils.get_args_raw(message): - self.set("punish_suffix", "") - await utils.answer(message, self.strings("suffix_removed")) - else: - suffix = utils.get_args_raw(message) - self.set("punish_suffix", suffix) - await utils.answer(message, self.strings("suffix_updated").format(suffix)) - - @error_handler - @chat_command - async def sethclogcmd(self, message: Message): - """Set logchat""" - if not utils.get_args_raw(message): - self.set("logchat", "") - await utils.answer(message, self.strings("logchat_removed")) - return - - logchat = utils.get_args_raw(message) - if logchat.isdigit(): - logchat = int(logchat) - - try: - logchat = await self._client.get_entity(logchat) - except Exception: - await utils.answer(message, self.strings("logchat_invalid")) - return - - self.set("logchat", logchat.id) - await utils.answer( - message, - self.strings("logchat_set").format(utils.escape_html(logchat.title)), - ) - - @error_handler - @chat_command - async def funbancmd(self, message: Message): - """<user> [reason] - Unban user in federation""" - fed = await self.find_fed(message) - - if not fed: - await utils.answer(message, self.strings("no_fed")) - return - - a = await self.args_parser(message) - - if not a: - await utils.answer(message, self.strings("args")) - return - - user, _, _ = a - - chats = self.api.feds[fed]["chats"] - - unbanned_in = [] - - for c in chats: - try: - if str(c).isdigit(): - c = int(c) - chat = await self._client.get_entity(c) - except Exception: - continue - - if not chat.admin_rights and not chat.creator: - continue - - try: - await self._client.edit_permissions( - chat, - user, - until_date=0, - **{right: True for right in BANNED_RIGHTS.keys()}, - ) - unbanned_in += [chat.title] - except UserAdminInvalidError: - pass - - m = ( - self.strings("funban").format( - utils.get_link(user), - get_first_name(user), - self.api.feds[fed]["name"], - ) - + "<b>" - + "\n".join(unbanned_in) - + "</b>" - ) - - if self.get("logchat"): - await self._client.send_message(self.get("logchat"), m) - - await utils.answer(message, m) - - @error_handler - @chat_command - async def fmutecmd(self, message: Message): - """<reply | user> [reason] - Mute user in federation""" - fed = await self.find_fed(message) - - if not fed: - await utils.answer(message, self.strings("no_fed")) - return - - a = await self.args_parser(message, include_force=True) - - if not a: - await utils.answer(message, self.strings("args")) - return - - user, t, reason, force = a - - if not force and user.id in list(map(int, self.api.feds[fed]["fdef"])): - await utils.answer(message, self.strings("fdef403").format("fmute")) - return - - chats = self.api.feds[fed]["chats"] - - muted_in = [] - - for c in chats: - try: - if str(c).isdigit(): - c = int(c) - chat = await self._client.get_entity(c) - except Exception: - continue - - if not chat.admin_rights and not chat.creator: - continue - - try: - await self.mute(chat, user, t, reason, message, silent=True) - muted_in += [ - f'<a href="{utils.get_link(chat)}">{get_full_name(chat)}</a>' - ] - except Exception: - pass - - msg = ( - self.strings("fmute").format( - utils.get_link(user), - get_first_name(user), - self.api.feds[fed]["name"], - f"for {t // 60} min(-s)" if t else "forever", - reason, - self.get("punish_suffix", ""), - ) - + "\n\n<b>" - + "\n".join(muted_in) - + "</b>" - ) - - if self._is_inline: - punishment_info = { - "reply_markup": { - "text": self.strings("btn_funmute"), - "data": f"ufm/{utils.get_chat_id(message)}/{user.id}", - }, - } - - if self.get("logchat"): - await utils.answer(message, msg) - await self.inline.form( - text=self.strings("fmute").format( - utils.get_link(user), - get_first_name(user), - self.api.feds[fed]["name"], - f"for {t // 60} min(-s)" if t else "forever", - reason, - "", - ) - + "\n\n<b>" - + "\n".join(muted_in) - + "</b>", - message=self.get("logchat"), - **punishment_info, - silent=True, - ) - else: - await self.inline.form( - text=msg, message=message, **punishment_info, silent=True - ) - else: - await utils.answer(message, msg) - - @error_handler - @chat_command - async def funmutecmd(self, message: Message): - """<user> [reason] - Unban user in federation""" - fed = await self.find_fed(message) - - if not fed: - await utils.answer(message, self.strings("no_fed")) - return - - a = await self.args_parser(message) - - if not a: - await utils.answer(message, self.strings("args")) - return - - user, _, _ = a - - chats = self.api.feds[fed]["chats"] - - unbanned_in = [] - - for c in chats: - try: - if str(c).isdigit(): - c = int(c) - chat = await self._client.get_entity(c) - except Exception: - continue - - if not chat.admin_rights and not chat.creator: - continue - - try: - await self._client.edit_permissions( - chat, - user, - until_date=0, - **{right: True for right in BANNED_RIGHTS.keys()}, - ) - unbanned_in += [chat.title] - except UserAdminInvalidError: - pass - - msg = ( - self.strings("funmute").format( - utils.get_link(user), - get_first_name(user), - self.api.feds[fed]["name"], - ) - + "\n\n<b>" - + "\n".join(unbanned_in) - + "</b>" - ) - - await utils.answer(message, msg) - - if self.get("logchat"): - await self._client.send_message(self.get("logchat"), msg) - - @error_handler - @chat_command - async def kickcmd(self, message: Message): - """<user> [reason] - Kick user""" - chat = await message.get_chat() - - if not chat.admin_rights and not chat.creator: - await utils.answer(message, self.strings("not_admin")) - return - - reply = await message.get_reply_message() - args = utils.get_args_raw(message) - user, reason = None, None - - try: - if reply: - user = await self._client.get_entity(reply.sender_id) - reason = args or self.strings - else: - uid = args.split(maxsplit=1)[0] - if str(uid).isdigit(): - uid = int(uid) - user = await self._client.get_entity(uid) - reason = ( - args.split(maxsplit=1)[1] - if len(args.split(maxsplit=1)) > 1 - else self.strings("no_reason") - ) - except Exception: - await utils.answer(message, self.strings("args")) - return - - try: - await self._client.kick_participant(utils.get_chat_id(message), user) - msg = self.strings("kick").format( - utils.get_link(user), - get_first_name(user), - reason, - self.get("punish_suffix", ""), - ) - await utils.answer(message, msg) - - if self.get("logchat"): - await self._client.send_message( - self.get("logchat"), - self.strings("kick_log").format( - utils.get_link(user), - get_first_name(user), - utils.get_link(chat), - get_first_name(chat), - reason, - "", - ), - ) - except UserAdminInvalidError: - await utils.answer(message, self.strings("not_admin")) - return - - @error_handler - @chat_command - async def bancmd(self, message: Message): - """<user> [reason] - Ban user""" - chat = await message.get_chat() - - a = await self.args_parser(message, include_force=True) - if not a: - await utils.answer(message, self.strings("args")) - return - - user, t, reason, force = a - - if not chat.admin_rights and not chat.creator: - await utils.answer(message, self.strings("not_admin")) - return - - fed = await self.find_fed(message) - if ( - not force - and fed in self.api.feds - and user.id in list(map(int, self.api.feds[fed]["fdef"])) - ): - await utils.answer(message, self.strings("fdef403").format("ban")) - return - - try: - await self.ban(chat, user, t, reason, message) - except UserAdminInvalidError: - await utils.answer(message, self.strings("not_admin")) - return - - @error_handler - @chat_command - async def mutecmd(self, message: Message): - """<user> [time] [reason] - Mute user""" - chat = await message.get_chat() - - a = await self.args_parser(message, include_force=True) - if not a: - await utils.answer(message, self.strings("args")) - return - - user, t, reason, force = a - - if not chat.admin_rights and not chat.creator: - await utils.answer(message, self.strings("not_admin")) - return - - fed = await self.find_fed(message) - if ( - not force - and fed in self.api.feds - and user.id in list(map(int, self.api.feds[fed]["fdef"])) - ): - await utils.answer(message, self.strings("fdef403").format("mute")) - return - - try: - await self.mute(chat, user, t, reason, message) - except UserAdminInvalidError: - await utils.answer(message, self.strings("not_admin")) - return - - @error_handler - @chat_command - async def unmutecmd(self, message: Message): - """<reply | user> - Unmute user""" - chat = await message.get_chat() - - if not chat.admin_rights and not chat.creator: - await utils.answer(message, self.strings("not_admin")) - return - - reply = await message.get_reply_message() - args = utils.get_args_raw(message) - user = None - - try: - if args.isdigit(): - args = int(args) - user = await self._client.get_entity(args) - except Exception: - try: - user = await self._client.get_entity(reply.sender_id) - except Exception: - await utils.answer(message, self.strings("args")) - return - - try: - await self._client.edit_permissions( - chat, - user, - until_date=0, - send_messages=True, - ) - msg = self.strings("unmuted").format( - utils.get_link(user), get_first_name(user) - ) - await utils.answer(message, msg) - - if self.get("logchat"): - await self._client.send_message( - self.get("logchat"), - self.strings("unmuted_log").format( - utils.get_link(user), - get_first_name(user), - utils.get_link(chat), - get_first_name(chat), - ), - ) - except UserAdminInvalidError: - await utils.answer(message, self.strings("not_admin")) - return - - @error_handler - @chat_command - async def unbancmd(self, message: Message): - """<user> - Unban user""" - chat = await message.get_chat() - - if not chat.admin_rights and not chat.creator: - await utils.answer(message, self.strings("not_admin")) - return - - reply = await message.get_reply_message() - args = utils.get_args_raw(message) - user = None - - try: - if args.isdigit(): - args = int(args) - user = await self._client.get_entity(args) - except Exception: - try: - user = await self._client.get_entity(reply.sender_id) - except Exception: - await utils.answer(message, self.strings("args")) - return - - try: - await self._client.edit_permissions( - chat, - user, - until_date=0, - **{right: True for right in BANNED_RIGHTS.keys()}, - ) - msg = self.strings("unban").format( - utils.get_link(user), get_first_name(user) - ) - await utils.answer(message, msg) - - if self.get("logchat"): - await self._client.send_message( - self.get("logchat"), - self.strings("unban_log").format( - utils.get_link(user), - get_first_name(user), - utils.get_link(chat), - get_first_name(chat), - ), - ) - except UserAdminInvalidError: - await utils.answer(message, self.strings("not_admin")) - return - - @error_handler - async def protectscmd(self, message: Message): - """typing.List available filters""" - await utils.answer( - message, - ( - self.strings("protections") - if self.api._inited - else "\n".join( - [ - line - for line in self.strings("protections").splitlines() - if "antinsfw" not in line.lower() - and "report" not in line.lower() - ] - ) - ), - ) - - @error_handler - async def fedscmd(self, message: Message): - """typing.List federations""" - res = self.strings("feds_header") - - if not self.api.feds: - await utils.answer(message, self.strings("no_federations")) - return - - for shortname, config in self.api.feds.copy().items(): - res += f" ☮️ <b>{config['name']}</b> (<code>{shortname}</code>)" - for chat in config["chats"]: - try: - if str(chat).isdigit(): - chat = int(chat) - c = await self._client.get_entity(chat) - except Exception: - continue - - res += ( - "\n <b>- <a" - f' href="tg://resolve?domain={getattr(c, "username", "")}">{c.title}</a></b>' - ) - - res += ( - "\n <b><emoji document_id=5193091781327068499>👮‍♀️</emoji>" - f" {len(config.get('warns', []))} warns</b>\n\n" - ) - - await utils.answer(message, res) - - @error_handler - @chat_command - async def fedcmd(self, message: Message): - """<shortname> - Info about federation""" - args = utils.get_args_raw(message) - chat = utils.get_chat_id(message) - - fed = await self.find_fed(message) - - if (not args or args not in self.api.feds) and not fed: - await utils.answer(message, self.strings("no_fed")) - return - - if not args or args not in self.api.feds: - args = fed - - res = self.strings("fed") - - fed = args - - admins = "" - for admin in self.api.feds[fed]["admins"]: - try: - if str(admin).isdigit(): - admin = int(admin) - user = await self._client.get_entity(admin) - except Exception: - continue - name = get_full_name(user) - status = ( - "<code> 🧃 online</code>" - if isinstance(getattr(user, "status", None), UserStatusOnline) - else "" - ) - admins += ( - f' <b>👤 <a href="{utils.get_link(user)}">{name}</a></b>{status}\n' - ) - - chats = "" - channels = "" - for chat in self.api.feds[fed]["chats"]: - try: - if str(chat).isdigit(): - chat = int(chat) - c = await self._client.get_entity(chat) - except Exception: - continue - - if str(chat) in self._linked_channels: - try: - channel = await self._client.get_entity( - self._linked_channels[str(chat)] - ) - channels += ( - " <b><emoji document_id=5213203794619277246>📣</emoji> <a" - f' href="{utils.get_link(channel)}">{utils.escape_html(channel.title)}</a></b>\n' - ) - except Exception: - pass - - chats += ( - " <b>🫂 <a" - f' href="{utils.get_link(c)}">{utils.escape_html(c.title)}</a></b>\n' - ) - - await utils.answer( - message, - res.format( - self.api.feds[fed]["name"], - chats or "-", - channels or "-", - admins or "-", - len(self.api.feds[fed].get("warns", [])), - ), - ) - - @error_handler - @chat_command - async def pchatcmd(self, message: Message): - """typing.List protection for current chat""" - chat_id = utils.get_chat_id(message) - try: - await self.inline.form( - message=message, - **(await self.get_config(chat_id)), - manual_security=True, - silent=True, - ) - except KeyError: - await utils.answer(message, self.strings("no_protects")) - - @error_handler - @chat_command - async def warncmd(self, message: Message): - """<user> - Warn user""" - chat = await message.get_chat() - - if not chat.admin_rights and not chat.creator: - await utils.answer(message, self.strings("not_admin")) - return - - args = utils.get_args_raw(message) - - if " -f" in args: - args = args.replace(" -f", "") - force = True - else: - force = False - - reply = await message.get_reply_message() - user = None - if reply: - user = await self._client.get_entity(reply.sender_id) - reason = args or self.strings("no_reason") - else: - try: - u = args.split(maxsplit=1)[0] - if u.isdigit(): - u = int(u) - - user = await self._client.get_entity(u) - except IndexError: - await utils.answer(message, self.strings("args")) - return - - try: - reason = args.split(maxsplit=1)[1] - except IndexError: - reason = self.strings("no_reason") - - fed = await self.find_fed(message) - - if not fed: - await utils.answer(message, self.strings("no_fed")) - return - - if not force and user.id in list(map(int, self.api.feds[fed]["fdef"])): - await utils.answer(message, self.strings("fdef403").format("warn")) - return - - self.api.request( - { - "action": "warn user", - "args": { - "uid": self.api.feds[fed]["uid"], - "user": user.id, - "reason": reason, - }, - }, - message, - ) - warns = self.api.feds[fed].get("warns", {}).get(str(user.id), []) + [reason] - - if len(warns) >= self.config["warns_limit"]: - user_name = get_first_name(user) - chats = self.api.feds[fed]["chats"] - for c in chats: - if str(c).isdigit(): - c = int(str(c)) - - await self._client( - EditBannedRequest( - c, - user, - ChatBannedRights( - until_date=time.time() + 60**2 * 24 * 7, - send_messages=True, - ), - ) - ) - - if c == utils.get_chat_id(message): - await self._client.send_message( - c, - self.strings("warns_limit").format( - utils.get_link(user), - user_name, - "muted him in federation for 7 days", - ), - ) - - if message.out: - await message.delete() - - self.api.request( - { - "action": "clear all user warns", - "args": {"uid": self.api.feds[fed]["uid"], "user": user.id}, - }, - message, - ) - else: - msg = self.strings("fwarn", message).format( - utils.get_link(user), - get_first_name(user), - len(warns), - self.config["warns_limit"], - reason, - self.get("punish_suffix", ""), - ) - - if self._is_inline: - punishment_info = { - "reply_markup": { - "text": self.strings("btn_unwarn"), - "data": f"dw/{utils.get_chat_id(message)}/{user.id}", - }, - } - - if self.get("logchat"): - await utils.answer(message, msg) - await self.inline.form( - text=self.strings("fwarn", message).format( - utils.get_link(user), - get_first_name(user), - len(warns), - self.config["warns_limit"], - reason, - "", - ), - message=self.get("logchat"), - **punishment_info, - silent=True, - ) - else: - await self.inline.form( - text=msg, message=message, **punishment_info, silent=True - ) - else: - await utils.answer(message, msg) - - @error_handler - @chat_command - async def warnscmd(self, message: Message): - """[user] - Show warns in chat \\ of user""" - chat_id = utils.get_chat_id(message) - - fed = await self.find_fed(message) - - async def check_member(user_id): - try: - await self._client.get_permissions(chat_id, user_id) - return True - except Exception: - return False - - if not fed: - await utils.answer(message, self.strings("no_fed")) - return - - warns = self.api.feds[fed].get("warns", {}) - - if not warns: - await utils.answer(message, self.strings("no_fed_warns")) - return - - async def send_user_warns(usid): - try: - if int(usid) < 0: - usid = int(str(usid)[4:]) - except Exception: - pass - - if not warns: - await utils.answer(message, self.strings("no_fed_warns")) - return - - if str(usid) not in warns or not warns[str(usid)]: - user_obj = await self._client.get_entity(usid) - await utils.answer( - message, - self.strings("no_warns").format( - utils.get_link(user_obj), get_full_name(user_obj) - ), - ) - else: - user_obj = await self._client.get_entity(usid) - _warns = "" - processed = [] - for warn in warns[str(usid)].copy(): - if warn in processed: - continue - processed += [warn] - _warns += ( - "<emoji document_id=4974362561664254705>🛑</emoji> <i>" - + warn - + ( - f" </i><b>[x{warns[str(usid)].count(warn)}]</b><i>" - if warns[str(usid)].count(warn) > 1 - else "" - ) - + "</i>\n" - ) - await utils.answer( - message, - self.strings("warns").format( - utils.get_link(user_obj), - get_full_name(user_obj), - len(warns[str(usid)]), - self.config["warns_limit"], - _warns, - ), - ) - - if not await self.check_admin(chat_id, message.sender_id): - await send_user_warns(message.sender_id) - else: - reply = await message.get_reply_message() - args = utils.get_args_raw(message) - if not reply and not args: - res = self.strings("warns_adm_fed") - for user, _warns in warns.copy().items(): - try: - user_obj = await self._client.get_entity(int(user)) - except Exception: - continue - - if isinstance(user_obj, User): - try: - name = get_full_name(user_obj) - except TypeError: - continue - else: - name = user_obj.title - - res += ( - "<emoji document_id=5467759840463953770>🐺</emoji> <b><a" - f' href="{utils.get_link(user_obj)}">' + name + "</a></b>\n" - ) - processed = [] - for warn in _warns.copy(): - if warn in processed: - continue - processed += [warn] - res += ( - "<code> </code>🏴󠁧󠁢󠁥󠁮󠁧󠁿 <i>" - + warn - + ( - f" </i><b>[x{_warns.count(warn)}]</b><i>" - if _warns.count(warn) > 1 - else "" - ) - + "</i>\n" - ) - - await utils.answer(message, res) - return - elif reply: - await send_user_warns(reply.sender_id) - elif args: - await send_user_warns(args) - - @error_handler - @chat_command - async def delwarncmd(self, message: Message): - """<user> - Forgave last warn""" - args = utils.get_args_raw(message) - reply = await message.get_reply_message() - user = None - - if reply: - user = await self._client.get_entity(reply.sender_id) - else: - if args.isdigit(): - args = int(args) - - try: - user = await self._client.get_entity(args) - except IndexError: - await utils.answer(message, self.strings("args")) - return - - fed = await self.find_fed(message) - - if not fed: - await utils.answer(message, self.strings("no_fed")) - return - - self.api.request( - { - "action": "forgive user warn", - "args": {"uid": self.api.feds[fed]["uid"], "user": user.id}, - }, - message, - ) - - msg = self.strings("dwarn_fed").format( - utils.get_link(user), get_first_name(user) - ) - - await utils.answer(message, msg) - - if self.get("logchat", False): - await self._client.send_message(self.get("logchat"), msg) - - @error_handler - @chat_command - async def clrwarnscmd(self, message: Message): - """<reply | user_id | username> - Remove all warns from user""" - args = utils.get_args_raw(message) - reply = await message.get_reply_message() - user = None - if reply: - user = await self._client.get_entity(reply.sender_id) - else: - if args.isdigit(): - args = int(args) - - try: - user = await self._client.get_entity(args) - except IndexError: - await utils.answer(message, self.strings("args")) - return - - fed = await self.find_fed(message) - - if not fed: - await utils.answer(message, self.strings("no_fed")) - return - - self.api.request( - { - "action": "clear all user warns", - "args": {"uid": self.api.feds[fed]["uid"], "user": user.id}, - }, - message, - ) - - await utils.answer( - message, - self.strings("clrwarns_fed").format( - utils.get_link(user), get_first_name(user) - ), - ) - - @error_handler - @chat_command - async def clrallwarnscmd(self, message: Message): - """Remove all warns from current federation""" - fed = await self.find_fed(message) - - if not fed: - await utils.answer(message, self.strings("no_fed")) - return - - self.api.request( - { - "action": "clear federation warns", - "args": {"uid": self.api.feds[fed]["uid"]}, - }, - message, - ) - - await utils.answer(message, self.strings("clrallwarns_fed")) - - @error_handler - @chat_command - async def welcomecmd(self, message: Message): - """<text> - Change welcome text""" - chat_id = utils.get_chat_id(message) - args = utils.get_args_raw(message) or "off" - - self.api.request( - { - "action": "update protections", - "args": {"protection": "welcome", "state": args, "chat": chat_id}, - }, - message, - ) - - if args and args != "off": - await utils.answer(message, self.strings("welcome").format(args)) - else: - await utils.answer(message, self.strings("unwelcome")) - - @error_handler - @chat_command - async def fdefcmd(self, message: Message): - """<user> - Toggle global user invulnerability""" - fed = await self.find_fed(message) - - if not fed: - await utils.answer(message, self.strings("no_fed")) - return - - args = utils.get_args_raw(message) - reply = await message.get_reply_message() - user = None - if reply: - user = await self._client.get_entity(reply.sender_id) - else: - if str(args).isdigit(): - args = int(args) - - try: - user = await self._client.get_entity(args) - except Exception: - await utils.answer(message, self.strings("args")) - return - - self.api.request( - { - "action": "protect user", - "args": {"uid": self.api.feds[fed]["uid"], "user": user.id}, - }, - message, - ) - - await utils.answer( - message, - self.strings("defense").format( - utils.get_link(user), - get_first_name(user), - "on" if str(user.id) not in self.api.feds[fed]["fdef"] else "off", - ), - ) - - @error_handler - @chat_command - async def fsavecmd(self, message: Message): - """<note name> <reply> - Save federative note""" - fed = await self.find_fed(message) - - if not fed: - await utils.answer(message, self.strings("no_fed")) - return - - args = utils.get_args_raw(message) - reply = await message.get_reply_message() - if not reply or not args or not reply.text: - await utils.answer(message, self.strings("fsave_args")) - return - - self.api.request( - { - "action": "new note", - "args": { - "uid": self.api.feds[fed]["uid"], - "shortname": args, - "note": reply.text, - }, - }, - message, - ) - - await utils.answer(message, self.strings("fsave").format(args)) - - @error_handler - @chat_command - async def fstopcmd(self, message: Message): - """<note name> - Remove federative note""" - fed = await self.find_fed(message) - - if not fed: - await utils.answer(message, self.strings("no_fed")) - return - - args = utils.get_args_raw(message) - if not args: - await utils.answer(message, self.strings("fstop_args")) - return - - self.api.request( - { - "action": "delete note", - "args": {"uid": self.api.feds[fed]["uid"], "shortname": args}, - }, - message, - ) - - await utils.answer(message, self.strings("fstop").format(args)) - - @error_handler - @chat_command - async def fnotescmd(self, message: Message, from_watcher: bool = False): - """Show federative notes""" - fed = await self.find_fed(message) - - if not fed: - await utils.answer(message, self.strings("no_fed")) - return - - res = {} - cache = {} - - for shortname, note in self.api.feds[fed].get("notes", {}).items(): - if int(note["creator"]) != self._tg_id and from_watcher: - continue - - try: - if int(note["creator"]) not in cache: - obj = await self._client.get_entity(int(note["creator"])) - cache[int(note["creator"])] = obj.first_name or obj.title - key = ( - f'<a href="{utils.get_link(obj)}">{cache[int(note["creator"])]}</a>' - ) - if key not in res: - res[key] = "" - res[key] += f" <code>{shortname}</code>\n" - except Exception: - key = "unknown" - if key not in res: - res[key] = "" - res[key] += f" <code>{shortname}</code>\n" - - notes = "".join(f"\nby {owner}:\n{note}" for owner, note in res.items()) - - if not notes and not from_watcher: - await utils.answer(message, self.strings("no_notes")) - return - - if not notes: - return - - await utils.answer(message, self.strings("fnotes").format(notes)) - - @error_handler - @chat_command - async def fdeflistcmd(self, message: Message): - """Show global invulnerable users""" - fed = await self.find_fed(message) - - if not fed: - await utils.answer(message, self.strings("no_fed")) - return - - if not self.api.feds[fed].get("fdef", []): - await utils.answer(message, self.strings("no_defense")) - return - - res = "" - for user in self.api.feds[fed].get("fdef", []).copy(): - try: - u = await self._client.get_entity(int(user), exp=0) - except Exception: - self.api.request( - { - "action": "protect user", - "args": {"uid": self.api.feds[fed]["uid"], "user": user}, - }, - message, - ) - await asyncio.sleep(0.2) - continue - - tit = get_full_name(u) - - res += f' 🇻🇦 <a href="{utils.get_link(u)}">{tit}</a>\n' - - await utils.answer(message, self.strings("defense_list").format(res)) - return - - @error_handler - @chat_command - async def dmutecmd(self, message: Message): - """Delete and mute""" - reply = await message.get_reply_message() - await self.mutecmd(message) - await reply.delete() - - @error_handler - @chat_command - async def dbancmd(self, message: Message): - """Delete and ban""" - reply = await message.get_reply_message() - await self.bancmd(message) - await reply.delete() - - @error_handler - @chat_command - async def dwarncmd(self, message: Message): - """Delete and warn""" - reply = await message.get_reply_message() - await self.warncmd(message) - await reply.delete() - - @error_handler - @chat_command - async def frenamecmd(self, message: Message): - """Rename federation""" - args = utils.get_args_raw(message) - fed = await self.find_fed(message) - - if not fed: - await utils.answer(message, self.strings("no_fed")) - return - - if not args: - await utils.answer(message, self.strings("rename_noargs")) - return - - self.api.request( - { - "action": "rename federation", - "args": {"uid": self.api.feds[fed]["uid"], "name": args}, - }, - message, - ) - - await utils.answer( - message, - self.strings("rename_success").format(utils.escape_html(args)), - ) - - @error_handler - @chat_command - async def clnraidcmd(self, message: Message): - """<number of users> - Clean raid""" - args = utils.get_args_raw(message) - if not args or not args.isdigit(): - await utils.answer(message, self.strings("clnraid_args")) - return - - args = min(int(args), 10000) - - await self.inline.form( - message=message, - text=self.strings("clnraid_confirm").format(args), - reply_markup=[ - { - "text": self.strings("clnraid_yes"), - "callback": self._clnraid, - "args": (utils.get_chat_id(message), args), - }, - { - "text": self.strings("clnraid_cancel"), - "action": "close", - }, - ], - silent=True, - ) - - async def _clnraid( - self, - call: typing.Union[InlineCall, InlineMessage], - chat_id: int, - quantity: int, - ) -> InlineCall: - if call is not None: - await call.edit(self.strings("clnraid_started").format(quantity)) - - deleted = 0 - actually_deleted = 0 - async for log_msg in self._client.iter_admin_log(chat_id, join=True): - if deleted >= quantity: - break - - deleted += 1 - - try: - await self.inline.bot.kick_chat_member( - int(f"-100{chat_id}"), - log_msg.user.id, - ) - except Exception: - logger.debug("Can't kick member", exc_info=True) - else: - actually_deleted += 1 - - if call is not None: - await call.edit(self.strings("clnraid_complete").format(actually_deleted)) - - return call - - @error_handler - async def myrightscmd(self, message: Message): - """typing.List your admin rights in all chats""" - if not PIL_AVAILABLE: - await utils.answer(message, self.strings("pil_unavailable")) - return - - message = await utils.answer(message, self.strings("processing_myrights")) - - rights = [] - async for chat in self._client.iter_dialogs(): - ent = chat.entity - - if ( - not ( - isinstance(ent, Chat) - or (isinstance(ent, Channel) and getattr(ent, "megagroup", False)) - ) - or not ent.admin_rights - or ent.participants_count < 5 - ): - continue - - r = ent.admin_rights - - rights += [ - [ - ent.title if len(ent.title) < 30 else f"{ent.title[:30]}...", - "YES" if r.change_info else "-----", - "YES" if r.delete_messages else "-----", - "YES" if r.ban_users else "-----", - "YES" if r.invite_users else "-----", - "YES" if r.pin_messages else "-----", - "YES" if r.add_admins else "-----", - ] - ] - - await self._client.send_file( - message.peer_id, - self.render_table( - [ - [ - "Chat", - "change_info", - "delete_messages", - "ban_users", - "invite_users", - "pin_messages", - "add_admins", - ] - ] - + rights - ), - ) - - if message.out: - await message.delete() - - @error_handler - async def p__antiservice(self, chat_id: typing.Union[str, int], message: Message): - if ( - self.api.should_protect(chat_id, "antiservice") - and str(chat_id) not in self._ban_ninja - and getattr(message, "action_message", False) - ): - if self.api.should_protect(chat_id, "captcha") and ( - getattr(message, "user_joined", False) - or getattr(message, "user_added", False) - ): - self._delete_soon += [(message, time.time() + 5 * 60)] - return - - try: - await self.inline.bot.delete_message( - int(f"-100{chat_id}"), - message.action_message.id, - ) - except Exception: - await message.delete() - - async def _update_ban_ninja(self, chat_id: str): - while ( - chat_id in self._ban_ninja_forms and self._ban_ninja[chat_id] > time.time() - ): - try: - await self._ban_ninja_forms[chat_id].edit( - self.strings("smart_anti_raid_active").format( - ( - self.strings("forbid_messages") - if self.config["close_on_raid"] - else "" - ), - self._ban_ninja_progress[chat_id], - ), - { - "text": self.strings("smart_anti_raid_off"), - "callback": self.disable_smart_anti_raid, - "args": (chat_id,), - }, - ) - except Exception: - pass - - await asyncio.sleep(15) - - try: - await self.disable_smart_anti_raid(None, chat_id) - except Exception: - pass - - @error_handler - async def p__banninja( - self, - chat_id: typing.Union[str, int], - user_id: typing.Union[str, int], - message: Message, - ) -> bool: - if not ( - self.api.should_protect(chat_id, "banninja") - and ( - getattr(message, "user_joined", False) - or getattr(message, "user_added", False) - ) - ): - return False - - chat_id = str(chat_id) - - if chat_id in self._ban_ninja: - if self._ban_ninja[chat_id] > time.time(): - self._ban_ninja[chat_id] = time.time() + int( - self.config["banninja_cooldown"] - ) - await self.inline.bot.kick_chat_member(int(f"-100{chat_id}"), user_id) - - self._ban_ninja_progress[chat_id] += 1 - - try: - await self.inline.bot.delete_message( - int(f"-100{chat_id}"), - message.action_message.id, - ) - except TelergamAPIError: - await self._promote_bot(chat_id) - await self.inline.bot.delete_message( - int(f"-100{chat_id}"), - message.action_message.id, - ) - logger.debug( - f"BanNinja is active in chat {chat_id=}, I kicked {user_id=}" - ) - return True - - await self.disable_smart_anti_raid(None, chat_id) - - if chat_id not in self._join_ratelimit: - self._join_ratelimit[chat_id] = [] - - self._join_ratelimit[chat_id] += [[user_id, round(time.time())]] - - processed = [] - - for u, t in self._join_ratelimit[chat_id].copy(): - if u in processed or t + 60 < time.time(): - self._join_ratelimit[chat_id].remove([u, t]) - else: - processed += [u] - - self.set("join_ratelimit", self._join_ratelimit) - - if len(self._join_ratelimit[chat_id]) >= self.config["join_ratelimit"]: - if chat_id in self._ban_ninja: - return False - - self._ban_ninja[chat_id] = ( - round(time.time()) + self.config["banninja_cooldown"] - ) - form = await self.inline.form( - self.strings("smart_anti_raid_active").format( - ( - self.strings("forbid_messages") - if self.config["close_on_raid"] - else "" - ), - self.config["join_ratelimit"], - ), - message=int(chat_id), - reply_markup={ - "text": self.strings("smart_anti_raid_off"), - "callback": self.disable_smart_anti_raid, - "args": (chat_id,), - }, - silent=True, - ) - - if self.config["close_on_raid"]: - try: - chat = await message.get_chat() - self._ban_ninja_default_rights[chat_id] = chat.default_banned_rights - await self._client( - EditChatDefaultBannedRightsRequest( - chat.id, - ChatBannedRights(send_messages=True, until_date=2**31 - 1), - ) - ) - except Exception: - pass - - self._ban_ninja_forms[chat_id] = form - self._ban_ninja_progress[chat_id] = self.config["join_ratelimit"] - self._ban_ninja_tasks[chat_id] = asyncio.ensure_future( - self._update_ban_ninja(chat_id) - ) - - await ( - await self._clnraid( - call=( - await self.inline.form( - self.strings("clnraid_started").format("*loading*"), - message=int(chat_id), - reply_markup={"text": ".", "action": "close"}, - silent=True, - ) - ), - chat_id=int(chat_id), - quantity=self.config["join_ratelimit"], - ) - ).delete() - - messages = [] - users = [] - for u, m in self._ban_ninja_messages: - if u not in users: - if len(users) > self.config["join_ratelimit"]: - break - - users += [u] - - messages += [m] - - for m in messages: - try: - await self.inline.bot.delete_message( - int(f"-100{utils.get_chat_id(m)}"), - m.id, - ) - except MessageToDeleteNotFound: - pass - except MessageCantBeDeleted: - await self._promote_bot(utils.get_chat_id(m)) - await self.inline.bot.delete_message( - int(f"-100{utils.get_chat_id(m)}"), - m.id, - ) - except Exception: - await m.delete() - - try: - await self._client.pin_message(int(chat_id), form.form["message_id"]) - except Exception: - pass - - return False - - async def disable_smart_anti_raid(self, call: InlineCall, chat_id: int): - chat_id = str(chat_id) - if chat_id in self._ban_ninja: - del self._ban_ninja[chat_id] - if call: - await call.edit(self.strings("smart_anti_raid_stopped")) - - if call: - await call.answer("Success") - - try: - await self._client.unpin_message( - int(chat_id), - self._ban_ninja_forms[str(chat_id)].form["message_id"], - ) - except Exception: - pass - - if self.config["close_on_raid"]: - try: - await self._client( - EditChatDefaultBannedRightsRequest( - int(chat_id), - self._ban_ninja_default_rights[chat_id], - ) - ) - del self._ban_ninja_default_rights[chat_id] - except Exception: - pass - - await self._client.send_message( - int(chat_id), - self.strings("banninja_report").format( - self._ban_ninja_progress[chat_id] - ), - ) - - if chat_id in self._ban_ninja_forms: - await self._ban_ninja_forms[chat_id].delete() - del self._ban_ninja_forms[chat_id] - - if chat_id in self._ban_ninja_progress: - del self._ban_ninja_progress[chat_id] - - if chat_id in self._ban_ninja_tasks: - self._ban_ninja_tasks[chat_id].cancel() - del self._ban_ninja_tasks[chat_id] - - return - - await call.answer("Already stopped") - - @error_handler - async def p__antiraid( - self, - chat_id: typing.Union[str, int], - user_id: typing.Union[str, int], - user: typing.Union[User, Channel], - message: Message, - chat: typing.Union[Chat, Channel], - ) -> bool: - if self.api.should_protect(chat_id, "antiraid") and ( - getattr(message, "user_joined", False) - or getattr(message, "user_added", False) - ): - action = self.api.chats[str(chat_id)]["antiraid"][0] - if action == "kick": - await self._client.send_message( - "me", - self.strings("antiraid").format( - "kicked", - user.id, - get_full_name(user), - utils.escape_html(chat.title), - ), - ) - - await self._client.kick_participant(chat_id, user) - elif action == "ban": - await self._client.send_message( - "me", - self.strings("antiraid").format( - "banned", - user.id, - get_full_name(user), - utils.escape_html(chat.title), - ), - ) - - await self.ban(chat, user, 0, "antiraid") - elif action == "mute": - await self._client.send_message( - "me", - self.strings("antiraid").format( - "muted", - user.id, - get_full_name(user), - utils.escape_html(chat.title), - ), - ) - - await self.mute(chat, user, 0, "antiraid") - - return True - - return False - - async def _captcha_invalid( - self, - call: InlineCall, - chat_id: int, - user: User, - ): - if call.from_user.id != user.id: - await call.answer("Not for you....") - return - - with contextlib.suppress(KeyError): - del self._captcha_db[chat_id][user.id] - - await call.answer("Sorry ☹️") - - await self.punish( - chat_id, - user, - "captcha_failed", - self.api.chats[str(chat_id)]["captcha"][0], - get_full_name(user), - fulltime=True, - message=None, - ) - - with contextlib.suppress(Exception): - await self._captcha_messages[chat_id][user.id].delete() - - async def _captcha_valid(self, call: InlineCall, chat_id: int, user_id: int): - if call.from_user.id != user_id: - await call.answer("Not for you....") - return - - if self._captcha_db[chat_id][user_id]["unmute"]: - await self._client.edit_permissions( - int(chat_id), - int(user_id), - until_date=0, - send_messages=True, - ) - - with contextlib.suppress(KeyError): - del self._captcha_db[chat_id][user_id] - - with contextlib.suppress(Exception): - await self._captcha_messages[chat_id][user_id].delete() - - await call.answer("Welcome!") - - @error_handler - async def p__captcha( - self, - chat_id: typing.Union[str, int], - user_id: typing.Union[str, int], - user: typing.Union[User, Channel], - message: Message, - chat: Chat, - ) -> bool: - if not ( - self.api.should_protect(chat_id, "captcha") - and str(chat_id) not in self._ban_ninja - and ( - getattr(message, "user_joined", False) - or getattr(message, "user_added", False) - ) - ): - return False - - valid = utils.rand(6) - invalid = [utils.rand(6) for _ in range(5)] - - markup = [ - { - "text": i, - "callback": self._captcha_invalid, - "args": (chat_id, user), - } - for i in invalid - ] + [ - {"text": valid, "callback": self._captcha_valid, "args": (chat_id, user_id)} - ] - - random.shuffle(markup) - markup = utils.chunks(markup, 2) - - unmute = False - - if not ( - await self._client.get_permissions(int(chat_id), int(user_id)) - ).is_banned: - unmute = True - await self.mute(chat, user, 15 * 60, "captcha_processing", silent=True) - - for _ in range(5): - try: - m = await self.inline.form( - message=(await message.reply("🪄 <b>Loading captcha...</b>")), - text=self.strings("complete_captcha").format( - user.id, - get_full_name(user), - ), - photo=f"https://hikarichat.hikariatama.ru/captcha/{valid}", - reply_markup=markup, - disable_security=True, - ) - except WebpageCurlFailedError: - await asyncio.sleep(0.5) - else: - break - - if chat_id not in self._captcha_db: - self._captcha_db[chat_id] = {} - - if chat_id not in self._captcha_messages: - self._captcha_messages[chat_id] = {} - - self._captcha_db[chat_id][user_id] = { - "time": time.time() + 5 * 60, - "user": user, - "unmute": unmute, - } - - self._captcha_messages[chat_id][user_id] = m - - self._ban_ninja_messages = [(user_id, m)] + self._ban_ninja_messages - - @error_handler - async def p__cas( - self, - chat_id: typing.Union[str, int], - user_id: typing.Union[str, int], - user: typing.Union[User, Channel], - message: Message, - chat: Chat, - ) -> bool: - if not ( - self.api.should_protect(chat_id, "cas") - and str(chat_id) not in self._ban_ninja - and ( - getattr(message, "user_joined", False) - or getattr(message, "user_added", False) - ) - ): - return False - - return ( - self.api.chats[str(chat_id)]["cas"][0] - if ( - ( - await utils.run_sync( - requests.get, - f"https://api.cas.chat/check?user_id={user_id}", - ) - ) - .json() - .get("result", {}) - .get("offenses", False) - ) - else False - ) - - @error_handler - async def p__welcome( - self, - chat_id: typing.Union[str, int], - user_id: typing.Union[str, int], - user: typing.Union[User, Channel], - message: Message, - chat: Chat, - ) -> bool: - if not ( - self.api.should_protect(chat_id, "welcome") - and str(chat_id) not in self._ban_ninja - and ( - getattr(message, "user_joined", False) - or getattr(message, "user_added", False) - ) - ): - return False - - m = await self._client.send_message( - chat_id, - self.api.chats[str(chat_id)]["welcome"][0] - .replace("{user}", get_full_name(user)) - .replace("{chat}", utils.escape_html(chat.title)) - .replace( - "{mention}", - f'<a href="{utils.get_link(user)}">{get_full_name(user)}</a>', - ), - reply_to=message.action_message.id, - ) - - self._ban_ninja_messages = [(user_id, m)] + self._ban_ninja_messages - - return True - - @error_handler - async def p__report( - self, - chat_id: typing.Union[str, int], - user_id: typing.Union[str, int], - user: typing.Union[User, Channel], - message: Message, - ): - if not self.api.should_protect(chat_id, "report") or not getattr( - message, - "reply_to_msg_id", - False, - ): - return - - reply = await message.get_reply_message() - if ( - str(user_id) not in self._ratelimit["report"] - or self._ratelimit["report"][str(user_id)] < time.time() - ) and ( - ( - message.raw_text.startswith("#report") - or message.raw_text.startswith("/report") - ) - and reply - ): - fed = await self.find_fed(message) - if fed in self.api.feds and reply.sender_id in list( - map(int, self.api.feds[fed]["fdef"]) - ): - await utils.answer(message, self.strings("fdef403").format("report")) - return - - chat = await message.get_chat() - - reason = ( - message.raw_text.split(maxsplit=1)[1] - if message.raw_text.count(" ") >= 1 - else self.strings("no_reason") - ) - - self.api.request( - { - "action": "report", - "args": { - "chat": chat_id, - "reason": reason, - "link": await utils.get_message_link(reply, chat), - "user_link": utils.get_link(user), - "user_name": get_full_name(user), - "text_thumbnail": (getattr(reply, "raw_text", "") or "")[:1024] - or "<media>", - }, - }, - message, - ) - - msg = self.strings("reported").format( - utils.get_link(user), - get_full_name(user), - reason, - ) - - if self._is_inline: - m = await self._client.send_message( - chat.id, - "🌘 <b>Reporting message to admins...</b>", - reply_to=message.reply_to_msg_id, - ) - await self.inline.form( - message=m, - text=msg, - reply_markup=[ - [ - { - "text": self.strings("btn_mute"), - "data": f"m/{chat.id}/{reply.sender_id}#{reply.id}", - }, - { - "text": self.strings("btn_ban"), - "data": f"b/{chat.id}/{reply.sender_id}#{reply.id}", - }, - ], - [ - { - "text": self.strings("btn_fban"), - "data": f"fb/{chat.id}/{reply.sender_id}#{reply.id}", - }, - { - "text": self.strings("btn_del"), - "data": f"d/{chat.id}/{reply.sender_id}#{reply.id}", - }, - ], - ], - silent=True, - ) - else: - await (utils.answer if message else self._client.send_message)( - message or chat.id, - msg, - ) - - self._ratelimit["report"][str(user_id)] = time.time() + 30 - - try: - await self.inline.bot.delete_message( - int(f"-100{chat_id}"), - getattr(message, "action_message", message).id, - ) - except MessageToDeleteNotFound: - pass - except MessageCantBeDeleted: - await self._promote_bot(chat_id) - await self.inline.bot.delete_message( - int(f"-100{chat_id}"), - getattr(message, "action_message", message).id, - ) - - @error_handler - async def _promote_bot(self, chat_id: int): - try: - await self._client( - InviteToChannelRequest( - int(chat_id), - [self.inline.bot_username], - ) - ) - except Exception: - logger.warning( - "Unable to invite cleaner to chat. Maybe he's already there?" - ) - - try: - await self._client( - EditAdminRequest( - channel=int(chat_id), - user_id=self.inline.bot_username, - admin_rights=ChatAdminRights(ban_users=True, delete_messages=True), - rank="HikariChat", - ) - ) - except Exception: - logger.exception("Cleaner promotion failed!") - - @error_handler - async def p__antiflood( - self, - chat_id: typing.Union[str, int], - user_id: typing.Union[str, int], - user: typing.Union[User, Channel], - message: Message, - ) -> typing.Union[bool, str]: - if self.api.should_protect(chat_id, "antiflood"): - if str(chat_id) not in self._flood_cache: - self._flood_cache[str(chat_id)] = {} - - if str(user_id) not in self._flood_cache[str(chat_id)]: - self._flood_cache[str(chat_id)][str(user_id)] = [] - - for item in self._flood_cache[str(chat_id)][str(user_id)].copy(): - if time.time() - item > self.flood_timeout: - self._flood_cache[str(chat_id)][str(user_id)].remove(item) - - self._flood_cache[str(chat_id)][str(user_id)].append( - round(time.mktime(message.date.timetuple())) - if getattr(message, "date", False) - else round(time.time()) - ) - self.set("flood_cache", self._flood_cache) - - if ( - len(self._flood_cache[str(chat_id)][str(user_id)]) - >= self.flood_threshold - ): - return self.api.chats[str(chat_id)]["antiflood"][0] - - return False - - @error_handler - async def p__antichannel( - self, - chat_id: typing.Union[str, int], - user_id: typing.Union[str, int], - user: typing.Union[User, Channel], - message: Message, - ) -> bool: - if ( - self.api.should_protect(chat_id, "antichannel") - and getattr(message, "sender_id", 0) < 0 - ): - await self.ban(chat_id, user_id, 0, "", None, True) - try: - await self.inline.bot.delete_message(int(f"-100{chat_id}"), message.id) - except Exception: - await message.delete() - - return True - - return False - - @error_handler - async def p__antigif( - self, - chat_id: typing.Union[str, int], - user_id: typing.Union[str, int], - user: typing.Union[User, Channel], - message: Message, - ) -> bool: - if self.api.should_protect(chat_id, "antigif"): - try: - if ( - message.media - and DocumentAttributeAnimated() in message.media.document.attributes - ): - await message.delete() - return True - except Exception: - pass - - return False - - @error_handler - async def p__antispoiler( - self, - chat_id: typing.Union[str, int], - user_id: typing.Union[str, int], - user: typing.Union[User, Channel], - message: Message, - ) -> bool: - if self.api.should_protect(chat_id, "antispoiler"): - try: - if any(isinstance(_, MessageEntitySpoiler) for _ in message.entities): - await message.delete() - return True - except Exception: - pass - - return False - - @error_handler - async def p__antiexplicit( - self, - chat_id: typing.Union[str, int], - user_id: typing.Union[str, int], - user: typing.Union[User, Channel], - message: Message, - ) -> typing.Union[bool, str]: - if self.api.should_protect(chat_id, "antiexplicit"): - text = getattr(message, "raw_text", "") - P = "пПnPp" - I = "иИiI1uІИ́Їіи́ї" # noqa: E741 - E = "еЕeEЕ́е́" - D = "дДdD" - Z = "зЗ3zZ3" - M = "мМmM" - U = "уУyYuUУ́у́" - O = "оОoO0О́о́" # noqa: E741 - L = "лЛlL1" - A = "аАaAА́а́@" - N = "нНhH" - G = "гГgG" - K = "кКkK" - R = "рРpPrR" - H = "хХxXhH" - YI = "йЙyуУY" - YA = "яЯЯ́я́" - YO = "ёЁ" - YU = "юЮЮ́ю́" - B = "бБ6bB" - T = "тТtT1" - HS = "ъЪ" - SS = "ьЬ" - Y = "ыЫ" - - occurrences = re.findall( - rf"""\b[0-9]*(\w*[{P}][{I}{E}][{Z}][{D}]\w*|(?:[^{I}{U}\s]+|{N}{I})?(?<!стра)[{H}][{U}][{YI}{E}{YA}{YO}{I}{L}{YU}](?!иг)\w*|\w*[{B}][{L}](?:[{YA}]+[{D}{T}]?|[{I}]+[{D}{T}]+|[{I}]+[{A}]+)(?!х)\w*|(?:\w*[{YI}{U}{E}{A}{O}{HS}{SS}{Y}{YA}][{E}{YO}{YA}{I}][{B}{P}](?!ы\b|ол)\w*|[{E}{YO}][{B}]\w*|[{I}][{B}][{A}]\w+|[{YI}][{O}][{B}{P}]\w*)|\w*(?:[{P}][{I}{E}][{D}][{A}{O}{E}]?[{R}](?!о)\w*|[{P}][{E}][{D}][{E}{I}]?[{G}{K}])|\w*[{Z}][{A}{O}][{L}][{U}][{P}]\w*|\w*[{M}][{A}][{N}][{D}][{A}{O}]\w*|\w*[{G}][{O}{A}][{N}][{D}][{O}][{N}]\w*)""", - text, - ) - - occurrences = [ - word - for word in occurrences - if all( - excl not in word for excl in self.api.variables["censor_exclusions"] - ) - ] - - if occurrences: - return self.api.chats[str(chat_id)]["antiexplicit"][0] - - return False - - @error_handler - async def p__antinsfw( - self, - chat_id: typing.Union[str, int], - user_id: typing.Union[str, int], - user: typing.Union[User, Channel], - message: Message, - ) -> typing.Union[bool, str]: - if not self.api.should_protect(chat_id, "antinsfw"): - return False - - media = False - - if getattr(message, "sticker", False): - media = message.sticker - elif getattr(message, "media", False): - media = message.media - - if not media: - return False - - photo = io.BytesIO() - await self._client.download_media(message.media, photo) - photo.seek(0) - - if imghdr.what(photo) not in self.api.variables["image_types"]: - return False - - response = await self.api.nsfw(photo) - if response != "nsfw": - return False - - todel = [] - async for _ in self._client.iter_messages( - message.peer_id, - reverse=True, - offset_id=message.id - 1, - ): - todel += [_] - if _.sender_id != message.sender_id: - break - - await self._client.delete_messages( - message.peer_id, - message_ids=todel, - revoke=True, - ) - - return self.api.chats[str(chat_id)]["antinsfw"][0] - - @error_handler - async def p__antitagall( - self, - chat_id: typing.Union[str, int], - user_id: typing.Union[str, int], - user: typing.Union[User, Channel], - message: Message, - ) -> typing.Union[bool, str]: - return ( - self.api.chats[str(chat_id)]["antitagall"][0] - if self.api.should_protect(chat_id, "antitagall") - and getattr(message, "text", False) - and message.text.count("tg://user?id=") >= 5 - else False - ) - - @error_handler - async def p__antihelp( - self, - chat_id: typing.Union[str, int], - user_id: typing.Union[str, int], - user: typing.Union[User, Channel], - message: Message, - ) -> bool: - if not self.api.should_protect(chat_id, "antihelp") or not getattr( - message, "text", False - ): - return False - - search = message.text - if "@" in search: - search = search[: search.find("@")] - - if ( - not search.split() - or search.split()[0][1:] not in self.api.variables["blocked_commands"] - ): - return False - - await message.delete() - return True - - @error_handler - async def p__antiarab( - self, - chat_id: typing.Union[str, int], - user_id: typing.Union[str, int], - user: typing.Union[User, Channel], - message: Message, - ) -> typing.Union[bool, str]: - return ( - self.api.chats[str(chat_id)]["antiarab"][0] - if ( - self.api.should_protect(chat_id, "antiarab") - and ( - getattr(message, "user_joined", False) - or getattr(message, "user_added", False) - ) - and ( - len(re.findall("[\u4e00-\u9fff]+", get_full_name(user))) != 0 - or len(re.findall("[\u0621-\u064a]+", get_full_name(user))) != 0 - ) - ) - else False - ) - - @error_handler - async def p__antizalgo( - self, - chat_id: typing.Union[str, int], - user_id: typing.Union[str, int], - user: typing.Union[User, Channel], - message: Message, - ) -> typing.Union[bool, str]: - return ( - self.api.chats[str(chat_id)]["antizalgo"][0] - if ( - self.api.should_protect(chat_id, "antizalgo") - and len( - re.findall( - "[\u200f\u200e\u0300-\u0361\u0316-\u0362\u0334-\u0338\u0363-\u036f\u3164\ud83d\udd07\u0020\u00a0\u2000-\u2009\u200a\u2028\u205f\u1160\ufff4]", - get_full_name(user), - ) - ) - / len(get_full_name(user)) - >= 0.6 - ) - else False - ) - - @error_handler - async def p__bnd( - self, - chat_id: typing.Union[str, int], - user_id: typing.Union[str, int], - user: typing.Union[User, Channel], - message: Message, - ) -> typing.Union[bool, str]: - if not self.api.should_protect(chat_id, "bnd"): - return False - - if ( - self.get("bnd_cache", {}).get(str(chat_id), {}).get(str(user_id), 0) - >= time.time() - ): - return False - - try: - assert ( - ( - await self.inline.bot.get_chat_member( - int(f"-100{chat_id}"), - int(user_id), - ) - ).status - ) not in {"left", "kicked"} - except Exception: - return self.api.chats[str(chat_id)]["bnd"][0] - else: - bnd_cache = self.get("bnd_cache", {}) - bnd_cache.setdefault(str(chat_id), {}).update( - {str(user_id): round(time.time()) + 60} - ) - self.set("bnd_cache", bnd_cache) - return False - - @error_handler - async def p__antistick( - self, - chat_id: typing.Union[str, int], - user_id: typing.Union[str, int], - user: typing.Union[User, Channel], - message: Message, - ) -> typing.Union[bool, str]: - if not self.api.should_protect(chat_id, "antistick") or not ( - getattr(message, "sticker", False) - or getattr(message, "media", False) - and isinstance(message.media, MessageMediaUnsupported) - ): - return False - - sender = user.id - if sender not in self._sticks_ratelimit: - self._sticks_ratelimit[sender] = [] - - self._sticks_ratelimit[sender] += [round(time.time())] - - for timing in self._sticks_ratelimit[sender].copy(): - if time.time() - timing > 60: - self._sticks_ratelimit[sender].remove(timing) - - if len(self._sticks_ratelimit[sender]) > self._sticks_limit: - return self.api.chats[str(chat_id)]["antistick"][0] - - @error_handler - async def p__antilagsticks( - self, - chat_id: typing.Union[str, int], - user_id: typing.Union[str, int], - user: typing.Union[User, Channel], - message: Message, - ) -> typing.Union[bool, str]: - res = ( - self.api.should_protect(chat_id, "antilagsticks") - and getattr(message, "sticker", False) - and getattr(message.sticker, "id", False) - in self.api.variables["destructive_sticks"] - ) - if res: - await message.delete() - - return res - - @error_handler - async def watcher(self, message: Message): - self._global_queue += [message] - - @error_handler - async def _global_queue_handler(self): - while True: - while self._global_queue: - await self._global_queue_handler_process(self._global_queue.pop(0)) - - for chat_id, info in self._captcha_db.copy().items(): - for user_id, captcha in info.copy().items(): - if captcha["time"] < time.time(): - del self._captcha_db[chat_id][user_id] - await self.punish( - chat_id, - captcha["user"], - "captcha_timeout", - self.api.chats[str(chat_id)]["captcha"][0], - get_full_name(captcha["user"]), - fulltime=True, - message=None, - ) - with contextlib.suppress(Exception): - await self._captcha_messages[chat_id][user_id].delete() - - for message, deletion_ts in self._delete_soon.copy(): - if deletion_ts < time.time(): - with contextlib.suppress(Exception): - await message.delete() - - with contextlib.suppress(Exception): - self._delete_soon.remove((message, deletion_ts)) - - await asyncio.sleep(0.01) - - @error_handler - async def _global_queue_handler_process(self, message: Message): - if not isinstance(getattr(message, "chat", 0), (Chat, Channel)): - return - - chat_id = utils.get_chat_id(message) - - if ( - isinstance(getattr(message, "chat", 0), Channel) - and not getattr(message, "megagroup", False) - and int(chat_id) in reverse_dict(self._linked_channels) - ): - actual_chat = str(reverse_dict(self._linked_channels)[int(chat_id)]) - await self.p__antiservice(actual_chat, message) - return - - await self.p__antiservice(chat_id, message) - - try: - user_id = ( - getattr(message, "sender_id", False) - or message.action_message.action.users[0] - ) - except Exception: - try: - user_id = message.action_message.action.from_id.user_id - except Exception: - try: - user_id = message.from_id.user_id - except Exception: - try: - user_id = message.action_message.from_id.user_id - except Exception: - try: - user_id = message.action.from_user.id - except Exception: - try: - user_id = (await message.get_user()).id - except Exception: - logger.debug( - f"Can't extract entity from event {type(message)}" - ) - return - - user_id = ( - int(str(user_id)[4:]) if str(user_id).startswith("-100") else int(user_id) - ) - - if await self.p__banninja(chat_id, user_id, message): - return - - fed = await self.find_fed(message) - - if fed in self.api.feds: - if ( - getattr(message, "raw_text", False) - and ( - str(user_id) not in self._ratelimit["notes"] - or self._ratelimit["notes"][str(user_id)] < time.time() - ) - and not message.raw_text.startswith(self.get_prefix()) - ): - logger.debug("Checking message for notes...") - if message.raw_text.lower().strip() in ["#заметки", "#notes", "/notes"]: - self._ratelimit["notes"][str(user_id)] = time.time() + 3 - if any( - str(note["creator"]) == str(self._tg_id) - for _, note in self.api.feds[fed]["notes"].items() - ): - await self.fnotescmd( - await message.reply( - f"<code>{self.get_prefix()}fnotes</code>" - ), - True, - ) - - for note, note_info in self.api.feds[fed]["notes"].items(): - if str(note_info["creator"]) != str(self._tg_id): - continue - - if note.lower() in message.raw_text.lower(): - txt = note_info["text"] - self._ratelimit["notes"][str(user_id)] = time.time() + 3 - - if not txt.startswith("@inline"): - await utils.answer(message, txt) - break - - txt = "\n".join(txt.splitlines()[1:]) - buttons = [] - button_re = r"\[(.+)\]\((https?://.*)\)" - txt_r = [] - for line in txt.splitlines(): - if re.match(button_re, re.sub(r"<.*?>", "", line).strip()): - match = re.search( - button_re, re.sub(r"<.*?>", "", line).strip() - ) - buttons += [ - [{"text": match.group(1), "url": match.group(2)}] - ] - else: - txt_r += [line] - - if not buttons: - await utils.answer(message, txt) - break - - await self.inline.form( - message=message, - text="\n".join(txt_r), - reply_markup=buttons, - silent=True, - ) - - if int(user_id) in ( - list(map(int, self.api.feds[fed]["fdef"])) - + list(self._linked_channels.values()) - ): - return - - if str(chat_id) not in self.api.chats or not self.api.chats[str(chat_id)]: - return - - try: - user = await self._client.get_entity(user_id) - except ValueError: - return - - chat = await message.get_chat() - user_name = get_full_name(user) - - args = (chat_id, user_id, user, message) - - await self.p__report(*args) - - try: - if ( - await self._client.get_perms_cached(chat_id, message.sender_id) - ).is_admin: - return - except Exception: - pass - - if await self.p__antiraid(*args, chat): - return - - cas_result = False - if self.api.should_protect(chat_id, "cas"): - cas_result = await self.p__cas(*args, chat) - - if cas_result: - await self.punish( - chat_id, - user, - "cas", - cas_result, - user_name, - message=message, - ) - return - - r = await self.p__antiarab(*args) - if r: - await self.punish( - chat_id, - user, - "arabic_nickname", - r, - user_name, - message=message, - ) - return - - if await self.p__welcome(*args, chat) and not self.api.should_protect( - chat_id, - "captcha", - ): - return - - if await self.p__captcha(*args, chat): - return - - if getattr(message, "action", ""): - return - - await self.p__report(*args) - - r = await self.p__bnd(*args) - if r: - await self.punish(chat_id, user, "bnd", r, user_name, message=message) - return - - r = await self.p__antiflood(*args) - if r: - await self.punish(chat_id, user, "flood", r, user_name, message=message) - return - - if await self.p__antichannel(*args): - return - - r = await self.p__antizalgo(*args) - if r: - await self.punish(chat_id, user, "zalgo", r, user_name, message=message) - return - - if await self.p__antigif(*args): - return - - r = await self.p__antilagsticks(*args) - if r: - await self.punish( - chat_id, user, "destructive_stick", "ban", user_name, message=message - ) - return - - r = await self.p__antistick(*args) - if r: - await self.punish(chat_id, user, "stick", r, user_name, message=message) - return - - if await self.p__antispoiler(*args): - return - - r = await self.p__antiexplicit(*args) - if r: - await self.punish(chat_id, user, "explicit", r, user_name, message=message) - return - - r = await self.p__antinsfw(*args) - if r: - await self.punish( - chat_id, - user, - "nsfw_content", - r, - user_name, - message=message, - ) - return - - r = await self.p__antitagall(*args) - if r: - await self.punish(chat_id, user, "tagall", r, user_name, message=message) - return - - await self.p__antihelp(*args) - - async def client_ready( - self, - client: "CustomTelegramClient", # type: ignore - db: "hikka.database.Database", # type: ignore - ): - """Entry point""" - global api - - self._is_inline = self.inline.init_complete - - self._sticks_limit = 7 - - self._join_ratelimit = self.get("join_ratelimit", {}) - self._flood_cache = self.get("flood_cache", {}) - - self.api = api - await api.init(client, db, self) - - for protection in self.api.variables["protections"]: - setattr(self, f"{protection}cmd", self.protection_template(protection)) - - # We can override class docstings because of abc meta - self.__doc__ = ( - "Advanced chat admin toolkit\nNow became free...\n\n💻 Developer:" - " t.me/hikariatama\n📣" - " Downloaded from: @hikarimods\n\n" - + f"📦Version: {version}\n" - + ("🗃 Local" if not self.api._inited else "⭐️ Full") - ) - - self._pt_task = asyncio.ensure_future(self._global_queue_handler()) - - if PIL_AVAILABLE: - asyncio.ensure_future(self._download_font()) - - async def _download_font(self): - self.font = ( - await utils.run_sync( - requests.get, - "https://github.com/hikariatama/assets/raw/master/EversonMono.ttf", - ) - ).content diff --git a/archquise/H.Modules/animals.py b/archquise/H.Modules/animals.py deleted file mode 100644 index ddef281..0000000 --- a/archquise/H.Modules/animals.py +++ /dev/null @@ -1,100 +0,0 @@ -# Proprietary License Agreement - -# Copyright (c) 2024-29 CodWiz - -# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions: - -# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author. - -# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author. - -# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software. - -# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software. - -# 5. By using the Software, you agree to be bound by the terms and conditions of this license. - -# For any inquiries or requests for permissions, please contact codwiz@yandex.ru. - -# --------------------------------------------------------------------------------- -# Name: animals -# Description: Random cats and dogs -# Author: @hikka_mods -# --------------------------------------------------------------------------------- -# meta developer: @hikka_mods -# scope: Api animals -# scope: Api animals 0.0.1 -# requires: requests -# --------------------------------------------------------------------------------- - -import logging - -import requests - -from .. import loader, utils - -logger = logging.getLogger(__name__) - -@loader.tds -class animals(loader.Module): - """Random cats and dogs""" - - strings = { - "name": "animals", - "loading": "<b>Generation is underway</b> <emoji document_id=5215484787325676090>🕐</emoji>", - "done": "<b>Here is your salute</b> <emoji document_id=5436246187944460315>❤️</emoji>", - } - - strings_ru = { - "loading": "<b>Генерация идет полным ходом</b> <emoji document_id=5215484787325676090>🕐</emoji>", - "done": "<b>Вот ваш результат</b> <emoji document_id=5436246187944460315>❤️</emoji>", - } - - # thanks https://github.com/C0dwiz/H.Modules/pull/1 - async def get_photo(self, prefix: str) -> str: - response = requests.get(f"https://api.{prefix}.com/v1/images/search") - return response.json()[0]["url"] - - @loader.command( - ru_doc="Файлы случайных фотографий кошек", - en_doc="Random photos of cats files", - ) - async def fcatcmd(self, message): - await utils.answer(message, self.strings("loading")) - cat_url = await self.get_photo("thecatapi") - await utils.answer_file( - message, cat_url, self.strings("done"), force_document=True - ) - - @loader.command( - ru_doc="Случайные фотографии собачьих файлов", - en_doc="Random photos of dog files", - ) - async def fdogcmd(self, message): - await utils.answer(message, self.strings("loading")) - dog_url = await self.get_photo("thedogapi") - await utils.answer_file( - message, dog_url, self.strings("done"), force_document=True - ) - - @loader.command( - ru_doc="Случайные фотографии кошек", - en_doc="Random photos of cats", - ) - async def catcmd(self, message): - await utils.answer(message, self.strings("loading")) - cat_url = await self.get_photo("thecatapi") - await utils.answer_file( - message, cat_url, self.strings("done"), force_document=False - ) - - @loader.command( - ru_doc="Случайные фотографии собаки", - en_doc="Random photos of dog", - ) - async def dogcmd(self, message): - await utils.answer(message, self.strings("loading")) - dog_url = await self.get_photo("thedogapi") - await utils.answer_file( - message, dog_url, self.strings("done"), force_document=False - ) diff --git a/archquise/H.Modules/caliases.py b/archquise/H.Modules/caliases.py deleted file mode 100644 index 6fcb859..0000000 --- a/archquise/H.Modules/caliases.py +++ /dev/null @@ -1,223 +0,0 @@ -# Proprietary License Agreement - -# Copyright (c) 2024-29 CodWiz - -# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions: - -# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author. - -# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author. - -# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software. - -# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software. - -# 5. By using the Software, you agree to be bound by the terms and conditions of this license. - -# For any inquiries or requests for permissions, please contact codwiz@yandex.ru. - -# --------------------------------------------------------------------------------- -# Name: CAliases -# Description: Module for custom aliases -# Author: @hikka_mods -# --------------------------------------------------------------------------------- -# meta developer: @hikka_mods -# scope: CAliases -# scope: CAliases 0.0.1 -# --------------------------------------------------------------------------------- - -import logging -from typing import Dict, Optional - -from telethon import types - -from .. import loader, utils - -logger = logging.getLogger(__name__) - - -@loader.tds -class CustomAliasesMod(loader.Module): - """Module for custom aliases""" - - strings = { - "name": "CAliases", - "c404": "<emoji document_id=5312526098750252863>❌</emoji> <b>Command <code>{}</code> not found!</b>", - "a404": "<emoji document_id=5312526098750252863>❌</emoji> <b>Custom alias <code>{}</code> not found!</b>", - "no_args": "<emoji document_id=5312526098750252863>❌</emoji> <b>You must specify two args: alias name and command</b>", - "added": ( - "<emoji document_id=5314250708508220914>✅</emoji> <b>Custom alias <i>{alias}</i> for command " - "<code>{prefix}{cmd}</code> successfully added!</b>\n<b>Use it like:</b> <code>{prefix}{alias}{args}</code>" - ), - "argsopt": " [args (optional)]", - "deleted": "<emoji document_id=5314250708508220914>✅</emoji> <b>Custom alias <code>{}</code> successfully deleted</b>", - "list": "<emoji document_id=5974492756494519709>🔗</emoji> <b>Custom aliases ({len}):</b>\n", - "no_aliases": "<emoji document_id=5312526098750252863>❌</emoji> <b>You have no custom aliases!</b>", - } - - strings_ru = { - "c404": "<emoji document_id=5312526098750252863>❌</emoji> <b>Команда <code>{}</code> не найдена!</b>", - "a404": "<emoji document_id=5312526098750252863>❌</emoji> <b>Кастомный алиас <code>{}</code> не найден!</b>", - "no_args": "<emoji document_id=5312526098750252863>❌</emoji> <b>Вы должны указать как минимум два аргумента: имя алиаса и команду</b>", - "added": ( - "<emoji document_id=5314250708508220914>✅</emoji> <b>Успешно добавил алиас с названием <i>{alias}</i> " - "для команды <code>{prefix}{cmd}</code></b>\n<b>Используй его так:</b> <code>{prefix}{alias}{args}</code>" - ), - "argsopt": " [аргументы (необязательно)]", - "deleted": "<emoji document_id=5314250708508220914>✅</emoji> <b>Кастомный алиас <code>{}</code> успешно удалён</b>", - "list": "<emoji document_id=5974492756494519709>🔗</emoji> <b>Кастомные алиасы (всего {len}):</b>\n", - "no_aliases": "<emoji document_id=5312526098750252863>❌</emoji> <b>У вас нет кастомных алиасов!</b>", - } - - def __init__(self): - self._aliases_cache: Optional[Dict[str, Dict[str, str]]] = None - self._prefix_cache: Optional[str] = None - - def _get_aliases(self) -> Dict[str, Dict[str, str]]: - if self._aliases_cache is None: - self._aliases_cache = self.get("aliases", {}) - return self._aliases_cache - - def _save_aliases(self, aliases: Dict[str, Dict[str, str]]) -> None: - self.set("aliases", aliases) - self._aliases_cache = aliases - - def _get_prefix(self) -> str: - if self._prefix_cache is None: - self._prefix_cache = self.get_prefix() - return self._prefix_cache - - def _format_alias_list(self) -> str: - """Format aliases list for display""" - aliases = self._get_aliases() - if not aliases: - return self.strings["no_aliases"] - - lines = [self.strings["list"].format(len=len(aliases))] - - for alias_name, alias_data in aliases.items(): - cmd = alias_data["command"] - if alias_data.get("args"): - cmd += f" {alias_data['args']}" - - lines.append( - f" <emoji document_id=5280726938279749656>▪️</emoji> <code>{alias_name}</code> " - f"<emoji document_id=5960671702059848143>👈</emoji> <code>{cmd}</code>" - ) - - return "\n".join(lines) - - def _validate_command(self, cmd: str) -> bool: - """Check if command exists""" - return cmd in self.allmodules.commands - - def _parse_alias_args(self, message: types.Message) -> tuple: - """Parse alias command arguments""" - raw_args = utils.get_args_raw(message) - if not raw_args: - return None, None, None - - parts = raw_args.split(" ", 2) - if len(parts) < 2: - return None, None, None - - alias_name = parts[0] - command = parts[1] - cmd_args = parts[2] if len(parts) > 2 else "" - - return alias_name, command, cmd_args - - @loader.command( - ru_doc="Получить список всех алиасов", - en_doc=" Get list of all aliases" - ) - async def caliasescmd(self, message: types.Message): - """Get all aliases""" - await utils.answer(message, self._format_alias_list()) - - @loader.command( - ru_doc="<имя> Удалить алиас", - en_doc="<name> Remove alias" - ) - async def rmcaliascmd(self, message: types.Message): - """Remove alias""" - args = utils.get_args(message) - if not args: - return await utils.answer(message, self.strings["no_args"]) - - alias_name = args[0] - aliases = self._get_aliases() - - if alias_name not in aliases: - return await utils.answer(message, self.strings["a404"].format(alias_name)) - - del aliases[alias_name] - self._save_aliases(aliases) - await utils.answer(message, self.strings["deleted"].format(alias_name)) - - @loader.command( - ru_doc="<имя> <команда> [аргументы] Добавить новый алиас (может содержать ключевое слово {args})", - en_doc="<name> <command> [arguments] Add new alias (may contain {args} keyword)", - ) - async def caliascmd(self, message: types.Message): - """Add new alias (may contain {args} keyword)""" - alias_name, command, cmd_args = self._parse_alias_args(message) - - if not alias_name or not command: - return await utils.answer(message, self.strings["no_args"]) - - if not self._validate_command(command): - return await utils.answer(message, self.strings["c404"].format(command)) - - aliases = self._get_aliases() - aliases[alias_name] = {"command": command, "args": cmd_args} - self._save_aliases(aliases) - - prefix = self._get_prefix() - full_cmd = f"{command} {cmd_args}" if cmd_args else command - args_display = self.strings["argsopt"] if "{args}" in cmd_args else "" - - await utils.answer( - message, - self.strings["added"].format( - alias=alias_name, - prefix=prefix, - cmd=full_cmd, - args=args_display, - ), - ) - - @loader.tag(only_messages=True, no_media=True, no_inline=True, out=True) - async def watcher(self, message: types.Message): - """Handle alias execution""" - if not message.raw_text: - return - - aliases = self._get_aliases() - prefix = self._get_prefix() - text = message.raw_text - first_word = text.split()[0].lower() - - if not first_word.startswith(prefix): - return - - alias_name = first_word[len(prefix) :] - if alias_name not in aliases: - return - - alias_data = aliases[alias_name] - command = alias_data["command"] - template_args = alias_data.get("args", "") - - user_args = utils.get_args_raw(message) - if user_args and template_args: - final_command = f"{command} {template_args}".format(args=user_args) - else: - final_command = f"{command} {template_args}" if template_args else command - - try: - await self.allmodules.commands[command]( - await utils.answer(message, f"{prefix}{final_command}") - ) - except Exception as e: - logger.error(f"Error executing alias '{alias_name}': {e}") diff --git a/archquise/H.Modules/coddrago/DelMessTools.py b/archquise/H.Modules/coddrago/DelMessTools.py deleted file mode 100644 index fb4e9f9..0000000 --- a/archquise/H.Modules/coddrago/DelMessTools.py +++ /dev/null @@ -1,369 +0,0 @@ -# --------------------------------------------------------------------------------- -# ░█▀▄░▄▀▀▄░█▀▄░█▀▀▄░█▀▀▄░█▀▀▀░▄▀▀▄░░░█▀▄▀█ -# ░█░░░█░░█░█░█░█▄▄▀░█▄▄█░█░▀▄░█░░█░░░█░▀░█ -# ░▀▀▀░░▀▀░░▀▀░░▀░▀▀░▀░░▀░▀▀▀▀░░▀▀░░░░▀░░▒▀ -# Name: DelMessTools -# Description: Module to manage and delete your messages in the current chat -# Author: @codrago_m -# --------------------------------------------------------------------------------- -# 🔒 Licensed under the GNU AGPLv3 -# 🌐 https://www.gnu.org/licenses/agpl-3.0.html -# --------------------------------------------------------------------------------- -# Author: @codrago -# Commands: nopurge, purgetime, purgelength, purgekeyword, purge -# scope: hikka_only -# meta developer: @codrago_m -# meta banner: https://raw.githubusercontent.com/coddrago/modules/refs/heads/main/banner.png -# meta pic: https://envs.sh/HJx.webp -# --------------------------------------------------------------------------------- - -__version__ = (1, 1, 0) - -from telethon.tl.types import Message, DocumentAttributeFilename - -from .. import loader, utils - - -class DelMessTools(loader.Module): - """Module to manage and delete your messages in the current chat""" - - strings = { - "name": "DelMessTools", - "purge_complete": "All your messages have been deleted.", - "purge_reply_complete": "Messages up to the replied message have been deleted.", - "purge_keyword_complete": "Messages containing the keyword have been deleted.", - "purge_time_complete": "Messages within the specified time range have been deleted.", - "purge_media_complete": "All your media messages have been deleted.", - "purge_length_complete": "Messages with the specified length have been deleted.", - "purge_type_complete": "Messages of the specified type have been deleted.", - "enabled": "It's not operational now anyway.", - "disabled": "Operation status changed to disabled.", - "interrupted": "The deletion was interrupted because you changed your mind.", - "none": "You didn't even intend to delete anything here, but anyway it's disabled now.", - "no_args": "Please specify arguments for the command.", - "no_keyword": "Please specify a keyword to delete messages.", - "no_length": "Please specify a valid length.", - "invalid_time": "Invalid time format. Please use the format: YYYY-MM-DD HH:MM:SS", - "invalid_length": "Please specify a valid number for length.", - } - - strings_ru = { - "purge_complete": "Все ваши сообщения были удалены.", - "purge_reply_complete": "Сообщения до указанного ответа были удалены.", - "purge_keyword_complete": "Сообщения, содержащие ключевое слово, были удалены.", - "purge_time_complete": "Сообщения в указанном временном диапазоне были удалены.", - "purge_media_complete": "Все ваши медиа-сообщения были удалены.", - "purge_length_complete": "Сообщения указанной длины были удалены.", - "purge_type_complete": "Сообщения указанного типа были удалены.", - "enabled": "Оно итак сейчас не работает.", - "disabled": "Режим работы изменен на выключено.", - "interrupted": "Удаление было прервано т.к вы передумали.", - "none": "Вы даже не пытались ничего здесь удалить, в любом случае сейчас оно выключено.", - "no_args": "Пожалуйста, укажите аргументы для команды.", - "no_keyword": "Пожалуйста, укажите ключевое слово для удаления сообщений.", - "no_length": "Пожалуйста, укажите корректную длину.", - "invalid_time": "Неверный формат времени. Используйте формат: YYYY-MM-DD HH:MM:SS", - "invalid_length": "Пожалуйста, укажите корректное число для длины.", - } - - def __init__(self): - self.client = None - self.db = None - self.tg_id = None - - async def client_ready(self, client, db): - self.client = client - self.db = db - self.tg_id = (await client.get_me()).id - - @loader.command( - ru_doc="[reply] [-img] [-voice] [-file] [-all] - удалить все ваши сообщения в текущем чате или только до сообщения, на которое ответили\n -all - удалять сообщения в каждой теме, если это форум, иначе флаг игнорируется", - en_doc="[reply] [-img] [-voice] [-file] [-all] - delete all your messages in current chat or only ones up to the message you replied to\n -all - to delete messages in each topic if this is a forum otherwise the flag'll just be ignored", - ) - async def purge(self, message: Message): - if not self.client or not self.db or not self.tg_id: - return - - reply = await message.get_reply_message() - is_last = False - args, types_filter, is_each = self.get_types_filter(message) - - try: - entity = await self.client.get_entity(message.chat.id) - is_forum = getattr(entity, "forum", False) - except Exception: - is_forum = False - - status = self.db.get(__name__, "status", {}) - status[message.chat.id] = True - self.db.set(__name__, "status", status) - - deleted_count = 0 - async for i in self.client.iter_messages(message.peer_id): - status = self.db.get(__name__, "status", {}) - if status.get(message.chat.id, None) is not True: - return await utils.answer(message, self.strings["interrupted"]) - - if is_forum and not is_each: - try: - if utils.get_topic(message) != utils.get_topic(i): - continue - except Exception: - pass - - if i.from_id == self.tg_id and self.is_valid_type(i, types_filter): - if reply: - if is_last: - break - if i.id == reply.id: - is_last = True - try: - await self.client.delete_messages(message.peer_id, [i.id]) - deleted_count += 1 - except Exception: - pass - - if reply: - await utils.answer(message, self.strings["purge_reply_complete"]) - else: - await utils.answer(message, self.strings["purge_complete"]) - - @loader.command( - ru_doc="<ключевое слово> [-img] [-voice] [-file] [-all] - удалить все ваши сообщения с указанным ключевым словом в текущем чате\n -all - удалять сообщения в каждой теме, если это форум, иначе флаг игнорируется", - en_doc="<keyword> [-img] [-voice] [-file] [-all] - delete all your messages containing the specified keyword in the current chat\n -all - to delete messages in each topic if this is a forum otherwise the flag'll just be ignored", - ) - async def purgekeyword(self, message: Message): - if not self.client or not self.db or not self.tg_id: - return - - args = utils.get_args_raw(message) - if not args: - return await utils.answer(message, self.strings["no_args"]) - - args, types_filter, is_each = self.get_types_filter(message) - if not args: - return await utils.answer(message, self.strings["no_keyword"]) - - try: - entity = await self.client.get_entity(message.chat.id) - is_forum = getattr(entity, "forum", False) - except Exception: - is_forum = False - - status = self.db.get(__name__, "status", {}) - status[message.chat.id] = True - self.db.set(__name__, "status", status) - - deleted_count = 0 - async for i in self.client.iter_messages(message.peer_id): - status = self.db.get(__name__, "status", {}) - if status.get(message.chat.id, None) is not True: - return await utils.answer(message, self.strings["interrupted"]) - - if is_forum and not is_each: - try: - if utils.get_topic(message) != utils.get_topic(i): - continue - except Exception: - pass - - if ( - i.from_id == self.tg_id - and args.lower() in (i.text or "").lower() - and self.is_valid_type(i, types_filter) - ): - try: - await self.client.delete_messages(message.chat.id, [i.id]) - deleted_count += 1 - except Exception: - pass - - await utils.answer(message, self.strings["purge_keyword_complete"]) - - @loader.command( - ru_doc="<начальное время> <конечное время> [-img] [-voice] [-file] [-all] - удалить все ваши сообщения в указанном временном диапазоне в текущем чате\n -all - удалять сообщения в каждой теме, если это форум, иначе флаг игнорируется\n Формат времени: YYYY-MM-DD HH:MM:SS", - en_doc="<start_time> <end_time> [-img] [-voice] [-file] [-all] - delete all your messages within the specified time range in the current chat\n -all - to delete messages in each topic if this is a forum otherwise the flag'll just be ignored\n Time format: YYYY-MM-DD HH:MM:SS", - ) - async def purgetime(self, message: Message): - if not self.client or not self.db or not self.tg_id: - return - - args = utils.get_args_raw(message) - if not args: - return await utils.answer(message, self.strings["no_args"]) - - args, types_filter, is_each = self.get_types_filter(message) - args = args.split() - - if not args or len(args) < 2: - return await utils.answer(message, self.strings["no_args"]) - - from datetime import datetime - - try: - start_time = datetime.strptime(args[0], "%Y-%m-%d %H:%M:%S") - end_time = datetime.strptime(args[1], "%Y-%m-%d %H:%M:%S") - except ValueError: - return await utils.answer(message, self.strings["invalid_time"]) - - try: - entity = await self.client.get_entity(message.chat.id) - is_forum = getattr(entity, "forum", False) - except Exception: - is_forum = False - - status = self.db.get(__name__, "status", {}) - status[message.chat.id] = True - self.db.set(__name__, "status", status) - - deleted_count = 0 - async for i in self.client.iter_messages(message.peer_id): - status = self.db.get(__name__, "status", {}) - if status.get(message.chat.id, None) is not True: - return await utils.answer(message, self.strings["interrupted"]) - - if is_forum and not is_each: - try: - if utils.get_topic(message) != utils.get_topic(i): - continue - except Exception: - pass - - if ( - i.from_id == self.tg_id - and start_time <= i.date <= end_time - and self.is_valid_type(i, types_filter) - ): - try: - await self.client.delete_messages(message.peer_id, [i.id]) - deleted_count += 1 - except Exception: - pass - - await utils.answer(message, self.strings["purge_time_complete"]) - - @loader.command( - ru_doc="<длина> [-img] [-voice] [-file] [-all] - удалить все ваши сообщения указанной длины в текущем чате\n -all - удалять сообщения в каждой теме, если это форум, иначе флаг игнорируется", - en_doc="<length> [-img] [-voice] [-file] [-all] - delete all your messages with the specified length in the current chat\n -all - to delete messages in each topic if this is a forum otherwise the flag'll just be ignored", - ) - async def purgelength(self, message: Message): - if not self.client or not self.db or not self.tg_id: - return - - args = utils.get_args_raw(message) - if not args: - return await utils.answer(message, self.strings["no_args"]) - - args, types_filter, is_each = self.get_types_filter(message) - if not args: - return await utils.answer(message, self.strings["no_length"]) - - try: - length = int(args) - except ValueError: - return await utils.answer(message, self.strings["invalid_length"]) - - try: - entity = await self.client.get_entity(message.chat.id) - is_forum = getattr(entity, "forum", False) - except Exception: - is_forum = False - - status = self.db.get(__name__, "status", {}) - status[message.chat.id] = True - self.db.set(__name__, "status", status) - - deleted_count = 0 - async for i in self.client.iter_messages(message.peer_id): - status = self.db.get(__name__, "status", {}) - if status.get(message.chat.id, None) is not True: - return await utils.answer(message, self.strings["interrupted"]) - - if is_forum and not is_each: - try: - if utils.get_topic(message) != utils.get_topic(i): - continue - except Exception: - pass - - if ( - i.from_id == self.tg_id - and len(i.text or "") == length - and self.is_valid_type(i, types_filter) - ): - try: - await self.client.delete_messages(message.peer_id, [i.id]) - deleted_count += 1 - except Exception: - pass - - await utils.answer(message, self.strings["purge_length_complete"]) - - @loader.command( - ru_doc="Прервать процесс удаления\nИспользуйте в чате, где вы ранее начали удаление", - en_doc="Interrupt the deletion process\nUse in the chat where you've previously started deletion", - ) - async def nopurge(self, message: Message): - if not self.db: - return - - chat_id = utils.get_chat_id(message) - - status = self.db.get(__name__, "status", {}) - _status = status.get(chat_id, None) - status[chat_id] = False - self.db.set(__name__, "status", status) - - if _status is True: - await utils.answer(message, self.strings["disabled"]) - elif _status is False: - await utils.answer(message, self.strings["enabled"]) - else: - await utils.answer(message, self.strings["none"]) - - def get_types_filter(self, message: Message): - """Get the types filter from the command arguments.""" - args_raw = utils.get_args_raw(message) - if not args_raw: - return "", [], False - - args = args_raw.split() - types_filter = [] - valid_types = ["-img", "-voice", "-file", "-all"] - is_each = "-all" in args - - _args = args_raw - args_ = "" - - for i, arg in enumerate(args): - if arg in valid_types: - _args = " ".join(args[:i]) - args_ = " ".join(args[i:]) - break - - if "-img" in args_: - types_filter.append("img") - if "-voice" in args_: - types_filter.append("voice") - if "-file" in args_: - types_filter.append("file") - if "-all" in args_: - is_each = True - - return _args, types_filter, is_each - - def is_valid_type(self, message: Message, types_filter): - """Check if the message matches the specified types filter.""" - if not types_filter: - return True # No filtering means all types are valid - - if "img" in types_filter and hasattr(message, "photo") and message.photo: - return True - if "voice" in types_filter and hasattr(message, "voice") and message.voice: - return True - if "file" in types_filter and hasattr(message, "document") and message.document: - for attr in getattr(message.document, "attributes", []): - if isinstance(attr, DocumentAttributeFilename): - return True - - return False diff --git a/archquise/H.Modules/full.txt b/archquise/H.Modules/full.txt deleted file mode 100644 index b54203a..0000000 --- a/archquise/H.Modules/full.txt +++ /dev/null @@ -1,54 +0,0 @@ -ASCIIArt -AccountData -AniLiberty -AnimeQuotes -Article -AutofarmCookies -BirthdayTime -CheckSpamBan -CodeShare -CryptoCurrency -FakeActions -FakeWallet -FolderAutoRead -GigaChat -H -HAFK -HInstall -InfoBannersManager -InlineButton -InlineCoin -InlineHelper -IrisSimpleMod -KBSwapper -Memes -MessageMonitor -MooFarmRC1 -Music -ReplaceVowels -SMAcrhiver -TaskManager -TelegramStatusCodes -TempChat -Text2File -TikTokDownloader -TimedEmojiStatus -UserbotAvast -Video2GIF -VirusTotal -VoiceDL -Weather -WindowsKeys -animals -face -globalrestrict -hikkahost -mediatools -novoice -nsfwart -passgen -profile -search -shortener -timezone -ytdl diff --git a/archquise/H.Modules/globalrestrict.py b/archquise/H.Modules/globalrestrict.py deleted file mode 100644 index f117092..0000000 --- a/archquise/H.Modules/globalrestrict.py +++ /dev/null @@ -1,684 +0,0 @@ -# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀ -# █▀█ █ █ █ █▀█ █▀▄ █ -# © Copyright 2022 -# https://t.me/hikariatama -# -# 🔒 Licensed under the GNU AGPLv3 -# 🌐 https://www.gnu.org/licenses/agpl-3.0.html - -# Some functions took from Hikarichat by Hikariatama - -# --------------------------------------------------------------------------------- - -# Proprietary License Agreement - -# Copyright (c) 2024-29 CodWiz - -# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions: - -# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author. - -# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author. - -# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software. - -# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software. - -# 5. By using the Software, you agree to be bound by the terms and conditions of this license. - -# For any inquiries or requests for permissions, please contact codwiz@yandex.ru. - -# --------------------------------------------------------------------------------- -# Name: GlobalRestrict -# Description: Global mutation or ban -# Author: @hikka_mods -# --------------------------------------------------------------------------------- -# meta developer: @hikka_mods -# scope: Api GlobalRestrict -# scope: Api GlobalRestrict 0.0.1 -# --------------------------------------------------------------------------------- - -import logging -import re -import time -import typing - -from telethon.tl.types import ( - Channel, - Chat, - Message, - User, -) - -from .. import loader, utils - -logger = logging.getLogger(__name__) - -BANNED_RIGHTS = { - "view_messages": False, - "send_messages": False, - "send_media": False, - "send_stickers": False, - "send_gifs": False, - "send_games": False, - "send_inline": False, - "send_polls": False, - "change_info": False, - "invite_users": False, -} - -MUTES_RIGHTS = { - "view_messages": True, - "send_messages": False, - "send_media": False, - "send_stickers": False, - "send_gifs": False, - "send_games": False, - "send_inline": False, - "send_polls": False, - "change_info": False, - "invite_users": False, -} - - -def get_full_name(user: typing.Union[User, Channel]) -> str: - return utils.escape_html( - user.title - if isinstance(user, Channel) - else ( - f"{user.first_name} " - + (user.last_name if getattr(user, "last_name", False) else "") - ) - ).strip() - - -@loader.tds -class GlobalRestrict(loader.Module): - """Global mutation or ban""" - - strings = { - "name": "GlobalRestrict", - "no_reason": "Not specified", - "args": ( - "<emoji document_id=5300759756669984376>🚫</emoji> <b>Incorrect arguments</b>" - ), - "glban": ( - '<emoji document_id=5301059317753979286>🖕</emoji> <b><a href="{}">{}</a>' - " has been globally banned.</b>\n<b>Reason: </b><i>{}</i>\n\n{}" - ), - "glbanning": ( - "<emoji document_id=5301059317753979286>🖕</emoji> <b>Globally banning <a" - ' href="{}">{}</a>...</b>' - ), - "gunban": ( - '<emoji document_id=6334872157947955302>🤗</emoji> <b><a href="{}">{}</a>' - " has been globally unbanned.</b>\n\n{}" - ), - "gunbanning": ( - "<emoji document_id=6334872157947955302>🤗</emoji> <b>Global unbanning <a" - ' href="{}">{}</a>...</b>' - ), - "in_n_chats": ( - "<emoji document_id=5379568936218009290>👎</emoji> <b>Banned in {}" - " chat(s)</b>" - ), - "unbanned_in_n_chats": ( - "<emoji document_id=5461129450341014019>✋️</emoji> <b>Unbanned in {}" - " chat(s)</b>" - ), - "glmute": ( - '<emoji document_id=5301059317753979286>🖕</emoji> <b><a href="{}">{}</a>' - " has been globally muted.</b>\n<b>Reason: </b><i>{}</i>\n\n{}" - ), - "glmutes": ( - "<emoji document_id=5301059317753979286>🖕</emoji> <b>Global mute <a" - ' href="{}">{}</a>...</b>' - ), - "gunmute": ( - '<emoji document_id=6334872157947955302>🤗</emoji> <b><a href="{}">{}</a>' - " has been globally unmuted.</b>\n\n{}" - ), - "gunmutes": ( - "<emoji document_id=6334872157947955302>🤗</emoji> <b>Global unmute <a" - ' href="{}">{}</a>...</b>' - ), - "in_m_chats": ( - "<emoji document_id=5379568936218009290>👎</emoji> <b>Muted in {}" - " chat(s)</b>" - ), - "unmute_in_n_chats": ( - "<emoji document_id=5461129450341014019>✋️</emoji> <b>Unmuted in {}" - " chat(s)</b>" - ), - } - - strings_ru = { - "no_reason": "Не указана", - "args": ( - "<emoji document_id=5300759756669984376>🚫</emoji> <b>Неверные" - " аргументы</b>" - ), - "glban": ( - '<emoji document_id=5301059317753979286>🖕</emoji> <b><a href="{}">{}</a>' - " был гзабанен.</b>\n<b>Причина: </b><i>{}</i>\n\n{}" - ), - "glbanning": ( - "<emoji document_id=5301059317753979286>🖕</emoji> <b>Гбан <a" - ' href="{}">{}</a>...</b>' - ), - "gunban": ( - '<emoji document_id=6334872157947955302>🤗</emoji> <b><a href="{}">{}</a>' - " был гразбанен.</b>\n\n{}" - ), - "gunbanning": ( - "<emoji document_id=6334872157947955302>🤗</emoji> <b>Гразбан <a" - ' href="{}">{}</a>...</b>' - ), - "in_n_chats": ( - "<emoji document_id=5379568936218009290>👎</emoji> <b>Забанил в {}" - " чат(-ах)</b>" - ), - "unbanned_in_n_chats": ( - "<emoji document_id=5461129450341014019>✋️</emoji> <b>Разбанил in {}" - " чат(-ах)</b>" - ), - "glmute": ( - '<emoji document_id=5301059317753979286>🖕</emoji> <b><a href="{}">{}</a>' - " был замучен.</b>\n<b>Причина: </b><i>{}</i>\n\n{}" - ), - "glmutes": ( - "<emoji document_id=5301059317753979286>🖕</emoji> <b>Гмут <a" - ' href="{}">{}</a>...</b>' - ), - "gunmute": ( - '<emoji document_id=6334872157947955302>🤗</emoji> <b><a href="{}">{}</a>' - " был размучен.</b>\n\n{}" - ), - "gunmutes": ( - "<emoji document_id=6334872157947955302>🤗</emoji> <b>Гразмут <a" - ' href="{}">{}</a>...</b>' - ), - "in_m_chats": ( - "<emoji document_id=5379568936218009290>👎</emoji> <b>Мут в {} чат(-ах)</b>" - ), - "unmute_in_n_chats": ( - "<emoji document_id=5461129450341014019>✋️</emoji> <b>Размут in {}" - " чат(-ах)</b>" - ), - } - - def __init__(self): - self._gban_cache = {} - self._gmute_cache = {} - - @staticmethod - def convert_time(t: str) -> int: - """ - Tries to export time from text - """ - try: - if not str(t)[:-1].isdigit(): - return 0 - - if "d" in str(t): - t = int(t[:-1]) * 60 * 60 * 24 - - if "h" in str(t): - t = int(t[:-1]) * 60 * 60 - - if "m" in str(t): - t = int(t[:-1]) * 60 - - if "s" in str(t): - t = int(t[:-1]) - - t = int(re.sub(r"[^0-9]", "", str(t))) - except ValueError: - return 0 - - return t - - async def args_parser( - self, - message: Message, - include_force: bool = False, - include_silent: bool = False, - ) -> tuple: - """Get args from message""" - args = " " + utils.get_args_raw(message) - if include_force and " -f" in args: - force = True - args = args.replace(" -f", "") - else: - force = False - - if include_silent and " -s" in args: - silent = True - args = args.replace(" -s", "") - else: - silent = False - - args = args.strip() - - reply = await message.get_reply_message() - - if reply and not args: - return ( - (await self._client.get_entity(reply.sender_id)), - 0, - utils.escape_html(self.strings("no_reason")).strip(), - *((force,) if include_force else []), - *((silent,) if include_silent else []), - ) - - try: - a = args.split()[0] - if str(a).isdigit(): - a = int(a) - user = await self._client.get_entity(a) - except Exception: - try: - user = await self._client.get_entity(reply.sender_id) - except Exception: - return False - - t = ([arg for arg in args.split() if self.convert_time(arg)] or ["0"])[0] - args = args.replace(t, "").replace(" ", " ") - t = self.convert_time(t) - - if not reply: - try: - args = " ".join(args.split()[1:]) - except Exception: - pass - - if time.time() + t >= 2208978000: # 01.01.2040 00:00:00 - t = 0 - - return ( - user, - t, - utils.escape_html(args or self.strings("no_reason")).strip(), - *((force,) if include_force else []), - *((silent,) if include_silent else []), - ) - - async def ban( - self, - chat: typing.Union[Chat, int], - user: typing.Union[User, Channel, int], - period: int = 0, - reason: str = None, - message: typing.Optional[Message] = None, - silent: bool = False, - ): - """Ban user in chat""" - if str(user).isdigit(): - user = int(user) - - if reason is None: - reason = self.strings("no_reason") - - try: - await self.inline.bot.kick_chat_member( - int(f"-100{getattr(chat, 'id', chat)}"), - int(getattr(user, "id", user)), - ) - except Exception: - await self._client.edit_permissions( - chat, - user, - until_date=(time.time() + period) if period else 0, - **BANNED_RIGHTS, - ) - - if silent: - return - - async def mute( - self, - chat: typing.Union[Chat, int], - user: typing.Union[User, Channel, int], - period: int = 0, - reason: str = None, - message: typing.Optional[Message] = None, - silent: bool = False, - ): - """Mute user in chat""" - if str(user).isdigit(): - user = int(user) - - if reason is None: - reason = self.strings("no_reason") - - try: - await self.inline.bot.restrict_chat_member( - int(f"-100{getattr(chat, 'id', chat)}"), - int(getattr(user, "id", user)), - ) - except Exception: - await self._client.edit_permissions( - chat, - user, - until_date=(time.time() + period) if period else 0, - **MUTES_RIGHTS, - ) - - if silent: - return - - @loader.command( - ru_doc="<реплай | юзер> [причина] [-s] - Забанить пользователя во всех чатах где ты админ", - en_doc="<replay | user> [reason] [-s] - Ban the user in all chats where you are the admin", - ) - async def glban(self, message): - reply = await message.get_reply_message() - args = utils.get_args_raw(message) - if not reply and not args: - await utils.answer(message, self.strings("args")) - return - - a = await self.args_parser(message, include_silent=True) - - if not a: - await utils.answer(message, self.strings("args")) - return - - user, t, reason, silent = a - - message = await utils.answer( - message, - self.strings("glbanning").format( - utils.get_entity_url(user), - utils.escape_html(get_full_name(user)), - ), - ) - - if not self._gban_cache or self._gban_cache["exp"] < time.time(): - self._gban_cache = { - "exp": int(time.time()) + 10 * 60, - "chats": [ - chat.entity.id - async for chat in self._client.iter_dialogs() - if ( - ( - isinstance(chat.entity, Chat) - or ( - isinstance(chat.entity, Channel) - and getattr(chat.entity, "megagroup", False) - ) - ) - and chat.entity.admin_rights - and chat.entity.participants_count > 5 - and chat.entity.admin_rights.ban_users - ) - ], - } - - chats = "" - counter = 0 - - for chat in self._gban_cache["chats"]: - try: - await self.ban(chat, user, 0, reason, silent=True) - except Exception: - pass - else: - chats += '▫️ <b><a href="{}">{}</a></b>\n'.format( - utils.get_entity_url(await self._client.get_entity(chat, exp=0)), - utils.escape_html( - get_full_name(await self._client.get_entity(chat, exp=0)) - ), - ) - counter += 1 - - await utils.answer( - message, - self.strings("glban").format( - utils.get_entity_url(user), - utils.escape_html(get_full_name(user)), - reason, - self.strings("in_n_chats").format(counter) if silent else chats, - ), - ) - - @loader.command( - ru_doc="<реплай | юзер> [причина] [-s] - Разбанить пользователя во всех где ты админ", - en_doc="<replay | user> [reason] [-s] - To unban the user in all where you are the admin", - ) - async def glunban(self, message: Message): - reply = await message.get_reply_message() - args = utils.get_args_raw(message) - if not reply and not args: - await utils.answer(message, self.strings("args")) - return - - a = await self.args_parser(message, include_silent=True) - - if not a: - await utils.answer(message, self.strings("args")) - return - - user, t, reason, silent = a - - message = await utils.answer( - message, - self.strings("gunbanning").format( - utils.get_entity_url(user), - utils.escape_html(get_full_name(user)), - ), - ) - - if not self._gban_cache or self._gban_cache["exp"] < time.time(): - self._gban_cache = { - "exp": int(time.time()) + 10 * 60, - "chats": [ - chat.entity.id - async for chat in self._client.iter_dialogs() - if ( - ( - isinstance(chat.entity, Chat) - or ( - isinstance(chat.entity, Channel) - and getattr(chat.entity, "megagroup", False) - ) - ) - and chat.entity.admin_rights - and chat.entity.participants_count > 5 - and chat.entity.admin_rights.ban_users - ) - ], - } - - chats = "" - counter = 0 - - for chat in self._gban_cache["chats"]: - try: - await self._client.edit_permissions( - chat, - user, - until_date=0, - **{right: True for right in BANNED_RIGHTS.keys()}, - ) - except Exception: - pass - else: - chats += '▫️ <b><a href="{}">{}</a></b>\n'.format( - utils.get_entity_url(await self._client.get_entity(chat, exp=0)), - utils.escape_html( - get_full_name(await self._client.get_entity(chat, exp=0)) - ), - ) - counter += 1 - - await utils.answer( - message, - self.strings("gunban").format( - utils.get_entity_url(user), - utils.escape_html(get_full_name(user)), - ( - self.strings("unbanned_in_n_chats").format(counter) - if silent - else chats - ), - ), - ) - - @loader.command( - ru_doc="<реплай | юзер> [причина] [-s] - Замутить пользователя во всех чатах где ты админ", - en_doc="<replay | user> [reason] [-s] - To hook up the user in all chats where you are the admin", - ) - async def glmute(self, message): - reply = await message.get_reply_message() - args = utils.get_args_raw(message) - if not reply and not args: - await utils.answer(message, self.strings("args")) - return - - a = await self.args_parser(message, include_silent=True) - - if not a: - await utils.answer(message, self.strings("args")) - return - - user, t, reason, silent = a - - message = await utils.answer( - message, - self.strings("glmutes").format( - utils.get_entity_url(user), - utils.escape_html(get_full_name(user)), - ), - ) - - if not self._gmute_cache or self._gmute_cache["exp"] < time.time(): - self._gmute_cache = { - "exp": int(time.time()) + 10 * 60, - "chats": [ - chat.entity.id - async for chat in self._client.iter_dialogs() - if ( - ( - isinstance(chat.entity, Chat) - or ( - isinstance(chat.entity, Channel) - and getattr(chat.entity, "megagroup", False) - ) - ) - and chat.entity.admin_rights - and chat.entity.participants_count > 5 - and chat.entity.admin_rights.ban_users - ) - ], - } - - chats = "" - counter = 0 - - for chat in self._gmute_cache["chats"]: - try: - await self.mute(chat, user, 0, reason, silent=True) - except Exception: - pass - else: - chats += '▫️ <b><a href="{}">{}</a></b>\n'.format( - utils.get_entity_url(await self._client.get_entity(chat, exp=0)), - utils.escape_html( - get_full_name(await self._client.get_entity(chat, exp=0)) - ), - ) - counter += 1 - - await utils.answer( - message, - self.strings("glmute").format( - utils.get_entity_url(user), - utils.escape_html(get_full_name(user)), - reason, - self.strings("in_m_chats").format(counter) if silent else chats, - ), - ) - - @loader.command( - ru_doc="<реплай | юзер> [причина] [-s] - Размутит пользователя во всех где ты админ", - en_doc="<replay | user> [reason] [-s] - Will confuse the user in all where you are the admin", - ) - async def glunmute(self, message: Message): - reply = await message.get_reply_message() - args = utils.get_args_raw(message) - if not reply and not args: - await utils.answer(message, self.strings("args")) - return - - a = await self.args_parser(message, include_silent=True) - - if not a: - await utils.answer(message, self.strings("args")) - return - - user, t, reason, silent = a - - message = await utils.answer( - message, - self.strings("gunmutes").format( - utils.get_entity_url(user), - utils.escape_html(get_full_name(user)), - ), - ) - - if not self._gmute_cache or self._gmute_cache["exp"] < time.time(): - self._gmute_cache = { - "exp": int(time.time()) + 10 * 60, - "chats": [ - chat.entity.id - async for chat in self._client.iter_dialogs() - if ( - ( - isinstance(chat.entity, Chat) - or ( - isinstance(chat.entity, Channel) - and getattr(chat.entity, "megagroup", False) - ) - ) - and chat.entity.admin_rights - and chat.entity.participants_count > 5 - and chat.entity.admin_rights.ban_users - ) - ], - } - - chats = "" - counter = 0 - - for chat in self._gmute_cache["chats"]: - try: - await self._client.edit_permissions( - chat, - user, - until_date=0, - **{right: True for right in MUTES_RIGHTS.keys()}, - ) - except Exception: - pass - else: - chats += '▫️ <b><a href="{}">{}</a></b>\n'.format( - utils.get_entity_url(await self._client.get_entity(chat, exp=0)), - utils.escape_html( - get_full_name(await self._client.get_entity(chat, exp=0)) - ), - ) - counter += 1 - - await utils.answer( - message, - self.strings("gunmute").format( - utils.get_entity_url(user), - utils.escape_html(get_full_name(user)), - ( - self.strings("unmute_in_n_chats").format(counter) - if silent - else chats - ), - ), - ) diff --git a/archquise/H.Modules/hikkahost.py b/archquise/H.Modules/hikkahost.py deleted file mode 100644 index 2f1e681..0000000 --- a/archquise/H.Modules/hikkahost.py +++ /dev/null @@ -1,326 +0,0 @@ -# Proprietary License Agreement - -# Copyright (c) 2024-29 CodWiz - -# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions: - -# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author. - -# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author. - -# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software. - -# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software. - -# 5. By using the Software, you agree to be bound by the terms and conditions of this license. - -# For any inquiries or requests for permissions, please contact codwiz@yandex.ru. - -# --------------------------------------------------------------------------------- -# Name: HikkaHost -# Description: Hikkahost manager. -# Author: @hikka_mods -# --------------------------------------------------------------------------------- -# meta developer: @hikka_mods -# scope: api HikkaHost -# scope: api HikkaHost 0.0.1 -# --------------------------------------------------------------------------------- - -import json -import logging -from datetime import datetime, timedelta, timezone - -import aiohttp - -from .. import loader, utils - -logger = logging.getLogger(__name__) - - -class HostApi: - """ - A class for interacting with a Host API. - - Args: - token (str): The API token. - """ - - def __init__(self, token: str): - self.token = token - - async def _request(self, path: str, method: str = "GET") -> dict: - """ - Sends a request to the API. - - Args: - path (str): The API path. - method (str, optional): The HTTP method. Defaults to "GET". - - Returns: - dict: The API response as a dictionary. - """ - url = "https://api.hikka.host" + path - async with aiohttp.ClientSession( - trust_env=True, - timeout=aiohttp.ClientTimeout(total=15), - ) as session: - async with session.request( - method, - url, - headers={ - "Content-Type": "application/json", - "token": self.token, - }, - ) as response: - return await response.json() - - async def stats(self, user_id: int) -> dict: - """ - Gets the host stats. - - Args: - user_id (int): The user ID. - - Returns: - dict: The host stats. - """ - url = f"/api/host/{user_id}/stats" - return await self._request(url) - - async def host_info(self, user_id: int) -> dict: - """ - Gets the host information. - - Args: - user_id (int): The user ID. - - Returns: - dict: The host information. - """ - url = f"/api/host/{user_id}" - return await self._request(url) - - async def status(self, user_id: int) -> dict: - """ - Gets the host status. - - Args: - user_id (int): The user ID. - - Returns: - dict: The host status. - """ - url = f"/api/host/{user_id}/status" - return await self._request(url) - - async def logs(self, user_id: int) -> dict: - """ - Gets the host logs. - - Args: - user_id (int): The user ID. - - Returns: - dict: The host logs. - """ - url = f"/api/host/{user_id}/logs/all" - return await self._request(url) - - async def action(self, user_id: int, action: str = "restart") -> dict: - """ - Performs an action on the host. - - Args: - user_id (int): The user ID. - action (str, optional): The action to perform. Defaults to "restart". - - Returns: - dict: The action result. - """ - url = f"/api/host/{user_id}?action={action}" - return await self._request(url, method="PUT") - - -def bytes_to_megabytes(b: int): - """ - Converts bytes to megabytes. - - Args: - b (int): The number of bytes. - - Returns: - float: The number of megabytes. - """ - return round(b / 1024 / 1024, 1) - - -@loader.tds -class HikkahostMod(loader.Module): - """Hikkahost manager.""" - - MAX_RAM = 750 - - strings = { - "name": "HikkaHost", - "info": ( - "<emoji document_id=5879770735999717115>👤</emoji> <b>Information panel</b>\n\n" - "<emoji document_id=5974526806995242353>🆔</emoji> <b>Server ID:</b> <code>{server_id}</code>\n" - "<emoji document_id=6005570495603282482>🔑</emoji> <b>ID:</b> <code>{id}</code>\n" - "<emoji document_id=5874986954180791957>📶</emoji> <b>Status:</b> <code>{status}</code>\n" - "<emoji document_id=5451646226975955576>⌛️</emoji> <b>Subscription ends:</b> <code>{end_dates}</code> | <code>{days_end} days</code>\n\n" - "<emoji document_id=5877260593903177342>⚙️</emoji> <b>CPU:</b> <code>{cpu_percent} %</code>\n" - "<emoji document_id=5379652232813750191>💾</emoji> <b>RAM:</b> <code>{memory} / {max_ram} MB</code> <b>{ram_percent} %</b>" - ), - "logs": ( - "<emoji document_id=5188377234380954537>🌘</emoji> <b>Here are your logs</b>" - ), - "restart": ( - "<emoji document_id=5789886476472815477>✅</emoji> <b>Restart request sent</b>\n" - "This message remains unchanged after the restart" - ), - "loading_info": "<emoji document_id=5451646226975955576>⌛️</emoji> Loading...", - "no_apikey": "<emoji document_id=5260342697075416641>🚫</emoji> You have not specified an API Key\nTo get a token.\n\n1. Go to the @hikkahost_bot\n2. Write /token\n3. Paste it into the config", - "condition": "works", - } - - strings_ru = { - "info": ( - "<emoji document_id=5879770735999717115>👤</emoji> <b>Панель информации</b>\n\n" - "<emoji document_id=5974526806995242353>🆔</emoji> <b>Server ID:</b> <code>{server_id}</code>\n" - "<emoji document_id=6005570495603282482>🔑</emoji> <b>ID:</b> <code>{id}</code>\n" - "<emoji document_id=5874986954180791957>📶</emoji> <b>Статус:</b> <code>{status}</code>\n" - "<emoji document_id=5451646226975955576>⌛️</emoji> <b>Подписка закончится:</b> <code>{end_dates}</code> | <code>{days_end} дней</code>\n\n" - "<emoji document_id=5877260593903177342>⚙️</emoji> <b>CPU:</b> <code>{cpu_percent} %</code>\n" - "<emoji document_id=5379652232813750191>💾</emoji> <b>RAM:</b> <code>{memory} / {max_ram} MB</code> <b>{ram_percent} %</b>" - ), - "logs": ( - "<emoji document_id=5188377234380954537>🌘</emoji> <b>Вот ваши логи</b>" - ), - "restart": ( - "<emoji document_id=5789886476472815477>✅</emoji> <b>Запрос на рестарт отправил</b>\n" - "Это сообщение не изменяется после рестарта" - ), - "loading_info": "<emoji document_id=5451646226975955576>⌛️</emoji> Загрузка...", - "no_apikey": "<emoji document_id=5260342697075416641>🚫</emoji> Вы не указали Api Key\nЧтобы получить token.\n\n1. Перейдите в бота @hikkahost_bot\n2. Напишите /token\n3. Вставьте его в конфиг", - "condition": "работает", - } - - def __init__(self): - self.name = self.strings["name"] - self.config = loader.ModuleConfig( - loader.ConfigValue( - "token", - None, - validator=loader.validators.Hidden(), - ), - ) - - @loader.command( - ru_doc="Статус HikkaHost", - en_doc="Status HikkaHost", - ) - async def hinfocmd(self, message): - message = await utils.answer(message, self.strings("loading_info")) - if self.config["token"] is None: - await utils.answer(message, self.strings("no_apikey")) - return - - token = self.config["token"] - user_id = token.split(":")[0] - api = HostApi(token) - - stats_data = await api.stats(user_id) - host_data = await api.host_info(user_id) - datas = await api.status(user_id) - - memory = bytes_to_megabytes(stats_data["stats"]["memory_stats"]["usage"]) - cpu_percent = ( - round( - ( - stats_data["stats"]["cpu_stats"]["cpu_usage"]["total_usage"] - / stats_data["stats"]["cpu_stats"]["system_cpu_usage"] - ) - * 100.0, - 2, - ) - if stats_data["stats"]["cpu_stats"]["cpu_usage"]["total_usage"] - and stats_data["stats"]["cpu_stats"]["system_cpu_usage"] - else None - ) - ram_percent = round( - bytes_to_megabytes( - stats_data["stats"]["memory_stats"]["usage"] / self.MAX_RAM - ) - * 100, - 2, - ) - - server_id = host_data["host"]["server_id"] - target_data = datetime.fromisoformat( - host_data["host"]["end_date"].replace("Z", "+00:00") - ).replace(tzinfo=timezone.utc) - current_data = datetime.now(timezone.utc) - days_end = (target_data - current_data).days - end_dates = (current_data + timedelta(days=days_end)).strftime("%d-%m-%Y") - - if "status" in datas and datas["status"] == "running": - status = self.strings("condition") - - await utils.answer( - message, - self.strings("info").format( - server_id=server_id, - id=user_id, - status=status, - end_dates=end_dates, - days_end=days_end, - cpu_percent=cpu_percent, - memory=memory, - max_ram=self.MAX_RAM, - ram_percent=ram_percent, - ), - ) - - @loader.command( - ru_doc="Логи HikkaHost", - en_doc="Logs HikkaHost", - ) - async def hlogscmd(self, message): - if self.config["token"] is None: - await utils.answer(message, self.strings("no_apikey")) - return - - token = self.config["token"] - user_id = token.split(":")[0] - api = HostApi(token) - data = await api.logs(user_id) - - files_log = data["logs"] - - import tempfile - import os - - fd, tmp_path = tempfile.mkstemp(suffix=".txt", prefix="hikkahost_log_") - try: - with os.fdopen(fd, "w") as log_file: - json.dump(files_log, log_file) - await utils.answer_file(message, tmp_path, self.strings("logs")) - finally: - os.unlink(tmp_path) - - @loader.command( - ru_doc="Рестарт HikkaHost", - en_doc="Restart HikkaHost", - ) - async def hrestartcmd(self, message): - await utils.answer(message, self.strings("restart")) - - if self.config["token"] is None: - await utils.answer(message, self.strings("no_apikey")) - return - - token = self.config["token"] - user_id = token.split(":")[0] - api = HostApi(token) - - await api.action(user_id) diff --git a/archquise/H.Modules/index.html b/archquise/H.Modules/index.html deleted file mode 100644 index b31ed64..0000000 --- a/archquise/H.Modules/index.html +++ /dev/null @@ -1,68 +0,0 @@ -<!DOCTYPE html> -<html lang="en"> -<head> -<meta charset="UTF-8"> -<meta name="viewport" content="width=device-width, initial-scale=1.0"> -<title>H:Mods - - - - - - diff --git a/archquise/H.Modules/jacques.py b/archquise/H.Modules/jacques.py deleted file mode 100644 index cc425fa..0000000 --- a/archquise/H.Modules/jacques.py +++ /dev/null @@ -1,130 +0,0 @@ -# Proprietary License Agreement - -# Copyright (c) 2024-29 CodWiz - -# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions: - -# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author. - -# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author. - -# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software. - -# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software. - -# 5. By using the Software, you agree to be bound by the terms and conditions of this license. - -# For any inquiries or requests for permissions, please contact codwiz@yandex.ru. - -# --------------------------------------------------------------------------------- -# Name: Жаконизатор -# Description: Жаконизатор -# Author: @hikka_mods -# --------------------------------------------------------------------------------- - -# meta developer: @hikka_mods -# scope: Жаконизатор -# scope: Жаконизатор 0.0.1 -# --------------------------------------------------------------------------------- - -import io -import logging -from textwrap import wrap -from typing import Optional - -import aiohttp -from PIL import Image, ImageDraw, ImageFont - -from .. import loader, utils - -logger = logging.getLogger(__name__) - - -@loader.tds -class JacquesMod(loader.Module): - """Жаконизатор""" - - strings = {"name": "Жаконизатор", "usage": "Write .help Жаконизатор"} - - strings_ru = {"usage": "Напиши .help Жаконизатор"} - - def __init__(self): - self.name = self.strings["name"] - self._me = None - self._ratelimit = [] - self._session: Optional[aiohttp.ClientSession] = None - self.config = loader.ModuleConfig( - loader.ConfigValue( - "font", - "https://github.com/Codwizer/ReModules/blob/main/assets/OpenSans-Light.ttf?raw=true", - lambda: "добавьте ссылку на нужный вам шрифт", - ), - loader.ConfigValue( - "location", - "center", - "Можно указать left, right или center", - validator=loader.validators.Choice(["left", "right", "center"]), - ), - ) - - async def _get_session(self) -> aiohttp.ClientSession: - if self._session is None or self._session.closed: - self._session = aiohttp.ClientSession( - timeout=aiohttp.ClientTimeout(total=30) - ) - return self._session - - async def on_unload(self): - if self._session and not self._session.closed: - await self._session.close() - - @loader.command( - ru_doc="<реплай на сообщение/свой текст>", - en_doc="", - ) - async def ionicmd(self, message): - reply = await message.get_reply_message() - args = utils.get_args_raw(message) - - if not args: - if not reply: - await utils.answer(message, self.strings("usage", message)) - return - else: - txt = reply.raw_text - else: - txt = args - - session = await self._get_session() - async with session.get(self.config["font"]) as font_response: - font_data = await font_response.read() - - async with session.get( - "https://raw.githubusercontent.com/Codwizer/ReModules/main/assets/IMG_20231128_152538.jpg" - ) as pic_response: - pic_data = await pic_response.read() - - img = Image.open(io.BytesIO(pic_data)).convert("RGB") - - wrapped_text = "\n".join(wrap(txt, 19)) + "\n" - draw = ImageDraw.Draw(img) - font = ImageFont.truetype(io.BytesIO(font_data), 32, encoding="UTF-8") - - text_bbox = draw.multiline_textbbox((0, 0), wrapped_text, font=font) - text_size = (text_bbox[2] - text_bbox[0], text_bbox[3] - text_bbox[1]) - imtext = Image.new("RGBA", (text_size[0] + 10, text_size[1] + 10), (0, 0, 0, 0)) - draw_imtext = ImageDraw.Draw(imtext) - draw_imtext.multiline_text( - (10, 10), wrapped_text, (0, 0, 0), font=font, align=self.config["location"] - ) - - imtext.thumbnail((350, 195)) - img.paste(imtext, (10, 10), imtext) - - out = io.BytesIO() - out.name = "hikka_mods.jpg" - img.save(out) - out.seek(0) - - await message.client.send_file(message.to_id, out, reply_to=reply) - await message.delete() diff --git a/archquise/H.Modules/mediatools.py b/archquise/H.Modules/mediatools.py deleted file mode 100644 index e91fd9e..0000000 --- a/archquise/H.Modules/mediatools.py +++ /dev/null @@ -1,770 +0,0 @@ -# Proprietary License Agreement - -# Copyright (c) 2024-29 CodWiz - -# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions: - -# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author. - -# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author. - -# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software. - -# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software. - -# 5. By using the Software, you agree to be bound by the terms and conditions of this license. - -# For any inquiries or requests for permissions, please contact codwiz@yandex.ru. - -# --------------------------------------------------------------------------------- -# Name: MediaTools -# Description: Powerful tools for working with media files -# Author: @hikka_mods -# --------------------------------------------------------------------------------- -# meta developer: @hikka_mods -# scope: MediaTools -# scope: MediaTools 0.0.1 -# --------------------------------------------------------------------------------- - -import asyncio -import logging -import math -import os -import re -import shutil -from typing import Optional - -from telethon.types import Message - -from .. import loader, utils - -logger = logging.getLogger(__name__) - -def check_ffmpeg(): - return shutil.which("ffmpeg") is not None - - -@loader.tds -class MediaToolsMod(loader.Module): - """Powerful tools for working with media files""" - - strings = { - "name": "MediaTools", - "no_reply": "🚫 Reply to a media file!", - "no_ffmpeg": "❌ FFmpeg is not installed! Install: apt-get install ffmpeg", - "processing": "⚙️ Processing...", - "converted": "✅ Converted to {}", - "downloaded": "✅ Voice message saved", - "gif_created": "✅ GIF created", - "cut_done": "✅ Trimmed", - "circle_done": "✅ Video circle created", - "audio_extracted": "✅ Audio extracted", - "compressed": "✅ Compressed to {}", - "split_done": "✅ Split into {} parts", - "merged": "✅ Merged", - "metadata_removed": "✅ Metadata removed", - "invalid_args": "❌ Invalid arguments", - "error": "❌ Error: {}", - "available_formats": "Available formats:\n🎵 Audio: mp3, flac, wav, aac, ogg, m4a, opus\n🎬 Video: mp4, avi, mkv, mov, wmv, flv, webm, 3gp, hevc, h264", - "cut_usage": "Usage: .cut 20s6ms:8m16s3ms", - "compress_usage": "Available qualities: 144p, 240p, 360p, 480p, 720p, 1080p, 1440p, 2160p", - "split_time_usage": "Example: .split 10m (10 minutes)", - "split_size_usage": "Usage: .split 10m or .split 5MB", - "merge_usage": "Reply to first video/audio", - "min_files": "Need at least 2 media files in chain", - "downloading": "Downloading {} files...", - "part": "Part {}/{}", - } - - strings_ru = { - "no_reply": "🚫 Ответьте на медиафайл!", - "no_ffmpeg": "❌ FFmpeg не установлен! Установите: apt-get install ffmpeg", - "processing": "⚙️ Обрабатываю...", - "converted": "✅ Конвертировано в {}", - "downloaded": "✅ Голосовое сохранено", - "gif_created": "✅ GIF создан", - "cut_done": "✅ Обрезано", - "circle_done": "✅ Видео в кружок", - "audio_extracted": "✅ Аудио извлечено", - "compressed": "✅ Сжато до {}", - "split_done": "✅ Разделено на {} частей", - "merged": "✅ Объединено", - "metadata_removed": "✅ Метаданные удалены", - "invalid_args": "❌ Неверные аргументы", - "error": "❌ Ошибка: {}", - "available_formats": "Доступные форматы:\n🎵 Аудио: mp3, flac, wav, aac, ogg, m4a, opus\n🎬 Видео: mp4, avi, mkv, mov, wmv, flv, webm, 3gp, hevc, h264", - "cut_usage": "Используйте: .cut 20с6мс:8м16с3мс", - "compress_usage": "Доступные качества: 144p, 240p, 360p, 480p, 720p, 1080p, 1440p, 2160p", - "split_time_usage": "Пример: .split 10m (10 минут)", - "split_size_usage": "Используйте: .split 10m или .split 5MB", - "merge_usage": "Ответьте на первое видео/аудио", - "min_files": "Нужно как минимум 2 медиафайла в цепочке", - "downloading": "Скачиваю {} файлов...", - "part": "Часть {}/{}", - } - - async def client_ready(self, client, db): - self._client = client - self._db = db - if not check_ffmpeg(): - self.logger.warning(self.strings["no_ffmpeg"]) - - @loader.command( - ru_doc="<формат> - конвертировать медиа в указанный формат", - en_doc=" - convert media to specified format", - ) - async def convert(self, message: Message): - reply = await message.get_reply_message() - if not reply or not reply.media: - return await utils.answer(message, self.strings["no_reply"]) - - if not check_ffmpeg(): - return await utils.answer(message, self.strings["no_ffmpeg"]) - - args = utils.get_args_raw(message).lower() - formats = { - "mp3": "audio", - "flac": "audio", - "wav": "audio", - "aac": "audio", - "ogg": "audio", - "m4a": "audio", - "opus": "audio", - "mp4": "video", - "avi": "video", - "mkv": "video", - "mov": "video", - "wmv": "video", - "flv": "video", - "webm": "video", - "3gp": "video", - "hevc": "video", - "h264": "video", - } - - if not args or args not in formats: - return await utils.answer(message, self.strings["available_formats"]) - - msg = await utils.answer(message, self.strings["processing"]) - - try: - file = await reply.download_media(file="temp/") - output = f"{file.rsplit('.', 1)[0]}_converted.{args}" - - cmd = ["ffmpeg", "-i", file, "-y"] - if formats[args] == "audio": - if args == "mp3": - cmd.extend(["-codec:a", "libmp3lame", "-q:a", "2"]) - elif args == "flac": - cmd.extend(["-codec:a", "flac", "-compression_level", "12"]) - elif args == "opus": - cmd.extend(["-codec:a", "libopus", "-b:a", "128k"]) - elif args == "aac": - cmd.extend(["-codec:a", "aac", "-b:a", "192k"]) - elif formats[args] == "video": - if args in ["hevc", "h264"]: - codec = "libx265" if args == "hevc" else "libx264" - cmd.extend(["-codec:v", codec, "-preset", "medium", "-crf", "23"]) - if args == "webm": - cmd.extend(["-codec:v", "libvpx-vp9", "-b:v", "1M"]) - - cmd.append(output) - - process = await asyncio.create_subprocess_exec( - *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE - ) - await process.communicate() - - await message.client.send_file( - message.peer_id, - output, - caption=self.strings["converted"].format(args), - reply_to=reply.id, - ) - - os.remove(file) - if os.path.exists(output): - os.remove(output) - - await msg.delete() - - except Exception as e: - await utils.answer(message, self.strings["error"].format(str(e))) - - @loader.command( - ru_doc="Скачать голосовое сообщение как файл", - en_doc="Download voice message as file", - ) - async def voicedl(self, message: Message): - reply = await message.get_reply_message() - if not reply or not reply.voice: - return await utils.answer(message, self.strings["no_reply"]) - - msg = await utils.answer(message, self.strings["processing"]) - - try: - file = await reply.download_media(file="temp/voice.ogg") - new_file = file.replace(".ogg", ".opus") - os.rename(file, new_file) - - await message.client.send_file( - message.peer_id, - new_file, - caption=self.strings["downloaded"], - reply_to=reply.id, - voice_note=False, - ) - - os.remove(new_file) - await msg.delete() - - except Exception as e: - await utils.answer(message, self.strings["error"].format(str(e))) - - @loader.command(ru_doc="Преобразовать видео в GIF", en_doc="Convert video to GIF") - async def gif(self, message: Message): - if not check_ffmpeg(): - return await utils.answer(message, self.strings["no_ffmpeg"]) - - reply = await message.get_reply_message() - if not reply or not reply.video: - return await utils.answer(message, self.strings["no_reply"]) - - msg = await utils.answer(message, self.strings["processing"]) - - try: - file = await reply.download_media(file="temp/") - output = f"{file.rsplit('.', 1)[0]}.gif" - - cmd = [ - "ffmpeg", - "-i", - file, - "-vf", - "fps=10,scale=480:-1:flags=lanczos", - "-gifflags", - "+transdiff", - "-y", - output, - ] - - process = await asyncio.create_subprocess_exec(*cmd) - await process.communicate() - - await message.client.send_file( - message.peer_id, - output, - caption=self.strings["gif_created"], - reply_to=reply.id, - ) - - os.remove(file) - os.remove(output) - await msg.delete() - - except Exception as e: - await utils.answer(message, self.strings["error"].format(str(e))) - - def parse_time(self, time_str: str) -> Optional[float]: - time_str = time_str.lower() - total = 0 - pattern = r"(\d+\.?\d*)([мm]?[сc]|[мm][сc]?)" - matches = re.findall(pattern, time_str) - - for value, unit in matches: - value = float(value) - if "м" in unit or "m" in unit: - total += value * 60 - elif "с" in unit or "c" in unit: - total += value - - return total if total > 0 else None - - @loader.command( - ru_doc="<начало:конец> - обрезать медиа по времени", - en_doc=" - trim media by time", - ) - async def cut(self, message: Message): - if not check_ffmpeg(): - return await utils.answer(message, self.strings["no_ffmpeg"]) - - reply = await message.get_reply_message() - if not reply or not reply.media: - return await utils.answer(message, self.strings["no_reply"]) - - args = utils.get_args_raw(message) - if not args or ":" not in args: - return await utils.answer(message, self.strings["cut_usage"]) - - start_str, end_str = args.split(":", 1) - start = self.parse_time(start_str) - end = self.parse_time(end_str) - - if start is None or end is None or start >= end: - return await utils.answer(message, self.strings["invalid_args"]) - - msg = await utils.answer(message, self.strings["processing"]) - - try: - file = await reply.download_media(file="temp/") - output = f"{file.rsplit('.', 1)[0]}_cut.{file.rsplit('.', 1)[1]}" - - cmd = [ - "ffmpeg", - "-i", - file, - "-ss", - str(start), - "-to", - str(end), - "-c", - "copy", - "-avoid_negative_ts", - "make_zero", - "-y", - output, - ] - - process = await asyncio.create_subprocess_exec(*cmd) - await process.communicate() - - await message.client.send_file( - message.peer_id, - output, - caption=self.strings["cut_done"], - reply_to=reply.id, - ) - - os.remove(file) - os.remove(output) - await msg.delete() - - except Exception as e: - await utils.answer(message, self.strings["error"].format(str(e))) - - @loader.command( - ru_doc="[начало:конец] - Видео в кружок", - en_doc="[start:end] - Convert video to circle", - ) - async def vircle(self, message: Message): - if not check_ffmpeg(): - return await utils.answer(message, self.strings["no_ffmpeg"]) - - reply = await message.get_reply_message() - if not reply or not (reply.video or reply.gif): - return await utils.answer(message, self.strings["no_reply"]) - - args = utils.get_args_raw(message) - filter_args = "" - - if args and ":" in args: - start_str, end_str = args.split(":", 1) - start = self.parse_time(start_str) - end = self.parse_time(end_str) - - if start is not None and end is not None and start < end: - filter_args = f",trim=start={start}:end={end},setpts=PTS-STARTPTS" - - msg = await utils.answer(message, self.strings["processing"]) - - try: - file = await reply.download_media(file="temp/") - output = f"{file.rsplit('.', 1)[0]}_circle.mp4" - - cmd = [ - "ffmpeg", - "-i", - file, - "-vf", - f"scale=720:720:force_original_aspect_ratio=increase,crop=720:720{filter_args},format=rgba,geq='if(gt(X,360),if(gt(Y,360),if(lt(sqrt((X-360)^2+(Y-360)^2),360),p(X,Y),0),0),0)'", - "-c:v", - "libx264", - "-preset", - "fast", - "-crf", - "23", - "-pix_fmt", - "yuv420p", - "-y", - output, - ] - - process = await asyncio.create_subprocess_exec(*cmd) - await process.communicate() - - await message.client.send_file( - message.peer_id, - output, - caption=self.strings["circle_done"], - reply_to=reply.id, - video_note=True, - ) - - os.remove(file) - os.remove(output) - await msg.delete() - - except Exception as e: - await utils.answer(message, self.strings["error"].format(str(e))) - - @loader.command( - ru_doc="[начало:конец] - Извлечь аудио из видео", - en_doc="[start:end] - Extract audio from video", - ) - async def vsound(self, message: Message): - if not check_ffmpeg(): - return await utils.answer(message, self.strings["no_ffmpeg"]) - - reply = await message.get_reply_message() - if not reply or not reply.video: - return await utils.answer(message, self.strings["no_reply"]) - - args = utils.get_args_raw(message) - msg = await utils.answer(message, self.strings["processing"]) - - try: - file = await reply.download_media(file="temp/") - output = f"{file.rsplit('.', 1)[0]}_audio.mp3" - - cmd = ["ffmpeg", "-i", file] - if args and ":" in args: - start_str, end_str = args.split(":", 1) - start = self.parse_time(start_str) - end = self.parse_time(end_str) - - if start is not None and end is not None and start < end: - cmd.extend(["-ss", str(start), "-to", str(end)]) - - cmd.extend(["-q:a", "2", "-map", "a", "-y", output]) - - process = await asyncio.create_subprocess_exec(*cmd) - await process.communicate() - - await message.client.send_file( - message.peer_id, - output, - caption=self.strings["audio_extracted"], - reply_to=reply.id, - ) - - os.remove(file) - os.remove(output) - await msg.delete() - - except Exception as e: - await utils.answer(message, self.strings["error"].format(str(e))) - - @loader.command( - ru_doc="<качество> - Сжать видео", en_doc=" - Compress video" - ) - async def compress(self, message: Message): - if not check_ffmpeg(): - return await utils.answer(message, self.strings["no_ffmpeg"]) - - reply = await message.get_reply_message() - if not reply or not reply.video: - return await utils.answer(message, self.strings["no_reply"]) - - args = utils.get_args_raw(message).lower() - resolutions = { - "144p": "256x144", - "240p": "426x240", - "360p": "640x360", - "480p": "854x480", - "720p": "1280x720", - "1080p": "1920x1080", - "1440p": "2560x1440", - "2160p": "3840x2160", - } - - if not args or args not in resolutions: - return await utils.answer(message, self.strings["compress_usage"]) - - msg = await utils.answer(message, self.strings["processing"]) - - try: - file = await reply.download_media(file="temp/") - output = f"{file.rsplit('.', 1)[0]}_compressed.mp4" - - probe_cmd = [ - "ffprobe", - "-v", - "error", - "-select_streams", - "v:0", - "-show_entries", - "stream=bit_rate", - "-of", - "default=noprint_wrappers=1:nokey=1", - file, - ] - - process = await asyncio.create_subprocess_exec( - *probe_cmd, - stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE, - ) - stdout, _ = await process.communicate() - original_bitrate = stdout.decode().strip() - - scale_factor = { - "144p": 0.1, - "240p": 0.2, - "360p": 0.3, - "480p": 0.4, - "720p": 0.6, - "1080p": 0.8, - "1440p": 0.9, - "2160p": 1.0, - } - - target_bitrate = "500k" - if original_bitrate and original_bitrate.isdigit(): - original_br = int(original_bitrate) - target_br = int(original_br * scale_factor[args] / 1000) - target_bitrate = f"{max(200, target_br)}k" - - cmd = [ - "ffmpeg", - "-i", - file, - "-vf", - f"scale={resolutions[args]}:force_original_aspect_ratio=decrease", - "-c:v", - "libx264", - "-preset", - "medium", - "-b:v", - target_bitrate, - "-maxrate", - target_bitrate, - "-bufsize", - f"{int(target_bitrate[:-1]) * 2}k", - "-c:a", - "aac", - "-b:a", - "128k", - "-y", - output, - ] - - process = await asyncio.create_subprocess_exec(*cmd) - await process.communicate() - - await message.client.send_file( - message.peer_id, - output, - caption=self.strings["compressed"].format(args), - reply_to=reply.id, - ) - - os.remove(file) - os.remove(output) - await msg.delete() - - except Exception as e: - await utils.answer(message, self.strings["error"].format(str(e))) - - @loader.command( - ru_doc="<время/размер> - Разделить медиа на части", - en_doc="