diff --git a/C0dwiz/H.Modules/.github/workflows/generate_index.yml b/C0dwiz/H.Modules/.github/workflows/generate_index.yml deleted file mode 100644 index b065043..0000000 --- a/C0dwiz/H.Modules/.github/workflows/generate_index.yml +++ /dev/null @@ -1,51 +0,0 @@ -name: Generate Index Page - -on: - push: - branches: - - main - -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 < index.html - - - - - - H:Mods - - - -
-

H:Mods modules

- -
- - - EOF - - name: Deploy to GitHub Pages - uses: JamesIves/github-pages-deploy-action@v4 - with: - branch: main - folder: . diff --git a/C0dwiz/H.Modules/.gitignore b/C0dwiz/H.Modules/.gitignore deleted file mode 100644 index cf83b5b..0000000 --- a/C0dwiz/H.Modules/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -full.py - -# Ruff Format -.ruff_cache/ \ No newline at end of file diff --git a/C0dwiz/H.Modules/ASCIIArt.py b/C0dwiz/H.Modules/ASCIIArt.py deleted file mode 100644 index b0b5551..0000000 --- a/C0dwiz/H.Modules/ASCIIArt.py +++ /dev/null @@ -1,133 +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 os -import tempfile - -from PIL import Image -from .. import loader, utils - - -@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: - print(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/C0dwiz/H.Modules/AccountData.py b/C0dwiz/H.Modules/AccountData.py deleted file mode 100644 index 2e82bc4..0000000 --- a/C0dwiz/H.Modules/AccountData.py +++ /dev/null @@ -1,65 +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 -# --------------------------------------------------------------------------------- - -from .. import loader, utils - -@loader.tds -class AccountData(loader.Module): - """Find out the approximate date of registration of the telegram account""" - - strings = { - "name": "AccountData", - "date_text": "⏰️ Date of registration of this account: {data}", - "date_text_ps": " The registration date is approximate, as it is almost impossible to know for sure", - "no_reply": "💬 You did not reply to the user's message", - } - - strings_ru = { - "date_text": "⏰️ Дата регистрации этого аккаунта: {data}", - "date_text_ps": " Дата регистрации примерная, так как точно узнать практически невозможно", - "no_reply": "💬 Вы не ответили на сообщение пользователя", - } - - async def client_ready(self, client, db): - self.hmodslib = await self.import_lib( - "https://raw.githubusercontent.com/C0dwiz/H.Modules/refs/heads/main-fix/HModsLibrary.py" - ) - - @loader.command( - ru_doc="Узнать примерную дату регистрации аккаунта телеграмм", - 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(): - data = await self.hmodslib.get_creation_date(reply.from_id) - await utils.answer( - message, - f"{self.strings('date_text').format(data=data)}\n\n{self.strings('date_text_ps')}", - ) - else: - await utils.answer(message, self.strings("no_reply")) diff --git a/C0dwiz/H.Modules/AniLibria.py b/C0dwiz/H.Modules/AniLibria.py deleted file mode 100644 index 7c4c339..0000000 --- a/C0dwiz/H.Modules/AniLibria.py +++ /dev/null @@ -1,188 +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: AniLibria -# Description: Searches and gives random agtme on the AniLibria database. -# Author: @hikka_mods -# --------------------------------------------------------------------------------- -# meta developer: @hikka_mods -# scope: AniLibria -# scope: AniLibria 0.0.1 -# requires: git+https://github.com/C0dwiz/anilibria.py.git -# --------------------------------------------------------------------------------- - -from ..inline.types import InlineQuery -from aiogram.types import InlineQueryResultPhoto, CallbackQuery -from anilibria import AniLibriaClient - -from .. import loader - -ani_client = AniLibriaClient() - - -@loader.tds -class AniLibriaMod(loader.Module): - """Searches and gives random agtme on the AniLibria database.""" - - strings = { - "name": "AniLibria", - "announce": "The announcement:", - "status": "Status:", - "type": "Type:", - "genres": "Genres:", - "favorite": "Favourites <3:", # < == < - "season": "Season:", - } - - strings_ru = { - "announce": "Анонс:", - "status": "Статус:", - "type": "Тип:", - "genres": "Жанры:", - "favorite": "Избранное <3:", # < == < - "season": "Сезон:", - } - - link = "https://anilibria.tv" - - - @loader.command( - ru_doc="Возвращает случайный тайтл из базы", - en_doc="Returns a random title from the database", - ) - async def arandom(self, message) -> None: - anime_title = await ani_client.get_random_title() - - text = f"{anime_title.names.ru} \n" - text += f"{self.strings['status']} {anime_title.status.string}\n\n" - text += f"{self.strings['type']} {anime_title.type.full_string}\n" - text += f"{self.strings['season']} {anime_title.season.string}\n" - text += f"{self.strings['genres']} {' '.join(anime_title.genres)}\n\n" - - text += f"{anime_title.description}\n\n" - text += f"{self.strings['favorite']} {anime_title.in_favorites}" - - kb = [ - [ - { - "text": "Ссылка", - "url": f"https://anilibria.tv/release/{anime_title.code}.html", - } - ] - ] - - kb.extend( - [ - { - "text": f"{torrent.quality.string}", - "url": f"https://anilibria.tv/{torrent.url}", - } - ] - for torrent in anime_title.torrents.list - ) - kb.append([{"text": "🔃 Обновить", "callback": self.inline__update}]) - kb.append([{"text": "🚫 Закрыть", "callback": self.inline__close}]) - - await self.inline.form( - text=text, - photo=self.link + anime_title.posters.original.url, - message=message, - reply_markup=kb, - silent=True, - ) - - @loader.inline_handler( - ru_doc="Возвращает список найденных по названию тайтлов", - en_doc="Returns a list of titles found by name", - ) - async def asearch_inline_handler(self, query: InlineQuery) -> None: - text = query.args - - if not text: - return - - anime_titles = await ani_client.search_titles(search=text) - - inline_query = [] - for anime_title in anime_titles: - title_text = ( - f"{anime_title.names.ru} | {anime_title.names.en}\n" - f"{self.strings['status']} {anime_title.status.string}\n\n" - f"{self.strings['type']} {anime_title.type.full_string}\n" - f"{self.strings['season']} {anime_title.season.string} {anime_title.season.year}\n" - f"{self.strings['genres']} {' '.join(anime_title.genres)}\n\n" - f"{anime_title.description}\n\n" - f"{self.strings['favorite']} {anime_title.in_favorites}" - ) - - inline_query.append( - InlineQueryResultPhoto( - id=str(anime_title.code), - title=anime_title.names.ru, - description=anime_title.type.full_string, - caption=title_text, - thumb_url=self.link + anime_title.posters.small.url, - photo_url=self.link + anime_title.posters.original.url, - parse_mode="html", - ) - ) - await query.answer(inline_query, cache_time=0) - - async def inline__close(self, call: CallbackQuery) -> None: - await call.delete() - - async def inline__update(self, call: CallbackQuery) -> None: - anime_title = await ani_client.get_random_title() - - text = ( - f"{anime_title.names.ru} \n" - f"{self.strings['status']} {anime_title.status.string}\n\n" - f"{self.strings['type']} {anime_title.type.full_string}\n" - f"{self.strings['season']} {anime_title.season.string}\n" - f"{self.strings['genres']} {' '.join(anime_title.genres)}\n\n" - f"{anime_title.description}\n\n" - f"{self.strings['favorite']} {anime_title.in_favorites}" - ) - - kb = [ - [ - { - "text": "Ссылка", - "url": f"https://anilibria.tv/release/{anime_title.code}.html", - } - ] - ] - - kb.extend( - [ - { - "text": f"{torrent.quality.string}", - "url": f"https://anilibria.tv/{torrent.url}", - } - ] - for torrent in anime_title.torrents.list - ) - kb.append([{"text": "🔃 Обновить", "callback": self.inline__update}]) - kb.append([{"text": "🚫 Закрыть", "callback": self.inline__close}]) - - await call.edit( - text=text, - photo=self.link + anime_title.posters.original.url, - reply_markup=kb, - ) diff --git a/C0dwiz/H.Modules/AnimeQuotes.py b/C0dwiz/H.Modules/AnimeQuotes.py deleted file mode 100644 index 0908122..0000000 --- a/C0dwiz/H.Modules/AnimeQuotes.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: 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 aiohttp - -from .. import loader, utils - - -@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": "Не удалось получить цитату. Попробуйте позже!", - } - - @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: - async with aiohttp.ClientSession() as 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/C0dwiz/H.Modules/Article.py b/C0dwiz/H.Modules/Article.py deleted file mode 100644 index 341d0c9..0000000 --- a/C0dwiz/H.Modules/Article.py +++ /dev/null @@ -1,73 +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 requests -import json -import random -from typing import Dict - -from .. import loader, utils - - -@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/C0dwiz/H.Modules/AutofarmCookies.py b/C0dwiz/H.Modules/AutofarmCookies.py deleted file mode 100644 index 840d6cb..0000000 --- a/C0dwiz/H.Modules/AutofarmCookies.py +++ /dev/null @@ -1,204 +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 random - -from datetime import timedelta -from telethon import functions - -from .. import loader, utils - -__version__ = (1, 0, 0) - - -@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 - Выключает авто фарм. - .farm - Показывает сколько вы нафармили. - .me - Показывает ваш ммешок""" - await utils.answer(message, chelp) diff --git a/C0dwiz/H.Modules/BirthdayTime.py b/C0dwiz/H.Modules/BirthdayTime.py deleted file mode 100644 index 30deae7..0000000 --- a/C0dwiz/H.Modules/BirthdayTime.py +++ /dev/null @@ -1,242 +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 random -import asyncio -import calendar -from datetime import datetime - -from telethon.tl.functions.users import GetFullUserRequest -from telethon.tl.functions.account import UpdateProfileRequest -from telethon.errors.rpcerrorlist import UserPrivacyRestrictedError - -from .. import loader, utils - -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) - print("Error: Can't change name due to privacy settings.") - except Exception as e: - print(f"Error in checker: {e}") - finally: - await asyncio.sleep(60) - - @loader.command( - ru_doc="Выставить таймер дней в ник (нестабильно)", - en_doc="Set the timer of days in the nickname (unstable)", - ) - async def btname(self, message): - try: - user = await self.client(GetFullUserRequest(self.client.hikka_me.id)) - name = user.users[0].last_name or "" - except Exception as e: - print(f"Error getting user info: {e}") - await utils.answer(message, self.strings("error")) - return - - self.db.set(__name__, "last_name", name) - change_name = self.db.get(__name__, "change_name", False) - - if change_name: - 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: - print(f"Error removing name: {e}") - await utils.answer(message, self.strings("error")) - - else: - self.db.set(__name__, "change_name", True) - await utils.answer(message, self.strings("btname_yes")) - - @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: - print(f"Error in bt command: {e}") - await utils.answer(message, self.strings("error")) diff --git a/C0dwiz/H.Modules/CNAME b/C0dwiz/H.Modules/CNAME deleted file mode 100644 index 6277f4e..0000000 --- a/C0dwiz/H.Modules/CNAME +++ /dev/null @@ -1 +0,0 @@ -mods.codwiz.life \ No newline at end of file diff --git a/C0dwiz/H.Modules/CheckSpamBan.py b/C0dwiz/H.Modules/CheckSpamBan.py deleted file mode 100644 index c78c23e..0000000 --- a/C0dwiz/H.Modules/CheckSpamBan.py +++ /dev/null @@ -1,53 +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/C0dwiz/H.Modules/CryptoCurrency.py b/C0dwiz/H.Modules/CryptoCurrency.py deleted file mode 100644 index 3fbcf84..0000000 --- a/C0dwiz/H.Modules/CryptoCurrency.py +++ /dev/null @@ -1,107 +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 aiohttp - -from .. import loader, utils - - -@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}' не найдена.", - } - - async def fetch_json(self, url): - """Fetch JSON data from a given URL.""" - async with aiohttp.ClientSession() as 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/C0dwiz/H.Modules/EnvsSH.py b/C0dwiz/H.Modules/EnvsSH.py deleted file mode 100644 index 4f055d8..0000000 --- a/C0dwiz/H.Modules/EnvsSH.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: EnvsSH -# Description: Module for reuploading files to envs.sh -# Author: @hikka_mods -# --------------------------------------------------------------------------------- -# meta developer: @hikka_mods -# scope: Api EnvsSH -# scope: Api EnvsSH 0.0.1 -# requires: aiohttp -# --------------------------------------------------------------------------------- - -import aiohttp - -from .. import loader, utils # pylint: disable=relative-beyond-top-level - - -@loader.tds -class EnvsMod(loader.Module): - """Module for reuploading files to envs.sh""" - - strings = { - "name": "EnvsSH", - "connection_error": "🚫 Host is unreachable for now, try again later.", - "no_reply": "⚠️ You must reply to a message with media", - "success": "✅ URL for {}:\n\n{}", - "error": "❌ An error occurred:\n{}", - "uploading": "⏳ Uploading {} ({}{})...", - } - - strings_ru = { - "connection_error": "🚫 Хост в настоящее время недоступен, попробуйте позже.", - "no_reply": "⚠️ Вы должны ответить на сообщение с медиа", - "success": "✅ URL для {}:\n\n{}", - "error": "❌ Произошла ошибка:\n{}", - "uploading": "⏳ Загрузка {} ({}{})...", - } - - async def client_ready(self, client, db): - self.hmodslib = await self.import_lib('https://raw.githubusercontent.com/C0dwiz/H.Modules/refs/heads/main-fix/HModsLibrary.py') - - async def envcmd(self, message): - """Reupload to envs.sh.""" - reply = await message.get_reply_message() - if not reply or not reply.media: - return await utils.answer(message, self.strings["no_reply"]) - - size_len, size_unit = self.hmodslib.convert_size(reply.file.size) - await utils.answer( - message, - self.strings["uploading"].format(reply.file.name, size_len, size_unit), - ) - - path = await self.client.download_media(reply) - try: - uploaded_url = await self.hmodslib.upload_to_envs(path) - except aiohttp.ClientConnectionError: - await utils.answer(message, self.strings["connection_error"]) - except aiohttp.ClientResponseError as e: - await utils.answer(message, self.strings["error"].format(str(e))) - else: - await utils.answer( - message, self.strings["success"].format(path, uploaded_url) - ) diff --git a/C0dwiz/H.Modules/FakeActions.py b/C0dwiz/H.Modules/FakeActions.py deleted file mode 100644 index 99b687c..0000000 --- a/C0dwiz/H.Modules/FakeActions.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: 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 - -from .. import loader, utils - - -@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/C0dwiz/H.Modules/FakeWallet.py b/C0dwiz/H.Modules/FakeWallet.py deleted file mode 100644 index 0920731..0000000 --- a/C0dwiz/H.Modules/FakeWallet.py +++ /dev/null @@ -1,176 +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 -# ----------------------------------------------------------------------------------- - -from .. import loader, utils - - -@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/C0dwiz/H.Modules/GigaChat.py b/C0dwiz/H.Modules/GigaChat.py deleted file mode 100644 index 0b051ce..0000000 --- a/C0dwiz/H.Modules/GigaChat.py +++ /dev/null @@ -1,121 +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 -# --------------------------------------------------------------------------------- - -from .. import loader, utils - - -@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://raw.githubusercontent.com/C0dwiz/H.Modules/refs/heads/main-fix/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/C0dwiz/H.Modules/H.py b/C0dwiz/H.Modules/H.py deleted file mode 100644 index 55975f2..0000000 --- a/C0dwiz/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")) \ No newline at end of file diff --git a/C0dwiz/H.Modules/HAFK.py b/C0dwiz/H.Modules/HAFK.py deleted file mode 100644 index 3f0f94c..0000000 --- a/C0dwiz/H.Modules/HAFK.py +++ /dev/null @@ -1,307 +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 datetime -import logging -import time -import asyncio - -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) - db_key = "afk" if global_afk else f"afk_here_{chat_id}" - 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/C0dwiz/H.Modules/HModsLibrary.py b/C0dwiz/H.Modules/HModsLibrary.py deleted file mode 100644 index 7274f0b..0000000 --- a/C0dwiz/H.Modules/HModsLibrary.py +++ /dev/null @@ -1,335 +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: HModsLibrary -# Description: Library required for most H:Mods modules. -# Author: @hikka_mods -# --------------------------------------------------------------------------------- -# meta developer: @hikka_mods -# scope: HModsLibrary -# scope: HModsLibrary 0.0.1 -# --------------------------------------------------------------------------------- - -import logging -import re -import aiohttp -import random -import asyncio -import os - -from bs4 import BeautifulSoup -from gigachat import GigaChat -from typing import Optional, Dict, Any -from .. import loader, utils - -logger = logging.getLogger(__name__) -__version__ = (0, 0, 2) - - -class HModsLib(loader.Library): - """Library required for most H:Mods modules.""" - - developer = "@hikka_mods" - version = __version__ - - async def parse_time(self, time_str): - time_units = {"d": 86400, "h": 3600, "m": 60, "s": 1} - if not re.fullmatch(r"(\d+[dhms])+", time_str): - return None - seconds = 0 - matches = re.findall(r"(\d+)([dhms])", time_str) - for amount, unit in matches: - seconds += int(amount) * time_units[unit] - return seconds if seconds > 0 else None - - @staticmethod - def convert_size(size): - """Convert file size to human-readable format.""" - power = 2**10 - n = 0 - units = {0: "B", 1: "KB", 2: "MB", 3: "GB", 4: "TB"} - while size > power: - size /= power - n += 1 - return round(size, 2), units[n] - - async def upload_to_envs(self, path): - """Upload file to envs.sh and return the URL.""" - url = "https://envs.sh" - async with aiohttp.ClientSession() as session: - async with session.post(url, data={"file": open(path, "rb")}) as response: - if response.status != 200: - os.remove(path) - raise aiohttp.ClientResponseError( - request_info=response.request_info, - history=response.history, - status=response.status, - message=await response.text(), - headers=response.headers, - ) - result = await response.text() - - os.remove(path) - return result - - async def get_creation_date(tg_id: int) -> str: - url = "https://restore-access.indream.app/regdate" - headers = { - "accept": "*/*", - "content-type": "application/x-www-form-urlencoded", - "user-agent": "Nicegram/92 CFNetwork/1390 Darwin/22.0.0", - "x-api-key": "e758fb28-79be-4d1c-af6b-066633ded128", - "accept-language": "en-US,en;q=0.9", - } - data = {"telegramId": tg_id} - - async with aiohttp.ClientSession() as session: - async with session.post(url, headers=headers, json=data) as response: - if response.status == 200: - json_response = await response.json() - return json_response["data"]["date"] - else: - return "Ошибка получения данных" - - async def get_giga_response(self, api_key, query): - """Gets a response from GigaChat with the specified query.""" - async with GigaChat( - credentials=api_key, - scope="GIGACHAT_API_PERS", - model=self.config["GIGACHAT_MODEL"], - verify_ssl_certs=False, - ) as giga: - response = giga.chat(query) - if response.choices: - return response.choices[0].message.content - return None - - async def get_giga_models(self, api_key): - """Gets a response from GigaChat with the specified query.""" - async with GigaChat( - credentials=api_key, scope="GIGACHAT_API_PERS", verify_ssl_certs=False - ) as giga: - response = giga.get_models() - if response: - return ( - [model.id_ for model in response.data] - if hasattr(response, "data") - else [] - ) - return None - - async def get_random_image(): - random_site = random.randint(1, 3389) - url = f"https://www.memify.ru/memes/{random_site}" - - async with aiohttp.ClientSession() as session: - async with session.get(url) as response: - content = await response.text() - soup = BeautifulSoup(content, "html.parser") - items = soup.find_all("div", {"class": "infinite-item card"}) - random_item = random.choice(items) - second_a = random_item.find_all("a")[1] - img = second_a.get("href") - - return img - - async def virustotal_request( - self, - session: aiohttp.ClientSession, - url: str, - headers: Dict[str, str], - method: str = "GET", - data: Optional[Dict[str, Any]] = None, - files: Optional[Dict[str, Any]] = None, - ) -> Optional[Dict[str, Any]]: - """ - Generic function to make requests to the VirusTotal API. - """ - try: - if files: - form = aiohttp.FormData() - for k, v in files.items(): - form.add_field(k, v) - - async with session.request( - method, url, headers=headers, data=form - ) as response: - logger.debug(f"Response status: {response.status}") - logger.debug(f"Response body: {await response.text()}") - - if response.status == 200: - return await response.json() - else: - logger.error( - f"VirusTotal API request failed with status: {response.status}, reason: {response.reason}" - ) - return None - else: - async with session.request( - method, url, headers=headers, json=data - ) as response: - logger.debug(f"Response status: {response.status}") - logger.debug(f"Response body: {await response.text()}") - - if response.status == 200: - return await response.json() - else: - logger.error( - f"VirusTotal API request failed with status: {response.status}, reason: {response.reason}" - ) - return None - - except aiohttp.ClientError as e: - logger.exception(f"AIOHTTP Client error: {e}") - return None - except Exception as e: - logger.exception(f"An unexpected error occurred: {e}") - return None - - async def get_upload_url(self, api_key: str) -> Optional[str]: - """ - Retrieves a special upload URL for large files. - """ - headers = {"x-apikey": api_key, "accept": "application/json"} - url = "https://www.virustotal.com/api/v3/files/upload_url" - - async with aiohttp.ClientSession() as session: - response = await self.virustotal_request(session, url, headers) - - if response and "data" in response and isinstance(response["data"], str): - return response["data"] - else: - logger.error(f"Failed to retrieve upload URL: {response}") - return None - - async def scan_file_virustotal( - self, file_path: str, api_key: str, is_large_file: bool = False - ) -> Optional[Dict[str, Any]]: - """ - Uploads a file to VirusTotal and retrieves the analysis results. Handles files larger than 32MB. - """ - headers = {"x-apikey": api_key} - url = "https://www.virustotal.com/api/v3/files" - - async with aiohttp.ClientSession() as session: - try: - with open(file_path, "rb") as file: - files = {"file": file} - if is_large_file: - upload_url = await self.get_upload_url(api_key) - if not upload_url: - logger.error("Failed to get upload URL for large file.") - return None - url = upload_url - - upload_response = await self.virustotal_request( - session, url, headers, method="POST", files=files - ) - - if ( - upload_response - and "data" in upload_response - and "id" in upload_response["data"] - ): - analysis_id = upload_response["data"]["id"] - - analysis_url = ( - f"https://www.virustotal.com/api/v3/analyses/{analysis_id}" - ) - for attempt in range(20): - analysis_response = await self.virustotal_request( - session, analysis_url, headers - ) - logger.debug( - f"Analysis response (attempt {attempt + 1}): {analysis_response}" - ) - if ( - analysis_response - and "data" in analysis_response - and "attributes" in analysis_response["data"] - and analysis_response["data"]["attributes"].get( - "status" - ) - == "completed" - ): - return analysis_response - await asyncio.sleep(10) - - logger.warning( - f"Analysis not completed after multiple retries for ID: {analysis_id}" - ) - return None - - else: - logger.error( - f"File upload or analysis request failed: {upload_response}" - ) - return None - - except FileNotFoundError: - logger.error(f"File not found: {file_path}") - return None - except Exception as e: - logger.exception(f"An error occurred during file scanning: {e}") - return None - - def format_analysis_results(self, analysis_results: Dict[str, Any]) -> str: - """ - Formats the analysis results into a user-friendly message, including specific detections. - """ - if ( - not analysis_results - or "data" not in analysis_results - or "attributes" not in analysis_results["data"] - or "stats" not in analysis_results["data"]["attributes"] - or "results" not in analysis_results["data"]["attributes"] - ): - logger.warning( - f"Unexpected structure in analysis_results: {analysis_results}" - ) - return self.strings("error") - - stats = analysis_results["data"]["attributes"]["stats"] - harmless = stats.get("harmless", 0) - malicious = stats.get("malicious", 0) - suspicious = stats.get("suspicious", 0) - undetected = stats.get("undetected", 0) - total_scans = harmless + malicious + suspicious + undetected - analysis_id = analysis_results["data"]["id"] - url = f"https://www.virustotal.com/gui/file-analysis/{analysis_id}" - - text = ( - f"📊 VirusTotal Scan Results\n\n" - f"🦠 Detections: {malicious} / {total_scans}\n" - f"🟢 Harmless: {harmless}\n" - f"⚠️ Suspicious: {suspicious}\n" - f"❓ Undetected: {undetected}\n\n" - ) - - if malicious > 0: - text += "⚠️ Detections by engine:\n" - results = analysis_results["data"]["attributes"]["results"] - for engine, result in results.items(): - if result["category"] == "malicious": - engine_name = engine.replace("_", " ").title() - text += f" • {engine_name}: {result['result']}\n" - - text += "\n" - - return {"text": text, "url": url} diff --git a/C0dwiz/H.Modules/InlineButton.py b/C0dwiz/H.Modules/InlineButton.py deleted file mode 100644 index 18eda89..0000000 --- a/C0dwiz/H.Modules/InlineButton.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: InlineButton -# Description: Create inline button -# Author: @hikka_mods -# --------------------------------------------------------------------------------- -# meta developer: @hikka_mods -# scope: InlineButton -# scope: InlineButton 0.0.1 -# --------------------------------------------------------------------------------- - -from ..inline.types import InlineQuery - -from .. import loader, utils - - -@loader.tds -class InlineButtonMod(loader.Module): - """Create inline button""" - - strings = { - "name": "InlineButton", - "titles": "Create a message with the Inline Button", - "error_title": "Error", - "error_description": "Invalid input format. Please provide exactly three comma-separated values.", - "error_message": "Make sure your input is formatted as: message, name, url.", - } - - strings_ru = { - "titles": "Создай сообщение с Inline Кнопкой", - "error_title": "Ошибка", - "error_description": "Неверный формат ввода. Пожалуйста, укажите ровно три значения, разделенных запятыми.", - "error_message": "Убедитесь, что ваш ввод имеет следующий формат: сообщение, имя, 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 args: - args_list = [arg.strip() for arg in args.split(",")] - - if len(args_list) == 3: - message, name, url = args_list - - return { - "title": self.strings("titles"), - "description": f"{message}, {name}, {url}", - "message": message, - "reply_markup": [{ - "text": name, - "url": url - }] - } - - return { - "title": self.strings("error_title"), - "description": self.strings("error_description"), - "message": self.strings("error_message"), - } \ No newline at end of file diff --git a/C0dwiz/H.Modules/InlineCoin.py b/C0dwiz/H.Modules/InlineCoin.py deleted file mode 100644 index 6b23bb8..0000000 --- a/C0dwiz/H.Modules/InlineCoin.py +++ /dev/null @@ -1,74 +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 random - -from ..inline.types import InlineQuery -from .. import loader - - -@loader.tds -class CoinSexMod(loader.Module): - """Mini game heads or tails""" - - 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 the edge!", - } - - strings_ru = { - "titles": "Орёл или решка?", - "description": "Давай узнаем!", - "heads": "🌚 Выпал орёл!", - "tails": "🌝 Выпала решка!", - "edge": "🙀 Чудо, монетка осталась на ребре!", - } - - def get_coin_flip_result(self) -> dict: - results = [self.strings("heads"), self.strings("tails")] - if random.random() < 0.1: - return self.strings("edge") - else: - return random.choice(results) - - @loader.command( - ru_doc="Подбросит монетку ", - en_doc="Flip a coin", - ) - async def coin_inline_handler(self, query: InlineQuery): - result = self.get_coin_flip_result() - return { - "title": self.strings("titles"), - "description": self.strings("description"), - "message": f"{result}", - "thumb": "https://github.com/Codwizer/ReModules/blob/main/assets/images.png", - } diff --git a/C0dwiz/H.Modules/InlineHelper.py b/C0dwiz/H.Modules/InlineHelper.py deleted file mode 100644 index a586976..0000000 --- a/C0dwiz/H.Modules/InlineHelper.py +++ /dev/null @@ -1,213 +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 sys -import os -import asyncio -import logging - -from ..inline.types import InlineQuery - -from .. import loader, utils, main - - -@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": "Successfully reset prefix to default", - "restart_inline_handler_title": "Restart Userbot", - "restart_inline_handler_description": "Restart your userbot via inline", - "restart_inline_handler_message": "Press the button below to restart your userbot", - "restart_inline_handler_reply_text": "Restart", - "update_inline_handler_title": "Update Userbot", - "update_inline_handler_description": "Update your userbot via inline", - "update_inline_handler_message": "Press the button below to update your userbot", - "update_inline_handler_reply_text": "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": "☘️ Installed modules:\n", - "resetprefix_inline_handler_title": "Reset Prefix", - "resetprefix_inline_handler_description": "Reset your prefix back to default", - "resetprefix_inline_handler_message": "Are you sure you want to reset your prefix to default dot?", - "resetprefix_inline_handler_reply_text_yes": "Yes", - "resetprefix_inline_handler_reply_text_no": "No", - } - - strings_ru = { - "call_restart": "Перезагружаю...", - "call_update": "Обновляю...", - "res_prefix": "Префикс успешно сброшен по умолчанию", - "restart_inline_handler_title": "Перезагрузить юзербота", - "restart_inline_handler_description": "Перезагрузить юзербота через инлайн", - "restart_inline_handler_message": "Нажмите на кнопку ниже для рестарта юзербота", - "restart_inline_handler_reply_text": "Перезапуск", - "update_inline_handler_title": "Обновить юзербота", - "update_inline_handler_description": "Обновить юзербота через инлайн", - "update_inline_handler_message": "Нажмите на кнопку ниже для обновления юзербота", - "update_inline_handler_reply_text": "Обновить", - "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", - "resetprefix_inline_handler_title": "Сбросить префикс", - "resetprefix_inline_handler_description": "Сбросить префикс по умолчанию", - "resetprefix_inline_handler_message": "Вы действительно хотите сбросить ваш префикс и установить стандартную точку?", - "resetprefix_inline_handler_reply_text_yes": "Да", - "resetprefix_inline_handler_reply_text_no": "Нет", - } - - async def client_ready(self, client, db): - self.client = client - self.db = db - - async def restart(self, call): - """Restart callback""" - logging.error("InlineHelper: restarting userbot...") - await call.edit(self.strings("call_restart")) - await sys.exit(0) - - async def update(self, call): - """Update callback""" - logging.error("InlineHelper: updating userbot...") - os.system(f"cd {utils.get_base_dir()} && cd .. && git reset --hard HEAD") - os.system("git pull") - await call.edit(self.strings("call_update")) - await sys.exit(0) - - async def reset_prefix(self, call): - """Reset prefix""" - self.db.set(main.__name__, "command_prefix", ".") - await call.edit(self.strings("res_prefix")) - - @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, _: InlineQuery): - text = _.args - - await asyncio.create_subprocess_shell( - f"{text}", - stdin=asyncio.subprocess.PIPE, - stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE, - cwd=utils.get_base_dir(), - ) - - return { - "title": self.strings("terminal_inline_handler_title"), - "description": self.strings("terminal_inline_handler_description"), - "message": self.strings("terminal_inline_handler_message").format( - text=text - ), - } - - @loader.inline_handler( - ru_doc="Вывести список установленных модулей через инлайн", - en_doc="Display a list of installed modules via the inline", - ) - async def modules_inline_handler(self, _: InlineQuery): - result = self.strings("modules_inline_handler_result") - - for mod in self.allmodules.modules: - try: - name = mod.strings["name"] - except KeyError: - name = mod.__clas__.__name__ - result += f"• {name}\n" - - 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/C0dwiz/H.Modules/IrisSimpleMod.py b/C0dwiz/H.Modules/IrisSimpleMod.py deleted file mode 100644 index a7f1cf7..0000000 --- a/C0dwiz/H.Modules/IrisSimpleMod.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: IrisSimpleMod -# Description: Module for basic interaction with Iris. -# Author: @hikka_mods -# --------------------------------------------------------------------------------- -# meta developer: @hikka_mods -# scope: IrisSimpleMod -# scope: IrisSimpleMod 1.0.1 -# --------------------------------------------------------------------------------- - -from .. import loader, utils - -__version__ = (1, 0, 1) - -@loader.tds -class IrisSimpleMod(loader.Module): - """Модуль для базового взаимодействия с Ирисом""" - strings = {"name": "IrisSimpleMod"} - - - @loader.command( - ru_doc="Проверить мешок" - ) - async def bag(self, message): - """Check bag""" - async with self.client.conversation(5443619563) as conv: - usermessage = await conv.send_message('мешок') - await usermessage.delete() - bagmessage = await conv.get_response() - await utils.answer(message, 'Ваш мешок:\n' + bagmessage.text) - await bagmessage.delete() - - - @loader.command( - ru_doc="Зафармить ирис-коины" - ) - async def farm(self, message): - """Farm iris-coins""" - async with self.client.conversation(5443619563) as conv: - usermessage = await conv.send_message('ферма') - await usermessage.delete() - farmmessage = await conv.get_response() - await utils.answer(message, farmmessage.text) - await farmmessage.delete() - - @loader.command( - ru_doc="Вывести анкету" - ) - async def irisstats(self, message): - """Display user stats""" - async with self.client.conversation(5443619563) as conv: - usermessage = await conv.send_message('анкета') - await usermessage.delete() - statsmessage = await conv.get_response() - await utils.answer(message, statsmessage.text) - await statsmessage.delete() - - @loader.command( - ru_doc="Вывести статистику ботов" - ) - async def irisping(self, message): - """Display bot stats""" - async with self.client.conversation(5443619563) as conv: - usermessage = await conv.send_message('🌺 Семейство ирисовых') - await usermessage.delete() - pingmessage = await conv.get_response() - await utils.answer(message, pingmessage.text) - await pingmessage.delete() - - diff --git a/C0dwiz/H.Modules/KBSwapper.py b/C0dwiz/H.Modules/KBSwapper.py deleted file mode 100644 index 4a94195..0000000 --- a/C0dwiz/H.Modules/KBSwapper.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: 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 string - -from .. import loader, utils - - -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: - await utils.answer(message, self.strings("no_text")) - return - - try: - first_char = original_text[0].lower() - if first_char in string.ascii_lowercase: - fixed_text = original_text.translate(EN_TO_RU) - elif first_char in "йцукенгшщзхъфывапролджэячсмитьбю.ё": - fixed_text = original_text.translate(RU_TO_EN) - else: - fixed_text = original_text - - 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: - print(f"Error during swap: {e}") - await utils.answer(message, self.strings("error")) diff --git a/C0dwiz/H.Modules/LICENSE b/C0dwiz/H.Modules/LICENSE deleted file mode 100644 index 4c6e283..0000000 --- a/C0dwiz/H.Modules/LICENSE +++ /dev/null @@ -1,20 +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. - -Sergei -08.10.2024 \ No newline at end of file diff --git a/C0dwiz/H.Modules/Memes.py b/C0dwiz/H.Modules/Memes.py deleted file mode 100644 index 031cbfe..0000000 --- a/C0dwiz/H.Modules/Memes.py +++ /dev/null @@ -1,107 +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 -# --------------------------------------------------------------------------------- - -from bs4 import BeautifulSoup -import aiohttp -import random - -from .. import loader - - -@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://raw.githubusercontent.com/C0dwiz/H.Modules/refs/heads/main-fix/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/C0dwiz/H.Modules/MooFarmRC1.py b/C0dwiz/H.Modules/MooFarmRC1.py deleted file mode 100644 index acd0e05..0000000 --- a/C0dwiz/H.Modules/MooFarmRC1.py +++ /dev/null @@ -1,1501 +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 -# --------------------------------------------------------------------------------- - -__version__ = (0, 1, 4, 10) - -import os -import re -import typing -import asyncio -import base64 -import aioredis -from typing import Optional -from telethon.tl.types import Message -from telethon.tl.types import InputDocument -from telethon.tl.types import User -from telethon import events -from .. import loader, utils -from ..inline.types import InlineCall -import json - -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 и ссылку добавьте в конфиг - - """ - # Todo: Автокрафт и Автолес готовы на 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.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 as e: - await call.answer("❌ Ошибка валидации") - return - - await redraw_callback(call) - - async def syncskin_inline(self, call: InlineCall): - await call.answer("🔄 Синхронизация началась...") - - chat_id = self.get_chat_id - bot_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 - user_id = self.tg_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" - f"Данные: {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" - f"Ключ: {key}\n" - f"Данные: {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: - """ - chat_id = 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/C0dwiz/H.Modules/Music.py b/C0dwiz/H.Modules/Music.py deleted file mode 100644 index f38ab88..0000000 --- a/C0dwiz/H.Modules/Music.py +++ /dev/null @@ -1,202 +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: {}.", - "invalid_service": "🚫 Invalid service. (yandex, vk)", - "usage": "Usage: .music [yandex|vk] [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": "😫 Трек не найден: {}.", - "invalid_service": "🚫 Неверный сервис. (yandex, vk)", - "usage": "Использование: .music [yandex|vk] [название трека]", - "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" - self.vk_bot = "@vkmusic_bot" - - @loader.command( - ru_doc="Найти трек в Yandex Music или VK: `.music yandex {название}` или `.music vk {название}`", - en_doc="Find a track in Yandex Music or VK: `.music yandex {name}` or `.music vk {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 - - service, query = args[0].lower(), " ".join(args[1:]) - - if service == "yandex": - await self._yafind(message, query) - elif service == "vk": - await self._vkfind(message, query) - else: - await utils.answer(message, self.strings("invalid_service", message)) - - 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)) - - async def _vkfind(self, message, query: str): - if not query: - return await utils.answer(message, self.strings("no_query", message)) - - await utils.answer(message, self.strings("searching", message)) - - try: - music = await message.client.inline_query(self.vk_bot, query) - - if not music or len(music) <= 1: - return await utils.answer( - message, self.strings("not_found", message).format(query) - ) - - for i in range(1, len(music), 2): - try: - result = music[i].result - if hasattr(result, "audio") and result.audio: - await message.client.send_file( - message.to_id, - result.audio, - caption=self.strings("found", message), - reply_to=utils.get_topic(message) - if message.reply_to_msg_id - else None, - ) - await message.delete() - return - if hasattr(result, "document") and result.document: - await message.client.send_file( - message.to_id, - result.document, - caption=self.strings("found", message), - reply_to=utils.get_topic(message) - if message.reply_to_msg_id - else None, - ) - await message.delete() - return - - logger.warning(f"No audio/document in result {i}") - await utils.answer(message, self.strings("no_audio", message)) - await message.delete() - return - - except MessageNotModifiedError: - logger.warning("MessageNotModifiedError, skipping.") - except Exception as e: - logger.error(f"Send error: {e}") - - await utils.answer( - message, self.strings("not_found", message).format(query) - ) - - except BotMethodInvalidError as e: - logger.error(f"VK bot error: {e}") - await utils.answer(message, self.strings("bot_error", message).format(e)) - except FloodWaitError as e: - logger.warning(f"Flood wait: {e.seconds}s") - await utils.answer( - message, self.strings("flood_wait", message).format(e.seconds) - ) - except Exception as e: - logger.exception("VK search error:") - await utils.answer(message, self.strings("error", message).format(e)) diff --git a/C0dwiz/H.Modules/PastebinAPI.py b/C0dwiz/H.Modules/PastebinAPI.py deleted file mode 100644 index c12a4c3..0000000 --- a/C0dwiz/H.Modules/PastebinAPI.py +++ /dev/null @@ -1,94 +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: PastebinAPI -# Description: fills in the code on pastebin -# Author: @hikka_mods -# --------------------------------------------------------------------------------- -# meta developer: @hikka_mods -# scope: PastebinAPI -# scope: PastebinAPI 0.0.1 -# requires: aiohttp -# --------------------------------------------------------------------------------- - -import aiohttp - -from .. import loader, utils - - -@loader.tds -class PastebinAPIMod(loader.Module): - """PastebinAPI""" - - strings = { - "name": "PastebinAPI", - "no_reply": ( - "🚫 You didn't specify the text" - ), - "no_key": "🚫 The key was not found", - "done": "Your link with the code\n➡️ {response_text}", - } - - strings_ru = { - "no_reply": ( - "🚫 Вы не указали текст" - ), - "no_key": "🚫 Ключ не найден", - "done": "Ваша ссылка с кодом\n➡️ {response_text}", - } - - def __init__(self): - self.config = loader.ModuleConfig( - loader.ConfigValue( - "pastebin", - None, - lambda: "link to get api https://pastebin.com/doc_api#1", - validator=loader.validators.Hidden(), - ) - ) - - @loader.command( - ru_doc="Заливает код в Pastebin", - en_doc="Uploads the code to Pastebin", - ) - async def past(self, message): - text = utils.get_args(message) - - if self.config["pastebin"] is None: - await utils.answer(message, self.strings("no_key")) - return - - if not text: - await utils.answer(message, self.strings("no_reply")) - return - - async with aiohttp.ClientSession() as Session: - async with Session.post( - url="https://pastebin.com/api/api_post.php", - data={ - "api_dev_key": self.config["pastebin"], - "api_paste_code": text, - "api_option": "paste", - }, - ) as response: - response_text = await response.text() - - await utils.answer( - message, self.strings("done").format(response_text=response_text) - ) diff --git a/C0dwiz/H.Modules/README.md b/C0dwiz/H.Modules/README.md deleted file mode 100644 index 68cd05a..0000000 --- a/C0dwiz/H.Modules/README.md +++ /dev/null @@ -1,91 +0,0 @@ -
- hikka_mods -
- -

H:Mods - Hikka Modules

- -

- Enhance your Hikka experience with a curated collection of community modules. -

- -

- - Telegram Channel - -

- ---- - -## ✨ Installation Guide - -### 1. Direct Installation - -To install a module directly from the repository, use the following command: - - -```bash -.dlm https://raw.githubusercontent.com/C0dwiz/H.Modules/main/.py -``` -**Example:** To install example_module.py, use: - - -```bash -.dlm https://raw.githubusercontent.com/C0dwiz/H.Modules/main/example_module.py -``` -### 2. Repository Installation (Recommended) - -For easier module management and updates, install the entire repository. - -**Step 1: Add the Repository** - - -```bash -.addrepo https://github.com/C0dwiz/H.Modules/raw/main -``` - -**Step 2: Install Modules from the Repository** - -```bash -.dlm -``` -**Example:** After adding the repository, to install example_module, use: - -```bash -.dlm example_module -``` ---- - -## 📜 License - -This project is licensed under a **Proprietary License**. By using this software, you agree to the terms and conditions outlined below. - -> **You are granted permission to use the Software for personal and non-commercial purposes, subject to the following conditions:** - -1. Modification or alteration of the Software is strictly prohibited without explicit written permission from the author. -2. Redistribution of the Software, in original or modified form, is strictly prohibited without explicit written permission from the author. -3. The Software is provided "as is", without any warranty, express or implied. -4. The copyright notice and this permission notice must be included 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. - -**Contact:** For any inquiries or requests for permissions, please contact codwiz@yandex.ru. - -
- Show Full License Details - > All files of this repository are under Proprietary License.
- > 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. - 4. The copyright notice and this permission notice must be included 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. -
- ---- - -

- -

- diff --git a/C0dwiz/H.Modules/ReplaceVowels.py b/C0dwiz/H.Modules/ReplaceVowels.py deleted file mode 100644 index c5bdd4c..0000000 --- a/C0dwiz/H.Modules/ReplaceVowels.py +++ /dev/null @@ -1,78 +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 -# --------------------------------------------------------------------------------- - -from telethon.tl.types import Message - -from .. import loader, utils - - -@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/C0dwiz/H.Modules/SMAcrhiver.py b/C0dwiz/H.Modules/SMAcrhiver.py deleted file mode 100644 index d1afd68..0000000 --- a/C0dwiz/H.Modules/SMAcrhiver.py +++ /dev/null @@ -1,149 +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 zipfile -import os -from datetime import datetime -from .. import loader, utils - - -@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 = 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) - - 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/C0dwiz/H.Modules/SafetyMod.py b/C0dwiz/H.Modules/SafetyMod.py deleted file mode 100644 index 2a7431d..0000000 --- a/C0dwiz/H.Modules/SafetyMod.py +++ /dev/null @@ -1,103 +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: SafetyMod -# Description: generate random password -# Author: @hikka_mods -# --------------------------------------------------------------------------------- -# meta developer: @hikka_mods -# scope: Api SafetyMod -# scope: Api SafetyMod 0.0.1 -# --------------------------------------------------------------------------------- - -import random -import string - -from .. import loader, utils - - -def generate_password( - length: int, letters: bool = True, numbers: bool = True, symbols: bool = True -) -> str: - """Generates a random password with customizable options. - - Args: - length: The desired length of the password. - letters: Include lowercase and uppercase letters (default: True). - numbers: Include digits (default: True). - symbols: Include common symbols (default: True). - - Returns: - A randomly generated password string. - - Raises: - ValueError: If all character sets are disabled (letters, numbers, symbols). - """ - character_sets = [] - if letters: - character_sets.append(string.ascii_letters) - if numbers: - character_sets.append(string.digits) - if symbols: - character_sets.append(string.punctuation) - - if not character_sets: - raise ValueError("At least one of letters, numbers, or symbols must be True") - - combined_characters = "".join(character_sets) - password = "".join(random.choice(combined_characters) for _ in range(length)) - return password - - -@loader.tds -class SafetyMod(loader.Module): - """generate random password""" - - strings = { - "name": "Safety", - "pass": "*⃣ Here is your secure password: {}", - } - strings_ru = { - "pass": "*⃣ Вот ваш безопасный пароль: {}" - } - - @loader.command( - ru_doc="Случайный пароль\n-n - цифры\n-s - символы \n -l - буквы", - en_doc="Random password\n-n - numbers\n-s - symbols \n -l - letters", - ) - async def password(self, message): - """random password\n-n - numbers\n-s - symbols \n -l - letters""" - text = message.text.split() - length = 10 - letters = True - numbers = False - symbols = False - for i in text: - if i.startswith("password"): - length = int(i.split("password")[1]) - elif i == "-n": - numbers = True - elif i == "-s": - symbols = True - elif i == "-l": - letters = True - password = generate_password( - length=length, letters=letters, numbers=numbers, symbols=symbols - ) - await utils.answer(message, self.strings("pass").format(password)) diff --git a/C0dwiz/H.Modules/TaskManager.py b/C0dwiz/H.Modules/TaskManager.py deleted file mode 100644 index a87922d..0000000 --- a/C0dwiz/H.Modules/TaskManager.py +++ /dev/null @@ -1,358 +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 datetime -import json -import logging -import os -from dataclasses import dataclass, field -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) - - -class TaskManager: - """Manages tasks, storing them in a JSON file.""" - - def __init__(self, data_file: str): - self.data_file = data_file - self.tasks: Dict[int, List[Task]] = {} - self.load_data() - - def load_data(self): - """Loads task data from the JSON file.""" - if os.path.exists(self.data_file): - try: - with open(self.data_file, "r") as f: - data = json.load(f) - self.tasks = { - int(user_id): [ - Task( - description=task["description"], - due_date=datetime.datetime.fromisoformat( - task["due_date"] - ) - if task["due_date"] - else None, - completed=task["completed"], - created_at=datetime.datetime.fromisoformat( - task["created_at"] - ), - ) - for task in task_list - ] - for user_id, task_list in data.items() - } - except (FileNotFoundError, json.JSONDecodeError) as e: - logger.warning(f"Failed to load task data: {e}. Starting empty.") - self.tasks = {} - else: - self.tasks = {} - logger.info("Task data file not found. Starting empty.") - - def save_data(self): - """Saves task data to the JSON file.""" - try: - with open(self.data_file, "w") as f: - data = { - user_id: [ - { - "description": task.description, - "due_date": task.due_date.isoformat() - if task.due_date - else None, - "completed": task.completed, - "created_at": task.created_at.isoformat(), - } - for task in task_list - ] - for user_id, task_list in self.tasks.items() - } - json.dump(data, f, indent=4, default=str) - except IOError as e: - logger.error(f"Failed to save task data: {e}") - - def add_task(self, user_id: int, task: Task): - self.tasks.setdefault(user_id, []).append(task) - self.save_data() - - def remove_task(self, user_id: int, index: int): - if user_id in self.tasks and 0 <= index < len(self.tasks[user_id]): - del self.tasks[user_id][index] - self.save_data() - else: - logger.warning(f"Invalid index for removal: {index}, user: {user_id}") - - def complete_task(self, user_id: int, index: int): - if user_id in self.tasks and 0 <= index < len(self.tasks[user_id]): - self.tasks[user_id][index].completed = True - self.save_data() - else: - logger.warning(f"Invalid index for completion: {index}, user: {user_id}") - - def get_tasks(self, user_id: int) -> List[Task]: - return self.tasks.get(user_id, []) - - def clear_tasks(self, user_id: int): - if user_id in self.tasks: - self.tasks[user_id] = [] - self.save_data() - else: - logger.info(f"No tasks to clear for user: {user_id}") - - def get_task(self, user_id: int, index: int) -> Optional[Task]: - return ( - self.tasks[user_id][index] - if user_id in self.tasks and 0 <= index < len(self.tasks[user_id]) - else None - ) - - -@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): - self.task_manager = TaskManager(os.path.join(os.getcwd(), "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) - 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 self.task_manager.get_task(message.sender_id, index) is None: - await utils.answer(message, self.strings("task_not_found")) - return - - self.task_manager.remove_task(message.sender_id, index) - await utils.answer(message, self.strings("task_removed")) - - @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 self.task_manager.get_task(message.sender_id, index) is None: - await utils.answer(message, self.strings("task_not_found")) - return - - self.task_manager.complete_task(message.sender_id, index) - await utils.answer(message, self.strings("task_completed")) - - @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.""" - self.task_manager.clear_tasks(call.from_user.id) - await call.edit(self.strings("tasks_cleared")) - - async def clear_cancel(self, call): - """Callback for canceling task clearing.""" - await call.edit(self.strings("clear_cancelled")) \ No newline at end of file diff --git a/C0dwiz/H.Modules/TelegramStatusCodes.py b/C0dwiz/H.Modules/TelegramStatusCodes.py deleted file mode 100644 index b410165..0000000 --- a/C0dwiz/H.Modules/TelegramStatusCodes.py +++ /dev/null @@ -1,133 +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 -# --------------------------------------------------------------------------------- - -from .. import loader, utils - -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""", - ), -} - - -@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": "<код> - Получить информацию о Telegram error", - "_cmd_doc_httpscs": "Показать все доступные коды", - "_cls_doc": "Словарь telegram error", - } - - @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)) - - 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)) - - await utils.answer( - message, - self.strings("scode", message).format( - responses[int(args[0])][0], args[0], responses[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/C0dwiz/H.Modules/TempChat.py b/C0dwiz/H.Modules/TempChat.py deleted file mode 100644 index 1d4d774..0000000 --- a/C0dwiz/H.Modules/TempChat.py +++ /dev/null @@ -1,162 +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: TempChat -# Description: Creates a temporary private chat with a message forwarding restriction and adds the specified user to it. -# Author: @hikka_mods -# --------------------------------------------------------------------------------- -# meta developer: @hikka_mods -# scope: TempChat -# scope: TempChat 0.0.1 -# --------------------------------------------------------------------------------- - -import logging -import asyncio - -from hikkatl import functions -from datetime import datetime as dt - -from .. import loader, utils - -logging.basicConfig(level=logging.ERROR) -logger = logging.getLogger(__name__) - - -@loader.tds -class TempChatMod(loader.Module): - """Creates a temporary private chat with a message forwarding restriction and adds the specified user to it.""" - - strings = { - "name": "TempChat", - "selfchat": "You can't create a chat with yourself.", - "wrongargs": " Wrong arguments. Use .tmpchat [@user/reply] [time]", - "alreadychatting": " You already have an active conversation with this person.", - "invalidtime": " Invalid time format. Use combinations like 1h30m.", - "invitemsg": "🛡 You've been invited to a temporary private chat!\n\n⌛️ Auto-deletes in ", - "joinlink": "🔗 Join link: ", - "chatcreated": " The temporary chat has been successfully created!" - - } - - strings_ru = { - "selfchat": "Ты не можешь создать чат сам с собой.", - "wrongargs": " Неверные аргументы. Используй .tmpchat [@user/reply] [время]", - "alreadychatting": " У вас уже есть открытая переписка с этим человеком.", - "invalidtime": " Неверный формат времени. Убедитесь, что вы вводите время в формате 1h, 2h30m.", - "invitemsg": "🛡 Вы были приглашены во временный приватный чат!\n\n⌛️ Авто-удаление через ", - "joinlink": "🔗 Ссылка: ", - "chatcreated": " Временный чат успешно создан!", - } - - def __init__(self): - self.temp_chats = {} - - async def check_expired_chats(self): - while True: - now = dt.now().timestamp() - for chat_id in list(self.temp_chats.keys()): - if self.temp_chats[chat_id][1] <= now: - try: - await self.client( - functions.channels.DeleteChannelRequest(chat_id) - ) - del self.temp_chats[chat_id] - self.set("temp_chats", self.temp_chats) - except Exception as e: - logger.error(f"Error deleting chat {chat_id}: {e}") - try: - self.client( - functions.channels.GetFullChannelRequest( - channel=chat_id - ) - ) - except Exception: - del self.temp_chats[chat_id] - self.set("temp_chats", self.temp_chats) - await asyncio.sleep(30) - - async def client_ready(self, client, db): - self.hmodslib = await self.import_lib( - "https://raw.githubusercontent.com/C0dwiz/H.Modules/refs/heads/main-fix/HModsLibrary.py" - ) - self.temp_chats = self.get("temp_chats", {}) - asyncio.create_task(self.check_expired_chats()) - - @loader.command( - ru_doc="Создает временный чат. Использование: .tmpchat [@user/reply] [time]" - ) - async def tmpchat(self, message): - """Create temporary chat. Usage: .tmpchat [@user/reply] [time]""" - args = utils.get_args_raw(message) - reply = await message.get_reply_message() - - if reply: - user = await self.client.get_entity(reply.sender_id) - time_str = args.strip() if args else None - else: - parts = args.split(",", 1) if "," in args else args.rsplit(" ", 1) - if len(parts) != 2: - return await utils.answer(message, self.strings["wrongargs"]) - user_str, time_str = parts[0].strip(), parts[1].strip() - try: - user = await self.client.get_entity(user_str) - except Exception: - return await utils.answer(message, self.strings["wrongargs"]) - - if not time_str: - return await utils.answer(message, self.strings["wrongargs"]) - seconds = await self.hmodslib.parse_time(time_str) - if not seconds: - return await utils.answer(message, self.strings["invalidtime"]) - - if any(user.id == uid for uid, _ in self.temp_chats.values()): - return await utils.answer(message, self.strings["alreadychatting"]) - - try: - created = await self.client( - functions.channels.CreateChannelRequest( - title=f"TempChat #{user.id}", - about=f"Temporary private chat with {user.id} | Expires after: {time_str}", - megagroup=True, - ) - ) - chat_id = created.chats[0].id - expires_at = dt.now().timestamp() + seconds - - await self.client( - functions.messages.ToggleNoForwardsRequest(peer=chat_id, enabled=True) - ) - - self.temp_chats[chat_id] = (user.id, expires_at) - self.set("temp_chats", self.temp_chats) - - invite = await self.client( - functions.messages.ExportChatInviteRequest(peer=chat_id, usage_limit=1) - ) - invite_message = ( - self.strings["invitemsg"] - + time_str - + f"\n{self.strings['joinlink']} {invite.link}" - ) - await self.client.send_message(user.id, invite_message) - await utils.answer(message, self.strings["chatcreated"]) - - except Exception as e: - logger.error(f"Error creating temp chat: {e}") - await utils.answer(message, "❌ Error! Check log-chat.") diff --git a/C0dwiz/H.Modules/Text2File.py b/C0dwiz/H.Modules/Text2File.py deleted file mode 100644 index b13b6be..0000000 --- a/C0dwiz/H.Modules/Text2File.py +++ /dev/null @@ -1,75 +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 - -from .. import loader, utils - - -@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")) - else: - text = args - by = io.BytesIO(text.encode("utf-8")) - by.name = self.config["name"] - - await utils.answer_file( - message, - by, - reply_to=getattr(message, "reply_to_msg_id", None), - ) diff --git a/C0dwiz/H.Modules/Text_Sticker.py b/C0dwiz/H.Modules/Text_Sticker.py deleted file mode 100644 index 5601181..0000000 --- a/C0dwiz/H.Modules/Text_Sticker.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 codwiz@yandex.ru. - -# --------------------------------------------------------------------------------- -# Name: Text in sticker -# Description: Text in sticker -# Author: @hikka_mods -# Commands: -# .st [text] -# --------------------------------------------------------------------------------- -# meta developer: @hikka_mods -# scope: Text in sticker -# scope: Text in sticker 0.0.1 -# requires: requests -# --------------------------------------------------------------------------------- - -import io -from textwrap import wrap - -import requests -from PIL import Image, ImageColor, ImageDraw -from PIL import ImageFont - -from .. import loader, utils - - -@loader.tds -class TextinstickerMod(loader.Module): - """Text to sticker""" - - strings = { - "name": "Text in sticker", - "error": "white st [text]", - } - - strings_ru = { - "error": "Укажите .st [text]", - } - - def __init__(self): - self.config = loader.ModuleConfig( - loader.ConfigValue( - "font", - "https://github.com/CodWize/ReModules/blob/main/assets/Samson.ttf?raw=true", - lambda: "add a link to the font you want", - ) - ) - - @loader.command( - ru_doc="<название цвета> [текст]", - en_doc=" [text]", - ) - @loader.owner - async def stcmd(self, message): - await message.delete() - text = utils.get_args_raw(message) - reply = await message.get_reply_message() - if not text: - if not reply: - text = self.strings("error") - elif not reply.message: - text = self.strings("error") - else: - text = reply.raw_text - color_name = text.split(" ", 1)[0].lower() - color = None - if len(text.split(" ", 1)) > 1: - text = text.split(" ", 1)[1] - else: - if reply and reply.message: - text = reply.raw_text - try: - color = ImageColor.getrgb(color_name) - except ValueError: - color = (255, 255, 255) - txt = [] - for line in text.split("\n"): - txt.append("\n".join(wrap(line, 30))) - text = "\n".join(txt) - bytes_font = requests.get(self.config["font"]).content - font = io.BytesIO(bytes_font) - font = ImageFont.truetype(font, 100) - image = Image.new("RGBA", (1, 1), (0, 0, 0, 0)) - draw = ImageDraw.Draw(image) - w, h = draw.multiline_textsize(text=text, font=font) - image = Image.new("RGBA", (w + 100, h + 100), (0, 0, 0, 0)) - draw = ImageDraw.Draw(image) - draw.multiline_text((50, 50), text=text, font=font, fill=color, align="center") - output = io.BytesIO() - output.name = f"{color_name}.webp" - image.save(output, "WEBP") - output.seek(0) - await self.client.send_file(message.to_id, output, reply_to=reply) diff --git a/C0dwiz/H.Modules/TikTokDownloader.py b/C0dwiz/H.Modules/TikTokDownloader.py deleted file mode 100644 index 14b8aa5..0000000 --- a/C0dwiz/H.Modules/TikTokDownloader.py +++ /dev/null @@ -1,327 +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 aiohttp -import asyncio -import re -import os -import warnings -import functools -import logging - -from dataclasses import dataclass -from urllib.parse import urljoin -from typing import Union, Optional, List -from tqdm import tqdm -from .. import loader, utils - - -@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) - - def _warn(reason: str = "This function is NOT used but may be useful"): - def decorator(func): - @functools.wraps(func) - def wrapper(*args, **kwargs): - warnings.warn( - f"Warning! Deprecated: {func.__name__}\nReason: {reason}", - category=DeprecationWarning, - stacklevel=2, - ) - return func(*args, **kwargs) - - return wrapper - - return decorator - - async def close_session(self): - await self.session.close() - - async def _ensure_data(self, link: str): - try: - if self.result is None or self.link != link: - self.link = link - self.result = await self.fetch(link) - self.logger.info("Successfully ensured data from the link") - except Exception as e: - self.logger.error(f"Error occurred when trying to get data from tikwm: {e}") - raise - - async def __getimages(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 __getvideo(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 _makerequest(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 - - @_warn() - async def convert_share_urls(self, url: str) -> Optional[str]: - url = self.get_url(url) - if "@" in url: - return url - async with self.session.get( - url, headers=self.headers, allow_redirects=False - ) as response: - if response.status == 301: - return response.headers["Location"].split("?")[0] - return None - - @_warn() - async def get_tiktok_video_id(self, original_url: str) -> Optional[str]: - original_url = await self.convert_share_urls(original_url) - matches = re.findall(r"/video|v|photo/(\d+)", original_url) - return matches[0] if matches else None - - async def fetch(self, link: str) -> dict: - url = self.get_url(link) - params = {"url": url, "hd": 1} - return await self._makerequest(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: Union[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: Union[str], video_filename: Optional[str] = None, hd: bool = True - ) -> data: - """ - Asynchronously downloads a TikTok video or photo post. - - Args: - video_filename (Optional[str]): The name of the file for the TikTok video or photo. If None, the file will be named based on the video or photo ID. - hd (bool): If True, downloads the video in HD format. Defaults to False. - - Returns: - dir_name (str): Directory name - media (Union[str, List[str]]): Full list of downloaded media - type (str): The type of downloaded objects: Images or video - - Raises: - Exception: No downloadable content found in the provided link. - - """ - await self._ensure_data(link) - if "images" in self.result: - self.logger.info("Starting to download images") - return await self.__getimages(video_filename) - elif "hdplay" in self.result or "play" in self.result: - self.logger.info("Starting to download video.") - return await self.__getvideo(video_filename, hd) - else: - self.logger.error("No downloadable content found in the provided link.") - raise Exception("No downloadable content found in the provided link.") - - def _get_video_link(self, unique_id: str, aweme_id: str) -> str: - return f"https://www.tiktok.com/@{unique_id}/video/{aweme_id}" - - def _get_uploader_link(self, 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": " Downloading…", - "success_photo": "❤️ The photo(s) has/have been successfully downloaded!!", - "success_video": "❤️ The video has been successfully downloaded!", - "success_sound": "❤️ The sound has been successfully downloaded!", - "error": "Error occurred while downloading.\n{}", - } - - strings_ru = { - "downloading": " Загружаем…", - "success_photo": "❤️ Фотография(-и) была(-и) успешно загружены!!", - "success_video": "❤️ Видео было успешно загружено!", - "success_sound": "❤️ Звук был успешно загружен!", - "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/C0dwiz/H.Modules/UserbotAvast.py b/C0dwiz/H.Modules/UserbotAvast.py deleted file mode 100644 index 8d34505..0000000 --- a/C0dwiz/H.Modules/UserbotAvast.py +++ /dev/null @@ -1,972 +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 logging -import ast -import astor -import requests -import base64 -import zlib -import re -import urllib.parse - -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 astor.to_source(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 astor.to_source(tree): - sleep_calls = re.findall(r"time\.sleep\((.*?)\)", astor.to_source(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 += "⚠️ Код был расшифрован перед анализом.\n\n" - else: - report += "⚠️ Анализ проводился над исходным кодом, расшифровка не удалась.\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": "🛡️ Отчет об анализе безопасности модуля:\n\n", - "critical_header": "🔴 Критические угрозы:\n", - "warning_header": "🟠 Предупреждения:\n", - "info_header": "🔵 Информация:\n", - "issue_format": " - ⚠️ {keyword}: {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": "🛡️ Отчет об анализе безопасности модуля:\n\n", - "critical_header": "🔴 Критические угрозы:\n", - "warning_header": "🟠 Предупреждения:\n", - "info_header": "🔵 Информация:\n", - "issue_format": " - ⚠️ {keyword}: {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/C0dwiz/H.Modules/Video2GIF.py b/C0dwiz/H.Modules/Video2GIF.py deleted file mode 100644 index 76e6ff1..0000000 --- a/C0dwiz/H.Modules/Video2GIF.py +++ /dev/null @@ -1,105 +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 -# requires: moviepy -# --------------------------------------------------------------------------------- - -import os -import subprocess - -from .. import loader, utils - - -@loader.tds -class Video2GIF(loader.Module): - """Converts video to GIF""" - - strings = { - "name": "Video2GIF", - "conversion_success": "🎉 The conversion is completed!", - "conversion_error": "❌ An error occurred when converting video to GIF.", - "not_video": "⚠️ Please reply to the message with the video or send the video in one message.", - "loading": "⏳ Conversion is underway", - } - - strings_ru = { - "conversion_success": "🎉 Преобразование завершено!", - "conversion_error": "❌ Произошла ошибка при преобразовании видео в GIF.", - "not_video": "⚠️ Пожалуйста, ответьте на сообщение с видео или отправьте видео одним сообщением.", - "loading": "⏳ Идет преобразование", - } - - @loader.command( - ru_doc="[reply | в одном сообщении с видео] — конвертирует видео в GIF.", - en_doc="[reply | in one message with video] — Converts video to GIF.", - ) - async def gifc(self, message): - video = await self.get_video_from_message(message) - - if not video: - await utils.answer(message, self.strings["not_video"]) - return - - await utils.answer(message, self.strings["loading"]) - video_path = await self.client.download_media(video) - gif_path = f"{os.path.splitext(video_path)[0]}.gif" - - try: - self.convert_video_to_gif(video_path, gif_path) - await message.client.send_file( - message.chat_id, gif_path, caption=self.strings["conversion_success"] - ) - except Exception as e: - await utils.answer(message, self.strings["conversion_error"]) - print(f"Error during conversion: {e}") - finally: - self.cleanup_temp_files(video_path, gif_path) - - async def get_video_from_message(self, message): - """Получает видео из сообщения.""" - if reply := await message.get_reply_message(): - return reply.video - return message.video - - def convert_video_to_gif(self, video_path: str, gif_path: str) -> None: - """Конвертирует видео в GIF с улучшенными параметрами.""" - command = [ - "ffmpeg", - "-i", - video_path, - "-vf", - "fps=30,scale=640:-1:flags=lanczos", - "-c:v", - "gif", - gif_path, - ] - subprocess.run(command, check=True) - - def cleanup_temp_files(self, video_path: str, gif_path: str) -> None: - """Удаляет временные файлы.""" - for temp_file in [video_path, gif_path]: - if os.path.exists(temp_file): - os.remove(temp_file) diff --git a/C0dwiz/H.Modules/VirusTotal.py b/C0dwiz/H.Modules/VirusTotal.py deleted file mode 100644 index 16d7c88..0000000 --- a/C0dwiz/H.Modules/VirusTotal.py +++ /dev/null @@ -1,161 +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 os -import tempfile -import logging - -from .. import loader, utils - -logger = logging.getLogger(__name__) - - -@loader.tds -class VirusTotalMod(loader.Module): - """Checks files for viruses using VirusTotal.""" - - strings = { - "name": "VirusTotal", - "no_file": "🚫 You haven't selected a file.", - "download": "😑 Downloading...", - "scan": "🫥 Scanning...", - "link": "🦠 VirusTotal Link", - "no_virus": "✅ File is clean.", - "error": "⚠️ Scan error.", - "no_format": "This format is not supported.", - "no_apikey": "🚫 You have not specified an API Key", - "config": "Need a token with www.virustotal.com/gui/my-apikey", - "scanning": "🫥 Waiting for scan results...", - "getting_upload_url": "🫥 Getting upload URL...", - "analysis_failed": "⚠️ Analysis failed after multiple retries.", - } - - strings_ru = { - "no_file": "🚫 Вы не выбрали файл.
", - "download": "😑 Скачивание...", - "scan": "🫥 Сканирую...", - "link": "🦠 Ссылка на VirusTotal", - "no_virus": "✅ Файл чист.", - "error": "⚠️ Ошибка сканирования.", - "no_format": "Этот формат не поддерживается.", - "no_apikey": "🚫 Вы не указали Api Key", - "config": "Need a token with www.virustotal.com/gui/my-apikey", - "scanning": "🫥 Ожидание результатов сканирования...", - "getting_upload_url": "🫥 Получение URL для загрузки...", - "analysis_failed": "⚠️ Анализ не удался после нескольких попыток.", - } - - def __init__(self): - self.config = loader.ModuleConfig( - loader.ConfigValue( - "token-vt", - None, - lambda: "Need a token with www.virustotal.com/gui/my-apikey", - validator=loader.validators.Hidden(), - ) - ) - - async def client_ready(self, client, db): - self.hmodslib = await self.import_lib( - "https://raw.githubusercontent.com/C0dwiz/H.Modules/refs/heads/main-fix/HModsLibrary.py" - ) - - @loader.command( - ru_doc="<ответ на файл> - Проверяет файлы на наличие вирусов с использованием VirusTotal", - en_doc=" - Checks files for viruses using VirusTotal", - ) - async def vt(self, message): - if not message.is_reply: - await utils.answer(message, self.strings("no_file")) - return - - reply = await message.get_reply_message() - if not reply.document: - await utils.answer(message, self.strings("no_file")) - return - - api_key = self.config.get("token-vt") - if not api_key: - await utils.answer(message, self.strings("no_apikey")) - return - - file_extension = os.path.splitext(reply.file.name)[1].lower() - allowed_extensions = (".jpg", ".png", ".ico", ".mp3", ".mp4", ".gif", ".txt") - if file_extension in allowed_extensions: - await utils.answer(message, self.strings("no_format")) - return - - try: - await utils.answer(message, self.strings("download")) - with tempfile.TemporaryDirectory() as temp_dir: - file_path = os.path.join(temp_dir, reply.file.name) - await reply.download_media(file_path) - - file_size = os.path.getsize(file_path) - is_large_file = file_size > 32 * 1024 * 1024 - - if is_large_file: - await utils.answer(message, self.strings("getting_upload_url")) - await utils.answer(message, self.strings("scan")) - - analysis_results = await self.hmodslib.scan_file_virustotal( - file_path, api_key, is_large_file - ) - - if analysis_results: - formatted_results = self.hmodslib.format_analysis_results( - analysis_results - ) - try: - await self.inline.form( - text=formatted_results["text"], - message=message, - reply_markup={ - "text": self.strings("link"), - "url": formatted_results["url"], - } - if formatted_results["url"] - else None, - ) - except Exception as e: - logger.error(f"Error displaying inline results: {e}") - await utils.answer( - message, - self.strings("error_report").format( - formatted_results["url"] - ), - ) - - else: - await utils.answer(message, self.strings("analysis_failed")) - - except Exception as e: - logger.exception("An error occurred during the VT scan process.") - await utils.answer( - message, self.strings("error") + f"\n\n{type(e).__name__}: {str(e)}" - ) diff --git a/C0dwiz/H.Modules/VoiceDL.py b/C0dwiz/H.Modules/VoiceDL.py deleted file mode 100644 index 21ca3ec..0000000 --- a/C0dwiz/H.Modules/VoiceDL.py +++ /dev/null @@ -1,125 +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 os -import subprocess -import tempfile -import time - -from .. import loader, utils - - -@loader.tds -class VoiceDL(loader.Module): - """Voice Downloader module""" - - strings = { - "name": "VoiceDL", - "download_success": "Voice message downloaded in MP3 format.", - "download_error": "Error downloading voice message.", - "no_voice_message": "Please reply to a voice message.", - "conversion_error": "Error converting to MP3.", - "file_not_found": "File not found.", - "unsupported_format": "The file format is not supported.", - } - - strings_ru = { - "download_success": "Голосовое сообщение загружено в формате MP3.", - "download_error": "Ошибка при загрузке голосового сообщения.", - "no_voice_message": "Пожалуйста, ответьте на голосовое сообщение.", - "conversion_error": "Ошибка при конвертации в MP3.", - "file_not_found": "Файл не найден.", - "unsupported_format": "Формат файла не поддерживается.", - } - - @loader.command( - ru_doc=" [reply] — загружает выбранное голосовое сообщение в виде файла mp3 и кидает его в чат.", - en_doc=" [reply] — downloads the selected voice message as an MP3 file and sends it in the chat.", - ) - async def voicedl(self, message): - reply = await message.get_reply_message() - - if reply: - if reply.voice: - try: - with tempfile.NamedTemporaryFile( - delete=False, suffix=".ogg" - ) as temp_voice_file: - voice_file_path = temp_voice_file.name - await message.client.download_file(reply.voice, voice_file_path) - - timestamp = int(time.time()) - mp3_file_path = f"voice_message_{timestamp}.mp3" - - await self.convert_to_mp3(voice_file_path, mp3_file_path) - - await message.client.send_file( - message.chat.id, - mp3_file_path, - caption=self.strings("download_success"), - ) - - os.remove(voice_file_path) - os.remove(mp3_file_path) - - except FileNotFoundError: - await utils.answer( - message, - self.strings("download_error") - + " " - + self.strings("file_not_found"), - ) - except subprocess.CalledProcessError as e: - await utils.answer( - message, - self.strings("conversion_error") + f" {e.stderr.decode()}", - ) - except Exception as e: - await utils.answer( - message, self.strings("download_error") + f" {str(e)}" - ) - else: - await utils.answer(message, self.strings("no_voice_message")) - else: - await utils.answer(message, self.strings("no_voice_message")) - - async def convert_to_mp3(self, input_file: str, output_file: str): - """Convert audio file to MP3 format using FFmpeg.""" - command = ["ffmpeg", "-i", input_file, output_file] - process = subprocess.run( - command, stdout=subprocess.PIPE, stderr=subprocess.PIPE - ) - - if process.returncode != 0: - raise subprocess.CalledProcessError( - process.returncode, - command, - output=process.stdout, - stderr=process.stderr, - ) diff --git a/C0dwiz/H.Modules/Weather.py b/C0dwiz/H.Modules/Weather.py deleted file mode 100644 index 8749c5e..0000000 --- a/C0dwiz/H.Modules/Weather.py +++ /dev/null @@ -1,329 +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 -import requests - -from datetime import datetime -from typing import Union, Dict, List -from dataclasses import dataclass - -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", "☀️"), - WeatherCondition("sunny", "☀️"), - WeatherCondition("partly cloudy", "⛅️"), - WeatherCondition("cloudy", "☁️☁️"), - WeatherCondition("overcast", "☁️"), - WeatherCondition("mist", "😶‍🌫️"), - WeatherCondition("fog", "😶‍🌫️"), - WeatherCondition("light rain", "🌦"), - WeatherCondition("rain", "🌧"), - WeatherCondition("heavy rain", ""), - WeatherCondition("thunderstorm", ""), - WeatherCondition("snow", "🌨"), - WeatherCondition("heavy snow", "❄️"), - WeatherCondition("sleet", "🌨"), - 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": "🚫 Please specify a city", - "invalid_city": "🚫 City not found", - "loading": "🔄 Fetching weather data for {}...", - "error": " Error retrieving weather data", - "default_city": " Default city set to: {city}", - "weather_text": """{emoji} Weather: {location} - -📊 Current conditions: -├ 🌡 Temperature: {temp}°C -├– Feels like: {feels_like}°C -├ 💧 Humidity: {humidity}% -├ 💨 Wind: {wind_speed} km/h {wind_direction} -├ 🌪 Pressure: {pressure} mmHg -├ 👁 Visibility: {visibility} km -└ ☁️ Cloudiness: {clouds} - -🌅 Time: -├ 🌅 Sunrise: {sunrise} -├ 🌇 Sunset: {sunset} -└ ⏱ Local time: {local_time} - -📅 Forecast for {forecast_days} days: -{forecast} -⏰ Updated: {updated}""", - "forecast_day": """{date} {emoji} -├ 🌡 Temperature: {temp_min}°C ... {temp_max}°C -└ 💨 Wind: {wind_speed} km/h {wind_direction} - -""", - } - - strings_ru = { - "no_city": "🚫 Пожалуйста, укажите город", - "invalid_city": "🚫 Город не найден", - "loading": "🔄 Получаю метеоданные для {}...", - "default_city": " Город по умолчанию установлен: {city}", - "error": " Ошибка при получении данных о погоде", - "weather_text": """{emoji} Погода: {location} - -📊 Текущие условия: -├ 🌡 Температура: {temp}°C -├– Ощущается как: {feels_like}°C -├ 💧 Влажность: {humidity}% -├ 💨 Ветер: {wind_speed} км/ч {wind_direction} -├ 🌪 Давление: {pressure} мм.рт.ст -├ 👁 Видимость: {visibility} км -└ ☁️ Облачность: {clouds} - -🌅 Время: -├ 🌅 Восход: {sunrise} -├ 🌇 Закат: {sunset} -└ ⏱ Местное время: {local_time} - -📅 Прогноз на {forecast_days} дня: -{forecast} -⏰ Обновлено: {updated}""", - "forecast_day": """{date} {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/C0dwiz/H.Modules/WindowsKeys.py b/C0dwiz/H.Modules/WindowsKeys.py deleted file mode 100644 index 2e4770e..0000000 --- a/C0dwiz/H.Modules/WindowsKeys.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: WindowsKeys -# Description: Provides you Windows activation keys -# Author: @hikka_mods -# --------------------------------------------------------------------------------- -# meta developer: @hikka_mods -# scope: WindowsKeys -# scope: WindowsKeys 0.0.1 -# requires: requests -# --------------------------------------------------------------------------------- - -import logging -import json -import requests - -from .. import loader, utils - -logger = logging.getLogger(__name__) - - -@loader.tds -class WindowsKeys(loader.Module): - """Provides you Windows activation keys""" - - strings = { - "name": "WindowsKeys", - "winkey": "✅ Your key: {}\n\n⚠ Warning! This key is not a pirate key. It is taken from the official Microsoft site and is intended for further activation via KMS-server", - "error": "❌ An error occurred while retrieving the key. Please try again later.", - } - - strings_ru = { - "winkey": "✅ Ваш ключ: {}\n\n⚠ Внимание! Указанный ключ не является пиратским. Он взят с официального сайта Microsoft и предназначен для дальнейшей активации посредством KMS-сервера", - "error": "❌ Произошла ошибка при получении ключа. Попробуйте позже.", - } - - @loader.command( - ru_doc="Открывает выбор ключа для активации Windows", - en_doc="Opens the Windows activation key selection", - ) - async def winkey(self, message): - await self.inline.form( - text="🔓 Выберите версию и издание Windows, для которой вам необходим ключ", - message=message, - reply_markup=[ - [ - { - "text": "Windows 10/11 Pro", - "callback": self._inline__give_key, - "args": ["win10_11pro"], - } - ], - [ - { - "text": "Windows 10/11 Enterprise LTSC", - "callback": self._inline__give_key, - "args": ["win10_11enterpriseLTSC"], - } - ], - [ - { - "text": "Windows 8.1 Pro", - "callback": self._inline__give_key, - "args": ["win8.1pro"], - } - ], - [ - { - "text": "Windows 8 Pro", - "callback": self._inline__give_key, - "args": ["win8pro"], - } - ], - [ - { - "text": "Windows 7 Pro", - "callback": self._inline__give_key, - "args": ["win7pro"], - } - ], - [ - { - "text": "Windows Vista Business", - "callback": self._inline__give_key, - "args": ["winvistabusiness"], - } - ], - [ - { - "text": "🎈 Закрыть", - "action": "close", - } - ], - ], - force_me=False, - silent=True, - ) - - async def _inline__give_key(self, call, winver): - url = "https://raw.githubusercontent.com/C0dwiz/H.Modules/refs/heads/assets/winkeys.json" - try: - response = requests.get(url) - response.raise_for_status() - data = response.json() - await call.edit(self.strings["winkey"].format(data[winver])) - - except requests.exceptions.RequestException as e: - logger.error("Request error: %e", e) - await call.answer(self.strings("error"), show_alert=True) - except json.JSONDecodeError as e: - logger.error("JSON decode error: %e", e) - await call.answer(self.strings("error"), show_alert=True) - except KeyError as e: - logger.error("Key error: %e", e) - await call.answer(self.strings("error"), show_alert=True) - - except Exception as e: - logger.exception("An unexpected error occurred: %e", e) - await call.answer(self.strings("error"), show_alert=True) \ No newline at end of file diff --git a/C0dwiz/H.Modules/aiogram3/hikarichat.py b/C0dwiz/H.Modules/aiogram3/hikarichat.py deleted file mode 100644 index 82431fb..0000000 --- a/C0dwiz/H.Modules/aiogram3/hikarichat.py +++ /dev/null @@ -1,6213 +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"HikariChat {version}" - -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 = ( - "🚫 API Error:" - " {}" - ) - - 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"], - ( - "🚫" - " Bad API arguments, PROKAZNIK!" - ), - ) - 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": ( - "🚫 Args are" - " incorrect" - ), - "no_reason": "Not specified", - "antitagall_on": ( - "🐵 AntiTagAll is now on" - " in this chat\nAction: {}" - ), - "antitagall_off": ( - "🐵 AntiTagAll is now off" - " in this chat" - ), - "antiarab_on": ( - "🇵🇸 AntiArab is now on in" - " this chat\nAction: {}" - ), - "antiarab_off": ( - "🇵🇸 AntiArab is now off" - " in this chat" - ), - "antilagsticks_on": ( - "💣 Destructive stickers" - " protection is now on in this chat" - ), - "antilagsticks_off": ( - "💣 Destructive stickers" - " protection is now off in this chat" - ), - "antizalgo_on": ( - "🌀 AntiZALGO is now" - " on in" - " this chat\nAction: {}" - ), - "antizalgo_off": ( - "🌀 AntiZALGO is now off" - " in this chat" - ), - "antistick_on": ( - "🎨 AntiStick is now" - " on in" - " this chat\nAction: {}" - ), - "antistick_off": ( - "🎨 AntiStick is now off" - " in this chat" - ), - "antihelp_on": ( - "🐺 AntiHelp is now on in" - " this chat" - ), - "antihelp_off": ( - "🐺 AntiHelp is now" - " off in" - " this chat" - ), - "antiraid_on": ( - "🚪 AntiRaid is now on" - " in this chat\nAction: {}" - ), - "antiraid_off": ( - "🚪 AntiRaid is now off" - " in this chat" - ), - "bnd_on": ( - "💬 Block-Non-Discussion" - " is now on in this chat\nAction: {}" - ), - "bnd_off": ( - "💬 Block-Non-Discussion" - " is now off in this chat" - ), - "antiraid": ( - "🚪 AntiRaid is On." - " I {}" - ' {} in chat {}' - ), - "antichannel_on": ( - "📯 AntiChannel is now on" - " in this chat" - ), - "antichannel_off": ( - "📯 AntiChannel is" - " now off" - " in this chat" - ), - "report_on": ( - "📣 Report is now on in" - " this chat" - ), - "report_off": ( - "📣 Report is now off in" - " this chat" - ), - "antiflood_on": ( - " AntiFlood is now on in" - " this chat\nAction: {}" - ), - "antiflood_off": ( - " AntiFlood is now off" - " in this chat" - ), - "antispoiler_on": ( - "👻 AntiSpoiler is now on" - " in this chat" - ), - "antispoiler_off": ( - "👻 AntiSpoiler is" - " now off" - " in this chat" - ), - "antigif_on": ( - "🎑 AntiGIF is now on in" - " this chat" - ), - "antigif_off": ( - "🎑 AntiGIF is now off in" - " this chat" - ), - "antiservice_on": ( - "⚙️ AntiService is now on" - " in this chat" - ), - "antiservice_off": ( - "⚙️ AntiService is now" - " off in this chat" - ), - "banninja_on": ( - "🥷 BanNinja is now on in" - " this chat" - ), - "banninja_off": ( - "🥷 BanNinja is now" - " off in" - " this chat" - ), - "antiexplicit_on": ( - "🤬 AntiExplicit is" - " now on" - " in this chat\nAction: {}" - ), - "antiexplicit_off": ( - "🤬 AntiExplicit is now" - " off in this chat" - ), - "captcha_on": ( - "🚥 Captcha is now on in" - " this chat\nAction: {}" - ), - "captcha_off": ( - "🚥 Captcha is now off in" - " this chat" - ), - "cas_on": ( - "🛡 CAS is now on in this" - " chat\nAction: {}" - ), - "cas_off": ( - "🛡 CAS is now off in this" - " chat" - ), - "antinsfw_on": ( - "🔞 AntiNSFW is now on in" - " this chat\nAction: {}" - ), - "antinsfw_off": ( - "🔞 AntiNSFW is now" - " off in" - " this chat" - ), - "arabic_nickname": ( - '🇵🇸 {}' - " has hieroglyphics in his nickname.\n👊 Action: I {}" - ), - "zalgo": ( - '🌀 {}' - " has ZALGO in his nickname.\n👊 Action: I {}" - ), - "bnd": ( - '💬 {}' - " sent a message to channel comments without being chat member.\n👊 Action:" - " I {}" - ), - "cas": ( - '🛡 {}' - " appears to be in Combat Anti Spam database.\n👊 Action: I {}" - ), - "stick": ( - "🎨 {} is' - " flooding stickers.\n👊 Action: I {}" - ), - "explicit": ( - '🤬 {}' - " sent explicit content.\n👊 Action: I {}" - ), - "destructive_stick": ( - '🚫 {}' - " sent destructive sticker.\n👊 Action: I {}" - ), - "nsfw_content": ( - '🔞 {}' - " sent NSFW content.\n👊 Action: I {}" - ), - "flood": ( - ' {} is' - " flooding.\n👊 Action: I {}" - ), - "tagall": ( - '🐵 {}' - " used TagAll.\n👊 Action: I {}" - ), - "fwarn": ( - "👮‍♀️💼 {} got' - " {}/{} federative warn\nReason: {}\n\n{}" - ), - "no_fed_warns": ( - "👮‍♀️ This federation has" - " no warns yet" - ), - "no_warns": ( - '👮‍♀️ {}' - " has no warns yet" - ), - "warns": ( - '👮‍♀️ {}' - " has {}/{} warns\n\n{}" - ), - "warns_adm_fed": ( - "👮‍♀️ Warns in this" - " federation:\n" - ), - "dwarn_fed": ( - "👮‍♀️ Forgave last" - ' federative warn of {}' - ), - "clrwarns_fed": ( - "👮‍♀️ Forgave all" - ' federative warns of {}' - ), - "warns_limit": ( - '👮‍♀️ {}' - " reached warns limit.\nAction: I {}" - ), - "welcome": ( - "👋 Now I will greet" - " people in this chat\n{}" - ), - "unwelcome": ( - "👋 Now I will not greet" - " people in this chat" - ), - "chat404": "🔓 I am not protecting this chat yet.\n", - "protections": ( - "🇵🇸" - " .AntiArab - Bans spammy arabs\n🐺 .AntiHelp -" - " Removes frequent userbot commands\n🐵 .AntiTagAll -" - " Restricts tagging all members\n👋 .Welcome - Greets" - " new members\n🚪 .AntiRaid" - " - Bans all new members\n📯 .AntiChannel -" - " Restricts writing on behalf of channels\n👻 .AntiSpoiler -" - " Restricts spoilers\n🎑" - " .AntiGIF - Restricts GIFs\n🍓 .AntiNSFW -" - " Restricts NSFW photos and stickers\n.AntiFlood -" - " Prevents flooding\n🤬" - " .AntiExplicit - Restricts explicit content\n⚙️ .AntiService -" - " Removes service messages\n🌀 .AntiZALGO -" - " Penalty for users with ZALGO in nickname\n🎨 .AntiStick -" - " Prevents stickers flood\n🚥 .Captcha -" - " Requires every new participant to complete captcha\n🛡 .CAS - Check every" - " new participant through Combat Anti Spam\n💬 .BND - Restricts" - " messages from users, which are not a participants of chat" - " (comments)\n🥷" - " .BanNinja - Automatic version of AntiRaid\n⚰️" - " .AntiLagSticks - Bans laggy stickers\n👾 Admin:" - " .ban .kick" - " .mute\n.unban .unmute - Admin" - " tools\n👮‍♀️" - " Warns: .warn .warns\n.dwarn" - " .clrwarns - Warning system\n💼 Federations:" - " .fadd .frm" - " .newfed\n.namefed .fban" - " .rmfed .feds\n.fpromote" - " .fdemote\n.fdef .fdeflist -" - " Controlling multiple chats\n🗒 Notes: .fsave" - " .fstop .fnotes - Federative notes" - ), - "not_admin": "🤷‍♂️ I'm not admin here, or don't have enough rights", - "mute": ( - '🤐 {}' - " was muted {}. Reason: {}\n\n{}" - ), - "mute_log": ( - '🤐 {}' - ' was muted {} in {}. Reason: {}\n\n{}' - ), - "ban": ( - '🔒 {}' - " was banned {}. Reason: {}\n\n{}" - ), - "ban_log": ( - '🔒 {}' - ' was banned {} in {}. Reason: {}\n\n{}' - ), - "kick": ( - '🚪 {}' - " was kicked. Reason: {}\n\n{}" - ), - "kick_log": ( - '🚪 {}' - ' was kicked in {}. Reason: {}\n\n{}' - ), - "unmuted": ( - '🎉 {}' - " was unmuted" - ), - "unmuted_log": ( - '🎉 {}' - ' was unmuted in {}' - ), - "unban": ( - '🪄 {}' - " was unbanned" - ), - "unban_log": ( - '🪄 {}' - ' was unbanned in {}' - ), - "defense": ( - "🛡 Shield for {} is now {}' - ), - "no_defense": ( - "🛡 Federative defense" - " list is empty" - ), - "defense_list": ( - "🛡 Federative defense" - " list:\n{}" - ), - "fadded": ( - "💼 Current chat added to" - ' federation "{}"' - ), - "newfed": ( - "💼 Created federation" - ' "{}"' - ), - "rmfed": ( - "💼 Removed federation" - ' "{}"' - ), - "fed404": ( - "💼 Federation not" - " found" - ), - "frem": ( - "💼 Current chat removed" - ' from federation "{}"' - ), - "f404": ( - "💼 Current chat is" - " not in" - ' federation "{}"' - ), - "fexists": ( - "💼 Current chat is" - ' already in federation "{}"' - ), - "fedexists": ( - "💼 Federation exists" - ), - "joinfed": ( - "💼 Federation joined" - ), - "namedfed": ( - "💼 Federation renamed to" - " {}" - ), - "nofed": ( - "💼 Current chat is" - " not in" - " any federation" - ), - "fban": ( - '💼 {}' - " was banned in federation {} {}\nReason: {}\n{}" - ), - "gban": ( - '🖕 {}' - " was gbanned.\nReason: {}\n\n{}" - ), - "gbanning": ( - "🖕 Gbanning {}...' - ), - "gunban": ( - '🤗 {}' - " was gunbanned.\n\n{}" - ), - "gunbanning": ( - "🤗 Gunbanning {}...' - ), - "in_n_chats": ( - "👎 Banned in {}" - " chat(-s)" - ), - "unbanned_in_n_chats": ( - "✋️ Unbanned in {}" - " chat(-s)" - ), - "fmute": ( - '💼 {}' - " muted in federation {} {}\nReason: {}\n{}" - ), - "funban": ( - '💼 {}' - " unbanned in federation {}\n" - ), - "funmute": ( - '💼 {}' - " unmuted in federation {}\n" - ), - "feds_header": ( - "💼 Federations:\n\n" - ), - "fed": ( - '💼 Federation "{}"' - " info:\n🔰 Chats:\n{}\n🔰 Channels:\n{}\n🔰" - " Admins:\n{}\n🔰 Warns: {}\n" - ), - "no_fed": ( - "💼 This chat is not in" - " any federation" - ), - "fpromoted": ( - '💼 {}' - " promoted in federation {}" - ), - "fdemoted": ( - '💼 {}' - " demoted in federation {}" - ), - "api_error": ( - "🚫 api.hikariatama.ru" - " Error!\n{}" - ), - "fsave_args": ( - "💼 Usage: .fsave" - " shortname <reply>" - ), - "fstop_args": ( - "💼 Usage: .fstop" - " shortname" - ), - "fsave": ( - "💼 Federative note" - " {} saved!" - ), - "fstop": ( - "💼 Federative note" - " {} removed!" - ), - "fnotes": ( - "💼 Federative" - " notes:\n{}" - ), - "usage": "ℹ️ Usage: .{} <on/off>", - "chat_only": "ℹ️ This command is for chats only", - "version": ( - "🎢 {}\n\n🤘 Author:" - " t.me/hikariatama\n☺️" - " Downloaded from @hikarimods\n{}" - ), - "error": ( - "💀 HikariChat Issued" - " error" - ), - "reported": ( - '💼 {}' - " reported this message to admins\nReason: {}" - ), - "no_federations": ( - "💼 You have no active" - " federations" - ), - "clrallwarns_fed": ( - "👮‍♀️ Forgave all" - " federative warns of federation" - ), - "cleaning": ( - "🫥 Looking for Deleted" - " accounts..." - ), - "deleted": ( - "🫥 Removed {} Deleted" - " accounts" - ), - "fcleaning": ( - "🫥 Looking for Deleted" - " accounts in federation..." - ), - "btn_unban": "🔓 Unban (ADM)", - "btn_unmute": "🔈 Unmute (ADM)", - "btn_unwarn": "♻️ De-Warn (ADM)", - "inline_unbanned": ( - '🔓 {} unbanned by {}' - ), - "inline_unmuted": ( - '🔈 {} unmuted by {}' - ), - "inline_unwarned": ( - '♻️ Forgave last warn of {} by {}' - ), - "inline_funbanned": ( - '🔓 {} unbanned in federation by {}' - ), - "inline_funmuted": ( - '🔈 {} unmuted in federation by {}' - ), - "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": ( - '💼 {}' - ' banned in federation by {}' - ), - "inline_muted": '🙊 {} muted by {}', - "inline_banned": ( - '🔒 {}' - ' banned by {}' - ), - "inline_deleted": '🗑 Deleted by {}', - "sync": "🔄 Syncing chats and feds with server in force mode...", - "sync_complete": "😌 Successfully synced", - "rename_noargs": ( - "🚫 Specify new" - " federation" - " name" - ), - "rename_success": '😇 Federation renamed to "{}"', - "suffix_removed": "📼 Punishment suffix removed", - "suffix_updated": "📼 New punishment suffix saved\n\n{}", - "processing_myrights": "😌 Processing chats", - "logchat_removed": "📲 Log chat disabled", - "logchat_invalid": ( - "🚫 Log chat invalid" - ), - "logchat_set": "📲 Log chat updated to {}", - "clnraid_args": ( - "🥷 Example usage:" - " .clnraid 10" - ), - "clnraid_admin": ( - "🥷 Error occured while" - " promoting cleaner. Please, ensure you have enough rights in chat" - ), - "clnraid_started": ( - "🥷 RaidCleaner is in" - " progress... Found {} users to kick..." - ), - "clnraid_confirm": ( - "🥷 Please, confirm that" - " you want to start RaidCleaner on {} users" - ), - "clnraid_yes": "🥷 Start", - "clnraid_cancel": "🔻 Cancel", - "clnraid_stop": "🚨 Stop", - "clnraid_complete": ( - "🥷 RaidCleaner complete!" - " Removed: {} user(-s)" - ), - "clnraid_cancelled": ( - "🥷 RaidCleaner" - " cancelled." - " Removed: {} user(-s)" - ), - "smart_anti_raid_active": ( - "🥷 BanNinja is working" - " hard to prevent intrusion to this chat.\n\n{}Deleted {}" - " bot(-s)" - ), - "smart_anti_raid_off": "🚨 Stop", - "smart_anti_raid_stopped": ( - "🥷 BanNinja Stopped" - ), - "banninja_report": ( - "🥷 BanNinja has done his" - " job.\nDeleted {} bot(-s)\n\n🏹 «BanNinja can handle any" - " size" - " of attack» © @hikariatama" - ), - "forbid_messages": ( - "⚠️ I've forbidden sending messages until attack is fully" - " released\n\n" - ), - "confirm_rmfed": ( - "⚠️ Warning! This operation can't be reverted! Are you sure, " - "you want to delete federation {}?" - ), - "confirm_rmfed_btn": "🗑 Delete", - "decline_rmfed_btn": "🔻 Cancel", - "pil_unavailable": ( - "🚫 Pillow package" - " unavailable" - ), - "action": "", - "configure": "Configure", - "toggle": "Toggle", - "no_protects": ( - "🚫 This chat has no" - " active protections to show" - ), - "from_where": ( - "🚫 Reply to a message to" - " purge from" - ), - "no_notes": ( - "🚫 No notes found" - ), - "complete_captcha": ( - "🚥 {}, please, complete captcha within 5' - " minutes" - ), - "captcha_timeout": ( - '🚥 {}' - " have not completed captcha in time.\n👊 Action: I {}" - ), - "captcha_failed": ( - '🚥 {}' - " failed captcha.\n👊 Action: I {}" - ), - "fdef403": ( - "🛡 You can't {} this" - " user, because he is under federative protection" - ), - } - - strings_ru = { - "complete_captcha": ( - "🚥 {}, пожалуйста, пройди капчу в течение 5' - " минут" - ), - "captcha_timeout": ( - "🚥 {} не' - " прошел капчу вовремя.\n👊 Действие: {}" - ), - "captcha_failed": ( - "🚥 {} не' - " прошел капчу.\n👊 Действие: {}" - ), - "cas_on": ( - "🛡 CAS теперь включен в" - " этом чате\nДействие: {}" - ), - "cas_off": ( - "🛡 CAS теперь выключен в" - " этом чате" - ), - "cas": ( - '🛡 {}' - " appears to be in Combat Anti Spam database.\n👊 Action: I {}" - ), - "from_where": ( - "🚫 Ответь на сообщение," - " начиная с которого надо удалить." - ), - "smart_anti_raid_active": ( - "🥷 BanNinja работает в" - " поте лица, отбивая атаку на этот чат.\n\n{}Удалено {} бот(-ов)" - ), - "forbid_messages": ( - "⚠️ Я запретил отправку сообщений, пока атака не будет полностью" - " отражена\n\n" - ), - "smart_anti_raid_off": "🚨 Остановить", - "smart_anti_raid_stopped": ( - "🥷 BanNinja" - " остановлен" - ), - "error": "😵 Произошла ошибка HikariChat", - "args": ( - "🚫 Неверные" - " аргументы" - ), - "no_reason": "Не указана", - "antitagall_on": ( - "🐵 AntiTagAll теперь" - " включен в этом чате\nДействие: {}" - ), - "antitagall_off": ( - "🐵 AntiTagAll теперь" - " выключен в этом чате" - ), - "antiarab_on": ( - "🇵🇸 AntiArab теперь" - " включен в этом чате\nДействие: {}" - ), - "antiarab_off": ( - "🇵🇸 AntiArab теперь" - " выключен в этом чате" - ), - "antizalgo_on": ( - "🌀 AntiZALGO теперь" - " включен в этом чате\nДействие: {}" - ), - "antizalgo_off": ( - "🌀 AntiZALGO теперь" - " выключен в этом чате" - ), - "antistick_on": ( - "🎨 AntiStick теперь" - " включен в этом чате\nДействие: {}" - ), - "antistick_off": ( - "🎨 AntiStick теперь" - " выключен в этом чате" - ), - "antihelp_on": ( - "🐺 AntiHelp теперь" - " включен в этом чате" - ), - "antihelp_off": ( - "🐺 AntiHelp теперь" - " выключен в этом чате" - ), - "antiraid_on": ( - "🚪 AntiRaid теперь" - " включен в этом чате\nДействие: {}" - ), - "antiraid_off": ( - "🚪 AntiRaid теперь" - " выключен в этом чате" - ), - "bnd_on": ( - "💬 Block-Non-Discussion" - " теперь включен в этом чате\nДействие: {}" - ), - "bnd_off": ( - "💬 Block-Non-Discussion" - " теперь выключен в этом чате" - ), - "antichannel_on": ( - "📯 AntiChannel теперь" - " включен в этом чате" - ), - "antichannel_off": ( - "📯 AntiChannel теперь" - " выключен в этом чате" - ), - "report_on": ( - "📣 Report теперь включен" - " в этом чате" - ), - "report_off": ( - "📣 Report теперь" - " выключен" - " в этом чате" - ), - "antiflood_on": ( - " AntiFlood теперь" - " включен в этом чате\nДействие: {}" - ), - "antiflood_off": ( - " AntiFlood теперь" - " выключен в этом чате" - ), - "antispoiler_on": ( - "👻 AntiSpoiler теперь" - " включен в этом чате" - ), - "antispoiler_off": ( - "👻 AntiSpoiler теперь" - " выключен в этом чате" - ), - "antigif_on": ( - "🎑 AntiGIF теперь" - " включен" - " в этом чате" - ), - "antigif_off": ( - "🎑 AntiGIF теперь" - " выключен в этом чате" - ), - "antiservice_on": ( - "⚙️ AntiService теперь" - " включен в этом чате" - ), - "antiservice_off": ( - "⚙️ AntiService теперь" - " выключен в этом чате" - ), - "banninja_on": ( - "🥷 BanNinja теперь" - " включен в этом чате" - ), - "banninja_off": ( - "🥷 BanNinja теперь" - " выключен в этом чате" - ), - "antiexplicit_on": ( - "🤬 AntiExplicit теперь" - " включен в этом чате\nДействие: {}" - ), - "antiexplicit_off": ( - "🤬 AntiExplicit теперь" - " выключен в этом чате" - ), - "antinsfw_on": ( - "🔞 AntiNSFW теперь" - " включен в этом чате\nДействие: {}" - ), - "antinsfw_off": ( - "🔞 AntiNSFW теперь" - " выключен в этом чате" - ), - "captcha_on": ( - "🚥 Captcha теперь" - " включена в этом чате\nДействие: {}" - ), - "captcha_off": ( - "🚥 Captcha теперь" - " выключена в этом чате" - ), - "no_fed_warns": ( - "👮‍♀️ This federation has" - " no warns yet" - ), - "warns_adm_fed": ( - "👮‍♀️ Warns in this" - " federation:\n" - ), - "welcome": ( - "👋 Теперь я буду" - " приветствовать людей в этом чате\n{}" - ), - "unwelcome": ( - "👋 Я больше не буду" - " приветствовать людей в этом чате" - ), - "chat404": "🔓 Этот чат еще не защищен.\n", - "not_admin": "🤷‍♂️ Я здесь не админ, или у меня недостаточно прав", - "no_defense": ( - "🛡 Федеративный список" - " защиты пуст" - ), - "defense_list": ( - "🛡 Федеративный список" - " защиты:\n{}" - ), - "fed404": ( - "💼 Федерация не" - " найдена" - ), - "fedexists": ( - "💼 Федерация" - " существует" - ), - "joinfed": ( - "💼 Присоединился к" - " федерации" - ), - "namedfed": ( - "💼 Федерация" - " переименована в {}" - ), - "nofed": ( - "💼 Этот чат не находится" - " ни в одной из федераций" - ), - "feds_header": ( - "💼 Федерации:\n\n" - ), - "no_fed": ( - "💼 Этот чат не находится" - " ни в одной из федераций" - ), - "api_error": ( - "🚫 Ошибка" - " api.hikariatama.ru!\n{}" - ), - "fsave_args": ( - "💼 Пример: .fsave" - " shortname <reply>" - ), - "fstop_args": ( - "💼 Пример: .fstop" - " shortname" - ), - "fsave": ( - "💼 Федеративная заметка" - " {} сохранена!" - ), - "fstop": ( - "💼 Федеративная заметка" - " {} удалена!" - ), - "fnotes": ( - "💼 Федеративные" - " заметки:\n{}" - ), - "usage": "ℹ️ Пример: .{} <on/off>", - "chat_only": "ℹ️ Эта команда предназначена для чатов", - "no_federations": ( - "💼 Нет активных" - " федераций" - ), - "clrallwarns_fed": ( - "👮‍♀️ Прощены все" - " предупреждения в федерации" - ), - "cleaning": ( - "🫥 Поиск удаленных" - " аккаунтов..." - ), - "deleted": ( - "🫥 Удалено {} удаленных" - " аккаунтов" - ), - "fcleaning": ( - "🫥 Поиск удаленных" - " аккаунтов в федерации..." - ), - "btn_unban": "🔓 Разбанить (админ)", - "btn_unmute": "🔈 Размутить (админ)", - "btn_unwarn": "♻️ Удалить предупреждение (админ)", - "btn_funmute": "🔈 Размутить в федерации (админ)", - "btn_funban": "🔓 Разбанить в федерации (админ)", - "btn_mute": "🙊 Мут", - "btn_ban": "🔒 Бан", - "btn_fban": "💼 Фед. бан", - "btn_del": "🗑 Удалить", - "sync": ( - "🔄 Принудительная синхронизация федераций и чатов с сервером..." - ), - "sync_complete": "😌 Сихнронизирован", - "rename_noargs": ( - "🚫 Укажи имя" - " федерации" - ), - "suffix_removed": "📼 Суффикс предупреждения удален", - "suffix_updated": "📼 Установлен новый суффикс предупреждения\n\n{}", - "processing_myrights": "😌 Обработка чатов", - "logchat_removed": "📲 Логирование отключено", - "logchat_invalid": ( - "🚫 Неверный чат" - " логирования" - ), - "logchat_set": "📲 Чат логирования установлен на {}", - "clnraid_args": ( - "🥷 Пример:" - " .clnraid 10" - ), - "clnraid_admin": ( - "🥷 Ошибка выдачи прав" - " боту. Убедись, что у тебя достаточно прав" - ), - "clnraid_started": ( - "🥷 RaidCleaner" - " активен..." - " Найдено {} пользователей для бана..." - ), - "clnraid_confirm": ( - "🥷 Подтвердите запуск" - " RaidCleaner на {} пользователях" - ), - "clnraid_yes": "🥷 Начать", - "banninja_report": ( - "🥷 BanNinja закончил" - " работу.\nУдалено {} бот(-ов)\n\n🏹 «BanNinja can handle any" - " size of attack» © @hikariatama" - ), - "clnraid_cancel": "🔻 Отмена", - "clnraid_stop": "🚨 Остановить", - "clnraid_complete": ( - "🥷 RaidCleaner закончил" - " работу! Удалено: {} бот(-ов)" - ), - "clnraid_cancelled": ( - "🥷 RaidCleaner" - " остановлен. Удалено: {} бот(-ов)" - ), - "confirm_rmfed_btn": "🗑 Удалить", - "decline_rmfed_btn": "🔻 Отмена", - "pil_unavailable": ( - "🚫 Библиотека Pillow" - " недоступна" - ), - "_cmd_doc_version": "Получить информацию о модуле", - "_cmd_doc_deleted": "Очистка удалнных аккаунтов в чате", - "_cmd_doc_fclean": "Очистка удаленных аккаунтов в федерации", - "_cmd_doc_newfed": " <имя> - Создать новую федерацию", - "_cmd_doc_rmfed": " - Удалить федерацию", - "_cmd_doc_fpromote": "<пользователь> - Выдать пользователю права в федерации", - "_cmd_doc_fdemote": ( - " <пользователь> - Забрать у пользователя права в федерации" - ), - "_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": " - Информация о федерации", - "_cmd_doc_pchat": "Показать защиты в чате", - "_cmd_doc_warn": "<пользователь> - Предупредить пользователя", - "_cmd_doc_warns": ( - "[пользователь] - Показать предупреждения в чате \\ у пользователя" - ), - "_cmd_doc_delwarn": "<пользователь> - Простить последнее предупреждение", - "_cmd_doc_clrwarns": ( - "<пользователь> - Простить все предупреждения пользователя" - ), - "_cmd_doc_clrallwarns": "Простить все предупреждения в федерации", - "_cmd_doc_welcome": " - Изменить текст приветствовия", - "_cmd_doc_fdef": ( - "<пользователь> - Включить\\выключить федеративную защиту пользователя" - ), - "_cmd_doc_fsave": " - Сохранить федеративную заметку", - "_cmd_doc_fstop": " - Удалить федеративную заметку", - "_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": ( - "💼 Федерация" - ' "{}":\n🔰' - " Чаты:\n{}\n🔰 Каналы:\n{}\n🔰" - " Админы:\n{}\n🔰 Предупреждения: {}\n" - ), - "confirm_rmfed": ( - "⚠️ Внимание! Это действие нельзя отменить! Ты уверен, что хочешь" - " удалить федерацию {}?" - ), - "_cls_doc": "Must-have модуль администратора чата", - "no_notes": ( - "🚫 Нет заметок" - ), - } - - 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"🪆 HikariChat protection\n{get_full_name(cinfo)}\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" {style}: {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💼 {fed['name']}" - 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"🌁 {get_full_name(cinfo)}:" - f" {PROTECTS[protection]} (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 "" - ) - - 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 += [ - "👥 {utils.escape_html(chat.title)}' - 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 += [ - "📣 {utils.escape_html(channel.title)}' - f" - {kicked}" - ] - except ChatAdminRequiredError: - pass - - await utils.answer( - message, - self.strings("deleted").format(overall) - + "\n\n" - + "\n".join(cleaned_in) - + "" - + "\n\n" - + "\n".join(cleaned_in_c) - + "", - ) - - @error_handler - @chat_command - async def newfedcmd(self, message: Message): - """ - 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): - """ - 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): - """ - 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): - """ - 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): - """ - 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): - """ [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 += '▫️ {}\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): - """ [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 += '▫️ {}\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): - """ [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'{get_full_name(chat)}' - ] - 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" - + "\n".join(banned_in) - + "" - ) - - 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, - "", - ) - + "" - + "\n".join(banned_in) - + "", - 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): - """ [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"], - ) - + "" - + "\n".join(unbanned_in) - + "" - ) - - 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): - """ [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'{get_full_name(chat)}' - ] - 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" - + "\n".join(muted_in) - + "" - ) - - 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" - + "\n".join(muted_in) - + "", - 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): - """ [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" - + "\n".join(unbanned_in) - + "" - ) - - 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): - """ [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): - """ [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): - """ [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): - """ - 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): - """ - 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" ☮️ {config['name']} ({shortname})" - 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 - {c.title}" - ) - - res += ( - "\n 👮‍♀️" - f" {len(config.get('warns', []))} warns\n\n" - ) - - await utils.answer(message, res) - - @error_handler - @chat_command - async def fedcmd(self, message: Message): - """ - 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 = ( - " 🧃 online" - if isinstance(getattr(user, "status", None), UserStatusOnline) - else "" - ) - admins += ( - f' 👤 {name}{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 += ( - " 📣 {utils.escape_html(channel.title)}\n' - ) - except Exception: - pass - - chats += ( - " 🫂 {utils.escape_html(c.title)}\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): - """ - 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 += ( - "🛑 " - + warn - + ( - f" [x{warns[str(usid)].count(warn)}]" - if warns[str(usid)].count(warn) > 1 - else "" - ) - + "\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 += ( - "🐺 ' - + name - + "\n" - ) - processed = [] - for warn in _warns.copy(): - if warn in processed: - continue - processed += [warn] - res += ( - " 🏴󠁧󠁢󠁥󠁮󠁧󠁿 " - + warn - + ( - f" [x{_warns.count(warn)}]" - if _warns.count(warn) > 1 - else "" - ) - + "\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): - """ - 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): - """ - 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): - """ - 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): - """ - 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): - """ - 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): - """ - 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'{cache[int(note["creator"])]}' - ) - if key not in res: - res[key] = "" - res[key] += f" {shortname}\n" - except Exception: - key = "unknown" - if key not in res: - res[key] = "" - res[key] += f" {shortname}\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' 🇻🇦 {tit}\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): - """ - 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("🪄 Loading captcha...")), - 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'{get_full_name(user)}', - ), - 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 "", - }, - }, - 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, - "🌘 Reporting message to admins...", - 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})?(? 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"{self.get_prefix()}fnotes" - ), - 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/C0dwiz/H.Modules/animals.py b/C0dwiz/H.Modules/animals.py deleted file mode 100644 index fabc690..0000000 --- a/C0dwiz/H.Modules/animals.py +++ /dev/null @@ -1,97 +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 requests - -from .. import loader, utils - - -@loader.tds -class animals(loader.Module): - """Random cats and dogs""" - - strings = { - "name": "animals", - "loading": "Generation is underway", - "done": "Here is your salute", - } - - strings_ru = { - "loading": "Генерация идет полным ходом", - "done": "Вот ваш результат", - } - - # 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("thecat") - 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("thecat") - 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/C0dwiz/H.Modules/face.py b/C0dwiz/H.Modules/face.py deleted file mode 100644 index 359d2aa..0000000 --- a/C0dwiz/H.Modules/face.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: face -# Description: Random face -# Author: @hikka_mods -# --------------------------------------------------------------------------------- -# meta developer: @hikka_mods -# scope: Api face -# scope: Api face 0.0.1 -# requires: aiohttp -# --------------------------------------------------------------------------------- - -import aiohttp - -from .. import loader, utils - - -@loader.tds -class face(loader.Module): - """random face""" - - strings = { - "name": "face", - "loading": ( - "🔍 I'm looking for you kaomoji" - ), - "random_face": ( - "🗿 Here is your random one kaomoji\n{}" - ), - "error": "An error has occurred!", - } - - strings_ru = { - "loading": ( - "🔍 Ищю вам kaomoji" - ), - "random_face": ( - "🗿 Вот ваш рандомный kaomoji\n{}" - ), - "error": "Произошла ошибка!", - } - - @loader.command( - ru_doc="Рандом kaomoji", - en_doc="Random kaomoji", - ) - async def rfacecmd(self, message): - await utils.answer(message, self.strings("loading")) - - url = "https://vsecoder.dev/api/faces" - - async with aiohttp.ClientSession() as session: - async with session.get(url) as response: - if response.status == 200: - data = await response.json() - random_face = data["data"] - await utils.answer(message, self.strings("random_face").format(random_face)) - else: - await utils.answer(message, self.strings("error")) \ No newline at end of file diff --git a/C0dwiz/H.Modules/full.txt b/C0dwiz/H.Modules/full.txt deleted file mode 100644 index a2d3dfc..0000000 --- a/C0dwiz/H.Modules/full.txt +++ /dev/null @@ -1,46 +0,0 @@ -AccountData -AniLibria -animals -AnimeQuotes -Article -ASCIIArt -AutofarmCookies -BirthdayTime -CheckSpamBan -CryptoCurrency -EnvsSH -face -FakeActions -FakeWallet -full -GigaChat -globalrestrict -hikkahost -InlineButton -InlineCoin -InlineHelper -IrisSimpleMod -jacques -KBSwapper -Memes -Music -novoice -nsfwart -numbersapi -PastebinAPI -profile -ReplaceVowels -SafetyMod -search -shortener -SMAcrhiver -TelegramStatusCodes -TempChat -Text2File -Text_Sticker -TikTokDownloader -Video2GIF -VirusTotal -VoiceDL -HAFK -Weather \ No newline at end of file diff --git a/C0dwiz/H.Modules/globalrestrict.py b/C0dwiz/H.Modules/globalrestrict.py deleted file mode 100644 index 91fdaac..0000000 --- a/C0dwiz/H.Modules/globalrestrict.py +++ /dev/null @@ -1,681 +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 re -import time -import typing - -from telethon.tl.types import ( - Channel, - Chat, - Message, - User, -) - -from .. import loader, utils - -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": ( - "🚫 Incorrect arguments" - ), - "glban": ( - '🖕 {}' - " has been globally banned.\nReason: {}\n\n{}" - ), - "glbanning": ( - "🖕 Globally banning {}...' - ), - "gunban": ( - '🤗 {}' - " has been globally unbanned.\n\n{}" - ), - "gunbanning": ( - "🤗 Global unbanning {}...' - ), - "in_n_chats": ( - "👎 Banned in {}" - " chat(s)" - ), - "unbanned_in_n_chats": ( - "✋️ Unbanned in {}" - " chat(s)" - ), - "glmute": ( - '🖕 {}' - " has been globally muted.\nReason: {}\n\n{}" - ), - "glmutes": ( - "🖕 Global mute {}...' - ), - "gunmute": ( - '🤗 {}' - " has been globally unmuted.\n\n{}" - ), - "gunmutes": ( - "🤗 Global unmute {}...' - ), - "in_m_chats": ( - "👎 Muted in {}" - " chat(s)" - ), - "unmute_in_n_chats": ( - "✋️ Unmuted in {}" - " chat(s)" - ), - } - - strings_ru = { - "no_reason": "Не указана", - "args": ( - "🚫 Неверные" - " аргументы" - ), - "glban": ( - '🖕 {}' - " был гзабанен.\nПричина: {}\n\n{}" - ), - "glbanning": ( - "🖕 Гбан {}...' - ), - "gunban": ( - '🤗 {}' - " был гразбанен.\n\n{}" - ), - "gunbanning": ( - "🤗 Гразбан {}...' - ), - "in_n_chats": ( - "👎 Забанил в {}" - " чат(-ах)" - ), - "unbanned_in_n_chats": ( - "✋️ Разбанил in {}" - " чат(-ах)" - ), - "glmute": ( - '🖕 {}' - " был замучен.\nПричина: {}\n\n{}" - ), - "glmutes": ( - "🖕 Гмут {}...' - ), - "gunmute": ( - '🤗 {}' - " был размучен.\n\n{}" - ), - "gunmutes": ( - "🤗 Гразмут {}...' - ), - "in_m_chats": ( - "👎 Мут в {} чат(-ах)" - ), - "unmute_in_n_chats": ( - "✋️ Размут in {}" - " чат(-ах)" - ), - } - - 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=" [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 += '▫️ {}\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=" [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 += '▫️ {}\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=" [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 += '▫️ {}\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=" [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 += '▫️ {}\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("unmutes_in_n_chats").format(counter) - if silent - else chats - ), - ), - ) diff --git a/C0dwiz/H.Modules/hikkahost.py b/C0dwiz/H.Modules/hikkahost.py deleted file mode 100644 index c9ccf6c..0000000 --- a/C0dwiz/H.Modules/hikkahost.py +++ /dev/null @@ -1,314 +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 aiohttp -import json -from datetime import datetime, timedelta, timezone - -from .. import loader, utils - - -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 = "http://158.160.84.24:5000" + path - async with aiohttp.ClientSession(trust_env=True) as session: - async with session.request( - method, - url, - headers={ - "Content-Type": "application/json", - "token": self.token, - }, - ssl=False, - ) 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": ( - "👤 Information panel\n\n" - "🆔 Server ID: {server_id}\n" - "🔑 ID: {id}\n" - "📶 Status: {status}\n" - "⌛️ Subscription ends: {end_dates} | {days_end} days\n\n" - "⚙️ CPU: {cpu_percent} %\n" - "💾 RAM: {memory} / {max_ram} MB {ram_percent} %" - ), - "logs": ( - "🌘 Here are your logs" - ), - "restart": ( - " Restart request sent\n" - "This message remains unchanged after the restart" - ), - "loading_info": "⌛️ Loading...", - "no_apikey": "🚫 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": ( - "👤 Панель информации\n\n" - "🆔 Server ID: {server_id}\n" - "🔑 ID: {id}\n" - "📶 Статус: {status}\n" - "⌛️ Подписка закончится: {end_dates} | {days_end} дней\n\n" - "⚙️ CPU: {cpu_percent} %\n" - "💾 RAM: {memory} / {max_ram} MB {ram_percent} %" - ), - "logs": ( - "🌘 Вот ваши логи" - ), - "restart": ( - " Запрос на рестарт отправил\n" - "Это сообщение не изменяется после рестарта" - ), - "loading_info": "⌛️ Загрузка...", - "no_apikey": "🚫 Вы не указали 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, token) - - files_log = data["logs"] - - with open("log.txt", "w") as log_file: - json.dump(files_log, log_file) - - await utils.answer_file(message, "log.txt", self.strings("logs")) - - @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) - - data = await api.action(user_id, token) diff --git a/C0dwiz/H.Modules/index.html b/C0dwiz/H.Modules/index.html deleted file mode 100644 index 9e8966f..0000000 --- a/C0dwiz/H.Modules/index.html +++ /dev/null @@ -1,67 +0,0 @@ - - - - - -H:Mods - - - - - - diff --git a/C0dwiz/H.Modules/jacques.py b/C0dwiz/H.Modules/jacques.py deleted file mode 100644 index 0427468..0000000 --- a/C0dwiz/H.Modules/jacques.py +++ /dev/null @@ -1,109 +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 aiohttp -import io -from PIL import Image, ImageDraw, ImageFont -from textwrap import wrap - -from .. import loader, utils - - - -@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.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"]), - ), - ) - - @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 - - async with aiohttp.ClientSession() as 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_size = draw.multiline_textsize(wrapped_text, font=font) - 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() \ No newline at end of file diff --git a/C0dwiz/H.Modules/novoice.py b/C0dwiz/H.Modules/novoice.py deleted file mode 100644 index ddaa322..0000000 --- a/C0dwiz/H.Modules/novoice.py +++ /dev/null @@ -1,159 +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: NoVoice -# Description: A module for prohibiting the sending of voice and video messages -# Author: @hikka_mods -# --------------------------------------------------------------------------------- -# meta developer: @hikka_mods -# scope: NoVoice -# scope: NoVoice 0.0.1 -# --------------------------------------------------------------------------------- - -import logging - -from telethon.tl.custom import Message - -from .. import loader, utils - -logger = logging.INFO(__name__) - -@loader.tds -class NoVoiceMod(loader.Module): - """A module for prohibiting the sending of voice and video messages""" - - strings = { - "name": "NoVoice", - "novoice_true": "❌ Voice messages are disabled for all users!", - "novoice_false": "✅ Voice messages are allowed for all users again!", - "novoice_no_args": "Usage: .novoice [on/off]", - "novoiceuser_no_reply": "Usage: .novoiceuser [username/reply]", - "novoiceuser_true": "❌ User {user_id} is now forbidden to send voice messages!", - "novoicerm_no_reply": "Usage: .novoicerm [username/reply]", - "novoicerm_yes": "✅ User {user_id} is now allowed to send voice messages again!", - "novoicerm_no": "⚠️ User {user_id} not found in the banned list.", - "text": "❌ I do not accept voice messages!", - } - - strings_ru = { - "novoice_true": "❌ Голосовые сообщения отключены для всех пользователей!", - "novoice_false": "✅ Голосовые сообщения снова разрешены для всех пользователей!", - "novoice_no_args": "Использование: .novoice [on/off]", - "novoiceuser_no_reply": "Использование: .novoiceuser [username/reply]", - "novoiceuser_true": "❌ Пользователю {user_id} запрещено отправлять голосовые сообщения!", - "novoicerm_no_reply": "Использование: .novoicerm [username/reply]", - "novoicerm_yes": "✅ Пользователю {user_id} снова разрешено отправлять голосовые сообщения!", - "novoicerm_no": "⚠ Пользователь {user_id} не найден в списке запрещенных.", - "text": "❌ Я не принимаю голосовые сообщения!", - } - - async def client_ready(self, client, db): - self.client = client - self.db = db - self.novoice_global = self.db.get("NoVoice", "global", False) - self.banned_users = self.db.get("NoVoice", "banned_users", {}) - - @loader.command( - ru_doc="[on/off] — запрещает/разрешает всем пользователям отправку голосовых и видеосообщений.", - en_doc="[on/off] — prohibits/allows all users to send voice and video messages.", - ) - async def novoice(self, message): - args = utils.get_args_raw(message) - if args == "on": - self.novoice_global = True - self.db.set("NoVoice", "global", self.novoice_global) - await utils.answer(message, self.strings("novoice_true")) - elif args == "off": - self.novoice_global = False - self.db.set("NoVoice", "global", self.novoice_global) - await utils.answer(message, self.strings("novoice_false")) - else: - await utils.answer(message, self.strings("novoice_no_args")) - - @loader.command( - ru_doc="[username/reply] — запрещает пользователю отправку голосовых и видеосообщений.", - en_doc="[username/reply] — prohibits the user from sending voice and video messages.", - ) - async def novoiceuser(self, message): - args = utils.get_args_raw(message) - reply = await message.get_reply_message() - if not args and not reply: - return await utils.answer(message, self.strings("novoiceuser_no_reply")) - - if reply: - user_id = reply.from_id - else: - user = await self.client.get_entity(args) - user_id = user.id - - self.banned_users[user_id] = True - self.db.set("NoVoice", "banned_users", self.banned_users) - await utils.answer( - message, self.strings("novoiceuser_true").format(user_id=user_id) - ) - - @loader.command( - ru_doc="[username/reply] — разрешает пользователю отправку голосовых и видеосообщений.", - en_doc="[username/reply] — allows the user to send voice and video messages.", - ) - async def novoicerm(self, message): - args = utils.get_args_raw(message) - reply = await message.get_reply_message() - - if not args and not reply: - return await utils.answer(message, self.strings("novoicerm_no_reply")) - - user_id = None - if reply: - user_id = reply.sender_id - else: - try: - user = await self.client.get_entity(args) - user_id = user.id - except Exception as e: - logger.error(f"Failed to get entity for {args}: {e}") - - if user_id in self.banned_users: - del self.banned_users[user_id] - self.db.set("NoVoice", "banned_users", self.banned_users) - await utils.answer( - message, self.strings("novoicerm_yes").format(user_id=user_id) - ) - else: - await utils.answer( - message, self.strings("novoicerm_no").format(user_id=user_id) - ) - - async def watcher(self, message: Message): - """Обрабатывает входящие сообщения""" - if ( - isinstance(message, Message) - and not message.out - and message.is_private - and (self.novoice_global or message.sender_id in self.banned_users) - and (message.voice or message.video_note) - ): - await message.delete() - await utils.answer(message, self.strings("text")) - - logger.debug( - "Deleted voice/video message from user %s in chat %s", - message.sender_id, - message.chat_id, - ) diff --git a/C0dwiz/H.Modules/nsfwart.py b/C0dwiz/H.Modules/nsfwart.py deleted file mode 100644 index 50ec97c..0000000 --- a/C0dwiz/H.Modules/nsfwart.py +++ /dev/null @@ -1,97 +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: NSFWArt -# Description: Sends cute anime nsfw-art -# Author: @hikka_mods -# --------------------------------------------------------------------------------- -# meta developer: @hikka_mods -# scope: Api NSFWArt -# scope: Api NSFWArt 0.0.1 -# --------------------------------------------------------------------------------- - -import functools -import requests -from typing import List - -from .. import loader, utils - - -async def photos(tags: str, subreddit: str, quantity: int) -> List[str]: - ans = ( - await utils.run_sync( - requests.get, - f"https://api.lolicon.app/setu/v2?tag={tags}", - json={ - "query": ( - " query SubredditQuery( $url: String! $filter: SubredditPostFilter" - " $iterator: String ) { getSubreddit(url: $url) { children(" - f" limit: {quantity} iterator: $iterator filter: $filter" - " disabledHosts: null ) { iterator items {url subredditTitle" - " isNsfw mediaSources { url } } } } } " - ), - "variables": {"url": subreddit, "filter": None, "hostsDown": None}, - "authorization": None, - }, - ) - ).json() - - return [ans["data"][0]["urls"]["original"]] - - -@loader.tds -class NSFWArtMod(loader.Module): - """Sends cute anime nsfw-art""" - - strings = { - "name": "NSFWArt", - "sreddit404": "🚫 Subreddit not found", - } - - def __init__(self): - self.config = loader.ModuleConfig( - loader.ConfigValue( - "tags", - "drool", - lambda: "tag: masturbation, drool, completely, sleeping, yuri", - ) - ) - - @loader.command( - ru_doc="Отправьте симпатичный nsfw-арт", - en_doc="Send cute nsfw-art", - ) - async def nsfwartcmd(self, message): - tags = self.config["tags"] - subreddit = f"/v2?tag={tags}" - - ans = await utils.run_sync( - requests.get, f"https://api.lolicon.app/setu{subreddit}" - ) - if ans.status_code != 200: - await utils.answer(message, self.strings("sreddit404", message)) - return - - await self.inline.gallery( - message=message, - next_handler=functools.partial( - photos, tags, subreddit=subreddit, quantity=15 - ), - caption=f"{utils.ascii_face()}", - ) diff --git a/C0dwiz/H.Modules/numbersapi.py b/C0dwiz/H.Modules/numbersapi.py deleted file mode 100644 index adff125..0000000 --- a/C0dwiz/H.Modules/numbersapi.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: NumbersAPI -# Description: Many interesting facts about numbers. -# Author: @hikka_mods -# --------------------------------------------------------------------------------- -# meta developer: @hikka_mods -# scope: NumbersAPI -# scope: NumbersAPI 0.0.1 -# --------------------------------------------------------------------------------- - -import aiohttp -from datetime import datetime - -from .. import loader, utils - - -async def get_fact_about_number(number, fact_type): - url = f"http://numbersapi.com/{number}/{fact_type}" - - async with aiohttp.ClientSession() as session: - async with session.get(url) as response: - if response.status == 200: - return await response.text() - else: - return "Извините, не удалось получить факт." - -async def get_fact_about_date(month, day): - date_str = datetime.now().replace(month=month, day=day).strftime("%m/%d") - url = f"http://numbersapi.com/{date_str}/date" - - async with aiohttp.ClientSession() as session: - async with session.get(url) as response: - if response.status == 200: - return await response.text() - else: - return "Извините, не удалось получить факт." - - -@loader.tds -class NumbersAPI(loader.Module): - """Many interesting facts about numbers.""" - - strings = {"name": "NumbersAPI"} - - @loader.command( - ru_doc="Дает интересный факт про число или дату\nНапример: .num 10 math или .num 01.01 date", - en_doc="Gives an interesting fact about a number or date\nexample: .num 10 math or .num 01.01 date", - ) - async def num(self, message): - args = utils.get_args_raw(message).split() - - if len(args) < 2: - await utils.answer(message, "Использование: .num <число или дата> <тип>") - return - - num_or_date = args[0] - fact_type = args[1] - - if "." in num_or_date: - try: - month, day = map(int, num_or_date.split(".")) - result = await get_fact_about_date(month, day) - except ValueError: - await utils.answer(message, "Ошибка: некорректный формат даты. Используйте: месяц.день") - return - else: - try: - number = int(num_or_date) - result = await get_fact_about_number(number, fact_type) - except ValueError: - await utils.answer(message, "Ошибка: некорректный ввод числа.") - return - - await utils.answer(message, result) \ No newline at end of file diff --git a/C0dwiz/H.Modules/profile.py b/C0dwiz/H.Modules/profile.py deleted file mode 100644 index 7145ee9..0000000 --- a/C0dwiz/H.Modules/profile.py +++ /dev/null @@ -1,97 +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: Profile -# Description: This module can change your Telegram profile -# Author: @hikka_mods -# --------------------------------------------------------------------------------- -# meta developer: @hikka_mods -# scope: Profile -# scope: Profile 0.0.1 -# --------------------------------------------------------------------------------- - -from telethon.errors.rpcerrorlist import UsernameOccupiedError -from telethon.tl.functions.account import UpdateProfileRequest, UpdateUsernameRequest -from .. import loader, utils - - -@loader.tds -class ProfileEditorMod(loader.Module): - """This module can change your Telegram profile.""" - - strings = { - "name": "Profile", - "error_format": "Incorrect format of args. Try again.", - "done_name": "The new name was successfully unstalled!", - "done_bio": "The new bio was successfully unstaled!", - "done_username": "The new username was succesfully installed!", - "error_occupied": "The new username is already occupied!", - } - - strings_ru = { - "error_format": "Неправильный формат аргумента. Попробуйте еще раз.", - "done_name": "Новое имя успешно настроено!", - "done_bio": "Новое био успешно настроено!", - "done_username": "Новое имя пользователя успешно установлено!", - "error_occupied": "Новое имя пользователя уже занято!", - } - - @loader.command( - ru_doc="для того, чтобы сменить свое имя/отчество", - en_doc="for change your first/second name", - ) - async def namecmd(self, message): - args = utils.get_args_raw(message).split("/") - - if len(args) < 1 or len(args) > 2: - return await utils.answer(message, self.strings("error_format")) - - firstname = args[0] - lastname = args[1] if len(args) == 2 else "" - - await message.client( - UpdateProfileRequest(first_name=firstname, last_name=lastname) - ) - await utils.answer(message, self.strings("done_name")) - - @loader.command( - ru_doc="чтобы изменить свою биографию", - en_doc="for change your bio", - ) - async def aboutcmd(self, message): - args = utils.get_args_raw(message) - if not args: - return await utils.answer(message, self.strings("error_format")) - await message.client(UpdateProfileRequest(about=args)) - await utils.answer(message, self.strings("done_bio")) - - @loader.command( - ru_doc="для изменения вашего имени пользователя. Введите значение без '@'", - en_doc="for change your username. Enter value without '@'", - ) - async def usercmd(self, message): - """- for change your username. Enter value without "@".""" - args = utils.get_args_raw(message) - if not args: - return await utils.answer(message, self.strings("error_format")) - try: - await message.client(UpdateUsernameRequest(args)) - await utils.answer(message, self.strings("done_username")) - except UsernameOccupiedError: - await utils.answer(message, self.strings("error_occupied")) diff --git a/C0dwiz/H.Modules/search.py b/C0dwiz/H.Modules/search.py deleted file mode 100644 index a8cd885..0000000 --- a/C0dwiz/H.Modules/search.py +++ /dev/null @@ -1,204 +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: Search -# Description: Search for your question on the Internet -# Author: @hikka_mods -# --------------------------------------------------------------------------------- -# meta developer: @hikka_mods -# scope: Api Search -# scope: Api Search 0.0.1 -# --------------------------------------------------------------------------------- - -from .. import loader, utils - - -@loader.tds -class Search(loader.Module): - """Поисковик""" - - strings = { - "name": "Search", - "search": "🌎 I searched for information for you", - "isearch": "🔎 I searched for information for you ", - "link": "🗂️ Link to your request", - "close": "❌ Close", - } - - strings_ru = { - "search": "🌎 Я поискал информацию за тебя", - "isearch": "🔎 Я поискал информацию за тебя ", - "link": "🗂️ Ссылка на ваш запрос", - "close": "❌ Закрыть", - } - - @loader.command( - ru_doc="Поискать в Google", - en_doc="Search on Google", - ) - async def google(self, message): - await self.search_engine(message, "https://google.com/search?q=") - - @loader.command( - ru_doc="Поискать в Yandex", - en_doc="Search on Yandex", - ) - async def yandex(self, message): - await self.search_engine(message, "https://yandex.ru/?q=") - - @loader.command( - ru_doc="Поискать в Duckduckgo", - en_doc="Search on Duckduckgo", - ) - async def duckduckgo(self, message): - await self.search_engine(message, "https://duckduckgo.com/?q=") - - @loader.command( - ru_doc="Поискать в Bing", - en_doc="Search on Bing", - ) - async def bing(self, message): - await self.search_engine(message, "https://bing.com/?q=") - - @loader.command( - ru_doc="Поискать в You", - en_doc="Search on You", - ) - async def you(self, message): - await self.search_engine(message, "https://you.com/?q=") - - async def search_engine(self, message, base_url: str) -> None: - """Searches on a given search engine.""" - query = utils.get_args_raw(message) - search_url = f"{base_url}{query}" - await utils.answer(message, self.strings("search") + f": link") - - @loader.command( - ru_doc="Поискать в Google инлайн", - en_doc="Search on Google inline", - ) - async def igoogle(self, message): - g = utils.get_args_raw(message) - google = f"https://google.com/search?q={g}" - await self.inline.form( - text=self.strings("isearch"), - message=message, - reply_markup=[ - [ - { - "text": self.strings("link"), - "url": google, - } - ], - [{"text": self.strings("close"), "action": "close"}], - ], - silent=True, - ) - - @loader.command( - ru_doc="Поискать в Yandex инлайн", - en_doc="Search on Yandex inline", - ) - async def iyandex(self, message): - y = utils.get_args_raw(message) - yandex = f"https://yandex.ru/?q={y}" - await self.inline.form( - text=self.strings("isearch"), - message=message, - reply_markup=[ - [ - { - "text": self.strings("link"), - "url": yandex, - } - ], - [{"text": self.strings("close"), "action": "close"}], - ], - silent=True, - ) - - @loader.command( - ru_doc="Поискать в Duckduckgo инлайн", - en_doc="Search on Duckduckgo inline", - ) - async def iduckduckgo(self, message): - d = utils.get_args_raw(message) - duckduckgo = f"https://duckduckgo.com/?q={d}" - await self.inline.form( - text=self.strings("isearch"), - message=message, - reply_markup=[ - [ - { - "text": self.strings("link"), - "url": duckduckgo, - } - ], - [{"text": self.strings("close"), "action": "close"}], - ], - silent=True, - ) - - @loader.command( - ru_doc="Поискать в Bing инлайн", - en_doc="Search on Bing inline", - ) - async def ibing(self, message): - b = utils.get_args_raw(message) - bing = f"https://bing.com/?q={b}" - await self.inline.form( - text=self.strings("isearch"), - message=message, - reply_markup=[ - [ - { - "text": self.strings("link"), - "url": bing, - } - ], - [{"text": self.strings("close"), "action": "close"}], - ], - silent=True, - ) - - @loader.command( - ru_doc="Поискать в You инлайн", - en_doc="Search on You inline", - ) - async def iyou(self, message): - y = utils.get_args_raw(message) - you = f"https://you.com/?q={y}" - await self.inline.form( - text=self.strings("isearch"), - message=message, - reply_markup=[ - [ - { - "text": self.strings("link"), - "url": you, - } - ], - [{"text": self.strings("close"), "action": "close"}], - ], - silent=True, - ) - - async def close(self, call): - """Callback button""" - await call.delete() diff --git a/C0dwiz/H.Modules/shortener.py b/C0dwiz/H.Modules/shortener.py deleted file mode 100644 index 2d64c40..0000000 --- a/C0dwiz/H.Modules/shortener.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: Shortener -# Description: shortening the link -# Author: @hikka_mods -# --------------------------------------------------------------------------------- -# meta developer: @hikka_mods -# scope: Shortener -# scope: Shortener 0.0.1 -# requires: pyshorteners -# --------------------------------------------------------------------------------- - -import pyshorteners - -from .. import loader, utils - - -@loader.tds -class Shortener(loader.Module): - """Module for working with the api bit.ly""" - - strings = { - "name": "Shortener", - "no_api": " You have not specified an API token from the site bit.ly", - "statclcmd": "📊 Statistics on clicks for this link: {c}", - "shortencmd": " Your shortened link is ready: {c}", - } - - strings_ru = { - "no_api": " Вы не указали api токен с сайта bit.ly", - "statclcmd": "📊 Статистика о переходе по этой ссылке: {c}", - "shortencmd": " Ваша сокращённая ссылка готова: {c}", - } - - def __init__(self): - self.config = loader.ModuleConfig( - loader.ConfigValue( - "token", - None, - lambda: "Need a token with https://app.bitly.com/settings/api/", - validator=loader.validators.Hidden(), - ) - ) - - @loader.command( - ru_doc="Сократить ссылку через bit.ly", - en_doc="Shorten the link via bit.ly", - ) - async def shortencmd(self, message): - if self.config["token"] is None: - await utils.answer(message, self.strings("no_api")) - return - - s = pyshorteners.Shortener(api_key=self.config["token"]) - args = utils.get_args_raw(message) - await utils.answer( - message, self.strings("shortencmd").format(c=s.bitly.short(args)) - ) - - @loader.command( - ru_doc="Посмотреть статистику ссылки через bit.ly", - en_doc="View link statistics via bit.ly", - ) - async def statclcmd(self, message): - if self.config["token"] is None: - await utils.answer(message, self.strings("no_api")) - return - - s = pyshorteners.Shortener(api_key=self.config["token"]) - args = utils.get_args_raw(message) - await utils.answer( - message, self.strings("statclcmd").format(c=s.bitly.total_clicks(args)) - ) diff --git a/SenkoGuardian/SenModules/Gemini.py b/SenkoGuardian/SenModules/Gemini.py index adb7101..6901a26 100644 --- a/SenkoGuardian/SenModules/Gemini.py +++ b/SenkoGuardian/SenModules/Gemini.py @@ -3,7 +3,9 @@ # This software is released under the MIT License. # https://opensource.org/licenses/MIT -__version__ = (5, 2, 6) # Meow~ +__version__ = (5, 6, 0) # Емае, ну это уже слишком + +#фырфырфырфырфырфыр # meta developer: @SenkoGuardianModules @@ -25,7 +27,17 @@ import aiohttp import tempfile from markdown_it import MarkdownIt import pytz +# New SDK +from google import genai as google_genai_sdk +try: + from google.genai import types as new_types + NEW_SDK_AVAILABLE = True +except ImportError: + NEW_SDK_AVAILABLE = False +# Old SDK import google.ai.generativelanguage as glm +import google.generativeai as old_genai +from google.generativeai import types as old_types from telethon import types from telethon.tl.types import Message, DocumentAttributeFilename from telethon.utils import get_display_name, get_peer_id @@ -35,17 +47,26 @@ from telethon.errors.rpcerrorlist import ( UserNotParticipantError, ChannelPrivateError ) + try: - import google.generativeai as genai - import google.ai.generativelanguage import google.api_core.exceptions as google_exceptions GOOGLE_AVAILABLE = True except ImportError: GOOGLE_AVAILABLE = False + from .. import loader, utils from ..inline.types import InlineCall -# requires: google-generativeai google-api-core pytz markdown_it_py +logger = logging.getLogger(__name__) + +DB_HISTORY_KEY = "gemini_conversations_v4" +DB_GAUTO_HISTORY_KEY = "gemini_gauto_conversations_v1" +DB_IMPERSONATION_KEY = "gemini_impersonation_chats" +GEMINI_TIMEOUT = 840 +MAX_FFMPEG_SIZE = 90 * 1024 * 1024 + +# requires: google-generativeai google-genai google-api-core pytz markdown_it_py +# рекомендованные версии: google-generativeai==0.8.5 google-genai==1.52.0 google-api-core==2.28.1 logger = logging.getLogger(__name__) @@ -69,6 +90,8 @@ class Gemini(loader.Module): "cfg_impersonation_prompt_doc": "Промпт для режима авто-ответа. {my_name} и {chat_history} будут заменены.", "cfg_impersonation_history_limit_doc": "Сколько последних сообщений из чата отправлять в качестве контекста для авто-ответа.", "cfg_impersonation_reply_chance_doc": "Вероятность ответа в режиме gauto (от 0.0 до 1.0). 0.2 = 20% шанс.", + "cfg_temperature_doc": "Температура генерации (креативность). От 0.0 до 2.0. По умолчанию 1.0.", + "cfg_google_search_doc": "Включить поиск Google (Grounding) для актуальной информации.", "no_api_key": '❗️ Api ключ(и) не настроен(ы).\nПолучить Api ключ можно здесь.\nДобавьте ключ(и) в конфиге модуля: .cfg gemini api_key', "invalid_api_key": '❗️ Предоставленный API ключ недействителен.\nУбедитесь, что он правильно скопирован из Google AI Studio и что для него включен Gemini API.', "all_keys_exhausted": "❗️ Все доступные API ключи ({}) исчерпали свою квоту.\nПопробуйте позже или добавьте новые ключи в конфиге: .cfg gemini api_key", @@ -124,6 +147,14 @@ class Gemini(loader.Module): "gmodel_img_warn": "⚠️ Текущая модель ({}) не может генерировать изображения(или не доступна по API).\nРекомендуем: gemini-2.5-flash-image", "gme_chat_not_found": "🚫 Не удалось найти чат для экспорта: {}", "gme_sent_to_saved": "💾 История экспортирована в избранное.", + "new_sdk_missing": "⚠️ Для работы поиска (Grounding) нужна библиотека google-genai.\nВыполните: pip install google-genai", + "gprompt_usage": "ℹ️ Использование:\n.gprompt <текст> — установить промпт.\n.gprompt -c — очистить.\nИли ответьте на .txt файл.", + "gprompt_updated": "✅ Системный промпт обновлен!\nДлина: {} симв.", + "gprompt_cleared": "🗑 Системный промпт очищен.", + "gprompt_current": "📝 Текущий системный промпт:", + "gprompt_file_error": "❗️ Ошибка чтения файла: {}", + "gprompt_file_too_big": "❗️ Файл слишком большой (лимит 1 МБ).", + "gprompt_not_text": "❗️ Это не похоже на текстовый файл.(txt)", } TEXT_MIME_TYPES = { "text/plain", "text/markdown", "text/html", "text/css", "text/csv", @@ -131,39 +162,41 @@ class Gemini(loader.Module): "application/javascript", "application/x-sh", } def __init__(self): - self.config = loader.ModuleConfig( - loader.ConfigValue( - "api_key", "", self.strings["cfg_api_key_doc"], - validator=loader.validators.Hidden() - ), - loader.ConfigValue("model_name", "gemini-1.5-flash", self.strings["cfg_model_name_doc"]), - loader.ConfigValue("interactive_buttons", True, self.strings["cfg_buttons_doc"], validator=loader.validators.Boolean()), - loader.ConfigValue("system_instruction", "", self.strings["cfg_system_instruction_doc"], validator=loader.validators.String()), - loader.ConfigValue("max_history_length", 10, self.strings["cfg_max_history_length_doc"], validator=loader.validators.Integer(minimum=0)), - loader.ConfigValue("timezone", "Europe/Moscow", self.strings["cfg_timezone_doc"]), - loader.ConfigValue("proxy", "", self.strings["cfg_proxy_doc"]), - loader.ConfigValue( - "impersonation_prompt", - ( - "Ты — это я, пользователь Telegram по имени {my_name}. Твоя задача — отвечать в чате от моего имени, основываясь на последних сообщениях. " - "Веди себя естественно, как обычный человек, а не как ассистент. Не признавайся, что ты — ИИ, даже если тебя спросят напрямую. " - "Твоя личность: [Здесь опишите свой характер: остроумный, серьезный, саркастичный, дружелюбный и т.д.].\n" - "Правила:\n- Отвечай кратко и по делу.\n- Используй неформальный язык, сленг.\n- Не отвечай на каждое сообщение.\n- На медиа (стикер, фото) реагируй как человек ('лол', 'ору', 'жиза').\n- Не используй префиксы и кавычки.\n\n" - "ИСТОРИЯ ЧАТА:\n{chat_history}\n\n{my_name}:" + self.config = loader.ModuleConfig( + loader.ConfigValue( + "api_key", "", self.strings["cfg_api_key_doc"], + validator=loader.validators.Hidden() ), - self.strings["cfg_impersonation_prompt_doc"], - validator=loader.validators.String(), - ), - loader.ConfigValue("impersonation_history_limit", 20, self.strings["cfg_impersonation_history_limit_doc"], validator=loader.validators.Integer(minimum=5, maximum=100)), - loader.ConfigValue("impersonation_reply_chance", 0.25, self.strings["cfg_impersonation_reply_chance_doc"], validator=loader.validators.Float(minimum=0.0, maximum=1.0)), - loader.ConfigValue("gauto_in_pm", False, "Разрешить авто-ответы в личных сообщениях (ЛС).", validator=loader.validators.Boolean()), - ) - self.conversations = {} - self.gauto_conversations = {} - self.last_requests = {} - self.impersonation_chats = set() - self._lock = asyncio.Lock() - self.memory_disabled_chats = set() + loader.ConfigValue("model_name", "gemini-2.5-flash", self.strings["cfg_model_name_doc"]), + loader.ConfigValue("interactive_buttons", True, self.strings["cfg_buttons_doc"], validator=loader.validators.Boolean()), + loader.ConfigValue("system_instruction", "", self.strings["cfg_system_instruction_doc"], validator=loader.validators.String()), + loader.ConfigValue("max_history_length", 800, self.strings["cfg_max_history_length_doc"], validator=loader.validators.Integer(minimum=0)), + loader.ConfigValue("timezone", "Europe/Moscow", self.strings["cfg_timezone_doc"]), + loader.ConfigValue("proxy", "", self.strings["cfg_proxy_doc"]), + loader.ConfigValue( + "impersonation_prompt", + ( + "Ты — это я, пользователь Telegram по имени {my_name}. Твоя задача — отвечать в чате от моего имени, основываясь на последних сообщениях. " + "Веди себя естественно, как обычный человек, а не как ассистент. Не признавайся, что ты — ИИ, даже если тебя спросят напрямую. " + "Твоя личность: [Здесь опишите свой характер: остроумный, серьезный, саркастичный, дружелюбный и т.д.].\n" + "Правила:\n- Отвечай кратко и по делу.\n- Используй неформальный язык, сленг.\n- Не отвечай на каждое сообщение.\n- На медиа (стикер, фото) реагируй как человек ('лол', 'ору', 'жиза').\n- Не используй префиксы и кавычки.\n\n" + "ИСТОРИЯ ЧАТА:\n{chat_history}\n\n{my_name}:" + ), + self.strings["cfg_impersonation_prompt_doc"], + validator=loader.validators.String(), + ), + loader.ConfigValue("impersonation_history_limit", 20, self.strings["cfg_impersonation_history_limit_doc"], validator=loader.validators.Integer(minimum=5, maximum=100)), + loader.ConfigValue("impersonation_reply_chance", 0.25, self.strings["cfg_impersonation_reply_chance_doc"], validator=loader.validators.Float(minimum=0.0, maximum=1.0)), + loader.ConfigValue("gauto_in_pm", False, "Разрешить авто-ответы в личных сообщениях (ЛС).", validator=loader.validators.Boolean()), + loader.ConfigValue("google_search", False, self.strings["cfg_google_search_doc"], validator=loader.validators.Boolean()), + loader.ConfigValue("temperature", 1.0, self.strings["cfg_temperature_doc"], validator=loader.validators.Float(minimum=0.0, maximum=2.0)), + ) + self.conversations = {} + self.gauto_conversations = {} + self.last_requests = {} + self.impersonation_chats = set() + self._lock = asyncio.Lock() + self.memory_disabled_chats = set() async def client_ready(self, client, db): self.client = client @@ -296,116 +329,143 @@ class Gemini(loader.Module): except Exception: msg_obj=None else: chat_id=utils.get_chat_id(message); base_message_id=message.id; msg_obj=message + api_key_str = self.config["api_key"] + self.api_keys = [k.strip() for k in api_key_str.split(",") if k.strip()] if api_key_str else [] try: if not self.api_keys: - if not impersonation_mode and status_msg: - await utils.answer(status_msg, self.strings['no_api_key']) + if not impersonation_mode and status_msg: await utils.answer(status_msg, self.strings['no_api_key']) return None if impersonation_mode else "" - tools_list=[] - if use_url_context: - try: tools_list.append(genai.types.Tool(url_context=genai.types.UrlContext())) - except AttributeError: logger.error("Инструмент UrlContext не поддерживается вашей версией библиотеки.") - system_instruction_to_use=None; api_history_content=[] - if impersonation_mode: - my_name=get_display_name(self.me); chat_history_text=await self._get_recent_chat_text(chat_id); system_instruction_to_use=self.config["impersonation_prompt"].format(my_name=my_name, chat_history=chat_history_text) - raw_history=self._get_structured_history(chat_id, gauto=True); api_history_content=[glm.Content(role=e["role"], parts=[glm.Part(text=e['content'])]) for e in raw_history] - else: - system_instruction_val=self.config["system_instruction"]; system_instruction_to_use=(system_instruction_val.strip() if isinstance(system_instruction_val, str) else "") or None - raw_history=self._get_structured_history(chat_id, gauto=False) - if regeneration: raw_history=raw_history[:-2] - api_history_content=[glm.Content(role=e["role"], parts=[glm.Part(text=e['content'])]) for e in raw_history] - full_request_content=list(api_history_content) - if not impersonation_mode: - from datetime import datetime - try: user_timezone=pytz.timezone(self.config["timezone"]) - except pytz.UnknownTimeZoneError: user_timezone=pytz.utc - now=datetime.now(user_timezone); time_str=now.strftime("%Y-%m-%d %H:%M:%S %Z"); time_note=f"[System note: Current time is {time_str}]" - text_part_found=False - for p in parts: - if hasattr(p, 'text'): p.text=f"{time_note}\n\n{p.text}"; text_part_found=True; break - if not text_part_found: parts.insert(0, glm.Part(text=time_note)) + api_key = self.api_keys[self.current_api_key_index] if regeneration: - current_turn_parts,request_text_for_display=self.last_requests.get(f"{chat_id}:{base_message_id}", (parts, "[регенерация]")) + current_turn_parts, request_text_for_display = self.last_requests.get(f"{chat_id}:{base_message_id}", (parts, "[регенерация]")) else: - current_turn_parts=parts; request_text_for_display=display_prompt or (self.strings["media_reply_placeholder"] if any("inline_data" in str(p) for p in parts) else ""); self.last_requests[f"{chat_id}:{base_message_id}"]=(current_turn_parts, request_text_for_display) - if current_turn_parts: full_request_content.append(glm.Content(role="user", parts=current_turn_parts)) - if not full_request_content and not system_instruction_to_use: - if not impersonation_mode and status_msg: await utils.answer(status_msg, self.strings["no_prompt_or_media"]) - return None if impersonation_mode else "" - response = None - error_to_report = None - max_retries = len(self.api_keys) - for i in range(max_retries): - current_key_index = (self.current_api_key_index + i) % max_retries - api_key = self.api_keys[current_key_index] + current_turn_parts = parts + request_text_for_display = display_prompt or (self.strings["media_reply_placeholder"] if any("inline_data" in str(p) for p in parts) else "") + self.last_requests[f"{chat_id}:{base_message_id}"] = (current_turn_parts, request_text_for_display) + has_media = False + for p in current_turn_parts: + if hasattr(p, 'inline_data') and p.inline_data: + has_media = True; break + should_use_new_sdk = (self.config["google_search"] or use_url_context) and NEW_SDK_AVAILABLE and not has_media + result_text = ""; was_successful = False; search_icon = "" + if should_use_new_sdk: try: - genai.configure(api_key=api_key) - sanitized_model_name = self.config["model_name"].lower().replace(" ", "-") - model = genai.GenerativeModel( - sanitized_model_name, - safety_settings=self.safety_settings, - system_instruction=system_instruction_to_use - ) - api_response = await asyncio.wait_for( - model.generate_content_async(full_request_content, tools=tools_list or None), - timeout=GEMINI_TIMEOUT - ) - response = api_response - self.current_api_key_index = current_key_index - break - except google_exceptions.GoogleAPIError as e: - msg = str(e) - if "quota" in msg.lower() or "exceeded" in msg.lower(): - if max_retries == 1: - error_to_report = e - break - logger.warning(f"Ключ Gemini API №{current_key_index + 1} исчерпал квоту. Пробую следующий.") - if i == max_retries - 1: - error_to_report = RuntimeError("Все ключи исчерпали квоту.") - continue + client = google_genai_sdk.Client(api_key=api_key); sys_instruct = None + if impersonation_mode: + my_name = get_display_name(self.me); chat_history_text = await self._get_recent_chat_text(chat_id) + sys_instruct = self.config["impersonation_prompt"].format(my_name=my_name, chat_history=chat_history_text) else: - error_to_report = e - break + sys_val = self.config["system_instruction"]; sys_instruct = (sys_val.strip() if isinstance(sys_val, str) else "") or None + new_contents = []; raw_hist = self._get_structured_history(chat_id, gauto=impersonation_mode) + if regeneration and raw_hist: raw_hist = raw_hist[:-2] + for item in raw_hist: + new_contents.append(new_types.Content(role=item['role'], parts=[new_types.Part(text=item['content'])])) + new_parts = [] + for p in current_turn_parts: + if hasattr(p, 'text'): new_parts.append(new_types.Part(text=p.text)) + new_contents.append(new_types.Content(role="user", parts=new_parts)) + tools = [] + if self.config["google_search"] or use_url_context: + tools.append(new_types.Tool(google_search=new_types.GoogleSearch())) + config_new = new_types.GenerateContentConfig(tools=tools if tools else None, temperature=self.config["temperature"], system_instruction=sys_instruct) + response = await asyncio.to_thread(client.models.generate_content, model=self.config["model_name"], contents=new_contents, config=config_new) + if response.text: + result_text = response.text; was_successful = True + if self.config["google_search"]: search_icon = " 🌐" + else: result_text = "⚠️ Пустой ответ от модели (возможно, блокировка безопасности)." except Exception as e: - error_to_report = e - break - if error_to_report: - raise error_to_report - if response is None: - raise RuntimeError("Не удалось получить ответ от Gemini.") - result_text,was_successful="",False - try: - if response.prompt_feedback.block_reason: result_text=f"🚫 Запрос был заблокирован Google.\nПричина: {response.prompt_feedback.block_reason.name}." - except AttributeError: pass - if not result_text: - try: - result_text = re.sub(r"]*>", "", response.text) - was_successful=True - except ValueError: - reason="Неизвестная причина" + logger.error(f"New SDK Error: {e}") + if "quota" in str(e).lower(): raise e + was_successful = False + if not was_successful: + if (self.config["google_search"] or use_url_context) and not NEW_SDK_AVAILABLE and not has_media: + if status_msg: await utils.answer(status_msg, self.strings["new_sdk_missing"]) + return None + tools_list = [] + if use_url_context and not has_media: + try: tools_list.append(old_types.Tool(url_context=old_types.UrlContext())) + except AttributeError: pass + system_instruction_to_use = None; api_history_content = [] + if impersonation_mode: + my_name = get_display_name(self.me); chat_history_text = await self._get_recent_chat_text(chat_id) + system_instruction_to_use = self.config["impersonation_prompt"].format(my_name=my_name, chat_history=chat_history_text) + raw_history = self._get_structured_history(chat_id, gauto=True) + api_history_content = [glm.Content(role=e["role"], parts=[glm.Part(text=e['content'])]) for e in raw_history] + else: + system_instruction_val = self.config["system_instruction"] + system_instruction_to_use = (system_instruction_val.strip() if isinstance(system_instruction_val, str) else "") or None + raw_history = self._get_structured_history(chat_id, gauto=False) + if regeneration: raw_history = raw_history[:-2] + api_history_content = [glm.Content(role=e["role"], parts=[glm.Part(text=e['content'])]) for e in raw_history] + full_request_content = list(api_history_content) + if not impersonation_mode: + from datetime import datetime + try: user_timezone = pytz.timezone(self.config["timezone"]) + except pytz.UnknownTimeZoneError: user_timezone = pytz.utc + now = datetime.now(user_timezone); time_str = now.strftime("%Y-%m-%d %H:%M:%S %Z"); time_note = f"[System note: Current time is {time_str}]" + request_content_parts = list(current_turn_parts) + if request_content_parts and hasattr(request_content_parts[0], 'text'): + original_text = request_content_parts[0].text + request_content_parts[0] = glm.Part(text=f"{time_note}\n\n{original_text}") + else: request_content_parts.insert(0, glm.Part(text=time_note)) + else: request_content_parts = current_turn_parts + if request_content_parts: full_request_content.append(glm.Content(role="user", parts=request_content_parts)) + if not full_request_content and not system_instruction_to_use: + if not impersonation_mode and status_msg: await utils.answer(status_msg, self.strings["no_prompt_or_media"]) + return None if impersonation_mode else "" + error_to_report = None; max_retries = len(self.api_keys) + generation_cfg = old_types.GenerationConfig(temperature=self.config["temperature"]) + for i in range(max_retries): + current_key_index = (self.current_api_key_index + i) % max_retries + api_key = self.api_keys[current_key_index] try: - if response.candidates: reason=response.candidates[0].finish_reason.name - except(IndexError, AttributeError): pass - result_text=f"❗️ Gemini не смог сгенерировать ответ.\nПричина завершения: {reason}." - if was_successful and self._is_memory_enabled(str(chat_id)): self._update_history(chat_id, current_turn_parts, result_text, regeneration, msg_obj, gauto=impersonation_mode) + old_genai.configure(api_key=api_key) + sanitized_model_name = self.config["model_name"].lower().replace(" ", "-") + model = old_genai.GenerativeModel(sanitized_model_name, safety_settings=self.safety_settings, system_instruction=system_instruction_to_use) + api_response = await asyncio.wait_for(model.generate_content_async(full_request_content, tools=tools_list or None, generation_config=generation_cfg), timeout=GEMINI_TIMEOUT) + response = api_response; self.current_api_key_index = current_key_index; break + except google_exceptions.GoogleAPIError as e: + msg = str(e) + if "quota" in msg.lower() or "exceeded" in msg.lower(): + if max_retries == 1: error_to_report = e; break + logger.warning(f"Ключ №{current_key_index + 1} исчерпал квоту.") + if i == max_retries - 1: error_to_report = RuntimeError("Все ключи исчерпали квоту."); continue + else: error_to_report = e; break + except Exception as e: error_to_report = e; break + if error_to_report: raise error_to_report + if response is None: raise RuntimeError("Не удалось получить ответ.") + try: + if response.prompt_feedback.block_reason: result_text = f"🚫 Запрос заблокирован.\nПричина: {response.prompt_feedback.block_reason.name}." + except AttributeError: pass + if not result_text: + try: + result_text = re.sub(r"]*>", "", response.text); was_successful = True + except ValueError: result_text = "❗️ Gemini не смог сгенерировать ответ (возможно, только safety ratings)." + if was_successful and self._is_memory_enabled(str(chat_id)): + self._update_history(chat_id, current_turn_parts, result_text, regeneration, msg_obj, gauto=impersonation_mode) if impersonation_mode: return result_text if was_successful else None - hist_len_pairs=len(self._get_structured_history(chat_id, gauto=False)) // 2; limit=self.config["max_history_length"]; mem_indicator=self.strings["memory_status_unlimited"].format(hist_len_pairs) if limit <= 0 else self.strings["memory_status"].format(hist_len_pairs, limit) - question_html=f"
{utils.escape_html(request_text_for_display[:200])}
"; response_html=self._markdown_to_html(result_text); formatted_body=self._format_response_with_smart_separation(response_html) - header=f"{mem_indicator}\n\n{self.strings['question_prefix']}\n{question_html}\n\n{self.strings['response_prefix']}\n"; text_to_send=f"{header}{formatted_body}" - buttons=self._get_inline_buttons(chat_id, base_message_id) if self.config["interactive_buttons"] else None + hist_len_pairs = len(self._get_structured_history(chat_id, gauto=False)) // 2 + limit = self.config["max_history_length"] + mem_indicator = self.strings["memory_status_unlimited"].format(hist_len_pairs) if limit <= 0 else self.strings["memory_status"].format(hist_len_pairs, limit) + question_html = f"
{utils.escape_html(request_text_for_display[:200])}
" + response_html = self._markdown_to_html(result_text) + formatted_body = self._format_response_with_smart_separation(response_html) + header = f"{mem_indicator}\n\n{self.strings['question_prefix']}\n{question_html}\n\n{self.strings['response_prefix']}{search_icon}\n" + text_to_send = f"{header}{formatted_body}" + buttons = self._get_inline_buttons(chat_id, base_message_id) if self.config["interactive_buttons"] else None if len(text_to_send) > 4096: - file_content=(f"Вопрос: {display_prompt}\n\n════════════════════\n\nОтвет Gemini:\n{result_text}") - file=io.BytesIO(file_content.encode("utf-8")); file.name="Gemini_response.txt" + file_content = (f"Вопрос: {display_prompt}\n\n════════════════════\n\nОтвет Gemini:\n{result_text}") + file = io.BytesIO(file_content.encode("utf-8")); file.name = "Gemini_response.txt" if call: - await call.answer("Ответ слишком длинный, отправляю файлом...", show_alert=False); await self.client.send_file(call.chat_id, file, caption=self.strings["response_too_long"], reply_to=call.message_id); await call.edit(f"✅ {self.strings['response_too_long']}", reply_markup=None) + await call.answer("Ответ длинный, отправляю файлом...", show_alert=False); await self.client.send_file(call.chat_id, file, caption=self.strings["response_too_long"], reply_to=call.message_id); await call.edit(f"✅ {self.strings['response_too_long']}", reply_markup=None) elif status_msg: await status_msg.delete(); await self.client.send_file(chat_id, file, caption=self.strings["response_too_long"], reply_to=base_message_id) else: if call: await call.edit(text_to_send, reply_markup=buttons) elif status_msg: await utils.answer(status_msg, text_to_send, reply_markup=buttons) except Exception as e: - error_text=self._handle_error(e) - if impersonation_mode: logger.error(f"Gauto | Ошибка авто-ответа: {error_text}") + error_text = self._handle_error(e) + if impersonation_mode: logger.error(f"Gauto | Ошибка: {error_text}") elif call: await call.edit(error_text, reply_markup=None) elif status_msg: await utils.answer(status_msg, error_text) return None if impersonation_mode else "" @@ -491,10 +551,13 @@ class Gemini(loader.Module): current_key_index = (self.current_api_key_index + i) % max_retries api_key = self.api_keys[current_key_index] try: - genai.configure(api_key=api_key) + old_genai.configure(api_key=api_key) sanitized_model_name = self.config["model_name"].lower().replace(" ", "-") - model = genai.GenerativeModel(sanitized_model_name, safety_settings=self.safety_settings) - api_response = await asyncio.wait_for(model.generate_content_async(full_prompt), timeout=GEMINI_TIMEOUT) + generation_cfg = old_types.GenerationConfig( + temperature=self.config["temperature"] + ) + model = old_genai.GenerativeModel(sanitized_model_name, safety_settings=self.safety_settings) + api_response = await asyncio.wait_for(model.generate_content_async(full_prompt, generation_config=generation_cfg), timeout=GEMINI_TIMEOUT) response = api_response self.current_api_key_index = current_key_index break @@ -526,6 +589,41 @@ class Gemini(loader.Module): except Exception as e: await utils.answer(status_msg, self._handle_error(e)) + @loader.command() + async def gprompt(self, message: Message): + """[текст / -c / ответ на файл] — [-c (очистить)] / (ничего. увидеть промпт) Установить системный промпт (инструкцию/system_instruction).""" + args = utils.get_args_raw(message) + reply = await message.get_reply_message() + if args == "-c": + self.config["system_instruction"] = "" + return await utils.answer(message, self.strings["gprompt_cleared"]) + new_prompt = None + if reply and reply.file: + if reply.file.size > 1024 * 1024: + return await utils.answer(message, self.strings["gprompt_file_too_big"]) + try: + file_data = await self.client.download_file(reply.media, bytes) + try: + new_prompt = file_data.decode("utf-8") + except UnicodeDecodeError: + return await utils.answer(message, self.strings["gprompt_not_text"]) + except Exception as e: + return await utils.answer(message, self.strings["gprompt_file_error"].format(e)) + elif args: + new_prompt = args + if new_prompt is not None: + self.config["system_instruction"] = new_prompt + return await utils.answer(message, self.strings["gprompt_updated"].format(len(new_prompt))) + current_prompt = self.config["system_instruction"] + if not current_prompt: + return await utils.answer(message, self.strings["gprompt_usage"]) + if len(current_prompt) > 4000: + file = io.BytesIO(current_prompt.encode("utf-8")) + file.name = "system_instruction.txt" + await utils.answer(message, self.strings["gprompt_current"], file=file) + else: + await utils.answer(message, f"{self.strings['gprompt_current']}\n{utils.escape_html(current_prompt)}") + @loader.command() async def gauto(self, message: Message): """ — Вкл/выкл авто-ответ в чате.""" @@ -811,9 +909,9 @@ class Gemini(loader.Module): status_msg = await utils.answer(message, self.strings["processing"]) try: api_key = self.api_keys[self.current_api_key_index] - genai.configure(api_key=api_key) + old_genai.configure(api_key=api_key) models_list = [] - for model_obj in genai.list_models(): + for model_obj in old_genai.list_models(): model_name = model_obj.name display_name = model_obj.display_name or "Неизвестно" methods = ", ".join(model_obj.supported_generation_methods) if model_obj.supported_generation_methods else "Нет" @@ -875,8 +973,9 @@ class Gemini(loader.Module): if message.is_private and not self.config["gauto_in_pm"]: return is_from_self_user = isinstance(message.from_id, types.PeerUser) and message.from_id.user_id == self.me.id - is_command = message.text and message.text.startswith(self.get_prefix()) - if message.out or is_from_self_user or is_command: + if message.out or is_from_self_user: + return + if message.text and message.text.startswith(self.get_prefix()): return sender = await message.get_sender() is_sender_a_bot = isinstance(sender, types.User) and sender.bot @@ -891,8 +990,20 @@ class Gemini(loader.Module): return response_text = await self._send_to_gemini(message=message, parts=parts, impersonation_mode=True) if response_text and response_text.strip(): - await asyncio.sleep(random.uniform(1.0, 2.5)) - await message.reply(response_text.strip()) + clean_text = response_text.strip() + reaction_delay = random.uniform(2.0, 10.0) + await asyncio.sleep(reaction_delay) + try: + await self.client.send_read_acknowledge(message.chat_id, message=message) + except Exception: + pass + char_count = len(clean_text) + typing_speed = random.uniform(0.1, 0.25) + typing_duration = char_count * typing_speed + typing_duration = max(1.5, min(typing_duration, 25.0)) + async with message.client.action(message.chat_id, "typing"): + await asyncio.sleep(typing_duration) + await message.reply(clean_text) def _load_history_from_db(self, db_key: str) -> dict: raw_conversations=self.db.get(self.strings["name"], db_key, {}) diff --git a/ZetGoHack/nullmod/Chess.py b/ZetGoHack/nullmod/Chess.py index 6f2dcc0..a70dcc9 100644 --- a/ZetGoHack/nullmod/Chess.py +++ b/ZetGoHack/nullmod/Chess.py @@ -1,4 +1,4 @@ -__version__ = (2, 0, 0) +__version__ = (2, 0, 6) #░░░███░███░███░███░███ #░░░░░█░█░░░░█░░█░░░█░█ #░░░░█░░███░░█░░█░█░█░█ @@ -7,24 +7,25 @@ __version__ = (2, 0, 0) #H:Mods Team [💎] # meta developer: @nullmod -# requires: python-chess +# requires: python-chess gdown # packurl: https://github.com/ZetGoHack/TestingModules/raw/main/chess.yml - -from .. import loader, utils -from ..inline.types import BotInlineCall, InlineCall, InlineMessage - import asyncio import chess +import chess.engine import chess.pgn import copy +import logging +import os import random as r import time -from datetime import datetime, timezone from telethon.tl.types import PeerUser, User, Message +from datetime import datetime, timezone from typing import TypedDict +from .. import loader, utils +from ..inline.types import BotInlineCall, InlineCall, InlineMessage class Timer: def __init__(self, scnds): @@ -33,7 +34,7 @@ class Timer: self.running = {"white": False, "black": False} self.last_time = time.monotonic() self.t = None - + def minutes(self) -> int: return self.starttime // 60 @@ -120,19 +121,65 @@ class Game(TypedDict): state: str reason: str add_params: GameParams + bot: chess.engine.SimpleEngine | None class GameObj(TypedDict): game_id: str + vs_bot: bool + bot_elo: int game: Game sender: Player opponent: Player Timer: TimerDict time: int - host_plays: bool + host_plays: bool # True - white, False - black style: dict[str, str] GamesDict = dict[str, GameObj] +logger = logging.getLogger(__name__) + +def install_stockfish() -> str | None: + import platform + import gdown + import zipfile + system = platform.system() + if system == "Windows": + url = "https://github.com/official-stockfish/Stockfish/releases/latest/download/stockfish-windows-x86-64.zip" + elif system == "Linux": + if platform.machine().lower() == "aarch64": + url = "https://github.com/official-stockfish/Stockfish/releases/latest/download/stockfish-android-armv8.tar" + else: + url = "https://github.com/official-stockfish/Stockfish/releases/latest/download/stockfish-ubuntu-x86-64.tar" + else: + return None + file_name = url.split("/")[-1] + try: + gdown.download(url, file_name, quiet=True) + if file_name.endswith(".zip"): + with zipfile.ZipFile(file_name, 'r') as file: + file.extractall() + elif file_name.endswith(".tar"): + import tarfile + with tarfile.open(file_name, 'r') as file: + file.extractall() # noqa: S202 + os.remove(file_name) + + return find_stfsh_exe() + except Exception: + logger.exception("Failed to install Stockfish") + return None + +def find_stfsh_exe() -> str | None: + for root, _, files in os.walk("./stockfish"): + for file in files: + if "stockfish" in file.lower(): + return os.path.join(root, file) + +def check_path(path: str) -> bool: + return os.path.isfile(path) + + @loader.tds class Chess(loader.Module): """A reworked version of the Chess module""" @@ -148,10 +195,16 @@ class Chess(loader.Module): False, "Allows you to make moves without turn checks (also, you can play with yourself)", validator=loader.validators.Boolean(), - ) + ), + loader.ConfigValue( + "stockfish_path", + None, + "Path to stockfish engine", + ), ) async def client_ready(self): + self._last_backup = 0 self.styles = { "figures-with-circles": { "symbol": "[♔⚪] ", @@ -195,9 +248,6 @@ class Chess(loader.Module): for col in "hgfedcba" } self.games: GamesDict = self.get("games_backup", {}) - self.gsettings = { - "style": "figures-with-circles", - } self.pgn = { 'Event': "Chess Play In Module", 'Site': "https://t.me/nullmod/", @@ -206,8 +256,10 @@ class Chess(loader.Module): 'White': "{player}", 'Black': "{player}", } + if (path := find_stfsh_exe()): + self.config["stockfish_path"] = path - async def _check_player(self, call: InlineCall, game_id: str, only_opponent: bool = False, skip_turn_check: bool = False) -> bool: + async def _check_player(self, call: InlineCall | Message | None, game_id: str, only_opponent: bool = False, skip_turn_check: bool = False) -> bool: if isinstance(call, (BotInlineCall, InlineCall, InlineMessage)): game = self.games[game_id] _from_id = call.from_user.id @@ -215,8 +267,7 @@ class Chess(loader.Module): if game.get("game", None) and game["game"]["state"] == "the_end": await call.answer(self.strings["game_ended"], show_alert=True) return - if _from_id != game["sender"]["id"]: - if _from_id != game["opponent"]["id"]: + if _from_id != game["sender"]["id"] and _from_id != game["opponent"]["id"]: await call.answer(self.strings["not_available"]) return False if _from_id == game["sender"]["id"] and only_opponent and not self.config["play_self"]: @@ -230,70 +281,88 @@ class Chess(loader.Module): await call.answer(self.strings["opp_move"]) return False return True - - async def get_players(self, message: Message): - sender = { - "id": message.from_id.user_id if isinstance(message.peer_id, PeerUser) else message.sender.id, - "name": (await self.client.get_entity(message.from_id if isinstance(message.peer_id, PeerUser) else message.sender.id)).first_name - } - if message.is_reply: + + async def install_stockfish(self, call: InlineCall): + await utils.answer(call, self.strings["installing"]) + path = install_stockfish() + if path: + self.config["stockfish_path"] = path + await utils.answer(call, self.strings["stockfish_installed"].format(path=path)) + else: + await utils.answer(call, self.strings["stockfish_install_failed"]) + + async def get_players(self, message: Message | InlineCall, sender: dict = None, sender_only: bool = False, opponent_only: bool = False): + if not sender: + sender = { + "id": message.sender_id, + "name": (await self.client.get_entity(message.sender_id)).first_name + } + if sender_only: + return sender + + if isinstance(message, InlineCall): + opp_id = message.from_user.id + opp_name = message.from_user.first_name + + elif message.is_reply: r = await message.get_reply_message() opponent = r.sender if not isinstance(opponent, User): await utils.answer(message, self.strings["not_a_user"]) - return (None, None) + return (sender, None) opp_id = opponent.id opp_name = opponent.first_name else: args = utils.get_args(message) + if len(args)==0: - await utils.answer(message, self.strings["noargs"]) - return (None, None) + return (sender, None) + opponent = args[0] try: if opponent.isdigit(): opp_id = int(opponent) opponent = await self.client.get_entity(opp_id) + if not isinstance(opponent, User): await utils.answer(message, self.strings["not_a_user"]) - return (None, None) + return (sender, None) opp_name = opponent.first_name else: opponent = await self.client.get_entity(opponent) + if not isinstance(opponent, User): await utils.answer(message, self.strings["not_a_user"]) - return (None, None) + return (sender, None) + opp_name = opponent.first_name opp_id = opponent.id - except: + except ValueError: await utils.answer(message, self.strings["whosthat"]) - return (None, None) + return (sender, None) + opponent = { "id": opp_id, "name": opp_name } + if opponent_only: + return opponent + return (sender, opponent) async def _invite(self, call: InlineCall, game_id: str): if not await self._check_player(call, game_id): return game = self.games[game_id] - await utils.answer( - call, - self.strings["invite"].format(opponent=utils.escape_html(self.games[game_id]["opponent"]["name"])) + self.strings['settings_text'].format( - style=game['style'], - timer=self.strings['available'] if game['Timer']['available'] and not game['Timer']['timer'] - else self.strings['timer'].format(game['Timer']['timer'].minutes()) if game['Timer']['timer'] - else self.strings['not_available'], - - color=self.strings['random'] if game['host_plays'] == 'r' - else self.strings['white'] if game['host_plays'] == True - else self.strings['black'] - ), + await utils.answer( + call, + self.strings["invite_bot" if game["vs_bot"] else "invite" if not game.get("alr_accepted", False) else "not_invite"].format( + opponent=utils.escape_html(self.games[game_id]["opponent"]["name"]) + ) + self._get_settings_text(game_id), reply_markup = [ [ { - "text": self.strings["yes"], + "text": self.strings["bot_yes" if game["vs_bot"] else "yes"], "callback": self._init_game, "args": (game_id,) }, @@ -318,17 +387,23 @@ class Chess(loader.Module): if not await self._check_player(call, game_id): return game = self.games[game_id] reply_markup = [] + + if game["vs_bot"]: + reply_markup.append([ + {"text": self.strings["bot_elo_btn"], "callback": self._settings, "args": (game_id, "e")} + ]) + if game["Timer"]["available"]: reply_markup.append([ - {"text": self.strings["time_btn"], "callback": self._settings, "args": (game_id, "t", )} + {"text": self.strings["time_btn"], "callback": self._settings, "args": (game_id, "t",)} ]) reply_markup.extend([ [ - {"text": self.strings["color_btn"], "callback": self._settings, "args": (game_id, "c", )} + {"text": self.strings["color_btn"], "callback": self._settings, "args": (game_id, "c",)} ], [ - {"text": self.strings["style_btn"], "callback": self._settings, "args": (game_id, "s", )} + {"text": self.strings["style_btn"], "callback": self._settings, "args": (game_id, "s",)} ], [ {"text": self.strings['back'], "callback": self._invite, "args": (game_id,)} @@ -336,64 +411,72 @@ class Chess(loader.Module): ]) await utils.answer( call, - self.strings['settings_text'].format( - style=game['style'], - - timer=self.strings['available'] if game['Timer']['available'] and not game['Timer']['timer'] - else self.strings['timer'].format(game['Timer']['timer'].minutes()) if game['Timer']['timer'] - else self.strings['not_available'], - - color=self.strings['random'] if game['host_plays'] == 'r' - else self.strings['white'] if game['host_plays'] == True - else self.strings['black'] - ), + self._get_settings_text(game_id), reply_markup=reply_markup, disable_security=True ) - async def _settings(self, call: InlineCall, game_id: str, ruleset: str | list): + + async def _elo_validator(self, call: InlineCall, data, game_id: str): + reply_markup = {"text": self.strings['back'], "callback": self._settings, "args": (game_id, "e")} + + if not str(data).isdigit(): + return await utils.answer(call, self.strings["not_int_err"], reply_markup=reply_markup) + if not 1400 <= int(data) <= 3200: + return await utils.answer(call, self.strings["out_of_range_err"], reply_markup=reply_markup) + + self.games[game_id]["bot_elo"] = int(data) + self.set("bot_elo", int(data)) + await self._settings(call, game_id, "MARKASSUCCESS") + + async def _settings(self, call: InlineCall, game_id: str, page: str = "", param: str = "", value = None): reply_markup = [] - text = "🍓" - if isinstance(ruleset, str): - if ruleset == "t": + text = "✅" + if page: + if page == "t": text = "⏳" reply_markup.extend([ [ {"text": self.strings['blitz_text'], "action": "answer", "message": self.strings['blitz_message']} ], [ - {"text": self.strings['timer'].format(3), "callback":self._settings, "args": (game_id, ['Timer', 3])}, - {"text": self.strings['timer'].format(5), "callback":self._settings, "args": (game_id, ['Timer', 5])}, + {"text": self.strings['timer'].format(3), "callback":self._settings, "args": (game_id,), "kwargs": {"param": 'Timer', "value": 3}}, + {"text": self.strings['timer'].format(5), "callback":self._settings, "args": (game_id,), "kwargs": {"param": 'Timer', "value": 5}}, ], [ {"text": self.strings['rapid_text'], "action": "answer", "message": self.strings['rapid_message']} ], [ - {"text": self.strings['timer'].format(10), "callback":self._settings, "args": (game_id, ['Timer', 10])}, - {"text": self.strings['timer'].format(15), "callback":self._settings, "args": (game_id, ['Timer', 15])}, - {"text": self.strings['timer'].format(30), "callback":self._settings, "args": (game_id, ['Timer', 30])}, - {"text": self.strings['timer'].format(60), "callback":self._settings, "args": (game_id, ['Timer', 60])} + {"text": self.strings['timer'].format(10), "callback":self._settings, "args": (game_id,), "kwargs": {"param": 'Timer', "value": 10}}, + {"text": self.strings['timer'].format(15), "callback":self._settings, "args": (game_id,), "kwargs": {"param": 'Timer', "value": 15}}, + {"text": self.strings['timer'].format(30), "callback":self._settings, "args": (game_id,), "kwargs": {"param": 'Timer', "value": 30}}, + {"text": self.strings['timer'].format(60), "callback":self._settings, "args": (game_id,), "kwargs": {"param": 'Timer', "value": 60}} ], [ - {"text": self.strings['no_clock_text'], "callback":self._settings, "args": (game_id, ['Timer', True])} + {"text": self.strings['no_clock_text'], "callback":self._settings, "args": (game_id,), "kwargs": {"param": 'Timer', "value": True}} ] ]) - elif ruleset == "c": + elif page == "c": text = "♟️" reply_markup.extend([ [ - {"text": self.strings['white'], "callback":self._settings, "args": (game_id, ['host_plays', True])}, - {"text": self.strings['black'], "callback":self._settings, "args": (game_id, ['host_plays', True] )} + {"text": self.strings['white'], "callback":self._settings, "args": (game_id,), "kwargs": {"param": 'host_plays', "value": True}}, + {"text": self.strings['black'], "callback":self._settings, "args": (game_id,), "kwargs": {"param": 'host_plays', "value": False}} ], [ - {"text": self.strings['random'], "callback":self._settings, "args": (game_id, ['host_plays', 'r'])} + {"text": self.strings['random'], "callback":self._settings, "args": (game_id,), "kwargs": {"param": 'host_plays', "value": 'r'}} ] ]) - elif ruleset == "s": + elif page == "s": text = "✏️" reply_markup.extend([ - [{"text": st["symbol"] + self.strings[name], "callback":self._settings, "args": (game_id, ["style", name])}] + [{"text": st["symbol"] + self.strings[name], "callback":self._settings, "args": (game_id,), "kwargs": {"param": "style", "value": name}}] for name, st in self.styles.items() ]) + elif page == "e": + text = "🧠" + reply_markup.extend([ + [{"text": self.strings["set_btn"], "input": self.strings["bot_elo_btn"], "handler": self._elo_validator, "args": (game_id,)}] + ]) reply_markup.append( [ @@ -404,48 +487,147 @@ class Chess(loader.Module): await utils.answer(call, text, reply_markup=reply_markup, disable_security=True) else: await call.answer("✅") - if ruleset[0] == "style": - self.set('style', ruleset[1]) - if ruleset[0] == "Timer" and isinstance(ruleset[1], int): - self.games[game_id]['Timer']['timer'] = Timer(ruleset[1]*60) - else: - self.games[game_id][ruleset[0]] = ruleset[1] - await self.settings(call, game_id) - + if param == "style": + self.set("style", value) - @loader.command(ru_doc="[reply/username/id] - предложить человеку сыграть партию") - async def chess(self, message: Message): - """[reply/username/id] - propose a person to play a game""" - sender, opponent = await self.get_players(message) - if not sender or not opponent: return - if sender['id'] == opponent['id'] and not self.config["play_self"]: - await utils.answer(message, self.strings["playing_with_yourself?"]) - return + if param == "Timer" and isinstance(value, int): + self.games[game_id]['Timer']['timer'] = Timer(value*60) + else: + self.games[game_id][param] = value + await self.settings(call, game_id) + + def _get_settings_text(self, game_id: str): + game = self.games[game_id] + timer = game['Timer'] + return ( + self.strings['settings_text'].format( + style=game['style'], + + timer=self.strings['available'] if timer['available'] and not timer['timer'] + else self.strings['timer'].format(timer['timer'].minutes()) if timer['timer'] + else self.strings['not_available'], + + color=self.strings['random'] if game['host_plays'] == 'r' + else self.strings['white'] if game['host_plays'] + else self.strings['black'] + ) + + ("\n " + self.strings["bot_elo"].format(elo=game["bot_elo"]) if game["vs_bot"] else "") + ) + + + def _get_new_game_id(self): if self.games: - past_game = next(reversed(self.games.values())) + past_game = next(reversed(self.games.values())) if not past_game.get("game", None): self.games.pop(past_game['game_id'], None) if not self.games: game_id = str(1) else: game_id = str(max(map(int, self.games.keys())) + 1) - self.games[game_id] = GameObj( - game_id = game_id, - sender = sender, - opponent = opponent, - Timer = {"available": True if isinstance(message.peer_id, PeerUser) else False, "timer": None, "timer_loop": False}, - time = int(time.time()), - host_plays = "r", - style = self.gsettings['style'] - ) + + return game_id + + def _create_game(self, game_id: str, _params: dict = None): + params = { + "game_id": game_id, + "vs_bot": False, + "bot_elo": self.get("bot_elo", 3400), + "sender": None, + "opponent": None, + "Timer": { + "available": False, + "timer": None, + "timer_loop": False + }, + "time": int(time.time()), + "host_plays": "r", + "style": self.get("style", "figures-with-circles") + } + + if _params: + params.update(_params) + + self.games[game_id] = GameObj(**params) + + + @loader.command(ru_doc="[reply/username/id] - предложить человеку сыграть партию") + async def chess(self, message: Message | InlineCall, _sender: dict = None): + """[nothing/reply/username/id] - propose a person to play a game""" + if _sender is None: + _sender = {} + sender, opponent = await self.get_players(message, sender=_sender) + if not opponent: + r_m = {"text": self.strings["i_wanna"], "callback": self.chess, "args": (sender,)} + + await utils.answer( + message, + self.strings["is_someone_wanna_play"], + reply_markup=r_m, + disable_security=True, + ) + return + if sender['id'] == opponent['id'] and not self.config["play_self"]: + await utils.answer(message, self.strings["playing_with_yourself?"]) + return + + game_id = self._get_new_game_id() + + mod_params = { + "sender": sender, + "opponent": opponent, + "Timer": { + "available": isinstance(message, Message) and isinstance(message.peer_id, PeerUser), + "timer": None, + "timer_loop": False + } + } + + self._create_game(game_id, mod_params) + + if _sender: + self.games[game_id]["alr_accepted"] = True + + await self._invite(message, game_id) + + @loader.command(ru_doc="[reply/username/id] - предложить человеку сыграть партию против 🐟 Stockfish") + async def stockfish(self, message: Message): + """[reply/username/id] - propose a person to play a game against a 🐟 Stockfish""" + if not self.config["stockfish_path"] or not check_path(self.config["stockfish_path"]): + return await utils.answer( + message, + self.strings["stockfish_not_found"], + reply_markup={ + "text": self.strings["install_stockfish"], + "callback": self.install_stockfish, + } + ) + + if message.is_reply: + player = await self.get_players(message, opponent_only=True) + else: + player = await self.get_players(message, sender_only=True) + + stockfish = { + "name": "Stockfish", + "id": -42, + } + + game_id = self._get_new_game_id() + + mod_params = { + "vs_bot": True, + "sender": stockfish, + "opponent": player, + "Timer": { + "available": isinstance(message, Message) and isinstance(message.peer_id, PeerUser), + "timer": None, + "timer_loop": False + } + } + + self._create_game(game_id, mod_params) + await self._invite(message, game_id) - -# @loader.command(ru_doc="посмотреть текущее состояние модуля и статистику своих партий") -# async def chesstats(self, message: Message): -# """view the current state of the module and statistics of your games""" -# total_games = len(self.get("games_backup", {})) -# await utils.answer(message, f"♟️ {self.strings['name']} ♟️\n\nTotal games played: {total_games}") - # TODO: добавить кнопки для просмотра состояния каждой партии; считать победы/поражения/ничьи и прочую бесполезную статистику; проверка на наличие исполняемого файла шахматного движка для возможности игры против ИИ; возможность экспорта партии в PGN; возможность продолжить сохранённую партию ############## Preparing all for game start... ############## @@ -455,26 +637,29 @@ class Chess(loader.Module): self.games.pop(game_id, None) await utils.answer(call, self.strings["declined"]) return + + await utils.answer(call, "🌒") + game = self.games[game_id] - await utils.answer(call, self.strings["step1"]) - await asyncio.sleep(0.8) - await utils.answer(call, self.strings["step2"]) game["style"] = self.styles[game["style"]] - await asyncio.sleep(0.8) - await utils.answer(call, self.strings["step3"]) + if (turn := game.pop("host_plays")) == "r": turn = r.choice([True, False]) - game["sender"]["color"] = True if turn else False - game["opponent"]["color"] = False if turn else True - await asyncio.sleep(0.8) - await utils.answer(call, self.strings["step4"]) + + game["sender"]["color"] = turn + game["opponent"]["color"] = not turn game["Timer"].pop("available", None) + if game.get("alr_accepted", None): + game.pop("alr_accepted") + await asyncio.sleep(0.8) + if isinstance(self.games[game_id]["Timer"]["timer"], Timer): await utils.answer(call, self.strings["step4.T"]) await self._set_timer(call, game_id, call._units[call.unit_id]['chat']) await asyncio.sleep(0.8) return await utils.answer(call, self.strings["waiting_for_start"]) + await self._start_game(call, game_id) async def _set_timer(self, board_call: InlineCall, game_id: str, chat_id): @@ -486,7 +671,11 @@ class Chess(loader.Module): "" ), chat_id, - reply_markup = {"text": self.strings["start_timer"], "callback": self._start_timer, "args": (board_call, game_id,)}, + reply_markup = { + "text": self.strings["start_timer"], + "callback": self._start_timer, + "args": (board_call, game_id,) + }, disable_security = True, ) ) @@ -496,23 +685,26 @@ class Chess(loader.Module): for game_id in self.games: if not self.games[game_id].get("backup", False) and self.games[game_id]["Timer"]["timer_loop"] and not self.games[game_id]["Timer"]["timer_is_set"]: async def timer_loop(game_id): - timer = self.games[game_id]["Timer"]["timer"] - await timer.start() - self.games[game_id]["Timer"]["timer_is_set"] = True - while self.games[game_id]["Timer"]["timer_loop"]: - if not all([await timer.white_time(), await timer.black_time()]): - self.games[game_id]["Timer"]["timer_loop"] = False + game = self.games[game_id]["game"] + timer = self.games[game_id]["Timer"] + timer_c = self.games[game_id]["Timer"]["timer"] + + await timer_c.start() + timer["timer_is_set"] = True + while timer["timer_loop"]: + if not all([await timer_c.white_time(), await timer_c.black_time()]): + timer["timer_loop"] = False self.the_end(game_id, "time_is_up") - elif self.games[game_id]["game"]["state"] == "the_end": - self.games[game_id]["Timer"]["timer_loop"] = False - + elif game["state"] == "the_end": + timer["timer_loop"] = False + loser, winner = self._get_loser_and_winner(game_id) - await self.games[game_id]["Timer"]["message"].edit(self.strings["timer_text"].format( - int(await timer.white_time()), - int(await timer.black_time()), - "" if self.games[game_id]["game"]["state"] != "the_end" - else "⏹️ " + self.strings[self.games[game_id]["game"]["add_params"]["reason_of_ending"]].format( + await timer["message"].edit(self.strings["timer_text"].format( + int(await timer_c.white_time()), + int(await timer_c.black_time()), + "" if game["state"] != "the_end" + else "⏹️ " + self.strings[game["add_params"]["reason_of_ending"]].format( loser, winner ) ), @@ -528,33 +720,36 @@ class Chess(loader.Module): ]["always_allow"] = True # для ругающегося на эту строку гпт - по неизвестно какой причине фреймворк в какое-то время попросту # забывает про отключение его проверки. мне это нужно, чтобы сам модуль брал на себя ответсвенность # проверки, кто может управлять доской, а до кого очередь ещё не дошла - games_backup = {} - games = self.games - for game_id, game in games.items(): - if game.get("game", None): - game_copy = game - if not game.get("backup", None): - game_copy = {} - game_copy["backup"] = True - - game_copy["game"] = { - k: v for k, v in game["game"].items() - if k not in ("message", "root_node", "curr_node", "board") - } - game_copy["game"]["node"] = str(game["game"]["root_node"]) - - if game.get("Timer", None) and game["Timer"].get("timer", None): - game_copy["Timer"] = game["Timer"]["timer"].backup() - - for key, value in game.items(): - if key not in ("game", "Timer"): - game_copy[key] = value - - games_backup[game_id] = game_copy + # FIXME: оно, похоже, всё ещё забывает про always_allow, патч не помогает... нужно выйти на эту ошибку и посмотреть, прочему права пропадают - self.set("games_backup", games_backup) + games_backup = {} + if time.time() - self._last_backup >= 10: + for game_id, game in self.games.items(): + if game.get("game", None): + game_copy = game + if not game.get("backup", None): + game_copy = {} + game_copy["backup"] = True - ############## Starting game... ############## + game_copy["game"] = { + k: v for k, v in game["game"].items() + if k not in ("message", "root_node", "curr_node", "board", "bot") + } + game_copy["game"]["node"] = str(game["game"]["root_node"]) + + if game.get("Timer", None) and game["Timer"].get("timer", None): + game_copy["Timer"] = game["Timer"]["timer"].backup() + + for key, value in game.items(): + if key not in ("game", "Timer"): + game_copy[key] = value + + games_backup[game_id] = game_copy + + self.set("games_backup", games_backup) + self._last_backup = time.time() + + ############## Starting game... ############## async def _start_timer(self, call: InlineCall, board_call: InlineCall, game_id: str): if not await self._check_player(call, game_id): return @@ -562,6 +757,14 @@ class Chess(loader.Module): timer["timer_loop"] = True await self._start_game(board_call, game_id) + async def _init_bot(self, game_id: str, params: dict): + if not self.games[game_id]["vs_bot"]: return + + engine = chess.engine.SimpleEngine.popen_uci(self.config["stockfish_path"]) + engine.configure({"UCI_LimitStrength": True, "UCI_Elo": params["elo"]}) + + self.games[game_id]["game"]["bot"] = engine + async def _start_game(self, call: InlineCall, game_id: str): if not await self._check_player(call, game_id): return game = self.games[game_id] @@ -573,21 +776,23 @@ class Chess(loader.Module): pgn["Black"] = game["opponent"]["name"] if game["sender"]["color"] else game["sender"]["name"] pgn["Result"] = "*" node.headers.update(pgn) - game["game"] = { - "board": chess.Board(), - "message": call, - "root_node": node, - "curr_node": node, - "state": "idle", - "add_params": { - "chosen_figure_coord": "", - "reason_of_ending": "", - "promotion_move": "", - "winner_color": None, - "resigner_color": None, - "draw_offerer": None, - } - } + game["game"] = Game( + board = chess.Board(), + message = call, + root_node = node, + curr_node = node, + state = "idle", + add_params = GameParams( + chosen_figure_coord = "", + reason_of_ending = "", + promotion_move = "", + winner_color = None, + resigner_color = None, + draw_offerer = None, + ), + bot = None, + ) + await self._init_bot(game_id, {"elo": game["bot_elo"]}) await self.update_board(game_id) def idle(self, game_id: str): @@ -596,7 +801,7 @@ class Chess(loader.Module): game["add_params"]["chosen_figure_coord"] = "" game["add_params"]["promotion_move"] = "" game["add_params"]["draw_offerer"] = None - + def choose(self, game_id: str, coord: str): game = self.games[game_id]["game"] game["state"] = "in_choose" @@ -608,7 +813,7 @@ class Chess(loader.Module): game["state"] = "in_promotion" game["add_params"]["chosen_figure_coord"] = "" game["add_params"]["promotion_move"] = move - + def the_end(self, game_id: str, reason: str, winner: bool = None): game = self.games[game_id]["game"] game["state"] = "the_end" @@ -621,6 +826,8 @@ class Chess(loader.Module): "0-1" if winner is False else "1/2-1/2" ) + if self.games[game_id]["vs_bot"]: + game["bot"].quit() def _get_loser_and_winner(self, game_id: str) -> tuple[str, str]: game = self.games[game_id] @@ -633,7 +840,7 @@ class Chess(loader.Module): game = self.games[game_id] piece = game["game"]["board"].piece_at(chess.parse_square(coord)) return game["style"][piece.symbol()] if piece else " " - + def _get_move_symbol(self, game_id: str, move: str) -> str: game = self.games[game_id] if len(move) == 5: @@ -648,7 +855,7 @@ class Chess(loader.Module): and game["game"]["board"].is_capture(move) else "move" ] - + def _get_available_moves(self, game_id: str, coord: str) -> list[str]: if not coord: return [] game = self.games[game_id] @@ -661,18 +868,19 @@ class Chess(loader.Module): coords = copy.deepcopy(self.coords) for coord in self.coords: coords[coord] = self._get_piece_symbol(game_id, coord) - + if game["game"]["state"] == "in_choose": choosen_coord = game["game"]["add_params"]["chosen_figure_coord"] for move in self._get_available_moves(game_id, choosen_coord): coord = move[2:4] coords[coord] = self._get_move_symbol(game_id, move) - + return coords def _get_reply_markup(self, game_id: str, promotion: bool = False, resign_confirm: bool = False, draw_confirm: bool = False) -> list[list[dict]]: game = self.games[game_id] is_end = game["game"]["state"] == "the_end" + reply_markup = utils.chunks( [ { @@ -754,12 +962,16 @@ class Chess(loader.Module): "callback": self.resign, "args": (game_id,), }, - { - "text": "🤝", - "callback": self.draw, - "args": (game_id,), - } ] + + if not game["vs_bot"]: + resign.append( + { + "text": "🤝", + "callback": self.draw, + "args": (game_id,), + } + ) reply_markup.append(resign) return reply_markup @@ -795,6 +1007,33 @@ class Chess(loader.Module): reply_markup=reply_markup, ) + if game["vs_bot"] and game["game"]["board"].turn == game["sender"]["color"] and game["game"]["state"] == "idle": + await self._bot_process_board(game_id) + + async def _bot_process_board(self, game_id: str): + if not (game := self.games[game_id])["vs_bot"]: + return + + board = game["game"]["board"] + bot = game["game"]["bot"] + + _d = game["bot_elo"] // 100 - 10 + depth = r.randint(_d, _d + 15) + + result = bot.play(board, limit=chess.engine.Limit(time=0.1, depth=depth)) + move = result.move + from_coord = chess.square_name(result.move.from_square) + to_coord = chess.square_name(result.move.to_square) + + await asyncio.sleep(r.randint(1, 3)) + await self.choose_coord(None, game_id, from_coord) + await asyncio.sleep(0.7) + await self.choose_coord(None, game_id, to_coord) + + if move.promotion: + await asyncio.sleep(0.7) + await self.pawn_promotion(None, game_id, chess.piece_symbol(move.promotion)) + def make_move(self, game_id: str, move: str): game = self.games[game_id]["game"] move = chess.Move.from_uci(move) @@ -810,44 +1049,52 @@ class Chess(loader.Module): self.set_game_state(game_id) return await self.update_board(game_id) - + async def _back_to_game(self, _, game_id: str): self.set_game_state(game_id) await self.update_board(game_id) async def resign(self, call: InlineCall, game_id: str, confirm: bool = False): - if not await self._check_player(call, game_id): return + if not await self._check_player(call, game_id, skip_turn_check=True): return game = self.games[game_id] if not confirm: - game["game"]["add_params"]["resign_offerer"] = self._get_color_by_player( + game["game"]["add_params"]["resigner_color"] = self._get_color_by_player( game_id, call.from_user.id ) return await self.update_board(game_id, resign_confirm=True) - self.the_end(game_id, "resign", winner=not game["game"]["board"].turn) + + resigner = self._get_player_by_color( + game_id, game["game"]["add_params"]["resigner_color"] + ) + + if call.from_user.id != resigner["id"]: + return await call.answer(self.strings["resign_not_you"]) + + self.the_end(game_id, "resign", winner=not resigner["color"]) await self.update_board(game_id) async def draw(self, call: InlineCall, game_id: str, accept: bool = False): if not await self._check_player(call, game_id, skip_turn_check=True): return game = self.games[game_id] if accept: - offerer_id = self._get_player_by_color( + offerer = self._get_player_by_color( game_id, game["game"]["add_params"]["draw_offerer"] - )["id"] + ) - if call.from_user.id == offerer_id: + if call.from_user.id == offerer["id"]: await call.answer(self.strings["draw_not_you"]) return self.the_end(game_id, "draw") return await self.update_board(game_id) - + game["game"]["add_params"]["draw_offerer"] = self._get_color_by_player( game_id, call.from_user.id ) return await self.update_board(game_id, draw_confirm=True) - + def set_game_state(self, game_id: str): game = self.games[game_id]["game"] board = game["board"] @@ -862,7 +1109,7 @@ class Chess(loader.Module): self.the_end(game_id, "seventyfive_moves") elif board.is_fivefold_repetition(): self.the_end(game_id, "fivefold_repetition") - + async def choose_coord(self, call: BotInlineCall, game_id: str, coord: str): if not await self._check_player(call, game_id): return game = self.games[game_id]["game"] @@ -874,12 +1121,12 @@ class Chess(loader.Module): else: await call.answer(self.strings["no_moves"]) return await self.update_board(game_id) - + elif state == "in_choose": if coord == game["add_params"]["chosen_figure_coord"]: self.idle(game_id) return await self.update_board(game_id) - + av_moves = self._get_available_moves(game_id, game["add_params"]["chosen_figure_coord"]) coord_matches = [move for move in av_moves if coord in move] @@ -896,14 +1143,14 @@ class Chess(loader.Module): elif game["board"].piece_at(chess.parse_square(coord)): self.choose(game_id, coord) return await self.update_board(game_id) - + else: self.idle(game_id) return await self.update_board(game_id) - + elif state == "in_promotion": return await call.answer(self.strings["can_not_move"]) - + elif state == "the_end": return await call.answer(self.strings["game_ended"]) @@ -917,7 +1164,7 @@ class Chess(loader.Module): def _get_player_by_color(self, game_id: str, color: bool): game = self.games[game_id] return game["sender"] if game["sender"]["color"] == color else game["opponent"] - + def _get_color_by_player(self, game_id: str, player_id: int): game = self.games[game_id] if game["sender"]["id"] == player_id: diff --git a/ZetGoHack/nullmod/HaremManager.py b/ZetGoHack/nullmod/HaremManager.py index 16e8790..19d8c83 100644 --- a/ZetGoHack/nullmod/HaremManager.py +++ b/ZetGoHack/nullmod/HaremManager.py @@ -1,4 +1,4 @@ -__version__ = (1,1,7) +__version__ = (1,1,8) #░░░███░███░███░███░███ #░░░░░█░█░░░░█░░█░░░█░█ #░░░░█░░███░░█░░█░█░█░█ @@ -71,6 +71,12 @@ class HaremManager(loader.Module): "Интервал между автобонусом", validator=loader.validators.Float(2.0) ), + loader.ConfigValue( + "skip-requests", + True, + "Пропускать ли ссылки с заявками? *(подачи заявки вам может начать спамить рекламный бот, что не многим может понравиться)", + validator=loader.validators.Boolean() + ), ) async def client_ready(self): @@ -270,69 +276,75 @@ class HaremManager(loader.Module): a = r.buttons for i in a: for button in i: # каждая кнопка... - if button.url: - alr = False # "уже зашёл" - if "addlist/" in button.url: # добавление папок - slug = button.url.split("addlist/")[-1] - peers = await self.client(CheckChatlistInviteRequest(slug=slug)) - if peers: - peers = peers.peers + try: + if button.url: + alr = False # "уже зашёл" + if "addlist/" in button.url: # добавление папок + slug = button.url.split("addlist/")[-1] + peers = await self.client(CheckChatlistInviteRequest(slug=slug)) + if peers: + peers = peers.peers + try: + a = await self.client(JoinChatlistInviteRequest(slug=slug, peers=peers)) + chats_in_folders.append(peers) # для выхода + for update in a.updates: + if isinstance(update, UpdateDialogFilter): + folders.append(InputChatlistDialogFilter(filter_id=update.id)) # для удаления папки + except: pass + continue + if "t.me/boost" in button.url: # бустить не обязательно + wait_boost = True + continue + if not bool(re.match(r"^https?:\/\/t\.me\/[^\/]+\/?$", button.url)): # дополнительные вложения отметаем + continue + if "t.me/+" in button.url: # приватные чаты try: - a = await self.client(JoinChatlistInviteRequest(slug=slug, peers=peers)) - chats_in_folders.append(peers) # для выхода - for update in a.updates: - if isinstance(update, UpdateDialogFilter): - folders.append(InputChatlistDialogFilter(filter_id=update.id)) # для удаления папки - except: pass - continue - if "t.me/boost" in button.url: # бустить не обязательно - wait_boost = True - continue - if not bool(re.match(r"^https?:\/\/t\.me\/[^\/]+\/?$", button.url)): # дополнительные вложения отметаем - continue - if "t.me/+" in button.url: # приватные чаты - try: - a = await self.client(CheckChatInviteRequest(button.url.split("+")[-1])) - if not hasattr(a, "request_needed") or not a.request_needed: # получить айди приватного чата/канала с приглашениями без входа невозможно - pass - else: - url = button.url.split("?")[0] if "?" in button.url else button.url - try: - await self.client(ImportChatInviteRequest(button.url.split("+")[-1])) - except InviteRequestSentError: pass - await asyncio.sleep(3) - try: - entity = await self.client.get_entity(url) - except ValueError: - try: - await asyncio.sleep(15) - entity = await self.client.get_entity(url) - except: - continue - except: + a = await self.client(CheckChatInviteRequest(button.url.split("+")[-1])) + if not hasattr(a, "request_needed") or not a.request_needed: # получить айди приватного чата/канала с приглашениями без входа невозможно pass - alr = True - except: continue - url = button.url.split("?")[0] if "?" in button.url else button.url - if not alr: - try: - entity = await self.client.get_entity(url) - except: - entity = (await self.client(ImportChatInviteRequest(button.url.split("+")[-1]))).chats[0] #gotten class Updates - alr = True - if hasattr(entity, "broadcast"): + else: + if self.config["skip-requests"]: + continue + + url = button.url.split("?")[0] if "?" in button.url else button.url + try: + await self.client(ImportChatInviteRequest(button.url.split("+")[-1])) + except InviteRequestSentError: pass + await asyncio.sleep(3) + try: + entity = await self.client.get_entity(url) + except ValueError: + try: + await asyncio.sleep(15) + entity = await self.client.get_entity(url) + except: + continue + except: + pass + alr = True + except: continue + url = button.url.split("?")[0] if "?" in button.url else button.url if not alr: - await self.client(JoinChannelRequest(button.url)) - to_leave.append(entity.id) - else: - to_leave.append(entity.chat.id) if hasattr(entity,"chat") else to_leave.append(entity.id) if hasattr(entity,"id") else None - elif hasattr(entity, "bot"): - username = entity.username if entity.username is not None else entity.usernames[0].username - try: - await self.client(UnblockRequest(username)) - except: print("блин") - await self.client.send_message(entity, "/start") - to_block.append(username) + try: + entity = await self.client.get_entity(url) + except: + entity = (await self.client(ImportChatInviteRequest(button.url.split("+")[-1]))).chats[0] #gotten class Updates + alr = True + if hasattr(entity, "broadcast"): + if not alr: + await self.client(JoinChannelRequest(button.url)) + to_leave.append(entity.id) + else: + to_leave.append(entity.chat.id) if hasattr(entity,"chat") else to_leave.append(entity.id) if hasattr(entity,"id") else None + elif hasattr(entity, "bot"): + username = entity.username if entity.username is not None else entity.usernames[0].username + try: + await self.client(UnblockRequest(username)) + except: print("блин") + await self.client.send_message(entity, "/start") + to_block.append(username) + except: + continue flyer_messages = await self.client.get_messages(id, limit=1) if wait_boost: await asyncio.sleep(150) @@ -348,7 +360,7 @@ class HaremManager(loader.Module): for channel in to_leave: try: await self.client(LeaveChannelRequest(channel)) - except Exception as e: + except Exception: pass count = 0 if not self.get(f"last_lout-{bot}") or int(time.time()) - self.get(f"last_lout-{bot}") > 43200: @@ -424,7 +436,7 @@ class HaremManager(loader.Module): await utils.answer(message, "💡") clicks = self._solution(pattern) if not clicks: - await utils.answer(message, "Иди код трейси гений.") + await utils.answer(message, "⚠️ Если вы получили такое сообщение, обратитесь в чат @AnyModule. такое поведение некорректно для команды") return #*смачный пинок кодеру под зад.* for i in range(len(clicks)): if clicks[i] == 1: diff --git a/modules.json b/modules.json index 11f7496..0a476a5 100644 --- a/modules.json +++ b/modules.json @@ -5797,7 +5797,10 @@ }, "commands": [ { - "chess": "[reply/username/id] - propose a person to play a game [reply/username/id] - предложить человеку сыграть партию" + "chess": "[nothing/reply/username/id] - propose a person to play a game [reply/username/id] - предложить человеку сыграть партию" + }, + { + "stockfish": "[reply/username/id] - propose a person to play a game against a 🐟 Stockfish [reply/username/id] - предложить человеку сыграть партию против 🐟 Stockfish" } ], "new_commands": [ @@ -5805,7 +5808,14 @@ "chess": { "ru_doc": "[reply/username/id] - предложить человеку сыграть партию", "en_doc": null, - "doc": "[reply/username/id] - propose a person to play a game" + "doc": "[nothing/reply/username/id] - propose a person to play a game" + } + }, + { + "stockfish": { + "ru_doc": "[reply/username/id] - предложить человеку сыграть партию против 🐟 Stockfish", + "en_doc": null, + "doc": "[reply/username/id] - propose a person to play a game against a 🐟 Stockfish" } } ], @@ -30498,2312 +30508,6 @@ "Chat" ] }, - "C0dwiz/H.Modules/Text2File.py": { - "name": "Text2File", - "description": "Module for convertation your text to file", - "meta": { - "pic": null, - "banner": null, - "developer": "@hikka_mods" - }, - "commands": [ - { - "ttfcmd": "Создать файл с вашим текстом или кодом Create a file with your text or code" - } - ], - "new_commands": [ - { - "ttf": { - "ru_doc": "Создать файл с вашим текстом или кодом", - "en_doc": "Create a file with your text or code", - "doc": null - } - } - ], - "category": [ - "Tools", - "Chat" - ] - }, - "C0dwiz/H.Modules/IrisSimpleMod.py": { - "name": "IrisSimpleMod", - "description": "Модуль для базового взаимодействия с Ирисом", - "meta": { - "pic": null, - "banner": null, - "developer": "@hikka_mods" - }, - "commands": [ - { - "bag": "Check bag Проверить мешок" - }, - { - "farm": "Farm iris-coins Зафармить ирис-коины" - }, - { - "irisstats": "Display user stats Вывести анкету" - }, - { - "irisping": "Display bot stats Вывести статистику ботов" - } - ], - "new_commands": [ - { - "bag": { - "ru_doc": "Проверить мешок", - "en_doc": null, - "doc": "Check bag" - } - }, - { - "farm": { - "ru_doc": "Зафармить ирис-коины", - "en_doc": null, - "doc": "Farm iris-coins" - } - }, - { - "irisstats": { - "ru_doc": "Вывести анкету", - "en_doc": null, - "doc": "Display user stats" - } - }, - { - "irisping": { - "ru_doc": "Вывести статистику ботов", - "en_doc": null, - "doc": "Display bot stats" - } - } - ], - "category": [ - "Tools", - "Chat" - ] - }, - "C0dwiz/H.Modules/nsfwart.py": { - "name": "NSFWArtMod", - "description": "Sends cute anime nsfw-art", - "meta": { - "pic": null, - "banner": null, - "developer": "@hikka_mods" - }, - "commands": [ - { - "nsfwartcmd": "Отправьте симпатичный nsfw-арт Send cute nsfw-art" - } - ], - "new_commands": [ - { - "nsfwart": { - "ru_doc": "Отправьте симпатичный nsfw-арт", - "en_doc": "Send cute nsfw-art", - "doc": null - } - } - ], - "category": [ - "Tools", - "Fun" - ] - }, - "C0dwiz/H.Modules/CryptoCurrency.py": { - "name": "CryptoCurrencyMod", - "description": "Module for displaying current cryptocurrency exchange rates.", - "meta": { - "pic": null, - "banner": null, - "developer": "@hikka_mods" - }, - "commands": [ - { - "crypto": "Отображает текущий курс криптовалюты в рублях, долларах США и евро Displays the current cryptocurrency rate in RUB, USD, and EUR" - } - ], - "new_commands": [ - { - "crypto": { - "ru_doc": "Отображает текущий курс криптовалюты в рублях, долларах США и евро", - "en_doc": "Displays the current cryptocurrency rate in RUB, USD, and EUR", - "doc": null - } - } - ], - "category": [ - "Tools", - "Media" - ] - }, - "C0dwiz/H.Modules/AnimeQuotes.py": { - "name": "AnimeQuotesMod", - "description": "A module for sending random quotes from anime", - "meta": { - "pic": null, - "banner": null, - "developer": "@hikka_mods" - }, - "commands": [ - { - "quote": "Получить случайную цитату из аниме Get a random quote from the anime" - } - ], - "new_commands": [ - { - "quote": { - "ru_doc": "Получить случайную цитату из аниме", - "en_doc": "Get a random quote from the anime", - "doc": null - } - } - ], - "category": [ - "Tools", - "Fun" - ] - }, - "C0dwiz/H.Modules/HModsLibrary.py": { - "name": "HModsLib", - "description": "Library required for most H:Mods modules.", - "meta": { - "pic": null, - "banner": null, - "developer": "@hikka_mods" - }, - "commands": [], - "new_commands": [], - "category": [ - "Fun", - "Tools" - ] - }, - "C0dwiz/H.Modules/VoiceDL.py": { - "name": "VoiceDL", - "description": "Voice Downloader module", - "meta": { - "pic": null, - "banner": null, - "developer": "@hikka_mods" - }, - "commands": [ - { - "voicedl": " [reply] — загружает выбранное голосовое сообщение в виде файла mp3 и кидает его в чат. [reply] — downloads the selected voice message as an MP3 file and sends it in the chat." - } - ], - "new_commands": [ - { - "voicedl": { - "ru_doc": " [reply] — загружает выбранное голосовое сообщение в виде файла mp3 и кидает его в чат.", - "en_doc": " [reply] — downloads the selected voice message as an MP3 file and sends it in the chat.", - "doc": null - } - } - ], - "category": [ - "Chat", - "Tools" - ] - }, - "C0dwiz/H.Modules/FakeWallet.py": { - "name": "FakeWallet", - "description": "Fun joke - fake crypto wallet. You can change cryptocurrency values ​​using .cfg FakeWallet.", - "meta": { - "pic": null, - "banner": null, - "developer": "@hikka_mods" - }, - "commands": [ - { - "fwalletcmd": "Чтобы заполучить поддельный кошелек To get a fake wallet" - }, - { - "fwinfocmd": "Информация о FakeModule Info about FakeModule" - } - ], - "new_commands": [ - { - "fwallet": { - "ru_doc": "Чтобы заполучить поддельный кошелек", - "en_doc": "To get a fake wallet", - "doc": null - } - }, - { - "fwinfo": { - "ru_doc": "Информация о FakeModule", - "en_doc": "Info about FakeModule", - "doc": null - } - } - ], - "category": [ - "Tools", - "Admin" - ] - }, - "C0dwiz/H.Modules/profile.py": { - "name": "ProfileEditorMod", - "description": "This module can change your Telegram profile.", - "meta": { - "pic": null, - "banner": null, - "developer": "@hikka_mods" - }, - "commands": [ - { - "namecmd": "для того, чтобы сменить свое имя/отчество for change your first/second name" - }, - { - "aboutcmd": "чтобы изменить свою биографию for change your bio" - }, - { - "usercmd": "- for change your username. Enter value without \"@\". для изменения вашего имени пользователя. Введите значение без '@' for change your username. Enter value without '@'" - } - ], - "new_commands": [ - { - "name": { - "ru_doc": "для того, чтобы сменить свое имя/отчество", - "en_doc": "for change your first/second name", - "doc": null - } - }, - { - "about": { - "ru_doc": "чтобы изменить свою биографию", - "en_doc": "for change your bio", - "doc": null - } - }, - { - "user": { - "ru_doc": "для изменения вашего имени пользователя. Введите значение без '@'", - "en_doc": "for change your username. Enter value without '@'", - "doc": "- for change your username. Enter value without \"@\"." - } - } - ], - "category": [ - "Tools", - "Chat" - ] - }, - "C0dwiz/H.Modules/animals.py": { - "name": "animals", - "description": "Random cats and dogs", - "meta": { - "pic": null, - "banner": null, - "developer": "@hikka_mods" - }, - "commands": [ - { - "fcatcmd": "Файлы случайных фотографий кошек Random photos of cats files" - }, - { - "fdogcmd": "Случайные фотографии собачьих файлов Random photos of dog files" - }, - { - "catcmd": "Случайные фотографии кошек Random photos of cats" - }, - { - "dogcmd": "Случайные фотографии собаки Random photos of dog" - } - ], - "new_commands": [ - { - "fcat": { - "ru_doc": "Файлы случайных фотографий кошек", - "en_doc": "Random photos of cats files", - "doc": null - } - }, - { - "fdog": { - "ru_doc": "Случайные фотографии собачьих файлов", - "en_doc": "Random photos of dog files", - "doc": null - } - }, - { - "cat": { - "ru_doc": "Случайные фотографии кошек", - "en_doc": "Random photos of cats", - "doc": null - } - }, - { - "dog": { - "ru_doc": "Случайные фотографии собаки", - "en_doc": "Random photos of dog", - "doc": null - } - } - ], - "category": [ - "Tools", - "Fun" - ] - }, - "C0dwiz/H.Modules/FakeActions.py": { - "name": "FakeActionsMod", - "description": "Module for simulating various actions in chat", - "meta": { - "pic": null, - "banner": null, - "developer": "@hikka_mods" - }, - "commands": [ - { - "ftcmd": " - Simulates typing in chat for the specified number of seconds." - }, - { - "ffcmd": " - Simulates sending a file." - }, - { - "fgcmd": " - Simulates recording a voice message." - }, - { - "fvgcmd": " - Simulates recording a video message." - }, - { - "fpgcmd": " - Simulates playing a game." - } - ], - "new_commands": [ - { - "ft": { - "ru_doc": null, - "en_doc": null, - "doc": " - Simulates typing in chat for the specified number of seconds." - } - }, - { - "ff": { - "ru_doc": null, - "en_doc": null, - "doc": " - Simulates sending a file." - } - }, - { - "fg": { - "ru_doc": null, - "en_doc": null, - "doc": " - Simulates recording a voice message." - } - }, - { - "fvg": { - "ru_doc": null, - "en_doc": null, - "doc": " - Simulates recording a video message." - } - }, - { - "fpg": { - "ru_doc": null, - "en_doc": null, - "doc": " - Simulates playing a game." - } - } - ], - "category": [ - "Chat", - "Tools" - ] - }, - "C0dwiz/H.Modules/AutofarmCookies.py": { - "name": "AutofarmCookiesMod", - "description": "Autofarm in the bot @cookies_game_bot", - "meta": { - "pic": null, - "banner": null, - "developer": "@hikka_mods" - }, - "commands": [ - { - "cookon": "Запустить автофарминг Launch auto-farming" - }, - { - "cookoff": "Остановить автофарминг Stop auto-farming" - }, - { - "cookies": "Вывод кол-ва коинов, добытых этим модулем Output of the number of coins mined by this module" - }, - { - "me": "Показывает ваш мешок Shows your bag" - }, - { - "ckies": "Помощь по модулю AutofarmCookies Help with the AutofarmCookies module" - } - ], - "new_commands": [ - { - "cookon": { - "ru_doc": "Запустить автофарминг", - "en_doc": "Launch auto-farming", - "doc": null - } - }, - { - "cookoff": { - "ru_doc": "Остановить автофарминг", - "en_doc": "Stop auto-farming", - "doc": null - } - }, - { - "cookies": { - "ru_doc": "Вывод кол-ва коинов, добытых этим модулем", - "en_doc": "Output of the number of coins mined by this module", - "doc": null - } - }, - { - "me": { - "ru_doc": "Показывает ваш мешок", - "en_doc": "Shows your bag", - "doc": null - } - }, - { - "ckies": { - "ru_doc": "Помощь по модулю AutofarmCookies", - "en_doc": "Help with the AutofarmCookies module", - "doc": null - } - } - ], - "category": [ - "Tools", - "Chat" - ] - }, - "C0dwiz/H.Modules/BirthdayTime.py": { - "name": "DaysToMyBirthday", - "description": "Counting down to your birthday", - "meta": { - "pic": null, - "banner": null, - "developer": "@hikka_mods" - }, - "commands": [ - { - "btname": "Выставить таймер дней в ник (нестабильно) Set the timer of days in the nickname (unstable)" - }, - { - "bt": "Вывести таймер Display the timer" - } - ], - "new_commands": [ - { - "btname": { - "ru_doc": "Выставить таймер дней в ник (нестабильно)", - "en_doc": "Set the timer of days in the nickname (unstable)", - "doc": null - } - }, - { - "bt": { - "ru_doc": "Вывести таймер", - "en_doc": "Display the timer", - "doc": null - } - } - ], - "category": [ - "Tools", - "Productivity" - ] - }, - "C0dwiz/H.Modules/face.py": { - "name": "face", - "description": "random face", - "meta": { - "pic": null, - "banner": null, - "developer": "@hikka_mods" - }, - "commands": [ - { - "rfacecmd": "Рандом kaomoji Random kaomoji" - } - ], - "new_commands": [ - { - "rface": { - "ru_doc": "Рандом kaomoji", - "en_doc": "Random kaomoji", - "doc": null - } - } - ], - "category": [ - "Tools", - "Fun" - ] - }, - "C0dwiz/H.Modules/HAFK.py": { - "name": "HAFK", - "description": null, - "meta": { - "pic": null, - "banner": null, - "developer": "@hikka_mods" - }, - "commands": [ - { - "afk": "[reason / none] – Установить режим AFK [reason / none] – Set AFK mode globally" - }, - { - "afkhere": "[reason / none] – Установить режим AFK только в этом чате. [reason / none] – Set AFK mode in current chat only." - }, - { - "unafk": "Выйти из режима AFK Exit AFK mode" - }, - { - "unafkhere": "Выйти из режима AFK в этом чате Exit AFK mode in this chat" - } - ], - "new_commands": [ - { - "afk": { - "ru_doc": "[reason / none] – Установить режим AFK", - "en_doc": "[reason / none] – Set AFK mode globally", - "doc": null - } - }, - { - "afkhere": { - "ru_doc": "[reason / none] – Установить режим AFK только в этом чате.", - "en_doc": "[reason / none] – Set AFK mode in current chat only.", - "doc": null - } - }, - { - "unafk": { - "ru_doc": "Выйти из режима AFK", - "en_doc": "Exit AFK mode", - "doc": null - } - }, - { - "unafkhere": { - "ru_doc": "Выйти из режима AFK в этом чате", - "en_doc": "Exit AFK mode in this chat", - "doc": null - } - } - ], - "category": [ - "Tools", - "Chat" - ] - }, - "C0dwiz/H.Modules/TelegramStatusCodes.py": { - "name": "TelegramStatusCodes", - "description": "Dictionary of telegram status codes", - "meta": { - "pic": null, - "banner": null, - "developer": "@hikka_mods" - }, - "commands": [ - { - "tgccmd": "<код состояния> - Получение информации о коде состояния - Get status code info" - }, - { - "tgcscmd": "Получите все коды статуса telegram Get all telegram status codes" - } - ], - "new_commands": [ - { - "tgc": { - "ru_doc": "<код состояния> - Получение информации о коде состояния", - "en_doc": " - Get status code info", - "doc": null - } - }, - { - "tgcs": { - "ru_doc": "Получите все коды статуса telegram", - "en_doc": "Get all telegram status codes", - "doc": null - } - } - ], - "category": [ - "Tools", - "Chat" - ] - }, - "C0dwiz/H.Modules/GigaChat.py": { - "name": "GigaChatMod", - "description": "Module for using GigaChat", - "meta": { - "pic": null, - "banner": null, - "developer": "@hikka_mods" - }, - "commands": [ - { - "giga": "Получите исчерпывающий ответ на свой вопрос Get GigaResponse to your question" - }, - { - "gigamodel": "Получить список моделей Get a list of models" - } - ], - "new_commands": [ - { - "giga": { - "ru_doc": "Получите исчерпывающий ответ на свой вопрос", - "en_doc": "Get GigaResponse to your question", - "doc": null - } - }, - { - "gigamodel": { - "ru_doc": "Получить список моделей", - "en_doc": "Get a list of models", - "doc": null - } - } - ], - "category": [ - "Tools", - "Chat" - ] - }, - "C0dwiz/H.Modules/InlineButton.py": { - "name": "InlineButtonMod", - "description": "Create inline button", - "meta": { - "pic": null, - "banner": null, - "developer": "@hikka_mods" - }, - "commands": [ - { - "crinl_inline_handler": "Создать inline кнопку\nНапример: @username_bot crinl Текст сообщения, Текст кнопки, Ссылка в кнопке Create an inline button\nexample: @username_bot crinl Message text, Button text, Link in the button" - } - ], - "new_commands": [ - { - "crinl_inline_handler": { - "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", - "doc": null - } - } - ], - "category": [ - "Tools", - "Chat" - ] - }, - "C0dwiz/H.Modules/Memes.py": { - "name": "MemesMod", - "description": "Random memes", - "meta": { - "pic": null, - "banner": null, - "developer": "@hikka_mods" - }, - "commands": [ - { - "memescmd": "" - } - ], - "new_commands": [ - { - "memes": { - "ru_doc": null, - "en_doc": null, - "doc": null - } - } - ], - "category": [ - "Tools", - "Fun" - ] - }, - "C0dwiz/H.Modules/H.py": { - "name": "H", - "description": "H", - "meta": { - "pic": null, - "banner": null, - "developer": "@hikka_mods" - }, - "commands": [ - { - "h": "H H" - } - ], - "new_commands": [ - { - "h": { - "ru_doc": "H", - "en_doc": null, - "doc": "H" - } - } - ], - "category": [ - "Fun", - "Tools" - ] - }, - "C0dwiz/H.Modules/WindowsKeys.py": { - "name": "WindowsKeys", - "description": "Provides you Windows activation keys", - "meta": { - "pic": null, - "banner": null, - "developer": "@hikka_mods" - }, - "commands": [ - { - "winkey": "Открывает выбор ключа для активации Windows Opens the Windows activation key selection" - } - ], - "new_commands": [ - { - "winkey": { - "ru_doc": "Открывает выбор ключа для активации Windows", - "en_doc": "Opens the Windows activation key selection", - "doc": null - } - } - ], - "category": [ - "Tools", - "Fun" - ] - }, - "C0dwiz/H.Modules/AniLibria.py": { - "name": "AniLibriaMod", - "description": "Searches and gives random agtme on the AniLibria database.", - "meta": { - "pic": null, - "banner": null, - "developer": "@hikka_mods" - }, - "commands": [ - { - "arandom": "Возвращает случайный тайтл из базы Returns a random title from the database" - } - ], - "new_commands": [ - { - "arandom": { - "ru_doc": "Возвращает случайный тайтл из базы", - "en_doc": "Returns a random title from the database", - "doc": null - } - } - ], - "category": [ - "Tools", - "Fun" - ] - }, - "C0dwiz/H.Modules/hikkahost.py": { - "name": "HikkahostMod", - "description": "Hikkahost manager.", - "meta": { - "pic": null, - "banner": null, - "developer": "@hikka_mods" - }, - "commands": [ - { - "hinfocmd": "Статус HikkaHost Status HikkaHost" - }, - { - "hlogscmd": "Логи HikkaHost Logs HikkaHost" - }, - { - "hrestartcmd": "Рестарт HikkaHost Restart HikkaHost" - } - ], - "new_commands": [ - { - "hinfo": { - "ru_doc": "Статус HikkaHost", - "en_doc": "Status HikkaHost", - "doc": null - } - }, - { - "hlogs": { - "ru_doc": "Логи HikkaHost", - "en_doc": "Logs HikkaHost", - "doc": null - } - }, - { - "hrestart": { - "ru_doc": "Рестарт HikkaHost", - "en_doc": "Restart HikkaHost", - "doc": null - } - } - ], - "category": [ - "Tools", - "Fun" - ] - }, - "C0dwiz/H.Modules/Video2GIF.py": { - "name": "Video2GIF", - "description": "Converts video to GIF", - "meta": { - "pic": null, - "banner": null, - "developer": "@hikka_mods" - }, - "commands": [ - { - "gifc": "[reply | в одном сообщении с видео] — конвертирует видео в GIF. [reply | in one message with video] — Converts video to GIF." - } - ], - "new_commands": [ - { - "gifc": { - "ru_doc": "[reply | в одном сообщении с видео] — конвертирует видео в GIF.", - "en_doc": "[reply | in one message with video] — Converts video to GIF.", - "doc": null - } - } - ], - "category": [ - "Media", - "Tools" - ] - }, - "C0dwiz/H.Modules/ReplaceVowels.py": { - "name": "VowelReplacer", - "description": "Replaces vowel letters with ё", - "meta": { - "pic": null, - "banner": null, - "developer": "@hikka_mods" - }, - "commands": [ - { - "vowelreplace": "Включить или отключить замену гласных на ё. Enable or disable vowel substitution for ё." - } - ], - "new_commands": [ - { - "vowelreplace": { - "ru_doc": "Включить или отключить замену гласных на ё.", - "en_doc": "Enable or disable vowel substitution for ё.", - "doc": null - } - } - ], - "category": [ - "Tools", - "Chat" - ] - }, - "C0dwiz/H.Modules/TempChat.py": { - "name": "TempChatMod", - "description": "Creates a temporary private chat with a message forwarding restriction and adds the specified user to it.", - "meta": { - "pic": null, - "banner": null, - "developer": "@hikka_mods" - }, - "commands": [ - { - "tmpchat": "Create temporary chat. Usage: .tmpchat [@user/reply] [time] Создает временный чат. Использование: .tmpchat [@user/reply] [time]" - } - ], - "new_commands": [ - { - "tmpchat": { - "ru_doc": "Создает временный чат. Использование: .tmpchat [@user/reply] [time]", - "en_doc": null, - "doc": "Create temporary chat. Usage: .tmpchat [@user/reply] [time]" - } - } - ], - "category": [ - "Chat", - "Tools" - ] - }, - "C0dwiz/H.Modules/Weather.py": { - "name": "Weather", - "description": "Advanced weather module with detailed information", - "meta": { - "pic": null, - "banner": null, - "developer": "@hikka_mods" - }, - "commands": [ - { - "weather": "Узнайте погоду для указанного города Get the weather for the specified city" - }, - { - "weatherset": "Установите город по умолчанию для определения погоды Set the default city for weather" - } - ], - "new_commands": [ - { - "weather": { - "ru_doc": "Узнайте погоду для указанного города", - "en_doc": "Get the weather for the specified city", - "doc": null - } - }, - { - "weatherset": { - "ru_doc": "Установите город по умолчанию для определения погоды", - "en_doc": "Set the default city for weather", - "doc": null - } - } - ], - "category": [ - "Tools", - "Chat" - ] - }, - "C0dwiz/H.Modules/Article.py": { - "name": "ArticleMod", - "description": "Displays your article Criminal Code of the Russian Federation", - "meta": { - "pic": null, - "banner": null, - "developer": "@hikka_mods" - }, - "commands": [ - { - "arccmd": "Отображается ваша статья Уголовного кодекса Российской Федерации Displays your article Criminal Code of the Russian Federation" - } - ], - "new_commands": [ - { - "arc": { - "ru_doc": "Отображается ваша статья Уголовного кодекса Российской Федерации", - "en_doc": "Displays your article Criminal Code of the Russian Federation", - "doc": null - } - } - ], - "category": [ - "Tools", - "Fun" - ] - }, - "C0dwiz/H.Modules/EnvsSH.py": { - "name": "EnvsMod", - "description": "Module for reuploading files to envs.sh", - "meta": { - "pic": null, - "banner": null, - "developer": "@hikka_mods" - }, - "commands": [ - { - "envcmd": "Reupload to envs.sh." - } - ], - "new_commands": [ - { - "env": { - "ru_doc": null, - "en_doc": null, - "doc": "Reupload to envs.sh." - } - } - ], - "category": [ - "Chat", - "Tools" - ] - }, - "C0dwiz/H.Modules/SMAcrhiver.py": { - "name": "SMArchiver", - "description": "unloads all messages from Favorites", - "meta": { - "pic": null, - "banner": null, - "developer": "@hikka_mods" - }, - "commands": [ - { - "smdump": "выгружает все сообщения из Избранного / Saved Messages и собирает их в одном архиве. downloads all messages from Favorites / Saved Messages and collects them in one archive." - } - ], - "new_commands": [ - { - "smdump": { - "ru_doc": "выгружает все сообщения из Избранного / Saved Messages и собирает их в одном архиве.", - "en_doc": "downloads all messages from Favorites / Saved Messages and collects them in one archive.", - "doc": null - } - } - ], - "category": [ - "Chat", - "Tools" - ] - }, - "C0dwiz/H.Modules/jacques.py": { - "name": "JacquesMod", - "description": "Жаконизатор", - "meta": { - "pic": null, - "banner": null, - "developer": "@hikka_mods" - }, - "commands": [ - { - "ionicmd": "<реплай на сообщение/свой текст> " - } - ], - "new_commands": [ - { - "ioni": { - "ru_doc": "<реплай на сообщение/свой текст>", - "en_doc": "", - "doc": null - } - } - ], - "category": [ - "Tools", - "Chat" - ] - }, - "C0dwiz/H.Modules/ASCIIArt.py": { - "name": "ASCIIArtMod", - "description": "Converting images to ASCII art", - "meta": { - "pic": null, - "banner": null, - "developer": "@hikka_mods" - }, - "commands": [ - { - "cascii": "<реплай на изображение> сделать ascii art make ascii art" - } - ], - "new_commands": [ - { - "cascii": { - "ru_doc": "<реплай на изображение> сделать ascii art", - "en_doc": " make ascii art", - "doc": null - } - } - ], - "category": [ - "Tools", - "Chat" - ] - }, - "C0dwiz/H.Modules/shortener.py": { - "name": "Shortener", - "description": "Module for working with the api bit.ly", - "meta": { - "pic": null, - "banner": null, - "developer": "@hikka_mods" - }, - "commands": [ - { - "shortencmd": "Сократить ссылку через bit.ly Shorten the link via bit.ly" - }, - { - "statclcmd": "Посмотреть статистику ссылки через bit.ly View link statistics via bit.ly" - } - ], - "new_commands": [ - { - "shorten": { - "ru_doc": "Сократить ссылку через bit.ly", - "en_doc": "Shorten the link via bit.ly", - "doc": null - } - }, - { - "statcl": { - "ru_doc": "Посмотреть статистику ссылки через bit.ly", - "en_doc": "View link statistics via bit.ly", - "doc": null - } - } - ], - "category": [ - "Tools", - "Chat" - ] - }, - "C0dwiz/H.Modules/InlineHelper.py": { - "name": "InlineHelperMod", - "description": "Basic management of the UB in case only the inline works", - "meta": { - "pic": null, - "banner": null, - "developer": "@hikka_mods" - }, - "commands": [ - { - "terminal_inline_handler": "" - } - ], - "new_commands": [ - { - "terminal_inline_handler": { - "ru_doc": null, - "en_doc": null, - "doc": null - } - } - ], - "category": [ - "Tools", - "Chat" - ] - }, - "C0dwiz/H.Modules/PastebinAPI.py": { - "name": "PastebinAPIMod", - "description": "PastebinAPI", - "meta": { - "pic": null, - "banner": null, - "developer": "@hikka_mods" - }, - "commands": [ - { - "past": "Заливает код в Pastebin Uploads the code to Pastebin" - } - ], - "new_commands": [ - { - "past": { - "ru_doc": "Заливает код в Pastebin", - "en_doc": "Uploads the code to Pastebin", - "doc": null - } - } - ], - "category": [ - "Tools", - "Chat" - ] - }, - "C0dwiz/H.Modules/UserbotAvast.py": { - "name": "UserbotAvast", - "description": "A module for checking modules for security.", - "meta": { - "pic": null, - "banner": null, - "developer": "@hikka_mods" - }, - "commands": [ - { - "checkmodcmd": "[module_link] или [reply file] или [send file] - выполняет проверку модуля на безопасность." - } - ], - "new_commands": [ - { - "checkmod": { - "ru_doc": null, - "en_doc": null, - "doc": "[module_link] или [reply file] или [send file] - выполняет проверку модуля на безопасность." - } - } - ], - "category": [ - "Tools", - "Chat" - ] - }, - "C0dwiz/H.Modules/MooFarmRC1.py": { - "name": "AutoFarmbotMod", - "description": "Модуль для автофарма в \"Коровке\"!\nВ конфиге настройте: сhat_id и bot_id ->\nСинхронизируйте скин в меню ->\nЗарегистрируйтесь на Redis.io и ссылку добавьте в конфиг", - "meta": { - "pic": null, - "banner": null, - "developer": "@hikka_mods and @Frost_Shard" - }, - "commands": [ - { - "fmoo": "Инлайн-меню управления автофармом" - }, - { - "auto_eating": "Автоматически кормит персонажа, если уровень еды ниже 70%" - }, - { - "auto_craft_txt": "Команда для автоматической работы авто-крафта" - }, - { - "auto_forest_txt": "Команда для автоматической работы авто-леса" - } - ], - "new_commands": [ - { - "fmoo": { - "ru_doc": null, - "en_doc": null, - "doc": "Инлайн-меню управления автофармом" - } - }, - { - "auto_eating": { - "ru_doc": null, - "en_doc": null, - "doc": "Автоматически кормит персонажа, если уровень еды ниже 70%" - } - }, - { - "auto_craft_txt": { - "ru_doc": null, - "en_doc": null, - "doc": "Команда для автоматической работы авто-крафта" - } - }, - { - "auto_forest_txt": { - "ru_doc": null, - "en_doc": null, - "doc": "Команда для автоматической работы авто-леса" - } - } - ], - "category": [ - "Tools", - "Chat" - ] - }, - "C0dwiz/H.Modules/Text_Sticker.py": { - "name": "TextinstickerMod", - "description": "Text to sticker", - "meta": { - "pic": null, - "banner": null, - "developer": "@hikka_mods" - }, - "commands": [ - { - "stcmd": "<название цвета> [текст] [text]" - } - ], - "new_commands": [ - { - "st": { - "ru_doc": "<название цвета> [текст]", - "en_doc": " [text]", - "doc": null - } - } - ], - "category": [ - "Tools", - "Chat" - ] - }, - "C0dwiz/H.Modules/VirusTotal.py": { - "name": "VirusTotalMod", - "description": "Checks files for viruses using VirusTotal.", - "meta": { - "pic": null, - "banner": null, - "developer": "@hikka_mods" - }, - "commands": [ - { - "vt": "<ответ на файл> - Проверяет файлы на наличие вирусов с использованием VirusTotal - Checks files for viruses using VirusTotal" - } - ], - "new_commands": [ - { - "vt": { - "ru_doc": "<ответ на файл> - Проверяет файлы на наличие вирусов с использованием VirusTotal", - "en_doc": " - Checks files for viruses using VirusTotal", - "doc": null - } - } - ], - "category": [ - "Security", - "Chat" - ] - }, - "C0dwiz/H.Modules/search.py": { - "name": "Search", - "description": "Поисковик", - "meta": { - "pic": null, - "banner": null, - "developer": "@hikka_mods" - }, - "commands": [ - { - "google": "Поискать в Google Search on Google" - }, - { - "yandex": "Поискать в Yandex Search on Yandex" - }, - { - "duckduckgo": "Поискать в Duckduckgo Search on Duckduckgo" - }, - { - "bing": "Поискать в Bing Search on Bing" - }, - { - "you": "Поискать в You Search on You" - }, - { - "igoogle": "Поискать в Google инлайн Search on Google inline" - }, - { - "iyandex": "Поискать в Yandex инлайн Search on Yandex inline" - }, - { - "iduckduckgo": "Поискать в Duckduckgo инлайн Search on Duckduckgo inline" - }, - { - "ibing": "Поискать в Bing инлайн Search on Bing inline" - }, - { - "iyou": "Поискать в You инлайн Search on You inline" - } - ], - "new_commands": [ - { - "google": { - "ru_doc": "Поискать в Google", - "en_doc": "Search on Google", - "doc": null - } - }, - { - "yandex": { - "ru_doc": "Поискать в Yandex", - "en_doc": "Search on Yandex", - "doc": null - } - }, - { - "duckduckgo": { - "ru_doc": "Поискать в Duckduckgo", - "en_doc": "Search on Duckduckgo", - "doc": null - } - }, - { - "bing": { - "ru_doc": "Поискать в Bing", - "en_doc": "Search on Bing", - "doc": null - } - }, - { - "you": { - "ru_doc": "Поискать в You", - "en_doc": "Search on You", - "doc": null - } - }, - { - "igoogle": { - "ru_doc": "Поискать в Google инлайн", - "en_doc": "Search on Google inline", - "doc": null - } - }, - { - "iyandex": { - "ru_doc": "Поискать в Yandex инлайн", - "en_doc": "Search on Yandex inline", - "doc": null - } - }, - { - "iduckduckgo": { - "ru_doc": "Поискать в Duckduckgo инлайн", - "en_doc": "Search on Duckduckgo inline", - "doc": null - } - }, - { - "ibing": { - "ru_doc": "Поискать в Bing инлайн", - "en_doc": "Search on Bing inline", - "doc": null - } - }, - { - "iyou": { - "ru_doc": "Поискать в You инлайн", - "en_doc": "Search on You inline", - "doc": null - } - } - ], - "category": [ - "Tools", - "Chat" - ] - }, - "C0dwiz/H.Modules/TaskManager.py": { - "name": "TaskManagerModule", - "description": "Manages tasks with Telegram commands and inline keyboards.", - "meta": { - "pic": null, - "banner": null, - "developer": "@hikka_mods" - }, - "commands": [ - { - "taskadd": "Добавить задачу:\n.taskadd <описание> | <дата (необязательно)> Add task:\n.taskadd | " - }, - { - "taskremove": "[index] - удалить задачу [index] - remove task" - }, - { - "taskcomplete": "[index] - Завершите задачу [index] - Complete task" - }, - { - "tasklist": "Список задач List tasks" - }, - { - "taskinfo": "[index] - Посмотреть информацию о задаче [index] - Show task info" - }, - { - "taskclear": "Удалить все задачи Clear all tasks" - } - ], - "new_commands": [ - { - "taskadd": { - "ru_doc": "Добавить задачу:\n.taskadd <описание> | <дата (необязательно)>", - "en_doc": "Add task:\n.taskadd | ", - "doc": null - } - }, - { - "taskremove": { - "ru_doc": "[index] - удалить задачу", - "en_doc": "[index] - remove task", - "doc": null - } - }, - { - "taskcomplete": { - "ru_doc": "[index] - Завершите задачу", - "en_doc": "[index] - Complete task", - "doc": null - } - }, - { - "tasklist": { - "ru_doc": "Список задач", - "en_doc": "List tasks", - "doc": null - } - }, - { - "taskinfo": { - "ru_doc": "[index] - Посмотреть информацию о задаче", - "en_doc": "[index] - Show task info", - "doc": null - } - }, - { - "taskclear": { - "ru_doc": "Удалить все задачи", - "en_doc": "Clear all tasks", - "doc": null - } - } - ], - "category": [ - "Chat", - "Tools" - ] - }, - "C0dwiz/H.Modules/globalrestrict.py": { - "name": "GlobalRestrict", - "description": "Global mutation or ban", - "meta": { - "pic": null, - "banner": null, - "developer": "@hikka_mods" - }, - "commands": [ - { - "glban": "<реплай | юзер> [причина] [-s] - Забанить пользователя во всех чатах где ты админ [reason] [-s] - Ban the user in all chats where you are the admin" - }, - { - "glunban": "<реплай | юзер> [причина] [-s] - Разбанить пользователя во всех где ты админ [reason] [-s] - To unban the user in all where you are the admin" - }, - { - "glmute": "<реплай | юзер> [причина] [-s] - Замутить пользователя во всех чатах где ты админ [reason] [-s] - To hook up the user in all chats where you are the admin" - }, - { - "glunmute": "<реплай | юзер> [причина] [-s] - Размутит пользователя во всех где ты админ [reason] [-s] - Will confuse the user in all where you are the admin" - } - ], - "new_commands": [ - { - "glban": { - "ru_doc": "<реплай | юзер> [причина] [-s] - Забанить пользователя во всех чатах где ты админ", - "en_doc": " [reason] [-s] - Ban the user in all chats where you are the admin", - "doc": null - } - }, - { - "glunban": { - "ru_doc": "<реплай | юзер> [причина] [-s] - Разбанить пользователя во всех где ты админ", - "en_doc": " [reason] [-s] - To unban the user in all where you are the admin", - "doc": null - } - }, - { - "glmute": { - "ru_doc": "<реплай | юзер> [причина] [-s] - Замутить пользователя во всех чатах где ты админ", - "en_doc": " [reason] [-s] - To hook up the user in all chats where you are the admin", - "doc": null - } - }, - { - "glunmute": { - "ru_doc": "<реплай | юзер> [причина] [-s] - Размутит пользователя во всех где ты админ", - "en_doc": " [reason] [-s] - Will confuse the user in all where you are the admin", - "doc": null - } - } - ], - "category": [ - "Tools", - "Chat" - ] - }, - "C0dwiz/H.Modules/TikTokDownloader.py": { - "name": "TikTokDownloader", - "description": "TikTok Downloader module", - "meta": { - "pic": null, - "banner": null, - "developer": "@hikka_mods" - }, - "commands": [ - { - "ttsound": "Скачать звук с TikTok Download sound from TikTok" - }, - { - "tt": "Скачать видео или фото с TikTok Download videos or photos from TikTok" - } - ], - "new_commands": [ - { - "ttsound": { - "ru_doc": "Скачать звук с TikTok", - "en_doc": "Download sound from TikTok", - "doc": null - } - }, - { - "tt": { - "ru_doc": "Скачать видео или фото с TikTok", - "en_doc": "Download videos or photos from TikTok", - "doc": null - } - } - ], - "category": [ - "Media", - "Tools" - ] - }, - "C0dwiz/H.Modules/InlineCoin.py": { - "name": "CoinSexMod", - "description": "Mini game heads or tails", - "meta": { - "pic": null, - "banner": null, - "developer": "@hikka_mods" - }, - "commands": [ - { - "coin_inline_handler": "Подбросит монетку Flip a coin" - } - ], - "new_commands": [ - { - "coin_inline_handler": { - "ru_doc": "Подбросит монетку ", - "en_doc": "Flip a coin", - "doc": null - } - } - ], - "category": [ - "Tools", - "Fun" - ] - }, - "C0dwiz/H.Modules/AccountData.py": { - "name": "AccountData", - "description": "Find out the approximate date of registration of the telegram account", - "meta": { - "pic": null, - "banner": null, - "developer": "@hikka_mods" - }, - "commands": [ - { - "accdata": "Узнать примерную дату регистрации аккаунта телеграмм Find out the approximate date of registration of the telegram account" - } - ], - "new_commands": [ - { - "accdata": { - "ru_doc": "Узнать примерную дату регистрации аккаунта телеграмм", - "en_doc": "Find out the approximate date of registration of the telegram account", - "doc": null - } - } - ], - "category": [ - "Tools", - "Fun" - ] - }, - "C0dwiz/H.Modules/CheckSpamBan.py": { - "name": "SpamBanCheckMod", - "description": "Checks spam ban for your account.", - "meta": { - "pic": null, - "banner": null, - "developer": "@hikka_mods" - }, - "commands": [ - { - "spambot": "Проверяет вашу учетную запись на спам-бан с помощью бота @SpamBot Checks your account for spam ban via @SpamBot bot" - } - ], - "new_commands": [ - { - "spambot": { - "ru_doc": "Проверяет вашу учетную запись на спам-бан с помощью бота @SpamBot", - "en_doc": "Checks your account for spam ban via @SpamBot bot", - "doc": null - } - } - ], - "category": [ - "Tools", - "Chat" - ] - }, - "C0dwiz/H.Modules/Music.py": { - "name": "MusicMod", - "description": null, - "meta": { - "pic": null, - "banner": null, - "developer": "@hikka_mods" - }, - "commands": [ - { - "music": "Найти трек в Yandex Music или VK: `.music yandex {название}` или `.music vk {название}` Find a track in Yandex Music or VK: `.music yandex {name}` or `.music vk {name}`" - } - ], - "new_commands": [ - { - "music": { - "ru_doc": "Найти трек в Yandex Music или VK: `.music yandex {название}` или `.music vk {название}`", - "en_doc": "Find a track in Yandex Music or VK: `.music yandex {name}` or `.music vk {name}`", - "doc": null - } - } - ], - "category": [ - "Tools", - "Chat" - ] - }, - "C0dwiz/H.Modules/SafetyMod.py": { - "name": "SafetyMod", - "description": "generate random password", - "meta": { - "pic": null, - "banner": null, - "developer": "@hikka_mods" - }, - "commands": [ - { - "password": "random password\n-n - numbers\n-s - symbols \n -l - letters Случайный пароль\n-n - цифры\n-s - символы \n -l - буквы Random password\n-n - numbers\n-s - symbols \n -l - letters" - } - ], - "new_commands": [ - { - "password": { - "ru_doc": "Случайный пароль\n-n - цифры\n-s - символы \n -l - буквы", - "en_doc": "Random password\n-n - numbers\n-s - symbols \n -l - letters", - "doc": "random password\n-n - numbers\n-s - symbols \n -l - letters" - } - } - ], - "category": [ - "Security", - "Tools" - ] - }, - "C0dwiz/H.Modules/novoice.py": { - "name": "NoVoiceMod", - "description": "A module for prohibiting the sending of voice and video messages", - "meta": { - "pic": null, - "banner": null, - "developer": "@hikka_mods" - }, - "commands": [ - { - "novoice": "[on/off] — запрещает/разрешает всем пользователям отправку голосовых и видеосообщений. [on/off] — prohibits/allows all users to send voice and video messages." - }, - { - "novoiceuser": "[username/reply] — запрещает пользователю отправку голосовых и видеосообщений. [username/reply] — prohibits the user from sending voice and video messages." - }, - { - "novoicerm": "[username/reply] — разрешает пользователю отправку голосовых и видеосообщений. [username/reply] — allows the user to send voice and video messages." - } - ], - "new_commands": [ - { - "novoice": { - "ru_doc": "[on/off] — запрещает/разрешает всем пользователям отправку голосовых и видеосообщений.", - "en_doc": "[on/off] — prohibits/allows all users to send voice and video messages.", - "doc": null - } - }, - { - "novoiceuser": { - "ru_doc": "[username/reply] — запрещает пользователю отправку голосовых и видеосообщений.", - "en_doc": "[username/reply] — prohibits the user from sending voice and video messages.", - "doc": null - } - }, - { - "novoicerm": { - "ru_doc": "[username/reply] — разрешает пользователю отправку голосовых и видеосообщений.", - "en_doc": "[username/reply] — allows the user to send voice and video messages.", - "doc": null - } - } - ], - "category": [ - "Tools", - "Chat" - ] - }, - "C0dwiz/H.Modules/KBSwapper.py": { - "name": "KBSwapperMod", - "description": "KBSwapper is a module for changing the keyboard layout", - "meta": { - "pic": null, - "banner": null, - "developer": "@hikka_mods" - }, - "commands": [ - { - "swap": "При ответе на своё сообщение меняет раскладку путем редактирования, на чужое — в отдельном сообщении. Change keyboard layout for the replied message." - } - ], - "new_commands": [ - { - "swap": { - "ru_doc": "При ответе на своё сообщение меняет раскладку путем редактирования, на чужое — в отдельном сообщении.", - "en_doc": "Change keyboard layout for the replied message.", - "doc": null - } - } - ], - "category": [ - "Tools", - "Security" - ] - }, - "C0dwiz/H.Modules/aiogram3/hikarichat.py": { - "name": "HikariChatMod", - "description": "Advanced chat admin toolkit", - "meta": { - "pic": "https://static.dan.tatar/hikarichat_icon.png", - "banner": "https://mods.hikariatama.ru/badges/hikarichat.jpg", - "desc": "Chat administrator toolkit, now with powerful free version", - "developer": "@hikarimods" - }, - "commands": [ - { - "purgecmd": "[user(-s)] - Clean message history starting from replied one" - }, - { - "delcmd": "Delete the replied message" - }, - { - "versioncmd": "Get module info" - }, - { - "deletedcmd": "Remove deleted accounts from chat" - }, - { - "fcleancmd": "Remove deleted accounts from federation" - }, - { - "newfedcmd": " - Create new federation" - }, - { - "rmfedcmd": " - Remove federation" - }, - { - "fpromotecmd": " - Promote user in federation" - }, - { - "fdemotecmd": " - Demote user in federation" - }, - { - "faddcmd": " - Add chat to federation" - }, - { - "frmcmd": "Remove chat from federation" - }, - { - "gban": " [reason] [-s] - Ban user in all chats where you are admin <реплай | юзер> [причина] [-s] - Заблокировать пользователя во всех чатах, где ты админ" - }, - { - "gunban": " [reason] [-s] - Unban user in all chats where you are admin <реплай | юзер> [причина] [-s] - Разблокировать пользователя во всех чатах, где ты админ" - }, - { - "fbancmd": " [reason] - Ban user in federation" - }, - { - "punishsuffcmd": "Set new punishment suffix" - }, - { - "sethclogcmd": "Set logchat" - }, - { - "funbancmd": " [reason] - Unban user in federation" - }, - { - "fmutecmd": " [reason] - Mute user in federation" - }, - { - "funmutecmd": " [reason] - Unban user in federation" - }, - { - "kickcmd": " [reason] - Kick user" - }, - { - "bancmd": " [reason] - Ban user" - }, - { - "mutecmd": " [time] [reason] - Mute user" - }, - { - "unmutecmd": " - Unmute user" - }, - { - "unbancmd": " - Unban user" - }, - { - "protectscmd": "typing.List available filters" - }, - { - "fedscmd": "typing.List federations" - }, - { - "fedcmd": " - Info about federation" - }, - { - "pchatcmd": "typing.List protection for current chat" - }, - { - "warncmd": " - Warn user" - }, - { - "warnscmd": "[user] - Show warns in chat \\ of user" - }, - { - "delwarncmd": " - Forgave last warn" - }, - { - "clrwarnscmd": " - Remove all warns from user" - }, - { - "clrallwarnscmd": "Remove all warns from current federation" - }, - { - "welcomecmd": " - Change welcome text" - }, - { - "fdefcmd": " - Toggle global user invulnerability" - }, - { - "fsavecmd": " - Save federative note" - }, - { - "fstopcmd": " - Remove federative note" - }, - { - "fnotescmd": "Show federative notes" - }, - { - "fdeflistcmd": "Show global invulnerable users" - }, - { - "dmutecmd": "Delete and mute" - }, - { - "dbancmd": "Delete and ban" - }, - { - "dwarncmd": "Delete and warn" - }, - { - "frenamecmd": "Rename federation" - }, - { - "clnraidcmd": " - Clean raid" - }, - { - "myrightscmd": "typing.List your admin rights in all chats" - } - ], - "new_commands": [ - { - "purge": { - "ru_doc": null, - "en_doc": null, - "doc": "[user(-s)] - Clean message history starting from replied one" - } - }, - { - "del": { - "ru_doc": null, - "en_doc": null, - "doc": "Delete the replied message" - } - }, - { - "version": { - "ru_doc": null, - "en_doc": null, - "doc": "Get module info" - } - }, - { - "deleted": { - "ru_doc": null, - "en_doc": null, - "doc": "Remove deleted accounts from chat" - } - }, - { - "fclean": { - "ru_doc": null, - "en_doc": null, - "doc": "Remove deleted accounts from federation" - } - }, - { - "newfed": { - "ru_doc": null, - "en_doc": null, - "doc": " - Create new federation" - } - }, - { - "rmfed": { - "ru_doc": null, - "en_doc": null, - "doc": " - Remove federation" - } - }, - { - "fpromote": { - "ru_doc": null, - "en_doc": null, - "doc": " - Promote user in federation" - } - }, - { - "fdemote": { - "ru_doc": null, - "en_doc": null, - "doc": " - Demote user in federation" - } - }, - { - "fadd": { - "ru_doc": null, - "en_doc": null, - "doc": " - Add chat to federation" - } - }, - { - "frm": { - "ru_doc": null, - "en_doc": null, - "doc": "Remove chat from federation" - } - }, - { - "gban": { - "ru_doc": "<реплай | юзер> [причина] [-s] - Заблокировать пользователя во всех чатах, где ты админ", - "en_doc": null, - "doc": " [reason] [-s] - Ban user in all chats where you are admin" - } - }, - { - "gunban": { - "ru_doc": "<реплай | юзер> [причина] [-s] - Разблокировать пользователя во всех чатах, где ты админ", - "en_doc": null, - "doc": " [reason] [-s] - Unban user in all chats where you are admin" - } - }, - { - "fban": { - "ru_doc": null, - "en_doc": null, - "doc": " [reason] - Ban user in federation" - } - }, - { - "punishsuff": { - "ru_doc": null, - "en_doc": null, - "doc": "Set new punishment suffix" - } - }, - { - "sethclog": { - "ru_doc": null, - "en_doc": null, - "doc": "Set logchat" - } - }, - { - "funban": { - "ru_doc": null, - "en_doc": null, - "doc": " [reason] - Unban user in federation" - } - }, - { - "fmute": { - "ru_doc": null, - "en_doc": null, - "doc": " [reason] - Mute user in federation" - } - }, - { - "funmute": { - "ru_doc": null, - "en_doc": null, - "doc": " [reason] - Unban user in federation" - } - }, - { - "kick": { - "ru_doc": null, - "en_doc": null, - "doc": " [reason] - Kick user" - } - }, - { - "ban": { - "ru_doc": null, - "en_doc": null, - "doc": " [reason] - Ban user" - } - }, - { - "mute": { - "ru_doc": null, - "en_doc": null, - "doc": " [time] [reason] - Mute user" - } - }, - { - "unmute": { - "ru_doc": null, - "en_doc": null, - "doc": " - Unmute user" - } - }, - { - "unban": { - "ru_doc": null, - "en_doc": null, - "doc": " - Unban user" - } - }, - { - "protects": { - "ru_doc": null, - "en_doc": null, - "doc": "typing.List available filters" - } - }, - { - "feds": { - "ru_doc": null, - "en_doc": null, - "doc": "typing.List federations" - } - }, - { - "fed": { - "ru_doc": null, - "en_doc": null, - "doc": " - Info about federation" - } - }, - { - "pchat": { - "ru_doc": null, - "en_doc": null, - "doc": "typing.List protection for current chat" - } - }, - { - "warn": { - "ru_doc": null, - "en_doc": null, - "doc": " - Warn user" - } - }, - { - "warns": { - "ru_doc": null, - "en_doc": null, - "doc": "[user] - Show warns in chat \\ of user" - } - }, - { - "delwarn": { - "ru_doc": null, - "en_doc": null, - "doc": " - Forgave last warn" - } - }, - { - "clrwarns": { - "ru_doc": null, - "en_doc": null, - "doc": " - Remove all warns from user" - } - }, - { - "clrallwarns": { - "ru_doc": null, - "en_doc": null, - "doc": "Remove all warns from current federation" - } - }, - { - "welcome": { - "ru_doc": null, - "en_doc": null, - "doc": " - Change welcome text" - } - }, - { - "fdef": { - "ru_doc": null, - "en_doc": null, - "doc": " - Toggle global user invulnerability" - } - }, - { - "fsave": { - "ru_doc": null, - "en_doc": null, - "doc": " - Save federative note" - } - }, - { - "fstop": { - "ru_doc": null, - "en_doc": null, - "doc": " - Remove federative note" - } - }, - { - "fnotes": { - "ru_doc": null, - "en_doc": null, - "doc": "Show federative notes" - } - }, - { - "fdeflist": { - "ru_doc": null, - "en_doc": null, - "doc": "Show global invulnerable users" - } - }, - { - "dmute": { - "ru_doc": null, - "en_doc": null, - "doc": "Delete and mute" - } - }, - { - "dban": { - "ru_doc": null, - "en_doc": null, - "doc": "Delete and ban" - } - }, - { - "dwarn": { - "ru_doc": null, - "en_doc": null, - "doc": "Delete and warn" - } - }, - { - "frename": { - "ru_doc": null, - "en_doc": null, - "doc": "Rename federation" - } - }, - { - "clnraid": { - "ru_doc": null, - "en_doc": null, - "doc": " - Clean raid" - } - }, - { - "myrights": { - "ru_doc": null, - "en_doc": null, - "doc": "typing.List your admin rights in all chats" - } - } - ], - "category": [ - "Tools", - "Chat" - ] - }, "SkillsAngels/Modules/IrisLab.py": { "name": "IrisLabMod", "description": "Показывает лаб/жертв. Возможны задержки на получение инф-ции",