From 4e18cd0eb34a1a3475f1c613fdc1e3baef347729 Mon Sep 17 00:00:00 2001 From: mr-forust Date: Tue, 11 Nov 2025 00:02:49 +0100 Subject: [PATCH 01/23] init, .gitignore --- .deepsource.toml | 10 + .dockerignore | 1 + .gitignore | 25 + .unused/Aptfile | 5 + .unused/Cloudfile | 10 + .unused/Procfile | 1 + .unused/app.json | 88 +++ .unused/app.py | 12 + .unused/cloud.sh | 17 + .unused/render.yaml | 17 + .unused/run_command.txt | 1 + Dockerfile | 10 + README.md | 0 compose.yml | 76 ++ images/icons.py | 115 +++ images/imgur.py | 52 ++ images/ncode.py | 55 ++ images/pinterest.py | 102 +++ images/risearch.py | 105 +++ images/unsplash.py | 94 +++ images/unsplash2.py | 103 +++ install.py | 52 ++ install.sh | 338 +++++++++ main.py | 179 +++++ modules/1000-7.py | 41 + modules/admintool.py | 353 +++++++++ modules/admlist.py | 380 ++++++++++ modules/afk.py | 88 +++ modules/aimage.py | 129 ++++ modules/amogus.py | 58 ++ modules/amongus.py | 218 ++++++ modules/animations.py | 478 ++++++++++++ modules/anime/anilist.py | 243 ++++++ modules/anime/anime.py | 69 ++ modules/anime/neko.py | 72 ++ modules/aniquotes.py | 43 ++ modules/antipm.py | 256 +++++++ modules/blackbox.py | 134 ++++ modules/calculator.py | 46 ++ modules/cdxl.py | 57 ++ modules/chatbot.py | 105 +++ modules/circle.py | 146 ++++ modules/clear_notifs.py | 79 ++ modules/cohere.py | 134 ++++ modules/demotivator.py | 93 +++ modules/destroy.py | 84 +++ modules/dice.py | 33 + modules/direct.py | 313 ++++++++ modules/duckduckgo.py | 16 + modules/durov.py | 17 + modules/example.py | 62 ++ modules/f.py | 58 ++ modules/fakeactions.py | 88 +++ modules/filters.py | 264 +++++++ modules/fliptext.py | 106 +++ modules/flux.py | 52 ++ modules/gemini.py | 72 ++ modules/google.py | 47 ++ modules/hearts.py | 93 +++ modules/help.py | 83 ++ modules/huggingface.py | 226 ++++++ modules/id.py | 88 +++ modules/joindate.py | 44 ++ modules/kokodrilo_explodando.py | 22 + modules/leave_chat.py | 37 + modules/loader.py | 340 +++++++++ modules/markitdown.py | 51 ++ modules/mention.py | 72 ++ modules/mirror_flip.py | 61 ++ modules/misc/autobackup.py | 59 ++ modules/misc/autofwd.py | 177 +++++ modules/misc/backup.py | 251 +++++++ modules/misc/cama.py | 109 +++ modules/misc/mlog.py | 144 ++++ modules/misc/prayer.py | 90 +++ modules/misc/safone.py | 534 +++++++++++++ modules/misc/sarethai.py | 565 ++++++++++++++ modules/misc/search.py | 79 ++ modules/misc/summary.py | 64 ++ modules/misc/switch.py | 53 ++ modules/misc/transcribeyt.py | 110 +++ modules/notes.py | 143 ++++ modules/open.py | 126 ++++ modules/pdf2md.py | 88 +++ modules/perfectrussian.py | 37 + modules/ping.py | 32 + modules/prefix.py | 52 ++ modules/purge.py | 54 ++ modules/python.py | 86 +++ modules/reactionspam.py | 44 ++ modules/removebg.py | 194 +++++ modules/say.py | 37 + modules/sendmod.py | 58 ++ modules/sessionkiller.py | 156 ++++ modules/sgb.py | 52 ++ modules/shell.py | 64 ++ modules/socialstalk.py | 200 +++++ modules/spam.py | 54 ++ modules/spin.py | 158 ++++ modules/squotes.py | 476 ++++++++++++ modules/stickers.py | 163 ++++ modules/support.py | 88 +++ modules/thumbnail.py | 43 ++ modules/type.py | 43 ++ modules/updater.py | 117 +++ modules/upl.py | 132 ++++ modules/url.py | 285 +++++++ modules/user_info.py | 115 +++ modules/vt.py | 136 ++++ pyproject.toml | 28 + requirements.txt | 20 + start | 9 + string_gen.py | 10 + termux-install.sh | 143 ++++ utils/__init__.py | 0 utils/config.py | 32 + utils/conv.py | 189 +++++ utils/db.py | 237 ++++++ utils/handlers.py | 1251 +++++++++++++++++++++++++++++++ utils/misc.py | 55 ++ utils/module.py | 78 ++ utils/rentry.py | 200 +++++ utils/scripts.py | 542 +++++++++++++ uv.lock | 1019 +++++++++++++++++++++++++ 124 files changed, 16300 insertions(+) create mode 100644 .deepsource.toml create mode 100644 .dockerignore create mode 100644 .gitignore create mode 100644 .unused/Aptfile create mode 100644 .unused/Cloudfile create mode 100644 .unused/Procfile create mode 100644 .unused/app.json create mode 100644 .unused/app.py create mode 100644 .unused/cloud.sh create mode 100644 .unused/render.yaml create mode 100644 .unused/run_command.txt create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 compose.yml create mode 100644 images/icons.py create mode 100644 images/imgur.py create mode 100644 images/ncode.py create mode 100644 images/pinterest.py create mode 100644 images/risearch.py create mode 100644 images/unsplash.py create mode 100644 images/unsplash2.py create mode 100644 install.py create mode 100755 install.sh create mode 100644 main.py create mode 100644 modules/1000-7.py create mode 100644 modules/admintool.py create mode 100644 modules/admlist.py create mode 100644 modules/afk.py create mode 100644 modules/aimage.py create mode 100644 modules/amogus.py create mode 100644 modules/amongus.py create mode 100644 modules/animations.py create mode 100644 modules/anime/anilist.py create mode 100644 modules/anime/anime.py create mode 100644 modules/anime/neko.py create mode 100644 modules/aniquotes.py create mode 100644 modules/antipm.py create mode 100644 modules/blackbox.py create mode 100644 modules/calculator.py create mode 100644 modules/cdxl.py create mode 100644 modules/chatbot.py create mode 100644 modules/circle.py create mode 100644 modules/clear_notifs.py create mode 100644 modules/cohere.py create mode 100644 modules/demotivator.py create mode 100644 modules/destroy.py create mode 100644 modules/dice.py create mode 100644 modules/direct.py create mode 100644 modules/duckduckgo.py create mode 100644 modules/durov.py create mode 100644 modules/example.py create mode 100644 modules/f.py create mode 100644 modules/fakeactions.py create mode 100644 modules/filters.py create mode 100644 modules/fliptext.py create mode 100644 modules/flux.py create mode 100644 modules/gemini.py create mode 100644 modules/google.py create mode 100644 modules/hearts.py create mode 100644 modules/help.py create mode 100644 modules/huggingface.py create mode 100644 modules/id.py create mode 100644 modules/joindate.py create mode 100644 modules/kokodrilo_explodando.py create mode 100644 modules/leave_chat.py create mode 100644 modules/loader.py create mode 100644 modules/markitdown.py create mode 100644 modules/mention.py create mode 100644 modules/mirror_flip.py create mode 100644 modules/misc/autobackup.py create mode 100644 modules/misc/autofwd.py create mode 100644 modules/misc/backup.py create mode 100644 modules/misc/cama.py create mode 100644 modules/misc/mlog.py create mode 100644 modules/misc/prayer.py create mode 100644 modules/misc/safone.py create mode 100644 modules/misc/sarethai.py create mode 100644 modules/misc/search.py create mode 100644 modules/misc/summary.py create mode 100644 modules/misc/switch.py create mode 100644 modules/misc/transcribeyt.py create mode 100644 modules/notes.py create mode 100644 modules/open.py create mode 100644 modules/pdf2md.py create mode 100644 modules/perfectrussian.py create mode 100644 modules/ping.py create mode 100644 modules/prefix.py create mode 100644 modules/purge.py create mode 100644 modules/python.py create mode 100644 modules/reactionspam.py create mode 100644 modules/removebg.py create mode 100644 modules/say.py create mode 100644 modules/sendmod.py create mode 100644 modules/sessionkiller.py create mode 100644 modules/sgb.py create mode 100644 modules/shell.py create mode 100644 modules/socialstalk.py create mode 100644 modules/spam.py create mode 100644 modules/spin.py create mode 100644 modules/squotes.py create mode 100644 modules/stickers.py create mode 100644 modules/support.py create mode 100644 modules/thumbnail.py create mode 100644 modules/type.py create mode 100644 modules/updater.py create mode 100644 modules/upl.py create mode 100644 modules/url.py create mode 100644 modules/user_info.py create mode 100644 modules/vt.py create mode 100644 pyproject.toml create mode 100644 requirements.txt create mode 100755 start create mode 100644 string_gen.py create mode 100644 termux-install.sh create mode 100644 utils/__init__.py create mode 100644 utils/config.py create mode 100644 utils/conv.py create mode 100644 utils/db.py create mode 100644 utils/handlers.py create mode 100644 utils/misc.py create mode 100644 utils/module.py create mode 100644 utils/rentry.py create mode 100644 utils/scripts.py create mode 100644 uv.lock diff --git a/.deepsource.toml b/.deepsource.toml new file mode 100644 index 0000000..60128ea --- /dev/null +++ b/.deepsource.toml @@ -0,0 +1,10 @@ +version = 1 + +[[analyzers]] +name = "shell" + +[[analyzers]] +name = "python" + + [analyzers.meta] + runtime_version = "3.x.x" diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..ed7cba4 --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +.unused diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..64097b5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,25 @@ +.DS_Store +.gitattributes +.vscode +/modules/__pycache__/ +__pycache__/ +*.session +*.session-old +*.db +*.sqlite3 +*-journal +/venv/ +.venv/ +/downloads/ +/Downloads/ +/modules/custom_modules +.idea +config.ini +unknown_errors.txt +moonlogs.txt +thumb.jpg +antipm_pic.jpg +musicbot/ +.trunk/ +previous_profiles/ +.python-version \ No newline at end of file diff --git a/.unused/Aptfile b/.unused/Aptfile new file mode 100644 index 0000000..4b48999 --- /dev/null +++ b/.unused/Aptfile @@ -0,0 +1,5 @@ +git +wget +ffmpeg +mediainfo +yt-dlp diff --git a/.unused/Cloudfile b/.unused/Cloudfile new file mode 100644 index 0000000..21218db --- /dev/null +++ b/.unused/Cloudfile @@ -0,0 +1,10 @@ +FROM python:3.11 +WORKDIR /app +COPY . /app +RUN apt-get -qq update && apt-get -qq install -y git wget ffmpeg mediainfo \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* +RUN python -m venv --copies /opt/venv +ENV PATH="/opt/venv/bin:$PATH" +RUN pip install --no-cache-dir -r requirements.txt +CMD ["bash", "cloud.sh"] diff --git a/.unused/Procfile b/.unused/Procfile new file mode 100644 index 0000000..ad9aac5 --- /dev/null +++ b/.unused/Procfile @@ -0,0 +1 @@ +web: bash cloud.sh diff --git a/.unused/app.json b/.unused/app.json new file mode 100644 index 0000000..56cd2ea --- /dev/null +++ b/.unused/app.json @@ -0,0 +1,88 @@ +{ + "name": "Moon-userbot", + "description": "A Simple, Fast, Customizable, Ai powered Userbot for Telegram with most easiest installation.", + "logo": "https://camo.githubusercontent.com/1efdfa6416b3cd08471d865ca9ebf0fbdd38602ea95425f18a9bec6aeeefe49b/68747470733a2f2f74656c656772612e70682f66696c652f3063333763326662306631393463633163303334342e6a7067", + "keywords": [ + "telegram", + "Moon-userbot", + "bot", + "python", + "pyrogram" + ], + "env": { + "API_ID": { + "description": "Get it from my.telegram.org", + "required": true + }, + "API_HASH": { + "description": "Get it from my.telegram.org", + "required": true + }, + "PM_LIMIT": { + "description": "set your pm permit warn limit, default is 4", + "value": "4", + "required": true + }, + "SECOND_SESSION": { + "description": "Pyrorogram v2 session string for music bot, only fill this if you want to use music bot feature", + "required": false + }, + "DATABASE_URL": { + "description": "ONLY for MongoDB, get it from https://cloud.mongodb.com", + "required": false + }, + "DATABASE_NAME": { + "description": "set database name, if using sqlite then change it to `db.sqlite3`", + "value": "moonub", + "required": true + }, + "DATABASE_TYPE": { + "description": "set to sqlite3 if want to use sqlite3 db", + "value": "mongodb", + "required": true + }, + "STRINGSESSION": { + "description": "Pyrogram V2 Session String. Don't use bots or else you'll be responsible for your actions. Gen yourself https://github.com/The-MoonTg-project/Moon-Userbot?tab=readme-ov-file#-optional-vars.", + "required": true + }, + "APIFLASH_KEY": { + "description": "ONLY, If you want to use web screenshot plugin You can get it from https://apiflash.com/dashboard/access_keys", + "value": "123456779:ABCDE", + "required": true + }, + "RMBG_KEY": { + "description": "ONLY, If you want to use removbg plugin You can get it from https://www.remove.bg/dashboard#api-key", + "value": "123456779:ABCDE", + "required": true + }, + "VT_KEY": { + "description": "ONLY, If you want to use VirusTotal plugin You can get it from https://www.virustotal.com/gui/", + "value": "123456779:ABCDE", + "required": true + }, + "GEMINI_KEY": { + "description": "ONLY, If you want to use gemini ai plugin You can get it from https://makersuite.google.com/app/apikey", + "value": "123456779:ABCDE", + "required": true + }, + "COHERE_KEY": { + "description": "ONLY, If you want to use cohere ai plugin You can get it from https://dashboard.cohere.com/api-keys", + "value": "123456779:ABCDE", + "required": true + } + }, + "buildpacks": [ + { + "url": "heroku/python" + }, + { + "url": "https://github.com/heroku/heroku-buildpack-apt" + }, + { + "url": "https://github.com/heroku/heroku-buildpack-activestorage-preview" + }, + { + "url": "https://github.com/The-MoonTg-project/heroku-buildpack-yt-dlp" + } + ] +} diff --git a/.unused/app.py b/.unused/app.py new file mode 100644 index 0000000..d70930e --- /dev/null +++ b/.unused/app.py @@ -0,0 +1,12 @@ +from flask import Flask + +app = Flask(__name__) + + +@app.route("/") +def hello_world(): + return "This is Moon" + + +if __name__ == "__main__": + app.run() diff --git a/.unused/cloud.sh b/.unused/cloud.sh new file mode 100644 index 0000000..0f32f6b --- /dev/null +++ b/.unused/cloud.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +cat <<'EOF' + _ ____ ____ _ +/ \__/|/ _ \/ _ \/ \ /| +| |\/||| / \|| / \|| |\ || +| | ||| \_/|| \_/|| | \|| +\_/ \|\____/\____/\_/ \| + +Copyright (C) 2020-2023 by MoonTg-project@Github, < https://github.com/The-MoonTg-project >. +This file is part of < https://github.com/The-MoonTg-project/Moon-Userbot > project, +and is released under the "GNU v3.0 License Agreement". +Please see < https://github.com/The-MoonTg-project/Moon-Userbot/blob/main/LICENSE > +All rights reserved. +EOF + +gunicorn app:app --daemon && python main.py diff --git a/.unused/render.yaml b/.unused/render.yaml new file mode 100644 index 0000000..812d213 --- /dev/null +++ b/.unused/render.yaml @@ -0,0 +1,17 @@ +services: +- type: worker + name: Moon-Userbot + runtime: docker + repo: https://github.com/The-MoonTg-project/Moon-Userbot + plan: starter + envVars: + - key: STABILITY_KEY + sync: false + - key: CLARIFAI_PAT + sync: false + - key: .env + sync: false + region: oregon + dockerContext: . + dockerfilePath: ./Dockerfile +version: "1" diff --git a/.unused/run_command.txt b/.unused/run_command.txt new file mode 100644 index 0000000..974c034 --- /dev/null +++ b/.unused/run_command.txt @@ -0,0 +1 @@ +gunicorn app:app & python3 main.py \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..8f85225 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,10 @@ +FROM python:3.11 +WORKDIR /app +COPY . /app +RUN apt-get -qq update && apt-get -qq install -y git wget ffmpeg mediainfo\ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* +RUN python -m venv --copies /opt/venv +ENV PATH="/opt/venv/bin:$PATH" +RUN pip install --no-cache-dir -r requirements.txt +CMD ["python", "main.py"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/compose.yml b/compose.yml new file mode 100644 index 0000000..974d011 --- /dev/null +++ b/compose.yml @@ -0,0 +1,76 @@ +services: + userbot_forust: + build: + context: . + dockerfile: Dockerfile + container_name: userbot_forust + restart: unless-stopped + env_file: + - .env + - .env.forust + volumes: + - ./volumes/data_forust:/app/data + - ./Downloads:/app/downloads + - ./volumes/logs_forust:/app/logs + networks: + - userbot_network + dns: + - 8.8.8.8 + - 1.1.1.1 + develop: + watch: + - action: sync + path: ./modules + target: /app/modules + - action: sync + path: ./utils + target: /app/utils + - action: sync + path: ./main.py + target: /app/main.py + - action: rebuild + path: .env + - action: rebuild + path: .env.forust + + userbot_anna: + build: + context: . + dockerfile: Dockerfile + container_name: userbot_anna + restart: unless-stopped + env_file: + - .env + - .env.anna + volumes: + - ./volumes/data_anna:/app/data + - ./Downloads:/app/downloads + - ./volumes/logs_anna:/app/logs + networks: + - userbot_network + dns: + - 8.8.8.8 + - 1.1.1.1 + develop: + watch: + - action: sync + path: ./modules + target: /app/modules + - action: sync + path: ./utils + target: /app/utils + - action: sync + path: ./main.py + target: /app/main.py + - action: rebuild + path: .env + - action: rebuild + path: .env.anna + + +volumes: + downloads: + +networks: + userbot_network: + driver: bridge diff --git a/images/icons.py b/images/icons.py new file mode 100644 index 0000000..ba24af5 --- /dev/null +++ b/images/icons.py @@ -0,0 +1,115 @@ +import re +import requests +import random +from io import BytesIO +from bs4 import BeautifulSoup as bs + +from pyrogram import Client, filters +from pyrogram.types import Message, InputMediaPhoto +from pyrogram.errors import RPCError + +from utils.misc import modules_help, prefix + + +@Client.on_message(filters.command("icon", prefix) & filters.me) +async def search_icon(_, message: Message): + if not len(message.command) == 2: + return await message.edit_text( + "Please provide some text to search icons from Flaticon.com." + ) + query = message.text.split(maxsplit=1)[1] + + await message.edit_text("Searching for icons...") + search_query = query.replace(" ", "%20") + url = f"https://www.flaticon.com/search?word={search_query}" + + try: + html_content = requests.get(url).text + soup = bs(html_content, "html.parser") + results = soup.find_all( + "img", + src=re.compile(r"https://cdn-icons-png.flaticon.com/128/[0-9]+/[0-9]+.png"), + ) + + if not results: + return await message.edit("No results found.") + + random.shuffle(results) + icons = [] + for i in range(5): + icons.append(results[i]["src"].replace("128", "512")) + + for icon in icons: + await message.reply_document(icon) + + return await message.delete() + except Exception as e: + await message.edit(f"An error occurred: {e}") + print(f"Error: {e}") + + +@Client.on_message(filters.command("freepik", prefix) & filters.me) +async def freepik_search(client: Client, message: Message): + parts = message.text.split(" ", 1) + if len(parts) < 2: + await message.edit_text("Please provide a search query!") + return + + query = parts[1] + limit = 5 + if " ; " in query: + match, limit_str = query.split(" ; ", 1) + try: + limit = int(limit_str) + except ValueError: + await message.edit_text("Invalid limit! Using the default value of 5.") + else: + match = query + + match = match.replace(" ", "%20") + await message.edit_text("Searching Freepik...") + + try: + url = f"https://www.freepik.com/api/regular/search?locale=en&term={match}" + json_content = requests.get(url).json() + results = [] + for i in json_content["items"]: + results.append(i["preview"]["url"]) + + if results is None: + return await message.edit_text("No results found.") + + random.shuffle(results) + img_urls = results[:limit] + + media_group = [] + for img_url in img_urls: + icon = requests.get(img_url) + if icon.status_code == 200: + media_group.append(InputMediaPhoto(media=BytesIO(icon.content))) + + if not media_group: + await message.edit_text("No images could be downloaded.") + return + + try: + await client.send_media_group(chat_id=message.chat.id, media=media_group) + except RPCError: + await message.edit_text( + "Failed to send some images. Retrying individually..." + ) + for media in media_group: + try: + await message.reply_photo(photo=media.media) + except Exception as e: + await message.edit_text(f"Error sending image: {e}") + + except Exception as e: + await message.edit_text(f"Failed to fetch data: {e}") + print(f"Error: {e}") + + +modules_help["icons"] = { + "icon [query]": "Search for icons on Flaticon.", + "freepik [query] [limit]": "Search for images on Freepik. Limit is optional and defaults to 5.", +} diff --git a/images/imgur.py b/images/imgur.py new file mode 100644 index 0000000..d37ee76 --- /dev/null +++ b/images/imgur.py @@ -0,0 +1,52 @@ +import os +import base64 +import requests + + +from pyrogram import Client, filters +from pyrogram.types import Message + +from utils.misc import modules_help, prefix + + +@Client.on_message(filters.command(["imgur"], prefix) & filters.me) +async def imgur(_, message: Message): + # Check if a reply exists + msg = await message.edit_text("🎉 Please wait. trying to upload...") + if message.reply_to_message and message.reply_to_message.photo: + # Download the photo + photo_path = await message.reply_to_message.download() + # Read the photo file and encode as base64 + with open(photo_path, "rb") as file: + data = file.read() + base64_data = base64.b64encode(data) + # Set API endpoint and headers for image upload + url = "https://api.imgur.com/3/image" + headers = {"Authorization": "Client-ID a10ad04550b0648"} + # Upload image to Imgur and get URL + response = requests.post(url, headers=headers, data={"image": base64_data}) + result = response.json() + await msg.edit_text(result["data"]["link"]) + elif message.reply_to_message and message.reply_to_message.animation: + # Download the animation (GIF) + animation_path = await message.reply_to_message.download() + # Read the animation file and encode as base64 + with open(animation_path, "rb") as file: + data = file.read() + base64_data = base64.b64encode(data) + # Set API endpoint and headers for animation upload + url = "https://api.imgur.com/3/image" + headers = {"Authorization": "Client-ID a10ad04550b0648"} + # Upload animation to Imgur and get URL + response = requests.post(url, headers=headers, data={"image": base64_data}) + result = response.json() + await msg.edit_text(result["data"]["link"]) + else: + await msg.edit_text( + "Please reply to a photo or animation (GIF) to upload to Imgur." + ) + + +modules_help["imgur"] = { + "imgur [img]*": "upload a photo or animation (GIF) to imgur", +} diff --git a/images/ncode.py b/images/ncode.py new file mode 100644 index 0000000..65d1a00 --- /dev/null +++ b/images/ncode.py @@ -0,0 +1,55 @@ +from pyrogram import Client, filters +from pyrogram.types import Message + +from utils.misc import prefix, modules_help + + +from pyrogram import Client, filters +from pyrogram.types import Message +from pyrogram.errors import MessageNotModified +import os +import pygments +from pygments.formatters import ImageFormatter +from pygments.lexers import Python3Lexer + + +@Client.on_message(filters.command("ncode", prefix) & filters.me) +async def coder_print(client, message: Message): + if message.reply_to_message: + reply_message = message.reply_to_message + if reply_message.media: + download_path = await client.download_media(reply_message) + with open(download_path, "r") as file: + code = file.read() + if os.path.exists(download_path): + os.remove(download_path) + pygments.highlight( + f"{code}", + Python3Lexer(), + ImageFormatter(font_name="DejaVu Sans Mono", line_numbers=True), + "result.png", + ) + try: + sent_message = await message.edit_text( + "Pasting this code on my page..." + ) + await client.send_document( + chat_id=message.chat.id, + document="result.png", + caption="Code highlighted by Pygments", + reply_to_message_id=message.id, + ) + except MessageNotModified: + pass + await sent_message.delete() + if os.path.exists("result.png"): + os.remove("result.png") + else: + return await message.reply_text("Please reply to a text or a file.") + else: + return await message.reply_text("Please reply to a text or a file.") + + +modules_help["ncode"] = { + "ncode": "Highlight the code using Pygments and send it as an image." +} diff --git a/images/pinterest.py b/images/pinterest.py new file mode 100644 index 0000000..abe0930 --- /dev/null +++ b/images/pinterest.py @@ -0,0 +1,102 @@ +from pyrogram import Client, filters, enums +from pyrogram.types import Message, InputMediaPhoto +from io import BytesIO +from PIL import Image +import requests +import asyncio +from utils.misc import modules_help, prefix + +# Pinterest API URL +API_URL = "https://bk9.fun/pinterest/search?q=" + + +def resize_image(image_bytes): + try: + with Image.open(image_bytes) as img: + max_size = (1280, 1280) + if img.size > max_size: + img.thumbnail(max_size) + output = BytesIO() + img.save(output, format="JPEG") + output.seek(0) + return output + image_bytes.seek(0) # Reset pointer if not resized + return image_bytes + except Exception as e: + print(f"Error resizing image: {e}") + return image_bytes + + +async def download_image(url): + try: + response = requests.get(url) + if response.status_code == 200: + img_bytes = BytesIO(response.content) + return resize_image(img_bytes) + except Exception as e: + print(f"Error downloading image: {e}") + return None + + +@Client.on_message(filters.command("pinterest", prefix) & filters.me) +async def pinterest_search(client: Client, message: Message): + if len(message.command) < 2: + await message.edit( + "Usage: `pinterest [number] `", parse_mode=enums.ParseMode.MARKDOWN + ) + return + + num_pics = int(message.command[1]) if message.command[1].isdigit() else 10 + query = " ".join(message.command[2:]) + + # Update status + status_message = await message.edit( + "Searching for images...", parse_mode=enums.ParseMode.MARKDOWN + ) + + url = f"{API_URL}{query}" + response = requests.get(url) + + if response.status_code == 200: + data = response.json() + if data.get("status"): + urls = [item["images_url"] for item in data.get("BK9", [])[:num_pics]] + images = [download_image(img_url) for img_url in urls] + + # Download images + downloaded_images = await asyncio.gather(*images) + + media = [ + InputMediaPhoto(media=img_bytes) + for img_bytes in downloaded_images + if img_bytes + ] + + if media: + await status_message.edit( + "Uploading pictures...", parse_mode=enums.ParseMode.MARKDOWN + ) + while media: + batch = media[:10] + media = media[10:] + await message.reply_media_group(batch) + await status_message.delete() # Delete status message after uploading + else: + await status_message.edit( + "No valid images found.", parse_mode=enums.ParseMode.MARKDOWN + ) + else: + await status_message.edit( + "No images found for the given query.", + parse_mode=enums.ParseMode.MARKDOWN, + ) + else: + await status_message.edit( + "An error occurred, please try again later.", + parse_mode=enums.ParseMode.MARKDOWN, + ) + + +modules_help["pinterest"] = { + "pinterest [number]* [query]": "Get images from Pinterest. Default number of images is 10", +} diff --git a/images/risearch.py b/images/risearch.py new file mode 100644 index 0000000..8dff45e --- /dev/null +++ b/images/risearch.py @@ -0,0 +1,105 @@ +from utils.misc import modules_help, prefix +import requests +from pyrogram import Client, filters +from pyrogram.types import Message +from modules.url import generate_screenshot +import os + +# API endpoints for reverse image search engines +SEARCH_ENGINES = { + "lens": "https://lens.google.com/uploadbyurl?url={image}", + "reverse": "https://www.google.com/searchbyimage?sbisrc=4chanx&image_url={image}&safe=off", + "tineye": "https://www.tineye.com/search?url={image}", + "bing": "https://www.bing.com/images/search?view=detailv2&iss=sbi&form=SBIVSP&sbisrc=UrlPaste&q=imgurl:{image}", + "yandex": "https://yandex.com/images/search?source=collections&&url={image}&rpt=imageview", + "saucenao": "https://saucenao.com/search.php?db=999&url={image}", +} + + +@Client.on_message(filters.command("risearch", prefix) & filters.reply) +async def reverse_image_search(client: Client, message: Message): + if not message.reply_to_message or not message.reply_to_message.photo: + await message.reply_text( + f"Please reply to an image with {prefix}risearch [engine] or {prefix}risearch." + ) + return + + command_parts = message.text.split(maxsplit=1) + engines_to_use = ( + [command_parts[1].strip().lower()] + if len(command_parts) > 1 and command_parts[1].strip() + else list(SEARCH_ENGINES.keys()) + ) + + invalid_engines = [ + engine for engine in engines_to_use if engine not in SEARCH_ENGINES + ] + if invalid_engines: + await message.reply_text( + f"Invalid engine(s): {', '.join(invalid_engines)}. Available: {', '.join(SEARCH_ENGINES.keys())}" + ) + return + + processing_message = await message.edit_text("Processing the image...") + + try: + # Download and upload the image + photo_path = await message.reply_to_message.download() + img_url = upload_image(photo_path) + print(img_url) + if not img_url: + await processing_message.edit("Error: Could not upload the image.") + return + + # Perform searches for the selected engines + for engine in engines_to_use: + search_url = SEARCH_ENGINES[engine].format(image=img_url) + await send_screenshot(client, message, search_url, engine) + except Exception as e: + await processing_message.edit(f"An error occurred: {e}") + finally: + if photo_path and os.path.exists(photo_path): + os.remove(photo_path) + + +def upload_image(photo_path): + """Uploads an image to tmpfiles.org and returns the direct download URL.""" + try: + with open(photo_path, "rb") as image_file: + response = requests.post( + "https://tmpfiles.org/api/v1/upload", files={"file": image_file} + ) + if response.status_code == 200: + data = response.json() + url = data["data"]["url"] + pic_url = url.split("/")[-2] + "/" + url.split("/")[-1] + direct_download_url = url.replace(f"/{pic_url}", f"/dl/{pic_url}") + print(direct_download_url) + return direct_download_url + else: + return None + except Exception: + return None + + +async def send_screenshot(client, message, url, engine_name): + """Takes a screenshot of the URL and sends it to the chat.""" + screenshot_data = generate_screenshot(url) + if screenshot_data: + await client.send_photo( + message.chat.id, + screenshot_data, + caption=f"{engine_name.capitalize()} Result\nURL: {url}", + reply_to_message_id=message.id, + ) + else: + await message.reply( + f"Failed to take screenshot for {engine_name.capitalize()}." + ) + + +# Add module details to help +modules_help["risearch"] = { + "risearch": f"Reply to a photo with `{prefix}risearch [engine]` (e.g., `{prefix}risearch lens`, `{prefix}risearch bing`) " + f"\nor use `{prefix}risearch` to analyze the image with all engines.", +} diff --git a/images/unsplash.py b/images/unsplash.py new file mode 100644 index 0000000..5fa726f --- /dev/null +++ b/images/unsplash.py @@ -0,0 +1,94 @@ +import asyncio +import json +import os +import shutil + +import aiohttp +from pyrogram import Client, enums, filters +from pyrogram.types import Message +import requests +from utils.misc import modules_help, prefix + + +class AioHttp: + async def get_json(self, link): + headers = { + "accept": "*/*", + "accept-language": "en-US", + "cache-control": "no-cache", + "client-geo-region": "global", + "dnt": "1", + "pragma": "no-cache", + "priority": "u=1, i", + "sec-ch-ua": '"Chromium";v="130", "Microsoft Edge";v="130", "Not?A_Brand";v="99"', + "sec-ch-ua-mobile": "?0", + "sec-ch-ua-platform": '"Windows"', + "sec-fetch-dest": "empty", + "sec-fetch-mode": "cors", + "sec-fetch-site": "same-origin", + "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36 Edg/130.0.0.0", + } + + async with aiohttp.ClientSession() as session: + async with session.get(link, headers=headers) as resp: + return await resp.json() + + +@Client.on_message(filters.command("unsplash", prefix) & filters.me) +async def unsplash(client: Client, message: Message): + if len(message.command) > 1 and isinstance(message.command[1], str): + keyword = message.command[1] + unsplash_dir = "downloads/unsplash/" + if not os.path.exists(unsplash_dir): + os.makedirs(unsplash_dir) + + if len(message.command) > 2 and 2 <= int(message.command[2]) <= 10: + await message.edit( + "Getting Pictures", parse_mode=enums.ParseMode.HTML + ) + count = int(message.command[2]) + images = [] + data = await AioHttp().get_json( + f"https://unsplash.com/napi/search/photos?page=1&per_page={count}&query={keyword}" + ) + while len(images) < count: + for ia in range(len(images), count): + img = data["results"][ia]["urls"]["raw"] + if img.startswith("https://images.unsplash.com/photo"): + image_content = requests.get(img).content + with open(f"{unsplash_dir}/unsplash_{ia}.jpg", "wb") as f: + f.write(image_content) + imgr = f"{unsplash_dir}/unsplash_{ia}.jpg" + images.append(imgr) + else: + images.append(img) + if len(images) == count: + break + + for img in images: + await client.send_document(message.chat.id, img) + + await message.delete() + shutil.rmtree(unsplash_dir) + return + else: + await message.edit( + "Getting Picture", parse_mode=enums.ParseMode.HTML + ) + data = await AioHttp().get_json( + f"https://unsplash.com/napi/search/photos?page=1&per_page=1&query={keyword}" + ) + img = data["results"][0]["urls"]["raw"] + await asyncio.gather( + message.delete(), client.send_document(message.chat.id, str(img)) + ) + + +modules_help["unsplash"] = { + "unsplash": f"[keyword]*", + "unsplash": f"[keyword]* [number of results you want]*\n" + "Makes a request to unsplash.com and sends the image with the keyword you provided.\n\n" + "Note:\n1. The number of results you can get is limited to 10.\n" + "2. Keyword is required and should be of one word only!.\n" + "3. Images are sent as document to maintain quality.", +} diff --git a/images/unsplash2.py b/images/unsplash2.py new file mode 100644 index 0000000..856e187 --- /dev/null +++ b/images/unsplash2.py @@ -0,0 +1,103 @@ +from pyrogram import Client, filters, enums +from pyrogram.types import Message, InputMediaPhoto +from io import BytesIO +from PIL import Image +import requests +import asyncio +from utils.misc import modules_help, prefix + +API_URL = "https://bk9.fun/search/unsplash?q=" + + +def resize_image(image_bytes): + try: + with Image.open(image_bytes) as img: + max_size = (1280, 1280) + if img.size > max_size: + img.thumbnail(max_size) + output = BytesIO() + img.save(output, format="JPEG") + output.seek(0) + return output + image_bytes.seek(0) # Reset pointer if not resized + return image_bytes + except Exception as e: + print(f"Error resizing image: {e}") + return image_bytes + + +async def download_image(url): + try: + response = requests.get(url) + if response.status_code == 200: + img_bytes = BytesIO(response.content) + resized_img_bytes = resize_image(img_bytes) + return resized_img_bytes + except Exception as e: + print(f"Error downloading image: {e}") + return None + + +@Client.on_message(filters.command(["unsplash2", "usp2"], prefix) & filters.me) +async def imgsearch(client: Client, message: Message): + if len(message.command) < 2: + await message.edit( + "Usage: `img [number] `", parse_mode=enums.ParseMode.MARKDOWN + ) + return + + num_pics = int(message.command[1]) if message.command[1].isdigit() else 10 + query = " ".join(message.command[2:]) + + # Update status + status_message = await message.edit( + "Searching for images...", parse_mode=enums.ParseMode.MARKDOWN + ) + + url = f"{API_URL}{query}" + response = requests.get(url) + + if response.status_code == 200: + data = response.json() + if data.get("status"): + urls = data.get("BK9", [])[:num_pics] + images = [download_image(img_url) for img_url in urls] + + # Download images + downloaded_images = await asyncio.gather(*images) + + media = [ + InputMediaPhoto(media=img_bytes) + for img_bytes in downloaded_images + if img_bytes + ] + + if media: + await status_message.edit( + "Uploading pictures...", parse_mode=enums.ParseMode.MARKDOWN + ) + while media: + batch = media[:10] + media = media[10:] + await message.reply_media_group(batch) + await status_message.delete() # Delete status message after uploading + else: + await status_message.edit( + "No valid images found.", parse_mode=enums.ParseMode.MARKDOWN + ) + else: + await status_message.edit( + "No images found for the given query.", + parse_mode=enums.ParseMode.MARKDOWN, + ) + else: + await status_message.edit( + "An error occurred, please try again later.", + parse_mode=enums.ParseMode.MARKDOWN, + ) + + +modules_help["unsplash2"] = { + "unsplash2 [number]* [query]": "Get HD images. Default number of images is 10", + "usp2 [number]* [query]": "Get HD images. Default number of images is 10", +} diff --git a/install.py b/install.py new file mode 100644 index 0000000..24560ff --- /dev/null +++ b/install.py @@ -0,0 +1,52 @@ +from datetime import datetime +import sys +from pyrogram import Client + +from utils import config + +common_params = { + "api_id": config.api_id, + "api_hash": config.api_hash, + "hide_password": True, + "test_mode": config.test_server, +} + +if __name__ == "__main__": + if config.STRINGSESSION: + common_params["session_string"] = config.STRINGSESSION + + app = Client("my_account", **common_params) + + if config.db_type in ["mongo", "mongodb"]: + from pymongo import MongoClient, errors + + db = MongoClient(config.db_url) + try: + db.server_info() + except errors.ConnectionFailure as e: + raise RuntimeError( + "MongoDB server isn't available! " + f"Provided url: {config.db_url}. " + "Enter valid URL and restart installation" + ) from e + + install_type = sys.argv[1] if len(sys.argv) > 1 else "3" + if install_type == "1": + restart = "pm2 restart Moon" + elif install_type == "2": + restart = "sudo systemctl restart Moon" + else: + restart = "cd Moon-Userbot/ && python main.py" + + app.start() + try: + app.send_message( + "me", + f"[{datetime.now()}] Userbot launched! \n" + "Custom modules: @moonub_modules\n" + f"For restart, enter:\n" + f"{restart}", + ) + except Exception as e: + print(f"[ERROR]: Sending Message to me failed! {e}") + app.stop() diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..deed859 --- /dev/null +++ b/install.sh @@ -0,0 +1,338 @@ +#!/usr/bin/env bash + +# Define color codes +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[0;33m' +BLUE='\033[0;34m' +INPUT='\033[1;30m' +NC='\033[0m' # No Color + +PACKAGE_MANAGER="" + +# Ensure the script is run with root privileges +if [[ $UID != 0 ]]; then + printf "${YELLOW}This script requires root privileges.${NC}\n" # skipcq + printf "Please enter the root password to continue.\n" + exec sudo "$0" "$@" +else + printf "${YELLOW}Running with root privileges${NC}\n" # skipcq +fi + +# Detect available package manager +if command -v apt &>/dev/null; then + PACKAGE_MANAGER="apt" +elif command -v apk &>/dev/null; then + PACKAGE_MANAGER="apk" +elif command -v yum &>/dev/null; then + PACKAGE_MANAGER="yum" +elif command -v pacman &>/dev/null; then + PACKAGE_MANAGER="pacman" +else + printf "${RED}Unsupported package manager. Please use a compatible distribution or update the installer script.${NC}\n" # skipcq + exit 1 +fi + +if command -v termux-setup-storage; then + printf "${RED}For termux, please use https://raw.githubusercontent.com/The-MoonTg-project/Moon-Userbot/main/termux-install.sh${NC}\n" # skipcq + exit 1 +fi + +# Install necessary packages based on detected package manager +case "$PACKAGE_MANAGER" in +apt) + apt update -y + apt install python3 python3-venv git wget -y || exit 2 + ;; +apk) + apk update + apk add python3 py3-virtualenv git wget || exit 2 # Packages here may be wrong, to verify + ;; +yum) + yum update -y + yum install python3 python3-venv git wget -y || exit 2 # Packages here may be wrong, to verify + ;; +pacman) + pacman -S --noconfirm python python-virtualenv git wget || exit 2 + ;; +esac + +# Clone repository if not exists +if [[ -d "Moon-Userbot" && "$(basename "$PWD")" != "Moon-Userbot" ]]; then + cd Moon-Userbot || exit 2 +elif [[ "$(basename "$PWD")" == "Moon-Userbot" && -f ".env.dist" && -f "main.py" && -d "modules" ]]; then + printf "${BLUE}Already inside the Moon-Userbot repo, proceeding...${NC}\n" # skipcq +else + git clone https://github.com/The-MoonTg-project/Moon-Userbot || exit 2 + cd Moon-Userbot || exit 2 +fi + +if [[ -f ".env" ]] && [[ -f "my_account.session" ]]; then + printf "${GREEN}It seems that Moon-Userbot is already installed. Exiting...${NC}\n" # skipcq + exit +fi + +# Prompt user if they want to proceed with creating a virtual environment +printf "${YELLOW}It's recommended to use a virtual environment for Python projects.${NC}\n" # skipcq +printf "Note: If your drive resources are very limited, you might consider not creating a virtual environment, but it shouldn't be rejected otherwise unless you know what you're doing.\n" +printf "If you're unsure, it's better to create a virtual environment.\n" +printf "${INPUT}Would you like to create a virtual environment? (Y/n)${NC} > " # skipcq +read -r create_venv + +if [[ "$create_venv" != "n" ]] && [[ "$create_venv" != "N" ]]; then + # Create a virtual environment inside the cloned repository and activate it + python3 -m venv venv + . venv/bin/activate + + # Upgrade pip and install wheel and pillow + pip install -U pip wheel pillow +fi + +if [ -d ".venv" ]; then + . .venv/bin/activate +elif [ -d "venv" ]; then + . venv/bin/activate +fi + +# Install Python requirements +pip install -U -r requirements.txt || exit 2 +# Prompt for API_ID and API_HASH +printf "Enter API_ID and API_HASH\n" +printf "You can get it here -> https://my.telegram.org/\n" +printf "Leave empty to use defaults (please note that using default keys is a ${RED}very bad idea${NC} and significantly increases your ban chances)\n" # skipcq +read -r -p "API_ID > " api_id + +# Default API_ID and API_HASH +if [[ $api_id = "" ]]; then + printf "${RED}You have chosen to use the default API_ID and API_HASH, which is strongly discouraged.${NC}\n" # skipcq + printf "${YELLOW}Please type${NC} '${BLUE}I agree${NC}'${YELLOW} to confirm that you understand the risks and still wish to proceed.${NC}\n" # skipcq + read -r -p "Confirmation > " confirmation + if [[ $confirmation = "I agree" ]]; then + api_id="2040" + api_hash="b18441a1ff607e10a989891a5462e627" + else + printf "${RED}Confirmation not provided. Exiting...${NC}\n" # skipcq + exit 1 + fi +else + read -r -p "API_HASH > " api_hash +fi +# Prompt for PM PERMIT warn limit +# PM PERMIT warn limit is the number of messages a user can receive from others before giving them a warning, requires `antipm` plugin to be enabled +printf "SET PM PERMIT warn limit\n" +# Now below is more clear version: +printf "The number of messages others can send you before receiving a warning, and eventually a ban or leave empty for default (3), requires antipm plugin to be enabled\n" +read -r -p "PM_LIMIT warn limit > " pm_limit + +if [[ $pm_limit = "" ]]; then + pm_limit="3" + printf "Limit not provided by user; set to default\n" +fi + +# Prompt for musicbot usage +printf "Do you want to use musicbot? (y/N)" +read -r musicbot +if [[ $musicbot = "y" ]]; then + printf "Enter SECOND_SESSION_STRING to be used by musicbot\n" + read -r -p "SECOND_SESSION > " second_session + if [[ $second_session = "" ]]; then + printf "SECOND_SESSION not provided by user\n" + second_session="" + fi +fi + +# Prompt for various API keys +printf "Enter APIFLASH_KEY for webshot plugin\n" +printf "You can get it here -> https://apiflash.com/dashboard/access_keys\n" +read -r -p "APIFLASH_KEY > " apiflash_key + +if [[ $apiflash_key = "" ]]; then + printf "NOTE: API Not set; you'll get errors with webshot & ws module\n" +fi + +printf "Enter RMBG_KEY for remove background module\n" +printf "You can get it here -> https://www.remove.bg/dashboard#api-key\n" +read -r -p "RMBG_KEY > " rmbg_key + +if [[ $rmbg_key = "" ]]; then + printf "NOTE: API Not set; you'll not be able to use remove background modules\n" +fi + +printf "Enter VT_KEY for VirusTotal\n" +printf "You can get it here -> https://www.virustotal.com/\n" +read -r -p "VT_KEY > " vt_key + +if [[ $vt_key = "" ]]; then + printf "NOTE: API Not set; you'll not be able to use VirusTotal module\n" +fi + +printf "Enter GEMINI_KEY if you want to use AI\n" +printf "You can get it here -> https://makersuite.google.com/app/apikey\n" +read -r -p "GEMINI_KEY > " gemini_key + +if [[ $gemini_key = "" ]]; then + printf "NOTE: API Not set; you'll not be able to use Gemini AI modules\n" +fi + +printf "Enter COHERE_KEY if you want to use AI" +printf "You can get it here -> https://dashboard.cohere.com/api-keys\n" +read -r -p "COHERE_KEY > " cohere_key + +if [[ $cohere_key = "" ]]; then + printf "NOTE: API Not set; you'll not be able to use Coral AI modules\n" +fi + +while true; do + # Prompt for database type and database URL if MongoDB is selected + printf "${YELLOW}Choose database type:${NC}\n" # skipcq + printf "[1] MongoDB db_url\n" + printf "[2] MongoDB localhost\n" + printf "[3] Sqlite (default)\n" + read -r -p "> " db_type + + case $db_type in + 1) + printf "Please enter db_url\n" + printf "You can get it here -> https://mongodb.com/atlas\n" + read -r -p "> " db_url + db_name=Moon_Userbot + db_type=mongodb + break + ;; + 2) + if ! command -v apt &>/dev/null; then + printf "This option requires apt package manager, which is not available on your system.\n" + printf "Please choose a different database type.\n" + continue + fi + + if systemctl status mongodb; then + wget -qO - https://www.mongodb.org/static/pgp/server-5.0.asc | apt-key add - + source /etc/os-release + printf "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu %s/mongodb-org/5.0 multiverse\n" "${UBUNTU_CODENAME}" | tee /etc/apt/sources.list.d/mongodb-org-5.0.list + apt update + apt install mongodb -y + systemctl daemon-reload + systemctl enable mongodb + fi + systemctl start mongodb + + db_url=mongodb://localhost:27017 + db_name=Moon_Userbot + db_type=mongodb + break + ;; + 3) + db_name=db.sqlite3 + db_type=sqlite3 + break + ;; + *) + printf "${RED}Invalid choice!${NC}\n" # skipcq + ;; + esac +done + +# Generate .env file with collected variables +cat >.env < " install_type + + case $install_type in + 1) + if ! command -v apt &>/dev/null; then + printf "This option requires apt package manager, which is not available on your system.\n" + printf "Please choose a different installation type.\n" + continue + fi + + if ! command -v pm2 &>/dev/null; then + curl -fsSL https://deb.nodesource.com/setup_17.x | bash + apt install nodejs -y + npm install pm2 -g + su -c "pm2 startup" $SUDO_USER + env PATH=$PATH:/usr/bin /usr/lib/node_modules/pm2/bin/pm2 startup systemd -u $SUDO_USER --hp /home/$SUDO_USER + fi + su -c "pm2 start main.py --name Moon --interpreter python3" $SUDO_USER + su -c "pm2 save" $SUDO_USER + + printf "${GREEN}============================\\n" # skipcq + printf "Great! Moon-Userbot installed successfully and running now!\n" + printf "Installation type: PM2\n" + printf "Start with: \"pm2 start Moon\"\n" + printf "Stop with: \"pm2 stop Moon\"\n" + printf "Process name: Moon\n" + printf "============================${NC}\n" # skipcq + break + ;; + 2) + cat >/etc/systemd/system/Moon.service <. + +# /// script +# requires-python = ">=3.11" +# dependencies = [ +# "pip", +# "pyrofork", +# "tgcrypto", +# "wheel", +# "gunicorn", +# "flask", +# "humanize", +# "pygments", +# "pymongo", +# "psutil", +# "Pillow>=10.3.0", +# "click", +# "dnspython", +# "requests", +# "environs", +# "GitPython", +# "beautifulsoup4", +# "aiohttp", +# "aiofiles", +# "pySmartDL", +# ] +# /// +import os +import logging + +import sqlite3 +import platform +import subprocess + +from pyrogram import Client, idle, errors +from pyrogram.enums.parse_mode import ParseMode +from pyrogram.raw.functions.account import GetAuthorizations, DeleteAccount +import requests + +from utils import config +from utils.db import db +from utils.misc import gitrepo, userbot_version +from utils.scripts import restart +from utils.rentry import rentry_cleanup_job +from utils.module import ModuleManager + +SCRIPT_PATH = os.path.dirname(os.path.realpath(__file__)) +if SCRIPT_PATH != os.getcwd(): + os.chdir(SCRIPT_PATH) + +common_params = { + "api_id": config.api_id, + "api_hash": config.api_hash, + "hide_password": True, + "workdir": SCRIPT_PATH, + "app_version": userbot_version, + "device_model": f"mUserbot", + "system_version": platform.version() + " " + platform.machine(), + "sleep_threshold": 30, + "test_mode": config.test_server, + "parse_mode": ParseMode.HTML, +} + +if config.STRINGSESSION: + common_params["session_string"] = config.STRINGSESSION + +app = Client("my_account", **common_params) + + +def load_missing_modules(): + all_modules = db.get("custom.modules", "allModules", []) + if not all_modules: + return + + custom_modules_path = f"{SCRIPT_PATH}/modules/custom_modules" + os.makedirs(custom_modules_path, exist_ok=True) + + try: + f = requests.get( + "https://raw.githubusercontent.com/The-MoonTg-project/custom_modules/main/full.txt" + ).text + except Exception: + logging.error("Failed to fetch custom modules list") + return + modules_dict = { + line.split("/")[-1].split()[0]: line.strip() for line in f.splitlines() + } + + for module_name in all_modules: + module_path = f"{custom_modules_path}/{module_name}.py" + if not os.path.exists(module_path) and module_name in modules_dict: + url = f"https://raw.githubusercontent.com/The-MoonTg-project/custom_modules/main/{modules_dict[module_name]}.py" + resp = requests.get(url) + if resp.ok: + with open(module_path, "wb") as f: + f.write(resp.content) + logging.info("Loaded missing module: %s", module_name) + else: + logging.warning("Failed to load module: %s", module_name) + + +async def main(): + logging.basicConfig( + format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", + handlers=[logging.FileHandler("moonlogs.txt"), logging.StreamHandler()], + level=logging.INFO, + ) + DeleteAccount.__new__ = None + + try: + await app.start() + except sqlite3.OperationalError as e: + if str(e) == "database is locked" and os.name == "posix": + logging.warning( + "Session file is locked. Trying to kill blocking process..." + ) + subprocess.run(["fuser", "-k", "my_account.session"], check=True) + restart() + raise + except (errors.NotAcceptable, errors.Unauthorized) as e: + logging.error( + "%s: %s\nMoving session file to my_account.session-old...", + e.__class__.__name__, + e, + ) + os.rename("./my_account.session", "./my_account.session-old") + restart() + + load_missing_modules() + module_manager = ModuleManager.get_instance() + await module_manager.load_modules(app) + + if info := db.get("core.updater", "restart_info"): + text = { + "restart": "Restart completed!", + "update": "Update process completed!", + }[info["type"]] + try: + await app.edit_message_text(info["chat_id"], info["message_id"], text) + except errors.RPCError: + pass + db.remove("core.updater", "restart_info") + + # required for sessionkiller module + if db.get("core.sessionkiller", "enabled", False): + db.set( + "core.sessionkiller", + "auths_hashes", + [ + auth.hash + for auth in (await app.invoke(GetAuthorizations())).authorizations + ], + ) + + logging.info("Moon-Userbot started!") + + app.loop.create_task(rentry_cleanup_job()) + + await idle() + + await app.stop() + + +if __name__ == "__main__": + app.run(main()) diff --git a/modules/1000-7.py b/modules/1000-7.py new file mode 100644 index 0000000..780e3cd --- /dev/null +++ b/modules/1000-7.py @@ -0,0 +1,41 @@ +from asyncio import sleep + +from pyrogram import Client, filters +from pyrogram.types import Message + +from utils.misc import modules_help, prefix + +digits = { + str(i): el + for i, el in enumerate(["0️⃣", "1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6️⃣", "7️⃣", "8️⃣", "9️⃣"]) +} + + +def prettify(val: int) -> str: + return "".join(digits[i] for i in str(val)) + + +@Client.on_message(filters.command("ghoul", prefix) & filters.me) +async def ghoul_counter(_, message: Message): + await message.delete() + + if len(message.command) > 1 and message.command[1].isdigit(): + counter = int(message.command[1]) + else: + counter = 1000 + + msg = await message.reply(prettify(counter), quote=False) + + await sleep(1) + + while counter // 7: + counter -= 7 + await msg.edit(prettify(counter)) + await sleep(1) + + await msg.edit("🤡 GHOUL 🤡") + + +modules_help["1000-7"] = { + "ghoul [count_from]": "counting from 1000 (or given [count_from] to 0 as a ghoul" +} diff --git a/modules/admintool.py b/modules/admintool.py new file mode 100644 index 0000000..7ca2ae5 --- /dev/null +++ b/modules/admintool.py @@ -0,0 +1,353 @@ +# Moon-Userbot - telegram userbot +# Copyright (C) 2020-present Moon Userbot Organization +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from contextlib import suppress + +from pyrogram.enums import ChatType +from pyrogram import Client, ContinuePropagation, filters +from pyrogram.errors import ( + UserAdminInvalid, + ChatAdminRequired, + RPCError, +) +from pyrogram.raw import functions +from pyrogram.types import Message, ChatPermissions + +from utils.db import db +from utils.scripts import format_exc, with_reply +from utils.misc import modules_help, prefix + +from utils.handlers import ( + BanHandler, + UnbanHandler, + KickHandler, + KickDeletedAccountsHandler, + TimeMuteHandler, + TimeUnmuteHandler, + TimeMuteUsersHandler, + UnmuteHandler, + MuteHandler, + DemoteHandler, + PromoteHandler, + AntiChannelsHandler, + DeleteHistoryHandler, + AntiRaidHandler, +) + + +db_cache: dict = db.get_collection("core.ats") + + +def update_cache(): + db_cache.clear() + db_cache.update(db.get_collection("core.ats")) + + +@Client.on_message(filters.group & ~filters.me) +async def admintool_handler(_, message: Message): + if message.sender_chat and ( + message.sender_chat.type == "supergroup" + or message.sender_chat.id == db_cache.get(f"linked{message.chat.id}", 0) + ): + raise ContinuePropagation + + if message.sender_chat and db_cache.get(f"antich{message.chat.id}", False): + with suppress(RPCError): + await message.delete() + await message.chat.ban_member(message.sender_chat.id) + + tmuted_users = db_cache.get(f"c{message.chat.id}", []) + if ( + message.from_user + and message.from_user.id in tmuted_users + or message.sender_chat + and message.sender_chat.id in tmuted_users + ): + with suppress(RPCError): + await message.delete() + + if db_cache.get(f"antiraid{message.chat.id}", False): + with suppress(RPCError): + await message.delete() + if message.from_user: + await message.chat.ban_member(message.from_user.id) + elif message.sender_chat: + await message.chat.ban_member(message.sender_chat.id) + + if message.new_chat_members and db_cache.get( + f"welcome_enabled{message.chat.id}", False + ): + await message.reply( + db_cache.get(f"welcome_text{message.chat.id}"), + disable_web_page_preview=True, + ) + + raise ContinuePropagation + + +async def get_user_and_name(message): + if message.reply_to_message.from_user: + return ( + message.reply_to_message.from_user.id, + message.reply_to_message.from_user.first_name, + ) + if message.reply_to_message.sender_chat: + return ( + message.reply_to_message.sender_chat.id, + message.reply_to_message.sender_chat.title, + ) + + +@Client.on_message(filters.command(["ban"], prefix) & filters.me) +async def ban_command(client: Client, message: Message): + handler = BanHandler(client, message) + await handler.handle_ban() + + +@Client.on_message(filters.command(["unban"], prefix) & filters.me) +async def unban_command(client: Client, message: Message): + handler = UnbanHandler(client, message) + await handler.handle_unban() + + +@Client.on_message(filters.command(["kick"], prefix) & filters.me) +async def kick_command(client: Client, message: Message): + handler = KickHandler(client, message) + await handler.handle_kick() + + +@Client.on_message(filters.command(["kickdel"], prefix) & filters.me) +async def kickdel_cmd(client: Client, message: Message): + handler = KickDeletedAccountsHandler(client, message) + await handler.kick_deleted_accounts() + + +@Client.on_message(filters.command(["tmute"], prefix) & filters.me) +async def tmute_command(client: Client, message: Message): + handler = TimeMuteHandler(client, message) + await handler.handle_tmute() + update_cache() + + +@Client.on_message(filters.command(["tunmute"], prefix) & filters.me) +async def tunmute_command(client: Client, message: Message): + handler = TimeUnmuteHandler(client, message) + await handler.handle_tunmute() + update_cache() + + +@Client.on_message(filters.command(["tmute_users"], prefix) & filters.me) +async def tunmute_users_command(client: Client, message: Message): + handler = TimeMuteUsersHandler(client, message) + await handler.list_tmuted_users() + + +@Client.on_message(filters.command(["unmute"], prefix) & filters.me) +async def unmute_command(client: Client, message: Message): + handler = UnmuteHandler(client, message) + await handler.handle_unmute() + + +@Client.on_message(filters.command(["mute"], prefix) & filters.me) +async def mute_command(client: Client, message: Message): + handler = MuteHandler(client, message) + await handler.handle_mute() + + +@Client.on_message(filters.command(["demote"], prefix) & filters.me) +async def demote_command(client: Client, message: Message): + handler = DemoteHandler(client, message) + await handler.handle_demote() + + +@Client.on_message(filters.command(["promote"], prefix) & filters.me) +async def promote_command(client: Client, message: Message): + handler = PromoteHandler(client, message) + await handler.handle_promote() + + +@Client.on_message(filters.command(["antich"], prefix)) +async def anti_channels(client: Client, message: Message): + handler = AntiChannelsHandler(client, message) + await handler.handle_anti_channels() + update_cache() + + +@Client.on_message(filters.command(["delete_history", "dh"], prefix)) +async def delete_history(client: Client, message: Message): + handler = DeleteHistoryHandler(client, message) + await handler.handle_delete_history() + + +@Client.on_message(filters.command(["report_spam", "rs"], prefix)) +@with_reply +async def report_spam(client: Client, message: Message): + try: + channel = await client.resolve_peer(message.chat.id) + + user_id, name = await get_user_and_name(message) + peer = await client.resolve_peer(user_id) + await client.invoke( + functions.channels.ReportSpam( + channel=channel, + participant=peer, + id=[message.reply_to_message.id], + ) + ) + except Exception as e: + await message.edit(format_exc(e)) + else: + await message.edit(f"Message from {name} was reported") + + +@Client.on_message(filters.command("pin", prefix) & filters.me) +@with_reply +async def pin(_, message: Message): + try: + await message.reply_to_message.pin() + await message.edit("Pinned!") + except Exception as e: + await message.edit(format_exc(e)) + + +@Client.on_message(filters.command("unpin", prefix) & filters.me) +@with_reply +async def unpin(_, message: Message): + try: + await message.reply_to_message.unpin() + await message.edit("Unpinned!") + except Exception as e: + await message.edit(format_exc(e)) + + +@Client.on_message(filters.command("ro", prefix) & filters.me) +async def ro(client: Client, message: Message): + if message.chat.type not in [ChatType.GROUP, ChatType.SUPERGROUP]: + await message.edit("Invalid chat type") + return + + try: + perms = message.chat.permissions + perms_list = [ + perms.can_send_messages, + perms.can_send_media_messages, + perms.can_send_polls, + perms.can_add_web_page_previews, + perms.can_change_info, + perms.can_invite_users, + perms.can_pin_messages, + ] + db.set("core.ats", f"ro{message.chat.id}", perms_list) + + try: + await client.set_chat_permissions(message.chat.id, ChatPermissions()) + except (UserAdminInvalid, ChatAdminRequired): + await message.edit("No rights") + else: + await message.edit( + "Read-only mode activated!\n" + f"Turn off with:{prefix}unro" + ) + except Exception as e: + await message.edit(format_exc(e)) + + +@Client.on_message(filters.command("unro", prefix) & filters.me) +async def unro(client: Client, message: Message): + if message.chat.type not in [ChatType.GROUP, ChatType.SUPERGROUP]: + await message.edit("Invalid chat type") + return + + try: + perms_list = db.get( + "core.ats", + f"ro{message.chat.id}", + [True, True, False, False, False, False, False], + ) + + common_perms = { + "can_send_messages": perms_list[0], + "can_send_media_messages": perms_list[1], + "can_send_polls": perms_list[2], + "can_add_web_page_previews": perms_list[3], + "can_change_info": perms_list[4], + "can_invite_users": perms_list[5], + "can_pin_messages": perms_list[6], + } + + perms = ChatPermissions(**common_perms) + + try: + await client.set_chat_permissions(message.chat.id, perms) + except (UserAdminInvalid, ChatAdminRequired): + await message.edit("No rights") + else: + await message.edit("Read-only mode disabled!") + except Exception as e: + await message.edit(format_exc(e)) + + +@Client.on_message(filters.command("antiraid", prefix) & filters.me) +async def antiraid(client: Client, message: Message): + handler = AntiRaidHandler(client, message) + await handler.handle_antiraid() + update_cache() + + +@Client.on_message(filters.command(["welcome", "wc"], prefix) & filters.me) +async def welcome(_, message: Message): + if message.chat.type not in [ChatType.GROUP, ChatType.SUPERGROUP]: + return await message.edit("Unsupported chat type") + + if len(message.command) > 1: + text = message.text.split(maxsplit=1)[1] + db.set("core.ats", f"welcome_enabled{message.chat.id}", True) + db.set("core.ats", f"welcome_text{message.chat.id}", text) + + await message.edit( + f"Welcome enabled in this chat\nText: {text}" + ) + else: + db.set("core.ats", f"welcome_enabled{message.chat.id}", False) + await message.edit("Welcome disabled in this chat") + + update_cache() + + +modules_help["admintool"] = { + "ban [reply]/[username/id]* [reason] [report_spam] [delete_history]": "ban user in chat", + "unban [reply]/[username/id]* [reason]": "unban user in chat", + "kick [reply]/[userid]* [reason] [report_spam] [delete_history]": "kick user out of chat", + "mute [reply]/[userid]* [reason] [1m]/[1h]/[1d]/[1w]": "mute user in chat", + "unmute [reply]/[userid]* [reason]": "unmute user in chat", + "promote [reply]/[userid]* [prefix]": "promote user in chat", + "demote [reply]/[userid]* [reason]": "demote user in chat", + "tmute [reply]/[username/id]* [reason]": "delete all new messages from user in chat", + "tunmute [reply]/[username/id]* [reason]": "stop deleting all messages from user in chat", + "tmute_users": "list of tmuted (.tmute) users", + "antich [enable/disable]": "turn on/off blocking channels in this chat", + "delete_history [reply]/[username/id]* [reason]": "delete history from member in chat", + "report_spam [reply]*": "report spam message in chat", + "pin [reply]*": "Pin replied message", + "unpin [reply]*": "Unpin replied message", + "ro": "enable read-only mode", + "unro": "disable read-only mode", + "antiraid [on|off]": "when enabled, anyone who writes message will be blocked. Useful in raids. " + "Running without arguments equals to toggling state", + "welcome [text]*": "enable auto-welcome to new users in groups. " + "Running without text equals to disable", + "kickdel": "Kick all deleted accounts", +} diff --git a/modules/admlist.py b/modules/admlist.py new file mode 100644 index 0000000..ba2bc49 --- /dev/null +++ b/modules/admlist.py @@ -0,0 +1,380 @@ +# Moon-Userbot - telegram userbot +# Copyright (C) 2020-present Moon Userbot Organization +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from time import perf_counter +from typing import AsyncGenerator, List, Optional, Union + +from pyrogram import Client, enums, filters, raw, types, utils +from pyrogram.types.object import Object + +from utils.misc import modules_help, prefix +from utils.scripts import format_exc + + +class Chat(Object): + def __init__( + self, + *, + client: "Client" = None, + id: id, + type: type, + is_verified: bool = None, + is_restricted: bool = None, + is_creator: bool = None, + is_scam: bool = None, + is_fake: bool = None, + is_support: bool = None, + title: str = None, + username: str = None, + first_name: str = None, + last_name: str = None, + photo: "types.ChatPhoto" = None, + bio: str = None, + description: str = None, + dc_id: int = None, + has_protected_content: bool = None, + invite_link: str = None, + pinned_message=None, + sticker_set_name: str = None, + can_set_sticker_set: bool = None, + members_count: int = None, + restrictions: List["types.Restriction"] = None, + permissions: "types.ChatPermissions" = None, + distance: int = None, + linked_chat: "types.Chat" = None, + send_as_chat: "types.Chat" = None, + available_reactions: Optional["types.ChatReactions"] = None, + is_admin: bool = False, + deactivated: bool = False, + ): + super().__init__(client) + + self.id = id + self.type = type + self.is_verified = is_verified + self.is_restricted = is_restricted + self.is_creator = is_creator + self.is_scam = is_scam + self.is_fake = is_fake + self.is_support = is_support + self.title = title + self.username = username + self.first_name = first_name + self.last_name = last_name + self.photo = photo + self.bio = bio + self.description = description + self.dc_id = dc_id + self.has_protected_content = has_protected_content + self.invite_link = invite_link + self.pinned_message = pinned_message + self.sticker_set_name = sticker_set_name + self.can_set_sticker_set = can_set_sticker_set + self.members_count = members_count + self.restrictions = restrictions + self.permissions = permissions + self.distance = distance + self.linked_chat = linked_chat + self.send_as_chat = send_as_chat + self.available_reactions = available_reactions + self.is_admin = is_admin + self.deactivated = deactivated + + @staticmethod + def _parse_user_chat(client, user: raw.types.User) -> "Chat": + peer_id = user.id + + return Chat( + id=peer_id, + type=enums.ChatType.BOT if user.bot else enums.ChatType.PRIVATE, + is_verified=getattr(user, "verified", None), + is_restricted=getattr(user, "restricted", None), + is_scam=getattr(user, "scam", None), + is_fake=getattr(user, "fake", None), + is_support=getattr(user, "support", None), + username=user.username, + first_name=user.first_name, + last_name=user.last_name, + photo=types.ChatPhoto._parse(client, user.photo, peer_id, user.access_hash), + restrictions=types.List( + [types.Restriction._parse(r) for r in user.restriction_reason] + ) + or None, + dc_id=getattr(getattr(user, "photo", None), "dc_id", None), + client=client, + ) + + @staticmethod + def _parse_chat_chat(client, chat: raw.types.Chat) -> "Chat": + peer_id = -chat.id + return Chat( + id=peer_id, + type=enums.ChatType.GROUP, + title=chat.title, + is_creator=getattr(chat, "creator", None), + photo=types.ChatPhoto._parse( + client, getattr(chat, "photo", None), peer_id, 0 + ), + permissions=types.ChatPermissions._parse( + getattr(chat, "default_banned_rights", None) + ), + members_count=getattr(chat, "participants_count", None), + dc_id=getattr(getattr(chat, "photo", None), "dc_id", None), + has_protected_content=getattr(chat, "noforwards", None), + client=client, + is_admin=bool(getattr(chat, "admin_rights", False)), + deactivated=chat.deactivated, + ) + + @staticmethod + def _parse_channel_chat(client, channel: raw.types.Channel) -> "Chat": + peer_id = utils.get_channel_id(channel.id) + restriction_reason = getattr(channel, "restriction_reason", []) + + return Chat( + id=peer_id, + type=( + enums.ChatType.SUPERGROUP + if getattr(channel, "megagroup", None) + else enums.ChatType.CHANNEL + ), + is_verified=getattr(channel, "verified", None), + is_restricted=getattr(channel, "restricted", None), + is_creator=getattr(channel, "creator", None), + is_scam=getattr(channel, "scam", None), + is_fake=getattr(channel, "fake", None), + title=channel.title, + username=getattr(channel, "username", None), + photo=types.ChatPhoto._parse( + client, + getattr(channel, "photo", None), + peer_id, + getattr(channel, "access_hash", 0), + ), + restrictions=types.List( + [types.Restriction._parse(r) for r in restriction_reason] + ) + or None, + permissions=types.ChatPermissions._parse( + getattr(channel, "default_banned_rights", None) + ), + members_count=getattr(channel, "participants_count", None), + dc_id=getattr(getattr(channel, "photo", None), "dc_id", None), + has_protected_content=getattr(channel, "noforwards", None), + is_admin=bool(getattr(channel, "admin_rights", False)), + client=client, + ) + + @staticmethod + def _parse( + client, + message: Union[raw.types.Message, raw.types.MessageService], + users: dict, + chats: dict, + is_chat: bool, + ) -> "Chat": + from_id = utils.get_raw_peer_id(message.from_id) + peer_id = utils.get_raw_peer_id(message.peer_id) + chat_id = (peer_id or from_id) if is_chat else (from_id or peer_id) + + if isinstance(message.peer_id, raw.types.PeerUser): + return Chat._parse_user_chat(client, users[chat_id]) + + if isinstance(message.peer_id, raw.types.PeerChat): + return Chat._parse_chat_chat(client, chats[chat_id]) + + return Chat._parse_channel_chat(client, chats[chat_id]) + + @staticmethod + def _parse_dialog(client, peer, users: dict, chats: dict): + if isinstance(peer, raw.types.PeerUser): + return Chat._parse_user_chat(client, users[peer.user_id]) + if isinstance(peer, raw.types.PeerChat): + return Chat._parse_chat_chat(client, chats[peer.chat_id]) + return Chat._parse_channel_chat(client, chats[peer.channel_id]) + + +class Dialog(Object): + def __init__( + self, + *, + client: "Client" = None, + chat: "types.Chat", + top_message: "types.Message", + unread_messages_count: int, + unread_mentions_count: int, + unread_mark: bool, + is_pinned: bool, + ): + super().__init__(client) + + self.chat = chat + self.top_message = top_message + self.unread_messages_count = unread_messages_count + self.unread_mentions_count = unread_mentions_count + self.unread_mark = unread_mark + self.is_pinned = is_pinned + + @staticmethod + def _parse(client, dialog: "raw.types.Dialog", messages, users, chats) -> "Dialog": + return Dialog( + chat=Chat._parse_dialog(client, dialog.peer, users, chats), + top_message=messages.get(utils.get_peer_id(dialog.peer)), + unread_messages_count=dialog.unread_count, + unread_mentions_count=dialog.unread_mentions_count, + unread_mark=dialog.unread_mark, + is_pinned=dialog.pinned, + client=client, + ) + + +async def get_dialogs( + self: "Client", limit: int = 0 +) -> Optional[AsyncGenerator["types.Dialog", None]]: + current = 0 + total = limit or (1 << 31) - 1 + limit = min(100, total) + offset_date = 0 + offset_id = 0 + offset_peer = raw.types.InputPeerEmpty() + while True: + r = await self.invoke( + raw.functions.messages.GetDialogs( + offset_date=offset_date, + offset_id=offset_id, + offset_peer=offset_peer, + limit=limit, + hash=0, + ), + sleep_threshold=60, + ) + users = {i.id: i for i in r.users} + chats = {i.id: i for i in r.chats} + messages = {} + for message in r.messages: + if isinstance(message, raw.types.MessageEmpty): + continue + chat_id = utils.get_peer_id(message.peer_id) + messages[chat_id] = await types.Message._parse(self, message, users, chats) + dialogs = [] + for dialog in r.dialogs: + if not isinstance(dialog, raw.types.Dialog): + continue + dialogs.append(Dialog._parse(self, dialog, messages, users, chats)) + if not dialogs: + return + last = dialogs[-1] + offset_id = last.top_message.id + offset_date = utils.datetime_to_timestamp(last.top_message.date) + offset_peer = await self.resolve_peer(last.chat.id) + for dialog in dialogs: + yield dialog + current += 1 + if current >= total: + return + + +@Client.on_message(filters.command("admlist", prefix) & filters.me) +async def admlist(client: Client, message: types.Message): + await message.edit("Retrieving information... (it'll take some time)") + + start = perf_counter() + try: + adminned_chats = [] + owned_chats = [] + owned_usernamed_chats = [] + async for dialog in get_dialogs(client): + chat = dialog.chat + if getattr(chat, "deactivated", False): + continue + if getattr(chat, "is_creator", False) and getattr(chat, "username", None): + owned_usernamed_chats.append(chat) + elif getattr(chat, "is_creator", False): + owned_chats.append(chat) + elif getattr(chat, "is_admin", False): + adminned_chats.append(chat) + + text = "Adminned chats:\n" + for index, chat in enumerate(adminned_chats): + cid = str(chat.id).replace("-100", "") + text += f"{index + 1}. {chat.title}\n" + + text += "\nOwned chats:\n" + for index, chat in enumerate(owned_chats): + cid = str(chat.id).replace("-100", "") + text += f"{index + 1}. {chat.title}\n" + + text += "\nOwned chats with username:\n" + for index, chat in enumerate(owned_usernamed_chats): + cid = str(chat.id).replace("-100", "") + text += f"{index + 1}. {chat.title}\n" + + stop = perf_counter() + total_count = ( + len(adminned_chats) + len(owned_chats) + len(owned_usernamed_chats) + ) + await message.edit( + text + "\n" + f"Total: {total_count}" + f"\nAdminned chats: {len(adminned_chats)}\n" + f"Owned chats: {len(owned_chats)}\n" + f"Owned chats with username: {len(owned_usernamed_chats)}\n\n" + f"Done in {round(stop - start, 3)} seconds.", + ) + except Exception as e: + await message.edit(format_exc(e)) + return + + +@Client.on_message(filters.command("admcount", prefix) & filters.me) +async def admcount(client: Client, message: types.Message): + await message.edit("Retrieving information... (it'll take some time)") + + start = perf_counter() + try: + adminned_chats = 0 + owned_chats = 0 + owned_usernamed_chats = 0 + async for dialog in get_dialogs(client): + chat = dialog.chat + if getattr(chat, "deactivated", False): + continue + if getattr(chat, "is_creator", False) and getattr(chat, "username", None): + owned_usernamed_chats += 1 + elif getattr(chat, "is_creator", False): + owned_chats += 1 + elif getattr(chat, "is_admin", False): + adminned_chats += 1 + + stop = perf_counter() + total_count = adminned_chats + owned_chats + owned_usernamed_chats + await message.edit( + f"Total: {total_count}" + f"\nAdminned chats: {adminned_chats}\n" + f"Owned chats: {owned_chats}\n" + f"Owned chats with username: {owned_usernamed_chats}\n\n" + f"Done in {round(stop - start, 3)} seconds.\n\n" + f"Get full list: {prefix}admlist", + ) + except Exception as e: + await message.edit(format_exc(e)) + return + + +modules_help["admlist"] = { + "admcount": "Get count of adminned and owned chats", + "admlist": "Get list of adminned and owned chats", +} diff --git a/modules/afk.py b/modules/afk.py new file mode 100644 index 0000000..00af2e4 --- /dev/null +++ b/modules/afk.py @@ -0,0 +1,88 @@ +# Dragon-Userbot - telegram userbot +# Copyright (C) 2020-present Dragon Userbot Organization +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import datetime + +from pyrogram import Client, filters, types + +from utils.db import db +from utils.misc import modules_help, prefix + +# avoid using global variables +afk_info = db.get( + "core.afk", + "afk_info", + { + "start": 0, + "is_afk": False, + "reason": "", + }, +) + +is_afk = filters.create(lambda _, __, ___: afk_info["is_afk"]) +is_support = filters.create(lambda _, __, message: message.chat.is_support) + + +@Client.on_message( + is_afk + & (filters.private | filters.mentioned) + & ~filters.channel + & ~filters.me + & ~filters.bot + & ~is_support +) +async def afk_handler(_, message: types.Message): + start = datetime.datetime.fromtimestamp(afk_info["start"]) + end = datetime.datetime.now().replace(microsecond=0) + afk_time = end - start + await message.reply( + f"I'm AFK {afk_time}\nReason: {afk_info['reason']}" + ) + + +@Client.on_message(filters.command("afk", prefix) & filters.me) +async def afk(_, message): + if len(message.text.split()) >= 2: + reason = message.text.split(" ", maxsplit=1)[1] + else: + reason = "None" + + afk_info["start"] = int(datetime.datetime.now().timestamp()) + afk_info["is_afk"] = True + afk_info["reason"] = reason + + await message.edit(f"I'm going AFK.\n" f"Reason: {reason}") + + db.set("core.afk", "afk_info", afk_info) + + +@Client.on_message(filters.command("unafk", prefix) & filters.me) +async def unafk(_, message): + if afk_info["is_afk"]: + start = datetime.datetime.fromtimestamp(afk_info["start"]) + end = datetime.datetime.now().replace(microsecond=0) + afk_time = end - start + await message.edit( + f"I'm not AFK anymore.\n" f"I was afk {afk_time}" + ) + afk_info["is_afk"] = False + else: + await message.edit("You weren't afk") + + db.set("core.afk", "afk_info", afk_info) + + +modules_help["afk"] = {"afk [reason]": "Go to afk", "unafk": "Get out of AFK"} diff --git a/modules/aimage.py b/modules/aimage.py new file mode 100644 index 0000000..c24a5cd --- /dev/null +++ b/modules/aimage.py @@ -0,0 +1,129 @@ +import os +from PIL import Image + +from pyrogram import Client, filters, enums +from pyrogram.types import Message + +from utils.misc import modules_help, prefix +from utils.scripts import format_exc, import_library + +genai = import_library("google.generativeai", "google-generativeai") + +from utils.config import gemini_key + +genai.configure(api_key=gemini_key) + +generation_config_cook = { + "temperature": 0.35, + "top_p": 0.95, + "top_k": 40, + "max_output_tokens": 1024, +} + +model = genai.GenerativeModel("gemini-1.5-flash-latest") +model_cook = genai.GenerativeModel( + model_name="gemini-1.5-flash-latest", generation_config=generation_config_cook +) + + +@Client.on_message(filters.command("getai", prefix) & filters.me) +async def getai(_, message: Message): + try: + await message.edit_text("Please Wait...") + try: + base_img = await message.reply_to_message.download() + except AttributeError: + return await message.edit_text("Please reply to an image...") + + img = Image.open(base_img) + prompt = "Get details of given image, be as accurate as possible." + + response = model.generate_content([prompt, img]) + + await message.edit_text( + f"**Detail Of Image:** {response.text}", parse_mode=enums.ParseMode.MARKDOWN + ) + os.remove(base_img) + return + except Exception as e: + await message.edit_text(f"An error occurred: {format_exc(e)}") + + +@Client.on_message(filters.command("aicook", prefix) & filters.me) +async def aicook(_, message: Message): + if message.reply_to_message: + try: + await message.edit_text("Cooking...") + + try: + base_img = await message.reply_to_message.download() + except AttributeError: + return await message.edit_text( + "Please reply to an image..." + ) + + img = Image.open(base_img) + cook_img = [ + "Accurately identify the baked good in the image and provide an appropriate and recipe consistent with your analysis. ", + img, + ] + + response = model_cook.generate_content(cook_img) + + await message.edit_text( + f"{response.text}", parse_mode=enums.ParseMode.MARKDOWN + ) + os.remove(base_img) + return + except Exception as e: + await message.edit_text(str(e)) + return await message.edit_text("Please reply to an image...") + + +@Client.on_message(filters.command("aiseller", prefix) & filters.me) +async def aiseller(_, message: Message): + if message.reply_to_message: + try: + await message.edit_text("Generating...") + if len(message.command) > 1: + taud = message.text.split(maxsplit=1)[1] + else: + return await message.edit_text( + f"Usage: {prefix}aiseller [target audience] [reply to product image]" + ) + + try: + base_img = await message.reply_to_message.download() + except AttributeError: + return await message.edit_text( + "Please reply to an image..." + ) + + img = Image.open(base_img) + sell_img = [ + "Given an image of a product and its target audience, write an engaging marketing description", + "Product Image: ", + img, + "Target Audience: ", + taud, + ] + + response = model.generate_content(sell_img) + + await message.edit_text( + f"{response.text}", parse_mode=enums.ParseMode.MARKDOWN + ) + os.remove(base_img) + return + except Exception: + await message.edit_text( + f"Usage: {prefix}aiseller [target audience] [reply to product image]" + ) + return await message.edit_text("Please reply to an image...") + + +modules_help["aimage"] = { + "getai [reply to image]*": "Get details of image with Ai", + "aicook [reply to image]*": "Generate Cooking instrunctions of the given food image", + "aiseller [target audience] [reply to product image]*": "Generate a promotional message for the given image product for the given target audience", +} diff --git a/modules/amogus.py b/modules/amogus.py new file mode 100644 index 0000000..514f1ad --- /dev/null +++ b/modules/amogus.py @@ -0,0 +1,58 @@ +from io import BytesIO +from random import randint + +from pyrogram import Client, filters, enums +from pyrogram.types import Message + +from requests import get +from PIL import Image, ImageFont, ImageDraw +from textwrap import wrap + +from utils.misc import modules_help, prefix + + +@Client.on_message(filters.command("amogus", prefix) & filters.me) +async def amogus(client: Client, message: Message): + text = " ".join(message.command[1:]) + + await message.edit( + "amgus, tun tun tun tun tun tun tun tudududn tun tun...", + parse_mode=enums.ParseMode.HTML, + ) + + clr = randint(1, 12) + + url = "https://raw.githubusercontent.com/The-MoonTg-project/AmongUs/master/" + font = ImageFont.truetype(BytesIO(get(url + "bold.ttf").content), 60) + imposter = Image.open(BytesIO(get(f"{url}{clr}.png").content)) + + text_ = "\n".join(["\n".join(wrap(part, 30)) for part in text.split("\n")]) + bbox = ImageDraw.Draw(Image.new("RGB", (1, 1))).multiline_textbbox( + (0, 0), text_, font, stroke_width=2 + ) + # w, h = bbox[2] - bbox[0], bbox[3] - bbox[1] + w, h = bbox[2], bbox[3] + text = Image.new("RGBA", (w + 30, h + 30)) + ImageDraw.Draw(text).multiline_text( + (15, 15), text_, "#FFF", font, stroke_width=2, stroke_fill="#000" + ) + w = imposter.width + text.width + 10 + h = max(imposter.height, text.height) + + image = Image.new("RGBA", (w, h)) + image.paste(imposter, (0, h - imposter.height), imposter) + image.paste(text, (w - text.width, 0), text) + image.thumbnail((512, 512)) + + output = BytesIO() + output.name = "imposter.webp" + image.save(output) + output.seek(0) + + await message.delete() + await client.send_sticker(message.chat.id, output) + + +modules_help["amogus"] = { + "amogus [text]": "amgus, tun tun tun tun tun tun tun tudududn tun tun" +} diff --git a/modules/amongus.py b/modules/amongus.py new file mode 100644 index 0000000..8ecaad6 --- /dev/null +++ b/modules/amongus.py @@ -0,0 +1,218 @@ +# From CatUB +import asyncio +import re +from io import BytesIO +from random import choice, randint +from textwrap import wrap + +from PIL import Image, ImageDraw, ImageFont +from click import edit +import requests +from pyrogram import Client, filters, enums +from pyrogram.types import Message + +from utils.scripts import edit_or_reply, ReplyCheck +from utils.misc import modules_help, prefix + + +async def amongus_gen(text: str, clr: int) -> str: + url = ( + "https://github.com/TgCatUB/CatUserbot-Resources/raw/master/Resources/Amongus/" + ) + font = ImageFont.truetype( + BytesIO( + requests.get( + "https://github.com/TgCatUB/CatUserbot-Resources/raw/master/Resources/fonts/bold.ttf" + ).content + ), + 60, + ) + imposter = Image.open(BytesIO(requests.get(f"{url}{clr}.png").content)) + text_ = "\n".join("\n".join(wrap(part, 30)) for part in text.split("\n")) + bbox = ImageDraw.Draw(Image.new("RGB", (1, 1))).multiline_textbbox( + (0, 0), text_, font, stroke_width=2 + ) + w, h = bbox[2], bbox[3] + text = Image.new("RGBA", (w + 30, h + 30)) + ImageDraw.Draw(text).multiline_text( + (15, 15), text_, "#FFF", font, stroke_width=2, stroke_fill="#000" + ) + w = imposter.width + text.width + 10 + h = max(imposter.height, text.height) + image = Image.new("RGBA", (w, h)) + image.paste(imposter, (0, h - imposter.height), imposter) + image.paste(text, (w - text.width, 0), text) + image.thumbnail((512, 512)) + output = BytesIO() + output.name = "imposter.webp" + image.save(output, "WebP") + output.seek(0) + return output + + +async def get_imposter_img(text: str) -> BytesIO: + background = requests.get( + f"https://github.com/TgCatUB/CatUserbot-Resources/raw/master/Resources/imposter/impostor{randint(1, 22)}.png" + ).content + font = requests.get( + "https://github.com/TgCatUB/CatUserbot-Resources/raw/master/Resources/fonts/roboto_regular.ttf" + ).content + font = BytesIO(font) + font = ImageFont.truetype(font, 30) + image = Image.new("RGBA", (1, 1), (0, 0, 0, 0)) + draw = ImageDraw.Draw(image) + bbox = ImageDraw.Draw(Image.new("RGB", (1, 1))).multiline_textbbox( + (0, 0), text, font, stroke_width=2 + ) + w, h = bbox[2], bbox[3] + image = Image.open(BytesIO(background)) + x, y = image.size + draw = ImageDraw.Draw(image) + draw.multiline_text( + ((x - w) // 2, (y - h) // 2), text=text, font=font, fill="white", align="center" + ) + output = BytesIO() + output.name = "impostor.png" + image.save(output, "PNG") + output.seek(0) + return output + + +@Client.on_message(filters.command("amongus", prefix) & filters.me) +async def amongus_cmd(client: Client, message: Message): + text = " ".join(message.command[1:]) if len(message.command) > 1 else "" + reply = message.reply_to_message + await message.edit("tun tun tun...") + if not text and reply: + text = reply.text or reply.caption or "" + + clr = re.findall(r"-c\d+", text) + try: + clr = clr[0] + clr = clr.replace("-c", "") + text = text.replace(f"-c{clr}", "") + clr = int(clr) + if clr > 12 or clr < 1: + clr = randint(1, 12) + except IndexError: + clr = randint(1, 12) + + if not text: + if not reply: + text = f"{message.from_user.first_name} Was a traitor!" + else: + text = f"{reply.from_user.first_name} Was a traitor!" + + imposter_file = await amongus_gen(text, clr) + await message.delete() + await client.send_sticker( + message.chat.id, + imposter_file, + reply_to_message_id=ReplyCheck(message), + ) + + +@Client.on_message(filters.command("imposter", prefix) & filters.me) +async def imposter_cmd(client: Client, message: Message): + remain = randint(1, 2) + imps = ["wasn't the impostor", "was the impostor"] + + if message.reply_to_message: + user = message.reply_to_message.from_user + text = f"{user.first_name} {choice(imps)}." + else: + args = message.text.split()[1:] + if args: + text = " ".join(args) + else: + text = f"{message.from_user.first_name} {choice(imps)}." + + text += f"\n{remain} impostor(s) remain." + imposter_file = await get_imposter_img(text) + await message.delete() + await client.send_photo( + message.chat.id, + imposter_file, + reply_to_message_id=ReplyCheck(message), + ) + + +@Client.on_message(filters.command(["imp", "impn"], prefix) & filters.me) +async def imp_animation(client: Client, message: Message): + name = " ".join(message.command[1:]) if len(message.command) > 1 else "" + if not name: + reply = message.reply_to_message + if reply: + name = reply.from_user.first_name + else: + name = message.from_user.first_name + cmd = message.command[0].lower() + + text1 = await edit_or_reply(message, "Uhmm... Something is wrong here!!") + await asyncio.sleep(2) + await text1.delete() + + stcr1 = await client.send_sticker(message.chat.id, "CAADAQADRwADnjOcH98isYD5RJTwAg") + text2 = await message.reply( + f"{message.from_user.first_name}: I have to call discussion" + ) + await asyncio.sleep(3) + await stcr1.delete() + await text2.delete() + + stcr2 = await client.send_sticker(message.chat.id, "CAADAQADRgADnjOcH9odHIXtfgmvAg") + text3 = await message.reply( + f"{message.from_user.first_name}: We have to eject the imposter or will lose" + ) + await asyncio.sleep(3) + await stcr2.delete() + await text3.delete() + + stcr3 = await client.send_sticker(message.chat.id, "CAADAQADOwADnjOcH77v3Ap51R7gAg") + text4 = await message.reply("Others: Where???") + await asyncio.sleep(2) + await text4.edit("Others: Who??") + await asyncio.sleep(2) + await text4.edit( + f"{message.from_user.first_name}: Its {name}, I saw {name} using vent" + ) + await asyncio.sleep(3) + await text4.edit(f"Others: Okay.. Vote {name}") + await asyncio.sleep(2) + await stcr3.delete() + await text4.delete() + + stcr4 = await client.send_sticker(message.chat.id, "CAADAQADLwADnjOcH-wxu-ehy6NRAg") + event = await message.reply(f"{name} is ejected.......") + + # Ejection animation + for _ in range(9): + await asyncio.sleep(0.5) + curr_pos = _ + 1 + spaces_before = "ㅤ" * curr_pos + await event.edit(f"{spaces_before}ඞ{'ㅤ' * (9 - curr_pos)}") + + await asyncio.sleep(0.5) + await event.edit("ㅤㅤㅤㅤㅤㅤㅤㅤㅤ") + await asyncio.sleep(0.2) + await stcr4.delete() + + if cmd == "imp": + text = f".    。    •   ゚  。   .\n .      .     。   。 .  \n\n .    。   ඞ 。 .    •     •\n\n ゚{name} was an Imposter. 。 .     。 . 。 . \n  . 。   . \n ' 0 Impostor remains   。 .   . 。 . 。   . 。   . . . , 。\n  ゚   .  . ,   。   .   . 。" + sticker_id = "CAADAQADLQADnjOcH39IqwyR6Q_0Ag" + else: + text = f".    。    •   ゚  。   .\n .      .     。   。 .  \n\n .    。   ඞ 。 .    •     •\n\n ゚{name} was not an Imposter. 。 .     。 . 。 . \n  . 。   . \n ' 1 Impostor remains   。 .   . 。 . 。   . 。   . . . , 。\n  ゚   .  . ,   。   .   . 。" + sticker_id = "CAADAQADQAADnjOcH-WOkB8DEctJAg" + + await event.edit(text) + await asyncio.sleep(4) + await event.delete() + await client.send_sticker(message.chat.id, sticker_id) + + +modules_help["amongus"] = { + "amongus": "Create Among Us themed sticker [text/reply] [-c1 to -c12 for colors]", + "imposter": "Create Among Us imposter image [username/reply]", + "imp": "Create Among Us ejection animation (imposter)", + "impn": "Create Among Us ejection animation (not imposter)", +} diff --git a/modules/animations.py b/modules/animations.py new file mode 100644 index 0000000..8e80c2d --- /dev/null +++ b/modules/animations.py @@ -0,0 +1,478 @@ +import asyncio +from collections import deque +from pyrogram import Client, filters, enums +from pyrogram.types import Message +from utils.misc import modules_help, prefix + +mention = ( + f"Moon" +) + + +@Client.on_message(filters.command("stupid", prefix) & filters.me) +async def stupid(_, message: Message): + animation_interval = 0.5 + animation_ttl = range(0, 14) + await message.edit_text("stupid boy") + animation_chars = [ + "YOUR BRAIN ➡️ 🧠\n\n🧠 (^_^)🗑", + "YOUR BRAIN ➡️ 🧠\n\n🧠 (^_^) 🗑", + "YOUR BRAIN ➡️ 🧠\n\n🧠 (^_^) 🗑", + "YOUR BRAIN ➡️ 🧠\n\n🧠 (^_^) 🗑", + "YOUR BRAIN ➡️ 🧠\n\n🧠 (^_^) 🗑", + "YOUR BRAIN ➡️ 🧠\n\n🧠< (^_^ <) 🗑", + "YOUR BRAIN ➡️ 🧠\n\n(> ^_^)>🧠 🗑", + "YOUR BRAIN ➡️ 🧠\n\n (> ^_^)>🧠 🗑", + "YOUR BRAIN ➡️ 🧠\n\n (> ^_^)>🧠 🗑", + "YOUR BRAIN ➡️ 🧠\n\n (> ^_^)>🧠 🗑", + "YOUR BRAIN ➡️ 🧠\n\n (> ^_^)>🧠 🗑", + "YOUR BRAIN ➡️ 🧠\n\n (> ^_^)>🧠🗑", + "YOUR BRAIN ➡️ 🧠\n\n (> ^_^)>🗑", + "YOUR BRAIN ➡️ 🧠\n\n < (^_^ <)🗑", + ] + + for i in animation_ttl: + + await asyncio.sleep(animation_interval) + await message.edit_text( + animation_chars[i % 14], parse_mode=enums.ParseMode.HTML + ) + + +@Client.on_message(filters.command("bombs", prefix) & filters.me) +async def bombs(_, message: Message): + + await message.edit_text("▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n") + await asyncio.sleep(0.5) + await message.edit_text("💣💣💣💣 \n▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n") + await asyncio.sleep(0.5) + await message.edit_text("▪️▪️▪️▪️ \n💣💣💣💣 \n▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n") + await asyncio.sleep(0.5) + await message.edit_text("▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n💣💣💣💣 \n▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n") + await asyncio.sleep(0.5) + await message.edit_text("▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n💣💣💣💣 \n▪️▪️▪️▪️ \n") + await asyncio.sleep(0.5) + await message.edit_text("▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n💣💣💣💣 \n") + await asyncio.sleep(1) + await message.edit_text("▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n💥💥💥💥 \n") + await asyncio.sleep(0.5) + await message.edit_text("▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n💥💥💥💥 \n💥💥💥💥 \n") + await asyncio.sleep(0.5) + await message.edit_text("▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n😵😵😵😵 \n") + await asyncio.sleep(0.5) + await message.edit_text("RIP PLOXXX......") + await asyncio.sleep(2) + + +@Client.on_message(filters.command("call", prefix) & filters.me) +async def cell(_, message: Message): + + animation_interval = 3 + animation_ttl = range(0, 18) + await message.edit_text("Calling Pavel Durov (ceo of telegram)......") + animation_chars = [ + "Connecting To Telegram Headquarters...", + "Call Connected.", + "Telegram: Hello This is Telegram HQ. Who is this?", + f"Me: Yo this is Moon ,Please Connect me to my lil bro,Pavel Durov ", + "User Authorised.", + "Calling Shivamani At +916969696969", + "Private Call Connected...", + "Me: Hello Sir, Please Ban This Telegram Account.", + "Shivamani : May I Know Who Is This?", + f"Me: Yo Brah, I Am Moon", + "Shivamani : OMG!!! Long time no see, Wassup cat...\nI'll Make Sure That Guy Account Will Get Blocked Within 24Hrs.", + "Me: Thanks, See You Later Brah.", + "Shivamani : Please Don't Thank Brah, Telegram Is Our's. Just Gimme A Call When You Become Free.", + "Me: Is There Any Issue/Emergency???", + "Shivamani : Yes Sur, There Is A Bug In Telegram v69.6.9.\nI Am Not Able To Fix It. If Possible, Please Help Fix The Bug.", + "Me: Send Me The App On My Telegram Account, I Will Fix The Bug & Send You.", + "Shivamani : Sure Sur \nTC Bye Bye :)", + "Private Call Disconnected.", + ] + for i in animation_ttl: + await asyncio.sleep(animation_interval) + await message.edit_text(animation_chars[i % 18]) + + +@Client.on_message(filters.command("wtf", prefix) & filters.me) +async def wtf(_, message: Message): + + animation_interval = 0.8 + animation_ttl = range(0, 5) + await message.edit_text("wtf") + animation_chars = [ + "What", + "What The", + "What The F", + "What The F Brah", + "What The F Brah\nhttps://telegra.ph//file/f3b760e4a99340d331f9b.jpg", + ] + for i in animation_ttl: + + await asyncio.sleep(animation_interval) + await message.edit_text(animation_chars[i % 5], parse_mode=enums.ParseMode.HTML) + + +@Client.on_message(filters.command("ding", prefix) & filters.me) +async def ding(_, message: Message): + animation_interval = 0.3 + animation_ttl = range(0, 30) + animation_chars = [ + "🔴⬛⬛⬜⬜\n⬜⬜⬜⬜⬜\n⬜⬜⬜⬜⬜", + "⬜⬜⬛⬜⬜\n⬜⬛⬜⬜⬜\n🔴⬜⬜⬜⬜", + "⬜⬜⬛⬜⬜\n⬜⬜⬛⬜⬜\n⬜⬜🔴⬜⬜", + "⬜⬜⬛⬜⬜\n⬜⬜⬜⬛⬜\n⬜⬜⬜⬜🔴", + "⬜⬜⬛⬛🔴\n⬜⬜⬜⬜⬜\n⬜⬜⬜⬜⬜", + "⬜⬜⬛⬜⬜\n⬜⬜⬜⬛⬜\n⬜⬜⬜⬜🔴", + "⬜⬜⬛⬜⬜\n⬜⬜⬛⬜⬜\n⬜⬜🔴⬜⬜", + "⬜⬜⬛⬜⬜\n⬜⬛⬜⬜⬜\n🔴⬜⬜⬜⬜", + "🔴⬛⬛⬜⬜\n⬜⬜⬜⬜⬜\n⬜⬜⬜⬜⬜", + "⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜\n⬜ Moon IS BEST ⬜\n⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜", + ] + + await message.edit_text("ding..dong..ding..dong ...") + await asyncio.sleep(4) + for i in animation_ttl: + await asyncio.sleep(animation_interval) + await message.edit_text( + animation_chars[i % 10], parse_mode=enums.ParseMode.HTML + ) + + +@Client.on_message(filters.command("hypo", prefix) & filters.me) +async def hypo(_, message: Message): + animation_interval = 0.3 + animation_ttl = range(0, 15) + await message.edit_text("hypo....") + animation_chars = [ + "⬜⬜⬜⬜⬜⬜⬜\n⬜⬜⬜⬜⬜⬜⬜\n⬜⬜⬜⬜⬜⬜⬜\n⬜⬜⬜⬜⬜⬜⬜\n⬜⬜⬜⬜⬜⬜⬜\n⬜⬜⬜⬜⬜⬜⬜\n⬜⬜⬜⬜⬜⬜⬜", + "⬜⬜⬜⬜⬜⬜⬜\n⬜⬜⬜⬜⬜⬜⬜\n⬜⬜⬜⬜⬜⬜⬜\n⬜⬜⬜⬛⬜⬜⬜\n⬜⬜⬜⬜⬜⬜⬜\n⬜⬜⬜⬜⬜⬜⬜\n⬜⬜⬜⬜⬜⬜⬜", + "⬜⬜⬜⬜⬜⬜⬜\n⬜⬜⬜⬜⬜⬜⬜\n⬜⬜⬛⬛⬛⬜⬜\n⬜⬜⬛⬜⬛⬜⬜\n⬜⬜⬛⬛⬛⬜⬜\n⬜⬜⬜⬜⬜⬜⬜\n⬜⬜⬜⬜⬜⬜⬜", + "⬜⬜⬜⬜⬜⬜⬜\n⬜⬛⬛⬛⬛⬛⬜\n⬜⬛⬜⬜⬜⬛⬜\n⬜⬛⬜⬜⬜⬛⬜\n⬜⬛⬜⬜⬜⬛⬜\n⬜⬛⬛⬛⬛⬛⬜\n⬜⬜⬜⬜⬜⬜⬜", + "⬛⬛⬛⬛⬛⬛⬛\n⬛⬜⬜⬜⬜⬜⬛\n⬛⬜⬜⬜⬜⬜⬛\n⬛⬜⬜⬜⬜⬜⬛\n⬛⬜⬜⬜⬜⬜⬛\n⬛⬜⬜⬜⬜⬜⬛\n⬛⬛⬛⬛⬛⬛⬛", + "⬛⬜⬛⬜⬛⬜⬛⬜\n⬜⬛⬜⬛⬜⬛⬜⬛\n⬛⬜⬛⬜⬛⬜⬛⬜\n⬜⬛⬜⬛⬜⬛⬜⬛\n⬛⬜⬛⬜⬛⬜⬛⬜\n⬜⬛⬜⬛⬜⬛⬜⬛\n⬛⬜⬛⬜⬛⬜⬛⬜\n⬜⬛⬜⬛⬜⬛⬜⬛", + "⬜⬛⬜⬛⬜⬛⬜⬛\n⬛⬜⬛⬜⬛⬜⬛⬜\n⬜⬛⬜⬛⬜⬛⬜⬛\n⬛⬜⬛⬜⬛⬜⬛⬜\n⬜⬛⬜⬛⬜⬛⬜⬛\n⬛⬜⬛⬜⬛⬜⬛⬜\n⬜⬛⬜⬛⬜⬛⬜⬛\n⬛⬜⬛⬜⬛⬜⬛⬜", + "⬜⬜⬜⬜⬜⬜⬜\n⬜⬛⬛⬛⬛⬛⬜\n⬜⬛⬜⬜⬜⬛⬜\n⬜⬛⬜⬛⬜⬛⬜\n⬜⬛⬜⬜⬜⬛⬜\n⬜⬛⬛⬛⬛⬛⬜\n⬜⬜⬜⬜⬜⬜⬜", + "⬛⬛⬛⬛⬛⬛⬛\n⬛⬜⬜⬜⬜⬜⬛\n⬛⬜⬛⬛⬛⬜⬛\n⬛⬜⬛⬜⬛⬜⬛\n⬛⬜⬛⬛⬛⬜⬛\n⬛⬜⬜⬜⬜⬜⬛\n⬛⬛⬛⬛⬛⬛⬛", + "⬜⬜⬜⬜⬜⬜⬜\n⬜⬛⬛⬛⬛⬛⬜\n⬜⬛⬜⬜⬜⬛⬜\n⬜⬛⬜⬛⬜⬛⬜\n⬜⬛⬜⬜⬜⬛⬜\n⬜⬛⬛⬛⬛⬛⬜\n⬜⬜⬜⬜⬜⬜⬜", + "⬛⬛⬛⬛⬛⬛⬛\n⬛⬜⬜⬜⬜⬜⬛\n⬛⬜⬛⬛⬛⬜⬛\n⬛⬜⬛⬜⬛⬜⬛\n⬛⬜⬛⬛⬛⬜⬛\n⬛⬜⬜⬜⬜⬜⬛\n⬛⬛⬛⬛⬛⬛⬛", + "⬜⬜⬜⬜⬜⬜⬜\n⬜⬛⬛⬛⬛⬛⬜\n⬜⬛⬜⬜⬜⬛⬜\n⬜⬛⬜⬛⬜⬛⬜\n⬜⬛⬜⬜⬜⬛⬜\n⬜⬛⬛⬛⬛⬛⬜\n⬜⬜⬜⬜⬜⬜⬜", + "⬛⬛⬛⬛⬛\n⬛⬜⬜⬜⬛\n⬛⬜⬛⬜⬛\n⬛⬜⬜⬜⬛\n⬛⬛⬛⬛⬛", + "⬜⬜⬜\n⬜⬛⬜\n⬜⬜⬜", + "[👉🔴👈])", + ] + for i in animation_ttl: + await asyncio.sleep(animation_interval) + await message.edit_text(animation_chars[i % 15]) + + +@Client.on_message(filters.command("gangster", prefix) & filters.me) +async def gangster(_, message: Message): + await message.edit_text("EVERyBOdy") + await asyncio.sleep(0.3) + await message.edit_text("iZ") + await asyncio.sleep(0.2) + await message.edit_text("GangSTur") + await asyncio.sleep(0.5) + await message.edit_text("UNtIL ") + await asyncio.sleep(0.2) + await message.edit_text("I") + await asyncio.sleep(0.3) + await message.edit_text("ArRivE") + await asyncio.sleep(0.3) + await message.edit_text("🔥🔥🔥") + await asyncio.sleep(0.3) + await message.edit_text("EVERyBOdy iZ GangSTur UNtIL I ArRivE 🔥🔥🔥") + + +@Client.on_message(filters.command("charge", prefix) & filters.me) +async def timer_blankx(_, message: Message): + txt = ( + message.text[10:] + + "\n\nTesla Wireless Charging (beta) Started...\nDevice Detected: Apple iPad 13\nBattery Percentage: " + ) + j = 10 + k = j + for j in range(j): + await message.edit_text(txt + str(k), parse_mode=enums.ParseMode.HTML) + k = k + 10 + await asyncio.sleep(1) + await asyncio.sleep(1) + await message.edit_text( + "Tesla Wireless Charging (beta) Completed...\nDevice Detected: Apple iPad 13 (Space Grey Varient)\nBattery Percentage: [100%](https://telegra.ph/file/a45aa7450c8eefed599d9.mp4) ", + link_preview=True, + parse_mode=enums.ParseMode.HTML, + ) + + +@Client.on_message(filters.command("kill", prefix) & filters.me) +async def kill(_, message: Message): + animation_interval = 0.3 + animation_ttl = range(0, 103) + animation_chars = [ + "Fiiiiire", + "( ・ิω・ิ)︻デ═一-->", + "---->____________⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠", + "------>__________⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠", + "-------->⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠_________", + "---------->⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠_______", + "------------>⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠_____", + "-------------->⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠____", + "------------------>", + "------>;(^。^)ノ", + "( ̄ー ̄) DED", + "Target killed successfully (°̥̥̥̥̥̥̥̥•̀.̫•́°̥̥̥̥̥̥̥)", + ] + + await message.edit_text("You're goonnaa diieeeee!") + await asyncio.sleep(3) + for i in animation_ttl: + await asyncio.sleep(animation_interval) + await message.edit_text( + animation_chars[i % 103], parse_mode=enums.ParseMode.HTML + ) + + +@Client.on_message(filters.command("earth", prefix) & filters.me) +async def earth(_, message: Message): + deq = deque(list("🌏🌍🌎🌎🌍🌏🌍🌎")) + for _ in range(48): + await asyncio.sleep(0.1) + await message.edit_text("".join(deq)) + deq.rotate(1) + + +@Client.on_message(filters.command("think", prefix) & filters.me) +async def think(_, message: Message): + deq = deque(list("🤔🧐🤔🧐🤔🧐")) + for _ in range(48): + await asyncio.sleep(0.1) + await message.edit_text("".join(deq)) + deq.rotate(1) + + +@Client.on_message(filters.command("lmao", prefix) & filters.me) +async def lmao(_, message: Message): + deq = deque(list("😂🤣😂🤣😂🤣")) + for _ in range(48): + await asyncio.sleep(0.1) + await message.edit_text("".join(deq)) + deq.rotate(1) + + +@Client.on_message(filters.command("clock", prefix) & filters.me) +async def clock(_, message: Message): + deq = deque(list("🕙🕘🕗🕖🕕🕔🕓🕒🕑🕐🕛")) + for _ in range(48): + await asyncio.sleep(0.1) + await message.edit_text("".join(deq)) + deq.rotate(1) + + +@Client.on_message(filters.command("heart", prefix) & filters.me) +async def heart(_, message: Message): + deq = deque(list("❤️🧡💛💚💙💜🖤")) + for _ in range(48): + await asyncio.sleep(0.1) + await message.edit_text("".join(deq)) + deq.rotate(1) + + +@Client.on_message(filters.command("gym", prefix) & filters.me) +async def gym(_, message: Message): + deq = deque(list("🏃‍🏋‍🤸‍🏃‍🏋‍🤸‍🏃‍🏋‍🤸‍")) + for _ in range(48): + await asyncio.sleep(0.1) + await message.edit_text("".join(deq)) + deq.rotate(1) + + +@Client.on_message(filters.command("moon", prefix) & filters.me) +async def moon(_, message: Message): + deq = deque(list("🌗🌘🌑🌒🌓🌔🌕🌖‍")) + for _ in range(48): + await asyncio.sleep(0.1) + await message.edit_text("".join(deq)) + deq.rotate(1) + + +@Client.on_message(filters.command("stars", prefix) & filters.me) +async def stars(_, message: Message): + deq = deque(list("🦋✨🦋✨🦋✨🦋✨‍")) + for _ in range(48): + await asyncio.sleep(0.1) + await message.edit_text("".join(deq)) + deq.rotate(1) + + +@Client.on_message(filters.command("boxs", prefix) & filters.me) +async def boxs(_, message: Message): + deq = deque(list("🟥🟧🟨🟩🟦🟪🟫⬛⬜")) + for _ in range(48): + await asyncio.sleep(0.1) + await message.edit_text("".join(deq)) + deq.rotate(1) + + +@Client.on_message(filters.command("rain", prefix) & filters.me) +async def rain(_, message: Message): + deq = deque(list("🌬☁️🌩🌨🌧🌦🌥⛅🌤")) + for _ in range(48): + await asyncio.sleep(0.1) + await message.edit_text("".join(deq)) + deq.rotate(1) + + +@Client.on_message(filters.command("bird", prefix) & filters.me) +async def bird(_, message: Message): + deq = deque(list("𓅰𓅬𓅭𓅮𓅯")) + for _ in range(48): + await asyncio.sleep(0.1) + await message.edit_text("".join(deq)) + deq.rotate(1) + + +@Client.on_message(filters.command("fish", prefix) & filters.me) +async def fish(_, message: Message): + deq = deque(list("𓆝𓆟𓆞𓆝𓆟")) + for _ in range(48): + await asyncio.sleep(0.1) + await message.edit_text("".join(deq)) + deq.rotate(1) + + +@Client.on_message(filters.command("goat", prefix) & filters.me) +async def goat(_, message: Message): + deq = deque(list("𓃖𓃗𓃘𓃙𓃚𓃛𓃜")) + for _ in range(48): + await asyncio.sleep(0.1) + await message.edit_text("".join(deq)) + deq.rotate(1) + + +@Client.on_message(filters.command("smoon", prefix) & filters.me) +async def smoon(_, message: Message): + animation_interval = 0.2 + animation_ttl = range(101) + animation_chars = [ + "🌗🌗🌗🌗🌗\n🌓🌓🌓🌓🌓\n🌗🌗🌗🌗🌗\n🌓🌓🌓🌓🌓\n🌗🌗🌗🌗🌗", + "🌘🌘🌘🌘🌘\n🌔🌔🌔🌔🌔\n🌘🌘🌘🌘🌘\n🌔🌔🌔🌔🌔\n🌘🌘🌘🌘🌘", + "🌑🌑🌑🌑🌑\n🌕🌕🌕🌕🌕\n🌑🌑🌑🌑🌑\n🌕🌕🌕🌕🌕\n🌑🌑🌑🌑🌑", + "🌒🌒🌒🌒🌒\n🌖🌖🌖🌖🌖\n🌒🌒🌒🌒🌒\n🌖🌖🌖🌖🌖\n🌒🌒🌒🌒🌒", + "🌓🌓🌓🌓🌓\n🌗🌗🌗🌗🌗\n🌓🌓🌓🌓🌓\n🌗🌗🌗🌗🌗\n🌓🌓🌓🌓🌓", + "🌔🌔🌔🌔🌔\n🌘🌘🌘🌘🌘\n🌔🌔🌔🌔🌔\n🌘🌘🌘🌘🌘\n🌔🌔🌔🌔🌔", + "🌕🌕🌕🌕🌕\n🌑🌑🌑🌑🌑\n🌕🌕🌕🌕🌕\n🌑🌑🌑🌑🌑\n🌕🌕🌕🌕🌕", + "🌖🌖🌖🌖🌖\n🌒🌒🌒🌒🌒\n🌖🌖🌖🌖🌖\n🌒🌒🌒🌒🌒\n🌖🌖🌖🌖🌖", + ] + + await message.edit_text("smoon...") + await asyncio.sleep(3) + for i in animation_ttl: + await asyncio.sleep(animation_interval) + await message.edit_text(animation_chars[i % 8]) + + +@Client.on_message(filters.command("deploy", prefix) & filters.me) +async def deploy(_, message: Message): + animation_interval = 3 + animation_ttl = range(101) + animation_chars = [ + "Heroku Connecting To Latest Github Build ", + f"Build started by user {mention}", + f"Deploy 535a74f0 by user {mention}", + "Restarting Heroku Server...", + "State changed from up to starting", + "Stopping all processes with SIGTERM", + "Process exited with status 143", + "Starting process with command python3 -m userbot", + "State changed from starting to up", + "INFO:Userbot:Logged in as 557667062", + "INFO:Userbot:Successfully loaded all plugins", + "Build Succeeded", + ] + + await message.edit_text( + "Deploying...", parse_mode=enums.ParseMode.HTML + ) + await asyncio.sleep(3) + for i in animation_ttl: + await asyncio.sleep(animation_interval) + await message.edit_text( + animation_chars[i % 12], parse_mode=enums.ParseMode.HTML + ) + + +@Client.on_message(filters.command("tmoon", prefix) & filters.me) +async def tmoon(_, message: Message): + animation_interval = 0.2 + animation_ttl = range(96) + animation_chars = [ + "🌗", + "🌘", + "🌑", + "🌒", + "🌓", + "🌔", + "🌕", + "🌖", + "🌗", + "🌘", + "🌑", + "🌒", + "🌓", + "🌔", + "🌕", + "🌖", + "🌗", + "🌘", + "🌑", + "🌒", + "🌓", + "🌔", + "🌕", + "🌖", + "🌗", + "🌘", + "🌑", + "🌒", + "🌓", + "🌔", + "🌕", + "🌖", + ] + + await message.edit_text("tmoon...") + await asyncio.sleep(3) + for i in animation_ttl: + await asyncio.sleep(animation_interval) + await message.edit_text(animation_chars[i % 32]) + + +modules_help["animations"] = { + "stupid": " stupid animation", + "gangster": "YO! Gangster animation", + "hypo": "checkout yourself ~_+", + "charge": "Apple iPad charging animation", + "ding": "ding.dong.ding animation", + "wtf": "wtf animation must use if you want tell wtf", + "call": "call to durov", + "bombs": "bombing animation", + "kill": "fireee animation = target killed ~_- ", + "deploy": "Fake heroku deploy animation", + "gym": "Fun animation try yourself to know more", + "boxs": "Fun animation try yourself to know more", + "stars": "Fun animation try yourself to know more", + "goat": "Fun animation try yourself to know more", + "bird": "Fun animation try yourself to know more", + "rain": "Fun animation try yourself to know more", + "fish": "Fun animation try yourself to know more", + "heart": "Fun animation try yourself to know more", + "clock": "Fun animation try yourself to know more", + "lmao": "Fun animation try yourself to know more", + "think": "Fun animation try yourself to know more", + "earth": "Fun animation try yourself to know more", + "moon": "Fun animation try yourself to know more", + "smoon": "Fun animation try yourself to know more", + "tmoon": "Fun animation try yourself to know more", +} diff --git a/modules/anime/anilist.py b/modules/anime/anilist.py new file mode 100644 index 0000000..be6d6a6 --- /dev/null +++ b/modules/anime/anilist.py @@ -0,0 +1,243 @@ +import os +import requests +import aiofiles + +from pyrogram import Client, filters, enums +from pyrogram.types import Message, InputMediaPhoto +from pyrogram.errors import MediaCaptionTooLong + +from utils.misc import prefix, modules_help +from utils.scripts import format_exc + +url = "https://api.safone.co" + +headers = { + "Accept-Language": "en-US,en;q=0.9", + "Connection": "keep-alive", + "DNT": "1", + "Referer": "https://api.safone.co/docs", + "Sec-Fetch-Dest": "empty", + "Sec-Fetch-Mode": "cors", + "Sec-Fetch-Site": "same-origin", + "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36", + "accept": "application/json", + "sec-ch-ua": '"Chromium";v="124", "Google Chrome";v="124", "Not-A.Brand";v="99"', + "sec-ch-ua-mobile": "?0", + "sec-ch-ua-platform": '"Linux"', +} + + +@Client.on_message(filters.command("anime_search", prefix) & filters.me) +async def anime_search(client: Client, message: Message): + try: + chat_id = message.chat.id + await message.edit_text("Processing...") + if len(message.command) > 1: + query = message.text.split(maxsplit=1)[1] + else: + message.edit_text( + "What should i search? You didn't provided me with any value to search" + ) + + response = requests.get( + url=f"{url}/anime/search?query={query}", headers=headers, timeout=5 + ) + if response.status_code != 200: + await message.edit_text("Something went wrong") + return + + result = response.json() + + averageScore = result["averageScore"] + try: + coverImage_url = result["imageUrl"] + coverImage = requests.get(url=coverImage_url).content + async with aiofiles.open("coverImage.jpg", mode="wb") as f: + await f.write(coverImage) + + except Exception: + coverImage = None + + title = result["title"]["english"] + trailer = result["trailer"]["id"] + description = result["description"] + episodes = result["episodes"] + genres = ", ".join(result["genres"]) + isAdult = result["isAdult"] + status = result["status"] + studios = ", ".join(result["studios"]) + + await message.delete() + await client.send_media_group( + chat_id, + [ + InputMediaPhoto( + "coverImage.jpg", + caption=f"Title: {title}\nAverage Score: {averageScore}\nStatus: {status}\nGenres: {genres}\nEpisodes: {episodes}\nIs Adult: {isAdult}\nStudios: {studios}\nDescription: {description}\nTrailer: Click Here", + ) + ], + ) + + except MediaCaptionTooLong: + description = description[:850] + await message.delete() + await client.send_media_group( + chat_id, + [ + InputMediaPhoto( + "coverImage.jpg", + caption=f"Title: {title}\nAverage Score: {averageScore}\nStatus: {status}\nGenres: {genres}\nEpisodes: {episodes}\nIs Adult: {isAdult}\nStudios: {studios}\nDescription: {description}\nTrailer: Click Here", + ) + ], + ) + except Exception as e: + await message.edit_text(format_exc(e)) + finally: + if os.path.exists("coverImage.jpg"): + os.remove("coverImage.jpg") + + +@Client.on_message(filters.command("manga_search", prefix) & filters.me) +async def manga_search(client: Client, message: Message): + try: + chat_id = message.chat.id + await message.edit_text("Processing...") + if len(message.command) > 1: + query = message.text.split(maxsplit=1)[1] + else: + message.edit_text( + "What should i search? You didn't provided me with any value to search" + ) + + response = requests.get( + url=f"{url}/anime/manga?query={query}", headers=headers, timeout=5 + ) + if response.status_code != 200: + await message.edit_text("Something went wrong") + return + + result = response.json() + + averageScore = result["averageScore"] + try: + coverImage_url = result["imageUrl"] + coverImage = requests.get(url=coverImage_url).content + async with aiofiles.open("coverImage.jpg", mode="wb") as f: + await f.write(coverImage) + + except Exception: + coverImage = None + + title = result["title"]["english"] + trailer = result["trailer"]["id"] + description = result["description"] + chapters = result["chapters"] + genres = ", ".join(result["genres"]) + isAdult = result["isAdult"] + status = result["status"] + studios = ", ".join(result["studios"]) + + await message.delete() + await client.send_media_group( + chat_id, + [ + InputMediaPhoto( + "coverImage.jpg", + caption=f"Title: {title}\nAverage Score: {averageScore}\nStatus: {status}\nGenres: {genres}\nChapters: {chapters}\nIs Adult: {isAdult}\nStudios: {studios}\nDescription: {description}\nTrailer: Click Here", + ) + ], + ) + + except MediaCaptionTooLong: + description = description[:850] + await message.delete() + await client.send_media_group( + chat_id, + [ + InputMediaPhoto( + "coverImage.jpg", + caption=f"Title: {title}\nAverage Score: {averageScore}\nStatus: {status}\nGenres: {genres}\nChapters: {chapters}\nIs Adult: {isAdult}\nStudios: {studios}\nDescription: {description}\nTrailer: Click Here", + ) + ], + ) + except Exception as e: + await message.edit_text(format_exc(e)) + finally: + if os.path.exists("coverImage.jpg"): + os.remove("coverImage.jpg") + + +@Client.on_message(filters.command("character", prefix) & filters.me) +async def character(client: Client, message: Message): + try: + chat_id = message.chat.id + await message.edit_text("Processing...") + if len(message.command) > 1: + query = message.text.split(maxsplit=1)[1] + else: + message.edit_text( + "What should i search? You didn't provided me with any value to search" + ) + + response = requests.get( + url=f"{url}/anime/character?query={query}", headers=headers, timeout=5 + ) + if response.status_code != 200: + await message.edit_text("Something went wrong") + return + + result = response.json() + + try: + coverImage_url = result["image"]["large"] + coverImage = requests.get(url=coverImage_url).content + async with aiofiles.open("coverImage.jpg", mode="wb") as f: + await f.write(coverImage) + + except Exception: + coverImage = None + + age = result["age"] + description = result["description"] + height = result["height"] + name = result["name"]["full"] + native_name = result["name"]["native"] + read_more = result["siteUrl"] + + await message.delete() + await client.send_media_group( + chat_id, + [ + InputMediaPhoto( + "coverImage.jpg", + caption=f"**Name:** `{name}`\n**Native Name:** `{native_name}`\n**Age:** `{age}`\n**Height:** {height}\n**Description:** `{description}`[read more...]({read_more})", + parse_mode=enums.ParseMode.MARKDOWN, + ) + ], + ) + + except MediaCaptionTooLong: + description = description[:850] + await message.delete() + await client.send_media_group( + chat_id, + [ + InputMediaPhoto( + "coverImage.jpg", + caption=f"**Name:** `{name}`\n**Native Name:** `{native_name}`\n**Age:** `{age}`\n**Height:** {height}\n**Description:** `{description}`[read more...]({read_more})", + parse_mode=enums.ParseMode.MARKDOWN, + ) + ], + ) + except Exception as e: + await message.edit_text(format_exc(e)) + finally: + if os.path.exists("coverImage.jpg"): + os.remove("coverImage.jpg") + + +modules_help["anilist"] = { + "anime_search": "Search for anime on Anilist", + "manga_search": "Search for manga on Anilist", + "character": "Search for character on Anilist", +} diff --git a/modules/anime/anime.py b/modules/anime/anime.py new file mode 100644 index 0000000..eff5f64 --- /dev/null +++ b/modules/anime/anime.py @@ -0,0 +1,69 @@ +from pyrogram import Client, filters, enums +from pyrogram.types import Message + +# noinspection PyUnresolvedReferences +from utils.misc import modules_help, prefix +from utils.scripts import format_exc +from aiohttp import ClientSession +from io import BytesIO + +session = ClientSession() + + +class Post: + def __init__(self, source: dict, session: ClientSession): + self._json = source + self.session = session + + @property + async def image(self): + return ( + self.file_url + if self.file_url + else ( + self.large_file_url + if self.large_file_url + else ( + self.source + if self.source and "pximg" not in self.source + else await self.pximg if self.source else None + ) + ) + ) + + @property + async def pximg(self): + async with self.session.get(self.source) as response: + return BytesIO(await response.read()) + + def __getattr__(self, item): + return self._json.get(item) + + +async def random(): + async with session.get( + url="https://danbooru.donmai.us/posts/random.json" + ) as response: + return Post(await response.json(encoding="utf-8"), session) + + +@Client.on_message(filters.command(["arnd", "arandom"], prefix) & filters.me) +async def anime_handler(client: Client, message: Message): + try: + await message.edit("Searching art", parse_mode=enums.ParseMode.HTML) + ra = await random() + img = await ra.image + await message.reply_photo( + photo=img, + caption=f'{ra.tag_string_general if ra.tag_string_general else "Untitled"}', + parse_mode=enums.ParseMode.HTML, + ) + return await message.delete() + except Exception as e: + await message.edit(format_exc(e), parse_mode=enums.ParseMode.HTML) + + +modules_help["anime"] = { + "arnd": "Random anime art (May get caught 18+)", + "arandom": "Random anime art (May get caught 18+)", +} diff --git a/modules/anime/neko.py b/modules/anime/neko.py new file mode 100644 index 0000000..4ba8545 --- /dev/null +++ b/modules/anime/neko.py @@ -0,0 +1,72 @@ +# Moon-Userbot - telegram userbot +# Copyright (C) 2020-present Moon Userbot Organization +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import asyncio + +import requests +from pyrogram import Client, filters, enums +from pyrogram.types import Message + +from utils.misc import modules_help, prefix +from utils.scripts import format_exc + + +def get_neko_media(query): + return requests.get(f"https://nekos.life/api/v2/img/{query}").json()["url"] + + +@Client.on_message(filters.command("neko", prefix) & filters.me) +async def neko(_, message: Message): + if len(message.command) == 1: + await message.edit( + "Neko type isn't provided\n" + f"You can get available neko types with {prefix}neko_types" + ) + + query = message.command[1] + await message.edit("Loading...") + try: + await message.edit(f"{get_neko_media(query)}", disable_web_page_preview=False) + except Exception as e: + await message.edit(format_exc(e)) + + +@Client.on_message(filters.command(["nekotypes", "neko_types"], prefix) & filters.me) +async def neko_types_func(_, message: Message): + neko_types = """hug kiss tickle lewd neko pat lizard 8ball cat chat fact smug woof gasm goose cuddle avatar slap gecg feed fox_girl meow wallpaper spank waifu ngif name owoify spoiler why""" + await message.edit(" ".join(f"{n}" for n in neko_types.split())) + + +@Client.on_message(filters.command(["nekospam", "neko_spam"], prefix) & filters.me) +async def neko_spam(client: Client, message: Message): + query = message.command[1] + amount = int(message.command[2]) + + await message.delete() + + for _ in range(amount): + if message.reply_to_message: + await message.reply_to_message.reply(get_neko_media(query)) + else: + await client.send_message(message.chat.id, get_neko_media(query)) + await asyncio.sleep(0.1) + + +modules_help["neko"] = { + "neko [type]*": "Get neko media", + "neko_types": "Available neko types", + "neko_spam [type]* [amount]*": "Start spam with neko media", +} diff --git a/modules/aniquotes.py b/modules/aniquotes.py new file mode 100644 index 0000000..867c88f --- /dev/null +++ b/modules/aniquotes.py @@ -0,0 +1,43 @@ +from random import choice, randint + +from pyrogram import Client, filters, enums +from pyrogram.types import Message + +from utils.misc import modules_help, prefix +from utils.scripts import format_exc + + +@Client.on_message(filters.command(["aniq", "aq"], prefix) & filters.me) +async def aniquotes_handler(client: Client, message: Message): + if message.reply_to_message and message.reply_to_message.text: + query = message.reply_to_message.text[:512] + elif message.reply_to_message and message.reply_to_message.caption: + query = message.reply_to_message.caption[:512] + elif len(message.command) > 1: + query = message.text.split(maxsplit=1)[1][:512] + else: + return await message.edit( + "[💮 Aniquotes] Please enter text to create sticker.", + parse_mode=enums.ParseMode.HTML, + ) + + try: + await message.delete() + result = await client.get_inline_bot_results("@quotafbot", query) + return await message.reply_inline_bot_result( + query_id=result.query_id, + result_id=result.results[randint(1, 2)].id, + reply_to_message_id=( + message.reply_to_message.id if message.reply_to_message else None + ), + ) + except Exception as e: + return await message.reply( + f"[💮 Aniquotes]\n{format_exc(e)}", + parse_mode=enums.ParseMode.HTML, + ) + + +modules_help["aniquotes"] = { + "aq [text]": "Create animated sticker with text", +} diff --git a/modules/antipm.py b/modules/antipm.py new file mode 100644 index 0000000..9ce1b23 --- /dev/null +++ b/modules/antipm.py @@ -0,0 +1,256 @@ +# Moon-Userbot - telegram userbot +# Copyright (C) 2020-present Moon Userbot Organization +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import os + +from pyrogram import Client, filters +from pyrogram.raw import functions +from pyrogram.types import Message + +from utils.config import pm_limit +from utils.db import db +from utils.misc import modules_help, prefix + +anti_pm_enabled = filters.create( + lambda _, __, ___: db.get("core.antipm", "status", False) +) + +in_contact_list = filters.create(lambda _, __, message: message.from_user.is_contact) + +is_support = filters.create(lambda _, __, message: message.chat.is_support) + +USER_WARNINGS = {} + + +@Client.on_message( + filters.private + & ~filters.me + & ~filters.bot + & ~in_contact_list + & ~is_support + & anti_pm_enabled +) +async def anti_pm_handler(client: Client, message: Message): + user_id = message.from_user.id + ids = message.chat.id + b_f = await client.get_me() + u_n = b_f.first_name + user = await client.get_users(ids) + u_f = user.first_name + default_text = db.get("core.antipm", "antipm_msg", None) + if default_text is None: + default_text = f"""Hello, {u_f}! +This is the Assistant Of {u_n}. +My Boss is away or busy as of now, You can wait for him to respond. +Do not spam further messages else I may have to block you! + +This is an automated message by the assistant. +Currently You Have {USER_WARNINGS.get(user_id, 0)} Warnings. + """ + else: + default_text = default_text.format( + user=u_f, my_name=u_n, warns=USER_WARNINGS.get(user_id, 0) + ) + + if db.get("core.antipm", "spamrep", False): + user_info = await client.resolve_peer(ids) + await client.invoke(functions.messages.ReportSpam(peer=user_info)) + + if db.get("core.antipm", "block", False): + await client.block_user(user_id) + + if db.get("core.antipm", f"disallowusers{ids}") == user_id != db.get( + "core.antipm", f"allowusers{ids}" + ) or db.get("core.antipm", f"disallowusers{ids}") != user_id != db.get( + "core.antipm", f"allowusers{ids}" + ): + default_pic = db.get("core.antipm", "antipm_pic", None) + if default_pic and os.path.exists(default_pic): + await client.send_photo(message.chat.id, default_pic, caption=default_text) + else: + await client.send_message(message.chat.id, default_text) + + if user_id in USER_WARNINGS: + USER_WARNINGS[user_id] += 1 + else: + USER_WARNINGS[user_id] = 1 + + if USER_WARNINGS[user_id] > pm_limit: + await client.send_message( + message.chat.id, + "Ehm...! That was your Last warn, Bye Bye see you L0L", + ) + await client.block_user(user_id) + del USER_WARNINGS[user_id] + + +@Client.on_message(filters.command(["antipm", "anti_pm"], prefix) & filters.me) +async def anti_pm(_, message: Message): + if len(message.command) == 1: + if db.get("core.antipm", "status", False): + await message.edit( + "Anti-PM status: enabled\n" + f"Disable with: {prefix}antipm disable" + ) + else: + await message.edit( + "Anti-PM status: disabled\n" + f"Enable with: {prefix}antipm enable" + ) + elif message.command[1] in ["enable", "on", "1", "yes", "true"]: + db.set("core.antipm", "status", True) + await message.edit("Anti-PM enabled!") + elif message.command[1] in ["disable", "off", "0", "no", "false"]: + db.set("core.antipm", "status", False) + await message.edit("Anti-PM disabled!") + else: + await message.edit(f"Usage: {prefix}antipm [enable|disable]") + + +@Client.on_message(filters.command(["antipm_report"], prefix) & filters.me) +async def antipm_report(_, message: Message): + if len(message.command) == 1: + if db.get("core.antipm", "spamrep", False): + await message.edit( + "Spam-reporting enabled.\n" + f"Disable with: {prefix}antipm_report disable" + ) + else: + await message.edit( + "Spam-reporting disabled.\n" + f"Enable with: {prefix}antipm_report enable" + ) + elif message.command[1] in ["enable", "on", "1", "yes", "true"]: + db.set("core.antipm", "spamrep", True) + await message.edit("Spam-reporting enabled!") + elif message.command[1] in ["disable", "off", "0", "no", "false"]: + db.set("core.antipm", "spamrep", False) + await message.edit("Spam-reporting disabled!") + else: + await message.edit(f"Usage: {prefix}antipm_report [enable|disable]") + + +@Client.on_message(filters.command(["antipm_block"], prefix) & filters.me) +async def antipm_block(_, message: Message): + if len(message.command) == 1: + if db.get("core.antipm", "block", False): + await message.edit( + "Blocking users enabled.\n" + f"Disable with: {prefix}antipm_block disable" + ) + else: + await message.edit( + "Blocking users disabled.\n" + f"Enable with: {prefix}antipm_block enable" + ) + elif message.command[1] in ["enable", "on", "1", "yes", "true"]: + db.set("core.antipm", "block", True) + await message.edit("Blocking users enabled!") + elif message.command[1] in ["disable", "off", "0", "no", "false"]: + db.set("core.antipm", "block", False) + await message.edit("Blocking users disabled!") + else: + await message.edit(f"Usage: {prefix}antipm_block [enable|disable]") + + +@Client.on_message(filters.command(["a"], prefix) & filters.me) +async def add_contact(_, message: Message): + ids = message.chat.id + + db.set("core.antipm", f"allowusers{ids}", ids) + if ids in USER_WARNINGS: + del USER_WARNINGS[ids] + await message.edit("User Approved!") + + +@Client.on_message(filters.command(["d"], prefix) & filters.me) +async def del_contact(_, message: Message): + ids = message.chat.id + + db.set("core.antipm", f"disallowusers{ids}", ids) + db.remove("core.antipm", f"allowusers{ids}") + await message.edit("User DisApproved!") + + +@Client.on_message(filters.command(["setantipmmsg", "sam"], prefix) & filters.me) +async def set_antipm_msg(_, message: Message): + if not message.reply_to_message: + db.set("core.antipm", "antipm_msg", None) + await message.edit("antipm message set to default.") + return + + msg = message.reply_to_message + afk_msg = msg.text or msg.caption + + if not afk_msg: + return await message.edit( + "Reply to a text or caption message to set it as your antipm message." + ) + + if len(afk_msg) > 200: + return await message.edit( + "antipm message is too long. It should be less than 200 characters." + ) + + if "{user}" not in afk_msg: + return await message.edit( + "antipm message must contain {user} to mention the user." + ) + if "{my_name}" not in afk_msg: + return await message.edit( + "antipm message must contain {my_name} to mention your name." + ) + if "{warns}" not in afk_msg: + return await message.edit( + "antipm message must contain {warns} to mention the warns count." + ) + + old_afk_msg = db.get("core.antipm", "antipm_msg", None) + if old_afk_msg: + db.remove("core.antipm", "antipm_msg") + db.set("core.antipm", "antipm_msg", afk_msg) + await message.edit(f"antipm message set to:\n\n{afk_msg}") + + +@Client.on_message(filters.command(["setantipmpic", "sap"], prefix) & filters.me) +async def set_antipm_pic(_, message: Message): + if not message.reply_to_message or not message.reply_to_message.photo: + db.set("core.antipm", "antipm_pic", None) + await message.edit("antipm picture set to default.") + return + + await message.edit("Setting antipm picture...") + + photo = await message.reply_to_message.download("antipm_pic.jpg") + + old_antipm_pic = db.get("core.antipm", "antipm_pic", None) + if old_antipm_pic: + db.remove("core.antipm", "antipm_pic") + db.set("core.antipm", "antipm_pic", photo) + await message.edit("antipm picture set successfully.") + + +modules_help["antipm"] = { + "antipm [enable|disable]*": "Enable Pm permit", + "antipm_report [enable|disable]*": "Enable spam reporting", + "antipm_block [enable|disable]*": "Enable user blocking", + "setantipmmsg [reply to message]*": "Set antipm message. Use {user} to mention the user and {my_name} to mention your name and {warns} to mention the warns count.", + "sam [reply to message]*": "Set antipm message. Use {user} to mention the user and {my_name} to mention your name and {warns} to mention the warns count.", + "setantipmpic [reply to photo]*": "Set antipm picture.", + "sap [reply to photo]*": "Set antipm picture.", + "a": "Approve User", + "d": "DisApprove User", +} diff --git a/modules/blackbox.py b/modules/blackbox.py new file mode 100644 index 0000000..7b176a4 --- /dev/null +++ b/modules/blackbox.py @@ -0,0 +1,134 @@ +import uuid +import re + +from aiohttp import ClientSession, FormData + +from pyrogram import Client, filters + +from utils.misc import modules_help, prefix + + +def id_generator() -> str: + return str(uuid.uuid4()) + + +@Client.on_message(filters.command(["bbox", "blackbox"], prefix) & filters.me) +async def blackbox(client, message): + m = message + msg = await m.edit_text("🔍") + + if len(m.text.split()) == 1: + return await msg.edit_text( + "Type some query buddy 🐼\n" + f"{prefix}blackbox text with reply to the photo or just text" + ) + else: + try: + session = ClientSession() + prompt = m.text.split(maxsplit=1)[1] + user_id = id_generator() + image = None + + if m.reply_to_message and ( + m.reply_to_message.photo + or ( + m.reply_to_message.sticker + and not m.reply_to_message.sticker.is_video + ) + ): + file_name = f"blackbox_{m.chat.id}.jpeg" + file_path = await m.reply_to_message.download(file_name=file_name) + with open(file_path, "rb") as file: + image = file.read() + + if image: + data = FormData() + data.add_field("fileName", file_name) + data.add_field("userId", user_id) + data.add_field( + "image", image, filename=file_name, content_type="image/jpeg" + ) + api_url = "https://www.blackbox.ai/api/upload" + try: + async with session.post(api_url, data=data) as response: + response_json = await response.json() + except Exception as e: + return await msg.edit(f"❌ Error: {str(e)}") + + messages = [ + { + "role": "user", + "content": response_json["response"] + "\n#\n" + prompt, + } + ] + data = { + "messages": messages, + "user_id": user_id, + "codeModelMode": True, + "agentMode": {}, + "trendingAgentMode": {}, + } + headers = {"Content-Type": "application/json"} + url = "https://www.blackbox.ai/api/chat" + try: + async with session.post( + url, headers=headers, json=data + ) as response: + response_text = await response.text() + except Exception as e: + return await msg.edit(f"❌ Error: {str(e)}") + + cleaned_response_text = re.sub( + r"^\$?@?\$?v=undefined-rv\d+@?\$?|\$?@?\$?v=v\d+\.\d+-rv\d+@?\$?", + "", + response_text, + ) + text = cleaned_response_text.strip()[2:] + if "$~~~$" in text: + text = re.sub(r"\$~~~\$.*?\$~~~\$", "", text, flags=re.DOTALL) + rdata = {"reply": text} + + return await msg.edit_text(text=rdata["reply"]) + else: + reply = m.reply_to_message + if reply and reply.text: + prompt = f"Old conversation:\n{reply.text}\n\nQuestion:\n{prompt}" + messages = [{"role": "user", "content": prompt}] + data = { + "messages": messages, + "user_id": user_id, + "codeModelMode": True, + "agentMode": {}, + "trendingAgentMode": {}, + } + headers = {"Content-Type": "application/json"} + url = "https://www.blackbox.ai/api/chat" + try: + async with session.post( + url, headers=headers, json=data + ) as response: + response_text = await response.text() + except Exception as e: + return await msg.edit(f"❌ Error: {str(e)}") + + cleaned_response_text = re.sub( + r"^\$?@?\$?v=undefined-rv\d+@?\$?|\$?@?\$?v=v\d+\.\d+-rv\d+@?\$?", + "", + response_text, + ) + text = cleaned_response_text.strip()[2:] + if "$~~~$" in text: + text = re.sub(r"\$~~~\$.*?\$~~~\$", "", text, flags=re.DOTALL) + rdata = {"reply": text} + + return await msg.edit_text(text=rdata["reply"]) + except Exception as e: + return await msg.edit(f"�� Error: {str(e)}") + finally: + await session.close() + + +modules_help["blackbox"] = { + "blackbox [query]*": "Ask anything to Blackbox", + "bbox [query]*": "Ask anything to Blackbox", +} diff --git a/modules/calculator.py b/modules/calculator.py new file mode 100644 index 0000000..08927bf --- /dev/null +++ b/modules/calculator.py @@ -0,0 +1,46 @@ +import asyncio + +from pyrogram import Client, filters +from pyrogram.types import Message + +from utils.misc import modules_help, prefix + + +@Client.on_message(filters.command("calc", prefix) & filters.me) +async def calc(_, message: Message): + if len(message.command) <= 1: + return + args = " ".join(message.command[1:]) + try: + result = str(eval(args)) + + if len(result) > 4096: + i = 0 + for x in range(0, len(result), 4096): + if i == 0: + await message.edit( + f"{args}={result[x:x + 4000]}", + parse_mode="HTML", + ) + else: + await message.reply( + f"{result[x:x + 4096]}", parse_mode="HTML" + ) + i += 1 + await asyncio.sleep(0.18) + else: + await message.edit( + f"{args}={result}", parse_mode="HTML" + ) + except Exception as e: + await message.edit(f"{args}=={e}", parse_mode="HTML") + + +modules_help["calculator"] = { + "calc [expression]*": "solve a math problem\n" + "+ – addition\n" + "– – subtraction\n" + "* – multiplication\n" + "/ – division\n" + "** – degree" +} diff --git a/modules/cdxl.py b/modules/cdxl.py new file mode 100644 index 0000000..19c1597 --- /dev/null +++ b/modules/cdxl.py @@ -0,0 +1,57 @@ +import base64, os + +from pyrogram import Client, filters +from pyrogram.types import Message + +from utils.misc import modules_help, prefix +from utils.scripts import format_exc, import_library + +clarifai = import_library("clarifai") + +from clarifai.client.model import Model + + +@Client.on_message(filters.command("cdxl", prefix) & filters.me) +async def cdxl(c: Client, message: Message): + try: + chat_id = message.chat.id + await message.edit_text("Please Wait...") + + if len(message.command) > 1: + prompt = message.text.split(maxsplit=1)[1] + elif message.reply_to_message: + prompt = message.reply_to_message.text + else: + await message.edit_text( + f"Usage: {prefix}vdxl [prompt/reply to prompt]" + ) + return + + inference_params = dict(width=1024, height=1024, steps=50, cfg_scale=9.0) + + model_prediction = Model( + "https://clarifai.com/stability-ai/stable-diffusion-2/models/stable-diffusion-xl" + ).predict_by_bytes( + prompt.encode(), input_type="text", inference_params=inference_params + ) + + output_base64 = model_prediction.outputs[0].data.image.base64 + + with open("sdxl_out.png", "wb") as f: + f.write(output_base64) + + await message.delete() + await c.send_photo( + chat_id, + photo=f"sdxl_out.png", + caption=f"Prompt:{prompt}", + ) + os.remove(f"sdxl_out.png") + + except Exception as e: + await message.edit_text(f"An error occurred: {format_exc(e)}") + + +modules_help["cdxl"] = { + "cdxl [prompt/reply to prompt]*": "Text to Image with SDXL model", +} diff --git a/modules/chatbot.py b/modules/chatbot.py new file mode 100644 index 0000000..5d606f1 --- /dev/null +++ b/modules/chatbot.py @@ -0,0 +1,105 @@ +from pyrogram import Client, enums, filters +from pyrogram.types import Message + +from utils.misc import modules_help, prefix +from utils.config import cohere_key +from utils.db import db +from utils.scripts import format_exc, import_library, restart + +cohere = import_library("cohere") + +co = cohere.Client(cohere_key) + +chatai_users = db.getaiusers() + + +@Client.on_message(filters.command("addai", prefix) & filters.me) +async def adduser(_, message: Message): + if len(message.command) > 1: + user_id = message.text.split(maxsplit=1)[1] + if user_id.isdigit(): + user_id = int(user_id) + db.addaiuser(user_id) + await message.edit_text("User ID Added") + restart() + else: + await message.edit_text("User ID is invalid.") + return + else: + await message.edit_text(f"Usage: {prefix}addai [user_id]") + return + + +@Client.on_message(filters.command("remai", prefix) & filters.me) +async def remuser(_, message: Message): + if len(message.command) > 1: + user_id = message.text.split(maxsplit=1)[1] + if user_id.isdigit(): + user_id = int(user_id) + db.remaiuser(user_id) + await message.edit_text("User ID Removed") + restart() + else: + await message.edit_text("User ID is invalid.") + return + else: + await message.edit_text(f"Usage: {prefix}remai [user_id]") + return + + +@Client.on_message(filters.user(users=chatai_users) & filters.text) +async def chatbot(_, message: Message): + user_id = message.chat.id + + if user_id in chatai_users: + pass + else: + return + try: + await message.reply_chat_action(enums.ChatAction.TYPING) + + chat_history = db.get_chat_history(user_id) + + prompt = message.text + + db.add_chat_history(user_id, {"role": "USER", "message": prompt}) + + response = co.chat( + chat_history=chat_history, + model="command-r-plus", + message=prompt, + temperature=0.3, + connectors=[{"id": "web-search", "options": {"site": "wikipedia.com"}}], + prompt_truncation="AUTO", + ) + + db.add_chat_history(user_id, {"role": "CHATBOT", "message": response.text}) + + await message.reply_text( + f"{response.text}", parse_mode=enums.ParseMode.MARKDOWN + ) + + except Exception as e: + await message.reply_text(f"An error occurred: {format_exc(e)}") + + +@Client.on_message(filters.command("chatoff", prefix) & filters.me) +async def chatoff(_, message: Message): + db.remove("core.chatbot", "chatai_users") + await message.reply_text("ChatBot is off now") + restart() + + +@Client.on_message(filters.command("listai", prefix) & filters.me) +async def listai(_, message: Message): + await message.edit_text( + f"User ID's Currently in AI ChatBot List:\n {chatai_users}" + ) + + +modules_help["chatbot"] = { + "addai [user_id]*": "Add A user to AI ChatBot List", + "remai [user_id]*": "Remove A user from AI ChatBot List", + "listai": "List A user from AI ChatBot List", + "chatoff": "Turn off AI ChatBot", +} diff --git a/modules/circle.py b/modules/circle.py new file mode 100644 index 0000000..45eea7b --- /dev/null +++ b/modules/circle.py @@ -0,0 +1,146 @@ +import asyncio +import os +from io import BytesIO + +from PIL import Image, ImageDraw, ImageFilter, ImageOps +from pyrogram import Client, filters, enums +from pyrogram.types import Message + +# noinspection PyUnresolvedReferences +from utils.misc import modules_help, prefix + +# noinspection PyUnresolvedReferences +from utils.scripts import import_library, format_exc + + +VideoFileClip = import_library("moviepy", "moviepy==2.2.1").VideoFileClip + +im = None + + +def process_img(filename): + global im + im = Image.open(f"downloads/{filename}") + w, h = im.size + img = Image.new("RGBA", (w, h), (0, 0, 0, 0)) + img.paste(im, (0, 0)) + m = min(w, h) + img = img.crop(((w - m) // 2, (h - m) // 2, (w + m) // 2, (h + m) // 2)) + w, h = img.size + mask = Image.new("L", (w, h), 0) + draw = ImageDraw.Draw(mask) + draw.ellipse((10, 10, w - 10, h - 10), fill=255) + mask = mask.filter(ImageFilter.GaussianBlur(2)) + img = ImageOps.fit(img, (w, h)) + img.putalpha(mask) + im = BytesIO() + im.name = "img.webp" + img.save(im) + im.seek(0) + + +video = None + + +def process_vid(filename): + global video + video = VideoFileClip(f"downloads/{filename}") + w, h = video.size + m = min(w, h) + box = { + "x1": (w - m) // 2, + "y1": (h - m) // 2, + "x2": (w + m) // 2, + "y2": (h + m) // 2, + } + video = video.cropped(**box) + + +@Client.on_message(filters.command(["circle", "round"], prefix) & filters.me) +async def circle(_, message: Message): + try: + if not message.reply_to_message: + return await message.reply( + "Reply is required for this command", + parse_mode=enums.ParseMode.HTML, + ) + if message.reply_to_message.photo: + filename = "circle.jpg" + typ = "photo" + elif message.reply_to_message.sticker: + if message.reply_to_message.sticker.is_video: + return await message.reply( + "Video stickers is not supported", + parse_mode=enums.ParseMode.HTML, + ) + filename = "circle.webp" + typ = "photo" + elif message.reply_to_message.video: + filename = "circle.mp4" + typ = "video" + elif message.reply_to_message.document: + _filename = message.reply_to_message.document.file_name.casefold() + if _filename.endswith(".png"): + filename = "circle.png" + typ = "photo" + elif _filename.endswith(".jpg"): + filename = "circle.jpg" + typ = "photo" + elif _filename.endswith(".jpeg"): + filename = "circle.jpeg" + typ = "photo" + elif _filename.endswith(".webp"): + filename = "circle.webp" + typ = "photo" + elif _filename.endswith(".mp4"): + filename = "circle.mp4" + typ = "video" + else: + return await message.reply( + "Invalid file type", parse_mode=enums.ParseMode.HTML + ) + else: + return await message.reply( + "Invalid file type", parse_mode=enums.ParseMode.HTML + ) + + if typ == "photo": + await message.edit( + "Processing image📷", parse_mode=enums.ParseMode.HTML + ) + await message.reply_to_message.download(f"downloads/{filename}") + await asyncio.get_event_loop().run_in_executor(None, process_img, filename) + await message.delete() + return await message.reply_sticker( + sticker=im, reply_to_message_id=message.reply_to_message.id + ) + else: + await message.edit( + "Processing video🎥", parse_mode=enums.ParseMode.HTML + ) + await message.reply_to_message.download(f"downloads/{filename}") + await asyncio.get_event_loop().run_in_executor(None, process_vid, filename) + + await message.edit("Saving video📼", parse_mode=enums.ParseMode.HTML) + await asyncio.get_event_loop().run_in_executor( + None, video.write_videofile, "downloads/result.mp4" + ) + + await message.delete() + await message.reply_video_note( + video_note="downloads/result.mp4", + duration=int(video.duration), + reply_to_message_id=message.reply_to_message.id, + ) + if isinstance(video, VideoFileClip): + video.close() + os.remove(f"downloads/{filename}") + os.remove("downloads/result.mp4") + except Exception as e: + await message.reply(format_exc(e), parse_mode=enums.ParseMode.HTML) + + +modules_help["circle"] = { + "round": "Round a photo or video.", + "circle": "Circle a photo or video.", +} diff --git a/modules/clear_notifs.py b/modules/clear_notifs.py new file mode 100644 index 0000000..1b3fe2a --- /dev/null +++ b/modules/clear_notifs.py @@ -0,0 +1,79 @@ +# Moon-Userbot - telegram userbot +# Copyright (C) 2020-present Moon Userbot Organization +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from pyrogram import Client, filters +from pyrogram.raw import functions +from pyrogram.types import Message + +from utils.misc import modules_help, prefix + + +@Client.on_message(filters.command(["clear_@"], prefix) & filters.me) +async def solo_mention_clear(client: Client, message: Message): + await message.delete() + peer = await client.resolve_peer(message.chat.id) + request = functions.messages.ReadMentions(peer=peer) + await client.invoke(request) + + +@Client.on_message(filters.command(["clear_all_@"], prefix) & filters.me) +async def global_mention_clear(client: Client, message: Message): + counter: int = 0 + await message.edit_text( + f"Clearing all mentions...\n\nCleared: {counter} chats" + ) + async for dialog in client.get_dialogs(): + peer = await client.resolve_peer(dialog.chat.id) + request = functions.messages.ReadMentions(peer=peer) + await client.invoke(request) + counter += 1 + await message.edit_text( + f"Clearing all mentions...\n\nCleared: {counter} chats" + ) + await message.delete() + + +@Client.on_message(filters.command(["clear_reacts"], prefix) & filters.me) +async def solo_reaction_clear(client: Client, message: Message): + await message.delete() + peer = await client.resolve_peer(message.chat.id) + request = functions.messages.ReadReactions(peer=peer) + await client.invoke(request) + + +@Client.on_message(filters.command(["clear_all_reacts"], prefix) & filters.me) +async def global_reaction_clear(client: Client, message: Message): + counter: int = 0 + await message.edit_text( + f"Clearing all reactions...\n\nCleared: {counter} chats" + ) + async for dialog in client.get_dialogs(): + peer = await client.resolve_peer(dialog.chat.id) + request = functions.messages.ReadReactions(peer=peer) + await client.invoke(request) + counter += 1 + await message.edit_text( + f"Clearing all reactions...\n\nCleared: {counter} chats" + ) + await message.delete() + + +modules_help["clear_notifs"] = { + "clear_@": "clear all mentions in this chat", + "clear_all_@": "clear all mentions in all chats", + "clear_reacts": "clear all reactions in this chat", + "clear_all_reacts": "clear all reactions in all chats (except private chats)", +} diff --git a/modules/cohere.py b/modules/cohere.py new file mode 100644 index 0000000..fa26034 --- /dev/null +++ b/modules/cohere.py @@ -0,0 +1,134 @@ +import asyncio +from json import tool +from utils.scripts import import_library +from utils.config import cohere_key + +cohere = import_library("cohere") + +import cohere + +co = cohere.Client(cohere_key) + +from utils.misc import modules_help, prefix +from utils.scripts import format_exc +from utils.db import db +from utils.rentry import paste as rentry_paste + + +from pyrogram import Client, filters, enums +from pyrogram.types import Message +from pyrogram.errors import MessageTooLong + + +@Client.on_message(filters.command("cohere", prefix) & filters.me) +async def cohere(c: Client, message: Message): + try: + user_id = message.from_user.id + chat_history = db.get_chat_history(user_id) + + if len(message.command) > 1: + prompt = message.text.split(maxsplit=1)[1] + elif message.reply_to_message: + prompt = message.reply_to_message.text + else: + await message.edit_text( + f"Usage: {prefix}cohere [prompt/reply to message]" + ) + return + + db.add_chat_history(user_id, {"role": "USER", "message": prompt}) + + await message.edit_text("Umm, lemme think...") + + response = co.chat_stream( + chat_history=chat_history, + model="command-r-plus", + message=prompt, + temperature=0.8, + tools=[{"name": "internet_search"}], + connectors=[], + prompt_truncation="OFF", + ) + output = "" + tool_message = "" + data = [] + for event in response: + if event.event_type == "tool-calls-chunk": + if event.tool_call_delta and event.tool_call_delta.text is None: + tool_message += "" + else: + tool_message += event.text + if event.event_type == "search-results": + data.append(event.documents) + if event.event_type == "text-generation": + output += event.text + if output == "": + output = "I can't seem to find an answer to that" + + db.add_chat_history(user_id, {"role": "CHATBOT", "message": output}) + + await message.edit_text(f"{tool_message}") + + await asyncio.sleep(5) + + try: + data = data[0] + references = "" + reference_dict = {} + for item in data: + title = item["title"] + url = item["url"] + if title not in reference_dict: + reference_dict[title] = url + + i = 1 + for title, url in reference_dict.items(): + references += f"**{i}.** [{title}]({url})\n" + i += 1 + + await message.edit_text( + f"**Question:**`{prompt}`\n**Answer:** {output}\n\n**References:**\n{references}", + parse_mode=enums.ParseMode.MARKDOWN, + disable_web_page_preview=True, + ) + + except IndexError: + references = "" + await message.edit_text( + f"**Question:**`{prompt}`\n**Answer:** {output}\n", + parse_mode=enums.ParseMode.MARKDOWN, + disable_web_page_preview=True, + ) + except MessageTooLong: + await message.edit_text( + "Output is too long... Pasting to rentry..." + ) + try: + output = output + "\n\n" + references if references else output + rentry_url, edit_code = await rentry_paste( + text=output, return_edit=True + ) + except RuntimeError: + await message.edit_text( + "Error: Failed to paste to rentry" + ) + return + await c.send_message( + "me", + f"Here's your edit code for Url: {rentry_url}\nEdit code: {edit_code}", + disable_web_page_preview=True, + ) + await message.edit_text( + f"Output: {rentry_url}\nNote: Edit Code has been sent to your saved messages", + disable_web_page_preview=True, + ) + + except Exception as e: + await message.edit_text(f"An error occurred: {format_exc(e)}") + + +modules_help["cohere"] = { + "cohere": "Chat with cohere ai" + + "\nSupports Chat History\n" + + "Supports real time internet search" +} diff --git a/modules/demotivator.py b/modules/demotivator.py new file mode 100644 index 0000000..2fc6647 --- /dev/null +++ b/modules/demotivator.py @@ -0,0 +1,93 @@ +import random +from io import BytesIO + +from pyrogram import Client, filters, enums +from pyrogram.types import Message + +from utils.misc import modules_help, prefix +from utils.scripts import import_library + +requests = import_library("requests") +PIL = import_library("PIL", "pillow") + +from PIL import Image, ImageDraw, ImageFont + + +@Client.on_message(filters.command(["dem"], prefix) & filters.me) +async def demotivator(client: Client, message: Message): + await message.edit( + "Process of demotivation...", parse_mode=enums.ParseMode.HTML + ) + font = requests.get( + "https://github.com/The-MoonTg-project/files/blob/main/Times%20New%20Roman.ttf?raw=true" + ) + f = font.content + template_dem = requests.get( + "https://raw.githubusercontent.com/files/main/demotivator.png" + ) + if message.reply_to_message: + words = ["random", "text", "typing", "fuck"] + if message.reply_to_message.photo: + donwloads = await client.download_media( + message.reply_to_message.photo.file_id + ) + photo = Image.open(f"{donwloads}") + resize_photo = photo.resize((469, 312)) + text = ( + message.text.split(" ", maxsplit=1)[1] + if len(message.text.split()) > 1 + else random.choice(words) + ) + im = Image.open(BytesIO(template_dem.content)) + im.paste(resize_photo, (65, 48)) + text_font = ImageFont.truetype(BytesIO(f), 22) + text_draw = ImageDraw.Draw(im) + text_draw.multiline_text( + (299, 412), text, font=text_font, fill=(255, 255, 255), anchor="ms" + ) + im.save(f"downloads/{message.id}.png") + await message.reply_to_message.reply_photo(f"downloads/{message.id}.png") + await message.delete() + elif message.reply_to_message.sticker: + if not message.reply_to_message.sticker.is_animated: + donwloads = await client.download_media( + message.reply_to_message.sticker.file_id + ) + photo = Image.open(f"{donwloads}") + resize_photo = photo.resize((469, 312)) + text = ( + message.text.split(" ", maxsplit=1)[1] + if len(message.text.split()) > 1 + else random.choice(words) + ) + im = Image.open(BytesIO(template_dem.content)) + im.paste(resize_photo, (65, 48)) + text_font = ImageFont.truetype(BytesIO(f), 22) + text_draw = ImageDraw.Draw(im) + text_draw.multiline_text( + (299, 412), text, font=text_font, fill=(255, 255, 255), anchor="ms" + ) + im.save(f"downloads/{message.id}.png") + await message.reply_to_message.reply_photo( + f"downloads/{message.id}.png" + ) + await message.delete() + else: + await message.edit( + "Animated stickers are not supported", + parse_mode=enums.ParseMode.HTML, + ) + else: + await message.edit( + "Need to answer the photo/sticker", + parse_mode=enums.ParseMode.HTML, + ) + else: + await message.edit( + "Need to answer the photo/sticker", parse_mode=enums.ParseMode.HTML + ) + + +modules_help["demotivator"] = { + "dem [text]*": "Reply to the picture to make a demotivator out of it" +} diff --git a/modules/destroy.py b/modules/destroy.py new file mode 100644 index 0000000..0765f86 --- /dev/null +++ b/modules/destroy.py @@ -0,0 +1,84 @@ +import os +from pyrogram import Client, filters, enums +from pyrogram.types import Message + +from utils.misc import modules_help, prefix +from utils.scripts import import_library + +lottie = import_library("lottie") +from lottie.exporters import exporters +from lottie.importers import importers + + +@Client.on_message(filters.command("destroy", prefix) & filters.me) +async def destroy_sticker(client: Client, message: Message): + """Destroy animated stickers by modifying their animation properties""" + try: + reply = message.reply_to_message + if not reply or not reply.sticker or not reply.sticker.is_animated: + return await message.edit( + "**Please reply to an animated sticker!**", + parse_mode=enums.ParseMode.MARKDOWN, + ) + + edit_msg = await message.edit( + "**🔄 Destroying sticker...**", parse_mode=enums.ParseMode.MARKDOWN + ) + + # Download sticker + tgs_path = await reply.download() + if not tgs_path or not os.path.exists(tgs_path): + return await edit_msg.edit( + "**❌ Download failed!**", parse_mode=enums.ParseMode.MARKDOWN + ) + + # Conversion process + json_path = "temp.json" + output_path = "MoonUB.tgs" + + importer = importers.get_from_filename(tgs_path) + if not importer: + return await edit_msg.edit( + "**❌ JSON conversion failed!**", parse_mode=enums.ParseMode.MARKDOWN + ) + + animation = importer.process(tgs_path) + exporter = exporters.get_from_filename(json_path) + exporter.process(animation, json_path) + + # Modify JSON data + with open(json_path, "r+") as f: + content = f.read() + modified = ( + content.replace("[1]", "[2]") + .replace("[2]", "[3]") + .replace("[3]", "[4]") + .replace("[4]", "[5]") + .replace("[5]", "[6]") + ) + f.seek(0) + f.write(modified) + f.truncate() + + importer = importers.get_from_filename(json_path) + animation = importer.process(json_path) + exporter = exporters.get_from_filename(output_path) + exporter.process(animation, output_path) + + # Send result + await message.reply_document(output_path, reply_to_message_id=reply.id) + await edit_msg.delete() + + except Exception as e: + await message.edit(f"**❌ Error:** `{e}`", parse_mode=enums.ParseMode.MARKDOWN) + finally: + # Cleanup temporary files + for file_path in [tgs_path, json_path, output_path]: + if file_path and os.path.exists(file_path): + try: + os.remove(file_path) + except Exception as clean_error: + print(f"Cleanup error: {clean_error}") + + +modules_help["destroy"] = {"destroy [reply]": "Modify and destroy animated stickers"} diff --git a/modules/dice.py b/modules/dice.py new file mode 100644 index 0000000..472d038 --- /dev/null +++ b/modules/dice.py @@ -0,0 +1,33 @@ +from pyrogram import Client, filters, enums +from pyrogram.types import Message +from utils.misc import modules_help, prefix +from utils.scripts import format_exc +import asyncio + + +@Client.on_message(filters.command("dice", prefix) & filters.me) +async def dice_text(client: Client, message: Message): + try: + value = int(message.command[1]) + if value not in range(1, 7): + raise AssertionError + except (ValueError, IndexError, AssertionError): + return await message.edit( + "Invalid value", parse_mode=enums.ParseMode.HTML + ) + + try: + message.dice = type("bruh", (), {"value": 0})() + while message.dice.value != value: + message = ( + await asyncio.gather( + message.delete(), client.send_dice(message.chat.id) + ) + )[1] + except Exception as e: + await message.edit(format_exc(e), parse_mode=enums.ParseMode.HTML) + + +modules_help["dice"] = { + "dice [1-6]*": "Generate dice with specified value. Works only in groups" +} diff --git a/modules/direct.py b/modules/direct.py new file mode 100644 index 0000000..4f9b75f --- /dev/null +++ b/modules/direct.py @@ -0,0 +1,313 @@ +# Copyright (C) 2019 The Raphielscape Company LLC. +# +# Licensed under the Raphielscape Public License, Version 1.d (the "License"); +# you may not use this file except in compliance with the License. +# +""" Userbot module containing various sites direct links generators""" + +import json +import re +import urllib.parse +from random import choice +from subprocess import PIPE, Popen + +import requests +from bs4 import BeautifulSoup +from humanize import naturalsize + +from pyrogram import Client, enums, filters +from pyrogram.types import Message + +from utils.misc import modules_help, prefix + + +def subprocess_run(cmd): + reply = "" + cmd_args = cmd.split() + subproc = Popen( + cmd_args, + stdout=PIPE, + stderr=PIPE, + universal_newlines=True, + executable="bash", + ) + talk = subproc.communicate() + exitCode = subproc.returncode + if exitCode != 0: + reply += ( + "```An error was detected while running the subprocess:\n" + f"exit code: {exitCode}\n" + f"stdout: {talk[0]}\n" + f"stderr: {talk[1]}```" + ) + return reply + return talk + + +@Client.on_message(filters.command("direct", prefix) & filters.me) +async def direct_link_generator(_, m: Message): + if len(m.command) > 1: + message = m.text.split(maxsplit=1)[1] + elif m.reply_to_message: + message = m.reply_to_message.text + else: + await m.edit(f"Usage: {prefix}direct [url]") + return + reply = "" + links = re.findall(r"\bhttps?://.*\.\S+", message) + if not links: + reply = "`No links found!`" + await m.edit(reply, parse_mode=enums.ParseMode.MARKDOWN) + for link in links: + if "drive.google.com" in link: + reply += gdrive(link) + elif "yadi.sk" in link: + reply += yandex_disk(link) + elif "cloud.mail.ru" in link: + reply += cm_ru(link) + elif "mediafire.com" in link: + reply += mediafire(link) + elif "sourceforge.net" in link: + reply += sourceforge(link) + elif "osdn.net" in link: + reply += osdn(link) + elif "androidfilehost.com" in link: + reply += androidfilehost(link) + else: + reply += re.findall(r"\bhttps?://(.*?[^/]+)", link)[0] + " is not supported" + await m.edit(reply, parse_mode=enums.ParseMode.MARKDOWN) + + +def gdrive(url: str) -> str: + """GDrive direct links generator""" + drive = "https://drive.google.com" + try: + link = re.findall(r"\bhttps?://drive\.google\.com\S+", url)[0] + except IndexError: + reply = "`No Google drive links found`\n" + return reply + file_id = "" + reply = "" + if link.find("view") != -1: + file_id = link.split("/")[-2] + elif link.find("open?id=") != -1: + file_id = link.split("open?id=")[1].strip() + elif link.find("uc?id=") != -1: + file_id = link.split("uc?id=")[1].strip() + url = f"{drive}/uc?export=download&id={file_id}" + download = requests.get(url, stream=True, allow_redirects=False) + cookies = download.cookies + try: + # In case of small file size, Google downloads directly + dl_url = download.headers["location"] + page = BeautifulSoup(download.content, "html.parser") + if "accounts.google.com" in dl_url: # non-public file + reply += "`Link is not public!`\n" + return reply + name = "Direct Download Link" + except KeyError: + # In case of download warning page + page = BeautifulSoup(download.content, "html.parser") + if download.headers is not None: + dl_url = download.headers.get("location") + page_element = page.find("a", {"id": "uc-download-link"}) + if page_element is not None: + export = drive + page_element.get("href") + name = page.find("span", {"class": "uc-name-size"}).text + response = requests.get( + export, stream=True, allow_redirects=False, cookies=cookies + ) + dl_url = response.headers["location"] + if "accounts.google.com" in dl_url: + name = page.find("span", {"class": "uc-name-size"}).text + reply += "Link is not public!" + return reply + if "=sharing" in dl_url: + name = page.find("span", {"class": "uc-name-size"}).text + reply += "```Provide GDrive Link not directc sharing of GDrive!```" + return reply + + reply += f"[{name}]({dl_url})\n" + return reply + + +def yandex_disk(url: str) -> str: + """Yandex.Disk direct links generator + Based on https://github.com/wldhx/yadisk-direct""" + reply = "" + try: + link = re.findall(r"\bhttps?://.*yadi\.sk\S+", url)[0] + except IndexError: + reply = "`No Yandex.Disk links found`\n" + return reply + api = "https://cloud-api.yandex.net/v1/disk/public/resources/download?public_key={}" + try: + dl_url = requests.get(api.format(link)).json()["href"] + name = dl_url.split("filename=")[1].split("&disposition")[0] + reply += f"[{name}]({dl_url})\n" + except KeyError: + reply += "`Error: File not found / Download limit reached`\n" + return reply + return reply + + +def cm_ru(url: str) -> str: + """cloud.mail.ru direct links generator + Using https://github.com/JrMasterModelBuilder/cmrudl.py""" + reply = "" + try: + link = re.findall(r"\bhttps?://.*cloud\.mail\.ru\S+", url)[0] + except IndexError: + reply = "`No cloud.mail.ru links found`\n" + return reply + cmd = f"bin/cmrudl -s {link}" + result = subprocess_run(cmd) + try: + result = result[0].splitlines()[-1] + data = json.loads(result) + except json.decoder.JSONDecodeError: + reply += "`Error: Can't extract the link`\n" + return reply + except IndexError: + return reply + dl_url = data["download"] + name = data["file_name"] + size = naturalsize(int(data["file_size"])) + reply += f"[{name} ({size})]({dl_url})\n" + return reply + + +def mediafire(url: str) -> str: + """MediaFire direct links generator""" + try: + link = re.findall(r"\bhttps?://.*mediafire\.com\S+", url)[0] + except IndexError: + reply = "`No MediaFire links found`\n" + return reply + reply = "" + page = BeautifulSoup(requests.get(link).content, "lxml") + info = page.find("a", {"aria-label": "Download file"}) + dl_url = info.get("href") + size = re.findall(r"\(.*\)", info.text)[0] + name = page.find("div", {"class": "filename"}).text + reply += f"[{name} {size}]({dl_url})\n" + return reply + + +def sourceforge(url: str) -> str: + """SourceForge direct links generator""" + try: + link = re.findall(r"\bhttps?://.*sourceforge\.net\S+", url)[0] + except IndexError: + reply = "`No SourceForge links found`\n" + return reply + file_path = re.findall(r"files(.*)/download", link)[0] + reply = f"Mirrors for __{file_path.split('/')[-1]}__\n" + project = re.findall(r"projects?/(.*?)/files", link)[0] + mirrors = ( + f"https://sourceforge.net/settings/mirror_choices?" + f"projectname={project}&filename={file_path}" + ) + page = BeautifulSoup(requests.get(mirrors).content, "html.parser") + info = page.find("ul", {"id": "mirrorList"}).findAll("li") + for mirror in info[1:]: + name = re.findall(r"\((.*)\)", mirror.text.strip())[0] + dl_url = ( + f'https://{mirror["id"]}.dl.sourceforge.net/project/{project}/{file_path}' + ) + reply += f"[{name}]({dl_url}) " + return reply + + +def osdn(url: str) -> str: + """OSDN direct links generator""" + osdn_link = "https://osdn.net" + try: + link = re.findall(r"\bhttps?://.*osdn\.net\S+", url)[0] + except IndexError: + reply = "`No OSDN links found`\n" + return reply + page = BeautifulSoup(requests.get(link, allow_redirects=True).content, "lxml") + info = page.find("a", {"class": "mirror_link"}) + link = urllib.parse.unquote(osdn_link + info["href"]) + reply = f"Mirrors for __{link.split('/')[-1]}__\n" + mirrors = page.find("form", {"id": "mirror-select-form"}).findAll("tr") + for data in mirrors[1:]: + mirror = data.find("input")["value"] + name = re.findall(r"\((.*)\)", data.findAll("td")[-1].text.strip())[0] + dl_url = re.sub(r"m=(.*)&f", f"m={mirror}&f", link) + reply += f"[{name}]({dl_url}) " + return reply + + +def androidfilehost(url: str) -> str: + """AFH direct links generator""" + try: + link = re.findall(r"\bhttps?://.*androidfilehost.*fid.*\S+", url)[0] + except IndexError: + reply = "`No AFH links found`\n" + return reply + fid = re.findall(r"\?fid=(.*)", link)[0] + session = requests.Session() + user_agent = useragent() + headers = {"user-agent": user_agent} + res = session.get(link, headers=headers, allow_redirects=True) + headers = { + "origin": "https://androidfilehost.com", + "accept-encoding": "gzip, deflate, br", + "accept-language": "en-US,en;q=0.9", + "user-agent": user_agent, + "content-type": "application/x-www-form-urlencoded; charset=UTF-8", + "x-mod-sbb-ctype": "xhr", + "accept": "*/*", + "referer": f"https://androidfilehost.com/?fid={fid}", + "authority": "androidfilehost.com", + "x-requested-with": "XMLHttpRequest", + } + data = {"submit": "submit", "action": "getdownloadmirrors", "fid": f"{fid}"} + mirrors = None + reply = "" + error = "`Error: Can't find Mirrors for the link`\n" + try: + req = session.post( + "https://androidfilehost.com/libs/otf/mirrors.otf.php", + headers=headers, + data=data, + cookies=res.cookies, + ) + mirrors = req.json()["MIRRORS"] + except (json.decoder.JSONDecodeError, TypeError): + reply += error + if not mirrors: + reply += error + return reply + for item in mirrors: + name = item["name"] + dl_url = item["url"] + reply += f"[{name}]({dl_url}) " + return reply + + +def useragent(): + """ + useragent random setter + """ + useragents = BeautifulSoup( + requests.get( + "https://developers.whatismybrowser.com/" + "useragents/explore/operating_system_name/android/" + ).content, + "lxml", + ).findAll("td", {"class": "useragent"}) + if not useragents: + return "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3" + user_agent = choice(useragents) + return user_agent.text + + +modules_help["direct"] = { + "direct": "Url/reply to Url\ +\n\nSyntax : .direct [url/reply] \ +\nUsage : Generates direct download link from supported URL(s)\ +\n\nSupported websites : Google Drive - MEGA.nz - Cloud Mail - Yandex.Disk - AFH - MediaFire - SourceForge - OSDN" +} diff --git a/modules/duckduckgo.py b/modules/duckduckgo.py new file mode 100644 index 0000000..257b322 --- /dev/null +++ b/modules/duckduckgo.py @@ -0,0 +1,16 @@ +from pyrogram import Client, filters +from pyrogram.types import Message +from utils.misc import modules_help, requirements_list, prefix + + +@Client.on_message(filters.command("duck", prefix) & filters.me) +async def duckgo(client: Client, message: Message): + input_str = " ".join(message.command[1:]) + sample_url = "https://duckduckgo.com/?q={}".format(input_str.replace(" ", "+")) + if sample_url: + link = sample_url.rstrip() + await message.edit_text( + "Let me 🦆 DuckDuckGo that for you:\n🔎 [{}]({})".format(input_str, link) + ) + else: + await message.edit_text("something is wrong. please try again later.") diff --git a/modules/durov.py b/modules/durov.py new file mode 100644 index 0000000..e01d165 --- /dev/null +++ b/modules/durov.py @@ -0,0 +1,17 @@ +from random import randint + +from pyrogram import Client, filters, enums +from pyrogram.types import Message + +from utils.misc import modules_help, prefix + + +@Client.on_message(filters.command("durov", prefix) & filters.me) +async def durov(_, message: Message): + await message.edit( + f"Random post from channel: https://t.me/durov/{randint(21, 36500)}", + parse_mode=enums.ParseMode.HTML, + ) + + +modules_help["durov"] = {"durov": "Send random post from durov channel"} diff --git a/modules/example.py b/modules/example.py new file mode 100644 index 0000000..b4a1b65 --- /dev/null +++ b/modules/example.py @@ -0,0 +1,62 @@ +#  Moon-Userbot - telegram userbot +#  Copyright (C) 2020-present Moon Userbot Organization +# +#  This program is free software: you can redistribute it and/or modify +#  it under the terms of the GNU General Public License as published by +#  the Free Software Foundation, either version 3 of the License, or +#  (at your option) any later version. + +#  This program is distributed in the hope that it will be useful, +#  but WITHOUT ANY WARRANTY; without even the implied warranty of +#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +#  GNU General Public License for more details. + +#  You should have received a copy of the GNU General Public License +#  along with this program.  If not, see . + +from pyrogram import Client, enums, filters +from pyrogram.types import Message + +from utils.misc import modules_help, prefix + +# if your module has packages from PyPi + +# from utils.scripts import import_library +# example_1 = import_library("example_1") +# example_2 = import_library("example_2") + +# import_library() will automatically install required library +# if it isn't installed + + +@Client.on_message(filters.command("example_edit", prefix) & filters.me) +async def example_edit(client: Client, message: Message): + try: + await message.edit("This is an example module") + except Exception as e: + await message.edit( + f"[{e.error_code}: {enums.MessageType.TEXT}] - {e.error_details}" + ) + + +@Client.on_message(filters.command("example_send", prefix) & filters.me) +async def example_send(client: Client, message: Message): + try: + await client.send_message(message.chat.id, "This is an example module") + except Exception as e: + await message.edit( + f"[{e.error_code}: {enums.MessageType.TEXT}] - {e.error_details}" + ) + + +# This adds instructions for your module +modules_help["example"] = { + "example_send": "example send", + "example_edit": "example edit", +} + +# modules_help["example"] = { "example_send [text]": "example send" } +# | | | | +# | | | └─ command description +# module_name command_name └─ optional command arguments +# (only snake_case) (only snake_case too) diff --git a/modules/f.py b/modules/f.py new file mode 100644 index 0000000..40ea2eb --- /dev/null +++ b/modules/f.py @@ -0,0 +1,58 @@ +import os +from random import randint + +import aiohttp +from pyrogram import Client, filters +from pyrogram.types import Message + +from utils.misc import modules_help, prefix + + +async def download_sticker(url, filename): + headers = { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", + "accept-language": "en-US,en;q=0.9;q=0.8", + "cache-control": "no-cache", + "dnt": "1", + "pragma": "no-cache", + "priority": "u=0, i", + "sec-ch-ua": '"Not)A;Brand";v="8", "Chromium";v="138", "Microsoft Edge";v="138"', + "sec-ch-ua-mobile": "?0", + "sec-ch-ua-platform": '"Windows"', + "sec-fetch-dest": "document", + "sec-fetch-mode": "navigate", + "sec-fetch-site": "none", + "sec-fetch-user": "?1", + "upgrade-insecure-requests": "1", + "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0", + } + cookies = {"country": "US", "lang": "en"} + + async with aiohttp.ClientSession() as session: + async with session.get(url, headers=headers, cookies=cookies) as response: + if response.status == 200: + with open(filename, "wb") as f: + f.write(await response.read()) + + +@Client.on_message(filters.command(["f"], prefix) & filters.me) +async def random_stiker(client: Client, message: Message): + await message.delete() + random = randint(1, 63) + index = f"00{random}" if random < 10 else f"0{random}" + sticker = ( + f"https://www.chpic.su/_data/stickers/f/FforRespect/FforRespect_{index}.webp" + ) + await download_sticker(sticker, "f.webp") + if os.path.exists("f.webp"): + await client.send_document( + message.chat.id, + "f.webp", + reply_to_message_id=message.reply_to_message.id + if message.reply_to_message + else None, + ) + os.remove("f.webp") + + +modules_help["f"] = {"f": "Send f to pay respect"} diff --git a/modules/fakeactions.py b/modules/fakeactions.py new file mode 100644 index 0000000..71c1ba8 --- /dev/null +++ b/modules/fakeactions.py @@ -0,0 +1,88 @@ +from asyncio import sleep + +from pyrogram import Client, filters, enums +from pyrogram.raw import functions +from pyrogram.types import Message, InputReplyToMessage +from utils.misc import modules_help, prefix +from utils.scripts import format_exc + +commands = { + "ftype": enums.ChatAction.TYPING, + "faudio": enums.ChatAction.UPLOAD_AUDIO, + "fvideo": enums.ChatAction.UPLOAD_VIDEO, + "fphoto": enums.ChatAction.UPLOAD_PHOTO, + "fdocument": enums.ChatAction.UPLOAD_DOCUMENT, + "flocation": enums.ChatAction.FIND_LOCATION, + "frvideo": enums.ChatAction.RECORD_VIDEO, + "frvoice": enums.ChatAction.RECORD_AUDIO, + "frvideor": enums.ChatAction.RECORD_VIDEO_NOTE, + "fvideor": enums.ChatAction.UPLOAD_VIDEO_NOTE, + "fgame": enums.ChatAction.PLAYING, + "fcontact": enums.ChatAction.CHOOSE_CONTACT, + "fstop": enums.ChatAction.CANCEL, + "fscrn": "screenshot", +} + + +# noinspection PyUnusedLocal +@Client.on_message(filters.command(list(commands), prefix) & filters.me) +async def fakeactions_handler(client: Client, message: Message): + cmd = message.command[0] + try: + sec = int(message.command[1]) + if sec > 60: + sec = 60 + except: + sec = None + await message.delete() + + action = commands[cmd] + + try: + if action != "screenshot": + if sec and action != enums.ChatAction.CANCEL: + while sec > 0: + await client.send_chat_action( + chat_id=message.chat.id, action=action + ) + await sleep(1) + sec -= 1 + return await client.send_chat_action(chat_id=message.chat.id, action=action) + else: + for _ in range(sec if sec else 1): + await client.invoke( + functions.messages.SendScreenshotNotification( + peer=await client.resolve_peer(message.chat.id), + reply_to=InputReplyToMessage( + reply_to_message_id=message.reply_to_message.id + ), + random_id=client.rnd_id(), + ) + ) + await sleep(0.1) + except AttributeError: + return await client.send_message( + "me", f"Error in fakeactions" "reply to message is required" + ) + except Exception as e: + return await client.send_message( + "me", f"Error in fakeactions" f" module:\n" + format_exc(e) + ) + + +modules_help["fakeactions"] = { + "ftype [sec]": "Typing... action", + "faudio [sec]": "Sending voice... action", + "fvideo [sec]": "Sending video... action", + "fphoto [sec]": "Sending photo... action", + "fdocument [sec]": "Sending document... action", + "flocation [sec]": "Find location... action", + "fcontact [sec]": "Sending contact... action", + "frvideo [sec]": "Recording video... action", + "frvoice [sec]": "Recording voice... action", + "frvideor [sec]": "Recording round video... action", + "fvideor [sec]": "Uploading round video... action", + "fgame [sec]": "Playing game... action", + "fstop": "Stop actions", + "fscrn [amount] [reply_to_message]*": "Make screenshot action", +} diff --git a/modules/filters.py b/modules/filters.py new file mode 100644 index 0000000..b1f66eb --- /dev/null +++ b/modules/filters.py @@ -0,0 +1,264 @@ +# Moon-Userbot - telegram userbot +# Copyright (C) 2020-present Moon Userbot Organization +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from pyrogram import Client, ContinuePropagation, errors, filters +from pyrogram.types import ( + InputMediaAudio, + InputMediaDocument, + InputMediaPhoto, + InputMediaVideo, + Message, +) + +from utils.db import db +from utils.misc import modules_help, prefix +from utils.scripts import format_exc + + +def get_filters_chat(chat_id): + return db.get("core.filters", f"{chat_id}", {}) + + +def set_filters_chat(chat_id, filters_): + return db.set("core.filters", f"{chat_id}", filters_) + + +async def contains_filter(_, __, m): + return m.text and m.text.lower() in get_filters_chat(m.chat.id).keys() + + +contains = filters.create(contains_filter) + + +# noinspection PyTypeChecker +@Client.on_message(contains) +async def filters_main_handler(client: Client, message: Message): + value = get_filters_chat(message.chat.id)[message.text.lower()] + try: + await client.get_messages(int(value["CHAT_ID"]), int(value["MESSAGE_ID"])) + except errors.RPCError as exc: + raise ContinuePropagation from exc + + if value.get("MEDIA_GROUP"): + messages_grouped = await client.get_media_group( + int(value["CHAT_ID"]), int(value["MESSAGE_ID"]) + ) + media_grouped_list = [] + for _ in messages_grouped: + if _.photo: + if _.caption: + media_grouped_list.append( + InputMediaPhoto(_.photo.file_id, _.caption.HTML) + ) + else: + media_grouped_list.append(InputMediaPhoto(_.photo.file_id)) + elif _.video: + if _.caption: + if _.video.thumbs: + media_grouped_list.append( + InputMediaVideo( + _.video.file_id, + _.video.thumbs[0].file_id, + _.caption.HTML, + ) + ) + else: + media_grouped_list.append( + InputMediaVideo(_.video.file_id, _.caption.HTML) + ) + elif _.video.thumbs: + media_grouped_list.append( + InputMediaVideo(_.video.file_id, _.video.thumbs[0].file_id) + ) + else: + media_grouped_list.append(InputMediaVideo(_.video.file_id)) + elif _.audio: + if _.caption: + media_grouped_list.append( + InputMediaAudio(_.audio.file_id, _.caption.HTML) + ) + else: + media_grouped_list.append(InputMediaAudio(_.audio.file_id)) + elif _.document: + if _.caption: + if _.document.thumbs: + media_grouped_list.append( + InputMediaDocument( + _.document.file_id, + _.document.thumbs[0].file_id, + _.caption.HTML, + ) + ) + else: + media_grouped_list.append( + InputMediaDocument(_.document.file_id, _.caption.HTML) + ) + elif _.document.thumbs: + media_grouped_list.append( + InputMediaDocument( + _.document.file_id, _.document.thumbs[0].file_id + ) + ) + else: + media_grouped_list.append(InputMediaDocument(_.document.file_id)) + await client.send_media_group( + message.chat.id, media_grouped_list, reply_to_message_id=message.id + ) + else: + await client.copy_message( + message.chat.id, + int(value["CHAT_ID"]), + int(value["MESSAGE_ID"]), + reply_to_message_id=message.id, + ) + raise ContinuePropagation + + +@Client.on_message(filters.command(["filter"], prefix) & filters.me) +async def filter_handler(client: Client, message: Message): + try: + if len(message.text.split()) < 2: + return await message.edit( + f"Usage: {prefix}filter [name] (Reply required)" + ) + name = message.text.split(maxsplit=1)[1].lower() + chat_filters = get_filters_chat(message.chat.id) + if name in chat_filters.keys(): + return await message.edit( + f"Filter {name} already exists." + ) + if not message.reply_to_message: + return await message.edit("Reply to message please.") + + try: + chat = await client.get_chat(db.get("core.notes", "chat_id", 0)) + except (errors.RPCError, ValueError, KeyError): + # group is not accessible or isn't created + chat = await client.create_supergroup( + "Userbot_Notes_Filters", "Don't touch this group, please" + ) + db.set("core.notes", "chat_id", chat.id) + + chat_id = chat.id + + if message.reply_to_message.media_group_id: + get_media_group = [ + _.id + for _ in await client.get_media_group( + message.chat.id, message.reply_to_message.id + ) + ] + try: + message_id = await client.forward_messages( + chat_id, message.chat.id, get_media_group + ) + except errors.ChatForwardsRestricted: + await message.edit( + "Forwarding messages is restricted by chat admins" + ) + return + filter_ = { + "MESSAGE_ID": str(message_id[1].id), + "MEDIA_GROUP": True, + "CHAT_ID": str(chat_id), + } + else: + try: + message_id = await message.reply_to_message.forward(chat_id) + except errors.ChatForwardsRestricted: + message_id = await message.copy(chat_id) + filter_ = { + "MEDIA_GROUP": False, + "MESSAGE_ID": str(message_id.id), + "CHAT_ID": str(chat_id), + } + + chat_filters.update({name: filter_}) + + set_filters_chat(message.chat.id, chat_filters) + return await message.edit( + f"Filter {name} has been added.", + ) + except Exception as e: + return await message.edit(format_exc(e)) + + +@Client.on_message(filters.command(["filters"], prefix) & filters.me) +async def filters_handler(_, message: Message): + try: + text = "" + for index, a in enumerate(get_filters_chat(message.chat.id).items(), start=1): + key, _ = a + key = key.replace("<", "").replace(">", "") + text += f"{index}. {key}\n" + text = f"Your filters in current chat:\n\n" f"{text}" + text = text[:4096] + return await message.edit(text) + except Exception as e: + return await message.edit(format_exc(e)) + + +@Client.on_message( + filters.command(["delfilter", "filterdel", "fdel"], prefix) & filters.me +) +async def filter_del_handler(_, message: Message): + try: + if len(message.text.split()) < 2: + return await message.edit( + f"Usage: {prefix}fdel [name]", + ) + name = message.text.split(maxsplit=1)[1].lower() + chat_filters = get_filters_chat(message.chat.id) + if name not in chat_filters.keys(): + return await message.edit( + f"Filter {name} doesn't exists.", + ) + del chat_filters[name] + set_filters_chat(message.chat.id, chat_filters) + return await message.edit( + f"Filter {name} has been deleted.", + ) + except Exception as e: + return await message.edit(format_exc(e)) + + +@Client.on_message(filters.command(["fsearch"], prefix) & filters.me) +async def filter_search_handler(_, message: Message): + try: + if len(message.text.split()) < 2: + return await message.edit( + f"Usage: {prefix}fsearch [name]", + ) + name = message.text.split(maxsplit=1)[1].lower() + chat_filters = get_filters_chat(message.chat.id) + if name not in chat_filters.keys(): + return await message.edit( + f"Filter {name} doesn't exists.", + ) + return await message.edit( + f"Trigger:\n{name}\nAnswer:\n{chat_filters[name]}" + ) + except Exception as e: + return await message.edit(format_exc(e)) + + +modules_help["filters"] = { + "filter [name]": "Create filter (Reply required)", + "filters": "List of all triggers", + "fdel [name]": "Delete filter by name", + "fsearch [name]": "Info filter by name", +} diff --git a/modules/fliptext.py b/modules/fliptext.py new file mode 100644 index 0000000..662c07d --- /dev/null +++ b/modules/fliptext.py @@ -0,0 +1,106 @@ +import asyncio +from pyrogram import Client, filters +from pyrogram.raw import functions +from pyrogram.types import Message +from utils.misc import modules_help, prefix +from utils.scripts import format_exc + +REPLACEMENT_MAP = { + "a": "ɐ", + "b": "q", + "c": "ɔ", + "d": "p", + "e": "ǝ", + "f": "ɟ", + "g": "ƃ", + "h": "ɥ", + "i": "ᴉ", + "j": "ɾ", + "k": "ʞ", + "l": "l", + "m": "ɯ", + "n": "u", + "o": "o", + "p": "d", + "q": "b", + "r": "ɹ", + "s": "s", + "t": "ʇ", + "u": "n", + "v": "ʌ", + "w": "ʍ", + "x": "x", + "y": "ʎ", + "z": "z", + "A": "∀", + "B": "B", + "C": "Ɔ", + "D": "D", + "E": "Ǝ", + "F": "Ⅎ", + "G": "פ", + "H": "H", + "I": "I", + "J": "ſ", + "K": "K", + "L": "˥", + "M": "W", + "N": "N", + "O": "O", + "P": "Ԁ", + "Q": "Q", + "R": "R", + "S": "S", + "T": "┴", + "U": "∩", + "V": "Λ", + "W": "M", + "X": "X", + "Y": "⅄", + "Z": "Z", + "0": "0", + "1": "Ɩ", + "2": "ᄅ", + "3": "Ɛ", + "4": "ㄣ", + "5": "ϛ", + "6": "9", + "7": "ㄥ", + "8": "8", + "9": "6", + ",": "'", + ".": "˙", + "?": "¿", + "!": "¡", + '"': ",,", + "'": ",", + "(": ")", + ")": "(", + "[": "]", + "]": "[", + "{": "}", + "}": "{", + "<": ">", + ">": "<", + "&": "⅋", + "_": "‾", +} + + +@Client.on_message(filters.command("flip", prefix) & filters.me) +async def flip(client: Client, message: Message): + text = " ".join(message.command[1:]) + final_str = "" + for char in text: + if char in REPLACEMENT_MAP.keys(): + new_char = REPLACEMENT_MAP[char] + else: + new_char = char + final_str += new_char + if text != final_str: + await message.edit(final_str) + else: + await message.edit(text) + + +modules_help["fliptext"] = {"flip [amount]*": "flip text upside down"} diff --git a/modules/flux.py b/modules/flux.py new file mode 100644 index 0000000..fd6c31b --- /dev/null +++ b/modules/flux.py @@ -0,0 +1,52 @@ +import os +import io +import time +import requests +from pyrogram import filters, Client +from pyrogram.types import Message + +from utils.misc import modules_help, prefix +from utils.scripts import format_exc, progress + + +def schellwithflux(args): + API_URL = "https://randydev-ryuzaki-api.hf.space/api/v1/akeno/fluxai" + payload = {"user_id": 1191668125, "args": args} # Please don't edit here + response = requests.post(API_URL, json=payload) + if response.status_code != 200: + print(f"Error status {response.status_code}") + return None + return response.content + + +@Client.on_message(filters.command("fluxai", prefix) & filters.me) +async def imgfluxai_(client: Client, message: Message): + question = message.text.split(" ", 1)[1] if len(message.command) > 1 else None + if not question: + return await message.reply_text("Please provide a question for Flux.") + try: + image_bytes = schellwithflux(question) + if image_bytes is None: + return await message.reply_text("Failed to generate an image.") + pro = await message.reply_text("Generating image, please wait...") + + # Write the image bytes directly to a file + with open("flux_gen.jpg", "wb") as f: + f.write(image_bytes) + + ok = await pro.edit_text("Uploading image...") + await message.reply_photo( + "flux_gen.jpg", + progress=progress, + progress_args=(ok, time.time(), "Uploading image..."), + ) + await ok.delete() + if os.path.exists("flux_gen.jpg"): + os.remove("flux_gen.jpg") + except Exception as e: + await message.edit_text(format_exc(e)) + + +modules_help["fluxai"] = { + "fluxai [prompt]*": "text to image fluxai", +} diff --git a/modules/gemini.py b/modules/gemini.py new file mode 100644 index 0000000..b564924 --- /dev/null +++ b/modules/gemini.py @@ -0,0 +1,72 @@ +# This scripts contains use cases for userbots +# This is used on my Moon-Userbot: https://github.com/The-MoonTg-project/Moon-Userbot +# YOu can check it out for uses example +import os + +from pyrogram import Client, filters, enums +from pyrogram.types import Message +from pyrogram.errors import MessageTooLong + +from utils.misc import modules_help, prefix +from utils.scripts import format_exc, import_library +from utils.config import gemini_key +from utils.rentry import paste as rentry_paste + +genai = import_library("google.generativeai", "google-generativeai") + +genai.configure(api_key=gemini_key) + +model = genai.GenerativeModel("gemini-2.0-flash") + + +@Client.on_message(filters.command("gemini", prefix) & filters.me) +async def say(client: Client, message: Message): + try: + await message.edit_text("Please Wait...") + + if len(message.command) > 1: + prompt = message.text.split(maxsplit=1)[1] + elif message.reply_to_message: + prompt = message.reply_to_message.text + else: + await message.edit_text( + f"Usage: {prefix}gemini [prompt/reply to message]" + ) + return + + chat = model.start_chat() + response = chat.send_message(prompt) + + await message.edit_text( + f"**Question:**`{prompt}`\n**Answer:** {response.text}", + parse_mode=enums.ParseMode.MARKDOWN, + ) + except MessageTooLong: + await message.edit_text( + "Output is too long... Pasting to rentry..." + ) + try: + rentry_url, edit_code = await rentry_paste( + text=response.text, return_edit=True + ) + except RuntimeError: + await message.edit_text( + "Error: Failed to paste to rentry" + ) + return + await client.send_message( + "me", + f"Here's your edit code for Url: {rentry_url}\nEdit code: {edit_code}", + disable_web_page_preview=True, + ) + await message.edit_text( + f"Output: {rentry_url}\nNote: Edit Code has been sent to your saved messages", + disable_web_page_preview=True, + ) + except Exception as e: + await message.edit_text(f"An error occurred: {format_exc(e)}") + + +modules_help["gemini"] = { + "gemini [prompt]*": "Ask questions with Gemini Ai", +} diff --git a/modules/google.py b/modules/google.py new file mode 100644 index 0000000..c1a811c --- /dev/null +++ b/modules/google.py @@ -0,0 +1,47 @@ +# Dragon-Userbot - telegram userbot +# Copyright (C) 2020-present Dragon Userbot Organization +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from pyrogram import Client, filters +from pyrogram.types import Message + +from utils.misc import modules_help, prefix + + +@Client.on_message(filters.command(["google", "g"], prefix) & filters.me) +async def webshot(_, message: Message): + user_request = " ".join(message.command[1:]) + + if user_request == "": + if message.reply_to_message: + reply_user_request = message.reply_to_message.text + request = reply_user_request.replace(" ", "+") + full_request = f"https://lmgtfy.app/?s=g&iie=1&q={request}" + await message.edit( + f"{reply_user_request}", + disable_web_page_preview=True, + ) + + else: + request = user_request.replace(" ", "+") + full_request = f"https://lmgtfy.app/?s=g&iie=1&q={request}" + await message.edit( + f"{user_request}", disable_web_page_preview=True + ) + + +modules_help["google"] = { + "google [request]": "To teach the interlocutor to use Google. Request isn't required." +} diff --git a/modules/hearts.py b/modules/hearts.py new file mode 100644 index 0000000..23e9ee0 --- /dev/null +++ b/modules/hearts.py @@ -0,0 +1,93 @@ +import random +import asyncio + +from pyrogram import Client, filters +from pyrogram.types import Message +from pyrogram.errors.exceptions.flood_420 import FloodWait +from utils.misc import modules_help, prefix + +R = "❤️" +W = "🤍" + +heart_list = [ + W * 9, + W * 2 + R * 2 + W + R * 2 + W * 2, + W + R * 7 + W, + W + R * 7 + W, + W + R * 7 + W, + W * 2 + R * 5 + W * 2, + W * 3 + R * 3 + W * 3, + W * 4 + R + W * 4, + W * 9, +] +joined_heart = "\n".join(heart_list) + +heartlet_len = joined_heart.count(R) + +SLEEP = 0.1 + + +async def _wrap_edit(message: Message, text: str): + """Floodwait-safe utility wrapper for edit""" + try: + await message.edit(text) + except FloodWait as fl: + await asyncio.sleep(fl.x) + + +async def phase1(message: Message): + """Big scroll""" + BIG_SCROLL = "🧡💛💚💙💜🖤🤎" + await _wrap_edit(message, joined_heart) + for heart in BIG_SCROLL: + await _wrap_edit(message, joined_heart.replace(R, heart)) + await asyncio.sleep(SLEEP) + + +async def phase2(message: Message): + """Per-heart randomiser""" + ALL = ["❤️"] + list("🧡💛💚💙💜🤎🖤") # don't include white heart + + format_heart = joined_heart.replace(R, "{}") + for _ in range(5): + heart = format_heart.format(*random.choices(ALL, k=heartlet_len)) + await _wrap_edit(message, heart) + await asyncio.sleep(SLEEP) + + +async def phase3(message: Message): + """Fill up heartlet matrix""" + await _wrap_edit(message, joined_heart) + await asyncio.sleep(SLEEP * 2) + repl = joined_heart + for _ in range(joined_heart.count(W)): + repl = repl.replace(W, R, 1) + await _wrap_edit(message, repl) + await asyncio.sleep(SLEEP) + + +async def phase4(message: Message): + """Matrix shrinking""" + for i in range(7, 0, -1): + heart_matrix = "\n".join([R * i] * i) + await _wrap_edit(message, heart_matrix) + await asyncio.sleep(SLEEP) + + +@Client.on_message(filters.command("hearts", prefix) & filters.me) +async def hearts(client: Client, message: Message): + await phase1(message) + await phase2(message) + await phase3(message) + await phase4(message) + await asyncio.sleep(SLEEP * 3) + + final_caption = " ".join(message.command[1:]) + if not final_caption: + final_caption = "💕 by @moonuserbot" + await message.edit(final_caption) + + +modules_help["hearts"] = { + "hearts": "Heart animation. May cause floodwaits, use at your own risk!" +} diff --git a/modules/help.py b/modules/help.py new file mode 100644 index 0000000..9a7ccf9 --- /dev/null +++ b/modules/help.py @@ -0,0 +1,83 @@ +# Moon-Userbot - telegram userbot +# Copyright (C) 2020-present Moon Userbot Organization +# +# This program is free software: you can redistribute it and/or modify + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from pyrogram import Client, filters +from pyrogram.types import Message + +from utils.misc import modules_help, prefix +from utils.scripts import format_module_help, with_reply +from utils.module import ModuleManager + +module_manager = ModuleManager.get_instance() + + +@Client.on_message(filters.command(["help", "h"], prefix) & filters.me) +async def help_cmd(_, message: Message): + if not module_manager.help_navigator: + await message.edit("Help system is not initialized yet. Please wait...") + return + + if len(message.command) == 1: + await module_manager.help_navigator.send_page(message) + elif message.command[1].lower() in modules_help: + await message.edit(format_module_help(message.command[1].lower(), prefix)) + else: + command_name = message.command[1].lower() + module_found = False + for module_name, commands in modules_help.items(): + for command in commands.keys(): + if command.split()[0] == command_name: + cmd = command.split(maxsplit=1) + cmd_desc = commands[command] + module_found = True + return await message.edit( + f"Help for command {prefix}{command_name}\n" + f"Module: {module_name} ({prefix}help {module_name})\n\n" + f"{prefix}{cmd[0]}" + f"{' ' + cmd[1] + '' if len(cmd) > 1 else ''}" + f" — {cmd_desc}", + ) + if not module_found: + await message.edit(f"Module or command {command_name} not found") + + +@Client.on_message(filters.command(["pn", "pp", "pq"], prefix) & filters.me) +@with_reply +async def handle_navigation(_, message: Message): + if not module_manager.help_navigator: + await message.edit("Help system is not initialized yet. Please wait...") + return + + reply_message = message.reply_to_message + if reply_message and "Help Page No:" in message.reply_to_message.text: + cmd = message.command[0].lower() + if cmd == "pn": + if module_manager.help_navigator.next_page(): + await module_manager.help_navigator.send_page(reply_message) + return await message.delete() + await message.edit("No more pages available.") + elif cmd == "pp": + if module_manager.help_navigator.prev_page(): + await module_manager.help_navigator.send_page(reply_message) + return await message.delete() + return await message.edit("This is the first page.") + elif cmd == "pq": + await reply_message.delete() + return await message.edit("Help closed.") + + +modules_help["help"] = { + "help [module/command name]": "Get common/module/command help", + "pn/pp/pq": "Navigate through help pages" + + " (pn: next page, pp: previous page, pq: quit help)", +} diff --git a/modules/huggingface.py b/modules/huggingface.py new file mode 100644 index 0000000..3f9427c --- /dev/null +++ b/modules/huggingface.py @@ -0,0 +1,226 @@ +import os +import io +import time +import aiohttp +import asyncio +import logging +from PIL import Image + +from pyrogram import filters, Client, enums +from pyrogram.types import Message + +from concurrent.futures import ThreadPoolExecutor + +from utils.db import db +from utils.misc import modules_help, prefix +from utils.scripts import format_exc + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + + +async def query_huggingface(payload): + api_key = db.get("custom.hf", "api_key", None) + model = db.get("custom.hf", "current_model", None) + + if not api_key: + raise ValueError( + f"API key not set. Use {prefix}set_hf api to set it." + ) + if not model: + raise ValueError( + f"Model not set. Use {prefix}set_hf model to set it." + ) + + api_url = f"https://api-inference.huggingface.co/models/{model}" + headers = {"Authorization": f"Bearer {api_key}"} + timeout = aiohttp.ClientTimeout(total=120) + start_time = time.time() + retries = 3 + + for attempt in range(1, retries + 1): + try: + async with aiohttp.ClientSession(timeout=timeout) as session: + async with session.post( + api_url, headers=headers, json=payload + ) as response: + fetch_time = int((time.time() - start_time) * 1000) + if response.status != 200: + error_text = await response.text() + logger.error(f"API Error {response.status}: {error_text}") + return None, fetch_time + return await response.read(), fetch_time + except asyncio.TimeoutError: + logger.error(f"TimeoutError: Attempt {attempt}/{retries} timed out.") + if attempt == retries: + raise + except asyncio.CancelledError: + logger.error( + "Request was cancelled. Ensure the task is not being forcefully terminated." + ) + raise + except aiohttp.ClientError as e: + logger.error(f"Network Error: {e}") + if attempt == retries: + raise + finally: + await asyncio.sleep(2) + + +async def save_image(image_bytes, path): + loop = asyncio.get_event_loop() + with ThreadPoolExecutor() as pool: + await loop.run_in_executor( + pool, lambda: Image.open(io.BytesIO(image_bytes)).save(path) + ) + + +@Client.on_message(filters.command(["set_hf"], prefix) & filters.me) +async def manage_huggingface(_, message: Message): + """Manage Hugging Face API key and models.""" + subcommand = message.command[1].lower() if len(message.command) > 1 else None + arg = message.command[2] if len(message.command) > 2 else None + + if subcommand == "api": + if arg: + db.set("custom.hf", "api_key", arg) + return await message.edit_text( + f"Hugging Face API key set successfully.\nAPI Key: {arg}" + ) + return await message.edit_text(f"Usage: {prefix}hf api ") + + if subcommand == "model": + if arg: + models = db.get("custom.hf", "models", []) + if arg not in models: + models.append(arg) + db.set("custom.hf", "models", models) + db.set("custom.hf", "current_model", arg) + return await message.edit_text( + f"Model '{arg}' added and set as the current model." + ) + return await message.edit_text(f"Usage: {prefix}hf model ") + + if subcommand == "select": + models = db.get("custom.hf", "models", []) + if arg and arg.lower() == "all": + db.set("custom.hf", "current_model", "all") + model_list = "\n".join([f"*{i + 1}. {m}" for i, m in enumerate(models)]) + return await message.edit_text( + f"All models selected:\n{model_list}\n\n" + f"Images will be generated from all models." + ) + if arg: + try: + index = int(arg) - 1 + if 0 <= index < len(models): + db.set("custom.hf", "current_model", models[index]) + return await message.edit_text(f"Model set to '{models[index]}'.") + return await message.edit_text("Invalid model number.") + except ValueError: + return await message.edit_text( + "Invalid model number. Use a valid integer." + ) + return await message.edit_text(f"Usage: {prefix}hf select ") + + if subcommand == "delete" and arg: + try: + index = int(arg) - 1 + models = db.get("custom.hf", "models", []) + if 0 <= index < len(models): + removed_model = models.pop(index) + db.set("custom.hf", "models", models) + if db.get("custom.hf", "current_model") == removed_model: + db.set( + "custom.hf", "current_model", models[0] if models else "None" + ) + return await message.edit_text(f"Model '{removed_model}' deleted.") + return await message.edit_text("Invalid model number.") + except ValueError: + return await message.edit_text("Invalid model number. Use a valid integer.") + + api_key = db.get("custom.hf", "api_key", None) + models = db.get("custom.hf", "models", []) + current_model = db.get("custom.hf", "current_model", "Not set") + model_list = "\n".join( + [ + f"{'*' if m == current_model or current_model == 'all' else ''}{i + 1}. {m}" + for i, m in enumerate(models) + ] + ) + settings = ( + f"Hugging Face settings:\n" + f"API Key:\n{api_key if api_key else 'Not set'}\n\n" + f"Available Models:\n{model_list}" + ) + usage_message = ( + f"{settings}\n\nUsage:\n" + f"{prefix}set_hf api, model, select, delete, select all" + ) + await message.edit_text(usage_message) + + +@Client.on_message(filters.command(["hf", "hface", "huggingface"], prefix)) +async def imgflux_(_, message: Message): + prompt = message.text.split(" ", 1)[1] if len(message.command) > 1 else None + if not prompt: + usage_message = ( + f"Usage: {prefix}{message.command[0]} [custom prompt]" + ) + return await ( + message.edit_text if message.from_user.is_self else message.reply_text + )(usage_message) + + processing_message = await ( + message.edit_text if message.from_user.is_self else message.reply_text + )("Processing...") + + try: + current_model = db.get("custom.hf", "current_model", None) + models = db.get("custom.hf", "models", []) + models_to_use = models if current_model == "all" else [current_model] + + generated_images = [] + + for model in models_to_use: + db.set("custom.hf", "current_model", model) + payload = {"inputs": prompt} + image_bytes, fetch_time = await query_huggingface(payload) + if not image_bytes: + logger.warning(f"Failed to fetch image for model: {model}") + continue + + image_path = f"hf_flux_gen_{model.replace('/', '_')}.jpg" + await save_image(image_bytes, image_path) + generated_images.append((image_path, model, fetch_time)) + + if not generated_images: + return await processing_message.edit_text( + "Failed to generate an image for all models." + ) + + for image_path, model_name, fetch_time in generated_images: + caption = ( + f"**Model:**\n> {model_name}\n" + f"**Prompt used:**\n> {prompt}\n\n" + f"**Fetching Time:** {fetch_time} ms" + ) + await message.reply_photo( + image_path, caption=caption, parse_mode=enums.ParseMode.MARKDOWN + ) + os.remove(image_path) + + except Exception as e: + logger.error(f"Unexpected Error: {e}") + await processing_message.edit_text(format_exc(e)) + finally: + await processing_message.delete() + + +modules_help["huggingface"] = { + "hf [prompt]*": "Generate an AI image using Hugging Face model(s).", + "set_hf *": "Set the Hugging Face API key.", + "set_hf model *": "Add and set a Hugging Face model.", + "set_hf select *": "Select a specific model or all models for use.", + "set_hf delete *": "Delete a model from the list.", +} diff --git a/modules/id.py b/modules/id.py new file mode 100644 index 0000000..5a5d65b --- /dev/null +++ b/modules/id.py @@ -0,0 +1,88 @@ +#  Moon-Userbot - telegram userbot +#  Copyright (C) 2020-present Moon Userbot Organization +# +#  This program is free software: you can redistribute it and/or modify +#  it under the terms of the GNU General Public License as published by +#  the Free Software Foundation, either version 3 of the License, or +#  (at your option) any later version. + +#  This program is distributed in the hope that it will be useful, +#  but WITHOUT ANY WARRANTY; without even the implied warranty of +#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +#  GNU General Public License for more details. + +#  You should have received a copy of the GNU General Public License +#  along with this program.  If not, see . + +from pyrogram import Client, enums, filters +from pyrogram.types import Message, MessageOriginHiddenUser + +from utils.misc import modules_help, prefix + + +@Client.on_message(filters.command("id", prefix) & filters.me) +async def ids(_, message: Message): + text = "\n".join( + [ + f"Chat ID: `{message.chat.id}`", + f"Chat DC ID: `{message.chat.dc_id}`\n", + f"Message ID: `{message.id}`", + ( + f"Your ID: `{message.from_user.id}`" + if message.from_user + else f"Channel/Group ID: `{message.sender_chat.id}`" + ), + ( + f"Your DC ID: `{message.from_user.dc_id}`" + if message.from_user + else f"Channel/Group ID: `{message.sender_chat.id}`" + ), + ] + ) + + if rtm := message.reply_to_message: + # print(rtm) + text += f"\n\nReplied Message ID: `{rtm.id}`" + + if user := rtm.from_user: + text = "\n".join( + [ + text, + f"Replied User ID: `{user.id}`", + f"Replied User DC ID: `{user.dc_id}`", + ] + ) + + else: + text = "\n".join( + [ + text, + f"Replied Chat ID: `{rtm.sender_chat.id}`", + f"Replied Chat DC ID: `{rtm.sender_chat.dc_id}`", + ] + ) + + if rtm.forward_origin and rtm.forward_origin.date: + if isinstance(rtm.forward_origin, MessageOriginHiddenUser): + text = "\n".join( + [ + text, + "\nForwarded from a hidden user.", + ] + ) + elif ffc := rtm.forward_origin.sender_user: + text = "\n".join( + [ + text, + f"\nForwarded Message ID: `{getattr(rtm.forward_origin, 'message_id', None)}`", + f"Forwarded from Chat ID: `{ffc.id}`", + f"Forwarded from Chat DC ID: `{ffc.dc_id}`", + ] + ) + + await message.edit("**__" + text + "__**", parse_mode=enums.ParseMode.MARKDOWN) + + +modules_help["id"] = { + "id": "simply run or reply to message", +} diff --git a/modules/joindate.py b/modules/joindate.py new file mode 100644 index 0000000..8a1d2c1 --- /dev/null +++ b/modules/joindate.py @@ -0,0 +1,44 @@ +import asyncio +import os +from datetime import datetime +from pyrogram import Client, filters +from pyrogram.raw import functions +from pyrogram.types import Message +from utils.misc import modules_help, prefix +from utils.scripts import format_exc + + +@Client.on_message(filters.command("joindate", prefix) & filters.me) +async def joindate(client: Client, message: Message): + await message.edit(f"One moment...") + members = [] + cgetmsg = await client.get_messages(message.chat.id, 1) + async for m in client.iter_chat_members(message.chat.id): + members.append( + ( + m.user.first_name, + m.joined_date or cgetmsg.date, + ) + ) + + members.sort(key=lambda member: member[1]) + + with open("joined_date.txt", "w", encoding="utf8") as f: + f.write("Join Date First Name\n") + for member in members: + f.write( + str(datetime.fromtimestamp(member[1]).strftime("%y-%m-%d %H:%M")) + + f" {member[0]}\n" + ) + + await message.delete() + await client.send_document(message.chat.id, "joined_date.txt") + os.remove("joined_date.txt") + + + +modules_help["joindate"] = { + "joindate": "Get a list of all chat members and sort them by the date they joined the group" + +} + diff --git a/modules/kokodrilo_explodando.py b/modules/kokodrilo_explodando.py new file mode 100644 index 0000000..6c8dae0 --- /dev/null +++ b/modules/kokodrilo_explodando.py @@ -0,0 +1,22 @@ +from pyrogram import Client, filters +from pyrogram.types import Message + +from utils.misc import modules_help, prefix +import asyncio, random + + +@Client.on_message(filters.command("kokodrilo", prefix) & filters.me) +async def kokodrilo_explodando(_, message: Message): + amount = 1 + if len(message.command) > 1: + amount = int(message.command[1]) + for _ in range(amount): + await message.edit("🐊") + await asyncio.sleep(random.uniform(1, 2.5)) + await message.edit("💥") + await asyncio.sleep(1.8) + + +modules_help["kokodrilo_explodando"] = { + "kokodrilo [number of explosions]": "kOkOdRiLo ExPlOrAdO", +} diff --git a/modules/leave_chat.py b/modules/leave_chat.py new file mode 100644 index 0000000..2fbfde9 --- /dev/null +++ b/modules/leave_chat.py @@ -0,0 +1,37 @@ +# Dragon-Userbot - telegram userbot +# Copyright (C) 2020-present Dragon Userbot Organization +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import asyncio + +from pyrogram import Client, filters +from pyrogram.types import Message + +from utils.misc import modules_help, prefix + + +@Client.on_message(filters.command(["leave_chat", "lc"], prefix) & filters.me) +async def leave_chat(_, message: Message): + if message.chat.type != "private": + await message.edit("Goodbye...") + await asyncio.sleep(3) + await message.chat.leave() + else: + await message.edit("Not supported in private chats") + + +modules_help["leave_chat"] = { + "leave_chat": "Quit chat", +} diff --git a/modules/loader.py b/modules/loader.py new file mode 100644 index 0000000..46a8042 --- /dev/null +++ b/modules/loader.py @@ -0,0 +1,340 @@ +# Moon-Userbot - telegram userbot +# Copyright (C) 2020-present Moon Userbot Organization +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import hashlib +import os +import shutil +import subprocess +import sys + +import requests +from pyrogram import Client, filters +from pyrogram.types import Message + +from utils.misc import modules_help, prefix +from utils.scripts import restart +from utils.db import db + + +BASE_PATH = os.path.abspath(os.getcwd()) +CATEGORIES = [ + "ai", + "dl", + "admin", + "anime", + "fun", + "images", + "info", + "misc", + "music", + "news", + "paste", + "rev", + "tts", + "utils", +] + + +@Client.on_message(filters.command(["modhash", "mh"], prefix) & filters.me) +async def get_mod_hash(_, message: Message): + if len(message.command) == 1: + return + url = message.command[1].lower() + resp = requests.get(url) + if not resp.ok: + await message.edit( + f"Troubleshooting with downloading module {url}" + ) + return + + await message.edit( + f"Module hash: {hashlib.sha256(resp.content).hexdigest()}\n" + f"Link: {url}\nFile: {url.split('/')[-1]}", + ) + + +@Client.on_message(filters.command(["loadmod", "lm"], prefix) & filters.me) +async def loadmod(_, message: Message): + if ( + not ( + message.reply_to_message + and message.reply_to_message.document + and message.reply_to_message.document.file_name.endswith(".py") + ) + and len(message.command) == 1 + ): + await message.edit("Specify module to download") + return + + if len(message.command) > 1: + await message.edit("Fetching module...") + url = message.command[1].lower() + + if url.startswith( + "https://raw.githubusercontent.com/The-MoonTg-project/custom_modules/main/" + ): + module_name = url.split("/")[-1].split(".")[0] + elif "." not in url: + module_name = url.lower() + try: + f = requests.get( + "https://raw.githubusercontent.com/The-MoonTg-project/custom_modules/main/full.txt" + ).text + except Exception: + return await message.edit("Failed to fetch custom modules list") + modules_dict = {line.split("/")[-1].split()[0]: line.strip() for line in f.splitlines()} + if module_name in modules_dict: + url = f"https://raw.githubusercontent.com/The-MoonTg-project/custom_modules/main/{modules_dict[module_name]}.py" + else: + await message.edit( + f"Module {module_name} is not found" + ) + return + else: + modules_hashes = requests.get( + "https://raw.githubusercontent.com/The-MoonTg-project/custom_modules/main/modules_hashes.txt" + ).text + resp = requests.get(url) + + if not resp.ok: + await message.edit( + f"Troubleshooting with downloading module {url}", + ) + return + + if hashlib.sha256(resp.content).hexdigest() not in modules_hashes: + return await message.edit( + "Only " + "verified modules or from the official " + "" + "custom_modules repository are supported!", + disable_web_page_preview=True, + ) + + module_name = url.split("/")[-1].split(".")[0] + + resp = requests.get(url) + if not resp.ok: + await message.edit(f"Module {module_name} is not found") + return + + if not os.path.exists(f"{BASE_PATH}/modules/custom_modules"): + os.mkdir(f"{BASE_PATH}/modules/custom_modules") + + with open(f"./modules/custom_modules/{module_name}.py", "wb") as f: + f.write(resp.content) + else: + file_name = await message.reply_to_message.download() + module_name = message.reply_to_message.document.file_name[:-3] + + with open(file_name, "rb") as f: + content = f.read() + + modules_hashes = requests.get( + "https://raw.githubusercontent.com/The-MoonTg-project/custom_modules/main/modules_hashes.txt" + ).text + + if hashlib.sha256(content).hexdigest() not in modules_hashes: + os.remove(file_name) + return await message.edit( + "Only " + "verified modules or from the official " + "" + "custom_modules repository are supported!", + disable_web_page_preview=True, + ) + os.rename(file_name, f"./modules/custom_modules/{module_name}.py") + + all_modules = db.get("custom.modules", "allModules", []) + if module_name not in all_modules: + all_modules.append(module_name) + db.set("custom.modules", "allModules", all_modules) + await message.edit( + f"The module {module_name} is loaded!\nRestarting..." + ) + db.set( + "core.updater", + "restart_info", + { + "type": "restart", + "chat_id": message.chat.id, + "message_id": message.id, + }, + ) + restart() + + +@Client.on_message(filters.command(["unloadmod", "ulm"], prefix) & filters.me) +async def unload_mods(_, message: Message): + if len(message.command) <= 1: + return + + module_name = message.command[1].lower() + + if module_name.startswith( + "https://raw.githubusercontent.com/The-MoonTg-project/custom_modules/main/" + ): + module_name = module_name.split("/")[-1].split(".")[0] + + if os.path.exists(f"{BASE_PATH}/modules/custom_modules/{module_name}.py"): + os.remove(f"{BASE_PATH}/modules/custom_modules/{module_name}.py") + if module_name == "musicbot": + subprocess.run( + [sys.executable, "-m", "pip", "uninstall", "-y", "requirements.txt"], + cwd=f"{BASE_PATH}/musicbot", + ) + shutil.rmtree(f"{BASE_PATH}/musicbot") + all_modules = db.get("custom.modules", "allModules", []) + if module_name in all_modules: + all_modules.remove(module_name) + db.set("custom.modules", "allModules", all_modules) + await message.edit( + f"The module {module_name} removed!\nRestarting..." + ) + db.set( + "core.updater", + "restart_info", + { + "type": "restart", + "chat_id": message.chat.id, + "message_id": message.id, + }, + ) + restart() + elif os.path.exists(f"{BASE_PATH}/modules/{module_name}.py"): + await message.edit( + "It is forbidden to remove built-in modules, it will disrupt the updater" + ) + else: + await message.edit(f"Module {module_name} is not found") + + +@Client.on_message(filters.command(["loadallmods", "lmall"], prefix) & filters.me) +async def load_all_mods(_, message: Message): + await message.edit("Fetching info...") + + if not os.path.exists(f"{BASE_PATH}/modules/custom_modules"): + os.mkdir(f"{BASE_PATH}/modules/custom_modules") + + try: + f = requests.get( + "https://raw.githubusercontent.com/The-MoonTg-project/custom_modules/main/full.txt" + ).text + except Exception: + return await message.edit("Failed to fetch custom modules list") + modules_list = f.splitlines() + + await message.edit("Loading modules...") + for module_name in modules_list: + url = f"https://raw.githubusercontent.com/The-MoonTg-project/custom_modules/main/{module_name}.py" + resp = requests.get(url) + if not resp.ok: + continue + with open( + f"./modules/custom_modules/{module_name.split('/')[1]}.py", "wb" + ) as f: + f.write(resp.content) + + await message.edit( + f"Successfully loaded new modules: {len(modules_list)}\nRestarting...", + ) + db.set( + "core.updater", + "restart_info", + { + "type": "restart", + "chat_id": message.chat.id, + "message_id": message.id, + }, + ) + restart() + + +@Client.on_message(filters.command(["unloadallmods", "ulmall"], prefix) & filters.me) +async def unload_all_mods(_, message: Message): + await message.edit("Fetching info...") + + if not os.path.exists(f"{BASE_PATH}/modules/custom_modules"): + return await message.edit("You don't have any modules installed") + shutil.rmtree(f"{BASE_PATH}/modules/custom_modules") + db.set("custom.modules", "allModules", []) + await message.edit("Successfully unloaded all modules!\nRestarting...") + + db.set( + "core.updater", + "restart_info", + { + "type": "restart", + "chat_id": message.chat.id, + "message_id": message.id, + }, + ) + restart() + + +@Client.on_message(filters.command(["updateallmods"], prefix) & filters.me) +async def updateallmods(_, message: Message): + await message.edit("Updating modules...") + + if not os.path.exists(f"{BASE_PATH}/modules/custom_modules"): + os.mkdir(f"{BASE_PATH}/modules/custom_modules") + + modules_installed = list(os.walk("modules/custom_modules"))[0][2] + + if not modules_installed: + return await message.edit("You don't have any modules installed") + + for module_name in modules_installed: + if not module_name.endswith(".py"): + continue + try: + f = requests.get( + "https://raw.githubusercontent.com/The-MoonTg-project/custom_modules/main/full.txt" + ).text + except Exception: + return await message.edit("Failed to fetch custom modules list") + modules_dict = {line.split("/")[-1].split()[0]: line.strip() for line in f.splitlines()} + if module_name in modules_dict: + resp = requests.get( + f"https://raw.githubusercontent.com/The-MoonTg-project/custom_modules/main/{modules_dict[module_name]}.py" + ) + if not resp.ok: + modules_installed.remove(module_name) + continue + + with open(f"./modules/custom_modules/{module_name}", "wb") as f: + f.write(resp.content) + + await message.edit(f"Successfully updated {len(modules_installed)} modules") + + +modules_help["loader"] = { + "loadmod [module_name]*": "Download module.\n" + "Only modules from the official custom_modules repository and proven " + "modules whose hashes are in modules_hashes.txt are supported", + "unloadmod [module_name]*": "Delete module", + "modhash [link]*": "Get module hash by link", + "loadallmods": "Load all custom modules (use it at your own risk)", + "unloadallmods": "Unload all custom modules", + "updateallmods": "Update all custom modules" + "\n\n* - required argument" + "\n short cmds:" + "\n loadmod - lm" + "\n unloadmod - ulm" + "\n modhash - mh" + "\n loadallmods - lmall" + "\n unloadallmods - ulmall", +} diff --git a/modules/markitdown.py b/modules/markitdown.py new file mode 100644 index 0000000..10b5243 --- /dev/null +++ b/modules/markitdown.py @@ -0,0 +1,51 @@ +#  Moon-Userbot - telegram userbot +#  Copyright (C) 2020-present Moon Userbot Organization +# +#  This program is free software: you can redistribute it and/or modify +#  it under the terms of the GNU General Public License as published by +#  the Free Software Foundation, either version 3 of the License, or +#  (at your option) any later version. + +#  This program is distributed in the hope that it will be useful, +#  but WITHOUT ANY WARRANTY; without even the implied warranty of +#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +#  GNU General Public License for more details. + +#  You should have received a copy of the GNU General Public License +#  along with this program.  If not, see . + +import os +from pyrogram import Client, filters +from pyrogram.types import Message + +from utils.misc import modules_help +from utils.scripts import prefix, import_library, with_reply + +import_library("markitdown") + +from markitdown import MarkItDown + + +@Client.on_message(filters.command(["markitdown", "mkdn"], prefix) & filters.me) +@with_reply +async def markitdown(client: Client, message: Message): + if message.reply_to_message.document: + await message.edit("Converting to Markdown...") + file = await message.reply_to_message.download() + file_name = (message.reply_to_message.document.file_name).split(".")[0] + ".md" + markitdown = MarkItDown() + result = markitdown.convert(file) + with open(file_name, "w") as f: + f.write(result.text_content) + await message.edit("Uploading...") + await client.send_document( + message.chat.id, file_name, reply_to_message_id=message.reply_to_message.id + ) + os.remove(file) + os.remove(file_name) + await message.delete() + else: + await message.edit("Reply to a document to convert it to Markdown.") + + +modules_help["markitdown"] = {"markitdown": "Convert a document to Markdown."} diff --git a/modules/mention.py b/modules/mention.py new file mode 100644 index 0000000..05db582 --- /dev/null +++ b/modules/mention.py @@ -0,0 +1,72 @@ +#  Moon-Userbot - telegram userbot +#  Copyright (C) 2020-present Moon Userbot Organization +# +#  This program is free software: you can redistribute it and/or modify +#  it under the terms of the GNU General Public License as published by +#  the Free Software Foundation, either version 3 of the License, or +#  (at your option) any later version. + +#  This program is distributed in the hope that it will be useful, +#  but WITHOUT ANY WARRANTY; without even the implied warranty of +#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +#  GNU General Public License for more details. + +#  You should have received a copy of the GNU General Public License +#  along with this program.  If not, see . + +from pyrogram import Client, filters +from pyrogram.types import Message +from pyrogram.types.user_and_chats.user import Link + +from utils.misc import modules_help, prefix + + +def custom_mention(user, custom_text): + return Link( + f"tg://user?id={user.id}", + custom_text, + user._client.parse_mode, + ) + + +@Client.on_message(filters.command("mention", prefix) & filters.me) +async def example_edit(client: Client, message: Message): + chat_id = message.chat.id + if message.reply_to_message and not len(message.text.split()) > 1: + user = message.reply_to_message.from_user + custom_text = ( + message.text.split(maxsplit=1)[1] if len(message.text.split()) > 1 else None + ) + if custom_text: + await message.edit(custom_mention(user, custom_text)) + else: + await message.delete() + await client.send_message(chat_id, user.mention) + else: + if len(message.text.split()) > 1: + user_id = message.text.split()[1] + if user_id.isdigit(): + text = ( + message.text.split(maxsplit=2)[2] + if len(message.text.split()) > 2 + else None + ) + if text: + men = Link(f"tg://user?id={user_id}", text, client.parse_mode) + else: + men = (await client.get_users(user_id)).mention + await message.edit(men) + else: + await message.edit("Invalid user_id") + await message.delete() + else: + await message.edit("Reply to a message or provide a user_id") + await message.delete() + + +modules_help["mention"] = { + "mention": "Mention the user you replied to.", + "mention [custom_text]": "Mention the user you replied to with custom text.", + "mention [user_id] [custom_text]": "Mention a user by their user_id with custom text.", + "mention [user_id]": "Mention a user by their user_id.", +} diff --git a/modules/mirror_flip.py b/modules/mirror_flip.py new file mode 100644 index 0000000..c635094 --- /dev/null +++ b/modules/mirror_flip.py @@ -0,0 +1,61 @@ +# original module https://raw.githubusercontent.com/KeyZenD/modules/master/MirrorFlipV2.py | t.me/the_kzd +import os + + +from pyrogram import Client, filters, enums +from pyrogram.types import Message + +from utils.misc import modules_help, prefix +from utils.scripts import import_library + +PIL = import_library("PIL", "pillow") +from PIL import Image, ImageOps + + +async def make(client, message, o): + reply = message.reply_to_message + if reply.photo or reply.sticker: + if reply.photo: + downloads = await client.download_media(reply.photo.file_id) + else: + downloads = await client.download_media(reply.sticker.file_id) + path = f"{downloads}" + img = Image.open(path) + await message.delete() + w, h = img.size + if o in [1, 2]: + if o == 2: + img = ImageOps.mirror(img) + part = img.crop([0, 0, w // 2, h]) + img = ImageOps.mirror(img) + else: + if o == 4: + img = ImageOps.flip(img) + part = img.crop([0, 0, w, h // 2]) + img = ImageOps.flip(img) + img.paste(part, (0, 0)) + img.save(path) + if reply.photo: + return await reply.reply_photo(photo=path) + elif reply.sticker: + return await reply.reply_sticker(sticker=path) + os.remove(path) + + return await message.edit( + "Need to answer the photo/sticker", parse_mode=enums.ParseMode.HTML + ) + + +@Client.on_message(filters.command(["ll", "rr", "dd", "uu"], prefix) & filters.me) +async def mirror_flip(client: Client, message: Message): + await message.edit("Processing...", parse_mode=enums.ParseMode.HTML) + param = {"ll": 1, "rr": 2, "dd": 3, "uu": 4}[message.command[0]] + await make(client, message, param) + + +modules_help["mirror_flip"] = { + "ll [reply on photo or sticker]*": "reflects the left side", + "rr [reply on photo or sticker]*": "reflects the right side", + "uu [reply on photo or sticker]*": "reflects the top", + "dd [reply on photo or sticker]*": "reflects the bottom", +} diff --git a/modules/misc/autobackup.py b/modules/misc/autobackup.py new file mode 100644 index 0000000..bfbc297 --- /dev/null +++ b/modules/misc/autobackup.py @@ -0,0 +1,59 @@ +from pyrogram import Client, filters, enums +from pyrogram.types import Message + +from os import listdir + +# noinspection PyUnresolvedReferences +from utils.misc import modules_help, prefix + +# noinspection PyUnresolvedReferences +from utils.scripts import format_exc + + +@Client.on_message(filters.command(["lback"], prefix) & filters.me) +async def backup_database_cmd(_: Client, message: Message): + """ + Backup the database. + """ + if len(message.command) == 1: + await message.edit("[😇] I think you didn't specify the name of the bot.") + return + await message.edit_text( + "I'm copying the database...", parse_mode=enums.ParseMode.HTML + ) + try: + name = message.command[1].lower() + folders = listdir("/root/") + if name not in folders: + await message.edit("[😇] There is no such bot in the root folder.") + return + folder = listdir("/root/" + name) + for file in folder: + if file.endswith((".db", ".sqlite", ".sqlite3")): + await message.reply_document( + document="/root/" + name + "/" + file, + caption="Bot Database " + name + "", + parse_mode=enums.ParseMode.HTML, + ) + return await message.delete() + folder = listdir("/root/" + name + "/assets") + for file in folder: + if file.endswith((".db", ".sqlite", ".sqlite3")): + await message.reply_document( + document="/root/" + name + "/assets/" + file, + caption="Bot Database " + name + "", + parse_mode=enums.ParseMode.HTML, + ) + return await message.delete() + await message.edit("[😇] Database not found.") + except Exception as ex: + await message.edit_text( + "Failed to back up the database!\n\n" f"{format_exc(ex)}", + parse_mode=enums.ParseMode.HTML, + ) + + +modules_help["autobackup"] = { + "lback [name]*": "Backup database from folder", + "lbackall": "Backup all databases", +} diff --git a/modules/misc/autofwd.py b/modules/misc/autofwd.py new file mode 100644 index 0000000..03f1bc9 --- /dev/null +++ b/modules/misc/autofwd.py @@ -0,0 +1,177 @@ +#  Moon-Userbot - telegram userbot +#  Copyright (C) 2020-present Moon Userbot Organization +# +#  This program is free software: you can redistribute it and/or modify +#  it under the terms of the GNU General Public License as published by +#  the Free Software Foundation, either version 3 of the License, or +#  (at your option) any later version. + +#  This program is distributed in the hope that it will be useful, +#  but WITHOUT ANY WARRANTY; without even the implied warranty of +#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +#  GNU General Public License for more details. + +#  You should have received a copy of the GNU General Public License +#  along with this program.  If not, see . + +from pyrogram import Client, filters +from pyrogram.types import Message +from pyrogram.errors import MessageTooLong + +from utils.misc import modules_help, prefix +from utils.db import db + + +def addtrg(channel_id): + channel_ids = db.get("custom.autofwd", "chatto", default=[]) + if channel_id not in channel_ids: + channel_ids.append(channel_id) + db.set("custom.autofwd", "chatto", channel_ids) + + +def rmtrg(channel_id): + channel_ids = db.get("custom.autofwd", "chatto", default=[]) + if channel_id in channel_ids: + channel_ids.remove(channel_id) + db.set("custom.autofwd", "chatto", channel_ids) + + +def addsrc(channel_id): + channel_ids = db.get("custom.autofwd", "chatsrc", default=[]) + if channel_id not in channel_ids: + channel_ids.append(channel_id) + db.set("custom.autofwd", "chatsrc", channel_ids) + + +def rmsrc(channel_id): + channel_ids = db.get("custom.autofwd", "chatsrc", default=[]) + if channel_id in channel_ids: + channel_ids.remove(channel_id) + db.set("custom.autofwd", "chatsrc", channel_ids) + + +def getfwd_data(): + source_chats = db.get("custom.autofwd", "chatsrc") + target_chats = db.get("custom.autofwd", "chatto") + return source_chats, target_chats + + +@Client.on_message(filters.command(["addfwd_src", "addfwd_to"], prefix) & filters.me) +async def addfwd(_, message: Message): + if message.command[0] == "addfwd_src": + if len(message.command) > 1: + channel_id = message.text.split(maxsplit=1)[1] + if not channel_id.startswith("-100"): + channel_id = "-100" + channel_id + # chat id should be integer + if not channel_id.isdigit(): + try: + channel_id = int(channel_id) + except Exception: + return await message.edit_text("Chat id should be in integer") + addsrc(channel_id=channel_id) + await message.edit_text( + f"Auto Forwarding Enabled for Chat with id: {channel_id}" + ) + else: + await message.edit_text("Chat id not provided!") + return + elif message.command[0] == "addfwd_to": + if len(message.command) > 1: + channel_id = message.text.split(maxsplit=1)[1] + if not channel_id.startswith("-100"): + channel_id = "-100" + channel_id + # chat id should be integer + if not channel_id.isdigit(): + try: + channel_id = int(channel_id) + except Exception: + return await message.edit_text("Chat id should be in integer") + addtrg(channel_id=channel_id) + await message.edit_text( + f"Auto Forwarding Enabled to Chat with id: {channel_id}" + ) + else: + await message.edit_text("Chat id not provided!") + return + + +@Client.on_message(filters.command(["delfwd_src", "delfwd_to"], prefix) & filters.me) +async def delfwd(_, message: Message): + if message.command[0] == "delfwd_src": + if len(message.command) > 1: + channel_id = message.text.split(maxsplit=1)[1] + if not channel_id.startswith("-100"): + channel_id = "-100" + channel_id + # chat id should be integer + if not channel_id.isdigit(): + try: + channel_id = int(channel_id) + except Exception: + return await message.edit_text("Chat id should be in integer") + rmsrc(channel_id=channel_id) + await message.edit_text( + f"Auto Forwarding Disabled for Chat with id: {channel_id}" + ) + else: + await message.edit_text("Chat id not provided!") + return + elif message.command[0] == "delfwd_to": + if len(message.command) > 1: + channel_id = message.text.split(maxsplit=1)[1] + if not channel_id.startswith("-100"): + channel_id = "-100" + channel_id + # chat id should be integer + if not channel_id.isdigit(): + try: + channel_id = int(channel_id) + except Exception: + return await message.edit_text("Chat id should be in integer") + rmtrg(channel_id=channel_id) + await message.edit_text( + f"Auto Forwarding Disabled to Chat with id: {channel_id}" + ) + else: + await message.edit_text("Chat id not provided!") + return + + +@Client.on_message(filters.command("autofwd", prefix) & filters.me) +async def autofwd(_, message: Message): + source_chats, target_chats = getfwd_data() + return await message.edit_text( + f"Source Chats: {source_chats}\nTarget Chats: {target_chats}" + ) + + +@Client.on_message(filters.channel | filters.group) +async def autofwd_main(client: Client, message: Message): + chat_id = message.chat.id + source_chats = db.get("custom.autofwd", "chatsrc") + target_chats = db.get("custom.autofwd", "chatto") + + if source_chats is not None and chat_id in source_chats: + if target_chats is not None: + for chat in target_chats: + try: + await message.copy(chat) + except Exception as e: + try: + await client.send_message( + "me", + f"Auto Forwarding Failed for Chat with id: {chat_id} to {chat}\n\n{e}", + ) + except MessageTooLong: + await client.send_message( + "me", + f"Auto Forwarding Failed for Chat with id: {chat_id} to {chat}, Please check logs!", + ) + + +modules_help["autofwd"] = { + "autofwd": "Retrieve Data of auto fwd", + "addfwd_src [channel_id]*": "Enable auto forwarding for a channel", + "addfwd_to [channel_id]*": "Enable auto forwarding to a channel", + "delfwd_src [channel_id]*": "Disable auto forwarding for a channel", + "delfwd_to [channel_id]*": "Disable auto forwarding to a channel", +} diff --git a/modules/misc/backup.py b/modules/misc/backup.py new file mode 100644 index 0000000..49ffb2f --- /dev/null +++ b/modules/misc/backup.py @@ -0,0 +1,251 @@ +import os + +import shutil + +from pyrogram import Client, filters, enums +from pyrogram.types import Message + +# noinspection PyUnresolvedReferences +from utils.misc import modules_help, prefix +from utils.scripts import format_exc, restart +from utils.db import db +from utils import config + +if config.db_type in ["mongodb", "mongo"]: + import bson + + +def dump_mongo(collections, path, db_): + for coll in collections: + with open(os.path.join(path, f"{coll}.bson"), "wb+") as f: + for doc in db_[coll].find(): + f.write(bson.BSON.encode(doc)) + + +def restore_mongo(path, db_): + for coll in os.listdir(path): + if coll.endswith(".bson"): + with open(os.path.join(path, coll), "rb+") as f: + db_[coll.split(".")[0]].insert_many(bson.decode_all(f.read())) + + +@Client.on_message(filters.command(["backup", "back"], prefix) & filters.me) +async def backup(client: Client, message: Message): + """ + Backup the database + """ + try: + if not os.path.exists("backups/"): + os.mkdir("backups/") + + await message.edit( + "Backing up database...", parse_mode=enums.ParseMode.HTML + ) + + if config.db_type in ["mongo", "mongodb"]: + dump_mongo(db._database.list_collection_names(), "backups/", db._database) + return await message.edit( + f"Database backed up to: backups/ folder", + parse_mode=enums.ParseMode.HTML, + ) + else: + shutil.copy(config.db_name, f"backups/{config.db_name}") + await client.send_document( + "me", + caption=f"Database backup complete!\nType: " + f".restore in response to this message to restore the database.", + document=f"backups/{config.db_name}", + parse_mode=enums.ParseMode.HTML, + ) + return await message.edit( + "Database backed up successfully! (Check your favorites)", + parse_mode=enums.ParseMode.HTML, + ) + except Exception as e: + await message.edit(format_exc(e), parse_mode=enums.ParseMode.HTML) + + +@Client.on_message(filters.command(["restore", "res"], prefix) & filters.me) +async def restore(client: Client, message: Message): + """ + Restore the database + """ + try: + await message.edit( + "Restoring database...", parse_mode=enums.ParseMode.HTML + ) + if config.db_type in ["mongo", "mongodb"]: + restore_mongo("backups/", db._database) + return await message.edit( + f"Database restored from: backups/ folder", + parse_mode=enums.ParseMode.HTML, + ) + else: + if not message.reply_to_message or not message.reply_to_message.document: + return await message.edit( + "Reply to a document to restore the database.", + parse_mode=enums.ParseMode.HTML, + ) + elif not message.reply_to_message.document.file_name.casefold().endswith( + (".db", ".sqlite", ".sqlite3") + ): + return await message.edit( + "Reply to a database file to restore the database.", + parse_mode=enums.ParseMode.HTML, + ) + await message.reply_to_message.download( + f"backups/{message.reply_to_message.document.file_name}" + ) + shutil.copy( + f"backups/{message.reply_to_message.document.file_name}", config.db_name + ) + await message.edit( + "Database restored successfully!", + parse_mode=enums.ParseMode.HTML, + ) + restart() + except Exception as e: + await message.edit(format_exc(e), parse_mode=enums.ParseMode.HTML) + + +@Client.on_message(filters.command(["backupmods", "bms"], prefix) & filters.me) +async def backupmods(client: Client, message: Message): + """ + Backup the modules + """ + try: + if not os.path.exists("backups/"): + os.mkdir("backups/") + + await message.edit( + "Backing up modules...", parse_mode=enums.ParseMode.HTML + ) + + from utils.misc import modules_help + + for mod in modules_help: + if os.path.isfile(f"modules/custom_modules/{mod}.py"): + f = open(f"backups/{mod}.py", "wb") + f.write(open(f"modules/custom_modules/{mod}.py", "rb").read()) + await message.edit( + text=f"All modules backed up to: backups/ folder", + parse_mode=enums.ParseMode.HTML, + ) + except Exception as e: + await message.edit(format_exc(e), parse_mode=enums.ParseMode.HTML) + + +@Client.on_message(filters.command(["backupmod", "bm"], prefix) & filters.me) +async def backupmod(client: Client, message: Message): + """ + Backup the module + """ + try: + if not os.path.exists("backups/"): + os.mkdir("backups/") + from utils.misc import modules_help + + try: + mod = message.text.split(maxsplit=1)[1].split(".")[0] + except: + return await message.edit( + f"Usage: {prefix}backupmod [module]", + parse_mode=enums.ParseMode.HTML, + ) + + await message.edit( + "Backing up module...", parse_mode=enums.ParseMode.HTML + ) + + if os.path.isfile(f"modules/custom_modules/{mod}.py"): + f = open(f"backups/{mod}.py", "wb") + f.write(open(f"modules/custom_modules/{mod}.py", "rb").read()) + else: + return await message.edit( + f"Module {mod} not found.", + parse_mode=enums.ParseMode.HTML, + ) + await message.reply_document( + document=f"backups/{mod}.py", + caption=f"Module {mod} backed up to: backups/ folder", + parse_mode=enums.ParseMode.HTML, + ) + except Exception as e: + await message.edit(format_exc(e), parse_mode=enums.ParseMode.HTML) + + +@Client.on_message(filters.command(["restoremod", "resmod"], prefix) & filters.me) +async def restoremod(client: Client, message: Message): + """ + Restore the module + """ + try: + if not os.path.exists("backups/"): + os.mkdir("backups/") + from utils.misc import modules_help + + try: + mod = message.text.split(maxsplit=1)[1].split(".")[0] + except: + return await message.edit( + f"Usage: {prefix}restoremod [module]", + parse_mode=enums.ParseMode.HTML, + ) + + await message.edit( + "Restoring module...", parse_mode=enums.ParseMode.HTML + ) + + if os.path.isfile(f"backups/{mod}.py"): + f = open(f"modules/custom_modules/{mod}.py", "wb") + f.write(open(f"backups/{mod}.py", "rb").read()) + else: + return await message.edit( + f"Module {mod} not found.", + parse_mode=enums.ParseMode.HTML, + ) + await message.edit( + f"Module {mod} restored successfully!", + parse_mode=enums.ParseMode.HTML, + ) + restart() + except Exception as e: + await message.edit(format_exc(e), parse_mode=enums.ParseMode.HTML) + + +@Client.on_message(filters.command(["restoremods", "resmods"], prefix) & filters.me) +async def restoremods(client: Client, message: Message): + """ + Restore the modules + """ + try: + if not os.path.exists("backups/"): + os.mkdir("backups/") + + await message.edit( + "Restoring modules...", parse_mode=enums.ParseMode.HTML + ) + + for mod in os.listdir("backups/"): + if mod.endswith(".py"): + if os.path.isfile(f"modules/{mod}"): + continue + f = open(f"modules/custom_modules/{mod}", "wb") + f.write(open(f"backups/{mod}", "rb").read()) + await message.edit( + text=f"All modules restored from: backups/ folder", + parse_mode=enums.ParseMode.HTML, + ) + restart() + except Exception as e: + await message.edit(format_exc(e), parse_mode=enums.ParseMode.HTML) + + +modules_help["backup"] = { + "backup": f"Backup database", + "restore [reply]": f"Restore database", + "backupmod [name]": f"Backup mod", + "backupmods": f"Backup all mods", + "resmod [name]": f"Restore mod", + "resmods": f"Restore all mods", +} diff --git a/modules/misc/cama.py b/modules/misc/cama.py new file mode 100644 index 0000000..ed51d42 --- /dev/null +++ b/modules/misc/cama.py @@ -0,0 +1,109 @@ +from pyrogram import Client, filters +import requests + +from utils.scripts import format_exc, import_library + +from utils.misc import modules_help, prefix + +pcp = import_library("pubchempy") + +INATURALIST_API_URL = "https://api.inaturalist.org/v1/observations" + + +def get_marine_life_details(species_name): + params = { + "taxon_name": species_name, + "quality_grade": "research", + "iconic_taxa": "Mollusca,Fish,Crustacea", + "per_page": 1, + } + + response = requests.get(INATURALIST_API_URL, params=params) + + if response.status_code == 200: + data = response.json() + if data["total_results"] > 0: + observation = data["results"][0] + species = observation["taxon"]["name"] + common_name = observation["taxon"]["preferred_common_name"] + photo_url = ( + observation["photos"][0]["url"] + if observation["photos"] + else "No photo available" + ) + description = ( + observation["description"] + if "description" in observation + else "No description available." + ) + return { + "species": species, + "common_name": common_name, + "photo_url": photo_url, + "description": description, + } + else: + return {"error": "No marine life found for this species."} + else: + return { + "error": f"Error {response.status_code}: Unable to connect to iNaturalist API." + } + + +@Client.on_message(filters.command("camistry", prefix) & filters.me) +async def fetch_chemical_data_with_visual(_, message): + query = " ".join(message.text.split()[1:]) # Combine query words properly + + try: + # Fetch chemical data by name + results = pcp.get_compounds(query, "name", record_type="3d") + + if results: + compound = results[0] + + # Send chemical information without SMILES structure + info = ( + f"Chemical Data for {compound.iupac_name}\n\n" + f"Molecular Formula: {compound.molecular_formula}\n" + f"Molecular Weight: {compound.molecular_weight}\n" + f"CID: {compound.cid}\n" + f"Synonyms: {', '.join(compound.synonyms[:5])}\n" + ) + await message.edit_text(info) + else: + await message.edit_text(f"No chemical data found for the query: '{query}'") + + except pcp.PubChemHTTPError as http_err: + await message.edit_text(f"HTTP error occurred: {format_exc(http_err)}") + + except Exception as e: + await message.edit_text(f"An error occurred: {format_exc(e)}") + + +@Client.on_message(filters.command("marinelife", prefix) & filters.me) +async def marine_life_command(_, message): + if len(message.command) < 2: + await message.edit_text( + f"Please specify a species name. Example: {prefix}marinelife dolphin" + ) + return + + species_name = " ".join(message.command[1:]) + marine_life = get_marine_life_details(species_name) + + if "error" in marine_life: + await message.edit_text(marine_life["error"]) + else: + reply_text = ( + f"Species: {marine_life['species']}\n" + f"Common Name: {marine_life['common_name']}\n" + f"Description: {marine_life['description']}\n" + f"Photo" + ) + await message.edit_text(reply_text) + + +modules_help["cama"] = { + "camistry [text]": " getting camicale info", + "marinelife [text]": " getting marinelife info", +} diff --git a/modules/misc/mlog.py b/modules/misc/mlog.py new file mode 100644 index 0000000..31e5d0b --- /dev/null +++ b/modules/misc/mlog.py @@ -0,0 +1,144 @@ +import asyncio +import os +from pyrogram import Client, filters +from pyrogram.errors import ( + FileReferenceExpired, + FileReferenceInvalid, + TopicDeleted, + TopicClosed, +) +from pyrogram.types import Message +from collections import defaultdict + +from utils.db import db +from utils.misc import modules_help, prefix + +mlog_enabled = filters.create(lambda _, __, ___: db.get("custom.mlog", "status", False)) + +# Media cache and processing tasks +user_media_cache = defaultdict(list) +media_processing_tasks = {} + +# Helper to get group-specific data +def get_group_data(group_id): + return db.get(f"custom.mlog", str(group_id), {}) + +# Helper to update group-specific data +def update_group_data(group_id, data): + db.set(f"custom.mlog", str(group_id), data) + + +@Client.on_message(filters.command(["mlog"], prefix) & filters.me) +async def mlog(_, message: Message): + if len(message.command) < 2 or message.command[1].lower() not in ["on", "off"]: + return await message.edit(f"Usage: {prefix}mlog [on/off]") + + status = message.command[1].lower() == "on" + db.set("custom.mlog", "status", status) + await message.edit(f"Media logging is now {'enabled' if status else 'disabled'}") + + +@Client.on_message(filters.command(["msetchat"], prefix) & filters.me) +async def set_chat(_, message: Message): + if len(message.command) < 2: + return await message.edit(f"Usage: {prefix}msetchat [chat_id]") + + try: + chat_id = message.command[1] + chat_id = int("-100" + chat_id if not chat_id.startswith("-100") else chat_id) + db.set("custom.mlog", "chat", chat_id) + await message.edit(f"Chat ID set to {chat_id}") + except ValueError: + await message.edit("Invalid chat ID") + + +@Client.on_message( + mlog_enabled + & filters.incoming + & filters.private + & filters.media + & ~filters.me + & ~filters.bot +) +async def media_log(client: Client, message: Message): + user_id = message.from_user.id + user_media_cache[user_id].append(message) + + if user_id not in media_processing_tasks: + media_processing_tasks[user_id] = asyncio.create_task(process_media(client, message.from_user)) + + +async def process_media(client: Client, user): + await asyncio.sleep(5) # Wait to group incoming media + user_id = user.id + + me = await client.get_me() + if user_id == me.id: + return + + chat_id = db.get("custom.mlog", "chat") + if not chat_id: + return await client.send_message( + "me", + f"Media Logger is on, but no Chat ID is set. Use {prefix}msetchat to set it.", + ) + + group_data = get_group_data(chat_id) + user_topics = group_data.get("user_topics", {}) + topic_id = user_topics.get(str(user_id)) # Fetch user's topic ID if it exists + + if not topic_id: + topic = await client.create_forum_topic(chat_id, user.first_name) + topic_id = topic.id + user_topics[str(user_id)] = topic_id # Store topic ID for this user + update_group_data(chat_id, {"user_topics": user_topics}) + m = await client.send_message( + chat_id=chat_id, + message_thread_id=topic_id, + text=f"Chat Name: {user.full_name}\nUser ID: {user_id}\nUsername: @{user.username or 'N/A'}\nPhone No: +{user.phone_number or 'N/A'}", + ) + await m.pin() + + messages_to_process = user_media_cache.pop(user_id, []) + for media_message in messages_to_process: + try: + await media_message.copy(chat_id=chat_id, message_thread_id=topic_id) + await asyncio.sleep(1) # Delay between sending media + except (FileReferenceExpired, FileReferenceInvalid): + # Handle self-destruct photos and video notes + await handle_self_destruct_media(client, media_message, chat_id, topic_id) + except TopicDeleted: + topic = await client.create_forum_topic(chat_id, user.first_name) + topic_id = topic.id + user_topics[str(user_id)] = topic_id # Update the new topic ID + update_group_data(chat_id, {"user_topics": user_topics}) + await client.send_message( + chat_id=chat_id, + message_thread_id=topic_id, + text=f"Chat Name: {user.full_name}\nUser ID: {user_id}\nUsername: @{user.username or 'N/A'}\nPhone No: +{user.phone_number or 'N/A'}", + ) + await handle_self_destruct_media(client, media_message, chat_id, topic_id) + except TopicClosed: + await client.reopen_forum_topic(chat_id=chat_id, topic_id=topic_id) + await media_message.copy(chat_id=chat_id, message_thread_id=topic_id) + + media_processing_tasks.pop(user_id, None) + + +async def handle_self_destruct_media(client: Client, message: Message, chat_id: int, topic_id: int): + try: + # Download the self-destructing media + file_path = await message.download() + if message.photo: + await client.send_photo(chat_id, file_path, message_thread_id=topic_id) + elif message.video_note: + await client.send_video(chat_id, file_path, message_thread_id=topic_id) + os.remove(file_path) # Clean up after sending + except Exception as e: + print(f"Error handling self-destructing media: {e}") + + +modules_help["mlog"] = { + "mlog [on/off]": "Enable or disable media logging", + "msetchat [chat_id]": "Set the chat ID for media logging", +} diff --git a/modules/misc/prayer.py b/modules/misc/prayer.py new file mode 100644 index 0000000..83219ee --- /dev/null +++ b/modules/misc/prayer.py @@ -0,0 +1,90 @@ +from pyrogram import Client, filters +from pyrogram.types import Message +import aiohttp +from datetime import datetime + +from utils.misc import modules_help, prefix + +# Aladhan API credentials +ALADHAN_API_URL = "https://api.aladhan.com/v1/timingsByCity" +DEFAULT_METHOD = 2 # Islamic Society of North America +DEFAULT_CITY = "Lahore" +DEFAULT_COUNTRY = "PK" + + +async def fetch_namaz_times(city_name: str, country_name: str) -> dict: + params = {"city": city_name, "country": country_name, "method": DEFAULT_METHOD} + async with aiohttp.ClientSession() as session: + try: + async with session.get(ALADHAN_API_URL, params=params) as response: + response.raise_for_status() + return await response.json() + except Exception as e: + return {"error": str(e)} + + +def format_time_12hr(time_str: str) -> str: + try: + # Split time into hours and minutes + hours, minutes = map(int, time_str.split(":")) + # Convert to 12-hour format + period = "AM" if hours < 12 else "PM" + if hours == 0: + hours = 12 + elif hours > 12: + hours -= 12 + elif hours == 12: + period = "PM" + return f"{hours}:{minutes:02d} {period}" + except Exception as e: + return f"Error formatting time: {str(e)}" + + +@Client.on_message(filters.command("prayer", prefix) & filters.me) +async def namaz_times(client: Client, message: Message): + if message.reply_to_message: + city_name = message.reply_to_message.text + country_name = DEFAULT_COUNTRY # Default to Pakistan if no country is provided + else: + args = message.text.split(maxsplit=2) + if len(args) < 2: + city_name = DEFAULT_CITY + country_name = DEFAULT_COUNTRY + elif len(args) == 2: + city_name = args[1] + country_name = DEFAULT_COUNTRY + else: + city_name = args[1] + country_name = args[2] + + data = await fetch_namaz_times(city_name, country_name) + + if "error" in data: + result = f"Error: {data['error']}" + elif "data" in data: + timings = data["data"]["timings"] + today = datetime.now().strftime("%Y-%m-%d") + formatted_timings = { + "Fajr": format_time_12hr(timings["Fajr"]), + "Dhuhr": format_time_12hr(timings["Dhuhr"]), + "Asr": format_time_12hr(timings["Asr"]), + "Maghrib": format_time_12hr(timings["Maghrib"]), + "Isha": format_time_12hr(timings["Isha"]), + } + result = ( + f"Prayer Times for {city_name}, {country_name} on {today}:\n\n" + f"Fajr: {formatted_timings['Fajr']}\n" + f"Dhuhr: {formatted_timings['Dhuhr']}\n" + f"Asr: {formatted_timings['Asr']}\n" + f"Maghrib: {formatted_timings['Maghrib']}\n" + f"Isha: {formatted_timings['Isha']}\n" + ) + else: + result = "Error: Unable to get prayer times for the specified location." + + await message.edit_text(result) + + +modules_help["namaz"] = { + "prayer [city_name] [country_name]": "Shows the prayer times. Default to Pakistan if no country is provided" +} diff --git a/modules/misc/safone.py b/modules/misc/safone.py new file mode 100644 index 0000000..f97f2b9 --- /dev/null +++ b/modules/misc/safone.py @@ -0,0 +1,534 @@ +# Moon-Userbot - telegram userbot +# Copyright (C) 2020-present Moon Userbot Organization +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import os +import requests +import aiofiles +import base64 + +from pyrogram import Client, enums, filters +from pyrogram.types import Message, InputMediaPhoto +from pyrogram.errors import MediaCaptionTooLong, MessageTooLong + +from utils.misc import prefix, modules_help +from utils.scripts import format_exc + +url = "https://api.safone.co" + +headers = { + "Accept-Language": "en-US,en;q=0.9", + "Connection": "keep-alive", + "DNT": "1", + "Referer": "https://api.safone.dev/docs", + "Sec-Fetch-Dest": "empty", + "Sec-Fetch-Mode": "cors", + "Sec-Fetch-Site": "same-origin", + "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36", + "accept": "application/json", + "sec-ch-ua": '"Chromium";v="124", "Google Chrome";v="124", "Not-A.Brand";v="99"', + "sec-ch-ua-mobile": "?0", + "sec-ch-ua-platform": '"Linux"', +} + +async def make_carbon(code): + url = "https://carbonara.solopov.dev/api/cook" + + async with aiohttp.ClientSession() as session: + async with session.post(url, json={"code": code}) as resp: + image_data = await resp.read() + + carbon_image = Image.open(BytesIO(image_data)) + + enhancer = ImageEnhance.Brightness(carbon_image) + bright_image = enhancer.enhance(1.0) + + output_image = BytesIO() + bright_image.save(output_image, format="PNG", quality=95) + output_image.name = "carbon.png" + + return output_image + +async def telegraph(title, user_name, content): + formatted_content = "
".join(content.split("\n")) + formatted_content = "

" + formatted_content + "

" + + data = {"title": title, "content": formatted_content, "author_name": user_name} + + response = requests.post( + url=f"{url}/telegraph/text", headers=headers, json=data, timeout=5 + ) + + result = response.json() + + return result["url"] + + +async def voice_characters(): + response = requests.get(url=f"{url}/speech/characters", headers=headers, timeout=5) + + result = response.json() + + return ", ".join(result["characters"]) + + +async def make_rayso(code: str, title: str, theme: str): + data = { + "code": code, + "title": title, + "theme": theme, + "padding": 64, + "language": "auto", + "darkMode": False, + } + response = requests.post(f"{url}/rayso", data=data, headers=headers) + if response.status_code != 200: + return None + result = response.json() + try: + if result["error"] is not None: + return None + except KeyError: + pass + image_data = result["image"] + file_name = "rayso.png" + with open(file_name, "wb") as f: + f.write(base64.b64decode(image_data)) + return file_name + + +@Client.on_message(filters.command("asq", prefix) & filters.me) +async def asq(_, message: Message): + if len(message.command) > 1: + query = message.text.split(maxsplit=1)[1] + else: + await message.edit_text("Query not provided!") + return + await message.edit_text("Processing...") + response = requests.get(url=f"{url}/asq?query={query}", headers=headers, timeout=5) + if response.status_code != 200: + await message.edit_text("Something went wrong!") + return + + result = response.json() + + ans = result["answer"] + await message.edit_text( + f"Q. {query}\n A. {ans}", parse_mode=enums.ParseMode.MARKDOWN + ) + + +@Client.on_message(filters.command("sgemini", prefix) & filters.me) +async def sgemini(_, message: Message): + if len(message.command) > 1: + prompt = message.text.split(maxsplit=1)[1] + else: + await message.edit_text("prompt not provided!") + return + await message.edit_text("Processing...") + response = requests.get(url=f"{url}/bard?query={prompt}", headers=headers) + if response.status_code != 200: + await message.edit_text("Something went wrong!") + return + + result = response.json() + + ans = result["message"] + await message.edit_text( + f"Prompt: {prompt}\n Ans: {ans}", parse_mode=enums.ParseMode.MARKDOWN + ) + + +@Client.on_message(filters.command("app", prefix) & filters.me) +async def app(client: Client, message: Message): + try: + chat_id = message.chat.id + await message.edit_text("Processing...") + if len(message.command) > 1: + query = message.text.split(maxsplit=1)[1] + else: + message.edit_text( + "What should i search? You didn't provided me with any value to search" + ) + + response = requests.get( + url=f"{url}/apps?query={query}&limit=1", headers=headers, timeout=5 + ) + if response.status_code != 200: + await message.edit_text("Something went wrong") + return + + result = response.json() + + try: + coverImage_url = result["results"][0]["icon"] + coverImage = requests.get(url=coverImage_url).content + async with aiofiles.open("coverImage.jpg", mode="wb") as f: + await f.write(coverImage) + + except Exception: + coverImage = None + + description = result["results"][0]["description"] + developer = result["results"][0]["developer"] + IsFree = result["results"][0]["free"] + genre = result["results"][0]["genre"] + package_name = result["results"][0]["id"] + title = result["results"][0]["title"] + price = result["results"][0]["price"] + link = result["results"][0]["link"] + rating = result["results"][0]["rating"] + + await message.delete() + await client.send_media_group( + chat_id, + [ + InputMediaPhoto( + "coverImage.jpg", + caption=f"Title: {title}\nRating: {rating}\nIsFree: {IsFree}\nPrice: {price}\nPackage Name: {package_name}\nGenres: {genre}\nDeveloper: {developer}\nDescription: {description}\nLink: {link}", + ) + ], + ) + + except MediaCaptionTooLong: + description = description[:850] + await message.delete() + await client.send_media_group( + chat_id, + [ + InputMediaPhoto( + "coverImage.jpg", + caption=f"Title: {title}\nRating: {rating}\nIsFree: {IsFree}\nPrice: {price}\nPackage Name: {package_name}\nGenres: {genre}\nDeveloper: {developer}\nDescription: {description}\nLink: {link}", + ) + ], + ) + except Exception as e: + await message.edit_text(format_exc(e)) + finally: + if os.path.exists("coverImage.jpg"): + os.remove("coverImage.jpg") + + +@Client.on_message(filters.command("tsearch", prefix) & filters.me) +async def tsearch(client: Client, message: Message): + try: + chat_id = message.chat.id + limit = 10 + await message.edit_text("Processing...") + if len(message.command) > 1: + query = message.text.split(maxsplit=1)[1] + else: + message.edit_text( + "What should i search? You didn't provided me with any value to search" + ) + + response = requests.get( + url=f"{url}/torrent?query={query}&limit={limit}", headers=headers + ) + if response.status_code != 200: + await message.edit_text("Something went wrong") + return + + result = response.json() + + coverImage_url = result["results"][0]["thumbnail"] + description = result["results"][0]["description"] + genre = result["results"][0]["genre"] + category = result["results"][0]["category"] + title = result["results"][0]["name"] + link = result["results"][0]["magnetLink"] + link_result = await telegraph( + title=title, user_name=message.from_user.first_name, content=link + ) + language = result["results"][0]["language"] + size = result["results"][0]["size"] + + results = [] + + for i in range(min(limit, len(result["results"]))): + descriptions = result["results"][i]["description"] + genres = result["results"][i]["genre"] + categorys = result["results"][i]["category"] + titles = result["results"][i]["name"] + links = result["results"][i]["magnetLink"] + languages = result["results"][i]["language"] + sizes = result["results"][i]["size"] + + r = f"Title: {titles}\nCategory: {categorys}\nLanguage: {languages}\nSize: {sizes}\nGenres: {genres}\nDescription: {descriptions}\nMagnet Link: {links}
" + results.append(r) + + all_results_content = "
".join(results) + + link_results = await telegraph( + title="Search Results", + user_name=message.from_user.first_name, + content=all_results_content, + ) + + if coverImage_url is not None: + coverImage = requests.get(url=coverImage_url).content + async with aiofiles.open("coverImage.jpg", mode="wb") as f: + await f.write(coverImage) + + await message.delete() + await client.send_media_group( + chat_id, + [ + InputMediaPhoto( + "coverImage.jpg", + caption=f"Title: {title}\nCategory: {category}\nLanguage: {language}\nSize: {size}\nGenres: {genre}\nDescription: {description}\nMagnet Link: Click Here\nMore Results: Click Here", + ) + ], + ) + else: + await message.edit_text( + f"Title: {title}\nCategory: {category}\nLanguage: {language}\nSize: {size}\nGenres: {genre}\nDescription: {description}\nMagnet Link: Click Here\nMore Results: Click Here", + disable_web_page_preview=True, + ) + + except MediaCaptionTooLong: + description = description[:850] + await message.delete() + await client.send_media_group( + chat_id, + [ + InputMediaPhoto( + "coverImage.jpg", + caption=f"Title: {title}\nCategory: {category}\nLanguage: {language}\nSize: {size}\nGenres: {genre}\nDescription: {description}\nMagnet Link: Click Here", + ) + ], + ) + + except MessageTooLong: + description = description[:150] + await message.edit_text( + f"Title: {title}\nCategory: {category}\nLanguage: {language}\nSize: {size}\nGenres: {genre}\nDescription: {description}\nMagnet Link: Click Here", + disable_web_page_preview=True, + ) + + except Exception as e: + await message.edit_text(format_exc(e)) + finally: + if os.path.exists("coverImage.jpg"): + os.remove("coverImage.jpg") + + +@Client.on_message(filters.command("stts", prefix) & filters.me) +async def tts(client: Client, message: Message): + characters = await voice_characters() + await message.edit_text("Please Wait...") + try: + if len(message.command) > 2: + character, prompt = message.text.split(maxsplit=2)[1:] + if character not in characters: + await message.edit_text( + f"Usage: {prefix}tts [character]* [text/reply to text]*\n Available Characters:
{characters}
" + ) + return + + elif message.reply_to_message and len(message.command) > 1: + character = message.text.split(maxsplit=1)[1] + if character in characters: + prompt = message.reply_to_message.text + else: + await message.edit_text( + f"Usage: {prefix}tts [character]* [text/reply to text]*\n Available Characters:
{characters}
" + ) + return + + else: + await message.edit_text( + f"Usage: {prefix}tts [character]* [text/reply to text]*\n Available Characters:
{characters}
" + ) + return + + data = {"text": prompt, "character": character} + response = requests.post(url=f"{url}/speech", headers=headers, json=data) + if response.status_code != 200: + await message.edit_text("Something went wrong") + return + + result = response.json() + audio_data = result["audio"] + audio_data = base64.b64decode(audio_data) + async with aiofiles.open(f"{prompt}.mp3", mode="wb") as f: + await f.write(audio_data) + + await message.delete() + await client.send_audio( + chat_id=message.chat.id, + audio=f"{prompt}.mp3", + caption=f"Characters: {character}\nPrompt: {prompt}", + ) + if os.path.exists(f"{prompt}.mp3"): + os.remove(f"{prompt}.mp3") + + except KeyError: + try: + error = result["error"] + await message.edit_text(error) + except KeyError: + await message.edit_text( + f"Usage: {prefix}tts [character]* [text/reply to text]*\n Available Characters:
{characters}
" + ) + except Exception as e: + await message.edit_text(format_exc(e)) + + +@Client.on_message( + filters.command(["carbonnowsh", "carboon", "carbon", "cboon"], prefix) & filters.me +) +async def carbon(client: Client, message: Message): + if message.reply_to_message: + text = message.reply_to_message.text + message_id = message.reply_to_message.id + elif len(message.command) > 1: + message_id = None + text = message.text.split(maxsplit=1)[1] + else: + await message.edit_text("Query not provided!") + return + await message.edit_text("Processing...") + + image_file = await make_carbon(text) + + await message.delete() + try: + await client.send_photo( + chat_id=message.chat.id, + photo=image_file, + caption=f"Text: {text}", + reply_to_message_id=message_id, + ) + except MediaCaptionTooLong: + cap = text[:850] + await client.send_photo( + chat_id=message.chat.id, + photo=image_file, + caption=f"Text: {cap}", + reply_to_message_id=message_id, + ) + except Exception as e: + await message.edit_text(format_exc(e)) + if os.path.exists("carbon.png"): + os.remove("carbon.png") + + +@Client.on_message(filters.command("ccgen", prefix) & filters.me) +async def ccgen(_, message: Message): + if len(message.command) > 1: + bins = message.text.split(maxsplit=1)[1] + else: + await message.edit_text("Code not provided!") + return + await message.edit_text("Processing...") + response = requests.get(url=f"{url}/ccgen?bins={bins}", headers=headers) + if response.status_code != 200: + await message.edit_text("Something went wrong") + return + + result = response.json() + + cards = result["results"][0]["cards"] + cards_str = "\n".join([f'"{card}"' for card in cards]) + bins = result["results"][0]["bin"] + await message.edit_text( + f"Bins: {bins}\nTotal: {len(cards)}\nCards: \n{cards_str}" + ) + + +@Client.on_message(filters.command("rayso", prefix) & filters.me) +async def rayso(client: Client, message: Message): + title = "Untitled" + themes = [ + "vercel", + "supabase", + "tailwind", + "clerk", + "mintlify", + "prisma", + "bitmap", + "noir", + "ice", + "sand", + "forest", + "mono", + "breeze", + "candy", + "crimson", + "falcon", + "meadow", + "midnight", + "raindrop", + "sunset", + ] + if message.reply_to_message: + text = message.reply_to_message.text + message_id = message.reply_to_message.id + if 2 <= len(message.command) <= 3: + title = message.text.split(maxsplit=2)[1] + theme = message.text.split(maxsplit=2)[2].lower() + if theme not in themes: + theme = "breeze" + elif len(message.command) > 1: + message_id = message.id + title = message.text.split(maxsplit=3)[1] + theme = message.text.split(maxsplit=3)[2] + if theme not in themes: + theme = "breeze" + text = message.text.split(maxsplit=3)[3] + else: + await message.edit_text("Query not provided!") + return + await message.edit_text("Processing...") + + image_file = await make_rayso(text, title, theme) + + if image_file is None: + await message.edit_text("Something went wrong") + return + try: + await client.send_photo( + chat_id=message.chat.id, + photo=image_file, + caption=f"Text: {text}", + reply_to_message_id=message_id, + ) + await message.delete() + except MediaCaptionTooLong: + cap = text[:850] + await client.send_photo( + chat_id=message.chat.id, + photo=image_file, + caption=f"Text: {cap}", + reply_to_message_id=message_id, + ) + await message.delete() + except Exception as e: + await message.edit_text(format_exc(e)) + if os.path.exists(image_file): + os.remove(image_file) + + +modules_help["safone"] = { + "asq [query]*": "Asq", + "app [query]*": "Search for an app on Play Store", + "tsearch [query]*": "Search Torrent", + "tts [character]* [text/reply to text]*": "Convert Text to Speech", + "sgemini [prompt]*": "Gemini Model through safone api", + "carbon [code/file/reply]": "Create beautiful image with your code", + "ccgen [bins]*": "Generate credit cards", + "rayso [title]* [theme]* [text/reply to text]*": "Create beautiful image with your text", +} diff --git a/modules/misc/sarethai.py b/modules/misc/sarethai.py new file mode 100644 index 0000000..e7975e0 --- /dev/null +++ b/modules/misc/sarethai.py @@ -0,0 +1,565 @@ +#  Moon-Userbot - telegram userbot +#  Copyright (C) 2020-present Moon Userbot Organization +# +#  This program is free software: you can redistribute it and/or modify +#  it under the terms of the GNU General Public License as published by +#  the Free Software Foundation, either version 3 of the License, or +#  (at your option) any later version. + +#  This program is distributed in the hope that it will be useful, +#  but WITHOUT ANY WARRANTY; without even the implied warranty of +#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +#  GNU General Public License for more details. + +#  You should have received a copy of the GNU General Public License +#  along with this program.  If not, see . + +import os +import requests +import asyncio +from pyrogram import Client, enums, filters +from pyrogram.types import Message + +from utils.misc import modules_help, prefix +from modules.url import generate_screenshot + +from pyrogram import filters + +from utils.scripts import with_reply, no_prefix + +np = no_prefix(prefix) + +# API URLs +BASE_URL = "https://deliriussapi-oficial.vercel.app" +URL = f"{BASE_URL}/ia" +GOOGLE_SEARCH_URL = f"{BASE_URL}/search/googlesearch?query=" +YOUTUBE_SEARCH_URL = f"{BASE_URL}/search/ytsearch?q=" +MOVIE_SEARCH_URL = f"{BASE_URL}/search/movie?query=" +APK_SEARCH_URL = f"https://bk9.fun/search/apkfab?q=" +APK_DOWNLOAD_URL = "https://bk9.fun/download/apkfab?url=" + + +def clean_data(data): + parts = data.split("$@$") + + if len(parts) > 1: + return parts[-1] + else: + return data + + +# Store search results temporarily +search_results = {} + + +# Helper Functions +def format_google_results(results): + results = results[:15] + return results, "\n\n".join( + [ + f"{i+1}. **[{item['title']}]({item['url']})**\n{item['description']}" + for i, item in enumerate(results) + ] + ) + + +def format_youtube_results(results): + results = results[:15] + return results, "\n\n".join( + [ + f"{i+1}. **[{item['title']}]({item['url']})**\nPublished by: [{item['author']['name']}]({item['author']['url']}) - {item['views']} views - {item['duration']}" + for i, item in enumerate(results) + ] + ) + + +def format_movie_results(results): + results = results[:15] + return results, "\n\n".join( + [ + f"{i+1}. **{item['title']}** ({item['release_date']})\nRating: {item['vote_average']}/10\nVotes: {item['vote_count']}" + for i, item in enumerate(results) + ] + ) + + +def format_apk_results(results): + results = results[:15] + return results, "\n\n".join( + [f"{i+1}. [{item['title']}]({item['link']})" for i, item in enumerate(results)] + ) + + +async def send_screenshot(client, message, url): + screenshot_data = generate_screenshot(url) + if screenshot_data: + await client.send_photo( + message.chat.id, + screenshot_data, + caption=f"Screenshot of {url}", + reply_to_message_id=message.id, + ) + else: + await message.reply("Failed to take screenshot.") + + +async def delete_search_data(client, chat_id, message_id): + await asyncio.sleep(60) + for key in ["google", "youtube", "movie", "apk"]: + search_key = f"{chat_id}_{key}" + if ( + search_key in search_results + and search_results[search_key]["message_id"] == message_id + ): + del search_results[search_key] + try: + await client.delete_messages(chat_id, message_id) + except: + pass + break + + +def format_spotify_result(data): + result = "" + for item in data[:15]: # Limit to 15 results + result += f"🎵 **{item['title']}** by {item['artist']}\n" + result += f"Album: {item['album']}\n" + result += f"Duration: {item['duration']}\n" + result += f"Popularity: {item['popularity']}\n" + result += f"Publish Date: {item['publish']}\n" + result += f"[Listen on Spotify]({item['url']})\n\n" + return result + + +def format_lyrics_result(data): + return f"🎵 **{data['fullTitle']}** by {data['artist']}\n\n{data['lyrics']}" + + +def format_soundcloud_result(data): + result = "" + for item in data[:15]: # Limit to 15 results + result += f"🎵 **{item['title']}**\n" + result += f"Genre: {item['genre']}\n" + result += f"Duration: {item['duration'] // 1000 // 60}:{item['duration'] // 1000 % 60}\n" + result += f"Likes: {item['likes']}\n" + result += f"Plays: {item['play']}\n" + result += f"[Listen on SoundCloud]({item['link']})\n\n" + return result + + +def format_deezer_result(data): + result = "" + for item in data[:15]: # Limit to 15 results + result += f"🎵 **{item['title']}** by {item['artist']}\n" + result += f"Duration: {item['duration']}\n" + result += f"Rank: {item['rank']}\n" + result += f"[Listen on Deezer]({item['url']})\n\n" + return result + + +def format_apple_music_result(data): + result = "" + for item in data[:15]: # Limit to 15 results + title = item.get("title", "Unknown Title") + artists = item.get("artists", "Unknown Artist") + music_type = item.get("type", "Unknown Type") + url = item.get("url", "#") + result += f"🎵 **{title}** by {artists}\n" + result += f"Type: {music_type}\n" + result += f"[Listen on Apple Music]({url})\n\n" + return result + + +async def search_music(api_url, format_function, message, query): + await message.edit("Searching...") + url = f"{api_url}{query}&limit=10" + response = requests.get(url) + + if response.status_code == 200: + try: + data = response.json() # Directly get the JSON data + + if isinstance(data, list): + result = format_function(data) + elif isinstance(data, dict) and "data" in data: + result = format_function(data["data"]) + else: + result = "No data found or unexpected format." + + await message.edit(result, parse_mode=enums.ParseMode.MARKDOWN) + except (ValueError, KeyError, TypeError) as e: + await message.edit(f"An error occurred while processing the data: {str(e)}") + elif response.json()["msg"]: + await message.edit(response.json()["msg"]) + else: + await message.edit("An error occurred, please try again later.") + + +@Client.on_message(filters.command(["gsearch"], prefix) & filters.me) +async def google_search(client: Client, message: Message): + if message.reply_to_message: + query = message.reply_to_message.text.strip() + elif len(message.command) > 1: + query = message.text.split(maxsplit=1)[1] + else: + return await message.edit_text(f"{prefix}gsearch ") + + await message.edit("Searching...") + url = f"{GOOGLE_SEARCH_URL}{query}" + response = requests.get(url) + if response.status_code == 200: + data = response.json() + results, formatted_results = format_google_results(data["data"]) + search_message = await message.edit( + f"**Google Search Results for:** `{query}`\n\n{formatted_results}", + parse_mode=enums.ParseMode.MARKDOWN, + disable_web_page_preview=True, + ) + search_key = f"{message.chat.id}_google" + global search_results + search_results[search_key] = { + "results": results, + "message_id": search_message.id, + } + google_url = f"https://www.google.com/search?q={query}" + await send_screenshot(client, message, google_url) + asyncio.create_task( + delete_search_data(client, message.chat.id, search_message.id) + ) + else: + await message.edit("An error occurred, please try again later.") + + +@Client.on_message(filters.command(["ytsearch"], prefix) & filters.me) +async def youtube_search(client: Client, message: Message): + if message.reply_to_message: + query = message.reply_to_message.text.strip() + elif len(message.command) > 1: + query = message.text.split(maxsplit=1)[1] + else: + return await message.edit_text(f"{prefix}ytsearch ") + + await message.edit("Searching...") + url = f"{YOUTUBE_SEARCH_URL}{query}" + response = requests.get(url) + if response.status_code == 200: + data = response.json() + results, formatted_results = format_youtube_results(data["data"]) + search_message = await message.edit( + f"**YouTube Search Results for:** `{query}`\n\n{formatted_results}", + parse_mode=enums.ParseMode.MARKDOWN, + disable_web_page_preview=True, + ) + search_key = f"{message.chat.id}_youtube" + global search_results + search_results[search_key] = { + "results": results, + "message_id": search_message.id, + } + youtube_url = f"https://www.youtube.com/results?search_query={query}" + await send_screenshot(client, message, youtube_url) + asyncio.create_task( + delete_search_data(client, message.chat.id, search_message.id) + ) + else: + await message.edit("An error occurred, please try again later.") + + +@Client.on_message(filters.command(["moviesearch"], prefix) & filters.me) +async def movie_search(client, message: Message): + if message.reply_to_message: + query = message.reply_to_message.text.strip() + elif len(message.command) > 1: + query = message.text.split(maxsplit=1)[1] + else: + return await message.edit_text(f"{prefix}moviesearch ") + + await message.edit("Searching...") + url = f"{MOVIE_SEARCH_URL}{query}" + response = requests.get(url) + if response.status_code == 200: + data = response.json() + results, formatted_results = format_movie_results(data["data"]) + + # Split the message into multiple parts if it's too long + parts = [] + part_length = 4096 # Telegram's message length limit + for i in range(0, len(formatted_results), part_length): + parts.append(formatted_results[i : i + part_length]) + + for part in parts: + search_message = await message.reply( + f"**Movie Search Results for:** `{query}`\n\n{part}", + parse_mode=enums.ParseMode.MARKDOWN, + disable_web_page_preview=True, + ) + + search_key = f"{message.chat.id}_movie" + global search_results + search_results[search_key] = { + "results": results, + "message_id": search_message.id, + } + asyncio.create_task( + delete_search_data(client, message.chat.id, search_message.id) + ) + else: + await message.edit("An error occurred, please try again later.") + + +@Client.on_message(filters.command(["apksearch"], prefix) & filters.me) +async def apk_search(client, message: Message): + if message.reply_to_message: + query = message.reply_to_message.text.strip() + elif len(message.command) > 1: + query = message.text.split(maxsplit=1)[1] + else: + return await message.edit_text(f"{prefix}apksearch ") + + await message.edit("Searching...") + url = f"{APK_SEARCH_URL}{query}" + response = requests.get(url) + if response.status_code == 200: + data = response.json() + results, formatted_results = format_apk_results(data["BK9"]) + search_message = await message.edit( + f"**APK Search Results for:** `{query}`\n\n{formatted_results}", + parse_mode=enums.ParseMode.MARKDOWN, + disable_web_page_preview=True, + ) + search_key = f"{message.chat.id}_apk" + global search_results + search_results[search_key] = { + "results": results, + "message_id": search_message.id, + } + + asyncio.create_task( + delete_search_data(client, message.chat.id, search_message.id) + ) + else: + await message.edit("An error occurred, please try again later.") + + +@Client.on_message(filters.command(["wgpt", "gptweb"], prefix) & filters.me) +async def gptweb(_, message: Message): + if len(message.command) < 2: + await message.edit("Usage: `wgpt `") + return + await message.edit("Thinking...") + query = " ".join(message.command[1:]) + url = f"{URL}/gptweb?text={query}" + response = requests.get(url) + if response.status_code == 200: + data = response.json() + await message.edit( + f"**Question:**\n{query}\n**Answer:**\n{data['data']}", + parse_mode=enums.ParseMode.MARKDOWN, + ) + else: + await message.edit("An error occurred, please try again later.") + + +@Client.on_message(filters.command(["wgemini"], prefix) & filters.me) +async def gemini(_, message: Message): + if len(message.command) < 2: + await message.edit("Usage: `wgemini `") + return + await message.edit("Thinking...") + query = " ".join(message.command[1:]) + url = f"{URL}/gemini?query={query}" + response = requests.get(url) + if response.status_code == 200: + data = response.json() + await message.edit( + f"**Question:**\n{query}\n**Answer:**\n{data['message']}", + parse_mode=enums.ParseMode.MARKDOWN, + ) + else: + await message.edit("An error occurred, please try again later.") + + +@Client.on_message(filters.command(["sputify"], prefix) & filters.me) +async def spotify_search(_, message: Message): + query = ( + message.text.split(maxsplit=1)[1] + if len(message.command) > 1 + else message.reply_to_message.text + ) + if not query: + await message.edit("Usage: spotify ") + return + await search_music( + f"{BASE_URL}/search/spotify?q=", format_spotify_result, message, query + ) + + +@Client.on_message(filters.command(["lyrics"], prefix) & filters.me) +async def lyrics_search(_, message: Message): + query = ( + message.text.split(maxsplit=1)[1] + if len(message.command) > 1 + else message.reply_to_message.text + ) + if not query: + await message.edit("Usage: lyrics ") + return + await search_music( + f"{BASE_URL}/search/letra?query=", format_lyrics_result, message, query + ) + + +@Client.on_message(filters.command(["soundcloud"], prefix) & filters.me) +async def soundcloud_search(_, message: Message): + query = ( + message.text.split(maxsplit=1)[1] + if len(message.command) > 1 + else message.reply_to_message.text + ) + if not query: + await message.edit("Usage: soundcloud ") + return + await search_music( + f"{BASE_URL}/search/soundcloud?q=", format_soundcloud_result, message, query + ) + + +@Client.on_message(filters.command(["deezer"], prefix) & filters.me) +async def deezer_search(_, message: Message): + query = ( + message.text.split(maxsplit=1)[1] + if len(message.command) > 1 + else message.reply_to_message.text + ) + if not query: + await message.edit("Usage: deezer ") + return + await search_music( + f"{BASE_URL}/search/deezer?q=", format_deezer_result, message, query + ) + + +@Client.on_message(filters.command(["applemusic"], prefix) & filters.me) +async def applemusic_search(_, message: Message): + query = ( + message.text.split(maxsplit=1)[1] + if len(message.command) > 1 + else message.reply_to_message.text + ) + if not query: + await message.edit("Usage: applemusic ") + return + await search_music( + f"{BASE_URL}/search/applemusic?text=", format_apple_music_result, message, query + ) + + +@Client.on_message(filters.reply & filters.text & filters.me & np) +async def handle_reply(client: Client, message: Message): + chat_id = message.chat.id + search_keys = [ + f"{chat_id}_google", + f"{chat_id}_youtube", + f"{chat_id}_movie", + f"{chat_id}_apk", + ] + + for search_key in search_keys: + if search_key in search_results: + try: + # Check if the replied-to message is one of the bot's search result messages + if message.reply_to_message.from_user.id != (await client.get_me()).id: + return + + index = int(message.text.strip()) - 1 + results = search_results[search_key]["results"] + search_message_id = search_results[search_key]["message_id"] + if ( + message.reply_to_message.id == search_message_id + and 0 <= index < len(results) + ): + await message.edit("Please wait...") + + if search_key.endswith("_movie"): + # Send movie details with image + movie = results[index] + caption = ( + f"**{movie['title']}** ({movie['release_date']})\n" + f"Original Title: {movie['original_title']}\n" + f"Language: {movie['original_language']}\n" + f"Overview: {movie['overview']}\n" + f"Popularity: {movie['popularity']}\n" + f"Rating: {movie['vote_average']}/10\n" + f"Votes: {movie['vote_count']}" + ) + if "image" in movie: + response = requests.get(movie["image"]) + if response.status_code == 200: + with open("movie_image.jpg", "wb") as f: + f.write(response.content) + await message.reply_photo( + photo="movie_image.jpg", + caption=caption, + parse_mode=enums.ParseMode.MARKDOWN, + ) + os.remove("movie_image.jpg") + else: + await message.reply( + caption, parse_mode=enums.ParseMode.MARKDOWN + ) + else: + await message.reply( + caption, parse_mode=enums.ParseMode.MARKDOWN + ) + elif search_key.endswith("_apk"): + apk_url = f"{APK_DOWNLOAD_URL}{results[index]['link']}" + fetch_apk_url = requests.get(apk_url) + if fetch_apk_url.status_code != 200: + await message.edit("Failed to fetch APK data.") + else: + data_apk = fetch_apk_url.json() + download_url = data_apk["BK9"]["link"] + size_apk = data_apk["BK9"]["size"] + apk_size = float(size_apk.split(" ")[0]) + + if "GB" in size_apk or apk_size > 100: + await message.edit( + "File size is too large to download." + ) + else: + apk_file_name = f"{data_apk['BK9']['title']}.apk" + response = requests.get(download_url) + + if response.status_code != 200: + await message.edit( + "Failed to download the APK file." + ) + else: + with open(apk_file_name, "wb") as f: + f.write(response.content) + + await message.reply_document(apk_file_name) + os.remove(apk_file_name) + else: + url = results[index]["url"] + await send_screenshot(client, message, url) + await message.delete() + return + except ValueError: + pass + + +modules_help["sarethai"] = { + "wgpt [query]*": "Ask anything to GPT-Web", + "gptweb [query]*": "Ask anything to GPT-Web", + "wgemini [query]*": "Ask anything to Gemini", + "sputify [query]*": "Search for songs on Spotify", + "lyrics [song name]*": "Get the lyrics of a song", + "soundcloud [query]*": "Search for songs on SoundCloud", + "deezer [query]*": "Search for songs on Deezer", + "applemusic [query]*": "Search for songs on Apple Music", + "gsearch [query]*": "Searches Google for the query.", + "ytsearch [query]*": "Searches YouTube for the query.", + "moviesearch [query]*": "Searches movies for the query and returns results.", + "apksearch [query]*": "Searches APKs for the query and returns results.", +} diff --git a/modules/misc/search.py b/modules/misc/search.py new file mode 100644 index 0000000..7ffbbb3 --- /dev/null +++ b/modules/misc/search.py @@ -0,0 +1,79 @@ +from asyncio import sleep + +from pyrogram import Client, enums, filters +from pyrogram.types import Message + +# noinspection PyUnresolvedReferences +from utils.misc import modules_help, prefix + +# noinspection PyUnresolvedReferences +from utils.scripts import format_exc + +now = {} + + +@Client.on_message(filters.command(["search"], prefix) & filters.me) +async def search_cmd(client: Client, message: Message): + if now.get(message.chat.id): + return await message.edit( + "You already have a search in progress!\n" + "Type: {}scancel to cancel it.".format(prefix), + parse_mode=enums.ParseMode.HTML, + ) + + await message.edit("Start searching...", parse_mode=enums.ParseMode.HTML) + finished = False + local = False + try: + cmd = message.command[1] + word = message.command[2].lower() + timeout = float(message.command[3]) if len(message.command) > 3 else 2 + except: + return await message.edit( + "Usage: {}search [/cmd]* [search_word]* [timeout=2.0]".format( + prefix + ), + parse_mode=enums.ParseMode.HTML, + ) + + now[message.chat.id] = True + + try: + await message.reply_text(quote=False, text=cmd, reply_to_message_id=None) + while not finished and now[message.chat.id]: + async for msg in client.get_chat_history(message.chat.id, limit=2): + if msg.from_user.id == message.from_user.id: + continue + elif word in msg.text.lower(): + finished = True + local = True + if not local: + await sleep(timeout) + await message.reply_text( + quote=False, text=cmd, reply_to_message_id=None + ) + else: + break + if now[message.chat.id]: + await message.reply_text( + "Search finished!", parse_mode=enums.ParseMode.HTML + ) + except Exception as ex: + await message.edit(format_exc(ex), parse_mode=enums.ParseMode.HTML) + now[message.chat.id] = False + + +@Client.on_message(filters.command(["scancel"], prefix) & filters.me) +async def scancel_cmd(_: Client, message: Message): + if not now.get(message.chat.id): + return await message.edit( + "There is no search in progress!", parse_mode=enums.ParseMode.HTML + ) + now[message.chat.id] = False + await message.edit("Search cancelled!", parse_mode=enums.ParseMode.HTML) + + +modules_help["search"] = { + "search [/cmd]* [search_word]* [timeout=2.0]": "Search for a specific word in bot (while)", + "scancel": "Cancel current search", +} diff --git a/modules/misc/summary.py b/modules/misc/summary.py new file mode 100644 index 0000000..763a19c --- /dev/null +++ b/modules/misc/summary.py @@ -0,0 +1,64 @@ +# copyright by https/t.me/shado_hackers + +from pyrogram import Client, filters +from pyrogram.types import Message + +from utils.scripts import format_exc, import_library +from utils.misc import modules_help, prefix + +import_library("lxml_html_clean") +import_library("newspaper", "newspaper3k") +nltk = import_library("nltk") +from newspaper import Article +from newspaper.article import ArticleException + + +nltk.download("all") + + +@Client.on_message(filters.command("summary", prefix) & filters.me) +async def summarize_article(_, message: Message): + """ + Summarize an article from a given URL. + + Args: + client (Client): Pyrogram client instance. + message (Message): Incoming message. + + Returns: + None + """ + # Extract the URL from the message text (removing the command part) + url = message.text.split(maxsplit=1)[1] if len(message.text.split()) > 1 else None + + if not url: + await message.edit_text("Please provide a valid URL after the command.") + return + + try: + # Extract and summarize the article + article = Article(url) + article.download() + article.parse() + article.nlp() # Uses NLP to analyze the article + + response = f""" + Article Summary + Title: {article.title} + Authors: {', '.join(article.authors) if article.authors else 'N/A'} + Summary: +
{article.summary}
+ """ + await message.edit_text(response) + except ArticleException: + return await message.edit_text( + "Unable to extract information from the provided URL." + ) + + except Exception as e: + return await message.edit_text(f"An error occurred: {format_exc(e)}") + + +modules_help["summary"] = { + "summary [url]": "Reply with article links, getting summary of articles" +} diff --git a/modules/misc/switch.py b/modules/misc/switch.py new file mode 100644 index 0000000..3f55768 --- /dev/null +++ b/modules/misc/switch.py @@ -0,0 +1,53 @@ +# Moon-Userbot - telegram userbot +# Copyright (C) 2020-present Moon Userbot Organization +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from pyrogram import Client, filters, enums +from pyrogram.types import Message + +from utils.misc import modules_help, prefix + +ru_keys = ( + """ёйцукенгшщзхъфывапролджэячсмитьбю.Ё"№;%:?ЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭ/ЯЧСМИТЬБЮ,""" +) +en_keys = ( + """`qwertyuiop[]asdfghjkl;'zxcvbnm,./~@#$%^&QWERTYUIOP{}ASDFGHJKL:"|ZXCVBNM<>?""" +) +table = str.maketrans(ru_keys + en_keys, en_keys + ru_keys) + + +@Client.on_message(filters.command(["switch", "sw"], prefix) & filters.me) +async def switch(client: Client, message: Message): + if len(message.command) == 1: + if message.reply_to_message: + text = message.reply_to_message.text + else: + history = await client.get_history(message.chat.id, limit=2) + if history and history[1].from_user.is_self and history[1].text: + text = history[1].text + else: + await message.edit( + "Text to switch not found", parse_mode=enums.ParseMode.HTML + ) + return + else: + text = message.text.split(maxsplit=1)[1] + + await message.edit(str.translate(text, table), parse_mode=enums.ParseMode.HTML) + + +modules_help["switch"] = { + "sw [reply/text for switch]*": "Useful when you forgot to change the keyboard layout[RU]", +} diff --git a/modules/misc/transcribeyt.py b/modules/misc/transcribeyt.py new file mode 100644 index 0000000..7cc0a97 --- /dev/null +++ b/modules/misc/transcribeyt.py @@ -0,0 +1,110 @@ +import requests +import time +from pyrogram import Client, filters, enums +from pyrogram.types import Message +from pyrogram.errors import MessageTooLong +import os +from utils.misc import modules_help, prefix + + +# Replace with your Gladia API key +gladia_key = "your key " +gladia_url = "https://api.gladia.io/v2/transcription/" + + +# Function to make fetch requests to the Gladia API +def make_fetch_request(url, headers, method="GET", data=None): + if method == "POST": + response = requests.post(url, headers=headers, json=data) + else: + response = requests.get(url, headers=headers) + return response.json() + + +@Client.on_message(filters.command("transcribeyt", prefix) & filters.me) +async def transcribe_audio(_, message: Message): + """ + Transcribe an audio URL using the Gladia API. + + Usage: .transcribe + """ + audio_url = ( + message.text.split(maxsplit=1)[1] if len(message.text.split()) > 1 else None + ) + + # Check if a valid URL was provided + if not audio_url: + await message.reply("Please provide a valid audio URL.") + return + + headers = {"x-gladia-key": gladia_key, "Content-Type": "application/json"} + + request_data = {"audio_url": audio_url} + + # Send initial request to Gladia API + status_message = await message.reply("- Sending initial request to Gladia API...") + initial_response = make_fetch_request(gladia_url, headers, "POST", request_data) + + # Check if the response contains the result_url + if "result_url" not in initial_response: + await status_message.edit(f"Error in transcription request: {initial_response}") + return + + result_url = initial_response["result_url"] + await status_message.edit( + f"Initial request sent. Polling for transcription results..." + ) + + # Polling for transcription result + while True: + poll_response = make_fetch_request(result_url, headers) + + if poll_response.get("status") == "done": + transcription = ( + poll_response.get("result", {}) + .get("transcription", {}) + .get("full_transcript") + ) + if transcription: + # Format the transcription result with HTML + result_html = f""" + Transcription Result +
+
{transcription}
+ """ + try: + # Attempt to send transcription as a message + await message.reply_text( + result_html, parse_mode=enums.ParseMode.HTML + ) + except MessageTooLong: + # Save the large response to a file + with open("transcription.txt", "w") as f: + f.write(result_html) + + # Read general details to include in the caption + general_details = "Here's the transcription result." + + # Send the file with a caption + await message.reply_document( + "transcription.txt", + caption=f"General Details:\n{general_details}", + ) + + # Clean up by removing the file + os.remove("transcription.html") + else: + await status_message.edit( + "Transcription completed, but no transcript was found." + ) + break + else: + await status_message.edit( + f"Transcription status: {poll_response.get('status')}" + ) + time.sleep(30) # Wait for a few seconds before polling again + + +modules_help["transcribeyt"] = { + "transcribeyt [yt video url]": "Reply with a YT video link to get transcribed " +} diff --git a/modules/notes.py b/modules/notes.py new file mode 100644 index 0000000..b43dc3e --- /dev/null +++ b/modules/notes.py @@ -0,0 +1,143 @@ +# Moon-Userbot - telegram userbot +# Copyright (C) 2020-present Moon Userbot Organization +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from pyrogram import Client, errors, filters +from pyrogram.types import Message + +from utils.db import db +from utils.handlers import NoteSendHandler +from utils.misc import modules_help, prefix + + +@Client.on_message(filters.command(["save"], prefix) & filters.me) +async def save_note(client: Client, message: Message): + await message.edit("Loading...") + + try: + chat = await client.get_chat(db.get("core.notes", "chat_id", 0)) + except (errors.RPCError, ValueError, KeyError): + # group is not accessible or isn't created + chat = await client.create_supergroup( + "Userbot_Notes_Filters", "Don't touch this group, please" + ) + db.set("core.notes", "chat_id", chat.id) + + chat_id = chat.id + + if message.reply_to_message and len(message.text.split()) >= 2: + note_name = message.text.split(maxsplit=1)[1] + if message.reply_to_message.media_group_id: + checking_note = db.get("core.notes", f"note{note_name}", False) + if not checking_note: + get_media_group = [ + _.id + for _ in await client.get_media_group( + message.chat.id, message.reply_to_message.id + ) + ] + try: + message_id = await client.forward_messages( + chat_id, message.chat.id, get_media_group + ) + except errors.ChatForwardsRestricted: + await message.edit( + "Forwarding messages is restricted by chat admins", + ) + return + note = { + "MESSAGE_ID": str(message_id[1].id), + "MEDIA_GROUP": True, + "CHAT_ID": str(chat_id), + } + db.set("core.notes", f"note{note_name}", note) + await message.edit(f"Note {note_name} saved") + else: + await message.edit("This note already exists") + else: + checking_note = db.get("core.notes", f"note{note_name}", False) + if not checking_note: + try: + message_id = await message.reply_to_message.forward(chat_id) + except errors.ChatForwardsRestricted: + message_id = await message.copy(chat_id) + note = { + "MEDIA_GROUP": False, + "MESSAGE_ID": str(message_id.id), + "CHAT_ID": str(chat_id), + } + db.set("core.notes", f"note{note_name}", note) + await message.edit(f"Note {note_name} saved") + else: + await message.edit("This note already exists") + elif len(message.text.split()) >= 3: + note_name = message.text.split(maxsplit=1)[1].split()[0] + checking_note = db.get("core.notes", f"note{note_name}", False) + if not checking_note: + message_id = await client.send_message( + chat_id, message.text.split(note_name)[1].strip() + ) + note = { + "MEDIA_GROUP": False, + "MESSAGE_ID": str(message_id.id), + "CHAT_ID": str(chat_id), + } + db.set("core.notes", f"note{note_name}", note) + await message.edit(f"Note {note_name} saved") + else: + await message.edit("This note already exists") + else: + await message.edit( + f"Example: {prefix}save note_name", + ) + + +@Client.on_message(filters.command("note", prefix) & filters.me) +async def note_send(client: Client, message: Message): + handler = NoteSendHandler(client, message) + await handler.handle_note_send() + + +@Client.on_message(filters.command(["notes"], prefix) & filters.me) +async def notes(_, message: Message): + await message.edit("Loading...") + text = "Available notes:\n\n" + collection = db.get_collection("core.notes") + for note in collection.keys(): + if note[:4] == "note": + text += f"{note[4:]}\n" + await message.edit(text) + + +@Client.on_message(filters.command(["clear"], prefix) & filters.me) +async def clear_note(_, message: Message): + if len(message.text.split()) >= 2: + note_name = message.text.split(maxsplit=1)[1] + find_note = db.get("core.notes", f"note{note_name}", False) + if find_note: + db.remove("core.notes", f"note{note_name}") + await message.edit(f"Note {note_name} deleted") + else: + await message.edit("There is no such note") + else: + await message.edit(f"Example: {prefix}clear note_name") + + +modules_help["notes"] = { + "save [name]*": "Save note", + "note [name]*": "Get saved note", + "notes": "Get note list", + "clear [name]*": "Delete note", +} diff --git a/modules/open.py b/modules/open.py new file mode 100644 index 0000000..dd6da30 --- /dev/null +++ b/modules/open.py @@ -0,0 +1,126 @@ +# Moon-Userbot - telegram userbot +# Copyright (C) 2020-present Moon Userbot Organization +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import datetime +import os +import time + +import aiofiles +from pyrogram import Client, filters +from pyrogram.errors import MessageTooLong +from pyrogram.types import Message + +from utils.misc import modules_help, prefix +from utils.scripts import edit_or_reply, format_exc, progress +from utils.rentry import paste as rentry_paste + + +async def read_file(file_path): + async with aiofiles.open(file_path, mode="r") as file: + content = await file.read() + return content + + +def check_extension(file_path): + extensions = { + ".txt": "
",
+        ".py": "
",
+        ".js": "
",
+        ".json": "
",
+        ".smali": "
",
+        ".sh": "
",
+        ".c": "
",
+        ".java": "
",
+        ".php": "
",
+        ".doc": "
",
+        ".docx": "
",
+        ".rtf": "
",
+        ".s": "
",
+        ".dart": "
",
+        ".cfg": "
",
+        ".swift": "
",
+        ".cs": "
",
+        ".vb": "
",
+        ".css": "
",
+        ".htm": "
",
+        ".html": "
",
+        ".rss": "
",
+        ".xhtml": "
",
+        ".cpp": "
",
+    }
+
+    ext = os.path.splitext(file_path)[1].lower()
+
+    return extensions.get(ext, "
")
+
+
+@Client.on_message(filters.command("open", prefix) & filters.me)
+async def openfile(client: Client, message: Message):
+    if not message.reply_to_message:
+        return await message.edit_text("Kindly Reply to a File")
+
+    try:
+        ms = await edit_or_reply(message, "Downloading...")
+        ct = time.time()
+        file_path = await message.reply_to_message.download(
+            progress=progress, progress_args=(ms, ct, "Downloading...")
+        )
+        await ms.edit_text("Trying to open file...")
+        file_info = os.stat(file_path)
+        file_name = file_path.split("/")[-1:]
+        file_size = file_info.st_size
+        last_modified = datetime.datetime.fromtimestamp(file_info.st_mtime).strftime(
+            "%Y-%m-%d %H:%M:%S"
+        )
+        code_start = check_extension(file_path=file_path)
+        content = await read_file(file_path=file_path)
+        await ms.edit_text(
+            f"File Name: {file_name[0]}\nSize: {file_size} bytes\nLast Modified: {last_modified}\nContent: {code_start}{content}
", + ) + + except MessageTooLong: + await ms.edit_text( + "File Content is too long... Pasting to rentry..." + ) + content_new = f"```{code_start[11:-2]}\n{content}```" + try: + rentry_url, edit_code = await rentry_paste( + text=content_new, return_edit=True + ) + except RuntimeError: + await ms.edit_text("Error: Failed to paste to rentry") + return + await client.send_message( + "me", + f"Here's your edit code for Url: {rentry_url}\nEdit code: {edit_code}", + disable_web_page_preview=True, + ) + await ms.edit_text( + f"File Name: {file_name[0]}\nSize: {file_size} bytes\nLast Modified: {last_modified}\nContent: {rentry_url}\nNote: Edit Code has been sent to your saved messages", + disable_web_page_preview=True, + ) + + except Exception as e: + await ms.edit_text(format_exc(e)) + + finally: + if os.path.exists(file_path): + os.remove(file_path) + + +modules_help["open"] = { + "open": "Open content of any text supported filetype and show it's raw data" +} diff --git a/modules/pdf2md.py b/modules/pdf2md.py new file mode 100644 index 0000000..8d1649f --- /dev/null +++ b/modules/pdf2md.py @@ -0,0 +1,88 @@ +import os +from pyrogram import Client, filters +from pyrogram.types import Message + +from utils.misc import modules_help, prefix +from utils.scripts import import_library +from utils.config import gemini_key + + +import_library("pyzerox", "py-zerox") + +from pyzerox import zerox +from pyzerox.errors import ModelAccessError, NotAVisionModel +import litellm + +kwargs = {} + +CUSTOM_SYSTEM_PROMPT = "For the below pdf page, convert it into as accurate markdown format as possible with it's structure intact i.e, tables, charts, layouts etc. Return only the markdown with no explanation text. Do not exclude any content from the page." + + +MODEL = "gemini/gemini-1.5-pro" + + +@Client.on_message(filters.command("pdf2md", prefix) & filters.me) +async def pdf2md(client: Client, message: Message): + if not message.reply_to_message: + await message.edit("Reply to a pdf file") + return + if not message.reply_to_message.document: + await message.edit("Reply to a pdf file") + return + if not message.reply_to_message.document.mime_type == "application/pdf": + await message.edit("Reply to a pdf file") + return + if gemini_key == "": + await message.edit("Set GEMINI_KEY to use this command") + return + file_name = message.reply_to_message.document.file_name + file_name = file_name.split(".")[0] + if os.path.exists(f"{file_name}.md"): + os.remove(f"{file_name}.md") + md = f"{file_name}.md" + os.environ["GEMINI_API_KEY"] = gemini_key + await message.edit("Downloading pdf...") + pdf = await message.reply_to_message.download() + await message.edit("Converting pdf to markdown...") + try: + result = await zerox( + file_path=pdf, + model=MODEL, + custom_system_prompt=CUSTOM_SYSTEM_PROMPT, + select_pages=None, + **kwargs, + ) + if result: + pages = result.pages + for page in pages: + with open(md, "a") as f: + f.write(page.content) + f.write("\n\n") + else: + await message.edit("No result") + return + except ModelAccessError: + await message.edit("Model not accessible") + return + except NotAVisionModel: + await message.edit("Model is not a vision model") + return + except litellm.InternalServerError: + await message.edit("Internal Server Error") + return + except Exception as e: + await message.edit(f"Error: {e}") + return + await message.edit("Uploading markdown...") + await client.send_document( + message.chat.id, + document=md, + file_name=f"{message.reply_to_message.document.file_name.split('.')[0]}.md", + reply_to_message_id=message.reply_to_message.id, + ) + await message.delete() + os.remove(pdf) + os.remove(md) + + +modules_help["pdf2md"] = {"pdf2md": "Convert a pdf to markdown"} diff --git a/modules/perfectrussian.py b/modules/perfectrussian.py new file mode 100644 index 0000000..46e5d79 --- /dev/null +++ b/modules/perfectrussian.py @@ -0,0 +1,37 @@ +from pyrogram import Client, filters, enums +from pyrogram.types import Message + +from utils.misc import modules_help, prefix +from utils.scripts import with_reply + +import random + + +@Client.on_message(filters.command("prus", prefix) & filters.me) +@with_reply +async def prussian_cmd(_, message: Message): + words = [ + "сука", + "нахуй", + "блять", + "блядь", + "пиздец", + "еблан", + "уебан", + "уебок", + "пизда", + "очко", + "хуй", + ] + splitted = message.reply_to_message.text.split() + + for i in range(0, len(splitted), random.randint(2, 3)): + for j in range(1, 2): + splitted.insert(i, random.choice(words)) + + await message.edit(" ".join(splitted), parse_mode=enums.ParseMode.HTML) + + +modules_help["perfectrussian"] = { + "prus": "translate your message into perfect 🇷🇺Russian", +} diff --git a/modules/ping.py b/modules/ping.py new file mode 100644 index 0000000..9a2cb31 --- /dev/null +++ b/modules/ping.py @@ -0,0 +1,32 @@ +# Moon-Userbot - telegram userbot +# Copyright (C) 2020-present Moon Userbot Organization +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +from pyrogram import Client, filters +from pyrogram.types import Message + +from utils.misc import modules_help, prefix + + +@Client.on_message(filters.command(["ping", "p"], prefix) & filters.me) +async def ping(client: Client, message: Message): + latency = await client.ping() + await message.edit(f"Pong! {latency}ms") + + +modules_help["ping"] = { + "ping": "Check ping to Telegram servers", +} diff --git a/modules/prefix.py b/modules/prefix.py new file mode 100644 index 0000000..dcec0d5 --- /dev/null +++ b/modules/prefix.py @@ -0,0 +1,52 @@ +# Moon-Userbot - telegram userbot +# Copyright (C) 2020-present Moon Userbot Organization +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from pyrogram import Client, filters +from pyrogram.types import Message + +from utils.db import db +from utils.misc import modules_help, prefix +from utils.scripts import restart + + +@Client.on_message( + filters.command(["sp", "setprefix"], prefix) & filters.me +) +async def setprefix(_, message: Message): + if len(message.command) > 1: + pref = message.command[1] + db.set("core.main", "prefix", pref) + await message.edit( + f"Prefix [ {pref} ] is set!\nRestarting..." + ) + db.set( + "core.updater", + "restart_info", + { + "type": "restart", + "chat_id": message.chat.id, + "message_id": message.id, + }, + ) + restart() + else: + await message.edit("The prefix must not be empty!") + + +modules_help["prefix"] = { + "sp [prefix]": "Set custom prefix", + "setprefix [prefix]": "Set custom prefix", +} diff --git a/modules/purge.py b/modules/purge.py new file mode 100644 index 0000000..ba435d8 --- /dev/null +++ b/modules/purge.py @@ -0,0 +1,54 @@ +# Moon-Userbot - telegram userbot +# Copyright (C) 2020-present Moon Userbot Organization +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import asyncio +from pyrogram import Client, filters +from pyrogram.types import Message + +from utils.misc import modules_help, prefix +from utils.scripts import with_reply + + +@Client.on_message(filters.command("del", prefix) & filters.me) +async def del_msg(_, message: Message): + await message.delete() + await message.reply_to_message.delete() + + +@Client.on_message(filters.command("purge", prefix) & filters.me) +@with_reply +async def purge(client: Client, message: Message): + chunk = [] + async for msg in client.get_chat_history( + chat_id=message.chat.id, + limit=message.id - message.reply_to_message.id + 1, + ): + if msg.id < message.reply_to_message.id: + break + chunk.append(msg.id) + if len(chunk) >= 100: + await client.delete_messages(message.chat.id, chunk) + chunk.clear() + await asyncio.sleep(1) + + if len(chunk) > 0: + await client.delete_messages(message.chat.id, chunk) + + +modules_help["purge"] = { + "purge [reply]": "Purge (delete all messages) chat from replied message to last", + "del [reply]": "Delete replied message", +} diff --git a/modules/python.py b/modules/python.py new file mode 100644 index 0000000..ecaa181 --- /dev/null +++ b/modules/python.py @@ -0,0 +1,86 @@ +# Moon-Userbot - telegram userbot +# Copyright (C) 2020-present Moon Userbot Organization +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from contextlib import redirect_stdout +from io import StringIO + +from pyrogram import Client, filters +from pyrogram.types import Message + +# noinspection PyUnresolvedReferences +from utils.misc import modules_help, prefix +from utils.scripts import format_exc + +# noinspection PyUnresolvedReferences + + +# noinspection PyUnusedLocal +@Client.on_message( + filters.command(["ex", "exec", "py", "exnoedit"], prefix) & filters.me +) +async def user_exec(_: Client, message: Message): + if len(message.command) == 1: + await message.edit("Code to execute isn't provided") + return + + code = message.text.split(maxsplit=1)[1] + stdout = StringIO() + + await message.edit("Executing...") + + try: + with redirect_stdout(stdout): + exec(code) # skipcq + text = ( + "Code:\n" + f"{code}\n\n" + "Result:\n" + f"{stdout.getvalue()}" + ) + if message.command[0] == "exnoedit": + await message.reply(text) + else: + await message.edit(text) + except Exception as e: + await message.edit(format_exc(e)) + + +# noinspection PyUnusedLocal +@Client.on_message(filters.command(["ev", "eval"], prefix) & filters.me) +async def user_eval(client: Client, message: Message): + if len(message.command) == 1: + await message.edit("Code to eval isn't provided") + return + + code = message.text.split(maxsplit=1)[1] + + try: + result = eval(code) # skipcq + await message.edit( + "Expression:\n" + f"{code}\n\n" + "Result:\n" + f"{result}" + ) + except Exception as e: + await message.edit(format_exc(e)) + + +modules_help["python"] = { + "ex [python code]": "Execute Python code", + "exnoedit [python code]": "Execute Python code and return result with reply", + "eval [python code]": "Eval Python code", +} diff --git a/modules/reactionspam.py b/modules/reactionspam.py new file mode 100644 index 0000000..3eeb1a5 --- /dev/null +++ b/modules/reactionspam.py @@ -0,0 +1,44 @@ +import asyncio, random +from pyrogram import Client, filters, enums +from pyrogram.raw import functions +from pyrogram.types import Message +from utils.misc import modules_help, prefix +from utils.scripts import format_exc + +emojis = [ + "👍", + "👎", + "❤️", + "🔥", + "🥰", + "👏", + "😁", + "🤔", + "🤯", + "😱", + "🤬", + "😢", + "🎉", + "🤩", + "🤮", + "💩", +] + + +@Client.on_message(filters.command("reactspam", prefix) & filters.me) +async def reactspam(client: Client, message: Message): + await message.edit(f"One moment...", parse_mode=enums.ParseMode.HTML) + try: + selected_emojis = random.sample(emojis, 3) + print(selected_emojis) + await client.send_reaction( + message.chat.id, + message_id=message.reply_to_message.id, + emoji=selected_emojis, + ) + await message.delete() + except Exception as e: + return await message.edit_text(format_exc(e)) + + +modules_help["reactionspam"] = {"reactspam [amount]* [emoji]*": "spam reactions"} diff --git a/modules/removebg.py b/modules/removebg.py new file mode 100644 index 0000000..535d56d --- /dev/null +++ b/modules/removebg.py @@ -0,0 +1,194 @@ +# Copyright (C) 2020-2021 by DevsExpo@Github, < https://github.com/DevsExpo >. +# +# This file is part of < https://github.com/DevsExpo/FridayUserBot > project, +# and is released under the "GNU v3.0 License Agreement". +# Please see < https://github.com/DevsExpo/blob/master/LICENSE > +# +# All rights reserved. +# Modifed by @moonuserbot + +import io +import os +from datetime import datetime +from functools import wraps +from io import BytesIO + +import requests +from PIL import Image +from pyrogram import Client, enums, filters +from pyrogram.types import Message + +from utils.config import rmbg_key +from utils.misc import modules_help, prefix +from utils.scripts import edit_or_reply, format_exc + + +async def convert_to_image(message, client) -> None | str: + """Convert Most Media Formats To Raw Image""" + if not message: + return None + if not message.reply_to_message: + return None + final_path = None + if not ( + message.reply_to_message.video + or message.reply_to_message.photo + or message.reply_to_message.sticker + or message.reply_to_message.media + or message.reply_to_message.animation + or message.reply_to_message.audio + or message.reply_to_message.document + ): + return None + if message.reply_to_message.photo: + final_path = await message.reply_to_message.download() + elif message.reply_to_message.sticker: + if message.reply_to_message.sticker.mime_type == "image/webp": + final_path = "webp_to_png_s_proton.png" + path_s = await message.reply_to_message.download() + im = Image.open(path_s) + im.save(final_path, "PNG") + else: + path_s = await client.download_media(message.reply_to_message) + final_path = "lottie_proton.png" + cmd = ( + f"lottie_convert.py --frame 0 -if lottie -of png {path_s} {final_path}" + ) + await exec(cmd) # skipcq + elif message.reply_to_message.audio: + thumb = message.reply_to_message.audio.thumbs[0].file_id + final_path = await client.download_media(thumb) + elif message.reply_to_message.video or message.reply_to_message.animation: + final_path = "fetched_thumb.png" + vid_path = await client.download_media(message.reply_to_message) + await exec( + f"ffmpeg -i {vid_path} -filter:v scale=500:500 -an {final_path}" + ) # skipcq + elif message.reply_to_message.document: + if message.reply_to_message.document.mime_type == "image/jpeg": + final_path = await message.reply_to_message.download() + elif message.reply_to_message.document.mime_type == "image/png": + final_path = await message.reply_to_message.download() + elif message.reply_to_message.document.mime_type == "image/webp": + final_path = "webp_to_png_s_proton.png" + path_s = await message.reply_to_message.download() + im = Image.open(path_s) + im.save(final_path, "PNG") + else: + return None + return final_path + + +def remove_background(photo_data): + with open(photo_data, "rb") as image_file: + image_data = image_file.read() + response = requests.post( + "https://api.remove.bg/v1.0/removebg", + files={"image_file": image_data}, + data={"size": "auto"}, + headers={"X-Api-Key": rmbg_key}, + ) + if response.status_code == 200: + return BytesIO(response.content) + print("Error:", response.status_code, response.text) + return None + + +def _check_rmbg(func): + @wraps(func) + async def check_rmbg(client: Client, message: Message): + if not rmbg_key: + await edit_or_reply( + message, + "Is Your RMBG Api 'rmbg_key' Valid Or You Didn't Add It??", + ) + else: + await func(client, message) + + return check_rmbg + + +@Client.on_message(filters.command("rmbg", prefix) & filters.me) +@_check_rmbg +async def rmbg(client: Client, message: Message): + pablo = await edit_or_reply(message, "Processing...") + if not message.reply_to_message: + await pablo.edit("Reply To A Image Please!") + return + cool = await convert_to_image(message, client) + if not cool: + await pablo.edit("Reply to a valid media first.") + return + start = datetime.now() + await pablo.edit("sending to ReMove.BG") + input_file_name = cool + files = { + "image_file": (input_file_name, open(input_file_name, "rb")), + } + r = requests.post( + "https://api.remove.bg/v1.0/removebg", + headers={"X-Api-Key": rmbg_key}, + files=files, + allow_redirects=True, + stream=True, + ) + if os.path.exists(cool): + os.remove(cool) + output_file_name = r + contentType = output_file_name.headers.get("content-type") + if "image" in contentType: + with io.BytesIO(output_file_name.content) as remove_bg_image: + remove_bg_image.name = "BG_rem.png" + await client.send_document( + message.chat.id, remove_bg_image, reply_to_message_id=message.id + ) + end = datetime.now() + ms = (end - start).seconds + await pablo.edit( + f"Removed image's Background in {ms} seconds." + ) + if os.path.exists("BG_rem.png"): + os.remove("BG_rem.png") + else: + await pablo.edit( + "ReMove.BG API returned Errors." + + f"\n`{output_file_name.content.decode('UTF-8')}" + ) + + +@Client.on_message(filters.command("rebg", prefix) & filters.me) +async def rembg(client: Client, message: Message): + await message.edit("Processing...") + chat_id = message.chat.id + try: + try: + photo_data = await message.download() + except ValueError: + try: + photo_data = await message.reply_to_message.download() + except ValueError: + await message.edit("File not found") + return + background_removed_data = remove_background(photo_data) + + if background_removed_data: + await message.delete() + await client.send_photo( + chat_id, photo=background_removed_data, caption="Background removed!" + ) + else: + await message.edit_text( + "`Is Your RMBG Api 'rmbg_key' Valid Or You Didn't Add It??`\n **Check logs for details**", + parse_mode=enums.ParseMode.MARKDOWN, + ) + except Exception as e: + await message.reply_text(f"An error occurred: {format_exc(e)}") + finally: + if os.path.exists(photo_data): + os.remove(photo_data) + + +modules_help["removebg"] = { + "rebg [reply to image]*": "remove background from image without transparency", + "rmbg [reply to image]*": "remove background from image with transparency", +} diff --git a/modules/say.py b/modules/say.py new file mode 100644 index 0000000..6ccd962 --- /dev/null +++ b/modules/say.py @@ -0,0 +1,37 @@ +# Moon-Userbot - telegram userbot +# Copyright (C) 2020-present Moon Userbot Organization +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from pyrogram import Client, filters +from pyrogram.types import Message + +from utils.misc import modules_help, prefix + + +@Client.on_message(filters.command(["say", "s"], prefix) & filters.me) +async def say(_, message: Message): + if len(message.command) == 1: + return + command = " ".join(message.command[1:]) + await message.edit(f"{command}") + + +modules_help["say"] = { + "say [command]*": "Send message that won't be interpreted by userbot", +} diff --git a/modules/sendmod.py b/modules/sendmod.py new file mode 100644 index 0000000..b989164 --- /dev/null +++ b/modules/sendmod.py @@ -0,0 +1,58 @@ +# Moon-Userbot - telegram userbot +# Copyright (C) 2020-present Moon Userbot Organization +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import os + +from pyrogram import Client, filters +from pyrogram.types import Message + +from utils.misc import modules_help, prefix +from utils.scripts import format_exc, format_module_help, format_small_module_help + + +@Client.on_message(filters.command(["sendmod", "sm"], prefix) & filters.me) +async def sendmod(client: Client, message: Message): + if len(message.command) == 1: + await message.edit("Module name to send is not provided") + return + + await message.edit("Dispatching...") + try: + module_name = message.command[1].lower() + if module_name in modules_help: + text = format_module_help(module_name) + if len(text) >= 1024: + text = format_small_module_help(module_name) + if os.path.isfile(f"modules/{module_name}.py"): + await client.send_document( + message.chat.id, f"modules/{module_name}.py", caption=text + ) + elif os.path.isfile(f"modules/custom_modules/{module_name.lower()}.py"): + await client.send_document( + message.chat.id, + f"modules/custom_modules/{module_name}.py", + caption=text, + ) + await message.delete() + else: + await message.edit(f"Module {module_name} not found!") + except Exception as e: + await message.edit(format_exc(e)) + + +modules_help["sendmod"] = { + "sendmod [module_name]": "Send module to interlocutor", +} diff --git a/modules/sessionkiller.py b/modules/sessionkiller.py new file mode 100644 index 0000000..053d310 --- /dev/null +++ b/modules/sessionkiller.py @@ -0,0 +1,156 @@ +# Moon-Userbot - telegram userbot +# Copyright (C) 2020-present Moon Userbot Organization +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# TODO: Add ability to kill session by hash + +import time +from datetime import datetime +from html import escape + +from pyrogram import Client, filters +from pyrogram import ContinuePropagation +from pyrogram.errors import RPCError +from pyrogram.raw.functions.account import GetAuthorizations, ResetAuthorization +from pyrogram.raw.types import UpdateServiceNotification +from pyrogram.types import Message + +from utils.db import db +from utils.misc import modules_help, prefix + +auth_hashes = db.get("core.sessionkiller", "auths_hashes", []) + + +@Client.on_message(filters.command(["sessions"], prefix) & filters.me) +async def sessions_list(client: Client, message: Message): + formatted_sessions = [] + sessions = (await client.invoke(GetAuthorizations())).authorizations + for num, session in enumerate(sessions, 1): + formatted_sessions.append( + f"{num}. {escape(session.device_model)} on {escape(session.platform if session.platform != '' else 'unknown platform')}\n" + f"Hash: {session.hash}\n" + f"App name: {escape(session.app_name)} v.{escape(session.app_version if session.app_version != '' else 'unknown')}\n" + f"Created (last activity): {datetime.fromtimestamp(session.date_created).isoformat()} ({datetime.fromtimestamp(session.date_active).isoformat()})\n" + f"IP and location: : {session.ip} ({session.country})\n" + f"Official status: {'✅' if session.official_app else '❌️'}\n" + f"2FA accepted: {'❌️️' if session.password_pending else '✅'}\n" + f"Can accept calls / secret chats: {'❌️️' if session.call_requests_disabled else '✅'} / {'❌️️' if session.encrypted_requests_disabled else '✅'}" + ) + answer = "Active sessions at your account:\n\n" + chunk = [] + for s in formatted_sessions: + chunk.append(s) + if len(chunk) == 5: + answer += "\n\n".join(chunk) + await message.reply(answer) + answer = "" + chunk.clear() + if chunk: + await message.reply("\n\n".join(chunk)) + await message.delete() + + +@Client.on_message(filters.command(["sessionkiller", "sk"], prefix) & filters.me) +async def sessionkiller(client: Client, message: Message): + if len(message.command) == 1: + if db.get("core.sessionkiller", "enabled", False): + await message.edit( + "Sessionkiller status: enabled\n" + f"You can disable it with {prefix}sessionkiller disable" + ) + else: + await message.edit( + "Sessionkiller status: disabled\n" + f"You can enable it with {prefix}sessionkiller enable" + ) + elif message.command[1] in ["enable", "on", "1", "yes", "true"]: + db.set("core.sessionkiller", "enabled", True) + await message.edit("Sessionkiller enabled!") + db.set( + "core.sessionkiller", + "auths_hashes", + [ + auth.hash + for auth in (await client.invoke(GetAuthorizations())).authorizations + ], + ) + + elif message.command[1] in ["disable", "off", "0", "no", "false"]: + db.set("core.sessionkiller", "enabled", False) + await message.edit("Sessionkiller disabled!") + else: + await message.edit(f"Usage: {prefix}sessionkiller [enable|disable]") + + +@Client.on_raw_update() +async def check_new_login(client: Client, update: UpdateServiceNotification, _, __): + if not isinstance(update, UpdateServiceNotification) or not update.type.startswith( + "auth" + ): + raise ContinuePropagation + if not db.get("core.sessionkiller", "enabled", False): + raise ContinuePropagation + authorizations = (await client.invoke(GetAuthorizations()))["authorizations"] + for auth in authorizations: + if auth.current: + continue + if auth["hash"] not in auth_hashes: + # found new unexpected login + try: + await client.invoke(ResetAuthorization(hash=auth.hash)) + except RPCError: + info_text = ( + "Someone tried to log in to your account. You can see this report because you" + "turned on this feature. But I couldn't terminate attacker's session and " + "⚠ you must reset it manually. You should change your 2FA password " + "(if enabled), or set it.\n" + ) + else: + info_text = ( + "Someone tried to log in to your account. Since you have enabled " + "this feature, I deleted the attacker's session from your account. " + "You should change your 2FA password (if enabled), or set it.\n" + ) + logined_time = datetime.utcfromtimestamp(auth.date_created).strftime( + "%d-%m-%Y %H-%M-%S UTC" + ) + full_report = ( + "!!! ACTION REQUIRED !!!\n" + + info_text + + "Below is the information about the attacker that I got.\n\n" + f"Unique authorization hash: {auth.hash} (not valid anymore)\n" + f"Device model: {escape(auth.device_model)}\n" + f"Platform: {escape(auth.platform)}\n" + f"API ID: {auth.api_id}\n" + f"App name: {escape(auth.app_name)}\n" + f"App version: {auth.app_version}\n" + f"Logined at: {logined_time}\n" + f"IP: {auth.ip}\n" + f"Country: {auth.country}\n" + f"Official app: {'yes' if auth.official_app else 'no'}\n\n" + f"It is you? Type {prefix}sk off and try logging " + f"in again." + ) + # schedule sending report message so user will get notification + schedule_date = int(time.time() + 15) + await client.send_message("me", full_report, schedule_date=schedule_date) + return + + +modules_help["sessions"] = { + "sessionkiller [enable|disable]": "When enabled, every new session will be terminated.\n" + "Useful for additional protection for your account", + "sessions": "List all sessions on your account", +} diff --git a/modules/sgb.py b/modules/sgb.py new file mode 100644 index 0000000..ceada81 --- /dev/null +++ b/modules/sgb.py @@ -0,0 +1,52 @@ +# Moon-Userbot - telegram userbot +# Copyright (C) 2020-present Moon Userbot Organization +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +from pyrogram import Client, filters +from pyrogram.errors import YouBlockedUser +from pyrogram.types import Message + +from utils.conv import Conversation +from utils.misc import modules_help, prefix +from utils.scripts import format_exc + + +@Client.on_message(filters.command("sgb", prefix) & filters.me) +async def sg(client: Client, message: Message): + if message.reply_to_message and message.reply_to_message.from_user: + user_id = message.reply_to_message.from_user.id + else: + await message.edit(f"Usage: {prefix}sgb [id]") + return + try: + await message.edit("Processing please wait") + bot_username = "@SangMata_beta_bot" + async with Conversation(client, bot_username, timeout=15) as conv: + await conv.send_message(str(user_id)) + response = await conv.get_response(timeout=10) + if "you have used up your quota" in response.text: + await message.edit(response.text.splitlines()[0]) + return + return await message.edit(response.text) + except YouBlockedUser: + await message.edit("Please unblock @SangMata_beta_bot first.") + except TimeoutError: + await message.edit("No response from bot within the timeout period.") + except Exception as e: + await message.edit(f"Error: {format_exc(e)}") + + +modules_help["sangmata"] = {"sgb": "reply to any user"} diff --git a/modules/shell.py b/modules/shell.py new file mode 100644 index 0000000..d7f5b9b --- /dev/null +++ b/modules/shell.py @@ -0,0 +1,64 @@ +# Moon-Userbot - telegram userbot +# Copyright (C) 2020-present Moon Userbot Organization +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from subprocess import Popen, PIPE, TimeoutExpired +import os +from time import perf_counter + +from pyrogram import Client, filters +from pyrogram.types import Message +from pyrogram.errors import MessageTooLong + +from utils.misc import modules_help, prefix + + +@Client.on_message(filters.command(["shell", "sh"], prefix) & filters.me) +async def shell(_, message: Message): + if len(message.command) < 2: + return await message.edit("Specify the command in message text") + cmd_text = message.text.split(maxsplit=1)[1] + cmd_args = cmd_text.split() + cmd_obj = Popen( + cmd_args, + stdout=PIPE, + stderr=PIPE, + text=True, + ) + + char = "#" if os.getuid() == 0 else "$" + text = f"{char} {cmd_text}\n\n" + + await message.edit(text + "Running...") + try: + start_time = perf_counter() + stdout, stderr = cmd_obj.communicate(timeout=60) + except TimeoutExpired: + text += "Timeout expired (60 seconds)" + else: + stop_time = perf_counter() + if stdout: + text += f"Output:\n{stdout}\n\n" + if stderr: + text += f"Error:\n{stderr}\n\n" + text += f"Completed in {round(stop_time - start_time, 5)} seconds with code {cmd_obj.returncode}" + try: + await message.edit(text) + except MessageTooLong: + await message.edit(text[:-100]) + cmd_obj.kill() + + +modules_help["shell"] = {"sh [command]*": "Execute command in shell"} diff --git a/modules/socialstalk.py b/modules/socialstalk.py new file mode 100644 index 0000000..1be08cb --- /dev/null +++ b/modules/socialstalk.py @@ -0,0 +1,200 @@ +from datetime import datetime +import json +import requests +from pyrogram import Client, enums, filters +from pyrogram.types import Message +import io + +from utils.misc import modules_help, prefix + +TIKTOK_API_URL = "https://api.maher-zubair.tech/stalk/tiktok?q=" +INSTAGRAM_API_URL = "https://tools.betabotz.eu.org/tools/stalk-ig?q=" +GH_STALK = "https://api.github.com/users/" + + +@Client.on_message(filters.command(["tiktokstalk"], prefix) & filters.me) +async def tiktok_stalk(_, message: Message): + query = "" + if len(message.command) > 1: + query = message.command[1] + elif message.reply_to_message: + query = message.reply_to_message.text.strip() + + if not query: + await message.edit( + "Usage: `tiktokstalk ` or reply to a message containing the username." + ) + return + + await message.edit("Fetching TikTok profile...") + url = f"{TIKTOK_API_URL}{query}" + response = requests.get(url) + if response.status_code == 200: + data = response.json().get("result", {}) + if data: + profile_pic_url = data.get("profile", "") + profile_pic = requests.get(profile_pic_url).content + profile_pic_stream = io.BytesIO(profile_pic) + profile_pic_stream.name = "profile.jpg" + + await message.reply_photo( + photo=profile_pic_stream, + caption=( + f"TikTok Profile:\n" + f"Name: {data.get('name', 'N/A')}\n" + f"Username: {data.get('username', 'N/A')}\n" + f"Followers: {data.get('followers', 'N/A')}\n" + f"Following: {data.get('following', 'N/A')}\n" + f"Likes: {data.get('likes', 'N/A')}\n" + f"Description: {data.get('desc', 'N/A')}\n" + f"Bio: {data.get('bio', 'N/A')}" + ), + parse_mode=enums.ParseMode.MARKDOWN, + ) + else: + await message.edit("No data found for this TikTok user.") + await message.delete() + else: + await message.edit("An error occurred, please try again later.") + + +@Client.on_message(filters.command("ipinfo", prefix) & filters.me) +async def ipinfo(_, message: Message): + searchip = message.text.split(" ", 1) + if len(searchip) == 1: + await message.edit_text(f"Usage:{prefix}ipinfo [ip]") + return + searchip = searchip[1] + m = await message.edit_text("Searching...") + await m.edit_text("🔎") + try: + url = requests.get( + f"http://ip-api.com/json/{searchip}?fields=status,message,continent,continentCode,country,countryCode,region,regionName,city,district,zip,lat,lon,timezone,offset,currency,isp,org,as,asname,reverse,mobile,proxy,hosting,query" + ) + response = json.loads(url.text) + text = f""" +IP Address: {response['query']} +Status: {response['status']} +Continent Code: {response['continentCode']} +Country: {response['country']} +Country Code : {response['countryCode']} +Region: {response['region']} +Region Name : {response['regionName']} +City: {response['city']} +District: {response['district']} +ZIP: {response['zip']} +Latitude: {response['lat']} +Longitude: {response['lon']} +Time Zone: {response['timezone']} +Offset: {response['offset']} +Currency: {response['currency']} +ISP: {response['isp']} +Org: {response['org']} +As: {response['as']} +Asname: {response['asname']} +Reverse: {response['reverse']} +User is on Mobile: {response['mobile']} +Proxy: {response['proxy']} +Hosting: {response['hosting']}""" + await m.edit_text(text) + except: + await m.edit_text("Unable To Find Info!") + + +@Client.on_message(filters.command("instastalk", prefix) & filters.me) +async def instagram_stalk(_, message: Message): + if len(message.command) > 1: + query = message.text.split(maxsplit=1)[1] + elif message.reply_to_message: + query = message.reply_to_message.text + else: + await message.edit( + f"Usage: {prefix}instastalk or reply to a message containing the username." + ) + return + + await message.edit("Fetching Instagram profile...") + url = f"{INSTAGRAM_API_URL}{query}" + response = requests.get(url) + if response.status_code == 200: + data = response.json().get("result", {}).get("user_info", {}) + if data: + profile_pic_url = data.get("profile_pic_url", "") + profile_pic = requests.get(profile_pic_url).content + profile_pic_stream = io.BytesIO(profile_pic) + profile_pic_stream.name = "profile.jpg" + + await message.reply_photo( + photo=profile_pic_stream, + caption=( + f"Instagram Profile:\n" + f"Full Name: {data.get('full_name', 'N/A')}\n" + f"Username: {data.get('username', 'N/A')}\n" + f"Biography: {data.get('biography', 'N/A')}\n" + f"External URL: {data.get('external_url', 'N/A')}\n" + f"Posts: {data.get('posts', 'N/A')}\n" + f"Followers: {data.get('followers', 'N/A')}\n" + f"Following: {data.get('following', 'N/A')}" + ), + parse_mode=enums.ParseMode.MARKDOWN, + ) + else: + await message.edit("No data found for this Instagram user.") + await message.delete() + else: + await message.edit("An error occurred, please try again later.") + + +@Client.on_message(filters.command("ghstalk", prefix) & filters.me) +async def github_stalk(_, message: Message): + if len(message.command) > 1: + query = message.text.split(maxsplit=1)[1] + elif message.reply_to_message: + query = message.reply_to_message.text + else: + await message.edit( + f"Usage: {prefix}ghstalk or reply to a message containing the username." + ) + return + + await message.edit("Fetching GitHub profile...") + url = f"{GH_STALK}{query}" + response = requests.get(url) + if response.status_code == 200: + data = response.json() + created_at = data.get("created_at", "N/A") + formatted_date = ( + datetime.strptime(created_at, "%Y-%m-%dT%H:%M:%S%z") + if created_at != "N/A" + else None + ) + if data: + await message.reply_photo( + photo=data.get("avatar_url", "").replace("?v=4", ""), + caption=f"GitHub Profile:\n" + f"Name: {data.get('name', 'N/A')}\n" + f"Username: {data.get('login', 'N/A')}\n" + f"Bio: {data.get('bio', 'N/A')}\n" + f"Public Repositories: {data.get('public_repos', 'N/A')}\n" + f"Public Gists: {data.get('public_gists', 'N/A')}\n" + f"Company: {data.get('company', 'N/A')}\n" + f"Location: {data.get('location', 'N/A')}\n" + f"Email: {data.get('email', 'N/A')}\n" + f"Website: {data.get('blog', 'N/A')}\n" + f"Created At: {formatted_date.strftime('%Y-%m-%d %I:%M:%S %p') if formatted_date else 'N/A'}\n" + f"Hireable: {data.get('hireable', 'N/A')}\n" + f"Followers: {data.get('followers', 'N/A')}\n" + f"Following: {data.get('following', 'N/A')}", + ) + else: + await message.edit("No data found for this GitHub user.") + else: + await message.edit("An error occurred, please try again later.") + + +modules_help["socialstalk"] = { + "tiktokstalk [username]*": "Get TikTok profile information", + "ipinfo [IP address]*": "Get information about an IP address", + "instastalk [username]*": "Get Instagram profile information", + "ghstalk [username]*": "Get GitHub profile information", +} diff --git a/modules/spam.py b/modules/spam.py new file mode 100644 index 0000000..deb8431 --- /dev/null +++ b/modules/spam.py @@ -0,0 +1,54 @@ +# Moon-Userbot - telegram userbot +# Copyright (C) 2020-present Moon Userbot Organization +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import asyncio + +from pyrogram import Client, filters +from pyrogram.types import Message + +from utils.misc import modules_help, prefix + +commands = ["spam", "statspam", "slowspam", "fastspam"] + + +@Client.on_message(filters.command(commands, prefix) & filters.me) +async def spam(client: Client, message: Message): + amount = int(message.command[1]) + text = " ".join(message.command[2:]) + + cooldown = {"spam": 0.15, "statspam": 0.1, "slowspam": 0.9, "fastspam": 0} + + await message.delete() + + for _msg in range(amount): + if message.reply_to_message: + sent = await message.reply_to_message.reply(text) + else: + sent = await client.send_message(message.chat.id, text) + + if message.command[0] == "statspam": + await asyncio.sleep(0.1) + await sent.delete() + + await asyncio.sleep(cooldown[message.command[0]]) + + +modules_help["spam"] = { + "spam [amount] [text]": "Start spam", + "statspam [amount] [text]": "Send and delete", + "fastspam [amount] [text]": "Start fast spam", + "slowspam [amount] [text]": "Start slow spam", +} diff --git a/modules/spin.py b/modules/spin.py new file mode 100644 index 0000000..d7e8e1b --- /dev/null +++ b/modules/spin.py @@ -0,0 +1,158 @@ +import asyncio +import random +from io import BytesIO + +import aiohttp + +# noinspection PyUnresolvedReferences +from modules.squotes import render_message +from pyrogram import Client, enums, filters, types +from pyrogram.types import Message + +# noinspection PyUnresolvedReferences +from utils.misc import modules_help, prefix +from utils.scripts import format_exc, import_library, resize_image + +Image = import_library("PIL", "pillow").Image +np = import_library("numpy") +imageio = import_library("imageio") + + +def create_gif(filename: str, offset: int, fps: int = 2, typ: str = "spin"): + img = Image.open(f"downloads/{filename}") + if typ.lower() != "spin": + img = img.resize( + (random.randint(200, 1280), random.randint(200, 1280)), Image.ANTIALIAS + ) + imageio.mimsave( + "downloads/video.gif", + [img.rotate(-(i % 360)) for i in range(1, 361, offset)], + fps=fps, + ) + + +async def quote_cmd(client: Client, message: types.Message): + count = 1 + + is_png = False + send_for_me = False + no_reply = False + + messages = [] + + async for msg in client.get_chat_history( + message.chat.id, offset_id=message.reply_to_message.id, reverse=True + ): + if msg.empty: + continue + if msg.message_id >= message.id: + break + if no_reply: + msg.reply_to_message = None + + messages.append(msg) + + if len(messages) >= count: + break + + if send_for_me: + await message.delete() + message = await client.send_message( + "me", "Generating...", parse_mode=enums.ParseMode.HTML + ) + else: + await message.edit("Generating...", parse_mode=enums.ParseMode.HTML) + + url = "https://quotes.fl1yd.su/generate" + params = { + "messages": [ + await render_message(client, msg) for msg in messages if not msg.empty + ], + "quote_color": "#162330", + "text_color": "#fff", + } + + response = await aiohttp.ClientSession().post(url, json=params) + if response.status != 200: + return await message.edit( + f"Quotes API error!\n" f"{response.text}", + parse_mode=enums.ParseMode.HTML, + ) + + resized = resize_image( + BytesIO(await response.read()), img_type="PNG" if is_png else "WEBP" + ) + return resized, is_png + + +@Client.on_message(filters.command(["spin", "dspin"], prefix) & filters.me) +async def spin_handler(client: Client, message: Message): + if not message.reply_to_message: + await message.edit( + "Reply to a message to spin it!", + parse_mode=enums.ParseMode.HTML, + ) + await message.edit( + "Downloading sticker...", parse_mode=enums.ParseMode.HTML + ) + return await message.edit( + "Invalid file type!", parse_mode=enums.ParseMode.HTML + ) + try: + coro = True + if message.reply_to_message.document: + filename = message.reply_to_message.document.file_name + if ( + not filename.endswith(".webp") + and not filename.endswith(".png") + and not filename.endswith(".jpg") + and not filename.endswith(".jpeg") + ): + return await message.edit( + "Invalid file type!", parse_mode=enums.ParseMode.HTML + ) + elif message.reply_to_message.sticker: + if message.reply_to_message.sticker.is_video: + return await message.edit( + "Video stickers not allowed", parse_mode=enums.ParseMode.HTML + ) + filename = "sticker.webp" + elif message.reply_to_message.text: + result = await quote_cmd(client, message) + if result[1]: + filename = "sticker.png" + else: + filename = "sticker.webp" + open(f"downloads/" + filename, "wb").write(result[0].getbuffer()) + coro = False + else: + filename = "photo.jpg" + if coro: + await message.reply_to_message.download(f"downloads/{filename}") + except Exception as ex: + return await message.edit( + f"Message can not be loaded:\n{format_exc(ex)}", + parse_mode=enums.ParseMode.HTML, + ) + await message.edit("Spinning...", parse_mode=enums.ParseMode.HTML) + offset = int(message.command[1]) if len(message.command) > 1 else 10 + fps = int(message.command[2]) if len(message.command) > 2 else 30 + try: + loop = asyncio.get_event_loop() + await loop.run_in_executor( + None, lambda: create_gif(filename, offset, fps, message.command[0]) + ) + await message.delete() + return await client.send_animation( + chat_id=message.chat.id, + animation="downloads/video.gif", + reply_to_message_id=message.reply_to_message.id, + ) + except Exception as e: + await message.reply(format_exc(e), parse_mode=enums.ParseMode.HTML) + + +modules_help["spin"] = { + "spin [offset] [fps]": "Spin message (Reply required)", + "dspin [offset] [fps]": "SHAKAL spin message (Reply required)", +} diff --git a/modules/squotes.py b/modules/squotes.py new file mode 100644 index 0000000..34605d6 --- /dev/null +++ b/modules/squotes.py @@ -0,0 +1,476 @@ +# Moon-Userbot - telegram userbot +# Copyright (C) 2020-present Moon Userbot Organization +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import base64 +from io import BytesIO + +import requests +from pyrogram import Client, filters, errors, types +from pyrogram.types import Message + +from utils.misc import modules_help, prefix +from utils.scripts import with_reply, format_exc, resize_image + +QUOTES_API = "https://quotes-o042.onrender.com/generate" + + +@Client.on_message(filters.command(["q", "quote"], prefix) & filters.me) +@with_reply +async def quote_cmd(client: Client, message: Message): + if len(message.command) > 1 and message.command[1].isdigit(): + count = int(message.command[1]) + if count < 1: + count = 1 + elif count > 15: + count = 15 + else: + count = 1 + + is_png = "!png" in message.command or "!file" in message.command + send_for_me = "!me" in message.command or "!ls" in message.command + no_reply = "!noreply" in message.command or "!nr" in message.command + + messages = [] + + async for msg in client.get_chat_history( + message.chat.id, + offset_id=message.reply_to_message.id + count, + limit=count, + ): + if msg.empty: + continue + if msg.id >= message.id: + break + if no_reply: + msg.reply_to_message = None + + messages.append(msg) + + if len(messages) >= count: + break + + messages.reverse() + + if send_for_me: + await message.delete() + message = await client.send_message("me", "Generating...") + else: + await message.edit("Generating...") + + params = { + "messages": [ + await render_message(client, msg) for msg in messages if not msg.empty + ], + "quote_color": "#162330", + "text_color": "#fff", + } + + response = requests.post(QUOTES_API, json=params) + if not response.ok: + return await message.edit( + f"Quotes API error!\n{response.text}" + ) + + resized = resize_image( + BytesIO(response.content), img_type="PNG" if is_png else "WEBP" + ) + await message.edit("Sending...") + + try: + func = client.send_document if is_png else client.send_sticker + chat_id = "me" if send_for_me else message.chat.id + await func(chat_id, resized) + except errors.RPCError as e: # no rights to send stickers, etc + await message.edit(format_exc(e)) + else: + await message.delete() + + +@Client.on_message(filters.command(["fq", "fakequote"], prefix) & filters.me) +@with_reply +async def fake_quote_cmd(client: Client, message: types.Message): + is_png = "!png" in message.command or "!file" in message.command + send_for_me = "!me" in message.command or "!ls" in message.command + no_reply = "!noreply" in message.command or "!nr" in message.command + + fake_quote_text = " ".join( + [ + arg + for arg in message.command[1:] + if arg not in ["!png", "!file", "!me", "!ls", "!noreply", "!nr"] + ] # remove some special arg words + ) + + if not fake_quote_text: + return await message.edit("Fake quote text is empty") + + q_message = await client.get_messages(message.chat.id, message.reply_to_message.id) + q_message.text = fake_quote_text + q_message.entities = None + if no_reply: + q_message.reply_to_message = None + + if send_for_me: + await message.delete() + message = await client.send_message("me", "Generating...") + else: + await message.edit("Generating...") + + params = { + "messages": [await render_message(client, q_message)], + "quote_color": "#162330", + "text_color": "#fff", + } + + response = requests.post(QUOTES_API, json=params) + if not response.ok: + return await message.edit( + f"Quotes API error!\n{response.text}" + ) + + resized = resize_image( + BytesIO(response.content), img_type="PNG" if is_png else "WEBP" + ) + await message.edit("Sending...") + + try: + func = client.send_document if is_png else client.send_sticker + chat_id = "me" if send_for_me else message.chat.id + await func(chat_id, resized) + except errors.RPCError as e: # no rights to send stickers, etc + await message.edit(format_exc(e)) + else: + await message.delete() + + +files_cache = {} + + +async def render_message(app: Client, message: types.Message) -> dict: + async def get_file(file_id) -> str: + if file_id in files_cache: + return files_cache[file_id] + + content = await app.download_media(file_id, in_memory=True) + data = base64.b64encode(bytes(content.getbuffer())).decode() + files_cache[file_id] = data + return data + + # text + if message.photo: + text = message.caption if message.caption else "" + elif message.poll: + text = get_poll_text(message.poll) + elif message.sticker: + text = "" + else: + text = get_reply_text(message) + + # media + if message.photo: + media = await get_file(message.photo.file_id) + elif message.sticker: + media = await get_file(message.sticker.file_id) + else: + media = "" + + # entities + entities = [] + if message.entities: + for entity in message.entities: + entities.append( + { + "offset": entity.offset, + "length": entity.length, + "type": str(entity.type).split(".")[-1].lower(), + } + ) + + def move_forwards(msg: types.Message): + if msg.forward_origin: + if isinstance(msg.forward_origin, types.MessageOriginUser): + msg.from_user = msg.forward_origin.sender_user + elif isinstance(msg.forward_origin, types.MessageOriginHiddenUser): + msg.from_user.id = 0 + msg.from_user.first_name = msg.forward_origin.sender_user_name + msg.from_user.last_name = "" + elif isinstance(msg.forward_origin, types.MessageOriginChat): + msg.sender_chat = msg.forward_origin.sender_chat + msg.from_user.id = 0 + if msg.forward_origin.author_signature: + msg.author_signature = msg.forward_origin.author_signature + + move_forwards(message) + + # author + author = {} + if message.from_user and message.from_user.id != 0: + from_user = message.from_user + + author["id"] = from_user.id + author["name"] = get_full_name(from_user) + if message.author_signature: + author["rank"] = message.author_signature + elif message.chat.type != "supergroup" or message.forward_date: + author["rank"] = "" + else: + try: + member = await message.chat.get_member(from_user.id) + except errors.UserNotParticipant: + author["rank"] = "" + else: + author["rank"] = getattr(member, "title", "") or ( + "owner" + if member.status == "creator" + else "admin" + if member.status == "administrator" + else "" + ) + + if from_user.photo: + author["avatar"] = await get_file(from_user.photo.big_file_id) + elif not from_user.photo and from_user.username: + # may be user blocked us, we will try to get avatar via t.me + t_me_page = requests.get(f"https://t.me/{from_user.username}").text + sub = ' 0 + and link[0] + and link[0] != "https://telegram.org/img/t_logo.png" + ): + # found valid link + avatar = requests.get(link[0]).content + author["avatar"] = base64.b64encode(avatar).decode() + else: + author["avatar"] = "" + else: + author["avatar"] = "" + else: + author["avatar"] = "" + elif message.from_user and message.from_user.id == 0: + author["id"] = 0 + author["name"] = message.from_user.first_name + author["rank"] = "" + else: + author["id"] = message.sender_chat.id + author["name"] = message.sender_chat.title + author["rank"] = "channel" if message.sender_chat.type == "channel" else "" + + if message.sender_chat.photo: + author["avatar"] = await get_file(message.sender_chat.photo.big_file_id) + else: + author["avatar"] = "" + author["via_bot"] = message.via_bot.username if message.via_bot else "" + + # reply + reply = {} + reply_msg = message.reply_to_message + if reply_msg and not reply_msg.empty: + move_forwards(reply_msg) + + if reply_msg.from_user: + reply["id"] = reply_msg.from_user.id + reply["name"] = get_full_name(reply_msg.from_user) + else: + reply["id"] = reply_msg.sender_chat.id + reply["name"] = reply_msg.sender_chat.title + + reply["text"] = get_reply_text(reply_msg) + + return { + "text": text, + "media": media, + "entities": entities, + "author": author, + "reply": reply, + } + + +def get_audio_text(audio: types.Audio) -> str: + if audio.title and audio.performer: + return f" ({audio.title} — {audio.performer})" + if audio.title: + return f" ({audio.title})" + if audio.performer: + return f" ({audio.performer})" + return "" + + +def get_reply_text(reply: types.Message) -> str: + return ( + "📷 Photo" + ("\n" + reply.caption if reply.caption else "") + if reply.photo + else ( + get_reply_poll_text(reply.poll) + if reply.poll + else ( + "📍 Location" + if reply.location or reply.venue + else ( + "👤 Contact" + if reply.contact + else ( + "🖼 GIF" + if reply.animation + else ( + "🎧 Music" + get_audio_text(reply.audio) + if reply.audio + else ( + "📹 Video" + if reply.video + else ( + "📹 Videomessage" + if reply.video_note + else ( + "🎵 Voice" + if reply.voice + else ( + ( + reply.sticker.emoji + " " + if reply.sticker.emoji + else "" + ) + + "Sticker" + if reply.sticker + else ( + "💾 File " + reply.document.file_name + if reply.document + else ( + "🎮 Game" + if reply.game + else ( + "🎮 set new record" + if reply.game_high_score + else ( + f"{reply.dice.emoji} - {reply.dice.value}" + if reply.dice + else ( + ( + "👤 joined the group" + if reply.new_chat_members[ + 0 + ].id + == reply.from_user.id + else f"👤 invited {get_full_name(reply.new_chat_members[0])} to the group" + ) + if reply.new_chat_members + else ( + ( + "👤 left the group" + if reply.left_chat_member.id + == reply.from_user.id + else f"👤 removed {get_full_name(reply.left_chat_member)}" + ) + if reply.left_chat_member + else ( + f"✏ changed group name to {reply.new_chat_title}" + if reply.new_chat_title + else ( + "🖼 changed group photo" + if reply.new_chat_photo + else ( + "🖼 removed group photo" + if reply.delete_chat_photo + else ( + "📍 pinned message" + if reply.pinned_message + else ( + "🎤 started a new video chat" + if reply.video_chat_started + else ( + "🎤 ended the video chat" + if reply.video_chat_ended + else ( + "🎤 invited participants to the video chat" + if reply.video_chat_members_invited + else ( + "👥 created the group" + if reply.group_chat_created + or reply.supergroup_chat_created + else ( + "👥 created the channel" + if reply.channel_chat_created + else reply.text + or "unsupported message" + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) + + +def get_poll_text(poll: types.Poll) -> str: + text = get_reply_poll_text(poll) + "\n" + + text += poll.question + "\n" + for option in poll.options: + text += f"- {option.text}" + if option.voter_count > 0: + text += f" ({option.voter_count} voted)" + text += "\n" + + text += f"Total: {poll.total_voter_count} voted" + + return text + + +def get_reply_poll_text(poll: types.Poll) -> str: + if poll.is_anonymous: + text = "📊 Anonymous poll" if poll.type == "regular" else "📊 Anonymous quiz" + else: + text = "📊 Poll" if poll.type == "regular" else "📊 Quiz" + if poll.is_closed: + text += " (closed)" + + return text + + +def get_full_name(user: types.User) -> str: + name = user.first_name + if user.last_name: + name += " " + user.last_name + return name + + +modules_help["squotes"] = { + "q [reply]* [count 1-15] [!png] [!me] [!noreply]": "Generate a quote\n" + "Available options: !png — send as PNG, !me — send quote to" + "saved messages, !noreply — generate quote without reply", + "fq [reply]* [!png] [!me] [!noreply] [text]*": "Generate a fake quote", +} diff --git a/modules/stickers.py b/modules/stickers.py new file mode 100644 index 0000000..499d42c --- /dev/null +++ b/modules/stickers.py @@ -0,0 +1,163 @@ +# Moon-Userbot - telegram userbot +# Copyright (C) 2020-present Moon Userbot Organization +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import os +from io import BytesIO + +from pyrogram import Client, filters, types, enums + +from utils.misc import modules_help, prefix +from utils.scripts import ( + with_reply, + interact_with, + interact_with_to_delete, + format_exc, + resize_image, +) + + +@Client.on_message(filters.command("kang", prefix) & filters.me) +@with_reply +async def kang(client: Client, message: types.Message): + await message.edit("Please wait...") + + if len(message.command) < 2: + await message.edit( + "No arguments provided\n" + f"Usage: {prefix}kang [pack]* [emoji]", + ) + return + + pack = message.command[1] + if len(message.command) >= 3: + emoji = message.command[2] + else: + emoji = "✨" + + await client.unblock_user("@stickers") + await interact_with( + await client.send_message( + "@stickers", "/cancel", parse_mode=enums.ParseMode.MARKDOWN + ) + ) + await interact_with( + await client.send_message( + "@stickers", "/addsticker", parse_mode=enums.ParseMode.MARKDOWN + ) + ) + + result = await interact_with( + await client.send_message( + "@stickers", pack, parse_mode=enums.ParseMode.MARKDOWN + ) + ) + if ".TGS" in result.text: + await message.edit("Animated packs aren't supported") + return + if "StickerExample.psd" not in result.text: + await message.edit( + "Stickerpack doesn't exitst. Create it using @Stickers bot (via /newpack command)", + ) + return + + try: + path = await message.reply_to_message.download() + except ValueError: + await message.edit( + "Replied message doesn't contain any downloadable media", + ) + return + + resized = resize_image(path) + if os.path.exists(path): + os.remove(path) + + await interact_with( + await client.send_document( + "@stickers", resized, parse_mode=enums.ParseMode.MARKDOWN + ) + ) + response = await interact_with( + await client.send_message( + "@stickers", emoji, parse_mode=enums.ParseMode.MARKDOWN + ) + ) + if "/done" in response.text: + # ok + await interact_with( + await client.send_message( + "@stickers", "/done", parse_mode=enums.ParseMode.MARKDOWN + ) + ) + await client.delete_messages("@stickers", interact_with_to_delete) + await message.edit( + f"Sticker added to pack", + ) + else: + await message.edit("Something went wrong. Check history with @stickers") + interact_with_to_delete.clear() + + +@Client.on_message(filters.command(["stp", "s2p", "stick2png"], prefix) & filters.me) +@with_reply +async def stick2png(client: Client, message: types.Message): + try: + await message.edit("Downloading...") + + path = await message.reply_to_message.download() + with open(path, "rb") as f: + content = f.read() + if os.path.exists(path): + os.remove(path) + + file_io = BytesIO(content) + file_io.name = "sticker.png" + + await client.send_document( + message.chat.id, file_io, parse_mode=enums.ParseMode.MARKDOWN + ) + except Exception as e: + await message.edit(format_exc(e)) + else: + await message.delete() + + +@Client.on_message(filters.command(["resize"], prefix) & filters.me) +@with_reply +async def resize_cmd(client: Client, message: types.Message): + try: + await message.edit("Downloading...") + + path = await message.reply_to_message.download() + resized = resize_image(path) + resized.name = "image.png" + if os.path.exists(path): + os.remove(path) + + await client.send_document( + message.chat.id, resized, parse_mode=enums.ParseMode.MARKDOWN + ) + except Exception as e: + await message.edit(format_exc(e)) + else: + await message.delete() + + +modules_help["stickers"] = { + "kang [reply]* [pack]* [emoji]": "Add sticker to defined pack", + "stp [reply]*": "Convert replied sticker to PNG", + "resize [reply]*": "Resize replied image to 512xN format", +} diff --git a/modules/support.py b/modules/support.py new file mode 100644 index 0000000..3fd4257 --- /dev/null +++ b/modules/support.py @@ -0,0 +1,88 @@ +# Moon-Userbot - telegram userbot +# Copyright (C) 2020-present Moon Userbot Organization +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from pyrogram import Client, filters +from pyrogram.types import Message +import random +import datetime + +from utils.misc import modules_help, prefix, userbot_version, python_version, gitrepo + + +@Client.on_message(filters.command(["support", "repo"], prefix) & filters.me) +async def support(_, message: Message): + devs = ["@Qbtaumai", "@H4T3H46K3R"] + random.shuffle(devs) + + commands_count = 0.0 + for module in modules_help: + for _cmd in module: + commands_count += 1 + + await message.edit( + f"Moon-Userbot\n\n" + "GitHub: Moon-Userbot\n" + "Custom modules repository: " + "custom_modules\n" + "License: GNU GPL v3\n\n" + "Channel: @moonuserbot\n" + "Custom modules: @moonub_modules\n" + "Chat [EN]: @moonub_chat\n" + f"Main developers: {', '.join(devs)}\n\n" + f"Python version: {python_version}\n" + f"Modules count: {len(modules_help) / 1}\n" + f"Commands count: {commands_count}", + disable_web_page_preview=True, + ) + + +@Client.on_message(filters.command(["version", "ver"], prefix) & filters.me) +async def version(client: Client, message: Message): + changelog = "" + ub_version = ".".join(userbot_version.split(".")[:2]) + async for m in client.search_messages("moonuserbot", query=f"{userbot_version}."): + if ub_version in m.text: + changelog = m.message_id + + await message.delete() + + remote_url = list(gitrepo.remote().urls)[0] + commit_time = ( + datetime.datetime.fromtimestamp(gitrepo.head.commit.committed_date) + .astimezone(datetime.timezone.utc) + .strftime("%Y-%m-%d %H:%M:%S %Z") + ) + + await message.reply( + f"Moon Userbot version: {userbot_version}\n" + f"Changelog in channel.\n" + f"Changelog written by " + f"Abhi\n\n" + + ( + f"Branch: {gitrepo.active_branch}\n" + if gitrepo.active_branch != "master" + else "" + ) + + f"Commit: " + f"{gitrepo.head.commit.hexsha[:7]} by {gitrepo.head.commit.author.name}\n" + f"Commit time: {commit_time}", + ) + + +modules_help["support"] = { + "support": "Information about userbot", + "version": "Check userbot version", +} diff --git a/modules/thumbnail.py b/modules/thumbnail.py new file mode 100644 index 0000000..4bf6e3c --- /dev/null +++ b/modules/thumbnail.py @@ -0,0 +1,43 @@ +# Moon-Userbot - telegram userbot +# Copyright (C) 2020-present Moon Userbot Organization +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import os +from PIL import Image + +from pyrogram import Client, filters +from pyrogram.types import Message + +from utils.misc import prefix, modules_help + + +@Client.on_message(filters.command("setthumb", prefix) & filters.me) +async def setthumb(_, message: Message): + THUMB_PATH = "downloads/thumb" + if message.reply_to_message: + if not os.path.exists(THUMB_PATH): + os.makedirs(THUMB_PATH) + new_thumb = await message.reply_to_message.download() + with Image.open(new_thumb) as img: + if img.format in ["PNG", "JPG", "JPEG"]: + new_path = os.path.join(THUMB_PATH, "thumb.jpg") + os.rename(new_thumb, new_path) + await message.edit_text("Thumbnail set successfully!") + else: + await message.edit_text("Kindly reply to a PHOTO Entity!") + return + + +modules_help["thumb"] = {"setthumb [reply_to_photo]*": "set your own custom thumbnail"} diff --git a/modules/type.py b/modules/type.py new file mode 100644 index 0000000..5941371 --- /dev/null +++ b/modules/type.py @@ -0,0 +1,43 @@ +# Moon-Userbot - telegram userbot +# Copyright (C) 2020-present Moon Userbot Organization +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import asyncio +import time + +from pyrogram import Client, filters +from pyrogram.errors import FloodWait +from pyrogram.types import Message + +from utils.misc import modules_help, prefix + + +@Client.on_message(filters.command(["type", "typewriter"], prefix) & filters.me) +async def type_cmd(_, message: Message): + text = message.text.split(maxsplit=1)[1] + typed = "" + typing_symbol = "▒" + + for char in text: + await message.edit(typed + typing_symbol) + await asyncio.sleep(0.1) + typed += char + await message.edit(typed) + await asyncio.sleep(0.1) + + +modules_help["type"] = { + "type | typewriter [text]*": "Typing emulation. Don't use a lot of characters, you can receive a lot of floodwaits!" +} diff --git a/modules/updater.py b/modules/updater.py new file mode 100644 index 0000000..e2b5018 --- /dev/null +++ b/modules/updater.py @@ -0,0 +1,117 @@ +# Moon-Userbot - telegram userbot +# Copyright (C) 2020-present Moon Userbot Organization +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import os +import sys +import shutil +import subprocess + +from pyrogram import Client, filters +from pyrogram.types import Message + +from utils.misc import modules_help, prefix, requirements_list +from utils.db import db +from utils.scripts import format_exc, restart + + +def check_command(command): + return shutil.which(command) is not None + + +@Client.on_message(filters.command("restart", prefix) & filters.me) +async def restart_cmd(_, message: Message): + db.set( + "core.updater", + "restart_info", + { + "type": "restart", + "chat_id": message.chat.id, + "message_id": message.id, + }, + ) + + if "LAVHOST" in os.environ: + await message.edit("Your lavHost is restarting...") + os.system("lavhost restart") + return + + await message.edit("Restarting...") + if os.path.exists("moonlogs.txt"): + os.remove("moonlogs.txt") + restart() + + +@Client.on_message(filters.command("update", prefix) & filters.me) +async def update(_, message: Message): + db.set( + "core.updater", + "restart_info", + { + "type": "update", + "chat_id": message.chat.id, + "message_id": message.id, + }, + ) + + if "LAVHOST" in os.environ: + await message.edit("Your lavHost is updating...") + os.system("lavhost update") + return + + await message.edit("Updating...") + try: + if not check_command("termux-setup-storage"): + subprocess.run( + [sys.executable, "-m", "pip", "install", "-U", "pip"], check=True + ) + subprocess.run(["git", "pull"], check=True) + + if ( + os.path.exists("requirements.txt") + and os.path.getsize("requirements.txt") > 0 + ): + subprocess.run( + [ + sys.executable, + "-m", + "pip", + "install", + "-U", + "-r", + "requirements.txt", + ], + check=True, + ) + + if requirements_list: + subprocess.run( + [sys.executable, "-m", "pip", "install", "-U", *requirements_list], + check=True, + ) + except Exception as e: + await message.edit(format_exc(e)) + db.remove("core.updater", "restart_info") + else: + await message.edit("Updating: done! Restarting...") + if os.path.exists("moonlogs.txt"): + os.remove("moonlogs.txt") + restart() + + +modules_help["updater"] = { + "update": "Update the userbot. If new core modules are avaliable, they will be installed", + "restart": "Restart userbot", +} diff --git a/modules/upl.py b/modules/upl.py new file mode 100644 index 0000000..a5e3dfd --- /dev/null +++ b/modules/upl.py @@ -0,0 +1,132 @@ +# Moon-Userbot - telegram userbot +# Copyright (C) 2020-present Moon Userbot Organization +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import io +import os +import time +from pyrogram import Client, filters +from pyrogram.types import Message +from utils.misc import modules_help, prefix +from utils.scripts import format_exc, progress + + +@Client.on_message(filters.command("upl", prefix) & filters.me) +async def upl(client: Client, message: Message): + if len(message.command) > 1: + link = message.text.split(maxsplit=1)[1] + elif message.reply_to_message: + link = message.reply_to_message.text + else: + await message.edit( + f"Usage: {prefix}upl [filepath to upload]" + ) + return + + if not os.path.isfile(link): + await message.edit( + f"Error: {link} is not a valid file path." + ) + return + + try: + await message.edit("Uploading Now...") + await client.send_document( + message.chat.id, + link, + progress=progress, + progress_args=(message, time.time(), "Uploading Now...", link), + ) + await message.delete() + except Exception as e: + await message.edit(format_exc(e)) + + +@Client.on_message(filters.command("dlf", prefix) & filters.me) +async def dlf(client: Client, message: Message): + if message.reply_to_message: + await client.download_media( + message.reply_to_message, + progress=progress, + progress_args=(message, time.time(), "Uploading Now..."), + ) + await message.edit("Downloaded Successfully!") + else: + await message.edit(f"Usage: {prefix}dlf [reply to a file]") + + +@Client.on_message(filters.command("moonlogs", prefix) & filters.me) +async def mupl(client: Client, message: Message): + link = "moonlogs.txt" + if os.path.exists(link): + try: + await message.edit("Uploading Now...") + with open(link, "rb") as f: + data = f.read() + bio = io.BytesIO(data) + bio.name = "moonlogs.txt" + await client.send_document( + message.chat.id, + bio, + progress=progress, + progress_args=(message, time.time(), "Uploading Now...") + ) + await message.delete() + except Exception as e: + await client.send_message(message.chat.id, format_exc(e)) + else: + await message.edit("Error: LOGS file doesn't exist.") + + +@Client.on_message(filters.command("uplr", prefix) & filters.me) +async def uplr(client: Client, message: Message): + if len(message.command) > 1: + link = message.text.split(maxsplit=1)[1] + elif message.reply_to_message: + link = message.reply_to_message.text + else: + await message.edit( + f"Usage: {prefix}upl [filepath to upload]" + ) + return + + if not os.path.isfile(link): + await message.edit( + f"Error: {link} is not a valid file path." + ) + return + + try: + await message.edit("Uploading Now...") + await client.send_document( + message.chat.id, + link, + progress=progress, + progress_args=(message, time.time(), "Uploading Now...", link), + ) + await message.delete() + except Exception as e: + await message.edit(format_exc(e)) + finally: + if os.path.exists(link): + os.remove(link) + + +modules_help["uplud"] = { + "upl [filepath]/[reply to path]*": "Upload a file from your local machine to Telegram", + "dlf": "Download a file from Telegram to your local machine", + "uplr [filepath]/[reply to path]*": "Upload a file from your local machine to Telegram, delete the file after uploading", + "moonlogs": "Upload the moonlogs.txt file to Telegram", +} diff --git a/modules/url.py b/modules/url.py new file mode 100644 index 0000000..94c3fad --- /dev/null +++ b/modules/url.py @@ -0,0 +1,285 @@ +# Moon-Userbot - telegram userbot +# Copyright (C) 2020-present Moon Userbot Organization +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import asyncio +import math +import mimetypes + +import os +import time +from datetime import datetime +from io import BytesIO +from urllib.parse import unquote + +import requests +import urllib3 +from pyrogram import Client, enums, filters +from pyrogram.types import Message +from pySmartDL import SmartDL + +from utils.config import apiflash_key +from utils.misc import modules_help, prefix +from utils.scripts import format_exc, humanbytes, progress + + +def generate_screenshot(url): + api_url = f"https://api.apiflash.com/v1/urltoimage?access_key={apiflash_key}&url={url}&format=png" + response = requests.get(api_url) + if response.status_code == 200: + return BytesIO(response.content) + return None + + +http = urllib3.PoolManager() + + +@Client.on_message(filters.command("short", prefix) & filters.me) +async def short(_, message: Message): + if len(message.command) > 1: + link = message.text.split(maxsplit=1)[1] + elif message.reply_to_message: + link = message.reply_to_message.text + else: + await message.edit(f"Usage: {prefix}short [url to short]") + return + r = http.request("GET", "https://clck.ru/--?url=" + link) + await message.edit( + r.data.decode().replace("https://", "Shortened Url:"), + disable_web_page_preview=True, + ) + + +@Client.on_message(filters.command("urldl", prefix) & filters.me) +async def urldl(client: Client, message: Message): + if len(message.command) > 1: + message_id = None + link = message.text.split(maxsplit=1)[1] + elif message.reply_to_message: + message_id = message.reply_to_message.id + link = message.reply_to_message.text + else: + await message.edit( + f"Usage: {prefix}urldl [url to download]" + ) + return + + await message.edit("Trying to download...") + + c_time = time.time() + + resp = requests.head(link, allow_redirects=True, timeout=5) + if resp.status_code != 200: + return await message.edit("Failed to fetch request header information") + + content_type = resp.headers.get("Content-Type").split(";")[0] + extension = mimetypes.guess_extension(content_type) + + # Check if the file is an executable binary + is_executable = content_type in [ + "application/octet-stream", + "application/x-msdownload", + ] + + # Get the file extension from the URL + url_extension = os.path.splitext(link)[1].lower() + + try: + os.makedirs("downloads") + if is_executable: + file_name = "downloads/" + link.split("/")[-1] + if not file_name.endswith(url_extension): + file_name += url_extension + elif extension: + file_name = "downloads/" + link.split("/")[-1] + if not file_name.endswith(extension): + file_name += extension + else: + file_name = "downloads/" + link.split("/")[-1] + except FileNotFoundError: + if is_executable: + file_name = "downloads/" + link.split("/")[-1] + if not file_name.endswith(url_extension): + file_name += url_extension + elif extension: + file_name = "downloads/" + link.split("/")[-1] + if not file_name.endswith(extension): + file_name += extension + else: + file_name = "downloads/" + link.split("/")[-1] + except FileExistsError: + if is_executable: + file_name = "downloads/" + link.split("/")[-1] + if not file_name.endswith(url_extension): + file_name += url_extension + elif extension: + file_name = "downloads/" + link.split("/")[-1] + if not file_name.endswith(extension): + file_name += extension + else: + file_name = "downloads/" + link.split("/")[-1] + + downloader = SmartDL(link, file_name, progress_bar=False, timeout=10) + start_t = datetime.now() + try: + downloader.start(blocking=False) + except Exception as e: + return await message.edit_text(format_exc(e)) + while not downloader.isFinished(): + total_length = downloader.filesize or None + downloaded = downloader.get_dl_size(human=True) + u_m = "" + now = time.time() + diff = now - c_time + percentage = downloader.get_progress() * 100 + speed = downloader.get_speed(human=True) + progress_str = ( + "".join(["▰" for _ in range(math.floor(percentage / 5))]) + + "".join(["▱" for _ in range(20 - math.floor(percentage / 5))]) + + f"\nProgress: {round(percentage, 2)}%" + ) + eta = downloader.get_eta(human=True) + try: + m = "Trying to download...\n" + m += f"File Name: {unquote(link.split('/')[-1])}\n" + m += f"Speed: {speed}\n" + m += f"{progress_str}\n" + m += f"{downloaded} of {humanbytes(total_length)}\n" + m += f"ETA: {eta}" + if round(diff % 10.00) == 0 and m != u_m: + await message.edit_text(disable_web_page_preview=True, text=m) + u_m = m + await asyncio.sleep(5) + except Exception as e: + await message.edit_text(format_exc(e)) + if os.path.exists(file_name): + end_t = datetime.now() + sec = (end_t - start_t).seconds + await message.edit_text( + f"Downloaded to {file_name} in {sec} seconds" + ) + ms_ = await message.edit("Starting Upload...") + await client.send_document( + message.chat.id, + file_name, + progress=progress, + progress_args=(ms_, c_time, "`Uploading...`"), + caption=f"File Name: {unquote(link.split('/')[-1])}\n", + reply_to_message_id=message_id, + ) + await message.delete() + os.remove(file_name) + else: + await message.edit("Failed to download") + + +@Client.on_message(filters.command("upload", prefix) & filters.me) +async def upload_cmd(_, message: Message): + max_size = 512 * 1024 * 1024 + max_size_mb = 100 + + min_file_age = 31 + max_file_age = 180 + + ms_ = await message.edit("`Downloading...`", parse_mode=enums.ParseMode.MARKDOWN) + c_time = time.time() + + try: + file_name = await message.download( + progress=progress, progress_args=(ms_, c_time, "`Downloading...`") + ) + except ValueError: + try: + file_name = await message.reply_to_message.download( + progress=progress, progress_args=(ms_, c_time, "`Downloading...`") + ) + except ValueError: + await message.edit("File to upload not found") + return + + if os.path.getsize(file_name) > max_size: + await message.edit(f"Files longer than {max_size_mb}MB isn't supported") + if os.path.exists(file_name): + os.remove(file_name) + return + + await message.edit("Uploading...") + with open(file_name, "rb") as f: + response = requests.post( + "https://x0.at", + files={"file": f}, + ) + + if response.ok: + file_size_mb = os.path.getsize(file_name) / 1024 / 1024 + file_age = int( + min_file_age + + (max_file_age - min_file_age) * ((1 - (file_size_mb / max_size_mb)) ** 2) + ) + url = response.text.replace("https://", "") + await message.edit( + f"Your URL: {url}\nYour file will remain live for {file_age} days", + disable_web_page_preview=True, + ) + else: + await message.edit( + f"API returned an error!\n{response.text}\n Not allowed" + ) + print(response.text) + if os.path.exists(file_name): + os.remove(file_name) + + +@Client.on_message(filters.command(["ws", "webshot"], prefix) & filters.me) +async def webshot(client: Client, message: Message): + if len(message.command) > 1: + url = message.text.split(maxsplit=1)[1] + if not url.startswith("https://"): + url = "https://" + message.text.split(maxsplit=1)[1] + elif message.reply_to_message: + url = message.reply_to_message.text + if not url.startswith("https://"): + url = "https://" + url + else: + await message.edit_text( + f"Usage: {prefix}webshot/{prefix}ws [url/reply to url]" + ) + return + + chat_id = message.chat.id + await message.edit("Generating screenshot...") + + try: + screenshot_data = generate_screenshot(url) + if screenshot_data: + await message.delete() + await client.send_photo( + chat_id, screenshot_data, caption=f"Screenshot of {url}" + ) + else: + await message.edit_text( + "Failed to generate screenshot...\nMake sure url is correct" + ) + except Exception as e: + await message.edit_text(f"An error occurred: {format_exc(e)}") + + +modules_help["url"] = { + "short [url]*": "short url", + "urldl [url]*": "download url content", + "upload [file|reply]*": "upload file to internet", + "webshot [link]*": "Screenshot of web page", + "ws [reply to link]*": "Screenshot of web page", +} diff --git a/modules/user_info.py b/modules/user_info.py new file mode 100644 index 0000000..ea022a6 --- /dev/null +++ b/modules/user_info.py @@ -0,0 +1,115 @@ +# Moon-Userbot - telegram userbot +# Copyright (C) 2020-present Moon Userbot Organization +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from pyrogram import Client, filters +from pyrogram.raw import functions +from pyrogram.types import Message + +from utils.misc import modules_help, prefix +from utils.scripts import format_exc, interact_with, interact_with_to_delete + + +@Client.on_message(filters.command("inf", prefix) & filters.me) +async def get_user_inf(client: Client, message: Message): + if len(message.command) >= 2: + peer = await client.resolve_peer(message.command[1]) + elif message.reply_to_message and message.reply_to_message.from_user: + peer = await client.resolve_peer(message.reply_to_message.from_user.id) + else: + peer = await client.resolve_peer("me") + + response = await client.invoke(functions.users.GetFullUser(id=peer)) + + user = response.users[0] + full_user = response.full_user + + if user.username is None: + username = "None" + else: + username = f"@{user.username}" + about = "None" if full_user.about is None else full_user.about + + user_info = f"""|=Username: {username} +|-Id: {user.id} +|-Bot: {user.bot} +|-Scam: {user.scam} +|-Name: {user.first_name} +|-Deleted: {user.deleted} +|-BIO: {about} +""" + await message.edit(user_info) + + +@Client.on_message(filters.command("inffull", prefix) & filters.me) +async def get_full_user_inf(client: Client, message: Message): + await message.edit("Receiving the information...") + + try: + if len(message.command) >= 2: + peer = await client.resolve_peer(message.command[1]) + elif message.reply_to_message and message.reply_to_message.from_user: + peer = await client.resolve_peer(message.reply_to_message.from_user.id) + else: + peer = await client.resolve_peer("me") + + response = await client.invoke(functions.users.GetFullUser(id=peer)) + + user = response.users[0] + full_user = response.full_user + + await client.unblock_user("@creationdatebot") + try: + response = await interact_with( + await client.send_message("creationdatebot", f"/id {user.id}") + ) + except RuntimeError: + creation_date = "None" + else: + creation_date = response.text + # await client.delete_messages("@creationdatebot", interact_with_to_delete) + interact_with_to_delete.clear() + + if user.username is None: + username = "None" + else: + username = f"@{user.username}" + about = "None" if full_user.about is None else full_user.about + user_info = f"""|=Username: {username} +|-Id: {user.id} +|-Account creation date: {creation_date} +|-Bot: {user.bot} +|-Scam: {user.scam} +|-Name: {user.first_name} +|-Deleted: {user.deleted} +|-BIO: {about} +|-Contact: {user.contact} +|-Can pin message: {full_user.can_pin_message} +|-Mutual contact: {user.mutual_contact} +|-Access hash: {user.access_hash} +|-Restricted: {user.restricted} +|-Verified: {user.verified} +|-Phone calls available: {full_user.phone_calls_available} +|-Phone calls private: {full_user.phone_calls_private} +|-Blocked: {full_user.blocked}""" + await message.edit(user_info) + except Exception as e: + await message.edit(format_exc(e)) + + +modules_help["user_info"] = { + "inf [reply|id|username]": "Get brief information about user", + "inffull [reply|id|username": "Get full information about user", +} diff --git a/modules/vt.py b/modules/vt.py new file mode 100644 index 0000000..1ca4ce8 --- /dev/null +++ b/modules/vt.py @@ -0,0 +1,136 @@ +# Copyright (C) 2020-2021 by DevsExpo@Github, < https://github.com/DevsExpo >. +# +# This file is part of < https://github.com/DevsExpo/FridayUserBot > project, +# and is released under the "GNU v3.0 License Agreement". +# Please see < https://github.com/DevsExpo/blob/master/LICENSE > +# +# All rights reserved. + +import os +import time + +import requests +from pyrogram import Client, enums, filters +from pyrogram.types import Message + +from utils.config import vt_key as vak +from utils.misc import modules_help, prefix +from utils.scripts import edit_or_reply, format_exc, progress + + +@Client.on_message(filters.command("vt", prefix) & filters.me) +async def scan_my_file(_, message: Message): + ms_ = await edit_or_reply(message, "`Please Wait! Scanning This File`") + if not message.reply_to_message: + return await ms_.edit( + "`Please Reply To File To Scan For Viruses`", + parse_mode=enums.ParseMode.MARKDOWN, + ) + if not message.reply_to_message.document: + return await ms_.edit( + "`Please Reply To File To Scan For Viruses`", + parse_mode=enums.ParseMode.MARKDOWN, + ) + if vak is None: + return await ms_.edit( + "`You Need To Set VIRUSTOTAL_API_KEY For Functing Of This Plugin.`", + parse_mode=enums.ParseMode.MARKDOWN, + ) + if int(message.reply_to_message.document.file_size) > 32000000: + return await ms_.edit( + f"**File Too Large, Use `{prefix}vtl` instead**", + parse_mode=enums.ParseMode.MARKDOWN, + ) + c_time = time.time() + downloaded_file_name = await message.reply_to_message.download( + progress=progress, + progress_args=(ms_, c_time, "`Downloading This File!`"), + ) + + url = "https://www.virustotal.com/vtapi/v2/file/scan" + params = {"apikey": vak} + files = {"file": (downloaded_file_name, open(downloaded_file_name, "rb"))} + response = requests.post(url, files=files, params=params, timeout=10) + try: + r_json = response.json() + md5 = r_json["md5"] + except Exception as e: + return await ms_.edit(format_exc(e)) + await ms_.edit( + f'Scanned {message.reply_to_message.document.file_name}. You Can Visit : Here In 5-10 Min To See File Report' + ) + if os.path.exists(downloaded_file_name): + os.remove(downloaded_file_name) + + +@Client.on_message(filters.command("vtl", prefix) & filters.me) +async def scan_my_large_file(_, message: Message): + ms_ = await edit_or_reply(message, "`Please Wait! Scanning This File`") + if not message.reply_to_message: + return await ms_.edit( + "`Please Reply To File To Scan For Viruses`", + parse_mode=enums.ParseMode.MARKDOWN, + ) + if not message.reply_to_message.document: + return await ms_.edit( + "`Please Reply To File To Scan For Viruses`", + parse_mode=enums.ParseMode.MARKDOWN, + ) + if vak is None: + return await ms_.edit( + "`You Need To Set VIRUSTOTAL_API_KEY For Functing Of This Plugin.`", + parse_mode=enums.ParseMode.MARKDOWN, + ) + if int(message.reply_to_message.document.file_size) > 650000000: + return await ms_.edit( + "**File Too Large, exceeded Max capacity of 650MB**", + parse_mode=enums.ParseMode.MARKDOWN, + ) + c_time = time.time() + downloaded_file_name = await message.reply_to_message.download( + progress=progress, + progress_args=(ms_, c_time, "`Downloading This File!`"), + ) + + url1 = "https://www.virustotal.com/api/v3/files/upload_url" + + headers = {"accept": "application/json", "x-apikey": vak} + + rponse = requests.get(url1, headers=headers, timeout=10) + try: + r_json = rponse.json() + upl_data = r_json["data"] + except Exception as e: + return await ms_.edit(format_exc(e)) + + url = upl_data + + files = {"file": (downloaded_file_name, open(downloaded_file_name, "rb"))} + headers = {"accept": "application/json", "x-apikey": vak} + response = requests.post(url, files=files, headers=headers, timeout=10) + + r_json = response.json() + analysis_url = r_json["data"]["links"]["self"] + + url = analysis_url + + headers = {"accept": "application/json", "x-apikey": vak} + + response_result = requests.get(url, headers=headers, timeout=10) + + try: + r_json = response_result.json() + md5 = r_json["meta"]["file_info"]["md5"] + except Exception as e: + return await ms_.edit(format_exc(e)) + await ms_.edit( + f'Scanned {message.reply_to_message.document.file_name}. You Can Visit : Here In 5-10 Min To See File Report' + ) + if os.path.exists(downloaded_file_name): + os.remove(downloaded_file_name) + + +modules_help["virustotal"] = { + "vt [reply to file]*": "Scan for viruses on Virus Total (for lower file size <32MB)", + "vtl [reply to file]*": "Scan for viruses on Virus Total (for lower file size >=32MB)", +} diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..d97068a --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,28 @@ +[project] +name = "userbot" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.13" +dependencies = [ + "aiofiles>=25.1.0", + "aiohttp>=3.13.2", + "beautifulsoup4>=4.14.2", + "click>=8.3.0", + "dnspython>=2.8.0", + "environs>=14.5.0", + "flask>=3.1.2", + "gitpython>=3.1.45", + "gunicorn>=23.0.0", + "humanize>=4.14.0", + "pillow>=10.3.0", + "psutil>=7.1.3", + "pygments>=2.19.2", + "pymongo>=4.15.3", + "pyrofork==2.3.68", + "pysmartdl>=1.3.4", + "qrcode>=8.2", + "requests>=2.32.5", + "tgcrypto>=1.2.5", + "wheel>=0.45.1", +] diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..7e26f79 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,20 @@ +pyrofork==2.3.68 +tgcrypto +wheel +gunicorn +flask +humanize +pygments +pymongo +psutil +Pillow>=10.3.0 +click +dnspython +requests +environs +GitPython +beautifulsoup4 +aiohttp +aiofiles +pySmartDL +qrcode diff --git a/start b/start new file mode 100755 index 0000000..cb333ff --- /dev/null +++ b/start @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +if [ -d ".venv" ]; then + . .venv/bin/activate +elif [ -d "venv" ]; then + . venv/bin/activate +fi + +python3 main.py diff --git a/string_gen.py b/string_gen.py new file mode 100644 index 0000000..39e9dd0 --- /dev/null +++ b/string_gen.py @@ -0,0 +1,10 @@ +from pyrogram import Client, enums + +api_id = input("Enter Your API ID: \n") +api_hash = input("Enter Your API HASH : \n") + +with Client("my_account", api_id=api_id, api_hash=api_hash, hide_password=True) as bot_: + first_name = (bot_.get_me()).first_name + string_session_ = f"String Session For {first_name} \n{bot_.export_session_string()}" + bot_.send_message("me", string_session_, parse_mode=enums.ParseMode.HTML, disable_web_page_preview=True) + print(f"String Has Been Sent To Your Saved Message : {first_name}") diff --git a/termux-install.sh b/termux-install.sh new file mode 100644 index 0000000..4a045ef --- /dev/null +++ b/termux-install.sh @@ -0,0 +1,143 @@ +if ! command -v termux-setup-storage; then + echo This script can be executed only on Termux + exit 1 +fi + +termux-wake-lock + +pkg update -y && pkg upgrade -y +pkg install python3 git clang ffmpeg wget libjpeg-turbo libcrypt ndk-sysroot zlib openssl -y || exit 2 + + +LDFLAGS="-L${PREFIX}/lib/" CFLAGS="-I${PREFIX}/include/" pip3 install --upgrade wheel pillow + +if [[ -d "Moon-Userbot" ]]; then + cd Moon-Userbot +elif [[ -f ".env.dist" ]] && [[ -f "main.py" ]] && [[ -d "modules" ]]; then + : +else + git clone https://github.com/The-MoonTg-project/Moon-Userbot || exit 2 + cd Moon-Userbot || exit 2 +fi + +if [[ -f ".env" ]] && [[ -f "my_account.session" ]]; then + echo "It seems that Moon-Userbot is already installed. Exiting..." + exit +fi + +python3 -m pip install -U -r requirements.txt || exit 2 + +echo +echo "Enter API_ID and API_HASH" +echo "You can get it here -> https://my.telegram.org/apps" +echo "Leave empty to use defaults (please note that default keys significantly increases your ban chances)" +read -r -p "API_ID > " api_id + +if [[ $api_id = "" ]]; then + api_id="2040" + api_hash="b18441a1ff607e10a989891a5462e627" +else + read -r -p "API_HASH > " api_hash +fi + +echo +echo "SET PM PERMIT warn limit" +read -r -p "PM_LIMIT warn limit > " pm_limit + +if [[ $pm_limit = "" ]]; then + pm_limit="3" + echo "limit not provided by user set to default" +fi + +echo +echo "Enter APIFLASH_KEY for webshot plugin" +echo "You can get it here -> https://apiflash.com/dashboard/access_keys" +read -r -p "APIFLASH_KEY > " apiflash_key + +if [[ $apiflash_key = "" ]]; then + echo "NOTE: API Not set you'll not be able to use .webshot plugin" +fi + +echo +echo "Enter RMBG_KEY for remove background module" +echo "You can get it here -> https://www.remove.bg/dashboard#api-key" +read -r -p "RMBG_KEY > " rmbg_key + +if [[ $rmbg_key = "" ]]; then + echo "NOTE: API Not set you'll not be able to use remove background modules" +fi + +echo +echo "Enter GEMINI_KEY if you want to use AI" +echo "NOTE: Don't Use unless you've enough storage in your device" +echo "MIN. REQ. STORAGE: 128GB" +echo "You can get it here -> https://makersuite.google.com/app/apikey" +read -r -p "GEMINI_KEY > " gemini_key + +if [[ $gemini_key = "" ]]; then + echo "NOTE: API Not set you'll not be able to use Gemini AI modules" +fi + +echo +echo "Enter COHERE_KEY if you want to use AI" +echo "You can get it here -> https://dashboard.cohere.com/api-keys" +read -r -p "COHERE_KEY > " cohere_key + +if [[ $cohere_key = "" ]]; then + echo "NOTE: API Not set you'll not be able to use Coral AI modules" +fi + +echo +echo "Enter VT_KEY for VirusTotal" +echo "You can get it here -> https://www.virustotal.com/" +read -r -p "VT_KEY > " vt_key + +if [[ $vt_key = "" ]]; then + echo "NOTE: API Not set you'll not be able to use VirusTotal module" +fi + +echo "Choose database type:" +echo "[1] MongoDB (your url)" +echo "[2] Sqlite" +read -r -p "> " db_type + +if [[ $db_type = 1 ]]; then + echo "Please enter db_url" + echo "You can get it here -> https://telegra.ph/How-to-get-Mongodb-URL-and-login-in-telegram-08-01" + read -r -p "> " db_url + db_name=Moon_Userbot + db_type=mongodb +else + db_name=db.sqlite3 + db_type=sqlite3 +fi + +cat > .env << EOL +API_ID=${api_id} +API_HASH=${api_hash} + +STRINGSESSION= + +# sqlite/sqlite3 or mongo/mongodb +DATABASE_TYPE=${db_type} +# file name for sqlite3, database name for mongodb +DATABASE_NAME=${db_name} + +# only for mongodb +DATABASE_URL=${db_url} + +APIFLASH_KEY=${apiflash_key} +RMBG_KEY=${rmbg_key} +VT_KEY=${vt_key} +GEMINI_KEY=${gemini_key} +COHERE_KEY=${cohere_key} +PM_LIMIT=${pm_limit} +EOL + +python3 install.py 3 || exit 3 + +echo +echo "============================" +echo "Great! Moon-Userbot installed successfully!" +echo "Start with: \"cd Moon-Userbot && python3 main.py\"" +echo "============================" diff --git a/utils/__init__.py b/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/utils/config.py b/utils/config.py new file mode 100644 index 0000000..4204e68 --- /dev/null +++ b/utils/config.py @@ -0,0 +1,32 @@ +import os +import environs + +try: + env = environs.Env() + env.read_env("./.env") +except FileNotFoundError: + print("No .env file found, using os.environ.") + +api_id = int(os.getenv("API_ID", env.int("API_ID"))) +api_hash = os.getenv("API_HASH", env.str("API_HASH")) + +STRINGSESSION = os.getenv("STRINGSESSION", env.str("STRINGSESSION")) + +second_session = os.getenv("SECOND_SESSION", env.str("SECOND_SESSION", "")) + +db_type = os.getenv("DATABASE_TYPE", env.str("DATABASE_TYPE")) +db_url = os.getenv("DATABASE_URL", env.str("DATABASE_URL", "")) +db_name = os.getenv("DATABASE_NAME", env.str("DATABASE_NAME")) + +apiflash_key = os.getenv("APIFLASH_KEY", env.str("APIFLASH_KEY")) +rmbg_key = os.getenv("RMBG_KEY", env.str("RMBG_KEY", "")) +vt_key = os.getenv("VT_KEY", env.str("VT_KEY", "")) +gemini_key = os.getenv("GEMINI_KEY", env.str("GEMINI_KEY", "")) +cohere_key = os.getenv("COHERE_KEY", env.str("COHERE_KEY", "")) + +pm_limit = int(os.getenv("PM_LIMIT", env.int("PM_LIMIT", 4))) + +test_server = bool(os.getenv("TEST_SERVER", env.bool("TEST_SERVER", False))) +modules_repo_branch = os.getenv( + "MODULES_REPO_BRANCH", env.str("MODULES_REPO_BRANCH", "master") +) diff --git a/utils/conv.py b/utils/conv.py new file mode 100644 index 0000000..5b238fb --- /dev/null +++ b/utils/conv.py @@ -0,0 +1,189 @@ +# Moon-Userbot - telegram userbot +# Copyright (C) 2020-present Moon Userbot Organization +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from collections import OrderedDict + +from pyrogram import Client, filters, types +from pyrogram.handlers import MessageHandler +from pyrogram.enums.parse_mode import ParseMode + +import asyncio +from typing import Union, List, Dict, Optional + + +class _TrueFilter(filters.Filter): + async def __call__(self, client: Client, update: types.Message): + return True + + +class Conversation: + _locks: Dict[int, asyncio.Lock] = {} + + def __init__( + self, + client: Client, + chat: Union[str, int], + timeout: float = 5, + delete_at_end=True, + exclusive=True, + ): + self.client = client + self.chat = chat + self.timeout = timeout + self.delete_at_end = delete_at_end + self.exclusive = exclusive + + self._chat_id = 0 + self._message_ids = [] + self._handler_object = None + self._chat_unique_lock: Optional[asyncio.Lock] = None + self._waiters: Dict[asyncio.Event, filters.Filter] = {} + self._responses: Dict[asyncio.Event, types.Message] = {} + self._pending_updates: List[types.Message] = [] + + async def __aenter__(self): + self._chat_id = (await self.client.get_chat(self.chat)).id + + if self._chat_id in self._locks: + self._chat_unique_lock = self._locks[self._chat_id] + else: + self._chat_unique_lock = self._locks[self._chat_id] = asyncio.Lock() + + if self.exclusive: + await self._chat_unique_lock.acquire() + + self._handler_object = MessageHandler( + self._handler, filters.chat(self._chat_id) + ) + + if -999 not in self.client.dispatcher.groups: + new_groups = OrderedDict(self.client.dispatcher.groups) + new_groups[-999] = [] + self.client.dispatcher.groups = new_groups + + self.client.dispatcher.groups[-999].append(self._handler_object) + + await asyncio.sleep(0) + + return self + + async def __aexit__(self, exc_type, exc_val, exc_tb): + self.client.dispatcher.groups[-999].remove(self._handler_object) + + if self.delete_at_end: + await self.client.delete_messages(self._chat_id, self._message_ids) + + if self.exclusive: + self._chat_unique_lock.release() + + async def _handler(self, _, message: types.Message): + for event, message_filter in self._waiters.items(): + if await message_filter(self.client, message): + self._responses[event] = message + event.set() + break + else: + self._pending_updates.append(message) + message.continue_propagation() + + async def get_response( + self, + message_filter: Optional[filters.Filter] = None, + timeout: float = None, + ) -> types.Message: + if timeout is None: + timeout = self.timeout + if message_filter is None: + message_filter = _TrueFilter() + + for message in self._pending_updates: + if await message_filter(self.client, message): + self._pending_updates.remove(message) + break + else: + message = await self._wait_message(message_filter, timeout) + + self._message_ids.append(message.id) + return message + + async def _wait_message( + self, message_filter: Optional[filters.Filter], timeout: float + ) -> types.Message: + event = asyncio.Event() + self._waiters[event] = message_filter + + try: + await asyncio.wait_for(event.wait(), timeout=timeout) + except asyncio.TimeoutError as e: + raise TimeoutError from e + finally: + self._waiters.pop(event) + + return self._responses.pop(event) + + async def send_message( + self, + text: str, + parse_mode: Optional[str] = ParseMode.HTML, + entities: List[types.MessageEntity] = None, + disable_web_page_preview: bool = None, + disable_notification: bool = None, + reply_to_message_id: int = None, + schedule_date: int = None, + ) -> types.Message: + """Send text messages. + + Parameters: + text (``str``): + Text of the message to be sent. + + parse_mode (``str``, *optional*): + By default, texts are parsed using HTML style. + Pass "markdown" or "md" to enable Markdown-style parsing. + Pass None to completely disable style parsing. + + entities (List of :obj:`~pyrogram.types.MessageEntity`): + List of special entities that appear in message text, which can be specified instead of *parse_mode*. + + disable_web_page_preview (``bool``, *optional*): + Disables link previews for links in this message. + + disable_notification (``bool``, *optional*): + Sends the message silently. + Users will receive a notification with no sound. + + reply_to_message_id (``int``, *optional*): + If the message is a reply, ID of the original message. + + schedule_date (``int``, *optional*): + Date when the message will be automatically sent. Unix time. + + Returns: + :obj:`~pyrogram.types.Message`: On success, the sent text message is returned. + """ + + sent = await self.client.send_message( + chat_id=self._chat_id, + text=text, + parse_mode=parse_mode, + entities=entities, + disable_web_page_preview=disable_web_page_preview, + disable_notification=disable_notification, + reply_to_message_id=reply_to_message_id, + schedule_date=schedule_date, + ) + self._message_ids.append(sent.id) + return sent diff --git a/utils/db.py b/utils/db.py new file mode 100644 index 0000000..2cac220 --- /dev/null +++ b/utils/db.py @@ -0,0 +1,237 @@ +# Moon-Userbot - telegram userbot +# Copyright (C) 2020-present Moon Userbot Organization +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import re +import json +import threading +import sqlite3 +from dns import resolver +import pymongo +from utils import config + +resolver.default_resolver = resolver.Resolver(configure=False) +resolver.default_resolver.nameservers = ["1.1.1.1"] + + +class Database: + def get(self, module: str, variable: str, default=None): + """Get value from database""" + raise NotImplementedError + + def set(self, module: str, variable: str, value): + """Set key in database""" + raise NotImplementedError + + def remove(self, module: str, variable: str): + """Remove key from database""" + raise NotImplementedError + + def get_collection(self, module: str) -> dict: + """Get database for selected module""" + raise NotImplementedError + + def close(self): + """Close the database""" + raise NotImplementedError + + +class MongoDatabase(Database): + def __init__(self, url, name): + self._client = pymongo.MongoClient(url) + self._database = self._client[name] + + def set(self, module: str, variable: str, value): + if not isinstance(module, str) or not isinstance(variable, str): + raise ValueError("Module and variable must be strings") + self._database[module].replace_one( + {"var": variable}, {"var": variable, "val": value}, upsert=True + ) + + def get(self, module: str, variable: str, default=None): + if not isinstance(module, str) or not isinstance(variable, str): + raise ValueError("Module and variable must be strings") + doc = self._database[module].find_one({"var": variable}) + return default if doc is None else doc["val"] + + def get_collection(self, module: str): + if not isinstance(module, str): + raise ValueError("Module must be a string") + return {item["var"]: item["val"] for item in self._database[module].find()} + + def remove(self, module: str, variable: str): + if not isinstance(module, str) or not isinstance(variable, str): + raise ValueError("Module and variable must be strings") + self._database[module].delete_one({"var": variable}) + + def close(self): + self._client.close() + + def add_chat_history(self, user_id, message): + chat_history = self.get_chat_history(user_id, default=[]) + chat_history.append(message) + self.set(f"core.cohere.user_{user_id}", "chat_history", chat_history) + + def get_chat_history(self, user_id, default=None): + if default is None: + default = [] + return self.get(f"core.cohere.user_{user_id}", "chat_history", default=[]) + + def addaiuser(self, user_id): + chatai_users = self.get("core.chatbot", "chatai_users", default=[]) + if user_id not in chatai_users: + chatai_users.append(user_id) + self.set("core.chatbot", "chatai_users", chatai_users) + + def remaiuser(self, user_id): + chatai_users = self.get("core.chatbot", "chatai_users", default=[]) + if user_id in chatai_users: + chatai_users.remove(user_id) + self.set("core.chatbot", "chatai_users", chatai_users) + + def getaiusers(self): + return self.get("core.chatbot", "chatai_users", default=[]) + + +class SqliteDatabase(Database): + def __init__(self, file): + self._conn = sqlite3.connect(file, check_same_thread=False) + self._conn.row_factory = sqlite3.Row + self._cursor = self._conn.cursor() + self._lock = threading.Lock() + + @staticmethod + def _parse_row(row: sqlite3.Row): + if row["type"] == "bool": + return row["val"] == "1" + if row["type"] == "int": + return int(row["val"]) + if row["type"] == "str": + return row["val"] + return json.loads(row["val"]) + + def _execute(self, module: str, *args, **kwargs) -> sqlite3.Cursor: + pattern = r"^(core|custom)" + if not re.match(pattern, module): + raise ValueError(f"Invalid module name format: {module}") + + self._lock.acquire() + try: + cursor = self._conn.cursor() + return cursor.execute(*args, **kwargs) + except sqlite3.OperationalError as e: + if str(e).startswith("no such table"): + sql = f""" + CREATE TABLE IF NOT EXISTS '{module}' ( + var TEXT UNIQUE NOT NULL, + val TEXT NOT NULL, + type TEXT NOT NULL + ) + """ + cursor = self._conn.cursor() + cursor.execute(sql) + self._conn.commit() + return cursor.execute(*args, **kwargs) + raise e from None + finally: + self._lock.release() + + def get(self, module: str, variable: str, default=None): + sql = f"SELECT * FROM '{module}' WHERE var=?" + cur = self._execute(module, sql, (variable,)) + + row = cur.fetchone() + if row is None: + return default + return self._parse_row(row) + + def set(self, module: str, variable: str, value) -> bool: + sql = f""" + INSERT INTO '{module}' VALUES ( ?, ?, ? ) + ON CONFLICT (var) DO + UPDATE SET val=?, type=? WHERE var=? + """ + + if isinstance(value, bool): + val = "1" if value else "0" + typ = "bool" + elif isinstance(value, str): + val = value + typ = "str" + elif isinstance(value, int): + val = str(value) + typ = "int" + else: + val = json.dumps(value) + typ = "json" + + self._execute(module, sql, (variable, val, typ, val, typ, variable)) + self._conn.commit() + + return True + + def remove(self, module: str, variable: str): + sql = f"DELETE FROM '{module}' WHERE var=?" + self._execute(module, sql, (variable,)) + self._conn.commit() + + def get_collection(self, module: str) -> dict: + pattern = r"^(core|custom)" + if not re.match(pattern, module): + raise ValueError(f"Invalid module name format: {module}") + + sql = f"SELECT * FROM '{module}'" + cur = self._execute(module, sql) + + collection = {} + for row in cur: + collection[row["var"]] = self._parse_row(row) + + return collection + + def close(self): + self._conn.commit() + self._conn.close() + + def add_chat_history(self, user_id, message): + chat_history = self.get_chat_history(user_id, default=[]) + chat_history.append(message) + self.set(f"core.cohere.user_{user_id}", "chat_history", chat_history) + + def get_chat_history(self, user_id, default=None): + if default is None: + default = [] + return self.get(f"core.cohere.user_{user_id}", "chat_history", default=[]) + + def addaiuser(self, user_id): + chatai_users = self.get("core.chatbot", "chatai_users", default=[]) + if user_id not in chatai_users: + chatai_users.append(user_id) + self.set("core.chatbot", "chatai_users", chatai_users) + + def remaiuser(self, user_id): + chatai_users = self.get("core.chatbot", "chatai_users", default=[]) + if user_id in chatai_users: + chatai_users.remove(user_id) + self.set("core.chatbot", "chatai_users", chatai_users) + + def getaiusers(self): + return self.get("core.chatbot", "chatai_users", default=[]) + + +if config.db_type in ["mongo", "mongodb"]: + db = MongoDatabase(config.db_url, config.db_name) +else: + db = SqliteDatabase(config.db_name) diff --git a/utils/handlers.py b/utils/handlers.py new file mode 100644 index 0000000..c4dba5e --- /dev/null +++ b/utils/handlers.py @@ -0,0 +1,1251 @@ +# Moon-Userbot - telegram userbot +# Copyright (C) 2020-present Moon Userbot Organization +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import re +from datetime import datetime, timedelta +from typing import Dict, Union + +from pyrogram import Client +from pyrogram.enums import ChatType +from pyrogram.errors import ( + ChatAdminRequired, + PeerIdInvalid, + RPCError, + UserAdminInvalid, + UsernameInvalid, +) +from pyrogram.raw import functions, types +from pyrogram.types import ( + ChatPermissions, + ChatPrivileges, + InputMediaAudio, + InputMediaDocument, + InputMediaPhoto, + InputMediaVideo, + Message, +) +from pyrogram.utils import ( + MAX_CHANNEL_ID, + MAX_USER_ID, + MIN_CHANNEL_ID, + MIN_CHAT_ID, + get_channel_id, +) + +from utils.db import db +from utils.misc import prefix +from utils.scripts import format_exc, text + + +async def check_username_or_id(data: Union[str, int]) -> str: + data = str(data) + if ( + not data.isdigit() + and data[0] == "-" + and not data[1:].isdigit() + or not data.isdigit() + and data[0] != "-" + ): + return "channel" + peer_id = int(data) + if peer_id < 0: + if MIN_CHAT_ID <= peer_id: + return "chat" + + if MIN_CHANNEL_ID <= peer_id < MAX_CHANNEL_ID: + return "channel" + elif 0 < peer_id <= MAX_USER_ID: + return "user" + + raise ValueError(f"Peer id invalid: {peer_id}") + + +async def get_user_and_name(message): + if message.reply_to_message.from_user: + return ( + message.reply_to_message.from_user.id, + message.reply_to_message.from_user.first_name, + ) + if message.reply_to_message.sender_chat: + return ( + message.reply_to_message.sender_chat.id, + message.reply_to_message.sender_chat.title, + ) + + +class BanHandler: + def __init__(self, client: Client, message: Message): + self.client = client + self.message = message + self.cause = text(message) + self.channel = None + self.user_id = None + self.name = None + + async def handle_ban(self): + if self.message.reply_to_message: + await self.handle_reply_ban() + elif not self.message.reply_to_message: + await self.handle_non_reply_ban() + else: + await self.message.edit("Unsupported") + + async def handle_reply_ban(self): + if self.message.chat.type not in [ChatType.PRIVATE, ChatType.CHANNEL]: + user_for_ban, self.name = await get_user_and_name(self.message) + await self.ban_user(user_for_ban) + + async def handle_non_reply_ban(self): + if ( + self.message.chat.type not in ["private", "channel"] + and len(self.cause.split()) > 1 + ): + user_to_ban = await self.get_user_to_ban() + if user_to_ban: + self.name = ( + user_to_ban.first_name + if getattr(user_to_ban, "first_name", None) + else user_to_ban.title + ) + await self.ban_user(user_to_ban.id) + + async def get_user_to_ban(self): + user_type = await check_username_or_id(self.cause.split(" ")[1]) + if user_type == "channel": + return await self.client.get_chat(self.cause.split(" ")[1]) + if user_type == "user": + return await self.client.get_users(self.cause.split(" ")[1]) + await self.message.edit("Invalid user type") + return None + + async def ban_user(self, user_id): + try: + await self.client.ban_chat_member(self.message.chat.id, user_id) + self.channel = await self.client.resolve_peer(self.message.chat.id) + self.user_id = await self.client.resolve_peer(user_id) + await self.handle_additional_actions() + await self.edit_message() + except UserAdminInvalid: + await self.message.edit("No rights") + except ChatAdminRequired: + await self.message.edit("No rights") + except Exception as e: + await self.message.edit(format_exc(e)) + + async def handle_additional_actions(self): + if "report_spam" in self.cause.lower().split(): + await self.client.invoke( + functions.channels.ReportSpam( + channel=self.channel, + participant=self.user_id, + id=[self.message.reply_to_message.id], + ) + ) + if "delete_history" in self.cause.lower().split(): + await self.client.invoke( + functions.channels.DeleteParticipantHistory( + channel=self.channel, participant=self.user_id + ) + ) + + async def edit_message(self): + text_c = "".join( + f" {_}" + for _ in self.cause.split() + if _.lower() not in ["delete_history", "report_spam"] + ) + await self.message.edit( + f"{self.name} banned!" + + f"\n{'Cause: ' + text_c.split(maxsplit=1)[1] + '' if len(text_c.split()) > 1 else ''}" + ) + + +class UnbanHandler: + def __init__(self, client: Client, message: Message): + self.client = client + self.message = message + self.cause = text(message) + self.channel = None + self.user_id = None + self.name = None + + async def handle_unban(self): + if self.message.reply_to_message: + await self.handle_reply_unban() + elif not self.message.reply_to_message: + await self.handle_non_reply_unban() + else: + await self.message.edit("Unsupported") + + async def handle_reply_unban(self): + if self.message.chat.type not in [ChatType.PRIVATE, ChatType.CHANNEL]: + user_for_unban, self.name = await get_user_and_name(self.message) + await self.unban_user(user_for_unban) + + async def handle_non_reply_unban(self): + if ( + self.message.chat.type not in ["private", "channel"] + and len(self.cause.split()) > 1 + ): + user_to_unban = await self.get_user_to_unban() + if user_to_unban: + self.name = ( + user_to_unban.first_name + if getattr(user_to_unban, "first_name", None) + else user_to_unban.title + ) + await self.unban_user(user_to_unban.id) + + async def get_user_to_unban(self): + user_type = await check_username_or_id(self.cause.split(" ")[1]) + if user_type == "channel": + return await self.client.get_chat(self.cause.split(" ")[1]) + if user_type == "user": + return await self.client.get_users(self.cause.split(" ")[1]) + await self.message.edit("Invalid user type") + return None + + async def unban_user(self, user_id): + try: + await self.client.unban_chat_member(self.message.chat.id, user_id) + self.channel = await self.client.resolve_peer(self.message.chat.id) + self.user_id = await self.client.resolve_peer(user_id) + await self.edit_message() + except UserAdminInvalid: + await self.message.edit("No rights") + except ChatAdminRequired: + await self.message.edit("No rights") + except Exception as e: + await self.message.edit(format_exc(e)) + + async def edit_message(self): + text_c = "".join( + f" {_}" + for _ in self.cause.split() + if _.lower() not in ["delete_history", "report_spam"] + ) + await self.message.edit( + f"{self.name} unbanned!" + + f"\n{'Cause: ' + text_c.split(maxsplit=1)[1] + '' if len(text_c.split()) > 1 else ''}" + ) + + +class KickHandler: + def __init__(self, client: Client, message: Message): + self.client = client + self.message = message + self.cause = text(message) + self.channel = None + self.user_id = None + self.name = None + + async def handle_kick(self): + if self.message.reply_to_message: + await self.handle_reply_kick() + elif not self.message.reply_to_message: + await self.handle_non_reply_kick() + else: + await self.message.edit("Unsupported") + + async def handle_reply_kick(self): + if self.message.chat.type not in [ChatType.PRIVATE, ChatType.CHANNEL]: + if self.message.reply_to_message.from_user: + self.name = self.message.reply_to_message.from_user.first_name + await self.kick_user(self.message.reply_to_message.from_user.id) + else: + await self.message.edit("Reply on user msg") + + async def handle_non_reply_kick(self): + if self.message.chat.type not in [ChatType.PRIVATE, ChatType.CHANNEL]: + if len(self.cause.split()) > 1: + user_to_kick = await self.get_user_to_kick() + if user_to_kick: + self.name = ( + user_to_kick.first_name + if getattr(user_to_kick, "first_name", None) + else user_to_kick.title + ) + await self.kick_user(user_to_kick.id) + else: + await self.message.edit("user_id or username") + + async def get_user_to_kick(self): + user_type = await check_username_or_id(self.cause.split(" ")[1]) + if user_type == "channel": + return await self.client.get_chat(self.cause.split(" ")[1]) + if user_type == "user": + return await self.client.get_users(self.cause.split(" ")[1]) + await self.message.edit("Invalid user type") + return None + + async def kick_user(self, user_id): + try: + await self.client.ban_chat_member( + self.message.chat.id, + user_id, + datetime.now() + timedelta(minutes=1), + ) + self.channel = await self.client.resolve_peer(self.message.chat.id) + self.user_id = await self.client.resolve_peer(user_id) + await self.handle_additional_actions() + await self.client.unban_chat_member(self.message.chat.id, user_id) + await self.edit_message() + except UserAdminInvalid: + await self.message.edit("No rights") + except ChatAdminRequired: + await self.message.edit("No rights") + except Exception as e: + await self.message.edit(format_exc(e)) + + async def handle_additional_actions(self): + if "report_spam" in self.cause.lower().split(): + await self.client.invoke( + functions.channels.ReportSpam( + channel=self.channel, + participant=self.user_id, + id=[self.message.reply_to_message.id], + ) + ) + if "delete_history" in self.cause.lower().split(): + await self.client.invoke( + functions.channels.DeleteParticipantHistory( + channel=self.channel, participant=self.user_id + ) + ) + + async def edit_message(self): + text_c = "".join( + f" {_}" + for _ in self.cause.split() + if _.lower() not in ["delete_history", "report_spam"] + ) + await self.message.edit( + f"{self.name} kicked!" + + f"\n{'Cause: ' + text_c.split(maxsplit=1)[1] + '' if len(text_c.split()) > 1 else ''}" + ) + + +class KickDeletedAccountsHandler: + def __init__(self, client: Client, message: Message): + self.client = client + self.message = message + self.chat_id = message.chat.id + self.kicked_count = 0 + + async def kick_deleted_accounts(self): + await self.message.edit("Kicking deleted accounts...") + try: + async for member in self.client.get_chat_members(self.chat_id): + if member.user.is_deleted: + await self.kick_member(member.user.id) + self.kicked_count += 1 + except Exception as e: + return await self.message.edit(format_exc(e)) + await self.message.edit( + f"Successfully kicked {self.kicked_count} deleted account(s)", + ) + + async def kick_member(self, user_id): + try: + await self.client.ban_chat_member( + self.chat_id, user_id, datetime.now() + timedelta(seconds=31) + ) + except Exception as e: + await self.message.edit(f"Failed to kick user {user_id}: {format_exc(e)}") + + +class TimeMuteHandler: + def __init__(self, client: Client, message: Message): + self.client = client + self.message = message + self.cause = text(message) + self.chat_id = message.chat.id + self.tmuted_users = db.get("core.ats", f"c{self.chat_id}", []) + + async def handle_tmute(self): + if self.message.reply_to_message: + await self.handle_reply_tmute() + elif not self.message.reply_to_message: + await self.handle_non_reply_tmute() + else: + await self.message.edit("Unsupported") + + async def handle_reply_tmute(self): + if self.message.chat.type not in [ChatType.PRIVATE, ChatType.CHANNEL]: + user_for_tmute, name = await get_user_and_name(self.message) + if user_for_tmute in self.tmuted_users: + await self.message.edit(f"{name} already in tmute") + else: + self.tmuted_users.append(user_for_tmute) + db.set("core.ats", f"c{self.chat_id}", self.tmuted_users) + await self.message.edit( + f"{name} in tmute" + + f"\n{'Cause: ' + self.cause.split(maxsplit=1)[1] + '' if len(self.cause.split()) > 1 else ''}", + ) + + async def handle_non_reply_tmute(self): + if self.message.chat.type not in [ChatType.PRIVATE, ChatType.CHANNEL]: + if len(self.cause.split()) > 1: + user_to_tmute = await self.get_user_to_tmute() + if user_to_tmute: + name = ( + user_to_tmute.first_name + if getattr(user_to_tmute, "first_name", None) + else user_to_tmute.title + ) + if user_to_tmute.id not in self.tmuted_users: + self.tmuted_users.append(user_to_tmute.id) + db.set("core.ats", f"c{self.chat_id}", self.tmuted_users) + await self.message.edit( + f"{name} in tmute" + + f"\n{'Cause: ' + self.cause.split(maxsplit=2)[2] + '' if len(self.cause.split()) > 2 else ''}", + ) + else: + await self.message.edit( + f"{name} already in tmute", + ) + else: + await self.message.edit("user_id or username") + + async def get_user_to_tmute(self): + user_type = await check_username_or_id(self.cause.split(" ")[1]) + if user_type == "channel": + return await self.client.get_chat(self.cause.split(" ")[1]) + if user_type == "user": + return await self.client.get_users(self.cause.split(" ")[1]) + await self.message.edit("Invalid user type") + return None + + +class TimeUnmuteHandler: + def __init__(self, client: Client, message: Message): + self.client = client + self.message = message + self.cause = text(message) + self.chat_id = message.chat.id + self.tmuted_users = db.get("core.ats", f"c{self.chat_id}", []) + + async def handle_tunmute(self): + if self.message.reply_to_message: + await self.handle_reply_tunmute() + elif not self.message.reply_to_message: + await self.handle_non_reply_tunmute() + else: + await self.message.edit("Unsupported") + + async def handle_reply_tunmute(self): + if self.message.chat.type not in [ChatType.PRIVATE, ChatType.CHANNEL]: + user_for_tunmute, name = await get_user_and_name(self.message) + if user_for_tunmute not in self.tmuted_users: + await self.message.edit(f"{name} not in tmute") + else: + self.tmuted_users.remove(user_for_tunmute) + db.set("core.ats", f"c{self.chat_id}", self.tmuted_users) + await self.message.edit( + f"{name} tunmuted" + + f"\n{'Cause: ' + self.cause.split(maxsplit=1)[1] + '' if len(self.cause.split()) > 1 else ''}", + ) + + async def handle_non_reply_tunmute(self): + if self.message.chat.type not in [ChatType.PRIVATE, ChatType.CHANNEL]: + if len(self.cause.split()) > 1: + user_to_tunmute = await self.get_user_to_tunmute() + if user_to_tunmute: + name = ( + user_to_tunmute.first_name + if getattr(user_to_tunmute, "first_name", None) + else user_to_tunmute.title + ) + if user_to_tunmute.id not in self.tmuted_users: + await self.message.edit( + f"{name} not in tmute", + ) + else: + self.tmuted_users.remove(user_to_tunmute.id) + db.set("core.ats", f"c{self.chat_id}", self.tmuted_users) + await self.message.edit( + f"{name} tunmuted" + + f"\n{'Cause: ' + self.cause.split(maxsplit=2)[2] + '' if len(self.cause.split()) > 2 else ''}", + ) + else: + await self.message.edit("user_id or username") + + async def get_user_to_tunmute(self): + user_type = await check_username_or_id(self.cause.split(" ")[1]) + if user_type == "channel": + return await self.client.get_chat(self.cause.split(" ")[1]) + if user_type == "user": + return await self.client.get_users(self.cause.split(" ")[1]) + await self.message.edit("Invalid user type") + return None + + +class TimeMuteUsersHandler: + def __init__(self, client: Client, message: Message): + self.client = client + self.message = message + self.chat_id = message.chat.id + self.tmuted_users = db.get("core.ats", f"c{self.chat_id}", []) + + async def list_tmuted_users(self): + if self.message.chat.type not in [ChatType.PRIVATE, ChatType.CHANNEL]: + text = f"All users {self.message.chat.title} who are now in tmute\n\n" + count = 0 + for user in self.tmuted_users: + try: + name = await self.get_user_name(user) + if name: + count += 1 + text += f"{count}. {name}\n" + except PeerIdInvalid: + pass + if count == 0: + await self.message.edit("No users in tmute") + else: + text += f"\nTotal users in tmute {count}" + await self.message.edit(text) + else: + await self.message.edit("Unsupported") + + async def get_user_name(self, user_id): + try: + _name_ = await self.client.get_chat(user_id) + if await check_username_or_id(_name_.id) == "channel": + channel = await self.client.invoke( + functions.channels.GetChannels( + id=[ + types.InputChannel( + channel_id=get_channel_id(_name_.id), + access_hash=0, + ) + ] + ) + ) + return channel.chats[0].title + if await check_username_or_id(_name_.id) == "user": + user = await self.client.get_users(_name_.id) + return user.first_name + except PeerIdInvalid: + return None + + +class UnmuteHandler: + def __init__(self, client: Client, message: Message): + self.client = client + self.message = message + self.cause = text(message) + self.chat_id = message.chat.id + self.permissions = message.chat.permissions + + async def handle_unmute(self): + if self.message.reply_to_message: + await self.handle_reply_unmute() + elif not self.message.reply_to_message: + await self.handle_non_reply_unmute() + else: + await self.message.edit("Unsupported") + + async def handle_reply_unmute(self): + if self.message.chat.type not in [ChatType.PRIVATE, ChatType.CHANNEL]: + user_for_unmute = self.message.reply_to_message.from_user + if user_for_unmute: + try: + await self.unmute_user(user_for_unmute.id) + await self.message.edit( + f"{user_for_unmute.first_name} unmuted" + + f"\n{'Cause: ' + self.cause.split(' ', maxsplit=1)[1] + '' if len(self.cause.split()) > 1 else ''}" + ) + except UserAdminInvalid: + await self.message.edit("No rights") + except ChatAdminRequired: + await self.message.edit("No rights") + except Exception as e: + await self.message.edit(format_exc(e)) + else: + await self.message.edit("Reply on user msg") + + async def handle_non_reply_unmute(self): + if self.message.chat.type not in [ChatType.PRIVATE, ChatType.CHANNEL]: + if len(self.cause.split()) > 1: + user_to_unmute = await self.get_user_to_unmute() + if user_to_unmute: + try: + await self.unmute_user(user_to_unmute.id) + await self.message.edit( + f"{user_to_unmute.first_name} unmuted!" + + f"\n{'Cause: ' + self.cause.split(' ', maxsplit=2)[2] + '' if len(self.cause.split()) > 2 else ''}" + ) + except UserAdminInvalid: + await self.message.edit("No rights") + except ChatAdminRequired: + await self.message.edit("No rights") + except Exception as e: + await self.message.edit(format_exc(e)) + else: + await self.message.edit("User is not found") + else: + await self.message.edit("user_id or username") + + async def get_user_to_unmute(self): + user_type = await check_username_or_id(self.cause.split(" ")[1]) + if user_type == "channel": + return await self.client.get_chat(self.cause.split(" ")[1]) + if user_type == "user": + return await self.client.get_users(self.cause.split(" ")[1]) + await self.message.edit("Invalid user type") + return None + + async def unmute_user(self, user_id): + try: + await self.client.restrict_chat_member( + self.chat_id, + user_id, + self.permissions, + datetime.now() + timedelta(seconds=30), + ) + except Exception as e: + await self.message.edit(format_exc(e)) + + +class MuteHandler: + def __init__(self, client: Client, message: Message): + self.client = client + self.message = message + self.cause = text(message) + self.chat_id = message.chat.id + + async def handle_mute(self): + if self.message.reply_to_message: + await self.handle_reply_mute() + elif not self.message.reply_to_message: + await self.handle_non_reply_mute() + else: + await self.message.edit("Unsupported") + + async def handle_reply_mute(self): + if self.message.chat.type not in [ChatType.PRIVATE, ChatType.CHANNEL]: + user_for_mute = self.message.reply_to_message.from_user + if user_for_mute: + mute_seconds = self.calculate_mute_seconds() + try: + await self.mute_user(user_for_mute.id, mute_seconds) + await self.message.edit( + self.construct_mute_message(user_for_mute, mute_seconds) + ) + except UserAdminInvalid: + await self.message.edit("No rights") + except ChatAdminRequired: + await self.message.edit("No rights") + except Exception as e: + await self.message.edit(format_exc(e)) + else: + await self.message.edit("Reply on user msg") + + async def handle_non_reply_mute(self): + if self.message.chat.type not in [ChatType.PRIVATE, ChatType.CHANNEL]: + if len(self.cause.split()) > 1: + user_to_mute = await self.get_user_to_mute() + if user_to_mute: + mute_seconds = self.calculate_mute_seconds() + try: + await self.mute_user(user_to_mute.id, mute_seconds) + await self.message.edit( + self.construct_mute_message(user_to_mute, mute_seconds) + ) + except UserAdminInvalid: + await self.message.edit("No rights") + except ChatAdminRequired: + await self.message.edit("No rights") + except Exception as e: + await self.message.edit(format_exc(e)) + else: + await self.message.edit("User is not found") + else: + await self.message.edit("user_id or username") + + async def get_user_to_mute(self): + user_type = await check_username_or_id(self.cause.split(" ")[1]) + if user_type == "channel": + return await self.client.get_chat(self.cause.split(" ")[1]) + if user_type == "user": + return await self.client.get_users(self.cause.split(" ")[1]) + await self.message.edit("Invalid user type") + return None + + def calculate_mute_seconds(self): + mute_seconds: int = 0 + for character in "mhdw": + match = re.search(rf"(\d+|(\d+\.\d+)){character}", self.message.text) + if match: + value = float(match.string[match.start() : match.end() - 1]) + if character == "m": + mute_seconds += int(value * 60) + if character == "h": + mute_seconds += int(value * 3600) + if character == "d": + mute_seconds += int(value * 86400) + if character == "w": + mute_seconds += int(value * 604800) + return mute_seconds + + async def mute_user(self, user_id, mute_seconds): + try: + if mute_seconds > 30: + await self.client.restrict_chat_member( + self.chat_id, + user_id, + ChatPermissions(), + datetime.now() + timedelta(seconds=mute_seconds), + ) + else: + await self.client.restrict_chat_member( + self.chat_id, + user_id, + ChatPermissions(), + ) + except Exception as e: + await self.message.edit(format_exc(e)) + + def construct_mute_message(self, user, mute_seconds): + mute_time: Dict[str, int] = { + "days": mute_seconds // 86400, + "hours": mute_seconds % 86400 // 3600, + "minutes": mute_seconds % 86400 % 3600 // 60, + } + message_text = ( + f"{user.first_name} was muted for" + f" {((str(mute_time['days']) + ' day') if mute_time['days'] > 0 else '') + ('s' if mute_time['days'] > 1 else '')}" + f" {((str(mute_time['hours']) + ' hour') if mute_time['hours'] > 0 else '') + ('s' if mute_time['hours'] > 1 else '')}" + f" {((str(mute_time['minutes']) + ' minute') if mute_time['minutes'] > 0 else '') + ('s' if mute_time['minutes'] > 1 else '')}" + + f"\n{'Cause: ' + self.cause.split(' ', maxsplit=2)[2] + '' if len(self.cause.split()) > 2 else ''}" + ) + message_text = " ".join(message_text.split()) + return message_text + + +class DemoteHandler: + def __init__(self, client: Client, message: Message): + self.client = client + self.message = message + self.cause = text(message) + self.chat_id = message.chat.id + self.common_privileges_demote = { + "is_anonymous": False, + "can_manage_chat": False, + "can_change_info": False, + "can_post_messages": False, + "can_edit_messages": False, + "can_delete_messages": False, + "can_manage_video_chats": False, + "can_restrict_members": False, + "can_invite_users": False, + "can_pin_messages": False, + "can_promote_members": False, + } + + async def handle_demote(self): + if self.message.reply_to_message: + await self.handle_reply_demote() + elif not self.message.reply_to_message: + await self.handle_non_reply_demote() + else: + await self.message.edit("Unsupported") + + async def handle_reply_demote(self): + if self.message.chat.type not in [ChatType.PRIVATE, ChatType.CHANNEL]: + user_for_demote = self.message.reply_to_message.from_user + if user_for_demote: + try: + await self.demote_user(user_for_demote.id) + await self.message.edit( + self.construct_demote_message(user_for_demote) + ) + except UserAdminInvalid: + await self.message.edit("No rights") + except ChatAdminRequired: + await self.message.edit("No rights") + except Exception as e: + await self.message.edit(format_exc(e)) + else: + await self.message.edit("Reply on user msg") + + async def handle_non_reply_demote(self): + if self.message.chat.type not in [ChatType.PRIVATE, ChatType.CHANNEL]: + if len(self.cause.split()) > 1: + user_to_demote = await self.get_user_to_demote() + if user_to_demote: + try: + await self.demote_user(user_to_demote.id) + await self.message.edit( + self.construct_demote_message(user_to_demote) + ) + except UserAdminInvalid: + await self.message.edit("No rights") + except ChatAdminRequired: + await self.message.edit("No rights") + except Exception as e: + await self.message.edit(format_exc(e)) + else: + await self.message.edit("User is not found") + else: + await self.message.edit("user_id or username not provided!") + + async def get_user_to_demote(self): + user_type = await check_username_or_id(self.cause.split(" ")[1]) + if user_type == "channel": + return await self.client.get_chat(self.cause.split(" ")[1]) + if user_type == "user": + return await self.client.get_users(self.cause.split(" ")[1]) + await self.message.edit("Invalid user type") + return None + + async def demote_user(self, user_id): + try: + await self.client.promote_chat_member( + self.chat_id, + user_id, + privileges=ChatPrivileges(**self.common_privileges_demote), + ) + except UserAdminInvalid: + raise UserAdminInvalid() + except ChatAdminRequired: + raise ChatAdminRequired() + except Exception as e: + await self.message.edit(format_exc(e)) + + def construct_demote_message(self, user): + return ( + f"{user.first_name} demoted!" + + f"\n{'Cause: ' + self.cause.split(' ', maxsplit=2)[2] + '' if len(self.cause.split()) > 2 else ''}" + ) + + +class PromoteHandler: + def __init__(self, client: Client, message: Message): + self.client = client + self.message = message + self.cause = text(message) + self.chat_id = message.chat.id + self.common_privileges_promote = { + "can_delete_messages": True, + "can_restrict_members": True, + "can_invite_users": True, + "can_pin_messages": True, + } + + async def handle_promote(self): + if self.message.reply_to_message: + await self.handle_reply_promote() + elif not self.message.reply_to_message: + await self.handle_non_reply_promote() + else: + await self.message.edit("Unsupported") + + async def handle_reply_promote(self): + if self.message.chat.type not in [ChatType.PRIVATE, ChatType.CHANNEL]: + user_for_promote = self.message.reply_to_message.from_user + promote_title = ( + self.message.text.split(maxsplit=1)[1] + if len(self.message.text.split()) > 1 + else None + ) + if promote_title and len(promote_title) > 16: + promote_title = promote_title[:16] + if user_for_promote: + try: + await self.promote_user(user_for_promote.id, promote_title) + await self.message.edit( + self.construct_promote_message(user_for_promote) + ) + except UserAdminInvalid: + await self.message.edit("No rights") + except ChatAdminRequired: + await self.message.edit("No rights") + except Exception as e: + await self.message.edit(format_exc(e)) + else: + await self.message.edit("Reply on user msg") + + async def handle_non_reply_promote(self): + if self.message.chat.type not in [ChatType.PRIVATE, ChatType.CHANNEL]: + if len(self.cause.split()) > 1: + user_to_promote = await self.get_user_to_promote() + promote_title = ( + " ".join(self.cause.split(" ")[2:]) + if len(self.cause.split(" ")) > 2 + else None + ) + if promote_title and len(promote_title) > 16: + promote_title = promote_title[:16] + if user_to_promote: + try: + await self.promote_user(user_to_promote.id, promote_title) + await self.message.edit( + self.construct_promote_message(user_to_promote) + ) + except UserAdminInvalid: + await self.message.edit("No rights") + except ChatAdminRequired: + await self.message.edit("No rights") + except Exception as e: + await self.message.edit(format_exc(e)) + else: + await self.message.edit("User is not found") + else: + await self.message.edit("user_id or username not provided!") + + async def get_user_to_promote(self): + user_type = await check_username_or_id(self.cause.split(" ")[1]) + if user_type == "channel": + return await self.client.get_chat(self.cause.split(" ")[1]) + if user_type == "user": + return await self.client.get_users(self.cause.split(" ")[1]) + await self.message.edit("Invalid user type") + return None + + async def promote_user(self, user_id, title): + try: + await self.client.promote_chat_member( + self.chat_id, + user_id, + privileges=ChatPrivileges(**self.common_privileges_promote), + title=title, + ) + if len(self.cause.split()) > 1 and self.message.chat.type == "group": + await self.client.set_administrator_title( + self.chat_id, + user_id, + self.cause.split(maxsplit=1)[1], + ) + except UserAdminInvalid: + raise UserAdminInvalid() + except ChatAdminRequired: + raise ChatAdminRequired() + except Exception as e: + await self.message.edit(format_exc(e)) + + def construct_promote_message(self, user): + return ( + f"{user.first_name} promoted!" + + f"\n{'Title: ' + self.cause.split(' ', maxsplit=1)[1] + '' if len(self.cause.split()) > 1 else ''}" + ) + + +class AntiChannelsHandler: + def __init__(self, client: Client, message: Message): + self.client = client + self.message = message + self.chat_id = message.chat.id + self.prefix = prefix + + async def handle_anti_channels(self): + if self.message.chat.type not in [ChatType.GROUP, ChatType.SUPERGROUP]: + await self.message.edit("Not supported in non-group chats") + return + + command = self.message.command + if len(command) == 1: + await self.toggle_anti_channels_status() + elif command[1] in ["enable", "on", "1", "yes", "true"]: + await self.enable_anti_channels() + elif command[1] in ["disable", "off", "0", "no", "false"]: + await self.disable_anti_channels() + else: + await self.message.edit( + f"Usage: {self.prefix}antich [enable|disable]" + ) + + async def toggle_anti_channels_status(self): + current_status = db.get("core.ats", f"antich{self.chat_id}", False) + new_status = not current_status + db.set("core.ats", f"antich{self.chat_id}", new_status) + if new_status: + await self.message.edit("Blocking channels in this chat enabled.") + else: + await self.message.edit("Blocking channels in this chat disabled.") + + async def enable_anti_channels(self): + db.set("core.ats", f"antich{self.chat_id}", True) + group = await self.client.get_chat(self.chat_id) + if group.linked_chat: + db.set("core.ats", f"linked{self.chat_id}", group.linked_chat.id) + else: + db.set("core.ats", f"linked{self.chat_id}", 0) + await self.message.edit("Blocking channels in this chat enabled.") + + async def disable_anti_channels(self): + db.set("core.ats", f"antich{self.chat_id}", False) + await self.message.edit("Blocking channels in this chat disabled.") + + +class DeleteHistoryHandler: + def __init__(self, client: Client, message: Message): + self.client = client + self.message = message + self.cause = text(message) + self.chat_id = message.chat.id + self.prefix = prefix + + async def handle_delete_history(self): + if self.message.chat.type not in [ChatType.PRIVATE, ChatType.CHANNEL]: + if self.message.reply_to_message: + await self.handle_reply_delete_history() + elif not self.message.reply_to_message: + await self.handle_non_reply_delete_history() + else: + await self.message.edit("Unsupported") + + async def handle_reply_delete_history(self): + if self.message.reply_to_message.from_user: + try: + user_for_delete, name = await get_user_and_name(self.message) + await self.delete_user_history(user_for_delete, name) + except UserAdminInvalid: + await self.message.edit("No rights") + except ChatAdminRequired: + await self.message.edit("No rights") + except Exception as e: + await self.message.edit(format_exc(e)) + else: + await self.message.edit("Reply on user msg") + + async def handle_non_reply_delete_history(self): + if len(self.cause.split()) > 1: + try: + user_to_delete = await self.get_user_to_delete() + if user_to_delete: + name = ( + user_to_delete.first_name + if getattr(user_to_delete, "first_name", None) + else user_to_delete.title + ) + await self.delete_user_history(user_to_delete.id, name) + else: + await self.message.edit("User is not found") + except PeerIdInvalid: + await self.message.edit("User is not found") + except UsernameInvalid: + await self.message.edit("User is not found") + except IndexError: + await self.message.edit("User is not found") + else: + await self.message.edit("user_id or username") + + async def get_user_to_delete(self): + user_type = await check_username_or_id(self.cause.split(" ")[1]) + if user_type == "channel": + return await self.client.get_chat(self.cause.split(" ")[1]) + if user_type == "user": + return await self.client.get_users(self.cause.split(" ")[1]) + await self.message.edit("Invalid user type") + return None + + async def delete_user_history(self, user_id, name): + try: + channel = await self.client.resolve_peer(self.chat_id) + user_id = await self.client.resolve_peer(user_id) + await self.client.invoke( + functions.channels.DeleteParticipantHistory( + channel=channel, participant=user_id + ) + ) + await self.message.edit( + f"History from {name} was deleted!" + + f"\n{'Cause: ' + self.cause.split(' ', maxsplit=1)[1] + '' if len(self.cause.split()) > 1 else ''}" + ) + except UserAdminInvalid: + await self.message.edit("No rights") + except ChatAdminRequired: + await self.message.edit("No rights") + except Exception as e: + await self.message.edit(format_exc(e)) + + +class AntiRaidHandler: + def __init__(self, client: Client, message: Message): + self.client = client + self.message = message + self.chat_id = message.chat.id + self.prefix = prefix + + async def handle_antiraid(self): + command = self.message.command + if len(command) > 1: + if command[1] == "on": + await self.enable_antiraid() + elif command[1] == "off": + await self.disable_antiraid() + else: + await self.toggle_antiraid() + + async def enable_antiraid(self): + db.set("core.ats", f"antiraid{self.chat_id}", True) + group = await self.client.get_chat(self.chat_id) + if group.linked_chat: + db.set("core.ats", f"linked{self.chat_id}", group.linked_chat.id) + else: + db.set("core.ats", f"linked{self.chat_id}", 0) + await self.message.edit( + "Anti-raid mode enabled!\n" + f"Disable with: {self.prefix}antiraid off" + ) + + async def disable_antiraid(self): + db.set("core.ats", f"antiraid{self.chat_id}", False) + await self.message.edit("Anti-raid mode disabled") + + async def toggle_antiraid(self): + current_status = db.get("core.ats", f"antiraid{self.chat_id}", False) + new_status = not current_status + db.set("core.ats", f"antiraid{self.chat_id}", new_status) + if new_status: + group = await self.client.get_chat(self.chat_id) + if group.linked_chat: + db.set("core.ats", f"linked{self.chat_id}", group.linked_chat.id) + else: + db.set("core.ats", f"linked{self.chat_id}", 0) + await self.message.edit( + "Anti-raid mode enabled!\n" + f"Disable with: {self.prefix}antiraid off" + ) + else: + await self.message.edit("Anti-raid mode disabled") + + +class NoteSendHandler: + def __init__(self, client: Client, message: Message): + self.client = client + self.message = message + self.chat_id = message.chat.id + self.prefix = prefix + + async def handle_note_send(self): + if len(self.message.text.split()) >= 2: + await self.message.edit("Loading...") + + note_name = self.message.text.split(maxsplit=1)[1] + find_note = db.get("core.notes", f"note{note_name}", False) + if find_note: + try: + await self.send_note(find_note) + except RPCError: + await self.message.edit( + "Sorry, but this note is unavailable.\n\n" + f"You can delete this note with " + f"{self.prefix}clear {note_name}" + ) + else: + await self.message.edit("There is no such note") + else: + await self.message.edit( + f"Example: {self.prefix}note note_name", + ) + + async def send_note(self, find_note): + if find_note.get("MEDIA_GROUP"): + await self.message.delete() + await self.send_media_group(find_note) + else: + await self.message.delete() + await self.copy_message(find_note) + + async def send_media_group(self, find_note): + messages_grouped = await self.client.get_media_group( + int(find_note["CHAT_ID"]), int(find_note["MESSAGE_ID"]) + ) + media_grouped_list = self.prepare_media_group(messages_grouped) + if self.message.reply_to_message: + await self.client.send_media_group( + self.message.chat.id, + media_grouped_list, + reply_to_message_id=self.message.reply_to_message.id, + ) + else: + await self.client.send_media_group(self.message.chat.id, media_grouped_list) + + async def copy_message(self, find_note): + if self.message.reply_to_message: + await self.client.copy_message( + self.message.chat.id, + int(find_note["CHAT_ID"]), + int(find_note["MESSAGE_ID"]), + reply_to_message_id=self.message.reply_to_message.id, + ) + else: + await self.client.copy_message( + self.message.chat.id, + int(find_note["CHAT_ID"]), + int(find_note["MESSAGE_ID"]), + ) + + def prepare_media_group(self, messages_grouped): + media_grouped_list = [] + for _ in messages_grouped: + if _.photo: + media_grouped_list.append(self.prepare_photo(_)) + elif _.video: + media_grouped_list.append(self.prepare_video(_)) + elif _.audio: + media_grouped_list.append(self.prepare_audio(_)) + elif _.document: + media_grouped_list.append(self.prepare_document(_)) + return media_grouped_list + + @staticmethod + def prepare_photo(message): + if message.caption: + return InputMediaPhoto(message.photo.file_id, message.caption.markdown) + return InputMediaPhoto(message.photo.file_id) + + @staticmethod + def prepare_video(message): + if message.caption: + if message.video.thumbs: + return InputMediaVideo( + message.video.file_id, + message.video.thumbs[0].file_id, + message.caption.markdown, + ) + return InputMediaVideo(message.video.file_id, message.caption.markdown) + if message.video.thumbs: + return InputMediaVideo( + message.video.file_id, message.video.thumbs[0].file_id + ) + return InputMediaVideo(message.video.file_id) + + @staticmethod + def prepare_audio(message): + if message.caption: + return InputMediaAudio(message.audio.file_id, message.caption.markdown) + return InputMediaAudio(message.audio.file_id) + + @staticmethod + def prepare_document(message): + if message.caption: + if message.document.thumbs: + return InputMediaDocument( + message.document.file_id, + message.document.thumbs[0].file_id, + message.caption.markdown, + ) + return InputMediaDocument( + message.document.file_id, message.caption.markdown + ) + if message.document.thumbs: + return InputMediaDocument( + message.document.file_id, message.document.thumbs[0].file_id + ) + return InputMediaDocument(message.document.file_id) diff --git a/utils/misc.py b/utils/misc.py new file mode 100644 index 0000000..2800ccf --- /dev/null +++ b/utils/misc.py @@ -0,0 +1,55 @@ +# Moon-Userbot - telegram userbot +# Copyright (C) 2020-present Moon Userbot Organization +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from sys import version_info +from .db import db +import git + +__all__ = [ + "modules_help", + "requirements_list", + "python_version", + "prefix", + "gitrepo", + "userbot_version", +] + + +modules_help = {} +requirements_list = [] + +python_version = f"{version_info[0]}.{version_info[1]}.{version_info[2]}" + +prefix = db.get("core.main", "prefix", ".") + +try: + gitrepo = git.Repo(".") +except git.exc.InvalidGitRepositoryError: + repo = git.Repo.init() + origin = repo.create_remote( + "origin", "https://github.com/The-MoonTg-project/Moon-Userbot" + ) + origin.fetch() + repo.create_head("main", origin.refs.main) + repo.heads.main.set_tracking_branch(origin.refs.main) + repo.heads.main.checkout(True) + gitrepo = git.Repo(".") + +if len(gitrepo.tags) > 0: + commits_since_tag = list(gitrepo.iter_commits(f"{gitrepo.tags[-1].name}..HEAD")) +else: + commits_since_tag = [] +userbot_version = f"2.5.{len(commits_since_tag)}" diff --git a/utils/module.py b/utils/module.py new file mode 100644 index 0000000..2b4cba6 --- /dev/null +++ b/utils/module.py @@ -0,0 +1,78 @@ +import logging +from pathlib import Path +from typing import Optional + +from pyrogram import Client + +from utils.scripts import load_module +from utils.misc import modules_help + + +class ModuleManager: + _instance: Optional["ModuleManager"] = None + + def __init__(self): + self.success_modules = 0 + self.failed_modules = 0 + self.help_navigator = None + + @classmethod + def get_instance(cls) -> "ModuleManager": + if cls._instance is None: + cls._instance = ModuleManager() + return cls._instance + + async def load_modules(self, app: Client): + """Load all modules and initialize help navigator""" + for path in Path("modules").rglob("*.py"): + try: + await load_module( + path.stem, app, core="custom_modules" not in path.parent.parts + ) + except Exception: + logging.warning("Can't import module %s", path.stem, exc_info=True) + self.failed_modules += 1 + else: + self.success_modules += 1 + + logging.info("Imported %d modules", self.success_modules) + if self.failed_modules: + logging.warning("Failed to import %d modules", self.failed_modules) + + self.help_navigator = HelpNavigator() + return self.help_navigator + + +class HelpNavigator: + def __init__(self): + self.current_page = 1 + self.module_list = list(modules_help.keys()) + self.total_pages = (len(modules_help) + 9) // 10 + logging.info("Initialized HelpNavigator with %d modules", len(self.module_list)) + + async def send_page(self, message): + from utils.misc import prefix + + start_index = (self.current_page - 1) * 10 + end_index = start_index + 10 + page_modules = self.module_list[start_index:end_index] + text = "Help\n" + text += f"For more help on how to use a command, type {prefix}help [module]\n\n" + text += f"Help Page No: {self.current_page}/{self.total_pages}\n\n" + for module_name in page_modules: + commands = modules_help[module_name] + text += f"• {module_name.title()}: {', '.join([f'{prefix + cmd_name.split()[0]}' for cmd_name in commands.keys()])}\n" + text += f"\nThe number of modules in the userbot: {len(modules_help)}" + await message.edit(text, disable_web_page_preview=True) + + def next_page(self) -> bool: + if self.current_page < self.total_pages: + self.current_page += 1 + return True + return False + + def prev_page(self) -> bool: + if self.current_page > 1: + self.current_page -= 1 + return True + return False diff --git a/utils/rentry.py b/utils/rentry.py new file mode 100644 index 0000000..dd6d7ad --- /dev/null +++ b/utils/rentry.py @@ -0,0 +1,200 @@ +#!/usr/bin/env python3 + +# @source: https://github.com/radude/rentry/blob/master/rentry.py +import asyncio +import http.cookiejar +import urllib.parse +import urllib.request + +from uuid import uuid4 +from datetime import datetime, timedelta +from http.cookies import SimpleCookie +from json import loads as json_loads + +from utils.db import db + +BASE_PROTOCOL = "https://" +BASE_URL = "rentry.co" + +_headers = {"Referer": f"{BASE_PROTOCOL}{BASE_URL}"} + + +class UrllibClient: + """Simple HTTP Session Client, keeps cookies.""" + + def __init__(self): + self.cookie_jar = http.cookiejar.CookieJar() + self.opener = urllib.request.build_opener( + urllib.request.HTTPCookieProcessor(self.cookie_jar) + ) + urllib.request.install_opener(self.opener) + + def get(self, url, headers=None): + if headers is None: + headers = {} + request = urllib.request.Request(url, headers=headers) + return self._request(request) + + def post(self, url, data=None, headers=None): + if headers is None: + headers = {} + postdata = urllib.parse.urlencode(data).encode() + request = urllib.request.Request(url, postdata, headers) + return self._request(request) + + def _request(self, request): + response = self.opener.open(request) + response.status_code = response.getcode() + response.data = response.read().decode("utf-8") + return response + + +def raw(url: str): + client = UrllibClient() + return json_loads(client.get(f"{BASE_PROTOCOL}{BASE_URL}/api/raw/{url}").data) + + +def new(text: str, edit_code: str = "", url: str = ""): + client, cookie = UrllibClient(), SimpleCookie() + + cookie.load(vars(client.get(f"{BASE_PROTOCOL}{BASE_URL}"))["headers"]["Set-Cookie"]) + csrftoken = cookie["csrftoken"].value + + payload = { + "csrfmiddlewaretoken": csrftoken, + "url": url, + "edit_code": edit_code, + "text": text, + } + + return json_loads( + client.post( + f"{BASE_PROTOCOL}{BASE_URL}" + "/api/new", payload, headers=_headers + ).data + ) + + +def edit(url_short: str, edit_code: str, text: str): + client, cookie = UrllibClient(), SimpleCookie() + + cookie.load(vars(client.get(f"{BASE_PROTOCOL}{BASE_URL}"))["headers"]["Set-Cookie"]) + csrftoken = cookie["csrftoken"].value + + payload = {"csrfmiddlewaretoken": csrftoken, "edit_code": edit_code, "text": text} + + return json_loads( + client.post( + f"{BASE_PROTOCOL}{BASE_URL}/api/edit/{url_short}", payload, headers=_headers + ).data + ) + + +def delete(url_short: str, edit_code: str): + client, cookie = UrllibClient(), SimpleCookie() + cookie.load(vars(client.get(f"{BASE_PROTOCOL}{BASE_URL}"))["headers"]["Set-Cookie"]) + csrftoken = cookie["csrftoken"].value + payload = {"csrfmiddlewaretoken": csrftoken, "edit_code": edit_code} + return json_loads( + client.post( + f"{BASE_PROTOCOL}{BASE_URL}/api/delete/{url_short}", + payload, + headers=_headers, + ).data + ) + + +async def paste( + text: str, + return_edit: bool = False, + edit_bin: bool = False, + edit_code: str = None, + url: str = None, + permanent: bool = False, +) -> str | tuple[str, str]: + """Pastes some text to rentry bin. + args: + text: Input text to paste + return_edit: If it should return edit code also + edit_bin: If this request is to edit an already existing bin + edit_code: Only required if edit_bin is True. It is the edit code used to edit bin. + url: Only required if edit_bin is True. It is the url on which the bin is located. + permanent: If the pasted content should not be deleted automatically + + returns: + The url of the paste or return a tuple containing url and edit code + """ + if not str(text): + return + + if edit_bin: + if not (url and edit_code): + raise ValueError("Please provide both, url and edit code") + response = edit(url_short=url, edit_code=edit_code, text=text) + else: + response = new(text=text) + + if response.get("status") != "200": + raise RuntimeError( + f"paste task terminated with status: {response.get('status')}\n" + f"Message: {response.get('content', 'No message provided')}" + ) + + url = response["url"] + edit_code = response["edit_code"] + + if not permanent: + short_url = response["url_short"] + time_now = datetime.now() + ftime = time_now.strftime("%d %I:%M:%S %p %Y") + + print(f"URL: {url} - Edit Code: {edit_code} - Time: {ftime}") + + rallUrls = db.get("core.rentry", "urls", default={"allUrls": {}}) + entry_id = str(uuid4()) + rallUrls["allUrls"][entry_id] = { + "url": short_url, + "edit_code": edit_code, + "time": ftime, + } + db.set("core.rentry", "urls", rallUrls) + + if return_edit: + return (url, edit_code) + return url + + +async def rentry_cleanup_job(): + """Periodically checks and deletes rentry pastes older than 24 hours""" + while True: + try: + rallUrls = db.get("core.rentry", "urls", default={"allUrls": {}}) + now = datetime.now() + deleted_count = 0 + error_count = 0 + + for entry_id, entry in list(rallUrls["allUrls"].items()): + url = entry["url"] + entry_time = datetime.strptime(entry["time"], "%d %I:%M:%S %p %Y") + + if now - entry_time > timedelta(days=1): + try: + delete(url, entry["edit_code"]) + del rallUrls["allUrls"][entry_id] + deleted_count += 1 + print(f"[#] Deleted expired rentry paste: {url}") + except Exception as e: + error_count += 1 + print(f"[!] Failed to delete rentry paste {url}: {str(e)}") + + if deleted_count or error_count: + print( + f"[*] Cleanup summary: {deleted_count} deleted, {error_count} failed" + ) + + if deleted_count: + db.set("core.rentry", "urls", rallUrls) + + except Exception as e: + print(f"[!] Error in rentry cleanup job: {str(e)}") + + await asyncio.sleep(12 * 60 * 60) diff --git a/utils/scripts.py b/utils/scripts.py new file mode 100644 index 0000000..6dc7d44 --- /dev/null +++ b/utils/scripts.py @@ -0,0 +1,542 @@ +# Moon-Userbot - telegram userbot +# Copyright (C) 2020-present Moon Userbot Organization +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import asyncio +import importlib +import math +import os +import re +import shlex +import subprocess +import sys +import time +import traceback +from PIL import Image +from io import BytesIO +from types import ModuleType +from typing import Dict, Tuple + +import psutil +from pyrogram import Client, errors, filters +from pyrogram.errors import FloodWait, MessageNotModified, UserNotParticipant +from pyrogram.types import Message +from pyrogram.enums import ChatMembersFilter + +from utils.db import db + +from .misc import modules_help, prefix, requirements_list + +META_COMMENTS = re.compile(r"^ *# *meta +(\S+) *: *(.*?)\s*$", re.MULTILINE) +interact_with_to_delete = [] + + +def time_formatter(milliseconds: int) -> str: + """Time Formatter""" + seconds, milliseconds = divmod(int(milliseconds), 1000) + minutes, seconds = divmod(seconds, 60) + hours, minutes = divmod(minutes, 60) + days, hours = divmod(hours, 24) + tmp = ( + ((str(days) + " day(s), ") if days else "") + + ((str(hours) + " hour(s), ") if hours else "") + + ((str(minutes) + " minute(s), ") if minutes else "") + + ((str(seconds) + " second(s), ") if seconds else "") + + ((str(milliseconds) + " millisecond(s), ") if milliseconds else "") + ) + return tmp[:-2] + + +def humanbytes(size): + """Convert Bytes To Bytes So That Human Can Read It""" + if not size: + return "" + power = 2**10 + raised_to_pow = 0 + dict_power_n = {0: "", 1: "Ki", 2: "Mi", 3: "Gi", 4: "Ti"} + while size > power: + size /= power + raised_to_pow += 1 + return str(round(size, 2)) + " " + dict_power_n[raised_to_pow] + "B" + + +async def edit_or_send_as_file( + tex: str, + message: Message, + client: Client, + caption: str = "Result!", + file_name: str = "result", +): + """Send As File If Len Of Text Exceeds Tg Limit Else Edit Message""" + if not tex: + await message.edit("Wait, What?") + return + if len(tex) > 1024: + await message.edit("OutPut is Too Large, Sending As File!") + file_names = f"{file_name}.txt" + with open(file_names, "w") as fn: + fn.write(tex) + await client.send_document(message.chat.id, file_names, caption=caption) + await message.delete() + if os.path.exists(file_names): + os.remove(file_names) + return + return await message.edit(tex) + + +def get_text(message: Message) -> None | str: + """Extract Text From Commands""" + text_to_return = message.text + if message.text is None: + return None + if " " in text_to_return: + try: + return message.text.split(None, 1)[1] + except IndexError: + return None + else: + return None + + +async def progress(current, total, message, start, type_of_ps, file_name=None): + """Progress Bar For Showing Progress While Uploading / Downloading File - Normal""" + now = time.time() + diff = now - start + if round(diff % 10.00) == 0 or current == total: + percentage = current * 100 / total + speed = current / diff + elapsed_time = round(diff) * 1000 + if elapsed_time == 0: + return + time_to_completion = round((total - current) / speed) * 1000 + estimated_total_time = elapsed_time + time_to_completion + progress_str = f"{''.join(['▰' for i in range(math.floor(percentage / 10))])}" + progress_str += ( + f"{''.join(['▱' for i in range(10 - math.floor(percentage / 10))])}" + ) + progress_str += f"{round(percentage, 2)}%\n" + tmp = f"{progress_str}{humanbytes(current)} of {humanbytes(total)}\n" + tmp += f"ETA: {time_formatter(estimated_total_time)}" + if file_name: + try: + await message.edit( + f"{type_of_ps}\nFile Name: {file_name}\n{tmp}" + ) + except FloodWait as e: + await asyncio.sleep(e.x) + except MessageNotModified: + pass + else: + try: + await message.edit(f"{type_of_ps}\n{tmp}") + except FloodWait as e: + await asyncio.sleep(e.x) + except MessageNotModified: + pass + + +async def run_cmd(prefix: str) -> Tuple[str, str, int, int]: + """Run Commands""" + args = shlex.split(prefix) + process = await asyncio.create_subprocess_exec( + *args, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE + ) + stdout, stderr = await process.communicate() + return ( + stdout.decode("utf-8", "replace").strip(), + stderr.decode("utf-8", "replace").strip(), + process.returncode, + process.pid, + ) + + +def mediainfo(media): + xx = str((str(media)).split("(", maxsplit=1)[0]) + m = "" + if xx == "MessageMediaDocument": + mim = media.document.mime_type + if mim == "application/x-tgsticker": + m = "sticker animated" + elif "image" in mim: + if mim == "image/webp": + m = "sticker" + elif mim == "image/gif": + m = "gif as doc" + else: + m = "pic as doc" + elif "video" in mim: + if "DocumentAttributeAnimated" in str(media): + m = "gif" + elif "DocumentAttributeVideo" in str(media): + i = str(media.document.attributes[0]) + if "supports_streaming=True" in i: + m = "video" + m = "video as doc" + else: + m = "video" + elif "audio" in mim: + m = "audio" + else: + m = "document" + elif xx == "MessageMediaPhoto": + m = "pic" + elif xx == "MessageMediaWebPage": + m = "web" + return m + + +async def edit_or_reply(message, txt): + """Edit Message If It's From Self, Else Reply To Message""" + if not message: + return + if message.from_user and message.from_user.is_self: + return await message.edit(txt) + return await message.reply(txt) + + +def text(message: Message) -> str: + """Find text in `Message` object""" + return message.text if message.text else message.caption + + +def restart() -> None: + music_bot_pid = db.get("custom.musicbot", "music_bot_pid", None) + if music_bot_pid is not None: + try: + music_bot_process = psutil.Process(music_bot_pid) + music_bot_process.terminate() + except psutil.NoSuchProcess: + print("Music bot is not running.") + os.execvp(sys.executable, [sys.executable, "main.py"]) # skipcq + + +def format_exc(e: Exception, suffix="") -> str: + traceback.print_exc() + err = traceback.format_exc() + if isinstance(e, errors.RPCError): + return ( + f"Telegram API error!\n" + f"[{e.CODE} {e.ID or e.NAME}] — {e.MESSAGE.format(value=e.value)}\n\n{suffix}" + ) + return f"Error!\n" f"{err}" + + +def with_reply(func): + async def wrapped(client: Client, message: Message): + if not message.reply_to_message: + await message.edit("Reply to message is required") + else: + return await func(client, message) + + return wrapped + + +def is_admin(func): + async def wrapped(client: Client, message: Message): + try: + chat_member = await client.get_chat_member( + message.chat.id, message.from_user.id + ) + chat_admins = [] + async for member in client.get_chat_members( + message.chat.id, filter=ChatMembersFilter.ADMINISTRATORS + ): + chat_admins.append(member) + if chat_member in chat_admins: + return await func(client, message) + await message.edit("You need to be an admin to perform this action.") + except UserNotParticipant: + await message.edit( + "You need to be a participant in the chat to perform this action." + ) + + return wrapped + + +async def interact_with(message: Message) -> Message: + """ + Check history with bot and return bot's response + + Example: + .. code-block:: python + bot_msg = await interact_with(await bot.send_message("@BotFather", "/start")) + :param message: already sent message to bot + :return: bot's response + """ + + await asyncio.sleep(1) + # noinspection PyProtectedMember + response = [ + msg async for msg in message._client.get_chat_history(message.chat.id, limit=1) + ] + seconds_waiting = 0 + + while response[0].from_user.is_self: + seconds_waiting += 1 + if seconds_waiting >= 5: + raise RuntimeError("bot didn't answer in 5 seconds") + + await asyncio.sleep(1) + # noinspection PyProtectedMember + response = [ + msg + async for msg in message._client.get_chat_history(message.chat.id, limit=1) + ] + + interact_with_to_delete.append(message.id) + interact_with_to_delete.append(response[0].id) + + return response[0] + + +def format_module_help(module_name: str, full=True): + commands = modules_help[module_name] + + help_text = ( + f"Help for |{module_name}|\n\nUsage:\n" if full else "Usage:\n" + ) + + for command, desc in commands.items(): + cmd = command.split(maxsplit=1) + args = " " + cmd[1] + "" if len(cmd) > 1 else "" + help_text += f"{prefix}{cmd[0]}{args} — {desc}\n" + + return help_text + + +def format_small_module_help(module_name: str, full=True): + commands = modules_help[module_name] + + help_text = ( + f"Help for |{module_name}|\n\nCommands list:\n" + if full + else "Commands list:\n" + ) + for command, _desc in commands.items(): + cmd = command.split(maxsplit=1) + args = " " + cmd[1] + "" if len(cmd) > 1 else "" + help_text += f"{prefix}{cmd[0]}{args}\n" + help_text += f"\nGet full usage: {prefix}help {module_name}" + + return help_text + + +def import_library(library_name: str, package_name: str = None): + """ + Loads a library, or installs it in ImportError case + :param library_name: library name (import example...) + :param package_name: package name in PyPi (pip install example) + :return: loaded module + """ + if package_name is None: + package_name = library_name + requirements_list.append(package_name) + + try: + return importlib.import_module(library_name) + except ImportError as exc: + completed = subprocess.run( + [sys.executable, "-m", "pip", "install", "--upgrade", package_name], + check=True, + ) + if completed.returncode != 0: + raise AssertionError( + f"Failed to install library {package_name} (pip exited with code {completed.returncode})" + ) from exc + return importlib.import_module(library_name) + + +def uninstall_library(package_name: str): + """ + Uninstalls a library + :param package_name: package name in PyPi (pip uninstall example) + """ + completed = subprocess.run( + [sys.executable, "-m", "pip", "uninstall", "-y", package_name], check=True + ) + if completed.returncode != 0: + raise AssertionError( + f"Failed to uninstall library {package_name} (pip exited with code {completed.returncode})" + ) + + +def resize_image( + input_img, output=None, img_type="PNG", size: int = 512, size2: int = None +): + if output is None: + output = BytesIO() + output.name = f"sticker.{img_type.lower()}" + + with Image.open(input_img) as img: + # We used to use thumbnail(size) here, but it returns with a *max* dimension of 512,512 + # rather than making one side exactly 512, so we have to calculate dimensions manually :( + if size2 is not None: + size = (size, size2) + elif img.width == img.height: + size = (size, size) + elif img.width < img.height: + size = (max(size * img.width // img.height, 1), size) + else: + size = (size, max(size * img.height // img.width, 1)) + + img.resize(size).save(output, img_type) + + return output + + +def resize_new_image(image_path, output_path, desired_width=None, desired_height=None): + """ + Resize an image to the desired dimensions while maintaining the aspect ratio. + + Args: + image_path (str): Path to the input image file. + output_path (str): Path to save the resized image. + desired_width (int, optional): Desired width in pixels. If not provided, the aspect ratio will be maintained. + desired_height (int, optional): Desired height in pixels. If not provided, the aspect ratio will be maintained. + """ + image = Image.open(image_path) + + width, height = image.size + + aspect_ratio = width / height + + if desired_width and desired_height: + new_width, new_height = desired_width, desired_height + elif desired_height: + new_width, new_height = int(desired_height * aspect_ratio), desired_height + else: + new_width, new_height = 150, 150 + + resized_image = image.resize((new_width, new_height), Image.Resampling.LANCZOS) + + resized_image.save(output_path) + if os.path.exists(image_path): + os.remove(image_path) + + +async def load_module( + module_name: str, + client: Client, + message: Message = None, + core=False, +) -> ModuleType: + if module_name in modules_help and not core: + await unload_module(module_name, client) + + path = f"modules.{'custom_modules.' if not core else ''}{module_name}" + + with open(f"{path.replace('.', '/')}.py", encoding="utf-8") as f: + code = f.read() + meta = parse_meta_comments(code) + + packages = meta.get("requires", "").split() + requirements_list.extend(packages) + + try: + module = importlib.import_module(path) + except ImportError as e: + if core: + # Core modules shouldn't raise ImportError + raise + + if not packages: + raise + + if message: + await message.edit(f"Installing requirements: {' '.join(packages)}") + + proc = await asyncio.create_subprocess_exec( + sys.executable, + "-m", + "pip", + "install", + "-U", + *packages, + ) + try: + await asyncio.wait_for(proc.wait(), timeout=120) + except asyncio.TimeoutError: + if message: + await message.edit( + "Timeout while installed requirements." + + "Try to install them manually" + ) + raise TimeoutError("timeout while installing requirements") from e + + if proc.returncode != 0: + if message: + await message.edit( + f"Failed to install requirements (pip exited with code {proc.returncode}). " + f"Check logs for futher info", + ) + raise RuntimeError("failed to install requirements") from e + + module = importlib.import_module(path) + + for _name, obj in vars(module).items(): + if isinstance(getattr(obj, "handlers", []), list): + for handler, group in getattr(obj, "handlers", []): + client.add_handler(handler, group) + + module.__meta__ = meta + + return module + + +async def unload_module(module_name: str, client: Client) -> bool: + path = "modules.custom_modules." + module_name + if path not in sys.modules: + return False + + module = importlib.import_module(path) + + for _name, obj in vars(module).items(): + for handler, group in getattr(obj, "handlers", []): + client.remove_handler(handler, group) + + del modules_help[module_name] + del sys.modules[path] + + return True + + +def no_prefix(handler): + def func(_, __, message): + if message.text and not message.text.startswith(handler): + return True + return False + + return filters.create(func) + + +def parse_meta_comments(code: str) -> Dict[str, str]: + try: + groups = META_COMMENTS.search(code).groups() + except AttributeError: + return {} + + return {groups[i]: groups[i + 1] for i in range(0, len(groups), 2)} + + +def ReplyCheck(message: Message): + reply_id = None + + if message.reply_to_message: + reply_id = message.reply_to_message.id + + elif not message.from_user.is_self: + reply_id = message.id + + return reply_id diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..245870b --- /dev/null +++ b/uv.lock @@ -0,0 +1,1019 @@ +version = 1 +revision = 3 +requires-python = ">=3.13" + +[[package]] +name = "aiofiles" +version = "25.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/41/c3/534eac40372d8ee36ef40df62ec129bee4fdb5ad9706e58a29be53b2c970/aiofiles-25.1.0.tar.gz", hash = "sha256:a8d728f0a29de45dc521f18f07297428d56992a742f0cd2701ba86e44d23d5b2", size = 46354, upload-time = "2025-10-09T20:51:04.358Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/8a/340a1555ae33d7354dbca4faa54948d76d89a27ceef032c8c3bc661d003e/aiofiles-25.1.0-py3-none-any.whl", hash = "sha256:abe311e527c862958650f9438e859c1fa7568a141b22abcd015e120e86a85695", size = 14668, upload-time = "2025-10-09T20:51:03.174Z" }, +] + +[[package]] +name = "aiohappyeyeballs" +version = "2.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760, upload-time = "2025-03-12T01:42:48.764Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265, upload-time = "2025-03-12T01:42:47.083Z" }, +] + +[[package]] +name = "aiohttp" +version = "3.13.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohappyeyeballs" }, + { name = "aiosignal" }, + { name = "attrs" }, + { name = "frozenlist" }, + { name = "multidict" }, + { name = "propcache" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1c/ce/3b83ebba6b3207a7135e5fcaba49706f8a4b6008153b4e30540c982fae26/aiohttp-3.13.2.tar.gz", hash = "sha256:40176a52c186aefef6eb3cad2cdd30cd06e3afbe88fe8ab2af9c0b90f228daca", size = 7837994, upload-time = "2025-10-28T20:59:39.937Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/78/7e90ca79e5aa39f9694dcfd74f4720782d3c6828113bb1f3197f7e7c4a56/aiohttp-3.13.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7519bdc7dfc1940d201651b52bf5e03f5503bda45ad6eacf64dda98be5b2b6be", size = 732139, upload-time = "2025-10-28T20:57:02.455Z" }, + { url = "https://files.pythonhosted.org/packages/db/ed/1f59215ab6853fbaa5c8495fa6cbc39edfc93553426152b75d82a5f32b76/aiohttp-3.13.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:088912a78b4d4f547a1f19c099d5a506df17eacec3c6f4375e2831ec1d995742", size = 490082, upload-time = "2025-10-28T20:57:04.784Z" }, + { url = "https://files.pythonhosted.org/packages/68/7b/fe0fe0f5e05e13629d893c760465173a15ad0039c0a5b0d0040995c8075e/aiohttp-3.13.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5276807b9de9092af38ed23ce120539ab0ac955547b38563a9ba4f5b07b95293", size = 489035, upload-time = "2025-10-28T20:57:06.894Z" }, + { url = "https://files.pythonhosted.org/packages/d2/04/db5279e38471b7ac801d7d36a57d1230feeee130bbe2a74f72731b23c2b1/aiohttp-3.13.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1237c1375eaef0db4dcd7c2559f42e8af7b87ea7d295b118c60c36a6e61cb811", size = 1720387, upload-time = "2025-10-28T20:57:08.685Z" }, + { url = "https://files.pythonhosted.org/packages/31/07/8ea4326bd7dae2bd59828f69d7fdc6e04523caa55e4a70f4a8725a7e4ed2/aiohttp-3.13.2-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:96581619c57419c3d7d78703d5b78c1e5e5fc0172d60f555bdebaced82ded19a", size = 1688314, upload-time = "2025-10-28T20:57:10.693Z" }, + { url = "https://files.pythonhosted.org/packages/48/ab/3d98007b5b87ffd519d065225438cc3b668b2f245572a8cb53da5dd2b1bc/aiohttp-3.13.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a2713a95b47374169409d18103366de1050fe0ea73db358fc7a7acb2880422d4", size = 1756317, upload-time = "2025-10-28T20:57:12.563Z" }, + { url = "https://files.pythonhosted.org/packages/97/3d/801ca172b3d857fafb7b50c7c03f91b72b867a13abca982ed6b3081774ef/aiohttp-3.13.2-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:228a1cd556b3caca590e9511a89444925da87d35219a49ab5da0c36d2d943a6a", size = 1858539, upload-time = "2025-10-28T20:57:14.623Z" }, + { url = "https://files.pythonhosted.org/packages/f7/0d/4764669bdf47bd472899b3d3db91fffbe925c8e3038ec591a2fd2ad6a14d/aiohttp-3.13.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ac6cde5fba8d7d8c6ac963dbb0256a9854e9fafff52fbcc58fdf819357892c3e", size = 1739597, upload-time = "2025-10-28T20:57:16.399Z" }, + { url = "https://files.pythonhosted.org/packages/c4/52/7bd3c6693da58ba16e657eb904a5b6decfc48ecd06e9ac098591653b1566/aiohttp-3.13.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f2bef8237544f4e42878c61cef4e2839fee6346dc60f5739f876a9c50be7fcdb", size = 1555006, upload-time = "2025-10-28T20:57:18.288Z" }, + { url = "https://files.pythonhosted.org/packages/48/30/9586667acec5993b6f41d2ebcf96e97a1255a85f62f3c653110a5de4d346/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:16f15a4eac3bc2d76c45f7ebdd48a65d41b242eb6c31c2245463b40b34584ded", size = 1683220, upload-time = "2025-10-28T20:57:20.241Z" }, + { url = "https://files.pythonhosted.org/packages/71/01/3afe4c96854cfd7b30d78333852e8e851dceaec1c40fd00fec90c6402dd2/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:bb7fb776645af5cc58ab804c58d7eba545a97e047254a52ce89c157b5af6cd0b", size = 1712570, upload-time = "2025-10-28T20:57:22.253Z" }, + { url = "https://files.pythonhosted.org/packages/11/2c/22799d8e720f4697a9e66fd9c02479e40a49de3de2f0bbe7f9f78a987808/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:e1b4951125ec10c70802f2cb09736c895861cd39fd9dcb35107b4dc8ae6220b8", size = 1733407, upload-time = "2025-10-28T20:57:24.37Z" }, + { url = "https://files.pythonhosted.org/packages/34/cb/90f15dd029f07cebbd91f8238a8b363978b530cd128488085b5703683594/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:550bf765101ae721ee1d37d8095f47b1f220650f85fe1af37a90ce75bab89d04", size = 1550093, upload-time = "2025-10-28T20:57:26.257Z" }, + { url = "https://files.pythonhosted.org/packages/69/46/12dce9be9d3303ecbf4d30ad45a7683dc63d90733c2d9fe512be6716cd40/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fe91b87fc295973096251e2d25a811388e7d8adf3bd2b97ef6ae78bc4ac6c476", size = 1758084, upload-time = "2025-10-28T20:57:28.349Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c8/0932b558da0c302ffd639fc6362a313b98fdf235dc417bc2493da8394df7/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e0c8e31cfcc4592cb200160344b2fb6ae0f9e4effe06c644b5a125d4ae5ebe23", size = 1716987, upload-time = "2025-10-28T20:57:30.233Z" }, + { url = "https://files.pythonhosted.org/packages/5d/8b/f5bd1a75003daed099baec373aed678f2e9b34f2ad40d85baa1368556396/aiohttp-3.13.2-cp313-cp313-win32.whl", hash = "sha256:0740f31a60848d6edb296a0df827473eede90c689b8f9f2a4cdde74889eb2254", size = 425859, upload-time = "2025-10-28T20:57:32.105Z" }, + { url = "https://files.pythonhosted.org/packages/5d/28/a8a9fc6957b2cee8902414e41816b5ab5536ecf43c3b1843c10e82c559b2/aiohttp-3.13.2-cp313-cp313-win_amd64.whl", hash = "sha256:a88d13e7ca367394908f8a276b89d04a3652044612b9a408a0bb22a5ed976a1a", size = 452192, upload-time = "2025-10-28T20:57:34.166Z" }, + { url = "https://files.pythonhosted.org/packages/9b/36/e2abae1bd815f01c957cbf7be817b3043304e1c87bad526292a0410fdcf9/aiohttp-3.13.2-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:2475391c29230e063ef53a66669b7b691c9bfc3f1426a0f7bcdf1216bdbac38b", size = 735234, upload-time = "2025-10-28T20:57:36.415Z" }, + { url = "https://files.pythonhosted.org/packages/ca/e3/1ee62dde9b335e4ed41db6bba02613295a0d5b41f74a783c142745a12763/aiohttp-3.13.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:f33c8748abef4d8717bb20e8fb1b3e07c6adacb7fd6beaae971a764cf5f30d61", size = 490733, upload-time = "2025-10-28T20:57:38.205Z" }, + { url = "https://files.pythonhosted.org/packages/1a/aa/7a451b1d6a04e8d15a362af3e9b897de71d86feac3babf8894545d08d537/aiohttp-3.13.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ae32f24bbfb7dbb485a24b30b1149e2f200be94777232aeadba3eecece4d0aa4", size = 491303, upload-time = "2025-10-28T20:57:40.122Z" }, + { url = "https://files.pythonhosted.org/packages/57/1e/209958dbb9b01174870f6a7538cd1f3f28274fdbc88a750c238e2c456295/aiohttp-3.13.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d7f02042c1f009ffb70067326ef183a047425bb2ff3bc434ead4dd4a4a66a2b", size = 1717965, upload-time = "2025-10-28T20:57:42.28Z" }, + { url = "https://files.pythonhosted.org/packages/08/aa/6a01848d6432f241416bc4866cae8dc03f05a5a884d2311280f6a09c73d6/aiohttp-3.13.2-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:93655083005d71cd6c072cdab54c886e6570ad2c4592139c3fb967bfc19e4694", size = 1667221, upload-time = "2025-10-28T20:57:44.869Z" }, + { url = "https://files.pythonhosted.org/packages/87/4f/36c1992432d31bbc789fa0b93c768d2e9047ec8c7177e5cd84ea85155f36/aiohttp-3.13.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0db1e24b852f5f664cd728db140cf11ea0e82450471232a394b3d1a540b0f906", size = 1757178, upload-time = "2025-10-28T20:57:47.216Z" }, + { url = "https://files.pythonhosted.org/packages/ac/b4/8e940dfb03b7e0f68a82b88fd182b9be0a65cb3f35612fe38c038c3112cf/aiohttp-3.13.2-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b009194665bcd128e23eaddef362e745601afa4641930848af4c8559e88f18f9", size = 1838001, upload-time = "2025-10-28T20:57:49.337Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ef/39f3448795499c440ab66084a9db7d20ca7662e94305f175a80f5b7e0072/aiohttp-3.13.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c038a8fdc8103cd51dbd986ecdce141473ffd9775a7a8057a6ed9c3653478011", size = 1716325, upload-time = "2025-10-28T20:57:51.327Z" }, + { url = "https://files.pythonhosted.org/packages/d7/51/b311500ffc860b181c05d91c59a1313bdd05c82960fdd4035a15740d431e/aiohttp-3.13.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:66bac29b95a00db411cd758fea0e4b9bdba6d549dfe333f9a945430f5f2cc5a6", size = 1547978, upload-time = "2025-10-28T20:57:53.554Z" }, + { url = "https://files.pythonhosted.org/packages/31/64/b9d733296ef79815226dab8c586ff9e3df41c6aff2e16c06697b2d2e6775/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4ebf9cfc9ba24a74cf0718f04aac2a3bbe745902cc7c5ebc55c0f3b5777ef213", size = 1682042, upload-time = "2025-10-28T20:57:55.617Z" }, + { url = "https://files.pythonhosted.org/packages/3f/30/43d3e0f9d6473a6db7d472104c4eff4417b1e9df01774cb930338806d36b/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a4b88ebe35ce54205c7074f7302bd08a4cb83256a3e0870c72d6f68a3aaf8e49", size = 1680085, upload-time = "2025-10-28T20:57:57.59Z" }, + { url = "https://files.pythonhosted.org/packages/16/51/c709f352c911b1864cfd1087577760ced64b3e5bee2aa88b8c0c8e2e4972/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:98c4fb90bb82b70a4ed79ca35f656f4281885be076f3f970ce315402b53099ae", size = 1728238, upload-time = "2025-10-28T20:57:59.525Z" }, + { url = "https://files.pythonhosted.org/packages/19/e2/19bd4c547092b773caeb48ff5ae4b1ae86756a0ee76c16727fcfd281404b/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:ec7534e63ae0f3759df3a1ed4fa6bc8f75082a924b590619c0dd2f76d7043caa", size = 1544395, upload-time = "2025-10-28T20:58:01.914Z" }, + { url = "https://files.pythonhosted.org/packages/cf/87/860f2803b27dfc5ed7be532832a3498e4919da61299b4a1f8eb89b8ff44d/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5b927cf9b935a13e33644cbed6c8c4b2d0f25b713d838743f8fe7191b33829c4", size = 1742965, upload-time = "2025-10-28T20:58:03.972Z" }, + { url = "https://files.pythonhosted.org/packages/67/7f/db2fc7618925e8c7a601094d5cbe539f732df4fb570740be88ed9e40e99a/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:88d6c017966a78c5265d996c19cdb79235be5e6412268d7e2ce7dee339471b7a", size = 1697585, upload-time = "2025-10-28T20:58:06.189Z" }, + { url = "https://files.pythonhosted.org/packages/0c/07/9127916cb09bb38284db5036036042b7b2c514c8ebaeee79da550c43a6d6/aiohttp-3.13.2-cp314-cp314-win32.whl", hash = "sha256:f7c183e786e299b5d6c49fb43a769f8eb8e04a2726a2bd5887b98b5cc2d67940", size = 431621, upload-time = "2025-10-28T20:58:08.636Z" }, + { url = "https://files.pythonhosted.org/packages/fb/41/554a8a380df6d3a2bba8a7726429a23f4ac62aaf38de43bb6d6cde7b4d4d/aiohttp-3.13.2-cp314-cp314-win_amd64.whl", hash = "sha256:fe242cd381e0fb65758faf5ad96c2e460df6ee5b2de1072fe97e4127927e00b4", size = 457627, upload-time = "2025-10-28T20:58:11Z" }, + { url = "https://files.pythonhosted.org/packages/c7/8e/3824ef98c039d3951cb65b9205a96dd2b20f22241ee17d89c5701557c826/aiohttp-3.13.2-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:f10d9c0b0188fe85398c61147bbd2a657d616c876863bfeff43376e0e3134673", size = 767360, upload-time = "2025-10-28T20:58:13.358Z" }, + { url = "https://files.pythonhosted.org/packages/a4/0f/6a03e3fc7595421274fa34122c973bde2d89344f8a881b728fa8c774e4f1/aiohttp-3.13.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:e7c952aefdf2460f4ae55c5e9c3e80aa72f706a6317e06020f80e96253b1accd", size = 504616, upload-time = "2025-10-28T20:58:15.339Z" }, + { url = "https://files.pythonhosted.org/packages/c6/aa/ed341b670f1bc8a6f2c6a718353d13b9546e2cef3544f573c6a1ff0da711/aiohttp-3.13.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c20423ce14771d98353d2e25e83591fa75dfa90a3c1848f3d7c68243b4fbded3", size = 509131, upload-time = "2025-10-28T20:58:17.693Z" }, + { url = "https://files.pythonhosted.org/packages/7f/f0/c68dac234189dae5c4bbccc0f96ce0cc16b76632cfc3a08fff180045cfa4/aiohttp-3.13.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e96eb1a34396e9430c19d8338d2ec33015e4a87ef2b4449db94c22412e25ccdf", size = 1864168, upload-time = "2025-10-28T20:58:20.113Z" }, + { url = "https://files.pythonhosted.org/packages/8f/65/75a9a76db8364b5d0e52a0c20eabc5d52297385d9af9c35335b924fafdee/aiohttp-3.13.2-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:23fb0783bc1a33640036465019d3bba069942616a6a2353c6907d7fe1ccdaf4e", size = 1719200, upload-time = "2025-10-28T20:58:22.583Z" }, + { url = "https://files.pythonhosted.org/packages/f5/55/8df2ed78d7f41d232f6bd3ff866b6f617026551aa1d07e2f03458f964575/aiohttp-3.13.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2e1a9bea6244a1d05a4e57c295d69e159a5c50d8ef16aa390948ee873478d9a5", size = 1843497, upload-time = "2025-10-28T20:58:24.672Z" }, + { url = "https://files.pythonhosted.org/packages/e9/e0/94d7215e405c5a02ccb6a35c7a3a6cfff242f457a00196496935f700cde5/aiohttp-3.13.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0a3d54e822688b56e9f6b5816fb3de3a3a64660efac64e4c2dc435230ad23bad", size = 1935703, upload-time = "2025-10-28T20:58:26.758Z" }, + { url = "https://files.pythonhosted.org/packages/0b/78/1eeb63c3f9b2d1015a4c02788fb543141aad0a03ae3f7a7b669b2483f8d4/aiohttp-3.13.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7a653d872afe9f33497215745da7a943d1dc15b728a9c8da1c3ac423af35178e", size = 1792738, upload-time = "2025-10-28T20:58:29.787Z" }, + { url = "https://files.pythonhosted.org/packages/41/75/aaf1eea4c188e51538c04cc568040e3082db263a57086ea74a7d38c39e42/aiohttp-3.13.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:56d36e80d2003fa3fc0207fac644216d8532e9504a785ef9a8fd013f84a42c61", size = 1624061, upload-time = "2025-10-28T20:58:32.529Z" }, + { url = "https://files.pythonhosted.org/packages/9b/c2/3b6034de81fbcc43de8aeb209073a2286dfb50b86e927b4efd81cf848197/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:78cd586d8331fb8e241c2dd6b2f4061778cc69e150514b39a9e28dd050475661", size = 1789201, upload-time = "2025-10-28T20:58:34.618Z" }, + { url = "https://files.pythonhosted.org/packages/c9/38/c15dcf6d4d890217dae79d7213988f4e5fe6183d43893a9cf2fe9e84ca8d/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:20b10bbfbff766294fe99987f7bb3b74fdd2f1a2905f2562132641ad434dcf98", size = 1776868, upload-time = "2025-10-28T20:58:38.835Z" }, + { url = "https://files.pythonhosted.org/packages/04/75/f74fd178ac81adf4f283a74847807ade5150e48feda6aef024403716c30c/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9ec49dff7e2b3c85cdeaa412e9d438f0ecd71676fde61ec57027dd392f00c693", size = 1790660, upload-time = "2025-10-28T20:58:41.507Z" }, + { url = "https://files.pythonhosted.org/packages/e7/80/7368bd0d06b16b3aba358c16b919e9c46cf11587dc572091031b0e9e3ef0/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:94f05348c4406450f9d73d38efb41d669ad6cd90c7ee194810d0eefbfa875a7a", size = 1617548, upload-time = "2025-10-28T20:58:43.674Z" }, + { url = "https://files.pythonhosted.org/packages/7d/4b/a6212790c50483cb3212e507378fbe26b5086d73941e1ec4b56a30439688/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:fa4dcb605c6f82a80c7f95713c2b11c3b8e9893b3ebd2bc9bde93165ed6107be", size = 1817240, upload-time = "2025-10-28T20:58:45.787Z" }, + { url = "https://files.pythonhosted.org/packages/ff/f7/ba5f0ba4ea8d8f3c32850912944532b933acbf0f3a75546b89269b9b7dde/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cf00e5db968c3f67eccd2778574cf64d8b27d95b237770aa32400bd7a1ca4f6c", size = 1762334, upload-time = "2025-10-28T20:58:47.936Z" }, + { url = "https://files.pythonhosted.org/packages/7e/83/1a5a1856574588b1cad63609ea9ad75b32a8353ac995d830bf5da9357364/aiohttp-3.13.2-cp314-cp314t-win32.whl", hash = "sha256:d23b5fe492b0805a50d3371e8a728a9134d8de5447dce4c885f5587294750734", size = 464685, upload-time = "2025-10-28T20:58:50.642Z" }, + { url = "https://files.pythonhosted.org/packages/9f/4d/d22668674122c08f4d56972297c51a624e64b3ed1efaa40187607a7cb66e/aiohttp-3.13.2-cp314-cp314t-win_amd64.whl", hash = "sha256:ff0a7b0a82a7ab905cbda74006318d1b12e37c797eb1b0d4eb3e316cf47f658f", size = 498093, upload-time = "2025-10-28T20:58:52.782Z" }, +] + +[[package]] +name = "aiosignal" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "frozenlist" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007, upload-time = "2025-07-03T22:54:43.528Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" }, +] + +[[package]] +name = "attrs" +version = "25.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, +] + +[[package]] +name = "beautifulsoup4" +version = "4.14.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "soupsieve" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/77/e9/df2358efd7659577435e2177bfa69cba6c33216681af51a707193dec162a/beautifulsoup4-4.14.2.tar.gz", hash = "sha256:2a98ab9f944a11acee9cc848508ec28d9228abfd522ef0fad6a02a72e0ded69e", size = 625822, upload-time = "2025-09-29T10:05:42.613Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/fe/3aed5d0be4d404d12d36ab97e2f1791424d9ca39c2f754a6285d59a3b01d/beautifulsoup4-4.14.2-py3-none-any.whl", hash = "sha256:5ef6fa3a8cbece8488d66985560f97ed091e22bbc4e9c2338508a9d5de6d4515", size = 106392, upload-time = "2025-09-29T10:05:43.771Z" }, +] + +[[package]] +name = "blinker" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", size = 22460, upload-time = "2024-11-08T17:25:47.436Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458, upload-time = "2024-11-08T17:25:46.184Z" }, +] + +[[package]] +name = "certifi" +version = "2025.10.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/5b/b6ce21586237c77ce67d01dc5507039d444b630dd76611bbca2d8e5dcd91/certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43", size = 164519, upload-time = "2025-10-05T04:12:15.808Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de", size = 163286, upload-time = "2025-10-05T04:12:14.03Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" }, + { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" }, + { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" }, + { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" }, + { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" }, + { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" }, + { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" }, + { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" }, + { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" }, + { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" }, + { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" }, + { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" }, + { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" }, + { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" }, + { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" }, + { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" }, + { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" }, + { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" }, + { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" }, + { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" }, + { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" }, + { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" }, + { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" }, + { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" }, + { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" }, + { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" }, + { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, +] + +[[package]] +name = "click" +version = "8.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/46/61/de6cd827efad202d7057d93e0fed9294b96952e188f7384832791c7b2254/click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4", size = 276943, upload-time = "2025-09-18T17:32:23.696Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc", size = 107295, upload-time = "2025-09-18T17:32:22.42Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "dnspython" +version = "2.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/8b/57666417c0f90f08bcafa776861060426765fdb422eb10212086fb811d26/dnspython-2.8.0.tar.gz", hash = "sha256:181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f", size = 368251, upload-time = "2025-09-07T18:58:00.022Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af", size = 331094, upload-time = "2025-09-07T18:57:58.071Z" }, +] + +[[package]] +name = "environs" +version = "14.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "marshmallow" }, + { name = "python-dotenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/aa/75/06801d5beeb398ed3903167af9376bb81c4ac41c44a53d45193065ebb1a8/environs-14.5.0.tar.gz", hash = "sha256:f7b8f6fcf3301bc674bc9c03e39b5986d116126ffb96764efd34c339ed9464ee", size = 35426, upload-time = "2025-11-02T21:30:36.78Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/f3/6961beb9a1e77d01dee1dd48f00fb3064429c8abcfa26aa863eb7cb2b6dd/environs-14.5.0-py3-none-any.whl", hash = "sha256:1abd3e3a5721fb09797438d6c902bc2f35d4580dfaffe68b8ee588b67b504e13", size = 17202, upload-time = "2025-11-02T21:30:35.186Z" }, +] + +[[package]] +name = "flask" +version = "3.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "blinker" }, + { name = "click" }, + { name = "itsdangerous" }, + { name = "jinja2" }, + { name = "markupsafe" }, + { name = "werkzeug" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dc/6d/cfe3c0fcc5e477df242b98bfe186a4c34357b4847e87ecaef04507332dab/flask-3.1.2.tar.gz", hash = "sha256:bf656c15c80190ed628ad08cdfd3aaa35beb087855e2f494910aa3774cc4fd87", size = 720160, upload-time = "2025-08-19T21:03:21.205Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/f9/7f9263c5695f4bd0023734af91bedb2ff8209e8de6ead162f35d8dc762fd/flask-3.1.2-py3-none-any.whl", hash = "sha256:ca1d8112ec8a6158cc29ea4858963350011b5c846a414cdb7a954aa9e967d03c", size = 103308, upload-time = "2025-08-19T21:03:19.499Z" }, +] + +[[package]] +name = "frozenlist" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875, upload-time = "2025-10-06T05:38:17.865Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2d/40/0832c31a37d60f60ed79e9dfb5a92e1e2af4f40a16a29abcc7992af9edff/frozenlist-1.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d92f1a84bb12d9e56f818b3a746f3efba93c1b63c8387a73dde655e1e42282a", size = 85717, upload-time = "2025-10-06T05:36:27.341Z" }, + { url = "https://files.pythonhosted.org/packages/30/ba/b0b3de23f40bc55a7057bd38434e25c34fa48e17f20ee273bbde5e0650f3/frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7", size = 49651, upload-time = "2025-10-06T05:36:28.855Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40", size = 49417, upload-time = "2025-10-06T05:36:29.877Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027", size = 234391, upload-time = "2025-10-06T05:36:31.301Z" }, + { url = "https://files.pythonhosted.org/packages/40/76/c202df58e3acdf12969a7895fd6f3bc016c642e6726aa63bd3025e0fc71c/frozenlist-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaa352d7047a31d87dafcacbabe89df0aa506abb5b1b85a2fb91bc3faa02d822", size = 233048, upload-time = "2025-10-06T05:36:32.531Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c0/8746afb90f17b73ca5979c7a3958116e105ff796e718575175319b5bb4ce/frozenlist-1.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:03ae967b4e297f58f8c774c7eabcce57fe3c2434817d4385c50661845a058121", size = 226549, upload-time = "2025-10-06T05:36:33.706Z" }, + { url = "https://files.pythonhosted.org/packages/7e/eb/4c7eefc718ff72f9b6c4893291abaae5fbc0c82226a32dcd8ef4f7a5dbef/frozenlist-1.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6292f1de555ffcc675941d65fffffb0a5bcd992905015f85d0592201793e0e5", size = 239833, upload-time = "2025-10-06T05:36:34.947Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4e/e5c02187cf704224f8b21bee886f3d713ca379535f16893233b9d672ea71/frozenlist-1.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29548f9b5b5e3460ce7378144c3010363d8035cea44bc0bf02d57f5a685e084e", size = 245363, upload-time = "2025-10-06T05:36:36.534Z" }, + { url = "https://files.pythonhosted.org/packages/1f/96/cb85ec608464472e82ad37a17f844889c36100eed57bea094518bf270692/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ec3cc8c5d4084591b4237c0a272cc4f50a5b03396a47d9caaf76f5d7b38a4f11", size = 229314, upload-time = "2025-10-06T05:36:38.582Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6f/4ae69c550e4cee66b57887daeebe006fe985917c01d0fff9caab9883f6d0/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:517279f58009d0b1f2e7c1b130b377a349405da3f7621ed6bfae50b10adf20c1", size = 243365, upload-time = "2025-10-06T05:36:40.152Z" }, + { url = "https://files.pythonhosted.org/packages/7a/58/afd56de246cf11780a40a2c28dc7cbabbf06337cc8ddb1c780a2d97e88d8/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:db1e72ede2d0d7ccb213f218df6a078a9c09a7de257c2fe8fcef16d5925230b1", size = 237763, upload-time = "2025-10-06T05:36:41.355Z" }, + { url = "https://files.pythonhosted.org/packages/cb/36/cdfaf6ed42e2644740d4a10452d8e97fa1c062e2a8006e4b09f1b5fd7d63/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b4dec9482a65c54a5044486847b8a66bf10c9cb4926d42927ec4e8fd5db7fed8", size = 240110, upload-time = "2025-10-06T05:36:42.716Z" }, + { url = "https://files.pythonhosted.org/packages/03/a8/9ea226fbefad669f11b52e864c55f0bd57d3c8d7eb07e9f2e9a0b39502e1/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:21900c48ae04d13d416f0e1e0c4d81f7931f73a9dfa0b7a8746fb2fe7dd970ed", size = 233717, upload-time = "2025-10-06T05:36:44.251Z" }, + { url = "https://files.pythonhosted.org/packages/1e/0b/1b5531611e83ba7d13ccc9988967ea1b51186af64c42b7a7af465dcc9568/frozenlist-1.8.0-cp313-cp313-win32.whl", hash = "sha256:8b7b94a067d1c504ee0b16def57ad5738701e4ba10cec90529f13fa03c833496", size = 39628, upload-time = "2025-10-06T05:36:45.423Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231", size = 43882, upload-time = "2025-10-06T05:36:46.796Z" }, + { url = "https://files.pythonhosted.org/packages/c1/17/502cd212cbfa96eb1388614fe39a3fc9ab87dbbe042b66f97acb57474834/frozenlist-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:44389d135b3ff43ba8cc89ff7f51f5a0bb6b63d829c8300f79a2fe4fe61bcc62", size = 39676, upload-time = "2025-10-06T05:36:47.8Z" }, + { url = "https://files.pythonhosted.org/packages/d2/5c/3bbfaa920dfab09e76946a5d2833a7cbdf7b9b4a91c714666ac4855b88b4/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e25ac20a2ef37e91c1b39938b591457666a0fa835c7783c3a8f33ea42870db94", size = 89235, upload-time = "2025-10-06T05:36:48.78Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d6/f03961ef72166cec1687e84e8925838442b615bd0b8854b54923ce5b7b8a/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07cdca25a91a4386d2e76ad992916a85038a9b97561bf7a3fd12d5d9ce31870c", size = 50742, upload-time = "2025-10-06T05:36:49.837Z" }, + { url = "https://files.pythonhosted.org/packages/1e/bb/a6d12b7ba4c3337667d0e421f7181c82dda448ce4e7ad7ecd249a16fa806/frozenlist-1.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e0c11f2cc6717e0a741f84a527c52616140741cd812a50422f83dc31749fb52", size = 51725, upload-time = "2025-10-06T05:36:50.851Z" }, + { url = "https://files.pythonhosted.org/packages/bc/71/d1fed0ffe2c2ccd70b43714c6cab0f4188f09f8a67a7914a6b46ee30f274/frozenlist-1.8.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3210649ee28062ea6099cfda39e147fa1bc039583c8ee4481cb7811e2448c51", size = 284533, upload-time = "2025-10-06T05:36:51.898Z" }, + { url = "https://files.pythonhosted.org/packages/c9/1f/fb1685a7b009d89f9bf78a42d94461bc06581f6e718c39344754a5d9bada/frozenlist-1.8.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581ef5194c48035a7de2aefc72ac6539823bb71508189e5de01d60c9dcd5fa65", size = 292506, upload-time = "2025-10-06T05:36:53.101Z" }, + { url = "https://files.pythonhosted.org/packages/e6/3b/b991fe1612703f7e0d05c0cf734c1b77aaf7c7d321df4572e8d36e7048c8/frozenlist-1.8.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3ef2d026f16a2b1866e1d86fc4e1291e1ed8a387b2c333809419a2f8b3a77b82", size = 274161, upload-time = "2025-10-06T05:36:54.309Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ec/c5c618767bcdf66e88945ec0157d7f6c4a1322f1473392319b7a2501ded7/frozenlist-1.8.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5500ef82073f599ac84d888e3a8c1f77ac831183244bfd7f11eaa0289fb30714", size = 294676, upload-time = "2025-10-06T05:36:55.566Z" }, + { url = "https://files.pythonhosted.org/packages/7c/ce/3934758637d8f8a88d11f0585d6495ef54b2044ed6ec84492a91fa3b27aa/frozenlist-1.8.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50066c3997d0091c411a66e710f4e11752251e6d2d73d70d8d5d4c76442a199d", size = 300638, upload-time = "2025-10-06T05:36:56.758Z" }, + { url = "https://files.pythonhosted.org/packages/fc/4f/a7e4d0d467298f42de4b41cbc7ddaf19d3cfeabaf9ff97c20c6c7ee409f9/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5c1c8e78426e59b3f8005e9b19f6ff46e5845895adbde20ece9218319eca6506", size = 283067, upload-time = "2025-10-06T05:36:57.965Z" }, + { url = "https://files.pythonhosted.org/packages/dc/48/c7b163063d55a83772b268e6d1affb960771b0e203b632cfe09522d67ea5/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:eefdba20de0d938cec6a89bd4d70f346a03108a19b9df4248d3cf0d88f1b0f51", size = 292101, upload-time = "2025-10-06T05:36:59.237Z" }, + { url = "https://files.pythonhosted.org/packages/9f/d0/2366d3c4ecdc2fd391e0afa6e11500bfba0ea772764d631bbf82f0136c9d/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cf253e0e1c3ceb4aaff6df637ce033ff6535fb8c70a764a8f46aafd3d6ab798e", size = 289901, upload-time = "2025-10-06T05:37:00.811Z" }, + { url = "https://files.pythonhosted.org/packages/b8/94/daff920e82c1b70e3618a2ac39fbc01ae3e2ff6124e80739ce5d71c9b920/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:032efa2674356903cd0261c4317a561a6850f3ac864a63fc1583147fb05a79b0", size = 289395, upload-time = "2025-10-06T05:37:02.115Z" }, + { url = "https://files.pythonhosted.org/packages/e3/20/bba307ab4235a09fdcd3cc5508dbabd17c4634a1af4b96e0f69bfe551ebd/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6da155091429aeba16851ecb10a9104a108bcd32f6c1642867eadaee401c1c41", size = 283659, upload-time = "2025-10-06T05:37:03.711Z" }, + { url = "https://files.pythonhosted.org/packages/fd/00/04ca1c3a7a124b6de4f8a9a17cc2fcad138b4608e7a3fc5877804b8715d7/frozenlist-1.8.0-cp313-cp313t-win32.whl", hash = "sha256:0f96534f8bfebc1a394209427d0f8a63d343c9779cda6fc25e8e121b5fd8555b", size = 43492, upload-time = "2025-10-06T05:37:04.915Z" }, + { url = "https://files.pythonhosted.org/packages/59/5e/c69f733a86a94ab10f68e496dc6b7e8bc078ebb415281d5698313e3af3a1/frozenlist-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5d63a068f978fc69421fb0e6eb91a9603187527c86b7cd3f534a5b77a592b888", size = 48034, upload-time = "2025-10-06T05:37:06.343Z" }, + { url = "https://files.pythonhosted.org/packages/16/6c/be9d79775d8abe79b05fa6d23da99ad6e7763a1d080fbae7290b286093fd/frozenlist-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf0a7e10b077bf5fb9380ad3ae8ce20ef919a6ad93b4552896419ac7e1d8e042", size = 41749, upload-time = "2025-10-06T05:37:07.431Z" }, + { url = "https://files.pythonhosted.org/packages/f1/c8/85da824b7e7b9b6e7f7705b2ecaf9591ba6f79c1177f324c2735e41d36a2/frozenlist-1.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cee686f1f4cadeb2136007ddedd0aaf928ab95216e7691c63e50a8ec066336d0", size = 86127, upload-time = "2025-10-06T05:37:08.438Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e8/a1185e236ec66c20afd72399522f142c3724c785789255202d27ae992818/frozenlist-1.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:119fb2a1bd47307e899c2fac7f28e85b9a543864df47aa7ec9d3c1b4545f096f", size = 49698, upload-time = "2025-10-06T05:37:09.48Z" }, + { url = "https://files.pythonhosted.org/packages/a1/93/72b1736d68f03fda5fdf0f2180fb6caaae3894f1b854d006ac61ecc727ee/frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4970ece02dbc8c3a92fcc5228e36a3e933a01a999f7094ff7c23fbd2beeaa67c", size = 49749, upload-time = "2025-10-06T05:37:10.569Z" }, + { url = "https://files.pythonhosted.org/packages/a7/b2/fabede9fafd976b991e9f1b9c8c873ed86f202889b864756f240ce6dd855/frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cba69cb73723c3f329622e34bdbf5ce1f80c21c290ff04256cff1cd3c2036ed2", size = 231298, upload-time = "2025-10-06T05:37:11.993Z" }, + { url = "https://files.pythonhosted.org/packages/3a/3b/d9b1e0b0eed36e70477ffb8360c49c85c8ca8ef9700a4e6711f39a6e8b45/frozenlist-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:778a11b15673f6f1df23d9586f83c4846c471a8af693a22e066508b77d201ec8", size = 232015, upload-time = "2025-10-06T05:37:13.194Z" }, + { url = "https://files.pythonhosted.org/packages/dc/94/be719d2766c1138148564a3960fc2c06eb688da592bdc25adcf856101be7/frozenlist-1.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686", size = 225038, upload-time = "2025-10-06T05:37:14.577Z" }, + { url = "https://files.pythonhosted.org/packages/e4/09/6712b6c5465f083f52f50cf74167b92d4ea2f50e46a9eea0523d658454ae/frozenlist-1.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:97260ff46b207a82a7567b581ab4190bd4dfa09f4db8a8b49d1a958f6aa4940e", size = 240130, upload-time = "2025-10-06T05:37:15.781Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d4/cd065cdcf21550b54f3ce6a22e143ac9e4836ca42a0de1022da8498eac89/frozenlist-1.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54b2077180eb7f83dd52c40b2750d0a9f175e06a42e3213ce047219de902717a", size = 242845, upload-time = "2025-10-06T05:37:17.037Z" }, + { url = "https://files.pythonhosted.org/packages/62/c3/f57a5c8c70cd1ead3d5d5f776f89d33110b1addae0ab010ad774d9a44fb9/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f05983daecab868a31e1da44462873306d3cbfd76d1f0b5b69c473d21dbb128", size = 229131, upload-time = "2025-10-06T05:37:18.221Z" }, + { url = "https://files.pythonhosted.org/packages/6c/52/232476fe9cb64f0742f3fde2b7d26c1dac18b6d62071c74d4ded55e0ef94/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:33f48f51a446114bc5d251fb2954ab0164d5be02ad3382abcbfe07e2531d650f", size = 240542, upload-time = "2025-10-06T05:37:19.771Z" }, + { url = "https://files.pythonhosted.org/packages/5f/85/07bf3f5d0fb5414aee5f47d33c6f5c77bfe49aac680bfece33d4fdf6a246/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:154e55ec0655291b5dd1b8731c637ecdb50975a2ae70c606d100750a540082f7", size = 237308, upload-time = "2025-10-06T05:37:20.969Z" }, + { url = "https://files.pythonhosted.org/packages/11/99/ae3a33d5befd41ac0ca2cc7fd3aa707c9c324de2e89db0e0f45db9a64c26/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:4314debad13beb564b708b4a496020e5306c7333fa9a3ab90374169a20ffab30", size = 238210, upload-time = "2025-10-06T05:37:22.252Z" }, + { url = "https://files.pythonhosted.org/packages/b2/60/b1d2da22f4970e7a155f0adde9b1435712ece01b3cd45ba63702aea33938/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:073f8bf8becba60aa931eb3bc420b217bb7d5b8f4750e6f8b3be7f3da85d38b7", size = 231972, upload-time = "2025-10-06T05:37:23.5Z" }, + { url = "https://files.pythonhosted.org/packages/3f/ab/945b2f32de889993b9c9133216c068b7fcf257d8595a0ac420ac8677cab0/frozenlist-1.8.0-cp314-cp314-win32.whl", hash = "sha256:bac9c42ba2ac65ddc115d930c78d24ab8d4f465fd3fc473cdedfccadb9429806", size = 40536, upload-time = "2025-10-06T05:37:25.581Z" }, + { url = "https://files.pythonhosted.org/packages/59/ad/9caa9b9c836d9ad6f067157a531ac48b7d36499f5036d4141ce78c230b1b/frozenlist-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:3e0761f4d1a44f1d1a47996511752cf3dcec5bbdd9cc2b4fe595caf97754b7a0", size = 44330, upload-time = "2025-10-06T05:37:26.928Z" }, + { url = "https://files.pythonhosted.org/packages/82/13/e6950121764f2676f43534c555249f57030150260aee9dcf7d64efda11dd/frozenlist-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:d1eaff1d00c7751b7c6662e9c5ba6eb2c17a2306ba5e2a37f24ddf3cc953402b", size = 40627, upload-time = "2025-10-06T05:37:28.075Z" }, + { url = "https://files.pythonhosted.org/packages/c0/c7/43200656ecc4e02d3f8bc248df68256cd9572b3f0017f0a0c4e93440ae23/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d3bb933317c52d7ea5004a1c442eef86f426886fba134ef8cf4226ea6ee1821d", size = 89238, upload-time = "2025-10-06T05:37:29.373Z" }, + { url = "https://files.pythonhosted.org/packages/d1/29/55c5f0689b9c0fb765055629f472c0de484dcaf0acee2f7707266ae3583c/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8009897cdef112072f93a0efdce29cd819e717fd2f649ee3016efd3cd885a7ed", size = 50738, upload-time = "2025-10-06T05:37:30.792Z" }, + { url = "https://files.pythonhosted.org/packages/ba/7d/b7282a445956506fa11da8c2db7d276adcbf2b17d8bb8407a47685263f90/frozenlist-1.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2c5dcbbc55383e5883246d11fd179782a9d07a986c40f49abe89ddf865913930", size = 51739, upload-time = "2025-10-06T05:37:32.127Z" }, + { url = "https://files.pythonhosted.org/packages/62/1c/3d8622e60d0b767a5510d1d3cf21065b9db874696a51ea6d7a43180a259c/frozenlist-1.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:39ecbc32f1390387d2aa4f5a995e465e9e2f79ba3adcac92d68e3e0afae6657c", size = 284186, upload-time = "2025-10-06T05:37:33.21Z" }, + { url = "https://files.pythonhosted.org/packages/2d/14/aa36d5f85a89679a85a1d44cd7a6657e0b1c75f61e7cad987b203d2daca8/frozenlist-1.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92db2bf818d5cc8d9c1f1fc56b897662e24ea5adb36ad1f1d82875bd64e03c24", size = 292196, upload-time = "2025-10-06T05:37:36.107Z" }, + { url = "https://files.pythonhosted.org/packages/05/23/6bde59eb55abd407d34f77d39a5126fb7b4f109a3f611d3929f14b700c66/frozenlist-1.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dc43a022e555de94c3b68a4ef0b11c4f747d12c024a520c7101709a2144fb37", size = 273830, upload-time = "2025-10-06T05:37:37.663Z" }, + { url = "https://files.pythonhosted.org/packages/d2/3f/22cff331bfad7a8afa616289000ba793347fcd7bc275f3b28ecea2a27909/frozenlist-1.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb89a7f2de3602cfed448095bab3f178399646ab7c61454315089787df07733a", size = 294289, upload-time = "2025-10-06T05:37:39.261Z" }, + { url = "https://files.pythonhosted.org/packages/a4/89/5b057c799de4838b6c69aa82b79705f2027615e01be996d2486a69ca99c4/frozenlist-1.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:33139dc858c580ea50e7e60a1b0ea003efa1fd42e6ec7fdbad78fff65fad2fd2", size = 300318, upload-time = "2025-10-06T05:37:43.213Z" }, + { url = "https://files.pythonhosted.org/packages/30/de/2c22ab3eb2a8af6d69dc799e48455813bab3690c760de58e1bf43b36da3e/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:168c0969a329b416119507ba30b9ea13688fafffac1b7822802537569a1cb0ef", size = 282814, upload-time = "2025-10-06T05:37:45.337Z" }, + { url = "https://files.pythonhosted.org/packages/59/f7/970141a6a8dbd7f556d94977858cfb36fa9b66e0892c6dd780d2219d8cd8/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:28bd570e8e189d7f7b001966435f9dac6718324b5be2990ac496cf1ea9ddb7fe", size = 291762, upload-time = "2025-10-06T05:37:46.657Z" }, + { url = "https://files.pythonhosted.org/packages/c1/15/ca1adae83a719f82df9116d66f5bb28bb95557b3951903d39135620ef157/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b2a095d45c5d46e5e79ba1e5b9cb787f541a8dee0433836cea4b96a2c439dcd8", size = 289470, upload-time = "2025-10-06T05:37:47.946Z" }, + { url = "https://files.pythonhosted.org/packages/ac/83/dca6dc53bf657d371fbc88ddeb21b79891e747189c5de990b9dfff2ccba1/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:eab8145831a0d56ec9c4139b6c3e594c7a83c2c8be25d5bcf2d86136a532287a", size = 289042, upload-time = "2025-10-06T05:37:49.499Z" }, + { url = "https://files.pythonhosted.org/packages/96/52/abddd34ca99be142f354398700536c5bd315880ed0a213812bc491cff5e4/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:974b28cf63cc99dfb2188d8d222bc6843656188164848c4f679e63dae4b0708e", size = 283148, upload-time = "2025-10-06T05:37:50.745Z" }, + { url = "https://files.pythonhosted.org/packages/af/d3/76bd4ed4317e7119c2b7f57c3f6934aba26d277acc6309f873341640e21f/frozenlist-1.8.0-cp314-cp314t-win32.whl", hash = "sha256:342c97bf697ac5480c0a7ec73cd700ecfa5a8a40ac923bd035484616efecc2df", size = 44676, upload-time = "2025-10-06T05:37:52.222Z" }, + { url = "https://files.pythonhosted.org/packages/89/76/c615883b7b521ead2944bb3480398cbb07e12b7b4e4d073d3752eb721558/frozenlist-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:06be8f67f39c8b1dc671f5d83aaefd3358ae5cdcf8314552c57e7ed3e6475bdd", size = 49451, upload-time = "2025-10-06T05:37:53.425Z" }, + { url = "https://files.pythonhosted.org/packages/e0/a3/5982da14e113d07b325230f95060e2169f5311b1017ea8af2a29b374c289/frozenlist-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:102e6314ca4da683dca92e3b1355490fed5f313b768500084fbe6371fddfdb79", size = 42507, upload-time = "2025-10-06T05:37:54.513Z" }, + { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" }, +] + +[[package]] +name = "gitdb" +version = "4.0.12" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "smmap" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/94/63b0fc47eb32792c7ba1fe1b694daec9a63620db1e313033d18140c2320a/gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571", size = 394684, upload-time = "2025-01-02T07:20:46.413Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf", size = 62794, upload-time = "2025-01-02T07:20:43.624Z" }, +] + +[[package]] +name = "gitpython" +version = "3.1.45" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "gitdb" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9a/c8/dd58967d119baab745caec2f9d853297cec1989ec1d63f677d3880632b88/gitpython-3.1.45.tar.gz", hash = "sha256:85b0ee964ceddf211c41b9f27a49086010a190fd8132a24e21f362a4b36a791c", size = 215076, upload-time = "2025-07-24T03:45:54.871Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/01/61/d4b89fec821f72385526e1b9d9a3a0385dda4a72b206d28049e2c7cd39b8/gitpython-3.1.45-py3-none-any.whl", hash = "sha256:8908cb2e02fb3b93b7eb0f2827125cb699869470432cc885f019b8fd0fccff77", size = 208168, upload-time = "2025-07-24T03:45:52.517Z" }, +] + +[[package]] +name = "gunicorn" +version = "23.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/34/72/9614c465dc206155d93eff0ca20d42e1e35afc533971379482de953521a4/gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec", size = 375031, upload-time = "2024-08-10T20:25:27.378Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/7d/6dac2a6e1eba33ee43f318edbed4ff29151a49b5d37f080aad1e6469bca4/gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", size = 85029, upload-time = "2024-08-10T20:25:24.996Z" }, +] + +[[package]] +name = "humanize" +version = "4.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b6/43/50033d25ad96a7f3845f40999b4778f753c3901a11808a584fed7c00d9f5/humanize-4.14.0.tar.gz", hash = "sha256:2fa092705ea640d605c435b1ca82b2866a1b601cdf96f076d70b79a855eba90d", size = 82939, upload-time = "2025-10-15T13:04:51.214Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/5b/9512c5fb6c8218332b530f13500c6ff5f3ce3342f35e0dd7be9ac3856fd3/humanize-4.14.0-py3-none-any.whl", hash = "sha256:d57701248d040ad456092820e6fde56c930f17749956ac47f4f655c0c547bfff", size = 132092, upload-time = "2025-10-15T13:04:49.404Z" }, +] + +[[package]] +name = "idna" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, +] + +[[package]] +name = "itsdangerous" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410, upload-time = "2024-04-16T21:28:15.614Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234, upload-time = "2024-04-16T21:28:14.499Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, +] + +[[package]] +name = "marshmallow" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/2c/e40834adb0bb6f21d7372ad90e616eda82116d4f090d93c29ceb2366cdaf/marshmallow-4.1.0.tar.gz", hash = "sha256:daa9862f74e2f7864980d25c29b4ea72944cde48aa17537e3bd5797a4ae62d71", size = 220619, upload-time = "2025-11-01T15:40:37.096Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/df/081ea8c41696d598e7cea4f101e49da718a9b6c9dcaaad4e76dfc11a022c/marshmallow-4.1.0-py3-none-any.whl", hash = "sha256:9901660499be3b880dc92d6b5ee0b9a79e94265b7793f71021f92040c07129f1", size = 48286, upload-time = "2025-11-01T15:40:35.542Z" }, +] + +[[package]] +name = "multidict" +version = "6.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/80/1e/5492c365f222f907de1039b91f922b93fa4f764c713ee858d235495d8f50/multidict-6.7.0.tar.gz", hash = "sha256:c6e99d9a65ca282e578dfea819cfa9c0a62b2499d8677392e09feaf305e9e6f5", size = 101834, upload-time = "2025-10-06T14:52:30.657Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/86/33272a544eeb36d66e4d9a920602d1a2f57d4ebea4ef3cdfe5a912574c95/multidict-6.7.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bee7c0588aa0076ce77c0ea5d19a68d76ad81fcd9fe8501003b9a24f9d4000f6", size = 76135, upload-time = "2025-10-06T14:49:54.26Z" }, + { url = "https://files.pythonhosted.org/packages/91/1c/eb97db117a1ebe46d457a3d235a7b9d2e6dcab174f42d1b67663dd9e5371/multidict-6.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7ef6b61cad77091056ce0e7ce69814ef72afacb150b7ac6a3e9470def2198159", size = 45117, upload-time = "2025-10-06T14:49:55.82Z" }, + { url = "https://files.pythonhosted.org/packages/f1/d8/6c3442322e41fb1dd4de8bd67bfd11cd72352ac131f6368315617de752f1/multidict-6.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9c0359b1ec12b1d6849c59f9d319610b7f20ef990a6d454ab151aa0e3b9f78ca", size = 43472, upload-time = "2025-10-06T14:49:57.048Z" }, + { url = "https://files.pythonhosted.org/packages/75/3f/e2639e80325af0b6c6febdf8e57cc07043ff15f57fa1ef808f4ccb5ac4cd/multidict-6.7.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cd240939f71c64bd658f186330603aac1a9a81bf6273f523fca63673cb7378a8", size = 249342, upload-time = "2025-10-06T14:49:58.368Z" }, + { url = "https://files.pythonhosted.org/packages/5d/cc/84e0585f805cbeaa9cbdaa95f9a3d6aed745b9d25700623ac89a6ecff400/multidict-6.7.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a60a4d75718a5efa473ebd5ab685786ba0c67b8381f781d1be14da49f1a2dc60", size = 257082, upload-time = "2025-10-06T14:49:59.89Z" }, + { url = "https://files.pythonhosted.org/packages/b0/9c/ac851c107c92289acbbf5cfb485694084690c1b17e555f44952c26ddc5bd/multidict-6.7.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53a42d364f323275126aff81fb67c5ca1b7a04fda0546245730a55c8c5f24bc4", size = 240704, upload-time = "2025-10-06T14:50:01.485Z" }, + { url = "https://files.pythonhosted.org/packages/50/cc/5f93e99427248c09da95b62d64b25748a5f5c98c7c2ab09825a1d6af0e15/multidict-6.7.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3b29b980d0ddbecb736735ee5bef69bb2ddca56eff603c86f3f29a1128299b4f", size = 266355, upload-time = "2025-10-06T14:50:02.955Z" }, + { url = "https://files.pythonhosted.org/packages/ec/0c/2ec1d883ceb79c6f7f6d7ad90c919c898f5d1c6ea96d322751420211e072/multidict-6.7.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f8a93b1c0ed2d04b97a5e9336fd2d33371b9a6e29ab7dd6503d63407c20ffbaf", size = 267259, upload-time = "2025-10-06T14:50:04.446Z" }, + { url = "https://files.pythonhosted.org/packages/c6/2d/f0b184fa88d6630aa267680bdb8623fb69cb0d024b8c6f0d23f9a0f406d3/multidict-6.7.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ff96e8815eecacc6645da76c413eb3b3d34cfca256c70b16b286a687d013c32", size = 254903, upload-time = "2025-10-06T14:50:05.98Z" }, + { url = "https://files.pythonhosted.org/packages/06/c9/11ea263ad0df7dfabcad404feb3c0dd40b131bc7f232d5537f2fb1356951/multidict-6.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7516c579652f6a6be0e266aec0acd0db80829ca305c3d771ed898538804c2036", size = 252365, upload-time = "2025-10-06T14:50:07.511Z" }, + { url = "https://files.pythonhosted.org/packages/41/88/d714b86ee2c17d6e09850c70c9d310abac3d808ab49dfa16b43aba9d53fd/multidict-6.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:040f393368e63fb0f3330e70c26bfd336656bed925e5cbe17c9da839a6ab13ec", size = 250062, upload-time = "2025-10-06T14:50:09.074Z" }, + { url = "https://files.pythonhosted.org/packages/15/fe/ad407bb9e818c2b31383f6131ca19ea7e35ce93cf1310fce69f12e89de75/multidict-6.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b3bc26a951007b1057a1c543af845f1c7e3e71cc240ed1ace7bf4484aa99196e", size = 249683, upload-time = "2025-10-06T14:50:10.714Z" }, + { url = "https://files.pythonhosted.org/packages/8c/a4/a89abdb0229e533fb925e7c6e5c40201c2873efebc9abaf14046a4536ee6/multidict-6.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7b022717c748dd1992a83e219587aabe45980d88969f01b316e78683e6285f64", size = 261254, upload-time = "2025-10-06T14:50:12.28Z" }, + { url = "https://files.pythonhosted.org/packages/8d/aa/0e2b27bd88b40a4fb8dc53dd74eecac70edaa4c1dd0707eb2164da3675b3/multidict-6.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:9600082733859f00d79dee64effc7aef1beb26adb297416a4ad2116fd61374bd", size = 257967, upload-time = "2025-10-06T14:50:14.16Z" }, + { url = "https://files.pythonhosted.org/packages/d0/8e/0c67b7120d5d5f6d874ed85a085f9dc770a7f9d8813e80f44a9fec820bb7/multidict-6.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:94218fcec4d72bc61df51c198d098ce2b378e0ccbac41ddbed5ef44092913288", size = 250085, upload-time = "2025-10-06T14:50:15.639Z" }, + { url = "https://files.pythonhosted.org/packages/ba/55/b73e1d624ea4b8fd4dd07a3bb70f6e4c7c6c5d9d640a41c6ffe5cdbd2a55/multidict-6.7.0-cp313-cp313-win32.whl", hash = "sha256:a37bd74c3fa9d00be2d7b8eca074dc56bd8077ddd2917a839bd989612671ed17", size = 41713, upload-time = "2025-10-06T14:50:17.066Z" }, + { url = "https://files.pythonhosted.org/packages/32/31/75c59e7d3b4205075b4c183fa4ca398a2daf2303ddf616b04ae6ef55cffe/multidict-6.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:30d193c6cc6d559db42b6bcec8a5d395d34d60c9877a0b71ecd7c204fcf15390", size = 45915, upload-time = "2025-10-06T14:50:18.264Z" }, + { url = "https://files.pythonhosted.org/packages/31/2a/8987831e811f1184c22bc2e45844934385363ee61c0a2dcfa8f71b87e608/multidict-6.7.0-cp313-cp313-win_arm64.whl", hash = "sha256:ea3334cabe4d41b7ccd01e4d349828678794edbc2d3ae97fc162a3312095092e", size = 43077, upload-time = "2025-10-06T14:50:19.853Z" }, + { url = "https://files.pythonhosted.org/packages/e8/68/7b3a5170a382a340147337b300b9eb25a9ddb573bcdfff19c0fa3f31ffba/multidict-6.7.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:ad9ce259f50abd98a1ca0aa6e490b58c316a0fce0617f609723e40804add2c00", size = 83114, upload-time = "2025-10-06T14:50:21.223Z" }, + { url = "https://files.pythonhosted.org/packages/55/5c/3fa2d07c84df4e302060f555bbf539310980362236ad49f50eeb0a1c1eb9/multidict-6.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07f5594ac6d084cbb5de2df218d78baf55ef150b91f0ff8a21cc7a2e3a5a58eb", size = 48442, upload-time = "2025-10-06T14:50:22.871Z" }, + { url = "https://files.pythonhosted.org/packages/fc/56/67212d33239797f9bd91962bb899d72bb0f4c35a8652dcdb8ed049bef878/multidict-6.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0591b48acf279821a579282444814a2d8d0af624ae0bc600aa4d1b920b6e924b", size = 46885, upload-time = "2025-10-06T14:50:24.258Z" }, + { url = "https://files.pythonhosted.org/packages/46/d1/908f896224290350721597a61a69cd19b89ad8ee0ae1f38b3f5cd12ea2ac/multidict-6.7.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:749a72584761531d2b9467cfbdfd29487ee21124c304c4b6cb760d8777b27f9c", size = 242588, upload-time = "2025-10-06T14:50:25.716Z" }, + { url = "https://files.pythonhosted.org/packages/ab/67/8604288bbd68680eee0ab568fdcb56171d8b23a01bcd5cb0c8fedf6e5d99/multidict-6.7.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b4c3d199f953acd5b446bf7c0de1fe25d94e09e79086f8dc2f48a11a129cdf1", size = 249966, upload-time = "2025-10-06T14:50:28.192Z" }, + { url = "https://files.pythonhosted.org/packages/20/33/9228d76339f1ba51e3efef7da3ebd91964d3006217aae13211653193c3ff/multidict-6.7.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9fb0211dfc3b51efea2f349ec92c114d7754dd62c01f81c3e32b765b70c45c9b", size = 228618, upload-time = "2025-10-06T14:50:29.82Z" }, + { url = "https://files.pythonhosted.org/packages/f8/2d/25d9b566d10cab1c42b3b9e5b11ef79c9111eaf4463b8c257a3bd89e0ead/multidict-6.7.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a027ec240fe73a8d6281872690b988eed307cd7d91b23998ff35ff577ca688b5", size = 257539, upload-time = "2025-10-06T14:50:31.731Z" }, + { url = "https://files.pythonhosted.org/packages/b6/b1/8d1a965e6637fc33de3c0d8f414485c2b7e4af00f42cab3d84e7b955c222/multidict-6.7.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1d964afecdf3a8288789df2f5751dc0a8261138c3768d9af117ed384e538fad", size = 256345, upload-time = "2025-10-06T14:50:33.26Z" }, + { url = "https://files.pythonhosted.org/packages/ba/0c/06b5a8adbdeedada6f4fb8d8f193d44a347223b11939b42953eeb6530b6b/multidict-6.7.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:caf53b15b1b7df9fbd0709aa01409000a2b4dd03a5f6f5cc548183c7c8f8b63c", size = 247934, upload-time = "2025-10-06T14:50:34.808Z" }, + { url = "https://files.pythonhosted.org/packages/8f/31/b2491b5fe167ca044c6eb4b8f2c9f3b8a00b24c432c365358eadac5d7625/multidict-6.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:654030da3197d927f05a536a66186070e98765aa5142794c9904555d3a9d8fb5", size = 245243, upload-time = "2025-10-06T14:50:36.436Z" }, + { url = "https://files.pythonhosted.org/packages/61/1a/982913957cb90406c8c94f53001abd9eafc271cb3e70ff6371590bec478e/multidict-6.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:2090d3718829d1e484706a2f525e50c892237b2bf9b17a79b059cb98cddc2f10", size = 235878, upload-time = "2025-10-06T14:50:37.953Z" }, + { url = "https://files.pythonhosted.org/packages/be/c0/21435d804c1a1cf7a2608593f4d19bca5bcbd7a81a70b253fdd1c12af9c0/multidict-6.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2d2cfeec3f6f45651b3d408c4acec0ebf3daa9bc8a112a084206f5db5d05b754", size = 243452, upload-time = "2025-10-06T14:50:39.574Z" }, + { url = "https://files.pythonhosted.org/packages/54/0a/4349d540d4a883863191be6eb9a928846d4ec0ea007d3dcd36323bb058ac/multidict-6.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:4ef089f985b8c194d341eb2c24ae6e7408c9a0e2e5658699c92f497437d88c3c", size = 252312, upload-time = "2025-10-06T14:50:41.612Z" }, + { url = "https://files.pythonhosted.org/packages/26/64/d5416038dbda1488daf16b676e4dbfd9674dde10a0cc8f4fc2b502d8125d/multidict-6.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e93a0617cd16998784bf4414c7e40f17a35d2350e5c6f0bd900d3a8e02bd3762", size = 246935, upload-time = "2025-10-06T14:50:43.972Z" }, + { url = "https://files.pythonhosted.org/packages/9f/8c/8290c50d14e49f35e0bd4abc25e1bc7711149ca9588ab7d04f886cdf03d9/multidict-6.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f0feece2ef8ebc42ed9e2e8c78fc4aa3cf455733b507c09ef7406364c94376c6", size = 243385, upload-time = "2025-10-06T14:50:45.648Z" }, + { url = "https://files.pythonhosted.org/packages/ef/a0/f83ae75e42d694b3fbad3e047670e511c138be747bc713cf1b10d5096416/multidict-6.7.0-cp313-cp313t-win32.whl", hash = "sha256:19a1d55338ec1be74ef62440ca9e04a2f001a04d0cc49a4983dc320ff0f3212d", size = 47777, upload-time = "2025-10-06T14:50:47.154Z" }, + { url = "https://files.pythonhosted.org/packages/dc/80/9b174a92814a3830b7357307a792300f42c9e94664b01dee8e457551fa66/multidict-6.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3da4fb467498df97e986af166b12d01f05d2e04f978a9c1c680ea1988e0bc4b6", size = 53104, upload-time = "2025-10-06T14:50:48.851Z" }, + { url = "https://files.pythonhosted.org/packages/cc/28/04baeaf0428d95bb7a7bea0e691ba2f31394338ba424fb0679a9ed0f4c09/multidict-6.7.0-cp313-cp313t-win_arm64.whl", hash = "sha256:b4121773c49a0776461f4a904cdf6264c88e42218aaa8407e803ca8025872792", size = 45503, upload-time = "2025-10-06T14:50:50.16Z" }, + { url = "https://files.pythonhosted.org/packages/e2/b1/3da6934455dd4b261d4c72f897e3a5728eba81db59959f3a639245891baa/multidict-6.7.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3bab1e4aff7adaa34410f93b1f8e57c4b36b9af0426a76003f441ee1d3c7e842", size = 75128, upload-time = "2025-10-06T14:50:51.92Z" }, + { url = "https://files.pythonhosted.org/packages/14/2c/f069cab5b51d175a1a2cb4ccdf7a2c2dabd58aa5bd933fa036a8d15e2404/multidict-6.7.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b8512bac933afc3e45fb2b18da8e59b78d4f408399a960339598374d4ae3b56b", size = 44410, upload-time = "2025-10-06T14:50:53.275Z" }, + { url = "https://files.pythonhosted.org/packages/42/e2/64bb41266427af6642b6b128e8774ed84c11b80a90702c13ac0a86bb10cc/multidict-6.7.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:79dcf9e477bc65414ebfea98ffd013cb39552b5ecd62908752e0e413d6d06e38", size = 43205, upload-time = "2025-10-06T14:50:54.911Z" }, + { url = "https://files.pythonhosted.org/packages/02/68/6b086fef8a3f1a8541b9236c594f0c9245617c29841f2e0395d979485cde/multidict-6.7.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:31bae522710064b5cbeddaf2e9f32b1abab70ac6ac91d42572502299e9953128", size = 245084, upload-time = "2025-10-06T14:50:56.369Z" }, + { url = "https://files.pythonhosted.org/packages/15/ee/f524093232007cd7a75c1d132df70f235cfd590a7c9eaccd7ff422ef4ae8/multidict-6.7.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a0df7ff02397bb63e2fd22af2c87dfa39e8c7f12947bc524dbdc528282c7e34", size = 252667, upload-time = "2025-10-06T14:50:57.991Z" }, + { url = "https://files.pythonhosted.org/packages/02/a5/eeb3f43ab45878f1895118c3ef157a480db58ede3f248e29b5354139c2c9/multidict-6.7.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7a0222514e8e4c514660e182d5156a415c13ef0aabbd71682fc714e327b95e99", size = 233590, upload-time = "2025-10-06T14:50:59.589Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1e/76d02f8270b97269d7e3dbd45644b1785bda457b474315f8cf999525a193/multidict-6.7.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2397ab4daaf2698eb51a76721e98db21ce4f52339e535725de03ea962b5a3202", size = 264112, upload-time = "2025-10-06T14:51:01.183Z" }, + { url = "https://files.pythonhosted.org/packages/76/0b/c28a70ecb58963847c2a8efe334904cd254812b10e535aefb3bcce513918/multidict-6.7.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8891681594162635948a636c9fe0ff21746aeb3dd5463f6e25d9bea3a8a39ca1", size = 261194, upload-time = "2025-10-06T14:51:02.794Z" }, + { url = "https://files.pythonhosted.org/packages/b4/63/2ab26e4209773223159b83aa32721b4021ffb08102f8ac7d689c943fded1/multidict-6.7.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18706cc31dbf402a7945916dd5cddf160251b6dab8a2c5f3d6d5a55949f676b3", size = 248510, upload-time = "2025-10-06T14:51:04.724Z" }, + { url = "https://files.pythonhosted.org/packages/93/cd/06c1fa8282af1d1c46fd55c10a7930af652afdce43999501d4d68664170c/multidict-6.7.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f844a1bbf1d207dd311a56f383f7eda2d0e134921d45751842d8235e7778965d", size = 248395, upload-time = "2025-10-06T14:51:06.306Z" }, + { url = "https://files.pythonhosted.org/packages/99/ac/82cb419dd6b04ccf9e7e61befc00c77614fc8134362488b553402ecd55ce/multidict-6.7.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d4393e3581e84e5645506923816b9cc81f5609a778c7e7534054091acc64d1c6", size = 239520, upload-time = "2025-10-06T14:51:08.091Z" }, + { url = "https://files.pythonhosted.org/packages/fa/f3/a0f9bf09493421bd8716a362e0cd1d244f5a6550f5beffdd6b47e885b331/multidict-6.7.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:fbd18dc82d7bf274b37aa48d664534330af744e03bccf696d6f4c6042e7d19e7", size = 245479, upload-time = "2025-10-06T14:51:10.365Z" }, + { url = "https://files.pythonhosted.org/packages/8d/01/476d38fc73a212843f43c852b0eee266b6971f0e28329c2184a8df90c376/multidict-6.7.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:b6234e14f9314731ec45c42fc4554b88133ad53a09092cc48a88e771c125dadb", size = 258903, upload-time = "2025-10-06T14:51:12.466Z" }, + { url = "https://files.pythonhosted.org/packages/49/6d/23faeb0868adba613b817d0e69c5f15531b24d462af8012c4f6de4fa8dc3/multidict-6.7.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:08d4379f9744d8f78d98c8673c06e202ffa88296f009c71bbafe8a6bf847d01f", size = 252333, upload-time = "2025-10-06T14:51:14.48Z" }, + { url = "https://files.pythonhosted.org/packages/1e/cc/48d02ac22b30fa247f7dad82866e4b1015431092f4ba6ebc7e77596e0b18/multidict-6.7.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9fe04da3f79387f450fd0061d4dd2e45a72749d31bf634aecc9e27f24fdc4b3f", size = 243411, upload-time = "2025-10-06T14:51:16.072Z" }, + { url = "https://files.pythonhosted.org/packages/4a/03/29a8bf5a18abf1fe34535c88adbdfa88c9fb869b5a3b120692c64abe8284/multidict-6.7.0-cp314-cp314-win32.whl", hash = "sha256:fbafe31d191dfa7c4c51f7a6149c9fb7e914dcf9ffead27dcfd9f1ae382b3885", size = 40940, upload-time = "2025-10-06T14:51:17.544Z" }, + { url = "https://files.pythonhosted.org/packages/82/16/7ed27b680791b939de138f906d5cf2b4657b0d45ca6f5dd6236fdddafb1a/multidict-6.7.0-cp314-cp314-win_amd64.whl", hash = "sha256:2f67396ec0310764b9222a1728ced1ab638f61aadc6226f17a71dd9324f9a99c", size = 45087, upload-time = "2025-10-06T14:51:18.875Z" }, + { url = "https://files.pythonhosted.org/packages/cd/3c/e3e62eb35a1950292fe39315d3c89941e30a9d07d5d2df42965ab041da43/multidict-6.7.0-cp314-cp314-win_arm64.whl", hash = "sha256:ba672b26069957ee369cfa7fc180dde1fc6f176eaf1e6beaf61fbebbd3d9c000", size = 42368, upload-time = "2025-10-06T14:51:20.225Z" }, + { url = "https://files.pythonhosted.org/packages/8b/40/cd499bd0dbc5f1136726db3153042a735fffd0d77268e2ee20d5f33c010f/multidict-6.7.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:c1dcc7524066fa918c6a27d61444d4ee7900ec635779058571f70d042d86ed63", size = 82326, upload-time = "2025-10-06T14:51:21.588Z" }, + { url = "https://files.pythonhosted.org/packages/13/8a/18e031eca251c8df76daf0288e6790561806e439f5ce99a170b4af30676b/multidict-6.7.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:27e0b36c2d388dc7b6ced3406671b401e84ad7eb0656b8f3a2f46ed0ce483718", size = 48065, upload-time = "2025-10-06T14:51:22.93Z" }, + { url = "https://files.pythonhosted.org/packages/40/71/5e6701277470a87d234e433fb0a3a7deaf3bcd92566e421e7ae9776319de/multidict-6.7.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2a7baa46a22e77f0988e3b23d4ede5513ebec1929e34ee9495be535662c0dfe2", size = 46475, upload-time = "2025-10-06T14:51:24.352Z" }, + { url = "https://files.pythonhosted.org/packages/fe/6a/bab00cbab6d9cfb57afe1663318f72ec28289ea03fd4e8236bb78429893a/multidict-6.7.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7bf77f54997a9166a2f5675d1201520586439424c2511723a7312bdb4bcc034e", size = 239324, upload-time = "2025-10-06T14:51:25.822Z" }, + { url = "https://files.pythonhosted.org/packages/2a/5f/8de95f629fc22a7769ade8b41028e3e5a822c1f8904f618d175945a81ad3/multidict-6.7.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e011555abada53f1578d63389610ac8a5400fc70ce71156b0aa30d326f1a5064", size = 246877, upload-time = "2025-10-06T14:51:27.604Z" }, + { url = "https://files.pythonhosted.org/packages/23/b4/38881a960458f25b89e9f4a4fdcb02ac101cfa710190db6e5528841e67de/multidict-6.7.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:28b37063541b897fd6a318007373930a75ca6d6ac7c940dbe14731ffdd8d498e", size = 225824, upload-time = "2025-10-06T14:51:29.664Z" }, + { url = "https://files.pythonhosted.org/packages/1e/39/6566210c83f8a261575f18e7144736059f0c460b362e96e9cf797a24b8e7/multidict-6.7.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:05047ada7a2fde2631a0ed706f1fd68b169a681dfe5e4cf0f8e4cb6618bbc2cd", size = 253558, upload-time = "2025-10-06T14:51:31.684Z" }, + { url = "https://files.pythonhosted.org/packages/00/a3/67f183VOE_STREET_ID0f64c269f46e6c0319fa87ba68f0f64f2b8e7fd7c72b913a0b/multidict-6.7.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:716133f7d1d946a4e1b91b1756b23c088881e70ff180c24e864c26192ad7534a", size = 252339, upload-time = "2025-10-06T14:51:33.699Z" }, + { url = "https://files.pythonhosted.org/packages/c8/2a/1cb77266afee2458d82f50da41beba02159b1d6b1f7973afc9a1cad1499b/multidict-6.7.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d1bed1b467ef657f2a0ae62844a607909ef1c6889562de5e1d505f74457d0b96", size = 244895, upload-time = "2025-10-06T14:51:36.189Z" }, + { url = "https://files.pythonhosted.org/packages/dd/72/09fa7dd487f119b2eb9524946ddd36e2067c08510576d43ff68469563b3b/multidict-6.7.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ca43bdfa5d37bd6aee89d85e1d0831fb86e25541be7e9d376ead1b28974f8e5e", size = 241862, upload-time = "2025-10-06T14:51:41.291Z" }, + { url = "https://files.pythonhosted.org/packages/65/92/bc1f8bd0853d8669300f732c801974dfc3702c3eeadae2f60cef54dc69d7/multidict-6.7.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:44b546bd3eb645fd26fb949e43c02a25a2e632e2ca21a35e2e132c8105dc8599", size = 232376, upload-time = "2025-10-06T14:51:43.55Z" }, + { url = "https://files.pythonhosted.org/packages/09/86/ac39399e5cb9d0c2ac8ef6e10a768e4d3bc933ac808d49c41f9dc23337eb/multidict-6.7.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:a6ef16328011d3f468e7ebc326f24c1445f001ca1dec335b2f8e66bed3006394", size = 240272, upload-time = "2025-10-06T14:51:45.265Z" }, + { url = "https://files.pythonhosted.org/packages/3d/b6/fed5ac6b8563ec72df6cb1ea8dac6d17f0a4a1f65045f66b6d3bf1497c02/multidict-6.7.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:5aa873cbc8e593d361ae65c68f85faadd755c3295ea2c12040ee146802f23b38", size = 248774, upload-time = "2025-10-06T14:51:46.836Z" }, + { url = "https://files.pythonhosted.org/packages/6b/8d/b954d8c0dc132b68f760aefd45870978deec6818897389dace00fcde32ff/multidict-6.7.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:3d7b6ccce016e29df4b7ca819659f516f0bc7a4b3efa3bb2012ba06431b044f9", size = 242731, upload-time = "2025-10-06T14:51:48.541Z" }, + { url = "https://files.pythonhosted.org/packages/16/9d/a2dac7009125d3540c2f54e194829ea18ac53716c61b655d8ed300120b0f/multidict-6.7.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:171b73bd4ee683d307599b66793ac80981b06f069b62eea1c9e29c9241aa66b0", size = 240193, upload-time = "2025-10-06T14:51:50.355Z" }, + { url = "https://files.pythonhosted.org/packages/39/ca/c05f144128ea232ae2178b008d5011d4e2cea86e4ee8c85c2631b1b94802/multidict-6.7.0-cp314-cp314t-win32.whl", hash = "sha256:b2d7f80c4e1fd010b07cb26820aae86b7e73b681ee4889684fb8d2d4537aab13", size = 48023, upload-time = "2025-10-06T14:51:51.883Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8f/0a60e501584145588be1af5cc829265701ba3c35a64aec8e07cbb71d39bb/multidict-6.7.0-cp314-cp314t-win_amd64.whl", hash = "sha256:09929cab6fcb68122776d575e03c6cc64ee0b8fca48d17e135474b042ce515cd", size = 53507, upload-time = "2025-10-06T14:51:53.672Z" }, + { url = "https://files.pythonhosted.org/packages/7f/ae/3148b988a9c6239903e786eac19c889fab607c31d6efa7fb2147e5680f23/multidict-6.7.0-cp314-cp314t-win_arm64.whl", hash = "sha256:cc41db090ed742f32bd2d2c721861725e6109681eddf835d0a82bd3a5c382827", size = 44804, upload-time = "2025-10-06T14:51:55.415Z" }, + { url = "https://files.pythonhosted.org/packages/b7/da/7d22601b625e241d4f23ef1ebff8acfc60da633c9e7e7922e24d10f592b3/multidict-6.7.0-py3-none-any.whl", hash = "sha256:394fc5c42a333c9ffc3e421a4c85e08580d990e08b99f6bf35b4132114c5dcb3", size = 12317, upload-time = "2025-10-06T14:52:29.272Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "pillow" +version = "12.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/cace85a1b0c9775a9f8f5d5423c8261c858760e2466c79b2dd184638b056/pillow-12.0.0.tar.gz", hash = "sha256:87d4f8125c9988bfbed67af47dd7a953e2fc7b0cc1e7800ec6d2080d490bb353", size = 47008828, upload-time = "2025-10-15T18:24:14.008Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/f2/de993bb2d21b33a98d031ecf6a978e4b61da207bef02f7b43093774c480d/pillow-12.0.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:0869154a2d0546545cde61d1789a6524319fc1897d9ee31218eae7a60ccc5643", size = 4045493, upload-time = "2025-10-15T18:22:25.758Z" }, + { url = "https://files.pythonhosted.org/packages/0e/b6/bc8d0c4c9f6f111a783d045310945deb769b806d7574764234ffd50bc5ea/pillow-12.0.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:a7921c5a6d31b3d756ec980f2f47c0cfdbce0fc48c22a39347a895f41f4a6ea4", size = 4120461, upload-time = "2025-10-15T18:22:27.286Z" }, + { url = "https://files.pythonhosted.org/packages/5d/57/d60d343709366a353dc56adb4ee1e7d8a2cc34e3fbc22905f4167cfec119/pillow-12.0.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:1ee80a59f6ce048ae13cda1abf7fbd2a34ab9ee7d401c46be3ca685d1999a399", size = 3576912, upload-time = "2025-10-15T18:22:28.751Z" }, + { url = "https://files.pythonhosted.org/packages/a4/a4/a0a31467e3f83b94d37568294b01d22b43ae3c5d85f2811769b9c66389dd/pillow-12.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c50f36a62a22d350c96e49ad02d0da41dbd17ddc2e29750dbdba4323f85eb4a5", size = 5249132, upload-time = "2025-10-15T18:22:30.641Z" }, + { url = "https://files.pythonhosted.org/packages/83/06/48eab21dd561de2914242711434c0c0eb992ed08ff3f6107a5f44527f5e9/pillow-12.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5193fde9a5f23c331ea26d0cf171fbf67e3f247585f50c08b3e205c7aeb4589b", size = 4650099, upload-time = "2025-10-15T18:22:32.73Z" }, + { url = "https://files.pythonhosted.org/packages/fc/bd/69ed99fd46a8dba7c1887156d3572fe4484e3f031405fcc5a92e31c04035/pillow-12.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bde737cff1a975b70652b62d626f7785e0480918dece11e8fef3c0cf057351c3", size = 6230808, upload-time = "2025-10-15T18:22:34.337Z" }, + { url = "https://files.pythonhosted.org/packages/ea/94/8fad659bcdbf86ed70099cb60ae40be6acca434bbc8c4c0d4ef356d7e0de/pillow-12.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a6597ff2b61d121172f5844b53f21467f7082f5fb385a9a29c01414463f93b07", size = 8037804, upload-time = "2025-10-15T18:22:36.402Z" }, + { url = "https://files.pythonhosted.org/packages/20/39/c685d05c06deecfd4e2d1950e9a908aa2ca8bc4e6c3b12d93b9cafbd7837/pillow-12.0.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b817e7035ea7f6b942c13aa03bb554fc44fea70838ea21f8eb31c638326584e", size = 6345553, upload-time = "2025-10-15T18:22:38.066Z" }, + { url = "https://files.pythonhosted.org/packages/38/57/755dbd06530a27a5ed74f8cb0a7a44a21722ebf318edbe67ddbd7fb28f88/pillow-12.0.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f4f1231b7dec408e8670264ce63e9c71409d9583dd21d32c163e25213ee2a344", size = 7037729, upload-time = "2025-10-15T18:22:39.769Z" }, + { url = "https://files.pythonhosted.org/packages/ca/b6/7e94f4c41d238615674d06ed677c14883103dce1c52e4af16f000338cfd7/pillow-12.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6e51b71417049ad6ab14c49608b4a24d8fb3fe605e5dfabfe523b58064dc3d27", size = 6459789, upload-time = "2025-10-15T18:22:41.437Z" }, + { url = "https://files.pythonhosted.org/packages/9c/14/4448bb0b5e0f22dd865290536d20ec8a23b64e2d04280b89139f09a36bb6/pillow-12.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d120c38a42c234dc9a8c5de7ceaaf899cf33561956acb4941653f8bdc657aa79", size = 7130917, upload-time = "2025-10-15T18:22:43.152Z" }, + { url = "https://files.pythonhosted.org/packages/dd/ca/16c6926cc1c015845745d5c16c9358e24282f1e588237a4c36d2b30f182f/pillow-12.0.0-cp313-cp313-win32.whl", hash = "sha256:4cc6b3b2efff105c6a1656cfe59da4fdde2cda9af1c5e0b58529b24525d0a098", size = 6302391, upload-time = "2025-10-15T18:22:44.753Z" }, + { url = "https://files.pythonhosted.org/packages/6d/2a/dd43dcfd6dae9b6a49ee28a8eedb98c7d5ff2de94a5d834565164667b97b/pillow-12.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:4cf7fed4b4580601c4345ceb5d4cbf5a980d030fd5ad07c4d2ec589f95f09905", size = 7007477, upload-time = "2025-10-15T18:22:46.838Z" }, + { url = "https://files.pythonhosted.org/packages/77/f0/72ea067f4b5ae5ead653053212af05ce3705807906ba3f3e8f58ddf617e6/pillow-12.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:9f0b04c6b8584c2c193babcccc908b38ed29524b29dd464bc8801bf10d746a3a", size = 2435918, upload-time = "2025-10-15T18:22:48.399Z" }, + { url = "https://files.pythonhosted.org/packages/f5/5e/9046b423735c21f0487ea6cb5b10f89ea8f8dfbe32576fe052b5ba9d4e5b/pillow-12.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7fa22993bac7b77b78cae22bad1e2a987ddf0d9015c63358032f84a53f23cdc3", size = 5251406, upload-time = "2025-10-15T18:22:49.905Z" }, + { url = "https://files.pythonhosted.org/packages/12/66/982ceebcdb13c97270ef7a56c3969635b4ee7cd45227fa707c94719229c5/pillow-12.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f135c702ac42262573fe9714dfe99c944b4ba307af5eb507abef1667e2cbbced", size = 4653218, upload-time = "2025-10-15T18:22:51.587Z" }, + { url = "https://files.pythonhosted.org/packages/16/b3/81e625524688c31859450119bf12674619429cab3119eec0e30a7a1029cb/pillow-12.0.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c85de1136429c524e55cfa4e033b4a7940ac5c8ee4d9401cc2d1bf48154bbc7b", size = 6266564, upload-time = "2025-10-15T18:22:53.215Z" }, + { url = "https://files.pythonhosted.org/packages/98/59/dfb38f2a41240d2408096e1a76c671d0a105a4a8471b1871c6902719450c/pillow-12.0.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:38df9b4bfd3db902c9c2bd369bcacaf9d935b2fff73709429d95cc41554f7b3d", size = 8069260, upload-time = "2025-10-15T18:22:54.933Z" }, + { url = "https://files.pythonhosted.org/packages/dc/3d/378dbea5cd1874b94c312425ca77b0f47776c78e0df2df751b820c8c1d6c/pillow-12.0.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d87ef5795da03d742bf49439f9ca4d027cde49c82c5371ba52464aee266699a", size = 6379248, upload-time = "2025-10-15T18:22:56.605Z" }, + { url = "https://files.pythonhosted.org/packages/84/b0/d525ef47d71590f162VOE_STREET_ID327acec75ae58c721dc071b17d8d652ca494d8/pillow-12.0.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aff9e4d82d082ff9513bdd6acd4f5bd359f5b2c870907d2b0a9c5e10d40c88fe", size = 7066043, upload-time = "2025-10-15T18:22:58.53Z" }, + { url = "https://files.pythonhosted.org/packages/61/2c/aced60e9cf9d0cde341d54bf7932c9ffc33ddb4a1595798b3a5150c7ec4e/pillow-12.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:8d8ca2b210ada074d57fcee40c30446c9562e542fc46aedc19baf758a93532ee", size = 6490915, upload-time = "2025-10-15T18:23:00.582Z" }, + { url = "https://files.pythonhosted.org/packages/ef/26/69dcb9b91f4e59f8f34b2332a4a0a951b44f547c4ed39d3e4dcfcff48f89/pillow-12.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:99a7f72fb6249302aa62245680754862a44179b545ded638cf1fef59befb57ef", size = 7157998, upload-time = "2025-10-15T18:23:02.627Z" }, + { url = "https://files.pythonhosted.org/packages/61/2b/726235842220ca95fa441ddf55dd2382b52ab5b8d9c0596fe6b3f23dafe8/pillow-12.0.0-cp313-cp313t-win32.whl", hash = "sha256:4078242472387600b2ce8d93ade8899c12bf33fa89e55ec89fe126e9d6d5d9e9", size = 6306201, upload-time = "2025-10-15T18:23:04.709Z" }, + { url = "https://files.pythonhosted.org/packages/c0/3d/2afaf4e840b2df71344ababf2f8edd75a705ce500e5dc1e7227808312ae1/pillow-12.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2c54c1a783d6d60595d3514f0efe9b37c8808746a66920315bfd34a938d7994b", size = 7013165, upload-time = "2025-10-15T18:23:06.46Z" }, + { url = "https://files.pythonhosted.org/packages/6f/75/3fa09aa5cf6ed04bee3fa575798ddf1ce0bace8edb47249c798077a81f7f/pillow-12.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:26d9f7d2b604cd23aba3e9faf795787456ac25634d82cd060556998e39c6fa47", size = 2437834, upload-time = "2025-10-15T18:23:08.194Z" }, + { url = "https://files.pythonhosted.org/packages/54/2a/9a8c6ba2c2c07b71bec92cf63e03370ca5e5f5c5b119b742bcc0cde3f9c5/pillow-12.0.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:beeae3f27f62308f1ddbcfb0690bf44b10732f2ef43758f169d5e9303165d3f9", size = 4045531, upload-time = "2025-10-15T18:23:10.121Z" }, + { url = "https://files.pythonhosted.org/packages/84/54/836fdbf1bfb3d66a59f0189ff0b9f5f666cee09c6188309300df04ad71fa/pillow-12.0.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:d4827615da15cd59784ce39d3388275ec093ae3ee8d7f0c089b76fa87af756c2", size = 4120554, upload-time = "2025-10-15T18:23:12.14Z" }, + { url = "https://files.pythonhosted.org/packages/0d/cd/16aec9f0da4793e98e6b54778a5fbce4f375c6646fe662e80600b8797379/pillow-12.0.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:3e42edad50b6909089750e65c91aa09aaf1e0a71310d383f11321b27c224ed8a", size = 3576812, upload-time = "2025-10-15T18:23:13.962Z" }, + { url = "https://files.pythonhosted.org/packages/f6/b7/13957fda356dc46339298b351cae0d327704986337c3c69bb54628c88155/pillow-12.0.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e5d8efac84c9afcb40914ab49ba063d94f5dbdf5066db4482c66a992f47a3a3b", size = 5252689, upload-time = "2025-10-15T18:23:15.562Z" }, + { url = "https://files.pythonhosted.org/packages/fc/f5/eae31a306341d8f331f43edb2e9122c7661b975433de5e447939ae61c5da/pillow-12.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:266cd5f2b63ff316d5a1bba46268e603c9caf5606d44f38c2873c380950576ad", size = 4650186, upload-time = "2025-10-15T18:23:17.379Z" }, + { url = "https://files.pythonhosted.org/packages/86/62/2a88339aa40c4c77e79108facbd307d6091e2c0eb5b8d3cf4977cfca2fe6/pillow-12.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:58eea5ebe51504057dd95c5b77d21700b77615ab0243d8152793dc00eb4faf01", size = 6230308, upload-time = "2025-10-15T18:23:18.971Z" }, + { url = "https://files.pythonhosted.org/packages/c7/33/5425a8992bcb32d1cb9fa3dd39a89e613d09a22f2c8083b7bf43c455f760/pillow-12.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f13711b1a5ba512d647a0e4ba79280d3a9a045aaf7e0cc6fbe96b91d4cdf6b0c", size = 8039222, upload-time = "2025-10-15T18:23:20.909Z" }, + { url = "https://files.pythonhosted.org/packages/d8/61/3f5d3b35c5728f37953d3eec5b5f3e77111949523bd2dd7f31a851e50690/pillow-12.0.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6846bd2d116ff42cba6b646edf5bf61d37e5cbd256425fa089fee4ff5c07a99e", size = 6346657, upload-time = "2025-10-15T18:23:23.077Z" }, + { url = "https://files.pythonhosted.org/packages/3a/be/ee90a3d79271227e0f0a33c453531efd6ed14b2e708596ba5dd9be948da3/pillow-12.0.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c98fa880d695de164b4135a52fd2e9cd7b7c90a9d8ac5e9e443a24a95ef9248e", size = 7038482, upload-time = "2025-10-15T18:23:25.005Z" }, + { url = "https://files.pythonhosted.org/packages/44/34/a16b6a4d1ad727de390e9bd9f19f5f669e079e5826ec0f329010ddea492f/pillow-12.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa3ed2a29a9e9d2d488b4da81dcb54720ac3104a20bf0bd273f1e4648aff5af9", size = 6461416, upload-time = "2025-10-15T18:23:27.009Z" }, + { url = "https://files.pythonhosted.org/packages/b6/39/1aa5850d2ade7d7ba9f54e4e4c17077244ff7a2d9e25998c38a29749eb3f/pillow-12.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d034140032870024e6b9892c692fe2968493790dd57208b2c37e3fb35f6df3ab", size = 7131584, upload-time = "2025-10-15T18:23:29.752Z" }, + { url = "https://files.pythonhosted.org/packages/bf/db/4fae862f8fad0167073a7733973bfa955f47e2cac3dc3e3e6257d10fab4a/pillow-12.0.0-cp314-cp314-win32.whl", hash = "sha256:1b1b133e6e16105f524a8dec491e0586d072948ce15c9b914e41cdadd209052b", size = 6400621, upload-time = "2025-10-15T18:23:32.06Z" }, + { url = "https://files.pythonhosted.org/packages/2b/24/b350c31543fb0107ab2599464d7e28e6f856027aadda995022e695313d94/pillow-12.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:8dc232e39d409036af549c86f24aed8273a40ffa459981146829a324e0848b4b", size = 7142916, upload-time = "2025-10-15T18:23:34.71Z" }, + { url = "https://files.pythonhosted.org/packages/0f/9b/0ba5a6fd9351793996ef7487c4fdbde8d3f5f75dbedc093bb598648fddf0/pillow-12.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:d52610d51e265a51518692045e372a4c363056130d922a7351429ac9f27e70b0", size = 2523836, upload-time = "2025-10-15T18:23:36.967Z" }, + { url = "https://files.pythonhosted.org/packages/f5/7a/ceee0840aebc579af529b523d530840338ecf63992395842e54edc805987/pillow-12.0.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1979f4566bb96c1e50a62d9831e2ea2d1211761e5662afc545fa766f996632f6", size = 5255092, upload-time = "2025-10-15T18:23:38.573Z" }, + { url = "https://files.pythonhosted.org/packages/44/76/20776057b4bfd1aef4eeca992ebde0f53a4dce874f3ae693d0ec90a4f79b/pillow-12.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b2e4b27a6e15b04832fe9bf292b94b5ca156016bbc1ea9c2c20098a0320d6cf6", size = 4653158, upload-time = "2025-10-15T18:23:40.238Z" }, + { url = "https://files.pythonhosted.org/packages/82/3f/d9ff92ace07be8836b4e7e87e6a4c7a8318d47c2f1463ffcf121fc57d9cb/pillow-12.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fb3096c30df99fd01c7bf8e544f392103d0795b9f98ba71a8054bcbf56b255f1", size = 6267882, upload-time = "2025-10-15T18:23:42.434Z" }, + { url = "https://files.pythonhosted.org/packages/9f/7a/4f7ff87f00d3ad33ba21af78bfcd2f032107710baf8280e3722ceec28cda/pillow-12.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7438839e9e053ef79f7112c881cef684013855016f928b168b81ed5835f3e75e", size = 8071001, upload-time = "2025-10-15T18:23:44.29Z" }, + { url = "https://files.pythonhosted.org/packages/75/87/fcea108944a52dad8cca0715ae6247e271eb80459364a98518f1e4f480c1/pillow-12.0.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d5c411a8eaa2299322b647cd932586b1427367fd3184ffbb8f7a219ea2041ca", size = 6380146, upload-time = "2025-10-15T18:23:46.065Z" }, + { url = "https://files.pythonhosted.org/packages/91/52/0d31b5e571ef5fd111d2978b84603fce26aba1b6092f28e941cb46570745/pillow-12.0.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7e091d464ac59d2c7ad8e7e08105eaf9dafbc3883fd7265ffccc2baad6ac925", size = 7067344, upload-time = "2025-10-15T18:23:47.898Z" }, + { url = "https://files.pythonhosted.org/packages/7b/f4/2dd3d721f875f928d48e83bb30a434dee75a2531bca839bb996bb0aa5a91/pillow-12.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:792a2c0be4dcc18af9d4a2dfd8a11a17d5e25274a1062b0ec1c2d79c76f3e7f8", size = 6491864, upload-time = "2025-10-15T18:23:49.607Z" }, + { url = "https://files.pythonhosted.org/packages/30/4b/667dfcf3d61fc309ba5a15b141845cece5915e39b99c1ceab0f34bf1d124/pillow-12.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:afbefa430092f71a9593a99ab6a4e7538bc9eabbf7bf94f9VOE_STREET_IDd3503943edc4", size = 7158911, upload-time = "2025-10-15T18:23:51.351Z" }, + { url = "https://files.pythonhosted.org/packages/a2/2f/16cabcc6426c32218ace36bf0d55955e813f2958afddbf1d391849fee9d1/pillow-12.0.0-cp314-cp314t-win32.whl", hash = "sha256:3830c769decf88f1289680a59d4f4c46c72573446352e2befec9a8512104fa52", size = 6408045, upload-time = "2025-10-15T18:23:53.177Z" }, + { url = "https://files.pythonhosted.org/packages/35/73/e29aa0c9c666cf787628d3f0dcf379f4791fba79f4936d02f8b37165bdf8/pillow-12.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:905b0365b210c73afb0ebe9101a32572152dfd1c144c7e28968a331b9217b94a", size = 7148282, upload-time = "2025-10-15T18:23:55.316Z" }, + { url = "https://files.pythonhosted.org/packages/c1/70/6b41bdcddf541b437bbb9f47f94d2db5d9ddef6c37ccab8c9107743748a4/pillow-12.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:99353a06902c2e43b43e8ff74ee65a7d90307d82370604746738a1e0661ccca7", size = 2525630, upload-time = "2025-10-15T18:23:57.149Z" }, +] + +[[package]] +name = "propcache" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9e/da/e9fc233cf63743258bff22b3dfa7ea5baef7b5bc324af47a0ad89b8ffc6f/propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d", size = 46442, upload-time = "2025-10-08T19:49:02.291Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/df/6d9c1b6ac12b003837dde8a10231a7344512186e87b36e855bef32241942/propcache-0.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:43eedf29202c08550aac1d14e0ee619b0430aaef78f85864c1a892294fbc28cf", size = 77750, upload-time = "2025-10-08T19:47:07.648Z" }, + { url = "https://files.pythonhosted.org/packages/8b/e8/677a0025e8a2acf07d3418a2e7ba529c9c33caf09d3c1f25513023c1db56/propcache-0.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d62cdfcfd89ccb8de04e0eda998535c406bf5e060ffd56be6c586cbcc05b3311", size = 44780, upload-time = "2025-10-08T19:47:08.851Z" }, + { url = "https://files.pythonhosted.org/packages/89/a4/92380f7ca60f99ebae761936bc48a72a639e8a47b29050615eef757cb2a7/propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cae65ad55793da34db5f54e4029b89d3b9b9490d8abe1b4c7ab5d4b8ec7ebf74", size = 46308, upload-time = "2025-10-08T19:47:09.982Z" }, + { url = "https://files.pythonhosted.org/packages/2d/48/c5ac64dee5262044348d1d78a5f85dd1a57464a60d30daee946699963eb3/propcache-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:333ddb9031d2704a301ee3e506dc46b1fe5f294ec198ed6435ad5b6a085facfe", size = 208182, upload-time = "2025-10-08T19:47:11.319Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0c/cd762dd011a9287389a6a3eb43aa30207bde253610cca06824aeabfe9653/propcache-0.4.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fd0858c20f078a32cf55f7e81473d96dcf3b93fd2ccdb3d40fdf54b8573df3af", size = 211215, upload-time = "2025-10-08T19:47:13.146Z" }, + { url = "https://files.pythonhosted.org/packages/30/3e/49861e90233ba36890ae0ca4c660e95df565b2cd15d4a68556ab5865974e/propcache-0.4.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:678ae89ebc632c5c204c794f8dab2837c5f159aeb59e6ed0539500400577298c", size = 218112, upload-time = "2025-10-08T19:47:14.913Z" }, + { url = "https://files.pythonhosted.org/packages/f1/8b/544bc867e24e1bd48f3118cecd3b05c694e160a168478fa28770f22fd094/propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d472aeb4fbf9865e0c6d622d7f4d54a4e101a89715d8904282bb5f9a2f476c3f", size = 204442, upload-time = "2025-10-08T19:47:16.277Z" }, + { url = "https://files.pythonhosted.org/packages/50/a6/4282772fd016a76d3e5c0df58380a5ea64900afd836cec2c2f662d1b9bb3/propcache-0.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4d3df5fa7e36b3225954fba85589da77a0fe6a53e3976de39caf04a0db4c36f1", size = 199398, upload-time = "2025-10-08T19:47:17.962Z" }, + { url = "https://files.pythonhosted.org/packages/3e/ec/d8a7cd406ee1ddb705db2139f8a10a8a427100347bd698e7014351c7af09/propcache-0.4.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ee17f18d2498f2673e432faaa71698032b0127ebf23ae5974eeaf806c279df24", size = 196920, upload-time = "2025-10-08T19:47:19.355Z" }, + { url = "https://files.pythonhosted.org/packages/f6/6c/f38ab64af3764f431e359f8baf9e0a21013e24329e8b85d2da32e8ed07ca/propcache-0.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:580e97762b950f993ae618e167e7be9256b8353c2dcd8b99ec100eb50f5286aa", size = 203748, upload-time = "2025-10-08T19:47:21.338Z" }, + { url = "https://files.pythonhosted.org/packages/d6/e3/fa846bd70f6534d647886621388f0a265254d30e3ce47e5c8e6e27dbf153/propcache-0.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:501d20b891688eb8e7aa903021f0b72d5a55db40ffaab27edefd1027caaafa61", size = 205877, upload-time = "2025-10-08T19:47:23.059Z" }, + { url = "https://files.pythonhosted.org/packages/e2/39/8163fc6f3133fea7b5f2827e8eba2029a0277ab2c5beee6c1db7b10fc23d/propcache-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a0bd56e5b100aef69bd8562b74b46254e7c8812918d3baa700c8a8009b0af66", size = 199437, upload-time = "2025-10-08T19:47:24.445Z" }, + { url = "https://files.pythonhosted.org/packages/93/89/caa9089970ca49c7c01662bd0eeedfe85494e863e8043565aeb6472ce8fe/propcache-0.4.1-cp313-cp313-win32.whl", hash = "sha256:bcc9aaa5d80322bc2fb24bb7accb4a30f81e90ab8d6ba187aec0744bc302ad81", size = 37586, upload-time = "2025-10-08T19:47:25.736Z" }, + { url = "https://files.pythonhosted.org/packages/f5/ab/f76ec3c3627c883215b5c8080debb4394ef5a7a29be811f786415fc1e6fd/propcache-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:381914df18634f5494334d201e98245c0596067504b9372d8cf93f4bb23e025e", size = 40790, upload-time = "2025-10-08T19:47:26.847Z" }, + { url = "https://files.pythonhosted.org/packages/59/1b/e71ae98235f8e2ba5004d8cb19765a74877abf189bc53fc0c80d799e56c3/propcache-0.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:8873eb4460fd55333ea49b7d189749ecf6e55bf85080f11b1c4530ed3034cba1", size = 37158, upload-time = "2025-10-08T19:47:27.961Z" }, + { url = "https://files.pythonhosted.org/packages/83/ce/a31bbdfc24ee0dcbba458c8175ed26089cf109a55bbe7b7640ed2470cfe9/propcache-0.4.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:92d1935ee1f8d7442da9c0c4fa7ac20d07e94064184811b685f5c4fada64553b", size = 81451, upload-time = "2025-10-08T19:47:29.445Z" }, + { url = "https://files.pythonhosted.org/packages/25/9c/442a45a470a68456e710d96cacd3573ef26a1d0a60067e6a7d5e655621ed/propcache-0.4.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:473c61b39e1460d386479b9b2f337da492042447c9b685f28be4f74d3529e566", size = 46374, upload-time = "2025-10-08T19:47:30.579Z" }, + { url = "https://files.pythonhosted.org/packages/f4/bf/b1d5e21dbc3b2e889ea4327044fb16312a736d97640fb8b6aa3f9c7b3b65/propcache-0.4.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c0ef0aaafc66fbd87842a3fe3902fd889825646bc21149eafe47be6072725835", size = 48396, upload-time = "2025-10-08T19:47:31.79Z" }, + { url = "https://files.pythonhosted.org/packages/f4/04/5b4c54a103d480e978d3c8a76073502b18db0c4bc17ab91b3cb5092ad949/propcache-0.4.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95393b4d66bfae908c3ca8d169d5f79cd65636ae15b5e7a4f6e67af675adb0e", size = 275950, upload-time = "2025-10-08T19:47:33.481Z" }, + { url = "https://files.pythonhosted.org/packages/b4/c1/86f846827fb969c4b78b0af79bba1d1ea2156492e1b83dea8b8a6ae27395/propcache-0.4.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c07fda85708bc48578467e85099645167a955ba093be0a2dcba962195676e859", size = 273856, upload-time = "2025-10-08T19:47:34.906Z" }, + { url = "https://files.pythonhosted.org/packages/36/1d/fc272a63c8d3bbad6878c336c7a7dea15e8f2d23a544bda43205dfa83ada/propcache-0.4.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:af223b406d6d000830c6f65f1e6431783fc3f713ba3e6cc8c024d5ee96170a4b", size = 280420, upload-time = "2025-10-08T19:47:36.338Z" }, + { url = "https://files.pythonhosted.org/packages/07/0c/01f2219d39f7e53d52e5173bcb09c976609ba30209912a0680adfb8c593a/propcache-0.4.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a78372c932c90ee474559c5ddfffd718238e8673c340dc21fe45c5b8b54559a0", size = 263254, upload-time = "2025-10-08T19:47:37.692Z" }, + { url = "https://files.pythonhosted.org/packages/2d/18/cd28081658ce597898f0c4d174d4d0f3c5b6d4dc27ffafeef835c95eb359/propcache-0.4.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:564d9f0d4d9509e1a870c920a89b2fec951b44bf5ba7d537a9e7c1ccec2c18af", size = 261205, upload-time = "2025-10-08T19:47:39.659Z" }, + { url = "https://files.pythonhosted.org/packages/7a/71/1f9e22eb8b8316701c2a19fa1f388c8a3185082607da8e406a803c9b954e/propcache-0.4.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:17612831fda0138059cc5546f4d12a2aacfb9e47068c06af35c400ba58ba7393", size = 247873, upload-time = "2025-10-08T19:47:41.084Z" }, + { url = "https://files.pythonhosted.org/packages/4a/65/3d4b61f36af2b4eddba9def857959f1016a51066b4f1ce348e0cf7881f58/propcache-0.4.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:41a89040cb10bd345b3c1a873b2bf36413d48da1def52f268a055f7398514874", size = 262739, upload-time = "2025-10-08T19:47:42.51Z" }, + { url = "https://files.pythonhosted.org/packages/2a/42/26746ab087faa77c1c68079b228810436ccd9a5ce9ac85e2b7307195fd06/propcache-0.4.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e35b88984e7fa64aacecea39236cee32dd9bd8c55f57ba8a75cf2399553f9bd7", size = 263514, upload-time = "2025-10-08T19:47:43.927Z" }, + { url = "https://files.pythonhosted.org/packages/94/13/630690fe201f5502d2403dd3cfd451ed8858fe3c738ee88d095ad2ff407b/propcache-0.4.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f8b465489f927b0df505cbe26ffbeed4d6d8a2bbc61ce90eb074ff129ef0ab1", size = 257781, upload-time = "2025-10-08T19:47:45.448Z" }, + { url = "https://files.pythonhosted.org/packages/92/f7/1d4ec5841505f423469efbfc381d64b7b467438cd5a4bbcbb063f3b73d27/propcache-0.4.1-cp313-cp313t-win32.whl", hash = "sha256:2ad890caa1d928c7c2965b48f3a3815c853180831d0e5503d35cf00c472f4717", size = 41396, upload-time = "2025-10-08T19:47:47.202Z" }, + { url = "https://files.pythonhosted.org/packages/48/f0/615c30622316496d2cbbc29f5985f7777d3ada70f23370608c1d3e081c1f/propcache-0.4.1-cp313-cp313t-win_amd64.whl", hash = "sha256:f7ee0e597f495cf415bcbd3da3caa3bd7e816b74d0d52b8145954c5e6fd3ff37", size = 44897, upload-time = "2025-10-08T19:47:48.336Z" }, + { url = "https://files.pythonhosted.org/packages/fd/ca/6002e46eccbe0e33dcd4069ef32f7f1c9e243736e07adca37ae8c4830ec3/propcache-0.4.1-cp313-cp313t-win_arm64.whl", hash = "sha256:929d7cbe1f01bb7baffb33dc14eb5691c95831450a26354cd210a8155170c93a", size = 39789, upload-time = "2025-10-08T19:47:49.876Z" }, + { url = "https://files.pythonhosted.org/packages/8e/5c/bca52d654a896f831b8256683457ceddd490ec18d9ec50e97dfd8fc726a8/propcache-0.4.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3f7124c9d820ba5548d431afb4632301acf965db49e666aa21c305cbe8c6de12", size = 78152, upload-time = "2025-10-08T19:47:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/65/9b/03b04e7d82a5f54fb16113d839f5ea1ede58a61e90edf515f6577c66fa8f/propcache-0.4.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c0d4b719b7da33599dfe3b22d3db1ef789210a0597bc650b7cee9c77c2be8c5c", size = 44869, upload-time = "2025-10-08T19:47:52.594Z" }, + { url = "https://files.pythonhosted.org/packages/b2/fa/89a8ef0468d5833a23fff277b143d0573897cf75bd56670a6d28126c7d68/propcache-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f302f4783709a78240ebc311b793f123328716a60911d667e0c036bc5dcbded", size = 46596, upload-time = "2025-10-08T19:47:54.073Z" }, + { url = "https://files.pythonhosted.org/packages/86/bd/47816020d337f4a746edc42fe8d53669965138f39ee117414c7d7a340cfe/propcache-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c80ee5802e3fb9ea37938e7eecc307fb984837091d5fd262bb37238b1ae97641", size = 206981, upload-time = "2025-10-08T19:47:55.715Z" }, + { url = "https://files.pythonhosted.org/packages/df/f6/c5fa1357cc9748510ee55f37173eb31bfde6d94e98ccd9e6f033f2fc06e1/propcache-0.4.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ed5a841e8bb29a55fb8159ed526b26adc5bdd7e8bd7bf793ce647cb08656cdf4", size = 211490, upload-time = "2025-10-08T19:47:57.499Z" }, + { url = "https://files.pythonhosted.org/packages/80/1e/e5889652a7c4a3846683401a48f0f2e5083ce0ec1a8a5221d8058fbd1adf/propcache-0.4.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:55c72fd6ea2da4c318e74ffdf93c4fe4e926051133657459131a95c846d16d44", size = 215371, upload-time = "2025-10-08T19:47:59.317Z" }, + { url = "https://files.pythonhosted.org/packages/b2/f2/889ad4b2408f72fe1a4f6a19491177b30ea7bf1a0fd5f17050ca08cfc882/propcache-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8326e144341460402713f91df60ade3c999d601e7eb5ff8f6f7862d54de0610d", size = 201424, upload-time = "2025-10-08T19:48:00.67Z" }, + { url = "https://files.pythonhosted.org/packages/27/73/033d63069b57b0812c8bd19f311faebeceb6ba31b8f32b73432d12a0b826/propcache-0.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:060b16ae65bc098da7f6d25bf359f1f31f688384858204fe5d652979e0015e5b", size = 197566, upload-time = "2025-10-08T19:48:02.604Z" }, + { url = "https://files.pythonhosted.org/packages/dc/89/ce24f3dc182630b4e07aa6d15f0ff4b14ed4b9955fae95a0b54c58d66c05/propcache-0.4.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:89eb3fa9524f7bec9de6e83cf3faed9d79bffa560672c118a96a171a6f55831e", size = 193130, upload-time = "2025-10-08T19:48:04.499Z" }, + { url = "https://files.pythonhosted.org/packages/a9/24/ef0d5fd1a811fb5c609278d0209c9f10c35f20581fcc16f818da959fc5b4/propcache-0.4.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:dee69d7015dc235f526fe80a9c90d65eb0039103fe565776250881731f06349f", size = 202625, upload-time = "2025-10-08T19:48:06.213Z" }, + { url = "https://files.pythonhosted.org/packages/f5/02/98ec20ff5546f68d673df2f7a69e8c0d076b5abd05ca882dc7ee3a83653d/propcache-0.4.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5558992a00dfd54ccbc64a32726a3357ec93825a418a401f5cc67df0ac5d9e49", size = 204209, upload-time = "2025-10-08T19:48:08.432Z" }, + { url = "https://files.pythonhosted.org/packages/a0/87/492694f76759b15f0467a2a93ab68d32859672b646aa8a04ce4864e7932d/propcache-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c9b822a577f560fbd9554812526831712c1436d2c046cedee4c3796d3543b144", size = 197797, upload-time = "2025-10-08T19:48:09.968Z" }, + { url = "https://files.pythonhosted.org/packages/ee/36/66367de3575db1d2d3f3d177432bd14ee577a39d3f5d1b3d5df8afe3b6e2/propcache-0.4.1-cp314-cp314-win32.whl", hash = "sha256:ab4c29b49d560fe48b696cdcb127dd36e0bc2472548f3bf56cc5cb3da2b2984f", size = 38140, upload-time = "2025-10-08T19:48:11.232Z" }, + { url = "https://files.pythonhosted.org/packages/0c/2a/a758b47de253636e1b8aef181c0b4f4f204bf0dd964914fb2af90a95b49b/propcache-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:5a103c3eb905fcea0ab98be99c3a9a5ab2de60228aa5aceedc614c0281cf6153", size = 41257, upload-time = "2025-10-08T19:48:12.707Z" }, + { url = "https://files.pythonhosted.org/packages/34/5e/63bd5896c3fec12edcbd6f12508d4890d23c265df28c74b175e1ef9f4f3b/propcache-0.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:74c1fb26515153e482e00177a1ad654721bf9207da8a494a0c05e797ad27b992", size = 38097, upload-time = "2025-10-08T19:48:13.923Z" }, + { url = "https://files.pythonhosted.org/packages/99/85/9ff785d787ccf9bbb3f3106f79884a130951436f58392000231b4c737c80/propcache-0.4.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:824e908bce90fb2743bd6b59db36eb4f45cd350a39637c9f73b1c1ea66f5b75f", size = 81455, upload-time = "2025-10-08T19:48:15.16Z" }, + { url = "https://files.pythonhosted.org/packages/90/85/2431c10c8e7ddb1445c1f7c4b54d886e8ad20e3c6307e7218f05922cad67/propcache-0.4.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2b5e7db5328427c57c8e8831abda175421b709672f6cfc3d630c3b7e2146393", size = 46372, upload-time = "2025-10-08T19:48:16.424Z" }, + { url = "https://files.pythonhosted.org/packages/01/20/b0972d902472da9bcb683fa595099911f4d2e86e5683bcc45de60dd05dc3/propcache-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6f6ff873ed40292cd4969ef5310179afd5db59fdf055897e282485043fc80ad0", size = 48411, upload-time = "2025-10-08T19:48:17.577Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e3/7dc89f4f21e8f99bad3d5ddb3a3389afcf9da4ac69e3deb2dcdc96e74169/propcache-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49a2dc67c154db2c1463013594c458881a069fcf98940e61a0569016a583020a", size = 275712, upload-time = "2025-10-08T19:48:18.901Z" }, + { url = "https://files.pythonhosted.org/packages/20/67/89800c8352489b21a8047c773067644e3897f02ecbbd610f4d46b7f08612/propcache-0.4.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:005f08e6a0529984491e37d8dbc3dd86f84bd78a8ceb5fa9a021f4c48d4984be", size = 273557, upload-time = "2025-10-08T19:48:20.762Z" }, + { url = "https://files.pythonhosted.org/packages/e2/a1/b52b055c766a54ce6d9c16d9aca0cad8059acd9637cdf8aa0222f4a026ef/propcache-0.4.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5c3310452e0d31390da9035c348633b43d7e7feb2e37be252be6da45abd1abcc", size = 280015, upload-time = "2025-10-08T19:48:22.592Z" }, + { url = "https://files.pythonhosted.org/packages/48/c8/33cee30bd890672c63743049f3c9e4be087e6780906bfc3ec58528be59c1/propcache-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c3c70630930447f9ef1caac7728c8ad1c56bc5015338b20fed0d08ea2480b3a", size = 262880, upload-time = "2025-10-08T19:48:23.947Z" }, + { url = "https://files.pythonhosted.org/packages/0c/b1/8f08a143b204b418285c88b83d00edbd61afbc2c6415ffafc8905da7038b/propcache-0.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e57061305815dfc910a3634dcf584f08168a8836e6999983569f51a8544cd89", size = 260938, upload-time = "2025-10-08T19:48:25.656Z" }, + { url = "https://files.pythonhosted.org/packages/cf/12/96e4664c82ca2f31e1c8dff86afb867348979eb78d3cb8546a680287a1e9/propcache-0.4.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:521a463429ef54143092c11a77e04056dd00636f72e8c45b70aaa3140d639726", size = 247641, upload-time = "2025-10-08T19:48:27.207Z" }, + { url = "https://files.pythonhosted.org/packages/18/ed/e7a9cfca28133386ba52278136d42209d3125db08d0a6395f0cba0c0285c/propcache-0.4.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:120c964da3fdc75e3731aa392527136d4ad35868cc556fd09bb6d09172d9a367", size = 262510, upload-time = "2025-10-08T19:48:28.65Z" }, + { url = "https://files.pythonhosted.org/packages/f5/76/16d8bf65e8845dd62b4e2b57444ab81f07f40caa5652b8969b87ddcf2ef6/propcache-0.4.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:d8f353eb14ee3441ee844ade4277d560cdd68288838673273b978e3d6d2c8f36", size = 263161, upload-time = "2025-10-08T19:48:30.133Z" }, + { url = "https://files.pythonhosted.org/packages/e7/70/c99e9edb5d91d5ad8a49fa3c1e8285ba64f1476782fed10ab251ff413ba1/propcache-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ab2943be7c652f09638800905ee1bab2c544e537edb57d527997a24c13dc1455", size = 257393, upload-time = "2025-10-08T19:48:31.567Z" }, + { url = "https://files.pythonhosted.org/packages/08/02/87b25304249a35c0915d236575bc3574a323f60b47939a2262b77632a3ee/propcache-0.4.1-cp314-cp314t-win32.whl", hash = "sha256:05674a162469f31358c30bcaa8883cb7829fa3110bf9c0991fe27d7896c42d85", size = 42546, upload-time = "2025-10-08T19:48:32.872Z" }, + { url = "https://files.pythonhosted.org/packages/cb/ef/3c6ecf8b317aa982f309835e8f96987466123c6e596646d4e6a1dfcd080f/propcache-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:990f6b3e2a27d683cb7602ed6c86f15ee6b43b1194736f9baaeb93d0016633b1", size = 46259, upload-time = "2025-10-08T19:48:34.226Z" }, + { url = "https://files.pythonhosted.org/packages/c4/2d/346e946d4951f37eca1e4f55be0f0174c52cd70720f84029b02f296f4a38/propcache-0.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ecef2343af4cc68e05131e45024ba34f6095821988a9d0a02aa7c73fcc448aa9", size = 40428, upload-time = "2025-10-08T19:48:35.441Z" }, + { url = "https://files.pythonhosted.org/packages/5b/5a/bc7b4a4ef808fa59a816c17b20c4bef6884daebbdf627ff2a161da67da19/propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237", size = 13305, upload-time = "2025-10-08T19:49:00.792Z" }, +] + +[[package]] +name = "psutil" +version = "7.1.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e1/88/bdd0a41e5857d5d703287598cbf08dad90aed56774ea52ae071bae9071b6/psutil-7.1.3.tar.gz", hash = "sha256:6c86281738d77335af7aec228328e944b30930899ea760ecf33a4dba66be5e74", size = 489059, upload-time = "2025-11-02T12:25:54.619Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/93/0c49e776b8734fef56ec9c5c57f923922f2cf0497d62e0f419465f28f3d0/psutil-7.1.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0005da714eee687b4b8decd3d6cc7c6db36215c9e74e5ad2264b90c3df7d92dc", size = 239751, upload-time = "2025-11-02T12:25:58.161Z" }, + { url = "https://files.pythonhosted.org/packages/6f/8d/b31e39c769e70780f007969815195a55c81a63efebdd4dbe9e7a113adb2f/psutil-7.1.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:19644c85dcb987e35eeeaefdc3915d059dac7bd1167cdcdbf27e0ce2df0c08c0", size = 240368, upload-time = "2025-11-02T12:26:00.491Z" }, + { url = "https://files.pythonhosted.org/packages/62/61/23fd4acc3c9eebbf6b6c78bcd89e5d020cfde4acf0a9233e9d4e3fa698b4/psutil-7.1.3-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95ef04cf2e5ba0ab9eaafc4a11eaae91b44f4ef5541acd2ee91d9108d00d59a7", size = 287134, upload-time = "2025-11-02T12:26:02.613Z" }, + { url = "https://files.pythonhosted.org/packages/30/1c/f921a009ea9ceb51aa355cb0cc118f68d354db36eae18174bab63affb3e6/psutil-7.1.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1068c303be3a72f8e18e412c5b2a8f6d31750fb152f9cb106b54090296c9d251", size = 289904, upload-time = "2025-11-02T12:26:05.207Z" }, + { url = "https://files.pythonhosted.org/packages/a6/82/62d68066e13e46a5116df187d319d1724b3f437ddd0f958756fc052677f4/psutil-7.1.3-cp313-cp313t-win_amd64.whl", hash = "sha256:18349c5c24b06ac5612c0428ec2a0331c26443d259e2a0144a9b24b4395b58fa", size = 249642, upload-time = "2025-11-02T12:26:07.447Z" }, + { url = "https://files.pythonhosted.org/packages/df/ad/c1cd5fe965c14a0392112f68362cfceb5230819dbb5b1888950d18a11d9f/psutil-7.1.3-cp313-cp313t-win_arm64.whl", hash = "sha256:c525ffa774fe4496282fb0b1187725793de3e7c6b29e41562733cae9ada151ee", size = 245518, upload-time = "2025-11-02T12:26:09.719Z" }, + { url = "https://files.pythonhosted.org/packages/2e/bb/6670bded3e3236eb4287c7bcdc167e9fae6e1e9286e437f7111caed2f909/psutil-7.1.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b403da1df4d6d43973dc004d19cee3b848e998ae3154cc8097d139b77156c353", size = 239843, upload-time = "2025-11-02T12:26:11.968Z" }, + { url = "https://files.pythonhosted.org/packages/b8/66/853d50e75a38c9a7370ddbeefabdd3d3116b9c31ef94dc92c6729bc36bec/psutil-7.1.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ad81425efc5e75da3f39b3e636293360ad8d0b49bed7df824c79764fb4ba9b8b", size = 240369, upload-time = "2025-11-02T12:26:14.358Z" }, + { url = "https://files.pythonhosted.org/packages/41/bd/313aba97cb5bfb26916dc29cf0646cbe4dd6a89ca69e8c6edce654876d39/psutil-7.1.3-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f33a3702e167783a9213db10ad29650ebf383946e91bc77f28a5eb083496bc9", size = 288210, upload-time = "2025-11-02T12:26:16.699Z" }, + { url = "https://files.pythonhosted.org/packages/c2/fa/76e3c06e760927a0cfb5705eb38164254de34e9bd86db656d4dbaa228b04/psutil-7.1.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fac9cd332c67f4422504297889da5ab7e05fd11e3c4392140f7370f4208ded1f", size = 291182, upload-time = "2025-11-02T12:26:18.848Z" }, + { url = "https://files.pythonhosted.org/packages/0f/1d/5774a91607035ee5078b8fd747686ebec28a962f178712de100d00b78a32/psutil-7.1.3-cp314-cp314t-win_amd64.whl", hash = "sha256:3792983e23b69843aea49c8f5b8f115572c5ab64c153bada5270086a2123c7e7", size = 250466, upload-time = "2025-11-02T12:26:21.183Z" }, + { url = "https://files.pythonhosted.org/packages/00/ca/e426584bacb43a5cb1ac91fae1937f478cd8fbe5e4ff96574e698a2c77cd/psutil-7.1.3-cp314-cp314t-win_arm64.whl", hash = "sha256:31d77fcedb7529f27bb3a0472bea9334349f9a04160e8e6e5020f22c59893264", size = 245756, upload-time = "2025-11-02T12:26:23.148Z" }, + { url = "https://files.pythonhosted.org/packages/ef/94/46b9154a800253e7ecff5aaacdf8ebf43db99de4a2dfa18575b02548654e/psutil-7.1.3-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2bdbcd0e58ca14996a42adf3621a6244f1bb2e2e528886959c72cf1e326677ab", size = 238359, upload-time = "2025-11-02T12:26:25.284Z" }, + { url = "https://files.pythonhosted.org/packages/68/3a/9f93cff5c025029a36d9a92fef47220ab4692ee7f2be0fba9f92813d0cb8/psutil-7.1.3-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:bc31fa00f1fbc3c3802141eede66f3a2d51d89716a194bf2cd6fc68310a19880", size = 239171, upload-time = "2025-11-02T12:26:27.23Z" }, + { url = "https://files.pythonhosted.org/packages/ce/b1/5f49af514f76431ba4eea935b8ad3725cdeb397e9245ab919dbc1d1dc20f/psutil-7.1.3-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3bb428f9f05c1225a558f53e30ccbad9930b11c3fc206836242de1091d3e7dd3", size = 263261, upload-time = "2025-11-02T12:26:29.48Z" }, + { url = "https://files.pythonhosted.org/packages/e0/95/992c8816a74016eb095e73585d747e0a8ea21a061ed3689474fabb29a395/psutil-7.1.3-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:56d974e02ca2c8eb4812c3f76c30e28836fffc311d55d979f1465c1feeb2b68b", size = 264635, upload-time = "2025-11-02T12:26:31.74Z" }, + { url = "https://files.pythonhosted.org/packages/55/4c/c3ed1a622b6ae2fd3c945a366e64eb35247a31e4db16cf5095e269e8eb3c/psutil-7.1.3-cp37-abi3-win_amd64.whl", hash = "sha256:f39c2c19fe824b47484b96f9692932248a54c43799a84282cfe58d05a6449efd", size = 247633, upload-time = "2025-11-02T12:26:33.887Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ad/33b2ccec09bf96c2b2ef3f9a6f66baac8253d7565d8839e024a6b905d45d/psutil-7.1.3-cp37-abi3-win_arm64.whl", hash = "sha256:bd0d69cee829226a761e92f28140bec9a5ee9d5b4fb4b0cc589068dbfff559b1", size = 244608, upload-time = "2025-11-02T12:26:36.136Z" }, +] + +[[package]] +name = "pyaes" +version = "1.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/44/66/2c17bae31c906613795711fc78045c285048168919ace2220daa372c7d72/pyaes-1.6.1.tar.gz", hash = "sha256:02c1b1405c38d3c370b085fb952dd8bea3fadcee6411ad99f312cc129c536d8f", size = 28536, upload-time = "2017-09-20T21:17:54.23Z" } + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pymediainfo-pyrofork" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/43/ebfd048e84bb264bb133d545312e35b49638bcd7d5ad973c023e0026a36b/pymediainfo_pyrofork-6.0.2.tar.gz", hash = "sha256:fce9402edfd1fa09aba7b3cac4c41ba7fcf6820e561b4db4f9c1a1a68c487c36", size = 446514, upload-time = "2024-10-08T14:31:39.531Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/16/7c2b2f969e84e5f196809c10da6c847c505fb722b9d636bf6f6bf8f2e919/pymediainfo_pyrofork-6.0.2-py2.py3-none-any.whl", hash = "sha256:674fa8e53de861635b9dc4f77c2ad712306a798bf28864952503bf328210c4c3", size = 9356, upload-time = "2024-10-08T14:31:37.054Z" }, +] + +[[package]] +name = "pymongo" +version = "4.15.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dnspython" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/7b/a709c85dc716eb85b69f71a4bb375cf1e72758a7e872103f27551243319c/pymongo-4.15.3.tar.gz", hash = "sha256:7a981271347623b5319932796690c2d301668ac3a1965974ac9f5c3b8a22cea5", size = 2470801, upload-time = "2025-10-07T21:57:50.384Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/a4/e1ce9d408a1c1bcb1554ff61251b108e16cefd7db91b33faa2afc92294de/pymongo-4.15.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a47a3218f7900f65bf0f36fcd1f2485af4945757360e7e143525db9d715d2010", size = 975329, upload-time = "2025-10-07T21:56:44.674Z" }, + { url = "https://files.pythonhosted.org/packages/74/3c/6796f653d22be43cc0b13c07dbed84133eebbc334ebed4426459b7250163/pymongo-4.15.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:09440e78dff397b2f34a624f445ac8eb44c9756a2688b85b3bf344d351d198e1", size = 975129, upload-time = "2025-10-07T21:56:46.104Z" }, + { url = "https://files.pythonhosted.org/packages/88/33/22453dbfe11031e89c9cbdfde6405c03960daaf5da1b4dfdd458891846b5/pymongo-4.15.3-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:97f9babdb98c31676f97d468f7fe2dc49b8a66fb6900effddc4904c1450196c8", size = 1950979, upload-time = "2025-10-07T21:56:47.877Z" }, + { url = "https://files.pythonhosted.org/packages/ba/07/094598e403112e2410a3376fb7845c69e2ec2dfc5ab5cc00b29dc2d26559/pymongo-4.15.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:71413cd8f091ae25b1fec3af7c2e531cf9bdb88ce4079470e64835f6a664282a", size = 1995271, upload-time = "2025-10-07T21:56:49.396Z" }, + { url = "https://files.pythonhosted.org/packages/47/9a/29e44f3dee68defc56e50ed7c9d3802ebf967ab81fefb175d8d729c0f276/pymongo-4.15.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:76a8d4de8dceb69f6e06736198ff6f7e1149515ef946f192ff2594d2cc98fc53", size = 2086587, upload-time = "2025-10-07T21:56:50.896Z" }, + { url = "https://files.pythonhosted.org/packages/ff/d5/e9ff16aa57f671349134475b904fd431e7b86e152b01a949aef4f254b2d5/pymongo-4.15.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:77353978be9fc9e5fe56369682efed0aac5f92a2a1570704d62b62a3c9e1a24f", size = 2070201, upload-time = "2025-10-07T21:56:52.425Z" }, + { url = "https://files.pythonhosted.org/packages/d6/a3/820772c0b2bbb671f253cfb0bede4cf694a38fb38134f3993d491e23ec11/pymongo-4.15.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9897a837677e3814873d0572f7e5d53c23ce18e274f3b5b87f05fb6eea22615b", size = 1985260, upload-time = "2025-10-07T21:56:54.56Z" }, + { url = "https://files.pythonhosted.org/packages/6e/7b/365ac821aefad7e8d36a4bc472a94429449aade1ccb7805d9ca754df5081/pymongo-4.15.3-cp313-cp313-win32.whl", hash = "sha256:d66da207ccb0d68c5792eaaac984a0d9c6c8ec609c6bcfa11193a35200dc5992", size = 938122, upload-time = "2025-10-07T21:56:55.993Z" }, + { url = "https://files.pythonhosted.org/packages/80/f3/5ca27e1765fa698c677771a1c0e042ef193e207c15f5d32a21fa5b13d8c3/pymongo-4.15.3-cp313-cp313-win_amd64.whl", hash = "sha256:52f40c4b8c00bc53d4e357fe0de13d031c4cddb5d201e1a027db437e8d2887f8", size = 962610, upload-time = "2025-10-07T21:56:57.397Z" }, + { url = "https://files.pythonhosted.org/packages/48/7c/42f0b6997324023e94939f8f32b9a8dd928499f4b5d7b4412905368686b5/pymongo-4.15.3-cp313-cp313-win_arm64.whl", hash = "sha256:fb384623ece34db78d445dd578a52d28b74e8319f4d9535fbaff79d0eae82b3d", size = 944300, upload-time = "2025-10-07T21:56:58.969Z" }, + { url = "https://files.pythonhosted.org/packages/e7/a3/d8aaf9c243ce1319bd2498004a9acccfcfb35a3ef9851abb856993d95255/pymongo-4.15.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:dcff15b9157c16bc796765d4d3d151df669322acfb0357e4c3ccd056153f0ff4", size = 1029873, upload-time = "2025-10-07T21:57:00.759Z" }, + { url = "https://files.pythonhosted.org/packages/64/10/91fd7791425ed3b56cbece6c23a36fb2696706a695655d8ea829e5e23c3a/pymongo-4.15.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1f681722c9f27e86c49c2e8a838e61b6ecf2285945fd1798bd01458134257834", size = 1029611, upload-time = "2025-10-07T21:57:02.488Z" }, + { url = "https://files.pythonhosted.org/packages/bb/9c/d9cf8d8a181f96877bca7bdec3e6ce135879d5e3d78694ea465833c53a3f/pymongo-4.15.3-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2c96dde79bdccd167b930a709875b0cd4321ac32641a490aebfa10bdcd0aa99b", size = 2211827, upload-time = "2025-10-07T21:57:03.907Z" }, + { url = "https://files.pythonhosted.org/packages/c2/40/12703964305216c155284100124222eaa955300a07d426c6e0ba3c9cbade/pymongo-4.15.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d2d4ca446348d850ac4a5c3dc603485640ae2e7805dbb90765c3ba7d79129b37", size = 2264654, upload-time = "2025-10-07T21:57:05.41Z" }, + { url = "https://files.pythonhosted.org/packages/0f/70/bf3c18b5d0cae0b9714158b210b07b5891a875eb1c503271cfe045942fd3/pymongo-4.15.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7c0fd3de3a12ff0a8113a3f64cedb01f87397ab8eaaffa88d7f18ca66cd39385", size = 2371830, upload-time = "2025-10-07T21:57:06.9Z" }, + { url = "https://files.pythonhosted.org/packages/21/6d/2dfaed2ae66304ab842d56ed9a1bd2706ca0ecf97975b328a5eeceb2a4c0/pymongo-4.15.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e84dec392cf5f72d365e0aac73f627b0a3170193ebb038c3f7e7df11b7983ee7", size = 2351878, upload-time = "2025-10-07T21:57:08.92Z" }, + { url = "https://files.pythonhosted.org/packages/17/ed/fe46ff9adfa6dc11ad2e0694503adfc98f40583cfcc6db4dbaf582f0e357/pymongo-4.15.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8d4b01a48369ea6d5bc83fea535f56279f806aa3e4991189f0477696dd736289", size = 2251356, upload-time = "2025-10-07T21:57:10.51Z" }, + { url = "https://files.pythonhosted.org/packages/12/c4/2e1a10b1e9bca9c106f2dc1b89d4ad70c63d387c194b3a1bfcca552b5a3f/pymongo-4.15.3-cp314-cp314-win32.whl", hash = "sha256:3561fa96c3123275ec5ccf919e595547e100c412ec0894e954aa0da93ecfdb9e", size = 992878, upload-time = "2025-10-07T21:57:12.119Z" }, + { url = "https://files.pythonhosted.org/packages/98/b5/14aa417a44ea86d4c31de83b26f6e6793f736cd60e7e7fda289ce5184bdf/pymongo-4.15.3-cp314-cp314-win_amd64.whl", hash = "sha256:9df2db6bd91b07400879b6ec89827004c0c2b55fc606bb62db93cafb7677c340", size = 1021209, upload-time = "2025-10-07T21:57:13.686Z" }, + { url = "https://files.pythonhosted.org/packages/94/9f/1097c6824fa50a4ffb11ba5194d2a9ef68d5509dd342e32ddb697d2efe4e/pymongo-4.15.3-cp314-cp314-win_arm64.whl", hash = "sha256:ff99864085d2c7f4bb672c7167680ceb7d273e9a93c1a8074c986a36dbb71cc6", size = 1000618, upload-time = "2025-10-07T21:57:15.212Z" }, + { url = "https://files.pythonhosted.org/packages/ad/31/37c76607a4f793f4491611741fa7a7c4238b956f48c4a9505cea0b5cf7ef/pymongo-4.15.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:ffe217d2502f3fba4e2b0dc015ce3b34f157b66dfe96835aa64432e909dd0d95", size = 1086576, upload-time = "2025-10-07T21:57:16.742Z" }, + { url = "https://files.pythonhosted.org/packages/92/b2/6d17d279cdd293eeeb0c9d5baeb4f8cdebb45354fd81cfcef2d1c69303ab/pymongo-4.15.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:390c4954c774eda280898e73aea36482bf20cba3ecb958dbb86d6a68b9ecdd68", size = 1086656, upload-time = "2025-10-07T21:57:18.774Z" }, + { url = "https://files.pythonhosted.org/packages/55/fd/c5da8619beca207d7e6231f24ed269cb537c5311dad59fd9f2ef7d43204a/pymongo-4.15.3-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7dd2a49f088890ca08930bbf96121443b48e26b02b84ba0a3e1ae2bf2c5a9b48", size = 2531646, upload-time = "2025-10-07T21:57:20.63Z" }, + { url = "https://files.pythonhosted.org/packages/93/8f/66a7e12b874f41eb205f352b3a719e5a964b5ba103996f6ac45e80560111/pymongo-4.15.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f6feb678f26171f2a6b2cbb340949889154c7067972bd4cc129b62161474f08", size = 2603799, upload-time = "2025-10-07T21:57:22.591Z" }, + { url = "https://files.pythonhosted.org/packages/10/98/baf0d1f8016087500899cc4ae14e591f29b016c643e99ab332fcafe6f7bc/pymongo-4.15.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:446417a34ff6c2411ce3809e17ce9a67269c9f1cb4966b01e49e0c590cc3c6b3", size = 2725238, upload-time = "2025-10-07T21:57:24.091Z" }, + { url = "https://files.pythonhosted.org/packages/c9/a2/112d8d3882d6e842f501e166fbe08dfc2bc9a35f8773cbcaa804f7991043/pymongo-4.15.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:cfa4a0a0f024a0336640e1201994e780a17bda5e6a7c0b4d23841eb9152e868b", size = 2704837, upload-time = "2025-10-07T21:57:25.626Z" }, + { url = "https://files.pythonhosted.org/packages/38/fe/043a9aac7b3fba5b8e216f48359bd18fdbe46a4d93b081786f773b25e997/pymongo-4.15.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9b03db2fe37c950aff94b29ded5c349b23729bccd90a0a5907bbf807d8c77298", size = 2582294, upload-time = "2025-10-07T21:57:27.221Z" }, + { url = "https://files.pythonhosted.org/packages/5b/fe/7a6a6b331d9f2024ab171028ab53d5d9026959b1d713fe170be591a4d9a8/pymongo-4.15.3-cp314-cp314t-win32.whl", hash = "sha256:e7cde58ef6470c0da922b65e885fb1ffe04deef81e526bd5dea429290fa358ca", size = 1043993, upload-time = "2025-10-07T21:57:28.727Z" }, + { url = "https://files.pythonhosted.org/packages/70/c8/bc64321711e19bd48ea3371f0082f10295c433833245d73e7606d3b9afbe/pymongo-4.15.3-cp314-cp314t-win_amd64.whl", hash = "sha256:fae552767d8e5153ed498f1bca92d905d0d46311d831eefb0f06de38f7695c95", size = 1078481, upload-time = "2025-10-07T21:57:30.372Z" }, + { url = "https://files.pythonhosted.org/packages/39/31/2bb2003bb978eb25dfef7b5f98e1c2d4a86e973e63b367cc508a9308d31c/pymongo-4.15.3-cp314-cp314t-win_arm64.whl", hash = "sha256:47ffb068e16ae5e43580d5c4e3b9437f05414ea80c32a1e5cac44a835859c259", size = 1051179, upload-time = "2025-10-07T21:57:31.829Z" }, +] + +[[package]] +name = "pyrofork" +version = "2.3.68" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyaes" }, + { name = "pymediainfo-pyrofork" }, + { name = "pysocks" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ce/6a/a35f0b5606d10c12dd4c6fadf0af5eb3b9f460a2eeefe88a3496d01a3881/pyrofork-2.3.68.tar.gz", hash = "sha256:12dcc7b78d2179b4e13fdbe31121d9235c4bf0089bc946c47ede62647f19229f", size = 501077, upload-time = "2025-07-10T12:59:52.02Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c4/8f/9fa88de00f6c02dcb77cbaa5a65d47f34a9bf2f804eb98d5183389634c3a/pyrofork-2.3.68-py3-none-any.whl", hash = "sha256:1a93c062682f0ed7e5fffd01b4f878e663cce0eea8a0df06c93f005768d8697a", size = 5043277, upload-time = "2025-07-10T12:59:49.935Z" }, +] + +[[package]] +name = "pysmartdl" +version = "1.3.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/4c/ed073b2373f115094a4a612431abe25b58e542bebd951557dcc881999ef9/pySmartDL-1.3.4.tar.gz", hash = "sha256:35275d1694f3474d33bdca93b27d3608265ffd42f5aeb28e56f38b906c0c35f4", size = 21467, upload-time = "2020-09-19T15:40:22.676Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ac/6a/582286ea74c54363cba30413214767904f0a239e12253c3817feaf78453f/pySmartDL-1.3.4-py3-none-any.whl", hash = "sha256:671c277ca710fb9b6603b19176f5c091041ec4ef6dcdb507c9a983a89ca35d31", size = 20000, upload-time = "2020-09-19T15:40:21.264Z" }, +] + +[[package]] +name = "pysocks" +version = "1.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/11/293dd436aea955d45fc4e8a35b6ae7270f5b8e00b53cf6c024c83b657a11/PySocks-1.7.1.tar.gz", hash = "sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0", size = 284429, upload-time = "2019-09-20T02:07:35.714Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/59/b4572118e098ac8e46e399a1dd0f2d85403ce8bbaad9ec79373ed6badaf9/PySocks-1.7.1-py3-none-any.whl", hash = "sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5", size = 16725, upload-time = "2019-09-20T02:06:22.938Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f0/26/19cadc79a718c5edbec86fd4919a6b6d3f681039a2f6d66d14be94e75fb9/python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6", size = 44221, upload-time = "2025-10-26T15:12:10.434Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61", size = 21230, upload-time = "2025-10-26T15:12:09.109Z" }, +] + +[[package]] +name = "qrcode" +version = "8.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8f/b2/7fc2931bfae0af02d5f53b174e9cf701adbb35f39d69c2af63d4a39f81a9/qrcode-8.2.tar.gz", hash = "sha256:35c3f2a4172b33136ab9f6b3ef1c00260dd2f66f858f24d88418a015f446506c", size = 43317, upload-time = "2025-05-01T15:44:24.726Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dd/b8/d2d6d731733f51684bbf76bf34dab3b70a9148e8f2cef2bb544fccec681a/qrcode-8.2-py3-none-any.whl", hash = "sha256:16e64e0716c14960108e85d853062c9e8bba5ca8252c0b4d0231b9df4060ff4f", size = 45986, upload-time = "2025-05-01T15:44:22.781Z" }, +] + +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, +] + +[[package]] +name = "smmap" +version = "5.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/44/cd/a040c4b3119bbe532e5b0732286f805445375489fceaec1f48306068ee3b/smmap-5.0.2.tar.gz", hash = "sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5", size = 22329, upload-time = "2025-01-02T07:14:40.909Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/be/d09147ad1ec7934636ad912901c5fd7667e1c858e19d355237db0d0cd5e4/smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e", size = 24303, upload-time = "2025-01-02T07:14:38.724Z" }, +] + +[[package]] +name = "soupsieve" +version = "2.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/e6/21ccce3262dd4889aa3332e5a119a3491a95e8f60939870a3a035aabac0d/soupsieve-2.8.tar.gz", hash = "sha256:e2dd4a40a628cb5f28f6d4b0db8800b8f581b65bb380b97de22ba5ca8d72572f", size = 103472, upload-time = "2025-08-27T15:39:51.78Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl", hash = "sha256:0cc76456a30e20f5d7f2e14a98a4ae2ee4e5abdc7c5ea0aafe795f344bc7984c", size = 36679, upload-time = "2025-08-27T15:39:50.179Z" }, +] + +[[package]] +name = "tgcrypto" +version = "1.2.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/33/59/7cf5ced989e3139a791d5452d58cb8994de589576b80f9267ba76d794f6c/TgCrypto-1.2.5.tar.gz", hash = "sha256:9bc2cac6fb9a12ef5b08f3dd500174fe374d89b660cce981f57e3138559cb682", size = 37280, upload-time = "2022-11-11T19:55:11.436Z" } + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "urllib3" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, +] + +[[package]] +name = "userbot" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "aiofiles" }, + { name = "aiohttp" }, + { name = "beautifulsoup4" }, + { name = "click" }, + { name = "dnspython" }, + { name = "environs" }, + { name = "flask" }, + { name = "gitpython" }, + { name = "gunicorn" }, + { name = "humanize" }, + { name = "pillow" }, + { name = "psutil" }, + { name = "pygments" }, + { name = "pymongo" }, + { name = "pyrofork" }, + { name = "pysmartdl" }, + { name = "qrcode" }, + { name = "requests" }, + { name = "tgcrypto" }, + { name = "wheel" }, +] + +[package.metadata] +requires-dist = [ + { name = "aiofiles", specifier = ">=25.1.0" }, + { name = "aiohttp", specifier = ">=3.13.2" }, + { name = "beautifulsoup4", specifier = ">=4.14.2" }, + { name = "click", specifier = ">=8.3.0" }, + { name = "dnspython", specifier = ">=2.8.0" }, + { name = "environs", specifier = ">=14.5.0" }, + { name = "flask", specifier = ">=3.1.2" }, + { name = "gitpython", specifier = ">=3.1.45" }, + { name = "gunicorn", specifier = ">=23.0.0" }, + { name = "humanize", specifier = ">=4.14.0" }, + { name = "pillow", specifier = ">=10.3.0" }, + { name = "psutil", specifier = ">=7.1.3" }, + { name = "pygments", specifier = ">=2.19.2" }, + { name = "pymongo", specifier = ">=4.15.3" }, + { name = "pyrofork", specifier = "==2.3.68" }, + { name = "pysmartdl", specifier = ">=1.3.4" }, + { name = "qrcode", specifier = ">=8.2" }, + { name = "requests", specifier = ">=2.32.5" }, + { name = "tgcrypto", specifier = ">=1.2.5" }, + { name = "wheel", specifier = ">=0.45.1" }, +] + +[[package]] +name = "werkzeug" +version = "3.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9f/69/83029f1f6300c5fb2471d621ab06f6ec6b3324685a2ce0f9777fd4a8b71e/werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746", size = 806925, upload-time = "2024-11-08T15:52:18.093Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/24/ab44c871b0f07f491e5d2ad12c9bd7358e527510618cb1b803a88e986db1/werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e", size = 224498, upload-time = "2024-11-08T15:52:16.132Z" }, +] + +[[package]] +name = "wheel" +version = "0.45.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/98/2d9906746cdc6a6ef809ae6338005b3f21bb568bea3165cfc6a243fdc25c/wheel-0.45.1.tar.gz", hash = "sha256:661e1abd9198507b1409a20c02106d9670b2576e916d58f520316666abca6729", size = 107545, upload-time = "2024-11-23T00:18:23.513Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/2c/87f3254fd8ffd29e4c02732eee68a83a1d3c346ae39bc6822dcbcb697f2b/wheel-0.45.1-py3-none-any.whl", hash = "sha256:708e7481cc80179af0e556bbf0cc00b8444c7321e2700b8d8580231d13017248", size = 72494, upload-time = "2024-11-23T00:18:21.207Z" }, +] + +[[package]] +name = "yarl" +version = "1.22.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "multidict" }, + { name = "propcache" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/57/63/0c6ebca57330cd313f6102b16dd57ffaf3ec4c83403dcb45dbd15c6f3ea1/yarl-1.22.0.tar.gz", hash = "sha256:bebf8557577d4401ba8bd9ff33906f1376c877aa78d1fe216ad01b4d6745af71", size = 187169, upload-time = "2025-10-06T14:12:55.963Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/f3/d67de7260456ee105dc1d162d43a019ecad6b91e2f51809d6cddaa56690e/yarl-1.22.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8dee9c25c74997f6a750cd317b8ca63545169c098faee42c84aa5e506c819b53", size = 139980, upload-time = "2025-10-06T14:10:14.601Z" }, + { url = "https://files.pythonhosted.org/packages/01/88/04d98af0b47e0ef42597b9b28863b9060bb515524da0a65d5f4db160b2d5/yarl-1.22.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01e73b85a5434f89fc4fe27dcda2aff08ddf35e4d47bbbea3bdcd25321af538a", size = 93424, upload-time = "2025-10-06T14:10:16.115Z" }, + { url = "https://files.pythonhosted.org/packages/18/91/3274b215fd8442a03975ce6bee5fe6aa57a8326b29b9d3d56234a1dca244/yarl-1.22.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:22965c2af250d20c873cdbee8ff958fb809940aeb2e74ba5f20aaf6b7ac8c70c", size = 93821, upload-time = "2025-10-06T14:10:17.993Z" }, + { url = "https://files.pythonhosted.org/packages/61/3a/caf4e25036db0f2da4ca22a353dfeb3c9d3c95d2761ebe9b14df8fc16eb0/yarl-1.22.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4f15793aa49793ec8d1c708ab7f9eded1aa72edc5174cae703651555ed1b601", size = 373243, upload-time = "2025-10-06T14:10:19.44Z" }, + { url = "https://files.pythonhosted.org/packages/6e/9e/51a77ac7516e8e7803b06e01f74e78649c24ee1021eca3d6a739cb6ea49c/yarl-1.22.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5542339dcf2747135c5c85f68680353d5cb9ffd741c0f2e8d832d054d41f35a", size = 342361, upload-time = "2025-10-06T14:10:21.124Z" }, + { url = "https://files.pythonhosted.org/packages/d4/f8/33b92454789dde8407f156c00303e9a891f1f51a0330b0fad7c909f87692/yarl-1.22.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5c401e05ad47a75869c3ab3e35137f8468b846770587e70d71e11de797d113df", size = 387036, upload-time = "2025-10-06T14:10:22.902Z" }, + { url = "https://files.pythonhosted.org/packages/d9/9a/c5db84ea024f76838220280f732970aa4ee154015d7f5c1bfb60a267af6f/yarl-1.22.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:243dda95d901c733f5b59214d28b0120893d91777cb8aa043e6ef059d3cddfe2", size = 397671, upload-time = "2025-10-06T14:10:24.523Z" }, + { url = "https://files.pythonhosted.org/packages/11/c9/cd8538dc2e7727095e0c1d867bad1e40c98f37763e6d995c1939f5fdc7b1/yarl-1.22.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bec03d0d388060058f5d291a813f21c011041938a441c593374da6077fe21b1b", size = 377059, upload-time = "2025-10-06T14:10:26.406Z" }, + { url = "https://files.pythonhosted.org/packages/a1/b9/ab437b261702ced75122ed78a876a6dec0a1b0f5e17a4ac7a9a2482d8abe/yarl-1.22.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0748275abb8c1e1e09301ee3cf90c8a99678a4e92e4373705f2a2570d581273", size = 365356, upload-time = "2025-10-06T14:10:28.461Z" }, + { url = "https://files.pythonhosted.org/packages/b2/9d/8e1ae6d1d008a9567877b08f0ce4077a29974c04c062dabdb923ed98e6fe/yarl-1.22.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:47fdb18187e2a4e18fda2c25c05d8251a9e4a521edaed757fef033e7d8498d9a", size = 361331, upload-time = "2025-10-06T14:10:30.541Z" }, + { url = "https://files.pythonhosted.org/packages/ca/5a/09b7be3905962f145b73beb468cdd53db8aa171cf18c80400a54c5b82846/yarl-1.22.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c7044802eec4524fde550afc28edda0dd5784c4c45f0be151a2d3ba017daca7d", size = 382590, upload-time = "2025-10-06T14:10:33.352Z" }, + { url = "https://files.pythonhosted.org/packages/aa/7f/59ec509abf90eda5048b0bc3e2d7b5099dffdb3e6b127019895ab9d5ef44/yarl-1.22.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:139718f35149ff544caba20fce6e8a2f71f1e39b92c700d8438a0b1d2a631a02", size = 385316, upload-time = "2025-10-06T14:10:35.034Z" }, + { url = "https://files.pythonhosted.org/packages/e5/84/891158426bc8036bfdfd862fabd0e0fa25df4176ec793e447f4b85cf1be4/yarl-1.22.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e1b51bebd221006d3d2f95fbe124b22b247136647ae5dcc8c7acafba66e5ee67", size = 374431, upload-time = "2025-10-06T14:10:37.76Z" }, + { url = "https://files.pythonhosted.org/packages/bb/49/03da1580665baa8bef5e8ed34c6df2c2aca0a2f28bf397ed238cc1bbc6f2/yarl-1.22.0-cp313-cp313-win32.whl", hash = "sha256:d3e32536234a95f513bd374e93d717cf6b2231a791758de6c509e3653f234c95", size = 81555, upload-time = "2025-10-06T14:10:39.649Z" }, + { url = "https://files.pythonhosted.org/packages/9a/ee/450914ae11b419eadd067c6183ae08381cfdfcb9798b90b2b713bbebddda/yarl-1.22.0-cp313-cp313-win_amd64.whl", hash = "sha256:47743b82b76d89a1d20b83e60d5c20314cbd5ba2befc9cda8f28300c4a08ed4d", size = 86965, upload-time = "2025-10-06T14:10:41.313Z" }, + { url = "https://files.pythonhosted.org/packages/98/4d/264a01eae03b6cf629ad69bae94e3b0e5344741e929073678e84bf7a3e3b/yarl-1.22.0-cp313-cp313-win_arm64.whl", hash = "sha256:5d0fcda9608875f7d052eff120c7a5da474a6796fe4d83e152e0e4d42f6d1a9b", size = 81205, upload-time = "2025-10-06T14:10:43.167Z" }, + { url = "https://files.pythonhosted.org/packages/88/fc/6908f062a2f77b5f9f6d69cecb1747260831ff206adcbc5b510aff88df91/yarl-1.22.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:719ae08b6972befcba4310e49edb1161a88cdd331e3a694b84466bd938a6ab10", size = 146209, upload-time = "2025-10-06T14:10:44.643Z" }, + { url = "https://files.pythonhosted.org/packages/65/47/76594ae8eab26210b4867be6f49129861ad33da1f1ebdf7051e98492bf62/yarl-1.22.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:47d8a5c446df1c4db9d21b49619ffdba90e77c89ec6e283f453856c74b50b9e3", size = 95966, upload-time = "2025-10-06T14:10:46.554Z" }, + { url = "https://files.pythonhosted.org/packages/ab/ce/05e9828a49271ba6b5b038b15b3934e996980dd78abdfeb52a04cfb9467e/yarl-1.22.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cfebc0ac8333520d2d0423cbbe43ae43c8838862ddb898f5ca68565e395516e9", size = 97312, upload-time = "2025-10-06T14:10:48.007Z" }, + { url = "https://files.pythonhosted.org/packages/d1/c5/7dffad5e4f2265b29c9d7ec869c369e4223166e4f9206fc2243ee9eea727/yarl-1.22.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4398557cbf484207df000309235979c79c4356518fd5c99158c7d38203c4da4f", size = 361967, upload-time = "2025-10-06T14:10:49.997Z" }, + { url = "https://files.pythonhosted.org/packages/50/b2/375b933c93a54bff7fc041e1a6ad2c0f6f733ffb0c6e642ce56ee3b39970/yarl-1.22.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2ca6fd72a8cd803be290d42f2dec5cdcd5299eeb93c2d929bf060ad9efaf5de0", size = 323949, upload-time = "2025-10-06T14:10:52.004Z" }, + { url = "https://files.pythonhosted.org/packages/66/50/bfc2a29a1d78644c5a7220ce2f304f38248dc94124a326794e677634b6cf/yarl-1.22.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca1f59c4e1ab6e72f0a23c13fca5430f889634166be85dbf1013683e49e3278e", size = 361818, upload-time = "2025-10-06T14:10:54.078Z" }, + { url = "https://files.pythonhosted.org/packages/46/96/f3941a46af7d5d0f0498f86d71275696800ddcdd20426298e572b19b91ff/yarl-1.22.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c5010a52015e7c70f86eb967db0f37f3c8bd503a695a49f8d45700144667708", size = 372626, upload-time = "2025-10-06T14:10:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/c1/42/8b27c83bb875cd89448e42cd627e0fb971fa1675c9ec546393d18826cb50/yarl-1.22.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d7672ecf7557476642c88497c2f8d8542f8e36596e928e9bcba0e42e1e7d71f", size = 341129, upload-time = "2025-10-06T14:10:57.985Z" }, + { url = "https://files.pythonhosted.org/packages/49/36/99ca3122201b382a3cf7cc937b95235b0ac944f7e9f2d5331d50821ed352/yarl-1.22.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3b7c88eeef021579d600e50363e0b6ee4f7f6f728cd3486b9d0f3ee7b946398d", size = 346776, upload-time = "2025-10-06T14:10:59.633Z" }, + { url = "https://files.pythonhosted.org/packages/85/b4/47328bf996acd01a4c16ef9dcd2f59c969f495073616586f78cd5f2efb99/yarl-1.22.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f4afb5c34f2c6fecdcc182dfcfc6af6cccf1aa923eed4d6a12e9d96904e1a0d8", size = 334879, upload-time = "2025-10-06T14:11:01.454Z" }, + { url = "https://files.pythonhosted.org/packages/c2/ad/b77d7b3f14a4283bffb8e92c6026496f6de49751c2f97d4352242bba3990/yarl-1.22.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:59c189e3e99a59cf8d83cbb31d4db02d66cda5a1a4374e8a012b51255341abf5", size = 350996, upload-time = "2025-10-06T14:11:03.452Z" }, + { url = "https://files.pythonhosted.org/packages/81/c8/06e1d69295792ba54d556f06686cbd6a7ce39c22307100e3fb4a2c0b0a1d/yarl-1.22.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:5a3bf7f62a289fa90f1990422dc8dff5a458469ea71d1624585ec3a4c8d6960f", size = 356047, upload-time = "2025-10-06T14:11:05.115Z" }, + { url = "https://files.pythonhosted.org/packages/4b/b8/4c0e9e9f597074b208d18cef227d83aac36184bfbc6eab204ea55783dbc5/yarl-1.22.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:de6b9a04c606978fdfe72666fa216ffcf2d1a9f6a381058d4378f8d7b1e5de62", size = 342947, upload-time = "2025-10-06T14:11:08.137Z" }, + { url = "https://files.pythonhosted.org/packages/e0/e5/11f140a58bf4c6ad7aca69a892bff0ee638c31bea4206748fc0df4ebcb3a/yarl-1.22.0-cp313-cp313t-win32.whl", hash = "sha256:1834bb90991cc2999f10f97f5f01317f99b143284766d197e43cd5b45eb18d03", size = 86943, upload-time = "2025-10-06T14:11:10.284Z" }, + { url = "https://files.pythonhosted.org/packages/31/74/8b74bae38ed7fe6793d0c15a0c8207bbb819cf287788459e5ed230996cdd/yarl-1.22.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ff86011bd159a9d2dfc89c34cfd8aff12875980e3bd6a39ff097887520e60249", size = 93715, upload-time = "2025-10-06T14:11:11.739Z" }, + { url = "https://files.pythonhosted.org/packages/69/66/991858aa4b5892d57aef7ee1ba6b4d01ec3b7eb3060795d34090a3ca3278/yarl-1.22.0-cp313-cp313t-win_arm64.whl", hash = "sha256:7861058d0582b847bc4e3a4a4c46828a410bca738673f35a29ba3ca5db0b473b", size = 83857, upload-time = "2025-10-06T14:11:13.586Z" }, + { url = "https://files.pythonhosted.org/packages/46/b3/e20ef504049f1a1c54a814b4b9bed96d1ac0e0610c3b4da178f87209db05/yarl-1.22.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:34b36c2c57124530884d89d50ed2c1478697ad7473efd59cfd479945c95650e4", size = 140520, upload-time = "2025-10-06T14:11:15.465Z" }, + { url = "https://files.pythonhosted.org/packages/e4/04/3532d990fdbab02e5ede063676b5c4260e7f3abea2VOE_STREET_ID99c2aa745acc4c/yarl-1.22.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:0dd9a702591ca2e543631c2a017e4a547e38a5c0f29eece37d9097e04a7ac683", size = 93504, upload-time = "2025-10-06T14:11:17.106Z" }, + { url = "https://files.pythonhosted.org/packages/11/63/ff458113c5c2dac9a9719ac68ee7c947cb621432bcf28c9972b1c0e83938/yarl-1.22.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:594fcab1032e2d2cc3321bb2e51271e7cd2b516c7d9aee780ece81b07ff8244b", size = 94282, upload-time = "2025-10-06T14:11:19.064Z" }, + { url = "https://files.pythonhosted.org/packages/a7/bc/315a56aca762d44a6aaaf7ad253f04d996cb6b27bad34410f82d76ea8038/yarl-1.22.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3d7a87a78d46a2e3d5b72587ac14b4c16952dd0887dbb051451eceac774411e", size = 372080, upload-time = "2025-10-06T14:11:20.996Z" }, + { url = "https://files.pythonhosted.org/packages/3f/3f/08e9b826ec2e099ea6e7c69a61272f4f6da62cb5b1b63590bb80ca2e4a40/yarl-1.22.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:852863707010316c973162e703bddabec35e8757e67fcb8ad58829de1ebc8590", size = 338696, upload-time = "2025-10-06T14:11:22.847Z" }, + { url = "https://files.pythonhosted.org/packages/e3/9f/90360108e3b32bd76789088e99538febfea24a102380ae73827f62073543/yarl-1.22.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:131a085a53bfe839a477c0845acf21efc77457ba2bcf5899618136d64f3303a2", size = 387121, upload-time = "2025-10-06T14:11:24.889Z" }, + { url = "https://files.pythonhosted.org/packages/98/92/ab8d4657bd5b46a38094cfaea498f18bb70ce6b63508fd7e909bd1f93066/yarl-1.22.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:078a8aefd263f4d4f923a9677b942b445a2be970ca24548a8102689a3a8ab8da", size = 394080, upload-time = "2025-10-06T14:11:27.307Z" }, + { url = "https://files.pythonhosted.org/packages/f5/e7/d8c5a7752fef68205296201f8ec2bf718f5c805a7a7e9880576c67600658/yarl-1.22.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bca03b91c323036913993ff5c738d0842fc9c60c4648e5c8d98331526df89784", size = 372661, upload-time = "2025-10-06T14:11:29.387Z" }, + { url = "https://files.pythonhosted.org/packages/b6/2e/f4d26183c8db0bb82d491b072f3127fb8c381a6206a3a56332714b79b751/yarl-1.22.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:68986a61557d37bb90d3051a45b91fa3d5c516d177dfc6dd6f2f436a07ff2b6b", size = 364645, upload-time = "2025-10-06T14:11:31.423Z" }, + { url = "https://files.pythonhosted.org/packages/80/7c/428e5812e6b87cd00ee8e898328a62c95825bf37c7fa87f0b6bb2ad31304/yarl-1.22.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:4792b262d585ff0dff6bcb787f8492e40698443ec982a3568c2096433660c694", size = 355361, upload-time = "2025-10-06T14:11:33.055Z" }, + { url = "https://files.pythonhosted.org/packages/ec/2a/249405fd26776f8b13c067378ef4d7dd49c9098d1b6457cdd152a99e96a9/yarl-1.22.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ebd4549b108d732dba1d4ace67614b9545b21ece30937a63a65dd34efa19732d", size = 381451, upload-time = "2025-10-06T14:11:35.136Z" }, + { url = "https://files.pythonhosted.org/packages/67/a8/fb6b1adbe98cf1e2dd9fad71003d3a63a1bc22459c6e15f5714eb9323b93/yarl-1.22.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f87ac53513d22240c7d59203f25cc3beac1e574c6cd681bbfd321987b69f95fd", size = 383814, upload-time = "2025-10-06T14:11:37.094Z" }, + { url = "https://files.pythonhosted.org/packages/d9/f9/3aa2c0e480fb73e872ae2814c43bc1e734740bb0d54e8cb2a95925f98131/yarl-1.22.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:22b029f2881599e2f1b06f8f1db2ee63bd309e2293ba2d566e008ba12778b8da", size = 370799, upload-time = "2025-10-06T14:11:38.83Z" }, + { url = "https://files.pythonhosted.org/packages/50/3c/af9dba3b8b5eeb302f36f16f92791f3ea62e3f47763406abf6d5a4a3333b/yarl-1.22.0-cp314-cp314-win32.whl", hash = "sha256:6a635ea45ba4ea8238463b4f7d0e721bad669f80878b7bfd1f89266e2ae63da2", size = 82990, upload-time = "2025-10-06T14:11:40.624Z" }, + { url = "https://files.pythonhosted.org/packages/ac/30/ac3a0c5bdc1d6efd1b41fa24d4897a4329b3b1e98de9449679dd327af4f0/yarl-1.22.0-cp314-cp314-win_amd64.whl", hash = "sha256:0d6e6885777af0f110b0e5d7e5dda8b704efed3894da26220b7f3d887b839a79", size = 88292, upload-time = "2025-10-06T14:11:42.578Z" }, + { url = "https://files.pythonhosted.org/packages/df/0a/227ab4ff5b998a1b7410abc7b46c9b7a26b0ca9e86c34ba4b8d8bc7c63d5/yarl-1.22.0-cp314-cp314-win_arm64.whl", hash = "sha256:8218f4e98d3c10d683584cb40f0424f4b9fd6e95610232dd75e13743b070ee33", size = 82888, upload-time = "2025-10-06T14:11:44.863Z" }, + { url = "https://files.pythonhosted.org/packages/06/5e/a15eb13db90abd87dfbefb9760c0f3f257ac42a5cac7e75dbc23bed97a9f/yarl-1.22.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:45c2842ff0e0d1b35a6bf1cd6c690939dacb617a70827f715232b2e0494d55d1", size = 146223, upload-time = "2025-10-06T14:11:46.796Z" }, + { url = "https://files.pythonhosted.org/packages/18/82/9665c61910d4d84f41a5bf6837597c89e665fa88aa4941080704645932a9/yarl-1.22.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d947071e6ebcf2e2bee8fce76e10faca8f7a14808ca36a910263acaacef08eca", size = 95981, upload-time = "2025-10-06T14:11:48.845Z" }, + { url = "https://files.pythonhosted.org/packages/5d/9a/2f65743589809af4d0a6d3aa749343c4b5f4c380cc24a8e94a3c6625a808/yarl-1.22.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:334b8721303e61b00019474cc103bdac3d7b1f65e91f0bfedeec2d56dfe74b53", size = 97303, upload-time = "2025-10-06T14:11:50.897Z" }, + { url = "https://files.pythonhosted.org/packages/b0/ab/5b13d3e157505c43c3b43b5a776cbf7b24a02bc4cccc40314771197e3508/yarl-1.22.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e7ce67c34138a058fd092f67d07a72b8e31ff0c9236e751957465a24b28910c", size = 361820, upload-time = "2025-10-06T14:11:52.549Z" }, + { url = "https://files.pythonhosted.org/packages/fb/76/242a5ef4677615cf95330cfc1b4610e78184400699bdda0acb897ef5e49a/yarl-1.22.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d77e1b2c6d04711478cb1c4ab90db07f1609ccf06a287d5607fcd90dc9863acf", size = 323203, upload-time = "2025-10-06T14:11:54.225Z" }, + { url = "https://files.pythonhosted.org/packages/8c/96/475509110d3f0153b43d06164cf4195c64d16999e0c7e2d8a099adcd6907/yarl-1.22.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4647674b6150d2cae088fc07de2738a84b8bcedebef29802cf0b0a82ab6face", size = 363173, upload-time = "2025-10-06T14:11:56.069Z" }, + { url = "https://files.pythonhosted.org/packages/c9/66/59db471aecfbd559a1fd48aedd954435558cd98c7d0da8b03cc6c140a32c/yarl-1.22.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:efb07073be061c8f79d03d04139a80ba33cbd390ca8f0297aae9cce6411e4c6b", size = 373562, upload-time = "2025-10-06T14:11:58.783Z" }, + { url = "https://files.pythonhosted.org/packages/03/1f/c5d94abc91557384719da10ff166b916107c1b45e4d0423a88457071dd88/yarl-1.22.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e51ac5435758ba97ad69617e13233da53908beccc6cfcd6c34bbed8dcbede486", size = 339828, upload-time = "2025-10-06T14:12:00.686Z" }, + { url = "https://files.pythonhosted.org/packages/5f/97/aa6a143d3afba17b6465733681c70cf175af89f76ec8d9286e08437a7454/yarl-1.22.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:33e32a0dd0c8205efa8e83d04fc9f19313772b78522d1bdc7d9aed706bfd6138", size = 347551, upload-time = "2025-10-06T14:12:02.628Z" }, + { url = "https://files.pythonhosted.org/packages/43/3c/45a2b6d80195959239a7b2a8810506d4eea5487dce61c2a3393e7fc3c52e/yarl-1.22.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:bf4a21e58b9cde0e401e683ebd00f6ed30a06d14e93f7c8fd059f8b6e8f87b6a", size = 334512, upload-time = "2025-10-06T14:12:04.871Z" }, + { url = "https://files.pythonhosted.org/packages/86/a0/c2ab48d74599c7c84cb104ebd799c5813de252bea0f360ffc29d270c2caa/yarl-1.22.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:e4b582bab49ac33c8deb97e058cd67c2c50dac0dd134874106d9c774fd272529", size = 352400, upload-time = "2025-10-06T14:12:06.624Z" }, + { url = "https://files.pythonhosted.org/packages/32/75/f8919b2eafc929567d3d8411f72bdb1a2109c01caaab4ebfa5f8ffadc15b/yarl-1.22.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0b5bcc1a9c4839e7e30b7b30dd47fe5e7e44fb7054ec29b5bb8d526aa1041093", size = 357140, upload-time = "2025-10-06T14:12:08.362Z" }, + { url = "https://files.pythonhosted.org/packages/cf/72/6a85bba382f22cf78add705d8c3731748397d986e197e53ecc7835e76de7/yarl-1.22.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c0232bce2170103ec23c454e54a57008a9a72b5d1c3105dc2496750da8cfa47c", size = 341473, upload-time = "2025-10-06T14:12:10.994Z" }, + { url = "https://files.pythonhosted.org/packages/35/18/55e6011f7c044dc80b98893060773cefcfdbf60dfefb8cb2f58b9bacbd83/yarl-1.22.0-cp314-cp314t-win32.whl", hash = "sha256:8009b3173bcd637be650922ac455946197d858b3630b6d8787aa9e5c4564533e", size = 89056, upload-time = "2025-10-06T14:12:13.317Z" }, + { url = "https://files.pythonhosted.org/packages/f9/86/0f0dccb6e59a9e7f122c5afd43568b1d31b8ab7dda5f1b01fb5c7025c9a9/yarl-1.22.0-cp314-cp314t-win_amd64.whl", hash = "sha256:9fb17ea16e972c63d25d4a97f016d235c78dd2344820eb35bc034bc32012ee27", size = 96292, upload-time = "2025-10-06T14:12:15.398Z" }, + { url = "https://files.pythonhosted.org/packages/48/b7/503c98092fb3b344a179579f55814b613c1fbb1c23b3ec14a7b008a66a6e/yarl-1.22.0-cp314-cp314t-win_arm64.whl", hash = "sha256:9f6d73c1436b934e3f01df1e1b21ff765cd1d28c77dfb9ace207f746d4610ee1", size = 85171, upload-time = "2025-10-06T14:12:16.935Z" }, + { url = "https://files.pythonhosted.org/packages/73/ae/b48f95715333080afb75a4504487cbe142cae1268afc482d06692d605ae6/yarl-1.22.0-py3-none-any.whl", hash = "sha256:1380560bdba02b6b6c90de54133c81c9f2a453dee9912fe58c1dcced1edb7cff", size = 46814, upload-time = "2025-10-06T14:12:53.872Z" }, +] From 3a5652daa706f5fe1e01d1fdd42db32d5706daee Mon Sep 17 00:00:00 2001 From: mr-forust Date: Tue, 11 Nov 2025 01:39:53 +0100 Subject: [PATCH 02/23] userbot/install.sh, start --- install.sh | 0 start | 0 2 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 install.sh mode change 100755 => 100644 start diff --git a/install.sh b/install.sh old mode 100755 new mode 100644 diff --git a/start b/start old mode 100755 new mode 100644 From 4ff80c07b0d7abbd62b667260b348c52b60553b2 Mon Sep 17 00:00:00 2001 From: mr-forust Date: Tue, 11 Nov 2025 01:41:24 +0100 Subject: [PATCH 03/23] deleting that install shit --- install.sh | 338 ----------------------------------------------------- start | 9 -- 2 files changed, 347 deletions(-) delete mode 100644 install.sh delete mode 100644 start diff --git a/install.sh b/install.sh deleted file mode 100644 index deed859..0000000 --- a/install.sh +++ /dev/null @@ -1,338 +0,0 @@ -#!/usr/bin/env bash - -# Define color codes -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[0;33m' -BLUE='\033[0;34m' -INPUT='\033[1;30m' -NC='\033[0m' # No Color - -PACKAGE_MANAGER="" - -# Ensure the script is run with root privileges -if [[ $UID != 0 ]]; then - printf "${YELLOW}This script requires root privileges.${NC}\n" # skipcq - printf "Please enter the root password to continue.\n" - exec sudo "$0" "$@" -else - printf "${YELLOW}Running with root privileges${NC}\n" # skipcq -fi - -# Detect available package manager -if command -v apt &>/dev/null; then - PACKAGE_MANAGER="apt" -elif command -v apk &>/dev/null; then - PACKAGE_MANAGER="apk" -elif command -v yum &>/dev/null; then - PACKAGE_MANAGER="yum" -elif command -v pacman &>/dev/null; then - PACKAGE_MANAGER="pacman" -else - printf "${RED}Unsupported package manager. Please use a compatible distribution or update the installer script.${NC}\n" # skipcq - exit 1 -fi - -if command -v termux-setup-storage; then - printf "${RED}For termux, please use https://raw.githubusercontent.com/The-MoonTg-project/Moon-Userbot/main/termux-install.sh${NC}\n" # skipcq - exit 1 -fi - -# Install necessary packages based on detected package manager -case "$PACKAGE_MANAGER" in -apt) - apt update -y - apt install python3 python3-venv git wget -y || exit 2 - ;; -apk) - apk update - apk add python3 py3-virtualenv git wget || exit 2 # Packages here may be wrong, to verify - ;; -yum) - yum update -y - yum install python3 python3-venv git wget -y || exit 2 # Packages here may be wrong, to verify - ;; -pacman) - pacman -S --noconfirm python python-virtualenv git wget || exit 2 - ;; -esac - -# Clone repository if not exists -if [[ -d "Moon-Userbot" && "$(basename "$PWD")" != "Moon-Userbot" ]]; then - cd Moon-Userbot || exit 2 -elif [[ "$(basename "$PWD")" == "Moon-Userbot" && -f ".env.dist" && -f "main.py" && -d "modules" ]]; then - printf "${BLUE}Already inside the Moon-Userbot repo, proceeding...${NC}\n" # skipcq -else - git clone https://github.com/The-MoonTg-project/Moon-Userbot || exit 2 - cd Moon-Userbot || exit 2 -fi - -if [[ -f ".env" ]] && [[ -f "my_account.session" ]]; then - printf "${GREEN}It seems that Moon-Userbot is already installed. Exiting...${NC}\n" # skipcq - exit -fi - -# Prompt user if they want to proceed with creating a virtual environment -printf "${YELLOW}It's recommended to use a virtual environment for Python projects.${NC}\n" # skipcq -printf "Note: If your drive resources are very limited, you might consider not creating a virtual environment, but it shouldn't be rejected otherwise unless you know what you're doing.\n" -printf "If you're unsure, it's better to create a virtual environment.\n" -printf "${INPUT}Would you like to create a virtual environment? (Y/n)${NC} > " # skipcq -read -r create_venv - -if [[ "$create_venv" != "n" ]] && [[ "$create_venv" != "N" ]]; then - # Create a virtual environment inside the cloned repository and activate it - python3 -m venv venv - . venv/bin/activate - - # Upgrade pip and install wheel and pillow - pip install -U pip wheel pillow -fi - -if [ -d ".venv" ]; then - . .venv/bin/activate -elif [ -d "venv" ]; then - . venv/bin/activate -fi - -# Install Python requirements -pip install -U -r requirements.txt || exit 2 -# Prompt for API_ID and API_HASH -printf "Enter API_ID and API_HASH\n" -printf "You can get it here -> https://my.telegram.org/\n" -printf "Leave empty to use defaults (please note that using default keys is a ${RED}very bad idea${NC} and significantly increases your ban chances)\n" # skipcq -read -r -p "API_ID > " api_id - -# Default API_ID and API_HASH -if [[ $api_id = "" ]]; then - printf "${RED}You have chosen to use the default API_ID and API_HASH, which is strongly discouraged.${NC}\n" # skipcq - printf "${YELLOW}Please type${NC} '${BLUE}I agree${NC}'${YELLOW} to confirm that you understand the risks and still wish to proceed.${NC}\n" # skipcq - read -r -p "Confirmation > " confirmation - if [[ $confirmation = "I agree" ]]; then - api_id="2040" - api_hash="b18441a1ff607e10a989891a5462e627" - else - printf "${RED}Confirmation not provided. Exiting...${NC}\n" # skipcq - exit 1 - fi -else - read -r -p "API_HASH > " api_hash -fi -# Prompt for PM PERMIT warn limit -# PM PERMIT warn limit is the number of messages a user can receive from others before giving them a warning, requires `antipm` plugin to be enabled -printf "SET PM PERMIT warn limit\n" -# Now below is more clear version: -printf "The number of messages others can send you before receiving a warning, and eventually a ban or leave empty for default (3), requires antipm plugin to be enabled\n" -read -r -p "PM_LIMIT warn limit > " pm_limit - -if [[ $pm_limit = "" ]]; then - pm_limit="3" - printf "Limit not provided by user; set to default\n" -fi - -# Prompt for musicbot usage -printf "Do you want to use musicbot? (y/N)" -read -r musicbot -if [[ $musicbot = "y" ]]; then - printf "Enter SECOND_SESSION_STRING to be used by musicbot\n" - read -r -p "SECOND_SESSION > " second_session - if [[ $second_session = "" ]]; then - printf "SECOND_SESSION not provided by user\n" - second_session="" - fi -fi - -# Prompt for various API keys -printf "Enter APIFLASH_KEY for webshot plugin\n" -printf "You can get it here -> https://apiflash.com/dashboard/access_keys\n" -read -r -p "APIFLASH_KEY > " apiflash_key - -if [[ $apiflash_key = "" ]]; then - printf "NOTE: API Not set; you'll get errors with webshot & ws module\n" -fi - -printf "Enter RMBG_KEY for remove background module\n" -printf "You can get it here -> https://www.remove.bg/dashboard#api-key\n" -read -r -p "RMBG_KEY > " rmbg_key - -if [[ $rmbg_key = "" ]]; then - printf "NOTE: API Not set; you'll not be able to use remove background modules\n" -fi - -printf "Enter VT_KEY for VirusTotal\n" -printf "You can get it here -> https://www.virustotal.com/\n" -read -r -p "VT_KEY > " vt_key - -if [[ $vt_key = "" ]]; then - printf "NOTE: API Not set; you'll not be able to use VirusTotal module\n" -fi - -printf "Enter GEMINI_KEY if you want to use AI\n" -printf "You can get it here -> https://makersuite.google.com/app/apikey\n" -read -r -p "GEMINI_KEY > " gemini_key - -if [[ $gemini_key = "" ]]; then - printf "NOTE: API Not set; you'll not be able to use Gemini AI modules\n" -fi - -printf "Enter COHERE_KEY if you want to use AI" -printf "You can get it here -> https://dashboard.cohere.com/api-keys\n" -read -r -p "COHERE_KEY > " cohere_key - -if [[ $cohere_key = "" ]]; then - printf "NOTE: API Not set; you'll not be able to use Coral AI modules\n" -fi - -while true; do - # Prompt for database type and database URL if MongoDB is selected - printf "${YELLOW}Choose database type:${NC}\n" # skipcq - printf "[1] MongoDB db_url\n" - printf "[2] MongoDB localhost\n" - printf "[3] Sqlite (default)\n" - read -r -p "> " db_type - - case $db_type in - 1) - printf "Please enter db_url\n" - printf "You can get it here -> https://mongodb.com/atlas\n" - read -r -p "> " db_url - db_name=Moon_Userbot - db_type=mongodb - break - ;; - 2) - if ! command -v apt &>/dev/null; then - printf "This option requires apt package manager, which is not available on your system.\n" - printf "Please choose a different database type.\n" - continue - fi - - if systemctl status mongodb; then - wget -qO - https://www.mongodb.org/static/pgp/server-5.0.asc | apt-key add - - source /etc/os-release - printf "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu %s/mongodb-org/5.0 multiverse\n" "${UBUNTU_CODENAME}" | tee /etc/apt/sources.list.d/mongodb-org-5.0.list - apt update - apt install mongodb -y - systemctl daemon-reload - systemctl enable mongodb - fi - systemctl start mongodb - - db_url=mongodb://localhost:27017 - db_name=Moon_Userbot - db_type=mongodb - break - ;; - 3) - db_name=db.sqlite3 - db_type=sqlite3 - break - ;; - *) - printf "${RED}Invalid choice!${NC}\n" # skipcq - ;; - esac -done - -# Generate .env file with collected variables -cat >.env < " install_type - - case $install_type in - 1) - if ! command -v apt &>/dev/null; then - printf "This option requires apt package manager, which is not available on your system.\n" - printf "Please choose a different installation type.\n" - continue - fi - - if ! command -v pm2 &>/dev/null; then - curl -fsSL https://deb.nodesource.com/setup_17.x | bash - apt install nodejs -y - npm install pm2 -g - su -c "pm2 startup" $SUDO_USER - env PATH=$PATH:/usr/bin /usr/lib/node_modules/pm2/bin/pm2 startup systemd -u $SUDO_USER --hp /home/$SUDO_USER - fi - su -c "pm2 start main.py --name Moon --interpreter python3" $SUDO_USER - su -c "pm2 save" $SUDO_USER - - printf "${GREEN}============================\\n" # skipcq - printf "Great! Moon-Userbot installed successfully and running now!\n" - printf "Installation type: PM2\n" - printf "Start with: \"pm2 start Moon\"\n" - printf "Stop with: \"pm2 stop Moon\"\n" - printf "Process name: Moon\n" - printf "============================${NC}\n" # skipcq - break - ;; - 2) - cat >/etc/systemd/system/Moon.service < Date: Tue, 11 Nov 2025 01:57:44 +0100 Subject: [PATCH 04/23] Move userbot Docker Compose configuration for forust and anna services --- compose.yml | 76 ----------------------------------------------------- 1 file changed, 76 deletions(-) delete mode 100644 compose.yml diff --git a/compose.yml b/compose.yml deleted file mode 100644 index 974d011..0000000 --- a/compose.yml +++ /dev/null @@ -1,76 +0,0 @@ -services: - userbot_forust: - build: - context: . - dockerfile: Dockerfile - container_name: userbot_forust - restart: unless-stopped - env_file: - - .env - - .env.forust - volumes: - - ./volumes/data_forust:/app/data - - ./Downloads:/app/downloads - - ./volumes/logs_forust:/app/logs - networks: - - userbot_network - dns: - - 8.8.8.8 - - 1.1.1.1 - develop: - watch: - - action: sync - path: ./modules - target: /app/modules - - action: sync - path: ./utils - target: /app/utils - - action: sync - path: ./main.py - target: /app/main.py - - action: rebuild - path: .env - - action: rebuild - path: .env.forust - - userbot_anna: - build: - context: . - dockerfile: Dockerfile - container_name: userbot_anna - restart: unless-stopped - env_file: - - .env - - .env.anna - volumes: - - ./volumes/data_anna:/app/data - - ./Downloads:/app/downloads - - ./volumes/logs_anna:/app/logs - networks: - - userbot_network - dns: - - 8.8.8.8 - - 1.1.1.1 - develop: - watch: - - action: sync - path: ./modules - target: /app/modules - - action: sync - path: ./utils - target: /app/utils - - action: sync - path: ./main.py - target: /app/main.py - - action: rebuild - path: .env - - action: rebuild - path: .env.anna - - -volumes: - downloads: - -networks: - userbot_network: - driver: bridge From 5f8fd052665069e633c70fc285de878eb01837af Mon Sep 17 00:00:00 2001 From: mr-forust Date: Thu, 13 Nov 2025 00:17:26 +0100 Subject: [PATCH 05/23] Add userbot Docker Compose configuration for userbot_forust and userbot_anna services --- userbot-docker-compose.yaml | 76 +++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 userbot-docker-compose.yaml diff --git a/userbot-docker-compose.yaml b/userbot-docker-compose.yaml new file mode 100644 index 0000000..1b1c29d --- /dev/null +++ b/userbot-docker-compose.yaml @@ -0,0 +1,76 @@ +services: + userbot_forust: + build: + context: . + dockerfile: Dockerfile + container_name: userbot_forust + restart: unless-stopped + env_file: + - .env + - .env.forust + volumes: + - ./volumes/data_forust:/app/data + - ./Downloads:/app/downloads + - ./volumes/logs_forust-:/app/logs + networks: + - userbot_network + dns: + - 8.8.8.8 + - 1.1.1.1 + develop: + watch: + - action: sync + path: modules + target: /app/modules + - action: sync + path: utils + target: /app/utils + - action: sync + path: main.py + target: /app/main.py + - action: rebuild + path: .env + - action: rebuild + path: .env.forust + + userbot_anna: + build: + context: . + dockerfile: Dockerfile + container_name: userbot_anna + restart: unless-stopped + env_file: + - .env + - .env.anna + volumes: + - ./volumes/data_anna:/app/data + - ./Downloads:/app/downloads + - ./volumes/logs_anna:/app/logs + networks: + - userbot_network + dns: + - 8.8.8.8 + - 1.1.1.1 + develop: + watch: + - action: sync + path: modules + target: /app/modules + - action: sync + path: utils + target: /app/utils + - action: sync + path: main.py + target: /app/main.py + - action: rebuild + path: .env + - action: rebuild + path: .env.anna + + +volumes: + downloads: + +networks: + userbot_network: + driver: bridge From b722467991155c26c79a5c4c4d64d959a740e5d4 Mon Sep 17 00:00:00 2001 From: mr-forust Date: Thu, 13 Nov 2025 03:48:50 +0100 Subject: [PATCH 06/23] Remove unnecessary network and development configurations from userbot Docker Compose file --- userbot-docker-compose.yaml | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/userbot-docker-compose.yaml b/userbot-docker-compose.yaml index 1b1c29d..01a1109 100644 --- a/userbot-docker-compose.yaml +++ b/userbot-docker-compose.yaml @@ -12,26 +12,9 @@ services: - ./volumes/data_forust:/app/data - ./Downloads:/app/downloads - ./volumes/logs_forust-:/app/logs - networks: - - userbot_network dns: - 8.8.8.8 - 1.1.1.1 - develop: - watch: - - action: sync - path: modules - target: /app/modules - - action: sync - path: utils - target: /app/utils - - action: sync - path: main.py - target: /app/main.py - - action: rebuild - path: .env - - action: rebuild - path: .env.forust userbot_anna: build: @@ -46,8 +29,6 @@ services: - ./volumes/data_anna:/app/data - ./Downloads:/app/downloads - ./volumes/logs_anna:/app/logs - networks: - - userbot_network dns: - 8.8.8.8 - 1.1.1.1 From a30bda4940eb394e3bdd66d673d699c2a171b79f Mon Sep 17 00:00:00 2001 From: mr-forust Date: Thu, 13 Nov 2025 04:00:23 +0100 Subject: [PATCH 07/23] Renamed userbot's dockercompose for easy access --- userbot-docker-compose.yaml => docker-compose.yaml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename userbot-docker-compose.yaml => docker-compose.yaml (100%) diff --git a/userbot-docker-compose.yaml b/docker-compose.yaml similarity index 100% rename from userbot-docker-compose.yaml rename to docker-compose.yaml From ce66a546f14905a594dd6ada4919a8972399d50d Mon Sep 17 00:00:00 2001 From: mr-forust Date: Thu, 27 Nov 2025 18:58:17 +0100 Subject: [PATCH 08/23] chore: update gitignore, add translations --- .gitignore | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index 64097b5..fccd91b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .DS_Store .gitattributes + .vscode /modules/__pycache__/ __pycache__/ @@ -12,14 +13,4 @@ __pycache__/ .venv/ /downloads/ /Downloads/ -/modules/custom_modules -.idea config.ini -unknown_errors.txt -moonlogs.txt -thumb.jpg -antipm_pic.jpg -musicbot/ -.trunk/ -previous_profiles/ -.python-version \ No newline at end of file From a699ceb935575faeb9c3a4d59d153c7e1c370063 Mon Sep 17 00:00:00 2001 From: mr-forust Date: Tue, 9 Dec 2025 21:45:24 +0100 Subject: [PATCH 09/23] refactor: userbots' compose-files cleanup --- docker-compose.yaml => compose.yaml | 31 +++-------------------------- 1 file changed, 3 insertions(+), 28 deletions(-) rename docker-compose.yaml => compose.yaml (52%) diff --git a/docker-compose.yaml b/compose.yaml similarity index 52% rename from docker-compose.yaml rename to compose.yaml index 01a1109..9cf008f 100644 --- a/docker-compose.yaml +++ b/compose.yaml @@ -1,9 +1,8 @@ services: - userbot_forust: + forust: build: context: . dockerfile: Dockerfile - container_name: userbot_forust restart: unless-stopped env_file: - .env @@ -16,11 +15,10 @@ services: - 8.8.8.8 - 1.1.1.1 - userbot_anna: + anna: build: context: . dockerfile: Dockerfile - container_name: userbot_anna restart: unless-stopped env_file: - .env @@ -31,27 +29,4 @@ services: - ./volumes/logs_anna:/app/logs dns: - 8.8.8.8 - - 1.1.1.1 - develop: - watch: - - action: sync - path: modules - target: /app/modules - - action: sync - path: utils - target: /app/utils - - action: sync - path: main.py - target: /app/main.py - - action: rebuild - path: .env - - action: rebuild - path: .env.anna - - -volumes: - downloads: - -networks: - userbot_network: - driver: bridge + - 1.1.1.1 \ No newline at end of file From cd0ce06246653aa11a62f422676bc9e1e0256310 Mon Sep 17 00:00:00 2001 From: mr-forust Date: Thu, 21 May 2026 22:12:33 +0200 Subject: [PATCH 10/23] refactor: imporved layer caching for dockerfile using existing built image for account 2 --- .dockerignore | 10 ++++++++++ Dockerfile | 24 ++++++++++++++++-------- compose.yaml | 5 ++--- pyproject.toml | 2 +- 4 files changed, 29 insertions(+), 12 deletions(-) diff --git a/.dockerignore b/.dockerignore index ed7cba4..f836c8d 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1 +1,11 @@ .unused +Downloads +.venv +volumes +.env +.env.* +my_account.session +.gitignore +README.md +.git +uv.lock \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 8f85225..0091949 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,10 +1,18 @@ -FROM python:3.11 +FROM python:3.11-slim + WORKDIR /app + +RUN apt-get update && apt-get install -y --no-install-recommends \ + git wget ffmpeg mediainfo && \ + rm -rf /var/lib/apt/lists/* + +COPY requirements.txt /app/ + +RUN python -m pip install --no-cache-dir --upgrade pip && \ + python -m pip install --no-cache-dir -r /app/requirements.txt + COPY . /app -RUN apt-get -qq update && apt-get -qq install -y git wget ffmpeg mediainfo\ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* -RUN python -m venv --copies /opt/venv -ENV PATH="/opt/venv/bin:$PATH" -RUN pip install --no-cache-dir -r requirements.txt -CMD ["python", "main.py"] + +ENV PYTHONUNBUFFERED=1 + +CMD ["python", "-u", "main.py"] \ No newline at end of file diff --git a/compose.yaml b/compose.yaml index 9cf008f..8714b67 100644 --- a/compose.yaml +++ b/compose.yaml @@ -3,6 +3,7 @@ services: build: context: . dockerfile: Dockerfile + image: userbot:latest restart: unless-stopped env_file: - .env @@ -16,9 +17,7 @@ services: - 1.1.1.1 anna: - build: - context: . - dockerfile: Dockerfile + image: userbot:latest restart: unless-stopped env_file: - .env diff --git a/pyproject.toml b/pyproject.toml index d97068a..7850b35 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ name = "userbot" version = "0.1.0" description = "Add your description here" readme = "README.md" -requires-python = ">=3.13" +requires-python = ">=3.11" dependencies = [ "aiofiles>=25.1.0", "aiohttp>=3.13.2", From 56e5d79e3e8571b83facf2dbcdf5f3fd2b3b98f2 Mon Sep 17 00:00:00 2001 From: mr-forust Date: Thu, 21 May 2026 22:14:29 +0200 Subject: [PATCH 11/23] refactor: improved error handling, timeouts and use temp files --- main.py | 18 +++++++++++++----- utils/config.py | 2 +- utils/scripts.py | 24 ++++++++++++------------ 3 files changed, 26 insertions(+), 18 deletions(-) diff --git a/main.py b/main.py index cc823b1..8e76c29 100644 --- a/main.py +++ b/main.py @@ -90,11 +90,19 @@ def load_missing_modules(): os.makedirs(custom_modules_path, exist_ok=True) try: - f = requests.get( - "https://raw.githubusercontent.com/The-MoonTg-project/custom_modules/main/full.txt" - ).text - except Exception: - logging.error("Failed to fetch custom modules list") + resp = requests.get( + "https://raw.githubusercontent.com/The-MoonTg-project/custom_modules/main/full.txt", + timeout=10, + ) + if not resp.ok: + logging.error( + "Failed to fetch custom modules list: HTTP %s", + resp.status_code, + ) + return + f = resp.text + except Exception as e: + logging.error("Failed to fetch custom modules list: %s", e) return modules_dict = { line.split("/")[-1].split()[0]: line.strip() for line in f.splitlines() diff --git a/utils/config.py b/utils/config.py index 4204e68..a8879d5 100644 --- a/utils/config.py +++ b/utils/config.py @@ -1,8 +1,8 @@ import os import environs +env = environs.Env() try: - env = environs.Env() env.read_env("./.env") except FileNotFoundError: print("No .env file found, using os.environ.") diff --git a/utils/scripts.py b/utils/scripts.py index 6dc7d44..cb9604e 100644 --- a/utils/scripts.py +++ b/utils/scripts.py @@ -22,6 +22,7 @@ import re import shlex import subprocess import sys +import tempfile import time import traceback from PIL import Image @@ -85,13 +86,17 @@ async def edit_or_send_as_file( return if len(tex) > 1024: await message.edit("OutPut is Too Large, Sending As File!") - file_names = f"{file_name}.txt" - with open(file_names, "w") as fn: + with tempfile.NamedTemporaryFile( + "w", delete=False, suffix=".txt", prefix=f"{file_name}_" + ) as fn: fn.write(tex) - await client.send_document(message.chat.id, file_names, caption=caption) - await message.delete() - if os.path.exists(file_names): - os.remove(file_names) + temp_path = fn.name + try: + await client.send_document(message.chat.id, temp_path, caption=caption) + await message.delete() + finally: + if os.path.exists(temp_path): + os.remove(temp_path) return return await message.edit(tex) @@ -249,12 +254,7 @@ def is_admin(func): chat_member = await client.get_chat_member( message.chat.id, message.from_user.id ) - chat_admins = [] - async for member in client.get_chat_members( - message.chat.id, filter=ChatMembersFilter.ADMINISTRATORS - ): - chat_admins.append(member) - if chat_member in chat_admins: + if chat_member.status in ("administrator", "creator"): return await func(client, message) await message.edit("You need to be an admin to perform this action.") except UserNotParticipant: From bed58c84ef458fa86040b3d3a4d8e8de02cf71a1 Mon Sep 17 00:00:00 2001 From: mr-forust Date: Mon, 25 May 2026 01:43:46 +0200 Subject: [PATCH 12/23] refactor: update .dockerignore pull_policy never to use local images --- .dockerignore | 3 ++- compose.yaml | 4 ++++ requirements.txt | 2 ++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.dockerignore b/.dockerignore index f836c8d..a2b070b 100644 --- a/.dockerignore +++ b/.dockerignore @@ -8,4 +8,5 @@ my_account.session .gitignore README.md .git -uv.lock \ No newline at end of file +uv.lock +.deepsource.toml \ No newline at end of file diff --git a/compose.yaml b/compose.yaml index 8714b67..f01019b 100644 --- a/compose.yaml +++ b/compose.yaml @@ -4,6 +4,7 @@ services: context: . dockerfile: Dockerfile image: userbot:latest + pull_policy: never restart: unless-stopped env_file: - .env @@ -18,6 +19,9 @@ services: anna: image: userbot:latest + pull_policy: never + depends_on: + - forust restart: unless-stopped env_file: - .env diff --git a/requirements.txt b/requirements.txt index 7e26f79..a4c4499 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,3 +18,5 @@ aiohttp aiofiles pySmartDL qrcode +bottle +dulwich \ No newline at end of file From cb40b10ecf6fca6ace7df2741dfcbfa634376c3a Mon Sep 17 00:00:00 2001 From: mr-forust Date: Sun, 7 Jun 2026 14:37:57 +0200 Subject: [PATCH 13/23] chore: add gitea container registry compose support for localy builded apps --- compose.yaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/compose.yaml b/compose.yaml index f01019b..78a991d 100644 --- a/compose.yaml +++ b/compose.yaml @@ -3,8 +3,8 @@ services: build: context: . dockerfile: Dockerfile - image: userbot:latest - pull_policy: never + image: gcr.forust.xyz/forust/userbot:latest + pull_policy: build restart: unless-stopped env_file: - .env @@ -12,14 +12,14 @@ services: volumes: - ./volumes/data_forust:/app/data - ./Downloads:/app/downloads - - ./volumes/logs_forust-:/app/logs + - ./volumes/logs_forust:/app/logs dns: - 8.8.8.8 - 1.1.1.1 anna: - image: userbot:latest - pull_policy: never + image: gcr.forust.xyz/forust/userbot:latest + pull_policy: build depends_on: - forust restart: unless-stopped @@ -32,4 +32,4 @@ services: - ./volumes/logs_anna:/app/logs dns: - 8.8.8.8 - - 1.1.1.1 \ No newline at end of file + - 1.1.1.1 From 4f01cdac317833875020691facbc42e74fc843f8 Mon Sep 17 00:00:00 2001 From: mr-forust Date: Sun, 7 Jun 2026 19:40:52 +0200 Subject: [PATCH 14/23] fix: removed git checkout (was destructive) - Remove git init/fetch/checkout from utils/misc.py - Hardcode userbot_version to 2.5.0 --- modules/support.py | 28 ++++++++++++++-------------- utils/misc.py | 34 +++++----------------------------- 2 files changed, 19 insertions(+), 43 deletions(-) diff --git a/modules/support.py b/modules/support.py index 3fd4257..9c5c5d6 100644 --- a/modules/support.py +++ b/modules/support.py @@ -59,26 +59,26 @@ async def version(client: Client, message: Message): await message.delete() - remote_url = list(gitrepo.remote().urls)[0] - commit_time = ( - datetime.datetime.fromtimestamp(gitrepo.head.commit.committed_date) - .astimezone(datetime.timezone.utc) - .strftime("%Y-%m-%d %H:%M:%S %Z") - ) + if gitrepo is not None: + remote_url = list(gitrepo.remote().urls)[0] + commit_time = ( + datetime.datetime.fromtimestamp(gitrepo.head.commit.committed_date) + .astimezone(datetime.timezone.utc) + .strftime("%Y-%m-%d %H:%M:%S %Z") + ) + git_info = ( + f"\nBranch: {gitrepo.active_branch}\n" + if gitrepo.active_branch != "master" else "\n" + ) + f"Commit: " f"{gitrepo.head.commit.hexsha[:7]} by {gitrepo.head.commit.author.name}\n" f"Commit time: {commit_time}" + else: + git_info = "" await message.reply( f"Moon Userbot version: {userbot_version}\n" f"Changelog in channel.\n" f"Changelog written by " f"Abhi\n\n" - + ( - f"Branch: {gitrepo.active_branch}\n" - if gitrepo.active_branch != "master" - else "" - ) - + f"Commit: " - f"{gitrepo.head.commit.hexsha[:7]} by {gitrepo.head.commit.author.name}\n" - f"Commit time: {commit_time}", + f"{git_info}", ) diff --git a/utils/misc.py b/utils/misc.py index 2800ccf..4b86d03 100644 --- a/utils/misc.py +++ b/utils/misc.py @@ -1,19 +1,3 @@ -# Moon-Userbot - telegram userbot -# Copyright (C) 2020-present Moon Userbot Organization -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - from sys import version_info from .db import db import git @@ -37,19 +21,11 @@ prefix = db.get("core.main", "prefix", ".") try: gitrepo = git.Repo(".") -except git.exc.InvalidGitRepositoryError: - repo = git.Repo.init() - origin = repo.create_remote( - "origin", "https://github.com/The-MoonTg-project/Moon-Userbot" - ) - origin.fetch() - repo.create_head("main", origin.refs.main) - repo.heads.main.set_tracking_branch(origin.refs.main) - repo.heads.main.checkout(True) - gitrepo = git.Repo(".") +except (git.exc.InvalidGitRepositoryError, git.exc.NoSuchPathError): + gitrepo = None -if len(gitrepo.tags) > 0: +if gitrepo is not None and len(gitrepo.tags) > 0: commits_since_tag = list(gitrepo.iter_commits(f"{gitrepo.tags[-1].name}..HEAD")) + userbot_version = f"2.5.{len(commits_since_tag)}" else: - commits_since_tag = [] -userbot_version = f"2.5.{len(commits_since_tag)}" + userbot_version = "2.5.0" From 444bb97f8efee3234b3b3564478f57ff8c4c616a Mon Sep 17 00:00:00 2001 From: mr-forust Date: Sun, 7 Jun 2026 19:41:28 +0200 Subject: [PATCH 15/23] feat: add userbot k8s deployment method - Kustomize: base + overlays/dev + overlays/prod --- .env.account.example | 3 + .env.example | 14 +++ .gitignore | 3 + k8s/base/common-config.yaml | 7 ++ k8s/base/kustomization.yaml | 9 ++ k8s/base/userbots.yaml | 114 +++++++++++++++++++++++++ k8s/overlays/dev/kustomization.yaml | 8 ++ k8s/overlays/dev/patch-downloads.yaml | 35 ++++++++ k8s/overlays/prod/kustomization.yaml | 8 ++ k8s/overlays/prod/patch-downloads.yaml | 35 ++++++++ 10 files changed, 236 insertions(+) create mode 100644 .env.account.example create mode 100644 .env.example create mode 100644 k8s/base/common-config.yaml create mode 100644 k8s/base/kustomization.yaml create mode 100644 k8s/base/userbots.yaml create mode 100644 k8s/overlays/dev/kustomization.yaml create mode 100644 k8s/overlays/dev/patch-downloads.yaml create mode 100644 k8s/overlays/prod/kustomization.yaml create mode 100644 k8s/overlays/prod/patch-downloads.yaml diff --git a/.env.account.example b/.env.account.example new file mode 100644 index 0000000..583a44e --- /dev/null +++ b/.env.account.example @@ -0,0 +1,3 @@ +API_ID="" +API_HASH="" +STRINGSESSION="" \ No newline at end of file diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..b1ba859 --- /dev/null +++ b/.env.example @@ -0,0 +1,14 @@ +# apiflash api key only for webshot plugin +APIFLASH_KEY="" +# gemini api key only for gemini plugin +GEMINI_KEY="" +# VT api key only for VirusTotal plugin +VT_KEY="" +# rmbg api key only for removebg plugin +RMBG_KEY="" +# cohere api key only for cohere plugin +COHERE_KEY="" +# sqlite/sqlite3 or mongo/mongodb +DATABASE_TYPE="" +# file name for sqlite3, database name for mongodb +DATABASE_NAME="" diff --git a/.gitignore b/.gitignore index fccd91b..b166610 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,6 @@ __pycache__/ /downloads/ /Downloads/ config.ini + +k8s/*/*secret*.yaml +!k8s/account-secrets.yaml.example \ No newline at end of file diff --git a/k8s/base/common-config.yaml b/k8s/base/common-config.yaml new file mode 100644 index 0000000..4a11507 --- /dev/null +++ b/k8s/base/common-config.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: userbot-common-config +data: + DATABASE_TYPE: "" + DATABASE_NAME: "" diff --git a/k8s/base/kustomization.yaml b/k8s/base/kustomization.yaml new file mode 100644 index 0000000..4af250f --- /dev/null +++ b/k8s/base/kustomization.yaml @@ -0,0 +1,9 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: +- userbots.yaml +- common-secret.yaml +- common-config.yaml +- forust-secrets.yaml +- anna-secrets.yaml diff --git a/k8s/base/userbots.yaml b/k8s/base/userbots.yaml new file mode 100644 index 0000000..7d35212 --- /dev/null +++ b/k8s/base/userbots.yaml @@ -0,0 +1,114 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: forust-userbot-deployment + labels: + app: forust-userbot +spec: + replicas: 1 + selector: + matchLabels: + app: forust-userbot + template: + metadata: + labels: + app: forust-userbot + spec: + containers: + - name: forust-userbot + image: gcr.forust.xyz/forust/userbot:latest + imagePullPolicy: Always + resources: + limits: + memory: "512Mi" + cpu: "500m" + requests: + memory: "256Mi" + cpu: "100m" + envFrom: + - secretRef: + name: userbot-common-secrets + - configMapRef: + name: userbot-common-config + - secretRef: + name: userbot-forust-secrets + volumeMounts: + - name: forust-storage + mountPath: /app/data + subPath: data + - name: forust-storage + mountPath: /app/logs + subPath: logs + volumes: + - name: forust-storage + persistentVolumeClaim: + claimName: forust-pvc +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: forust-pvc +spec: + resources: + requests: + storage: 1Gi + accessModes: + - ReadWriteOnce + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: anna-userbot-deployment + labels: + app: anna-userbot +spec: + replicas: 1 + selector: + matchLabels: + app: anna-userbot + template: + metadata: + labels: + app: anna-userbot + spec: + containers: + - name: anna-userbot + image: gcr.forust.xyz/forust/userbot:latest + imagePullPolicy: Always + resources: + limits: + memory: "512Mi" + cpu: "500m" + requests: + memory: "256Mi" + cpu: "100m" + envFrom: + - secretRef: + name: userbot-common-secrets + - configMapRef: + name: userbot-common-config + - secretRef: + name: userbot-anna-secrets + volumeMounts: + - name: anna-storage + mountPath: /app/data + subPath: data + - name: anna-storage + mountPath: /app/logs + subPath: logs + volumes: + - name: anna-storage + persistentVolumeClaim: + claimName: anna-pvc +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: anna-pvc +spec: + resources: + requests: + storage: 1Gi + accessModes: + - ReadWriteOnce diff --git a/k8s/overlays/dev/kustomization.yaml b/k8s/overlays/dev/kustomization.yaml new file mode 100644 index 0000000..a06a318 --- /dev/null +++ b/k8s/overlays/dev/kustomization.yaml @@ -0,0 +1,8 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: +- ../../base + +patches: +- path: patch-downloads.yaml diff --git a/k8s/overlays/dev/patch-downloads.yaml b/k8s/overlays/dev/patch-downloads.yaml new file mode 100644 index 0000000..bf80f37 --- /dev/null +++ b/k8s/overlays/dev/patch-downloads.yaml @@ -0,0 +1,35 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: forust-userbot-deployment +spec: + template: + spec: + containers: + - name: forust-userbot + volumeMounts: + - name: downloads + mountPath: /app/downloads + volumes: + - name: downloads + hostPath: + path: /home/user/projects/homelab/userbot/Downloads + type: DirectoryOrCreate +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: anna-userbot-deployment +spec: + template: + spec: + containers: + - name: anna-userbot + volumeMounts: + - name: downloads + mountPath: /app/downloads + volumes: + - name: downloads + hostPath: + path: /home/user/projects/homelab/userbot/Downloads + type: DirectoryOrCreate diff --git a/k8s/overlays/prod/kustomization.yaml b/k8s/overlays/prod/kustomization.yaml new file mode 100644 index 0000000..a06a318 --- /dev/null +++ b/k8s/overlays/prod/kustomization.yaml @@ -0,0 +1,8 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: +- ../../base + +patches: +- path: patch-downloads.yaml diff --git a/k8s/overlays/prod/patch-downloads.yaml b/k8s/overlays/prod/patch-downloads.yaml new file mode 100644 index 0000000..b691a80 --- /dev/null +++ b/k8s/overlays/prod/patch-downloads.yaml @@ -0,0 +1,35 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: forust-userbot-deployment +spec: + template: + spec: + containers: + - name: forust-userbot + volumeMounts: + - name: downloads + mountPath: /app/downloads + volumes: + - name: downloads + hostPath: + path: /srv/homelab/userbot/Downloads + type: DirectoryOrCreate +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: anna-userbot-deployment +spec: + template: + spec: + containers: + - name: anna-userbot + volumeMounts: + - name: downloads + mountPath: /app/downloads + volumes: + - name: downloads + hostPath: + path: /srv/homelab/userbot/Downloads + type: DirectoryOrCreate From b7854447af761595fcc690c21a328d9926acb10a Mon Sep 17 00:00:00 2001 From: mr-forust Date: Tue, 9 Jun 2026 12:59:36 +0200 Subject: [PATCH 16/23] lint: yaml spaces and tabs --- k8s/base/kustomization.yaml | 10 +-- k8s/base/userbots.yaml | 108 ++++++++++++------------- k8s/overlays/dev/kustomization.yaml | 4 +- k8s/overlays/dev/patch-downloads.yaml | 32 ++++---- k8s/overlays/prod/kustomization.yaml | 4 +- k8s/overlays/prod/patch-downloads.yaml | 32 ++++---- 6 files changed, 95 insertions(+), 95 deletions(-) diff --git a/k8s/base/kustomization.yaml b/k8s/base/kustomization.yaml index 4af250f..ea5fa72 100644 --- a/k8s/base/kustomization.yaml +++ b/k8s/base/kustomization.yaml @@ -2,8 +2,8 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: -- userbots.yaml -- common-secret.yaml -- common-config.yaml -- forust-secrets.yaml -- anna-secrets.yaml + - userbots.yaml + - common-secret.yaml + - common-config.yaml + - forust-secrets.yaml + - anna-secrets.yaml diff --git a/k8s/base/userbots.yaml b/k8s/base/userbots.yaml index 7d35212..4f10ce4 100644 --- a/k8s/base/userbots.yaml +++ b/k8s/base/userbots.yaml @@ -15,34 +15,34 @@ spec: app: forust-userbot spec: containers: - - name: forust-userbot - image: gcr.forust.xyz/forust/userbot:latest - imagePullPolicy: Always - resources: - limits: - memory: "512Mi" - cpu: "500m" - requests: - memory: "256Mi" - cpu: "100m" - envFrom: - - secretRef: - name: userbot-common-secrets - - configMapRef: - name: userbot-common-config - - secretRef: - name: userbot-forust-secrets - volumeMounts: - - name: forust-storage - mountPath: /app/data - subPath: data - - name: forust-storage - mountPath: /app/logs - subPath: logs + - name: forust-userbot + image: gcr.forust.xyz/forust/userbot:latest + imagePullPolicy: Always + resources: + limits: + memory: "512Mi" + cpu: "500m" + requests: + memory: "256Mi" + cpu: "100m" + envFrom: + - secretRef: + name: userbot-common-secrets + - configMapRef: + name: userbot-common-config + - secretRef: + name: userbot-forust-secrets + volumeMounts: + - name: forust-storage + mountPath: /app/data + subPath: data + - name: forust-storage + mountPath: /app/logs + subPath: logs volumes: - - name: forust-storage - persistentVolumeClaim: - claimName: forust-pvc + - name: forust-storage + persistentVolumeClaim: + claimName: forust-pvc --- apiVersion: v1 kind: PersistentVolumeClaim @@ -73,34 +73,34 @@ spec: app: anna-userbot spec: containers: - - name: anna-userbot - image: gcr.forust.xyz/forust/userbot:latest - imagePullPolicy: Always - resources: - limits: - memory: "512Mi" - cpu: "500m" - requests: - memory: "256Mi" - cpu: "100m" - envFrom: - - secretRef: - name: userbot-common-secrets - - configMapRef: - name: userbot-common-config - - secretRef: - name: userbot-anna-secrets - volumeMounts: - - name: anna-storage - mountPath: /app/data - subPath: data - - name: anna-storage - mountPath: /app/logs - subPath: logs + - name: anna-userbot + image: gcr.forust.xyz/forust/userbot:latest + imagePullPolicy: Always + resources: + limits: + memory: "512Mi" + cpu: "500m" + requests: + memory: "256Mi" + cpu: "100m" + envFrom: + - secretRef: + name: userbot-common-secrets + - configMapRef: + name: userbot-common-config + - secretRef: + name: userbot-anna-secrets + volumeMounts: + - name: anna-storage + mountPath: /app/data + subPath: data + - name: anna-storage + mountPath: /app/logs + subPath: logs volumes: - - name: anna-storage - persistentVolumeClaim: - claimName: anna-pvc + - name: anna-storage + persistentVolumeClaim: + claimName: anna-pvc --- apiVersion: v1 kind: PersistentVolumeClaim diff --git a/k8s/overlays/dev/kustomization.yaml b/k8s/overlays/dev/kustomization.yaml index a06a318..1e8df73 100644 --- a/k8s/overlays/dev/kustomization.yaml +++ b/k8s/overlays/dev/kustomization.yaml @@ -2,7 +2,7 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: -- ../../base + - ../../base patches: -- path: patch-downloads.yaml + - path: patch-downloads.yaml diff --git a/k8s/overlays/dev/patch-downloads.yaml b/k8s/overlays/dev/patch-downloads.yaml index bf80f37..0dcab07 100644 --- a/k8s/overlays/dev/patch-downloads.yaml +++ b/k8s/overlays/dev/patch-downloads.yaml @@ -6,15 +6,15 @@ spec: template: spec: containers: - - name: forust-userbot - volumeMounts: - - name: downloads - mountPath: /app/downloads + - name: forust-userbot + volumeMounts: + - name: downloads + mountPath: /app/downloads volumes: - - name: downloads - hostPath: - path: /home/user/projects/homelab/userbot/Downloads - type: DirectoryOrCreate + - name: downloads + hostPath: + path: /home/user/projects/homelab/userbot/Downloads + type: DirectoryOrCreate --- apiVersion: apps/v1 kind: Deployment @@ -24,12 +24,12 @@ spec: template: spec: containers: - - name: anna-userbot - volumeMounts: - - name: downloads - mountPath: /app/downloads + - name: anna-userbot + volumeMounts: + - name: downloads + mountPath: /app/downloads volumes: - - name: downloads - hostPath: - path: /home/user/projects/homelab/userbot/Downloads - type: DirectoryOrCreate + - name: downloads + hostPath: + path: /home/user/projects/homelab/userbot/Downloads + type: DirectoryOrCreate diff --git a/k8s/overlays/prod/kustomization.yaml b/k8s/overlays/prod/kustomization.yaml index a06a318..1e8df73 100644 --- a/k8s/overlays/prod/kustomization.yaml +++ b/k8s/overlays/prod/kustomization.yaml @@ -2,7 +2,7 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: -- ../../base + - ../../base patches: -- path: patch-downloads.yaml + - path: patch-downloads.yaml diff --git a/k8s/overlays/prod/patch-downloads.yaml b/k8s/overlays/prod/patch-downloads.yaml index b691a80..af06d6a 100644 --- a/k8s/overlays/prod/patch-downloads.yaml +++ b/k8s/overlays/prod/patch-downloads.yaml @@ -6,15 +6,15 @@ spec: template: spec: containers: - - name: forust-userbot - volumeMounts: - - name: downloads - mountPath: /app/downloads + - name: forust-userbot + volumeMounts: + - name: downloads + mountPath: /app/downloads volumes: - - name: downloads - hostPath: - path: /srv/homelab/userbot/Downloads - type: DirectoryOrCreate + - name: downloads + hostPath: + path: /srv/homelab/userbot/Downloads + type: DirectoryOrCreate --- apiVersion: apps/v1 kind: Deployment @@ -24,12 +24,12 @@ spec: template: spec: containers: - - name: anna-userbot - volumeMounts: - - name: downloads - mountPath: /app/downloads + - name: anna-userbot + volumeMounts: + - name: downloads + mountPath: /app/downloads volumes: - - name: downloads - hostPath: - path: /srv/homelab/userbot/Downloads - type: DirectoryOrCreate + - name: downloads + hostPath: + path: /srv/homelab/userbot/Downloads + type: DirectoryOrCreate From bb821e138ab962591ad32f953f0b23a3d95de385 Mon Sep 17 00:00:00 2001 From: mr-forust Date: Wed, 10 Jun 2026 19:37:16 +0200 Subject: [PATCH 17/23] chore(k8): adjusted system resources requests and limits based on manual monitoring --- k8s/base/userbots.yaml | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/k8s/base/userbots.yaml b/k8s/base/userbots.yaml index 4f10ce4..0bb70a6 100644 --- a/k8s/base/userbots.yaml +++ b/k8s/base/userbots.yaml @@ -21,10 +21,7 @@ spec: resources: limits: memory: "512Mi" - cpu: "500m" - requests: - memory: "256Mi" - cpu: "100m" + cpu: "80m" envFrom: - secretRef: name: userbot-common-secrets @@ -78,11 +75,11 @@ spec: imagePullPolicy: Always resources: limits: - memory: "512Mi" - cpu: "500m" + memory: "1.5Gi" + cpu: "300m" requests: - memory: "256Mi" - cpu: "100m" + memory: "512Mi" + cpu: "80m" envFrom: - secretRef: name: userbot-common-secrets From 20b8c93275d240f03a1a14a9ee544068afbfe14b Mon Sep 17 00:00:00 2001 From: mr-forust Date: Wed, 10 Jun 2026 19:40:45 +0200 Subject: [PATCH 18/23] chore(k8s): make dockmon statefulset + adjusted userbot sys reqs --- k8s/base/userbots.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/k8s/base/userbots.yaml b/k8s/base/userbots.yaml index 0bb70a6..74618d9 100644 --- a/k8s/base/userbots.yaml +++ b/k8s/base/userbots.yaml @@ -20,6 +20,9 @@ spec: imagePullPolicy: Always resources: limits: + memory: "1.5Gi" + cpu: "300m" + requests: memory: "512Mi" cpu: "80m" envFrom: From 95cec59263d2f9c53fddb1e6d892043c1d31cd40 Mon Sep 17 00:00:00 2001 From: mr-forust Date: Fri, 19 Jun 2026 12:03:13 +0200 Subject: [PATCH 19/23] chore(userbot): apply prettier formatting across manifests --- .unused/app.json | 8 +------- .unused/render.yaml | 30 +++++++++++++++--------------- 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/.unused/app.json b/.unused/app.json index 56cd2ea..f0d3493 100644 --- a/.unused/app.json +++ b/.unused/app.json @@ -2,13 +2,7 @@ "name": "Moon-userbot", "description": "A Simple, Fast, Customizable, Ai powered Userbot for Telegram with most easiest installation.", "logo": "https://camo.githubusercontent.com/1efdfa6416b3cd08471d865ca9ebf0fbdd38602ea95425f18a9bec6aeeefe49b/68747470733a2f2f74656c656772612e70682f66696c652f3063333763326662306631393463633163303334342e6a7067", - "keywords": [ - "telegram", - "Moon-userbot", - "bot", - "python", - "pyrogram" - ], + "keywords": ["telegram", "Moon-userbot", "bot", "python", "pyrogram"], "env": { "API_ID": { "description": "Get it from my.telegram.org", diff --git a/.unused/render.yaml b/.unused/render.yaml index 812d213..1172ce1 100644 --- a/.unused/render.yaml +++ b/.unused/render.yaml @@ -1,17 +1,17 @@ services: -- type: worker - name: Moon-Userbot - runtime: docker - repo: https://github.com/The-MoonTg-project/Moon-Userbot - plan: starter - envVars: - - key: STABILITY_KEY - sync: false - - key: CLARIFAI_PAT - sync: false - - key: .env - sync: false - region: oregon - dockerContext: . - dockerfilePath: ./Dockerfile + - type: worker + name: Moon-Userbot + runtime: docker + repo: https://github.com/The-MoonTg-project/Moon-Userbot + plan: starter + envVars: + - key: STABILITY_KEY + sync: false + - key: CLARIFAI_PAT + sync: false + - key: .env + sync: false + region: oregon + dockerContext: . + dockerfilePath: ./Dockerfile version: "1" From 7ba6bc44f2b8be0fe7173699fccedc43f7ffca84 Mon Sep 17 00:00:00 2001 From: mr-forust Date: Fri, 19 Jun 2026 12:19:01 +0200 Subject: [PATCH 20/23] chore(userbot): apply ruff check --fix and ruff format - ruff check --fix: 210 auto-fixed errors (import sorting, trailing whitespace, unused imports, f-string fixups, deprecated annotations) - ruff format: 104 files reformatted to consistent style - 268 non-auto-fixable issues remain (S113 requests timeout, etc.) --- .unused/app.py | 6 +- images/icons.py | 77 ++- images/imgur.py | 37 +- images/ncode.py | 44 +- images/pinterest.py | 59 +- images/risearch.py | 59 +- images/unsplash.py | 80 ++- images/unsplash2.py | 61 +-- install.py | 48 +- main.py | 111 ++-- modules/1000-7.py | 16 +- modules/admintool.py | 197 ++++--- modules/admlist.py | 188 +++---- modules/afk.py | 58 +- modules/aimage.py | 83 ++- modules/amogus.py | 42 +- modules/amongus.py | 140 ++--- modules/animations.py | 943 ++++++++++++++++---------------- modules/anime/anilist.py | 175 +++--- modules/anime/anime.py | 29 +- modules/anime/neko.py | 28 +- modules/aniquotes.py | 21 +- modules/antipm.py | 204 +++---- modules/blackbox.py | 113 ++-- modules/calculator.py | 33 +- modules/cdxl.py | 33 +- modules/chatbot.py | 59 +- modules/circle.py | 117 ++-- modules/clear_notifs.py | 35 +- modules/cohere.py | 96 ++-- modules/demotivator.py | 77 +-- modules/destroy.py | 44 +- modules/dice.py | 23 +- modules/direct.py | 264 +++++---- modules/duckduckgo.py | 30 +- modules/durov.py | 9 +- modules/example.py | 23 +- modules/f.py | 64 +-- modules/fakeactions.py | 80 ++- modules/filters.py | 133 ++--- modules/fliptext.py | 169 +++--- modules/flux.py | 39 +- modules/gemini.py | 46 +- modules/google.py | 25 +- modules/hearts.py | 28 +- modules/help.py | 44 +- modules/huggingface.py | 212 +++---- modules/id.py | 51 +- modules/joindate.py | 54 +- modules/kokodrilo_explodando.py | 45 +- modules/leave_chat.py | 13 +- modules/loader.py | 284 +++++----- modules/markitdown.py | 24 +- modules/mention.py | 31 +- modules/mirror_flip.py | 28 +- modules/misc/autobackup.py | 42 +- modules/misc/autofwd.py | 115 ++-- modules/misc/backup.py | 181 +++--- modules/misc/cama.py | 96 ++-- modules/misc/mlog.py | 71 ++- modules/misc/prayer.py | 66 +-- modules/misc/safone.py | 378 ++++++------- modules/misc/sarethai.py | 440 +++++++-------- modules/misc/search.py | 33 +- modules/misc/summary.py | 26 +- modules/misc/switch.py | 21 +- modules/misc/transcribeyt.py | 78 ++- modules/notes.py | 108 ++-- modules/open.py | 95 ++-- modules/pdf2md.py | 59 +- modules/perfectrussian.py | 37 +- modules/ping.py | 9 +- modules/prefix.py | 29 +- modules/purge.py | 12 +- modules/python.py | 32 +- modules/reactionspam.py | 44 +- modules/removebg.py | 101 ++-- modules/say.py | 11 +- modules/sendmod.py | 23 +- modules/sessionkiller.py | 129 ++--- modules/sgb.py | 19 +- modules/shell.py | 25 +- modules/socialstalk.py | 154 +++--- modules/spam.py | 19 +- modules/spin.py | 100 ++-- modules/squotes.py | 268 ++++----- modules/stickers.py | 96 ++-- modules/support.py | 78 +-- modules/thumbnail.py | 19 +- modules/type.py | 13 +- modules/updater.py | 90 ++- modules/upl.py | 62 +-- modules/url.py | 151 +++-- modules/user_info.py | 37 +- modules/vt.py | 59 +- string_gen.py | 12 +- utils/config.py | 37 +- utils/conv.py | 34 +- utils/db.py | 96 ++-- utils/handlers.py | 627 ++++++++++----------- utils/misc.py | 28 +- utils/module.py | 28 +- utils/rentry.py | 111 ++-- utils/scripts.py | 226 ++++---- 104 files changed, 4338 insertions(+), 5319 deletions(-) diff --git a/.unused/app.py b/.unused/app.py index d70930e..65a84b2 100644 --- a/.unused/app.py +++ b/.unused/app.py @@ -3,10 +3,10 @@ from flask import Flask app = Flask(__name__) -@app.route("/") +@app.route('/') def hello_world(): - return "This is Moon" + return 'This is Moon' -if __name__ == "__main__": +if __name__ == '__main__': app.run() diff --git a/images/icons.py b/images/icons.py index ba24af5..a621147 100644 --- a/images/icons.py +++ b/images/icons.py @@ -1,83 +1,80 @@ -import re -import requests import random +import re from io import BytesIO + +import requests from bs4 import BeautifulSoup as bs - from pyrogram import Client, filters -from pyrogram.types import Message, InputMediaPhoto from pyrogram.errors import RPCError - +from pyrogram.types import InputMediaPhoto, Message from utils.misc import modules_help, prefix -@Client.on_message(filters.command("icon", prefix) & filters.me) +@Client.on_message(filters.command('icon', prefix) & filters.me) async def search_icon(_, message: Message): if not len(message.command) == 2: - return await message.edit_text( - "Please provide some text to search icons from Flaticon.com." - ) + return await message.edit_text('Please provide some text to search icons from Flaticon.com.') query = message.text.split(maxsplit=1)[1] - await message.edit_text("Searching for icons...") - search_query = query.replace(" ", "%20") - url = f"https://www.flaticon.com/search?word={search_query}" + await message.edit_text('Searching for icons...') + search_query = query.replace(' ', '%20') + url = f'https://www.flaticon.com/search?word={search_query}' try: html_content = requests.get(url).text - soup = bs(html_content, "html.parser") + soup = bs(html_content, 'html.parser') results = soup.find_all( - "img", - src=re.compile(r"https://cdn-icons-png.flaticon.com/128/[0-9]+/[0-9]+.png"), + 'img', + src=re.compile(r'https://cdn-icons-png.flaticon.com/128/[0-9]+/[0-9]+.png'), ) if not results: - return await message.edit("No results found.") + return await message.edit('No results found.') random.shuffle(results) icons = [] for i in range(5): - icons.append(results[i]["src"].replace("128", "512")) + icons.append(results[i]['src'].replace('128', '512')) for icon in icons: await message.reply_document(icon) return await message.delete() except Exception as e: - await message.edit(f"An error occurred: {e}") - print(f"Error: {e}") + await message.edit(f'An error occurred: {e}') + print(f'Error: {e}') -@Client.on_message(filters.command("freepik", prefix) & filters.me) +@Client.on_message(filters.command('freepik', prefix) & filters.me) async def freepik_search(client: Client, message: Message): - parts = message.text.split(" ", 1) + parts = message.text.split(' ', 1) if len(parts) < 2: - await message.edit_text("Please provide a search query!") + await message.edit_text('Please provide a search query!') return query = parts[1] limit = 5 - if " ; " in query: - match, limit_str = query.split(" ; ", 1) + if ' ; ' in query: + match, limit_str = query.split(' ; ', 1) try: limit = int(limit_str) except ValueError: - await message.edit_text("Invalid limit! Using the default value of 5.") + await message.edit_text('Invalid limit! Using the default value of 5.') else: match = query - match = match.replace(" ", "%20") - await message.edit_text("Searching Freepik...") + match = match.replace(' ', '%20') + await message.edit_text('Searching Freepik...') try: - url = f"https://www.freepik.com/api/regular/search?locale=en&term={match}" + url = f'https://www.freepik.com/api/regular/search?locale=en&term={match}' json_content = requests.get(url).json() results = [] - for i in json_content["items"]: - results.append(i["preview"]["url"]) + for i in json_content['items']: + results.append(i['preview']['url']) if results is None: - return await message.edit_text("No results found.") + return await message.edit_text('No results found.') random.shuffle(results) img_urls = results[:limit] @@ -89,27 +86,25 @@ async def freepik_search(client: Client, message: Message): media_group.append(InputMediaPhoto(media=BytesIO(icon.content))) if not media_group: - await message.edit_text("No images could be downloaded.") + await message.edit_text('No images could be downloaded.') return try: await client.send_media_group(chat_id=message.chat.id, media=media_group) except RPCError: - await message.edit_text( - "Failed to send some images. Retrying individually..." - ) + await message.edit_text('Failed to send some images. Retrying individually...') for media in media_group: try: await message.reply_photo(photo=media.media) except Exception as e: - await message.edit_text(f"Error sending image: {e}") + await message.edit_text(f'Error sending image: {e}') except Exception as e: - await message.edit_text(f"Failed to fetch data: {e}") - print(f"Error: {e}") + await message.edit_text(f'Failed to fetch data: {e}') + print(f'Error: {e}') -modules_help["icons"] = { - "icon [query]": "Search for icons on Flaticon.", - "freepik [query] [limit]": "Search for images on Freepik. Limit is optional and defaults to 5.", +modules_help['icons'] = { + 'icon [query]': 'Search for icons on Flaticon.', + 'freepik [query] [limit]': 'Search for images on Freepik. Limit is optional and defaults to 5.', } diff --git a/images/imgur.py b/images/imgur.py index d37ee76..94e486c 100644 --- a/images/imgur.py +++ b/images/imgur.py @@ -1,52 +1,47 @@ -import os import base64 + import requests - - from pyrogram import Client, filters from pyrogram.types import Message - from utils.misc import modules_help, prefix -@Client.on_message(filters.command(["imgur"], prefix) & filters.me) +@Client.on_message(filters.command(['imgur'], prefix) & filters.me) async def imgur(_, message: Message): # Check if a reply exists - msg = await message.edit_text("🎉 Please wait. trying to upload...") + msg = await message.edit_text('🎉 Please wait. trying to upload...') if message.reply_to_message and message.reply_to_message.photo: # Download the photo photo_path = await message.reply_to_message.download() # Read the photo file and encode as base64 - with open(photo_path, "rb") as file: + with open(photo_path, 'rb') as file: data = file.read() base64_data = base64.b64encode(data) # Set API endpoint and headers for image upload - url = "https://api.imgur.com/3/image" - headers = {"Authorization": "Client-ID a10ad04550b0648"} + url = 'https://api.imgur.com/3/image' + headers = {'Authorization': 'Client-ID a10ad04550b0648'} # Upload image to Imgur and get URL - response = requests.post(url, headers=headers, data={"image": base64_data}) + response = requests.post(url, headers=headers, data={'image': base64_data}) result = response.json() - await msg.edit_text(result["data"]["link"]) + await msg.edit_text(result['data']['link']) elif message.reply_to_message and message.reply_to_message.animation: # Download the animation (GIF) animation_path = await message.reply_to_message.download() # Read the animation file and encode as base64 - with open(animation_path, "rb") as file: + with open(animation_path, 'rb') as file: data = file.read() base64_data = base64.b64encode(data) # Set API endpoint and headers for animation upload - url = "https://api.imgur.com/3/image" - headers = {"Authorization": "Client-ID a10ad04550b0648"} + url = 'https://api.imgur.com/3/image' + headers = {'Authorization': 'Client-ID a10ad04550b0648'} # Upload animation to Imgur and get URL - response = requests.post(url, headers=headers, data={"image": base64_data}) + response = requests.post(url, headers=headers, data={'image': base64_data}) result = response.json() - await msg.edit_text(result["data"]["link"]) + await msg.edit_text(result['data']['link']) else: - await msg.edit_text( - "Please reply to a photo or animation (GIF) to upload to Imgur." - ) + await msg.edit_text('Please reply to a photo or animation (GIF) to upload to Imgur.') -modules_help["imgur"] = { - "imgur [img]*": "upload a photo or animation (GIF) to imgur", +modules_help['imgur'] = { + 'imgur [img]*': 'upload a photo or animation (GIF) to imgur', } diff --git a/images/ncode.py b/images/ncode.py index 65d1a00..9b80f06 100644 --- a/images/ncode.py +++ b/images/ncode.py @@ -1,55 +1,47 @@ -from pyrogram import Client, filters -from pyrogram.types import Message - -from utils.misc import prefix, modules_help - - -from pyrogram import Client, filters -from pyrogram.types import Message -from pyrogram.errors import MessageNotModified import os + import pygments from pygments.formatters import ImageFormatter from pygments.lexers import Python3Lexer +from pyrogram import Client, filters +from pyrogram.errors import MessageNotModified +from pyrogram.types import Message +from utils.misc import modules_help, prefix -@Client.on_message(filters.command("ncode", prefix) & filters.me) +@Client.on_message(filters.command('ncode', prefix) & filters.me) async def coder_print(client, message: Message): if message.reply_to_message: reply_message = message.reply_to_message if reply_message.media: download_path = await client.download_media(reply_message) - with open(download_path, "r") as file: + with open(download_path) as file: code = file.read() if os.path.exists(download_path): os.remove(download_path) pygments.highlight( - f"{code}", + f'{code}', Python3Lexer(), - ImageFormatter(font_name="DejaVu Sans Mono", line_numbers=True), - "result.png", + ImageFormatter(font_name='DejaVu Sans Mono', line_numbers=True), + 'result.png', ) try: - sent_message = await message.edit_text( - "Pasting this code on my page..." - ) + sent_message = await message.edit_text('Pasting this code on my page...') await client.send_document( chat_id=message.chat.id, - document="result.png", - caption="Code highlighted by Pygments", + document='result.png', + caption='Code highlighted by Pygments', reply_to_message_id=message.id, ) except MessageNotModified: pass await sent_message.delete() - if os.path.exists("result.png"): - os.remove("result.png") + if os.path.exists('result.png'): + os.remove('result.png') else: - return await message.reply_text("Please reply to a text or a file.") + return await message.reply_text('Please reply to a text or a file.') else: - return await message.reply_text("Please reply to a text or a file.") + return await message.reply_text('Please reply to a text or a file.') -modules_help["ncode"] = { - "ncode": "Highlight the code using Pygments and send it as an image." -} +modules_help['ncode'] = {'ncode': 'Highlight the code using Pygments and send it as an image.'} diff --git a/images/pinterest.py b/images/pinterest.py index abe0930..6c30718 100644 --- a/images/pinterest.py +++ b/images/pinterest.py @@ -1,13 +1,14 @@ -from pyrogram import Client, filters, enums -from pyrogram.types import Message, InputMediaPhoto -from io import BytesIO -from PIL import Image -import requests import asyncio +from io import BytesIO + +import requests +from PIL import Image +from pyrogram import Client, enums, filters +from pyrogram.types import InputMediaPhoto, Message from utils.misc import modules_help, prefix # Pinterest API URL -API_URL = "https://bk9.fun/pinterest/search?q=" +API_URL = 'https://bk9.fun/pinterest/search?q=' def resize_image(image_bytes): @@ -17,13 +18,13 @@ def resize_image(image_bytes): if img.size > max_size: img.thumbnail(max_size) output = BytesIO() - img.save(output, format="JPEG") + img.save(output, format='JPEG') output.seek(0) return output image_bytes.seek(0) # Reset pointer if not resized return image_bytes except Exception as e: - print(f"Error resizing image: {e}") + print(f'Error resizing image: {e}') return image_bytes @@ -34,69 +35,57 @@ async def download_image(url): img_bytes = BytesIO(response.content) return resize_image(img_bytes) except Exception as e: - print(f"Error downloading image: {e}") + print(f'Error downloading image: {e}') return None -@Client.on_message(filters.command("pinterest", prefix) & filters.me) +@Client.on_message(filters.command('pinterest', prefix) & filters.me) async def pinterest_search(client: Client, message: Message): if len(message.command) < 2: - await message.edit( - "Usage: `pinterest [number] `", parse_mode=enums.ParseMode.MARKDOWN - ) + await message.edit('Usage: `pinterest [number] `', parse_mode=enums.ParseMode.MARKDOWN) return num_pics = int(message.command[1]) if message.command[1].isdigit() else 10 - query = " ".join(message.command[2:]) + query = ' '.join(message.command[2:]) # Update status - status_message = await message.edit( - "Searching for images...", parse_mode=enums.ParseMode.MARKDOWN - ) + status_message = await message.edit('Searching for images...', parse_mode=enums.ParseMode.MARKDOWN) - url = f"{API_URL}{query}" + url = f'{API_URL}{query}' response = requests.get(url) if response.status_code == 200: data = response.json() - if data.get("status"): - urls = [item["images_url"] for item in data.get("BK9", [])[:num_pics]] + if data.get('status'): + urls = [item['images_url'] for item in data.get('BK9', [])[:num_pics]] images = [download_image(img_url) for img_url in urls] # Download images downloaded_images = await asyncio.gather(*images) - media = [ - InputMediaPhoto(media=img_bytes) - for img_bytes in downloaded_images - if img_bytes - ] + media = [InputMediaPhoto(media=img_bytes) for img_bytes in downloaded_images if img_bytes] if media: - await status_message.edit( - "Uploading pictures...", parse_mode=enums.ParseMode.MARKDOWN - ) + await status_message.edit('Uploading pictures...', parse_mode=enums.ParseMode.MARKDOWN) while media: batch = media[:10] media = media[10:] await message.reply_media_group(batch) await status_message.delete() # Delete status message after uploading else: - await status_message.edit( - "No valid images found.", parse_mode=enums.ParseMode.MARKDOWN - ) + await status_message.edit('No valid images found.', parse_mode=enums.ParseMode.MARKDOWN) else: await status_message.edit( - "No images found for the given query.", + 'No images found for the given query.', parse_mode=enums.ParseMode.MARKDOWN, ) else: await status_message.edit( - "An error occurred, please try again later.", + 'An error occurred, please try again later.', parse_mode=enums.ParseMode.MARKDOWN, ) -modules_help["pinterest"] = { - "pinterest [number]* [query]": "Get images from Pinterest. Default number of images is 10", +modules_help['pinterest'] = { + 'pinterest [number]* [query]': 'Get images from Pinterest. Default number of images is 10', } diff --git a/images/risearch.py b/images/risearch.py index 8dff45e..ed42b60 100644 --- a/images/risearch.py +++ b/images/risearch.py @@ -1,26 +1,27 @@ -from utils.misc import modules_help, prefix +import os + import requests +from modules.url import generate_screenshot from pyrogram import Client, filters from pyrogram.types import Message -from modules.url import generate_screenshot -import os +from utils.misc import modules_help, prefix # API endpoints for reverse image search engines SEARCH_ENGINES = { - "lens": "https://lens.google.com/uploadbyurl?url={image}", - "reverse": "https://www.google.com/searchbyimage?sbisrc=4chanx&image_url={image}&safe=off", - "tineye": "https://www.tineye.com/search?url={image}", - "bing": "https://www.bing.com/images/search?view=detailv2&iss=sbi&form=SBIVSP&sbisrc=UrlPaste&q=imgurl:{image}", - "yandex": "https://yandex.com/images/search?source=collections&&url={image}&rpt=imageview", - "saucenao": "https://saucenao.com/search.php?db=999&url={image}", + 'lens': 'https://lens.google.com/uploadbyurl?url={image}', + 'reverse': 'https://www.google.com/searchbyimage?sbisrc=4chanx&image_url={image}&safe=off', + 'tineye': 'https://www.tineye.com/search?url={image}', + 'bing': 'https://www.bing.com/images/search?view=detailv2&iss=sbi&form=SBIVSP&sbisrc=UrlPaste&q=imgurl:{image}', + 'yandex': 'https://yandex.com/images/search?source=collections&&url={image}&rpt=imageview', + 'saucenao': 'https://saucenao.com/search.php?db=999&url={image}', } -@Client.on_message(filters.command("risearch", prefix) & filters.reply) +@Client.on_message(filters.command('risearch', prefix) & filters.reply) async def reverse_image_search(client: Client, message: Message): if not message.reply_to_message or not message.reply_to_message.photo: await message.reply_text( - f"Please reply to an image with {prefix}risearch [engine] or {prefix}risearch." + f'Please reply to an image with {prefix}risearch [engine] or {prefix}risearch.' ) return @@ -31,16 +32,14 @@ async def reverse_image_search(client: Client, message: Message): else list(SEARCH_ENGINES.keys()) ) - invalid_engines = [ - engine for engine in engines_to_use if engine not in SEARCH_ENGINES - ] + invalid_engines = [engine for engine in engines_to_use if engine not in SEARCH_ENGINES] if invalid_engines: await message.reply_text( - f"Invalid engine(s): {', '.join(invalid_engines)}. Available: {', '.join(SEARCH_ENGINES.keys())}" + f'Invalid engine(s): {", ".join(invalid_engines)}. Available: {", ".join(SEARCH_ENGINES.keys())}' ) return - processing_message = await message.edit_text("Processing the image...") + processing_message = await message.edit_text('Processing the image...') try: # Download and upload the image @@ -48,7 +47,7 @@ async def reverse_image_search(client: Client, message: Message): img_url = upload_image(photo_path) print(img_url) if not img_url: - await processing_message.edit("Error: Could not upload the image.") + await processing_message.edit('Error: Could not upload the image.') return # Perform searches for the selected engines @@ -56,7 +55,7 @@ async def reverse_image_search(client: Client, message: Message): search_url = SEARCH_ENGINES[engine].format(image=img_url) await send_screenshot(client, message, search_url, engine) except Exception as e: - await processing_message.edit(f"An error occurred: {e}") + await processing_message.edit(f'An error occurred: {e}') finally: if photo_path and os.path.exists(photo_path): os.remove(photo_path) @@ -65,15 +64,13 @@ async def reverse_image_search(client: Client, message: Message): def upload_image(photo_path): """Uploads an image to tmpfiles.org and returns the direct download URL.""" try: - with open(photo_path, "rb") as image_file: - response = requests.post( - "https://tmpfiles.org/api/v1/upload", files={"file": image_file} - ) + with open(photo_path, 'rb') as image_file: + response = requests.post('https://tmpfiles.org/api/v1/upload', files={'file': image_file}) if response.status_code == 200: data = response.json() - url = data["data"]["url"] - pic_url = url.split("/")[-2] + "/" + url.split("/")[-1] - direct_download_url = url.replace(f"/{pic_url}", f"/dl/{pic_url}") + url = data['data']['url'] + pic_url = url.split('/')[-2] + '/' + url.split('/')[-1] + direct_download_url = url.replace(f'/{pic_url}', f'/dl/{pic_url}') print(direct_download_url) return direct_download_url else: @@ -89,17 +86,15 @@ async def send_screenshot(client, message, url, engine_name): await client.send_photo( message.chat.id, screenshot_data, - caption=f"{engine_name.capitalize()} Result\nURL: {url}", + caption=f'{engine_name.capitalize()} Result\nURL: {url}', reply_to_message_id=message.id, ) else: - await message.reply( - f"Failed to take screenshot for {engine_name.capitalize()}." - ) + await message.reply(f'Failed to take screenshot for {engine_name.capitalize()}.') # Add module details to help -modules_help["risearch"] = { - "risearch": f"Reply to a photo with `{prefix}risearch [engine]` (e.g., `{prefix}risearch lens`, `{prefix}risearch bing`) " - f"\nor use `{prefix}risearch` to analyze the image with all engines.", +modules_help['risearch'] = { + 'risearch': f'Reply to a photo with `{prefix}risearch [engine]` (e.g., `{prefix}risearch lens`, `{prefix}risearch bing`) ' + f'\nor use `{prefix}risearch` to analyze the image with all engines.', } diff --git a/images/unsplash.py b/images/unsplash.py index 5fa726f..ba97a6a 100644 --- a/images/unsplash.py +++ b/images/unsplash.py @@ -1,64 +1,60 @@ import asyncio -import json import os import shutil import aiohttp +import requests from pyrogram import Client, enums, filters from pyrogram.types import Message -import requests from utils.misc import modules_help, prefix class AioHttp: async def get_json(self, link): headers = { - "accept": "*/*", - "accept-language": "en-US", - "cache-control": "no-cache", - "client-geo-region": "global", - "dnt": "1", - "pragma": "no-cache", - "priority": "u=1, i", - "sec-ch-ua": '"Chromium";v="130", "Microsoft Edge";v="130", "Not?A_Brand";v="99"', - "sec-ch-ua-mobile": "?0", - "sec-ch-ua-platform": '"Windows"', - "sec-fetch-dest": "empty", - "sec-fetch-mode": "cors", - "sec-fetch-site": "same-origin", - "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36 Edg/130.0.0.0", + 'accept': '*/*', + 'accept-language': 'en-US', + 'cache-control': 'no-cache', + 'client-geo-region': 'global', + 'dnt': '1', + 'pragma': 'no-cache', + 'priority': 'u=1, i', + 'sec-ch-ua': '"Chromium";v="130", "Microsoft Edge";v="130", "Not?A_Brand";v="99"', + 'sec-ch-ua-mobile': '?0', + 'sec-ch-ua-platform': '"Windows"', + 'sec-fetch-dest': 'empty', + 'sec-fetch-mode': 'cors', + 'sec-fetch-site': 'same-origin', + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36 Edg/130.0.0.0', } - async with aiohttp.ClientSession() as session: - async with session.get(link, headers=headers) as resp: - return await resp.json() + async with aiohttp.ClientSession() as session, session.get(link, headers=headers) as resp: + return await resp.json() -@Client.on_message(filters.command("unsplash", prefix) & filters.me) +@Client.on_message(filters.command('unsplash', prefix) & filters.me) async def unsplash(client: Client, message: Message): if len(message.command) > 1 and isinstance(message.command[1], str): keyword = message.command[1] - unsplash_dir = "downloads/unsplash/" + unsplash_dir = 'downloads/unsplash/' if not os.path.exists(unsplash_dir): os.makedirs(unsplash_dir) if len(message.command) > 2 and 2 <= int(message.command[2]) <= 10: - await message.edit( - "Getting Pictures", parse_mode=enums.ParseMode.HTML - ) + await message.edit('Getting Pictures', parse_mode=enums.ParseMode.HTML) count = int(message.command[2]) images = [] data = await AioHttp().get_json( - f"https://unsplash.com/napi/search/photos?page=1&per_page={count}&query={keyword}" + f'https://unsplash.com/napi/search/photos?page=1&per_page={count}&query={keyword}' ) while len(images) < count: for ia in range(len(images), count): - img = data["results"][ia]["urls"]["raw"] - if img.startswith("https://images.unsplash.com/photo"): + img = data['results'][ia]['urls']['raw'] + if img.startswith('https://images.unsplash.com/photo'): image_content = requests.get(img).content - with open(f"{unsplash_dir}/unsplash_{ia}.jpg", "wb") as f: + with open(f'{unsplash_dir}/unsplash_{ia}.jpg', 'wb') as f: f.write(image_content) - imgr = f"{unsplash_dir}/unsplash_{ia}.jpg" + imgr = f'{unsplash_dir}/unsplash_{ia}.jpg' images.append(imgr) else: images.append(img) @@ -72,23 +68,19 @@ async def unsplash(client: Client, message: Message): shutil.rmtree(unsplash_dir) return else: - await message.edit( - "Getting Picture", parse_mode=enums.ParseMode.HTML - ) + await message.edit('Getting Picture', parse_mode=enums.ParseMode.HTML) data = await AioHttp().get_json( - f"https://unsplash.com/napi/search/photos?page=1&per_page=1&query={keyword}" - ) - img = data["results"][0]["urls"]["raw"] - await asyncio.gather( - message.delete(), client.send_document(message.chat.id, str(img)) + f'https://unsplash.com/napi/search/photos?page=1&per_page=1&query={keyword}' ) + img = data['results'][0]['urls']['raw'] + await asyncio.gather(message.delete(), client.send_document(message.chat.id, str(img))) -modules_help["unsplash"] = { - "unsplash": f"[keyword]*", - "unsplash": f"[keyword]* [number of results you want]*\n" - "Makes a request to unsplash.com and sends the image with the keyword you provided.\n\n" - "Note:\n1. The number of results you can get is limited to 10.\n" - "2. Keyword is required and should be of one word only!.\n" - "3. Images are sent as document to maintain quality.", +modules_help['unsplash'] = { + 'unsplash': '[keyword]*', + 'unsplash': '[keyword]* [number of results you want]*\n' + 'Makes a request to unsplash.com and sends the image with the keyword you provided.\n\n' + 'Note:\n1. The number of results you can get is limited to 10.\n' + '2. Keyword is required and should be of one word only!.\n' + '3. Images are sent as document to maintain quality.', } diff --git a/images/unsplash2.py b/images/unsplash2.py index 856e187..36db6e2 100644 --- a/images/unsplash2.py +++ b/images/unsplash2.py @@ -1,12 +1,13 @@ -from pyrogram import Client, filters, enums -from pyrogram.types import Message, InputMediaPhoto -from io import BytesIO -from PIL import Image -import requests import asyncio +from io import BytesIO + +import requests +from PIL import Image +from pyrogram import Client, enums, filters +from pyrogram.types import InputMediaPhoto, Message from utils.misc import modules_help, prefix -API_URL = "https://bk9.fun/search/unsplash?q=" +API_URL = 'https://bk9.fun/search/unsplash?q=' def resize_image(image_bytes): @@ -16,13 +17,13 @@ def resize_image(image_bytes): if img.size > max_size: img.thumbnail(max_size) output = BytesIO() - img.save(output, format="JPEG") + img.save(output, format='JPEG') output.seek(0) return output image_bytes.seek(0) # Reset pointer if not resized return image_bytes except Exception as e: - print(f"Error resizing image: {e}") + print(f'Error resizing image: {e}') return image_bytes @@ -34,70 +35,58 @@ async def download_image(url): resized_img_bytes = resize_image(img_bytes) return resized_img_bytes except Exception as e: - print(f"Error downloading image: {e}") + print(f'Error downloading image: {e}') return None -@Client.on_message(filters.command(["unsplash2", "usp2"], prefix) & filters.me) +@Client.on_message(filters.command(['unsplash2', 'usp2'], prefix) & filters.me) async def imgsearch(client: Client, message: Message): if len(message.command) < 2: - await message.edit( - "Usage: `img [number] `", parse_mode=enums.ParseMode.MARKDOWN - ) + await message.edit('Usage: `img [number] `', parse_mode=enums.ParseMode.MARKDOWN) return num_pics = int(message.command[1]) if message.command[1].isdigit() else 10 - query = " ".join(message.command[2:]) + query = ' '.join(message.command[2:]) # Update status - status_message = await message.edit( - "Searching for images...", parse_mode=enums.ParseMode.MARKDOWN - ) + status_message = await message.edit('Searching for images...', parse_mode=enums.ParseMode.MARKDOWN) - url = f"{API_URL}{query}" + url = f'{API_URL}{query}' response = requests.get(url) if response.status_code == 200: data = response.json() - if data.get("status"): - urls = data.get("BK9", [])[:num_pics] + if data.get('status'): + urls = data.get('BK9', [])[:num_pics] images = [download_image(img_url) for img_url in urls] # Download images downloaded_images = await asyncio.gather(*images) - media = [ - InputMediaPhoto(media=img_bytes) - for img_bytes in downloaded_images - if img_bytes - ] + media = [InputMediaPhoto(media=img_bytes) for img_bytes in downloaded_images if img_bytes] if media: - await status_message.edit( - "Uploading pictures...", parse_mode=enums.ParseMode.MARKDOWN - ) + await status_message.edit('Uploading pictures...', parse_mode=enums.ParseMode.MARKDOWN) while media: batch = media[:10] media = media[10:] await message.reply_media_group(batch) await status_message.delete() # Delete status message after uploading else: - await status_message.edit( - "No valid images found.", parse_mode=enums.ParseMode.MARKDOWN - ) + await status_message.edit('No valid images found.', parse_mode=enums.ParseMode.MARKDOWN) else: await status_message.edit( - "No images found for the given query.", + 'No images found for the given query.', parse_mode=enums.ParseMode.MARKDOWN, ) else: await status_message.edit( - "An error occurred, please try again later.", + 'An error occurred, please try again later.', parse_mode=enums.ParseMode.MARKDOWN, ) -modules_help["unsplash2"] = { - "unsplash2 [number]* [query]": "Get HD images. Default number of images is 10", - "usp2 [number]* [query]": "Get HD images. Default number of images is 10", +modules_help['unsplash2'] = { + 'unsplash2 [number]* [query]': 'Get HD images. Default number of images is 10', + 'usp2 [number]* [query]': 'Get HD images. Default number of images is 10', } diff --git a/install.py b/install.py index 24560ff..3ecf9a2 100644 --- a/install.py +++ b/install.py @@ -1,23 +1,23 @@ -from datetime import datetime import sys -from pyrogram import Client +from datetime import datetime +from pyrogram import Client from utils import config common_params = { - "api_id": config.api_id, - "api_hash": config.api_hash, - "hide_password": True, - "test_mode": config.test_server, + 'api_id': config.api_id, + 'api_hash': config.api_hash, + 'hide_password': True, + 'test_mode': config.test_server, } -if __name__ == "__main__": +if __name__ == '__main__': if config.STRINGSESSION: - common_params["session_string"] = config.STRINGSESSION + common_params['session_string'] = config.STRINGSESSION - app = Client("my_account", **common_params) + app = Client('my_account', **common_params) - if config.db_type in ["mongo", "mongodb"]: + if config.db_type in ['mongo', 'mongodb']: from pymongo import MongoClient, errors db = MongoClient(config.db_url) @@ -26,27 +26,27 @@ if __name__ == "__main__": except errors.ConnectionFailure as e: raise RuntimeError( "MongoDB server isn't available! " - f"Provided url: {config.db_url}. " - "Enter valid URL and restart installation" + f'Provided url: {config.db_url}. ' + 'Enter valid URL and restart installation' ) from e - install_type = sys.argv[1] if len(sys.argv) > 1 else "3" - if install_type == "1": - restart = "pm2 restart Moon" - elif install_type == "2": - restart = "sudo systemctl restart Moon" + install_type = sys.argv[1] if len(sys.argv) > 1 else '3' + if install_type == '1': + restart = 'pm2 restart Moon' + elif install_type == '2': + restart = 'sudo systemctl restart Moon' else: - restart = "cd Moon-Userbot/ && python main.py" + restart = 'cd Moon-Userbot/ && python main.py' app.start() try: app.send_message( - "me", - f"[{datetime.now()}] Userbot launched! \n" - "Custom modules: @moonub_modules\n" - f"For restart, enter:\n" - f"{restart}", + 'me', + f'[{datetime.now()}] Userbot launched! \n' + 'Custom modules: @moonub_modules\n' + f'For restart, enter:\n' + f'{restart}', ) except Exception as e: - print(f"[ERROR]: Sending Message to me failed! {e}") + print(f'[ERROR]: Sending Message to me failed! {e}') app.stop() diff --git a/main.py b/main.py index 8e76c29..451e45c 100644 --- a/main.py +++ b/main.py @@ -39,92 +39,88 @@ # "pySmartDL", # ] # /// -import os import logging - -import sqlite3 +import os import platform +import sqlite3 import subprocess -from pyrogram import Client, idle, errors -from pyrogram.enums.parse_mode import ParseMode -from pyrogram.raw.functions.account import GetAuthorizations, DeleteAccount import requests - +from pyrogram import Client, errors, idle +from pyrogram.enums.parse_mode import ParseMode +from pyrogram.raw.functions.account import DeleteAccount, GetAuthorizations from utils import config from utils.db import db -from utils.misc import gitrepo, userbot_version -from utils.scripts import restart -from utils.rentry import rentry_cleanup_job +from utils.misc import userbot_version from utils.module import ModuleManager +from utils.rentry import rentry_cleanup_job +from utils.scripts import restart SCRIPT_PATH = os.path.dirname(os.path.realpath(__file__)) -if SCRIPT_PATH != os.getcwd(): +if os.getcwd() != SCRIPT_PATH: os.chdir(SCRIPT_PATH) common_params = { - "api_id": config.api_id, - "api_hash": config.api_hash, - "hide_password": True, - "workdir": SCRIPT_PATH, - "app_version": userbot_version, - "device_model": f"mUserbot", - "system_version": platform.version() + " " + platform.machine(), - "sleep_threshold": 30, - "test_mode": config.test_server, - "parse_mode": ParseMode.HTML, + 'api_id': config.api_id, + 'api_hash': config.api_hash, + 'hide_password': True, + 'workdir': SCRIPT_PATH, + 'app_version': userbot_version, + 'device_model': 'mUserbot', + 'system_version': platform.version() + ' ' + platform.machine(), + 'sleep_threshold': 30, + 'test_mode': config.test_server, + 'parse_mode': ParseMode.HTML, } if config.STRINGSESSION: - common_params["session_string"] = config.STRINGSESSION + common_params['session_string'] = config.STRINGSESSION -app = Client("my_account", **common_params) +app = Client('my_account', **common_params) def load_missing_modules(): - all_modules = db.get("custom.modules", "allModules", []) + all_modules = db.get('custom.modules', 'allModules', []) if not all_modules: return - custom_modules_path = f"{SCRIPT_PATH}/modules/custom_modules" + custom_modules_path = f'{SCRIPT_PATH}/modules/custom_modules' os.makedirs(custom_modules_path, exist_ok=True) try: resp = requests.get( - "https://raw.githubusercontent.com/The-MoonTg-project/custom_modules/main/full.txt", + 'https://raw.githubusercontent.com/The-MoonTg-project/custom_modules/main/full.txt', timeout=10, ) if not resp.ok: logging.error( - "Failed to fetch custom modules list: HTTP %s", + 'Failed to fetch custom modules list: HTTP %s', resp.status_code, ) return f = resp.text except Exception as e: - logging.error("Failed to fetch custom modules list: %s", e) + logging.error('Failed to fetch custom modules list: %s', e) return - modules_dict = { - line.split("/")[-1].split()[0]: line.strip() for line in f.splitlines() - } + modules_dict = {line.split('/')[-1].split()[0]: line.strip() for line in f.splitlines()} for module_name in all_modules: - module_path = f"{custom_modules_path}/{module_name}.py" + module_path = f'{custom_modules_path}/{module_name}.py' if not os.path.exists(module_path) and module_name in modules_dict: - url = f"https://raw.githubusercontent.com/The-MoonTg-project/custom_modules/main/{modules_dict[module_name]}.py" + url = f'https://raw.githubusercontent.com/The-MoonTg-project/custom_modules/main/{modules_dict[module_name]}.py' resp = requests.get(url) if resp.ok: - with open(module_path, "wb") as f: + with open(module_path, 'wb') as f: f.write(resp.content) - logging.info("Loaded missing module: %s", module_name) + logging.info('Loaded missing module: %s', module_name) else: - logging.warning("Failed to load module: %s", module_name) + logging.warning('Failed to load module: %s', module_name) async def main(): logging.basicConfig( - format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", - handlers=[logging.FileHandler("moonlogs.txt"), logging.StreamHandler()], + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', + handlers=[logging.FileHandler('moonlogs.txt'), logging.StreamHandler()], level=logging.INFO, ) DeleteAccount.__new__ = None @@ -132,49 +128,44 @@ async def main(): try: await app.start() except sqlite3.OperationalError as e: - if str(e) == "database is locked" and os.name == "posix": - logging.warning( - "Session file is locked. Trying to kill blocking process..." - ) - subprocess.run(["fuser", "-k", "my_account.session"], check=True) + if str(e) == 'database is locked' and os.name == 'posix': + logging.warning('Session file is locked. Trying to kill blocking process...') + subprocess.run(['fuser', '-k', 'my_account.session'], check=True) restart() raise except (errors.NotAcceptable, errors.Unauthorized) as e: logging.error( - "%s: %s\nMoving session file to my_account.session-old...", + '%s: %s\nMoving session file to my_account.session-old...', e.__class__.__name__, e, ) - os.rename("./my_account.session", "./my_account.session-old") + os.rename('./my_account.session', './my_account.session-old') restart() load_missing_modules() module_manager = ModuleManager.get_instance() await module_manager.load_modules(app) - if info := db.get("core.updater", "restart_info"): + if info := db.get('core.updater', 'restart_info'): text = { - "restart": "Restart completed!", - "update": "Update process completed!", - }[info["type"]] + 'restart': 'Restart completed!', + 'update': 'Update process completed!', + }[info['type']] try: - await app.edit_message_text(info["chat_id"], info["message_id"], text) + await app.edit_message_text(info['chat_id'], info['message_id'], text) except errors.RPCError: pass - db.remove("core.updater", "restart_info") + db.remove('core.updater', 'restart_info') # required for sessionkiller module - if db.get("core.sessionkiller", "enabled", False): + if db.get('core.sessionkiller', 'enabled', False): db.set( - "core.sessionkiller", - "auths_hashes", - [ - auth.hash - for auth in (await app.invoke(GetAuthorizations())).authorizations - ], + 'core.sessionkiller', + 'auths_hashes', + [auth.hash for auth in (await app.invoke(GetAuthorizations())).authorizations], ) - logging.info("Moon-Userbot started!") + logging.info('Moon-Userbot started!') app.loop.create_task(rentry_cleanup_job()) @@ -183,5 +174,5 @@ async def main(): await app.stop() -if __name__ == "__main__": +if __name__ == '__main__': app.run(main()) diff --git a/modules/1000-7.py b/modules/1000-7.py index 780e3cd..08740b7 100644 --- a/modules/1000-7.py +++ b/modules/1000-7.py @@ -2,20 +2,16 @@ from asyncio import sleep from pyrogram import Client, filters from pyrogram.types import Message - from utils.misc import modules_help, prefix -digits = { - str(i): el - for i, el in enumerate(["0️⃣", "1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6️⃣", "7️⃣", "8️⃣", "9️⃣"]) -} +digits = {str(i): el for i, el in enumerate(['0️⃣', '1️⃣', '2️⃣', '3️⃣', '4️⃣', '5️⃣', '6️⃣', '7️⃣', '8️⃣', '9️⃣'])} def prettify(val: int) -> str: - return "".join(digits[i] for i in str(val)) + return ''.join(digits[i] for i in str(val)) -@Client.on_message(filters.command("ghoul", prefix) & filters.me) +@Client.on_message(filters.command('ghoul', prefix) & filters.me) async def ghoul_counter(_, message: Message): await message.delete() @@ -33,9 +29,7 @@ async def ghoul_counter(_, message: Message): await msg.edit(prettify(counter)) await sleep(1) - await msg.edit("🤡 GHOUL 🤡") + await msg.edit('🤡 GHOUL 🤡') -modules_help["1000-7"] = { - "ghoul [count_from]": "counting from 1000 (or given [count_from] to 0 as a ghoul" -} +modules_help['1000-7'] = {'ghoul [count_from]': 'counting from 1000 (or given [count_from] to 0 as a ghoul'} diff --git a/modules/admintool.py b/modules/admintool.py index 7ca2ae5..5ccbe8e 100644 --- a/modules/admintool.py +++ b/modules/admintool.py @@ -16,60 +16,57 @@ from contextlib import suppress -from pyrogram.enums import ChatType from pyrogram import Client, ContinuePropagation, filters +from pyrogram.enums import ChatType from pyrogram.errors import ( - UserAdminInvalid, ChatAdminRequired, RPCError, + UserAdminInvalid, ) from pyrogram.raw import functions -from pyrogram.types import Message, ChatPermissions - +from pyrogram.types import ChatPermissions, Message from utils.db import db -from utils.scripts import format_exc, with_reply -from utils.misc import modules_help, prefix - from utils.handlers import ( - BanHandler, - UnbanHandler, - KickHandler, - KickDeletedAccountsHandler, - TimeMuteHandler, - TimeUnmuteHandler, - TimeMuteUsersHandler, - UnmuteHandler, - MuteHandler, - DemoteHandler, - PromoteHandler, AntiChannelsHandler, - DeleteHistoryHandler, AntiRaidHandler, + BanHandler, + DeleteHistoryHandler, + DemoteHandler, + KickDeletedAccountsHandler, + KickHandler, + MuteHandler, + PromoteHandler, + TimeMuteHandler, + TimeMuteUsersHandler, + TimeUnmuteHandler, + UnbanHandler, + UnmuteHandler, ) +from utils.misc import modules_help, prefix +from utils.scripts import format_exc, with_reply - -db_cache: dict = db.get_collection("core.ats") +db_cache: dict = db.get_collection('core.ats') def update_cache(): db_cache.clear() - db_cache.update(db.get_collection("core.ats")) + db_cache.update(db.get_collection('core.ats')) @Client.on_message(filters.group & ~filters.me) async def admintool_handler(_, message: Message): if message.sender_chat and ( - message.sender_chat.type == "supergroup" - or message.sender_chat.id == db_cache.get(f"linked{message.chat.id}", 0) + message.sender_chat.type == 'supergroup' + or message.sender_chat.id == db_cache.get(f'linked{message.chat.id}', 0) ): raise ContinuePropagation - if message.sender_chat and db_cache.get(f"antich{message.chat.id}", False): + if message.sender_chat and db_cache.get(f'antich{message.chat.id}', False): with suppress(RPCError): await message.delete() await message.chat.ban_member(message.sender_chat.id) - tmuted_users = db_cache.get(f"c{message.chat.id}", []) + tmuted_users = db_cache.get(f'c{message.chat.id}', []) if ( message.from_user and message.from_user.id in tmuted_users @@ -79,7 +76,7 @@ async def admintool_handler(_, message: Message): with suppress(RPCError): await message.delete() - if db_cache.get(f"antiraid{message.chat.id}", False): + if db_cache.get(f'antiraid{message.chat.id}', False): with suppress(RPCError): await message.delete() if message.from_user: @@ -87,11 +84,9 @@ async def admintool_handler(_, message: Message): elif message.sender_chat: await message.chat.ban_member(message.sender_chat.id) - if message.new_chat_members and db_cache.get( - f"welcome_enabled{message.chat.id}", False - ): + if message.new_chat_members and db_cache.get(f'welcome_enabled{message.chat.id}', False): await message.reply( - db_cache.get(f"welcome_text{message.chat.id}"), + db_cache.get(f'welcome_text{message.chat.id}'), disable_web_page_preview=True, ) @@ -111,88 +106,88 @@ async def get_user_and_name(message): ) -@Client.on_message(filters.command(["ban"], prefix) & filters.me) +@Client.on_message(filters.command(['ban'], prefix) & filters.me) async def ban_command(client: Client, message: Message): handler = BanHandler(client, message) await handler.handle_ban() -@Client.on_message(filters.command(["unban"], prefix) & filters.me) +@Client.on_message(filters.command(['unban'], prefix) & filters.me) async def unban_command(client: Client, message: Message): handler = UnbanHandler(client, message) await handler.handle_unban() -@Client.on_message(filters.command(["kick"], prefix) & filters.me) +@Client.on_message(filters.command(['kick'], prefix) & filters.me) async def kick_command(client: Client, message: Message): handler = KickHandler(client, message) await handler.handle_kick() -@Client.on_message(filters.command(["kickdel"], prefix) & filters.me) +@Client.on_message(filters.command(['kickdel'], prefix) & filters.me) async def kickdel_cmd(client: Client, message: Message): handler = KickDeletedAccountsHandler(client, message) await handler.kick_deleted_accounts() -@Client.on_message(filters.command(["tmute"], prefix) & filters.me) +@Client.on_message(filters.command(['tmute'], prefix) & filters.me) async def tmute_command(client: Client, message: Message): handler = TimeMuteHandler(client, message) await handler.handle_tmute() update_cache() -@Client.on_message(filters.command(["tunmute"], prefix) & filters.me) +@Client.on_message(filters.command(['tunmute'], prefix) & filters.me) async def tunmute_command(client: Client, message: Message): handler = TimeUnmuteHandler(client, message) await handler.handle_tunmute() update_cache() -@Client.on_message(filters.command(["tmute_users"], prefix) & filters.me) +@Client.on_message(filters.command(['tmute_users'], prefix) & filters.me) async def tunmute_users_command(client: Client, message: Message): handler = TimeMuteUsersHandler(client, message) await handler.list_tmuted_users() -@Client.on_message(filters.command(["unmute"], prefix) & filters.me) +@Client.on_message(filters.command(['unmute'], prefix) & filters.me) async def unmute_command(client: Client, message: Message): handler = UnmuteHandler(client, message) await handler.handle_unmute() -@Client.on_message(filters.command(["mute"], prefix) & filters.me) +@Client.on_message(filters.command(['mute'], prefix) & filters.me) async def mute_command(client: Client, message: Message): handler = MuteHandler(client, message) await handler.handle_mute() -@Client.on_message(filters.command(["demote"], prefix) & filters.me) +@Client.on_message(filters.command(['demote'], prefix) & filters.me) async def demote_command(client: Client, message: Message): handler = DemoteHandler(client, message) await handler.handle_demote() -@Client.on_message(filters.command(["promote"], prefix) & filters.me) +@Client.on_message(filters.command(['promote'], prefix) & filters.me) async def promote_command(client: Client, message: Message): handler = PromoteHandler(client, message) await handler.handle_promote() -@Client.on_message(filters.command(["antich"], prefix)) +@Client.on_message(filters.command(['antich'], prefix)) async def anti_channels(client: Client, message: Message): handler = AntiChannelsHandler(client, message) await handler.handle_anti_channels() update_cache() -@Client.on_message(filters.command(["delete_history", "dh"], prefix)) +@Client.on_message(filters.command(['delete_history', 'dh'], prefix)) async def delete_history(client: Client, message: Message): handler = DeleteHistoryHandler(client, message) await handler.handle_delete_history() -@Client.on_message(filters.command(["report_spam", "rs"], prefix)) +@Client.on_message(filters.command(['report_spam', 'rs'], prefix)) @with_reply async def report_spam(client: Client, message: Message): try: @@ -210,33 +205,33 @@ async def report_spam(client: Client, message: Message): except Exception as e: await message.edit(format_exc(e)) else: - await message.edit(f"Message from {name} was reported") + await message.edit(f'Message from {name} was reported') -@Client.on_message(filters.command("pin", prefix) & filters.me) +@Client.on_message(filters.command('pin', prefix) & filters.me) @with_reply async def pin(_, message: Message): try: await message.reply_to_message.pin() - await message.edit("Pinned!") + await message.edit('Pinned!') except Exception as e: await message.edit(format_exc(e)) -@Client.on_message(filters.command("unpin", prefix) & filters.me) +@Client.on_message(filters.command('unpin', prefix) & filters.me) @with_reply async def unpin(_, message: Message): try: await message.reply_to_message.unpin() - await message.edit("Unpinned!") + await message.edit('Unpinned!') except Exception as e: await message.edit(format_exc(e)) -@Client.on_message(filters.command("ro", prefix) & filters.me) +@Client.on_message(filters.command('ro', prefix) & filters.me) async def ro(client: Client, message: Message): if message.chat.type not in [ChatType.GROUP, ChatType.SUPERGROUP]: - await message.edit("Invalid chat type") + await message.edit('Invalid chat type') return try: @@ -250,42 +245,39 @@ async def ro(client: Client, message: Message): perms.can_invite_users, perms.can_pin_messages, ] - db.set("core.ats", f"ro{message.chat.id}", perms_list) + db.set('core.ats', f'ro{message.chat.id}', perms_list) try: await client.set_chat_permissions(message.chat.id, ChatPermissions()) except (UserAdminInvalid, ChatAdminRequired): - await message.edit("No rights") + await message.edit('No rights') else: - await message.edit( - "Read-only mode activated!\n" - f"Turn off with:{prefix}unro" - ) + await message.edit(f'Read-only mode activated!\nTurn off with:{prefix}unro') except Exception as e: await message.edit(format_exc(e)) -@Client.on_message(filters.command("unro", prefix) & filters.me) +@Client.on_message(filters.command('unro', prefix) & filters.me) async def unro(client: Client, message: Message): if message.chat.type not in [ChatType.GROUP, ChatType.SUPERGROUP]: - await message.edit("Invalid chat type") + await message.edit('Invalid chat type') return try: perms_list = db.get( - "core.ats", - f"ro{message.chat.id}", + 'core.ats', + f'ro{message.chat.id}', [True, True, False, False, False, False, False], ) common_perms = { - "can_send_messages": perms_list[0], - "can_send_media_messages": perms_list[1], - "can_send_polls": perms_list[2], - "can_add_web_page_previews": perms_list[3], - "can_change_info": perms_list[4], - "can_invite_users": perms_list[5], - "can_pin_messages": perms_list[6], + 'can_send_messages': perms_list[0], + 'can_send_media_messages': perms_list[1], + 'can_send_polls': perms_list[2], + 'can_add_web_page_previews': perms_list[3], + 'can_change_info': perms_list[4], + 'can_invite_users': perms_list[5], + 'can_pin_messages': perms_list[6], } perms = ChatPermissions(**common_perms) @@ -293,61 +285,58 @@ async def unro(client: Client, message: Message): try: await client.set_chat_permissions(message.chat.id, perms) except (UserAdminInvalid, ChatAdminRequired): - await message.edit("No rights") + await message.edit('No rights') else: - await message.edit("Read-only mode disabled!") + await message.edit('Read-only mode disabled!') except Exception as e: await message.edit(format_exc(e)) -@Client.on_message(filters.command("antiraid", prefix) & filters.me) +@Client.on_message(filters.command('antiraid', prefix) & filters.me) async def antiraid(client: Client, message: Message): handler = AntiRaidHandler(client, message) await handler.handle_antiraid() update_cache() -@Client.on_message(filters.command(["welcome", "wc"], prefix) & filters.me) +@Client.on_message(filters.command(['welcome', 'wc'], prefix) & filters.me) async def welcome(_, message: Message): if message.chat.type not in [ChatType.GROUP, ChatType.SUPERGROUP]: - return await message.edit("Unsupported chat type") + return await message.edit('Unsupported chat type') if len(message.command) > 1: text = message.text.split(maxsplit=1)[1] - db.set("core.ats", f"welcome_enabled{message.chat.id}", True) - db.set("core.ats", f"welcome_text{message.chat.id}", text) + db.set('core.ats', f'welcome_enabled{message.chat.id}', True) + db.set('core.ats', f'welcome_text{message.chat.id}', text) - await message.edit( - f"Welcome enabled in this chat\nText: {text}" - ) + await message.edit(f'Welcome enabled in this chat\nText: {text}') else: - db.set("core.ats", f"welcome_enabled{message.chat.id}", False) - await message.edit("Welcome disabled in this chat") + db.set('core.ats', f'welcome_enabled{message.chat.id}', False) + await message.edit('Welcome disabled in this chat') update_cache() -modules_help["admintool"] = { - "ban [reply]/[username/id]* [reason] [report_spam] [delete_history]": "ban user in chat", - "unban [reply]/[username/id]* [reason]": "unban user in chat", - "kick [reply]/[userid]* [reason] [report_spam] [delete_history]": "kick user out of chat", - "mute [reply]/[userid]* [reason] [1m]/[1h]/[1d]/[1w]": "mute user in chat", - "unmute [reply]/[userid]* [reason]": "unmute user in chat", - "promote [reply]/[userid]* [prefix]": "promote user in chat", - "demote [reply]/[userid]* [reason]": "demote user in chat", - "tmute [reply]/[username/id]* [reason]": "delete all new messages from user in chat", - "tunmute [reply]/[username/id]* [reason]": "stop deleting all messages from user in chat", - "tmute_users": "list of tmuted (.tmute) users", - "antich [enable/disable]": "turn on/off blocking channels in this chat", - "delete_history [reply]/[username/id]* [reason]": "delete history from member in chat", - "report_spam [reply]*": "report spam message in chat", - "pin [reply]*": "Pin replied message", - "unpin [reply]*": "Unpin replied message", - "ro": "enable read-only mode", - "unro": "disable read-only mode", - "antiraid [on|off]": "when enabled, anyone who writes message will be blocked. Useful in raids. " - "Running without arguments equals to toggling state", - "welcome [text]*": "enable auto-welcome to new users in groups. " - "Running without text equals to disable", - "kickdel": "Kick all deleted accounts", +modules_help['admintool'] = { + 'ban [reply]/[username/id]* [reason] [report_spam] [delete_history]': 'ban user in chat', + 'unban [reply]/[username/id]* [reason]': 'unban user in chat', + 'kick [reply]/[userid]* [reason] [report_spam] [delete_history]': 'kick user out of chat', + 'mute [reply]/[userid]* [reason] [1m]/[1h]/[1d]/[1w]': 'mute user in chat', + 'unmute [reply]/[userid]* [reason]': 'unmute user in chat', + 'promote [reply]/[userid]* [prefix]': 'promote user in chat', + 'demote [reply]/[userid]* [reason]': 'demote user in chat', + 'tmute [reply]/[username/id]* [reason]': 'delete all new messages from user in chat', + 'tunmute [reply]/[username/id]* [reason]': 'stop deleting all messages from user in chat', + 'tmute_users': 'list of tmuted (.tmute) users', + 'antich [enable/disable]': 'turn on/off blocking channels in this chat', + 'delete_history [reply]/[username/id]* [reason]': 'delete history from member in chat', + 'report_spam [reply]*': 'report spam message in chat', + 'pin [reply]*': 'Pin replied message', + 'unpin [reply]*': 'Unpin replied message', + 'ro': 'enable read-only mode', + 'unro': 'disable read-only mode', + 'antiraid [on|off]': 'when enabled, anyone who writes message will be blocked. Useful in raids. ' + 'Running without arguments equals to toggling state', + 'welcome [text]*': 'enable auto-welcome to new users in groups. Running without text equals to disable', + 'kickdel': 'Kick all deleted accounts', } diff --git a/modules/admlist.py b/modules/admlist.py index ba2bc49..58b9168 100644 --- a/modules/admlist.py +++ b/modules/admlist.py @@ -14,12 +14,12 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from collections.abc import AsyncGenerator from time import perf_counter -from typing import AsyncGenerator, List, Optional, Union +from typing import Optional from pyrogram import Client, enums, filters, raw, types, utils from pyrogram.types.object import Object - from utils.misc import modules_help, prefix from utils.scripts import format_exc @@ -28,7 +28,7 @@ class Chat(Object): def __init__( self, *, - client: "Client" = None, + client: 'Client' = None, id: id, type: type, is_verified: bool = None, @@ -41,7 +41,7 @@ class Chat(Object): username: str = None, first_name: str = None, last_name: str = None, - photo: "types.ChatPhoto" = None, + photo: 'types.ChatPhoto' = None, bio: str = None, description: str = None, dc_id: int = None, @@ -51,12 +51,12 @@ class Chat(Object): sticker_set_name: str = None, can_set_sticker_set: bool = None, members_count: int = None, - restrictions: List["types.Restriction"] = None, - permissions: "types.ChatPermissions" = None, + restrictions: list['types.Restriction'] = None, + permissions: 'types.ChatPermissions' = None, distance: int = None, - linked_chat: "types.Chat" = None, - send_as_chat: "types.Chat" = None, - available_reactions: Optional["types.ChatReactions"] = None, + linked_chat: 'types.Chat' = None, + send_as_chat: 'types.Chat' = None, + available_reactions: Optional['types.ChatReactions'] = None, is_admin: bool = False, deactivated: bool = False, ): @@ -94,98 +94,82 @@ class Chat(Object): self.deactivated = deactivated @staticmethod - def _parse_user_chat(client, user: raw.types.User) -> "Chat": + def _parse_user_chat(client, user: raw.types.User) -> 'Chat': peer_id = user.id return Chat( id=peer_id, type=enums.ChatType.BOT if user.bot else enums.ChatType.PRIVATE, - is_verified=getattr(user, "verified", None), - is_restricted=getattr(user, "restricted", None), - is_scam=getattr(user, "scam", None), - is_fake=getattr(user, "fake", None), - is_support=getattr(user, "support", None), + is_verified=getattr(user, 'verified', None), + is_restricted=getattr(user, 'restricted', None), + is_scam=getattr(user, 'scam', None), + is_fake=getattr(user, 'fake', None), + is_support=getattr(user, 'support', None), username=user.username, first_name=user.first_name, last_name=user.last_name, photo=types.ChatPhoto._parse(client, user.photo, peer_id, user.access_hash), - restrictions=types.List( - [types.Restriction._parse(r) for r in user.restriction_reason] - ) - or None, - dc_id=getattr(getattr(user, "photo", None), "dc_id", None), + restrictions=types.List([types.Restriction._parse(r) for r in user.restriction_reason]) or None, + dc_id=getattr(getattr(user, 'photo', None), 'dc_id', None), client=client, ) @staticmethod - def _parse_chat_chat(client, chat: raw.types.Chat) -> "Chat": + def _parse_chat_chat(client, chat: raw.types.Chat) -> 'Chat': peer_id = -chat.id return Chat( id=peer_id, type=enums.ChatType.GROUP, title=chat.title, - is_creator=getattr(chat, "creator", None), - photo=types.ChatPhoto._parse( - client, getattr(chat, "photo", None), peer_id, 0 - ), - permissions=types.ChatPermissions._parse( - getattr(chat, "default_banned_rights", None) - ), - members_count=getattr(chat, "participants_count", None), - dc_id=getattr(getattr(chat, "photo", None), "dc_id", None), - has_protected_content=getattr(chat, "noforwards", None), + is_creator=getattr(chat, 'creator', None), + photo=types.ChatPhoto._parse(client, getattr(chat, 'photo', None), peer_id, 0), + permissions=types.ChatPermissions._parse(getattr(chat, 'default_banned_rights', None)), + members_count=getattr(chat, 'participants_count', None), + dc_id=getattr(getattr(chat, 'photo', None), 'dc_id', None), + has_protected_content=getattr(chat, 'noforwards', None), client=client, - is_admin=bool(getattr(chat, "admin_rights", False)), + is_admin=bool(getattr(chat, 'admin_rights', False)), deactivated=chat.deactivated, ) @staticmethod - def _parse_channel_chat(client, channel: raw.types.Channel) -> "Chat": + def _parse_channel_chat(client, channel: raw.types.Channel) -> 'Chat': peer_id = utils.get_channel_id(channel.id) - restriction_reason = getattr(channel, "restriction_reason", []) + restriction_reason = getattr(channel, 'restriction_reason', []) return Chat( id=peer_id, - type=( - enums.ChatType.SUPERGROUP - if getattr(channel, "megagroup", None) - else enums.ChatType.CHANNEL - ), - is_verified=getattr(channel, "verified", None), - is_restricted=getattr(channel, "restricted", None), - is_creator=getattr(channel, "creator", None), - is_scam=getattr(channel, "scam", None), - is_fake=getattr(channel, "fake", None), + type=(enums.ChatType.SUPERGROUP if getattr(channel, 'megagroup', None) else enums.ChatType.CHANNEL), + is_verified=getattr(channel, 'verified', None), + is_restricted=getattr(channel, 'restricted', None), + is_creator=getattr(channel, 'creator', None), + is_scam=getattr(channel, 'scam', None), + is_fake=getattr(channel, 'fake', None), title=channel.title, - username=getattr(channel, "username", None), + username=getattr(channel, 'username', None), photo=types.ChatPhoto._parse( client, - getattr(channel, "photo", None), + getattr(channel, 'photo', None), peer_id, - getattr(channel, "access_hash", 0), + getattr(channel, 'access_hash', 0), ), - restrictions=types.List( - [types.Restriction._parse(r) for r in restriction_reason] - ) - or None, - permissions=types.ChatPermissions._parse( - getattr(channel, "default_banned_rights", None) - ), - members_count=getattr(channel, "participants_count", None), - dc_id=getattr(getattr(channel, "photo", None), "dc_id", None), - has_protected_content=getattr(channel, "noforwards", None), - is_admin=bool(getattr(channel, "admin_rights", False)), + restrictions=types.List([types.Restriction._parse(r) for r in restriction_reason]) or None, + permissions=types.ChatPermissions._parse(getattr(channel, 'default_banned_rights', None)), + members_count=getattr(channel, 'participants_count', None), + dc_id=getattr(getattr(channel, 'photo', None), 'dc_id', None), + has_protected_content=getattr(channel, 'noforwards', None), + is_admin=bool(getattr(channel, 'admin_rights', False)), client=client, ) @staticmethod def _parse( client, - message: Union[raw.types.Message, raw.types.MessageService], + message: raw.types.Message | raw.types.MessageService, users: dict, chats: dict, is_chat: bool, - ) -> "Chat": + ) -> 'Chat': from_id = utils.get_raw_peer_id(message.from_id) peer_id = utils.get_raw_peer_id(message.peer_id) chat_id = (peer_id or from_id) if is_chat else (from_id or peer_id) @@ -211,9 +195,9 @@ class Dialog(Object): def __init__( self, *, - client: "Client" = None, - chat: "types.Chat", - top_message: "types.Message", + client: 'Client' = None, + chat: 'types.Chat', + top_message: 'types.Message', unread_messages_count: int, unread_mentions_count: int, unread_mark: bool, @@ -229,7 +213,7 @@ class Dialog(Object): self.is_pinned = is_pinned @staticmethod - def _parse(client, dialog: "raw.types.Dialog", messages, users, chats) -> "Dialog": + def _parse(client, dialog: 'raw.types.Dialog', messages, users, chats) -> 'Dialog': return Dialog( chat=Chat._parse_dialog(client, dialog.peer, users, chats), top_message=messages.get(utils.get_peer_id(dialog.peer)), @@ -241,9 +225,7 @@ class Dialog(Object): ) -async def get_dialogs( - self: "Client", limit: int = 0 -) -> Optional[AsyncGenerator["types.Dialog", None]]: +async def get_dialogs(self: 'Client', limit: int = 0) -> AsyncGenerator['types.Dialog', None] | None: current = 0 total = limit or (1 << 31) - 1 limit = min(100, total) @@ -287,7 +269,7 @@ async def get_dialogs( return -@Client.on_message(filters.command("admlist", prefix) & filters.me) +@Client.on_message(filters.command('admlist', prefix) & filters.me) async def admlist(client: Client, message: types.Message): await message.edit("Retrieving information... (it'll take some time)") @@ -298,48 +280,46 @@ async def admlist(client: Client, message: types.Message): owned_usernamed_chats = [] async for dialog in get_dialogs(client): chat = dialog.chat - if getattr(chat, "deactivated", False): + if getattr(chat, 'deactivated', False): continue - if getattr(chat, "is_creator", False) and getattr(chat, "username", None): + if getattr(chat, 'is_creator', False) and getattr(chat, 'username', None): owned_usernamed_chats.append(chat) - elif getattr(chat, "is_creator", False): + elif getattr(chat, 'is_creator', False): owned_chats.append(chat) - elif getattr(chat, "is_admin", False): + elif getattr(chat, 'is_admin', False): adminned_chats.append(chat) - text = "Adminned chats:\n" + text = 'Adminned chats:\n' for index, chat in enumerate(adminned_chats): - cid = str(chat.id).replace("-100", "") - text += f"{index + 1}. {chat.title}\n" + cid = str(chat.id).replace('-100', '') + text += f'{index + 1}. {chat.title}\n' - text += "\nOwned chats:\n" + text += '\nOwned chats:\n' for index, chat in enumerate(owned_chats): - cid = str(chat.id).replace("-100", "") - text += f"{index + 1}. {chat.title}\n" + cid = str(chat.id).replace('-100', '') + text += f'{index + 1}. {chat.title}\n' - text += "\nOwned chats with username:\n" + text += '\nOwned chats with username:\n' for index, chat in enumerate(owned_usernamed_chats): - cid = str(chat.id).replace("-100", "") - text += f"{index + 1}. {chat.title}\n" + cid = str(chat.id).replace('-100', '') + text += f'{index + 1}. {chat.title}\n' stop = perf_counter() - total_count = ( - len(adminned_chats) + len(owned_chats) + len(owned_usernamed_chats) - ) + total_count = len(adminned_chats) + len(owned_chats) + len(owned_usernamed_chats) await message.edit( - text + "\n" - f"Total: {total_count}" - f"\nAdminned chats: {len(adminned_chats)}\n" - f"Owned chats: {len(owned_chats)}\n" - f"Owned chats with username: {len(owned_usernamed_chats)}\n\n" - f"Done in {round(stop - start, 3)} seconds.", + text + '\n' + f'Total: {total_count}' + f'\nAdminned chats: {len(adminned_chats)}\n' + f'Owned chats: {len(owned_chats)}\n' + f'Owned chats with username: {len(owned_usernamed_chats)}\n\n' + f'Done in {round(stop - start, 3)} seconds.', ) except Exception as e: await message.edit(format_exc(e)) return -@Client.on_message(filters.command("admcount", prefix) & filters.me) +@Client.on_message(filters.command('admcount', prefix) & filters.me) async def admcount(client: Client, message: types.Message): await message.edit("Retrieving information... (it'll take some time)") @@ -350,31 +330,31 @@ async def admcount(client: Client, message: types.Message): owned_usernamed_chats = 0 async for dialog in get_dialogs(client): chat = dialog.chat - if getattr(chat, "deactivated", False): + if getattr(chat, 'deactivated', False): continue - if getattr(chat, "is_creator", False) and getattr(chat, "username", None): + if getattr(chat, 'is_creator', False) and getattr(chat, 'username', None): owned_usernamed_chats += 1 - elif getattr(chat, "is_creator", False): + elif getattr(chat, 'is_creator', False): owned_chats += 1 - elif getattr(chat, "is_admin", False): + elif getattr(chat, 'is_admin', False): adminned_chats += 1 stop = perf_counter() total_count = adminned_chats + owned_chats + owned_usernamed_chats await message.edit( - f"Total: {total_count}" - f"\nAdminned chats: {adminned_chats}\n" - f"Owned chats: {owned_chats}\n" - f"Owned chats with username: {owned_usernamed_chats}\n\n" - f"Done in {round(stop - start, 3)} seconds.\n\n" - f"Get full list: {prefix}admlist", + f'Total: {total_count}' + f'\nAdminned chats: {adminned_chats}\n' + f'Owned chats: {owned_chats}\n' + f'Owned chats with username: {owned_usernamed_chats}\n\n' + f'Done in {round(stop - start, 3)} seconds.\n\n' + f'Get full list: {prefix}admlist', ) except Exception as e: await message.edit(format_exc(e)) return -modules_help["admlist"] = { - "admcount": "Get count of adminned and owned chats", - "admlist": "Get list of adminned and owned chats", +modules_help['admlist'] = { + 'admcount': 'Get count of adminned and owned chats', + 'admlist': 'Get list of adminned and owned chats', } diff --git a/modules/afk.py b/modules/afk.py index 00af2e4..4794225 100644 --- a/modules/afk.py +++ b/modules/afk.py @@ -17,72 +17,62 @@ import datetime from pyrogram import Client, filters, types - from utils.db import db from utils.misc import modules_help, prefix # avoid using global variables afk_info = db.get( - "core.afk", - "afk_info", + 'core.afk', + 'afk_info', { - "start": 0, - "is_afk": False, - "reason": "", + 'start': 0, + 'is_afk': False, + 'reason': '', }, ) -is_afk = filters.create(lambda _, __, ___: afk_info["is_afk"]) +is_afk = filters.create(lambda _, __, ___: afk_info['is_afk']) is_support = filters.create(lambda _, __, message: message.chat.is_support) @Client.on_message( - is_afk - & (filters.private | filters.mentioned) - & ~filters.channel - & ~filters.me - & ~filters.bot - & ~is_support + is_afk & (filters.private | filters.mentioned) & ~filters.channel & ~filters.me & ~filters.bot & ~is_support ) async def afk_handler(_, message: types.Message): - start = datetime.datetime.fromtimestamp(afk_info["start"]) + start = datetime.datetime.fromtimestamp(afk_info['start']) end = datetime.datetime.now().replace(microsecond=0) afk_time = end - start - await message.reply( - f"I'm AFK {afk_time}\nReason: {afk_info['reason']}" - ) + await message.reply(f"I'm AFK {afk_time}\nReason: {afk_info['reason']}") -@Client.on_message(filters.command("afk", prefix) & filters.me) +@Client.on_message(filters.command('afk', prefix) & filters.me) async def afk(_, message): if len(message.text.split()) >= 2: - reason = message.text.split(" ", maxsplit=1)[1] + reason = message.text.split(' ', maxsplit=1)[1] else: - reason = "None" + reason = 'None' - afk_info["start"] = int(datetime.datetime.now().timestamp()) - afk_info["is_afk"] = True - afk_info["reason"] = reason + afk_info['start'] = int(datetime.datetime.now().timestamp()) + afk_info['is_afk'] = True + afk_info['reason'] = reason - await message.edit(f"I'm going AFK.\n" f"Reason: {reason}") + await message.edit(f"I'm going AFK.\nReason: {reason}") - db.set("core.afk", "afk_info", afk_info) + db.set('core.afk', 'afk_info', afk_info) -@Client.on_message(filters.command("unafk", prefix) & filters.me) +@Client.on_message(filters.command('unafk', prefix) & filters.me) async def unafk(_, message): - if afk_info["is_afk"]: - start = datetime.datetime.fromtimestamp(afk_info["start"]) + if afk_info['is_afk']: + start = datetime.datetime.fromtimestamp(afk_info['start']) end = datetime.datetime.now().replace(microsecond=0) afk_time = end - start - await message.edit( - f"I'm not AFK anymore.\n" f"I was afk {afk_time}" - ) - afk_info["is_afk"] = False + await message.edit(f"I'm not AFK anymore.\nI was afk {afk_time}") + afk_info['is_afk'] = False else: await message.edit("You weren't afk") - db.set("core.afk", "afk_info", afk_info) + db.set('core.afk', 'afk_info', afk_info) -modules_help["afk"] = {"afk [reason]": "Go to afk", "unafk": "Get out of AFK"} +modules_help['afk'] = {'afk [reason]': 'Go to afk', 'unafk': 'Get out of AFK'} diff --git a/modules/aimage.py b/modules/aimage.py index c24a5cd..e144afa 100644 --- a/modules/aimage.py +++ b/modules/aimage.py @@ -1,129 +1,116 @@ import os + from PIL import Image - -from pyrogram import Client, filters, enums +from pyrogram import Client, enums, filters from pyrogram.types import Message - from utils.misc import modules_help, prefix from utils.scripts import format_exc, import_library -genai = import_library("google.generativeai", "google-generativeai") +genai = import_library('google.generativeai', 'google-generativeai') from utils.config import gemini_key genai.configure(api_key=gemini_key) generation_config_cook = { - "temperature": 0.35, - "top_p": 0.95, - "top_k": 40, - "max_output_tokens": 1024, + 'temperature': 0.35, + 'top_p': 0.95, + 'top_k': 40, + 'max_output_tokens': 1024, } -model = genai.GenerativeModel("gemini-1.5-flash-latest") -model_cook = genai.GenerativeModel( - model_name="gemini-1.5-flash-latest", generation_config=generation_config_cook -) +model = genai.GenerativeModel('gemini-1.5-flash-latest') +model_cook = genai.GenerativeModel(model_name='gemini-1.5-flash-latest', generation_config=generation_config_cook) -@Client.on_message(filters.command("getai", prefix) & filters.me) +@Client.on_message(filters.command('getai', prefix) & filters.me) async def getai(_, message: Message): try: - await message.edit_text("Please Wait...") + await message.edit_text('Please Wait...') try: base_img = await message.reply_to_message.download() except AttributeError: - return await message.edit_text("Please reply to an image...") + return await message.edit_text('Please reply to an image...') img = Image.open(base_img) - prompt = "Get details of given image, be as accurate as possible." + prompt = 'Get details of given image, be as accurate as possible.' response = model.generate_content([prompt, img]) - await message.edit_text( - f"**Detail Of Image:** {response.text}", parse_mode=enums.ParseMode.MARKDOWN - ) + await message.edit_text(f'**Detail Of Image:** {response.text}', parse_mode=enums.ParseMode.MARKDOWN) os.remove(base_img) return except Exception as e: - await message.edit_text(f"An error occurred: {format_exc(e)}") + await message.edit_text(f'An error occurred: {format_exc(e)}') -@Client.on_message(filters.command("aicook", prefix) & filters.me) +@Client.on_message(filters.command('aicook', prefix) & filters.me) async def aicook(_, message: Message): if message.reply_to_message: try: - await message.edit_text("Cooking...") + await message.edit_text('Cooking...') try: base_img = await message.reply_to_message.download() except AttributeError: - return await message.edit_text( - "Please reply to an image..." - ) + return await message.edit_text('Please reply to an image...') img = Image.open(base_img) cook_img = [ - "Accurately identify the baked good in the image and provide an appropriate and recipe consistent with your analysis. ", + 'Accurately identify the baked good in the image and provide an appropriate and recipe consistent with your analysis. ', img, ] response = model_cook.generate_content(cook_img) - await message.edit_text( - f"{response.text}", parse_mode=enums.ParseMode.MARKDOWN - ) + await message.edit_text(f'{response.text}', parse_mode=enums.ParseMode.MARKDOWN) os.remove(base_img) return except Exception as e: await message.edit_text(str(e)) - return await message.edit_text("Please reply to an image...") + return await message.edit_text('Please reply to an image...') -@Client.on_message(filters.command("aiseller", prefix) & filters.me) +@Client.on_message(filters.command('aiseller', prefix) & filters.me) async def aiseller(_, message: Message): if message.reply_to_message: try: - await message.edit_text("Generating...") + await message.edit_text('Generating...') if len(message.command) > 1: taud = message.text.split(maxsplit=1)[1] else: return await message.edit_text( - f"Usage: {prefix}aiseller [target audience] [reply to product image]" + f'Usage: {prefix}aiseller [target audience] [reply to product image]' ) try: base_img = await message.reply_to_message.download() except AttributeError: - return await message.edit_text( - "Please reply to an image..." - ) + return await message.edit_text('Please reply to an image...') img = Image.open(base_img) sell_img = [ - "Given an image of a product and its target audience, write an engaging marketing description", - "Product Image: ", + 'Given an image of a product and its target audience, write an engaging marketing description', + 'Product Image: ', img, - "Target Audience: ", + 'Target Audience: ', taud, ] response = model.generate_content(sell_img) - await message.edit_text( - f"{response.text}", parse_mode=enums.ParseMode.MARKDOWN - ) + await message.edit_text(f'{response.text}', parse_mode=enums.ParseMode.MARKDOWN) os.remove(base_img) return except Exception: await message.edit_text( - f"Usage: {prefix}aiseller [target audience] [reply to product image]" + f'Usage: {prefix}aiseller [target audience] [reply to product image]' ) - return await message.edit_text("Please reply to an image...") + return await message.edit_text('Please reply to an image...') -modules_help["aimage"] = { - "getai [reply to image]*": "Get details of image with Ai", - "aicook [reply to image]*": "Generate Cooking instrunctions of the given food image", - "aiseller [target audience] [reply to product image]*": "Generate a promotional message for the given image product for the given target audience", +modules_help['aimage'] = { + 'getai [reply to image]*': 'Get details of image with Ai', + 'aicook [reply to image]*': 'Generate Cooking instrunctions of the given food image', + 'aiseller [target audience] [reply to product image]*': 'Generate a promotional message for the given image product for the given target audience', } diff --git a/modules/amogus.py b/modules/amogus.py index 514f1ad..9c1f698 100644 --- a/modules/amogus.py +++ b/modules/amogus.py @@ -1,51 +1,45 @@ from io import BytesIO from random import randint - -from pyrogram import Client, filters, enums -from pyrogram.types import Message - -from requests import get -from PIL import Image, ImageFont, ImageDraw from textwrap import wrap +from PIL import Image, ImageDraw, ImageFont +from pyrogram import Client, enums, filters +from pyrogram.types import Message +from requests import get from utils.misc import modules_help, prefix -@Client.on_message(filters.command("amogus", prefix) & filters.me) +@Client.on_message(filters.command('amogus', prefix) & filters.me) async def amogus(client: Client, message: Message): - text = " ".join(message.command[1:]) + text = ' '.join(message.command[1:]) await message.edit( - "amgus, tun tun tun tun tun tun tun tudududn tun tun...", + 'amgus, tun tun tun tun tun tun tun tudududn tun tun...', parse_mode=enums.ParseMode.HTML, ) clr = randint(1, 12) - url = "https://raw.githubusercontent.com/The-MoonTg-project/AmongUs/master/" - font = ImageFont.truetype(BytesIO(get(url + "bold.ttf").content), 60) - imposter = Image.open(BytesIO(get(f"{url}{clr}.png").content)) + url = 'https://raw.githubusercontent.com/The-MoonTg-project/AmongUs/master/' + font = ImageFont.truetype(BytesIO(get(url + 'bold.ttf').content), 60) + imposter = Image.open(BytesIO(get(f'{url}{clr}.png').content)) - text_ = "\n".join(["\n".join(wrap(part, 30)) for part in text.split("\n")]) - bbox = ImageDraw.Draw(Image.new("RGB", (1, 1))).multiline_textbbox( - (0, 0), text_, font, stroke_width=2 - ) + text_ = '\n'.join(['\n'.join(wrap(part, 30)) for part in text.split('\n')]) + bbox = ImageDraw.Draw(Image.new('RGB', (1, 1))).multiline_textbbox((0, 0), text_, font, stroke_width=2) # w, h = bbox[2] - bbox[0], bbox[3] - bbox[1] w, h = bbox[2], bbox[3] - text = Image.new("RGBA", (w + 30, h + 30)) - ImageDraw.Draw(text).multiline_text( - (15, 15), text_, "#FFF", font, stroke_width=2, stroke_fill="#000" - ) + text = Image.new('RGBA', (w + 30, h + 30)) + ImageDraw.Draw(text).multiline_text((15, 15), text_, '#FFF', font, stroke_width=2, stroke_fill='#000') w = imposter.width + text.width + 10 h = max(imposter.height, text.height) - image = Image.new("RGBA", (w, h)) + image = Image.new('RGBA', (w, h)) image.paste(imposter, (0, h - imposter.height), imposter) image.paste(text, (w - text.width, 0), text) image.thumbnail((512, 512)) output = BytesIO() - output.name = "imposter.webp" + output.name = 'imposter.webp' image.save(output) output.seek(0) @@ -53,6 +47,4 @@ async def amogus(client: Client, message: Message): await client.send_sticker(message.chat.id, output) -modules_help["amogus"] = { - "amogus [text]": "amgus, tun tun tun tun tun tun tun tudududn tun tun" -} +modules_help['amogus'] = {'amogus [text]': 'amgus, tun tun tun tun tun tun tun tudududn tun tun'} diff --git a/modules/amongus.py b/modules/amongus.py index 8ecaad6..addb938 100644 --- a/modules/amongus.py +++ b/modules/amongus.py @@ -5,92 +5,78 @@ from io import BytesIO from random import choice, randint from textwrap import wrap -from PIL import Image, ImageDraw, ImageFont -from click import edit import requests -from pyrogram import Client, filters, enums +from PIL import Image, ImageDraw, ImageFont +from pyrogram import Client, filters from pyrogram.types import Message - -from utils.scripts import edit_or_reply, ReplyCheck from utils.misc import modules_help, prefix +from utils.scripts import ReplyCheck, edit_or_reply async def amongus_gen(text: str, clr: int) -> str: - url = ( - "https://github.com/TgCatUB/CatUserbot-Resources/raw/master/Resources/Amongus/" - ) + url = 'https://github.com/TgCatUB/CatUserbot-Resources/raw/master/Resources/Amongus/' font = ImageFont.truetype( BytesIO( - requests.get( - "https://github.com/TgCatUB/CatUserbot-Resources/raw/master/Resources/fonts/bold.ttf" - ).content + requests.get('https://github.com/TgCatUB/CatUserbot-Resources/raw/master/Resources/fonts/bold.ttf').content ), 60, ) - imposter = Image.open(BytesIO(requests.get(f"{url}{clr}.png").content)) - text_ = "\n".join("\n".join(wrap(part, 30)) for part in text.split("\n")) - bbox = ImageDraw.Draw(Image.new("RGB", (1, 1))).multiline_textbbox( - (0, 0), text_, font, stroke_width=2 - ) + imposter = Image.open(BytesIO(requests.get(f'{url}{clr}.png').content)) + text_ = '\n'.join('\n'.join(wrap(part, 30)) for part in text.split('\n')) + bbox = ImageDraw.Draw(Image.new('RGB', (1, 1))).multiline_textbbox((0, 0), text_, font, stroke_width=2) w, h = bbox[2], bbox[3] - text = Image.new("RGBA", (w + 30, h + 30)) - ImageDraw.Draw(text).multiline_text( - (15, 15), text_, "#FFF", font, stroke_width=2, stroke_fill="#000" - ) + text = Image.new('RGBA', (w + 30, h + 30)) + ImageDraw.Draw(text).multiline_text((15, 15), text_, '#FFF', font, stroke_width=2, stroke_fill='#000') w = imposter.width + text.width + 10 h = max(imposter.height, text.height) - image = Image.new("RGBA", (w, h)) + image = Image.new('RGBA', (w, h)) image.paste(imposter, (0, h - imposter.height), imposter) image.paste(text, (w - text.width, 0), text) image.thumbnail((512, 512)) output = BytesIO() - output.name = "imposter.webp" - image.save(output, "WebP") + output.name = 'imposter.webp' + image.save(output, 'WebP') output.seek(0) return output async def get_imposter_img(text: str) -> BytesIO: background = requests.get( - f"https://github.com/TgCatUB/CatUserbot-Resources/raw/master/Resources/imposter/impostor{randint(1, 22)}.png" + f'https://github.com/TgCatUB/CatUserbot-Resources/raw/master/Resources/imposter/impostor{randint(1, 22)}.png' ).content font = requests.get( - "https://github.com/TgCatUB/CatUserbot-Resources/raw/master/Resources/fonts/roboto_regular.ttf" + 'https://github.com/TgCatUB/CatUserbot-Resources/raw/master/Resources/fonts/roboto_regular.ttf' ).content font = BytesIO(font) font = ImageFont.truetype(font, 30) - image = Image.new("RGBA", (1, 1), (0, 0, 0, 0)) + image = Image.new('RGBA', (1, 1), (0, 0, 0, 0)) draw = ImageDraw.Draw(image) - bbox = ImageDraw.Draw(Image.new("RGB", (1, 1))).multiline_textbbox( - (0, 0), text, font, stroke_width=2 - ) + bbox = ImageDraw.Draw(Image.new('RGB', (1, 1))).multiline_textbbox((0, 0), text, font, stroke_width=2) w, h = bbox[2], bbox[3] image = Image.open(BytesIO(background)) x, y = image.size draw = ImageDraw.Draw(image) - draw.multiline_text( - ((x - w) // 2, (y - h) // 2), text=text, font=font, fill="white", align="center" - ) + draw.multiline_text(((x - w) // 2, (y - h) // 2), text=text, font=font, fill='white', align='center') output = BytesIO() - output.name = "impostor.png" - image.save(output, "PNG") + output.name = 'impostor.png' + image.save(output, 'PNG') output.seek(0) return output -@Client.on_message(filters.command("amongus", prefix) & filters.me) +@Client.on_message(filters.command('amongus', prefix) & filters.me) async def amongus_cmd(client: Client, message: Message): - text = " ".join(message.command[1:]) if len(message.command) > 1 else "" + text = ' '.join(message.command[1:]) if len(message.command) > 1 else '' reply = message.reply_to_message - await message.edit("tun tun tun...") + await message.edit('tun tun tun...') if not text and reply: - text = reply.text or reply.caption or "" + text = reply.text or reply.caption or '' - clr = re.findall(r"-c\d+", text) + clr = re.findall(r'-c\d+', text) try: clr = clr[0] - clr = clr.replace("-c", "") - text = text.replace(f"-c{clr}", "") + clr = clr.replace('-c', '') + text = text.replace(f'-c{clr}', '') clr = int(clr) if clr > 12 or clr < 1: clr = randint(1, 12) @@ -99,9 +85,9 @@ async def amongus_cmd(client: Client, message: Message): if not text: if not reply: - text = f"{message.from_user.first_name} Was a traitor!" + text = f'{message.from_user.first_name} Was a traitor!' else: - text = f"{reply.from_user.first_name} Was a traitor!" + text = f'{reply.from_user.first_name} Was a traitor!' imposter_file = await amongus_gen(text, clr) await message.delete() @@ -112,22 +98,22 @@ async def amongus_cmd(client: Client, message: Message): ) -@Client.on_message(filters.command("imposter", prefix) & filters.me) +@Client.on_message(filters.command('imposter', prefix) & filters.me) async def imposter_cmd(client: Client, message: Message): remain = randint(1, 2) - imps = ["wasn't the impostor", "was the impostor"] + imps = ["wasn't the impostor", 'was the impostor'] if message.reply_to_message: user = message.reply_to_message.from_user - text = f"{user.first_name} {choice(imps)}." + text = f'{user.first_name} {choice(imps)}.' else: args = message.text.split()[1:] if args: - text = " ".join(args) + text = ' '.join(args) else: - text = f"{message.from_user.first_name} {choice(imps)}." + text = f'{message.from_user.first_name} {choice(imps)}.' - text += f"\n{remain} impostor(s) remain." + text += f'\n{remain} impostor(s) remain.' imposter_file = await get_imposter_img(text) await message.delete() await client.send_photo( @@ -137,9 +123,9 @@ async def imposter_cmd(client: Client, message: Message): ) -@Client.on_message(filters.command(["imp", "impn"], prefix) & filters.me) +@Client.on_message(filters.command(['imp', 'impn'], prefix) & filters.me) async def imp_animation(client: Client, message: Message): - name = " ".join(message.command[1:]) if len(message.command) > 1 else "" + name = ' '.join(message.command[1:]) if len(message.command) > 1 else '' if not name: reply = message.reply_to_message if reply: @@ -148,61 +134,55 @@ async def imp_animation(client: Client, message: Message): name = message.from_user.first_name cmd = message.command[0].lower() - text1 = await edit_or_reply(message, "Uhmm... Something is wrong here!!") + text1 = await edit_or_reply(message, 'Uhmm... Something is wrong here!!') await asyncio.sleep(2) await text1.delete() - stcr1 = await client.send_sticker(message.chat.id, "CAADAQADRwADnjOcH98isYD5RJTwAg") - text2 = await message.reply( - f"{message.from_user.first_name}: I have to call discussion" - ) + stcr1 = await client.send_sticker(message.chat.id, 'CAADAQADRwADnjOcH98isYD5RJTwAg') + text2 = await message.reply(f'{message.from_user.first_name}: I have to call discussion') await asyncio.sleep(3) await stcr1.delete() await text2.delete() - stcr2 = await client.send_sticker(message.chat.id, "CAADAQADRgADnjOcH9odHIXtfgmvAg") - text3 = await message.reply( - f"{message.from_user.first_name}: We have to eject the imposter or will lose" - ) + stcr2 = await client.send_sticker(message.chat.id, 'CAADAQADRgADnjOcH9odHIXtfgmvAg') + text3 = await message.reply(f'{message.from_user.first_name}: We have to eject the imposter or will lose') await asyncio.sleep(3) await stcr2.delete() await text3.delete() - stcr3 = await client.send_sticker(message.chat.id, "CAADAQADOwADnjOcH77v3Ap51R7gAg") - text4 = await message.reply("Others: Where???") + stcr3 = await client.send_sticker(message.chat.id, 'CAADAQADOwADnjOcH77v3Ap51R7gAg') + text4 = await message.reply('Others: Where???') await asyncio.sleep(2) - await text4.edit("Others: Who??") + await text4.edit('Others: Who??') await asyncio.sleep(2) - await text4.edit( - f"{message.from_user.first_name}: Its {name}, I saw {name} using vent" - ) + await text4.edit(f'{message.from_user.first_name}: Its {name}, I saw {name} using vent') await asyncio.sleep(3) - await text4.edit(f"Others: Okay.. Vote {name}") + await text4.edit(f'Others: Okay.. Vote {name}') await asyncio.sleep(2) await stcr3.delete() await text4.delete() - stcr4 = await client.send_sticker(message.chat.id, "CAADAQADLwADnjOcH-wxu-ehy6NRAg") - event = await message.reply(f"{name} is ejected.......") + stcr4 = await client.send_sticker(message.chat.id, 'CAADAQADLwADnjOcH-wxu-ehy6NRAg') + event = await message.reply(f'{name} is ejected.......') # Ejection animation for _ in range(9): await asyncio.sleep(0.5) curr_pos = _ + 1 - spaces_before = "ㅤ" * curr_pos - await event.edit(f"{spaces_before}ඞ{'ㅤ' * (9 - curr_pos)}") + spaces_before = 'ㅤ' * curr_pos + await event.edit(f'{spaces_before}ඞ{"ㅤ" * (9 - curr_pos)}') await asyncio.sleep(0.5) - await event.edit("ㅤㅤㅤㅤㅤㅤㅤㅤㅤ") + await event.edit('ㅤㅤㅤㅤㅤㅤㅤㅤㅤ') await asyncio.sleep(0.2) await stcr4.delete() - if cmd == "imp": + if cmd == 'imp': text = f".    。    •   ゚  。   .\n .      .     。   。 .  \n\n .    。   ඞ 。 .    •     •\n\n ゚{name} was an Imposter. 。 .     。 . 。 . \n  . 。   . \n ' 0 Impostor remains   。 .   . 。 . 。   . 。   . . . , 。\n  ゚   .  . ,   。   .   . 。" - sticker_id = "CAADAQADLQADnjOcH39IqwyR6Q_0Ag" + sticker_id = 'CAADAQADLQADnjOcH39IqwyR6Q_0Ag' else: text = f".    。    •   ゚  。   .\n .      .     。   。 .  \n\n .    。   ඞ 。 .    •     •\n\n ゚{name} was not an Imposter. 。 .     。 . 。 . \n  . 。   . \n ' 1 Impostor remains   。 .   . 。 . 。   . 。   . . . , 。\n  ゚   .  . ,   。   .   . 。" - sticker_id = "CAADAQADQAADnjOcH-WOkB8DEctJAg" + sticker_id = 'CAADAQADQAADnjOcH-WOkB8DEctJAg' await event.edit(text) await asyncio.sleep(4) @@ -210,9 +190,9 @@ async def imp_animation(client: Client, message: Message): await client.send_sticker(message.chat.id, sticker_id) -modules_help["amongus"] = { - "amongus": "Create Among Us themed sticker [text/reply] [-c1 to -c12 for colors]", - "imposter": "Create Among Us imposter image [username/reply]", - "imp": "Create Among Us ejection animation (imposter)", - "impn": "Create Among Us ejection animation (not imposter)", +modules_help['amongus'] = { + 'amongus': 'Create Among Us themed sticker [text/reply] [-c1 to -c12 for colors]', + 'imposter': 'Create Among Us imposter image [username/reply]', + 'imp': 'Create Among Us ejection animation (imposter)', + 'impn': 'Create Among Us ejection animation (not imposter)', } diff --git a/modules/animations.py b/modules/animations.py index 8e80c2d..53c1b11 100644 --- a/modules/animations.py +++ b/modules/animations.py @@ -1,478 +1,465 @@ -import asyncio -from collections import deque -from pyrogram import Client, filters, enums -from pyrogram.types import Message -from utils.misc import modules_help, prefix - -mention = ( - f"Moon" -) - - -@Client.on_message(filters.command("stupid", prefix) & filters.me) -async def stupid(_, message: Message): - animation_interval = 0.5 - animation_ttl = range(0, 14) - await message.edit_text("stupid boy") - animation_chars = [ - "YOUR BRAIN ➡️ 🧠\n\n🧠 (^_^)🗑", - "YOUR BRAIN ➡️ 🧠\n\n🧠 (^_^) 🗑", - "YOUR BRAIN ➡️ 🧠\n\n🧠 (^_^) 🗑", - "YOUR BRAIN ➡️ 🧠\n\n🧠 (^_^) 🗑", - "YOUR BRAIN ➡️ 🧠\n\n🧠 (^_^) 🗑", - "YOUR BRAIN ➡️ 🧠\n\n🧠< (^_^ <) 🗑", - "YOUR BRAIN ➡️ 🧠\n\n(> ^_^)>🧠 🗑", - "YOUR BRAIN ➡️ 🧠\n\n (> ^_^)>🧠 🗑", - "YOUR BRAIN ➡️ 🧠\n\n (> ^_^)>🧠 🗑", - "YOUR BRAIN ➡️ 🧠\n\n (> ^_^)>🧠 🗑", - "YOUR BRAIN ➡️ 🧠\n\n (> ^_^)>🧠 🗑", - "YOUR BRAIN ➡️ 🧠\n\n (> ^_^)>🧠🗑", - "YOUR BRAIN ➡️ 🧠\n\n (> ^_^)>🗑", - "YOUR BRAIN ➡️ 🧠\n\n < (^_^ <)🗑", - ] - - for i in animation_ttl: - - await asyncio.sleep(animation_interval) - await message.edit_text( - animation_chars[i % 14], parse_mode=enums.ParseMode.HTML - ) - - -@Client.on_message(filters.command("bombs", prefix) & filters.me) -async def bombs(_, message: Message): - - await message.edit_text("▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n") - await asyncio.sleep(0.5) - await message.edit_text("💣💣💣💣 \n▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n") - await asyncio.sleep(0.5) - await message.edit_text("▪️▪️▪️▪️ \n💣💣💣💣 \n▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n") - await asyncio.sleep(0.5) - await message.edit_text("▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n💣💣💣💣 \n▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n") - await asyncio.sleep(0.5) - await message.edit_text("▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n💣💣💣💣 \n▪️▪️▪️▪️ \n") - await asyncio.sleep(0.5) - await message.edit_text("▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n💣💣💣💣 \n") - await asyncio.sleep(1) - await message.edit_text("▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n💥💥💥💥 \n") - await asyncio.sleep(0.5) - await message.edit_text("▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n💥💥💥💥 \n💥💥💥💥 \n") - await asyncio.sleep(0.5) - await message.edit_text("▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n😵😵😵😵 \n") - await asyncio.sleep(0.5) - await message.edit_text("RIP PLOXXX......") - await asyncio.sleep(2) - - -@Client.on_message(filters.command("call", prefix) & filters.me) -async def cell(_, message: Message): - - animation_interval = 3 - animation_ttl = range(0, 18) - await message.edit_text("Calling Pavel Durov (ceo of telegram)......") - animation_chars = [ - "Connecting To Telegram Headquarters...", - "Call Connected.", - "Telegram: Hello This is Telegram HQ. Who is this?", - f"Me: Yo this is Moon ,Please Connect me to my lil bro,Pavel Durov ", - "User Authorised.", - "Calling Shivamani At +916969696969", - "Private Call Connected...", - "Me: Hello Sir, Please Ban This Telegram Account.", - "Shivamani : May I Know Who Is This?", - f"Me: Yo Brah, I Am Moon", - "Shivamani : OMG!!! Long time no see, Wassup cat...\nI'll Make Sure That Guy Account Will Get Blocked Within 24Hrs.", - "Me: Thanks, See You Later Brah.", - "Shivamani : Please Don't Thank Brah, Telegram Is Our's. Just Gimme A Call When You Become Free.", - "Me: Is There Any Issue/Emergency???", - "Shivamani : Yes Sur, There Is A Bug In Telegram v69.6.9.\nI Am Not Able To Fix It. If Possible, Please Help Fix The Bug.", - "Me: Send Me The App On My Telegram Account, I Will Fix The Bug & Send You.", - "Shivamani : Sure Sur \nTC Bye Bye :)", - "Private Call Disconnected.", - ] - for i in animation_ttl: - await asyncio.sleep(animation_interval) - await message.edit_text(animation_chars[i % 18]) - - -@Client.on_message(filters.command("wtf", prefix) & filters.me) -async def wtf(_, message: Message): - - animation_interval = 0.8 - animation_ttl = range(0, 5) - await message.edit_text("wtf") - animation_chars = [ - "What", - "What The", - "What The F", - "What The F Brah", - "What The F Brah\nhttps://telegra.ph//file/f3b760e4a99340d331f9b.jpg", - ] - for i in animation_ttl: - - await asyncio.sleep(animation_interval) - await message.edit_text(animation_chars[i % 5], parse_mode=enums.ParseMode.HTML) - - -@Client.on_message(filters.command("ding", prefix) & filters.me) -async def ding(_, message: Message): - animation_interval = 0.3 - animation_ttl = range(0, 30) - animation_chars = [ - "🔴⬛⬛⬜⬜\n⬜⬜⬜⬜⬜\n⬜⬜⬜⬜⬜", - "⬜⬜⬛⬜⬜\n⬜⬛⬜⬜⬜\n🔴⬜⬜⬜⬜", - "⬜⬜⬛⬜⬜\n⬜⬜⬛⬜⬜\n⬜⬜🔴⬜⬜", - "⬜⬜⬛⬜⬜\n⬜⬜⬜⬛⬜\n⬜⬜⬜⬜🔴", - "⬜⬜⬛⬛🔴\n⬜⬜⬜⬜⬜\n⬜⬜⬜⬜⬜", - "⬜⬜⬛⬜⬜\n⬜⬜⬜⬛⬜\n⬜⬜⬜⬜🔴", - "⬜⬜⬛⬜⬜\n⬜⬜⬛⬜⬜\n⬜⬜🔴⬜⬜", - "⬜⬜⬛⬜⬜\n⬜⬛⬜⬜⬜\n🔴⬜⬜⬜⬜", - "🔴⬛⬛⬜⬜\n⬜⬜⬜⬜⬜\n⬜⬜⬜⬜⬜", - "⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜\n⬜ Moon IS BEST ⬜\n⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜", - ] - - await message.edit_text("ding..dong..ding..dong ...") - await asyncio.sleep(4) - for i in animation_ttl: - await asyncio.sleep(animation_interval) - await message.edit_text( - animation_chars[i % 10], parse_mode=enums.ParseMode.HTML - ) - - -@Client.on_message(filters.command("hypo", prefix) & filters.me) -async def hypo(_, message: Message): - animation_interval = 0.3 - animation_ttl = range(0, 15) - await message.edit_text("hypo....") - animation_chars = [ - "⬜⬜⬜⬜⬜⬜⬜\n⬜⬜⬜⬜⬜⬜⬜\n⬜⬜⬜⬜⬜⬜⬜\n⬜⬜⬜⬜⬜⬜⬜\n⬜⬜⬜⬜⬜⬜⬜\n⬜⬜⬜⬜⬜⬜⬜\n⬜⬜⬜⬜⬜⬜⬜", - "⬜⬜⬜⬜⬜⬜⬜\n⬜⬜⬜⬜⬜⬜⬜\n⬜⬜⬜⬜⬜⬜⬜\n⬜⬜⬜⬛⬜⬜⬜\n⬜⬜⬜⬜⬜⬜⬜\n⬜⬜⬜⬜⬜⬜⬜\n⬜⬜⬜⬜⬜⬜⬜", - "⬜⬜⬜⬜⬜⬜⬜\n⬜⬜⬜⬜⬜⬜⬜\n⬜⬜⬛⬛⬛⬜⬜\n⬜⬜⬛⬜⬛⬜⬜\n⬜⬜⬛⬛⬛⬜⬜\n⬜⬜⬜⬜⬜⬜⬜\n⬜⬜⬜⬜⬜⬜⬜", - "⬜⬜⬜⬜⬜⬜⬜\n⬜⬛⬛⬛⬛⬛⬜\n⬜⬛⬜⬜⬜⬛⬜\n⬜⬛⬜⬜⬜⬛⬜\n⬜⬛⬜⬜⬜⬛⬜\n⬜⬛⬛⬛⬛⬛⬜\n⬜⬜⬜⬜⬜⬜⬜", - "⬛⬛⬛⬛⬛⬛⬛\n⬛⬜⬜⬜⬜⬜⬛\n⬛⬜⬜⬜⬜⬜⬛\n⬛⬜⬜⬜⬜⬜⬛\n⬛⬜⬜⬜⬜⬜⬛\n⬛⬜⬜⬜⬜⬜⬛\n⬛⬛⬛⬛⬛⬛⬛", - "⬛⬜⬛⬜⬛⬜⬛⬜\n⬜⬛⬜⬛⬜⬛⬜⬛\n⬛⬜⬛⬜⬛⬜⬛⬜\n⬜⬛⬜⬛⬜⬛⬜⬛\n⬛⬜⬛⬜⬛⬜⬛⬜\n⬜⬛⬜⬛⬜⬛⬜⬛\n⬛⬜⬛⬜⬛⬜⬛⬜\n⬜⬛⬜⬛⬜⬛⬜⬛", - "⬜⬛⬜⬛⬜⬛⬜⬛\n⬛⬜⬛⬜⬛⬜⬛⬜\n⬜⬛⬜⬛⬜⬛⬜⬛\n⬛⬜⬛⬜⬛⬜⬛⬜\n⬜⬛⬜⬛⬜⬛⬜⬛\n⬛⬜⬛⬜⬛⬜⬛⬜\n⬜⬛⬜⬛⬜⬛⬜⬛\n⬛⬜⬛⬜⬛⬜⬛⬜", - "⬜⬜⬜⬜⬜⬜⬜\n⬜⬛⬛⬛⬛⬛⬜\n⬜⬛⬜⬜⬜⬛⬜\n⬜⬛⬜⬛⬜⬛⬜\n⬜⬛⬜⬜⬜⬛⬜\n⬜⬛⬛⬛⬛⬛⬜\n⬜⬜⬜⬜⬜⬜⬜", - "⬛⬛⬛⬛⬛⬛⬛\n⬛⬜⬜⬜⬜⬜⬛\n⬛⬜⬛⬛⬛⬜⬛\n⬛⬜⬛⬜⬛⬜⬛\n⬛⬜⬛⬛⬛⬜⬛\n⬛⬜⬜⬜⬜⬜⬛\n⬛⬛⬛⬛⬛⬛⬛", - "⬜⬜⬜⬜⬜⬜⬜\n⬜⬛⬛⬛⬛⬛⬜\n⬜⬛⬜⬜⬜⬛⬜\n⬜⬛⬜⬛⬜⬛⬜\n⬜⬛⬜⬜⬜⬛⬜\n⬜⬛⬛⬛⬛⬛⬜\n⬜⬜⬜⬜⬜⬜⬜", - "⬛⬛⬛⬛⬛⬛⬛\n⬛⬜⬜⬜⬜⬜⬛\n⬛⬜⬛⬛⬛⬜⬛\n⬛⬜⬛⬜⬛⬜⬛\n⬛⬜⬛⬛⬛⬜⬛\n⬛⬜⬜⬜⬜⬜⬛\n⬛⬛⬛⬛⬛⬛⬛", - "⬜⬜⬜⬜⬜⬜⬜\n⬜⬛⬛⬛⬛⬛⬜\n⬜⬛⬜⬜⬜⬛⬜\n⬜⬛⬜⬛⬜⬛⬜\n⬜⬛⬜⬜⬜⬛⬜\n⬜⬛⬛⬛⬛⬛⬜\n⬜⬜⬜⬜⬜⬜⬜", - "⬛⬛⬛⬛⬛\n⬛⬜⬜⬜⬛\n⬛⬜⬛⬜⬛\n⬛⬜⬜⬜⬛\n⬛⬛⬛⬛⬛", - "⬜⬜⬜\n⬜⬛⬜\n⬜⬜⬜", - "[👉🔴👈])", - ] - for i in animation_ttl: - await asyncio.sleep(animation_interval) - await message.edit_text(animation_chars[i % 15]) - - -@Client.on_message(filters.command("gangster", prefix) & filters.me) -async def gangster(_, message: Message): - await message.edit_text("EVERyBOdy") - await asyncio.sleep(0.3) - await message.edit_text("iZ") - await asyncio.sleep(0.2) - await message.edit_text("GangSTur") - await asyncio.sleep(0.5) - await message.edit_text("UNtIL ") - await asyncio.sleep(0.2) - await message.edit_text("I") - await asyncio.sleep(0.3) - await message.edit_text("ArRivE") - await asyncio.sleep(0.3) - await message.edit_text("🔥🔥🔥") - await asyncio.sleep(0.3) - await message.edit_text("EVERyBOdy iZ GangSTur UNtIL I ArRivE 🔥🔥🔥") - - -@Client.on_message(filters.command("charge", prefix) & filters.me) -async def timer_blankx(_, message: Message): - txt = ( - message.text[10:] - + "\n\nTesla Wireless Charging (beta) Started...\nDevice Detected: Apple iPad 13\nBattery Percentage: " - ) - j = 10 - k = j - for j in range(j): - await message.edit_text(txt + str(k), parse_mode=enums.ParseMode.HTML) - k = k + 10 - await asyncio.sleep(1) - await asyncio.sleep(1) - await message.edit_text( - "Tesla Wireless Charging (beta) Completed...\nDevice Detected: Apple iPad 13 (Space Grey Varient)\nBattery Percentage: [100%](https://telegra.ph/file/a45aa7450c8eefed599d9.mp4) ", - link_preview=True, - parse_mode=enums.ParseMode.HTML, - ) - - -@Client.on_message(filters.command("kill", prefix) & filters.me) -async def kill(_, message: Message): - animation_interval = 0.3 - animation_ttl = range(0, 103) - animation_chars = [ - "Fiiiiire", - "( ・ิω・ิ)︻デ═一-->", - "---->____________⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠", - "------>__________⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠", - "-------->⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠_________", - "---------->⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠_______", - "------------>⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠_____", - "-------------->⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠____", - "------------------>", - "------>;(^。^)ノ", - "( ̄ー ̄) DED", - "Target killed successfully (°̥̥̥̥̥̥̥̥•̀.̫•́°̥̥̥̥̥̥̥)", - ] - - await message.edit_text("You're goonnaa diieeeee!") - await asyncio.sleep(3) - for i in animation_ttl: - await asyncio.sleep(animation_interval) - await message.edit_text( - animation_chars[i % 103], parse_mode=enums.ParseMode.HTML - ) - - -@Client.on_message(filters.command("earth", prefix) & filters.me) -async def earth(_, message: Message): - deq = deque(list("🌏🌍🌎🌎🌍🌏🌍🌎")) - for _ in range(48): - await asyncio.sleep(0.1) - await message.edit_text("".join(deq)) - deq.rotate(1) - - -@Client.on_message(filters.command("think", prefix) & filters.me) -async def think(_, message: Message): - deq = deque(list("🤔🧐🤔🧐🤔🧐")) - for _ in range(48): - await asyncio.sleep(0.1) - await message.edit_text("".join(deq)) - deq.rotate(1) - - -@Client.on_message(filters.command("lmao", prefix) & filters.me) -async def lmao(_, message: Message): - deq = deque(list("😂🤣😂🤣😂🤣")) - for _ in range(48): - await asyncio.sleep(0.1) - await message.edit_text("".join(deq)) - deq.rotate(1) - - -@Client.on_message(filters.command("clock", prefix) & filters.me) -async def clock(_, message: Message): - deq = deque(list("🕙🕘🕗🕖🕕🕔🕓🕒🕑🕐🕛")) - for _ in range(48): - await asyncio.sleep(0.1) - await message.edit_text("".join(deq)) - deq.rotate(1) - - -@Client.on_message(filters.command("heart", prefix) & filters.me) -async def heart(_, message: Message): - deq = deque(list("❤️🧡💛💚💙💜🖤")) - for _ in range(48): - await asyncio.sleep(0.1) - await message.edit_text("".join(deq)) - deq.rotate(1) - - -@Client.on_message(filters.command("gym", prefix) & filters.me) -async def gym(_, message: Message): - deq = deque(list("🏃‍🏋‍🤸‍🏃‍🏋‍🤸‍🏃‍🏋‍🤸‍")) - for _ in range(48): - await asyncio.sleep(0.1) - await message.edit_text("".join(deq)) - deq.rotate(1) - - -@Client.on_message(filters.command("moon", prefix) & filters.me) -async def moon(_, message: Message): - deq = deque(list("🌗🌘🌑🌒🌓🌔🌕🌖‍")) - for _ in range(48): - await asyncio.sleep(0.1) - await message.edit_text("".join(deq)) - deq.rotate(1) - - -@Client.on_message(filters.command("stars", prefix) & filters.me) -async def stars(_, message: Message): - deq = deque(list("🦋✨🦋✨🦋✨🦋✨‍")) - for _ in range(48): - await asyncio.sleep(0.1) - await message.edit_text("".join(deq)) - deq.rotate(1) - - -@Client.on_message(filters.command("boxs", prefix) & filters.me) -async def boxs(_, message: Message): - deq = deque(list("🟥🟧🟨🟩🟦🟪🟫⬛⬜")) - for _ in range(48): - await asyncio.sleep(0.1) - await message.edit_text("".join(deq)) - deq.rotate(1) - - -@Client.on_message(filters.command("rain", prefix) & filters.me) -async def rain(_, message: Message): - deq = deque(list("🌬☁️🌩🌨🌧🌦🌥⛅🌤")) - for _ in range(48): - await asyncio.sleep(0.1) - await message.edit_text("".join(deq)) - deq.rotate(1) - - -@Client.on_message(filters.command("bird", prefix) & filters.me) -async def bird(_, message: Message): - deq = deque(list("𓅰𓅬𓅭𓅮𓅯")) - for _ in range(48): - await asyncio.sleep(0.1) - await message.edit_text("".join(deq)) - deq.rotate(1) - - -@Client.on_message(filters.command("fish", prefix) & filters.me) -async def fish(_, message: Message): - deq = deque(list("𓆝𓆟𓆞𓆝𓆟")) - for _ in range(48): - await asyncio.sleep(0.1) - await message.edit_text("".join(deq)) - deq.rotate(1) - - -@Client.on_message(filters.command("goat", prefix) & filters.me) -async def goat(_, message: Message): - deq = deque(list("𓃖𓃗𓃘𓃙𓃚𓃛𓃜")) - for _ in range(48): - await asyncio.sleep(0.1) - await message.edit_text("".join(deq)) - deq.rotate(1) - - -@Client.on_message(filters.command("smoon", prefix) & filters.me) -async def smoon(_, message: Message): - animation_interval = 0.2 - animation_ttl = range(101) - animation_chars = [ - "🌗🌗🌗🌗🌗\n🌓🌓🌓🌓🌓\n🌗🌗🌗🌗🌗\n🌓🌓🌓🌓🌓\n🌗🌗🌗🌗🌗", - "🌘🌘🌘🌘🌘\n🌔🌔🌔🌔🌔\n🌘🌘🌘🌘🌘\n🌔🌔🌔🌔🌔\n🌘🌘🌘🌘🌘", - "🌑🌑🌑🌑🌑\n🌕🌕🌕🌕🌕\n🌑🌑🌑🌑🌑\n🌕🌕🌕🌕🌕\n🌑🌑🌑🌑🌑", - "🌒🌒🌒🌒🌒\n🌖🌖🌖🌖🌖\n🌒🌒🌒🌒🌒\n🌖🌖🌖🌖🌖\n🌒🌒🌒🌒🌒", - "🌓🌓🌓🌓🌓\n🌗🌗🌗🌗🌗\n🌓🌓🌓🌓🌓\n🌗🌗🌗🌗🌗\n🌓🌓🌓🌓🌓", - "🌔🌔🌔🌔🌔\n🌘🌘🌘🌘🌘\n🌔🌔🌔🌔🌔\n🌘🌘🌘🌘🌘\n🌔🌔🌔🌔🌔", - "🌕🌕🌕🌕🌕\n🌑🌑🌑🌑🌑\n🌕🌕🌕🌕🌕\n🌑🌑🌑🌑🌑\n🌕🌕🌕🌕🌕", - "🌖🌖🌖🌖🌖\n🌒🌒🌒🌒🌒\n🌖🌖🌖🌖🌖\n🌒🌒🌒🌒🌒\n🌖🌖🌖🌖🌖", - ] - - await message.edit_text("smoon...") - await asyncio.sleep(3) - for i in animation_ttl: - await asyncio.sleep(animation_interval) - await message.edit_text(animation_chars[i % 8]) - - -@Client.on_message(filters.command("deploy", prefix) & filters.me) -async def deploy(_, message: Message): - animation_interval = 3 - animation_ttl = range(101) - animation_chars = [ - "Heroku Connecting To Latest Github Build ", - f"Build started by user {mention}", - f"Deploy 535a74f0 by user {mention}", - "Restarting Heroku Server...", - "State changed from up to starting", - "Stopping all processes with SIGTERM", - "Process exited with status 143", - "Starting process with command python3 -m userbot", - "State changed from starting to up", - "INFO:Userbot:Logged in as 557667062", - "INFO:Userbot:Successfully loaded all plugins", - "Build Succeeded", - ] - - await message.edit_text( - "Deploying...", parse_mode=enums.ParseMode.HTML - ) - await asyncio.sleep(3) - for i in animation_ttl: - await asyncio.sleep(animation_interval) - await message.edit_text( - animation_chars[i % 12], parse_mode=enums.ParseMode.HTML - ) - - -@Client.on_message(filters.command("tmoon", prefix) & filters.me) -async def tmoon(_, message: Message): - animation_interval = 0.2 - animation_ttl = range(96) - animation_chars = [ - "🌗", - "🌘", - "🌑", - "🌒", - "🌓", - "🌔", - "🌕", - "🌖", - "🌗", - "🌘", - "🌑", - "🌒", - "🌓", - "🌔", - "🌕", - "🌖", - "🌗", - "🌘", - "🌑", - "🌒", - "🌓", - "🌔", - "🌕", - "🌖", - "🌗", - "🌘", - "🌑", - "🌒", - "🌓", - "🌔", - "🌕", - "🌖", - ] - - await message.edit_text("tmoon...") - await asyncio.sleep(3) - for i in animation_ttl: - await asyncio.sleep(animation_interval) - await message.edit_text(animation_chars[i % 32]) - - -modules_help["animations"] = { - "stupid": " stupid animation", - "gangster": "YO! Gangster animation", - "hypo": "checkout yourself ~_+", - "charge": "Apple iPad charging animation", - "ding": "ding.dong.ding animation", - "wtf": "wtf animation must use if you want tell wtf", - "call": "call to durov", - "bombs": "bombing animation", - "kill": "fireee animation = target killed ~_- ", - "deploy": "Fake heroku deploy animation", - "gym": "Fun animation try yourself to know more", - "boxs": "Fun animation try yourself to know more", - "stars": "Fun animation try yourself to know more", - "goat": "Fun animation try yourself to know more", - "bird": "Fun animation try yourself to know more", - "rain": "Fun animation try yourself to know more", - "fish": "Fun animation try yourself to know more", - "heart": "Fun animation try yourself to know more", - "clock": "Fun animation try yourself to know more", - "lmao": "Fun animation try yourself to know more", - "think": "Fun animation try yourself to know more", - "earth": "Fun animation try yourself to know more", - "moon": "Fun animation try yourself to know more", - "smoon": "Fun animation try yourself to know more", - "tmoon": "Fun animation try yourself to know more", -} +import asyncio +from collections import deque + +from pyrogram import Client, enums, filters +from pyrogram.types import Message +from utils.misc import modules_help, prefix + +mention = 'Moon' + + +@Client.on_message(filters.command('stupid', prefix) & filters.me) +async def stupid(_, message: Message): + animation_interval = 0.5 + animation_ttl = range(0, 14) + await message.edit_text('stupid boy') + animation_chars = [ + 'YOUR BRAIN ➡️ 🧠\n\n🧠 (^_^)🗑', + 'YOUR BRAIN ➡️ 🧠\n\n🧠 (^_^) 🗑', + 'YOUR BRAIN ➡️ 🧠\n\n🧠 (^_^) 🗑', + 'YOUR BRAIN ➡️ 🧠\n\n🧠 (^_^) 🗑', + 'YOUR BRAIN ➡️ 🧠\n\n🧠 (^_^) 🗑', + 'YOUR BRAIN ➡️ 🧠\n\n🧠< (^_^ <) 🗑', + 'YOUR BRAIN ➡️ 🧠\n\n(> ^_^)>🧠 🗑', + 'YOUR BRAIN ➡️ 🧠\n\n (> ^_^)>🧠 🗑', + 'YOUR BRAIN ➡️ 🧠\n\n (> ^_^)>🧠 🗑', + 'YOUR BRAIN ➡️ 🧠\n\n (> ^_^)>🧠 🗑', + 'YOUR BRAIN ➡️ 🧠\n\n (> ^_^)>🧠 🗑', + 'YOUR BRAIN ➡️ 🧠\n\n (> ^_^)>🧠🗑', + 'YOUR BRAIN ➡️ 🧠\n\n (> ^_^)>🗑', + 'YOUR BRAIN ➡️ 🧠\n\n < (^_^ <)🗑', + ] + + for i in animation_ttl: + await asyncio.sleep(animation_interval) + await message.edit_text(animation_chars[i % 14], parse_mode=enums.ParseMode.HTML) + + +@Client.on_message(filters.command('bombs', prefix) & filters.me) +async def bombs(_, message: Message): + + await message.edit_text('▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n') + await asyncio.sleep(0.5) + await message.edit_text('💣💣💣💣 \n▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n') + await asyncio.sleep(0.5) + await message.edit_text('▪️▪️▪️▪️ \n💣💣💣💣 \n▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n') + await asyncio.sleep(0.5) + await message.edit_text('▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n💣💣💣💣 \n▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n') + await asyncio.sleep(0.5) + await message.edit_text('▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n💣💣💣💣 \n▪️▪️▪️▪️ \n') + await asyncio.sleep(0.5) + await message.edit_text('▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n💣💣💣💣 \n') + await asyncio.sleep(1) + await message.edit_text('▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n💥💥💥💥 \n') + await asyncio.sleep(0.5) + await message.edit_text('▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n💥💥💥💥 \n💥💥💥💥 \n') + await asyncio.sleep(0.5) + await message.edit_text('▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n▪️▪️▪️▪️ \n😵😵😵😵 \n') + await asyncio.sleep(0.5) + await message.edit_text('RIP PLOXXX......') + await asyncio.sleep(2) + + +@Client.on_message(filters.command('call', prefix) & filters.me) +async def cell(_, message: Message): + + animation_interval = 3 + animation_ttl = range(0, 18) + await message.edit_text('Calling Pavel Durov (ceo of telegram)......') + animation_chars = [ + 'Connecting To Telegram Headquarters...', + 'Call Connected.', + 'Telegram: Hello This is Telegram HQ. Who is this?', + 'Me: Yo this is Moon ,Please Connect me to my lil bro,Pavel Durov ', + 'User Authorised.', + 'Calling Shivamani At +916969696969', + 'Private Call Connected...', + 'Me: Hello Sir, Please Ban This Telegram Account.', + 'Shivamani : May I Know Who Is This?', + 'Me: Yo Brah, I Am Moon', + "Shivamani : OMG!!! Long time no see, Wassup cat...\nI'll Make Sure That Guy Account Will Get Blocked Within 24Hrs.", + 'Me: Thanks, See You Later Brah.', + "Shivamani : Please Don't Thank Brah, Telegram Is Our's. Just Gimme A Call When You Become Free.", + 'Me: Is There Any Issue/Emergency???', + 'Shivamani : Yes Sur, There Is A Bug In Telegram v69.6.9.\nI Am Not Able To Fix It. If Possible, Please Help Fix The Bug.', + 'Me: Send Me The App On My Telegram Account, I Will Fix The Bug & Send You.', + 'Shivamani : Sure Sur \nTC Bye Bye :)', + 'Private Call Disconnected.', + ] + for i in animation_ttl: + await asyncio.sleep(animation_interval) + await message.edit_text(animation_chars[i % 18]) + + +@Client.on_message(filters.command('wtf', prefix) & filters.me) +async def wtf(_, message: Message): + + animation_interval = 0.8 + animation_ttl = range(0, 5) + await message.edit_text('wtf') + animation_chars = [ + 'What', + 'What The', + 'What The F', + 'What The F Brah', + 'What The F Brah\nhttps://telegra.ph//file/f3b760e4a99340d331f9b.jpg', + ] + for i in animation_ttl: + await asyncio.sleep(animation_interval) + await message.edit_text(animation_chars[i % 5], parse_mode=enums.ParseMode.HTML) + + +@Client.on_message(filters.command('ding', prefix) & filters.me) +async def ding(_, message: Message): + animation_interval = 0.3 + animation_ttl = range(0, 30) + animation_chars = [ + '🔴⬛⬛⬜⬜\n⬜⬜⬜⬜⬜\n⬜⬜⬜⬜⬜', + '⬜⬜⬛⬜⬜\n⬜⬛⬜⬜⬜\n🔴⬜⬜⬜⬜', + '⬜⬜⬛⬜⬜\n⬜⬜⬛⬜⬜\n⬜⬜🔴⬜⬜', + '⬜⬜⬛⬜⬜\n⬜⬜⬜⬛⬜\n⬜⬜⬜⬜🔴', + '⬜⬜⬛⬛🔴\n⬜⬜⬜⬜⬜\n⬜⬜⬜⬜⬜', + '⬜⬜⬛⬜⬜\n⬜⬜⬜⬛⬜\n⬜⬜⬜⬜🔴', + '⬜⬜⬛⬜⬜\n⬜⬜⬛⬜⬜\n⬜⬜🔴⬜⬜', + '⬜⬜⬛⬜⬜\n⬜⬛⬜⬜⬜\n🔴⬜⬜⬜⬜', + '🔴⬛⬛⬜⬜\n⬜⬜⬜⬜⬜\n⬜⬜⬜⬜⬜', + '⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜\n⬜ Moon IS BEST ⬜\n⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜', + ] + + await message.edit_text('ding..dong..ding..dong ...') + await asyncio.sleep(4) + for i in animation_ttl: + await asyncio.sleep(animation_interval) + await message.edit_text(animation_chars[i % 10], parse_mode=enums.ParseMode.HTML) + + +@Client.on_message(filters.command('hypo', prefix) & filters.me) +async def hypo(_, message: Message): + animation_interval = 0.3 + animation_ttl = range(0, 15) + await message.edit_text('hypo....') + animation_chars = [ + '⬜⬜⬜⬜⬜⬜⬜\n⬜⬜⬜⬜⬜⬜⬜\n⬜⬜⬜⬜⬜⬜⬜\n⬜⬜⬜⬜⬜⬜⬜\n⬜⬜⬜⬜⬜⬜⬜\n⬜⬜⬜⬜⬜⬜⬜\n⬜⬜⬜⬜⬜⬜⬜', + '⬜⬜⬜⬜⬜⬜⬜\n⬜⬜⬜⬜⬜⬜⬜\n⬜⬜⬜⬜⬜⬜⬜\n⬜⬜⬜⬛⬜⬜⬜\n⬜⬜⬜⬜⬜⬜⬜\n⬜⬜⬜⬜⬜⬜⬜\n⬜⬜⬜⬜⬜⬜⬜', + '⬜⬜⬜⬜⬜⬜⬜\n⬜⬜⬜⬜⬜⬜⬜\n⬜⬜⬛⬛⬛⬜⬜\n⬜⬜⬛⬜⬛⬜⬜\n⬜⬜⬛⬛⬛⬜⬜\n⬜⬜⬜⬜⬜⬜⬜\n⬜⬜⬜⬜⬜⬜⬜', + '⬜⬜⬜⬜⬜⬜⬜\n⬜⬛⬛⬛⬛⬛⬜\n⬜⬛⬜⬜⬜⬛⬜\n⬜⬛⬜⬜⬜⬛⬜\n⬜⬛⬜⬜⬜⬛⬜\n⬜⬛⬛⬛⬛⬛⬜\n⬜⬜⬜⬜⬜⬜⬜', + '⬛⬛⬛⬛⬛⬛⬛\n⬛⬜⬜⬜⬜⬜⬛\n⬛⬜⬜⬜⬜⬜⬛\n⬛⬜⬜⬜⬜⬜⬛\n⬛⬜⬜⬜⬜⬜⬛\n⬛⬜⬜⬜⬜⬜⬛\n⬛⬛⬛⬛⬛⬛⬛', + '⬛⬜⬛⬜⬛⬜⬛⬜\n⬜⬛⬜⬛⬜⬛⬜⬛\n⬛⬜⬛⬜⬛⬜⬛⬜\n⬜⬛⬜⬛⬜⬛⬜⬛\n⬛⬜⬛⬜⬛⬜⬛⬜\n⬜⬛⬜⬛⬜⬛⬜⬛\n⬛⬜⬛⬜⬛⬜⬛⬜\n⬜⬛⬜⬛⬜⬛⬜⬛', + '⬜⬛⬜⬛⬜⬛⬜⬛\n⬛⬜⬛⬜⬛⬜⬛⬜\n⬜⬛⬜⬛⬜⬛⬜⬛\n⬛⬜⬛⬜⬛⬜⬛⬜\n⬜⬛⬜⬛⬜⬛⬜⬛\n⬛⬜⬛⬜⬛⬜⬛⬜\n⬜⬛⬜⬛⬜⬛⬜⬛\n⬛⬜⬛⬜⬛⬜⬛⬜', + '⬜⬜⬜⬜⬜⬜⬜\n⬜⬛⬛⬛⬛⬛⬜\n⬜⬛⬜⬜⬜⬛⬜\n⬜⬛⬜⬛⬜⬛⬜\n⬜⬛⬜⬜⬜⬛⬜\n⬜⬛⬛⬛⬛⬛⬜\n⬜⬜⬜⬜⬜⬜⬜', + '⬛⬛⬛⬛⬛⬛⬛\n⬛⬜⬜⬜⬜⬜⬛\n⬛⬜⬛⬛⬛⬜⬛\n⬛⬜⬛⬜⬛⬜⬛\n⬛⬜⬛⬛⬛⬜⬛\n⬛⬜⬜⬜⬜⬜⬛\n⬛⬛⬛⬛⬛⬛⬛', + '⬜⬜⬜⬜⬜⬜⬜\n⬜⬛⬛⬛⬛⬛⬜\n⬜⬛⬜⬜⬜⬛⬜\n⬜⬛⬜⬛⬜⬛⬜\n⬜⬛⬜⬜⬜⬛⬜\n⬜⬛⬛⬛⬛⬛⬜\n⬜⬜⬜⬜⬜⬜⬜', + '⬛⬛⬛⬛⬛⬛⬛\n⬛⬜⬜⬜⬜⬜⬛\n⬛⬜⬛⬛⬛⬜⬛\n⬛⬜⬛⬜⬛⬜⬛\n⬛⬜⬛⬛⬛⬜⬛\n⬛⬜⬜⬜⬜⬜⬛\n⬛⬛⬛⬛⬛⬛⬛', + '⬜⬜⬜⬜⬜⬜⬜\n⬜⬛⬛⬛⬛⬛⬜\n⬜⬛⬜⬜⬜⬛⬜\n⬜⬛⬜⬛⬜⬛⬜\n⬜⬛⬜⬜⬜⬛⬜\n⬜⬛⬛⬛⬛⬛⬜\n⬜⬜⬜⬜⬜⬜⬜', + '⬛⬛⬛⬛⬛\n⬛⬜⬜⬜⬛\n⬛⬜⬛⬜⬛\n⬛⬜⬜⬜⬛\n⬛⬛⬛⬛⬛', + '⬜⬜⬜\n⬜⬛⬜\n⬜⬜⬜', + '[👉🔴👈])', + ] + for i in animation_ttl: + await asyncio.sleep(animation_interval) + await message.edit_text(animation_chars[i % 15]) + + +@Client.on_message(filters.command('gangster', prefix) & filters.me) +async def gangster(_, message: Message): + await message.edit_text('EVERyBOdy') + await asyncio.sleep(0.3) + await message.edit_text('iZ') + await asyncio.sleep(0.2) + await message.edit_text('GangSTur') + await asyncio.sleep(0.5) + await message.edit_text('UNtIL ') + await asyncio.sleep(0.2) + await message.edit_text('I') + await asyncio.sleep(0.3) + await message.edit_text('ArRivE') + await asyncio.sleep(0.3) + await message.edit_text('🔥🔥🔥') + await asyncio.sleep(0.3) + await message.edit_text('EVERyBOdy iZ GangSTur UNtIL I ArRivE 🔥🔥🔥') + + +@Client.on_message(filters.command('charge', prefix) & filters.me) +async def timer_blankx(_, message: Message): + txt = ( + message.text[10:] + + '\n\nTesla Wireless Charging (beta) Started...\nDevice Detected: Apple iPad 13\nBattery Percentage: ' + ) + j = 10 + k = j + for j in range(j): + await message.edit_text(txt + str(k), parse_mode=enums.ParseMode.HTML) + k = k + 10 + await asyncio.sleep(1) + await asyncio.sleep(1) + await message.edit_text( + 'Tesla Wireless Charging (beta) Completed...\nDevice Detected: Apple iPad 13 (Space Grey Varient)\nBattery Percentage: [100%](https://telegra.ph/file/a45aa7450c8eefed599d9.mp4) ', + link_preview=True, + parse_mode=enums.ParseMode.HTML, + ) + + +@Client.on_message(filters.command('kill', prefix) & filters.me) +async def kill(_, message: Message): + animation_interval = 0.3 + animation_ttl = range(0, 103) + animation_chars = [ + 'Fiiiiire', + '( ・ิω・ิ)︻デ═一-->', + '---->____________⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠', + '------>__________⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠', + '-------->⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠_________', + '---------->⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠_______', + '------------>⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠_____', + '-------------->⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠____', + '------------------>', + '------>;(^。^)ノ', + '( ̄ー ̄) DED', + 'Target killed successfully (°̥̥̥̥̥̥̥̥•̀.̫•́°̥̥̥̥̥̥̥)', + ] + + await message.edit_text("You're goonnaa diieeeee!") + await asyncio.sleep(3) + for i in animation_ttl: + await asyncio.sleep(animation_interval) + await message.edit_text(animation_chars[i % 103], parse_mode=enums.ParseMode.HTML) + + +@Client.on_message(filters.command('earth', prefix) & filters.me) +async def earth(_, message: Message): + deq = deque(list('🌏🌍🌎🌎🌍🌏🌍🌎')) + for _ in range(48): + await asyncio.sleep(0.1) + await message.edit_text(''.join(deq)) + deq.rotate(1) + + +@Client.on_message(filters.command('think', prefix) & filters.me) +async def think(_, message: Message): + deq = deque(list('🤔🧐🤔🧐🤔🧐')) + for _ in range(48): + await asyncio.sleep(0.1) + await message.edit_text(''.join(deq)) + deq.rotate(1) + + +@Client.on_message(filters.command('lmao', prefix) & filters.me) +async def lmao(_, message: Message): + deq = deque(list('😂🤣😂🤣😂🤣')) + for _ in range(48): + await asyncio.sleep(0.1) + await message.edit_text(''.join(deq)) + deq.rotate(1) + + +@Client.on_message(filters.command('clock', prefix) & filters.me) +async def clock(_, message: Message): + deq = deque(list('🕙🕘🕗🕖🕕🕔🕓🕒🕑🕐🕛')) + for _ in range(48): + await asyncio.sleep(0.1) + await message.edit_text(''.join(deq)) + deq.rotate(1) + + +@Client.on_message(filters.command('heart', prefix) & filters.me) +async def heart(_, message: Message): + deq = deque(list('❤️🧡💛💚💙💜🖤')) + for _ in range(48): + await asyncio.sleep(0.1) + await message.edit_text(''.join(deq)) + deq.rotate(1) + + +@Client.on_message(filters.command('gym', prefix) & filters.me) +async def gym(_, message: Message): + deq = deque(list('🏃‍🏋‍🤸‍🏃‍🏋‍🤸‍🏃‍🏋‍🤸‍')) + for _ in range(48): + await asyncio.sleep(0.1) + await message.edit_text(''.join(deq)) + deq.rotate(1) + + +@Client.on_message(filters.command('moon', prefix) & filters.me) +async def moon(_, message: Message): + deq = deque(list('🌗🌘🌑🌒🌓🌔🌕🌖‍')) + for _ in range(48): + await asyncio.sleep(0.1) + await message.edit_text(''.join(deq)) + deq.rotate(1) + + +@Client.on_message(filters.command('stars', prefix) & filters.me) +async def stars(_, message: Message): + deq = deque(list('🦋✨🦋✨🦋✨🦋✨‍')) + for _ in range(48): + await asyncio.sleep(0.1) + await message.edit_text(''.join(deq)) + deq.rotate(1) + + +@Client.on_message(filters.command('boxs', prefix) & filters.me) +async def boxs(_, message: Message): + deq = deque(list('🟥🟧🟨🟩🟦🟪🟫⬛⬜')) + for _ in range(48): + await asyncio.sleep(0.1) + await message.edit_text(''.join(deq)) + deq.rotate(1) + + +@Client.on_message(filters.command('rain', prefix) & filters.me) +async def rain(_, message: Message): + deq = deque(list('🌬☁️🌩🌨🌧🌦🌥⛅🌤')) + for _ in range(48): + await asyncio.sleep(0.1) + await message.edit_text(''.join(deq)) + deq.rotate(1) + + +@Client.on_message(filters.command('bird', prefix) & filters.me) +async def bird(_, message: Message): + deq = deque(list('𓅰𓅬𓅭𓅮𓅯')) + for _ in range(48): + await asyncio.sleep(0.1) + await message.edit_text(''.join(deq)) + deq.rotate(1) + + +@Client.on_message(filters.command('fish', prefix) & filters.me) +async def fish(_, message: Message): + deq = deque(list('𓆝𓆟𓆞𓆝𓆟')) + for _ in range(48): + await asyncio.sleep(0.1) + await message.edit_text(''.join(deq)) + deq.rotate(1) + + +@Client.on_message(filters.command('goat', prefix) & filters.me) +async def goat(_, message: Message): + deq = deque(list('𓃖𓃗𓃘𓃙𓃚𓃛𓃜')) + for _ in range(48): + await asyncio.sleep(0.1) + await message.edit_text(''.join(deq)) + deq.rotate(1) + + +@Client.on_message(filters.command('smoon', prefix) & filters.me) +async def smoon(_, message: Message): + animation_interval = 0.2 + animation_ttl = range(101) + animation_chars = [ + '🌗🌗🌗🌗🌗\n🌓🌓🌓🌓🌓\n🌗🌗🌗🌗🌗\n🌓🌓🌓🌓🌓\n🌗🌗🌗🌗🌗', + '🌘🌘🌘🌘🌘\n🌔🌔🌔🌔🌔\n🌘🌘🌘🌘🌘\n🌔🌔🌔🌔🌔\n🌘🌘🌘🌘🌘', + '🌑🌑🌑🌑🌑\n🌕🌕🌕🌕🌕\n🌑🌑🌑🌑🌑\n🌕🌕🌕🌕🌕\n🌑🌑🌑🌑🌑', + '🌒🌒🌒🌒🌒\n🌖🌖🌖🌖🌖\n🌒🌒🌒🌒🌒\n🌖🌖🌖🌖🌖\n🌒🌒🌒🌒🌒', + '🌓🌓🌓🌓🌓\n🌗🌗🌗🌗🌗\n🌓🌓🌓🌓🌓\n🌗🌗🌗🌗🌗\n🌓🌓🌓🌓🌓', + '🌔🌔🌔🌔🌔\n🌘🌘🌘🌘🌘\n🌔🌔🌔🌔🌔\n🌘🌘🌘🌘🌘\n🌔🌔🌔🌔🌔', + '🌕🌕🌕🌕🌕\n🌑🌑🌑🌑🌑\n🌕🌕🌕🌕🌕\n🌑🌑🌑🌑🌑\n🌕🌕🌕🌕🌕', + '🌖🌖🌖🌖🌖\n🌒🌒🌒🌒🌒\n🌖🌖🌖🌖🌖\n🌒🌒🌒🌒🌒\n🌖🌖🌖🌖🌖', + ] + + await message.edit_text('smoon...') + await asyncio.sleep(3) + for i in animation_ttl: + await asyncio.sleep(animation_interval) + await message.edit_text(animation_chars[i % 8]) + + +@Client.on_message(filters.command('deploy', prefix) & filters.me) +async def deploy(_, message: Message): + animation_interval = 3 + animation_ttl = range(101) + animation_chars = [ + 'Heroku Connecting To Latest Github Build ', + f'Build started by user {mention}', + f'Deploy 535a74f0 by user {mention}', + 'Restarting Heroku Server...', + 'State changed from up to starting', + 'Stopping all processes with SIGTERM', + 'Process exited with status 143', + 'Starting process with command python3 -m userbot', + 'State changed from starting to up', + 'INFO:Userbot:Logged in as 557667062', + 'INFO:Userbot:Successfully loaded all plugins', + 'Build Succeeded', + ] + + await message.edit_text('Deploying...', parse_mode=enums.ParseMode.HTML) + await asyncio.sleep(3) + for i in animation_ttl: + await asyncio.sleep(animation_interval) + await message.edit_text(animation_chars[i % 12], parse_mode=enums.ParseMode.HTML) + + +@Client.on_message(filters.command('tmoon', prefix) & filters.me) +async def tmoon(_, message: Message): + animation_interval = 0.2 + animation_ttl = range(96) + animation_chars = [ + '🌗', + '🌘', + '🌑', + '🌒', + '🌓', + '🌔', + '🌕', + '🌖', + '🌗', + '🌘', + '🌑', + '🌒', + '🌓', + '🌔', + '🌕', + '🌖', + '🌗', + '🌘', + '🌑', + '🌒', + '🌓', + '🌔', + '🌕', + '🌖', + '🌗', + '🌘', + '🌑', + '🌒', + '🌓', + '🌔', + '🌕', + '🌖', + ] + + await message.edit_text('tmoon...') + await asyncio.sleep(3) + for i in animation_ttl: + await asyncio.sleep(animation_interval) + await message.edit_text(animation_chars[i % 32]) + + +modules_help['animations'] = { + 'stupid': ' stupid animation', + 'gangster': 'YO! Gangster animation', + 'hypo': 'checkout yourself ~_+', + 'charge': 'Apple iPad charging animation', + 'ding': 'ding.dong.ding animation', + 'wtf': 'wtf animation must use if you want tell wtf', + 'call': 'call to durov', + 'bombs': 'bombing animation', + 'kill': 'fireee animation = target killed ~_- ', + 'deploy': 'Fake heroku deploy animation', + 'gym': 'Fun animation try yourself to know more', + 'boxs': 'Fun animation try yourself to know more', + 'stars': 'Fun animation try yourself to know more', + 'goat': 'Fun animation try yourself to know more', + 'bird': 'Fun animation try yourself to know more', + 'rain': 'Fun animation try yourself to know more', + 'fish': 'Fun animation try yourself to know more', + 'heart': 'Fun animation try yourself to know more', + 'clock': 'Fun animation try yourself to know more', + 'lmao': 'Fun animation try yourself to know more', + 'think': 'Fun animation try yourself to know more', + 'earth': 'Fun animation try yourself to know more', + 'moon': 'Fun animation try yourself to know more', + 'smoon': 'Fun animation try yourself to know more', + 'tmoon': 'Fun animation try yourself to know more', +} diff --git a/modules/anime/anilist.py b/modules/anime/anilist.py index be6d6a6..2e41419 100644 --- a/modules/anime/anilist.py +++ b/modules/anime/anilist.py @@ -1,78 +1,73 @@ import os -import requests + import aiofiles - -from pyrogram import Client, filters, enums -from pyrogram.types import Message, InputMediaPhoto +import requests +from pyrogram import Client, enums, filters from pyrogram.errors import MediaCaptionTooLong - -from utils.misc import prefix, modules_help +from pyrogram.types import InputMediaPhoto, Message +from utils.misc import modules_help, prefix from utils.scripts import format_exc -url = "https://api.safone.co" +url = 'https://api.safone.co' headers = { - "Accept-Language": "en-US,en;q=0.9", - "Connection": "keep-alive", - "DNT": "1", - "Referer": "https://api.safone.co/docs", - "Sec-Fetch-Dest": "empty", - "Sec-Fetch-Mode": "cors", - "Sec-Fetch-Site": "same-origin", - "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36", - "accept": "application/json", - "sec-ch-ua": '"Chromium";v="124", "Google Chrome";v="124", "Not-A.Brand";v="99"', - "sec-ch-ua-mobile": "?0", - "sec-ch-ua-platform": '"Linux"', + 'Accept-Language': 'en-US,en;q=0.9', + 'Connection': 'keep-alive', + 'DNT': '1', + 'Referer': 'https://api.safone.co/docs', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'same-origin', + 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + 'accept': 'application/json', + 'sec-ch-ua': '"Chromium";v="124", "Google Chrome";v="124", "Not-A.Brand";v="99"', + 'sec-ch-ua-mobile': '?0', + 'sec-ch-ua-platform': '"Linux"', } -@Client.on_message(filters.command("anime_search", prefix) & filters.me) +@Client.on_message(filters.command('anime_search', prefix) & filters.me) async def anime_search(client: Client, message: Message): try: chat_id = message.chat.id - await message.edit_text("Processing...") + await message.edit_text('Processing...') if len(message.command) > 1: query = message.text.split(maxsplit=1)[1] else: - message.edit_text( - "What should i search? You didn't provided me with any value to search" - ) + message.edit_text("What should i search? You didn't provided me with any value to search") - response = requests.get( - url=f"{url}/anime/search?query={query}", headers=headers, timeout=5 - ) + response = requests.get(url=f'{url}/anime/search?query={query}', headers=headers, timeout=5) if response.status_code != 200: - await message.edit_text("Something went wrong") + await message.edit_text('Something went wrong') return result = response.json() - averageScore = result["averageScore"] + averageScore = result['averageScore'] try: - coverImage_url = result["imageUrl"] + coverImage_url = result['imageUrl'] coverImage = requests.get(url=coverImage_url).content - async with aiofiles.open("coverImage.jpg", mode="wb") as f: + async with aiofiles.open('coverImage.jpg', mode='wb') as f: await f.write(coverImage) except Exception: coverImage = None - title = result["title"]["english"] - trailer = result["trailer"]["id"] - description = result["description"] - episodes = result["episodes"] - genres = ", ".join(result["genres"]) - isAdult = result["isAdult"] - status = result["status"] - studios = ", ".join(result["studios"]) + title = result['title']['english'] + trailer = result['trailer']['id'] + description = result['description'] + episodes = result['episodes'] + genres = ', '.join(result['genres']) + isAdult = result['isAdult'] + status = result['status'] + studios = ', '.join(result['studios']) await message.delete() await client.send_media_group( chat_id, [ InputMediaPhoto( - "coverImage.jpg", + 'coverImage.jpg', caption=f"Title: {title}\nAverage Score: {averageScore}\nStatus: {status}\nGenres: {genres}\nEpisodes: {episodes}\nIs Adult: {isAdult}\nStudios: {studios}\nDescription: {description}\nTrailer: Click Here", ) ], @@ -85,7 +80,7 @@ async def anime_search(client: Client, message: Message): chat_id, [ InputMediaPhoto( - "coverImage.jpg", + 'coverImage.jpg', caption=f"Title: {title}\nAverage Score: {averageScore}\nStatus: {status}\nGenres: {genres}\nEpisodes: {episodes}\nIs Adult: {isAdult}\nStudios: {studios}\nDescription: {description}\nTrailer: Click Here", ) ], @@ -93,56 +88,52 @@ async def anime_search(client: Client, message: Message): except Exception as e: await message.edit_text(format_exc(e)) finally: - if os.path.exists("coverImage.jpg"): - os.remove("coverImage.jpg") + if os.path.exists('coverImage.jpg'): + os.remove('coverImage.jpg') -@Client.on_message(filters.command("manga_search", prefix) & filters.me) +@Client.on_message(filters.command('manga_search', prefix) & filters.me) async def manga_search(client: Client, message: Message): try: chat_id = message.chat.id - await message.edit_text("Processing...") + await message.edit_text('Processing...') if len(message.command) > 1: query = message.text.split(maxsplit=1)[1] else: - message.edit_text( - "What should i search? You didn't provided me with any value to search" - ) + message.edit_text("What should i search? You didn't provided me with any value to search") - response = requests.get( - url=f"{url}/anime/manga?query={query}", headers=headers, timeout=5 - ) + response = requests.get(url=f'{url}/anime/manga?query={query}', headers=headers, timeout=5) if response.status_code != 200: - await message.edit_text("Something went wrong") + await message.edit_text('Something went wrong') return result = response.json() - averageScore = result["averageScore"] + averageScore = result['averageScore'] try: - coverImage_url = result["imageUrl"] + coverImage_url = result['imageUrl'] coverImage = requests.get(url=coverImage_url).content - async with aiofiles.open("coverImage.jpg", mode="wb") as f: + async with aiofiles.open('coverImage.jpg', mode='wb') as f: await f.write(coverImage) except Exception: coverImage = None - title = result["title"]["english"] - trailer = result["trailer"]["id"] - description = result["description"] - chapters = result["chapters"] - genres = ", ".join(result["genres"]) - isAdult = result["isAdult"] - status = result["status"] - studios = ", ".join(result["studios"]) + title = result['title']['english'] + trailer = result['trailer']['id'] + description = result['description'] + chapters = result['chapters'] + genres = ', '.join(result['genres']) + isAdult = result['isAdult'] + status = result['status'] + studios = ', '.join(result['studios']) await message.delete() await client.send_media_group( chat_id, [ InputMediaPhoto( - "coverImage.jpg", + 'coverImage.jpg', caption=f"Title: {title}\nAverage Score: {averageScore}\nStatus: {status}\nGenres: {genres}\nChapters: {chapters}\nIs Adult: {isAdult}\nStudios: {studios}\nDescription: {description}\nTrailer: Click Here", ) ], @@ -155,7 +146,7 @@ async def manga_search(client: Client, message: Message): chat_id, [ InputMediaPhoto( - "coverImage.jpg", + 'coverImage.jpg', caption=f"Title: {title}\nAverage Score: {averageScore}\nStatus: {status}\nGenres: {genres}\nChapters: {chapters}\nIs Adult: {isAdult}\nStudios: {studios}\nDescription: {description}\nTrailer: Click Here", ) ], @@ -163,54 +154,50 @@ async def manga_search(client: Client, message: Message): except Exception as e: await message.edit_text(format_exc(e)) finally: - if os.path.exists("coverImage.jpg"): - os.remove("coverImage.jpg") + if os.path.exists('coverImage.jpg'): + os.remove('coverImage.jpg') -@Client.on_message(filters.command("character", prefix) & filters.me) +@Client.on_message(filters.command('character', prefix) & filters.me) async def character(client: Client, message: Message): try: chat_id = message.chat.id - await message.edit_text("Processing...") + await message.edit_text('Processing...') if len(message.command) > 1: query = message.text.split(maxsplit=1)[1] else: - message.edit_text( - "What should i search? You didn't provided me with any value to search" - ) + message.edit_text("What should i search? You didn't provided me with any value to search") - response = requests.get( - url=f"{url}/anime/character?query={query}", headers=headers, timeout=5 - ) + response = requests.get(url=f'{url}/anime/character?query={query}', headers=headers, timeout=5) if response.status_code != 200: - await message.edit_text("Something went wrong") + await message.edit_text('Something went wrong') return result = response.json() try: - coverImage_url = result["image"]["large"] + coverImage_url = result['image']['large'] coverImage = requests.get(url=coverImage_url).content - async with aiofiles.open("coverImage.jpg", mode="wb") as f: + async with aiofiles.open('coverImage.jpg', mode='wb') as f: await f.write(coverImage) except Exception: coverImage = None - age = result["age"] - description = result["description"] - height = result["height"] - name = result["name"]["full"] - native_name = result["name"]["native"] - read_more = result["siteUrl"] + age = result['age'] + description = result['description'] + height = result['height'] + name = result['name']['full'] + native_name = result['name']['native'] + read_more = result['siteUrl'] await message.delete() await client.send_media_group( chat_id, [ InputMediaPhoto( - "coverImage.jpg", - caption=f"**Name:** `{name}`\n**Native Name:** `{native_name}`\n**Age:** `{age}`\n**Height:** {height}\n**Description:** `{description}`[read more...]({read_more})", + 'coverImage.jpg', + caption=f'**Name:** `{name}`\n**Native Name:** `{native_name}`\n**Age:** `{age}`\n**Height:** {height}\n**Description:** `{description}`[read more...]({read_more})', parse_mode=enums.ParseMode.MARKDOWN, ) ], @@ -223,8 +210,8 @@ async def character(client: Client, message: Message): chat_id, [ InputMediaPhoto( - "coverImage.jpg", - caption=f"**Name:** `{name}`\n**Native Name:** `{native_name}`\n**Age:** `{age}`\n**Height:** {height}\n**Description:** `{description}`[read more...]({read_more})", + 'coverImage.jpg', + caption=f'**Name:** `{name}`\n**Native Name:** `{native_name}`\n**Age:** `{age}`\n**Height:** {height}\n**Description:** `{description}`[read more...]({read_more})', parse_mode=enums.ParseMode.MARKDOWN, ) ], @@ -232,12 +219,12 @@ async def character(client: Client, message: Message): except Exception as e: await message.edit_text(format_exc(e)) finally: - if os.path.exists("coverImage.jpg"): - os.remove("coverImage.jpg") + if os.path.exists('coverImage.jpg'): + os.remove('coverImage.jpg') -modules_help["anilist"] = { - "anime_search": "Search for anime on Anilist", - "manga_search": "Search for manga on Anilist", - "character": "Search for character on Anilist", +modules_help['anilist'] = { + 'anime_search': 'Search for anime on Anilist', + 'manga_search': 'Search for manga on Anilist', + 'character': 'Search for character on Anilist', } diff --git a/modules/anime/anime.py b/modules/anime/anime.py index eff5f64..0b6554a 100644 --- a/modules/anime/anime.py +++ b/modules/anime/anime.py @@ -1,11 +1,12 @@ -from pyrogram import Client, filters, enums +from io import BytesIO + +from aiohttp import ClientSession +from pyrogram import Client, enums, filters from pyrogram.types import Message # noinspection PyUnresolvedReferences from utils.misc import modules_help, prefix from utils.scripts import format_exc -from aiohttp import ClientSession -from io import BytesIO session = ClientSession() @@ -25,8 +26,10 @@ class Post: if self.large_file_url else ( self.source - if self.source and "pximg" not in self.source - else await self.pximg if self.source else None + if self.source and 'pximg' not in self.source + else await self.pximg + if self.source + else None ) ) ) @@ -41,16 +44,14 @@ class Post: async def random(): - async with session.get( - url="https://danbooru.donmai.us/posts/random.json" - ) as response: - return Post(await response.json(encoding="utf-8"), session) + async with session.get(url='https://danbooru.donmai.us/posts/random.json') as response: + return Post(await response.json(encoding='utf-8'), session) -@Client.on_message(filters.command(["arnd", "arandom"], prefix) & filters.me) +@Client.on_message(filters.command(['arnd', 'arandom'], prefix) & filters.me) async def anime_handler(client: Client, message: Message): try: - await message.edit("Searching art", parse_mode=enums.ParseMode.HTML) + await message.edit('Searching art', parse_mode=enums.ParseMode.HTML) ra = await random() img = await ra.image await message.reply_photo( @@ -63,7 +64,7 @@ async def anime_handler(client: Client, message: Message): await message.edit(format_exc(e), parse_mode=enums.ParseMode.HTML) -modules_help["anime"] = { - "arnd": "Random anime art (May get caught 18+)", - "arandom": "Random anime art (May get caught 18+)", +modules_help['anime'] = { + 'arnd': 'Random anime art (May get caught 18+)', + 'arandom': 'Random anime art (May get caught 18+)', } diff --git a/modules/anime/neko.py b/modules/anime/neko.py index 4ba8545..519ef58 100644 --- a/modules/anime/neko.py +++ b/modules/anime/neko.py @@ -17,40 +17,38 @@ import asyncio import requests -from pyrogram import Client, filters, enums +from pyrogram import Client, filters from pyrogram.types import Message - from utils.misc import modules_help, prefix from utils.scripts import format_exc def get_neko_media(query): - return requests.get(f"https://nekos.life/api/v2/img/{query}").json()["url"] + return requests.get(f'https://nekos.life/api/v2/img/{query}').json()['url'] -@Client.on_message(filters.command("neko", prefix) & filters.me) +@Client.on_message(filters.command('neko', prefix) & filters.me) async def neko(_, message: Message): if len(message.command) == 1: await message.edit( - "Neko type isn't provided\n" - f"You can get available neko types with {prefix}neko_types" + f"Neko type isn't provided\nYou can get available neko types with {prefix}neko_types" ) query = message.command[1] - await message.edit("Loading...") + await message.edit('Loading...') try: - await message.edit(f"{get_neko_media(query)}", disable_web_page_preview=False) + await message.edit(f'{get_neko_media(query)}', disable_web_page_preview=False) except Exception as e: await message.edit(format_exc(e)) -@Client.on_message(filters.command(["nekotypes", "neko_types"], prefix) & filters.me) +@Client.on_message(filters.command(['nekotypes', 'neko_types'], prefix) & filters.me) async def neko_types_func(_, message: Message): neko_types = """hug kiss tickle lewd neko pat lizard 8ball cat chat fact smug woof gasm goose cuddle avatar slap gecg feed fox_girl meow wallpaper spank waifu ngif name owoify spoiler why""" - await message.edit(" ".join(f"{n}" for n in neko_types.split())) + await message.edit(' '.join(f'{n}' for n in neko_types.split())) -@Client.on_message(filters.command(["nekospam", "neko_spam"], prefix) & filters.me) +@Client.on_message(filters.command(['nekospam', 'neko_spam'], prefix) & filters.me) async def neko_spam(client: Client, message: Message): query = message.command[1] amount = int(message.command[2]) @@ -65,8 +63,8 @@ async def neko_spam(client: Client, message: Message): await asyncio.sleep(0.1) -modules_help["neko"] = { - "neko [type]*": "Get neko media", - "neko_types": "Available neko types", - "neko_spam [type]* [amount]*": "Start spam with neko media", +modules_help['neko'] = { + 'neko [type]*': 'Get neko media', + 'neko_types': 'Available neko types', + 'neko_spam [type]* [amount]*': 'Start spam with neko media', } diff --git a/modules/aniquotes.py b/modules/aniquotes.py index 867c88f..3b304df 100644 --- a/modules/aniquotes.py +++ b/modules/aniquotes.py @@ -1,13 +1,12 @@ -from random import choice, randint +from random import randint -from pyrogram import Client, filters, enums +from pyrogram import Client, enums, filters from pyrogram.types import Message - from utils.misc import modules_help, prefix from utils.scripts import format_exc -@Client.on_message(filters.command(["aniq", "aq"], prefix) & filters.me) +@Client.on_message(filters.command(['aniq', 'aq'], prefix) & filters.me) async def aniquotes_handler(client: Client, message: Message): if message.reply_to_message and message.reply_to_message.text: query = message.reply_to_message.text[:512] @@ -17,27 +16,25 @@ async def aniquotes_handler(client: Client, message: Message): query = message.text.split(maxsplit=1)[1][:512] else: return await message.edit( - "[💮 Aniquotes] Please enter text to create sticker.", + '[💮 Aniquotes] Please enter text to create sticker.', parse_mode=enums.ParseMode.HTML, ) try: await message.delete() - result = await client.get_inline_bot_results("@quotafbot", query) + result = await client.get_inline_bot_results('@quotafbot', query) return await message.reply_inline_bot_result( query_id=result.query_id, result_id=result.results[randint(1, 2)].id, - reply_to_message_id=( - message.reply_to_message.id if message.reply_to_message else None - ), + reply_to_message_id=(message.reply_to_message.id if message.reply_to_message else None), ) except Exception as e: return await message.reply( - f"[💮 Aniquotes]\n{format_exc(e)}", + f'[💮 Aniquotes]\n{format_exc(e)}', parse_mode=enums.ParseMode.HTML, ) -modules_help["aniquotes"] = { - "aq [text]": "Create animated sticker with text", +modules_help['aniquotes'] = { + 'aq [text]': 'Create animated sticker with text', } diff --git a/modules/antipm.py b/modules/antipm.py index 9ce1b23..97e9bda 100644 --- a/modules/antipm.py +++ b/modules/antipm.py @@ -19,14 +19,11 @@ import os from pyrogram import Client, filters from pyrogram.raw import functions from pyrogram.types import Message - from utils.config import pm_limit from utils.db import db from utils.misc import modules_help, prefix -anti_pm_enabled = filters.create( - lambda _, __, ___: db.get("core.antipm", "status", False) -) +anti_pm_enabled = filters.create(lambda _, __, ___: db.get('core.antipm', 'status', False)) in_contact_list = filters.create(lambda _, __, message: message.from_user.is_contact) @@ -35,14 +32,7 @@ is_support = filters.create(lambda _, __, message: message.chat.is_support) USER_WARNINGS = {} -@Client.on_message( - filters.private - & ~filters.me - & ~filters.bot - & ~in_contact_list - & ~is_support - & anti_pm_enabled -) +@Client.on_message(filters.private & ~filters.me & ~filters.bot & ~in_contact_list & ~is_support & anti_pm_enabled) async def anti_pm_handler(client: Client, message: Message): user_id = message.from_user.id ids = message.chat.id @@ -50,7 +40,7 @@ async def anti_pm_handler(client: Client, message: Message): u_n = b_f.first_name user = await client.get_users(ids) u_f = user.first_name - default_text = db.get("core.antipm", "antipm_msg", None) + default_text = db.get('core.antipm', 'antipm_msg', None) if default_text is None: default_text = f"""Hello, {u_f}! This is the Assistant Of {u_n}. @@ -61,23 +51,19 @@ Do not spam further messages else I may have to block you! Currently You Have {USER_WARNINGS.get(user_id, 0)} Warnings. """ else: - default_text = default_text.format( - user=u_f, my_name=u_n, warns=USER_WARNINGS.get(user_id, 0) - ) + default_text = default_text.format(user=u_f, my_name=u_n, warns=USER_WARNINGS.get(user_id, 0)) - if db.get("core.antipm", "spamrep", False): + if db.get('core.antipm', 'spamrep', False): user_info = await client.resolve_peer(ids) await client.invoke(functions.messages.ReportSpam(peer=user_info)) - if db.get("core.antipm", "block", False): + if db.get('core.antipm', 'block', False): await client.block_user(user_id) - if db.get("core.antipm", f"disallowusers{ids}") == user_id != db.get( - "core.antipm", f"allowusers{ids}" - ) or db.get("core.antipm", f"disallowusers{ids}") != user_id != db.get( - "core.antipm", f"allowusers{ids}" - ): - default_pic = db.get("core.antipm", "antipm_pic", None) + if db.get('core.antipm', f'disallowusers{ids}') == user_id != db.get('core.antipm', f'allowusers{ids}') or db.get( + 'core.antipm', f'disallowusers{ids}' + ) != user_id != db.get('core.antipm', f'allowusers{ids}'): + default_pic = db.get('core.antipm', 'antipm_pic', None) if default_pic and os.path.exists(default_pic): await client.send_photo(message.chat.id, default_pic, caption=default_text) else: @@ -91,166 +77,146 @@ Do not spam further messages else I may have to block you! if USER_WARNINGS[user_id] > pm_limit: await client.send_message( message.chat.id, - "Ehm...! That was your Last warn, Bye Bye see you L0L", + 'Ehm...! That was your Last warn, Bye Bye see you L0L', ) await client.block_user(user_id) del USER_WARNINGS[user_id] -@Client.on_message(filters.command(["antipm", "anti_pm"], prefix) & filters.me) +@Client.on_message(filters.command(['antipm', 'anti_pm'], prefix) & filters.me) async def anti_pm(_, message: Message): if len(message.command) == 1: - if db.get("core.antipm", "status", False): - await message.edit( - "Anti-PM status: enabled\n" - f"Disable with: {prefix}antipm disable" - ) + if db.get('core.antipm', 'status', False): + await message.edit(f'Anti-PM status: enabled\nDisable with: {prefix}antipm disable') else: - await message.edit( - "Anti-PM status: disabled\n" - f"Enable with: {prefix}antipm enable" - ) - elif message.command[1] in ["enable", "on", "1", "yes", "true"]: - db.set("core.antipm", "status", True) - await message.edit("Anti-PM enabled!") - elif message.command[1] in ["disable", "off", "0", "no", "false"]: - db.set("core.antipm", "status", False) - await message.edit("Anti-PM disabled!") + await message.edit(f'Anti-PM status: disabled\nEnable with: {prefix}antipm enable') + elif message.command[1] in ['enable', 'on', '1', 'yes', 'true']: + db.set('core.antipm', 'status', True) + await message.edit('Anti-PM enabled!') + elif message.command[1] in ['disable', 'off', '0', 'no', 'false']: + db.set('core.antipm', 'status', False) + await message.edit('Anti-PM disabled!') else: - await message.edit(f"Usage: {prefix}antipm [enable|disable]") + await message.edit(f'Usage: {prefix}antipm [enable|disable]') -@Client.on_message(filters.command(["antipm_report"], prefix) & filters.me) +@Client.on_message(filters.command(['antipm_report'], prefix) & filters.me) async def antipm_report(_, message: Message): if len(message.command) == 1: - if db.get("core.antipm", "spamrep", False): + if db.get('core.antipm', 'spamrep', False): await message.edit( - "Spam-reporting enabled.\n" - f"Disable with: {prefix}antipm_report disable" + f'Spam-reporting enabled.\nDisable with: {prefix}antipm_report disable' ) else: await message.edit( - "Spam-reporting disabled.\n" - f"Enable with: {prefix}antipm_report enable" + f'Spam-reporting disabled.\nEnable with: {prefix}antipm_report enable' ) - elif message.command[1] in ["enable", "on", "1", "yes", "true"]: - db.set("core.antipm", "spamrep", True) - await message.edit("Spam-reporting enabled!") - elif message.command[1] in ["disable", "off", "0", "no", "false"]: - db.set("core.antipm", "spamrep", False) - await message.edit("Spam-reporting disabled!") + elif message.command[1] in ['enable', 'on', '1', 'yes', 'true']: + db.set('core.antipm', 'spamrep', True) + await message.edit('Spam-reporting enabled!') + elif message.command[1] in ['disable', 'off', '0', 'no', 'false']: + db.set('core.antipm', 'spamrep', False) + await message.edit('Spam-reporting disabled!') else: - await message.edit(f"Usage: {prefix}antipm_report [enable|disable]") + await message.edit(f'Usage: {prefix}antipm_report [enable|disable]') -@Client.on_message(filters.command(["antipm_block"], prefix) & filters.me) +@Client.on_message(filters.command(['antipm_block'], prefix) & filters.me) async def antipm_block(_, message: Message): if len(message.command) == 1: - if db.get("core.antipm", "block", False): + if db.get('core.antipm', 'block', False): await message.edit( - "Blocking users enabled.\n" - f"Disable with: {prefix}antipm_block disable" + f'Blocking users enabled.\nDisable with: {prefix}antipm_block disable' ) else: await message.edit( - "Blocking users disabled.\n" - f"Enable with: {prefix}antipm_block enable" + f'Blocking users disabled.\nEnable with: {prefix}antipm_block enable' ) - elif message.command[1] in ["enable", "on", "1", "yes", "true"]: - db.set("core.antipm", "block", True) - await message.edit("Blocking users enabled!") - elif message.command[1] in ["disable", "off", "0", "no", "false"]: - db.set("core.antipm", "block", False) - await message.edit("Blocking users disabled!") + elif message.command[1] in ['enable', 'on', '1', 'yes', 'true']: + db.set('core.antipm', 'block', True) + await message.edit('Blocking users enabled!') + elif message.command[1] in ['disable', 'off', '0', 'no', 'false']: + db.set('core.antipm', 'block', False) + await message.edit('Blocking users disabled!') else: - await message.edit(f"Usage: {prefix}antipm_block [enable|disable]") + await message.edit(f'Usage: {prefix}antipm_block [enable|disable]') -@Client.on_message(filters.command(["a"], prefix) & filters.me) +@Client.on_message(filters.command(['a'], prefix) & filters.me) async def add_contact(_, message: Message): ids = message.chat.id - db.set("core.antipm", f"allowusers{ids}", ids) + db.set('core.antipm', f'allowusers{ids}', ids) if ids in USER_WARNINGS: del USER_WARNINGS[ids] - await message.edit("User Approved!") + await message.edit('User Approved!') -@Client.on_message(filters.command(["d"], prefix) & filters.me) +@Client.on_message(filters.command(['d'], prefix) & filters.me) async def del_contact(_, message: Message): ids = message.chat.id - db.set("core.antipm", f"disallowusers{ids}", ids) - db.remove("core.antipm", f"allowusers{ids}") - await message.edit("User DisApproved!") + db.set('core.antipm', f'disallowusers{ids}', ids) + db.remove('core.antipm', f'allowusers{ids}') + await message.edit('User DisApproved!') -@Client.on_message(filters.command(["setantipmmsg", "sam"], prefix) & filters.me) +@Client.on_message(filters.command(['setantipmmsg', 'sam'], prefix) & filters.me) async def set_antipm_msg(_, message: Message): if not message.reply_to_message: - db.set("core.antipm", "antipm_msg", None) - await message.edit("antipm message set to default.") + db.set('core.antipm', 'antipm_msg', None) + await message.edit('antipm message set to default.') return msg = message.reply_to_message afk_msg = msg.text or msg.caption if not afk_msg: - return await message.edit( - "Reply to a text or caption message to set it as your antipm message." - ) + return await message.edit('Reply to a text or caption message to set it as your antipm message.') if len(afk_msg) > 200: - return await message.edit( - "antipm message is too long. It should be less than 200 characters." - ) + return await message.edit('antipm message is too long. It should be less than 200 characters.') - if "{user}" not in afk_msg: - return await message.edit( - "antipm message must contain {user} to mention the user." - ) - if "{my_name}" not in afk_msg: - return await message.edit( - "antipm message must contain {my_name} to mention your name." - ) - if "{warns}" not in afk_msg: - return await message.edit( - "antipm message must contain {warns} to mention the warns count." - ) + if '{user}' not in afk_msg: + return await message.edit('antipm message must contain {user} to mention the user.') + if '{my_name}' not in afk_msg: + return await message.edit('antipm message must contain {my_name} to mention your name.') + if '{warns}' not in afk_msg: + return await message.edit('antipm message must contain {warns} to mention the warns count.') - old_afk_msg = db.get("core.antipm", "antipm_msg", None) + old_afk_msg = db.get('core.antipm', 'antipm_msg', None) if old_afk_msg: - db.remove("core.antipm", "antipm_msg") - db.set("core.antipm", "antipm_msg", afk_msg) - await message.edit(f"antipm message set to:\n\n{afk_msg}") + db.remove('core.antipm', 'antipm_msg') + db.set('core.antipm', 'antipm_msg', afk_msg) + await message.edit(f'antipm message set to:\n\n{afk_msg}') -@Client.on_message(filters.command(["setantipmpic", "sap"], prefix) & filters.me) +@Client.on_message(filters.command(['setantipmpic', 'sap'], prefix) & filters.me) async def set_antipm_pic(_, message: Message): if not message.reply_to_message or not message.reply_to_message.photo: - db.set("core.antipm", "antipm_pic", None) - await message.edit("antipm picture set to default.") + db.set('core.antipm', 'antipm_pic', None) + await message.edit('antipm picture set to default.') return - await message.edit("Setting antipm picture...") + await message.edit('Setting antipm picture...') - photo = await message.reply_to_message.download("antipm_pic.jpg") + photo = await message.reply_to_message.download('antipm_pic.jpg') - old_antipm_pic = db.get("core.antipm", "antipm_pic", None) + old_antipm_pic = db.get('core.antipm', 'antipm_pic', None) if old_antipm_pic: - db.remove("core.antipm", "antipm_pic") - db.set("core.antipm", "antipm_pic", photo) - await message.edit("antipm picture set successfully.") + db.remove('core.antipm', 'antipm_pic') + db.set('core.antipm', 'antipm_pic', photo) + await message.edit('antipm picture set successfully.') -modules_help["antipm"] = { - "antipm [enable|disable]*": "Enable Pm permit", - "antipm_report [enable|disable]*": "Enable spam reporting", - "antipm_block [enable|disable]*": "Enable user blocking", - "setantipmmsg [reply to message]*": "Set antipm message. Use {user} to mention the user and {my_name} to mention your name and {warns} to mention the warns count.", - "sam [reply to message]*": "Set antipm message. Use {user} to mention the user and {my_name} to mention your name and {warns} to mention the warns count.", - "setantipmpic [reply to photo]*": "Set antipm picture.", - "sap [reply to photo]*": "Set antipm picture.", - "a": "Approve User", - "d": "DisApprove User", +modules_help['antipm'] = { + 'antipm [enable|disable]*': 'Enable Pm permit', + 'antipm_report [enable|disable]*': 'Enable spam reporting', + 'antipm_block [enable|disable]*': 'Enable user blocking', + 'setantipmmsg [reply to message]*': 'Set antipm message. Use {user} to mention the user and {my_name} to mention your name and {warns} to mention the warns count.', + 'sam [reply to message]*': 'Set antipm message. Use {user} to mention the user and {my_name} to mention your name and {warns} to mention the warns count.', + 'setantipmpic [reply to photo]*': 'Set antipm picture.', + 'sap [reply to photo]*': 'Set antipm picture.', + 'a': 'Approve User', + 'd': 'DisApprove User', } diff --git a/modules/blackbox.py b/modules/blackbox.py index 7b176a4..0a6bbf0 100644 --- a/modules/blackbox.py +++ b/modules/blackbox.py @@ -1,10 +1,8 @@ -import uuid import re +import uuid from aiohttp import ClientSession, FormData - from pyrogram import Client, filters - from utils.misc import modules_help, prefix @@ -12,15 +10,14 @@ def id_generator() -> str: return str(uuid.uuid4()) -@Client.on_message(filters.command(["bbox", "blackbox"], prefix) & filters.me) +@Client.on_message(filters.command(['bbox', 'blackbox'], prefix) & filters.me) async def blackbox(client, message): m = message - msg = await m.edit_text("🔍") + msg = await m.edit_text('🔍') if len(m.text.split()) == 1: return await msg.edit_text( - "Type some query buddy 🐼\n" - f"{prefix}blackbox text with reply to the photo or just text" + f'Type some query buddy 🐼\n{prefix}blackbox text with reply to the photo or just text' ) else: try: @@ -30,105 +27,95 @@ async def blackbox(client, message): image = None if m.reply_to_message and ( - m.reply_to_message.photo - or ( - m.reply_to_message.sticker - and not m.reply_to_message.sticker.is_video - ) + m.reply_to_message.photo or (m.reply_to_message.sticker and not m.reply_to_message.sticker.is_video) ): - file_name = f"blackbox_{m.chat.id}.jpeg" + file_name = f'blackbox_{m.chat.id}.jpeg' file_path = await m.reply_to_message.download(file_name=file_name) - with open(file_path, "rb") as file: + with open(file_path, 'rb') as file: image = file.read() if image: data = FormData() - data.add_field("fileName", file_name) - data.add_field("userId", user_id) - data.add_field( - "image", image, filename=file_name, content_type="image/jpeg" - ) - api_url = "https://www.blackbox.ai/api/upload" + data.add_field('fileName', file_name) + data.add_field('userId', user_id) + data.add_field('image', image, filename=file_name, content_type='image/jpeg') + api_url = 'https://www.blackbox.ai/api/upload' try: async with session.post(api_url, data=data) as response: response_json = await response.json() except Exception as e: - return await msg.edit(f"❌ Error: {str(e)}") + return await msg.edit(f'❌ Error: {str(e)}') messages = [ { - "role": "user", - "content": response_json["response"] + "\n#\n" + prompt, + 'role': 'user', + 'content': response_json['response'] + '\n#\n' + prompt, } ] data = { - "messages": messages, - "user_id": user_id, - "codeModelMode": True, - "agentMode": {}, - "trendingAgentMode": {}, + 'messages': messages, + 'user_id': user_id, + 'codeModelMode': True, + 'agentMode': {}, + 'trendingAgentMode': {}, } - headers = {"Content-Type": "application/json"} - url = "https://www.blackbox.ai/api/chat" + headers = {'Content-Type': 'application/json'} + url = 'https://www.blackbox.ai/api/chat' try: - async with session.post( - url, headers=headers, json=data - ) as response: + async with session.post(url, headers=headers, json=data) as response: response_text = await response.text() except Exception as e: - return await msg.edit(f"❌ Error: {str(e)}") + return await msg.edit(f'❌ Error: {str(e)}') cleaned_response_text = re.sub( - r"^\$?@?\$?v=undefined-rv\d+@?\$?|\$?@?\$?v=v\d+\.\d+-rv\d+@?\$?", - "", + r'^\$?@?\$?v=undefined-rv\d+@?\$?|\$?@?\$?v=v\d+\.\d+-rv\d+@?\$?', + '', response_text, ) text = cleaned_response_text.strip()[2:] - if "$~~~$" in text: - text = re.sub(r"\$~~~\$.*?\$~~~\$", "", text, flags=re.DOTALL) - rdata = {"reply": text} + if '$~~~$' in text: + text = re.sub(r'\$~~~\$.*?\$~~~\$', '', text, flags=re.DOTALL) + rdata = {'reply': text} - return await msg.edit_text(text=rdata["reply"]) + return await msg.edit_text(text=rdata['reply']) else: reply = m.reply_to_message if reply and reply.text: - prompt = f"Old conversation:\n{reply.text}\n\nQuestion:\n{prompt}" - messages = [{"role": "user", "content": prompt}] + prompt = f'Old conversation:\n{reply.text}\n\nQuestion:\n{prompt}' + messages = [{'role': 'user', 'content': prompt}] data = { - "messages": messages, - "user_id": user_id, - "codeModelMode": True, - "agentMode": {}, - "trendingAgentMode": {}, + 'messages': messages, + 'user_id': user_id, + 'codeModelMode': True, + 'agentMode': {}, + 'trendingAgentMode': {}, } - headers = {"Content-Type": "application/json"} - url = "https://www.blackbox.ai/api/chat" + headers = {'Content-Type': 'application/json'} + url = 'https://www.blackbox.ai/api/chat' try: - async with session.post( - url, headers=headers, json=data - ) as response: + async with session.post(url, headers=headers, json=data) as response: response_text = await response.text() except Exception as e: - return await msg.edit(f"❌ Error: {str(e)}") + return await msg.edit(f'❌ Error: {str(e)}') cleaned_response_text = re.sub( - r"^\$?@?\$?v=undefined-rv\d+@?\$?|\$?@?\$?v=v\d+\.\d+-rv\d+@?\$?", - "", + r'^\$?@?\$?v=undefined-rv\d+@?\$?|\$?@?\$?v=v\d+\.\d+-rv\d+@?\$?', + '', response_text, ) text = cleaned_response_text.strip()[2:] - if "$~~~$" in text: - text = re.sub(r"\$~~~\$.*?\$~~~\$", "", text, flags=re.DOTALL) - rdata = {"reply": text} + if '$~~~$' in text: + text = re.sub(r'\$~~~\$.*?\$~~~\$', '', text, flags=re.DOTALL) + rdata = {'reply': text} - return await msg.edit_text(text=rdata["reply"]) + return await msg.edit_text(text=rdata['reply']) except Exception as e: - return await msg.edit(f"�� Error: {str(e)}") + return await msg.edit(f'�� Error: {str(e)}') finally: await session.close() -modules_help["blackbox"] = { - "blackbox [query]*": "Ask anything to Blackbox", - "bbox [query]*": "Ask anything to Blackbox", +modules_help['blackbox'] = { + 'blackbox [query]*': 'Ask anything to Blackbox', + 'bbox [query]*': 'Ask anything to Blackbox', } diff --git a/modules/calculator.py b/modules/calculator.py index 08927bf..f25afd7 100644 --- a/modules/calculator.py +++ b/modules/calculator.py @@ -2,15 +2,14 @@ import asyncio from pyrogram import Client, filters from pyrogram.types import Message - from utils.misc import modules_help, prefix -@Client.on_message(filters.command("calc", prefix) & filters.me) +@Client.on_message(filters.command('calc', prefix) & filters.me) async def calc(_, message: Message): if len(message.command) <= 1: return - args = " ".join(message.command[1:]) + args = ' '.join(message.command[1:]) try: result = str(eval(args)) @@ -19,28 +18,24 @@ async def calc(_, message: Message): for x in range(0, len(result), 4096): if i == 0: await message.edit( - f"{args}={result[x:x + 4000]}", - parse_mode="HTML", + f'{args}={result[x : x + 4000]}', + parse_mode='HTML', ) else: - await message.reply( - f"{result[x:x + 4096]}", parse_mode="HTML" - ) + await message.reply(f'{result[x : x + 4096]}', parse_mode='HTML') i += 1 await asyncio.sleep(0.18) else: - await message.edit( - f"{args}={result}", parse_mode="HTML" - ) + await message.edit(f'{args}={result}', parse_mode='HTML') except Exception as e: - await message.edit(f"{args}=={e}", parse_mode="HTML") + await message.edit(f'{args}=={e}', parse_mode='HTML') -modules_help["calculator"] = { - "calc [expression]*": "solve a math problem\n" - "+ – addition\n" - "– – subtraction\n" - "* – multiplication\n" - "/ – division\n" - "** – degree" +modules_help['calculator'] = { + 'calc [expression]*': 'solve a math problem\n' + '+ – addition\n' + '– – subtraction\n' + '* – multiplication\n' + '/ – division\n' + '** – degree' } diff --git a/modules/cdxl.py b/modules/cdxl.py index 19c1597..a869d75 100644 --- a/modules/cdxl.py +++ b/modules/cdxl.py @@ -1,57 +1,52 @@ -import base64, os +import os from pyrogram import Client, filters from pyrogram.types import Message - from utils.misc import modules_help, prefix from utils.scripts import format_exc, import_library -clarifai = import_library("clarifai") +clarifai = import_library('clarifai') from clarifai.client.model import Model -@Client.on_message(filters.command("cdxl", prefix) & filters.me) +@Client.on_message(filters.command('cdxl', prefix) & filters.me) async def cdxl(c: Client, message: Message): try: chat_id = message.chat.id - await message.edit_text("Please Wait...") + await message.edit_text('Please Wait...') if len(message.command) > 1: prompt = message.text.split(maxsplit=1)[1] elif message.reply_to_message: prompt = message.reply_to_message.text else: - await message.edit_text( - f"Usage: {prefix}vdxl [prompt/reply to prompt]" - ) + await message.edit_text(f'Usage: {prefix}vdxl [prompt/reply to prompt]') return inference_params = dict(width=1024, height=1024, steps=50, cfg_scale=9.0) model_prediction = Model( - "https://clarifai.com/stability-ai/stable-diffusion-2/models/stable-diffusion-xl" - ).predict_by_bytes( - prompt.encode(), input_type="text", inference_params=inference_params - ) + 'https://clarifai.com/stability-ai/stable-diffusion-2/models/stable-diffusion-xl' + ).predict_by_bytes(prompt.encode(), input_type='text', inference_params=inference_params) output_base64 = model_prediction.outputs[0].data.image.base64 - with open("sdxl_out.png", "wb") as f: + with open('sdxl_out.png', 'wb') as f: f.write(output_base64) await message.delete() await c.send_photo( chat_id, - photo=f"sdxl_out.png", - caption=f"Prompt:{prompt}", + photo='sdxl_out.png', + caption=f'Prompt:{prompt}', ) - os.remove(f"sdxl_out.png") + os.remove('sdxl_out.png') except Exception as e: - await message.edit_text(f"An error occurred: {format_exc(e)}") + await message.edit_text(f'An error occurred: {format_exc(e)}') -modules_help["cdxl"] = { - "cdxl [prompt/reply to prompt]*": "Text to Image with SDXL model", +modules_help['cdxl'] = { + 'cdxl [prompt/reply to prompt]*': 'Text to Image with SDXL model', } diff --git a/modules/chatbot.py b/modules/chatbot.py index 5d606f1..fb49eeb 100644 --- a/modules/chatbot.py +++ b/modules/chatbot.py @@ -1,49 +1,48 @@ from pyrogram import Client, enums, filters from pyrogram.types import Message - -from utils.misc import modules_help, prefix from utils.config import cohere_key from utils.db import db +from utils.misc import modules_help, prefix from utils.scripts import format_exc, import_library, restart -cohere = import_library("cohere") +cohere = import_library('cohere') co = cohere.Client(cohere_key) chatai_users = db.getaiusers() -@Client.on_message(filters.command("addai", prefix) & filters.me) +@Client.on_message(filters.command('addai', prefix) & filters.me) async def adduser(_, message: Message): if len(message.command) > 1: user_id = message.text.split(maxsplit=1)[1] if user_id.isdigit(): user_id = int(user_id) db.addaiuser(user_id) - await message.edit_text("User ID Added") + await message.edit_text('User ID Added') restart() else: - await message.edit_text("User ID is invalid.") + await message.edit_text('User ID is invalid.') return else: - await message.edit_text(f"Usage: {prefix}addai [user_id]") + await message.edit_text(f'Usage: {prefix}addai [user_id]') return -@Client.on_message(filters.command("remai", prefix) & filters.me) +@Client.on_message(filters.command('remai', prefix) & filters.me) async def remuser(_, message: Message): if len(message.command) > 1: user_id = message.text.split(maxsplit=1)[1] if user_id.isdigit(): user_id = int(user_id) db.remaiuser(user_id) - await message.edit_text("User ID Removed") + await message.edit_text('User ID Removed') restart() else: - await message.edit_text("User ID is invalid.") + await message.edit_text('User ID is invalid.') return else: - await message.edit_text(f"Usage: {prefix}remai [user_id]") + await message.edit_text(f'Usage: {prefix}remai [user_id]') return @@ -62,44 +61,40 @@ async def chatbot(_, message: Message): prompt = message.text - db.add_chat_history(user_id, {"role": "USER", "message": prompt}) + db.add_chat_history(user_id, {'role': 'USER', 'message': prompt}) response = co.chat( chat_history=chat_history, - model="command-r-plus", + model='command-r-plus', message=prompt, temperature=0.3, - connectors=[{"id": "web-search", "options": {"site": "wikipedia.com"}}], - prompt_truncation="AUTO", + connectors=[{'id': 'web-search', 'options': {'site': 'wikipedia.com'}}], + prompt_truncation='AUTO', ) - db.add_chat_history(user_id, {"role": "CHATBOT", "message": response.text}) + db.add_chat_history(user_id, {'role': 'CHATBOT', 'message': response.text}) - await message.reply_text( - f"{response.text}", parse_mode=enums.ParseMode.MARKDOWN - ) + await message.reply_text(f'{response.text}', parse_mode=enums.ParseMode.MARKDOWN) except Exception as e: - await message.reply_text(f"An error occurred: {format_exc(e)}") + await message.reply_text(f'An error occurred: {format_exc(e)}') -@Client.on_message(filters.command("chatoff", prefix) & filters.me) +@Client.on_message(filters.command('chatoff', prefix) & filters.me) async def chatoff(_, message: Message): - db.remove("core.chatbot", "chatai_users") - await message.reply_text("ChatBot is off now") + db.remove('core.chatbot', 'chatai_users') + await message.reply_text('ChatBot is off now') restart() -@Client.on_message(filters.command("listai", prefix) & filters.me) +@Client.on_message(filters.command('listai', prefix) & filters.me) async def listai(_, message: Message): - await message.edit_text( - f"User ID's Currently in AI ChatBot List:\n {chatai_users}" - ) + await message.edit_text(f"User ID's Currently in AI ChatBot List:\n {chatai_users}") -modules_help["chatbot"] = { - "addai [user_id]*": "Add A user to AI ChatBot List", - "remai [user_id]*": "Remove A user from AI ChatBot List", - "listai": "List A user from AI ChatBot List", - "chatoff": "Turn off AI ChatBot", +modules_help['chatbot'] = { + 'addai [user_id]*': 'Add A user to AI ChatBot List', + 'remai [user_id]*': 'Remove A user from AI ChatBot List', + 'listai': 'List A user from AI ChatBot List', + 'chatoff': 'Turn off AI ChatBot', } diff --git a/modules/circle.py b/modules/circle.py index 45eea7b..fb45b48 100644 --- a/modules/circle.py +++ b/modules/circle.py @@ -3,38 +3,37 @@ import os from io import BytesIO from PIL import Image, ImageDraw, ImageFilter, ImageOps -from pyrogram import Client, filters, enums +from pyrogram import Client, enums, filters from pyrogram.types import Message # noinspection PyUnresolvedReferences from utils.misc import modules_help, prefix # noinspection PyUnresolvedReferences -from utils.scripts import import_library, format_exc +from utils.scripts import format_exc, import_library - -VideoFileClip = import_library("moviepy", "moviepy==2.2.1").VideoFileClip +VideoFileClip = import_library('moviepy', 'moviepy==2.2.1').VideoFileClip im = None def process_img(filename): global im - im = Image.open(f"downloads/{filename}") + im = Image.open(f'downloads/{filename}') w, h = im.size - img = Image.new("RGBA", (w, h), (0, 0, 0, 0)) + img = Image.new('RGBA', (w, h), (0, 0, 0, 0)) img.paste(im, (0, 0)) m = min(w, h) img = img.crop(((w - m) // 2, (h - m) // 2, (w + m) // 2, (h + m) // 2)) w, h = img.size - mask = Image.new("L", (w, h), 0) + mask = Image.new('L', (w, h), 0) draw = ImageDraw.Draw(mask) draw.ellipse((10, 10, w - 10, h - 10), fill=255) mask = mask.filter(ImageFilter.GaussianBlur(2)) img = ImageOps.fit(img, (w, h)) img.putalpha(mask) im = BytesIO() - im.name = "img.webp" + im.name = 'img.webp' img.save(im) im.seek(0) @@ -44,103 +43,91 @@ video = None def process_vid(filename): global video - video = VideoFileClip(f"downloads/{filename}") + video = VideoFileClip(f'downloads/{filename}') w, h = video.size m = min(w, h) box = { - "x1": (w - m) // 2, - "y1": (h - m) // 2, - "x2": (w + m) // 2, - "y2": (h + m) // 2, + 'x1': (w - m) // 2, + 'y1': (h - m) // 2, + 'x2': (w + m) // 2, + 'y2': (h + m) // 2, } video = video.cropped(**box) -@Client.on_message(filters.command(["circle", "round"], prefix) & filters.me) +@Client.on_message(filters.command(['circle', 'round'], prefix) & filters.me) async def circle(_, message: Message): try: if not message.reply_to_message: return await message.reply( - "Reply is required for this command", + 'Reply is required for this command', parse_mode=enums.ParseMode.HTML, ) if message.reply_to_message.photo: - filename = "circle.jpg" - typ = "photo" + filename = 'circle.jpg' + typ = 'photo' elif message.reply_to_message.sticker: if message.reply_to_message.sticker.is_video: return await message.reply( - "Video stickers is not supported", + 'Video stickers is not supported', parse_mode=enums.ParseMode.HTML, ) - filename = "circle.webp" - typ = "photo" + filename = 'circle.webp' + typ = 'photo' elif message.reply_to_message.video: - filename = "circle.mp4" - typ = "video" + filename = 'circle.mp4' + typ = 'video' elif message.reply_to_message.document: _filename = message.reply_to_message.document.file_name.casefold() - if _filename.endswith(".png"): - filename = "circle.png" - typ = "photo" - elif _filename.endswith(".jpg"): - filename = "circle.jpg" - typ = "photo" - elif _filename.endswith(".jpeg"): - filename = "circle.jpeg" - typ = "photo" - elif _filename.endswith(".webp"): - filename = "circle.webp" - typ = "photo" - elif _filename.endswith(".mp4"): - filename = "circle.mp4" - typ = "video" + if _filename.endswith('.png'): + filename = 'circle.png' + typ = 'photo' + elif _filename.endswith('.jpg'): + filename = 'circle.jpg' + typ = 'photo' + elif _filename.endswith('.jpeg'): + filename = 'circle.jpeg' + typ = 'photo' + elif _filename.endswith('.webp'): + filename = 'circle.webp' + typ = 'photo' + elif _filename.endswith('.mp4'): + filename = 'circle.mp4' + typ = 'video' else: - return await message.reply( - "Invalid file type", parse_mode=enums.ParseMode.HTML - ) + return await message.reply('Invalid file type', parse_mode=enums.ParseMode.HTML) else: - return await message.reply( - "Invalid file type", parse_mode=enums.ParseMode.HTML - ) + return await message.reply('Invalid file type', parse_mode=enums.ParseMode.HTML) - if typ == "photo": - await message.edit( - "Processing image📷", parse_mode=enums.ParseMode.HTML - ) - await message.reply_to_message.download(f"downloads/{filename}") + if typ == 'photo': + await message.edit('Processing image📷', parse_mode=enums.ParseMode.HTML) + await message.reply_to_message.download(f'downloads/{filename}') await asyncio.get_event_loop().run_in_executor(None, process_img, filename) await message.delete() - return await message.reply_sticker( - sticker=im, reply_to_message_id=message.reply_to_message.id - ) + return await message.reply_sticker(sticker=im, reply_to_message_id=message.reply_to_message.id) else: - await message.edit( - "Processing video🎥", parse_mode=enums.ParseMode.HTML - ) - await message.reply_to_message.download(f"downloads/{filename}") + await message.edit('Processing video🎥', parse_mode=enums.ParseMode.HTML) + await message.reply_to_message.download(f'downloads/{filename}') await asyncio.get_event_loop().run_in_executor(None, process_vid, filename) - await message.edit("Saving video📼", parse_mode=enums.ParseMode.HTML) - await asyncio.get_event_loop().run_in_executor( - None, video.write_videofile, "downloads/result.mp4" - ) + await message.edit('Saving video📼', parse_mode=enums.ParseMode.HTML) + await asyncio.get_event_loop().run_in_executor(None, video.write_videofile, 'downloads/result.mp4') await message.delete() await message.reply_video_note( - video_note="downloads/result.mp4", + video_note='downloads/result.mp4', duration=int(video.duration), reply_to_message_id=message.reply_to_message.id, ) if isinstance(video, VideoFileClip): video.close() - os.remove(f"downloads/{filename}") - os.remove("downloads/result.mp4") + os.remove(f'downloads/{filename}') + os.remove('downloads/result.mp4') except Exception as e: await message.reply(format_exc(e), parse_mode=enums.ParseMode.HTML) -modules_help["circle"] = { - "round": "Round a photo or video.", - "circle": "Circle a photo or video.", +modules_help['circle'] = { + 'round': 'Round a photo or video.', + 'circle': 'Circle a photo or video.', } diff --git a/modules/clear_notifs.py b/modules/clear_notifs.py index 1b3fe2a..37677d0 100644 --- a/modules/clear_notifs.py +++ b/modules/clear_notifs.py @@ -17,11 +17,10 @@ from pyrogram import Client, filters from pyrogram.raw import functions from pyrogram.types import Message - from utils.misc import modules_help, prefix -@Client.on_message(filters.command(["clear_@"], prefix) & filters.me) +@Client.on_message(filters.command(['clear_@'], prefix) & filters.me) async def solo_mention_clear(client: Client, message: Message): await message.delete() peer = await client.resolve_peer(message.chat.id) @@ -29,24 +28,20 @@ async def solo_mention_clear(client: Client, message: Message): await client.invoke(request) -@Client.on_message(filters.command(["clear_all_@"], prefix) & filters.me) +@Client.on_message(filters.command(['clear_all_@'], prefix) & filters.me) async def global_mention_clear(client: Client, message: Message): counter: int = 0 - await message.edit_text( - f"Clearing all mentions...\n\nCleared: {counter} chats" - ) + await message.edit_text(f'Clearing all mentions...\n\nCleared: {counter} chats') async for dialog in client.get_dialogs(): peer = await client.resolve_peer(dialog.chat.id) request = functions.messages.ReadMentions(peer=peer) await client.invoke(request) counter += 1 - await message.edit_text( - f"Clearing all mentions...\n\nCleared: {counter} chats" - ) + await message.edit_text(f'Clearing all mentions...\n\nCleared: {counter} chats') await message.delete() -@Client.on_message(filters.command(["clear_reacts"], prefix) & filters.me) +@Client.on_message(filters.command(['clear_reacts'], prefix) & filters.me) async def solo_reaction_clear(client: Client, message: Message): await message.delete() peer = await client.resolve_peer(message.chat.id) @@ -54,26 +49,22 @@ async def solo_reaction_clear(client: Client, message: Message): await client.invoke(request) -@Client.on_message(filters.command(["clear_all_reacts"], prefix) & filters.me) +@Client.on_message(filters.command(['clear_all_reacts'], prefix) & filters.me) async def global_reaction_clear(client: Client, message: Message): counter: int = 0 - await message.edit_text( - f"Clearing all reactions...\n\nCleared: {counter} chats" - ) + await message.edit_text(f'Clearing all reactions...\n\nCleared: {counter} chats') async for dialog in client.get_dialogs(): peer = await client.resolve_peer(dialog.chat.id) request = functions.messages.ReadReactions(peer=peer) await client.invoke(request) counter += 1 - await message.edit_text( - f"Clearing all reactions...\n\nCleared: {counter} chats" - ) + await message.edit_text(f'Clearing all reactions...\n\nCleared: {counter} chats') await message.delete() -modules_help["clear_notifs"] = { - "clear_@": "clear all mentions in this chat", - "clear_all_@": "clear all mentions in all chats", - "clear_reacts": "clear all reactions in this chat", - "clear_all_reacts": "clear all reactions in all chats (except private chats)", +modules_help['clear_notifs'] = { + 'clear_@': 'clear all mentions in this chat', + 'clear_all_@': 'clear all mentions in all chats', + 'clear_reacts': 'clear all reactions in this chat', + 'clear_all_reacts': 'clear all reactions in all chats (except private chats)', } diff --git a/modules/cohere.py b/modules/cohere.py index fa26034..f9c7eeb 100644 --- a/modules/cohere.py +++ b/modules/cohere.py @@ -1,26 +1,24 @@ import asyncio -from json import tool -from utils.scripts import import_library -from utils.config import cohere_key -cohere = import_library("cohere") +from utils.config import cohere_key +from utils.scripts import import_library + +cohere = import_library('cohere') import cohere co = cohere.Client(cohere_key) -from utils.misc import modules_help, prefix -from utils.scripts import format_exc -from utils.db import db -from utils.rentry import paste as rentry_paste - - -from pyrogram import Client, filters, enums -from pyrogram.types import Message +from pyrogram import Client, enums, filters from pyrogram.errors import MessageTooLong +from pyrogram.types import Message +from utils.db import db +from utils.misc import modules_help, prefix +from utils.rentry import paste as rentry_paste +from utils.scripts import format_exc -@Client.on_message(filters.command("cohere", prefix) & filters.me) +@Client.on_message(filters.command('cohere', prefix) & filters.me) async def cohere(c: Client, message: Message): try: user_id = message.from_user.id @@ -31,104 +29,94 @@ async def cohere(c: Client, message: Message): elif message.reply_to_message: prompt = message.reply_to_message.text else: - await message.edit_text( - f"Usage: {prefix}cohere [prompt/reply to message]" - ) + await message.edit_text(f'Usage: {prefix}cohere [prompt/reply to message]') return - db.add_chat_history(user_id, {"role": "USER", "message": prompt}) + db.add_chat_history(user_id, {'role': 'USER', 'message': prompt}) - await message.edit_text("Umm, lemme think...") + await message.edit_text('Umm, lemme think...') response = co.chat_stream( chat_history=chat_history, - model="command-r-plus", + model='command-r-plus', message=prompt, temperature=0.8, - tools=[{"name": "internet_search"}], + tools=[{'name': 'internet_search'}], connectors=[], - prompt_truncation="OFF", + prompt_truncation='OFF', ) - output = "" - tool_message = "" + output = '' + tool_message = '' data = [] for event in response: - if event.event_type == "tool-calls-chunk": + if event.event_type == 'tool-calls-chunk': if event.tool_call_delta and event.tool_call_delta.text is None: - tool_message += "" + tool_message += '' else: tool_message += event.text - if event.event_type == "search-results": + if event.event_type == 'search-results': data.append(event.documents) - if event.event_type == "text-generation": + if event.event_type == 'text-generation': output += event.text - if output == "": + if output == '': output = "I can't seem to find an answer to that" - db.add_chat_history(user_id, {"role": "CHATBOT", "message": output}) + db.add_chat_history(user_id, {'role': 'CHATBOT', 'message': output}) - await message.edit_text(f"{tool_message}") + await message.edit_text(f'{tool_message}') await asyncio.sleep(5) try: data = data[0] - references = "" + references = '' reference_dict = {} for item in data: - title = item["title"] - url = item["url"] + title = item['title'] + url = item['url'] if title not in reference_dict: reference_dict[title] = url i = 1 for title, url in reference_dict.items(): - references += f"**{i}.** [{title}]({url})\n" + references += f'**{i}.** [{title}]({url})\n' i += 1 await message.edit_text( - f"**Question:**`{prompt}`\n**Answer:** {output}\n\n**References:**\n{references}", + f'**Question:**`{prompt}`\n**Answer:** {output}\n\n**References:**\n{references}', parse_mode=enums.ParseMode.MARKDOWN, disable_web_page_preview=True, ) except IndexError: - references = "" + references = '' await message.edit_text( - f"**Question:**`{prompt}`\n**Answer:** {output}\n", + f'**Question:**`{prompt}`\n**Answer:** {output}\n', parse_mode=enums.ParseMode.MARKDOWN, disable_web_page_preview=True, ) except MessageTooLong: - await message.edit_text( - "Output is too long... Pasting to rentry..." - ) + await message.edit_text('Output is too long... Pasting to rentry...') try: - output = output + "\n\n" + references if references else output - rentry_url, edit_code = await rentry_paste( - text=output, return_edit=True - ) + output = output + '\n\n' + references if references else output + rentry_url, edit_code = await rentry_paste(text=output, return_edit=True) except RuntimeError: - await message.edit_text( - "Error: Failed to paste to rentry" - ) + await message.edit_text('Error: Failed to paste to rentry') return await c.send_message( - "me", + 'me', f"Here's your edit code for Url: {rentry_url}\nEdit code: {edit_code}", disable_web_page_preview=True, ) await message.edit_text( - f"Output: {rentry_url}\nNote: Edit Code has been sent to your saved messages", + f'Output: {rentry_url}\nNote: Edit Code has been sent to your saved messages', disable_web_page_preview=True, ) except Exception as e: - await message.edit_text(f"An error occurred: {format_exc(e)}") + await message.edit_text(f'An error occurred: {format_exc(e)}') -modules_help["cohere"] = { - "cohere": "Chat with cohere ai" - + "\nSupports Chat History\n" - + "Supports real time internet search" +modules_help['cohere'] = { + 'cohere': 'Chat with cohere ai' + '\nSupports Chat History\n' + 'Supports real time internet search' } diff --git a/modules/demotivator.py b/modules/demotivator.py index 2fc6647..fbea5d2 100644 --- a/modules/demotivator.py +++ b/modules/demotivator.py @@ -1,93 +1,64 @@ import random from io import BytesIO -from pyrogram import Client, filters, enums +from pyrogram import Client, enums, filters from pyrogram.types import Message - from utils.misc import modules_help, prefix from utils.scripts import import_library -requests = import_library("requests") -PIL = import_library("PIL", "pillow") +requests = import_library('requests') +PIL = import_library('PIL', 'pillow') from PIL import Image, ImageDraw, ImageFont -@Client.on_message(filters.command(["dem"], prefix) & filters.me) +@Client.on_message(filters.command(['dem'], prefix) & filters.me) async def demotivator(client: Client, message: Message): - await message.edit( - "Process of demotivation...", parse_mode=enums.ParseMode.HTML - ) - font = requests.get( - "https://github.com/The-MoonTg-project/files/blob/main/Times%20New%20Roman.ttf?raw=true" - ) + await message.edit('Process of demotivation...', parse_mode=enums.ParseMode.HTML) + font = requests.get('https://github.com/The-MoonTg-project/files/blob/main/Times%20New%20Roman.ttf?raw=true') f = font.content - template_dem = requests.get( - "https://raw.githubusercontent.com/files/main/demotivator.png" - ) + template_dem = requests.get('https://raw.githubusercontent.com/files/main/demotivator.png') if message.reply_to_message: - words = ["random", "text", "typing", "fuck"] + words = ['random', 'text', 'typing', 'fuck'] if message.reply_to_message.photo: - donwloads = await client.download_media( - message.reply_to_message.photo.file_id - ) - photo = Image.open(f"{donwloads}") + donwloads = await client.download_media(message.reply_to_message.photo.file_id) + photo = Image.open(f'{donwloads}') resize_photo = photo.resize((469, 312)) - text = ( - message.text.split(" ", maxsplit=1)[1] - if len(message.text.split()) > 1 - else random.choice(words) - ) + text = message.text.split(' ', maxsplit=1)[1] if len(message.text.split()) > 1 else random.choice(words) im = Image.open(BytesIO(template_dem.content)) im.paste(resize_photo, (65, 48)) text_font = ImageFont.truetype(BytesIO(f), 22) text_draw = ImageDraw.Draw(im) - text_draw.multiline_text( - (299, 412), text, font=text_font, fill=(255, 255, 255), anchor="ms" - ) - im.save(f"downloads/{message.id}.png") - await message.reply_to_message.reply_photo(f"downloads/{message.id}.png") + text_draw.multiline_text((299, 412), text, font=text_font, fill=(255, 255, 255), anchor='ms') + im.save(f'downloads/{message.id}.png') + await message.reply_to_message.reply_photo(f'downloads/{message.id}.png') await message.delete() elif message.reply_to_message.sticker: if not message.reply_to_message.sticker.is_animated: - donwloads = await client.download_media( - message.reply_to_message.sticker.file_id - ) - photo = Image.open(f"{donwloads}") + donwloads = await client.download_media(message.reply_to_message.sticker.file_id) + photo = Image.open(f'{donwloads}') resize_photo = photo.resize((469, 312)) - text = ( - message.text.split(" ", maxsplit=1)[1] - if len(message.text.split()) > 1 - else random.choice(words) - ) + text = message.text.split(' ', maxsplit=1)[1] if len(message.text.split()) > 1 else random.choice(words) im = Image.open(BytesIO(template_dem.content)) im.paste(resize_photo, (65, 48)) text_font = ImageFont.truetype(BytesIO(f), 22) text_draw = ImageDraw.Draw(im) - text_draw.multiline_text( - (299, 412), text, font=text_font, fill=(255, 255, 255), anchor="ms" - ) - im.save(f"downloads/{message.id}.png") - await message.reply_to_message.reply_photo( - f"downloads/{message.id}.png" - ) + text_draw.multiline_text((299, 412), text, font=text_font, fill=(255, 255, 255), anchor='ms') + im.save(f'downloads/{message.id}.png') + await message.reply_to_message.reply_photo(f'downloads/{message.id}.png') await message.delete() else: await message.edit( - "Animated stickers are not supported", + 'Animated stickers are not supported', parse_mode=enums.ParseMode.HTML, ) else: await message.edit( - "Need to answer the photo/sticker", + 'Need to answer the photo/sticker', parse_mode=enums.ParseMode.HTML, ) else: - await message.edit( - "Need to answer the photo/sticker", parse_mode=enums.ParseMode.HTML - ) + await message.edit('Need to answer the photo/sticker', parse_mode=enums.ParseMode.HTML) -modules_help["demotivator"] = { - "dem [text]*": "Reply to the picture to make a demotivator out of it" -} +modules_help['demotivator'] = {'dem [text]*': 'Reply to the picture to make a demotivator out of it'} diff --git a/modules/destroy.py b/modules/destroy.py index 0765f86..d85e1e2 100644 --- a/modules/destroy.py +++ b/modules/destroy.py @@ -1,60 +1,54 @@ import os -from pyrogram import Client, filters, enums -from pyrogram.types import Message +from pyrogram import Client, enums, filters +from pyrogram.types import Message from utils.misc import modules_help, prefix from utils.scripts import import_library -lottie = import_library("lottie") +lottie = import_library('lottie') from lottie.exporters import exporters from lottie.importers import importers -@Client.on_message(filters.command("destroy", prefix) & filters.me) +@Client.on_message(filters.command('destroy', prefix) & filters.me) async def destroy_sticker(client: Client, message: Message): """Destroy animated stickers by modifying their animation properties""" try: reply = message.reply_to_message if not reply or not reply.sticker or not reply.sticker.is_animated: return await message.edit( - "**Please reply to an animated sticker!**", + '**Please reply to an animated sticker!**', parse_mode=enums.ParseMode.MARKDOWN, ) - edit_msg = await message.edit( - "**🔄 Destroying sticker...**", parse_mode=enums.ParseMode.MARKDOWN - ) + edit_msg = await message.edit('**🔄 Destroying sticker...**', parse_mode=enums.ParseMode.MARKDOWN) # Download sticker tgs_path = await reply.download() if not tgs_path or not os.path.exists(tgs_path): - return await edit_msg.edit( - "**❌ Download failed!**", parse_mode=enums.ParseMode.MARKDOWN - ) + return await edit_msg.edit('**❌ Download failed!**', parse_mode=enums.ParseMode.MARKDOWN) # Conversion process - json_path = "temp.json" - output_path = "MoonUB.tgs" + json_path = 'temp.json' + output_path = 'MoonUB.tgs' importer = importers.get_from_filename(tgs_path) if not importer: - return await edit_msg.edit( - "**❌ JSON conversion failed!**", parse_mode=enums.ParseMode.MARKDOWN - ) + return await edit_msg.edit('**❌ JSON conversion failed!**', parse_mode=enums.ParseMode.MARKDOWN) animation = importer.process(tgs_path) exporter = exporters.get_from_filename(json_path) exporter.process(animation, json_path) # Modify JSON data - with open(json_path, "r+") as f: + with open(json_path, 'r+') as f: content = f.read() modified = ( - content.replace("[1]", "[2]") - .replace("[2]", "[3]") - .replace("[3]", "[4]") - .replace("[4]", "[5]") - .replace("[5]", "[6]") + content.replace('[1]', '[2]') + .replace('[2]', '[3]') + .replace('[3]', '[4]') + .replace('[4]', '[5]') + .replace('[5]', '[6]') ) f.seek(0) f.write(modified) @@ -70,7 +64,7 @@ async def destroy_sticker(client: Client, message: Message): await edit_msg.delete() except Exception as e: - await message.edit(f"**❌ Error:** `{e}`", parse_mode=enums.ParseMode.MARKDOWN) + await message.edit(f'**❌ Error:** `{e}`', parse_mode=enums.ParseMode.MARKDOWN) finally: # Cleanup temporary files for file_path in [tgs_path, json_path, output_path]: @@ -78,7 +72,7 @@ async def destroy_sticker(client: Client, message: Message): try: os.remove(file_path) except Exception as clean_error: - print(f"Cleanup error: {clean_error}") + print(f'Cleanup error: {clean_error}') -modules_help["destroy"] = {"destroy [reply]": "Modify and destroy animated stickers"} +modules_help['destroy'] = {'destroy [reply]': 'Modify and destroy animated stickers'} diff --git a/modules/dice.py b/modules/dice.py index 472d038..44bebf7 100644 --- a/modules/dice.py +++ b/modules/dice.py @@ -1,33 +1,26 @@ -from pyrogram import Client, filters, enums +import asyncio + +from pyrogram import Client, enums, filters from pyrogram.types import Message from utils.misc import modules_help, prefix from utils.scripts import format_exc -import asyncio -@Client.on_message(filters.command("dice", prefix) & filters.me) +@Client.on_message(filters.command('dice', prefix) & filters.me) async def dice_text(client: Client, message: Message): try: value = int(message.command[1]) if value not in range(1, 7): raise AssertionError except (ValueError, IndexError, AssertionError): - return await message.edit( - "Invalid value", parse_mode=enums.ParseMode.HTML - ) + return await message.edit('Invalid value', parse_mode=enums.ParseMode.HTML) try: - message.dice = type("bruh", (), {"value": 0})() + message.dice = type('bruh', (), {'value': 0})() while message.dice.value != value: - message = ( - await asyncio.gather( - message.delete(), client.send_dice(message.chat.id) - ) - )[1] + message = (await asyncio.gather(message.delete(), client.send_dice(message.chat.id)))[1] except Exception as e: await message.edit(format_exc(e), parse_mode=enums.ParseMode.HTML) -modules_help["dice"] = { - "dice [1-6]*": "Generate dice with specified value. Works only in groups" -} +modules_help['dice'] = {'dice [1-6]*': 'Generate dice with specified value. Works only in groups'} diff --git a/modules/direct.py b/modules/direct.py index 4f9b75f..aca8a95 100644 --- a/modules/direct.py +++ b/modules/direct.py @@ -3,7 +3,7 @@ # Licensed under the Raphielscape Public License, Version 1.d (the "License"); # you may not use this file except in compliance with the License. # -""" Userbot module containing various sites direct links generators""" +"""Userbot module containing various sites direct links generators""" import json import re @@ -14,139 +14,135 @@ from subprocess import PIPE, Popen import requests from bs4 import BeautifulSoup from humanize import naturalsize - from pyrogram import Client, enums, filters from pyrogram.types import Message - from utils.misc import modules_help, prefix def subprocess_run(cmd): - reply = "" + reply = '' cmd_args = cmd.split() subproc = Popen( cmd_args, stdout=PIPE, stderr=PIPE, universal_newlines=True, - executable="bash", + executable='bash', ) talk = subproc.communicate() exitCode = subproc.returncode if exitCode != 0: reply += ( - "```An error was detected while running the subprocess:\n" - f"exit code: {exitCode}\n" - f"stdout: {talk[0]}\n" - f"stderr: {talk[1]}```" + '```An error was detected while running the subprocess:\n' + f'exit code: {exitCode}\n' + f'stdout: {talk[0]}\n' + f'stderr: {talk[1]}```' ) return reply return talk -@Client.on_message(filters.command("direct", prefix) & filters.me) +@Client.on_message(filters.command('direct', prefix) & filters.me) async def direct_link_generator(_, m: Message): if len(m.command) > 1: message = m.text.split(maxsplit=1)[1] elif m.reply_to_message: message = m.reply_to_message.text else: - await m.edit(f"Usage: {prefix}direct [url]") + await m.edit(f'Usage: {prefix}direct [url]') return - reply = "" - links = re.findall(r"\bhttps?://.*\.\S+", message) + reply = '' + links = re.findall(r'\bhttps?://.*\.\S+', message) if not links: - reply = "`No links found!`" + reply = '`No links found!`' await m.edit(reply, parse_mode=enums.ParseMode.MARKDOWN) for link in links: - if "drive.google.com" in link: + if 'drive.google.com' in link: reply += gdrive(link) - elif "yadi.sk" in link: + elif 'yadi.sk' in link: reply += yandex_disk(link) - elif "cloud.mail.ru" in link: + elif 'cloud.mail.ru' in link: reply += cm_ru(link) - elif "mediafire.com" in link: + elif 'mediafire.com' in link: reply += mediafire(link) - elif "sourceforge.net" in link: + elif 'sourceforge.net' in link: reply += sourceforge(link) - elif "osdn.net" in link: + elif 'osdn.net' in link: reply += osdn(link) - elif "androidfilehost.com" in link: + elif 'androidfilehost.com' in link: reply += androidfilehost(link) else: - reply += re.findall(r"\bhttps?://(.*?[^/]+)", link)[0] + " is not supported" + reply += re.findall(r'\bhttps?://(.*?[^/]+)', link)[0] + ' is not supported' await m.edit(reply, parse_mode=enums.ParseMode.MARKDOWN) def gdrive(url: str) -> str: """GDrive direct links generator""" - drive = "https://drive.google.com" + drive = 'https://drive.google.com' try: - link = re.findall(r"\bhttps?://drive\.google\.com\S+", url)[0] + link = re.findall(r'\bhttps?://drive\.google\.com\S+', url)[0] except IndexError: - reply = "`No Google drive links found`\n" + reply = '`No Google drive links found`\n' return reply - file_id = "" - reply = "" - if link.find("view") != -1: - file_id = link.split("/")[-2] - elif link.find("open?id=") != -1: - file_id = link.split("open?id=")[1].strip() - elif link.find("uc?id=") != -1: - file_id = link.split("uc?id=")[1].strip() - url = f"{drive}/uc?export=download&id={file_id}" + file_id = '' + reply = '' + if link.find('view') != -1: + file_id = link.split('/')[-2] + elif link.find('open?id=') != -1: + file_id = link.split('open?id=')[1].strip() + elif link.find('uc?id=') != -1: + file_id = link.split('uc?id=')[1].strip() + url = f'{drive}/uc?export=download&id={file_id}' download = requests.get(url, stream=True, allow_redirects=False) cookies = download.cookies try: # In case of small file size, Google downloads directly - dl_url = download.headers["location"] - page = BeautifulSoup(download.content, "html.parser") - if "accounts.google.com" in dl_url: # non-public file - reply += "`Link is not public!`\n" + dl_url = download.headers['location'] + page = BeautifulSoup(download.content, 'html.parser') + if 'accounts.google.com' in dl_url: # non-public file + reply += '`Link is not public!`\n' return reply - name = "Direct Download Link" + name = 'Direct Download Link' except KeyError: # In case of download warning page - page = BeautifulSoup(download.content, "html.parser") + page = BeautifulSoup(download.content, 'html.parser') if download.headers is not None: - dl_url = download.headers.get("location") - page_element = page.find("a", {"id": "uc-download-link"}) + dl_url = download.headers.get('location') + page_element = page.find('a', {'id': 'uc-download-link'}) if page_element is not None: - export = drive + page_element.get("href") - name = page.find("span", {"class": "uc-name-size"}).text - response = requests.get( - export, stream=True, allow_redirects=False, cookies=cookies - ) - dl_url = response.headers["location"] - if "accounts.google.com" in dl_url: - name = page.find("span", {"class": "uc-name-size"}).text - reply += "Link is not public!" + export = drive + page_element.get('href') + name = page.find('span', {'class': 'uc-name-size'}).text + response = requests.get(export, stream=True, allow_redirects=False, cookies=cookies) + dl_url = response.headers['location'] + if 'accounts.google.com' in dl_url: + name = page.find('span', {'class': 'uc-name-size'}).text + reply += 'Link is not public!' return reply - if "=sharing" in dl_url: - name = page.find("span", {"class": "uc-name-size"}).text - reply += "```Provide GDrive Link not directc sharing of GDrive!```" + if '=sharing' in dl_url: + name = page.find('span', {'class': 'uc-name-size'}).text + reply += '```Provide GDrive Link not directc sharing of GDrive!```' return reply - reply += f"[{name}]({dl_url})\n" + reply += f'[{name}]({dl_url})\n' return reply def yandex_disk(url: str) -> str: """Yandex.Disk direct links generator Based on https://github.com/wldhx/yadisk-direct""" - reply = "" + reply = '' try: - link = re.findall(r"\bhttps?://.*yadi\.sk\S+", url)[0] + link = re.findall(r'\bhttps?://.*yadi\.sk\S+', url)[0] except IndexError: - reply = "`No Yandex.Disk links found`\n" + reply = '`No Yandex.Disk links found`\n' return reply - api = "https://cloud-api.yandex.net/v1/disk/public/resources/download?public_key={}" + api = 'https://cloud-api.yandex.net/v1/disk/public/resources/download?public_key={}' try: - dl_url = requests.get(api.format(link)).json()["href"] - name = dl_url.split("filename=")[1].split("&disposition")[0] - reply += f"[{name}]({dl_url})\n" + dl_url = requests.get(api.format(link)).json()['href'] + name = dl_url.split('filename=')[1].split('&disposition')[0] + reply += f'[{name}]({dl_url})\n' except KeyError: - reply += "`Error: File not found / Download limit reached`\n" + reply += '`Error: File not found / Download limit reached`\n' return reply return reply @@ -154,13 +150,13 @@ def yandex_disk(url: str) -> str: def cm_ru(url: str) -> str: """cloud.mail.ru direct links generator Using https://github.com/JrMasterModelBuilder/cmrudl.py""" - reply = "" + reply = '' try: - link = re.findall(r"\bhttps?://.*cloud\.mail\.ru\S+", url)[0] + link = re.findall(r'\bhttps?://.*cloud\.mail\.ru\S+', url)[0] except IndexError: - reply = "`No cloud.mail.ru links found`\n" + reply = '`No cloud.mail.ru links found`\n' return reply - cmd = f"bin/cmrudl -s {link}" + cmd = f'bin/cmrudl -s {link}' result = subprocess_run(cmd) try: result = result[0].splitlines()[-1] @@ -170,121 +166,116 @@ def cm_ru(url: str) -> str: return reply except IndexError: return reply - dl_url = data["download"] - name = data["file_name"] - size = naturalsize(int(data["file_size"])) - reply += f"[{name} ({size})]({dl_url})\n" + dl_url = data['download'] + name = data['file_name'] + size = naturalsize(int(data['file_size'])) + reply += f'[{name} ({size})]({dl_url})\n' return reply def mediafire(url: str) -> str: """MediaFire direct links generator""" try: - link = re.findall(r"\bhttps?://.*mediafire\.com\S+", url)[0] + link = re.findall(r'\bhttps?://.*mediafire\.com\S+', url)[0] except IndexError: - reply = "`No MediaFire links found`\n" + reply = '`No MediaFire links found`\n' return reply - reply = "" - page = BeautifulSoup(requests.get(link).content, "lxml") - info = page.find("a", {"aria-label": "Download file"}) - dl_url = info.get("href") - size = re.findall(r"\(.*\)", info.text)[0] - name = page.find("div", {"class": "filename"}).text - reply += f"[{name} {size}]({dl_url})\n" + reply = '' + page = BeautifulSoup(requests.get(link).content, 'lxml') + info = page.find('a', {'aria-label': 'Download file'}) + dl_url = info.get('href') + size = re.findall(r'\(.*\)', info.text)[0] + name = page.find('div', {'class': 'filename'}).text + reply += f'[{name} {size}]({dl_url})\n' return reply def sourceforge(url: str) -> str: """SourceForge direct links generator""" try: - link = re.findall(r"\bhttps?://.*sourceforge\.net\S+", url)[0] + link = re.findall(r'\bhttps?://.*sourceforge\.net\S+', url)[0] except IndexError: - reply = "`No SourceForge links found`\n" + reply = '`No SourceForge links found`\n' return reply - file_path = re.findall(r"files(.*)/download", link)[0] - reply = f"Mirrors for __{file_path.split('/')[-1]}__\n" - project = re.findall(r"projects?/(.*?)/files", link)[0] - mirrors = ( - f"https://sourceforge.net/settings/mirror_choices?" - f"projectname={project}&filename={file_path}" - ) - page = BeautifulSoup(requests.get(mirrors).content, "html.parser") - info = page.find("ul", {"id": "mirrorList"}).findAll("li") + file_path = re.findall(r'files(.*)/download', link)[0] + reply = f'Mirrors for __{file_path.split("/")[-1]}__\n' + project = re.findall(r'projects?/(.*?)/files', link)[0] + mirrors = f'https://sourceforge.net/settings/mirror_choices?projectname={project}&filename={file_path}' + page = BeautifulSoup(requests.get(mirrors).content, 'html.parser') + info = page.find('ul', {'id': 'mirrorList'}).findAll('li') for mirror in info[1:]: - name = re.findall(r"\((.*)\)", mirror.text.strip())[0] - dl_url = ( - f'https://{mirror["id"]}.dl.sourceforge.net/project/{project}/{file_path}' - ) - reply += f"[{name}]({dl_url}) " + name = re.findall(r'\((.*)\)', mirror.text.strip())[0] + dl_url = f'https://{mirror["id"]}.dl.sourceforge.net/project/{project}/{file_path}' + reply += f'[{name}]({dl_url}) ' return reply def osdn(url: str) -> str: """OSDN direct links generator""" - osdn_link = "https://osdn.net" + osdn_link = 'https://osdn.net' try: - link = re.findall(r"\bhttps?://.*osdn\.net\S+", url)[0] + link = re.findall(r'\bhttps?://.*osdn\.net\S+', url)[0] except IndexError: - reply = "`No OSDN links found`\n" + reply = '`No OSDN links found`\n' return reply - page = BeautifulSoup(requests.get(link, allow_redirects=True).content, "lxml") - info = page.find("a", {"class": "mirror_link"}) - link = urllib.parse.unquote(osdn_link + info["href"]) - reply = f"Mirrors for __{link.split('/')[-1]}__\n" - mirrors = page.find("form", {"id": "mirror-select-form"}).findAll("tr") + page = BeautifulSoup(requests.get(link, allow_redirects=True).content, 'lxml') + info = page.find('a', {'class': 'mirror_link'}) + link = urllib.parse.unquote(osdn_link + info['href']) + reply = f'Mirrors for __{link.split("/")[-1]}__\n' + mirrors = page.find('form', {'id': 'mirror-select-form'}).findAll('tr') for data in mirrors[1:]: - mirror = data.find("input")["value"] - name = re.findall(r"\((.*)\)", data.findAll("td")[-1].text.strip())[0] - dl_url = re.sub(r"m=(.*)&f", f"m={mirror}&f", link) - reply += f"[{name}]({dl_url}) " + mirror = data.find('input')['value'] + name = re.findall(r'\((.*)\)', data.findAll('td')[-1].text.strip())[0] + dl_url = re.sub(r'm=(.*)&f', f'm={mirror}&f', link) + reply += f'[{name}]({dl_url}) ' return reply def androidfilehost(url: str) -> str: """AFH direct links generator""" try: - link = re.findall(r"\bhttps?://.*androidfilehost.*fid.*\S+", url)[0] + link = re.findall(r'\bhttps?://.*androidfilehost.*fid.*\S+', url)[0] except IndexError: - reply = "`No AFH links found`\n" + reply = '`No AFH links found`\n' return reply - fid = re.findall(r"\?fid=(.*)", link)[0] + fid = re.findall(r'\?fid=(.*)', link)[0] session = requests.Session() user_agent = useragent() - headers = {"user-agent": user_agent} + headers = {'user-agent': user_agent} res = session.get(link, headers=headers, allow_redirects=True) headers = { - "origin": "https://androidfilehost.com", - "accept-encoding": "gzip, deflate, br", - "accept-language": "en-US,en;q=0.9", - "user-agent": user_agent, - "content-type": "application/x-www-form-urlencoded; charset=UTF-8", - "x-mod-sbb-ctype": "xhr", - "accept": "*/*", - "referer": f"https://androidfilehost.com/?fid={fid}", - "authority": "androidfilehost.com", - "x-requested-with": "XMLHttpRequest", + 'origin': 'https://androidfilehost.com', + 'accept-encoding': 'gzip, deflate, br', + 'accept-language': 'en-US,en;q=0.9', + 'user-agent': user_agent, + 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8', + 'x-mod-sbb-ctype': 'xhr', + 'accept': '*/*', + 'referer': f'https://androidfilehost.com/?fid={fid}', + 'authority': 'androidfilehost.com', + 'x-requested-with': 'XMLHttpRequest', } - data = {"submit": "submit", "action": "getdownloadmirrors", "fid": f"{fid}"} + data = {'submit': 'submit', 'action': 'getdownloadmirrors', 'fid': f'{fid}'} mirrors = None - reply = "" + reply = '' error = "`Error: Can't find Mirrors for the link`\n" try: req = session.post( - "https://androidfilehost.com/libs/otf/mirrors.otf.php", + 'https://androidfilehost.com/libs/otf/mirrors.otf.php', headers=headers, data=data, cookies=res.cookies, ) - mirrors = req.json()["MIRRORS"] + mirrors = req.json()['MIRRORS'] except (json.decoder.JSONDecodeError, TypeError): reply += error if not mirrors: reply += error return reply for item in mirrors: - name = item["name"] - dl_url = item["url"] - reply += f"[{name}]({dl_url}) " + name = item['name'] + dl_url = item['url'] + reply += f'[{name}]({dl_url}) ' return reply @@ -294,20 +285,19 @@ def useragent(): """ useragents = BeautifulSoup( requests.get( - "https://developers.whatismybrowser.com/" - "useragents/explore/operating_system_name/android/" + 'https://developers.whatismybrowser.com/useragents/explore/operating_system_name/android/' ).content, - "lxml", - ).findAll("td", {"class": "useragent"}) + 'lxml', + ).findAll('td', {'class': 'useragent'}) if not useragents: - return "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3" + return 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3' user_agent = choice(useragents) return user_agent.text -modules_help["direct"] = { - "direct": "Url/reply to Url\ +modules_help['direct'] = { + 'direct': 'Url/reply to Url\ \n\nSyntax : .direct [url/reply] \ \nUsage : Generates direct download link from supported URL(s)\ -\n\nSupported websites : Google Drive - MEGA.nz - Cloud Mail - Yandex.Disk - AFH - MediaFire - SourceForge - OSDN" +\n\nSupported websites : Google Drive - MEGA.nz - Cloud Mail - Yandex.Disk - AFH - MediaFire - SourceForge - OSDN' } diff --git a/modules/duckduckgo.py b/modules/duckduckgo.py index 257b322..1308dfc 100644 --- a/modules/duckduckgo.py +++ b/modules/duckduckgo.py @@ -1,16 +1,14 @@ -from pyrogram import Client, filters -from pyrogram.types import Message -from utils.misc import modules_help, requirements_list, prefix - - -@Client.on_message(filters.command("duck", prefix) & filters.me) -async def duckgo(client: Client, message: Message): - input_str = " ".join(message.command[1:]) - sample_url = "https://duckduckgo.com/?q={}".format(input_str.replace(" ", "+")) - if sample_url: - link = sample_url.rstrip() - await message.edit_text( - "Let me 🦆 DuckDuckGo that for you:\n🔎 [{}]({})".format(input_str, link) - ) - else: - await message.edit_text("something is wrong. please try again later.") +from pyrogram import Client, filters +from pyrogram.types import Message +from utils.misc import prefix + + +@Client.on_message(filters.command('duck', prefix) & filters.me) +async def duckgo(client: Client, message: Message): + input_str = ' '.join(message.command[1:]) + sample_url = 'https://duckduckgo.com/?q={}'.format(input_str.replace(' ', '+')) + if sample_url: + link = sample_url.rstrip() + await message.edit_text(f'Let me 🦆 DuckDuckGo that for you:\n🔎 [{input_str}]({link})') + else: + await message.edit_text('something is wrong. please try again later.') diff --git a/modules/durov.py b/modules/durov.py index e01d165..010709d 100644 --- a/modules/durov.py +++ b/modules/durov.py @@ -1,17 +1,16 @@ from random import randint -from pyrogram import Client, filters, enums +from pyrogram import Client, enums, filters from pyrogram.types import Message - from utils.misc import modules_help, prefix -@Client.on_message(filters.command("durov", prefix) & filters.me) +@Client.on_message(filters.command('durov', prefix) & filters.me) async def durov(_, message: Message): await message.edit( - f"Random post from channel: https://t.me/durov/{randint(21, 36500)}", + f'Random post from channel: https://t.me/durov/{randint(21, 36500)}', parse_mode=enums.ParseMode.HTML, ) -modules_help["durov"] = {"durov": "Send random post from durov channel"} +modules_help['durov'] = {'durov': 'Send random post from durov channel'} diff --git a/modules/example.py b/modules/example.py index b4a1b65..ca3da04 100644 --- a/modules/example.py +++ b/modules/example.py @@ -16,7 +16,6 @@ from pyrogram import Client, enums, filters from pyrogram.types import Message - from utils.misc import modules_help, prefix # if your module has packages from PyPi @@ -29,30 +28,26 @@ from utils.misc import modules_help, prefix # if it isn't installed -@Client.on_message(filters.command("example_edit", prefix) & filters.me) +@Client.on_message(filters.command('example_edit', prefix) & filters.me) async def example_edit(client: Client, message: Message): try: - await message.edit("This is an example module") + await message.edit('This is an example module') except Exception as e: - await message.edit( - f"[{e.error_code}: {enums.MessageType.TEXT}] - {e.error_details}" - ) + await message.edit(f'[{e.error_code}: {enums.MessageType.TEXT}] - {e.error_details}') -@Client.on_message(filters.command("example_send", prefix) & filters.me) +@Client.on_message(filters.command('example_send', prefix) & filters.me) async def example_send(client: Client, message: Message): try: - await client.send_message(message.chat.id, "This is an example module") + await client.send_message(message.chat.id, 'This is an example module') except Exception as e: - await message.edit( - f"[{e.error_code}: {enums.MessageType.TEXT}] - {e.error_details}" - ) + await message.edit(f'[{e.error_code}: {enums.MessageType.TEXT}] - {e.error_details}') # This adds instructions for your module -modules_help["example"] = { - "example_send": "example send", - "example_edit": "example edit", +modules_help['example'] = { + 'example_send': 'example send', + 'example_edit': 'example edit', } # modules_help["example"] = { "example_send [text]": "example send" } diff --git a/modules/f.py b/modules/f.py index 40ea2eb..61ef22f 100644 --- a/modules/f.py +++ b/modules/f.py @@ -4,55 +4,49 @@ from random import randint import aiohttp from pyrogram import Client, filters from pyrogram.types import Message - from utils.misc import modules_help, prefix async def download_sticker(url, filename): headers = { - "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", - "accept-language": "en-US,en;q=0.9;q=0.8", - "cache-control": "no-cache", - "dnt": "1", - "pragma": "no-cache", - "priority": "u=0, i", - "sec-ch-ua": '"Not)A;Brand";v="8", "Chromium";v="138", "Microsoft Edge";v="138"', - "sec-ch-ua-mobile": "?0", - "sec-ch-ua-platform": '"Windows"', - "sec-fetch-dest": "document", - "sec-fetch-mode": "navigate", - "sec-fetch-site": "none", - "sec-fetch-user": "?1", - "upgrade-insecure-requests": "1", - "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0", + 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', + 'accept-language': 'en-US,en;q=0.9;q=0.8', + 'cache-control': 'no-cache', + 'dnt': '1', + 'pragma': 'no-cache', + 'priority': 'u=0, i', + 'sec-ch-ua': '"Not)A;Brand";v="8", "Chromium";v="138", "Microsoft Edge";v="138"', + 'sec-ch-ua-mobile': '?0', + 'sec-ch-ua-platform': '"Windows"', + 'sec-fetch-dest': 'document', + 'sec-fetch-mode': 'navigate', + 'sec-fetch-site': 'none', + 'sec-fetch-user': '?1', + 'upgrade-insecure-requests': '1', + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0', } - cookies = {"country": "US", "lang": "en"} + cookies = {'country': 'US', 'lang': 'en'} - async with aiohttp.ClientSession() as session: - async with session.get(url, headers=headers, cookies=cookies) as response: - if response.status == 200: - with open(filename, "wb") as f: - f.write(await response.read()) + async with aiohttp.ClientSession() as session, session.get(url, headers=headers, cookies=cookies) as response: + if response.status == 200: + with open(filename, 'wb') as f: + f.write(await response.read()) -@Client.on_message(filters.command(["f"], prefix) & filters.me) +@Client.on_message(filters.command(['f'], prefix) & filters.me) async def random_stiker(client: Client, message: Message): await message.delete() random = randint(1, 63) - index = f"00{random}" if random < 10 else f"0{random}" - sticker = ( - f"https://www.chpic.su/_data/stickers/f/FforRespect/FforRespect_{index}.webp" - ) - await download_sticker(sticker, "f.webp") - if os.path.exists("f.webp"): + index = f'00{random}' if random < 10 else f'0{random}' + sticker = f'https://www.chpic.su/_data/stickers/f/FforRespect/FforRespect_{index}.webp' + await download_sticker(sticker, 'f.webp') + if os.path.exists('f.webp'): await client.send_document( message.chat.id, - "f.webp", - reply_to_message_id=message.reply_to_message.id - if message.reply_to_message - else None, + 'f.webp', + reply_to_message_id=message.reply_to_message.id if message.reply_to_message else None, ) - os.remove("f.webp") + os.remove('f.webp') -modules_help["f"] = {"f": "Send f to pay respect"} +modules_help['f'] = {'f': 'Send f to pay respect'} diff --git a/modules/fakeactions.py b/modules/fakeactions.py index 71c1ba8..dade85c 100644 --- a/modules/fakeactions.py +++ b/modules/fakeactions.py @@ -1,26 +1,26 @@ from asyncio import sleep -from pyrogram import Client, filters, enums +from pyrogram import Client, enums, filters from pyrogram.raw import functions -from pyrogram.types import Message, InputReplyToMessage +from pyrogram.types import InputReplyToMessage, Message from utils.misc import modules_help, prefix from utils.scripts import format_exc commands = { - "ftype": enums.ChatAction.TYPING, - "faudio": enums.ChatAction.UPLOAD_AUDIO, - "fvideo": enums.ChatAction.UPLOAD_VIDEO, - "fphoto": enums.ChatAction.UPLOAD_PHOTO, - "fdocument": enums.ChatAction.UPLOAD_DOCUMENT, - "flocation": enums.ChatAction.FIND_LOCATION, - "frvideo": enums.ChatAction.RECORD_VIDEO, - "frvoice": enums.ChatAction.RECORD_AUDIO, - "frvideor": enums.ChatAction.RECORD_VIDEO_NOTE, - "fvideor": enums.ChatAction.UPLOAD_VIDEO_NOTE, - "fgame": enums.ChatAction.PLAYING, - "fcontact": enums.ChatAction.CHOOSE_CONTACT, - "fstop": enums.ChatAction.CANCEL, - "fscrn": "screenshot", + 'ftype': enums.ChatAction.TYPING, + 'faudio': enums.ChatAction.UPLOAD_AUDIO, + 'fvideo': enums.ChatAction.UPLOAD_VIDEO, + 'fphoto': enums.ChatAction.UPLOAD_PHOTO, + 'fdocument': enums.ChatAction.UPLOAD_DOCUMENT, + 'flocation': enums.ChatAction.FIND_LOCATION, + 'frvideo': enums.ChatAction.RECORD_VIDEO, + 'frvoice': enums.ChatAction.RECORD_AUDIO, + 'frvideor': enums.ChatAction.RECORD_VIDEO_NOTE, + 'fvideor': enums.ChatAction.UPLOAD_VIDEO_NOTE, + 'fgame': enums.ChatAction.PLAYING, + 'fcontact': enums.ChatAction.CHOOSE_CONTACT, + 'fstop': enums.ChatAction.CANCEL, + 'fscrn': 'screenshot', } @@ -39,12 +39,10 @@ async def fakeactions_handler(client: Client, message: Message): action = commands[cmd] try: - if action != "screenshot": + if action != 'screenshot': if sec and action != enums.ChatAction.CANCEL: while sec > 0: - await client.send_chat_action( - chat_id=message.chat.id, action=action - ) + await client.send_chat_action(chat_id=message.chat.id, action=action) await sleep(1) sec -= 1 return await client.send_chat_action(chat_id=message.chat.id, action=action) @@ -53,36 +51,30 @@ async def fakeactions_handler(client: Client, message: Message): await client.invoke( functions.messages.SendScreenshotNotification( peer=await client.resolve_peer(message.chat.id), - reply_to=InputReplyToMessage( - reply_to_message_id=message.reply_to_message.id - ), + reply_to=InputReplyToMessage(reply_to_message_id=message.reply_to_message.id), random_id=client.rnd_id(), ) ) await sleep(0.1) except AttributeError: - return await client.send_message( - "me", f"Error in fakeactions" "reply to message is required" - ) + return await client.send_message('me', 'Error in fakeactionsreply to message is required') except Exception as e: - return await client.send_message( - "me", f"Error in fakeactions" f" module:\n" + format_exc(e) - ) + return await client.send_message('me', 'Error in fakeactions module:\n' + format_exc(e)) -modules_help["fakeactions"] = { - "ftype [sec]": "Typing... action", - "faudio [sec]": "Sending voice... action", - "fvideo [sec]": "Sending video... action", - "fphoto [sec]": "Sending photo... action", - "fdocument [sec]": "Sending document... action", - "flocation [sec]": "Find location... action", - "fcontact [sec]": "Sending contact... action", - "frvideo [sec]": "Recording video... action", - "frvoice [sec]": "Recording voice... action", - "frvideor [sec]": "Recording round video... action", - "fvideor [sec]": "Uploading round video... action", - "fgame [sec]": "Playing game... action", - "fstop": "Stop actions", - "fscrn [amount] [reply_to_message]*": "Make screenshot action", +modules_help['fakeactions'] = { + 'ftype [sec]': 'Typing... action', + 'faudio [sec]': 'Sending voice... action', + 'fvideo [sec]': 'Sending video... action', + 'fphoto [sec]': 'Sending photo... action', + 'fdocument [sec]': 'Sending document... action', + 'flocation [sec]': 'Find location... action', + 'fcontact [sec]': 'Sending contact... action', + 'frvideo [sec]': 'Recording video... action', + 'frvoice [sec]': 'Recording voice... action', + 'frvideor [sec]': 'Recording round video... action', + 'fvideor [sec]': 'Uploading round video... action', + 'fgame [sec]': 'Playing game... action', + 'fstop': 'Stop actions', + 'fscrn [amount] [reply_to_message]*': 'Make screenshot action', } diff --git a/modules/filters.py b/modules/filters.py index b1f66eb..95e68a7 100644 --- a/modules/filters.py +++ b/modules/filters.py @@ -22,18 +22,17 @@ from pyrogram.types import ( InputMediaVideo, Message, ) - from utils.db import db from utils.misc import modules_help, prefix from utils.scripts import format_exc def get_filters_chat(chat_id): - return db.get("core.filters", f"{chat_id}", {}) + return db.get('core.filters', f'{chat_id}', {}) def set_filters_chat(chat_id, filters_): - return db.set("core.filters", f"{chat_id}", filters_) + return db.set('core.filters', f'{chat_id}', filters_) async def contains_filter(_, __, m): @@ -48,21 +47,17 @@ contains = filters.create(contains_filter) async def filters_main_handler(client: Client, message: Message): value = get_filters_chat(message.chat.id)[message.text.lower()] try: - await client.get_messages(int(value["CHAT_ID"]), int(value["MESSAGE_ID"])) + await client.get_messages(int(value['CHAT_ID']), int(value['MESSAGE_ID'])) except errors.RPCError as exc: raise ContinuePropagation from exc - if value.get("MEDIA_GROUP"): - messages_grouped = await client.get_media_group( - int(value["CHAT_ID"]), int(value["MESSAGE_ID"]) - ) + if value.get('MEDIA_GROUP'): + messages_grouped = await client.get_media_group(int(value['CHAT_ID']), int(value['MESSAGE_ID'])) media_grouped_list = [] for _ in messages_grouped: if _.photo: if _.caption: - media_grouped_list.append( - InputMediaPhoto(_.photo.file_id, _.caption.HTML) - ) + media_grouped_list.append(InputMediaPhoto(_.photo.file_id, _.caption.HTML)) else: media_grouped_list.append(InputMediaPhoto(_.photo.file_id)) elif _.video: @@ -76,20 +71,14 @@ async def filters_main_handler(client: Client, message: Message): ) ) else: - media_grouped_list.append( - InputMediaVideo(_.video.file_id, _.caption.HTML) - ) + media_grouped_list.append(InputMediaVideo(_.video.file_id, _.caption.HTML)) elif _.video.thumbs: - media_grouped_list.append( - InputMediaVideo(_.video.file_id, _.video.thumbs[0].file_id) - ) + media_grouped_list.append(InputMediaVideo(_.video.file_id, _.video.thumbs[0].file_id)) else: media_grouped_list.append(InputMediaVideo(_.video.file_id)) elif _.audio: if _.caption: - media_grouped_list.append( - InputMediaAudio(_.audio.file_id, _.caption.HTML) - ) + media_grouped_list.append(InputMediaAudio(_.audio.file_id, _.caption.HTML)) else: media_grouped_list.append(InputMediaAudio(_.audio.file_id)) elif _.document: @@ -103,77 +92,54 @@ async def filters_main_handler(client: Client, message: Message): ) ) else: - media_grouped_list.append( - InputMediaDocument(_.document.file_id, _.caption.HTML) - ) + media_grouped_list.append(InputMediaDocument(_.document.file_id, _.caption.HTML)) elif _.document.thumbs: - media_grouped_list.append( - InputMediaDocument( - _.document.file_id, _.document.thumbs[0].file_id - ) - ) + media_grouped_list.append(InputMediaDocument(_.document.file_id, _.document.thumbs[0].file_id)) else: media_grouped_list.append(InputMediaDocument(_.document.file_id)) - await client.send_media_group( - message.chat.id, media_grouped_list, reply_to_message_id=message.id - ) + await client.send_media_group(message.chat.id, media_grouped_list, reply_to_message_id=message.id) else: await client.copy_message( message.chat.id, - int(value["CHAT_ID"]), - int(value["MESSAGE_ID"]), + int(value['CHAT_ID']), + int(value['MESSAGE_ID']), reply_to_message_id=message.id, ) raise ContinuePropagation -@Client.on_message(filters.command(["filter"], prefix) & filters.me) +@Client.on_message(filters.command(['filter'], prefix) & filters.me) async def filter_handler(client: Client, message: Message): try: if len(message.text.split()) < 2: - return await message.edit( - f"Usage: {prefix}filter [name] (Reply required)" - ) + return await message.edit(f'Usage: {prefix}filter [name] (Reply required)') name = message.text.split(maxsplit=1)[1].lower() chat_filters = get_filters_chat(message.chat.id) if name in chat_filters.keys(): - return await message.edit( - f"Filter {name} already exists." - ) + return await message.edit(f'Filter {name} already exists.') if not message.reply_to_message: - return await message.edit("Reply to message please.") + return await message.edit('Reply to message please.') try: - chat = await client.get_chat(db.get("core.notes", "chat_id", 0)) + chat = await client.get_chat(db.get('core.notes', 'chat_id', 0)) except (errors.RPCError, ValueError, KeyError): # group is not accessible or isn't created - chat = await client.create_supergroup( - "Userbot_Notes_Filters", "Don't touch this group, please" - ) - db.set("core.notes", "chat_id", chat.id) + chat = await client.create_supergroup('Userbot_Notes_Filters', "Don't touch this group, please") + db.set('core.notes', 'chat_id', chat.id) chat_id = chat.id if message.reply_to_message.media_group_id: - get_media_group = [ - _.id - for _ in await client.get_media_group( - message.chat.id, message.reply_to_message.id - ) - ] + get_media_group = [_.id for _ in await client.get_media_group(message.chat.id, message.reply_to_message.id)] try: - message_id = await client.forward_messages( - chat_id, message.chat.id, get_media_group - ) + message_id = await client.forward_messages(chat_id, message.chat.id, get_media_group) except errors.ChatForwardsRestricted: - await message.edit( - "Forwarding messages is restricted by chat admins" - ) + await message.edit('Forwarding messages is restricted by chat admins') return filter_ = { - "MESSAGE_ID": str(message_id[1].id), - "MEDIA_GROUP": True, - "CHAT_ID": str(chat_id), + 'MESSAGE_ID': str(message_id[1].id), + 'MEDIA_GROUP': True, + 'CHAT_ID': str(chat_id), } else: try: @@ -181,44 +147,42 @@ async def filter_handler(client: Client, message: Message): except errors.ChatForwardsRestricted: message_id = await message.copy(chat_id) filter_ = { - "MEDIA_GROUP": False, - "MESSAGE_ID": str(message_id.id), - "CHAT_ID": str(chat_id), + 'MEDIA_GROUP': False, + 'MESSAGE_ID': str(message_id.id), + 'CHAT_ID': str(chat_id), } chat_filters.update({name: filter_}) set_filters_chat(message.chat.id, chat_filters) return await message.edit( - f"Filter {name} has been added.", + f'Filter {name} has been added.', ) except Exception as e: return await message.edit(format_exc(e)) -@Client.on_message(filters.command(["filters"], prefix) & filters.me) +@Client.on_message(filters.command(['filters'], prefix) & filters.me) async def filters_handler(_, message: Message): try: - text = "" + text = '' for index, a in enumerate(get_filters_chat(message.chat.id).items(), start=1): key, _ = a - key = key.replace("<", "").replace(">", "") - text += f"{index}. {key}\n" - text = f"Your filters in current chat:\n\n" f"{text}" + key = key.replace('<', '').replace('>', '') + text += f'{index}. {key}\n' + text = f'Your filters in current chat:\n\n{text}' text = text[:4096] return await message.edit(text) except Exception as e: return await message.edit(format_exc(e)) -@Client.on_message( - filters.command(["delfilter", "filterdel", "fdel"], prefix) & filters.me -) +@Client.on_message(filters.command(['delfilter', 'filterdel', 'fdel'], prefix) & filters.me) async def filter_del_handler(_, message: Message): try: if len(message.text.split()) < 2: return await message.edit( - f"Usage: {prefix}fdel [name]", + f'Usage: {prefix}fdel [name]', ) name = message.text.split(maxsplit=1)[1].lower() chat_filters = get_filters_chat(message.chat.id) @@ -229,18 +193,18 @@ async def filter_del_handler(_, message: Message): del chat_filters[name] set_filters_chat(message.chat.id, chat_filters) return await message.edit( - f"Filter {name} has been deleted.", + f'Filter {name} has been deleted.', ) except Exception as e: return await message.edit(format_exc(e)) -@Client.on_message(filters.command(["fsearch"], prefix) & filters.me) +@Client.on_message(filters.command(['fsearch'], prefix) & filters.me) async def filter_search_handler(_, message: Message): try: if len(message.text.split()) < 2: return await message.edit( - f"Usage: {prefix}fsearch [name]", + f'Usage: {prefix}fsearch [name]', ) name = message.text.split(maxsplit=1)[1].lower() chat_filters = get_filters_chat(message.chat.id) @@ -248,17 +212,14 @@ async def filter_search_handler(_, message: Message): return await message.edit( f"Filter {name} doesn't exists.", ) - return await message.edit( - f"Trigger:\n{name}\nAnswer:\n{chat_filters[name]}" - ) + return await message.edit(f'Trigger:\n{name}\nAnswer:\n{chat_filters[name]}') except Exception as e: return await message.edit(format_exc(e)) -modules_help["filters"] = { - "filter [name]": "Create filter (Reply required)", - "filters": "List of all triggers", - "fdel [name]": "Delete filter by name", - "fsearch [name]": "Info filter by name", +modules_help['filters'] = { + 'filter [name]': 'Create filter (Reply required)', + 'filters': 'List of all triggers', + 'fdel [name]': 'Delete filter by name', + 'fsearch [name]': 'Info filter by name', } diff --git a/modules/fliptext.py b/modules/fliptext.py index 662c07d..619de2a 100644 --- a/modules/fliptext.py +++ b/modules/fliptext.py @@ -1,98 +1,95 @@ -import asyncio from pyrogram import Client, filters -from pyrogram.raw import functions from pyrogram.types import Message from utils.misc import modules_help, prefix -from utils.scripts import format_exc REPLACEMENT_MAP = { - "a": "ɐ", - "b": "q", - "c": "ɔ", - "d": "p", - "e": "ǝ", - "f": "ɟ", - "g": "ƃ", - "h": "ɥ", - "i": "ᴉ", - "j": "ɾ", - "k": "ʞ", - "l": "l", - "m": "ɯ", - "n": "u", - "o": "o", - "p": "d", - "q": "b", - "r": "ɹ", - "s": "s", - "t": "ʇ", - "u": "n", - "v": "ʌ", - "w": "ʍ", - "x": "x", - "y": "ʎ", - "z": "z", - "A": "∀", - "B": "B", - "C": "Ɔ", - "D": "D", - "E": "Ǝ", - "F": "Ⅎ", - "G": "פ", - "H": "H", - "I": "I", - "J": "ſ", - "K": "K", - "L": "˥", - "M": "W", - "N": "N", - "O": "O", - "P": "Ԁ", - "Q": "Q", - "R": "R", - "S": "S", - "T": "┴", - "U": "∩", - "V": "Λ", - "W": "M", - "X": "X", - "Y": "⅄", - "Z": "Z", - "0": "0", - "1": "Ɩ", - "2": "ᄅ", - "3": "Ɛ", - "4": "ㄣ", - "5": "ϛ", - "6": "9", - "7": "ㄥ", - "8": "8", - "9": "6", - ",": "'", - ".": "˙", - "?": "¿", - "!": "¡", - '"': ",,", - "'": ",", - "(": ")", - ")": "(", - "[": "]", - "]": "[", - "{": "}", - "}": "{", - "<": ">", - ">": "<", - "&": "⅋", - "_": "‾", + 'a': 'ɐ', + 'b': 'q', + 'c': 'ɔ', + 'd': 'p', + 'e': 'ǝ', + 'f': 'ɟ', + 'g': 'ƃ', + 'h': 'ɥ', + 'i': 'ᴉ', + 'j': 'ɾ', + 'k': 'ʞ', + 'l': 'l', + 'm': 'ɯ', + 'n': 'u', + 'o': 'o', + 'p': 'd', + 'q': 'b', + 'r': 'ɹ', + 's': 's', + 't': 'ʇ', + 'u': 'n', + 'v': 'ʌ', + 'w': 'ʍ', + 'x': 'x', + 'y': 'ʎ', + 'z': 'z', + 'A': '∀', + 'B': 'B', + 'C': 'Ɔ', + 'D': 'D', + 'E': 'Ǝ', + 'F': 'Ⅎ', + 'G': 'פ', + 'H': 'H', + 'I': 'I', + 'J': 'ſ', + 'K': 'K', + 'L': '˥', + 'M': 'W', + 'N': 'N', + 'O': 'O', + 'P': 'Ԁ', + 'Q': 'Q', + 'R': 'R', + 'S': 'S', + 'T': '┴', + 'U': '∩', + 'V': 'Λ', + 'W': 'M', + 'X': 'X', + 'Y': '⅄', + 'Z': 'Z', + '0': '0', + '1': 'Ɩ', + '2': 'ᄅ', + '3': 'Ɛ', + '4': 'ㄣ', + '5': 'ϛ', + '6': '9', + '7': 'ㄥ', + '8': '8', + '9': '6', + ',': "'", + '.': '˙', + '?': '¿', + '!': '¡', + '"': ',,', + "'": ',', + '(': ')', + ')': '(', + '[': ']', + ']': '[', + '{': '}', + '}': '{', + '<': '>', + '>': '<', + '&': '⅋', + '_': '‾', } -@Client.on_message(filters.command("flip", prefix) & filters.me) +@Client.on_message(filters.command('flip', prefix) & filters.me) async def flip(client: Client, message: Message): - text = " ".join(message.command[1:]) - final_str = "" + text = ' '.join(message.command[1:]) + final_str = '' for char in text: - if char in REPLACEMENT_MAP.keys(): + if char in REPLACEMENT_MAP: new_char = REPLACEMENT_MAP[char] else: new_char = char @@ -103,4 +100,4 @@ async def flip(client: Client, message: Message): await message.edit(text) -modules_help["fliptext"] = {"flip [amount]*": "flip text upside down"} +modules_help['fliptext'] = {'flip [amount]*': 'flip text upside down'} diff --git a/modules/flux.py b/modules/flux.py index fd6c31b..7e44db3 100644 --- a/modules/flux.py +++ b/modules/flux.py @@ -1,52 +1,51 @@ import os -import io import time -import requests -from pyrogram import filters, Client -from pyrogram.types import Message +import requests +from pyrogram import Client, filters +from pyrogram.types import Message from utils.misc import modules_help, prefix from utils.scripts import format_exc, progress def schellwithflux(args): - API_URL = "https://randydev-ryuzaki-api.hf.space/api/v1/akeno/fluxai" - payload = {"user_id": 1191668125, "args": args} # Please don't edit here + API_URL = 'https://randydev-ryuzaki-api.hf.space/api/v1/akeno/fluxai' + payload = {'user_id': 1191668125, 'args': args} # Please don't edit here response = requests.post(API_URL, json=payload) if response.status_code != 200: - print(f"Error status {response.status_code}") + print(f'Error status {response.status_code}') return None return response.content -@Client.on_message(filters.command("fluxai", prefix) & filters.me) +@Client.on_message(filters.command('fluxai', prefix) & filters.me) async def imgfluxai_(client: Client, message: Message): - question = message.text.split(" ", 1)[1] if len(message.command) > 1 else None + question = message.text.split(' ', 1)[1] if len(message.command) > 1 else None if not question: - return await message.reply_text("Please provide a question for Flux.") + return await message.reply_text('Please provide a question for Flux.') try: image_bytes = schellwithflux(question) if image_bytes is None: - return await message.reply_text("Failed to generate an image.") - pro = await message.reply_text("Generating image, please wait...") + return await message.reply_text('Failed to generate an image.') + pro = await message.reply_text('Generating image, please wait...') # Write the image bytes directly to a file - with open("flux_gen.jpg", "wb") as f: + with open('flux_gen.jpg', 'wb') as f: f.write(image_bytes) - ok = await pro.edit_text("Uploading image...") + ok = await pro.edit_text('Uploading image...') await message.reply_photo( - "flux_gen.jpg", + 'flux_gen.jpg', progress=progress, - progress_args=(ok, time.time(), "Uploading image..."), + progress_args=(ok, time.time(), 'Uploading image...'), ) await ok.delete() - if os.path.exists("flux_gen.jpg"): - os.remove("flux_gen.jpg") + if os.path.exists('flux_gen.jpg'): + os.remove('flux_gen.jpg') except Exception as e: await message.edit_text(format_exc(e)) -modules_help["fluxai"] = { - "fluxai [prompt]*": "text to image fluxai", +modules_help['fluxai'] = { + 'fluxai [prompt]*': 'text to image fluxai', } diff --git a/modules/gemini.py b/modules/gemini.py index b564924..5432f6e 100644 --- a/modules/gemini.py +++ b/modules/gemini.py @@ -1,72 +1,62 @@ # This scripts contains use cases for userbots # This is used on my Moon-Userbot: https://github.com/The-MoonTg-project/Moon-Userbot # YOu can check it out for uses example -import os -from pyrogram import Client, filters, enums -from pyrogram.types import Message +from pyrogram import Client, enums, filters from pyrogram.errors import MessageTooLong - -from utils.misc import modules_help, prefix -from utils.scripts import format_exc, import_library +from pyrogram.types import Message from utils.config import gemini_key +from utils.misc import modules_help, prefix from utils.rentry import paste as rentry_paste +from utils.scripts import format_exc, import_library -genai = import_library("google.generativeai", "google-generativeai") +genai = import_library('google.generativeai', 'google-generativeai') genai.configure(api_key=gemini_key) -model = genai.GenerativeModel("gemini-2.0-flash") +model = genai.GenerativeModel('gemini-2.0-flash') -@Client.on_message(filters.command("gemini", prefix) & filters.me) +@Client.on_message(filters.command('gemini', prefix) & filters.me) async def say(client: Client, message: Message): try: - await message.edit_text("Please Wait...") + await message.edit_text('Please Wait...') if len(message.command) > 1: prompt = message.text.split(maxsplit=1)[1] elif message.reply_to_message: prompt = message.reply_to_message.text else: - await message.edit_text( - f"Usage: {prefix}gemini [prompt/reply to message]" - ) + await message.edit_text(f'Usage: {prefix}gemini [prompt/reply to message]') return chat = model.start_chat() response = chat.send_message(prompt) await message.edit_text( - f"**Question:**`{prompt}`\n**Answer:** {response.text}", + f'**Question:**`{prompt}`\n**Answer:** {response.text}', parse_mode=enums.ParseMode.MARKDOWN, ) except MessageTooLong: - await message.edit_text( - "Output is too long... Pasting to rentry..." - ) + await message.edit_text('Output is too long... Pasting to rentry...') try: - rentry_url, edit_code = await rentry_paste( - text=response.text, return_edit=True - ) + rentry_url, edit_code = await rentry_paste(text=response.text, return_edit=True) except RuntimeError: - await message.edit_text( - "Error: Failed to paste to rentry" - ) + await message.edit_text('Error: Failed to paste to rentry') return await client.send_message( - "me", + 'me', f"Here's your edit code for Url: {rentry_url}\nEdit code: {edit_code}", disable_web_page_preview=True, ) await message.edit_text( - f"Output: {rentry_url}\nNote: Edit Code has been sent to your saved messages", + f'Output: {rentry_url}\nNote: Edit Code has been sent to your saved messages', disable_web_page_preview=True, ) except Exception as e: - await message.edit_text(f"An error occurred: {format_exc(e)}") + await message.edit_text(f'An error occurred: {format_exc(e)}') -modules_help["gemini"] = { - "gemini [prompt]*": "Ask questions with Gemini Ai", +modules_help['gemini'] = { + 'gemini [prompt]*': 'Ask questions with Gemini Ai', } diff --git a/modules/google.py b/modules/google.py index c1a811c..3832822 100644 --- a/modules/google.py +++ b/modules/google.py @@ -16,32 +16,27 @@ from pyrogram import Client, filters from pyrogram.types import Message - from utils.misc import modules_help, prefix -@Client.on_message(filters.command(["google", "g"], prefix) & filters.me) +@Client.on_message(filters.command(['google', 'g'], prefix) & filters.me) async def webshot(_, message: Message): - user_request = " ".join(message.command[1:]) + user_request = ' '.join(message.command[1:]) - if user_request == "": + if user_request == '': if message.reply_to_message: reply_user_request = message.reply_to_message.text - request = reply_user_request.replace(" ", "+") - full_request = f"https://lmgtfy.app/?s=g&iie=1&q={request}" + request = reply_user_request.replace(' ', '+') + full_request = f'https://lmgtfy.app/?s=g&iie=1&q={request}' await message.edit( - f"{reply_user_request}", + f'{reply_user_request}', disable_web_page_preview=True, ) else: - request = user_request.replace(" ", "+") - full_request = f"https://lmgtfy.app/?s=g&iie=1&q={request}" - await message.edit( - f"{user_request}", disable_web_page_preview=True - ) + request = user_request.replace(' ', '+') + full_request = f'https://lmgtfy.app/?s=g&iie=1&q={request}' + await message.edit(f'{user_request}', disable_web_page_preview=True) -modules_help["google"] = { - "google [request]": "To teach the interlocutor to use Google. Request isn't required." -} +modules_help['google'] = {'google [request]': "To teach the interlocutor to use Google. Request isn't required."} diff --git a/modules/hearts.py b/modules/hearts.py index 23e9ee0..ab8d95e 100644 --- a/modules/hearts.py +++ b/modules/hearts.py @@ -1,13 +1,13 @@ -import random import asyncio +import random from pyrogram import Client, filters -from pyrogram.types import Message from pyrogram.errors.exceptions.flood_420 import FloodWait +from pyrogram.types import Message from utils.misc import modules_help, prefix -R = "❤️" -W = "🤍" +R = '❤️' +W = '🤍' heart_list = [ W * 9, @@ -20,7 +20,7 @@ heart_list = [ W * 4 + R + W * 4, W * 9, ] -joined_heart = "\n".join(heart_list) +joined_heart = '\n'.join(heart_list) heartlet_len = joined_heart.count(R) @@ -37,7 +37,7 @@ async def _wrap_edit(message: Message, text: str): async def phase1(message: Message): """Big scroll""" - BIG_SCROLL = "🧡💛💚💙💜🖤🤎" + BIG_SCROLL = '🧡💛💚💙💜🖤🤎' await _wrap_edit(message, joined_heart) for heart in BIG_SCROLL: await _wrap_edit(message, joined_heart.replace(R, heart)) @@ -46,9 +46,9 @@ async def phase1(message: Message): async def phase2(message: Message): """Per-heart randomiser""" - ALL = ["❤️"] + list("🧡💛💚💙💜🤎🖤") # don't include white heart + ALL = ['❤️'] + list('🧡💛💚💙💜🤎🖤') # don't include white heart - format_heart = joined_heart.replace(R, "{}") + format_heart = joined_heart.replace(R, '{}') for _ in range(5): heart = format_heart.format(*random.choices(ALL, k=heartlet_len)) await _wrap_edit(message, heart) @@ -69,12 +69,12 @@ async def phase3(message: Message): async def phase4(message: Message): """Matrix shrinking""" for i in range(7, 0, -1): - heart_matrix = "\n".join([R * i] * i) + heart_matrix = '\n'.join([R * i] * i) await _wrap_edit(message, heart_matrix) await asyncio.sleep(SLEEP) -@Client.on_message(filters.command("hearts", prefix) & filters.me) +@Client.on_message(filters.command('hearts', prefix) & filters.me) async def hearts(client: Client, message: Message): await phase1(message) await phase2(message) @@ -82,12 +82,10 @@ async def hearts(client: Client, message: Message): await phase4(message) await asyncio.sleep(SLEEP * 3) - final_caption = " ".join(message.command[1:]) + final_caption = ' '.join(message.command[1:]) if not final_caption: - final_caption = "💕 by @moonuserbot" + final_caption = '💕 by @moonuserbot' await message.edit(final_caption) -modules_help["hearts"] = { - "hearts": "Heart animation. May cause floodwaits, use at your own risk!" -} +modules_help['hearts'] = {'hearts': 'Heart animation. May cause floodwaits, use at your own risk!'} diff --git a/modules/help.py b/modules/help.py index 9a7ccf9..4089299 100644 --- a/modules/help.py +++ b/modules/help.py @@ -13,18 +13,17 @@ from pyrogram import Client, filters from pyrogram.types import Message - from utils.misc import modules_help, prefix -from utils.scripts import format_module_help, with_reply from utils.module import ModuleManager +from utils.scripts import format_module_help, with_reply module_manager = ModuleManager.get_instance() -@Client.on_message(filters.command(["help", "h"], prefix) & filters.me) +@Client.on_message(filters.command(['help', 'h'], prefix) & filters.me) async def help_cmd(_, message: Message): if not module_manager.help_navigator: - await message.edit("Help system is not initialized yet. Please wait...") + await message.edit('Help system is not initialized yet. Please wait...') return if len(message.command) == 1: @@ -41,43 +40,42 @@ async def help_cmd(_, message: Message): cmd_desc = commands[command] module_found = True return await message.edit( - f"Help for command {prefix}{command_name}\n" - f"Module: {module_name} ({prefix}help {module_name})\n\n" - f"{prefix}{cmd[0]}" - f"{' ' + cmd[1] + '' if len(cmd) > 1 else ''}" - f" — {cmd_desc}", + f'Help for command {prefix}{command_name}\n' + f'Module: {module_name} ({prefix}help {module_name})\n\n' + f'{prefix}{cmd[0]}' + f'{" " + cmd[1] + "" if len(cmd) > 1 else ""}' + f' — {cmd_desc}', ) if not module_found: - await message.edit(f"Module or command {command_name} not found") + await message.edit(f'Module or command {command_name} not found') -@Client.on_message(filters.command(["pn", "pp", "pq"], prefix) & filters.me) +@Client.on_message(filters.command(['pn', 'pp', 'pq'], prefix) & filters.me) @with_reply async def handle_navigation(_, message: Message): if not module_manager.help_navigator: - await message.edit("Help system is not initialized yet. Please wait...") + await message.edit('Help system is not initialized yet. Please wait...') return reply_message = message.reply_to_message - if reply_message and "Help Page No:" in message.reply_to_message.text: + if reply_message and 'Help Page No:' in message.reply_to_message.text: cmd = message.command[0].lower() - if cmd == "pn": + if cmd == 'pn': if module_manager.help_navigator.next_page(): await module_manager.help_navigator.send_page(reply_message) return await message.delete() - await message.edit("No more pages available.") - elif cmd == "pp": + await message.edit('No more pages available.') + elif cmd == 'pp': if module_manager.help_navigator.prev_page(): await module_manager.help_navigator.send_page(reply_message) return await message.delete() - return await message.edit("This is the first page.") - elif cmd == "pq": + return await message.edit('This is the first page.') + elif cmd == 'pq': await reply_message.delete() - return await message.edit("Help closed.") + return await message.edit('Help closed.') -modules_help["help"] = { - "help [module/command name]": "Get common/module/command help", - "pn/pp/pq": "Navigate through help pages" - + " (pn: next page, pp: previous page, pq: quit help)", +modules_help['help'] = { + 'help [module/command name]': 'Get common/module/command help', + 'pn/pp/pq': 'Navigate through help pages' + ' (pn: next page, pp: previous page, pq: quit help)', } diff --git a/modules/huggingface.py b/modules/huggingface.py index 3f9427c..e93b071 100644 --- a/modules/huggingface.py +++ b/modules/huggingface.py @@ -1,16 +1,14 @@ -import os -import io -import time -import aiohttp import asyncio +import io import logging -from PIL import Image - -from pyrogram import filters, Client, enums -from pyrogram.types import Message - +import os +import time from concurrent.futures import ThreadPoolExecutor +import aiohttp +from PIL import Image +from pyrogram import Client, enums, filters +from pyrogram.types import Message from utils.db import db from utils.misc import modules_help, prefix from utils.scripts import format_exc @@ -20,47 +18,41 @@ logger = logging.getLogger(__name__) async def query_huggingface(payload): - api_key = db.get("custom.hf", "api_key", None) - model = db.get("custom.hf", "current_model", None) + api_key = db.get('custom.hf', 'api_key', None) + model = db.get('custom.hf', 'current_model', None) if not api_key: - raise ValueError( - f"API key not set. Use {prefix}set_hf api to set it." - ) + raise ValueError(f'API key not set. Use {prefix}set_hf api to set it.') if not model: - raise ValueError( - f"Model not set. Use {prefix}set_hf model to set it." - ) + raise ValueError(f'Model not set. Use {prefix}set_hf model to set it.') - api_url = f"https://api-inference.huggingface.co/models/{model}" - headers = {"Authorization": f"Bearer {api_key}"} + api_url = f'https://api-inference.huggingface.co/models/{model}' + headers = {'Authorization': f'Bearer {api_key}'} timeout = aiohttp.ClientTimeout(total=120) start_time = time.time() retries = 3 for attempt in range(1, retries + 1): try: - async with aiohttp.ClientSession(timeout=timeout) as session: - async with session.post( - api_url, headers=headers, json=payload - ) as response: - fetch_time = int((time.time() - start_time) * 1000) - if response.status != 200: - error_text = await response.text() - logger.error(f"API Error {response.status}: {error_text}") - return None, fetch_time - return await response.read(), fetch_time - except asyncio.TimeoutError: - logger.error(f"TimeoutError: Attempt {attempt}/{retries} timed out.") + async with ( + aiohttp.ClientSession(timeout=timeout) as session, + session.post(api_url, headers=headers, json=payload) as response, + ): + fetch_time = int((time.time() - start_time) * 1000) + if response.status != 200: + error_text = await response.text() + logger.error(f'API Error {response.status}: {error_text}') + return None, fetch_time + return await response.read(), fetch_time + except TimeoutError: + logger.error(f'TimeoutError: Attempt {attempt}/{retries} timed out.') if attempt == retries: raise except asyncio.CancelledError: - logger.error( - "Request was cancelled. Ensure the task is not being forcefully terminated." - ) + logger.error('Request was cancelled. Ensure the task is not being forcefully terminated.') raise except aiohttp.ClientError as e: - logger.error(f"Network Error: {e}") + logger.error(f'Network Error: {e}') if attempt == retries: raise finally: @@ -70,157 +62,129 @@ async def query_huggingface(payload): async def save_image(image_bytes, path): loop = asyncio.get_event_loop() with ThreadPoolExecutor() as pool: - await loop.run_in_executor( - pool, lambda: Image.open(io.BytesIO(image_bytes)).save(path) - ) + await loop.run_in_executor(pool, lambda: Image.open(io.BytesIO(image_bytes)).save(path)) -@Client.on_message(filters.command(["set_hf"], prefix) & filters.me) +@Client.on_message(filters.command(['set_hf'], prefix) & filters.me) async def manage_huggingface(_, message: Message): """Manage Hugging Face API key and models.""" subcommand = message.command[1].lower() if len(message.command) > 1 else None arg = message.command[2] if len(message.command) > 2 else None - if subcommand == "api": + if subcommand == 'api': if arg: - db.set("custom.hf", "api_key", arg) - return await message.edit_text( - f"Hugging Face API key set successfully.\nAPI Key: {arg}" - ) - return await message.edit_text(f"Usage: {prefix}hf api ") + db.set('custom.hf', 'api_key', arg) + return await message.edit_text(f'Hugging Face API key set successfully.\nAPI Key: {arg}') + return await message.edit_text(f'Usage: {prefix}hf api ') - if subcommand == "model": + if subcommand == 'model': if arg: - models = db.get("custom.hf", "models", []) + models = db.get('custom.hf', 'models', []) if arg not in models: models.append(arg) - db.set("custom.hf", "models", models) - db.set("custom.hf", "current_model", arg) - return await message.edit_text( - f"Model '{arg}' added and set as the current model." - ) - return await message.edit_text(f"Usage: {prefix}hf model ") + db.set('custom.hf', 'models', models) + db.set('custom.hf', 'current_model', arg) + return await message.edit_text(f"Model '{arg}' added and set as the current model.") + return await message.edit_text(f'Usage: {prefix}hf model ') - if subcommand == "select": - models = db.get("custom.hf", "models", []) - if arg and arg.lower() == "all": - db.set("custom.hf", "current_model", "all") - model_list = "\n".join([f"*{i + 1}. {m}" for i, m in enumerate(models)]) + if subcommand == 'select': + models = db.get('custom.hf', 'models', []) + if arg and arg.lower() == 'all': + db.set('custom.hf', 'current_model', 'all') + model_list = '\n'.join([f'*{i + 1}. {m}' for i, m in enumerate(models)]) return await message.edit_text( - f"All models selected:\n{model_list}\n\n" - f"Images will be generated from all models." + f'All models selected:\n{model_list}\n\nImages will be generated from all models.' ) if arg: try: index = int(arg) - 1 if 0 <= index < len(models): - db.set("custom.hf", "current_model", models[index]) + db.set('custom.hf', 'current_model', models[index]) return await message.edit_text(f"Model set to '{models[index]}'.") - return await message.edit_text("Invalid model number.") + return await message.edit_text('Invalid model number.') except ValueError: - return await message.edit_text( - "Invalid model number. Use a valid integer." - ) - return await message.edit_text(f"Usage: {prefix}hf select ") + return await message.edit_text('Invalid model number. Use a valid integer.') + return await message.edit_text(f'Usage: {prefix}hf select ') - if subcommand == "delete" and arg: + if subcommand == 'delete' and arg: try: index = int(arg) - 1 - models = db.get("custom.hf", "models", []) + models = db.get('custom.hf', 'models', []) if 0 <= index < len(models): removed_model = models.pop(index) - db.set("custom.hf", "models", models) - if db.get("custom.hf", "current_model") == removed_model: - db.set( - "custom.hf", "current_model", models[0] if models else "None" - ) + db.set('custom.hf', 'models', models) + if db.get('custom.hf', 'current_model') == removed_model: + db.set('custom.hf', 'current_model', models[0] if models else 'None') return await message.edit_text(f"Model '{removed_model}' deleted.") - return await message.edit_text("Invalid model number.") + return await message.edit_text('Invalid model number.') except ValueError: - return await message.edit_text("Invalid model number. Use a valid integer.") + return await message.edit_text('Invalid model number. Use a valid integer.') - api_key = db.get("custom.hf", "api_key", None) - models = db.get("custom.hf", "models", []) - current_model = db.get("custom.hf", "current_model", "Not set") - model_list = "\n".join( - [ - f"{'*' if m == current_model or current_model == 'all' else ''}{i + 1}. {m}" - for i, m in enumerate(models) - ] + api_key = db.get('custom.hf', 'api_key', None) + models = db.get('custom.hf', 'models', []) + current_model = db.get('custom.hf', 'current_model', 'Not set') + model_list = '\n'.join( + [f'{"*" if m == current_model or current_model == "all" else ""}{i + 1}. {m}' for i, m in enumerate(models)] ) settings = ( - f"Hugging Face settings:\n" - f"API Key:\n{api_key if api_key else 'Not set'}\n\n" - f"Available Models:\n{model_list}" + f'Hugging Face settings:\n' + f'API Key:\n{api_key if api_key else "Not set"}\n\n' + f'Available Models:\n{model_list}' ) usage_message = ( - f"{settings}\n\nUsage:\n" - f"{prefix}set_hf api, model, select, delete, select all" + f'{settings}\n\nUsage:\n' + f'{prefix}set_hf api, model, select, delete, select all' ) await message.edit_text(usage_message) -@Client.on_message(filters.command(["hf", "hface", "huggingface"], prefix)) +@Client.on_message(filters.command(['hf', 'hface', 'huggingface'], prefix)) async def imgflux_(_, message: Message): - prompt = message.text.split(" ", 1)[1] if len(message.command) > 1 else None + prompt = message.text.split(' ', 1)[1] if len(message.command) > 1 else None if not prompt: - usage_message = ( - f"Usage: {prefix}{message.command[0]} [custom prompt]" - ) - return await ( - message.edit_text if message.from_user.is_self else message.reply_text - )(usage_message) + usage_message = f'Usage: {prefix}{message.command[0]} [custom prompt]' + return await (message.edit_text if message.from_user.is_self else message.reply_text)(usage_message) - processing_message = await ( - message.edit_text if message.from_user.is_self else message.reply_text - )("Processing...") + processing_message = await (message.edit_text if message.from_user.is_self else message.reply_text)('Processing...') try: - current_model = db.get("custom.hf", "current_model", None) - models = db.get("custom.hf", "models", []) - models_to_use = models if current_model == "all" else [current_model] + current_model = db.get('custom.hf', 'current_model', None) + models = db.get('custom.hf', 'models', []) + models_to_use = models if current_model == 'all' else [current_model] generated_images = [] for model in models_to_use: - db.set("custom.hf", "current_model", model) - payload = {"inputs": prompt} + db.set('custom.hf', 'current_model', model) + payload = {'inputs': prompt} image_bytes, fetch_time = await query_huggingface(payload) if not image_bytes: - logger.warning(f"Failed to fetch image for model: {model}") + logger.warning(f'Failed to fetch image for model: {model}') continue - image_path = f"hf_flux_gen_{model.replace('/', '_')}.jpg" + image_path = f'hf_flux_gen_{model.replace("/", "_")}.jpg' await save_image(image_bytes, image_path) generated_images.append((image_path, model, fetch_time)) if not generated_images: - return await processing_message.edit_text( - "Failed to generate an image for all models." - ) + return await processing_message.edit_text('Failed to generate an image for all models.') for image_path, model_name, fetch_time in generated_images: - caption = ( - f"**Model:**\n> {model_name}\n" - f"**Prompt used:**\n> {prompt}\n\n" - f"**Fetching Time:** {fetch_time} ms" - ) - await message.reply_photo( - image_path, caption=caption, parse_mode=enums.ParseMode.MARKDOWN - ) + caption = f'**Model:**\n> {model_name}\n**Prompt used:**\n> {prompt}\n\n**Fetching Time:** {fetch_time} ms' + await message.reply_photo(image_path, caption=caption, parse_mode=enums.ParseMode.MARKDOWN) os.remove(image_path) except Exception as e: - logger.error(f"Unexpected Error: {e}") + logger.error(f'Unexpected Error: {e}') await processing_message.edit_text(format_exc(e)) finally: await processing_message.delete() -modules_help["huggingface"] = { - "hf [prompt]*": "Generate an AI image using Hugging Face model(s).", - "set_hf *": "Set the Hugging Face API key.", - "set_hf model *": "Add and set a Hugging Face model.", - "set_hf select *": "Select a specific model or all models for use.", - "set_hf delete *": "Delete a model from the list.", +modules_help['huggingface'] = { + 'hf [prompt]*': 'Generate an AI image using Hugging Face model(s).', + 'set_hf *': 'Set the Hugging Face API key.', + 'set_hf model *': 'Add and set a Hugging Face model.', + 'set_hf select *': 'Select a specific model or all models for use.', + 'set_hf delete *': 'Delete a model from the list.', } diff --git a/modules/id.py b/modules/id.py index 5a5d65b..be07263 100644 --- a/modules/id.py +++ b/modules/id.py @@ -16,73 +16,72 @@ from pyrogram import Client, enums, filters from pyrogram.types import Message, MessageOriginHiddenUser - from utils.misc import modules_help, prefix -@Client.on_message(filters.command("id", prefix) & filters.me) +@Client.on_message(filters.command('id', prefix) & filters.me) async def ids(_, message: Message): - text = "\n".join( + text = '\n'.join( [ - f"Chat ID: `{message.chat.id}`", - f"Chat DC ID: `{message.chat.dc_id}`\n", - f"Message ID: `{message.id}`", + f'Chat ID: `{message.chat.id}`', + f'Chat DC ID: `{message.chat.dc_id}`\n', + f'Message ID: `{message.id}`', ( - f"Your ID: `{message.from_user.id}`" + f'Your ID: `{message.from_user.id}`' if message.from_user - else f"Channel/Group ID: `{message.sender_chat.id}`" + else f'Channel/Group ID: `{message.sender_chat.id}`' ), ( - f"Your DC ID: `{message.from_user.dc_id}`" + f'Your DC ID: `{message.from_user.dc_id}`' if message.from_user - else f"Channel/Group ID: `{message.sender_chat.id}`" + else f'Channel/Group ID: `{message.sender_chat.id}`' ), ] ) if rtm := message.reply_to_message: # print(rtm) - text += f"\n\nReplied Message ID: `{rtm.id}`" + text += f'\n\nReplied Message ID: `{rtm.id}`' if user := rtm.from_user: - text = "\n".join( + text = '\n'.join( [ text, - f"Replied User ID: `{user.id}`", - f"Replied User DC ID: `{user.dc_id}`", + f'Replied User ID: `{user.id}`', + f'Replied User DC ID: `{user.dc_id}`', ] ) else: - text = "\n".join( + text = '\n'.join( [ text, - f"Replied Chat ID: `{rtm.sender_chat.id}`", - f"Replied Chat DC ID: `{rtm.sender_chat.dc_id}`", + f'Replied Chat ID: `{rtm.sender_chat.id}`', + f'Replied Chat DC ID: `{rtm.sender_chat.dc_id}`', ] ) if rtm.forward_origin and rtm.forward_origin.date: if isinstance(rtm.forward_origin, MessageOriginHiddenUser): - text = "\n".join( + text = '\n'.join( [ text, - "\nForwarded from a hidden user.", + '\nForwarded from a hidden user.', ] ) elif ffc := rtm.forward_origin.sender_user: - text = "\n".join( + text = '\n'.join( [ text, - f"\nForwarded Message ID: `{getattr(rtm.forward_origin, 'message_id', None)}`", - f"Forwarded from Chat ID: `{ffc.id}`", - f"Forwarded from Chat DC ID: `{ffc.dc_id}`", + f'\nForwarded Message ID: `{getattr(rtm.forward_origin, "message_id", None)}`', + f'Forwarded from Chat ID: `{ffc.id}`', + f'Forwarded from Chat DC ID: `{ffc.dc_id}`', ] ) - await message.edit("**__" + text + "__**", parse_mode=enums.ParseMode.MARKDOWN) + await message.edit('**__' + text + '__**', parse_mode=enums.ParseMode.MARKDOWN) -modules_help["id"] = { - "id": "simply run or reply to message", +modules_help['id'] = { + 'id': 'simply run or reply to message', } diff --git a/modules/joindate.py b/modules/joindate.py index 8a1d2c1..df87dfd 100644 --- a/modules/joindate.py +++ b/modules/joindate.py @@ -1,44 +1,36 @@ -import asyncio -import os +import os from datetime import datetime + from pyrogram import Client, filters -from pyrogram.raw import functions from pyrogram.types import Message from utils.misc import modules_help, prefix -from utils.scripts import format_exc -@Client.on_message(filters.command("joindate", prefix) & filters.me) +@Client.on_message(filters.command('joindate', prefix) & filters.me) async def joindate(client: Client, message: Message): - await message.edit(f"One moment...") - members = [] - cgetmsg = await client.get_messages(message.chat.id, 1) - async for m in client.iter_chat_members(message.chat.id): - members.append( - ( - m.user.first_name, - m.joined_date or cgetmsg.date, - ) - ) + await message.edit('One moment...') + members = [] + cgetmsg = await client.get_messages(message.chat.id, 1) + async for m in client.iter_chat_members(message.chat.id): + members.append( + ( + m.user.first_name, + m.joined_date or cgetmsg.date, + ) + ) - members.sort(key=lambda member: member[1]) + members.sort(key=lambda member: member[1]) - with open("joined_date.txt", "w", encoding="utf8") as f: - f.write("Join Date First Name\n") - for member in members: - f.write( - str(datetime.fromtimestamp(member[1]).strftime("%y-%m-%d %H:%M")) - + f" {member[0]}\n" - ) + with open('joined_date.txt', 'w', encoding='utf8') as f: + f.write('Join Date First Name\n') + for member in members: + f.write(str(datetime.fromtimestamp(member[1]).strftime('%y-%m-%d %H:%M')) + f' {member[0]}\n') - await message.delete() - await client.send_document(message.chat.id, "joined_date.txt") - os.remove("joined_date.txt") + await message.delete() + await client.send_document(message.chat.id, 'joined_date.txt') + os.remove('joined_date.txt') - -modules_help["joindate"] = { - "joindate": "Get a list of all chat members and sort them by the date they joined the group" - +modules_help['joindate'] = { + 'joindate': 'Get a list of all chat members and sort them by the date they joined the group' } - diff --git a/modules/kokodrilo_explodando.py b/modules/kokodrilo_explodando.py index 6c8dae0..7f07a2c 100644 --- a/modules/kokodrilo_explodando.py +++ b/modules/kokodrilo_explodando.py @@ -1,22 +1,23 @@ -from pyrogram import Client, filters -from pyrogram.types import Message - -from utils.misc import modules_help, prefix -import asyncio, random - - -@Client.on_message(filters.command("kokodrilo", prefix) & filters.me) -async def kokodrilo_explodando(_, message: Message): - amount = 1 - if len(message.command) > 1: - amount = int(message.command[1]) - for _ in range(amount): - await message.edit("🐊") - await asyncio.sleep(random.uniform(1, 2.5)) - await message.edit("💥") - await asyncio.sleep(1.8) - - -modules_help["kokodrilo_explodando"] = { - "kokodrilo [number of explosions]": "kOkOdRiLo ExPlOrAdO", -} +import asyncio +import random + +from pyrogram import Client, filters +from pyrogram.types import Message +from utils.misc import modules_help, prefix + + +@Client.on_message(filters.command('kokodrilo', prefix) & filters.me) +async def kokodrilo_explodando(_, message: Message): + amount = 1 + if len(message.command) > 1: + amount = int(message.command[1]) + for _ in range(amount): + await message.edit('🐊') + await asyncio.sleep(random.uniform(1, 2.5)) + await message.edit('💥') + await asyncio.sleep(1.8) + + +modules_help['kokodrilo_explodando'] = { + 'kokodrilo [number of explosions]': 'kOkOdRiLo ExPlOrAdO', +} diff --git a/modules/leave_chat.py b/modules/leave_chat.py index 2fbfde9..aa473d8 100644 --- a/modules/leave_chat.py +++ b/modules/leave_chat.py @@ -18,20 +18,19 @@ import asyncio from pyrogram import Client, filters from pyrogram.types import Message - from utils.misc import modules_help, prefix -@Client.on_message(filters.command(["leave_chat", "lc"], prefix) & filters.me) +@Client.on_message(filters.command(['leave_chat', 'lc'], prefix) & filters.me) async def leave_chat(_, message: Message): - if message.chat.type != "private": - await message.edit("Goodbye...") + if message.chat.type != 'private': + await message.edit('Goodbye...') await asyncio.sleep(3) await message.chat.leave() else: - await message.edit("Not supported in private chats") + await message.edit('Not supported in private chats') -modules_help["leave_chat"] = { - "leave_chat": "Quit chat", +modules_help['leave_chat'] = { + 'leave_chat': 'Quit chat', } diff --git a/modules/loader.py b/modules/loader.py index 46a8042..c5dee70 100644 --- a/modules/loader.py +++ b/modules/loader.py @@ -23,318 +23,296 @@ import sys import requests from pyrogram import Client, filters from pyrogram.types import Message - +from utils.db import db from utils.misc import modules_help, prefix from utils.scripts import restart -from utils.db import db - BASE_PATH = os.path.abspath(os.getcwd()) CATEGORIES = [ - "ai", - "dl", - "admin", - "anime", - "fun", - "images", - "info", - "misc", - "music", - "news", - "paste", - "rev", - "tts", - "utils", + 'ai', + 'dl', + 'admin', + 'anime', + 'fun', + 'images', + 'info', + 'misc', + 'music', + 'news', + 'paste', + 'rev', + 'tts', + 'utils', ] -@Client.on_message(filters.command(["modhash", "mh"], prefix) & filters.me) +@Client.on_message(filters.command(['modhash', 'mh'], prefix) & filters.me) async def get_mod_hash(_, message: Message): if len(message.command) == 1: return url = message.command[1].lower() resp = requests.get(url) if not resp.ok: - await message.edit( - f"Troubleshooting with downloading module {url}" - ) + await message.edit(f'Troubleshooting with downloading module {url}') return await message.edit( - f"Module hash: {hashlib.sha256(resp.content).hexdigest()}\n" - f"Link: {url}\nFile: {url.split('/')[-1]}", + f'Module hash: {hashlib.sha256(resp.content).hexdigest()}\n' + f'Link: {url}\nFile: {url.split("/")[-1]}', ) -@Client.on_message(filters.command(["loadmod", "lm"], prefix) & filters.me) +@Client.on_message(filters.command(['loadmod', 'lm'], prefix) & filters.me) async def loadmod(_, message: Message): if ( not ( message.reply_to_message and message.reply_to_message.document - and message.reply_to_message.document.file_name.endswith(".py") + and message.reply_to_message.document.file_name.endswith('.py') ) and len(message.command) == 1 ): - await message.edit("Specify module to download") + await message.edit('Specify module to download') return if len(message.command) > 1: - await message.edit("Fetching module...") + await message.edit('Fetching module...') url = message.command[1].lower() - if url.startswith( - "https://raw.githubusercontent.com/The-MoonTg-project/custom_modules/main/" - ): - module_name = url.split("/")[-1].split(".")[0] - elif "." not in url: + if url.startswith('https://raw.githubusercontent.com/The-MoonTg-project/custom_modules/main/'): + module_name = url.split('/')[-1].split('.')[0] + elif '.' not in url: module_name = url.lower() try: f = requests.get( - "https://raw.githubusercontent.com/The-MoonTg-project/custom_modules/main/full.txt" + 'https://raw.githubusercontent.com/The-MoonTg-project/custom_modules/main/full.txt' ).text except Exception: - return await message.edit("Failed to fetch custom modules list") - modules_dict = {line.split("/")[-1].split()[0]: line.strip() for line in f.splitlines()} + return await message.edit('Failed to fetch custom modules list') + modules_dict = {line.split('/')[-1].split()[0]: line.strip() for line in f.splitlines()} if module_name in modules_dict: - url = f"https://raw.githubusercontent.com/The-MoonTg-project/custom_modules/main/{modules_dict[module_name]}.py" + url = f'https://raw.githubusercontent.com/The-MoonTg-project/custom_modules/main/{modules_dict[module_name]}.py' else: - await message.edit( - f"Module {module_name} is not found" - ) + await message.edit(f'Module {module_name} is not found') return else: modules_hashes = requests.get( - "https://raw.githubusercontent.com/The-MoonTg-project/custom_modules/main/modules_hashes.txt" + 'https://raw.githubusercontent.com/The-MoonTg-project/custom_modules/main/modules_hashes.txt' ).text resp = requests.get(url) if not resp.ok: await message.edit( - f"Troubleshooting with downloading module {url}", + f'Troubleshooting with downloading module {url}', ) return if hashlib.sha256(resp.content).hexdigest() not in modules_hashes: return await message.edit( - "Only " - "verified modules or from the official " - "" - "custom_modules repository are supported!", + 'Only ' + 'verified modules or from the official ' + '' + 'custom_modules repository are supported!', disable_web_page_preview=True, ) - module_name = url.split("/")[-1].split(".")[0] + module_name = url.split('/')[-1].split('.')[0] resp = requests.get(url) if not resp.ok: - await message.edit(f"Module {module_name} is not found") + await message.edit(f'Module {module_name} is not found') return - if not os.path.exists(f"{BASE_PATH}/modules/custom_modules"): - os.mkdir(f"{BASE_PATH}/modules/custom_modules") + if not os.path.exists(f'{BASE_PATH}/modules/custom_modules'): + os.mkdir(f'{BASE_PATH}/modules/custom_modules') - with open(f"./modules/custom_modules/{module_name}.py", "wb") as f: + with open(f'./modules/custom_modules/{module_name}.py', 'wb') as f: f.write(resp.content) else: file_name = await message.reply_to_message.download() module_name = message.reply_to_message.document.file_name[:-3] - with open(file_name, "rb") as f: + with open(file_name, 'rb') as f: content = f.read() modules_hashes = requests.get( - "https://raw.githubusercontent.com/The-MoonTg-project/custom_modules/main/modules_hashes.txt" + 'https://raw.githubusercontent.com/The-MoonTg-project/custom_modules/main/modules_hashes.txt' ).text if hashlib.sha256(content).hexdigest() not in modules_hashes: os.remove(file_name) return await message.edit( - "Only " - "verified modules or from the official " - "" - "custom_modules repository are supported!", + 'Only ' + 'verified modules or from the official ' + '' + 'custom_modules repository are supported!', disable_web_page_preview=True, ) - os.rename(file_name, f"./modules/custom_modules/{module_name}.py") + os.rename(file_name, f'./modules/custom_modules/{module_name}.py') - all_modules = db.get("custom.modules", "allModules", []) + all_modules = db.get('custom.modules', 'allModules', []) if module_name not in all_modules: all_modules.append(module_name) - db.set("custom.modules", "allModules", all_modules) - await message.edit( - f"The module {module_name} is loaded!\nRestarting..." - ) + db.set('custom.modules', 'allModules', all_modules) + await message.edit(f'The module {module_name} is loaded!\nRestarting...') db.set( - "core.updater", - "restart_info", + 'core.updater', + 'restart_info', { - "type": "restart", - "chat_id": message.chat.id, - "message_id": message.id, + 'type': 'restart', + 'chat_id': message.chat.id, + 'message_id': message.id, }, ) restart() -@Client.on_message(filters.command(["unloadmod", "ulm"], prefix) & filters.me) +@Client.on_message(filters.command(['unloadmod', 'ulm'], prefix) & filters.me) async def unload_mods(_, message: Message): if len(message.command) <= 1: return module_name = message.command[1].lower() - if module_name.startswith( - "https://raw.githubusercontent.com/The-MoonTg-project/custom_modules/main/" - ): - module_name = module_name.split("/")[-1].split(".")[0] + if module_name.startswith('https://raw.githubusercontent.com/The-MoonTg-project/custom_modules/main/'): + module_name = module_name.split('/')[-1].split('.')[0] - if os.path.exists(f"{BASE_PATH}/modules/custom_modules/{module_name}.py"): - os.remove(f"{BASE_PATH}/modules/custom_modules/{module_name}.py") - if module_name == "musicbot": + if os.path.exists(f'{BASE_PATH}/modules/custom_modules/{module_name}.py'): + os.remove(f'{BASE_PATH}/modules/custom_modules/{module_name}.py') + if module_name == 'musicbot': subprocess.run( - [sys.executable, "-m", "pip", "uninstall", "-y", "requirements.txt"], - cwd=f"{BASE_PATH}/musicbot", + [sys.executable, '-m', 'pip', 'uninstall', '-y', 'requirements.txt'], + cwd=f'{BASE_PATH}/musicbot', ) - shutil.rmtree(f"{BASE_PATH}/musicbot") - all_modules = db.get("custom.modules", "allModules", []) + shutil.rmtree(f'{BASE_PATH}/musicbot') + all_modules = db.get('custom.modules', 'allModules', []) if module_name in all_modules: all_modules.remove(module_name) - db.set("custom.modules", "allModules", all_modules) - await message.edit( - f"The module {module_name} removed!\nRestarting..." - ) + db.set('custom.modules', 'allModules', all_modules) + await message.edit(f'The module {module_name} removed!\nRestarting...') db.set( - "core.updater", - "restart_info", + 'core.updater', + 'restart_info', { - "type": "restart", - "chat_id": message.chat.id, - "message_id": message.id, + 'type': 'restart', + 'chat_id': message.chat.id, + 'message_id': message.id, }, ) restart() - elif os.path.exists(f"{BASE_PATH}/modules/{module_name}.py"): - await message.edit( - "It is forbidden to remove built-in modules, it will disrupt the updater" - ) + elif os.path.exists(f'{BASE_PATH}/modules/{module_name}.py'): + await message.edit('It is forbidden to remove built-in modules, it will disrupt the updater') else: - await message.edit(f"Module {module_name} is not found") + await message.edit(f'Module {module_name} is not found') -@Client.on_message(filters.command(["loadallmods", "lmall"], prefix) & filters.me) +@Client.on_message(filters.command(['loadallmods', 'lmall'], prefix) & filters.me) async def load_all_mods(_, message: Message): - await message.edit("Fetching info...") + await message.edit('Fetching info...') - if not os.path.exists(f"{BASE_PATH}/modules/custom_modules"): - os.mkdir(f"{BASE_PATH}/modules/custom_modules") + if not os.path.exists(f'{BASE_PATH}/modules/custom_modules'): + os.mkdir(f'{BASE_PATH}/modules/custom_modules') try: - f = requests.get( - "https://raw.githubusercontent.com/The-MoonTg-project/custom_modules/main/full.txt" - ).text + f = requests.get('https://raw.githubusercontent.com/The-MoonTg-project/custom_modules/main/full.txt').text except Exception: - return await message.edit("Failed to fetch custom modules list") + return await message.edit('Failed to fetch custom modules list') modules_list = f.splitlines() - await message.edit("Loading modules...") + await message.edit('Loading modules...') for module_name in modules_list: - url = f"https://raw.githubusercontent.com/The-MoonTg-project/custom_modules/main/{module_name}.py" + url = f'https://raw.githubusercontent.com/The-MoonTg-project/custom_modules/main/{module_name}.py' resp = requests.get(url) if not resp.ok: continue - with open( - f"./modules/custom_modules/{module_name.split('/')[1]}.py", "wb" - ) as f: + with open(f'./modules/custom_modules/{module_name.split("/")[1]}.py', 'wb') as f: f.write(resp.content) await message.edit( - f"Successfully loaded new modules: {len(modules_list)}\nRestarting...", + f'Successfully loaded new modules: {len(modules_list)}\nRestarting...', ) db.set( - "core.updater", - "restart_info", + 'core.updater', + 'restart_info', { - "type": "restart", - "chat_id": message.chat.id, - "message_id": message.id, + 'type': 'restart', + 'chat_id': message.chat.id, + 'message_id': message.id, }, ) restart() -@Client.on_message(filters.command(["unloadallmods", "ulmall"], prefix) & filters.me) +@Client.on_message(filters.command(['unloadallmods', 'ulmall'], prefix) & filters.me) async def unload_all_mods(_, message: Message): - await message.edit("Fetching info...") + await message.edit('Fetching info...') - if not os.path.exists(f"{BASE_PATH}/modules/custom_modules"): + if not os.path.exists(f'{BASE_PATH}/modules/custom_modules'): return await message.edit("You don't have any modules installed") - shutil.rmtree(f"{BASE_PATH}/modules/custom_modules") - db.set("custom.modules", "allModules", []) - await message.edit("Successfully unloaded all modules!\nRestarting...") + shutil.rmtree(f'{BASE_PATH}/modules/custom_modules') + db.set('custom.modules', 'allModules', []) + await message.edit('Successfully unloaded all modules!\nRestarting...') db.set( - "core.updater", - "restart_info", + 'core.updater', + 'restart_info', { - "type": "restart", - "chat_id": message.chat.id, - "message_id": message.id, + 'type': 'restart', + 'chat_id': message.chat.id, + 'message_id': message.id, }, ) restart() -@Client.on_message(filters.command(["updateallmods"], prefix) & filters.me) +@Client.on_message(filters.command(['updateallmods'], prefix) & filters.me) async def updateallmods(_, message: Message): - await message.edit("Updating modules...") + await message.edit('Updating modules...') - if not os.path.exists(f"{BASE_PATH}/modules/custom_modules"): - os.mkdir(f"{BASE_PATH}/modules/custom_modules") + if not os.path.exists(f'{BASE_PATH}/modules/custom_modules'): + os.mkdir(f'{BASE_PATH}/modules/custom_modules') - modules_installed = list(os.walk("modules/custom_modules"))[0][2] + modules_installed = list(os.walk('modules/custom_modules'))[0][2] if not modules_installed: return await message.edit("You don't have any modules installed") for module_name in modules_installed: - if not module_name.endswith(".py"): + if not module_name.endswith('.py'): continue try: - f = requests.get( - "https://raw.githubusercontent.com/The-MoonTg-project/custom_modules/main/full.txt" - ).text + f = requests.get('https://raw.githubusercontent.com/The-MoonTg-project/custom_modules/main/full.txt').text except Exception: - return await message.edit("Failed to fetch custom modules list") - modules_dict = {line.split("/")[-1].split()[0]: line.strip() for line in f.splitlines()} + return await message.edit('Failed to fetch custom modules list') + modules_dict = {line.split('/')[-1].split()[0]: line.strip() for line in f.splitlines()} if module_name in modules_dict: resp = requests.get( - f"https://raw.githubusercontent.com/The-MoonTg-project/custom_modules/main/{modules_dict[module_name]}.py" + f'https://raw.githubusercontent.com/The-MoonTg-project/custom_modules/main/{modules_dict[module_name]}.py' ) if not resp.ok: modules_installed.remove(module_name) continue - with open(f"./modules/custom_modules/{module_name}", "wb") as f: + with open(f'./modules/custom_modules/{module_name}', 'wb') as f: f.write(resp.content) - await message.edit(f"Successfully updated {len(modules_installed)} modules") + await message.edit(f'Successfully updated {len(modules_installed)} modules') -modules_help["loader"] = { - "loadmod [module_name]*": "Download module.\n" - "Only modules from the official custom_modules repository and proven " - "modules whose hashes are in modules_hashes.txt are supported", - "unloadmod [module_name]*": "Delete module", - "modhash [link]*": "Get module hash by link", - "loadallmods": "Load all custom modules (use it at your own risk)", - "unloadallmods": "Unload all custom modules", - "updateallmods": "Update all custom modules" - "\n\n* - required argument" - "\n short cmds:" - "\n loadmod - lm" - "\n unloadmod - ulm" - "\n modhash - mh" - "\n loadallmods - lmall" - "\n unloadallmods - ulmall", +modules_help['loader'] = { + 'loadmod [module_name]*': 'Download module.\n' + 'Only modules from the official custom_modules repository and proven ' + 'modules whose hashes are in modules_hashes.txt are supported', + 'unloadmod [module_name]*': 'Delete module', + 'modhash [link]*': 'Get module hash by link', + 'loadallmods': 'Load all custom modules (use it at your own risk)', + 'unloadallmods': 'Unload all custom modules', + 'updateallmods': 'Update all custom modules' + '\n\n* - required argument' + '\n short cmds:' + '\n loadmod - lm' + '\n unloadmod - ulm' + '\n modhash - mh' + '\n loadallmods - lmall' + '\n unloadallmods - ulmall', } diff --git a/modules/markitdown.py b/modules/markitdown.py index 10b5243..6061e2a 100644 --- a/modules/markitdown.py +++ b/modules/markitdown.py @@ -15,37 +15,35 @@ #  along with this program.  If not, see . import os + from pyrogram import Client, filters from pyrogram.types import Message - from utils.misc import modules_help -from utils.scripts import prefix, import_library, with_reply +from utils.scripts import import_library, prefix, with_reply -import_library("markitdown") +import_library('markitdown') from markitdown import MarkItDown -@Client.on_message(filters.command(["markitdown", "mkdn"], prefix) & filters.me) +@Client.on_message(filters.command(['markitdown', 'mkdn'], prefix) & filters.me) @with_reply async def markitdown(client: Client, message: Message): if message.reply_to_message.document: - await message.edit("Converting to Markdown...") + await message.edit('Converting to Markdown...') file = await message.reply_to_message.download() - file_name = (message.reply_to_message.document.file_name).split(".")[0] + ".md" + file_name = (message.reply_to_message.document.file_name).split('.')[0] + '.md' markitdown = MarkItDown() result = markitdown.convert(file) - with open(file_name, "w") as f: + with open(file_name, 'w') as f: f.write(result.text_content) - await message.edit("Uploading...") - await client.send_document( - message.chat.id, file_name, reply_to_message_id=message.reply_to_message.id - ) + await message.edit('Uploading...') + await client.send_document(message.chat.id, file_name, reply_to_message_id=message.reply_to_message.id) os.remove(file) os.remove(file_name) await message.delete() else: - await message.edit("Reply to a document to convert it to Markdown.") + await message.edit('Reply to a document to convert it to Markdown.') -modules_help["markitdown"] = {"markitdown": "Convert a document to Markdown."} +modules_help['markitdown'] = {'markitdown': 'Convert a document to Markdown.'} diff --git a/modules/mention.py b/modules/mention.py index 05db582..b1d1719 100644 --- a/modules/mention.py +++ b/modules/mention.py @@ -17,26 +17,23 @@ from pyrogram import Client, filters from pyrogram.types import Message from pyrogram.types.user_and_chats.user import Link - from utils.misc import modules_help, prefix def custom_mention(user, custom_text): return Link( - f"tg://user?id={user.id}", + f'tg://user?id={user.id}', custom_text, user._client.parse_mode, ) -@Client.on_message(filters.command("mention", prefix) & filters.me) +@Client.on_message(filters.command('mention', prefix) & filters.me) async def example_edit(client: Client, message: Message): chat_id = message.chat.id if message.reply_to_message and not len(message.text.split()) > 1: user = message.reply_to_message.from_user - custom_text = ( - message.text.split(maxsplit=1)[1] if len(message.text.split()) > 1 else None - ) + custom_text = message.text.split(maxsplit=1)[1] if len(message.text.split()) > 1 else None if custom_text: await message.edit(custom_mention(user, custom_text)) else: @@ -46,27 +43,23 @@ async def example_edit(client: Client, message: Message): if len(message.text.split()) > 1: user_id = message.text.split()[1] if user_id.isdigit(): - text = ( - message.text.split(maxsplit=2)[2] - if len(message.text.split()) > 2 - else None - ) + text = message.text.split(maxsplit=2)[2] if len(message.text.split()) > 2 else None if text: - men = Link(f"tg://user?id={user_id}", text, client.parse_mode) + men = Link(f'tg://user?id={user_id}', text, client.parse_mode) else: men = (await client.get_users(user_id)).mention await message.edit(men) else: - await message.edit("Invalid user_id") + await message.edit('Invalid user_id') await message.delete() else: - await message.edit("Reply to a message or provide a user_id") + await message.edit('Reply to a message or provide a user_id') await message.delete() -modules_help["mention"] = { - "mention": "Mention the user you replied to.", - "mention [custom_text]": "Mention the user you replied to with custom text.", - "mention [user_id] [custom_text]": "Mention a user by their user_id with custom text.", - "mention [user_id]": "Mention a user by their user_id.", +modules_help['mention'] = { + 'mention': 'Mention the user you replied to.', + 'mention [custom_text]': 'Mention the user you replied to with custom text.', + 'mention [user_id] [custom_text]': 'Mention a user by their user_id with custom text.', + 'mention [user_id]': 'Mention a user by their user_id.', } diff --git a/modules/mirror_flip.py b/modules/mirror_flip.py index c635094..93f27c9 100644 --- a/modules/mirror_flip.py +++ b/modules/mirror_flip.py @@ -1,14 +1,12 @@ # original module https://raw.githubusercontent.com/KeyZenD/modules/master/MirrorFlipV2.py | t.me/the_kzd import os - -from pyrogram import Client, filters, enums +from pyrogram import Client, enums, filters from pyrogram.types import Message - from utils.misc import modules_help, prefix from utils.scripts import import_library -PIL = import_library("PIL", "pillow") +PIL = import_library('PIL', 'pillow') from PIL import Image, ImageOps @@ -19,7 +17,7 @@ async def make(client, message, o): downloads = await client.download_media(reply.photo.file_id) else: downloads = await client.download_media(reply.sticker.file_id) - path = f"{downloads}" + path = f'{downloads}' img = Image.open(path) await message.delete() w, h = img.size @@ -41,21 +39,19 @@ async def make(client, message, o): return await reply.reply_sticker(sticker=path) os.remove(path) - return await message.edit( - "Need to answer the photo/sticker", parse_mode=enums.ParseMode.HTML - ) + return await message.edit('Need to answer the photo/sticker', parse_mode=enums.ParseMode.HTML) -@Client.on_message(filters.command(["ll", "rr", "dd", "uu"], prefix) & filters.me) +@Client.on_message(filters.command(['ll', 'rr', 'dd', 'uu'], prefix) & filters.me) async def mirror_flip(client: Client, message: Message): - await message.edit("Processing...", parse_mode=enums.ParseMode.HTML) - param = {"ll": 1, "rr": 2, "dd": 3, "uu": 4}[message.command[0]] + await message.edit('Processing...', parse_mode=enums.ParseMode.HTML) + param = {'ll': 1, 'rr': 2, 'dd': 3, 'uu': 4}[message.command[0]] await make(client, message, param) -modules_help["mirror_flip"] = { - "ll [reply on photo or sticker]*": "reflects the left side", - "rr [reply on photo or sticker]*": "reflects the right side", - "uu [reply on photo or sticker]*": "reflects the top", - "dd [reply on photo or sticker]*": "reflects the bottom", +modules_help['mirror_flip'] = { + 'll [reply on photo or sticker]*': 'reflects the left side', + 'rr [reply on photo or sticker]*': 'reflects the right side', + 'uu [reply on photo or sticker]*': 'reflects the top', + 'dd [reply on photo or sticker]*': 'reflects the bottom', } diff --git a/modules/misc/autobackup.py b/modules/misc/autobackup.py index bfbc297..9c44111 100644 --- a/modules/misc/autobackup.py +++ b/modules/misc/autobackup.py @@ -1,8 +1,8 @@ -from pyrogram import Client, filters, enums -from pyrogram.types import Message - from os import listdir +from pyrogram import Client, enums, filters +from pyrogram.types import Message + # noinspection PyUnresolvedReferences from utils.misc import modules_help, prefix @@ -10,7 +10,7 @@ from utils.misc import modules_help, prefix from utils.scripts import format_exc -@Client.on_message(filters.command(["lback"], prefix) & filters.me) +@Client.on_message(filters.command(['lback'], prefix) & filters.me) async def backup_database_cmd(_: Client, message: Message): """ Backup the database. @@ -18,42 +18,40 @@ async def backup_database_cmd(_: Client, message: Message): if len(message.command) == 1: await message.edit("[😇] I think you didn't specify the name of the bot.") return - await message.edit_text( - "I'm copying the database...", parse_mode=enums.ParseMode.HTML - ) + await message.edit_text("I'm copying the database...", parse_mode=enums.ParseMode.HTML) try: name = message.command[1].lower() - folders = listdir("/root/") + folders = listdir('/root/') if name not in folders: - await message.edit("[😇] There is no such bot in the root folder.") + await message.edit('[😇] There is no such bot in the root folder.') return - folder = listdir("/root/" + name) + folder = listdir('/root/' + name) for file in folder: - if file.endswith((".db", ".sqlite", ".sqlite3")): + if file.endswith(('.db', '.sqlite', '.sqlite3')): await message.reply_document( - document="/root/" + name + "/" + file, - caption="Bot Database " + name + "", + document='/root/' + name + '/' + file, + caption='Bot Database ' + name + '', parse_mode=enums.ParseMode.HTML, ) return await message.delete() - folder = listdir("/root/" + name + "/assets") + folder = listdir('/root/' + name + '/assets') for file in folder: - if file.endswith((".db", ".sqlite", ".sqlite3")): + if file.endswith(('.db', '.sqlite', '.sqlite3')): await message.reply_document( - document="/root/" + name + "/assets/" + file, - caption="Bot Database " + name + "", + document='/root/' + name + '/assets/' + file, + caption='Bot Database ' + name + '', parse_mode=enums.ParseMode.HTML, ) return await message.delete() - await message.edit("[😇] Database not found.") + await message.edit('[😇] Database not found.') except Exception as ex: await message.edit_text( - "Failed to back up the database!\n\n" f"{format_exc(ex)}", + f'Failed to back up the database!\n\n{format_exc(ex)}', parse_mode=enums.ParseMode.HTML, ) -modules_help["autobackup"] = { - "lback [name]*": "Backup database from folder", - "lbackall": "Backup all databases", +modules_help['autobackup'] = { + 'lback [name]*': 'Backup database from folder', + 'lbackall': 'Backup all databases', } diff --git a/modules/misc/autofwd.py b/modules/misc/autofwd.py index 03f1bc9..070667a 100644 --- a/modules/misc/autofwd.py +++ b/modules/misc/autofwd.py @@ -15,140 +15,129 @@ #  along with this program.  If not, see . from pyrogram import Client, filters -from pyrogram.types import Message from pyrogram.errors import MessageTooLong - -from utils.misc import modules_help, prefix +from pyrogram.types import Message from utils.db import db +from utils.misc import modules_help, prefix def addtrg(channel_id): - channel_ids = db.get("custom.autofwd", "chatto", default=[]) + channel_ids = db.get('custom.autofwd', 'chatto', default=[]) if channel_id not in channel_ids: channel_ids.append(channel_id) - db.set("custom.autofwd", "chatto", channel_ids) + db.set('custom.autofwd', 'chatto', channel_ids) def rmtrg(channel_id): - channel_ids = db.get("custom.autofwd", "chatto", default=[]) + channel_ids = db.get('custom.autofwd', 'chatto', default=[]) if channel_id in channel_ids: channel_ids.remove(channel_id) - db.set("custom.autofwd", "chatto", channel_ids) + db.set('custom.autofwd', 'chatto', channel_ids) def addsrc(channel_id): - channel_ids = db.get("custom.autofwd", "chatsrc", default=[]) + channel_ids = db.get('custom.autofwd', 'chatsrc', default=[]) if channel_id not in channel_ids: channel_ids.append(channel_id) - db.set("custom.autofwd", "chatsrc", channel_ids) + db.set('custom.autofwd', 'chatsrc', channel_ids) def rmsrc(channel_id): - channel_ids = db.get("custom.autofwd", "chatsrc", default=[]) + channel_ids = db.get('custom.autofwd', 'chatsrc', default=[]) if channel_id in channel_ids: channel_ids.remove(channel_id) - db.set("custom.autofwd", "chatsrc", channel_ids) + db.set('custom.autofwd', 'chatsrc', channel_ids) def getfwd_data(): - source_chats = db.get("custom.autofwd", "chatsrc") - target_chats = db.get("custom.autofwd", "chatto") + source_chats = db.get('custom.autofwd', 'chatsrc') + target_chats = db.get('custom.autofwd', 'chatto') return source_chats, target_chats -@Client.on_message(filters.command(["addfwd_src", "addfwd_to"], prefix) & filters.me) +@Client.on_message(filters.command(['addfwd_src', 'addfwd_to'], prefix) & filters.me) async def addfwd(_, message: Message): - if message.command[0] == "addfwd_src": + if message.command[0] == 'addfwd_src': if len(message.command) > 1: channel_id = message.text.split(maxsplit=1)[1] - if not channel_id.startswith("-100"): - channel_id = "-100" + channel_id + if not channel_id.startswith('-100'): + channel_id = '-100' + channel_id # chat id should be integer if not channel_id.isdigit(): try: channel_id = int(channel_id) except Exception: - return await message.edit_text("Chat id should be in integer") + return await message.edit_text('Chat id should be in integer') addsrc(channel_id=channel_id) - await message.edit_text( - f"Auto Forwarding Enabled for Chat with id: {channel_id}" - ) + await message.edit_text(f'Auto Forwarding Enabled for Chat with id: {channel_id}') else: - await message.edit_text("Chat id not provided!") + await message.edit_text('Chat id not provided!') return - elif message.command[0] == "addfwd_to": + elif message.command[0] == 'addfwd_to': if len(message.command) > 1: channel_id = message.text.split(maxsplit=1)[1] - if not channel_id.startswith("-100"): - channel_id = "-100" + channel_id + if not channel_id.startswith('-100'): + channel_id = '-100' + channel_id # chat id should be integer if not channel_id.isdigit(): try: channel_id = int(channel_id) except Exception: - return await message.edit_text("Chat id should be in integer") + return await message.edit_text('Chat id should be in integer') addtrg(channel_id=channel_id) - await message.edit_text( - f"Auto Forwarding Enabled to Chat with id: {channel_id}" - ) + await message.edit_text(f'Auto Forwarding Enabled to Chat with id: {channel_id}') else: - await message.edit_text("Chat id not provided!") + await message.edit_text('Chat id not provided!') return -@Client.on_message(filters.command(["delfwd_src", "delfwd_to"], prefix) & filters.me) +@Client.on_message(filters.command(['delfwd_src', 'delfwd_to'], prefix) & filters.me) async def delfwd(_, message: Message): - if message.command[0] == "delfwd_src": + if message.command[0] == 'delfwd_src': if len(message.command) > 1: channel_id = message.text.split(maxsplit=1)[1] - if not channel_id.startswith("-100"): - channel_id = "-100" + channel_id + if not channel_id.startswith('-100'): + channel_id = '-100' + channel_id # chat id should be integer if not channel_id.isdigit(): try: channel_id = int(channel_id) except Exception: - return await message.edit_text("Chat id should be in integer") + return await message.edit_text('Chat id should be in integer') rmsrc(channel_id=channel_id) - await message.edit_text( - f"Auto Forwarding Disabled for Chat with id: {channel_id}" - ) + await message.edit_text(f'Auto Forwarding Disabled for Chat with id: {channel_id}') else: - await message.edit_text("Chat id not provided!") + await message.edit_text('Chat id not provided!') return - elif message.command[0] == "delfwd_to": + elif message.command[0] == 'delfwd_to': if len(message.command) > 1: channel_id = message.text.split(maxsplit=1)[1] - if not channel_id.startswith("-100"): - channel_id = "-100" + channel_id + if not channel_id.startswith('-100'): + channel_id = '-100' + channel_id # chat id should be integer if not channel_id.isdigit(): try: channel_id = int(channel_id) except Exception: - return await message.edit_text("Chat id should be in integer") + return await message.edit_text('Chat id should be in integer') rmtrg(channel_id=channel_id) - await message.edit_text( - f"Auto Forwarding Disabled to Chat with id: {channel_id}" - ) + await message.edit_text(f'Auto Forwarding Disabled to Chat with id: {channel_id}') else: - await message.edit_text("Chat id not provided!") + await message.edit_text('Chat id not provided!') return -@Client.on_message(filters.command("autofwd", prefix) & filters.me) +@Client.on_message(filters.command('autofwd', prefix) & filters.me) async def autofwd(_, message: Message): source_chats, target_chats = getfwd_data() - return await message.edit_text( - f"Source Chats: {source_chats}\nTarget Chats: {target_chats}" - ) + return await message.edit_text(f'Source Chats: {source_chats}\nTarget Chats: {target_chats}') @Client.on_message(filters.channel | filters.group) async def autofwd_main(client: Client, message: Message): chat_id = message.chat.id - source_chats = db.get("custom.autofwd", "chatsrc") - target_chats = db.get("custom.autofwd", "chatto") + source_chats = db.get('custom.autofwd', 'chatsrc') + target_chats = db.get('custom.autofwd', 'chatto') if source_chats is not None and chat_id in source_chats: if target_chats is not None: @@ -158,20 +147,20 @@ async def autofwd_main(client: Client, message: Message): except Exception as e: try: await client.send_message( - "me", - f"Auto Forwarding Failed for Chat with id: {chat_id} to {chat}\n\n{e}", + 'me', + f'Auto Forwarding Failed for Chat with id: {chat_id} to {chat}\n\n{e}', ) except MessageTooLong: await client.send_message( - "me", - f"Auto Forwarding Failed for Chat with id: {chat_id} to {chat}, Please check logs!", + 'me', + f'Auto Forwarding Failed for Chat with id: {chat_id} to {chat}, Please check logs!', ) -modules_help["autofwd"] = { - "autofwd": "Retrieve Data of auto fwd", - "addfwd_src [channel_id]*": "Enable auto forwarding for a channel", - "addfwd_to [channel_id]*": "Enable auto forwarding to a channel", - "delfwd_src [channel_id]*": "Disable auto forwarding for a channel", - "delfwd_to [channel_id]*": "Disable auto forwarding to a channel", +modules_help['autofwd'] = { + 'autofwd': 'Retrieve Data of auto fwd', + 'addfwd_src [channel_id]*': 'Enable auto forwarding for a channel', + 'addfwd_to [channel_id]*': 'Enable auto forwarding to a channel', + 'delfwd_src [channel_id]*': 'Disable auto forwarding for a channel', + 'delfwd_to [channel_id]*': 'Disable auto forwarding to a channel', } diff --git a/modules/misc/backup.py b/modules/misc/backup.py index 49ffb2f..0b1acde 100644 --- a/modules/misc/backup.py +++ b/modules/misc/backup.py @@ -1,106 +1,95 @@ import os - import shutil -from pyrogram import Client, filters, enums +from pyrogram import Client, enums, filters from pyrogram.types import Message +from utils import config +from utils.db import db # noinspection PyUnresolvedReferences from utils.misc import modules_help, prefix from utils.scripts import format_exc, restart -from utils.db import db -from utils import config -if config.db_type in ["mongodb", "mongo"]: +if config.db_type in ['mongodb', 'mongo']: import bson def dump_mongo(collections, path, db_): for coll in collections: - with open(os.path.join(path, f"{coll}.bson"), "wb+") as f: + with open(os.path.join(path, f'{coll}.bson'), 'wb+') as f: for doc in db_[coll].find(): f.write(bson.BSON.encode(doc)) def restore_mongo(path, db_): for coll in os.listdir(path): - if coll.endswith(".bson"): - with open(os.path.join(path, coll), "rb+") as f: - db_[coll.split(".")[0]].insert_many(bson.decode_all(f.read())) + if coll.endswith('.bson'): + with open(os.path.join(path, coll), 'rb+') as f: + db_[coll.split('.')[0]].insert_many(bson.decode_all(f.read())) -@Client.on_message(filters.command(["backup", "back"], prefix) & filters.me) +@Client.on_message(filters.command(['backup', 'back'], prefix) & filters.me) async def backup(client: Client, message: Message): """ Backup the database """ try: - if not os.path.exists("backups/"): - os.mkdir("backups/") + if not os.path.exists('backups/'): + os.mkdir('backups/') - await message.edit( - "Backing up database...", parse_mode=enums.ParseMode.HTML - ) + await message.edit('Backing up database...', parse_mode=enums.ParseMode.HTML) - if config.db_type in ["mongo", "mongodb"]: - dump_mongo(db._database.list_collection_names(), "backups/", db._database) + if config.db_type in ['mongo', 'mongodb']: + dump_mongo(db._database.list_collection_names(), 'backups/', db._database) return await message.edit( - f"Database backed up to: backups/ folder", + 'Database backed up to: backups/ folder', parse_mode=enums.ParseMode.HTML, ) else: - shutil.copy(config.db_name, f"backups/{config.db_name}") + shutil.copy(config.db_name, f'backups/{config.db_name}') await client.send_document( - "me", - caption=f"Database backup complete!\nType: " - f".restore in response to this message to restore the database.", - document=f"backups/{config.db_name}", + 'me', + caption='Database backup complete!\nType: ' + '.restore in response to this message to restore the database.', + document=f'backups/{config.db_name}', parse_mode=enums.ParseMode.HTML, ) return await message.edit( - "Database backed up successfully! (Check your favorites)", + 'Database backed up successfully! (Check your favorites)', parse_mode=enums.ParseMode.HTML, ) except Exception as e: await message.edit(format_exc(e), parse_mode=enums.ParseMode.HTML) -@Client.on_message(filters.command(["restore", "res"], prefix) & filters.me) +@Client.on_message(filters.command(['restore', 'res'], prefix) & filters.me) async def restore(client: Client, message: Message): """ Restore the database """ try: - await message.edit( - "Restoring database...", parse_mode=enums.ParseMode.HTML - ) - if config.db_type in ["mongo", "mongodb"]: - restore_mongo("backups/", db._database) + await message.edit('Restoring database...', parse_mode=enums.ParseMode.HTML) + if config.db_type in ['mongo', 'mongodb']: + restore_mongo('backups/', db._database) return await message.edit( - f"Database restored from: backups/ folder", + 'Database restored from: backups/ folder', parse_mode=enums.ParseMode.HTML, ) else: if not message.reply_to_message or not message.reply_to_message.document: return await message.edit( - "Reply to a document to restore the database.", + 'Reply to a document to restore the database.', parse_mode=enums.ParseMode.HTML, ) - elif not message.reply_to_message.document.file_name.casefold().endswith( - (".db", ".sqlite", ".sqlite3") - ): + elif not message.reply_to_message.document.file_name.casefold().endswith(('.db', '.sqlite', '.sqlite3')): return await message.edit( - "Reply to a database file to restore the database.", + 'Reply to a database file to restore the database.', parse_mode=enums.ParseMode.HTML, ) - await message.reply_to_message.download( - f"backups/{message.reply_to_message.document.file_name}" - ) - shutil.copy( - f"backups/{message.reply_to_message.document.file_name}", config.db_name - ) + await message.reply_to_message.download(f'backups/{message.reply_to_message.document.file_name}') + shutil.copy(f'backups/{message.reply_to_message.document.file_name}', config.db_name) await message.edit( - "Database restored successfully!", + 'Database restored successfully!', parse_mode=enums.ParseMode.HTML, ) restart() @@ -108,104 +97,96 @@ async def restore(client: Client, message: Message): await message.edit(format_exc(e), parse_mode=enums.ParseMode.HTML) -@Client.on_message(filters.command(["backupmods", "bms"], prefix) & filters.me) +@Client.on_message(filters.command(['backupmods', 'bms'], prefix) & filters.me) async def backupmods(client: Client, message: Message): """ Backup the modules """ try: - if not os.path.exists("backups/"): - os.mkdir("backups/") + if not os.path.exists('backups/'): + os.mkdir('backups/') - await message.edit( - "Backing up modules...", parse_mode=enums.ParseMode.HTML - ) + await message.edit('Backing up modules...', parse_mode=enums.ParseMode.HTML) from utils.misc import modules_help for mod in modules_help: - if os.path.isfile(f"modules/custom_modules/{mod}.py"): - f = open(f"backups/{mod}.py", "wb") - f.write(open(f"modules/custom_modules/{mod}.py", "rb").read()) + if os.path.isfile(f'modules/custom_modules/{mod}.py'): + f = open(f'backups/{mod}.py', 'wb') + f.write(open(f'modules/custom_modules/{mod}.py', 'rb').read()) await message.edit( - text=f"All modules backed up to: backups/ folder", + text='All modules backed up to: backups/ folder', parse_mode=enums.ParseMode.HTML, ) except Exception as e: await message.edit(format_exc(e), parse_mode=enums.ParseMode.HTML) -@Client.on_message(filters.command(["backupmod", "bm"], prefix) & filters.me) +@Client.on_message(filters.command(['backupmod', 'bm'], prefix) & filters.me) async def backupmod(client: Client, message: Message): """ Backup the module """ try: - if not os.path.exists("backups/"): - os.mkdir("backups/") - from utils.misc import modules_help + if not os.path.exists('backups/'): + os.mkdir('backups/') try: - mod = message.text.split(maxsplit=1)[1].split(".")[0] + mod = message.text.split(maxsplit=1)[1].split('.')[0] except: return await message.edit( - f"Usage: {prefix}backupmod [module]", + f'Usage: {prefix}backupmod [module]', parse_mode=enums.ParseMode.HTML, ) - await message.edit( - "Backing up module...", parse_mode=enums.ParseMode.HTML - ) + await message.edit('Backing up module...', parse_mode=enums.ParseMode.HTML) - if os.path.isfile(f"modules/custom_modules/{mod}.py"): - f = open(f"backups/{mod}.py", "wb") - f.write(open(f"modules/custom_modules/{mod}.py", "rb").read()) + if os.path.isfile(f'modules/custom_modules/{mod}.py'): + f = open(f'backups/{mod}.py', 'wb') + f.write(open(f'modules/custom_modules/{mod}.py', 'rb').read()) else: return await message.edit( - f"Module {mod} not found.", + f'Module {mod} not found.', parse_mode=enums.ParseMode.HTML, ) await message.reply_document( - document=f"backups/{mod}.py", - caption=f"Module {mod} backed up to: backups/ folder", + document=f'backups/{mod}.py', + caption=f'Module {mod} backed up to: backups/ folder', parse_mode=enums.ParseMode.HTML, ) except Exception as e: await message.edit(format_exc(e), parse_mode=enums.ParseMode.HTML) -@Client.on_message(filters.command(["restoremod", "resmod"], prefix) & filters.me) +@Client.on_message(filters.command(['restoremod', 'resmod'], prefix) & filters.me) async def restoremod(client: Client, message: Message): """ Restore the module """ try: - if not os.path.exists("backups/"): - os.mkdir("backups/") - from utils.misc import modules_help + if not os.path.exists('backups/'): + os.mkdir('backups/') try: - mod = message.text.split(maxsplit=1)[1].split(".")[0] + mod = message.text.split(maxsplit=1)[1].split('.')[0] except: return await message.edit( - f"Usage: {prefix}restoremod [module]", + f'Usage: {prefix}restoremod [module]', parse_mode=enums.ParseMode.HTML, ) - await message.edit( - "Restoring module...", parse_mode=enums.ParseMode.HTML - ) + await message.edit('Restoring module...', parse_mode=enums.ParseMode.HTML) - if os.path.isfile(f"backups/{mod}.py"): - f = open(f"modules/custom_modules/{mod}.py", "wb") - f.write(open(f"backups/{mod}.py", "rb").read()) + if os.path.isfile(f'backups/{mod}.py'): + f = open(f'modules/custom_modules/{mod}.py', 'wb') + f.write(open(f'backups/{mod}.py', 'rb').read()) else: return await message.edit( - f"Module {mod} not found.", + f'Module {mod} not found.', parse_mode=enums.ParseMode.HTML, ) await message.edit( - f"Module {mod} restored successfully!", + f'Module {mod} restored successfully!', parse_mode=enums.ParseMode.HTML, ) restart() @@ -213,27 +194,25 @@ async def restoremod(client: Client, message: Message): await message.edit(format_exc(e), parse_mode=enums.ParseMode.HTML) -@Client.on_message(filters.command(["restoremods", "resmods"], prefix) & filters.me) +@Client.on_message(filters.command(['restoremods', 'resmods'], prefix) & filters.me) async def restoremods(client: Client, message: Message): """ Restore the modules """ try: - if not os.path.exists("backups/"): - os.mkdir("backups/") + if not os.path.exists('backups/'): + os.mkdir('backups/') - await message.edit( - "Restoring modules...", parse_mode=enums.ParseMode.HTML - ) + await message.edit('Restoring modules...', parse_mode=enums.ParseMode.HTML) - for mod in os.listdir("backups/"): - if mod.endswith(".py"): - if os.path.isfile(f"modules/{mod}"): + for mod in os.listdir('backups/'): + if mod.endswith('.py'): + if os.path.isfile(f'modules/{mod}'): continue - f = open(f"modules/custom_modules/{mod}", "wb") - f.write(open(f"backups/{mod}", "rb").read()) + f = open(f'modules/custom_modules/{mod}', 'wb') + f.write(open(f'backups/{mod}', 'rb').read()) await message.edit( - text=f"All modules restored from: backups/ folder", + text='All modules restored from: backups/ folder', parse_mode=enums.ParseMode.HTML, ) restart() @@ -241,11 +220,11 @@ async def restoremods(client: Client, message: Message): await message.edit(format_exc(e), parse_mode=enums.ParseMode.HTML) -modules_help["backup"] = { - "backup": f"Backup database", - "restore [reply]": f"Restore database", - "backupmod [name]": f"Backup mod", - "backupmods": f"Backup all mods", - "resmod [name]": f"Restore mod", - "resmods": f"Restore all mods", +modules_help['backup'] = { + 'backup': 'Backup database', + 'restore [reply]': 'Restore database', + 'backupmod [name]': 'Backup mod', + 'backupmods': 'Backup all mods', + 'resmod [name]': 'Restore mod', + 'resmods': 'Restore all mods', } diff --git a/modules/misc/cama.py b/modules/misc/cama.py index ed51d42..a61f490 100644 --- a/modules/misc/cama.py +++ b/modules/misc/cama.py @@ -1,109 +1,95 @@ -from pyrogram import Client, filters import requests - +from pyrogram import Client, filters +from utils.misc import modules_help, prefix from utils.scripts import format_exc, import_library -from utils.misc import modules_help, prefix +pcp = import_library('pubchempy') -pcp = import_library("pubchempy") - -INATURALIST_API_URL = "https://api.inaturalist.org/v1/observations" +INATURALIST_API_URL = 'https://api.inaturalist.org/v1/observations' def get_marine_life_details(species_name): params = { - "taxon_name": species_name, - "quality_grade": "research", - "iconic_taxa": "Mollusca,Fish,Crustacea", - "per_page": 1, + 'taxon_name': species_name, + 'quality_grade': 'research', + 'iconic_taxa': 'Mollusca,Fish,Crustacea', + 'per_page': 1, } response = requests.get(INATURALIST_API_URL, params=params) if response.status_code == 200: data = response.json() - if data["total_results"] > 0: - observation = data["results"][0] - species = observation["taxon"]["name"] - common_name = observation["taxon"]["preferred_common_name"] - photo_url = ( - observation["photos"][0]["url"] - if observation["photos"] - else "No photo available" - ) - description = ( - observation["description"] - if "description" in observation - else "No description available." - ) + if data['total_results'] > 0: + observation = data['results'][0] + species = observation['taxon']['name'] + common_name = observation['taxon']['preferred_common_name'] + photo_url = observation['photos'][0]['url'] if observation['photos'] else 'No photo available' + description = observation['description'] if 'description' in observation else 'No description available.' return { - "species": species, - "common_name": common_name, - "photo_url": photo_url, - "description": description, + 'species': species, + 'common_name': common_name, + 'photo_url': photo_url, + 'description': description, } else: - return {"error": "No marine life found for this species."} + return {'error': 'No marine life found for this species.'} else: - return { - "error": f"Error {response.status_code}: Unable to connect to iNaturalist API." - } + return {'error': f'Error {response.status_code}: Unable to connect to iNaturalist API.'} -@Client.on_message(filters.command("camistry", prefix) & filters.me) +@Client.on_message(filters.command('camistry', prefix) & filters.me) async def fetch_chemical_data_with_visual(_, message): - query = " ".join(message.text.split()[1:]) # Combine query words properly + query = ' '.join(message.text.split()[1:]) # Combine query words properly try: # Fetch chemical data by name - results = pcp.get_compounds(query, "name", record_type="3d") + results = pcp.get_compounds(query, 'name', record_type='3d') if results: compound = results[0] # Send chemical information without SMILES structure info = ( - f"Chemical Data for {compound.iupac_name}\n\n" - f"Molecular Formula: {compound.molecular_formula}\n" - f"Molecular Weight: {compound.molecular_weight}\n" - f"CID: {compound.cid}\n" - f"Synonyms: {', '.join(compound.synonyms[:5])}\n" + f'Chemical Data for {compound.iupac_name}\n\n' + f'Molecular Formula: {compound.molecular_formula}\n' + f'Molecular Weight: {compound.molecular_weight}\n' + f'CID: {compound.cid}\n' + f'Synonyms: {", ".join(compound.synonyms[:5])}\n' ) await message.edit_text(info) else: await message.edit_text(f"No chemical data found for the query: '{query}'") except pcp.PubChemHTTPError as http_err: - await message.edit_text(f"HTTP error occurred: {format_exc(http_err)}") + await message.edit_text(f'HTTP error occurred: {format_exc(http_err)}') except Exception as e: - await message.edit_text(f"An error occurred: {format_exc(e)}") + await message.edit_text(f'An error occurred: {format_exc(e)}') -@Client.on_message(filters.command("marinelife", prefix) & filters.me) +@Client.on_message(filters.command('marinelife', prefix) & filters.me) async def marine_life_command(_, message): if len(message.command) < 2: - await message.edit_text( - f"Please specify a species name. Example: {prefix}marinelife dolphin" - ) + await message.edit_text(f'Please specify a species name. Example: {prefix}marinelife dolphin') return - species_name = " ".join(message.command[1:]) + species_name = ' '.join(message.command[1:]) marine_life = get_marine_life_details(species_name) - if "error" in marine_life: - await message.edit_text(marine_life["error"]) + if 'error' in marine_life: + await message.edit_text(marine_life['error']) else: reply_text = ( - f"Species: {marine_life['species']}\n" - f"Common Name: {marine_life['common_name']}\n" - f"Description: {marine_life['description']}\n" + f'Species: {marine_life["species"]}\n' + f'Common Name: {marine_life["common_name"]}\n' + f'Description: {marine_life["description"]}\n' f"Photo" ) await message.edit_text(reply_text) -modules_help["cama"] = { - "camistry [text]": " getting camicale info", - "marinelife [text]": " getting marinelife info", +modules_help['cama'] = { + 'camistry [text]': ' getting camicale info', + 'marinelife [text]': ' getting marinelife info', } diff --git a/modules/misc/mlog.py b/modules/misc/mlog.py index 31e5d0b..e6a4797 100644 --- a/modules/misc/mlog.py +++ b/modules/misc/mlog.py @@ -1,65 +1,60 @@ import asyncio import os +from collections import defaultdict + from pyrogram import Client, filters from pyrogram.errors import ( FileReferenceExpired, FileReferenceInvalid, - TopicDeleted, TopicClosed, + TopicDeleted, ) from pyrogram.types import Message -from collections import defaultdict - from utils.db import db from utils.misc import modules_help, prefix -mlog_enabled = filters.create(lambda _, __, ___: db.get("custom.mlog", "status", False)) +mlog_enabled = filters.create(lambda _, __, ___: db.get('custom.mlog', 'status', False)) # Media cache and processing tasks user_media_cache = defaultdict(list) media_processing_tasks = {} + # Helper to get group-specific data def get_group_data(group_id): - return db.get(f"custom.mlog", str(group_id), {}) + return db.get('custom.mlog', str(group_id), {}) + # Helper to update group-specific data def update_group_data(group_id, data): - db.set(f"custom.mlog", str(group_id), data) + db.set('custom.mlog', str(group_id), data) -@Client.on_message(filters.command(["mlog"], prefix) & filters.me) +@Client.on_message(filters.command(['mlog'], prefix) & filters.me) async def mlog(_, message: Message): - if len(message.command) < 2 or message.command[1].lower() not in ["on", "off"]: - return await message.edit(f"Usage: {prefix}mlog [on/off]") + if len(message.command) < 2 or message.command[1].lower() not in ['on', 'off']: + return await message.edit(f'Usage: {prefix}mlog [on/off]') - status = message.command[1].lower() == "on" - db.set("custom.mlog", "status", status) - await message.edit(f"Media logging is now {'enabled' if status else 'disabled'}") + status = message.command[1].lower() == 'on' + db.set('custom.mlog', 'status', status) + await message.edit(f'Media logging is now {"enabled" if status else "disabled"}') -@Client.on_message(filters.command(["msetchat"], prefix) & filters.me) +@Client.on_message(filters.command(['msetchat'], prefix) & filters.me) async def set_chat(_, message: Message): if len(message.command) < 2: - return await message.edit(f"Usage: {prefix}msetchat [chat_id]") + return await message.edit(f'Usage: {prefix}msetchat [chat_id]') try: chat_id = message.command[1] - chat_id = int("-100" + chat_id if not chat_id.startswith("-100") else chat_id) - db.set("custom.mlog", "chat", chat_id) - await message.edit(f"Chat ID set to {chat_id}") + chat_id = int('-100' + chat_id if not chat_id.startswith('-100') else chat_id) + db.set('custom.mlog', 'chat', chat_id) + await message.edit(f'Chat ID set to {chat_id}') except ValueError: - await message.edit("Invalid chat ID") + await message.edit('Invalid chat ID') -@Client.on_message( - mlog_enabled - & filters.incoming - & filters.private - & filters.media - & ~filters.me - & ~filters.bot -) +@Client.on_message(mlog_enabled & filters.incoming & filters.private & filters.media & ~filters.me & ~filters.bot) async def media_log(client: Client, message: Message): user_id = message.from_user.id user_media_cache[user_id].append(message) @@ -76,26 +71,26 @@ async def process_media(client: Client, user): if user_id == me.id: return - chat_id = db.get("custom.mlog", "chat") + chat_id = db.get('custom.mlog', 'chat') if not chat_id: return await client.send_message( - "me", - f"Media Logger is on, but no Chat ID is set. Use {prefix}msetchat to set it.", + 'me', + f'Media Logger is on, but no Chat ID is set. Use {prefix}msetchat to set it.', ) group_data = get_group_data(chat_id) - user_topics = group_data.get("user_topics", {}) + user_topics = group_data.get('user_topics', {}) topic_id = user_topics.get(str(user_id)) # Fetch user's topic ID if it exists if not topic_id: topic = await client.create_forum_topic(chat_id, user.first_name) topic_id = topic.id user_topics[str(user_id)] = topic_id # Store topic ID for this user - update_group_data(chat_id, {"user_topics": user_topics}) + update_group_data(chat_id, {'user_topics': user_topics}) m = await client.send_message( chat_id=chat_id, message_thread_id=topic_id, - text=f"Chat Name: {user.full_name}\nUser ID: {user_id}\nUsername: @{user.username or 'N/A'}\nPhone No: +{user.phone_number or 'N/A'}", + text=f'Chat Name: {user.full_name}\nUser ID: {user_id}\nUsername: @{user.username or "N/A"}\nPhone No: +{user.phone_number or "N/A"}', ) await m.pin() @@ -111,11 +106,11 @@ async def process_media(client: Client, user): topic = await client.create_forum_topic(chat_id, user.first_name) topic_id = topic.id user_topics[str(user_id)] = topic_id # Update the new topic ID - update_group_data(chat_id, {"user_topics": user_topics}) + update_group_data(chat_id, {'user_topics': user_topics}) await client.send_message( chat_id=chat_id, message_thread_id=topic_id, - text=f"Chat Name: {user.full_name}\nUser ID: {user_id}\nUsername: @{user.username or 'N/A'}\nPhone No: +{user.phone_number or 'N/A'}", + text=f'Chat Name: {user.full_name}\nUser ID: {user_id}\nUsername: @{user.username or "N/A"}\nPhone No: +{user.phone_number or "N/A"}', ) await handle_self_destruct_media(client, media_message, chat_id, topic_id) except TopicClosed: @@ -135,10 +130,10 @@ async def handle_self_destruct_media(client: Client, message: Message, chat_id: await client.send_video(chat_id, file_path, message_thread_id=topic_id) os.remove(file_path) # Clean up after sending except Exception as e: - print(f"Error handling self-destructing media: {e}") + print(f'Error handling self-destructing media: {e}') -modules_help["mlog"] = { - "mlog [on/off]": "Enable or disable media logging", - "msetchat [chat_id]": "Set the chat ID for media logging", +modules_help['mlog'] = { + 'mlog [on/off]': 'Enable or disable media logging', + 'msetchat [chat_id]': 'Set the chat ID for media logging', } diff --git a/modules/misc/prayer.py b/modules/misc/prayer.py index 83219ee..bf2d33c 100644 --- a/modules/misc/prayer.py +++ b/modules/misc/prayer.py @@ -1,46 +1,46 @@ -from pyrogram import Client, filters -from pyrogram.types import Message -import aiohttp from datetime import datetime +import aiohttp +from pyrogram import Client, filters +from pyrogram.types import Message from utils.misc import modules_help, prefix # Aladhan API credentials -ALADHAN_API_URL = "https://api.aladhan.com/v1/timingsByCity" +ALADHAN_API_URL = 'https://api.aladhan.com/v1/timingsByCity' DEFAULT_METHOD = 2 # Islamic Society of North America -DEFAULT_CITY = "Lahore" -DEFAULT_COUNTRY = "PK" +DEFAULT_CITY = 'Lahore' +DEFAULT_COUNTRY = 'PK' async def fetch_namaz_times(city_name: str, country_name: str) -> dict: - params = {"city": city_name, "country": country_name, "method": DEFAULT_METHOD} + params = {'city': city_name, 'country': country_name, 'method': DEFAULT_METHOD} async with aiohttp.ClientSession() as session: try: async with session.get(ALADHAN_API_URL, params=params) as response: response.raise_for_status() return await response.json() except Exception as e: - return {"error": str(e)} + return {'error': str(e)} def format_time_12hr(time_str: str) -> str: try: # Split time into hours and minutes - hours, minutes = map(int, time_str.split(":")) + hours, minutes = map(int, time_str.split(':')) # Convert to 12-hour format - period = "AM" if hours < 12 else "PM" + period = 'AM' if hours < 12 else 'PM' if hours == 0: hours = 12 elif hours > 12: hours -= 12 elif hours == 12: - period = "PM" - return f"{hours}:{minutes:02d} {period}" + period = 'PM' + return f'{hours}:{minutes:02d} {period}' except Exception as e: - return f"Error formatting time: {str(e)}" + return f'Error formatting time: {str(e)}' -@Client.on_message(filters.command("prayer", prefix) & filters.me) +@Client.on_message(filters.command('prayer', prefix) & filters.me) async def namaz_times(client: Client, message: Message): if message.reply_to_message: city_name = message.reply_to_message.text @@ -59,32 +59,32 @@ async def namaz_times(client: Client, message: Message): data = await fetch_namaz_times(city_name, country_name) - if "error" in data: - result = f"Error: {data['error']}" - elif "data" in data: - timings = data["data"]["timings"] - today = datetime.now().strftime("%Y-%m-%d") + if 'error' in data: + result = f'Error: {data["error"]}' + elif 'data' in data: + timings = data['data']['timings'] + today = datetime.now().strftime('%Y-%m-%d') formatted_timings = { - "Fajr": format_time_12hr(timings["Fajr"]), - "Dhuhr": format_time_12hr(timings["Dhuhr"]), - "Asr": format_time_12hr(timings["Asr"]), - "Maghrib": format_time_12hr(timings["Maghrib"]), - "Isha": format_time_12hr(timings["Isha"]), + 'Fajr': format_time_12hr(timings['Fajr']), + 'Dhuhr': format_time_12hr(timings['Dhuhr']), + 'Asr': format_time_12hr(timings['Asr']), + 'Maghrib': format_time_12hr(timings['Maghrib']), + 'Isha': format_time_12hr(timings['Isha']), } result = ( - f"Prayer Times for {city_name}, {country_name} on {today}:\n\n" - f"Fajr: {formatted_timings['Fajr']}\n" - f"Dhuhr: {formatted_timings['Dhuhr']}\n" - f"Asr: {formatted_timings['Asr']}\n" - f"Maghrib: {formatted_timings['Maghrib']}\n" - f"Isha: {formatted_timings['Isha']}\n" + f'Prayer Times for {city_name}, {country_name} on {today}:\n\n' + f'Fajr: {formatted_timings["Fajr"]}\n' + f'Dhuhr: {formatted_timings["Dhuhr"]}\n' + f'Asr: {formatted_timings["Asr"]}\n' + f'Maghrib: {formatted_timings["Maghrib"]}\n' + f'Isha: {formatted_timings["Isha"]}\n' ) else: - result = "Error: Unable to get prayer times for the specified location." + result = 'Error: Unable to get prayer times for the specified location.' await message.edit_text(result) -modules_help["namaz"] = { - "prayer [city_name] [country_name]": "Shows the prayer times. Default to Pakistan if no country is provided" +modules_help['namaz'] = { + 'prayer [city_name] [country_name]': 'Shows the prayer times. Default to Pakistan if no country is provided' } diff --git a/modules/misc/safone.py b/modules/misc/safone.py index f97f2b9..e54c0a2 100644 --- a/modules/misc/safone.py +++ b/modules/misc/safone.py @@ -14,41 +14,40 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import os -import requests -import aiofiles import base64 +import os +import aiofiles +import requests from pyrogram import Client, enums, filters -from pyrogram.types import Message, InputMediaPhoto from pyrogram.errors import MediaCaptionTooLong, MessageTooLong - -from utils.misc import prefix, modules_help +from pyrogram.types import InputMediaPhoto, Message +from utils.misc import modules_help, prefix from utils.scripts import format_exc -url = "https://api.safone.co" +url = 'https://api.safone.co' headers = { - "Accept-Language": "en-US,en;q=0.9", - "Connection": "keep-alive", - "DNT": "1", - "Referer": "https://api.safone.dev/docs", - "Sec-Fetch-Dest": "empty", - "Sec-Fetch-Mode": "cors", - "Sec-Fetch-Site": "same-origin", - "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36", - "accept": "application/json", - "sec-ch-ua": '"Chromium";v="124", "Google Chrome";v="124", "Not-A.Brand";v="99"', - "sec-ch-ua-mobile": "?0", - "sec-ch-ua-platform": '"Linux"', + 'Accept-Language': 'en-US,en;q=0.9', + 'Connection': 'keep-alive', + 'DNT': '1', + 'Referer': 'https://api.safone.dev/docs', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'same-origin', + 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + 'accept': 'application/json', + 'sec-ch-ua': '"Chromium";v="124", "Google Chrome";v="124", "Not-A.Brand";v="99"', + 'sec-ch-ua-mobile': '?0', + 'sec-ch-ua-platform': '"Linux"', } -async def make_carbon(code): - url = "https://carbonara.solopov.dev/api/cook" - async with aiohttp.ClientSession() as session: - async with session.post(url, json={"code": code}) as resp: - image_data = await resp.read() +async def make_carbon(code): + url = 'https://carbonara.solopov.dev/api/cook' + + async with aiohttp.ClientSession() as session, session.post(url, json={'code': code}) as resp: + image_data = await resp.read() carbon_image = Image.open(BytesIO(image_data)) @@ -56,148 +55,139 @@ async def make_carbon(code): bright_image = enhancer.enhance(1.0) output_image = BytesIO() - bright_image.save(output_image, format="PNG", quality=95) - output_image.name = "carbon.png" + bright_image.save(output_image, format='PNG', quality=95) + output_image.name = 'carbon.png' return output_image + async def telegraph(title, user_name, content): - formatted_content = "
".join(content.split("\n")) - formatted_content = "

" + formatted_content + "

" + formatted_content = '
'.join(content.split('\n')) + formatted_content = '

' + formatted_content + '

' - data = {"title": title, "content": formatted_content, "author_name": user_name} + data = {'title': title, 'content': formatted_content, 'author_name': user_name} - response = requests.post( - url=f"{url}/telegraph/text", headers=headers, json=data, timeout=5 - ) + response = requests.post(url=f'{url}/telegraph/text', headers=headers, json=data, timeout=5) result = response.json() - return result["url"] + return result['url'] async def voice_characters(): - response = requests.get(url=f"{url}/speech/characters", headers=headers, timeout=5) + response = requests.get(url=f'{url}/speech/characters', headers=headers, timeout=5) result = response.json() - return ", ".join(result["characters"]) + return ', '.join(result['characters']) async def make_rayso(code: str, title: str, theme: str): data = { - "code": code, - "title": title, - "theme": theme, - "padding": 64, - "language": "auto", - "darkMode": False, + 'code': code, + 'title': title, + 'theme': theme, + 'padding': 64, + 'language': 'auto', + 'darkMode': False, } - response = requests.post(f"{url}/rayso", data=data, headers=headers) + response = requests.post(f'{url}/rayso', data=data, headers=headers) if response.status_code != 200: return None result = response.json() try: - if result["error"] is not None: + if result['error'] is not None: return None except KeyError: pass - image_data = result["image"] - file_name = "rayso.png" - with open(file_name, "wb") as f: + image_data = result['image'] + file_name = 'rayso.png' + with open(file_name, 'wb') as f: f.write(base64.b64decode(image_data)) return file_name -@Client.on_message(filters.command("asq", prefix) & filters.me) +@Client.on_message(filters.command('asq', prefix) & filters.me) async def asq(_, message: Message): if len(message.command) > 1: query = message.text.split(maxsplit=1)[1] else: - await message.edit_text("Query not provided!") + await message.edit_text('Query not provided!') return - await message.edit_text("Processing...") - response = requests.get(url=f"{url}/asq?query={query}", headers=headers, timeout=5) + await message.edit_text('Processing...') + response = requests.get(url=f'{url}/asq?query={query}', headers=headers, timeout=5) if response.status_code != 200: - await message.edit_text("Something went wrong!") + await message.edit_text('Something went wrong!') return result = response.json() - ans = result["answer"] - await message.edit_text( - f"Q. {query}\n A. {ans}", parse_mode=enums.ParseMode.MARKDOWN - ) + ans = result['answer'] + await message.edit_text(f'Q. {query}\n A. {ans}', parse_mode=enums.ParseMode.MARKDOWN) -@Client.on_message(filters.command("sgemini", prefix) & filters.me) +@Client.on_message(filters.command('sgemini', prefix) & filters.me) async def sgemini(_, message: Message): if len(message.command) > 1: prompt = message.text.split(maxsplit=1)[1] else: - await message.edit_text("prompt not provided!") + await message.edit_text('prompt not provided!') return - await message.edit_text("Processing...") - response = requests.get(url=f"{url}/bard?query={prompt}", headers=headers) + await message.edit_text('Processing...') + response = requests.get(url=f'{url}/bard?query={prompt}', headers=headers) if response.status_code != 200: - await message.edit_text("Something went wrong!") + await message.edit_text('Something went wrong!') return result = response.json() - ans = result["message"] - await message.edit_text( - f"Prompt: {prompt}\n Ans: {ans}", parse_mode=enums.ParseMode.MARKDOWN - ) + ans = result['message'] + await message.edit_text(f'Prompt: {prompt}\n Ans: {ans}', parse_mode=enums.ParseMode.MARKDOWN) -@Client.on_message(filters.command("app", prefix) & filters.me) +@Client.on_message(filters.command('app', prefix) & filters.me) async def app(client: Client, message: Message): try: chat_id = message.chat.id - await message.edit_text("Processing...") + await message.edit_text('Processing...') if len(message.command) > 1: query = message.text.split(maxsplit=1)[1] else: - message.edit_text( - "What should i search? You didn't provided me with any value to search" - ) + message.edit_text("What should i search? You didn't provided me with any value to search") - response = requests.get( - url=f"{url}/apps?query={query}&limit=1", headers=headers, timeout=5 - ) + response = requests.get(url=f'{url}/apps?query={query}&limit=1', headers=headers, timeout=5) if response.status_code != 200: - await message.edit_text("Something went wrong") + await message.edit_text('Something went wrong') return result = response.json() try: - coverImage_url = result["results"][0]["icon"] + coverImage_url = result['results'][0]['icon'] coverImage = requests.get(url=coverImage_url).content - async with aiofiles.open("coverImage.jpg", mode="wb") as f: + async with aiofiles.open('coverImage.jpg', mode='wb') as f: await f.write(coverImage) except Exception: coverImage = None - description = result["results"][0]["description"] - developer = result["results"][0]["developer"] - IsFree = result["results"][0]["free"] - genre = result["results"][0]["genre"] - package_name = result["results"][0]["id"] - title = result["results"][0]["title"] - price = result["results"][0]["price"] - link = result["results"][0]["link"] - rating = result["results"][0]["rating"] + description = result['results'][0]['description'] + developer = result['results'][0]['developer'] + IsFree = result['results'][0]['free'] + genre = result['results'][0]['genre'] + package_name = result['results'][0]['id'] + title = result['results'][0]['title'] + price = result['results'][0]['price'] + link = result['results'][0]['link'] + rating = result['results'][0]['rating'] await message.delete() await client.send_media_group( chat_id, [ InputMediaPhoto( - "coverImage.jpg", - caption=f"Title: {title}\nRating: {rating}\nIsFree: {IsFree}\nPrice: {price}\nPackage Name: {package_name}\nGenres: {genre}\nDeveloper: {developer}\nDescription: {description}\nLink: {link}", + 'coverImage.jpg', + caption=f'Title: {title}\nRating: {rating}\nIsFree: {IsFree}\nPrice: {price}\nPackage Name: {package_name}\nGenres: {genre}\nDeveloper: {developer}\nDescription: {description}\nLink: {link}', ) ], ) @@ -209,77 +199,71 @@ async def app(client: Client, message: Message): chat_id, [ InputMediaPhoto( - "coverImage.jpg", - caption=f"Title: {title}\nRating: {rating}\nIsFree: {IsFree}\nPrice: {price}\nPackage Name: {package_name}\nGenres: {genre}\nDeveloper: {developer}\nDescription: {description}\nLink: {link}", + 'coverImage.jpg', + caption=f'Title: {title}\nRating: {rating}\nIsFree: {IsFree}\nPrice: {price}\nPackage Name: {package_name}\nGenres: {genre}\nDeveloper: {developer}\nDescription: {description}\nLink: {link}', ) ], ) except Exception as e: await message.edit_text(format_exc(e)) finally: - if os.path.exists("coverImage.jpg"): - os.remove("coverImage.jpg") + if os.path.exists('coverImage.jpg'): + os.remove('coverImage.jpg') -@Client.on_message(filters.command("tsearch", prefix) & filters.me) +@Client.on_message(filters.command('tsearch', prefix) & filters.me) async def tsearch(client: Client, message: Message): try: chat_id = message.chat.id limit = 10 - await message.edit_text("Processing...") + await message.edit_text('Processing...') if len(message.command) > 1: query = message.text.split(maxsplit=1)[1] else: - message.edit_text( - "What should i search? You didn't provided me with any value to search" - ) + message.edit_text("What should i search? You didn't provided me with any value to search") - response = requests.get( - url=f"{url}/torrent?query={query}&limit={limit}", headers=headers - ) + response = requests.get(url=f'{url}/torrent?query={query}&limit={limit}', headers=headers) if response.status_code != 200: - await message.edit_text("Something went wrong") + await message.edit_text('Something went wrong') return result = response.json() - coverImage_url = result["results"][0]["thumbnail"] - description = result["results"][0]["description"] - genre = result["results"][0]["genre"] - category = result["results"][0]["category"] - title = result["results"][0]["name"] - link = result["results"][0]["magnetLink"] - link_result = await telegraph( - title=title, user_name=message.from_user.first_name, content=link - ) - language = result["results"][0]["language"] - size = result["results"][0]["size"] + coverImage_url = result['results'][0]['thumbnail'] + description = result['results'][0]['description'] + genre = result['results'][0]['genre'] + category = result['results'][0]['category'] + title = result['results'][0]['name'] + link = result['results'][0]['magnetLink'] + link_result = await telegraph(title=title, user_name=message.from_user.first_name, content=link) + language = result['results'][0]['language'] + size = result['results'][0]['size'] results = [] - for i in range(min(limit, len(result["results"]))): - descriptions = result["results"][i]["description"] - genres = result["results"][i]["genre"] - categorys = result["results"][i]["category"] - titles = result["results"][i]["name"] - links = result["results"][i]["magnetLink"] - languages = result["results"][i]["language"] - sizes = result["results"][i]["size"] + for i in range(min(limit, len(result['results']))): + descriptions = result['results'][i]['description'] + genres = result['results'][i]['genre'] + categorys = result['results'][i]['category'] + titles = result['results'][i]['name'] + links = result['results'][i]['magnetLink'] + languages = result['results'][i]['language'] + sizes = result['results'][i]['size'] - r = f"Title: {titles}\nCategory: {categorys}\nLanguage: {languages}\nSize: {sizes}\nGenres: {genres}\nDescription: {descriptions}\nMagnet Link: {links}
" + r = f'Title: {titles}\nCategory: {categorys}\nLanguage: {languages}\nSize: {sizes}\nGenres: {genres}\nDescription: {descriptions}\nMagnet Link: {links}
' results.append(r) - all_results_content = "
".join(results) + all_results_content = '
'.join(results) link_results = await telegraph( - title="Search Results", + title='Search Results', user_name=message.from_user.first_name, content=all_results_content, ) if coverImage_url is not None: coverImage = requests.get(url=coverImage_url).content - async with aiofiles.open("coverImage.jpg", mode="wb") as f: + async with aiofiles.open('coverImage.jpg', mode='wb') as f: await f.write(coverImage) await message.delete() @@ -287,7 +271,7 @@ async def tsearch(client: Client, message: Message): chat_id, [ InputMediaPhoto( - "coverImage.jpg", + 'coverImage.jpg', caption=f"Title: {title}\nCategory: {category}\nLanguage: {language}\nSize: {size}\nGenres: {genre}\nDescription: {description}\nMagnet Link: Click Here\nMore Results: Click Here", ) ], @@ -305,7 +289,7 @@ async def tsearch(client: Client, message: Message): chat_id, [ InputMediaPhoto( - "coverImage.jpg", + 'coverImage.jpg', caption=f"Title: {title}\nCategory: {category}\nLanguage: {language}\nSize: {size}\nGenres: {genre}\nDescription: {description}\nMagnet Link: Click Here", ) ], @@ -321,20 +305,20 @@ async def tsearch(client: Client, message: Message): except Exception as e: await message.edit_text(format_exc(e)) finally: - if os.path.exists("coverImage.jpg"): - os.remove("coverImage.jpg") + if os.path.exists('coverImage.jpg'): + os.remove('coverImage.jpg') -@Client.on_message(filters.command("stts", prefix) & filters.me) +@Client.on_message(filters.command('stts', prefix) & filters.me) async def tts(client: Client, message: Message): characters = await voice_characters() - await message.edit_text("Please Wait...") + await message.edit_text('Please Wait...') try: if len(message.command) > 2: character, prompt = message.text.split(maxsplit=2)[1:] if character not in characters: await message.edit_text( - f"Usage: {prefix}tts [character]* [text/reply to text]*\n Available Characters:
{characters}
" + f'Usage: {prefix}tts [character]* [text/reply to text]*\n Available Characters:
{characters}
' ) return @@ -344,52 +328,50 @@ async def tts(client: Client, message: Message): prompt = message.reply_to_message.text else: await message.edit_text( - f"Usage: {prefix}tts [character]* [text/reply to text]*\n Available Characters:
{characters}
" + f'Usage: {prefix}tts [character]* [text/reply to text]*\n Available Characters:
{characters}
' ) return else: await message.edit_text( - f"Usage: {prefix}tts [character]* [text/reply to text]*\n Available Characters:
{characters}
" + f'Usage: {prefix}tts [character]* [text/reply to text]*\n Available Characters:
{characters}
' ) return - data = {"text": prompt, "character": character} - response = requests.post(url=f"{url}/speech", headers=headers, json=data) + data = {'text': prompt, 'character': character} + response = requests.post(url=f'{url}/speech', headers=headers, json=data) if response.status_code != 200: - await message.edit_text("Something went wrong") + await message.edit_text('Something went wrong') return result = response.json() - audio_data = result["audio"] + audio_data = result['audio'] audio_data = base64.b64decode(audio_data) - async with aiofiles.open(f"{prompt}.mp3", mode="wb") as f: + async with aiofiles.open(f'{prompt}.mp3', mode='wb') as f: await f.write(audio_data) await message.delete() await client.send_audio( chat_id=message.chat.id, - audio=f"{prompt}.mp3", - caption=f"Characters: {character}\nPrompt: {prompt}", + audio=f'{prompt}.mp3', + caption=f'Characters: {character}\nPrompt: {prompt}', ) - if os.path.exists(f"{prompt}.mp3"): - os.remove(f"{prompt}.mp3") + if os.path.exists(f'{prompt}.mp3'): + os.remove(f'{prompt}.mp3') except KeyError: try: - error = result["error"] + error = result['error'] await message.edit_text(error) except KeyError: await message.edit_text( - f"Usage: {prefix}tts [character]* [text/reply to text]*\n Available Characters:
{characters}
" + f'Usage: {prefix}tts [character]* [text/reply to text]*\n Available Characters:
{characters}
' ) except Exception as e: await message.edit_text(format_exc(e)) -@Client.on_message( - filters.command(["carbonnowsh", "carboon", "carbon", "cboon"], prefix) & filters.me -) +@Client.on_message(filters.command(['carbonnowsh', 'carboon', 'carbon', 'cboon'], prefix) & filters.me) async def carbon(client: Client, message: Message): if message.reply_to_message: text = message.reply_to_message.text @@ -398,9 +380,9 @@ async def carbon(client: Client, message: Message): message_id = None text = message.text.split(maxsplit=1)[1] else: - await message.edit_text("Query not provided!") + await message.edit_text('Query not provided!') return - await message.edit_text("Processing...") + await message.edit_text('Processing...') image_file = await make_carbon(text) @@ -409,7 +391,7 @@ async def carbon(client: Client, message: Message): await client.send_photo( chat_id=message.chat.id, photo=image_file, - caption=f"Text: {text}", + caption=f'Text: {text}', reply_to_message_id=message_id, ) except MediaCaptionTooLong: @@ -417,62 +399,62 @@ async def carbon(client: Client, message: Message): await client.send_photo( chat_id=message.chat.id, photo=image_file, - caption=f"Text: {cap}", + caption=f'Text: {cap}', reply_to_message_id=message_id, ) except Exception as e: await message.edit_text(format_exc(e)) - if os.path.exists("carbon.png"): - os.remove("carbon.png") + if os.path.exists('carbon.png'): + os.remove('carbon.png') -@Client.on_message(filters.command("ccgen", prefix) & filters.me) +@Client.on_message(filters.command('ccgen', prefix) & filters.me) async def ccgen(_, message: Message): if len(message.command) > 1: bins = message.text.split(maxsplit=1)[1] else: - await message.edit_text("Code not provided!") + await message.edit_text('Code not provided!') return - await message.edit_text("Processing...") - response = requests.get(url=f"{url}/ccgen?bins={bins}", headers=headers) + await message.edit_text('Processing...') + response = requests.get(url=f'{url}/ccgen?bins={bins}', headers=headers) if response.status_code != 200: - await message.edit_text("Something went wrong") + await message.edit_text('Something went wrong') return result = response.json() - cards = result["results"][0]["cards"] - cards_str = "\n".join([f'"{card}"' for card in cards]) - bins = result["results"][0]["bin"] + cards = result['results'][0]['cards'] + cards_str = '\n'.join([f'"{card}"' for card in cards]) + bins = result['results'][0]['bin'] await message.edit_text( - f"Bins: {bins}\nTotal: {len(cards)}\nCards: \n{cards_str}" + f'Bins: {bins}\nTotal: {len(cards)}\nCards: \n{cards_str}' ) -@Client.on_message(filters.command("rayso", prefix) & filters.me) +@Client.on_message(filters.command('rayso', prefix) & filters.me) async def rayso(client: Client, message: Message): - title = "Untitled" + title = 'Untitled' themes = [ - "vercel", - "supabase", - "tailwind", - "clerk", - "mintlify", - "prisma", - "bitmap", - "noir", - "ice", - "sand", - "forest", - "mono", - "breeze", - "candy", - "crimson", - "falcon", - "meadow", - "midnight", - "raindrop", - "sunset", + 'vercel', + 'supabase', + 'tailwind', + 'clerk', + 'mintlify', + 'prisma', + 'bitmap', + 'noir', + 'ice', + 'sand', + 'forest', + 'mono', + 'breeze', + 'candy', + 'crimson', + 'falcon', + 'meadow', + 'midnight', + 'raindrop', + 'sunset', ] if message.reply_to_message: text = message.reply_to_message.text @@ -481,29 +463,29 @@ async def rayso(client: Client, message: Message): title = message.text.split(maxsplit=2)[1] theme = message.text.split(maxsplit=2)[2].lower() if theme not in themes: - theme = "breeze" + theme = 'breeze' elif len(message.command) > 1: message_id = message.id title = message.text.split(maxsplit=3)[1] theme = message.text.split(maxsplit=3)[2] if theme not in themes: - theme = "breeze" + theme = 'breeze' text = message.text.split(maxsplit=3)[3] else: - await message.edit_text("Query not provided!") + await message.edit_text('Query not provided!') return - await message.edit_text("Processing...") + await message.edit_text('Processing...') image_file = await make_rayso(text, title, theme) if image_file is None: - await message.edit_text("Something went wrong") + await message.edit_text('Something went wrong') return try: await client.send_photo( chat_id=message.chat.id, photo=image_file, - caption=f"Text: {text}", + caption=f'Text: {text}', reply_to_message_id=message_id, ) await message.delete() @@ -512,7 +494,7 @@ async def rayso(client: Client, message: Message): await client.send_photo( chat_id=message.chat.id, photo=image_file, - caption=f"Text: {cap}", + caption=f'Text: {cap}', reply_to_message_id=message_id, ) await message.delete() @@ -522,13 +504,13 @@ async def rayso(client: Client, message: Message): os.remove(image_file) -modules_help["safone"] = { - "asq [query]*": "Asq", - "app [query]*": "Search for an app on Play Store", - "tsearch [query]*": "Search Torrent", - "tts [character]* [text/reply to text]*": "Convert Text to Speech", - "sgemini [prompt]*": "Gemini Model through safone api", - "carbon [code/file/reply]": "Create beautiful image with your code", - "ccgen [bins]*": "Generate credit cards", - "rayso [title]* [theme]* [text/reply to text]*": "Create beautiful image with your text", +modules_help['safone'] = { + 'asq [query]*': 'Asq', + 'app [query]*': 'Search for an app on Play Store', + 'tsearch [query]*': 'Search Torrent', + 'tts [character]* [text/reply to text]*': 'Convert Text to Speech', + 'sgemini [prompt]*': 'Gemini Model through safone api', + 'carbon [code/file/reply]': 'Create beautiful image with your code', + 'ccgen [bins]*': 'Generate credit cards', + 'rayso [title]* [theme]* [text/reply to text]*': 'Create beautiful image with your text', } diff --git a/modules/misc/sarethai.py b/modules/misc/sarethai.py index e7975e0..275a2ae 100644 --- a/modules/misc/sarethai.py +++ b/modules/misc/sarethai.py @@ -14,33 +14,30 @@ #  You should have received a copy of the GNU General Public License #  along with this program.  If not, see . -import os -import requests import asyncio +import os + +import requests +from modules.url import generate_screenshot from pyrogram import Client, enums, filters from pyrogram.types import Message - from utils.misc import modules_help, prefix -from modules.url import generate_screenshot - -from pyrogram import filters - -from utils.scripts import with_reply, no_prefix +from utils.scripts import no_prefix np = no_prefix(prefix) # API URLs -BASE_URL = "https://deliriussapi-oficial.vercel.app" -URL = f"{BASE_URL}/ia" -GOOGLE_SEARCH_URL = f"{BASE_URL}/search/googlesearch?query=" -YOUTUBE_SEARCH_URL = f"{BASE_URL}/search/ytsearch?q=" -MOVIE_SEARCH_URL = f"{BASE_URL}/search/movie?query=" -APK_SEARCH_URL = f"https://bk9.fun/search/apkfab?q=" -APK_DOWNLOAD_URL = "https://bk9.fun/download/apkfab?url=" +BASE_URL = 'https://deliriussapi-oficial.vercel.app' +URL = f'{BASE_URL}/ia' +GOOGLE_SEARCH_URL = f'{BASE_URL}/search/googlesearch?query=' +YOUTUBE_SEARCH_URL = f'{BASE_URL}/search/ytsearch?q=' +MOVIE_SEARCH_URL = f'{BASE_URL}/search/movie?query=' +APK_SEARCH_URL = 'https://bk9.fun/search/apkfab?q=' +APK_DOWNLOAD_URL = 'https://bk9.fun/download/apkfab?url=' def clean_data(data): - parts = data.split("$@$") + parts = data.split('$@$') if len(parts) > 1: return parts[-1] @@ -55,19 +52,16 @@ search_results = {} # Helper Functions def format_google_results(results): results = results[:15] - return results, "\n\n".join( - [ - f"{i+1}. **[{item['title']}]({item['url']})**\n{item['description']}" - for i, item in enumerate(results) - ] + return results, '\n\n'.join( + [f'{i + 1}. **[{item["title"]}]({item["url"]})**\n{item["description"]}' for i, item in enumerate(results)] ) def format_youtube_results(results): results = results[:15] - return results, "\n\n".join( + return results, '\n\n'.join( [ - f"{i+1}. **[{item['title']}]({item['url']})**\nPublished by: [{item['author']['name']}]({item['author']['url']}) - {item['views']} views - {item['duration']}" + f'{i + 1}. **[{item["title"]}]({item["url"]})**\nPublished by: [{item["author"]["name"]}]({item["author"]["url"]}) - {item["views"]} views - {item["duration"]}' for i, item in enumerate(results) ] ) @@ -75,9 +69,9 @@ def format_youtube_results(results): def format_movie_results(results): results = results[:15] - return results, "\n\n".join( + return results, '\n\n'.join( [ - f"{i+1}. **{item['title']}** ({item['release_date']})\nRating: {item['vote_average']}/10\nVotes: {item['vote_count']}" + f'{i + 1}. **{item["title"]}** ({item["release_date"]})\nRating: {item["vote_average"]}/10\nVotes: {item["vote_count"]}' for i, item in enumerate(results) ] ) @@ -85,9 +79,7 @@ def format_movie_results(results): def format_apk_results(results): results = results[:15] - return results, "\n\n".join( - [f"{i+1}. [{item['title']}]({item['link']})" for i, item in enumerate(results)] - ) + return results, '\n\n'.join([f'{i + 1}. [{item["title"]}]({item["link"]})' for i, item in enumerate(results)]) async def send_screenshot(client, message, url): @@ -96,21 +88,18 @@ async def send_screenshot(client, message, url): await client.send_photo( message.chat.id, screenshot_data, - caption=f"Screenshot of {url}", + caption=f'Screenshot of {url}', reply_to_message_id=message.id, ) else: - await message.reply("Failed to take screenshot.") + await message.reply('Failed to take screenshot.') async def delete_search_data(client, chat_id, message_id): await asyncio.sleep(60) - for key in ["google", "youtube", "movie", "apk"]: - search_key = f"{chat_id}_{key}" - if ( - search_key in search_results - and search_results[search_key]["message_id"] == message_id - ): + for key in ['google', 'youtube', 'movie', 'apk']: + search_key = f'{chat_id}_{key}' + if search_key in search_results and search_results[search_key]['message_id'] == message_id: del search_results[search_key] try: await client.delete_messages(chat_id, message_id) @@ -120,59 +109,59 @@ async def delete_search_data(client, chat_id, message_id): def format_spotify_result(data): - result = "" + result = '' for item in data[:15]: # Limit to 15 results - result += f"🎵 **{item['title']}** by {item['artist']}\n" - result += f"Album: {item['album']}\n" - result += f"Duration: {item['duration']}\n" - result += f"Popularity: {item['popularity']}\n" - result += f"Publish Date: {item['publish']}\n" - result += f"[Listen on Spotify]({item['url']})\n\n" + result += f'🎵 **{item["title"]}** by {item["artist"]}\n' + result += f'Album: {item["album"]}\n' + result += f'Duration: {item["duration"]}\n' + result += f'Popularity: {item["popularity"]}\n' + result += f'Publish Date: {item["publish"]}\n' + result += f'[Listen on Spotify]({item["url"]})\n\n' return result def format_lyrics_result(data): - return f"🎵 **{data['fullTitle']}** by {data['artist']}\n\n{data['lyrics']}" + return f'🎵 **{data["fullTitle"]}** by {data["artist"]}\n\n{data["lyrics"]}' def format_soundcloud_result(data): - result = "" + result = '' for item in data[:15]: # Limit to 15 results - result += f"🎵 **{item['title']}**\n" - result += f"Genre: {item['genre']}\n" - result += f"Duration: {item['duration'] // 1000 // 60}:{item['duration'] // 1000 % 60}\n" - result += f"Likes: {item['likes']}\n" - result += f"Plays: {item['play']}\n" - result += f"[Listen on SoundCloud]({item['link']})\n\n" + result += f'🎵 **{item["title"]}**\n' + result += f'Genre: {item["genre"]}\n' + result += f'Duration: {item["duration"] // 1000 // 60}:{item["duration"] // 1000 % 60}\n' + result += f'Likes: {item["likes"]}\n' + result += f'Plays: {item["play"]}\n' + result += f'[Listen on SoundCloud]({item["link"]})\n\n' return result def format_deezer_result(data): - result = "" + result = '' for item in data[:15]: # Limit to 15 results - result += f"🎵 **{item['title']}** by {item['artist']}\n" - result += f"Duration: {item['duration']}\n" - result += f"Rank: {item['rank']}\n" - result += f"[Listen on Deezer]({item['url']})\n\n" + result += f'🎵 **{item["title"]}** by {item["artist"]}\n' + result += f'Duration: {item["duration"]}\n' + result += f'Rank: {item["rank"]}\n' + result += f'[Listen on Deezer]({item["url"]})\n\n' return result def format_apple_music_result(data): - result = "" + result = '' for item in data[:15]: # Limit to 15 results - title = item.get("title", "Unknown Title") - artists = item.get("artists", "Unknown Artist") - music_type = item.get("type", "Unknown Type") - url = item.get("url", "#") - result += f"🎵 **{title}** by {artists}\n" - result += f"Type: {music_type}\n" - result += f"[Listen on Apple Music]({url})\n\n" + title = item.get('title', 'Unknown Title') + artists = item.get('artists', 'Unknown Artist') + music_type = item.get('type', 'Unknown Type') + url = item.get('url', '#') + result += f'🎵 **{title}** by {artists}\n' + result += f'Type: {music_type}\n' + result += f'[Listen on Apple Music]({url})\n\n' return result async def search_music(api_url, format_function, message, query): - await message.edit("Searching...") - url = f"{api_url}{query}&limit=10" + await message.edit('Searching...') + url = f'{api_url}{query}&limit=10' response = requests.get(url) if response.status_code == 200: @@ -181,105 +170,101 @@ async def search_music(api_url, format_function, message, query): if isinstance(data, list): result = format_function(data) - elif isinstance(data, dict) and "data" in data: - result = format_function(data["data"]) + elif isinstance(data, dict) and 'data' in data: + result = format_function(data['data']) else: - result = "No data found or unexpected format." + result = 'No data found or unexpected format.' await message.edit(result, parse_mode=enums.ParseMode.MARKDOWN) except (ValueError, KeyError, TypeError) as e: - await message.edit(f"An error occurred while processing the data: {str(e)}") - elif response.json()["msg"]: - await message.edit(response.json()["msg"]) + await message.edit(f'An error occurred while processing the data: {str(e)}') + elif response.json()['msg']: + await message.edit(response.json()['msg']) else: - await message.edit("An error occurred, please try again later.") + await message.edit('An error occurred, please try again later.') -@Client.on_message(filters.command(["gsearch"], prefix) & filters.me) +@Client.on_message(filters.command(['gsearch'], prefix) & filters.me) async def google_search(client: Client, message: Message): if message.reply_to_message: query = message.reply_to_message.text.strip() elif len(message.command) > 1: query = message.text.split(maxsplit=1)[1] else: - return await message.edit_text(f"{prefix}gsearch ") + return await message.edit_text(f'{prefix}gsearch ') - await message.edit("Searching...") - url = f"{GOOGLE_SEARCH_URL}{query}" + await message.edit('Searching...') + url = f'{GOOGLE_SEARCH_URL}{query}' response = requests.get(url) if response.status_code == 200: data = response.json() - results, formatted_results = format_google_results(data["data"]) + results, formatted_results = format_google_results(data['data']) search_message = await message.edit( - f"**Google Search Results for:** `{query}`\n\n{formatted_results}", + f'**Google Search Results for:** `{query}`\n\n{formatted_results}', parse_mode=enums.ParseMode.MARKDOWN, disable_web_page_preview=True, ) - search_key = f"{message.chat.id}_google" + search_key = f'{message.chat.id}_google' global search_results search_results[search_key] = { - "results": results, - "message_id": search_message.id, + 'results': results, + 'message_id': search_message.id, } - google_url = f"https://www.google.com/search?q={query}" + google_url = f'https://www.google.com/search?q={query}' await send_screenshot(client, message, google_url) - asyncio.create_task( - delete_search_data(client, message.chat.id, search_message.id) - ) + asyncio.create_task(delete_search_data(client, message.chat.id, search_message.id)) else: - await message.edit("An error occurred, please try again later.") + await message.edit('An error occurred, please try again later.') -@Client.on_message(filters.command(["ytsearch"], prefix) & filters.me) +@Client.on_message(filters.command(['ytsearch'], prefix) & filters.me) async def youtube_search(client: Client, message: Message): if message.reply_to_message: query = message.reply_to_message.text.strip() elif len(message.command) > 1: query = message.text.split(maxsplit=1)[1] else: - return await message.edit_text(f"{prefix}ytsearch ") + return await message.edit_text(f'{prefix}ytsearch ') - await message.edit("Searching...") - url = f"{YOUTUBE_SEARCH_URL}{query}" + await message.edit('Searching...') + url = f'{YOUTUBE_SEARCH_URL}{query}' response = requests.get(url) if response.status_code == 200: data = response.json() - results, formatted_results = format_youtube_results(data["data"]) + results, formatted_results = format_youtube_results(data['data']) search_message = await message.edit( - f"**YouTube Search Results for:** `{query}`\n\n{formatted_results}", + f'**YouTube Search Results for:** `{query}`\n\n{formatted_results}', parse_mode=enums.ParseMode.MARKDOWN, disable_web_page_preview=True, ) - search_key = f"{message.chat.id}_youtube" + search_key = f'{message.chat.id}_youtube' global search_results search_results[search_key] = { - "results": results, - "message_id": search_message.id, + 'results': results, + 'message_id': search_message.id, } - youtube_url = f"https://www.youtube.com/results?search_query={query}" + youtube_url = f'https://www.youtube.com/results?search_query={query}' await send_screenshot(client, message, youtube_url) - asyncio.create_task( - delete_search_data(client, message.chat.id, search_message.id) - ) + asyncio.create_task(delete_search_data(client, message.chat.id, search_message.id)) else: - await message.edit("An error occurred, please try again later.") + await message.edit('An error occurred, please try again later.') -@Client.on_message(filters.command(["moviesearch"], prefix) & filters.me) +@Client.on_message(filters.command(['moviesearch'], prefix) & filters.me) async def movie_search(client, message: Message): if message.reply_to_message: query = message.reply_to_message.text.strip() elif len(message.command) > 1: query = message.text.split(maxsplit=1)[1] else: - return await message.edit_text(f"{prefix}moviesearch ") + return await message.edit_text(f'{prefix}moviesearch ') - await message.edit("Searching...") - url = f"{MOVIE_SEARCH_URL}{query}" + await message.edit('Searching...') + url = f'{MOVIE_SEARCH_URL}{query}' response = requests.get(url) if response.status_code == 200: data = response.json() - results, formatted_results = format_movie_results(data["data"]) + results, formatted_results = format_movie_results(data['data']) # Split the message into multiple parts if it's too long parts = [] @@ -289,179 +274,145 @@ async def movie_search(client, message: Message): for part in parts: search_message = await message.reply( - f"**Movie Search Results for:** `{query}`\n\n{part}", + f'**Movie Search Results for:** `{query}`\n\n{part}', parse_mode=enums.ParseMode.MARKDOWN, disable_web_page_preview=True, ) - search_key = f"{message.chat.id}_movie" + search_key = f'{message.chat.id}_movie' global search_results search_results[search_key] = { - "results": results, - "message_id": search_message.id, + 'results': results, + 'message_id': search_message.id, } - asyncio.create_task( - delete_search_data(client, message.chat.id, search_message.id) - ) + asyncio.create_task(delete_search_data(client, message.chat.id, search_message.id)) else: - await message.edit("An error occurred, please try again later.") + await message.edit('An error occurred, please try again later.') -@Client.on_message(filters.command(["apksearch"], prefix) & filters.me) +@Client.on_message(filters.command(['apksearch'], prefix) & filters.me) async def apk_search(client, message: Message): if message.reply_to_message: query = message.reply_to_message.text.strip() elif len(message.command) > 1: query = message.text.split(maxsplit=1)[1] else: - return await message.edit_text(f"{prefix}apksearch ") + return await message.edit_text(f'{prefix}apksearch ') - await message.edit("Searching...") - url = f"{APK_SEARCH_URL}{query}" + await message.edit('Searching...') + url = f'{APK_SEARCH_URL}{query}' response = requests.get(url) if response.status_code == 200: data = response.json() - results, formatted_results = format_apk_results(data["BK9"]) + results, formatted_results = format_apk_results(data['BK9']) search_message = await message.edit( - f"**APK Search Results for:** `{query}`\n\n{formatted_results}", + f'**APK Search Results for:** `{query}`\n\n{formatted_results}', parse_mode=enums.ParseMode.MARKDOWN, disable_web_page_preview=True, ) - search_key = f"{message.chat.id}_apk" + search_key = f'{message.chat.id}_apk' global search_results search_results[search_key] = { - "results": results, - "message_id": search_message.id, + 'results': results, + 'message_id': search_message.id, } - asyncio.create_task( - delete_search_data(client, message.chat.id, search_message.id) - ) + asyncio.create_task(delete_search_data(client, message.chat.id, search_message.id)) else: - await message.edit("An error occurred, please try again later.") + await message.edit('An error occurred, please try again later.') -@Client.on_message(filters.command(["wgpt", "gptweb"], prefix) & filters.me) +@Client.on_message(filters.command(['wgpt', 'gptweb'], prefix) & filters.me) async def gptweb(_, message: Message): if len(message.command) < 2: - await message.edit("Usage: `wgpt `") + await message.edit('Usage: `wgpt `') return - await message.edit("Thinking...") - query = " ".join(message.command[1:]) - url = f"{URL}/gptweb?text={query}" + await message.edit('Thinking...') + query = ' '.join(message.command[1:]) + url = f'{URL}/gptweb?text={query}' response = requests.get(url) if response.status_code == 200: data = response.json() await message.edit( - f"**Question:**\n{query}\n**Answer:**\n{data['data']}", + f'**Question:**\n{query}\n**Answer:**\n{data["data"]}', parse_mode=enums.ParseMode.MARKDOWN, ) else: - await message.edit("An error occurred, please try again later.") + await message.edit('An error occurred, please try again later.') -@Client.on_message(filters.command(["wgemini"], prefix) & filters.me) +@Client.on_message(filters.command(['wgemini'], prefix) & filters.me) async def gemini(_, message: Message): if len(message.command) < 2: - await message.edit("Usage: `wgemini `") + await message.edit('Usage: `wgemini `') return - await message.edit("Thinking...") - query = " ".join(message.command[1:]) - url = f"{URL}/gemini?query={query}" + await message.edit('Thinking...') + query = ' '.join(message.command[1:]) + url = f'{URL}/gemini?query={query}' response = requests.get(url) if response.status_code == 200: data = response.json() await message.edit( - f"**Question:**\n{query}\n**Answer:**\n{data['message']}", + f'**Question:**\n{query}\n**Answer:**\n{data["message"]}', parse_mode=enums.ParseMode.MARKDOWN, ) else: - await message.edit("An error occurred, please try again later.") + await message.edit('An error occurred, please try again later.') -@Client.on_message(filters.command(["sputify"], prefix) & filters.me) +@Client.on_message(filters.command(['sputify'], prefix) & filters.me) async def spotify_search(_, message: Message): - query = ( - message.text.split(maxsplit=1)[1] - if len(message.command) > 1 - else message.reply_to_message.text - ) + query = message.text.split(maxsplit=1)[1] if len(message.command) > 1 else message.reply_to_message.text if not query: - await message.edit("Usage: spotify ") + await message.edit('Usage: spotify ') return - await search_music( - f"{BASE_URL}/search/spotify?q=", format_spotify_result, message, query - ) + await search_music(f'{BASE_URL}/search/spotify?q=', format_spotify_result, message, query) -@Client.on_message(filters.command(["lyrics"], prefix) & filters.me) +@Client.on_message(filters.command(['lyrics'], prefix) & filters.me) async def lyrics_search(_, message: Message): - query = ( - message.text.split(maxsplit=1)[1] - if len(message.command) > 1 - else message.reply_to_message.text - ) + query = message.text.split(maxsplit=1)[1] if len(message.command) > 1 else message.reply_to_message.text if not query: - await message.edit("Usage: lyrics ") + await message.edit('Usage: lyrics ') return - await search_music( - f"{BASE_URL}/search/letra?query=", format_lyrics_result, message, query - ) + await search_music(f'{BASE_URL}/search/letra?query=', format_lyrics_result, message, query) -@Client.on_message(filters.command(["soundcloud"], prefix) & filters.me) +@Client.on_message(filters.command(['soundcloud'], prefix) & filters.me) async def soundcloud_search(_, message: Message): - query = ( - message.text.split(maxsplit=1)[1] - if len(message.command) > 1 - else message.reply_to_message.text - ) + query = message.text.split(maxsplit=1)[1] if len(message.command) > 1 else message.reply_to_message.text if not query: - await message.edit("Usage: soundcloud ") + await message.edit('Usage: soundcloud ') return - await search_music( - f"{BASE_URL}/search/soundcloud?q=", format_soundcloud_result, message, query - ) + await search_music(f'{BASE_URL}/search/soundcloud?q=', format_soundcloud_result, message, query) -@Client.on_message(filters.command(["deezer"], prefix) & filters.me) +@Client.on_message(filters.command(['deezer'], prefix) & filters.me) async def deezer_search(_, message: Message): - query = ( - message.text.split(maxsplit=1)[1] - if len(message.command) > 1 - else message.reply_to_message.text - ) + query = message.text.split(maxsplit=1)[1] if len(message.command) > 1 else message.reply_to_message.text if not query: - await message.edit("Usage: deezer ") + await message.edit('Usage: deezer ') return - await search_music( - f"{BASE_URL}/search/deezer?q=", format_deezer_result, message, query - ) + await search_music(f'{BASE_URL}/search/deezer?q=', format_deezer_result, message, query) -@Client.on_message(filters.command(["applemusic"], prefix) & filters.me) +@Client.on_message(filters.command(['applemusic'], prefix) & filters.me) async def applemusic_search(_, message: Message): - query = ( - message.text.split(maxsplit=1)[1] - if len(message.command) > 1 - else message.reply_to_message.text - ) + query = message.text.split(maxsplit=1)[1] if len(message.command) > 1 else message.reply_to_message.text if not query: - await message.edit("Usage: applemusic ") + await message.edit('Usage: applemusic ') return - await search_music( - f"{BASE_URL}/search/applemusic?text=", format_apple_music_result, message, query - ) + await search_music(f'{BASE_URL}/search/applemusic?text=', format_apple_music_result, message, query) @Client.on_message(filters.reply & filters.text & filters.me & np) async def handle_reply(client: Client, message: Message): chat_id = message.chat.id search_keys = [ - f"{chat_id}_google", - f"{chat_id}_youtube", - f"{chat_id}_movie", - f"{chat_id}_apk", + f'{chat_id}_google', + f'{chat_id}_youtube', + f'{chat_id}_movie', + f'{chat_id}_apk', ] for search_key in search_keys: @@ -472,76 +423,65 @@ async def handle_reply(client: Client, message: Message): return index = int(message.text.strip()) - 1 - results = search_results[search_key]["results"] - search_message_id = search_results[search_key]["message_id"] - if ( - message.reply_to_message.id == search_message_id - and 0 <= index < len(results) - ): - await message.edit("Please wait...") + results = search_results[search_key]['results'] + search_message_id = search_results[search_key]['message_id'] + if message.reply_to_message.id == search_message_id and 0 <= index < len(results): + await message.edit('Please wait...') - if search_key.endswith("_movie"): + if search_key.endswith('_movie'): # Send movie details with image movie = results[index] caption = ( - f"**{movie['title']}** ({movie['release_date']})\n" - f"Original Title: {movie['original_title']}\n" - f"Language: {movie['original_language']}\n" - f"Overview: {movie['overview']}\n" - f"Popularity: {movie['popularity']}\n" - f"Rating: {movie['vote_average']}/10\n" - f"Votes: {movie['vote_count']}" + f'**{movie["title"]}** ({movie["release_date"]})\n' + f'Original Title: {movie["original_title"]}\n' + f'Language: {movie["original_language"]}\n' + f'Overview: {movie["overview"]}\n' + f'Popularity: {movie["popularity"]}\n' + f'Rating: {movie["vote_average"]}/10\n' + f'Votes: {movie["vote_count"]}' ) - if "image" in movie: - response = requests.get(movie["image"]) + if 'image' in movie: + response = requests.get(movie['image']) if response.status_code == 200: - with open("movie_image.jpg", "wb") as f: + with open('movie_image.jpg', 'wb') as f: f.write(response.content) await message.reply_photo( - photo="movie_image.jpg", + photo='movie_image.jpg', caption=caption, parse_mode=enums.ParseMode.MARKDOWN, ) - os.remove("movie_image.jpg") + os.remove('movie_image.jpg') else: - await message.reply( - caption, parse_mode=enums.ParseMode.MARKDOWN - ) + await message.reply(caption, parse_mode=enums.ParseMode.MARKDOWN) else: - await message.reply( - caption, parse_mode=enums.ParseMode.MARKDOWN - ) - elif search_key.endswith("_apk"): - apk_url = f"{APK_DOWNLOAD_URL}{results[index]['link']}" + await message.reply(caption, parse_mode=enums.ParseMode.MARKDOWN) + elif search_key.endswith('_apk'): + apk_url = f'{APK_DOWNLOAD_URL}{results[index]["link"]}' fetch_apk_url = requests.get(apk_url) if fetch_apk_url.status_code != 200: - await message.edit("Failed to fetch APK data.") + await message.edit('Failed to fetch APK data.') else: data_apk = fetch_apk_url.json() - download_url = data_apk["BK9"]["link"] - size_apk = data_apk["BK9"]["size"] - apk_size = float(size_apk.split(" ")[0]) + download_url = data_apk['BK9']['link'] + size_apk = data_apk['BK9']['size'] + apk_size = float(size_apk.split(' ')[0]) - if "GB" in size_apk or apk_size > 100: - await message.edit( - "File size is too large to download." - ) + if 'GB' in size_apk or apk_size > 100: + await message.edit('File size is too large to download.') else: - apk_file_name = f"{data_apk['BK9']['title']}.apk" + apk_file_name = f'{data_apk["BK9"]["title"]}.apk' response = requests.get(download_url) if response.status_code != 200: - await message.edit( - "Failed to download the APK file." - ) + await message.edit('Failed to download the APK file.') else: - with open(apk_file_name, "wb") as f: + with open(apk_file_name, 'wb') as f: f.write(response.content) await message.reply_document(apk_file_name) os.remove(apk_file_name) else: - url = results[index]["url"] + url = results[index]['url'] await send_screenshot(client, message, url) await message.delete() return @@ -549,17 +489,17 @@ async def handle_reply(client: Client, message: Message): pass -modules_help["sarethai"] = { - "wgpt [query]*": "Ask anything to GPT-Web", - "gptweb [query]*": "Ask anything to GPT-Web", - "wgemini [query]*": "Ask anything to Gemini", - "sputify [query]*": "Search for songs on Spotify", - "lyrics [song name]*": "Get the lyrics of a song", - "soundcloud [query]*": "Search for songs on SoundCloud", - "deezer [query]*": "Search for songs on Deezer", - "applemusic [query]*": "Search for songs on Apple Music", - "gsearch [query]*": "Searches Google for the query.", - "ytsearch [query]*": "Searches YouTube for the query.", - "moviesearch [query]*": "Searches movies for the query and returns results.", - "apksearch [query]*": "Searches APKs for the query and returns results.", +modules_help['sarethai'] = { + 'wgpt [query]*': 'Ask anything to GPT-Web', + 'gptweb [query]*': 'Ask anything to GPT-Web', + 'wgemini [query]*': 'Ask anything to Gemini', + 'sputify [query]*': 'Search for songs on Spotify', + 'lyrics [song name]*': 'Get the lyrics of a song', + 'soundcloud [query]*': 'Search for songs on SoundCloud', + 'deezer [query]*': 'Search for songs on Deezer', + 'applemusic [query]*': 'Search for songs on Apple Music', + 'gsearch [query]*': 'Searches Google for the query.', + 'ytsearch [query]*': 'Searches YouTube for the query.', + 'moviesearch [query]*': 'Searches movies for the query and returns results.', + 'apksearch [query]*': 'Searches APKs for the query and returns results.', } diff --git a/modules/misc/search.py b/modules/misc/search.py index 7ffbbb3..02dd472 100644 --- a/modules/misc/search.py +++ b/modules/misc/search.py @@ -12,16 +12,15 @@ from utils.scripts import format_exc now = {} -@Client.on_message(filters.command(["search"], prefix) & filters.me) +@Client.on_message(filters.command(['search'], prefix) & filters.me) async def search_cmd(client: Client, message: Message): if now.get(message.chat.id): return await message.edit( - "You already have a search in progress!\n" - "Type: {}scancel to cancel it.".format(prefix), + f'You already have a search in progress!\nType: {prefix}scancel to cancel it.', parse_mode=enums.ParseMode.HTML, ) - await message.edit("Start searching...", parse_mode=enums.ParseMode.HTML) + await message.edit('Start searching...', parse_mode=enums.ParseMode.HTML) finished = False local = False try: @@ -30,9 +29,7 @@ async def search_cmd(client: Client, message: Message): timeout = float(message.command[3]) if len(message.command) > 3 else 2 except: return await message.edit( - "Usage: {}search [/cmd]* [search_word]* [timeout=2.0]".format( - prefix - ), + f'Usage: {prefix}search [/cmd]* [search_word]* [timeout=2.0]', parse_mode=enums.ParseMode.HTML, ) @@ -49,31 +46,25 @@ async def search_cmd(client: Client, message: Message): local = True if not local: await sleep(timeout) - await message.reply_text( - quote=False, text=cmd, reply_to_message_id=None - ) + await message.reply_text(quote=False, text=cmd, reply_to_message_id=None) else: break if now[message.chat.id]: - await message.reply_text( - "Search finished!", parse_mode=enums.ParseMode.HTML - ) + await message.reply_text('Search finished!', parse_mode=enums.ParseMode.HTML) except Exception as ex: await message.edit(format_exc(ex), parse_mode=enums.ParseMode.HTML) now[message.chat.id] = False -@Client.on_message(filters.command(["scancel"], prefix) & filters.me) +@Client.on_message(filters.command(['scancel'], prefix) & filters.me) async def scancel_cmd(_: Client, message: Message): if not now.get(message.chat.id): - return await message.edit( - "There is no search in progress!", parse_mode=enums.ParseMode.HTML - ) + return await message.edit('There is no search in progress!', parse_mode=enums.ParseMode.HTML) now[message.chat.id] = False - await message.edit("Search cancelled!", parse_mode=enums.ParseMode.HTML) + await message.edit('Search cancelled!', parse_mode=enums.ParseMode.HTML) -modules_help["search"] = { - "search [/cmd]* [search_word]* [timeout=2.0]": "Search for a specific word in bot (while)", - "scancel": "Cancel current search", +modules_help['search'] = { + 'search [/cmd]* [search_word]* [timeout=2.0]': 'Search for a specific word in bot (while)', + 'scancel': 'Cancel current search', } diff --git a/modules/misc/summary.py b/modules/misc/summary.py index 763a19c..3a6c13c 100644 --- a/modules/misc/summary.py +++ b/modules/misc/summary.py @@ -2,21 +2,19 @@ from pyrogram import Client, filters from pyrogram.types import Message - -from utils.scripts import format_exc, import_library from utils.misc import modules_help, prefix +from utils.scripts import format_exc, import_library -import_library("lxml_html_clean") -import_library("newspaper", "newspaper3k") -nltk = import_library("nltk") +import_library('lxml_html_clean') +import_library('newspaper', 'newspaper3k') +nltk = import_library('nltk') from newspaper import Article from newspaper.article import ArticleException - -nltk.download("all") +nltk.download('all') -@Client.on_message(filters.command("summary", prefix) & filters.me) +@Client.on_message(filters.command('summary', prefix) & filters.me) async def summarize_article(_, message: Message): """ Summarize an article from a given URL. @@ -32,7 +30,7 @@ async def summarize_article(_, message: Message): url = message.text.split(maxsplit=1)[1] if len(message.text.split()) > 1 else None if not url: - await message.edit_text("Please provide a valid URL after the command.") + await message.edit_text('Please provide a valid URL after the command.') return try: @@ -51,14 +49,10 @@ async def summarize_article(_, message: Message): """ await message.edit_text(response) except ArticleException: - return await message.edit_text( - "Unable to extract information from the provided URL." - ) + return await message.edit_text('Unable to extract information from the provided URL.') except Exception as e: - return await message.edit_text(f"An error occurred: {format_exc(e)}") + return await message.edit_text(f'An error occurred: {format_exc(e)}') -modules_help["summary"] = { - "summary [url]": "Reply with article links, getting summary of articles" -} +modules_help['summary'] = {'summary [url]': 'Reply with article links, getting summary of articles'} diff --git a/modules/misc/switch.py b/modules/misc/switch.py index 3f55768..a3bd72d 100644 --- a/modules/misc/switch.py +++ b/modules/misc/switch.py @@ -14,21 +14,16 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from pyrogram import Client, filters, enums +from pyrogram import Client, enums, filters from pyrogram.types import Message - from utils.misc import modules_help, prefix -ru_keys = ( - """ёйцукенгшщзхъфывапролджэячсмитьбю.Ё"№;%:?ЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭ/ЯЧСМИТЬБЮ,""" -) -en_keys = ( - """`qwertyuiop[]asdfghjkl;'zxcvbnm,./~@#$%^&QWERTYUIOP{}ASDFGHJKL:"|ZXCVBNM<>?""" -) +ru_keys = """ёйцукенгшщзхъфывапролджэячсмитьбю.Ё"№;%:?ЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭ/ЯЧСМИТЬБЮ,""" +en_keys = """`qwertyuiop[]asdfghjkl;'zxcvbnm,./~@#$%^&QWERTYUIOP{}ASDFGHJKL:"|ZXCVBNM<>?""" table = str.maketrans(ru_keys + en_keys, en_keys + ru_keys) -@Client.on_message(filters.command(["switch", "sw"], prefix) & filters.me) +@Client.on_message(filters.command(['switch', 'sw'], prefix) & filters.me) async def switch(client: Client, message: Message): if len(message.command) == 1: if message.reply_to_message: @@ -38,9 +33,7 @@ async def switch(client: Client, message: Message): if history and history[1].from_user.is_self and history[1].text: text = history[1].text else: - await message.edit( - "Text to switch not found", parse_mode=enums.ParseMode.HTML - ) + await message.edit('Text to switch not found', parse_mode=enums.ParseMode.HTML) return else: text = message.text.split(maxsplit=1)[1] @@ -48,6 +41,6 @@ async def switch(client: Client, message: Message): await message.edit(str.translate(text, table), parse_mode=enums.ParseMode.HTML) -modules_help["switch"] = { - "sw [reply/text for switch]*": "Useful when you forgot to change the keyboard layout[RU]", +modules_help['switch'] = { + 'sw [reply/text for switch]*': 'Useful when you forgot to change the keyboard layout[RU]', } diff --git a/modules/misc/transcribeyt.py b/modules/misc/transcribeyt.py index 7cc0a97..f443e3d 100644 --- a/modules/misc/transcribeyt.py +++ b/modules/misc/transcribeyt.py @@ -1,70 +1,62 @@ -import requests -import time -from pyrogram import Client, filters, enums -from pyrogram.types import Message -from pyrogram.errors import MessageTooLong import os +import time + +import requests +from pyrogram import Client, enums, filters +from pyrogram.errors import MessageTooLong +from pyrogram.types import Message from utils.misc import modules_help, prefix - # Replace with your Gladia API key -gladia_key = "your key " -gladia_url = "https://api.gladia.io/v2/transcription/" +gladia_key = 'your key ' +gladia_url = 'https://api.gladia.io/v2/transcription/' # Function to make fetch requests to the Gladia API -def make_fetch_request(url, headers, method="GET", data=None): - if method == "POST": +def make_fetch_request(url, headers, method='GET', data=None): + if method == 'POST': response = requests.post(url, headers=headers, json=data) else: response = requests.get(url, headers=headers) return response.json() -@Client.on_message(filters.command("transcribeyt", prefix) & filters.me) +@Client.on_message(filters.command('transcribeyt', prefix) & filters.me) async def transcribe_audio(_, message: Message): """ Transcribe an audio URL using the Gladia API. Usage: .transcribe """ - audio_url = ( - message.text.split(maxsplit=1)[1] if len(message.text.split()) > 1 else None - ) + audio_url = message.text.split(maxsplit=1)[1] if len(message.text.split()) > 1 else None # Check if a valid URL was provided if not audio_url: - await message.reply("Please provide a valid audio URL.") + await message.reply('Please provide a valid audio URL.') return - headers = {"x-gladia-key": gladia_key, "Content-Type": "application/json"} + headers = {'x-gladia-key': gladia_key, 'Content-Type': 'application/json'} - request_data = {"audio_url": audio_url} + request_data = {'audio_url': audio_url} # Send initial request to Gladia API - status_message = await message.reply("- Sending initial request to Gladia API...") - initial_response = make_fetch_request(gladia_url, headers, "POST", request_data) + status_message = await message.reply('- Sending initial request to Gladia API...') + initial_response = make_fetch_request(gladia_url, headers, 'POST', request_data) # Check if the response contains the result_url - if "result_url" not in initial_response: - await status_message.edit(f"Error in transcription request: {initial_response}") + if 'result_url' not in initial_response: + await status_message.edit(f'Error in transcription request: {initial_response}') return - result_url = initial_response["result_url"] - await status_message.edit( - f"Initial request sent. Polling for transcription results..." - ) + result_url = initial_response['result_url'] + await status_message.edit('Initial request sent. Polling for transcription results...') # Polling for transcription result while True: poll_response = make_fetch_request(result_url, headers) - if poll_response.get("status") == "done": - transcription = ( - poll_response.get("result", {}) - .get("transcription", {}) - .get("full_transcript") - ) + if poll_response.get('status') == 'done': + transcription = poll_response.get('result', {}).get('transcription', {}).get('full_transcript') if transcription: # Format the transcription result with HTML result_html = f""" @@ -74,12 +66,10 @@ async def transcribe_audio(_, message: Message): """ try: # Attempt to send transcription as a message - await message.reply_text( - result_html, parse_mode=enums.ParseMode.HTML - ) + await message.reply_text(result_html, parse_mode=enums.ParseMode.HTML) except MessageTooLong: # Save the large response to a file - with open("transcription.txt", "w") as f: + with open('transcription.txt', 'w') as f: f.write(result_html) # Read general details to include in the caption @@ -87,24 +77,18 @@ async def transcribe_audio(_, message: Message): # Send the file with a caption await message.reply_document( - "transcription.txt", - caption=f"General Details:\n{general_details}", + 'transcription.txt', + caption=f'General Details:\n{general_details}', ) # Clean up by removing the file - os.remove("transcription.html") + os.remove('transcription.html') else: - await status_message.edit( - "Transcription completed, but no transcript was found." - ) + await status_message.edit('Transcription completed, but no transcript was found.') break else: - await status_message.edit( - f"Transcription status: {poll_response.get('status')}" - ) + await status_message.edit(f'Transcription status: {poll_response.get("status")}') time.sleep(30) # Wait for a few seconds before polling again -modules_help["transcribeyt"] = { - "transcribeyt [yt video url]": "Reply with a YT video link to get transcribed " -} +modules_help['transcribeyt'] = {'transcribeyt [yt video url]': 'Reply with a YT video link to get transcribed '} diff --git a/modules/notes.py b/modules/notes.py index b43dc3e..216a945 100644 --- a/modules/notes.py +++ b/modules/notes.py @@ -16,128 +16,118 @@ from pyrogram import Client, errors, filters from pyrogram.types import Message - from utils.db import db from utils.handlers import NoteSendHandler from utils.misc import modules_help, prefix -@Client.on_message(filters.command(["save"], prefix) & filters.me) +@Client.on_message(filters.command(['save'], prefix) & filters.me) async def save_note(client: Client, message: Message): - await message.edit("Loading...") + await message.edit('Loading...') try: - chat = await client.get_chat(db.get("core.notes", "chat_id", 0)) + chat = await client.get_chat(db.get('core.notes', 'chat_id', 0)) except (errors.RPCError, ValueError, KeyError): # group is not accessible or isn't created - chat = await client.create_supergroup( - "Userbot_Notes_Filters", "Don't touch this group, please" - ) - db.set("core.notes", "chat_id", chat.id) + chat = await client.create_supergroup('Userbot_Notes_Filters', "Don't touch this group, please") + db.set('core.notes', 'chat_id', chat.id) chat_id = chat.id if message.reply_to_message and len(message.text.split()) >= 2: note_name = message.text.split(maxsplit=1)[1] if message.reply_to_message.media_group_id: - checking_note = db.get("core.notes", f"note{note_name}", False) + checking_note = db.get('core.notes', f'note{note_name}', False) if not checking_note: get_media_group = [ - _.id - for _ in await client.get_media_group( - message.chat.id, message.reply_to_message.id - ) + _.id for _ in await client.get_media_group(message.chat.id, message.reply_to_message.id) ] try: - message_id = await client.forward_messages( - chat_id, message.chat.id, get_media_group - ) + message_id = await client.forward_messages(chat_id, message.chat.id, get_media_group) except errors.ChatForwardsRestricted: await message.edit( - "Forwarding messages is restricted by chat admins", + 'Forwarding messages is restricted by chat admins', ) return note = { - "MESSAGE_ID": str(message_id[1].id), - "MEDIA_GROUP": True, - "CHAT_ID": str(chat_id), + 'MESSAGE_ID': str(message_id[1].id), + 'MEDIA_GROUP': True, + 'CHAT_ID': str(chat_id), } - db.set("core.notes", f"note{note_name}", note) - await message.edit(f"Note {note_name} saved") + db.set('core.notes', f'note{note_name}', note) + await message.edit(f'Note {note_name} saved') else: - await message.edit("This note already exists") + await message.edit('This note already exists') else: - checking_note = db.get("core.notes", f"note{note_name}", False) + checking_note = db.get('core.notes', f'note{note_name}', False) if not checking_note: try: message_id = await message.reply_to_message.forward(chat_id) except errors.ChatForwardsRestricted: message_id = await message.copy(chat_id) note = { - "MEDIA_GROUP": False, - "MESSAGE_ID": str(message_id.id), - "CHAT_ID": str(chat_id), + 'MEDIA_GROUP': False, + 'MESSAGE_ID': str(message_id.id), + 'CHAT_ID': str(chat_id), } - db.set("core.notes", f"note{note_name}", note) - await message.edit(f"Note {note_name} saved") + db.set('core.notes', f'note{note_name}', note) + await message.edit(f'Note {note_name} saved') else: - await message.edit("This note already exists") + await message.edit('This note already exists') elif len(message.text.split()) >= 3: note_name = message.text.split(maxsplit=1)[1].split()[0] - checking_note = db.get("core.notes", f"note{note_name}", False) + checking_note = db.get('core.notes', f'note{note_name}', False) if not checking_note: - message_id = await client.send_message( - chat_id, message.text.split(note_name)[1].strip() - ) + message_id = await client.send_message(chat_id, message.text.split(note_name)[1].strip()) note = { - "MEDIA_GROUP": False, - "MESSAGE_ID": str(message_id.id), - "CHAT_ID": str(chat_id), + 'MEDIA_GROUP': False, + 'MESSAGE_ID': str(message_id.id), + 'CHAT_ID': str(chat_id), } - db.set("core.notes", f"note{note_name}", note) - await message.edit(f"Note {note_name} saved") + db.set('core.notes', f'note{note_name}', note) + await message.edit(f'Note {note_name} saved') else: - await message.edit("This note already exists") + await message.edit('This note already exists') else: await message.edit( - f"Example: {prefix}save note_name", + f'Example: {prefix}save note_name', ) -@Client.on_message(filters.command("note", prefix) & filters.me) +@Client.on_message(filters.command('note', prefix) & filters.me) async def note_send(client: Client, message: Message): handler = NoteSendHandler(client, message) await handler.handle_note_send() -@Client.on_message(filters.command(["notes"], prefix) & filters.me) +@Client.on_message(filters.command(['notes'], prefix) & filters.me) async def notes(_, message: Message): - await message.edit("Loading...") - text = "Available notes:\n\n" - collection = db.get_collection("core.notes") + await message.edit('Loading...') + text = 'Available notes:\n\n' + collection = db.get_collection('core.notes') for note in collection.keys(): - if note[:4] == "note": - text += f"{note[4:]}\n" + if note[:4] == 'note': + text += f'{note[4:]}\n' await message.edit(text) -@Client.on_message(filters.command(["clear"], prefix) & filters.me) +@Client.on_message(filters.command(['clear'], prefix) & filters.me) async def clear_note(_, message: Message): if len(message.text.split()) >= 2: note_name = message.text.split(maxsplit=1)[1] - find_note = db.get("core.notes", f"note{note_name}", False) + find_note = db.get('core.notes', f'note{note_name}', False) if find_note: - db.remove("core.notes", f"note{note_name}") - await message.edit(f"Note {note_name} deleted") + db.remove('core.notes', f'note{note_name}') + await message.edit(f'Note {note_name} deleted') else: - await message.edit("There is no such note") + await message.edit('There is no such note') else: - await message.edit(f"Example: {prefix}clear note_name") + await message.edit(f'Example: {prefix}clear note_name') -modules_help["notes"] = { - "save [name]*": "Save note", - "note [name]*": "Get saved note", - "notes": "Get note list", - "clear [name]*": "Delete note", +modules_help['notes'] = { + 'save [name]*': 'Save note', + 'note [name]*': 'Get saved note', + 'notes': 'Get note list', + 'clear [name]*': 'Delete note', } diff --git a/modules/open.py b/modules/open.py index dd6da30..9c25295 100644 --- a/modules/open.py +++ b/modules/open.py @@ -22,94 +22,85 @@ import aiofiles from pyrogram import Client, filters from pyrogram.errors import MessageTooLong from pyrogram.types import Message - from utils.misc import modules_help, prefix -from utils.scripts import edit_or_reply, format_exc, progress from utils.rentry import paste as rentry_paste +from utils.scripts import edit_or_reply, format_exc, progress async def read_file(file_path): - async with aiofiles.open(file_path, mode="r") as file: + async with aiofiles.open(file_path) as file: content = await file.read() return content def check_extension(file_path): extensions = { - ".txt": "
",
-        ".py": "
",
-        ".js": "
",
-        ".json": "
",
-        ".smali": "
",
-        ".sh": "
",
-        ".c": "
",
-        ".java": "
",
-        ".php": "
",
-        ".doc": "
",
-        ".docx": "
",
-        ".rtf": "
",
-        ".s": "
",
-        ".dart": "
",
-        ".cfg": "
",
-        ".swift": "
",
-        ".cs": "
",
-        ".vb": "
",
-        ".css": "
",
-        ".htm": "
",
-        ".html": "
",
-        ".rss": "
",
-        ".xhtml": "
",
-        ".cpp": "
",
+        '.txt': "
",
+        '.py': "
",
+        '.js': "
",
+        '.json': "
",
+        '.smali': "
",
+        '.sh': "
",
+        '.c': "
",
+        '.java': "
",
+        '.php': "
",
+        '.doc': "
",
+        '.docx': "
",
+        '.rtf': "
",
+        '.s': "
",
+        '.dart': "
",
+        '.cfg': "
",
+        '.swift': "
",
+        '.cs': "
",
+        '.vb': "
",
+        '.css': "
",
+        '.htm': "
",
+        '.html': "
",
+        '.rss': "
",
+        '.xhtml': "
",
+        '.cpp': "
",
     }
 
     ext = os.path.splitext(file_path)[1].lower()
 
-    return extensions.get(ext, "
")
+    return extensions.get(ext, '
')
 
 
-@Client.on_message(filters.command("open", prefix) & filters.me)
+@Client.on_message(filters.command('open', prefix) & filters.me)
 async def openfile(client: Client, message: Message):
     if not message.reply_to_message:
-        return await message.edit_text("Kindly Reply to a File")
+        return await message.edit_text('Kindly Reply to a File')
 
     try:
-        ms = await edit_or_reply(message, "Downloading...")
+        ms = await edit_or_reply(message, 'Downloading...')
         ct = time.time()
-        file_path = await message.reply_to_message.download(
-            progress=progress, progress_args=(ms, ct, "Downloading...")
-        )
-        await ms.edit_text("Trying to open file...")
+        file_path = await message.reply_to_message.download(progress=progress, progress_args=(ms, ct, 'Downloading...'))
+        await ms.edit_text('Trying to open file...')
         file_info = os.stat(file_path)
-        file_name = file_path.split("/")[-1:]
+        file_name = file_path.split('/')[-1:]
         file_size = file_info.st_size
-        last_modified = datetime.datetime.fromtimestamp(file_info.st_mtime).strftime(
-            "%Y-%m-%d %H:%M:%S"
-        )
+        last_modified = datetime.datetime.fromtimestamp(file_info.st_mtime).strftime('%Y-%m-%d %H:%M:%S')
         code_start = check_extension(file_path=file_path)
         content = await read_file(file_path=file_path)
         await ms.edit_text(
-            f"File Name: {file_name[0]}\nSize: {file_size} bytes\nLast Modified: {last_modified}\nContent: {code_start}{content}
", + f'File Name: {file_name[0]}\nSize: {file_size} bytes\nLast Modified: {last_modified}\nContent: {code_start}{content}
', ) except MessageTooLong: - await ms.edit_text( - "File Content is too long... Pasting to rentry..." - ) - content_new = f"```{code_start[11:-2]}\n{content}```" + await ms.edit_text('File Content is too long... Pasting to rentry...') + content_new = f'```{code_start[11:-2]}\n{content}```' try: - rentry_url, edit_code = await rentry_paste( - text=content_new, return_edit=True - ) + rentry_url, edit_code = await rentry_paste(text=content_new, return_edit=True) except RuntimeError: - await ms.edit_text("Error: Failed to paste to rentry") + await ms.edit_text('Error: Failed to paste to rentry') return await client.send_message( - "me", + 'me', f"Here's your edit code for Url: {rentry_url}\nEdit code: {edit_code}", disable_web_page_preview=True, ) await ms.edit_text( - f"File Name: {file_name[0]}\nSize: {file_size} bytes\nLast Modified: {last_modified}\nContent: {rentry_url}\nNote: Edit Code has been sent to your saved messages", + f'File Name: {file_name[0]}\nSize: {file_size} bytes\nLast Modified: {last_modified}\nContent: {rentry_url}\nNote: Edit Code has been sent to your saved messages', disable_web_page_preview=True, ) @@ -121,6 +112,4 @@ async def openfile(client: Client, message: Message): os.remove(file_path) -modules_help["open"] = { - "open": "Open content of any text supported filetype and show it's raw data" -} +modules_help['open'] = {'open': "Open content of any text supported filetype and show it's raw data"} diff --git a/modules/pdf2md.py b/modules/pdf2md.py index 8d1649f..d5cba9e 100644 --- a/modules/pdf2md.py +++ b/modules/pdf2md.py @@ -1,49 +1,48 @@ import os + from pyrogram import Client, filters from pyrogram.types import Message - +from utils.config import gemini_key from utils.misc import modules_help, prefix from utils.scripts import import_library -from utils.config import gemini_key +import_library('pyzerox', 'py-zerox') -import_library("pyzerox", "py-zerox") - +import litellm from pyzerox import zerox from pyzerox.errors import ModelAccessError, NotAVisionModel -import litellm kwargs = {} CUSTOM_SYSTEM_PROMPT = "For the below pdf page, convert it into as accurate markdown format as possible with it's structure intact i.e, tables, charts, layouts etc. Return only the markdown with no explanation text. Do not exclude any content from the page." -MODEL = "gemini/gemini-1.5-pro" +MODEL = 'gemini/gemini-1.5-pro' -@Client.on_message(filters.command("pdf2md", prefix) & filters.me) +@Client.on_message(filters.command('pdf2md', prefix) & filters.me) async def pdf2md(client: Client, message: Message): if not message.reply_to_message: - await message.edit("Reply to a pdf file") + await message.edit('Reply to a pdf file') return if not message.reply_to_message.document: - await message.edit("Reply to a pdf file") + await message.edit('Reply to a pdf file') return - if not message.reply_to_message.document.mime_type == "application/pdf": - await message.edit("Reply to a pdf file") + if not message.reply_to_message.document.mime_type == 'application/pdf': + await message.edit('Reply to a pdf file') return - if gemini_key == "": - await message.edit("Set GEMINI_KEY to use this command") + if gemini_key == '': + await message.edit('Set GEMINI_KEY to use this command') return file_name = message.reply_to_message.document.file_name - file_name = file_name.split(".")[0] - if os.path.exists(f"{file_name}.md"): - os.remove(f"{file_name}.md") - md = f"{file_name}.md" - os.environ["GEMINI_API_KEY"] = gemini_key - await message.edit("Downloading pdf...") + file_name = file_name.split('.')[0] + if os.path.exists(f'{file_name}.md'): + os.remove(f'{file_name}.md') + md = f'{file_name}.md' + os.environ['GEMINI_API_KEY'] = gemini_key + await message.edit('Downloading pdf...') pdf = await message.reply_to_message.download() - await message.edit("Converting pdf to markdown...") + await message.edit('Converting pdf to markdown...') try: result = await zerox( file_path=pdf, @@ -55,29 +54,29 @@ async def pdf2md(client: Client, message: Message): if result: pages = result.pages for page in pages: - with open(md, "a") as f: + with open(md, 'a') as f: f.write(page.content) - f.write("\n\n") + f.write('\n\n') else: - await message.edit("No result") + await message.edit('No result') return except ModelAccessError: - await message.edit("Model not accessible") + await message.edit('Model not accessible') return except NotAVisionModel: - await message.edit("Model is not a vision model") + await message.edit('Model is not a vision model') return except litellm.InternalServerError: - await message.edit("Internal Server Error") + await message.edit('Internal Server Error') return except Exception as e: - await message.edit(f"Error: {e}") + await message.edit(f'Error: {e}') return - await message.edit("Uploading markdown...") + await message.edit('Uploading markdown...') await client.send_document( message.chat.id, document=md, - file_name=f"{message.reply_to_message.document.file_name.split('.')[0]}.md", + file_name=f'{message.reply_to_message.document.file_name.split(".")[0]}.md', reply_to_message_id=message.reply_to_message.id, ) await message.delete() @@ -85,4 +84,4 @@ async def pdf2md(client: Client, message: Message): os.remove(md) -modules_help["pdf2md"] = {"pdf2md": "Convert a pdf to markdown"} +modules_help['pdf2md'] = {'pdf2md': 'Convert a pdf to markdown'} diff --git a/modules/perfectrussian.py b/modules/perfectrussian.py index 46e5d79..568663e 100644 --- a/modules/perfectrussian.py +++ b/modules/perfectrussian.py @@ -1,27 +1,26 @@ -from pyrogram import Client, filters, enums -from pyrogram.types import Message +import random +from pyrogram import Client, enums, filters +from pyrogram.types import Message from utils.misc import modules_help, prefix from utils.scripts import with_reply -import random - -@Client.on_message(filters.command("prus", prefix) & filters.me) +@Client.on_message(filters.command('prus', prefix) & filters.me) @with_reply async def prussian_cmd(_, message: Message): words = [ - "сука", - "нахуй", - "блять", - "блядь", - "пиздец", - "еблан", - "уебан", - "уебок", - "пизда", - "очко", - "хуй", + 'сука', + 'нахуй', + 'блять', + 'блядь', + 'пиздец', + 'еблан', + 'уебан', + 'уебок', + 'пизда', + 'очко', + 'хуй', ] splitted = message.reply_to_message.text.split() @@ -29,9 +28,9 @@ async def prussian_cmd(_, message: Message): for j in range(1, 2): splitted.insert(i, random.choice(words)) - await message.edit(" ".join(splitted), parse_mode=enums.ParseMode.HTML) + await message.edit(' '.join(splitted), parse_mode=enums.ParseMode.HTML) -modules_help["perfectrussian"] = { - "prus": "translate your message into perfect 🇷🇺Russian", +modules_help['perfectrussian'] = { + 'prus': 'translate your message into perfect 🇷🇺Russian', } diff --git a/modules/ping.py b/modules/ping.py index 9a2cb31..fecaeaf 100644 --- a/modules/ping.py +++ b/modules/ping.py @@ -17,16 +17,15 @@ from pyrogram import Client, filters from pyrogram.types import Message - from utils.misc import modules_help, prefix -@Client.on_message(filters.command(["ping", "p"], prefix) & filters.me) +@Client.on_message(filters.command(['ping', 'p'], prefix) & filters.me) async def ping(client: Client, message: Message): latency = await client.ping() - await message.edit(f"Pong! {latency}ms") + await message.edit(f'Pong! {latency}ms') -modules_help["ping"] = { - "ping": "Check ping to Telegram servers", +modules_help['ping'] = { + 'ping': 'Check ping to Telegram servers', } diff --git a/modules/prefix.py b/modules/prefix.py index dcec0d5..a7c49a0 100644 --- a/modules/prefix.py +++ b/modules/prefix.py @@ -16,37 +16,32 @@ from pyrogram import Client, filters from pyrogram.types import Message - from utils.db import db from utils.misc import modules_help, prefix from utils.scripts import restart -@Client.on_message( - filters.command(["sp", "setprefix"], prefix) & filters.me -) +@Client.on_message(filters.command(['sp', 'setprefix'], prefix) & filters.me) async def setprefix(_, message: Message): if len(message.command) > 1: pref = message.command[1] - db.set("core.main", "prefix", pref) - await message.edit( - f"Prefix [ {pref} ] is set!\nRestarting..." - ) + db.set('core.main', 'prefix', pref) + await message.edit(f'Prefix [ {pref} ] is set!\nRestarting...') db.set( - "core.updater", - "restart_info", + 'core.updater', + 'restart_info', { - "type": "restart", - "chat_id": message.chat.id, - "message_id": message.id, + 'type': 'restart', + 'chat_id': message.chat.id, + 'message_id': message.id, }, ) restart() else: - await message.edit("The prefix must not be empty!") + await message.edit('The prefix must not be empty!') -modules_help["prefix"] = { - "sp [prefix]": "Set custom prefix", - "setprefix [prefix]": "Set custom prefix", +modules_help['prefix'] = { + 'sp [prefix]': 'Set custom prefix', + 'setprefix [prefix]': 'Set custom prefix', } diff --git a/modules/purge.py b/modules/purge.py index ba435d8..7cb6059 100644 --- a/modules/purge.py +++ b/modules/purge.py @@ -15,20 +15,20 @@ # along with this program. If not, see . import asyncio + from pyrogram import Client, filters from pyrogram.types import Message - from utils.misc import modules_help, prefix from utils.scripts import with_reply -@Client.on_message(filters.command("del", prefix) & filters.me) +@Client.on_message(filters.command('del', prefix) & filters.me) async def del_msg(_, message: Message): await message.delete() await message.reply_to_message.delete() -@Client.on_message(filters.command("purge", prefix) & filters.me) +@Client.on_message(filters.command('purge', prefix) & filters.me) @with_reply async def purge(client: Client, message: Message): chunk = [] @@ -48,7 +48,7 @@ async def purge(client: Client, message: Message): await client.delete_messages(message.chat.id, chunk) -modules_help["purge"] = { - "purge [reply]": "Purge (delete all messages) chat from replied message to last", - "del [reply]": "Delete replied message", +modules_help['purge'] = { + 'purge [reply]': 'Purge (delete all messages) chat from replied message to last', + 'del [reply]': 'Delete replied message', } diff --git a/modules/python.py b/modules/python.py index ecaa181..a07d8a4 100644 --- a/modules/python.py +++ b/modules/python.py @@ -28,9 +28,7 @@ from utils.scripts import format_exc # noinspection PyUnusedLocal -@Client.on_message( - filters.command(["ex", "exec", "py", "exnoedit"], prefix) & filters.me -) +@Client.on_message(filters.command(['ex', 'exec', 'py', 'exnoedit'], prefix) & filters.me) async def user_exec(_: Client, message: Message): if len(message.command) == 1: await message.edit("Code to execute isn't provided") @@ -39,18 +37,13 @@ async def user_exec(_: Client, message: Message): code = message.text.split(maxsplit=1)[1] stdout = StringIO() - await message.edit("Executing...") + await message.edit('Executing...') try: with redirect_stdout(stdout): exec(code) # skipcq - text = ( - "Code:\n" - f"{code}\n\n" - "Result:\n" - f"{stdout.getvalue()}" - ) - if message.command[0] == "exnoedit": + text = f'Code:\n{code}\n\nResult:\n{stdout.getvalue()}' + if message.command[0] == 'exnoedit': await message.reply(text) else: await message.edit(text) @@ -59,7 +52,7 @@ async def user_exec(_: Client, message: Message): # noinspection PyUnusedLocal -@Client.on_message(filters.command(["ev", "eval"], prefix) & filters.me) +@Client.on_message(filters.command(['ev', 'eval'], prefix) & filters.me) async def user_eval(client: Client, message: Message): if len(message.command) == 1: await message.edit("Code to eval isn't provided") @@ -69,18 +62,13 @@ async def user_eval(client: Client, message: Message): try: result = eval(code) # skipcq - await message.edit( - "Expression:\n" - f"{code}\n\n" - "Result:\n" - f"{result}" - ) + await message.edit(f'Expression:\n{code}\n\nResult:\n{result}') except Exception as e: await message.edit(format_exc(e)) -modules_help["python"] = { - "ex [python code]": "Execute Python code", - "exnoedit [python code]": "Execute Python code and return result with reply", - "eval [python code]": "Eval Python code", +modules_help['python'] = { + 'ex [python code]': 'Execute Python code', + 'exnoedit [python code]': 'Execute Python code and return result with reply', + 'eval [python code]': 'Eval Python code', } diff --git a/modules/reactionspam.py b/modules/reactionspam.py index 3eeb1a5..0e38f6d 100644 --- a/modules/reactionspam.py +++ b/modules/reactionspam.py @@ -1,33 +1,33 @@ -import asyncio, random -from pyrogram import Client, filters, enums -from pyrogram.raw import functions +import random + +from pyrogram import Client, enums, filters from pyrogram.types import Message from utils.misc import modules_help, prefix from utils.scripts import format_exc emojis = [ - "👍", - "👎", - "❤️", - "🔥", - "🥰", - "👏", - "😁", - "🤔", - "🤯", - "😱", - "🤬", - "😢", - "🎉", - "🤩", - "🤮", - "💩", + '👍', + '👎', + '❤️', + '🔥', + '🥰', + '👏', + '😁', + '🤔', + '🤯', + '😱', + '🤬', + '😢', + '🎉', + '🤩', + '🤮', + '💩', ] -@Client.on_message(filters.command("reactspam", prefix) & filters.me) +@Client.on_message(filters.command('reactspam', prefix) & filters.me) async def reactspam(client: Client, message: Message): - await message.edit(f"One moment...", parse_mode=enums.ParseMode.HTML) + await message.edit('One moment...', parse_mode=enums.ParseMode.HTML) try: selected_emojis = random.sample(emojis, 3) print(selected_emojis) @@ -41,4 +41,4 @@ async def reactspam(client: Client, message: Message): return await message.edit_text(format_exc(e)) -modules_help["reactionspam"] = {"reactspam [amount]* [emoji]*": "spam reactions"} +modules_help['reactionspam'] = {'reactspam [amount]* [emoji]*': 'spam reactions'} diff --git a/modules/removebg.py b/modules/removebg.py index 535d56d..4e635e6 100644 --- a/modules/removebg.py +++ b/modules/removebg.py @@ -17,7 +17,6 @@ import requests from PIL import Image from pyrogram import Client, enums, filters from pyrogram.types import Message - from utils.config import rmbg_key from utils.misc import modules_help, prefix from utils.scripts import edit_or_reply, format_exc @@ -43,54 +42,51 @@ async def convert_to_image(message, client) -> None | str: if message.reply_to_message.photo: final_path = await message.reply_to_message.download() elif message.reply_to_message.sticker: - if message.reply_to_message.sticker.mime_type == "image/webp": - final_path = "webp_to_png_s_proton.png" + if message.reply_to_message.sticker.mime_type == 'image/webp': + final_path = 'webp_to_png_s_proton.png' path_s = await message.reply_to_message.download() im = Image.open(path_s) - im.save(final_path, "PNG") + im.save(final_path, 'PNG') else: path_s = await client.download_media(message.reply_to_message) - final_path = "lottie_proton.png" - cmd = ( - f"lottie_convert.py --frame 0 -if lottie -of png {path_s} {final_path}" - ) + final_path = 'lottie_proton.png' + cmd = f'lottie_convert.py --frame 0 -if lottie -of png {path_s} {final_path}' await exec(cmd) # skipcq elif message.reply_to_message.audio: thumb = message.reply_to_message.audio.thumbs[0].file_id final_path = await client.download_media(thumb) elif message.reply_to_message.video or message.reply_to_message.animation: - final_path = "fetched_thumb.png" + final_path = 'fetched_thumb.png' vid_path = await client.download_media(message.reply_to_message) - await exec( - f"ffmpeg -i {vid_path} -filter:v scale=500:500 -an {final_path}" - ) # skipcq + await exec(f'ffmpeg -i {vid_path} -filter:v scale=500:500 -an {final_path}') # skipcq elif message.reply_to_message.document: - if message.reply_to_message.document.mime_type == "image/jpeg": + if ( + message.reply_to_message.document.mime_type == 'image/jpeg' + or message.reply_to_message.document.mime_type == 'image/png' + ): final_path = await message.reply_to_message.download() - elif message.reply_to_message.document.mime_type == "image/png": - final_path = await message.reply_to_message.download() - elif message.reply_to_message.document.mime_type == "image/webp": - final_path = "webp_to_png_s_proton.png" + elif message.reply_to_message.document.mime_type == 'image/webp': + final_path = 'webp_to_png_s_proton.png' path_s = await message.reply_to_message.download() im = Image.open(path_s) - im.save(final_path, "PNG") + im.save(final_path, 'PNG') else: return None return final_path def remove_background(photo_data): - with open(photo_data, "rb") as image_file: + with open(photo_data, 'rb') as image_file: image_data = image_file.read() response = requests.post( - "https://api.remove.bg/v1.0/removebg", - files={"image_file": image_data}, - data={"size": "auto"}, - headers={"X-Api-Key": rmbg_key}, + 'https://api.remove.bg/v1.0/removebg', + files={'image_file': image_data}, + data={'size': 'auto'}, + headers={'X-Api-Key': rmbg_key}, ) if response.status_code == 200: return BytesIO(response.content) - print("Error:", response.status_code, response.text) + print('Error:', response.status_code, response.text) return None @@ -108,26 +104,26 @@ def _check_rmbg(func): return check_rmbg -@Client.on_message(filters.command("rmbg", prefix) & filters.me) +@Client.on_message(filters.command('rmbg', prefix) & filters.me) @_check_rmbg async def rmbg(client: Client, message: Message): - pablo = await edit_or_reply(message, "Processing...") + pablo = await edit_or_reply(message, 'Processing...') if not message.reply_to_message: - await pablo.edit("Reply To A Image Please!") + await pablo.edit('Reply To A Image Please!') return cool = await convert_to_image(message, client) if not cool: - await pablo.edit("Reply to a valid media first.") + await pablo.edit('Reply to a valid media first.') return start = datetime.now() - await pablo.edit("sending to ReMove.BG") + await pablo.edit('sending to ReMove.BG') input_file_name = cool files = { - "image_file": (input_file_name, open(input_file_name, "rb")), + 'image_file': (input_file_name, open(input_file_name, 'rb')), } r = requests.post( - "https://api.remove.bg/v1.0/removebg", - headers={"X-Api-Key": rmbg_key}, + 'https://api.remove.bg/v1.0/removebg', + headers={'X-Api-Key': rmbg_key}, files=files, allow_redirects=True, stream=True, @@ -135,30 +131,23 @@ async def rmbg(client: Client, message: Message): if os.path.exists(cool): os.remove(cool) output_file_name = r - contentType = output_file_name.headers.get("content-type") - if "image" in contentType: + contentType = output_file_name.headers.get('content-type') + if 'image' in contentType: with io.BytesIO(output_file_name.content) as remove_bg_image: - remove_bg_image.name = "BG_rem.png" - await client.send_document( - message.chat.id, remove_bg_image, reply_to_message_id=message.id - ) + remove_bg_image.name = 'BG_rem.png' + await client.send_document(message.chat.id, remove_bg_image, reply_to_message_id=message.id) end = datetime.now() ms = (end - start).seconds - await pablo.edit( - f"Removed image's Background in {ms} seconds." - ) - if os.path.exists("BG_rem.png"): - os.remove("BG_rem.png") + await pablo.edit(f"Removed image's Background in {ms} seconds.") + if os.path.exists('BG_rem.png'): + os.remove('BG_rem.png') else: - await pablo.edit( - "ReMove.BG API returned Errors." - + f"\n`{output_file_name.content.decode('UTF-8')}" - ) + await pablo.edit('ReMove.BG API returned Errors.' + f'\n`{output_file_name.content.decode("UTF-8")}') -@Client.on_message(filters.command("rebg", prefix) & filters.me) +@Client.on_message(filters.command('rebg', prefix) & filters.me) async def rembg(client: Client, message: Message): - await message.edit("Processing...") + await message.edit('Processing...') chat_id = message.chat.id try: try: @@ -167,28 +156,26 @@ async def rembg(client: Client, message: Message): try: photo_data = await message.reply_to_message.download() except ValueError: - await message.edit("File not found") + await message.edit('File not found') return background_removed_data = remove_background(photo_data) if background_removed_data: await message.delete() - await client.send_photo( - chat_id, photo=background_removed_data, caption="Background removed!" - ) + await client.send_photo(chat_id, photo=background_removed_data, caption='Background removed!') else: await message.edit_text( "`Is Your RMBG Api 'rmbg_key' Valid Or You Didn't Add It??`\n **Check logs for details**", parse_mode=enums.ParseMode.MARKDOWN, ) except Exception as e: - await message.reply_text(f"An error occurred: {format_exc(e)}") + await message.reply_text(f'An error occurred: {format_exc(e)}') finally: if os.path.exists(photo_data): os.remove(photo_data) -modules_help["removebg"] = { - "rebg [reply to image]*": "remove background from image without transparency", - "rmbg [reply to image]*": "remove background from image with transparency", +modules_help['removebg'] = { + 'rebg [reply to image]*': 'remove background from image without transparency', + 'rmbg [reply to image]*': 'remove background from image with transparency', } diff --git a/modules/say.py b/modules/say.py index 6ccd962..a38668d 100644 --- a/modules/say.py +++ b/modules/say.py @@ -20,18 +20,17 @@ from pyrogram import Client, filters from pyrogram.types import Message - from utils.misc import modules_help, prefix -@Client.on_message(filters.command(["say", "s"], prefix) & filters.me) +@Client.on_message(filters.command(['say', 's'], prefix) & filters.me) async def say(_, message: Message): if len(message.command) == 1: return - command = " ".join(message.command[1:]) - await message.edit(f"{command}") + command = ' '.join(message.command[1:]) + await message.edit(f'{command}') -modules_help["say"] = { - "say [command]*": "Send message that won't be interpreted by userbot", +modules_help['say'] = { + 'say [command]*': "Send message that won't be interpreted by userbot", } diff --git a/modules/sendmod.py b/modules/sendmod.py index b989164..b56adb7 100644 --- a/modules/sendmod.py +++ b/modules/sendmod.py @@ -18,41 +18,38 @@ import os from pyrogram import Client, filters from pyrogram.types import Message - from utils.misc import modules_help, prefix from utils.scripts import format_exc, format_module_help, format_small_module_help -@Client.on_message(filters.command(["sendmod", "sm"], prefix) & filters.me) +@Client.on_message(filters.command(['sendmod', 'sm'], prefix) & filters.me) async def sendmod(client: Client, message: Message): if len(message.command) == 1: - await message.edit("Module name to send is not provided") + await message.edit('Module name to send is not provided') return - await message.edit("Dispatching...") + await message.edit('Dispatching...') try: module_name = message.command[1].lower() if module_name in modules_help: text = format_module_help(module_name) if len(text) >= 1024: text = format_small_module_help(module_name) - if os.path.isfile(f"modules/{module_name}.py"): - await client.send_document( - message.chat.id, f"modules/{module_name}.py", caption=text - ) - elif os.path.isfile(f"modules/custom_modules/{module_name.lower()}.py"): + if os.path.isfile(f'modules/{module_name}.py'): + await client.send_document(message.chat.id, f'modules/{module_name}.py', caption=text) + elif os.path.isfile(f'modules/custom_modules/{module_name.lower()}.py'): await client.send_document( message.chat.id, - f"modules/custom_modules/{module_name}.py", + f'modules/custom_modules/{module_name}.py', caption=text, ) await message.delete() else: - await message.edit(f"Module {module_name} not found!") + await message.edit(f'Module {module_name} not found!') except Exception as e: await message.edit(format_exc(e)) -modules_help["sendmod"] = { - "sendmod [module_name]": "Send module to interlocutor", +modules_help['sendmod'] = { + 'sendmod [module_name]': 'Send module to interlocutor', } diff --git a/modules/sessionkiller.py b/modules/sessionkiller.py index 053d310..fecde80 100644 --- a/modules/sessionkiller.py +++ b/modules/sessionkiller.py @@ -20,137 +20,128 @@ import time from datetime import datetime from html import escape -from pyrogram import Client, filters -from pyrogram import ContinuePropagation +from pyrogram import Client, ContinuePropagation, filters from pyrogram.errors import RPCError from pyrogram.raw.functions.account import GetAuthorizations, ResetAuthorization from pyrogram.raw.types import UpdateServiceNotification from pyrogram.types import Message - from utils.db import db from utils.misc import modules_help, prefix -auth_hashes = db.get("core.sessionkiller", "auths_hashes", []) +auth_hashes = db.get('core.sessionkiller', 'auths_hashes', []) -@Client.on_message(filters.command(["sessions"], prefix) & filters.me) +@Client.on_message(filters.command(['sessions'], prefix) & filters.me) async def sessions_list(client: Client, message: Message): formatted_sessions = [] sessions = (await client.invoke(GetAuthorizations())).authorizations for num, session in enumerate(sessions, 1): formatted_sessions.append( - f"{num}. {escape(session.device_model)} on {escape(session.platform if session.platform != '' else 'unknown platform')}\n" - f"Hash: {session.hash}\n" - f"App name: {escape(session.app_name)} v.{escape(session.app_version if session.app_version != '' else 'unknown')}\n" - f"Created (last activity): {datetime.fromtimestamp(session.date_created).isoformat()} ({datetime.fromtimestamp(session.date_active).isoformat()})\n" - f"IP and location: : {session.ip} ({session.country})\n" - f"Official status: {'✅' if session.official_app else '❌️'}\n" - f"2FA accepted: {'❌️️' if session.password_pending else '✅'}\n" - f"Can accept calls / secret chats: {'❌️️' if session.call_requests_disabled else '✅'} / {'❌️️' if session.encrypted_requests_disabled else '✅'}" + f'{num}. {escape(session.device_model)} on {escape(session.platform if session.platform != "" else "unknown platform")}\n' + f'Hash: {session.hash}\n' + f'App name: {escape(session.app_name)} v.{escape(session.app_version if session.app_version != "" else "unknown")}\n' + f'Created (last activity): {datetime.fromtimestamp(session.date_created).isoformat()} ({datetime.fromtimestamp(session.date_active).isoformat()})\n' + f'IP and location: : {session.ip} ({session.country})\n' + f'Official status: {"✅" if session.official_app else "❌️"}\n' + f'2FA accepted: {"❌️️" if session.password_pending else "✅"}\n' + f'Can accept calls / secret chats: {"❌️️" if session.call_requests_disabled else "✅"} / {"❌️️" if session.encrypted_requests_disabled else "✅"}' ) - answer = "Active sessions at your account:\n\n" + answer = 'Active sessions at your account:\n\n' chunk = [] for s in formatted_sessions: chunk.append(s) if len(chunk) == 5: - answer += "\n\n".join(chunk) + answer += '\n\n'.join(chunk) await message.reply(answer) - answer = "" + answer = '' chunk.clear() if chunk: - await message.reply("\n\n".join(chunk)) + await message.reply('\n\n'.join(chunk)) await message.delete() -@Client.on_message(filters.command(["sessionkiller", "sk"], prefix) & filters.me) +@Client.on_message(filters.command(['sessionkiller', 'sk'], prefix) & filters.me) async def sessionkiller(client: Client, message: Message): if len(message.command) == 1: - if db.get("core.sessionkiller", "enabled", False): + if db.get('core.sessionkiller', 'enabled', False): await message.edit( - "Sessionkiller status: enabled\n" - f"You can disable it with {prefix}sessionkiller disable" + 'Sessionkiller status: enabled\n' + f'You can disable it with {prefix}sessionkiller disable' ) else: await message.edit( - "Sessionkiller status: disabled\n" - f"You can enable it with {prefix}sessionkiller enable" + 'Sessionkiller status: disabled\n' + f'You can enable it with {prefix}sessionkiller enable' ) - elif message.command[1] in ["enable", "on", "1", "yes", "true"]: - db.set("core.sessionkiller", "enabled", True) - await message.edit("Sessionkiller enabled!") + elif message.command[1] in ['enable', 'on', '1', 'yes', 'true']: + db.set('core.sessionkiller', 'enabled', True) + await message.edit('Sessionkiller enabled!') db.set( - "core.sessionkiller", - "auths_hashes", - [ - auth.hash - for auth in (await client.invoke(GetAuthorizations())).authorizations - ], + 'core.sessionkiller', + 'auths_hashes', + [auth.hash for auth in (await client.invoke(GetAuthorizations())).authorizations], ) - elif message.command[1] in ["disable", "off", "0", "no", "false"]: - db.set("core.sessionkiller", "enabled", False) - await message.edit("Sessionkiller disabled!") + elif message.command[1] in ['disable', 'off', '0', 'no', 'false']: + db.set('core.sessionkiller', 'enabled', False) + await message.edit('Sessionkiller disabled!') else: - await message.edit(f"Usage: {prefix}sessionkiller [enable|disable]") + await message.edit(f'Usage: {prefix}sessionkiller [enable|disable]') @Client.on_raw_update() async def check_new_login(client: Client, update: UpdateServiceNotification, _, __): - if not isinstance(update, UpdateServiceNotification) or not update.type.startswith( - "auth" - ): + if not isinstance(update, UpdateServiceNotification) or not update.type.startswith('auth'): raise ContinuePropagation - if not db.get("core.sessionkiller", "enabled", False): + if not db.get('core.sessionkiller', 'enabled', False): raise ContinuePropagation - authorizations = (await client.invoke(GetAuthorizations()))["authorizations"] + authorizations = (await client.invoke(GetAuthorizations()))['authorizations'] for auth in authorizations: if auth.current: continue - if auth["hash"] not in auth_hashes: + if auth['hash'] not in auth_hashes: # found new unexpected login try: await client.invoke(ResetAuthorization(hash=auth.hash)) except RPCError: info_text = ( - "Someone tried to log in to your account. You can see this report because you" + 'Someone tried to log in to your account. You can see this report because you' "turned on this feature. But I couldn't terminate attacker's session and " - "⚠ you must reset it manually. You should change your 2FA password " - "(if enabled), or set it.\n" + '⚠ you must reset it manually. You should change your 2FA password ' + '(if enabled), or set it.\n' ) else: info_text = ( - "Someone tried to log in to your account. Since you have enabled " + 'Someone tried to log in to your account. Since you have enabled ' "this feature, I deleted the attacker's session from your account. " - "You should change your 2FA password (if enabled), or set it.\n" + 'You should change your 2FA password (if enabled), or set it.\n' ) - logined_time = datetime.utcfromtimestamp(auth.date_created).strftime( - "%d-%m-%Y %H-%M-%S UTC" - ) + logined_time = datetime.utcfromtimestamp(auth.date_created).strftime('%d-%m-%Y %H-%M-%S UTC') full_report = ( - "!!! ACTION REQUIRED !!!\n" + '!!! ACTION REQUIRED !!!\n' + info_text - + "Below is the information about the attacker that I got.\n\n" - f"Unique authorization hash: {auth.hash} (not valid anymore)\n" - f"Device model: {escape(auth.device_model)}\n" - f"Platform: {escape(auth.platform)}\n" - f"API ID: {auth.api_id}\n" - f"App name: {escape(auth.app_name)}\n" - f"App version: {auth.app_version}\n" - f"Logined at: {logined_time}\n" - f"IP: {auth.ip}\n" - f"Country: {auth.country}\n" - f"Official app: {'yes' if auth.official_app else 'no'}\n\n" - f"It is you? Type {prefix}sk off and try logging " - f"in again." + + 'Below is the information about the attacker that I got.\n\n' + f'Unique authorization hash: {auth.hash} (not valid anymore)\n' + f'Device model: {escape(auth.device_model)}\n' + f'Platform: {escape(auth.platform)}\n' + f'API ID: {auth.api_id}\n' + f'App name: {escape(auth.app_name)}\n' + f'App version: {auth.app_version}\n' + f'Logined at: {logined_time}\n' + f'IP: {auth.ip}\n' + f'Country: {auth.country}\n' + f'Official app: {"yes" if auth.official_app else "no"}\n\n' + f'It is you? Type {prefix}sk off and try logging ' + f'in again.' ) # schedule sending report message so user will get notification schedule_date = int(time.time() + 15) - await client.send_message("me", full_report, schedule_date=schedule_date) + await client.send_message('me', full_report, schedule_date=schedule_date) return -modules_help["sessions"] = { - "sessionkiller [enable|disable]": "When enabled, every new session will be terminated.\n" - "Useful for additional protection for your account", - "sessions": "List all sessions on your account", +modules_help['sessions'] = { + 'sessionkiller [enable|disable]': 'When enabled, every new session will be terminated.\n' + 'Useful for additional protection for your account', + 'sessions': 'List all sessions on your account', } diff --git a/modules/sgb.py b/modules/sgb.py index ceada81..fb27e7e 100644 --- a/modules/sgb.py +++ b/modules/sgb.py @@ -18,35 +18,34 @@ from pyrogram import Client, filters from pyrogram.errors import YouBlockedUser from pyrogram.types import Message - from utils.conv import Conversation from utils.misc import modules_help, prefix from utils.scripts import format_exc -@Client.on_message(filters.command("sgb", prefix) & filters.me) +@Client.on_message(filters.command('sgb', prefix) & filters.me) async def sg(client: Client, message: Message): if message.reply_to_message and message.reply_to_message.from_user: user_id = message.reply_to_message.from_user.id else: - await message.edit(f"Usage: {prefix}sgb [id]") + await message.edit(f'Usage: {prefix}sgb [id]') return try: - await message.edit("Processing please wait") - bot_username = "@SangMata_beta_bot" + await message.edit('Processing please wait') + bot_username = '@SangMata_beta_bot' async with Conversation(client, bot_username, timeout=15) as conv: await conv.send_message(str(user_id)) response = await conv.get_response(timeout=10) - if "you have used up your quota" in response.text: + if 'you have used up your quota' in response.text: await message.edit(response.text.splitlines()[0]) return return await message.edit(response.text) except YouBlockedUser: - await message.edit("Please unblock @SangMata_beta_bot first.") + await message.edit('Please unblock @SangMata_beta_bot first.') except TimeoutError: - await message.edit("No response from bot within the timeout period.") + await message.edit('No response from bot within the timeout period.') except Exception as e: - await message.edit(f"Error: {format_exc(e)}") + await message.edit(f'Error: {format_exc(e)}') -modules_help["sangmata"] = {"sgb": "reply to any user"} +modules_help['sangmata'] = {'sgb': 'reply to any user'} diff --git a/modules/shell.py b/modules/shell.py index d7f5b9b..82602f7 100644 --- a/modules/shell.py +++ b/modules/shell.py @@ -14,21 +14,20 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from subprocess import Popen, PIPE, TimeoutExpired import os +from subprocess import PIPE, Popen, TimeoutExpired from time import perf_counter from pyrogram import Client, filters -from pyrogram.types import Message from pyrogram.errors import MessageTooLong - +from pyrogram.types import Message from utils.misc import modules_help, prefix -@Client.on_message(filters.command(["shell", "sh"], prefix) & filters.me) +@Client.on_message(filters.command(['shell', 'sh'], prefix) & filters.me) async def shell(_, message: Message): if len(message.command) < 2: - return await message.edit("Specify the command in message text") + return await message.edit('Specify the command in message text') cmd_text = message.text.split(maxsplit=1)[1] cmd_args = cmd_text.split() cmd_obj = Popen( @@ -38,22 +37,22 @@ async def shell(_, message: Message): text=True, ) - char = "#" if os.getuid() == 0 else "$" - text = f"{char} {cmd_text}\n\n" + char = '#' if os.getuid() == 0 else '$' + text = f'{char} {cmd_text}\n\n' - await message.edit(text + "Running...") + await message.edit(text + 'Running...') try: start_time = perf_counter() stdout, stderr = cmd_obj.communicate(timeout=60) except TimeoutExpired: - text += "Timeout expired (60 seconds)" + text += 'Timeout expired (60 seconds)' else: stop_time = perf_counter() if stdout: - text += f"Output:\n{stdout}\n\n" + text += f'Output:\n{stdout}\n\n' if stderr: - text += f"Error:\n{stderr}\n\n" - text += f"Completed in {round(stop_time - start_time, 5)} seconds with code {cmd_obj.returncode}" + text += f'Error:\n{stderr}\n\n' + text += f'Completed in {round(stop_time - start_time, 5)} seconds with code {cmd_obj.returncode}' try: await message.edit(text) except MessageTooLong: @@ -61,4 +60,4 @@ async def shell(_, message: Message): cmd_obj.kill() -modules_help["shell"] = {"sh [command]*": "Execute command in shell"} +modules_help['shell'] = {'sh [command]*': 'Execute command in shell'} diff --git a/modules/socialstalk.py b/modules/socialstalk.py index 1be08cb..62e6641 100644 --- a/modules/socialstalk.py +++ b/modules/socialstalk.py @@ -1,75 +1,73 @@ -from datetime import datetime +import io import json +from datetime import datetime + import requests from pyrogram import Client, enums, filters from pyrogram.types import Message -import io - from utils.misc import modules_help, prefix -TIKTOK_API_URL = "https://api.maher-zubair.tech/stalk/tiktok?q=" -INSTAGRAM_API_URL = "https://tools.betabotz.eu.org/tools/stalk-ig?q=" -GH_STALK = "https://api.github.com/users/" +TIKTOK_API_URL = 'https://api.maher-zubair.tech/stalk/tiktok?q=' +INSTAGRAM_API_URL = 'https://tools.betabotz.eu.org/tools/stalk-ig?q=' +GH_STALK = 'https://api.github.com/users/' -@Client.on_message(filters.command(["tiktokstalk"], prefix) & filters.me) +@Client.on_message(filters.command(['tiktokstalk'], prefix) & filters.me) async def tiktok_stalk(_, message: Message): - query = "" + query = '' if len(message.command) > 1: query = message.command[1] elif message.reply_to_message: query = message.reply_to_message.text.strip() if not query: - await message.edit( - "Usage: `tiktokstalk ` or reply to a message containing the username." - ) + await message.edit('Usage: `tiktokstalk ` or reply to a message containing the username.') return - await message.edit("Fetching TikTok profile...") - url = f"{TIKTOK_API_URL}{query}" + await message.edit('Fetching TikTok profile...') + url = f'{TIKTOK_API_URL}{query}' response = requests.get(url) if response.status_code == 200: - data = response.json().get("result", {}) + data = response.json().get('result', {}) if data: - profile_pic_url = data.get("profile", "") + profile_pic_url = data.get('profile', '') profile_pic = requests.get(profile_pic_url).content profile_pic_stream = io.BytesIO(profile_pic) - profile_pic_stream.name = "profile.jpg" + profile_pic_stream.name = 'profile.jpg' await message.reply_photo( photo=profile_pic_stream, caption=( - f"TikTok Profile:\n" - f"Name: {data.get('name', 'N/A')}\n" - f"Username: {data.get('username', 'N/A')}\n" - f"
Followers: {data.get('followers', 'N/A')}\n" - f"Following: {data.get('following', 'N/A')}\n" - f"Likes: {data.get('likes', 'N/A')}\n" - f"Description: {data.get('desc', 'N/A')}\n" - f"Bio: {data.get('bio', 'N/A')}" + f'TikTok Profile:\n' + f'Name: {data.get("name", "N/A")}\n' + f'Username: {data.get("username", "N/A")}\n' + f'Followers: {data.get("followers", "N/A")}\n' + f'Following: {data.get("following", "N/A")}\n' + f'Likes: {data.get("likes", "N/A")}\n' + f'Description: {data.get("desc", "N/A")}\n' + f'Bio: {data.get("bio", "N/A")}' ), parse_mode=enums.ParseMode.MARKDOWN, ) else: - await message.edit("No data found for this TikTok user.") + await message.edit('No data found for this TikTok user.') await message.delete() else: - await message.edit("An error occurred, please try again later.") + await message.edit('An error occurred, please try again later.') -@Client.on_message(filters.command("ipinfo", prefix) & filters.me) +@Client.on_message(filters.command('ipinfo', prefix) & filters.me) async def ipinfo(_, message: Message): - searchip = message.text.split(" ", 1) + searchip = message.text.split(' ', 1) if len(searchip) == 1: - await message.edit_text(f"Usage:{prefix}ipinfo [ip]") + await message.edit_text(f'Usage:{prefix}ipinfo [ip]') return searchip = searchip[1] - m = await message.edit_text("Searching...") - await m.edit_text("🔎") + m = await message.edit_text('Searching...') + await m.edit_text('🔎') try: url = requests.get( - f"http://ip-api.com/json/{searchip}?fields=status,message,continent,continentCode,country,countryCode,region,regionName,city,district,zip,lat,lon,timezone,offset,currency,isp,org,as,asname,reverse,mobile,proxy,hosting,query" + f'http://ip-api.com/json/{searchip}?fields=status,message,continent,continentCode,country,countryCode,region,regionName,city,district,zip,lat,lon,timezone,offset,currency,isp,org,as,asname,reverse,mobile,proxy,hosting,query' ) response = json.loads(url.text) text = f""" @@ -98,10 +96,10 @@ async def ipinfo(_, message: Message): Hosting: {response['hosting']}""" await m.edit_text(text) except: - await m.edit_text("Unable To Find Info!") + await m.edit_text('Unable To Find Info!') -@Client.on_message(filters.command("instastalk", prefix) & filters.me) +@Client.on_message(filters.command('instastalk', prefix) & filters.me) async def instagram_stalk(_, message: Message): if len(message.command) > 1: query = message.text.split(maxsplit=1)[1] @@ -109,43 +107,43 @@ async def instagram_stalk(_, message: Message): query = message.reply_to_message.text else: await message.edit( - f"Usage: {prefix}instastalk or reply to a message containing the username." + f'Usage: {prefix}instastalk or reply to a message containing the username.' ) return - await message.edit("Fetching Instagram profile...") - url = f"{INSTAGRAM_API_URL}{query}" + await message.edit('Fetching Instagram profile...') + url = f'{INSTAGRAM_API_URL}{query}' response = requests.get(url) if response.status_code == 200: - data = response.json().get("result", {}).get("user_info", {}) + data = response.json().get('result', {}).get('user_info', {}) if data: - profile_pic_url = data.get("profile_pic_url", "") + profile_pic_url = data.get('profile_pic_url', '') profile_pic = requests.get(profile_pic_url).content profile_pic_stream = io.BytesIO(profile_pic) - profile_pic_stream.name = "profile.jpg" + profile_pic_stream.name = 'profile.jpg' await message.reply_photo( photo=profile_pic_stream, caption=( - f"Instagram Profile:\n" - f"Full Name: {data.get('full_name', 'N/A')}\n" - f"Username: {data.get('username', 'N/A')}\n" - f"Biography: {data.get('biography', 'N/A')}\n" - f"External URL: {data.get('external_url', 'N/A')}\n" - f"Posts: {data.get('posts', 'N/A')}\n" - f"Followers: {data.get('followers', 'N/A')}\n" - f"Following: {data.get('following', 'N/A')}" + f'Instagram Profile:\n' + f'Full Name: {data.get("full_name", "N/A")}\n' + f'Username: {data.get("username", "N/A")}\n' + f'Biography: {data.get("biography", "N/A")}\n' + f'External URL: {data.get("external_url", "N/A")}\n' + f'Posts: {data.get("posts", "N/A")}\n' + f'Followers: {data.get("followers", "N/A")}\n' + f'Following: {data.get("following", "N/A")}' ), parse_mode=enums.ParseMode.MARKDOWN, ) else: - await message.edit("No data found for this Instagram user.") + await message.edit('No data found for this Instagram user.') await message.delete() else: - await message.edit("An error occurred, please try again later.") + await message.edit('An error occurred, please try again later.') -@Client.on_message(filters.command("ghstalk", prefix) & filters.me) +@Client.on_message(filters.command('ghstalk', prefix) & filters.me) async def github_stalk(_, message: Message): if len(message.command) > 1: query = message.text.split(maxsplit=1)[1] @@ -153,48 +151,44 @@ async def github_stalk(_, message: Message): query = message.reply_to_message.text else: await message.edit( - f"Usage: {prefix}ghstalk or reply to a message containing the username." + f'Usage: {prefix}ghstalk or reply to a message containing the username.' ) return - await message.edit("Fetching GitHub profile...") - url = f"{GH_STALK}{query}" + await message.edit('Fetching GitHub profile...') + url = f'{GH_STALK}{query}' response = requests.get(url) if response.status_code == 200: data = response.json() - created_at = data.get("created_at", "N/A") - formatted_date = ( - datetime.strptime(created_at, "%Y-%m-%dT%H:%M:%S%z") - if created_at != "N/A" - else None - ) + created_at = data.get('created_at', 'N/A') + formatted_date = datetime.strptime(created_at, '%Y-%m-%dT%H:%M:%S%z') if created_at != 'N/A' else None if data: await message.reply_photo( - photo=data.get("avatar_url", "").replace("?v=4", ""), - caption=f"GitHub Profile:\n" - f"Name: {data.get('name', 'N/A')}\n" - f"Username: {data.get('login', 'N/A')}\n" - f"Bio: {data.get('bio', 'N/A')}\n" + photo=data.get('avatar_url', '').replace('?v=4', ''), + caption=f'GitHub Profile:\n' + f'Name: {data.get("name", "N/A")}\n' + f'Username: {data.get("login", "N/A")}\n' + f'Bio: {data.get("bio", "N/A")}\n' f"Public Repositories: {data.get('public_repos', 'N/A')}\n" f"Public Gists: {data.get('public_gists', 'N/A')}\n" - f"Company: {data.get('company', 'N/A')}\n" - f"Location: {data.get('location', 'N/A')}\n" - f"Email: {data.get('email', 'N/A')}\n" - f"Website: {data.get('blog', 'N/A')}\n" - f"Created At: {formatted_date.strftime('%Y-%m-%d %I:%M:%S %p') if formatted_date else 'N/A'}\n" - f"Hireable: {data.get('hireable', 'N/A')}\n" - f"Followers: {data.get('followers', 'N/A')}\n" - f"Following: {data.get('following', 'N/A')}", + f'Company: {data.get("company", "N/A")}\n' + f'Location: {data.get("location", "N/A")}\n' + f'Email: {data.get("email", "N/A")}\n' + f'Website: {data.get("blog", "N/A")}\n' + f'Created At: {formatted_date.strftime("%Y-%m-%d %I:%M:%S %p") if formatted_date else "N/A"}\n' + f'Hireable: {data.get("hireable", "N/A")}\n' + f'Followers: {data.get("followers", "N/A")}\n' + f'Following: {data.get("following", "N/A")}', ) else: - await message.edit("No data found for this GitHub user.") + await message.edit('No data found for this GitHub user.') else: - await message.edit("An error occurred, please try again later.") + await message.edit('An error occurred, please try again later.') -modules_help["socialstalk"] = { - "tiktokstalk [username]*": "Get TikTok profile information", - "ipinfo [IP address]*": "Get information about an IP address", - "instastalk [username]*": "Get Instagram profile information", - "ghstalk [username]*": "Get GitHub profile information", +modules_help['socialstalk'] = { + 'tiktokstalk [username]*': 'Get TikTok profile information', + 'ipinfo [IP address]*': 'Get information about an IP address', + 'instastalk [username]*': 'Get Instagram profile information', + 'ghstalk [username]*': 'Get GitHub profile information', } diff --git a/modules/spam.py b/modules/spam.py index deb8431..69582a9 100644 --- a/modules/spam.py +++ b/modules/spam.py @@ -18,18 +18,17 @@ import asyncio from pyrogram import Client, filters from pyrogram.types import Message - from utils.misc import modules_help, prefix -commands = ["spam", "statspam", "slowspam", "fastspam"] +commands = ['spam', 'statspam', 'slowspam', 'fastspam'] @Client.on_message(filters.command(commands, prefix) & filters.me) async def spam(client: Client, message: Message): amount = int(message.command[1]) - text = " ".join(message.command[2:]) + text = ' '.join(message.command[2:]) - cooldown = {"spam": 0.15, "statspam": 0.1, "slowspam": 0.9, "fastspam": 0} + cooldown = {'spam': 0.15, 'statspam': 0.1, 'slowspam': 0.9, 'fastspam': 0} await message.delete() @@ -39,16 +38,16 @@ async def spam(client: Client, message: Message): else: sent = await client.send_message(message.chat.id, text) - if message.command[0] == "statspam": + if message.command[0] == 'statspam': await asyncio.sleep(0.1) await sent.delete() await asyncio.sleep(cooldown[message.command[0]]) -modules_help["spam"] = { - "spam [amount] [text]": "Start spam", - "statspam [amount] [text]": "Send and delete", - "fastspam [amount] [text]": "Start fast spam", - "slowspam [amount] [text]": "Start slow spam", +modules_help['spam'] = { + 'spam [amount] [text]': 'Start spam', + 'statspam [amount] [text]': 'Send and delete', + 'fastspam [amount] [text]': 'Start fast spam', + 'slowspam [amount] [text]': 'Start slow spam', } diff --git a/modules/spin.py b/modules/spin.py index d7e8e1b..e3c942b 100644 --- a/modules/spin.py +++ b/modules/spin.py @@ -13,19 +13,17 @@ from pyrogram.types import Message from utils.misc import modules_help, prefix from utils.scripts import format_exc, import_library, resize_image -Image = import_library("PIL", "pillow").Image -np = import_library("numpy") -imageio = import_library("imageio") +Image = import_library('PIL', 'pillow').Image +np = import_library('numpy') +imageio = import_library('imageio') -def create_gif(filename: str, offset: int, fps: int = 2, typ: str = "spin"): - img = Image.open(f"downloads/{filename}") - if typ.lower() != "spin": - img = img.resize( - (random.randint(200, 1280), random.randint(200, 1280)), Image.ANTIALIAS - ) +def create_gif(filename: str, offset: int, fps: int = 2, typ: str = 'spin'): + img = Image.open(f'downloads/{filename}') + if typ.lower() != 'spin': + img = img.resize((random.randint(200, 1280), random.randint(200, 1280)), Image.ANTIALIAS) imageio.mimsave( - "downloads/video.gif", + 'downloads/video.gif', [img.rotate(-(i % 360)) for i in range(1, 361, offset)], fps=fps, ) @@ -40,9 +38,7 @@ async def quote_cmd(client: Client, message: types.Message): messages = [] - async for msg in client.get_chat_history( - message.chat.id, offset_id=message.reply_to_message.id, reverse=True - ): + async for msg in client.get_chat_history(message.chat.id, offset_id=message.reply_to_message.id, reverse=True): if msg.empty: continue if msg.message_id >= message.id: @@ -57,102 +53,86 @@ async def quote_cmd(client: Client, message: types.Message): if send_for_me: await message.delete() - message = await client.send_message( - "me", "Generating...", parse_mode=enums.ParseMode.HTML - ) + message = await client.send_message('me', 'Generating...', parse_mode=enums.ParseMode.HTML) else: - await message.edit("Generating...", parse_mode=enums.ParseMode.HTML) + await message.edit('Generating...', parse_mode=enums.ParseMode.HTML) - url = "https://quotes.fl1yd.su/generate" + url = 'https://quotes.fl1yd.su/generate' params = { - "messages": [ - await render_message(client, msg) for msg in messages if not msg.empty - ], - "quote_color": "#162330", - "text_color": "#fff", + 'messages': [await render_message(client, msg) for msg in messages if not msg.empty], + 'quote_color': '#162330', + 'text_color': '#fff', } response = await aiohttp.ClientSession().post(url, json=params) if response.status != 200: return await message.edit( - f"Quotes API error!\n" f"{response.text}", + f'Quotes API error!\n{response.text}', parse_mode=enums.ParseMode.HTML, ) - resized = resize_image( - BytesIO(await response.read()), img_type="PNG" if is_png else "WEBP" - ) + resized = resize_image(BytesIO(await response.read()), img_type='PNG' if is_png else 'WEBP') return resized, is_png -@Client.on_message(filters.command(["spin", "dspin"], prefix) & filters.me) +@Client.on_message(filters.command(['spin', 'dspin'], prefix) & filters.me) async def spin_handler(client: Client, message: Message): if not message.reply_to_message: await message.edit( - "Reply to a message to spin it!", + 'Reply to a message to spin it!', parse_mode=enums.ParseMode.HTML, ) - await message.edit( - "Downloading sticker...", parse_mode=enums.ParseMode.HTML - ) - return await message.edit( - "Invalid file type!", parse_mode=enums.ParseMode.HTML - ) + await message.edit('Downloading sticker...', parse_mode=enums.ParseMode.HTML) + return await message.edit('Invalid file type!', parse_mode=enums.ParseMode.HTML) try: coro = True if message.reply_to_message.document: filename = message.reply_to_message.document.file_name if ( - not filename.endswith(".webp") - and not filename.endswith(".png") - and not filename.endswith(".jpg") - and not filename.endswith(".jpeg") + not filename.endswith('.webp') + and not filename.endswith('.png') + and not filename.endswith('.jpg') + and not filename.endswith('.jpeg') ): - return await message.edit( - "Invalid file type!", parse_mode=enums.ParseMode.HTML - ) + return await message.edit('Invalid file type!', parse_mode=enums.ParseMode.HTML) elif message.reply_to_message.sticker: if message.reply_to_message.sticker.is_video: - return await message.edit( - "Video stickers not allowed", parse_mode=enums.ParseMode.HTML - ) - filename = "sticker.webp" + return await message.edit('Video stickers not allowed', parse_mode=enums.ParseMode.HTML) + filename = 'sticker.webp' elif message.reply_to_message.text: result = await quote_cmd(client, message) if result[1]: - filename = "sticker.png" + filename = 'sticker.png' else: - filename = "sticker.webp" - open(f"downloads/" + filename, "wb").write(result[0].getbuffer()) + filename = 'sticker.webp' + open('downloads/' + filename, 'wb').write(result[0].getbuffer()) coro = False else: - filename = "photo.jpg" + filename = 'photo.jpg' if coro: - await message.reply_to_message.download(f"downloads/{filename}") + await message.reply_to_message.download(f'downloads/{filename}') except Exception as ex: return await message.edit( - f"Message can not be loaded:\n{format_exc(ex)}", + f'Message can not be loaded:\n{format_exc(ex)}', parse_mode=enums.ParseMode.HTML, ) - await message.edit("Spinning...", parse_mode=enums.ParseMode.HTML) + await message.edit('Spinning...', parse_mode=enums.ParseMode.HTML) offset = int(message.command[1]) if len(message.command) > 1 else 10 fps = int(message.command[2]) if len(message.command) > 2 else 30 try: loop = asyncio.get_event_loop() - await loop.run_in_executor( - None, lambda: create_gif(filename, offset, fps, message.command[0]) - ) + await loop.run_in_executor(None, lambda: create_gif(filename, offset, fps, message.command[0])) await message.delete() return await client.send_animation( chat_id=message.chat.id, - animation="downloads/video.gif", + animation='downloads/video.gif', reply_to_message_id=message.reply_to_message.id, ) except Exception as e: await message.reply(format_exc(e), parse_mode=enums.ParseMode.HTML) -modules_help["spin"] = { - "spin [offset] [fps]": "Spin message (Reply required)", - "dspin [offset] [fps]": "SHAKAL spin message (Reply required)", +modules_help['spin'] = { + 'spin [offset] [fps]': 'Spin message (Reply required)', + 'dspin [offset] [fps]': 'SHAKAL spin message (Reply required)', } diff --git a/modules/squotes.py b/modules/squotes.py index 34605d6..cf0c88c 100644 --- a/modules/squotes.py +++ b/modules/squotes.py @@ -18,16 +18,15 @@ import base64 from io import BytesIO import requests -from pyrogram import Client, filters, errors, types +from pyrogram import Client, errors, filters, types from pyrogram.types import Message - from utils.misc import modules_help, prefix -from utils.scripts import with_reply, format_exc, resize_image +from utils.scripts import format_exc, resize_image, with_reply -QUOTES_API = "https://quotes-o042.onrender.com/generate" +QUOTES_API = 'https://quotes-o042.onrender.com/generate' -@Client.on_message(filters.command(["q", "quote"], prefix) & filters.me) +@Client.on_message(filters.command(['q', 'quote'], prefix) & filters.me) @with_reply async def quote_cmd(client: Client, message: Message): if len(message.command) > 1 and message.command[1].isdigit(): @@ -39,9 +38,9 @@ async def quote_cmd(client: Client, message: Message): else: count = 1 - is_png = "!png" in message.command or "!file" in message.command - send_for_me = "!me" in message.command or "!ls" in message.command - no_reply = "!noreply" in message.command or "!nr" in message.command + is_png = '!png' in message.command or '!file' in message.command + send_for_me = '!me' in message.command or '!ls' in message.command + no_reply = '!noreply' in message.command or '!nr' in message.command messages = [] @@ -66,32 +65,26 @@ async def quote_cmd(client: Client, message: Message): if send_for_me: await message.delete() - message = await client.send_message("me", "Generating...") + message = await client.send_message('me', 'Generating...') else: - await message.edit("Generating...") + await message.edit('Generating...') params = { - "messages": [ - await render_message(client, msg) for msg in messages if not msg.empty - ], - "quote_color": "#162330", - "text_color": "#fff", + 'messages': [await render_message(client, msg) for msg in messages if not msg.empty], + 'quote_color': '#162330', + 'text_color': '#fff', } response = requests.post(QUOTES_API, json=params) if not response.ok: - return await message.edit( - f"Quotes API error!\n{response.text}" - ) + return await message.edit(f'Quotes API error!\n{response.text}') - resized = resize_image( - BytesIO(response.content), img_type="PNG" if is_png else "WEBP" - ) - await message.edit("Sending...") + resized = resize_image(BytesIO(response.content), img_type='PNG' if is_png else 'WEBP') + await message.edit('Sending...') try: func = client.send_document if is_png else client.send_sticker - chat_id = "me" if send_for_me else message.chat.id + chat_id = 'me' if send_for_me else message.chat.id await func(chat_id, resized) except errors.RPCError as e: # no rights to send stickers, etc await message.edit(format_exc(e)) @@ -99,23 +92,21 @@ async def quote_cmd(client: Client, message: Message): await message.delete() -@Client.on_message(filters.command(["fq", "fakequote"], prefix) & filters.me) +@Client.on_message(filters.command(['fq', 'fakequote'], prefix) & filters.me) @with_reply async def fake_quote_cmd(client: Client, message: types.Message): - is_png = "!png" in message.command or "!file" in message.command - send_for_me = "!me" in message.command or "!ls" in message.command - no_reply = "!noreply" in message.command or "!nr" in message.command + is_png = '!png' in message.command or '!file' in message.command + send_for_me = '!me' in message.command or '!ls' in message.command + no_reply = '!noreply' in message.command or '!nr' in message.command - fake_quote_text = " ".join( + fake_quote_text = ' '.join( [ - arg - for arg in message.command[1:] - if arg not in ["!png", "!file", "!me", "!ls", "!noreply", "!nr"] + arg for arg in message.command[1:] if arg not in ['!png', '!file', '!me', '!ls', '!noreply', '!nr'] ] # remove some special arg words ) if not fake_quote_text: - return await message.edit("Fake quote text is empty") + return await message.edit('Fake quote text is empty') q_message = await client.get_messages(message.chat.id, message.reply_to_message.id) q_message.text = fake_quote_text @@ -125,30 +116,26 @@ async def fake_quote_cmd(client: Client, message: types.Message): if send_for_me: await message.delete() - message = await client.send_message("me", "Generating...") + message = await client.send_message('me', 'Generating...') else: - await message.edit("Generating...") + await message.edit('Generating...') params = { - "messages": [await render_message(client, q_message)], - "quote_color": "#162330", - "text_color": "#fff", + 'messages': [await render_message(client, q_message)], + 'quote_color': '#162330', + 'text_color': '#fff', } response = requests.post(QUOTES_API, json=params) if not response.ok: - return await message.edit( - f"Quotes API error!\n{response.text}" - ) + return await message.edit(f'Quotes API error!\n{response.text}') - resized = resize_image( - BytesIO(response.content), img_type="PNG" if is_png else "WEBP" - ) - await message.edit("Sending...") + resized = resize_image(BytesIO(response.content), img_type='PNG' if is_png else 'WEBP') + await message.edit('Sending...') try: func = client.send_document if is_png else client.send_sticker - chat_id = "me" if send_for_me else message.chat.id + chat_id = 'me' if send_for_me else message.chat.id await func(chat_id, resized) except errors.RPCError as e: # no rights to send stickers, etc await message.edit(format_exc(e)) @@ -171,11 +158,11 @@ async def render_message(app: Client, message: types.Message) -> dict: # text if message.photo: - text = message.caption if message.caption else "" + text = message.caption if message.caption else '' elif message.poll: text = get_poll_text(message.poll) elif message.sticker: - text = "" + text = '' else: text = get_reply_text(message) @@ -185,7 +172,7 @@ async def render_message(app: Client, message: types.Message) -> dict: elif message.sticker: media = await get_file(message.sticker.file_id) else: - media = "" + media = '' # entities entities = [] @@ -193,9 +180,9 @@ async def render_message(app: Client, message: types.Message) -> dict: for entity in message.entities: entities.append( { - "offset": entity.offset, - "length": entity.length, - "type": str(entity.type).split(".")[-1].lower(), + 'offset': entity.offset, + 'length': entity.length, + 'type': str(entity.type).split('.')[-1].lower(), } ) @@ -206,7 +193,7 @@ async def render_message(app: Client, message: types.Message) -> dict: elif isinstance(msg.forward_origin, types.MessageOriginHiddenUser): msg.from_user.id = 0 msg.from_user.first_name = msg.forward_origin.sender_user_name - msg.from_user.last_name = "" + msg.from_user.last_name = '' elif isinstance(msg.forward_origin, types.MessageOriginChat): msg.sender_chat = msg.forward_origin.sender_chat msg.from_user.id = 0 @@ -220,63 +207,55 @@ async def render_message(app: Client, message: types.Message) -> dict: if message.from_user and message.from_user.id != 0: from_user = message.from_user - author["id"] = from_user.id - author["name"] = get_full_name(from_user) + author['id'] = from_user.id + author['name'] = get_full_name(from_user) if message.author_signature: - author["rank"] = message.author_signature - elif message.chat.type != "supergroup" or message.forward_date: - author["rank"] = "" + author['rank'] = message.author_signature + elif message.chat.type != 'supergroup' or message.forward_date: + author['rank'] = '' else: try: member = await message.chat.get_member(from_user.id) except errors.UserNotParticipant: - author["rank"] = "" + author['rank'] = '' else: - author["rank"] = getattr(member, "title", "") or ( - "owner" - if member.status == "creator" - else "admin" - if member.status == "administrator" - else "" + author['rank'] = getattr(member, 'title', '') or ( + 'owner' if member.status == 'creator' else 'admin' if member.status == 'administrator' else '' ) if from_user.photo: - author["avatar"] = await get_file(from_user.photo.big_file_id) + author['avatar'] = await get_file(from_user.photo.big_file_id) elif not from_user.photo and from_user.username: # may be user blocked us, we will try to get avatar via t.me - t_me_page = requests.get(f"https://t.me/{from_user.username}").text + t_me_page = requests.get(f'https://t.me/{from_user.username}').text sub = ' 0 - and link[0] - and link[0] != "https://telegram.org/img/t_logo.png" - ): + if len(link) > 0 and link[0] and link[0] != 'https://telegram.org/img/t_logo.png': # found valid link avatar = requests.get(link[0]).content - author["avatar"] = base64.b64encode(avatar).decode() + author['avatar'] = base64.b64encode(avatar).decode() else: - author["avatar"] = "" + author['avatar'] = '' else: - author["avatar"] = "" + author['avatar'] = '' else: - author["avatar"] = "" + author['avatar'] = '' elif message.from_user and message.from_user.id == 0: - author["id"] = 0 - author["name"] = message.from_user.first_name - author["rank"] = "" + author['id'] = 0 + author['name'] = message.from_user.first_name + author['rank'] = '' else: - author["id"] = message.sender_chat.id - author["name"] = message.sender_chat.title - author["rank"] = "channel" if message.sender_chat.type == "channel" else "" + author['id'] = message.sender_chat.id + author['name'] = message.sender_chat.title + author['rank'] = 'channel' if message.sender_chat.type == 'channel' else '' if message.sender_chat.photo: - author["avatar"] = await get_file(message.sender_chat.photo.big_file_id) + author['avatar'] = await get_file(message.sender_chat.photo.big_file_id) else: - author["avatar"] = "" - author["via_bot"] = message.via_bot.username if message.via_bot else "" + author['avatar'] = '' + author['via_bot'] = message.via_bot.username if message.via_bot else '' # reply reply = {} @@ -285,129 +264,122 @@ async def render_message(app: Client, message: types.Message) -> dict: move_forwards(reply_msg) if reply_msg.from_user: - reply["id"] = reply_msg.from_user.id - reply["name"] = get_full_name(reply_msg.from_user) + reply['id'] = reply_msg.from_user.id + reply['name'] = get_full_name(reply_msg.from_user) else: - reply["id"] = reply_msg.sender_chat.id - reply["name"] = reply_msg.sender_chat.title + reply['id'] = reply_msg.sender_chat.id + reply['name'] = reply_msg.sender_chat.title - reply["text"] = get_reply_text(reply_msg) + reply['text'] = get_reply_text(reply_msg) return { - "text": text, - "media": media, - "entities": entities, - "author": author, - "reply": reply, + 'text': text, + 'media': media, + 'entities': entities, + 'author': author, + 'reply': reply, } def get_audio_text(audio: types.Audio) -> str: if audio.title and audio.performer: - return f" ({audio.title} — {audio.performer})" + return f' ({audio.title} — {audio.performer})' if audio.title: - return f" ({audio.title})" + return f' ({audio.title})' if audio.performer: - return f" ({audio.performer})" - return "" + return f' ({audio.performer})' + return '' def get_reply_text(reply: types.Message) -> str: return ( - "📷 Photo" + ("\n" + reply.caption if reply.caption else "") + '📷 Photo' + ('\n' + reply.caption if reply.caption else '') if reply.photo else ( get_reply_poll_text(reply.poll) if reply.poll else ( - "📍 Location" + '📍 Location' if reply.location or reply.venue else ( - "👤 Contact" + '👤 Contact' if reply.contact else ( - "🖼 GIF" + '🖼 GIF' if reply.animation else ( - "🎧 Music" + get_audio_text(reply.audio) + '🎧 Music' + get_audio_text(reply.audio) if reply.audio else ( - "📹 Video" + '📹 Video' if reply.video else ( - "📹 Videomessage" + '📹 Videomessage' if reply.video_note else ( - "🎵 Voice" + '🎵 Voice' if reply.voice else ( - ( - reply.sticker.emoji + " " - if reply.sticker.emoji - else "" - ) - + "Sticker" + (reply.sticker.emoji + ' ' if reply.sticker.emoji else '') + 'Sticker' if reply.sticker else ( - "💾 File " + reply.document.file_name + '💾 File ' + reply.document.file_name if reply.document else ( - "🎮 Game" + '🎮 Game' if reply.game else ( - "🎮 set new record" + '🎮 set new record' if reply.game_high_score else ( - f"{reply.dice.emoji} - {reply.dice.value}" + f'{reply.dice.emoji} - {reply.dice.value}' if reply.dice else ( ( - "👤 joined the group" - if reply.new_chat_members[ - 0 - ].id + '👤 joined the group' + if reply.new_chat_members[0].id == reply.from_user.id - else f"👤 invited {get_full_name(reply.new_chat_members[0])} to the group" + else f'👤 invited {get_full_name(reply.new_chat_members[0])} to the group' ) if reply.new_chat_members else ( ( - "👤 left the group" + '👤 left the group' if reply.left_chat_member.id == reply.from_user.id - else f"👤 removed {get_full_name(reply.left_chat_member)}" + else f'👤 removed {get_full_name(reply.left_chat_member)}' ) if reply.left_chat_member else ( - f"✏ changed group name to {reply.new_chat_title}" + f'✏ changed group name to {reply.new_chat_title}' if reply.new_chat_title else ( - "🖼 changed group photo" + '🖼 changed group photo' if reply.new_chat_photo else ( - "🖼 removed group photo" + '🖼 removed group photo' if reply.delete_chat_photo else ( - "📍 pinned message" + '📍 pinned message' if reply.pinned_message else ( - "🎤 started a new video chat" + '🎤 started a new video chat' if reply.video_chat_started else ( - "🎤 ended the video chat" + '🎤 ended the video chat' if reply.video_chat_ended else ( - "🎤 invited participants to the video chat" + '🎤 invited participants to the video chat' if reply.video_chat_members_invited else ( - "👥 created the group" + '👥 created the group' if reply.group_chat_created or reply.supergroup_chat_created else ( - "👥 created the channel" + '👥 created the channel' if reply.channel_chat_created else reply.text - or "unsupported message" + or 'unsupported message' ) ) ) @@ -436,27 +408,27 @@ def get_reply_text(reply: types.Message) -> str: def get_poll_text(poll: types.Poll) -> str: - text = get_reply_poll_text(poll) + "\n" + text = get_reply_poll_text(poll) + '\n' - text += poll.question + "\n" + text += poll.question + '\n' for option in poll.options: - text += f"- {option.text}" + text += f'- {option.text}' if option.voter_count > 0: - text += f" ({option.voter_count} voted)" - text += "\n" + text += f' ({option.voter_count} voted)' + text += '\n' - text += f"Total: {poll.total_voter_count} voted" + text += f'Total: {poll.total_voter_count} voted' return text def get_reply_poll_text(poll: types.Poll) -> str: if poll.is_anonymous: - text = "📊 Anonymous poll" if poll.type == "regular" else "📊 Anonymous quiz" + text = '📊 Anonymous poll' if poll.type == 'regular' else '📊 Anonymous quiz' else: - text = "📊 Poll" if poll.type == "regular" else "📊 Quiz" + text = '📊 Poll' if poll.type == 'regular' else '📊 Quiz' if poll.is_closed: - text += " (closed)" + text += ' (closed)' return text @@ -464,13 +436,13 @@ def get_reply_poll_text(poll: types.Poll) -> str: def get_full_name(user: types.User) -> str: name = user.first_name if user.last_name: - name += " " + user.last_name + name += ' ' + user.last_name return name -modules_help["squotes"] = { - "q [reply]* [count 1-15] [!png] [!me] [!noreply]": "Generate a quote\n" - "Available options: !png — send as PNG, !me — send quote to" - "saved messages, !noreply — generate quote without reply", - "fq [reply]* [!png] [!me] [!noreply] [text]*": "Generate a fake quote", +modules_help['squotes'] = { + 'q [reply]* [count 1-15] [!png] [!me] [!noreply]': 'Generate a quote\n' + 'Available options: !png — send as PNG, !me — send quote to' + 'saved messages, !noreply — generate quote without reply', + 'fq [reply]* [!png] [!me] [!noreply] [text]*': 'Generate a fake quote', } diff --git a/modules/stickers.py b/modules/stickers.py index 499d42c..b1430eb 100644 --- a/modules/stickers.py +++ b/modules/stickers.py @@ -17,27 +17,25 @@ import os from io import BytesIO -from pyrogram import Client, filters, types, enums - +from pyrogram import Client, enums, filters, types from utils.misc import modules_help, prefix from utils.scripts import ( - with_reply, + format_exc, interact_with, interact_with_to_delete, - format_exc, resize_image, + with_reply, ) -@Client.on_message(filters.command("kang", prefix) & filters.me) +@Client.on_message(filters.command('kang', prefix) & filters.me) @with_reply async def kang(client: Client, message: types.Message): - await message.edit("Please wait...") + await message.edit('Please wait...') if len(message.command) < 2: await message.edit( - "No arguments provided\n" - f"Usage: {prefix}kang [pack]* [emoji]", + f'No arguments provided\nUsage: {prefix}kang [pack]* [emoji]', ) return @@ -45,29 +43,17 @@ async def kang(client: Client, message: types.Message): if len(message.command) >= 3: emoji = message.command[2] else: - emoji = "✨" + emoji = '✨' - await client.unblock_user("@stickers") - await interact_with( - await client.send_message( - "@stickers", "/cancel", parse_mode=enums.ParseMode.MARKDOWN - ) - ) - await interact_with( - await client.send_message( - "@stickers", "/addsticker", parse_mode=enums.ParseMode.MARKDOWN - ) - ) + await client.unblock_user('@stickers') + await interact_with(await client.send_message('@stickers', '/cancel', parse_mode=enums.ParseMode.MARKDOWN)) + await interact_with(await client.send_message('@stickers', '/addsticker', parse_mode=enums.ParseMode.MARKDOWN)) - result = await interact_with( - await client.send_message( - "@stickers", pack, parse_mode=enums.ParseMode.MARKDOWN - ) - ) - if ".TGS" in result.text: + result = await interact_with(await client.send_message('@stickers', pack, parse_mode=enums.ParseMode.MARKDOWN)) + if '.TGS' in result.text: await message.edit("Animated packs aren't supported") return - if "StickerExample.psd" not in result.text: + if 'StickerExample.psd' not in result.text: await message.edit( "Stickerpack doesn't exitst. Create it using @Stickers bot (via /newpack command)", ) @@ -85,79 +71,63 @@ async def kang(client: Client, message: types.Message): if os.path.exists(path): os.remove(path) - await interact_with( - await client.send_document( - "@stickers", resized, parse_mode=enums.ParseMode.MARKDOWN - ) - ) - response = await interact_with( - await client.send_message( - "@stickers", emoji, parse_mode=enums.ParseMode.MARKDOWN - ) - ) - if "/done" in response.text: + await interact_with(await client.send_document('@stickers', resized, parse_mode=enums.ParseMode.MARKDOWN)) + response = await interact_with(await client.send_message('@stickers', emoji, parse_mode=enums.ParseMode.MARKDOWN)) + if '/done' in response.text: # ok - await interact_with( - await client.send_message( - "@stickers", "/done", parse_mode=enums.ParseMode.MARKDOWN - ) - ) - await client.delete_messages("@stickers", interact_with_to_delete) + await interact_with(await client.send_message('@stickers', '/done', parse_mode=enums.ParseMode.MARKDOWN)) + await client.delete_messages('@stickers', interact_with_to_delete) await message.edit( - f"Sticker added to pack", + f'Sticker added to pack', ) else: - await message.edit("Something went wrong. Check history with @stickers") + await message.edit('Something went wrong. Check history with @stickers') interact_with_to_delete.clear() -@Client.on_message(filters.command(["stp", "s2p", "stick2png"], prefix) & filters.me) +@Client.on_message(filters.command(['stp', 's2p', 'stick2png'], prefix) & filters.me) @with_reply async def stick2png(client: Client, message: types.Message): try: - await message.edit("Downloading...") + await message.edit('Downloading...') path = await message.reply_to_message.download() - with open(path, "rb") as f: + with open(path, 'rb') as f: content = f.read() if os.path.exists(path): os.remove(path) file_io = BytesIO(content) - file_io.name = "sticker.png" + file_io.name = 'sticker.png' - await client.send_document( - message.chat.id, file_io, parse_mode=enums.ParseMode.MARKDOWN - ) + await client.send_document(message.chat.id, file_io, parse_mode=enums.ParseMode.MARKDOWN) except Exception as e: await message.edit(format_exc(e)) else: await message.delete() -@Client.on_message(filters.command(["resize"], prefix) & filters.me) +@Client.on_message(filters.command(['resize'], prefix) & filters.me) @with_reply async def resize_cmd(client: Client, message: types.Message): try: - await message.edit("Downloading...") + await message.edit('Downloading...') path = await message.reply_to_message.download() resized = resize_image(path) - resized.name = "image.png" + resized.name = 'image.png' if os.path.exists(path): os.remove(path) - await client.send_document( - message.chat.id, resized, parse_mode=enums.ParseMode.MARKDOWN - ) + await client.send_document(message.chat.id, resized, parse_mode=enums.ParseMode.MARKDOWN) except Exception as e: await message.edit(format_exc(e)) else: await message.delete() -modules_help["stickers"] = { - "kang [reply]* [pack]* [emoji]": "Add sticker to defined pack", - "stp [reply]*": "Convert replied sticker to PNG", - "resize [reply]*": "Resize replied image to 512xN format", +modules_help['stickers'] = { + 'kang [reply]* [pack]* [emoji]': 'Add sticker to defined pack', + 'stp [reply]*': 'Convert replied sticker to PNG', + 'resize [reply]*': 'Resize replied image to 512xN format', } diff --git a/modules/support.py b/modules/support.py index 9c5c5d6..03ae8c0 100644 --- a/modules/support.py +++ b/modules/support.py @@ -14,17 +14,17 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import datetime +import random + from pyrogram import Client, filters from pyrogram.types import Message -import random -import datetime - -from utils.misc import modules_help, prefix, userbot_version, python_version, gitrepo +from utils.misc import gitrepo, modules_help, prefix, python_version, userbot_version -@Client.on_message(filters.command(["support", "repo"], prefix) & filters.me) +@Client.on_message(filters.command(['support', 'repo'], prefix) & filters.me) async def support(_, message: Message): - devs = ["@Qbtaumai", "@H4T3H46K3R"] + devs = ['@Qbtaumai', '@H4T3H46K3R'] random.shuffle(devs) commands_count = 0.0 @@ -33,27 +33,27 @@ async def support(_, message: Message): commands_count += 1 await message.edit( - f"Moon-Userbot\n\n" - "GitHub: Moon-Userbot\n" - "Custom modules repository: " - "custom_modules\n" - "License: GNU GPL v3\n\n" - "Channel: @moonuserbot\n" - "Custom modules: @moonub_modules\n" - "Chat [EN]: @moonub_chat\n" - f"Main developers: {', '.join(devs)}\n\n" - f"Python version: {python_version}\n" - f"Modules count: {len(modules_help) / 1}\n" - f"Commands count: {commands_count}", + f'Moon-Userbot\n\n' + 'GitHub: Moon-Userbot\n' + 'Custom modules repository: ' + 'custom_modules\n' + 'License: GNU GPL v3\n\n' + 'Channel: @moonuserbot\n' + 'Custom modules: @moonub_modules\n' + 'Chat [EN]: @moonub_chat\n' + f'Main developers: {", ".join(devs)}\n\n' + f'Python version: {python_version}\n' + f'Modules count: {len(modules_help) / 1}\n' + f'Commands count: {commands_count}', disable_web_page_preview=True, ) -@Client.on_message(filters.command(["version", "ver"], prefix) & filters.me) +@Client.on_message(filters.command(['version', 'ver'], prefix) & filters.me) async def version(client: Client, message: Message): - changelog = "" - ub_version = ".".join(userbot_version.split(".")[:2]) - async for m in client.search_messages("moonuserbot", query=f"{userbot_version}."): + changelog = '' + ub_version = '.'.join(userbot_version.split('.')[:2]) + async for m in client.search_messages('moonuserbot', query=f'{userbot_version}.'): if ub_version in m.text: changelog = m.message_id @@ -63,26 +63,32 @@ async def version(client: Client, message: Message): remote_url = list(gitrepo.remote().urls)[0] commit_time = ( datetime.datetime.fromtimestamp(gitrepo.head.commit.committed_date) - .astimezone(datetime.timezone.utc) - .strftime("%Y-%m-%d %H:%M:%S %Z") + .astimezone(datetime.UTC) + .strftime('%Y-%m-%d %H:%M:%S %Z') ) git_info = ( - f"\nBranch: {gitrepo.active_branch}\n" - if gitrepo.active_branch != "master" else "\n" - ) + f"Commit: " f"{gitrepo.head.commit.hexsha[:7]} by {gitrepo.head.commit.author.name}\n" f"Commit time: {commit_time}" + ( + f'\nBranch: {gitrepo.active_branch}\n' + if gitrepo.active_branch != 'master' + else '\n' + ) + + f'Commit: ' + f'{gitrepo.head.commit.hexsha[:7]} by {gitrepo.head.commit.author.name}\n' + f'Commit time: {commit_time}' + ) else: - git_info = "" + git_info = '' await message.reply( - f"Moon Userbot version: {userbot_version}\n" - f"Changelog in channel.\n" - f"Changelog written by " - f"Abhi\n\n" - f"{git_info}", + f'Moon Userbot version: {userbot_version}\n' + f'Changelog in channel.\n' + f'Changelog written by ' + f'Abhi\n\n' + f'{git_info}', ) -modules_help["support"] = { - "support": "Information about userbot", - "version": "Check userbot version", +modules_help['support'] = { + 'support': 'Information about userbot', + 'version': 'Check userbot version', } diff --git a/modules/thumbnail.py b/modules/thumbnail.py index 4bf6e3c..99764b4 100644 --- a/modules/thumbnail.py +++ b/modules/thumbnail.py @@ -15,29 +15,28 @@ # along with this program. If not, see . import os -from PIL import Image +from PIL import Image from pyrogram import Client, filters from pyrogram.types import Message - -from utils.misc import prefix, modules_help +from utils.misc import modules_help, prefix -@Client.on_message(filters.command("setthumb", prefix) & filters.me) +@Client.on_message(filters.command('setthumb', prefix) & filters.me) async def setthumb(_, message: Message): - THUMB_PATH = "downloads/thumb" + THUMB_PATH = 'downloads/thumb' if message.reply_to_message: if not os.path.exists(THUMB_PATH): os.makedirs(THUMB_PATH) new_thumb = await message.reply_to_message.download() with Image.open(new_thumb) as img: - if img.format in ["PNG", "JPG", "JPEG"]: - new_path = os.path.join(THUMB_PATH, "thumb.jpg") + if img.format in ['PNG', 'JPG', 'JPEG']: + new_path = os.path.join(THUMB_PATH, 'thumb.jpg') os.rename(new_thumb, new_path) - await message.edit_text("Thumbnail set successfully!") + await message.edit_text('Thumbnail set successfully!') else: - await message.edit_text("Kindly reply to a PHOTO Entity!") + await message.edit_text('Kindly reply to a PHOTO Entity!') return -modules_help["thumb"] = {"setthumb [reply_to_photo]*": "set your own custom thumbnail"} +modules_help['thumb'] = {'setthumb [reply_to_photo]*': 'set your own custom thumbnail'} diff --git a/modules/type.py b/modules/type.py index 5941371..5f41773 100644 --- a/modules/type.py +++ b/modules/type.py @@ -15,20 +15,17 @@ # along with this program. If not, see . import asyncio -import time from pyrogram import Client, filters -from pyrogram.errors import FloodWait from pyrogram.types import Message - from utils.misc import modules_help, prefix -@Client.on_message(filters.command(["type", "typewriter"], prefix) & filters.me) +@Client.on_message(filters.command(['type', 'typewriter'], prefix) & filters.me) async def type_cmd(_, message: Message): text = message.text.split(maxsplit=1)[1] - typed = "" - typing_symbol = "▒" + typed = '' + typing_symbol = '▒' for char in text: await message.edit(typed + typing_symbol) @@ -38,6 +35,6 @@ async def type_cmd(_, message: Message): await asyncio.sleep(0.1) -modules_help["type"] = { - "type | typewriter [text]*": "Typing emulation. Don't use a lot of characters, you can receive a lot of floodwaits!" +modules_help['type'] = { + 'type | typewriter [text]*': "Typing emulation. Don't use a lot of characters, you can receive a lot of floodwaits!" } diff --git a/modules/updater.py b/modules/updater.py index e2b5018..0e1c99d 100644 --- a/modules/updater.py +++ b/modules/updater.py @@ -15,15 +15,14 @@ # along with this program. If not, see . import os -import sys import shutil import subprocess +import sys from pyrogram import Client, filters from pyrogram.types import Message - -from utils.misc import modules_help, prefix, requirements_list from utils.db import db +from utils.misc import modules_help, prefix, requirements_list from utils.scripts import format_exc, restart @@ -31,87 +30,82 @@ def check_command(command): return shutil.which(command) is not None -@Client.on_message(filters.command("restart", prefix) & filters.me) +@Client.on_message(filters.command('restart', prefix) & filters.me) async def restart_cmd(_, message: Message): db.set( - "core.updater", - "restart_info", + 'core.updater', + 'restart_info', { - "type": "restart", - "chat_id": message.chat.id, - "message_id": message.id, + 'type': 'restart', + 'chat_id': message.chat.id, + 'message_id': message.id, }, ) - if "LAVHOST" in os.environ: - await message.edit("Your lavHost is restarting...") - os.system("lavhost restart") + if 'LAVHOST' in os.environ: + await message.edit('Your lavHost is restarting...') + os.system('lavhost restart') return - await message.edit("Restarting...") - if os.path.exists("moonlogs.txt"): - os.remove("moonlogs.txt") + await message.edit('Restarting...') + if os.path.exists('moonlogs.txt'): + os.remove('moonlogs.txt') restart() -@Client.on_message(filters.command("update", prefix) & filters.me) +@Client.on_message(filters.command('update', prefix) & filters.me) async def update(_, message: Message): db.set( - "core.updater", - "restart_info", + 'core.updater', + 'restart_info', { - "type": "update", - "chat_id": message.chat.id, - "message_id": message.id, + 'type': 'update', + 'chat_id': message.chat.id, + 'message_id': message.id, }, ) - if "LAVHOST" in os.environ: - await message.edit("Your lavHost is updating...") - os.system("lavhost update") + if 'LAVHOST' in os.environ: + await message.edit('Your lavHost is updating...') + os.system('lavhost update') return - await message.edit("Updating...") + await message.edit('Updating...') try: - if not check_command("termux-setup-storage"): - subprocess.run( - [sys.executable, "-m", "pip", "install", "-U", "pip"], check=True - ) - subprocess.run(["git", "pull"], check=True) + if not check_command('termux-setup-storage'): + subprocess.run([sys.executable, '-m', 'pip', 'install', '-U', 'pip'], check=True) + subprocess.run(['git', 'pull'], check=True) - if ( - os.path.exists("requirements.txt") - and os.path.getsize("requirements.txt") > 0 - ): + if os.path.exists('requirements.txt') and os.path.getsize('requirements.txt') > 0: subprocess.run( [ sys.executable, - "-m", - "pip", - "install", - "-U", - "-r", - "requirements.txt", + '-m', + 'pip', + 'install', + '-U', + '-r', + 'requirements.txt', ], check=True, ) if requirements_list: subprocess.run( - [sys.executable, "-m", "pip", "install", "-U", *requirements_list], + [sys.executable, '-m', 'pip', 'install', '-U', *requirements_list], check=True, ) except Exception as e: await message.edit(format_exc(e)) - db.remove("core.updater", "restart_info") + db.remove('core.updater', 'restart_info') else: - await message.edit("Updating: done! Restarting...") - if os.path.exists("moonlogs.txt"): - os.remove("moonlogs.txt") + await message.edit('Updating: done! Restarting...') + if os.path.exists('moonlogs.txt'): + os.remove('moonlogs.txt') restart() -modules_help["updater"] = { - "update": "Update the userbot. If new core modules are avaliable, they will be installed", - "restart": "Restart userbot", +modules_help['updater'] = { + 'update': 'Update the userbot. If new core modules are avaliable, they will be installed', + 'restart': 'Restart userbot', } diff --git a/modules/upl.py b/modules/upl.py index a5e3dfd..2fb5d72 100644 --- a/modules/upl.py +++ b/modules/upl.py @@ -17,71 +17,65 @@ import io import os import time + from pyrogram import Client, filters from pyrogram.types import Message from utils.misc import modules_help, prefix from utils.scripts import format_exc, progress -@Client.on_message(filters.command("upl", prefix) & filters.me) +@Client.on_message(filters.command('upl', prefix) & filters.me) async def upl(client: Client, message: Message): if len(message.command) > 1: link = message.text.split(maxsplit=1)[1] elif message.reply_to_message: link = message.reply_to_message.text else: - await message.edit( - f"Usage: {prefix}upl [filepath to upload]" - ) + await message.edit(f'Usage: {prefix}upl [filepath to upload]') return if not os.path.isfile(link): - await message.edit( - f"Error: {link} is not a valid file path." - ) + await message.edit(f'Error: {link} is not a valid file path.') return try: - await message.edit("Uploading Now...") + await message.edit('Uploading Now...') await client.send_document( message.chat.id, link, progress=progress, - progress_args=(message, time.time(), "Uploading Now...", link), + progress_args=(message, time.time(), 'Uploading Now...', link), ) await message.delete() except Exception as e: await message.edit(format_exc(e)) -@Client.on_message(filters.command("dlf", prefix) & filters.me) +@Client.on_message(filters.command('dlf', prefix) & filters.me) async def dlf(client: Client, message: Message): if message.reply_to_message: await client.download_media( message.reply_to_message, progress=progress, - progress_args=(message, time.time(), "Uploading Now..."), + progress_args=(message, time.time(), 'Uploading Now...'), ) - await message.edit("Downloaded Successfully!") + await message.edit('Downloaded Successfully!') else: - await message.edit(f"Usage: {prefix}dlf [reply to a file]") + await message.edit(f'Usage: {prefix}dlf [reply to a file]') -@Client.on_message(filters.command("moonlogs", prefix) & filters.me) +@Client.on_message(filters.command('moonlogs', prefix) & filters.me) async def mupl(client: Client, message: Message): - link = "moonlogs.txt" + link = 'moonlogs.txt' if os.path.exists(link): try: - await message.edit("Uploading Now...") - with open(link, "rb") as f: + await message.edit('Uploading Now...') + with open(link, 'rb') as f: data = f.read() bio = io.BytesIO(data) - bio.name = "moonlogs.txt" + bio.name = 'moonlogs.txt' await client.send_document( - message.chat.id, - bio, - progress=progress, - progress_args=(message, time.time(), "Uploading Now...") + message.chat.id, bio, progress=progress, progress_args=(message, time.time(), 'Uploading Now...') ) await message.delete() except Exception as e: @@ -90,31 +84,27 @@ async def mupl(client: Client, message: Message): await message.edit("Error: LOGS file doesn't exist.") -@Client.on_message(filters.command("uplr", prefix) & filters.me) +@Client.on_message(filters.command('uplr', prefix) & filters.me) async def uplr(client: Client, message: Message): if len(message.command) > 1: link = message.text.split(maxsplit=1)[1] elif message.reply_to_message: link = message.reply_to_message.text else: - await message.edit( - f"Usage: {prefix}upl [filepath to upload]" - ) + await message.edit(f'Usage: {prefix}upl [filepath to upload]') return if not os.path.isfile(link): - await message.edit( - f"Error: {link} is not a valid file path." - ) + await message.edit(f'Error: {link} is not a valid file path.') return try: - await message.edit("Uploading Now...") + await message.edit('Uploading Now...') await client.send_document( message.chat.id, link, progress=progress, - progress_args=(message, time.time(), "Uploading Now...", link), + progress_args=(message, time.time(), 'Uploading Now...', link), ) await message.delete() except Exception as e: @@ -124,9 +114,9 @@ async def uplr(client: Client, message: Message): os.remove(link) -modules_help["uplud"] = { - "upl [filepath]/[reply to path]*": "Upload a file from your local machine to Telegram", - "dlf": "Download a file from Telegram to your local machine", - "uplr [filepath]/[reply to path]*": "Upload a file from your local machine to Telegram, delete the file after uploading", - "moonlogs": "Upload the moonlogs.txt file to Telegram", +modules_help['uplud'] = { + 'upl [filepath]/[reply to path]*': 'Upload a file from your local machine to Telegram', + 'dlf': 'Download a file from Telegram to your local machine', + 'uplr [filepath]/[reply to path]*': 'Upload a file from your local machine to Telegram, delete the file after uploading', + 'moonlogs': 'Upload the moonlogs.txt file to Telegram', } diff --git a/modules/url.py b/modules/url.py index 94c3fad..65ee09e 100644 --- a/modules/url.py +++ b/modules/url.py @@ -17,7 +17,6 @@ import asyncio import math import mimetypes - import os import time from datetime import datetime @@ -29,14 +28,13 @@ import urllib3 from pyrogram import Client, enums, filters from pyrogram.types import Message from pySmartDL import SmartDL - from utils.config import apiflash_key from utils.misc import modules_help, prefix from utils.scripts import format_exc, humanbytes, progress def generate_screenshot(url): - api_url = f"https://api.apiflash.com/v1/urltoimage?access_key={apiflash_key}&url={url}&format=png" + api_url = f'https://api.apiflash.com/v1/urltoimage?access_key={apiflash_key}&url={url}&format=png' response = requests.get(api_url) if response.status_code == 200: return BytesIO(response.content) @@ -46,23 +44,23 @@ def generate_screenshot(url): http = urllib3.PoolManager() -@Client.on_message(filters.command("short", prefix) & filters.me) +@Client.on_message(filters.command('short', prefix) & filters.me) async def short(_, message: Message): if len(message.command) > 1: link = message.text.split(maxsplit=1)[1] elif message.reply_to_message: link = message.reply_to_message.text else: - await message.edit(f"Usage: {prefix}short [url to short]") + await message.edit(f'Usage: {prefix}short [url to short]') return - r = http.request("GET", "https://clck.ru/--?url=" + link) + r = http.request('GET', 'https://clck.ru/--?url=' + link) await message.edit( - r.data.decode().replace("https://", "Shortened Url:"), + r.data.decode().replace('https://', 'Shortened Url:'), disable_web_page_preview=True, ) -@Client.on_message(filters.command("urldl", prefix) & filters.me) +@Client.on_message(filters.command('urldl', prefix) & filters.me) async def urldl(client: Client, message: Message): if len(message.command) > 1: message_id = None @@ -71,65 +69,63 @@ async def urldl(client: Client, message: Message): message_id = message.reply_to_message.id link = message.reply_to_message.text else: - await message.edit( - f"Usage: {prefix}urldl [url to download]" - ) + await message.edit(f'Usage: {prefix}urldl [url to download]') return - await message.edit("Trying to download...") + await message.edit('Trying to download...') c_time = time.time() resp = requests.head(link, allow_redirects=True, timeout=5) if resp.status_code != 200: - return await message.edit("Failed to fetch request header information") + return await message.edit('Failed to fetch request header information') - content_type = resp.headers.get("Content-Type").split(";")[0] + content_type = resp.headers.get('Content-Type').split(';')[0] extension = mimetypes.guess_extension(content_type) # Check if the file is an executable binary is_executable = content_type in [ - "application/octet-stream", - "application/x-msdownload", + 'application/octet-stream', + 'application/x-msdownload', ] # Get the file extension from the URL url_extension = os.path.splitext(link)[1].lower() try: - os.makedirs("downloads") + os.makedirs('downloads') if is_executable: - file_name = "downloads/" + link.split("/")[-1] + file_name = 'downloads/' + link.split('/')[-1] if not file_name.endswith(url_extension): file_name += url_extension elif extension: - file_name = "downloads/" + link.split("/")[-1] + file_name = 'downloads/' + link.split('/')[-1] if not file_name.endswith(extension): file_name += extension else: - file_name = "downloads/" + link.split("/")[-1] + file_name = 'downloads/' + link.split('/')[-1] except FileNotFoundError: if is_executable: - file_name = "downloads/" + link.split("/")[-1] + file_name = 'downloads/' + link.split('/')[-1] if not file_name.endswith(url_extension): file_name += url_extension elif extension: - file_name = "downloads/" + link.split("/")[-1] + file_name = 'downloads/' + link.split('/')[-1] if not file_name.endswith(extension): file_name += extension else: - file_name = "downloads/" + link.split("/")[-1] + file_name = 'downloads/' + link.split('/')[-1] except FileExistsError: if is_executable: - file_name = "downloads/" + link.split("/")[-1] + file_name = 'downloads/' + link.split('/')[-1] if not file_name.endswith(url_extension): file_name += url_extension elif extension: - file_name = "downloads/" + link.split("/")[-1] + file_name = 'downloads/' + link.split('/')[-1] if not file_name.endswith(extension): file_name += extension else: - file_name = "downloads/" + link.split("/")[-1] + file_name = 'downloads/' + link.split('/')[-1] downloader = SmartDL(link, file_name, progress_bar=False, timeout=10) start_t = datetime.now() @@ -140,24 +136,24 @@ async def urldl(client: Client, message: Message): while not downloader.isFinished(): total_length = downloader.filesize or None downloaded = downloader.get_dl_size(human=True) - u_m = "" + u_m = '' now = time.time() diff = now - c_time percentage = downloader.get_progress() * 100 speed = downloader.get_speed(human=True) progress_str = ( - "".join(["▰" for _ in range(math.floor(percentage / 5))]) - + "".join(["▱" for _ in range(20 - math.floor(percentage / 5))]) - + f"\nProgress: {round(percentage, 2)}%" + ''.join(['▰' for _ in range(math.floor(percentage / 5))]) + + ''.join(['▱' for _ in range(20 - math.floor(percentage / 5))]) + + f'\nProgress: {round(percentage, 2)}%' ) eta = downloader.get_eta(human=True) try: - m = "Trying to download...\n" - m += f"File Name: {unquote(link.split('/')[-1])}\n" - m += f"Speed: {speed}\n" - m += f"{progress_str}\n" - m += f"{downloaded} of {humanbytes(total_length)}\n" - m += f"ETA: {eta}" + m = 'Trying to download...\n' + m += f'File Name: {unquote(link.split("/")[-1])}\n' + m += f'Speed: {speed}\n' + m += f'{progress_str}\n' + m += f'{downloaded} of {humanbytes(total_length)}\n' + m += f'ETA: {eta}' if round(diff % 10.00) == 0 and m != u_m: await message.edit_text(disable_web_page_preview=True, text=m) u_m = m @@ -167,25 +163,23 @@ async def urldl(client: Client, message: Message): if os.path.exists(file_name): end_t = datetime.now() sec = (end_t - start_t).seconds - await message.edit_text( - f"Downloaded to {file_name} in {sec} seconds" - ) - ms_ = await message.edit("Starting Upload...") + await message.edit_text(f'Downloaded to {file_name} in {sec} seconds') + ms_ = await message.edit('Starting Upload...') await client.send_document( message.chat.id, file_name, progress=progress, - progress_args=(ms_, c_time, "`Uploading...`"), - caption=f"File Name: {unquote(link.split('/')[-1])}\n", + progress_args=(ms_, c_time, '`Uploading...`'), + caption=f'File Name: {unquote(link.split("/")[-1])}\n', reply_to_message_id=message_id, ) await message.delete() os.remove(file_name) else: - await message.edit("Failed to download") + await message.edit('Failed to download') -@Client.on_message(filters.command("upload", prefix) & filters.me) +@Client.on_message(filters.command('upload', prefix) & filters.me) async def upload_cmd(_, message: Message): max_size = 512 * 1024 * 1024 max_size_mb = 100 @@ -193,20 +187,18 @@ async def upload_cmd(_, message: Message): min_file_age = 31 max_file_age = 180 - ms_ = await message.edit("`Downloading...`", parse_mode=enums.ParseMode.MARKDOWN) + ms_ = await message.edit('`Downloading...`', parse_mode=enums.ParseMode.MARKDOWN) c_time = time.time() try: - file_name = await message.download( - progress=progress, progress_args=(ms_, c_time, "`Downloading...`") - ) + file_name = await message.download(progress=progress, progress_args=(ms_, c_time, '`Downloading...`')) except ValueError: try: file_name = await message.reply_to_message.download( - progress=progress, progress_args=(ms_, c_time, "`Downloading...`") + progress=progress, progress_args=(ms_, c_time, '`Downloading...`') ) except ValueError: - await message.edit("File to upload not found") + await message.edit('File to upload not found') return if os.path.getsize(file_name) > max_size: @@ -215,71 +207,60 @@ async def upload_cmd(_, message: Message): os.remove(file_name) return - await message.edit("Uploading...") - with open(file_name, "rb") as f: + await message.edit('Uploading...') + with open(file_name, 'rb') as f: response = requests.post( - "https://x0.at", - files={"file": f}, + 'https://x0.at', + files={'file': f}, ) if response.ok: file_size_mb = os.path.getsize(file_name) / 1024 / 1024 - file_age = int( - min_file_age - + (max_file_age - min_file_age) * ((1 - (file_size_mb / max_size_mb)) ** 2) - ) - url = response.text.replace("https://", "") + file_age = int(min_file_age + (max_file_age - min_file_age) * ((1 - (file_size_mb / max_size_mb)) ** 2)) + url = response.text.replace('https://', '') await message.edit( - f"Your URL: {url}\nYour file will remain live for {file_age} days", + f'Your URL: {url}\nYour file will remain live for {file_age} days', disable_web_page_preview=True, ) else: - await message.edit( - f"API returned an error!\n{response.text}\n Not allowed" - ) + await message.edit(f'API returned an error!\n{response.text}\n Not allowed') print(response.text) if os.path.exists(file_name): os.remove(file_name) -@Client.on_message(filters.command(["ws", "webshot"], prefix) & filters.me) +@Client.on_message(filters.command(['ws', 'webshot'], prefix) & filters.me) async def webshot(client: Client, message: Message): if len(message.command) > 1: url = message.text.split(maxsplit=1)[1] - if not url.startswith("https://"): - url = "https://" + message.text.split(maxsplit=1)[1] + if not url.startswith('https://'): + url = 'https://' + message.text.split(maxsplit=1)[1] elif message.reply_to_message: url = message.reply_to_message.text - if not url.startswith("https://"): - url = "https://" + url + if not url.startswith('https://'): + url = 'https://' + url else: - await message.edit_text( - f"Usage: {prefix}webshot/{prefix}ws [url/reply to url]" - ) + await message.edit_text(f'Usage: {prefix}webshot/{prefix}ws [url/reply to url]') return chat_id = message.chat.id - await message.edit("Generating screenshot...") + await message.edit('Generating screenshot...') try: screenshot_data = generate_screenshot(url) if screenshot_data: await message.delete() - await client.send_photo( - chat_id, screenshot_data, caption=f"Screenshot of {url}" - ) + await client.send_photo(chat_id, screenshot_data, caption=f'Screenshot of {url}') else: - await message.edit_text( - "Failed to generate screenshot...\nMake sure url is correct" - ) + await message.edit_text('Failed to generate screenshot...\nMake sure url is correct') except Exception as e: - await message.edit_text(f"An error occurred: {format_exc(e)}") + await message.edit_text(f'An error occurred: {format_exc(e)}') -modules_help["url"] = { - "short [url]*": "short url", - "urldl [url]*": "download url content", - "upload [file|reply]*": "upload file to internet", - "webshot [link]*": "Screenshot of web page", - "ws [reply to link]*": "Screenshot of web page", +modules_help['url'] = { + 'short [url]*': 'short url', + 'urldl [url]*': 'download url content', + 'upload [file|reply]*': 'upload file to internet', + 'webshot [link]*': 'Screenshot of web page', + 'ws [reply to link]*': 'Screenshot of web page', } diff --git a/modules/user_info.py b/modules/user_info.py index ea022a6..a79bb3b 100644 --- a/modules/user_info.py +++ b/modules/user_info.py @@ -17,19 +17,18 @@ from pyrogram import Client, filters from pyrogram.raw import functions from pyrogram.types import Message - from utils.misc import modules_help, prefix from utils.scripts import format_exc, interact_with, interact_with_to_delete -@Client.on_message(filters.command("inf", prefix) & filters.me) +@Client.on_message(filters.command('inf', prefix) & filters.me) async def get_user_inf(client: Client, message: Message): if len(message.command) >= 2: peer = await client.resolve_peer(message.command[1]) elif message.reply_to_message and message.reply_to_message.from_user: peer = await client.resolve_peer(message.reply_to_message.from_user.id) else: - peer = await client.resolve_peer("me") + peer = await client.resolve_peer('me') response = await client.invoke(functions.users.GetFullUser(id=peer)) @@ -37,10 +36,10 @@ async def get_user_inf(client: Client, message: Message): full_user = response.full_user if user.username is None: - username = "None" + username = 'None' else: - username = f"@{user.username}" - about = "None" if full_user.about is None else full_user.about + username = f'@{user.username}' + about = 'None' if full_user.about is None else full_user.about user_info = f"""|=Username: {username} |-Id: {user.id} @@ -53,9 +52,9 @@ async def get_user_inf(client: Client, message: Message): await message.edit(user_info) -@Client.on_message(filters.command("inffull", prefix) & filters.me) +@Client.on_message(filters.command('inffull', prefix) & filters.me) async def get_full_user_inf(client: Client, message: Message): - await message.edit("Receiving the information...") + await message.edit('Receiving the information...') try: if len(message.command) >= 2: @@ -63,30 +62,28 @@ async def get_full_user_inf(client: Client, message: Message): elif message.reply_to_message and message.reply_to_message.from_user: peer = await client.resolve_peer(message.reply_to_message.from_user.id) else: - peer = await client.resolve_peer("me") + peer = await client.resolve_peer('me') response = await client.invoke(functions.users.GetFullUser(id=peer)) user = response.users[0] full_user = response.full_user - await client.unblock_user("@creationdatebot") + await client.unblock_user('@creationdatebot') try: - response = await interact_with( - await client.send_message("creationdatebot", f"/id {user.id}") - ) + response = await interact_with(await client.send_message('creationdatebot', f'/id {user.id}')) except RuntimeError: - creation_date = "None" + creation_date = 'None' else: creation_date = response.text # await client.delete_messages("@creationdatebot", interact_with_to_delete) interact_with_to_delete.clear() if user.username is None: - username = "None" + username = 'None' else: - username = f"@{user.username}" - about = "None" if full_user.about is None else full_user.about + username = f'@{user.username}' + about = 'None' if full_user.about is None else full_user.about user_info = f"""|=Username: {username} |-Id: {user.id} |-Account creation date: {creation_date} @@ -109,7 +106,7 @@ async def get_full_user_inf(client: Client, message: Message): await message.edit(format_exc(e)) -modules_help["user_info"] = { - "inf [reply|id|username]": "Get brief information about user", - "inffull [reply|id|username": "Get full information about user", +modules_help['user_info'] = { + 'inf [reply|id|username]': 'Get brief information about user', + 'inffull [reply|id|username': 'Get full information about user', } diff --git a/modules/vt.py b/modules/vt.py index 1ca4ce8..efc70be 100644 --- a/modules/vt.py +++ b/modules/vt.py @@ -12,48 +12,47 @@ import time import requests from pyrogram import Client, enums, filters from pyrogram.types import Message - from utils.config import vt_key as vak from utils.misc import modules_help, prefix from utils.scripts import edit_or_reply, format_exc, progress -@Client.on_message(filters.command("vt", prefix) & filters.me) +@Client.on_message(filters.command('vt', prefix) & filters.me) async def scan_my_file(_, message: Message): - ms_ = await edit_or_reply(message, "`Please Wait! Scanning This File`") + ms_ = await edit_or_reply(message, '`Please Wait! Scanning This File`') if not message.reply_to_message: return await ms_.edit( - "`Please Reply To File To Scan For Viruses`", + '`Please Reply To File To Scan For Viruses`', parse_mode=enums.ParseMode.MARKDOWN, ) if not message.reply_to_message.document: return await ms_.edit( - "`Please Reply To File To Scan For Viruses`", + '`Please Reply To File To Scan For Viruses`', parse_mode=enums.ParseMode.MARKDOWN, ) if vak is None: return await ms_.edit( - "`You Need To Set VIRUSTOTAL_API_KEY For Functing Of This Plugin.`", + '`You Need To Set VIRUSTOTAL_API_KEY For Functing Of This Plugin.`', parse_mode=enums.ParseMode.MARKDOWN, ) if int(message.reply_to_message.document.file_size) > 32000000: return await ms_.edit( - f"**File Too Large, Use `{prefix}vtl` instead**", + f'**File Too Large, Use `{prefix}vtl` instead**', parse_mode=enums.ParseMode.MARKDOWN, ) c_time = time.time() downloaded_file_name = await message.reply_to_message.download( progress=progress, - progress_args=(ms_, c_time, "`Downloading This File!`"), + progress_args=(ms_, c_time, '`Downloading This File!`'), ) - url = "https://www.virustotal.com/vtapi/v2/file/scan" - params = {"apikey": vak} - files = {"file": (downloaded_file_name, open(downloaded_file_name, "rb"))} + url = 'https://www.virustotal.com/vtapi/v2/file/scan' + params = {'apikey': vak} + files = {'file': (downloaded_file_name, open(downloaded_file_name, 'rb'))} response = requests.post(url, files=files, params=params, timeout=10) try: r_json = response.json() - md5 = r_json["md5"] + md5 = r_json['md5'] except Exception as e: return await ms_.edit(format_exc(e)) await ms_.edit( @@ -63,64 +62,64 @@ async def scan_my_file(_, message: Message): os.remove(downloaded_file_name) -@Client.on_message(filters.command("vtl", prefix) & filters.me) +@Client.on_message(filters.command('vtl', prefix) & filters.me) async def scan_my_large_file(_, message: Message): - ms_ = await edit_or_reply(message, "`Please Wait! Scanning This File`") + ms_ = await edit_or_reply(message, '`Please Wait! Scanning This File`') if not message.reply_to_message: return await ms_.edit( - "`Please Reply To File To Scan For Viruses`", + '`Please Reply To File To Scan For Viruses`', parse_mode=enums.ParseMode.MARKDOWN, ) if not message.reply_to_message.document: return await ms_.edit( - "`Please Reply To File To Scan For Viruses`", + '`Please Reply To File To Scan For Viruses`', parse_mode=enums.ParseMode.MARKDOWN, ) if vak is None: return await ms_.edit( - "`You Need To Set VIRUSTOTAL_API_KEY For Functing Of This Plugin.`", + '`You Need To Set VIRUSTOTAL_API_KEY For Functing Of This Plugin.`', parse_mode=enums.ParseMode.MARKDOWN, ) if int(message.reply_to_message.document.file_size) > 650000000: return await ms_.edit( - "**File Too Large, exceeded Max capacity of 650MB**", + '**File Too Large, exceeded Max capacity of 650MB**', parse_mode=enums.ParseMode.MARKDOWN, ) c_time = time.time() downloaded_file_name = await message.reply_to_message.download( progress=progress, - progress_args=(ms_, c_time, "`Downloading This File!`"), + progress_args=(ms_, c_time, '`Downloading This File!`'), ) - url1 = "https://www.virustotal.com/api/v3/files/upload_url" + url1 = 'https://www.virustotal.com/api/v3/files/upload_url' - headers = {"accept": "application/json", "x-apikey": vak} + headers = {'accept': 'application/json', 'x-apikey': vak} rponse = requests.get(url1, headers=headers, timeout=10) try: r_json = rponse.json() - upl_data = r_json["data"] + upl_data = r_json['data'] except Exception as e: return await ms_.edit(format_exc(e)) url = upl_data - files = {"file": (downloaded_file_name, open(downloaded_file_name, "rb"))} - headers = {"accept": "application/json", "x-apikey": vak} + files = {'file': (downloaded_file_name, open(downloaded_file_name, 'rb'))} + headers = {'accept': 'application/json', 'x-apikey': vak} response = requests.post(url, files=files, headers=headers, timeout=10) r_json = response.json() - analysis_url = r_json["data"]["links"]["self"] + analysis_url = r_json['data']['links']['self'] url = analysis_url - headers = {"accept": "application/json", "x-apikey": vak} + headers = {'accept': 'application/json', 'x-apikey': vak} response_result = requests.get(url, headers=headers, timeout=10) try: r_json = response_result.json() - md5 = r_json["meta"]["file_info"]["md5"] + md5 = r_json['meta']['file_info']['md5'] except Exception as e: return await ms_.edit(format_exc(e)) await ms_.edit( @@ -130,7 +129,7 @@ async def scan_my_large_file(_, message: Message): os.remove(downloaded_file_name) -modules_help["virustotal"] = { - "vt [reply to file]*": "Scan for viruses on Virus Total (for lower file size <32MB)", - "vtl [reply to file]*": "Scan for viruses on Virus Total (for lower file size >=32MB)", +modules_help['virustotal'] = { + 'vt [reply to file]*': 'Scan for viruses on Virus Total (for lower file size <32MB)', + 'vtl [reply to file]*': 'Scan for viruses on Virus Total (for lower file size >=32MB)', } diff --git a/string_gen.py b/string_gen.py index 39e9dd0..5dcf665 100644 --- a/string_gen.py +++ b/string_gen.py @@ -1,10 +1,10 @@ from pyrogram import Client, enums -api_id = input("Enter Your API ID: \n") -api_hash = input("Enter Your API HASH : \n") +api_id = input('Enter Your API ID: \n') +api_hash = input('Enter Your API HASH : \n') -with Client("my_account", api_id=api_id, api_hash=api_hash, hide_password=True) as bot_: +with Client('my_account', api_id=api_id, api_hash=api_hash, hide_password=True) as bot_: first_name = (bot_.get_me()).first_name - string_session_ = f"String Session For {first_name} \n{bot_.export_session_string()}" - bot_.send_message("me", string_session_, parse_mode=enums.ParseMode.HTML, disable_web_page_preview=True) - print(f"String Has Been Sent To Your Saved Message : {first_name}") + string_session_ = f'String Session For {first_name} \n{bot_.export_session_string()}' + bot_.send_message('me', string_session_, parse_mode=enums.ParseMode.HTML, disable_web_page_preview=True) + print(f'String Has Been Sent To Your Saved Message : {first_name}') diff --git a/utils/config.py b/utils/config.py index a8879d5..bc0a8b2 100644 --- a/utils/config.py +++ b/utils/config.py @@ -1,32 +1,31 @@ import os + import environs env = environs.Env() try: - env.read_env("./.env") + env.read_env('./.env') except FileNotFoundError: - print("No .env file found, using os.environ.") + print('No .env file found, using os.environ.') -api_id = int(os.getenv("API_ID", env.int("API_ID"))) -api_hash = os.getenv("API_HASH", env.str("API_HASH")) +api_id = int(os.getenv('API_ID', env.int('API_ID'))) +api_hash = os.getenv('API_HASH', env.str('API_HASH')) -STRINGSESSION = os.getenv("STRINGSESSION", env.str("STRINGSESSION")) +STRINGSESSION = os.getenv('STRINGSESSION', env.str('STRINGSESSION')) -second_session = os.getenv("SECOND_SESSION", env.str("SECOND_SESSION", "")) +second_session = os.getenv('SECOND_SESSION', env.str('SECOND_SESSION', '')) -db_type = os.getenv("DATABASE_TYPE", env.str("DATABASE_TYPE")) -db_url = os.getenv("DATABASE_URL", env.str("DATABASE_URL", "")) -db_name = os.getenv("DATABASE_NAME", env.str("DATABASE_NAME")) +db_type = os.getenv('DATABASE_TYPE', env.str('DATABASE_TYPE')) +db_url = os.getenv('DATABASE_URL', env.str('DATABASE_URL', '')) +db_name = os.getenv('DATABASE_NAME', env.str('DATABASE_NAME')) -apiflash_key = os.getenv("APIFLASH_KEY", env.str("APIFLASH_KEY")) -rmbg_key = os.getenv("RMBG_KEY", env.str("RMBG_KEY", "")) -vt_key = os.getenv("VT_KEY", env.str("VT_KEY", "")) -gemini_key = os.getenv("GEMINI_KEY", env.str("GEMINI_KEY", "")) -cohere_key = os.getenv("COHERE_KEY", env.str("COHERE_KEY", "")) +apiflash_key = os.getenv('APIFLASH_KEY', env.str('APIFLASH_KEY')) +rmbg_key = os.getenv('RMBG_KEY', env.str('RMBG_KEY', '')) +vt_key = os.getenv('VT_KEY', env.str('VT_KEY', '')) +gemini_key = os.getenv('GEMINI_KEY', env.str('GEMINI_KEY', '')) +cohere_key = os.getenv('COHERE_KEY', env.str('COHERE_KEY', '')) -pm_limit = int(os.getenv("PM_LIMIT", env.int("PM_LIMIT", 4))) +pm_limit = int(os.getenv('PM_LIMIT', env.int('PM_LIMIT', 4))) -test_server = bool(os.getenv("TEST_SERVER", env.bool("TEST_SERVER", False))) -modules_repo_branch = os.getenv( - "MODULES_REPO_BRANCH", env.str("MODULES_REPO_BRANCH", "master") -) +test_server = bool(os.getenv('TEST_SERVER', env.bool('TEST_SERVER', False))) +modules_repo_branch = os.getenv('MODULES_REPO_BRANCH', env.str('MODULES_REPO_BRANCH', 'master')) diff --git a/utils/conv.py b/utils/conv.py index 5b238fb..b56e909 100644 --- a/utils/conv.py +++ b/utils/conv.py @@ -14,14 +14,12 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import asyncio from collections import OrderedDict from pyrogram import Client, filters, types -from pyrogram.handlers import MessageHandler from pyrogram.enums.parse_mode import ParseMode - -import asyncio -from typing import Union, List, Dict, Optional +from pyrogram.handlers import MessageHandler class _TrueFilter(filters.Filter): @@ -30,12 +28,12 @@ class _TrueFilter(filters.Filter): class Conversation: - _locks: Dict[int, asyncio.Lock] = {} + _locks: dict[int, asyncio.Lock] = {} def __init__( self, client: Client, - chat: Union[str, int], + chat: str | int, timeout: float = 5, delete_at_end=True, exclusive=True, @@ -49,10 +47,10 @@ class Conversation: self._chat_id = 0 self._message_ids = [] self._handler_object = None - self._chat_unique_lock: Optional[asyncio.Lock] = None - self._waiters: Dict[asyncio.Event, filters.Filter] = {} - self._responses: Dict[asyncio.Event, types.Message] = {} - self._pending_updates: List[types.Message] = [] + self._chat_unique_lock: asyncio.Lock | None = None + self._waiters: dict[asyncio.Event, filters.Filter] = {} + self._responses: dict[asyncio.Event, types.Message] = {} + self._pending_updates: list[types.Message] = [] async def __aenter__(self): self._chat_id = (await self.client.get_chat(self.chat)).id @@ -65,9 +63,7 @@ class Conversation: if self.exclusive: await self._chat_unique_lock.acquire() - self._handler_object = MessageHandler( - self._handler, filters.chat(self._chat_id) - ) + self._handler_object = MessageHandler(self._handler, filters.chat(self._chat_id)) if -999 not in self.client.dispatcher.groups: new_groups = OrderedDict(self.client.dispatcher.groups) @@ -101,7 +97,7 @@ class Conversation: async def get_response( self, - message_filter: Optional[filters.Filter] = None, + message_filter: filters.Filter | None = None, timeout: float = None, ) -> types.Message: if timeout is None: @@ -119,15 +115,13 @@ class Conversation: self._message_ids.append(message.id) return message - async def _wait_message( - self, message_filter: Optional[filters.Filter], timeout: float - ) -> types.Message: + async def _wait_message(self, message_filter: filters.Filter | None, timeout: float) -> types.Message: event = asyncio.Event() self._waiters[event] = message_filter try: await asyncio.wait_for(event.wait(), timeout=timeout) - except asyncio.TimeoutError as e: + except TimeoutError as e: raise TimeoutError from e finally: self._waiters.pop(event) @@ -137,8 +131,8 @@ class Conversation: async def send_message( self, text: str, - parse_mode: Optional[str] = ParseMode.HTML, - entities: List[types.MessageEntity] = None, + parse_mode: str | None = ParseMode.HTML, + entities: list[types.MessageEntity] = None, disable_web_page_preview: bool = None, disable_notification: bool = None, reply_to_message_id: int = None, diff --git a/utils/db.py b/utils/db.py index 2cac220..90a2dc2 100644 --- a/utils/db.py +++ b/utils/db.py @@ -14,16 +14,18 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import re import json -import threading +import re import sqlite3 -from dns import resolver +import threading + import pymongo +from dns import resolver + from utils import config resolver.default_resolver = resolver.Resolver(configure=False) -resolver.default_resolver.nameservers = ["1.1.1.1"] +resolver.default_resolver.nameservers = ['1.1.1.1'] class Database: @@ -55,26 +57,24 @@ class MongoDatabase(Database): def set(self, module: str, variable: str, value): if not isinstance(module, str) or not isinstance(variable, str): - raise ValueError("Module and variable must be strings") - self._database[module].replace_one( - {"var": variable}, {"var": variable, "val": value}, upsert=True - ) + raise ValueError('Module and variable must be strings') + self._database[module].replace_one({'var': variable}, {'var': variable, 'val': value}, upsert=True) def get(self, module: str, variable: str, default=None): if not isinstance(module, str) or not isinstance(variable, str): - raise ValueError("Module and variable must be strings") - doc = self._database[module].find_one({"var": variable}) - return default if doc is None else doc["val"] + raise ValueError('Module and variable must be strings') + doc = self._database[module].find_one({'var': variable}) + return default if doc is None else doc['val'] def get_collection(self, module: str): if not isinstance(module, str): - raise ValueError("Module must be a string") - return {item["var"]: item["val"] for item in self._database[module].find()} + raise ValueError('Module must be a string') + return {item['var']: item['val'] for item in self._database[module].find()} def remove(self, module: str, variable: str): if not isinstance(module, str) or not isinstance(variable, str): - raise ValueError("Module and variable must be strings") - self._database[module].delete_one({"var": variable}) + raise ValueError('Module and variable must be strings') + self._database[module].delete_one({'var': variable}) def close(self): self._client.close() @@ -82,27 +82,27 @@ class MongoDatabase(Database): def add_chat_history(self, user_id, message): chat_history = self.get_chat_history(user_id, default=[]) chat_history.append(message) - self.set(f"core.cohere.user_{user_id}", "chat_history", chat_history) + self.set(f'core.cohere.user_{user_id}', 'chat_history', chat_history) def get_chat_history(self, user_id, default=None): if default is None: default = [] - return self.get(f"core.cohere.user_{user_id}", "chat_history", default=[]) + return self.get(f'core.cohere.user_{user_id}', 'chat_history', default=[]) def addaiuser(self, user_id): - chatai_users = self.get("core.chatbot", "chatai_users", default=[]) + chatai_users = self.get('core.chatbot', 'chatai_users', default=[]) if user_id not in chatai_users: chatai_users.append(user_id) - self.set("core.chatbot", "chatai_users", chatai_users) + self.set('core.chatbot', 'chatai_users', chatai_users) def remaiuser(self, user_id): - chatai_users = self.get("core.chatbot", "chatai_users", default=[]) + chatai_users = self.get('core.chatbot', 'chatai_users', default=[]) if user_id in chatai_users: chatai_users.remove(user_id) - self.set("core.chatbot", "chatai_users", chatai_users) + self.set('core.chatbot', 'chatai_users', chatai_users) def getaiusers(self): - return self.get("core.chatbot", "chatai_users", default=[]) + return self.get('core.chatbot', 'chatai_users', default=[]) class SqliteDatabase(Database): @@ -114,25 +114,25 @@ class SqliteDatabase(Database): @staticmethod def _parse_row(row: sqlite3.Row): - if row["type"] == "bool": - return row["val"] == "1" - if row["type"] == "int": - return int(row["val"]) - if row["type"] == "str": - return row["val"] - return json.loads(row["val"]) + if row['type'] == 'bool': + return row['val'] == '1' + if row['type'] == 'int': + return int(row['val']) + if row['type'] == 'str': + return row['val'] + return json.loads(row['val']) def _execute(self, module: str, *args, **kwargs) -> sqlite3.Cursor: - pattern = r"^(core|custom)" + pattern = r'^(core|custom)' if not re.match(pattern, module): - raise ValueError(f"Invalid module name format: {module}") + raise ValueError(f'Invalid module name format: {module}') self._lock.acquire() try: cursor = self._conn.cursor() return cursor.execute(*args, **kwargs) except sqlite3.OperationalError as e: - if str(e).startswith("no such table"): + if str(e).startswith('no such table'): sql = f""" CREATE TABLE IF NOT EXISTS '{module}' ( var TEXT UNIQUE NOT NULL, @@ -165,17 +165,17 @@ class SqliteDatabase(Database): """ if isinstance(value, bool): - val = "1" if value else "0" - typ = "bool" + val = '1' if value else '0' + typ = 'bool' elif isinstance(value, str): val = value - typ = "str" + typ = 'str' elif isinstance(value, int): val = str(value) - typ = "int" + typ = 'int' else: val = json.dumps(value) - typ = "json" + typ = 'json' self._execute(module, sql, (variable, val, typ, val, typ, variable)) self._conn.commit() @@ -188,16 +188,16 @@ class SqliteDatabase(Database): self._conn.commit() def get_collection(self, module: str) -> dict: - pattern = r"^(core|custom)" + pattern = r'^(core|custom)' if not re.match(pattern, module): - raise ValueError(f"Invalid module name format: {module}") + raise ValueError(f'Invalid module name format: {module}') sql = f"SELECT * FROM '{module}'" cur = self._execute(module, sql) collection = {} for row in cur: - collection[row["var"]] = self._parse_row(row) + collection[row['var']] = self._parse_row(row) return collection @@ -208,30 +208,30 @@ class SqliteDatabase(Database): def add_chat_history(self, user_id, message): chat_history = self.get_chat_history(user_id, default=[]) chat_history.append(message) - self.set(f"core.cohere.user_{user_id}", "chat_history", chat_history) + self.set(f'core.cohere.user_{user_id}', 'chat_history', chat_history) def get_chat_history(self, user_id, default=None): if default is None: default = [] - return self.get(f"core.cohere.user_{user_id}", "chat_history", default=[]) + return self.get(f'core.cohere.user_{user_id}', 'chat_history', default=[]) def addaiuser(self, user_id): - chatai_users = self.get("core.chatbot", "chatai_users", default=[]) + chatai_users = self.get('core.chatbot', 'chatai_users', default=[]) if user_id not in chatai_users: chatai_users.append(user_id) - self.set("core.chatbot", "chatai_users", chatai_users) + self.set('core.chatbot', 'chatai_users', chatai_users) def remaiuser(self, user_id): - chatai_users = self.get("core.chatbot", "chatai_users", default=[]) + chatai_users = self.get('core.chatbot', 'chatai_users', default=[]) if user_id in chatai_users: chatai_users.remove(user_id) - self.set("core.chatbot", "chatai_users", chatai_users) + self.set('core.chatbot', 'chatai_users', chatai_users) def getaiusers(self): - return self.get("core.chatbot", "chatai_users", default=[]) + return self.get('core.chatbot', 'chatai_users', default=[]) -if config.db_type in ["mongo", "mongodb"]: +if config.db_type in ['mongo', 'mongodb']: db = MongoDatabase(config.db_url, config.db_name) else: db = SqliteDatabase(config.db_name) diff --git a/utils/handlers.py b/utils/handlers.py index c4dba5e..9a11822 100644 --- a/utils/handlers.py +++ b/utils/handlers.py @@ -16,7 +16,6 @@ import re from datetime import datetime, timedelta -from typing import Dict, Union from pyrogram import Client from pyrogram.enums import ChatType @@ -50,27 +49,21 @@ from utils.misc import prefix from utils.scripts import format_exc, text -async def check_username_or_id(data: Union[str, int]) -> str: +async def check_username_or_id(data: str | int) -> str: data = str(data) - if ( - not data.isdigit() - and data[0] == "-" - and not data[1:].isdigit() - or not data.isdigit() - and data[0] != "-" - ): - return "channel" + if not data.isdigit() and data[0] == '-' and not data[1:].isdigit() or not data.isdigit() and data[0] != '-': + return 'channel' peer_id = int(data) if peer_id < 0: - if MIN_CHAT_ID <= peer_id: - return "chat" + if peer_id >= MIN_CHAT_ID: + return 'chat' if MIN_CHANNEL_ID <= peer_id < MAX_CHANNEL_ID: - return "channel" + return 'channel' elif 0 < peer_id <= MAX_USER_ID: - return "user" + return 'user' - raise ValueError(f"Peer id invalid: {peer_id}") + raise ValueError(f'Peer id invalid: {peer_id}') async def get_user_and_name(message): @@ -101,7 +94,7 @@ class BanHandler: elif not self.message.reply_to_message: await self.handle_non_reply_ban() else: - await self.message.edit("Unsupported") + await self.message.edit('Unsupported') async def handle_reply_ban(self): if self.message.chat.type not in [ChatType.PRIVATE, ChatType.CHANNEL]: @@ -109,26 +102,19 @@ class BanHandler: await self.ban_user(user_for_ban) async def handle_non_reply_ban(self): - if ( - self.message.chat.type not in ["private", "channel"] - and len(self.cause.split()) > 1 - ): + if self.message.chat.type not in ['private', 'channel'] and len(self.cause.split()) > 1: user_to_ban = await self.get_user_to_ban() if user_to_ban: - self.name = ( - user_to_ban.first_name - if getattr(user_to_ban, "first_name", None) - else user_to_ban.title - ) + self.name = user_to_ban.first_name if getattr(user_to_ban, 'first_name', None) else user_to_ban.title await self.ban_user(user_to_ban.id) async def get_user_to_ban(self): - user_type = await check_username_or_id(self.cause.split(" ")[1]) - if user_type == "channel": - return await self.client.get_chat(self.cause.split(" ")[1]) - if user_type == "user": - return await self.client.get_users(self.cause.split(" ")[1]) - await self.message.edit("Invalid user type") + user_type = await check_username_or_id(self.cause.split(' ')[1]) + if user_type == 'channel': + return await self.client.get_chat(self.cause.split(' ')[1]) + if user_type == 'user': + return await self.client.get_users(self.cause.split(' ')[1]) + await self.message.edit('Invalid user type') return None async def ban_user(self, user_id): @@ -139,14 +125,14 @@ class BanHandler: await self.handle_additional_actions() await self.edit_message() except UserAdminInvalid: - await self.message.edit("No rights") + await self.message.edit('No rights') except ChatAdminRequired: - await self.message.edit("No rights") + await self.message.edit('No rights') except Exception as e: await self.message.edit(format_exc(e)) async def handle_additional_actions(self): - if "report_spam" in self.cause.lower().split(): + if 'report_spam' in self.cause.lower().split(): await self.client.invoke( functions.channels.ReportSpam( channel=self.channel, @@ -154,22 +140,16 @@ class BanHandler: id=[self.message.reply_to_message.id], ) ) - if "delete_history" in self.cause.lower().split(): + if 'delete_history' in self.cause.lower().split(): await self.client.invoke( - functions.channels.DeleteParticipantHistory( - channel=self.channel, participant=self.user_id - ) + functions.channels.DeleteParticipantHistory(channel=self.channel, participant=self.user_id) ) async def edit_message(self): - text_c = "".join( - f" {_}" - for _ in self.cause.split() - if _.lower() not in ["delete_history", "report_spam"] - ) + text_c = ''.join(f' {_}' for _ in self.cause.split() if _.lower() not in ['delete_history', 'report_spam']) await self.message.edit( - f"{self.name} banned!" - + f"\n{'Cause: ' + text_c.split(maxsplit=1)[1] + '' if len(text_c.split()) > 1 else ''}" + f'{self.name} banned!' + + f'\n{"Cause: " + text_c.split(maxsplit=1)[1] + "" if len(text_c.split()) > 1 else ""}' ) @@ -188,7 +168,7 @@ class UnbanHandler: elif not self.message.reply_to_message: await self.handle_non_reply_unban() else: - await self.message.edit("Unsupported") + await self.message.edit('Unsupported') async def handle_reply_unban(self): if self.message.chat.type not in [ChatType.PRIVATE, ChatType.CHANNEL]: @@ -196,26 +176,21 @@ class UnbanHandler: await self.unban_user(user_for_unban) async def handle_non_reply_unban(self): - if ( - self.message.chat.type not in ["private", "channel"] - and len(self.cause.split()) > 1 - ): + if self.message.chat.type not in ['private', 'channel'] and len(self.cause.split()) > 1: user_to_unban = await self.get_user_to_unban() if user_to_unban: self.name = ( - user_to_unban.first_name - if getattr(user_to_unban, "first_name", None) - else user_to_unban.title + user_to_unban.first_name if getattr(user_to_unban, 'first_name', None) else user_to_unban.title ) await self.unban_user(user_to_unban.id) async def get_user_to_unban(self): - user_type = await check_username_or_id(self.cause.split(" ")[1]) - if user_type == "channel": - return await self.client.get_chat(self.cause.split(" ")[1]) - if user_type == "user": - return await self.client.get_users(self.cause.split(" ")[1]) - await self.message.edit("Invalid user type") + user_type = await check_username_or_id(self.cause.split(' ')[1]) + if user_type == 'channel': + return await self.client.get_chat(self.cause.split(' ')[1]) + if user_type == 'user': + return await self.client.get_users(self.cause.split(' ')[1]) + await self.message.edit('Invalid user type') return None async def unban_user(self, user_id): @@ -225,21 +200,17 @@ class UnbanHandler: self.user_id = await self.client.resolve_peer(user_id) await self.edit_message() except UserAdminInvalid: - await self.message.edit("No rights") + await self.message.edit('No rights') except ChatAdminRequired: - await self.message.edit("No rights") + await self.message.edit('No rights') except Exception as e: await self.message.edit(format_exc(e)) async def edit_message(self): - text_c = "".join( - f" {_}" - for _ in self.cause.split() - if _.lower() not in ["delete_history", "report_spam"] - ) + text_c = ''.join(f' {_}' for _ in self.cause.split() if _.lower() not in ['delete_history', 'report_spam']) await self.message.edit( - f"{self.name} unbanned!" - + f"\n{'Cause: ' + text_c.split(maxsplit=1)[1] + '' if len(text_c.split()) > 1 else ''}" + f'{self.name} unbanned!' + + f'\n{"Cause: " + text_c.split(maxsplit=1)[1] + "" if len(text_c.split()) > 1 else ""}' ) @@ -258,7 +229,7 @@ class KickHandler: elif not self.message.reply_to_message: await self.handle_non_reply_kick() else: - await self.message.edit("Unsupported") + await self.message.edit('Unsupported') async def handle_reply_kick(self): if self.message.chat.type not in [ChatType.PRIVATE, ChatType.CHANNEL]: @@ -266,7 +237,7 @@ class KickHandler: self.name = self.message.reply_to_message.from_user.first_name await self.kick_user(self.message.reply_to_message.from_user.id) else: - await self.message.edit("Reply on user msg") + await self.message.edit('Reply on user msg') async def handle_non_reply_kick(self): if self.message.chat.type not in [ChatType.PRIVATE, ChatType.CHANNEL]: @@ -274,21 +245,19 @@ class KickHandler: user_to_kick = await self.get_user_to_kick() if user_to_kick: self.name = ( - user_to_kick.first_name - if getattr(user_to_kick, "first_name", None) - else user_to_kick.title + user_to_kick.first_name if getattr(user_to_kick, 'first_name', None) else user_to_kick.title ) await self.kick_user(user_to_kick.id) else: - await self.message.edit("user_id or username") + await self.message.edit('user_id or username') async def get_user_to_kick(self): - user_type = await check_username_or_id(self.cause.split(" ")[1]) - if user_type == "channel": - return await self.client.get_chat(self.cause.split(" ")[1]) - if user_type == "user": - return await self.client.get_users(self.cause.split(" ")[1]) - await self.message.edit("Invalid user type") + user_type = await check_username_or_id(self.cause.split(' ')[1]) + if user_type == 'channel': + return await self.client.get_chat(self.cause.split(' ')[1]) + if user_type == 'user': + return await self.client.get_users(self.cause.split(' ')[1]) + await self.message.edit('Invalid user type') return None async def kick_user(self, user_id): @@ -304,14 +273,14 @@ class KickHandler: await self.client.unban_chat_member(self.message.chat.id, user_id) await self.edit_message() except UserAdminInvalid: - await self.message.edit("No rights") + await self.message.edit('No rights') except ChatAdminRequired: - await self.message.edit("No rights") + await self.message.edit('No rights') except Exception as e: await self.message.edit(format_exc(e)) async def handle_additional_actions(self): - if "report_spam" in self.cause.lower().split(): + if 'report_spam' in self.cause.lower().split(): await self.client.invoke( functions.channels.ReportSpam( channel=self.channel, @@ -319,22 +288,16 @@ class KickHandler: id=[self.message.reply_to_message.id], ) ) - if "delete_history" in self.cause.lower().split(): + if 'delete_history' in self.cause.lower().split(): await self.client.invoke( - functions.channels.DeleteParticipantHistory( - channel=self.channel, participant=self.user_id - ) + functions.channels.DeleteParticipantHistory(channel=self.channel, participant=self.user_id) ) async def edit_message(self): - text_c = "".join( - f" {_}" - for _ in self.cause.split() - if _.lower() not in ["delete_history", "report_spam"] - ) + text_c = ''.join(f' {_}' for _ in self.cause.split() if _.lower() not in ['delete_history', 'report_spam']) await self.message.edit( - f"{self.name} kicked!" - + f"\n{'Cause: ' + text_c.split(maxsplit=1)[1] + '' if len(text_c.split()) > 1 else ''}" + f'{self.name} kicked!' + + f'\n{"Cause: " + text_c.split(maxsplit=1)[1] + "" if len(text_c.split()) > 1 else ""}' ) @@ -346,7 +309,7 @@ class KickDeletedAccountsHandler: self.kicked_count = 0 async def kick_deleted_accounts(self): - await self.message.edit("Kicking deleted accounts...") + await self.message.edit('Kicking deleted accounts...') try: async for member in self.client.get_chat_members(self.chat_id): if member.user.is_deleted: @@ -355,16 +318,14 @@ class KickDeletedAccountsHandler: except Exception as e: return await self.message.edit(format_exc(e)) await self.message.edit( - f"Successfully kicked {self.kicked_count} deleted account(s)", + f'Successfully kicked {self.kicked_count} deleted account(s)', ) async def kick_member(self, user_id): try: - await self.client.ban_chat_member( - self.chat_id, user_id, datetime.now() + timedelta(seconds=31) - ) + await self.client.ban_chat_member(self.chat_id, user_id, datetime.now() + timedelta(seconds=31)) except Exception as e: - await self.message.edit(f"Failed to kick user {user_id}: {format_exc(e)}") + await self.message.edit(f'Failed to kick user {user_id}: {format_exc(e)}') class TimeMuteHandler: @@ -373,7 +334,7 @@ class TimeMuteHandler: self.message = message self.cause = text(message) self.chat_id = message.chat.id - self.tmuted_users = db.get("core.ats", f"c{self.chat_id}", []) + self.tmuted_users = db.get('core.ats', f'c{self.chat_id}', []) async def handle_tmute(self): if self.message.reply_to_message: @@ -381,19 +342,19 @@ class TimeMuteHandler: elif not self.message.reply_to_message: await self.handle_non_reply_tmute() else: - await self.message.edit("Unsupported") + await self.message.edit('Unsupported') async def handle_reply_tmute(self): if self.message.chat.type not in [ChatType.PRIVATE, ChatType.CHANNEL]: user_for_tmute, name = await get_user_and_name(self.message) if user_for_tmute in self.tmuted_users: - await self.message.edit(f"{name} already in tmute") + await self.message.edit(f'{name} already in tmute') else: self.tmuted_users.append(user_for_tmute) - db.set("core.ats", f"c{self.chat_id}", self.tmuted_users) + db.set('core.ats', f'c{self.chat_id}', self.tmuted_users) await self.message.edit( - f"{name} in tmute" - + f"\n{'Cause: ' + self.cause.split(maxsplit=1)[1] + '' if len(self.cause.split()) > 1 else ''}", + f'{name} in tmute' + + f'\n{"Cause: " + self.cause.split(maxsplit=1)[1] + "" if len(self.cause.split()) > 1 else ""}', ) async def handle_non_reply_tmute(self): @@ -402,31 +363,29 @@ class TimeMuteHandler: user_to_tmute = await self.get_user_to_tmute() if user_to_tmute: name = ( - user_to_tmute.first_name - if getattr(user_to_tmute, "first_name", None) - else user_to_tmute.title + user_to_tmute.first_name if getattr(user_to_tmute, 'first_name', None) else user_to_tmute.title ) if user_to_tmute.id not in self.tmuted_users: self.tmuted_users.append(user_to_tmute.id) - db.set("core.ats", f"c{self.chat_id}", self.tmuted_users) + db.set('core.ats', f'c{self.chat_id}', self.tmuted_users) await self.message.edit( - f"{name} in tmute" - + f"\n{'Cause: ' + self.cause.split(maxsplit=2)[2] + '' if len(self.cause.split()) > 2 else ''}", + f'{name} in tmute' + + f'\n{"Cause: " + self.cause.split(maxsplit=2)[2] + "" if len(self.cause.split()) > 2 else ""}', ) else: await self.message.edit( - f"{name} already in tmute", + f'{name} already in tmute', ) else: - await self.message.edit("user_id or username") + await self.message.edit('user_id or username') async def get_user_to_tmute(self): - user_type = await check_username_or_id(self.cause.split(" ")[1]) - if user_type == "channel": - return await self.client.get_chat(self.cause.split(" ")[1]) - if user_type == "user": - return await self.client.get_users(self.cause.split(" ")[1]) - await self.message.edit("Invalid user type") + user_type = await check_username_or_id(self.cause.split(' ')[1]) + if user_type == 'channel': + return await self.client.get_chat(self.cause.split(' ')[1]) + if user_type == 'user': + return await self.client.get_users(self.cause.split(' ')[1]) + await self.message.edit('Invalid user type') return None @@ -436,7 +395,7 @@ class TimeUnmuteHandler: self.message = message self.cause = text(message) self.chat_id = message.chat.id - self.tmuted_users = db.get("core.ats", f"c{self.chat_id}", []) + self.tmuted_users = db.get('core.ats', f'c{self.chat_id}', []) async def handle_tunmute(self): if self.message.reply_to_message: @@ -444,19 +403,19 @@ class TimeUnmuteHandler: elif not self.message.reply_to_message: await self.handle_non_reply_tunmute() else: - await self.message.edit("Unsupported") + await self.message.edit('Unsupported') async def handle_reply_tunmute(self): if self.message.chat.type not in [ChatType.PRIVATE, ChatType.CHANNEL]: user_for_tunmute, name = await get_user_and_name(self.message) if user_for_tunmute not in self.tmuted_users: - await self.message.edit(f"{name} not in tmute") + await self.message.edit(f'{name} not in tmute') else: self.tmuted_users.remove(user_for_tunmute) - db.set("core.ats", f"c{self.chat_id}", self.tmuted_users) + db.set('core.ats', f'c{self.chat_id}', self.tmuted_users) await self.message.edit( - f"{name} tunmuted" - + f"\n{'Cause: ' + self.cause.split(maxsplit=1)[1] + '' if len(self.cause.split()) > 1 else ''}", + f'{name} tunmuted' + + f'\n{"Cause: " + self.cause.split(maxsplit=1)[1] + "" if len(self.cause.split()) > 1 else ""}', ) async def handle_non_reply_tunmute(self): @@ -466,30 +425,30 @@ class TimeUnmuteHandler: if user_to_tunmute: name = ( user_to_tunmute.first_name - if getattr(user_to_tunmute, "first_name", None) + if getattr(user_to_tunmute, 'first_name', None) else user_to_tunmute.title ) if user_to_tunmute.id not in self.tmuted_users: await self.message.edit( - f"{name} not in tmute", + f'{name} not in tmute', ) else: self.tmuted_users.remove(user_to_tunmute.id) - db.set("core.ats", f"c{self.chat_id}", self.tmuted_users) + db.set('core.ats', f'c{self.chat_id}', self.tmuted_users) await self.message.edit( - f"{name} tunmuted" - + f"\n{'Cause: ' + self.cause.split(maxsplit=2)[2] + '' if len(self.cause.split()) > 2 else ''}", + f'{name} tunmuted' + + f'\n{"Cause: " + self.cause.split(maxsplit=2)[2] + "" if len(self.cause.split()) > 2 else ""}', ) else: - await self.message.edit("user_id or username") + await self.message.edit('user_id or username') async def get_user_to_tunmute(self): - user_type = await check_username_or_id(self.cause.split(" ")[1]) - if user_type == "channel": - return await self.client.get_chat(self.cause.split(" ")[1]) - if user_type == "user": - return await self.client.get_users(self.cause.split(" ")[1]) - await self.message.edit("Invalid user type") + user_type = await check_username_or_id(self.cause.split(' ')[1]) + if user_type == 'channel': + return await self.client.get_chat(self.cause.split(' ')[1]) + if user_type == 'user': + return await self.client.get_users(self.cause.split(' ')[1]) + await self.message.edit('Invalid user type') return None @@ -498,32 +457,32 @@ class TimeMuteUsersHandler: self.client = client self.message = message self.chat_id = message.chat.id - self.tmuted_users = db.get("core.ats", f"c{self.chat_id}", []) + self.tmuted_users = db.get('core.ats', f'c{self.chat_id}', []) async def list_tmuted_users(self): if self.message.chat.type not in [ChatType.PRIVATE, ChatType.CHANNEL]: - text = f"All users {self.message.chat.title} who are now in tmute\n\n" + text = f'All users {self.message.chat.title} who are now in tmute\n\n' count = 0 for user in self.tmuted_users: try: name = await self.get_user_name(user) if name: count += 1 - text += f"{count}. {name}\n" + text += f'{count}. {name}\n' except PeerIdInvalid: pass if count == 0: - await self.message.edit("No users in tmute") + await self.message.edit('No users in tmute') else: - text += f"\nTotal users in tmute {count}" + text += f'\nTotal users in tmute {count}' await self.message.edit(text) else: - await self.message.edit("Unsupported") + await self.message.edit('Unsupported') async def get_user_name(self, user_id): try: _name_ = await self.client.get_chat(user_id) - if await check_username_or_id(_name_.id) == "channel": + if await check_username_or_id(_name_.id) == 'channel': channel = await self.client.invoke( functions.channels.GetChannels( id=[ @@ -535,7 +494,7 @@ class TimeMuteUsersHandler: ) ) return channel.chats[0].title - if await check_username_or_id(_name_.id) == "user": + if await check_username_or_id(_name_.id) == 'user': user = await self.client.get_users(_name_.id) return user.first_name except PeerIdInvalid: @@ -556,7 +515,7 @@ class UnmuteHandler: elif not self.message.reply_to_message: await self.handle_non_reply_unmute() else: - await self.message.edit("Unsupported") + await self.message.edit('Unsupported') async def handle_reply_unmute(self): if self.message.chat.type not in [ChatType.PRIVATE, ChatType.CHANNEL]: @@ -565,17 +524,17 @@ class UnmuteHandler: try: await self.unmute_user(user_for_unmute.id) await self.message.edit( - f"{user_for_unmute.first_name} unmuted" - + f"\n{'Cause: ' + self.cause.split(' ', maxsplit=1)[1] + '' if len(self.cause.split()) > 1 else ''}" + f'{user_for_unmute.first_name} unmuted' + + f'\n{"Cause: " + self.cause.split(" ", maxsplit=1)[1] + "" if len(self.cause.split()) > 1 else ""}' ) except UserAdminInvalid: - await self.message.edit("No rights") + await self.message.edit('No rights') except ChatAdminRequired: - await self.message.edit("No rights") + await self.message.edit('No rights') except Exception as e: await self.message.edit(format_exc(e)) else: - await self.message.edit("Reply on user msg") + await self.message.edit('Reply on user msg') async def handle_non_reply_unmute(self): if self.message.chat.type not in [ChatType.PRIVATE, ChatType.CHANNEL]: @@ -585,27 +544,27 @@ class UnmuteHandler: try: await self.unmute_user(user_to_unmute.id) await self.message.edit( - f"{user_to_unmute.first_name} unmuted!" - + f"\n{'Cause: ' + self.cause.split(' ', maxsplit=2)[2] + '' if len(self.cause.split()) > 2 else ''}" + f'{user_to_unmute.first_name} unmuted!' + + f'\n{"Cause: " + self.cause.split(" ", maxsplit=2)[2] + "" if len(self.cause.split()) > 2 else ""}' ) except UserAdminInvalid: - await self.message.edit("No rights") + await self.message.edit('No rights') except ChatAdminRequired: - await self.message.edit("No rights") + await self.message.edit('No rights') except Exception as e: await self.message.edit(format_exc(e)) else: - await self.message.edit("User is not found") + await self.message.edit('User is not found') else: - await self.message.edit("user_id or username") + await self.message.edit('user_id or username') async def get_user_to_unmute(self): - user_type = await check_username_or_id(self.cause.split(" ")[1]) - if user_type == "channel": - return await self.client.get_chat(self.cause.split(" ")[1]) - if user_type == "user": - return await self.client.get_users(self.cause.split(" ")[1]) - await self.message.edit("Invalid user type") + user_type = await check_username_or_id(self.cause.split(' ')[1]) + if user_type == 'channel': + return await self.client.get_chat(self.cause.split(' ')[1]) + if user_type == 'user': + return await self.client.get_users(self.cause.split(' ')[1]) + await self.message.edit('Invalid user type') return None async def unmute_user(self, user_id): @@ -633,7 +592,7 @@ class MuteHandler: elif not self.message.reply_to_message: await self.handle_non_reply_mute() else: - await self.message.edit("Unsupported") + await self.message.edit('Unsupported') async def handle_reply_mute(self): if self.message.chat.type not in [ChatType.PRIVATE, ChatType.CHANNEL]: @@ -642,17 +601,15 @@ class MuteHandler: mute_seconds = self.calculate_mute_seconds() try: await self.mute_user(user_for_mute.id, mute_seconds) - await self.message.edit( - self.construct_mute_message(user_for_mute, mute_seconds) - ) + await self.message.edit(self.construct_mute_message(user_for_mute, mute_seconds)) except UserAdminInvalid: - await self.message.edit("No rights") + await self.message.edit('No rights') except ChatAdminRequired: - await self.message.edit("No rights") + await self.message.edit('No rights') except Exception as e: await self.message.edit(format_exc(e)) else: - await self.message.edit("Reply on user msg") + await self.message.edit('Reply on user msg') async def handle_non_reply_mute(self): if self.message.chat.type not in [ChatType.PRIVATE, ChatType.CHANNEL]: @@ -662,42 +619,40 @@ class MuteHandler: mute_seconds = self.calculate_mute_seconds() try: await self.mute_user(user_to_mute.id, mute_seconds) - await self.message.edit( - self.construct_mute_message(user_to_mute, mute_seconds) - ) + await self.message.edit(self.construct_mute_message(user_to_mute, mute_seconds)) except UserAdminInvalid: - await self.message.edit("No rights") + await self.message.edit('No rights') except ChatAdminRequired: - await self.message.edit("No rights") + await self.message.edit('No rights') except Exception as e: await self.message.edit(format_exc(e)) else: - await self.message.edit("User is not found") + await self.message.edit('User is not found') else: - await self.message.edit("user_id or username") + await self.message.edit('user_id or username') async def get_user_to_mute(self): - user_type = await check_username_or_id(self.cause.split(" ")[1]) - if user_type == "channel": - return await self.client.get_chat(self.cause.split(" ")[1]) - if user_type == "user": - return await self.client.get_users(self.cause.split(" ")[1]) - await self.message.edit("Invalid user type") + user_type = await check_username_or_id(self.cause.split(' ')[1]) + if user_type == 'channel': + return await self.client.get_chat(self.cause.split(' ')[1]) + if user_type == 'user': + return await self.client.get_users(self.cause.split(' ')[1]) + await self.message.edit('Invalid user type') return None def calculate_mute_seconds(self): mute_seconds: int = 0 - for character in "mhdw": - match = re.search(rf"(\d+|(\d+\.\d+)){character}", self.message.text) + for character in 'mhdw': + match = re.search(rf'(\d+|(\d+\.\d+)){character}', self.message.text) if match: value = float(match.string[match.start() : match.end() - 1]) - if character == "m": + if character == 'm': mute_seconds += int(value * 60) - if character == "h": + if character == 'h': mute_seconds += int(value * 3600) - if character == "d": + if character == 'd': mute_seconds += int(value * 86400) - if character == "w": + if character == 'w': mute_seconds += int(value * 604800) return mute_seconds @@ -720,19 +675,19 @@ class MuteHandler: await self.message.edit(format_exc(e)) def construct_mute_message(self, user, mute_seconds): - mute_time: Dict[str, int] = { - "days": mute_seconds // 86400, - "hours": mute_seconds % 86400 // 3600, - "minutes": mute_seconds % 86400 % 3600 // 60, + mute_time: dict[str, int] = { + 'days': mute_seconds // 86400, + 'hours': mute_seconds % 86400 // 3600, + 'minutes': mute_seconds % 86400 % 3600 // 60, } message_text = ( - f"{user.first_name} was muted for" - f" {((str(mute_time['days']) + ' day') if mute_time['days'] > 0 else '') + ('s' if mute_time['days'] > 1 else '')}" - f" {((str(mute_time['hours']) + ' hour') if mute_time['hours'] > 0 else '') + ('s' if mute_time['hours'] > 1 else '')}" - f" {((str(mute_time['minutes']) + ' minute') if mute_time['minutes'] > 0 else '') + ('s' if mute_time['minutes'] > 1 else '')}" - + f"\n{'Cause: ' + self.cause.split(' ', maxsplit=2)[2] + '' if len(self.cause.split()) > 2 else ''}" + f'{user.first_name} was muted for' + f' {((str(mute_time["days"]) + " day") if mute_time["days"] > 0 else "") + ("s" if mute_time["days"] > 1 else "")}' + f' {((str(mute_time["hours"]) + " hour") if mute_time["hours"] > 0 else "") + ("s" if mute_time["hours"] > 1 else "")}' + f' {((str(mute_time["minutes"]) + " minute") if mute_time["minutes"] > 0 else "") + ("s" if mute_time["minutes"] > 1 else "")}' + + f'\n{"Cause: " + self.cause.split(" ", maxsplit=2)[2] + "" if len(self.cause.split()) > 2 else ""}' ) - message_text = " ".join(message_text.split()) + message_text = ' '.join(message_text.split()) return message_text @@ -743,17 +698,17 @@ class DemoteHandler: self.cause = text(message) self.chat_id = message.chat.id self.common_privileges_demote = { - "is_anonymous": False, - "can_manage_chat": False, - "can_change_info": False, - "can_post_messages": False, - "can_edit_messages": False, - "can_delete_messages": False, - "can_manage_video_chats": False, - "can_restrict_members": False, - "can_invite_users": False, - "can_pin_messages": False, - "can_promote_members": False, + 'is_anonymous': False, + 'can_manage_chat': False, + 'can_change_info': False, + 'can_post_messages': False, + 'can_edit_messages': False, + 'can_delete_messages': False, + 'can_manage_video_chats': False, + 'can_restrict_members': False, + 'can_invite_users': False, + 'can_pin_messages': False, + 'can_promote_members': False, } async def handle_demote(self): @@ -762,7 +717,7 @@ class DemoteHandler: elif not self.message.reply_to_message: await self.handle_non_reply_demote() else: - await self.message.edit("Unsupported") + await self.message.edit('Unsupported') async def handle_reply_demote(self): if self.message.chat.type not in [ChatType.PRIVATE, ChatType.CHANNEL]: @@ -770,17 +725,15 @@ class DemoteHandler: if user_for_demote: try: await self.demote_user(user_for_demote.id) - await self.message.edit( - self.construct_demote_message(user_for_demote) - ) + await self.message.edit(self.construct_demote_message(user_for_demote)) except UserAdminInvalid: - await self.message.edit("No rights") + await self.message.edit('No rights') except ChatAdminRequired: - await self.message.edit("No rights") + await self.message.edit('No rights') except Exception as e: await self.message.edit(format_exc(e)) else: - await self.message.edit("Reply on user msg") + await self.message.edit('Reply on user msg') async def handle_non_reply_demote(self): if self.message.chat.type not in [ChatType.PRIVATE, ChatType.CHANNEL]: @@ -789,27 +742,25 @@ class DemoteHandler: if user_to_demote: try: await self.demote_user(user_to_demote.id) - await self.message.edit( - self.construct_demote_message(user_to_demote) - ) + await self.message.edit(self.construct_demote_message(user_to_demote)) except UserAdminInvalid: - await self.message.edit("No rights") + await self.message.edit('No rights') except ChatAdminRequired: - await self.message.edit("No rights") + await self.message.edit('No rights') except Exception as e: await self.message.edit(format_exc(e)) else: - await self.message.edit("User is not found") + await self.message.edit('User is not found') else: - await self.message.edit("user_id or username not provided!") + await self.message.edit('user_id or username not provided!') async def get_user_to_demote(self): - user_type = await check_username_or_id(self.cause.split(" ")[1]) - if user_type == "channel": - return await self.client.get_chat(self.cause.split(" ")[1]) - if user_type == "user": - return await self.client.get_users(self.cause.split(" ")[1]) - await self.message.edit("Invalid user type") + user_type = await check_username_or_id(self.cause.split(' ')[1]) + if user_type == 'channel': + return await self.client.get_chat(self.cause.split(' ')[1]) + if user_type == 'user': + return await self.client.get_users(self.cause.split(' ')[1]) + await self.message.edit('Invalid user type') return None async def demote_user(self, user_id): @@ -828,8 +779,8 @@ class DemoteHandler: def construct_demote_message(self, user): return ( - f"{user.first_name} demoted!" - + f"\n{'Cause: ' + self.cause.split(' ', maxsplit=2)[2] + '' if len(self.cause.split()) > 2 else ''}" + f'{user.first_name} demoted!' + + f'\n{"Cause: " + self.cause.split(" ", maxsplit=2)[2] + "" if len(self.cause.split()) > 2 else ""}' ) @@ -840,10 +791,10 @@ class PromoteHandler: self.cause = text(message) self.chat_id = message.chat.id self.common_privileges_promote = { - "can_delete_messages": True, - "can_restrict_members": True, - "can_invite_users": True, - "can_pin_messages": True, + 'can_delete_messages': True, + 'can_restrict_members': True, + 'can_invite_users': True, + 'can_pin_messages': True, } async def handle_promote(self): @@ -852,68 +803,56 @@ class PromoteHandler: elif not self.message.reply_to_message: await self.handle_non_reply_promote() else: - await self.message.edit("Unsupported") + await self.message.edit('Unsupported') async def handle_reply_promote(self): if self.message.chat.type not in [ChatType.PRIVATE, ChatType.CHANNEL]: user_for_promote = self.message.reply_to_message.from_user - promote_title = ( - self.message.text.split(maxsplit=1)[1] - if len(self.message.text.split()) > 1 - else None - ) + promote_title = self.message.text.split(maxsplit=1)[1] if len(self.message.text.split()) > 1 else None if promote_title and len(promote_title) > 16: promote_title = promote_title[:16] if user_for_promote: try: await self.promote_user(user_for_promote.id, promote_title) - await self.message.edit( - self.construct_promote_message(user_for_promote) - ) + await self.message.edit(self.construct_promote_message(user_for_promote)) except UserAdminInvalid: - await self.message.edit("No rights") + await self.message.edit('No rights') except ChatAdminRequired: - await self.message.edit("No rights") + await self.message.edit('No rights') except Exception as e: await self.message.edit(format_exc(e)) else: - await self.message.edit("Reply on user msg") + await self.message.edit('Reply on user msg') async def handle_non_reply_promote(self): if self.message.chat.type not in [ChatType.PRIVATE, ChatType.CHANNEL]: if len(self.cause.split()) > 1: user_to_promote = await self.get_user_to_promote() - promote_title = ( - " ".join(self.cause.split(" ")[2:]) - if len(self.cause.split(" ")) > 2 - else None - ) + promote_title = ' '.join(self.cause.split(' ')[2:]) if len(self.cause.split(' ')) > 2 else None if promote_title and len(promote_title) > 16: promote_title = promote_title[:16] if user_to_promote: try: await self.promote_user(user_to_promote.id, promote_title) - await self.message.edit( - self.construct_promote_message(user_to_promote) - ) + await self.message.edit(self.construct_promote_message(user_to_promote)) except UserAdminInvalid: - await self.message.edit("No rights") + await self.message.edit('No rights') except ChatAdminRequired: - await self.message.edit("No rights") + await self.message.edit('No rights') except Exception as e: await self.message.edit(format_exc(e)) else: - await self.message.edit("User is not found") + await self.message.edit('User is not found') else: - await self.message.edit("user_id or username not provided!") + await self.message.edit('user_id or username not provided!') async def get_user_to_promote(self): - user_type = await check_username_or_id(self.cause.split(" ")[1]) - if user_type == "channel": - return await self.client.get_chat(self.cause.split(" ")[1]) - if user_type == "user": - return await self.client.get_users(self.cause.split(" ")[1]) - await self.message.edit("Invalid user type") + user_type = await check_username_or_id(self.cause.split(' ')[1]) + if user_type == 'channel': + return await self.client.get_chat(self.cause.split(' ')[1]) + if user_type == 'user': + return await self.client.get_users(self.cause.split(' ')[1]) + await self.message.edit('Invalid user type') return None async def promote_user(self, user_id, title): @@ -924,7 +863,7 @@ class PromoteHandler: privileges=ChatPrivileges(**self.common_privileges_promote), title=title, ) - if len(self.cause.split()) > 1 and self.message.chat.type == "group": + if len(self.cause.split()) > 1 and self.message.chat.type == 'group': await self.client.set_administrator_title( self.chat_id, user_id, @@ -939,8 +878,8 @@ class PromoteHandler: def construct_promote_message(self, user): return ( - f"{user.first_name} promoted!" - + f"\n{'Title: ' + self.cause.split(' ', maxsplit=1)[1] + '' if len(self.cause.split()) > 1 else ''}" + f'{user.first_name} promoted!' + + f'\n{"Title: " + self.cause.split(" ", maxsplit=1)[1] + "" if len(self.cause.split()) > 1 else ""}' ) @@ -953,42 +892,40 @@ class AntiChannelsHandler: async def handle_anti_channels(self): if self.message.chat.type not in [ChatType.GROUP, ChatType.SUPERGROUP]: - await self.message.edit("Not supported in non-group chats") + await self.message.edit('Not supported in non-group chats') return command = self.message.command if len(command) == 1: await self.toggle_anti_channels_status() - elif command[1] in ["enable", "on", "1", "yes", "true"]: + elif command[1] in ['enable', 'on', '1', 'yes', 'true']: await self.enable_anti_channels() - elif command[1] in ["disable", "off", "0", "no", "false"]: + elif command[1] in ['disable', 'off', '0', 'no', 'false']: await self.disable_anti_channels() else: - await self.message.edit( - f"Usage: {self.prefix}antich [enable|disable]" - ) + await self.message.edit(f'Usage: {self.prefix}antich [enable|disable]') async def toggle_anti_channels_status(self): - current_status = db.get("core.ats", f"antich{self.chat_id}", False) + current_status = db.get('core.ats', f'antich{self.chat_id}', False) new_status = not current_status - db.set("core.ats", f"antich{self.chat_id}", new_status) + db.set('core.ats', f'antich{self.chat_id}', new_status) if new_status: - await self.message.edit("Blocking channels in this chat enabled.") + await self.message.edit('Blocking channels in this chat enabled.') else: - await self.message.edit("Blocking channels in this chat disabled.") + await self.message.edit('Blocking channels in this chat disabled.') async def enable_anti_channels(self): - db.set("core.ats", f"antich{self.chat_id}", True) + db.set('core.ats', f'antich{self.chat_id}', True) group = await self.client.get_chat(self.chat_id) if group.linked_chat: - db.set("core.ats", f"linked{self.chat_id}", group.linked_chat.id) + db.set('core.ats', f'linked{self.chat_id}', group.linked_chat.id) else: - db.set("core.ats", f"linked{self.chat_id}", 0) - await self.message.edit("Blocking channels in this chat enabled.") + db.set('core.ats', f'linked{self.chat_id}', 0) + await self.message.edit('Blocking channels in this chat enabled.') async def disable_anti_channels(self): - db.set("core.ats", f"antich{self.chat_id}", False) - await self.message.edit("Blocking channels in this chat disabled.") + db.set('core.ats', f'antich{self.chat_id}', False) + await self.message.edit('Blocking channels in this chat disabled.') class DeleteHistoryHandler: @@ -1006,7 +943,7 @@ class DeleteHistoryHandler: elif not self.message.reply_to_message: await self.handle_non_reply_delete_history() else: - await self.message.edit("Unsupported") + await self.message.edit('Unsupported') async def handle_reply_delete_history(self): if self.message.reply_to_message.from_user: @@ -1014,13 +951,13 @@ class DeleteHistoryHandler: user_for_delete, name = await get_user_and_name(self.message) await self.delete_user_history(user_for_delete, name) except UserAdminInvalid: - await self.message.edit("No rights") + await self.message.edit('No rights') except ChatAdminRequired: - await self.message.edit("No rights") + await self.message.edit('No rights') except Exception as e: await self.message.edit(format_exc(e)) else: - await self.message.edit("Reply on user msg") + await self.message.edit('Reply on user msg') async def handle_non_reply_delete_history(self): if len(self.cause.split()) > 1: @@ -1029,47 +966,43 @@ class DeleteHistoryHandler: if user_to_delete: name = ( user_to_delete.first_name - if getattr(user_to_delete, "first_name", None) + if getattr(user_to_delete, 'first_name', None) else user_to_delete.title ) await self.delete_user_history(user_to_delete.id, name) else: - await self.message.edit("User is not found") + await self.message.edit('User is not found') except PeerIdInvalid: - await self.message.edit("User is not found") + await self.message.edit('User is not found') except UsernameInvalid: - await self.message.edit("User is not found") + await self.message.edit('User is not found') except IndexError: - await self.message.edit("User is not found") + await self.message.edit('User is not found') else: - await self.message.edit("user_id or username") + await self.message.edit('user_id or username') async def get_user_to_delete(self): - user_type = await check_username_or_id(self.cause.split(" ")[1]) - if user_type == "channel": - return await self.client.get_chat(self.cause.split(" ")[1]) - if user_type == "user": - return await self.client.get_users(self.cause.split(" ")[1]) - await self.message.edit("Invalid user type") + user_type = await check_username_or_id(self.cause.split(' ')[1]) + if user_type == 'channel': + return await self.client.get_chat(self.cause.split(' ')[1]) + if user_type == 'user': + return await self.client.get_users(self.cause.split(' ')[1]) + await self.message.edit('Invalid user type') return None async def delete_user_history(self, user_id, name): try: channel = await self.client.resolve_peer(self.chat_id) user_id = await self.client.resolve_peer(user_id) - await self.client.invoke( - functions.channels.DeleteParticipantHistory( - channel=channel, participant=user_id - ) - ) + await self.client.invoke(functions.channels.DeleteParticipantHistory(channel=channel, participant=user_id)) await self.message.edit( - f"History from {name} was deleted!" - + f"\n{'Cause: ' + self.cause.split(' ', maxsplit=1)[1] + '' if len(self.cause.split()) > 1 else ''}" + f'History from {name} was deleted!' + + f'\n{"Cause: " + self.cause.split(" ", maxsplit=1)[1] + "" if len(self.cause.split()) > 1 else ""}' ) except UserAdminInvalid: - await self.message.edit("No rights") + await self.message.edit('No rights') except ChatAdminRequired: - await self.message.edit("No rights") + await self.message.edit('No rights') except Exception as e: await self.message.edit(format_exc(e)) @@ -1084,45 +1017,41 @@ class AntiRaidHandler: async def handle_antiraid(self): command = self.message.command if len(command) > 1: - if command[1] == "on": + if command[1] == 'on': await self.enable_antiraid() - elif command[1] == "off": + elif command[1] == 'off': await self.disable_antiraid() else: await self.toggle_antiraid() async def enable_antiraid(self): - db.set("core.ats", f"antiraid{self.chat_id}", True) + db.set('core.ats', f'antiraid{self.chat_id}', True) group = await self.client.get_chat(self.chat_id) if group.linked_chat: - db.set("core.ats", f"linked{self.chat_id}", group.linked_chat.id) + db.set('core.ats', f'linked{self.chat_id}', group.linked_chat.id) else: - db.set("core.ats", f"linked{self.chat_id}", 0) - await self.message.edit( - "Anti-raid mode enabled!\n" - f"Disable with: {self.prefix}antiraid off" - ) + db.set('core.ats', f'linked{self.chat_id}', 0) + await self.message.edit(f'Anti-raid mode enabled!\nDisable with: {self.prefix}antiraid off') async def disable_antiraid(self): - db.set("core.ats", f"antiraid{self.chat_id}", False) - await self.message.edit("Anti-raid mode disabled") + db.set('core.ats', f'antiraid{self.chat_id}', False) + await self.message.edit('Anti-raid mode disabled') async def toggle_antiraid(self): - current_status = db.get("core.ats", f"antiraid{self.chat_id}", False) + current_status = db.get('core.ats', f'antiraid{self.chat_id}', False) new_status = not current_status - db.set("core.ats", f"antiraid{self.chat_id}", new_status) + db.set('core.ats', f'antiraid{self.chat_id}', new_status) if new_status: group = await self.client.get_chat(self.chat_id) if group.linked_chat: - db.set("core.ats", f"linked{self.chat_id}", group.linked_chat.id) + db.set('core.ats', f'linked{self.chat_id}', group.linked_chat.id) else: - db.set("core.ats", f"linked{self.chat_id}", 0) + db.set('core.ats', f'linked{self.chat_id}', 0) await self.message.edit( - "Anti-raid mode enabled!\n" - f"Disable with: {self.prefix}antiraid off" + f'Anti-raid mode enabled!\nDisable with: {self.prefix}antiraid off' ) else: - await self.message.edit("Anti-raid mode disabled") + await self.message.edit('Anti-raid mode disabled') class NoteSendHandler: @@ -1134,28 +1063,28 @@ class NoteSendHandler: async def handle_note_send(self): if len(self.message.text.split()) >= 2: - await self.message.edit("Loading...") + await self.message.edit('Loading...') note_name = self.message.text.split(maxsplit=1)[1] - find_note = db.get("core.notes", f"note{note_name}", False) + find_note = db.get('core.notes', f'note{note_name}', False) if find_note: try: await self.send_note(find_note) except RPCError: await self.message.edit( - "Sorry, but this note is unavailable.\n\n" - f"You can delete this note with " - f"{self.prefix}clear {note_name}" + 'Sorry, but this note is unavailable.\n\n' + f'You can delete this note with ' + f'{self.prefix}clear {note_name}' ) else: - await self.message.edit("There is no such note") + await self.message.edit('There is no such note') else: await self.message.edit( - f"Example: {self.prefix}note note_name", + f'Example: {self.prefix}note note_name', ) async def send_note(self, find_note): - if find_note.get("MEDIA_GROUP"): + if find_note.get('MEDIA_GROUP'): await self.message.delete() await self.send_media_group(find_note) else: @@ -1163,9 +1092,7 @@ class NoteSendHandler: await self.copy_message(find_note) async def send_media_group(self, find_note): - messages_grouped = await self.client.get_media_group( - int(find_note["CHAT_ID"]), int(find_note["MESSAGE_ID"]) - ) + messages_grouped = await self.client.get_media_group(int(find_note['CHAT_ID']), int(find_note['MESSAGE_ID'])) media_grouped_list = self.prepare_media_group(messages_grouped) if self.message.reply_to_message: await self.client.send_media_group( @@ -1180,15 +1107,15 @@ class NoteSendHandler: if self.message.reply_to_message: await self.client.copy_message( self.message.chat.id, - int(find_note["CHAT_ID"]), - int(find_note["MESSAGE_ID"]), + int(find_note['CHAT_ID']), + int(find_note['MESSAGE_ID']), reply_to_message_id=self.message.reply_to_message.id, ) else: await self.client.copy_message( self.message.chat.id, - int(find_note["CHAT_ID"]), - int(find_note["MESSAGE_ID"]), + int(find_note['CHAT_ID']), + int(find_note['MESSAGE_ID']), ) def prepare_media_group(self, messages_grouped): @@ -1221,9 +1148,7 @@ class NoteSendHandler: ) return InputMediaVideo(message.video.file_id, message.caption.markdown) if message.video.thumbs: - return InputMediaVideo( - message.video.file_id, message.video.thumbs[0].file_id - ) + return InputMediaVideo(message.video.file_id, message.video.thumbs[0].file_id) return InputMediaVideo(message.video.file_id) @staticmethod @@ -1241,11 +1166,7 @@ class NoteSendHandler: message.document.thumbs[0].file_id, message.caption.markdown, ) - return InputMediaDocument( - message.document.file_id, message.caption.markdown - ) + return InputMediaDocument(message.document.file_id, message.caption.markdown) if message.document.thumbs: - return InputMediaDocument( - message.document.file_id, message.document.thumbs[0].file_id - ) + return InputMediaDocument(message.document.file_id, message.document.thumbs[0].file_id) return InputMediaDocument(message.document.file_id) diff --git a/utils/misc.py b/utils/misc.py index 4b86d03..9d346cb 100644 --- a/utils/misc.py +++ b/utils/misc.py @@ -1,31 +1,33 @@ from sys import version_info -from .db import db + import git +from .db import db + __all__ = [ - "modules_help", - "requirements_list", - "python_version", - "prefix", - "gitrepo", - "userbot_version", + 'modules_help', + 'requirements_list', + 'python_version', + 'prefix', + 'gitrepo', + 'userbot_version', ] modules_help = {} requirements_list = [] -python_version = f"{version_info[0]}.{version_info[1]}.{version_info[2]}" +python_version = f'{version_info[0]}.{version_info[1]}.{version_info[2]}' -prefix = db.get("core.main", "prefix", ".") +prefix = db.get('core.main', 'prefix', '.') try: - gitrepo = git.Repo(".") + gitrepo = git.Repo('.') except (git.exc.InvalidGitRepositoryError, git.exc.NoSuchPathError): gitrepo = None if gitrepo is not None and len(gitrepo.tags) > 0: - commits_since_tag = list(gitrepo.iter_commits(f"{gitrepo.tags[-1].name}..HEAD")) - userbot_version = f"2.5.{len(commits_since_tag)}" + commits_since_tag = list(gitrepo.iter_commits(f'{gitrepo.tags[-1].name}..HEAD')) + userbot_version = f'2.5.{len(commits_since_tag)}' else: - userbot_version = "2.5.0" + userbot_version = '2.5.0' diff --git a/utils/module.py b/utils/module.py index 2b4cba6..515c064 100644 --- a/utils/module.py +++ b/utils/module.py @@ -4,12 +4,12 @@ from typing import Optional from pyrogram import Client -from utils.scripts import load_module from utils.misc import modules_help +from utils.scripts import load_module class ModuleManager: - _instance: Optional["ModuleManager"] = None + _instance: Optional['ModuleManager'] = None def __init__(self): self.success_modules = 0 @@ -17,27 +17,25 @@ class ModuleManager: self.help_navigator = None @classmethod - def get_instance(cls) -> "ModuleManager": + def get_instance(cls) -> 'ModuleManager': if cls._instance is None: cls._instance = ModuleManager() return cls._instance async def load_modules(self, app: Client): """Load all modules and initialize help navigator""" - for path in Path("modules").rglob("*.py"): + for path in Path('modules').rglob('*.py'): try: - await load_module( - path.stem, app, core="custom_modules" not in path.parent.parts - ) + await load_module(path.stem, app, core='custom_modules' not in path.parent.parts) except Exception: logging.warning("Can't import module %s", path.stem, exc_info=True) self.failed_modules += 1 else: self.success_modules += 1 - logging.info("Imported %d modules", self.success_modules) + logging.info('Imported %d modules', self.success_modules) if self.failed_modules: - logging.warning("Failed to import %d modules", self.failed_modules) + logging.warning('Failed to import %d modules', self.failed_modules) self.help_navigator = HelpNavigator() return self.help_navigator @@ -48,7 +46,7 @@ class HelpNavigator: self.current_page = 1 self.module_list = list(modules_help.keys()) self.total_pages = (len(modules_help) + 9) // 10 - logging.info("Initialized HelpNavigator with %d modules", len(self.module_list)) + logging.info('Initialized HelpNavigator with %d modules', len(self.module_list)) async def send_page(self, message): from utils.misc import prefix @@ -56,13 +54,13 @@ class HelpNavigator: start_index = (self.current_page - 1) * 10 end_index = start_index + 10 page_modules = self.module_list[start_index:end_index] - text = "Help\n" - text += f"For more help on how to use a command, type {prefix}help [module]\n\n" - text += f"Help Page No: {self.current_page}/{self.total_pages}\n\n" + text = 'Help\n' + text += f'For more help on how to use a command, type {prefix}help [module]\n\n' + text += f'Help Page No: {self.current_page}/{self.total_pages}\n\n' for module_name in page_modules: commands = modules_help[module_name] - text += f"• {module_name.title()}: {', '.join([f'{prefix + cmd_name.split()[0]}' for cmd_name in commands.keys()])}\n" - text += f"\nThe number of modules in the userbot: {len(modules_help)}" + text += f'• {module_name.title()}: {", ".join([f"{prefix + cmd_name.split()[0]}" for cmd_name in commands.keys()])}\n' + text += f'\nThe number of modules in the userbot: {len(modules_help)}' await message.edit(text, disable_web_page_preview=True) def next_page(self) -> bool: diff --git a/utils/rentry.py b/utils/rentry.py index dd6d7ad..cda0fc3 100644 --- a/utils/rentry.py +++ b/utils/rentry.py @@ -5,18 +5,17 @@ import asyncio import http.cookiejar import urllib.parse import urllib.request - -from uuid import uuid4 from datetime import datetime, timedelta from http.cookies import SimpleCookie from json import loads as json_loads +from uuid import uuid4 from utils.db import db -BASE_PROTOCOL = "https://" -BASE_URL = "rentry.co" +BASE_PROTOCOL = 'https://' +BASE_URL = 'rentry.co' -_headers = {"Referer": f"{BASE_PROTOCOL}{BASE_URL}"} +_headers = {'Referer': f'{BASE_PROTOCOL}{BASE_URL}'} class UrllibClient: @@ -24,9 +23,7 @@ class UrllibClient: def __init__(self): self.cookie_jar = http.cookiejar.CookieJar() - self.opener = urllib.request.build_opener( - urllib.request.HTTPCookieProcessor(self.cookie_jar) - ) + self.opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(self.cookie_jar)) urllib.request.install_opener(self.opener) def get(self, url, headers=None): @@ -45,58 +42,50 @@ class UrllibClient: def _request(self, request): response = self.opener.open(request) response.status_code = response.getcode() - response.data = response.read().decode("utf-8") + response.data = response.read().decode('utf-8') return response def raw(url: str): client = UrllibClient() - return json_loads(client.get(f"{BASE_PROTOCOL}{BASE_URL}/api/raw/{url}").data) + return json_loads(client.get(f'{BASE_PROTOCOL}{BASE_URL}/api/raw/{url}').data) -def new(text: str, edit_code: str = "", url: str = ""): +def new(text: str, edit_code: str = '', url: str = ''): client, cookie = UrllibClient(), SimpleCookie() - cookie.load(vars(client.get(f"{BASE_PROTOCOL}{BASE_URL}"))["headers"]["Set-Cookie"]) - csrftoken = cookie["csrftoken"].value + cookie.load(vars(client.get(f'{BASE_PROTOCOL}{BASE_URL}'))['headers']['Set-Cookie']) + csrftoken = cookie['csrftoken'].value payload = { - "csrfmiddlewaretoken": csrftoken, - "url": url, - "edit_code": edit_code, - "text": text, + 'csrfmiddlewaretoken': csrftoken, + 'url': url, + 'edit_code': edit_code, + 'text': text, } - return json_loads( - client.post( - f"{BASE_PROTOCOL}{BASE_URL}" + "/api/new", payload, headers=_headers - ).data - ) + return json_loads(client.post(f'{BASE_PROTOCOL}{BASE_URL}' + '/api/new', payload, headers=_headers).data) def edit(url_short: str, edit_code: str, text: str): client, cookie = UrllibClient(), SimpleCookie() - cookie.load(vars(client.get(f"{BASE_PROTOCOL}{BASE_URL}"))["headers"]["Set-Cookie"]) - csrftoken = cookie["csrftoken"].value + cookie.load(vars(client.get(f'{BASE_PROTOCOL}{BASE_URL}'))['headers']['Set-Cookie']) + csrftoken = cookie['csrftoken'].value - payload = {"csrfmiddlewaretoken": csrftoken, "edit_code": edit_code, "text": text} + payload = {'csrfmiddlewaretoken': csrftoken, 'edit_code': edit_code, 'text': text} - return json_loads( - client.post( - f"{BASE_PROTOCOL}{BASE_URL}/api/edit/{url_short}", payload, headers=_headers - ).data - ) + return json_loads(client.post(f'{BASE_PROTOCOL}{BASE_URL}/api/edit/{url_short}', payload, headers=_headers).data) def delete(url_short: str, edit_code: str): client, cookie = UrllibClient(), SimpleCookie() - cookie.load(vars(client.get(f"{BASE_PROTOCOL}{BASE_URL}"))["headers"]["Set-Cookie"]) - csrftoken = cookie["csrftoken"].value - payload = {"csrfmiddlewaretoken": csrftoken, "edit_code": edit_code} + cookie.load(vars(client.get(f'{BASE_PROTOCOL}{BASE_URL}'))['headers']['Set-Cookie']) + csrftoken = cookie['csrftoken'].value + payload = {'csrfmiddlewaretoken': csrftoken, 'edit_code': edit_code} return json_loads( client.post( - f"{BASE_PROTOCOL}{BASE_URL}/api/delete/{url_short}", + f'{BASE_PROTOCOL}{BASE_URL}/api/delete/{url_short}', payload, headers=_headers, ).data @@ -128,35 +117,35 @@ async def paste( if edit_bin: if not (url and edit_code): - raise ValueError("Please provide both, url and edit code") + raise ValueError('Please provide both, url and edit code') response = edit(url_short=url, edit_code=edit_code, text=text) else: response = new(text=text) - if response.get("status") != "200": + if response.get('status') != '200': raise RuntimeError( - f"paste task terminated with status: {response.get('status')}\n" - f"Message: {response.get('content', 'No message provided')}" + f'paste task terminated with status: {response.get("status")}\n' + f'Message: {response.get("content", "No message provided")}' ) - url = response["url"] - edit_code = response["edit_code"] + url = response['url'] + edit_code = response['edit_code'] if not permanent: - short_url = response["url_short"] + short_url = response['url_short'] time_now = datetime.now() - ftime = time_now.strftime("%d %I:%M:%S %p %Y") + ftime = time_now.strftime('%d %I:%M:%S %p %Y') - print(f"URL: {url} - Edit Code: {edit_code} - Time: {ftime}") + print(f'URL: {url} - Edit Code: {edit_code} - Time: {ftime}') - rallUrls = db.get("core.rentry", "urls", default={"allUrls": {}}) + rallUrls = db.get('core.rentry', 'urls', default={'allUrls': {}}) entry_id = str(uuid4()) - rallUrls["allUrls"][entry_id] = { - "url": short_url, - "edit_code": edit_code, - "time": ftime, + rallUrls['allUrls'][entry_id] = { + 'url': short_url, + 'edit_code': edit_code, + 'time': ftime, } - db.set("core.rentry", "urls", rallUrls) + db.set('core.rentry', 'urls', rallUrls) if return_edit: return (url, edit_code) @@ -167,34 +156,32 @@ async def rentry_cleanup_job(): """Periodically checks and deletes rentry pastes older than 24 hours""" while True: try: - rallUrls = db.get("core.rentry", "urls", default={"allUrls": {}}) + rallUrls = db.get('core.rentry', 'urls', default={'allUrls': {}}) now = datetime.now() deleted_count = 0 error_count = 0 - for entry_id, entry in list(rallUrls["allUrls"].items()): - url = entry["url"] - entry_time = datetime.strptime(entry["time"], "%d %I:%M:%S %p %Y") + for entry_id, entry in list(rallUrls['allUrls'].items()): + url = entry['url'] + entry_time = datetime.strptime(entry['time'], '%d %I:%M:%S %p %Y') if now - entry_time > timedelta(days=1): try: - delete(url, entry["edit_code"]) - del rallUrls["allUrls"][entry_id] + delete(url, entry['edit_code']) + del rallUrls['allUrls'][entry_id] deleted_count += 1 - print(f"[#] Deleted expired rentry paste: {url}") + print(f'[#] Deleted expired rentry paste: {url}') except Exception as e: error_count += 1 - print(f"[!] Failed to delete rentry paste {url}: {str(e)}") + print(f'[!] Failed to delete rentry paste {url}: {str(e)}') if deleted_count or error_count: - print( - f"[*] Cleanup summary: {deleted_count} deleted, {error_count} failed" - ) + print(f'[*] Cleanup summary: {deleted_count} deleted, {error_count} failed') if deleted_count: - db.set("core.rentry", "urls", rallUrls) + db.set('core.rentry', 'urls', rallUrls) except Exception as e: - print(f"[!] Error in rentry cleanup job: {str(e)}") + print(f'[!] Error in rentry cleanup job: {str(e)}') await asyncio.sleep(12 * 60 * 60) diff --git a/utils/scripts.py b/utils/scripts.py index cb9604e..88fea6e 100644 --- a/utils/scripts.py +++ b/utils/scripts.py @@ -25,22 +25,20 @@ import sys import tempfile import time import traceback -from PIL import Image from io import BytesIO from types import ModuleType -from typing import Dict, Tuple import psutil +from PIL import Image from pyrogram import Client, errors, filters from pyrogram.errors import FloodWait, MessageNotModified, UserNotParticipant from pyrogram.types import Message -from pyrogram.enums import ChatMembersFilter from utils.db import db from .misc import modules_help, prefix, requirements_list -META_COMMENTS = re.compile(r"^ *# *meta +(\S+) *: *(.*?)\s*$", re.MULTILINE) +META_COMMENTS = re.compile(r'^ *# *meta +(\S+) *: *(.*?)\s*$', re.MULTILINE) interact_with_to_delete = [] @@ -51,11 +49,11 @@ def time_formatter(milliseconds: int) -> str: hours, minutes = divmod(minutes, 60) days, hours = divmod(hours, 24) tmp = ( - ((str(days) + " day(s), ") if days else "") - + ((str(hours) + " hour(s), ") if hours else "") - + ((str(minutes) + " minute(s), ") if minutes else "") - + ((str(seconds) + " second(s), ") if seconds else "") - + ((str(milliseconds) + " millisecond(s), ") if milliseconds else "") + ((str(days) + ' day(s), ') if days else '') + + ((str(hours) + ' hour(s), ') if hours else '') + + ((str(minutes) + ' minute(s), ') if minutes else '') + + ((str(seconds) + ' second(s), ') if seconds else '') + + ((str(milliseconds) + ' millisecond(s), ') if milliseconds else '') ) return tmp[:-2] @@ -63,32 +61,30 @@ def time_formatter(milliseconds: int) -> str: def humanbytes(size): """Convert Bytes To Bytes So That Human Can Read It""" if not size: - return "" + return '' power = 2**10 raised_to_pow = 0 - dict_power_n = {0: "", 1: "Ki", 2: "Mi", 3: "Gi", 4: "Ti"} + dict_power_n = {0: '', 1: 'Ki', 2: 'Mi', 3: 'Gi', 4: 'Ti'} while size > power: size /= power raised_to_pow += 1 - return str(round(size, 2)) + " " + dict_power_n[raised_to_pow] + "B" + return str(round(size, 2)) + ' ' + dict_power_n[raised_to_pow] + 'B' async def edit_or_send_as_file( tex: str, message: Message, client: Client, - caption: str = "Result!", - file_name: str = "result", + caption: str = 'Result!', + file_name: str = 'result', ): """Send As File If Len Of Text Exceeds Tg Limit Else Edit Message""" if not tex: - await message.edit("Wait, What?") + await message.edit('Wait, What?') return if len(tex) > 1024: - await message.edit("OutPut is Too Large, Sending As File!") - with tempfile.NamedTemporaryFile( - "w", delete=False, suffix=".txt", prefix=f"{file_name}_" - ) as fn: + await message.edit('OutPut is Too Large, Sending As File!') + with tempfile.NamedTemporaryFile('w', delete=False, suffix='.txt', prefix=f'{file_name}_') as fn: fn.write(tex) temp_path = fn.name try: @@ -106,7 +102,7 @@ def get_text(message: Message) -> None | str: text_to_return = message.text if message.text is None: return None - if " " in text_to_return: + if ' ' in text_to_return: try: return message.text.split(None, 1)[1] except IndexError: @@ -127,32 +123,28 @@ async def progress(current, total, message, start, type_of_ps, file_name=None): return time_to_completion = round((total - current) / speed) * 1000 estimated_total_time = elapsed_time + time_to_completion - progress_str = f"{''.join(['▰' for i in range(math.floor(percentage / 10))])}" - progress_str += ( - f"{''.join(['▱' for i in range(10 - math.floor(percentage / 10))])}" - ) - progress_str += f"{round(percentage, 2)}%\n" - tmp = f"{progress_str}{humanbytes(current)} of {humanbytes(total)}\n" - tmp += f"ETA: {time_formatter(estimated_total_time)}" + progress_str = f'{"".join(["▰" for i in range(math.floor(percentage / 10))])}' + progress_str += f'{"".join(["▱" for i in range(10 - math.floor(percentage / 10))])}' + progress_str += f'{round(percentage, 2)}%\n' + tmp = f'{progress_str}{humanbytes(current)} of {humanbytes(total)}\n' + tmp += f'ETA: {time_formatter(estimated_total_time)}' if file_name: try: - await message.edit( - f"{type_of_ps}\nFile Name: {file_name}\n{tmp}" - ) + await message.edit(f'{type_of_ps}\nFile Name: {file_name}\n{tmp}') except FloodWait as e: await asyncio.sleep(e.x) except MessageNotModified: pass else: try: - await message.edit(f"{type_of_ps}\n{tmp}") + await message.edit(f'{type_of_ps}\n{tmp}') except FloodWait as e: await asyncio.sleep(e.x) except MessageNotModified: pass -async def run_cmd(prefix: str) -> Tuple[str, str, int, int]: +async def run_cmd(prefix: str) -> tuple[str, str, int, int]: """Run Commands""" args = shlex.split(prefix) process = await asyncio.create_subprocess_exec( @@ -160,45 +152,45 @@ async def run_cmd(prefix: str) -> Tuple[str, str, int, int]: ) stdout, stderr = await process.communicate() return ( - stdout.decode("utf-8", "replace").strip(), - stderr.decode("utf-8", "replace").strip(), + stdout.decode('utf-8', 'replace').strip(), + stderr.decode('utf-8', 'replace').strip(), process.returncode, process.pid, ) def mediainfo(media): - xx = str((str(media)).split("(", maxsplit=1)[0]) - m = "" - if xx == "MessageMediaDocument": + xx = str((str(media)).split('(', maxsplit=1)[0]) + m = '' + if xx == 'MessageMediaDocument': mim = media.document.mime_type - if mim == "application/x-tgsticker": - m = "sticker animated" - elif "image" in mim: - if mim == "image/webp": - m = "sticker" - elif mim == "image/gif": - m = "gif as doc" + if mim == 'application/x-tgsticker': + m = 'sticker animated' + elif 'image' in mim: + if mim == 'image/webp': + m = 'sticker' + elif mim == 'image/gif': + m = 'gif as doc' else: - m = "pic as doc" - elif "video" in mim: - if "DocumentAttributeAnimated" in str(media): - m = "gif" - elif "DocumentAttributeVideo" in str(media): + m = 'pic as doc' + elif 'video' in mim: + if 'DocumentAttributeAnimated' in str(media): + m = 'gif' + elif 'DocumentAttributeVideo' in str(media): i = str(media.document.attributes[0]) - if "supports_streaming=True" in i: - m = "video" - m = "video as doc" + if 'supports_streaming=True' in i: + m = 'video' + m = 'video as doc' else: - m = "video" - elif "audio" in mim: - m = "audio" + m = 'video' + elif 'audio' in mim: + m = 'audio' else: - m = "document" - elif xx == "MessageMediaPhoto": - m = "pic" - elif xx == "MessageMediaWebPage": - m = "web" + m = 'document' + elif xx == 'MessageMediaPhoto': + m = 'pic' + elif xx == 'MessageMediaWebPage': + m = 'web' return m @@ -217,31 +209,31 @@ def text(message: Message) -> str: def restart() -> None: - music_bot_pid = db.get("custom.musicbot", "music_bot_pid", None) + music_bot_pid = db.get('custom.musicbot', 'music_bot_pid', None) if music_bot_pid is not None: try: music_bot_process = psutil.Process(music_bot_pid) music_bot_process.terminate() except psutil.NoSuchProcess: - print("Music bot is not running.") - os.execvp(sys.executable, [sys.executable, "main.py"]) # skipcq + print('Music bot is not running.') + os.execvp(sys.executable, [sys.executable, 'main.py']) # skipcq -def format_exc(e: Exception, suffix="") -> str: +def format_exc(e: Exception, suffix='') -> str: traceback.print_exc() err = traceback.format_exc() if isinstance(e, errors.RPCError): return ( - f"Telegram API error!\n" - f"[{e.CODE} {e.ID or e.NAME}] — {e.MESSAGE.format(value=e.value)}\n\n{suffix}" + f'Telegram API error!\n' + f'[{e.CODE} {e.ID or e.NAME}] — {e.MESSAGE.format(value=e.value)}\n\n{suffix}' ) - return f"Error!\n" f"{err}" + return f'Error!\n{err}' def with_reply(func): async def wrapped(client: Client, message: Message): if not message.reply_to_message: - await message.edit("Reply to message is required") + await message.edit('Reply to message is required') else: return await func(client, message) @@ -251,16 +243,12 @@ def with_reply(func): def is_admin(func): async def wrapped(client: Client, message: Message): try: - chat_member = await client.get_chat_member( - message.chat.id, message.from_user.id - ) - if chat_member.status in ("administrator", "creator"): + chat_member = await client.get_chat_member(message.chat.id, message.from_user.id) + if chat_member.status in ('administrator', 'creator'): return await func(client, message) - await message.edit("You need to be an admin to perform this action.") + await message.edit('You need to be an admin to perform this action.') except UserNotParticipant: - await message.edit( - "You need to be a participant in the chat to perform this action." - ) + await message.edit('You need to be a participant in the chat to perform this action.') return wrapped @@ -278,9 +266,7 @@ async def interact_with(message: Message) -> Message: await asyncio.sleep(1) # noinspection PyProtectedMember - response = [ - msg async for msg in message._client.get_chat_history(message.chat.id, limit=1) - ] + response = [msg async for msg in message._client.get_chat_history(message.chat.id, limit=1)] seconds_waiting = 0 while response[0].from_user.is_self: @@ -290,10 +276,7 @@ async def interact_with(message: Message) -> Message: await asyncio.sleep(1) # noinspection PyProtectedMember - response = [ - msg - async for msg in message._client.get_chat_history(message.chat.id, limit=1) - ] + response = [msg async for msg in message._client.get_chat_history(message.chat.id, limit=1)] interact_with_to_delete.append(message.id) interact_with_to_delete.append(response[0].id) @@ -304,14 +287,12 @@ async def interact_with(message: Message) -> Message: def format_module_help(module_name: str, full=True): commands = modules_help[module_name] - help_text = ( - f"Help for |{module_name}|\n\nUsage:\n" if full else "Usage:\n" - ) + help_text = f'Help for |{module_name}|\n\nUsage:\n' if full else 'Usage:\n' for command, desc in commands.items(): cmd = command.split(maxsplit=1) - args = " " + cmd[1] + "" if len(cmd) > 1 else "" - help_text += f"{prefix}{cmd[0]}{args} — {desc}\n" + args = ' ' + cmd[1] + '' if len(cmd) > 1 else '' + help_text += f'{prefix}{cmd[0]}{args} — {desc}\n' return help_text @@ -319,16 +300,12 @@ def format_module_help(module_name: str, full=True): def format_small_module_help(module_name: str, full=True): commands = modules_help[module_name] - help_text = ( - f"Help for |{module_name}|\n\nCommands list:\n" - if full - else "Commands list:\n" - ) + help_text = f'Help for |{module_name}|\n\nCommands list:\n' if full else 'Commands list:\n' for command, _desc in commands.items(): cmd = command.split(maxsplit=1) - args = " " + cmd[1] + "" if len(cmd) > 1 else "" - help_text += f"{prefix}{cmd[0]}{args}\n" - help_text += f"\nGet full usage: {prefix}help {module_name}" + args = ' ' + cmd[1] + '' if len(cmd) > 1 else '' + help_text += f'{prefix}{cmd[0]}{args}\n' + help_text += f'\nGet full usage: {prefix}help {module_name}' return help_text @@ -348,12 +325,12 @@ def import_library(library_name: str, package_name: str = None): return importlib.import_module(library_name) except ImportError as exc: completed = subprocess.run( - [sys.executable, "-m", "pip", "install", "--upgrade", package_name], + [sys.executable, '-m', 'pip', 'install', '--upgrade', package_name], check=True, ) if completed.returncode != 0: raise AssertionError( - f"Failed to install library {package_name} (pip exited with code {completed.returncode})" + f'Failed to install library {package_name} (pip exited with code {completed.returncode})' ) from exc return importlib.import_module(library_name) @@ -363,21 +340,17 @@ def uninstall_library(package_name: str): Uninstalls a library :param package_name: package name in PyPi (pip uninstall example) """ - completed = subprocess.run( - [sys.executable, "-m", "pip", "uninstall", "-y", package_name], check=True - ) + completed = subprocess.run([sys.executable, '-m', 'pip', 'uninstall', '-y', package_name], check=True) if completed.returncode != 0: raise AssertionError( - f"Failed to uninstall library {package_name} (pip exited with code {completed.returncode})" + f'Failed to uninstall library {package_name} (pip exited with code {completed.returncode})' ) -def resize_image( - input_img, output=None, img_type="PNG", size: int = 512, size2: int = None -): +def resize_image(input_img, output=None, img_type='PNG', size: int = 512, size2: int = None): if output is None: output = BytesIO() - output.name = f"sticker.{img_type.lower()}" + output.name = f'sticker.{img_type.lower()}' with Image.open(input_img) as img: # We used to use thumbnail(size) here, but it returns with a *max* dimension of 512,512 @@ -435,13 +408,13 @@ async def load_module( if module_name in modules_help and not core: await unload_module(module_name, client) - path = f"modules.{'custom_modules.' if not core else ''}{module_name}" + path = f'modules.{"custom_modules." if not core else ""}{module_name}' - with open(f"{path.replace('.', '/')}.py", encoding="utf-8") as f: + with open(f'{path.replace(".", "/")}.py', encoding='utf-8') as f: code = f.read() meta = parse_meta_comments(code) - packages = meta.get("requires", "").split() + packages = meta.get('requires', '').split() requirements_list.extend(packages) try: @@ -455,39 +428,36 @@ async def load_module( raise if message: - await message.edit(f"Installing requirements: {' '.join(packages)}") + await message.edit(f'Installing requirements: {" ".join(packages)}') proc = await asyncio.create_subprocess_exec( sys.executable, - "-m", - "pip", - "install", - "-U", + '-m', + 'pip', + 'install', + '-U', *packages, ) try: await asyncio.wait_for(proc.wait(), timeout=120) - except asyncio.TimeoutError: + except TimeoutError: if message: - await message.edit( - "Timeout while installed requirements." - + "Try to install them manually" - ) - raise TimeoutError("timeout while installing requirements") from e + await message.edit('Timeout while installed requirements.' + 'Try to install them manually') + raise TimeoutError('timeout while installing requirements') from e if proc.returncode != 0: if message: await message.edit( - f"Failed to install requirements (pip exited with code {proc.returncode}). " - f"Check logs for futher info", + f'Failed to install requirements (pip exited with code {proc.returncode}). ' + f'Check logs for futher info', ) - raise RuntimeError("failed to install requirements") from e + raise RuntimeError('failed to install requirements') from e module = importlib.import_module(path) for _name, obj in vars(module).items(): - if isinstance(getattr(obj, "handlers", []), list): - for handler, group in getattr(obj, "handlers", []): + if isinstance(getattr(obj, 'handlers', []), list): + for handler, group in getattr(obj, 'handlers', []): client.add_handler(handler, group) module.__meta__ = meta @@ -496,14 +466,14 @@ async def load_module( async def unload_module(module_name: str, client: Client) -> bool: - path = "modules.custom_modules." + module_name + path = 'modules.custom_modules.' + module_name if path not in sys.modules: return False module = importlib.import_module(path) for _name, obj in vars(module).items(): - for handler, group in getattr(obj, "handlers", []): + for handler, group in getattr(obj, 'handlers', []): client.remove_handler(handler, group) del modules_help[module_name] @@ -521,7 +491,7 @@ def no_prefix(handler): return filters.create(func) -def parse_meta_comments(code: str) -> Dict[str, str]: +def parse_meta_comments(code: str) -> dict[str, str]: try: groups = META_COMMENTS.search(code).groups() except AttributeError: From c905bbd039f102e7ba4e99b42b436d4f6b7f2157 Mon Sep 17 00:00:00 2001 From: mr-forust Date: Fri, 19 Jun 2026 14:56:37 +0200 Subject: [PATCH 21/23] chore(userbot): optimize Dockerfile with multi-stage build and static ffmpeg - Multi-stage build: pip deps built in separate stage - Static ffmpeg binary instead of apt package (avoid 200+ deps) - Keep only git, mediainfo, wget via apt --- Dockerfile | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/Dockerfile b/Dockerfile index 0091949..6148d41 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,18 +1,28 @@ +FROM python:3.11-slim AS builder + +WORKDIR /src +COPY requirements.txt . +RUN pip install --no-cache-dir --user -r requirements.txt + FROM python:3.11-slim -WORKDIR /app - RUN apt-get update && apt-get install -y --no-install-recommends \ - git wget ffmpeg mediainfo && \ - rm -rf /var/lib/apt/lists/* + git \ + mediainfo \ + wget \ + xz-utils \ + && rm -rf /var/lib/apt/lists/* -COPY requirements.txt /app/ - -RUN python -m pip install --no-cache-dir --upgrade pip && \ - python -m pip install --no-cache-dir -r /app/requirements.txt +RUN wget -qO- https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz | \ + tar xJ && \ + cp ffmpeg-*-static/ffmpeg /usr/local/bin/ && \ + cp ffmpeg-*-static/ffprobe /usr/local/bin/ && \ + rm -rf ffmpeg-* +COPY --from=builder /root/.local /usr/local COPY . /app +WORKDIR /app ENV PYTHONUNBUFFERED=1 -CMD ["python", "-u", "main.py"] \ No newline at end of file +CMD ["python", "-u", "main.py"] From 20bce5b31c0e073a74246ca9a97d87c33aa1bc6c Mon Sep 17 00:00:00 2001 From: mr-forust Date: Fri, 19 Jun 2026 15:27:12 +0200 Subject: [PATCH 22/23] fix(userbot): add missing safone.py imports, apply ruff --unsafe-fixes - Add missing aiohttp, PIL, BytesIO imports to safone.py (F821 runtime bugs) - Apply ruff --unsafe-fixes (27 fixes): ternary operators, .get() patterns, contextlib.suppress, enumerate(), collapsible if/else, remove .keys() --- images/icons.py | 2 +- main.py | 5 ++--- modules/1000-7.py | 5 +---- modules/afk.py | 5 +---- modules/amongus.py | 10 ++-------- modules/cdxl.py | 2 +- modules/filters.py | 8 ++++---- modules/fliptext.py | 5 +---- modules/help.py | 2 +- modules/misc/cama.py | 2 +- modules/misc/safone.py | 3 +++ modules/misc/sarethai.py | 5 ++--- modules/notes.py | 2 +- modules/pdf2md.py | 2 +- modules/perfectrussian.py | 2 +- modules/spin.py | 5 +---- modules/stickers.py | 5 +---- modules/user_info.py | 10 ++-------- utils/module.py | 2 +- utils/scripts.py | 4 +--- 20 files changed, 29 insertions(+), 57 deletions(-) diff --git a/images/icons.py b/images/icons.py index a621147..f9859b1 100644 --- a/images/icons.py +++ b/images/icons.py @@ -12,7 +12,7 @@ from utils.misc import modules_help, prefix @Client.on_message(filters.command('icon', prefix) & filters.me) async def search_icon(_, message: Message): - if not len(message.command) == 2: + if len(message.command) != 2: return await message.edit_text('Please provide some text to search icons from Flaticon.com.') query = message.text.split(maxsplit=1)[1] diff --git a/main.py b/main.py index 451e45c..847514f 100644 --- a/main.py +++ b/main.py @@ -39,6 +39,7 @@ # "pySmartDL", # ] # /// +import contextlib import logging import os import platform @@ -151,10 +152,8 @@ async def main(): 'restart': 'Restart completed!', 'update': 'Update process completed!', }[info['type']] - try: + with contextlib.suppress(errors.RPCError): await app.edit_message_text(info['chat_id'], info['message_id'], text) - except errors.RPCError: - pass db.remove('core.updater', 'restart_info') # required for sessionkiller module diff --git a/modules/1000-7.py b/modules/1000-7.py index 08740b7..d77f3c3 100644 --- a/modules/1000-7.py +++ b/modules/1000-7.py @@ -15,10 +15,7 @@ def prettify(val: int) -> str: async def ghoul_counter(_, message: Message): await message.delete() - if len(message.command) > 1 and message.command[1].isdigit(): - counter = int(message.command[1]) - else: - counter = 1000 + counter = int(message.command[1]) if len(message.command) > 1 and message.command[1].isdigit() else 1000 msg = await message.reply(prettify(counter), quote=False) diff --git a/modules/afk.py b/modules/afk.py index 4794225..536a69d 100644 --- a/modules/afk.py +++ b/modules/afk.py @@ -47,10 +47,7 @@ async def afk_handler(_, message: types.Message): @Client.on_message(filters.command('afk', prefix) & filters.me) async def afk(_, message): - if len(message.text.split()) >= 2: - reason = message.text.split(' ', maxsplit=1)[1] - else: - reason = 'None' + reason = message.text.split(' ', maxsplit=1)[1] if len(message.text.split()) >= 2 else 'None' afk_info['start'] = int(datetime.datetime.now().timestamp()) afk_info['is_afk'] = True diff --git a/modules/amongus.py b/modules/amongus.py index addb938..054abb5 100644 --- a/modules/amongus.py +++ b/modules/amongus.py @@ -108,10 +108,7 @@ async def imposter_cmd(client: Client, message: Message): text = f'{user.first_name} {choice(imps)}.' else: args = message.text.split()[1:] - if args: - text = ' '.join(args) - else: - text = f'{message.from_user.first_name} {choice(imps)}.' + text = ' '.join(args) if args else f'{message.from_user.first_name} {choice(imps)}.' text += f'\n{remain} impostor(s) remain.' imposter_file = await get_imposter_img(text) @@ -128,10 +125,7 @@ async def imp_animation(client: Client, message: Message): name = ' '.join(message.command[1:]) if len(message.command) > 1 else '' if not name: reply = message.reply_to_message - if reply: - name = reply.from_user.first_name - else: - name = message.from_user.first_name + name = reply.from_user.first_name if reply else message.from_user.first_name cmd = message.command[0].lower() text1 = await edit_or_reply(message, 'Uhmm... Something is wrong here!!') diff --git a/modules/cdxl.py b/modules/cdxl.py index a869d75..5d4af81 100644 --- a/modules/cdxl.py +++ b/modules/cdxl.py @@ -24,7 +24,7 @@ async def cdxl(c: Client, message: Message): await message.edit_text(f'Usage: {prefix}vdxl [prompt/reply to prompt]') return - inference_params = dict(width=1024, height=1024, steps=50, cfg_scale=9.0) + inference_params = {'width': 1024, 'height': 1024, 'steps': 50, 'cfg_scale': 9.0} model_prediction = Model( 'https://clarifai.com/stability-ai/stable-diffusion-2/models/stable-diffusion-xl' diff --git a/modules/filters.py b/modules/filters.py index 95e68a7..bda36d6 100644 --- a/modules/filters.py +++ b/modules/filters.py @@ -36,7 +36,7 @@ def set_filters_chat(chat_id, filters_): async def contains_filter(_, __, m): - return m.text and m.text.lower() in get_filters_chat(m.chat.id).keys() + return m.text and m.text.lower() in get_filters_chat(m.chat.id) contains = filters.create(contains_filter) @@ -115,7 +115,7 @@ async def filter_handler(client: Client, message: Message): return await message.edit(f'Usage: {prefix}filter [name] (Reply required)') name = message.text.split(maxsplit=1)[1].lower() chat_filters = get_filters_chat(message.chat.id) - if name in chat_filters.keys(): + if name in chat_filters: return await message.edit(f'Filter {name} already exists.') if not message.reply_to_message: return await message.edit('Reply to message please.') @@ -186,7 +186,7 @@ async def filter_del_handler(_, message: Message): ) name = message.text.split(maxsplit=1)[1].lower() chat_filters = get_filters_chat(message.chat.id) - if name not in chat_filters.keys(): + if name not in chat_filters: return await message.edit( f"Filter {name} doesn't exists.", ) @@ -208,7 +208,7 @@ async def filter_search_handler(_, message: Message): ) name = message.text.split(maxsplit=1)[1].lower() chat_filters = get_filters_chat(message.chat.id) - if name not in chat_filters.keys(): + if name not in chat_filters: return await message.edit( f"Filter {name} doesn't exists.", ) diff --git a/modules/fliptext.py b/modules/fliptext.py index 619de2a..b85f36a 100644 --- a/modules/fliptext.py +++ b/modules/fliptext.py @@ -89,10 +89,7 @@ async def flip(client: Client, message: Message): text = ' '.join(message.command[1:]) final_str = '' for char in text: - if char in REPLACEMENT_MAP: - new_char = REPLACEMENT_MAP[char] - else: - new_char = char + new_char = REPLACEMENT_MAP.get(char, char) final_str += new_char if text != final_str: await message.edit(final_str) diff --git a/modules/help.py b/modules/help.py index 4089299..01aea3a 100644 --- a/modules/help.py +++ b/modules/help.py @@ -34,7 +34,7 @@ async def help_cmd(_, message: Message): command_name = message.command[1].lower() module_found = False for module_name, commands in modules_help.items(): - for command in commands.keys(): + for command in commands: if command.split()[0] == command_name: cmd = command.split(maxsplit=1) cmd_desc = commands[command] diff --git a/modules/misc/cama.py b/modules/misc/cama.py index a61f490..e1d3fd4 100644 --- a/modules/misc/cama.py +++ b/modules/misc/cama.py @@ -25,7 +25,7 @@ def get_marine_life_details(species_name): species = observation['taxon']['name'] common_name = observation['taxon']['preferred_common_name'] photo_url = observation['photos'][0]['url'] if observation['photos'] else 'No photo available' - description = observation['description'] if 'description' in observation else 'No description available.' + description = observation.get('description', 'No description available.') return { 'species': species, 'common_name': common_name, diff --git a/modules/misc/safone.py b/modules/misc/safone.py index e54c0a2..209a137 100644 --- a/modules/misc/safone.py +++ b/modules/misc/safone.py @@ -16,9 +16,12 @@ import base64 import os +from io import BytesIO import aiofiles +import aiohttp import requests +from PIL import Image, ImageEnhance from pyrogram import Client, enums, filters from pyrogram.errors import MediaCaptionTooLong, MessageTooLong from pyrogram.types import InputMediaPhoto, Message diff --git a/modules/misc/sarethai.py b/modules/misc/sarethai.py index 275a2ae..046c6ce 100644 --- a/modules/misc/sarethai.py +++ b/modules/misc/sarethai.py @@ -15,6 +15,7 @@ #  along with this program.  If not, see . import asyncio +import contextlib import os import requests @@ -101,10 +102,8 @@ async def delete_search_data(client, chat_id, message_id): search_key = f'{chat_id}_{key}' if search_key in search_results and search_results[search_key]['message_id'] == message_id: del search_results[search_key] - try: + with contextlib.suppress(BaseException): await client.delete_messages(chat_id, message_id) - except: - pass break diff --git a/modules/notes.py b/modules/notes.py index 216a945..98f8334 100644 --- a/modules/notes.py +++ b/modules/notes.py @@ -105,7 +105,7 @@ async def notes(_, message: Message): await message.edit('Loading...') text = 'Available notes:\n\n' collection = db.get_collection('core.notes') - for note in collection.keys(): + for note in collection: if note[:4] == 'note': text += f'{note[4:]}\n' await message.edit(text) diff --git a/modules/pdf2md.py b/modules/pdf2md.py index d5cba9e..6cb5249 100644 --- a/modules/pdf2md.py +++ b/modules/pdf2md.py @@ -28,7 +28,7 @@ async def pdf2md(client: Client, message: Message): if not message.reply_to_message.document: await message.edit('Reply to a pdf file') return - if not message.reply_to_message.document.mime_type == 'application/pdf': + if message.reply_to_message.document.mime_type != 'application/pdf': await message.edit('Reply to a pdf file') return if gemini_key == '': diff --git a/modules/perfectrussian.py b/modules/perfectrussian.py index 568663e..c15d112 100644 --- a/modules/perfectrussian.py +++ b/modules/perfectrussian.py @@ -25,7 +25,7 @@ async def prussian_cmd(_, message: Message): splitted = message.reply_to_message.text.split() for i in range(0, len(splitted), random.randint(2, 3)): - for j in range(1, 2): + for _j in range(1, 2): splitted.insert(i, random.choice(words)) await message.edit(' '.join(splitted), parse_mode=enums.ParseMode.HTML) diff --git a/modules/spin.py b/modules/spin.py index e3c942b..1636f3f 100644 --- a/modules/spin.py +++ b/modules/spin.py @@ -101,10 +101,7 @@ async def spin_handler(client: Client, message: Message): filename = 'sticker.webp' elif message.reply_to_message.text: result = await quote_cmd(client, message) - if result[1]: - filename = 'sticker.png' - else: - filename = 'sticker.webp' + filename = 'sticker.png' if result[1] else 'sticker.webp' open('downloads/' + filename, 'wb').write(result[0].getbuffer()) coro = False else: diff --git a/modules/stickers.py b/modules/stickers.py index b1430eb..d310280 100644 --- a/modules/stickers.py +++ b/modules/stickers.py @@ -40,10 +40,7 @@ async def kang(client: Client, message: types.Message): return pack = message.command[1] - if len(message.command) >= 3: - emoji = message.command[2] - else: - emoji = '✨' + emoji = message.command[2] if len(message.command) >= 3 else '✨' await client.unblock_user('@stickers') await interact_with(await client.send_message('@stickers', '/cancel', parse_mode=enums.ParseMode.MARKDOWN)) diff --git a/modules/user_info.py b/modules/user_info.py index a79bb3b..0f458a1 100644 --- a/modules/user_info.py +++ b/modules/user_info.py @@ -35,10 +35,7 @@ async def get_user_inf(client: Client, message: Message): user = response.users[0] full_user = response.full_user - if user.username is None: - username = 'None' - else: - username = f'@{user.username}' + username = 'None' if user.username is None else f'@{user.username}' about = 'None' if full_user.about is None else full_user.about user_info = f"""|=Username: {username} @@ -79,10 +76,7 @@ async def get_full_user_inf(client: Client, message: Message): # await client.delete_messages("@creationdatebot", interact_with_to_delete) interact_with_to_delete.clear() - if user.username is None: - username = 'None' - else: - username = f'@{user.username}' + username = 'None' if user.username is None else f'@{user.username}' about = 'None' if full_user.about is None else full_user.about user_info = f"""|=Username: {username} |-Id: {user.id} diff --git a/utils/module.py b/utils/module.py index 515c064..61bfb9c 100644 --- a/utils/module.py +++ b/utils/module.py @@ -59,7 +59,7 @@ class HelpNavigator: text += f'Help Page No: {self.current_page}/{self.total_pages}\n\n' for module_name in page_modules: commands = modules_help[module_name] - text += f'• {module_name.title()}: {", ".join([f"{prefix + cmd_name.split()[0]}" for cmd_name in commands.keys()])}\n' + text += f'• {module_name.title()}: {", ".join([f"{prefix + cmd_name.split()[0]}" for cmd_name in commands])}\n' text += f'\nThe number of modules in the userbot: {len(modules_help)}' await message.edit(text, disable_web_page_preview=True) diff --git a/utils/scripts.py b/utils/scripts.py index 88fea6e..76d8e6a 100644 --- a/utils/scripts.py +++ b/utils/scripts.py @@ -484,9 +484,7 @@ async def unload_module(module_name: str, client: Client) -> bool: def no_prefix(handler): def func(_, __, message): - if message.text and not message.text.startswith(handler): - return True - return False + return bool(message.text and not message.text.startswith(handler)) return filters.create(func) From 7fb0a0e17963667d722e764585629b34f521705c Mon Sep 17 00:00:00 2001 From: mr-forust Date: Fri, 19 Jun 2026 15:36:25 +0200 Subject: [PATCH 23/23] chore: batch lint fixes across userbot and edu_master - S113: Add timeout=10 to all requests calls (74 fixes) - E722: Replace bare except: with except Exception: - B904: Replace redundant re-raise with bare raise - E402: Add noqa for intentional late imports after import_library() - S102/S307/S310/S311/S603/S605/S606/S607/S108: Add noqa for intentional usage - F601: Fix duplicate dict key in unsplash.py - N802: Rename ReplyCheck -> reply_check with backward compat alias - N813: Rename bs -> BS in icons.py - B007/B020: Rename loop var _j in animations.py - SIM102: Collapse nested if in autofwd.py - SIM113: Use enumerate() in calculator.py - A002: Add noqa for builtin shadowing in admlist.py - F811: Add noqa for cohere redefinition - edu_master: Fix ARG001, S108, S110, SIM117, apply --unsafe-fixes - Add modules_list.txt with full module inventory --- images/icons.py | 10 +-- images/imgur.py | 4 +- images/pinterest.py | 4 +- images/risearch.py | 2 +- images/unsplash.py | 3 +- images/unsplash2.py | 4 +- main.py | 4 +- modules/admlist.py | 4 +- modules/aimage.py | 2 +- modules/amogus.py | 6 +- modules/amongus.py | 23 ++++--- modules/animations.py | 2 +- modules/anime/anilist.py | 6 +- modules/anime/neko.py | 2 +- modules/aniquotes.py | 2 +- modules/calculator.py | 6 +- modules/cdxl.py | 2 +- modules/cohere.py | 16 ++--- modules/demotivator.py | 10 +-- modules/destroy.py | 4 +- modules/direct.py | 19 +++--- modules/durov.py | 2 +- modules/f.py | 2 +- modules/fakeactions.py | 2 +- modules/flux.py | 2 +- modules/hearts.py | 2 +- modules/kokodrilo_explodando.py | 2 +- modules/loader.py | 20 +++--- modules/markitdown.py | 2 +- modules/mirror_flip.py | 2 +- modules/misc/autofwd.py | 3 +- modules/misc/backup.py | 4 +- modules/misc/cama.py | 2 +- modules/misc/safone.py | 14 ++-- modules/misc/sarethai.py | 20 +++--- modules/misc/search.py | 2 +- modules/misc/summary.py | 4 +- modules/misc/transcribeyt.py | 4 +- modules/pdf2md.py | 6 +- modules/perfectrussian.py | 4 +- modules/python.py | 4 +- modules/removebg.py | 6 +- modules/shell.py | 2 +- modules/socialstalk.py | 14 ++-- modules/spin.py | 2 +- modules/squotes.py | 8 +-- modules/updater.py | 8 +-- modules/url.py | 3 +- modules_list.txt | 115 ++++++++++++++++++++++++++++++++ utils/handlers.py | 8 +-- utils/rentry.py | 4 +- utils/scripts.py | 8 +-- 52 files changed, 267 insertions(+), 149 deletions(-) create mode 100644 modules_list.txt diff --git a/images/icons.py b/images/icons.py index f9859b1..0c9737d 100644 --- a/images/icons.py +++ b/images/icons.py @@ -3,7 +3,7 @@ import re from io import BytesIO import requests -from bs4 import BeautifulSoup as bs +from bs4 import BeautifulSoup as BS from pyrogram import Client, filters from pyrogram.errors import RPCError from pyrogram.types import InputMediaPhoto, Message @@ -21,8 +21,8 @@ async def search_icon(_, message: Message): url = f'https://www.flaticon.com/search?word={search_query}' try: - html_content = requests.get(url).text - soup = bs(html_content, 'html.parser') + html_content = requests.get(url, timeout=10).text + soup = BS(html_content, 'html.parser') results = soup.find_all( 'img', src=re.compile(r'https://cdn-icons-png.flaticon.com/128/[0-9]+/[0-9]+.png'), @@ -68,7 +68,7 @@ async def freepik_search(client: Client, message: Message): try: url = f'https://www.freepik.com/api/regular/search?locale=en&term={match}' - json_content = requests.get(url).json() + json_content = requests.get(url, timeout=10).json() results = [] for i in json_content['items']: results.append(i['preview']['url']) @@ -81,7 +81,7 @@ async def freepik_search(client: Client, message: Message): media_group = [] for img_url in img_urls: - icon = requests.get(img_url) + icon = requests.get(img_url, timeout=10) if icon.status_code == 200: media_group.append(InputMediaPhoto(media=BytesIO(icon.content))) diff --git a/images/imgur.py b/images/imgur.py index 94e486c..643a78e 100644 --- a/images/imgur.py +++ b/images/imgur.py @@ -21,7 +21,7 @@ async def imgur(_, message: Message): url = 'https://api.imgur.com/3/image' headers = {'Authorization': 'Client-ID a10ad04550b0648'} # Upload image to Imgur and get URL - response = requests.post(url, headers=headers, data={'image': base64_data}) + response = requests.post(url, headers=headers, data={'image': base64_data}, timeout=10) result = response.json() await msg.edit_text(result['data']['link']) elif message.reply_to_message and message.reply_to_message.animation: @@ -35,7 +35,7 @@ async def imgur(_, message: Message): url = 'https://api.imgur.com/3/image' headers = {'Authorization': 'Client-ID a10ad04550b0648'} # Upload animation to Imgur and get URL - response = requests.post(url, headers=headers, data={'image': base64_data}) + response = requests.post(url, headers=headers, data={'image': base64_data}, timeout=10) result = response.json() await msg.edit_text(result['data']['link']) else: diff --git a/images/pinterest.py b/images/pinterest.py index 6c30718..355f4b2 100644 --- a/images/pinterest.py +++ b/images/pinterest.py @@ -30,7 +30,7 @@ def resize_image(image_bytes): async def download_image(url): try: - response = requests.get(url) + response = requests.get(url, timeout=10) if response.status_code == 200: img_bytes = BytesIO(response.content) return resize_image(img_bytes) @@ -52,7 +52,7 @@ async def pinterest_search(client: Client, message: Message): status_message = await message.edit('Searching for images...', parse_mode=enums.ParseMode.MARKDOWN) url = f'{API_URL}{query}' - response = requests.get(url) + response = requests.get(url, timeout=10) if response.status_code == 200: data = response.json() diff --git a/images/risearch.py b/images/risearch.py index ed42b60..461db1e 100644 --- a/images/risearch.py +++ b/images/risearch.py @@ -65,7 +65,7 @@ def upload_image(photo_path): """Uploads an image to tmpfiles.org and returns the direct download URL.""" try: with open(photo_path, 'rb') as image_file: - response = requests.post('https://tmpfiles.org/api/v1/upload', files={'file': image_file}) + response = requests.post('https://tmpfiles.org/api/v1/upload', files={'file': image_file}, timeout=10) if response.status_code == 200: data = response.json() url = data['data']['url'] diff --git a/images/unsplash.py b/images/unsplash.py index ba97a6a..38abc93 100644 --- a/images/unsplash.py +++ b/images/unsplash.py @@ -51,7 +51,7 @@ async def unsplash(client: Client, message: Message): for ia in range(len(images), count): img = data['results'][ia]['urls']['raw'] if img.startswith('https://images.unsplash.com/photo'): - image_content = requests.get(img).content + image_content = requests.get(img, timeout=10).content with open(f'{unsplash_dir}/unsplash_{ia}.jpg', 'wb') as f: f.write(image_content) imgr = f'{unsplash_dir}/unsplash_{ia}.jpg' @@ -77,7 +77,6 @@ async def unsplash(client: Client, message: Message): modules_help['unsplash'] = { - 'unsplash': '[keyword]*', 'unsplash': '[keyword]* [number of results you want]*\n' 'Makes a request to unsplash.com and sends the image with the keyword you provided.\n\n' 'Note:\n1. The number of results you can get is limited to 10.\n' diff --git a/images/unsplash2.py b/images/unsplash2.py index 36db6e2..859a358 100644 --- a/images/unsplash2.py +++ b/images/unsplash2.py @@ -29,7 +29,7 @@ def resize_image(image_bytes): async def download_image(url): try: - response = requests.get(url) + response = requests.get(url, timeout=10) if response.status_code == 200: img_bytes = BytesIO(response.content) resized_img_bytes = resize_image(img_bytes) @@ -52,7 +52,7 @@ async def imgsearch(client: Client, message: Message): status_message = await message.edit('Searching for images...', parse_mode=enums.ParseMode.MARKDOWN) url = f'{API_URL}{query}' - response = requests.get(url) + response = requests.get(url, timeout=10) if response.status_code == 200: data = response.json() diff --git a/main.py b/main.py index 847514f..dc0a65a 100644 --- a/main.py +++ b/main.py @@ -109,7 +109,7 @@ def load_missing_modules(): module_path = f'{custom_modules_path}/{module_name}.py' if not os.path.exists(module_path) and module_name in modules_dict: url = f'https://raw.githubusercontent.com/The-MoonTg-project/custom_modules/main/{modules_dict[module_name]}.py' - resp = requests.get(url) + resp = requests.get(url, timeout=10) if resp.ok: with open(module_path, 'wb') as f: f.write(resp.content) @@ -131,7 +131,7 @@ async def main(): except sqlite3.OperationalError as e: if str(e) == 'database is locked' and os.name == 'posix': logging.warning('Session file is locked. Trying to kill blocking process...') - subprocess.run(['fuser', '-k', 'my_account.session'], check=True) + subprocess.run(['fuser', '-k', 'my_account.session'], check=True) # noqa: S607 restart() raise except (errors.NotAcceptable, errors.Unauthorized) as e: diff --git a/modules/admlist.py b/modules/admlist.py index 58b9168..6aa8175 100644 --- a/modules/admlist.py +++ b/modules/admlist.py @@ -29,8 +29,8 @@ class Chat(Object): self, *, client: 'Client' = None, - id: id, - type: type, + id: id, # noqa: A002 + type: type, # noqa: A002 is_verified: bool = None, is_restricted: bool = None, is_creator: bool = None, diff --git a/modules/aimage.py b/modules/aimage.py index e144afa..9c0678d 100644 --- a/modules/aimage.py +++ b/modules/aimage.py @@ -8,7 +8,7 @@ from utils.scripts import format_exc, import_library genai = import_library('google.generativeai', 'google-generativeai') -from utils.config import gemini_key +from utils.config import gemini_key # noqa: E402 genai.configure(api_key=gemini_key) diff --git a/modules/amogus.py b/modules/amogus.py index 9c1f698..d986926 100644 --- a/modules/amogus.py +++ b/modules/amogus.py @@ -18,11 +18,11 @@ async def amogus(client: Client, message: Message): parse_mode=enums.ParseMode.HTML, ) - clr = randint(1, 12) + clr = randint(1, 12) # noqa: S311 url = 'https://raw.githubusercontent.com/The-MoonTg-project/AmongUs/master/' - font = ImageFont.truetype(BytesIO(get(url + 'bold.ttf').content), 60) - imposter = Image.open(BytesIO(get(f'{url}{clr}.png').content)) + font = ImageFont.truetype(BytesIO(get(url + 'bold.ttf', timeout=10).content), 60) + imposter = Image.open(BytesIO(get(f'{url}{clr}.png', timeout=10).content)) text_ = '\n'.join(['\n'.join(wrap(part, 30)) for part in text.split('\n')]) bbox = ImageDraw.Draw(Image.new('RGB', (1, 1))).multiline_textbbox((0, 0), text_, font, stroke_width=2) diff --git a/modules/amongus.py b/modules/amongus.py index 054abb5..95851ed 100644 --- a/modules/amongus.py +++ b/modules/amongus.py @@ -10,18 +10,19 @@ from PIL import Image, ImageDraw, ImageFont from pyrogram import Client, filters from pyrogram.types import Message from utils.misc import modules_help, prefix -from utils.scripts import ReplyCheck, edit_or_reply +from utils.scripts import edit_or_reply +from utils.scripts import reply_check as ReplyCheck async def amongus_gen(text: str, clr: int) -> str: url = 'https://github.com/TgCatUB/CatUserbot-Resources/raw/master/Resources/Amongus/' font = ImageFont.truetype( BytesIO( - requests.get('https://github.com/TgCatUB/CatUserbot-Resources/raw/master/Resources/fonts/bold.ttf').content + requests.get('https://github.com/TgCatUB/CatUserbot-Resources/raw/master/Resources/fonts/bold.ttf', timeout=10).content ), 60, ) - imposter = Image.open(BytesIO(requests.get(f'{url}{clr}.png').content)) + imposter = Image.open(BytesIO(requests.get(f'{url}{clr}.png', timeout=10).content)) text_ = '\n'.join('\n'.join(wrap(part, 30)) for part in text.split('\n')) bbox = ImageDraw.Draw(Image.new('RGB', (1, 1))).multiline_textbbox((0, 0), text_, font, stroke_width=2) w, h = bbox[2], bbox[3] @@ -42,10 +43,12 @@ async def amongus_gen(text: str, clr: int) -> str: async def get_imposter_img(text: str) -> BytesIO: background = requests.get( - f'https://github.com/TgCatUB/CatUserbot-Resources/raw/master/Resources/imposter/impostor{randint(1, 22)}.png' + f'https://github.com/TgCatUB/CatUserbot-Resources/raw/master/Resources/imposter/impostor{randint(1, 22)}.png', # noqa: S311 + timeout=10, ).content font = requests.get( - 'https://github.com/TgCatUB/CatUserbot-Resources/raw/master/Resources/fonts/roboto_regular.ttf' + 'https://github.com/TgCatUB/CatUserbot-Resources/raw/master/Resources/fonts/roboto_regular.ttf', + timeout=10, ).content font = BytesIO(font) font = ImageFont.truetype(font, 30) @@ -79,9 +82,9 @@ async def amongus_cmd(client: Client, message: Message): text = text.replace(f'-c{clr}', '') clr = int(clr) if clr > 12 or clr < 1: - clr = randint(1, 12) + clr = randint(1, 12) # noqa: S311 except IndexError: - clr = randint(1, 12) + clr = randint(1, 12) # noqa: S311 if not text: if not reply: @@ -100,15 +103,15 @@ async def amongus_cmd(client: Client, message: Message): @Client.on_message(filters.command('imposter', prefix) & filters.me) async def imposter_cmd(client: Client, message: Message): - remain = randint(1, 2) + remain = randint(1, 2) # noqa: S311 imps = ["wasn't the impostor", 'was the impostor'] if message.reply_to_message: user = message.reply_to_message.from_user - text = f'{user.first_name} {choice(imps)}.' + text = f'{user.first_name} {choice(imps)}.' # noqa: S311 else: args = message.text.split()[1:] - text = ' '.join(args) if args else f'{message.from_user.first_name} {choice(imps)}.' + text = ' '.join(args) if args else f'{message.from_user.first_name} {choice(imps)}.' # noqa: S311 text += f'\n{remain} impostor(s) remain.' imposter_file = await get_imposter_img(text) diff --git a/modules/animations.py b/modules/animations.py index 53c1b11..9fdafce 100644 --- a/modules/animations.py +++ b/modules/animations.py @@ -187,7 +187,7 @@ async def timer_blankx(_, message: Message): ) j = 10 k = j - for j in range(j): + for _j in range(j): await message.edit_text(txt + str(k), parse_mode=enums.ParseMode.HTML) k = k + 10 await asyncio.sleep(1) diff --git a/modules/anime/anilist.py b/modules/anime/anilist.py index 2e41419..b0ea92a 100644 --- a/modules/anime/anilist.py +++ b/modules/anime/anilist.py @@ -46,7 +46,7 @@ async def anime_search(client: Client, message: Message): averageScore = result['averageScore'] try: coverImage_url = result['imageUrl'] - coverImage = requests.get(url=coverImage_url).content + coverImage = requests.get(url=coverImage_url, timeout=10).content async with aiofiles.open('coverImage.jpg', mode='wb') as f: await f.write(coverImage) @@ -112,7 +112,7 @@ async def manga_search(client: Client, message: Message): averageScore = result['averageScore'] try: coverImage_url = result['imageUrl'] - coverImage = requests.get(url=coverImage_url).content + coverImage = requests.get(url=coverImage_url, timeout=10).content async with aiofiles.open('coverImage.jpg', mode='wb') as f: await f.write(coverImage) @@ -177,7 +177,7 @@ async def character(client: Client, message: Message): try: coverImage_url = result['image']['large'] - coverImage = requests.get(url=coverImage_url).content + coverImage = requests.get(url=coverImage_url, timeout=10).content async with aiofiles.open('coverImage.jpg', mode='wb') as f: await f.write(coverImage) diff --git a/modules/anime/neko.py b/modules/anime/neko.py index 519ef58..95593e1 100644 --- a/modules/anime/neko.py +++ b/modules/anime/neko.py @@ -24,7 +24,7 @@ from utils.scripts import format_exc def get_neko_media(query): - return requests.get(f'https://nekos.life/api/v2/img/{query}').json()['url'] + return requests.get(f'https://nekos.life/api/v2/img/{query}', timeout=10).json()['url'] @Client.on_message(filters.command('neko', prefix) & filters.me) diff --git a/modules/aniquotes.py b/modules/aniquotes.py index 3b304df..a15feee 100644 --- a/modules/aniquotes.py +++ b/modules/aniquotes.py @@ -25,7 +25,7 @@ async def aniquotes_handler(client: Client, message: Message): result = await client.get_inline_bot_results('@quotafbot', query) return await message.reply_inline_bot_result( query_id=result.query_id, - result_id=result.results[randint(1, 2)].id, + result_id=result.results[randint(1, 2)].id, # noqa: S311 reply_to_message_id=(message.reply_to_message.id if message.reply_to_message else None), ) except Exception as e: diff --git a/modules/calculator.py b/modules/calculator.py index f25afd7..08e7249 100644 --- a/modules/calculator.py +++ b/modules/calculator.py @@ -11,11 +11,10 @@ async def calc(_, message: Message): return args = ' '.join(message.command[1:]) try: - result = str(eval(args)) + result = str(eval(args)) # noqa: S307 if len(result) > 4096: - i = 0 - for x in range(0, len(result), 4096): + for i, x in enumerate(range(0, len(result), 4096)): if i == 0: await message.edit( f'{args}={result[x : x + 4000]}', @@ -23,7 +22,6 @@ async def calc(_, message: Message): ) else: await message.reply(f'{result[x : x + 4096]}', parse_mode='HTML') - i += 1 await asyncio.sleep(0.18) else: await message.edit(f'{args}={result}', parse_mode='HTML') diff --git a/modules/cdxl.py b/modules/cdxl.py index 5d4af81..902ad69 100644 --- a/modules/cdxl.py +++ b/modules/cdxl.py @@ -7,7 +7,7 @@ from utils.scripts import format_exc, import_library clarifai = import_library('clarifai') -from clarifai.client.model import Model +from clarifai.client.model import Model # noqa: E402 @Client.on_message(filters.command('cdxl', prefix) & filters.me) diff --git a/modules/cohere.py b/modules/cohere.py index f9c7eeb..4470739 100644 --- a/modules/cohere.py +++ b/modules/cohere.py @@ -5,17 +5,17 @@ from utils.scripts import import_library cohere = import_library('cohere') -import cohere +import cohere # noqa: F811 co = cohere.Client(cohere_key) -from pyrogram import Client, enums, filters -from pyrogram.errors import MessageTooLong -from pyrogram.types import Message -from utils.db import db -from utils.misc import modules_help, prefix -from utils.rentry import paste as rentry_paste -from utils.scripts import format_exc +from pyrogram import Client, enums, filters # noqa: E402 +from pyrogram.errors import MessageTooLong # noqa: E402 +from pyrogram.types import Message # noqa: E402 +from utils.db import db # noqa: E402 +from utils.misc import modules_help, prefix # noqa: E402 +from utils.rentry import paste as rentry_paste # noqa: E402 +from utils.scripts import format_exc # noqa: E402 @Client.on_message(filters.command('cohere', prefix) & filters.me) diff --git a/modules/demotivator.py b/modules/demotivator.py index fbea5d2..c1b5d68 100644 --- a/modules/demotivator.py +++ b/modules/demotivator.py @@ -9,22 +9,22 @@ from utils.scripts import import_library requests = import_library('requests') PIL = import_library('PIL', 'pillow') -from PIL import Image, ImageDraw, ImageFont +from PIL import Image, ImageDraw, ImageFont # noqa: E402 @Client.on_message(filters.command(['dem'], prefix) & filters.me) async def demotivator(client: Client, message: Message): await message.edit('Process of demotivation...', parse_mode=enums.ParseMode.HTML) - font = requests.get('https://github.com/The-MoonTg-project/files/blob/main/Times%20New%20Roman.ttf?raw=true') + font = requests.get('https://github.com/The-MoonTg-project/files/blob/main/Times%20New%20Roman.ttf?raw=true', timeout=10) f = font.content - template_dem = requests.get('https://raw.githubusercontent.com/files/main/demotivator.png') + template_dem = requests.get('https://raw.githubusercontent.com/files/main/demotivator.png', timeout=10) if message.reply_to_message: words = ['random', 'text', 'typing', 'fuck'] if message.reply_to_message.photo: donwloads = await client.download_media(message.reply_to_message.photo.file_id) photo = Image.open(f'{donwloads}') resize_photo = photo.resize((469, 312)) - text = message.text.split(' ', maxsplit=1)[1] if len(message.text.split()) > 1 else random.choice(words) + text = message.text.split(' ', maxsplit=1)[1] if len(message.text.split()) > 1 else random.choice(words) # noqa: S311 im = Image.open(BytesIO(template_dem.content)) im.paste(resize_photo, (65, 48)) text_font = ImageFont.truetype(BytesIO(f), 22) @@ -38,7 +38,7 @@ async def demotivator(client: Client, message: Message): donwloads = await client.download_media(message.reply_to_message.sticker.file_id) photo = Image.open(f'{donwloads}') resize_photo = photo.resize((469, 312)) - text = message.text.split(' ', maxsplit=1)[1] if len(message.text.split()) > 1 else random.choice(words) + text = message.text.split(' ', maxsplit=1)[1] if len(message.text.split()) > 1 else random.choice(words) # noqa: S311 im = Image.open(BytesIO(template_dem.content)) im.paste(resize_photo, (65, 48)) text_font = ImageFont.truetype(BytesIO(f), 22) diff --git a/modules/destroy.py b/modules/destroy.py index d85e1e2..8c23595 100644 --- a/modules/destroy.py +++ b/modules/destroy.py @@ -6,8 +6,8 @@ from utils.misc import modules_help, prefix from utils.scripts import import_library lottie = import_library('lottie') -from lottie.exporters import exporters -from lottie.importers import importers +from lottie.exporters import exporters # noqa: E402 +from lottie.importers import importers # noqa: E402 @Client.on_message(filters.command('destroy', prefix) & filters.me) diff --git a/modules/direct.py b/modules/direct.py index aca8a95..498a444 100644 --- a/modules/direct.py +++ b/modules/direct.py @@ -22,7 +22,7 @@ from utils.misc import modules_help, prefix def subprocess_run(cmd): reply = '' cmd_args = cmd.split() - subproc = Popen( + subproc = Popen( # noqa: S603 cmd_args, stdout=PIPE, stderr=PIPE, @@ -93,7 +93,7 @@ def gdrive(url: str) -> str: elif link.find('uc?id=') != -1: file_id = link.split('uc?id=')[1].strip() url = f'{drive}/uc?export=download&id={file_id}' - download = requests.get(url, stream=True, allow_redirects=False) + download = requests.get(url, stream=True, allow_redirects=False, timeout=10) cookies = download.cookies try: # In case of small file size, Google downloads directly @@ -112,7 +112,7 @@ def gdrive(url: str) -> str: if page_element is not None: export = drive + page_element.get('href') name = page.find('span', {'class': 'uc-name-size'}).text - response = requests.get(export, stream=True, allow_redirects=False, cookies=cookies) + response = requests.get(export, stream=True, allow_redirects=False, cookies=cookies, timeout=10) dl_url = response.headers['location'] if 'accounts.google.com' in dl_url: name = page.find('span', {'class': 'uc-name-size'}).text @@ -138,7 +138,7 @@ def yandex_disk(url: str) -> str: return reply api = 'https://cloud-api.yandex.net/v1/disk/public/resources/download?public_key={}' try: - dl_url = requests.get(api.format(link)).json()['href'] + dl_url = requests.get(api.format(link), timeout=10).json()['href'] name = dl_url.split('filename=')[1].split('&disposition')[0] reply += f'[{name}]({dl_url})\n' except KeyError: @@ -181,7 +181,7 @@ def mediafire(url: str) -> str: reply = '`No MediaFire links found`\n' return reply reply = '' - page = BeautifulSoup(requests.get(link).content, 'lxml') + page = BeautifulSoup(requests.get(link, timeout=10).content, 'lxml') info = page.find('a', {'aria-label': 'Download file'}) dl_url = info.get('href') size = re.findall(r'\(.*\)', info.text)[0] @@ -201,7 +201,7 @@ def sourceforge(url: str) -> str: reply = f'Mirrors for __{file_path.split("/")[-1]}__\n' project = re.findall(r'projects?/(.*?)/files', link)[0] mirrors = f'https://sourceforge.net/settings/mirror_choices?projectname={project}&filename={file_path}' - page = BeautifulSoup(requests.get(mirrors).content, 'html.parser') + page = BeautifulSoup(requests.get(mirrors, timeout=10).content, 'html.parser') info = page.find('ul', {'id': 'mirrorList'}).findAll('li') for mirror in info[1:]: name = re.findall(r'\((.*)\)', mirror.text.strip())[0] @@ -218,7 +218,7 @@ def osdn(url: str) -> str: except IndexError: reply = '`No OSDN links found`\n' return reply - page = BeautifulSoup(requests.get(link, allow_redirects=True).content, 'lxml') + page = BeautifulSoup(requests.get(link, allow_redirects=True, timeout=10).content, 'lxml') info = page.find('a', {'class': 'mirror_link'}) link = urllib.parse.unquote(osdn_link + info['href']) reply = f'Mirrors for __{link.split("/")[-1]}__\n' @@ -285,13 +285,14 @@ def useragent(): """ useragents = BeautifulSoup( requests.get( - 'https://developers.whatismybrowser.com/useragents/explore/operating_system_name/android/' + 'https://developers.whatismybrowser.com/useragents/explore/operating_system_name/android/', + timeout=10, ).content, 'lxml', ).findAll('td', {'class': 'useragent'}) if not useragents: return 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3' - user_agent = choice(useragents) + user_agent = choice(useragents) # noqa: S311 return user_agent.text diff --git a/modules/durov.py b/modules/durov.py index 010709d..d091512 100644 --- a/modules/durov.py +++ b/modules/durov.py @@ -8,7 +8,7 @@ from utils.misc import modules_help, prefix @Client.on_message(filters.command('durov', prefix) & filters.me) async def durov(_, message: Message): await message.edit( - f'Random post from channel: https://t.me/durov/{randint(21, 36500)}', + f'Random post from channel: https://t.me/durov/{randint(21, 36500)}', # noqa: S311 parse_mode=enums.ParseMode.HTML, ) diff --git a/modules/f.py b/modules/f.py index 61ef22f..fc38c58 100644 --- a/modules/f.py +++ b/modules/f.py @@ -36,7 +36,7 @@ async def download_sticker(url, filename): @Client.on_message(filters.command(['f'], prefix) & filters.me) async def random_stiker(client: Client, message: Message): await message.delete() - random = randint(1, 63) + random = randint(1, 63) # noqa: S311 index = f'00{random}' if random < 10 else f'0{random}' sticker = f'https://www.chpic.su/_data/stickers/f/FforRespect/FforRespect_{index}.webp' await download_sticker(sticker, 'f.webp') diff --git a/modules/fakeactions.py b/modules/fakeactions.py index dade85c..dc19a2c 100644 --- a/modules/fakeactions.py +++ b/modules/fakeactions.py @@ -32,7 +32,7 @@ async def fakeactions_handler(client: Client, message: Message): sec = int(message.command[1]) if sec > 60: sec = 60 - except: + except Exception: sec = None await message.delete() diff --git a/modules/flux.py b/modules/flux.py index 7e44db3..4244a15 100644 --- a/modules/flux.py +++ b/modules/flux.py @@ -11,7 +11,7 @@ from utils.scripts import format_exc, progress def schellwithflux(args): API_URL = 'https://randydev-ryuzaki-api.hf.space/api/v1/akeno/fluxai' payload = {'user_id': 1191668125, 'args': args} # Please don't edit here - response = requests.post(API_URL, json=payload) + response = requests.post(API_URL, json=payload, timeout=10) if response.status_code != 200: print(f'Error status {response.status_code}') return None diff --git a/modules/hearts.py b/modules/hearts.py index ab8d95e..4b4f128 100644 --- a/modules/hearts.py +++ b/modules/hearts.py @@ -50,7 +50,7 @@ async def phase2(message: Message): format_heart = joined_heart.replace(R, '{}') for _ in range(5): - heart = format_heart.format(*random.choices(ALL, k=heartlet_len)) + heart = format_heart.format(*random.choices(ALL, k=heartlet_len)) # noqa: S311 await _wrap_edit(message, heart) await asyncio.sleep(SLEEP) diff --git a/modules/kokodrilo_explodando.py b/modules/kokodrilo_explodando.py index 7f07a2c..0399614 100644 --- a/modules/kokodrilo_explodando.py +++ b/modules/kokodrilo_explodando.py @@ -13,7 +13,7 @@ async def kokodrilo_explodando(_, message: Message): amount = int(message.command[1]) for _ in range(amount): await message.edit('🐊') - await asyncio.sleep(random.uniform(1, 2.5)) + await asyncio.sleep(random.uniform(1, 2.5)) # noqa: S311 await message.edit('💥') await asyncio.sleep(1.8) diff --git a/modules/loader.py b/modules/loader.py index c5dee70..7816d25 100644 --- a/modules/loader.py +++ b/modules/loader.py @@ -51,7 +51,7 @@ async def get_mod_hash(_, message: Message): if len(message.command) == 1: return url = message.command[1].lower() - resp = requests.get(url) + resp = requests.get(url, timeout=10) if not resp.ok: await message.edit(f'Troubleshooting with downloading module {url}') return @@ -86,7 +86,7 @@ async def loadmod(_, message: Message): try: f = requests.get( 'https://raw.githubusercontent.com/The-MoonTg-project/custom_modules/main/full.txt' - ).text + , timeout=10).text except Exception: return await message.edit('Failed to fetch custom modules list') modules_dict = {line.split('/')[-1].split()[0]: line.strip() for line in f.splitlines()} @@ -98,8 +98,8 @@ async def loadmod(_, message: Message): else: modules_hashes = requests.get( 'https://raw.githubusercontent.com/The-MoonTg-project/custom_modules/main/modules_hashes.txt' - ).text - resp = requests.get(url) + , timeout=10).text + resp = requests.get(url, timeout=10) if not resp.ok: await message.edit( @@ -118,7 +118,7 @@ async def loadmod(_, message: Message): module_name = url.split('/')[-1].split('.')[0] - resp = requests.get(url) + resp = requests.get(url, timeout=10) if not resp.ok: await message.edit(f'Module {module_name} is not found') return @@ -137,7 +137,7 @@ async def loadmod(_, message: Message): modules_hashes = requests.get( 'https://raw.githubusercontent.com/The-MoonTg-project/custom_modules/main/modules_hashes.txt' - ).text + , timeout=10).text if hashlib.sha256(content).hexdigest() not in modules_hashes: os.remove(file_name) @@ -214,7 +214,7 @@ async def load_all_mods(_, message: Message): os.mkdir(f'{BASE_PATH}/modules/custom_modules') try: - f = requests.get('https://raw.githubusercontent.com/The-MoonTg-project/custom_modules/main/full.txt').text + f = requests.get('https://raw.githubusercontent.com/The-MoonTg-project/custom_modules/main/full.txt', timeout=10).text except Exception: return await message.edit('Failed to fetch custom modules list') modules_list = f.splitlines() @@ -222,7 +222,7 @@ async def load_all_mods(_, message: Message): await message.edit('Loading modules...') for module_name in modules_list: url = f'https://raw.githubusercontent.com/The-MoonTg-project/custom_modules/main/{module_name}.py' - resp = requests.get(url) + resp = requests.get(url, timeout=10) if not resp.ok: continue with open(f'./modules/custom_modules/{module_name.split("/")[1]}.py', 'wb') as f: @@ -281,14 +281,14 @@ async def updateallmods(_, message: Message): if not module_name.endswith('.py'): continue try: - f = requests.get('https://raw.githubusercontent.com/The-MoonTg-project/custom_modules/main/full.txt').text + f = requests.get('https://raw.githubusercontent.com/The-MoonTg-project/custom_modules/main/full.txt', timeout=10).text except Exception: return await message.edit('Failed to fetch custom modules list') modules_dict = {line.split('/')[-1].split()[0]: line.strip() for line in f.splitlines()} if module_name in modules_dict: resp = requests.get( f'https://raw.githubusercontent.com/The-MoonTg-project/custom_modules/main/{modules_dict[module_name]}.py' - ) + , timeout=10) if not resp.ok: modules_installed.remove(module_name) continue diff --git a/modules/markitdown.py b/modules/markitdown.py index 6061e2a..c7b272f 100644 --- a/modules/markitdown.py +++ b/modules/markitdown.py @@ -23,7 +23,7 @@ from utils.scripts import import_library, prefix, with_reply import_library('markitdown') -from markitdown import MarkItDown +from markitdown import MarkItDown # noqa: E402 @Client.on_message(filters.command(['markitdown', 'mkdn'], prefix) & filters.me) diff --git a/modules/mirror_flip.py b/modules/mirror_flip.py index 93f27c9..65f1f78 100644 --- a/modules/mirror_flip.py +++ b/modules/mirror_flip.py @@ -7,7 +7,7 @@ from utils.misc import modules_help, prefix from utils.scripts import import_library PIL = import_library('PIL', 'pillow') -from PIL import Image, ImageOps +from PIL import Image, ImageOps # noqa: E402 async def make(client, message, o): diff --git a/modules/misc/autofwd.py b/modules/misc/autofwd.py index 070667a..73b2285 100644 --- a/modules/misc/autofwd.py +++ b/modules/misc/autofwd.py @@ -139,8 +139,7 @@ async def autofwd_main(client: Client, message: Message): source_chats = db.get('custom.autofwd', 'chatsrc') target_chats = db.get('custom.autofwd', 'chatto') - if source_chats is not None and chat_id in source_chats: - if target_chats is not None: + if source_chats is not None and chat_id in source_chats and target_chats is not None: for chat in target_chats: try: await message.copy(chat) diff --git a/modules/misc/backup.py b/modules/misc/backup.py index 0b1acde..f6f33fc 100644 --- a/modules/misc/backup.py +++ b/modules/misc/backup.py @@ -133,7 +133,7 @@ async def backupmod(client: Client, message: Message): try: mod = message.text.split(maxsplit=1)[1].split('.')[0] - except: + except Exception: return await message.edit( f'Usage: {prefix}backupmod [module]', parse_mode=enums.ParseMode.HTML, @@ -169,7 +169,7 @@ async def restoremod(client: Client, message: Message): try: mod = message.text.split(maxsplit=1)[1].split('.')[0] - except: + except Exception: return await message.edit( f'Usage: {prefix}restoremod [module]', parse_mode=enums.ParseMode.HTML, diff --git a/modules/misc/cama.py b/modules/misc/cama.py index e1d3fd4..9bcf053 100644 --- a/modules/misc/cama.py +++ b/modules/misc/cama.py @@ -16,7 +16,7 @@ def get_marine_life_details(species_name): 'per_page': 1, } - response = requests.get(INATURALIST_API_URL, params=params) + response = requests.get(INATURALIST_API_URL, params=params, timeout=10) if response.status_code == 200: data = response.json() diff --git a/modules/misc/safone.py b/modules/misc/safone.py index 209a137..d5520a6 100644 --- a/modules/misc/safone.py +++ b/modules/misc/safone.py @@ -94,7 +94,7 @@ async def make_rayso(code: str, title: str, theme: str): 'language': 'auto', 'darkMode': False, } - response = requests.post(f'{url}/rayso', data=data, headers=headers) + response = requests.post(f'{url}/rayso', data=data, headers=headers, timeout=10) if response.status_code != 200: return None result = response.json() @@ -137,7 +137,7 @@ async def sgemini(_, message: Message): await message.edit_text('prompt not provided!') return await message.edit_text('Processing...') - response = requests.get(url=f'{url}/bard?query={prompt}', headers=headers) + response = requests.get(url=f'{url}/bard?query={prompt}', headers=headers, timeout=10) if response.status_code != 200: await message.edit_text('Something went wrong!') return @@ -167,7 +167,7 @@ async def app(client: Client, message: Message): try: coverImage_url = result['results'][0]['icon'] - coverImage = requests.get(url=coverImage_url).content + coverImage = requests.get(url=coverImage_url, timeout=10).content async with aiofiles.open('coverImage.jpg', mode='wb') as f: await f.write(coverImage) @@ -225,7 +225,7 @@ async def tsearch(client: Client, message: Message): else: message.edit_text("What should i search? You didn't provided me with any value to search") - response = requests.get(url=f'{url}/torrent?query={query}&limit={limit}', headers=headers) + response = requests.get(url=f'{url}/torrent?query={query}&limit={limit}', headers=headers, timeout=10) if response.status_code != 200: await message.edit_text('Something went wrong') return @@ -265,7 +265,7 @@ async def tsearch(client: Client, message: Message): ) if coverImage_url is not None: - coverImage = requests.get(url=coverImage_url).content + coverImage = requests.get(url=coverImage_url, timeout=10).content async with aiofiles.open('coverImage.jpg', mode='wb') as f: await f.write(coverImage) @@ -342,7 +342,7 @@ async def tts(client: Client, message: Message): return data = {'text': prompt, 'character': character} - response = requests.post(url=f'{url}/speech', headers=headers, json=data) + response = requests.post(url=f'{url}/speech', headers=headers, json=data, timeout=10) if response.status_code != 200: await message.edit_text('Something went wrong') return @@ -419,7 +419,7 @@ async def ccgen(_, message: Message): await message.edit_text('Code not provided!') return await message.edit_text('Processing...') - response = requests.get(url=f'{url}/ccgen?bins={bins}', headers=headers) + response = requests.get(url=f'{url}/ccgen?bins={bins}', headers=headers, timeout=10) if response.status_code != 200: await message.edit_text('Something went wrong') return diff --git a/modules/misc/sarethai.py b/modules/misc/sarethai.py index 046c6ce..3f87857 100644 --- a/modules/misc/sarethai.py +++ b/modules/misc/sarethai.py @@ -161,7 +161,7 @@ def format_apple_music_result(data): async def search_music(api_url, format_function, message, query): await message.edit('Searching...') url = f'{api_url}{query}&limit=10' - response = requests.get(url) + response = requests.get(url, timeout=10) if response.status_code == 200: try: @@ -194,7 +194,7 @@ async def google_search(client: Client, message: Message): await message.edit('Searching...') url = f'{GOOGLE_SEARCH_URL}{query}' - response = requests.get(url) + response = requests.get(url, timeout=10) if response.status_code == 200: data = response.json() results, formatted_results = format_google_results(data['data']) @@ -227,7 +227,7 @@ async def youtube_search(client: Client, message: Message): await message.edit('Searching...') url = f'{YOUTUBE_SEARCH_URL}{query}' - response = requests.get(url) + response = requests.get(url, timeout=10) if response.status_code == 200: data = response.json() results, formatted_results = format_youtube_results(data['data']) @@ -260,7 +260,7 @@ async def movie_search(client, message: Message): await message.edit('Searching...') url = f'{MOVIE_SEARCH_URL}{query}' - response = requests.get(url) + response = requests.get(url, timeout=10) if response.status_code == 200: data = response.json() results, formatted_results = format_movie_results(data['data']) @@ -300,7 +300,7 @@ async def apk_search(client, message: Message): await message.edit('Searching...') url = f'{APK_SEARCH_URL}{query}' - response = requests.get(url) + response = requests.get(url, timeout=10) if response.status_code == 200: data = response.json() results, formatted_results = format_apk_results(data['BK9']) @@ -329,7 +329,7 @@ async def gptweb(_, message: Message): await message.edit('Thinking...') query = ' '.join(message.command[1:]) url = f'{URL}/gptweb?text={query}' - response = requests.get(url) + response = requests.get(url, timeout=10) if response.status_code == 200: data = response.json() await message.edit( @@ -348,7 +348,7 @@ async def gemini(_, message: Message): await message.edit('Thinking...') query = ' '.join(message.command[1:]) url = f'{URL}/gemini?query={query}' - response = requests.get(url) + response = requests.get(url, timeout=10) if response.status_code == 200: data = response.json() await message.edit( @@ -440,7 +440,7 @@ async def handle_reply(client: Client, message: Message): f'Votes: {movie["vote_count"]}' ) if 'image' in movie: - response = requests.get(movie['image']) + response = requests.get(movie['image'], timeout=10) if response.status_code == 200: with open('movie_image.jpg', 'wb') as f: f.write(response.content) @@ -456,7 +456,7 @@ async def handle_reply(client: Client, message: Message): await message.reply(caption, parse_mode=enums.ParseMode.MARKDOWN) elif search_key.endswith('_apk'): apk_url = f'{APK_DOWNLOAD_URL}{results[index]["link"]}' - fetch_apk_url = requests.get(apk_url) + fetch_apk_url = requests.get(apk_url, timeout=10) if fetch_apk_url.status_code != 200: await message.edit('Failed to fetch APK data.') else: @@ -469,7 +469,7 @@ async def handle_reply(client: Client, message: Message): await message.edit('File size is too large to download.') else: apk_file_name = f'{data_apk["BK9"]["title"]}.apk' - response = requests.get(download_url) + response = requests.get(download_url, timeout=10) if response.status_code != 200: await message.edit('Failed to download the APK file.') diff --git a/modules/misc/search.py b/modules/misc/search.py index 02dd472..8336b54 100644 --- a/modules/misc/search.py +++ b/modules/misc/search.py @@ -27,7 +27,7 @@ async def search_cmd(client: Client, message: Message): cmd = message.command[1] word = message.command[2].lower() timeout = float(message.command[3]) if len(message.command) > 3 else 2 - except: + except Exception: return await message.edit( f'Usage: {prefix}search [/cmd]* [search_word]* [timeout=2.0]', parse_mode=enums.ParseMode.HTML, diff --git a/modules/misc/summary.py b/modules/misc/summary.py index 3a6c13c..fb469f3 100644 --- a/modules/misc/summary.py +++ b/modules/misc/summary.py @@ -8,8 +8,8 @@ from utils.scripts import format_exc, import_library import_library('lxml_html_clean') import_library('newspaper', 'newspaper3k') nltk = import_library('nltk') -from newspaper import Article -from newspaper.article import ArticleException +from newspaper import Article # noqa: E402 +from newspaper.article import ArticleException # noqa: E402 nltk.download('all') diff --git a/modules/misc/transcribeyt.py b/modules/misc/transcribeyt.py index f443e3d..8bb409d 100644 --- a/modules/misc/transcribeyt.py +++ b/modules/misc/transcribeyt.py @@ -15,9 +15,9 @@ gladia_url = 'https://api.gladia.io/v2/transcription/' # Function to make fetch requests to the Gladia API def make_fetch_request(url, headers, method='GET', data=None): if method == 'POST': - response = requests.post(url, headers=headers, json=data) + response = requests.post(url, headers=headers, json=data, timeout=10) else: - response = requests.get(url, headers=headers) + response = requests.get(url, headers=headers, timeout=10) return response.json() diff --git a/modules/pdf2md.py b/modules/pdf2md.py index 6cb5249..0a7a2f4 100644 --- a/modules/pdf2md.py +++ b/modules/pdf2md.py @@ -8,9 +8,9 @@ from utils.scripts import import_library import_library('pyzerox', 'py-zerox') -import litellm -from pyzerox import zerox -from pyzerox.errors import ModelAccessError, NotAVisionModel +import litellm # noqa: E402 +from pyzerox import zerox # noqa: E402 +from pyzerox.errors import ModelAccessError, NotAVisionModel # noqa: E402 kwargs = {} diff --git a/modules/perfectrussian.py b/modules/perfectrussian.py index c15d112..0807dcc 100644 --- a/modules/perfectrussian.py +++ b/modules/perfectrussian.py @@ -24,9 +24,9 @@ async def prussian_cmd(_, message: Message): ] splitted = message.reply_to_message.text.split() - for i in range(0, len(splitted), random.randint(2, 3)): + for i in range(0, len(splitted), random.randint(2, 3)): # noqa: S311 for _j in range(1, 2): - splitted.insert(i, random.choice(words)) + splitted.insert(i, random.choice(words)) # noqa: S311 await message.edit(' '.join(splitted), parse_mode=enums.ParseMode.HTML) diff --git a/modules/python.py b/modules/python.py index a07d8a4..5e166f7 100644 --- a/modules/python.py +++ b/modules/python.py @@ -41,7 +41,7 @@ async def user_exec(_: Client, message: Message): try: with redirect_stdout(stdout): - exec(code) # skipcq + exec(code) # skipcq # noqa: S102 text = f'Code:\n{code}\n\nResult:\n{stdout.getvalue()}' if message.command[0] == 'exnoedit': await message.reply(text) @@ -61,7 +61,7 @@ async def user_eval(client: Client, message: Message): code = message.text.split(maxsplit=1)[1] try: - result = eval(code) # skipcq + result = eval(code) # skipcq # noqa: S307 await message.edit(f'Expression:\n{code}\n\nResult:\n{result}') except Exception as e: await message.edit(format_exc(e)) diff --git a/modules/removebg.py b/modules/removebg.py index 4e635e6..db720e1 100644 --- a/modules/removebg.py +++ b/modules/removebg.py @@ -51,14 +51,14 @@ async def convert_to_image(message, client) -> None | str: path_s = await client.download_media(message.reply_to_message) final_path = 'lottie_proton.png' cmd = f'lottie_convert.py --frame 0 -if lottie -of png {path_s} {final_path}' - await exec(cmd) # skipcq + await exec(cmd) # skipcq # noqa: S102 elif message.reply_to_message.audio: thumb = message.reply_to_message.audio.thumbs[0].file_id final_path = await client.download_media(thumb) elif message.reply_to_message.video or message.reply_to_message.animation: final_path = 'fetched_thumb.png' vid_path = await client.download_media(message.reply_to_message) - await exec(f'ffmpeg -i {vid_path} -filter:v scale=500:500 -an {final_path}') # skipcq + await exec(f'ffmpeg -i {vid_path} -filter:v scale=500:500 -an {final_path}') # skipcq # noqa: S102 elif message.reply_to_message.document: if ( message.reply_to_message.document.mime_type == 'image/jpeg' @@ -83,6 +83,7 @@ def remove_background(photo_data): files={'image_file': image_data}, data={'size': 'auto'}, headers={'X-Api-Key': rmbg_key}, + timeout=10, ) if response.status_code == 200: return BytesIO(response.content) @@ -127,6 +128,7 @@ async def rmbg(client: Client, message: Message): files=files, allow_redirects=True, stream=True, + timeout=10, ) if os.path.exists(cool): os.remove(cool) diff --git a/modules/shell.py b/modules/shell.py index 82602f7..b6b470f 100644 --- a/modules/shell.py +++ b/modules/shell.py @@ -30,7 +30,7 @@ async def shell(_, message: Message): return await message.edit('Specify the command in message text') cmd_text = message.text.split(maxsplit=1)[1] cmd_args = cmd_text.split() - cmd_obj = Popen( + cmd_obj = Popen( # noqa: S603 cmd_args, stdout=PIPE, stderr=PIPE, diff --git a/modules/socialstalk.py b/modules/socialstalk.py index 62e6641..f6769df 100644 --- a/modules/socialstalk.py +++ b/modules/socialstalk.py @@ -26,12 +26,12 @@ async def tiktok_stalk(_, message: Message): await message.edit('Fetching TikTok profile...') url = f'{TIKTOK_API_URL}{query}' - response = requests.get(url) + response = requests.get(url, timeout=10) if response.status_code == 200: data = response.json().get('result', {}) if data: profile_pic_url = data.get('profile', '') - profile_pic = requests.get(profile_pic_url).content + profile_pic = requests.get(profile_pic_url, timeout=10).content profile_pic_stream = io.BytesIO(profile_pic) profile_pic_stream.name = 'profile.jpg' @@ -68,7 +68,7 @@ async def ipinfo(_, message: Message): try: url = requests.get( f'http://ip-api.com/json/{searchip}?fields=status,message,continent,continentCode,country,countryCode,region,regionName,city,district,zip,lat,lon,timezone,offset,currency,isp,org,as,asname,reverse,mobile,proxy,hosting,query' - ) + , timeout=10) response = json.loads(url.text) text = f""" IP Address: {response['query']} @@ -95,7 +95,7 @@ async def ipinfo(_, message: Message): Proxy: {response['proxy']} Hosting: {response['hosting']}""" await m.edit_text(text) - except: + except Exception: await m.edit_text('Unable To Find Info!') @@ -113,12 +113,12 @@ async def instagram_stalk(_, message: Message): await message.edit('Fetching Instagram profile...') url = f'{INSTAGRAM_API_URL}{query}' - response = requests.get(url) + response = requests.get(url, timeout=10) if response.status_code == 200: data = response.json().get('result', {}).get('user_info', {}) if data: profile_pic_url = data.get('profile_pic_url', '') - profile_pic = requests.get(profile_pic_url).content + profile_pic = requests.get(profile_pic_url, timeout=10).content profile_pic_stream = io.BytesIO(profile_pic) profile_pic_stream.name = 'profile.jpg' @@ -157,7 +157,7 @@ async def github_stalk(_, message: Message): await message.edit('Fetching GitHub profile...') url = f'{GH_STALK}{query}' - response = requests.get(url) + response = requests.get(url, timeout=10) if response.status_code == 200: data = response.json() created_at = data.get('created_at', 'N/A') diff --git a/modules/spin.py b/modules/spin.py index 1636f3f..dac7778 100644 --- a/modules/spin.py +++ b/modules/spin.py @@ -21,7 +21,7 @@ imageio = import_library('imageio') def create_gif(filename: str, offset: int, fps: int = 2, typ: str = 'spin'): img = Image.open(f'downloads/{filename}') if typ.lower() != 'spin': - img = img.resize((random.randint(200, 1280), random.randint(200, 1280)), Image.ANTIALIAS) + img = img.resize((random.randint(200, 1280), random.randint(200, 1280)), Image.ANTIALIAS) # noqa: S311 # noqa: S311 imageio.mimsave( 'downloads/video.gif', [img.rotate(-(i % 360)) for i in range(1, 361, offset)], diff --git a/modules/squotes.py b/modules/squotes.py index cf0c88c..9f180ab 100644 --- a/modules/squotes.py +++ b/modules/squotes.py @@ -75,7 +75,7 @@ async def quote_cmd(client: Client, message: Message): 'text_color': '#fff', } - response = requests.post(QUOTES_API, json=params) + response = requests.post(QUOTES_API, json=params, timeout=10) if not response.ok: return await message.edit(f'Quotes API error!\n{response.text}') @@ -126,7 +126,7 @@ async def fake_quote_cmd(client: Client, message: types.Message): 'text_color': '#fff', } - response = requests.post(QUOTES_API, json=params) + response = requests.post(QUOTES_API, json=params, timeout=10) if not response.ok: return await message.edit(f'Quotes API error!\n{response.text}') @@ -227,14 +227,14 @@ async def render_message(app: Client, message: types.Message) -> dict: author['avatar'] = await get_file(from_user.photo.big_file_id) elif not from_user.photo and from_user.username: # may be user blocked us, we will try to get avatar via t.me - t_me_page = requests.get(f'https://t.me/{from_user.username}').text + t_me_page = requests.get(f'https://t.me/{from_user.username}', timeout=10).text sub = ' 0 and link[0] and link[0] != 'https://telegram.org/img/t_logo.png': # found valid link - avatar = requests.get(link[0]).content + avatar = requests.get(link[0], timeout=10).content author['avatar'] = base64.b64encode(avatar).decode() else: author['avatar'] = '' diff --git a/modules/updater.py b/modules/updater.py index 0e1c99d..39bdce1 100644 --- a/modules/updater.py +++ b/modules/updater.py @@ -44,7 +44,7 @@ async def restart_cmd(_, message: Message): if 'LAVHOST' in os.environ: await message.edit('Your lavHost is restarting...') - os.system('lavhost restart') + os.system('lavhost restart') # noqa: S605, S607 return await message.edit('Restarting...') @@ -67,14 +67,14 @@ async def update(_, message: Message): if 'LAVHOST' in os.environ: await message.edit('Your lavHost is updating...') - os.system('lavhost update') + os.system('lavhost update') # noqa: S605, S607 return await message.edit('Updating...') try: if not check_command('termux-setup-storage'): subprocess.run([sys.executable, '-m', 'pip', 'install', '-U', 'pip'], check=True) - subprocess.run(['git', 'pull'], check=True) + subprocess.run(['git', 'pull'], check=True) # noqa: S607 if os.path.exists('requirements.txt') and os.path.getsize('requirements.txt') > 0: subprocess.run( @@ -91,7 +91,7 @@ async def update(_, message: Message): ) if requirements_list: - subprocess.run( + subprocess.run( # noqa: S603 [sys.executable, '-m', 'pip', 'install', '-U', *requirements_list], check=True, ) diff --git a/modules/url.py b/modules/url.py index 65ee09e..09155cf 100644 --- a/modules/url.py +++ b/modules/url.py @@ -35,7 +35,7 @@ from utils.scripts import format_exc, humanbytes, progress def generate_screenshot(url): api_url = f'https://api.apiflash.com/v1/urltoimage?access_key={apiflash_key}&url={url}&format=png' - response = requests.get(api_url) + response = requests.get(api_url, timeout=10) if response.status_code == 200: return BytesIO(response.content) return None @@ -212,6 +212,7 @@ async def upload_cmd(_, message: Message): response = requests.post( 'https://x0.at', files={'file': f}, + timeout=10, ) if response.ok: diff --git a/modules_list.txt b/modules_list.txt new file mode 100644 index 0000000..9a37dd2 --- /dev/null +++ b/modules_list.txt @@ -0,0 +1,115 @@ +# Userbot Modules +# Generated: Fri Jun 19 03:34:52 PM CEST 2026 + +## Core Modules (utils/) +- utils/config.py +- utils/conv.py +- utils/db.py +- utils/handlers.py +- utils/__init__.py +- utils/misc.py +- utils/module.py +- utils/rentry.py +- utils/scripts.py + +## Image Modules (images/) +- images/icons.py +- images/imgur.py +- images/ncode.py +- images/pinterest.py +- images/risearch.py +- images/unsplash2.py +- images/unsplash.py + +## Bot Modules (modules/) +- modules/1000-7.py +- modules/admintool.py +- modules/admlist.py +- modules/afk.py +- modules/aimage.py +- modules/amogus.py +- modules/amongus.py +- modules/animations.py +- modules/anime/anilist.py +- modules/anime/anime.py +- modules/anime/neko.py +- modules/aniquotes.py +- modules/antipm.py +- modules/blackbox.py +- modules/calculator.py +- modules/cdxl.py +- modules/chatbot.py +- modules/circle.py +- modules/clear_notifs.py +- modules/cohere.py +- modules/demotivator.py +- modules/destroy.py +- modules/dice.py +- modules/direct.py +- modules/duckduckgo.py +- modules/durov.py +- modules/example.py +- modules/fakeactions.py +- modules/filters.py +- modules/fliptext.py +- modules/flux.py +- modules/f.py +- modules/gemini.py +- modules/google.py +- modules/hearts.py +- modules/help.py +- modules/huggingface.py +- modules/id.py +- modules/joindate.py +- modules/kokodrilo_explodando.py +- modules/leave_chat.py +- modules/loader.py +- modules/markitdown.py +- modules/mention.py +- modules/mirror_flip.py +- modules/misc/autobackup.py +- modules/misc/autofwd.py +- modules/misc/backup.py +- modules/misc/cama.py +- modules/misc/mlog.py +- modules/misc/prayer.py +- modules/misc/safone.py +- modules/misc/sarethai.py +- modules/misc/search.py +- modules/misc/summary.py +- modules/misc/switch.py +- modules/misc/transcribeyt.py +- modules/notes.py +- modules/open.py +- modules/pdf2md.py +- modules/perfectrussian.py +- modules/ping.py +- modules/prefix.py +- modules/purge.py +- modules/python.py +- modules/reactionspam.py +- modules/removebg.py +- modules/say.py +- modules/sendmod.py +- modules/sessionkiller.py +- modules/sgb.py +- modules/shell.py +- modules/socialstalk.py +- modules/spam.py +- modules/spin.py +- modules/squotes.py +- modules/stickers.py +- modules/support.py +- modules/thumbnail.py +- modules/type.py +- modules/updater.py +- modules/upl.py +- modules/url.py +- modules/user_info.py +- modules/vt.py + +## Custom Modules (modules/custom_modules/) +- (none) + +--- +Total: 101 modules (85 bot + 0 custom + 7 image + 9 core) diff --git a/utils/handlers.py b/utils/handlers.py index 9a11822..99a73d5 100644 --- a/utils/handlers.py +++ b/utils/handlers.py @@ -771,9 +771,9 @@ class DemoteHandler: privileges=ChatPrivileges(**self.common_privileges_demote), ) except UserAdminInvalid: - raise UserAdminInvalid() + raise # noqa: B904 except ChatAdminRequired: - raise ChatAdminRequired() + raise # noqa: B904 except Exception as e: await self.message.edit(format_exc(e)) @@ -870,9 +870,9 @@ class PromoteHandler: self.cause.split(maxsplit=1)[1], ) except UserAdminInvalid: - raise UserAdminInvalid() + raise # noqa: B904 except ChatAdminRequired: - raise ChatAdminRequired() + raise # noqa: B904 except Exception as e: await self.message.edit(format_exc(e)) diff --git a/utils/rentry.py b/utils/rentry.py index cda0fc3..32c6aae 100644 --- a/utils/rentry.py +++ b/utils/rentry.py @@ -29,14 +29,14 @@ class UrllibClient: def get(self, url, headers=None): if headers is None: headers = {} - request = urllib.request.Request(url, headers=headers) + request = urllib.request.Request(url, headers=headers) # noqa: S310 return self._request(request) def post(self, url, data=None, headers=None): if headers is None: headers = {} postdata = urllib.parse.urlencode(data).encode() - request = urllib.request.Request(url, postdata, headers) + request = urllib.request.Request(url, postdata, headers) # noqa: S310 return self._request(request) def _request(self, request): diff --git a/utils/scripts.py b/utils/scripts.py index 76d8e6a..d30cf55 100644 --- a/utils/scripts.py +++ b/utils/scripts.py @@ -216,7 +216,7 @@ def restart() -> None: music_bot_process.terminate() except psutil.NoSuchProcess: print('Music bot is not running.') - os.execvp(sys.executable, [sys.executable, 'main.py']) # skipcq + os.execvp(sys.executable, [sys.executable, 'main.py']) # skipcq # noqa: S606 def format_exc(e: Exception, suffix='') -> str: @@ -324,7 +324,7 @@ def import_library(library_name: str, package_name: str = None): try: return importlib.import_module(library_name) except ImportError as exc: - completed = subprocess.run( + completed = subprocess.run( # noqa: S603 [sys.executable, '-m', 'pip', 'install', '--upgrade', package_name], check=True, ) @@ -340,7 +340,7 @@ def uninstall_library(package_name: str): Uninstalls a library :param package_name: package name in PyPi (pip uninstall example) """ - completed = subprocess.run([sys.executable, '-m', 'pip', 'uninstall', '-y', package_name], check=True) + completed = subprocess.run([sys.executable, '-m', 'pip', 'uninstall', '-y', package_name], check=True) # noqa: S603 if completed.returncode != 0: raise AssertionError( f'Failed to uninstall library {package_name} (pip exited with code {completed.returncode})' @@ -498,7 +498,7 @@ def parse_meta_comments(code: str) -> dict[str, str]: return {groups[i]: groups[i + 1] for i in range(0, len(groups), 2)} -def ReplyCheck(message: Message): +def reply_check(message: Message): reply_id = None if message.reply_to_message: