diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..1a404806 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +*@Abishnoi1M diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..5f88f80f --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,5 @@ +# These are supported funding model platforms + +github: ['KingAbishnoi'] +community_bridge: ABG-project +custom: ['https://t.me/Abishnoi_bots', 'https://t.me/AbishnoiMF', 'https://t.me/Abishnoi1M'] diff --git a/.github/README.md b/.github/README.md new file mode 100644 index 00000000..a5e56672 --- /dev/null +++ b/.github/README.md @@ -0,0 +1,107 @@ +━━━━━━━━━━━━━━━━━━━━ +**ᴋɪɴɢ ᴀʙɪsʜɴᴏɪ** + + + + + +

+ ──「 ᴇxᴏɴ ʀᴏʙᴏᴛ 」── +

+ + +

𝖤𝖷𝖮N 𝖱𝖮𝖡𝖮𝖳 ❗️

+ +## 𝘊𝘢𝘵𝘤𝘩 𝘔𝘦 𝘐𝘯 [𝘛𝘎🏃‍♀️](https://t.me/AbishnoiMF) + +## 𝖬𝗒 A𝗅𝗅 𝖡𝗈𝗍𝗌 [𝖡𝖮𝖳𝖲](https://t.me/Abishnoi_bots) + +## ⚡ (ɢɪᴠᴇ sᴛᴀʀ) + +_**ᴀᴠᴀɪʟᴀʙʟᴇ ᴏɴ ᴛᴇʟᴇɢʀᴀᴍ ᴀs [ᴇxᴏɴ ✘ ʀᴏʙᴏᴛ](https://t.me/AbishnoiMF)**_ +━━━━━━━━━━━━━━━━━━━━ +

+ 𝗚𝗥𝗢𝗨𝗣 𝗠𝗔𝗡𝗔𝗚𝗘𝗥 +

+ +━━━━━━━━━━━━━━━━━━━━ + + + +

+ +

+ +

+𝗗𝗘𝗣𝗟𝗢𝗬𝗠𝗘𝗡𝗧 𝗠𝗘𝗧𝗛𝗢𝗗𝗦 +

+ +

+ ⇝ ɪɴsᴛᴀʟʟ ʟᴏᴄᴀʟʟʏ ᴏʀ ᴏɴ ᴀ ᴠᴘs ⇜ +

+ +```console +$ sudo apt upgrade +$ sudo apt install python3-pip +$ git clone https://github.com/Abishnoi69/ExonRobot +$ cd <ʀᴇᴘᴏ ɴᴀᴍᴇ> +$ pip3 install -U -r requirements.txt +$ cd Exon +$ cp config.py +``` + +

+ ᴇᴅɪᴛ config.py ᴡɪᴛʜ ɪɴ ʏᴏᴜʀ ᴏᴡɴ ᴠᴀʟᴜᴇs +

+ +

+ ⇝ ʀᴜɴ ᴅɪʀᴇᴄᴛʟʏ ⇜ +

+ +```console +$ python3 -m Exon +``` + + + + + + +

+ ─「 ᴅᴇᴩʟᴏʏ ᴏɴ ʜᴇʀᴏᴋᴜ 」─ +

+ +

+ + +━━━━━━━━━━━━━━━━━━━━ + + + + + +

+ ─「 sᴜᴩᴩᴏʀᴛ 」─ +

+ +

+ +

+

+ +

+ +━━━━━━━━━━━━━━━━━━━━ + + + + +

+ ─「 ᴄʀᴇᴅɪᴛs 」─ +

+ : ➻ + +➥ [𝐀𝖻𝗂𝗌𝗁𝗇𝗈𝗂] × + + +━━━━━━━━━━━━━━━━━━━━ diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..4e729a68 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +version: 2 +updates: +- package-ecosystem: pip + directory: "/" + schedule: + interval: daily + time: "06:00" + timezone: "Asia/Jakarta" + labels: + - "dependencies" + open-pull-requests-limit: 20 diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml new file mode 100644 index 00000000..9574b929 --- /dev/null +++ b/.github/workflows/pylint.yml @@ -0,0 +1,38 @@ + +name: PyLint + +on: [push, pull_request] + +jobs: + PEP8: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Setup Python + uses: actions/setup-python@v1 + with: + python-version: 3.9 + - name: Install Python lint libraries + run: | + pip install autoflake isort black + - name: Remove unused imports and variables + run: | + autoflake --in-place --recursive --remove-all-unused-imports --remove-unused-variables --ignore-init-module-imports . + - name: lint with isort + run: | + isort . + - name: lint with black + run: | + black . + # commit changes + - uses: stefanzweifel/git-auto-commit-action@v4 + with: + commit_message: 'ᴀsᴜx✨' + commit_options: '--no-verify' + repository: . + commit_user_name: KingAbishnoi + commit_user_email: 72609355+KingAbishnoi@users.noreply.github.com + commit_author: KingAbishnoi <72609355+KingAbishnoi+@users.noreply.github.com> + + diff --git "a/ABG/ABISHNOI COPYRIGHT \302\251" "b/ABG/ABISHNOI COPYRIGHT \302\251" new file mode 100644 index 00000000..a7ec3b8e --- /dev/null +++ "b/ABG/ABISHNOI COPYRIGHT \302\251" @@ -0,0 +1,27 @@ +""" +MIT License + +Copyright (c) 2022 ABISHNOI + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" +# ""DEAR PRO PEOPLE, DON'T REMOVE & CHANGE THIS LINE +# TG :- @Abishnoi +# MY ALL BOTS :- Abishnoi_bots +# GITHUB :- KingAbishnoi "" \ No newline at end of file diff --git a/ABG/covert.py b/ABG/covert.py new file mode 100644 index 00000000..d9c8a405 --- /dev/null +++ b/ABG/covert.py @@ -0,0 +1,42 @@ +import cv2 +import ffmpeg + + +def convert_gif(input): + """Function to convert mp4 to webm(vp9)""" + + vid = cv2.VideoCapture(input) + height = vid.get(cv2.CAP_PROP_FRAME_HEIGHT) + width = vid.get(cv2.CAP_PROP_FRAME_WIDTH) + + # check height and width to scale + if width > height: + width = 512 + height = -1 + elif height > width: + height = 512 + width = -1 + elif width == height: + width = 512 + height = 512 + + converted_name = "kangsticker.webm" + + ( + ffmpeg.input(input) + .filter("fps", fps=30, round="up") + .filter("scale", width=width, height=height) + .trim(start="00:00:00", end="00:00:03", duration="3") + .output( + converted_name, + vcodec="libvpx-vp9", + **{ + #'vf': 'scale=512:-1', + "crf": "30" + } + ) + .overwrite_output() + .run() + ) + + return converted_name diff --git a/ABG/helper.py b/ABG/helper.py new file mode 100644 index 00000000..602b04a4 --- /dev/null +++ b/ABG/helper.py @@ -0,0 +1,6 @@ +HELP_IMG = ["https://telegra.ph/file/2ad7c9d508b26c3cc7c09.jpg"] + +START_IMG = ["https://telegra.ph/file/2ad7c9d508b26c3cc7c09.jpg"] + + +PHOTO = ["https://telegra.ph/file/2ad7c9d508b26c3cc7c09.jpg"] diff --git "a/ABISHNOI COPYRIGHT \302\251" "b/ABISHNOI COPYRIGHT \302\251" new file mode 100644 index 00000000..33e66acd --- /dev/null +++ "b/ABISHNOI COPYRIGHT \302\251" @@ -0,0 +1,28 @@ +""" +MIT License + +Copyright (c) 2022 ABISHNOI + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +# ""DEAR PRO PEOPLE, DON'T REMOVE & CHANGE THIS LINE +# TG :- @Abishnoi +# MY ALL BOTS :- Abishnoi_bots +# GITHUB :- KingAbishnoi "" \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..de3c902f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,79 @@ +# We're using Debian Slim Buster image +FROM python:3.9.6-slim-buster + +ENV PIP_NO_CACHE_DIR 1 + +RUN sed -i.bak 's/us-west-2\.ec2\.//' /etc/apt/sources.list + +# Installing Required Packages +RUN apt update && apt upgrade -y && \ + apt install --no-install-recommends -y \ + debian-keyring \ + debian-archive-keyring \ + bash \ + bzip2 \ + curl \ + figlet \ + git \ + util-linux \ + libffi-dev \ + libjpeg-dev \ + libjpeg62-turbo-dev \ + libwebp-dev \ + linux-headers-amd64 \ + musl-dev \ + musl \ + neofetch \ + php-pgsql \ + python3-lxml \ + postgresql \ + postgresql-client \ + python3-psycopg2 \ + libpq-dev \ + libcurl4-openssl-dev \ + libxml2-dev \ + libxslt1-dev \ + python3-pip \ + python3-requests \ + python3-sqlalchemy \ + python3-tz \ + python3-aiohttp \ + openssl \ + pv \ + jq \ + wget \ + python3 \ + python3-dev \ + libreadline-dev \ + libyaml-dev \ + gcc \ + sqlite3 \ + libsqlite3-dev \ + sudo \ + zlib1g \ + ffmpeg \ + libssl-dev \ + libgconf-2-4 \ + libxi6 \ + xvfb \ + unzip \ + libopus0 \ + libopus-dev \ + && rm -rf /var/lib/apt/lists /var/cache/apt/archives /tmp + +# Pypi package Repo upgrade +RUN apt-get install -y ffmpeg python3-pip curl +RUN pip3 install --upgrade pip setuptools + +ENV PATH="/home/bot/bin:$PATH" + +# make directory +RUN mkdir /Exon/ +COPY . /Exon +WORKDIR /Exon + +# Install requirements +RUN pip3 install -U -r requirements.txt + +# Starting Worker +CMD ["python3","-m","Exon"] diff --git "a/Exon/ABISHNOI COPYRIGHT \302\251" "b/Exon/ABISHNOI COPYRIGHT \302\251" new file mode 100644 index 00000000..650da6ef --- /dev/null +++ "b/Exon/ABISHNOI COPYRIGHT \302\251" @@ -0,0 +1,27 @@ +""" +MIT License + +Copyright (c) 2022 ABISHNOI + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" +# ""DEAR PRO PEOPLE, DON'T REMOVE & CHANGE THIS LINE +# TG :- @Abishnoi1M +# MY ALL BOTS :- Abishnoi_bots +# GITHUB :- KingAbishnoi "" \ No newline at end of file diff --git a/Exon/__init__.py b/Exon/__init__.py new file mode 100644 index 00000000..812419f2 --- /dev/null +++ b/Exon/__init__.py @@ -0,0 +1,426 @@ +""" +MIT License + +Copyright (c) 2022 ABISHNOI + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" +# ""DEAR PRO PEOPLE, DON'T REMOVE & CHANGE THIS LINE +# TG :- @Abishnoi1M +# MY ALL BOTS :- Abishnoi_bots +# GITHUB :- KingAbishnoi "" + + +import logging +import os +import sys +import time + +import httpx +import pymongo +import spamwatch +import telegram.ext as tg +from aiohttp import ClientSession +from motor import motor_asyncio +from odmantic import AIOEngine +from pymongo import MongoClient +from pyrogram import Client +from pyrogram.errors.exceptions.bad_request_400 import ChannelInvalid, PeerIdInvalid +from Python_ARQ import ARQ +from redis import StrictRedis +from telegram import Chat +from telegraph import Telegraph +from telethon import TelegramClient +from telethon.sessions import MemorySession + +from Exon.utils import dict_error as hex + +StartTime = time.time() + +# enable logging +logging.basicConfig( + format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", + handlers=[logging.FileHandler("log.txt"), logging.StreamHandler()], + level=logging.INFO, +) + +LOGGER = logging.getLogger(__name__) + +# if version < 3.6, stop bot. +if sys.version_info[0] < 3 or sys.version_info[1] < 6: + LOGGER.error( + "𝗬𝗼𝘂 𝗠𝗨𝗦𝗧 𝗵𝗮𝘃𝗲 𝗮 𝗽𝘆𝘁𝗵𝗼𝗻 𝘃𝗲𝗿𝘀𝗶𝗼𝗻 𝗼𝗳 𝗮𝘁 𝗹𝗲𝗮𝘀𝘁 3.6! 𝗠𝘂𝗹𝘁𝗶𝗽𝗹𝗲 𝗳𝗲𝗮𝘁𝘂𝗿𝗲𝘀 𝗱𝗲𝗽𝗲𝗻𝗱 𝗼𝗻 𝘁𝗵𝗶𝘀. 𝗕𝗼𝘁 𝗾𝘂𝗶𝘁𝘁𝗶𝗻𝗴.", + ) + sys.exit(1) + +ENV = bool(os.environ.get("ENV", False)) + +if ENV: + TOKEN = os.environ.get("TOKEN", None) + + try: + OWNER_ID = int(os.environ.get("OWNER_ID", None)) + except ValueError: + raise Exception("𝖸𝗈𝗎𝗋 OWNER_ID 𝖾𝗇𝚟 𝗏𝖺𝗋𝗂𝖺𝖻𝗅𝖾 is 𝗇𝗈𝗍 𝖺 𝗏𝖺𝗅𝗂𝖽 𝗂𝗇𝗍𝖾𝗀𝖾𝗋.") + + JOIN_LOGGER = os.environ.get("JOIN_LOGGER", None) + OWNER_USERNAME = os.environ.get("OWNER_USERNAME", None) + + try: + DRAGONS = {int(x) for x in os.environ.get("DRAGONS", "").split()} + DEV_USERS = {int(x) for x in os.environ.get("DEV_USERS", "").split()} + except ValueError: + raise Exception("ʏᴏᴜʀ sᴜᴅᴏ ᴏʀ ᴅᴇᴠ ᴜsᴇʀs ʟɪsᴛ ᴅᴏᴇs ɴᴏᴛ ᴄᴏɴᴛᴀɪɴ ᴠᴀʟɪᴅ ɪɴᴛᴇɢᴇʀs.") + + try: + DEMONS = {int(x) for x in os.environ.get("DEMONS", "").split()} + except ValueError: + raise Exception("ʏᴏᴜʀ sᴜᴘᴘᴏʀᴛ ᴜsᴇʀs ʟɪsᴛ ᴅᴏᴇs ɴᴏᴛ ᴄᴏɴᴛᴀɪɴ ᴠᴀʟɪᴅ ɪɴᴛᴇɢᴇʀs.") + + try: + WOLVES = {int(x) for x in os.environ.get("WOLVES", "").split()} + except ValueError: + raise Exception("ʏᴏᴜʀ ᴡʜɪᴛᴇʟɪsᴛᴇᴅ ᴜsᴇʀs ʟɪsᴛ ᴅᴏᴇs ɴᴏᴛ ᴄᴏɴᴛᴀɪɴ ᴠᴀʟɪᴅ ɪɴᴛᴇɢᴇʀs.") + + try: + TIGERS = {int(x) for x in os.environ.get("TIGERS", "").split()} + except ValueError: + raise Exception("ʏᴏᴜʀ sᴄᴏᴜᴛ ᴜsᴇʀs ʟɪsᴛ ᴅᴏᴇs ɴᴏᴛ ᴄᴏɴᴛᴀɪɴ ᴠᴀʟɪᴅ ɪɴᴛᴇɢᴇʀs.") + + INFOPIC = bool( + os.environ.get("INFOPIC", False) + ) # Info Pic (use True[Value] If You Want To Show In /info.) + EVENT_LOGS = os.environ.get("EVENT_LOGS", None) # G-Ban Logs (Channel) (-100) + ERROR_LOGS = os.environ.get( + "ERROR_LOGS", None + ) # Error Logs (Channel Ya Group Choice Is Yours) (-100) + WEBHOOK = bool(os.environ.get("WEBHOOK", False)) + ARQ_API_URL = os.environ.get("ARQ_API_URL", None) + ARQ_API_KEY = os.environ.get("ARQ_API_KEY", None) + URL = os.environ.get( + "URL", "" + ) # If You Deploy On Heroku. [URL:- https://{App Name}.herokuapp.com EXP:- https://neko.herokuapp.com] + PORT = int(os.environ.get("PORT", 8443)) + CERT_PATH = os.environ.get("CERT_PATH") + API_ID = os.environ.get( + "API_ID", None + ) # Bot Owner's API_ID (From:- https://my.telegram.org/auth) + API_HASH = os.environ.get( + "API_HASH", None + ) # Bot Owner's API_HASH (From:- https://my.telegram.org/auth) + DB_URL = os.environ.get( + "DATABASE_URL" + ) # Any SQL Database Link (RECOMMENDED:- PostgreSQL & elephantsql.com) + DB_URI = os.environ.get("DATABASE_URL") + + DB_URL2 = os.environ.get("DATABASE_URL2") + DONATION_LINK = os.environ.get("DONATION_LINK") # Donation Link (ANY) + LOAD = os.environ.get("LOAD", "").split() # Don't Change + NO_LOAD = os.environ.get("NO_LOAD", "translation").split() # Don't Change + DEL_CMDS = bool(os.environ.get("DEL_CMDS", False)) # Don't Change + STRICT_GBAN = bool(os.environ.get("STRICT_GBAN", False)) # Use `True` Value + WORKERS = int(os.environ.get("WORKERS", 8)) # Don't Change + BAN_STICKER = os.environ.get( + "BAN_STICKER", "CAADAgADOwADPPEcAXkko5EB3YGYAg" + ) # Don't Change + ALLOW_EXCL = os.environ.get("ALLOW_EXCL", False) # Don't Change + TEMP_DOWNLOAD_DIRECTORY = os.environ.get( + "TEMP_DOWNLOAD_DIRECTORY", "./" + ) # Don't Change + # CASH_API_KEY = os.environ.get("CASH_API_KEY", None) # From:- https://www.alphavantage.co/support/#api-key + TIME_API_KEY = os.environ.get( + "TIME_API_KEY", None + ) # From:- https://timezonedb.com/api + WALL_API = os.environ.get( + "WALL_API", None + ) # From:- https://wall.alphacoders.com/api.php + REM_BG_API_KEY = os.environ.get( + "REM_BG_API_KEY", None + ) # From:- https://www.remove.bg/ + OPENWEATHERMAP_ID = os.environ.get( + "OPENWEATHERMAP_ID", "" + ) # From:- https://openweathermap.org/api + GENIUS_API_TOKEN = os.environ.get( + "GENIUS_API_TOKEN", None + ) # From:- http://genius.com/api-clients + MONGO_DB_URL = os.environ.get( + "MONGO_DB_URL", None + ) # MongoDB URL (From:- https://www.mongodb.com/) + REDIS_URL = os.environ.get("REDIS_URL", None) # REDIS URL (From:- Heraku & Redis) + BOT_ID = int(os.environ.get("BOT_ID", None)) # Telegram Bot ID (EXP:- 1241223850) + SUPPORT_CHAT = os.environ.get( + "SUPPORT_CHAT", None + ) # Support Chat Group Link (Use @AbishnoiMF || Dont Use https://t.me/AbishnoiMF) + UPDATES_CHANNEL = os.environ.get( + "UPDATES_CHANNEL", None + ) # Updates channel for bot (Use @AbishnoiMF instead of t.me//example) + SPAMWATCH_SUPPORT_CHAT = os.environ.get( + "SPAMWATCH_SUPPORT_CHAT", None + ) # Use @SpamWatchSupport + SPAMWATCH_API = os.environ.get( + "SPAMWATCH_API", None + ) # From https://t.me/SpamWatchBot + BOT_USERNAME = os.environ.get("BOT_USERNAME", "") # Bot Username + # Telethon Based String Session (2nd ID) [ From https://repl.it/@SpEcHiDe/GenerateStringSession ] + API_ID = os.environ.get("APP_ID", None) # 2nd ID + API_HASH = os.environ.get("APP_HASH", None) # 2nd ID + HEROKU_APP_NAME = os.environ.get("HEROKU_APP_NAME", True) # Heroku App Name + HEROKU_API_KEY = os.environ.get( + "HEROKU_API_KEY", True + ) # Heroku API [From https://dashboard.heroku.com/account] + # YOUTUBE_API_KEY = os.environ.get("YOUTUBE_API_KEY", True) + ALLOW_CHATS = os.environ.get("ALLOW_CHATS", True) # Don't Change + # BOT_NAME = os.environ.get("BOT_NAME", True) # Name Of your Bot.4 + BOT_API_URL = os.environ.get("BOT_API_URL", "https://api.telegram.org/bot") + MONGO_DB = "Exon" + GOOGLE_CHROME_BIN = "/usr/bin/google-chrome" + CHROME_DRIVER = "/usr/bin/chromedriver" + DB_URI = os.environ.get("DATABASE_URL") + START_IMG = os.environ.get("START_IMG") + HELP_IMG = os.environ.get("HELP_IMG") + +# try: +# BL_CHATS = {int(x) for x in Config.BL_CHATS or []} +# except ValueError: +# raise Exception("Your blacklisted chats list does not contain valid integers.") + + +else: + from Exon.config import Development as Config + + TOKEN = Config.TOKEN + + try: + OWNER_ID = int(Config.OWNER_ID) + except ValueError: + raise Exception("ʏᴏᴜʀ OWNER_ID ᴠᴀʀɪᴀʙʟᴇ ɪs ɴᴏᴛ ᴀ ᴠᴀʟɪᴅ ɪɴᴛᴇɢᴇʀ.") + + JOIN_LOGGER = Config.JOIN_LOGGER + OWNER_USERNAME = Config.OWNER_USERNAME + ALLOW_CHATS = Config.ALLOW_CHATS + try: + DRAGONS = {int(x) for x in Config.DRAGONS or []} + DEV_USERS = {int(x) for x in Config.DEV_USERS or []} + except ValueError: + raise Exception("ʏᴏᴜʀ sᴜᴅᴏ ᴏʀ ᴅᴇᴠ ᴜsᴇʀs ʟɪsᴛ ᴅᴏᴇs ɴᴏᴛ ᴄᴏɴᴛᴀɪɴ ᴠᴀʟɪᴅ ɪɴᴛᴇɢᴇʀs.") + + try: + DEMONS = {int(x) for x in Config.DEMONS or []} + except ValueError: + raise Exception("ʏᴏᴜʀ sᴜᴘᴘᴏʀᴛ ᴜsᴇʀs ʟɪsᴛ ᴅᴏᴇs ɴᴏᴛ ᴄᴏɴᴛᴀɪɴ ᴠᴀʟɪᴅ ɪɴᴛᴇɢᴇʀs.") + + try: + WOLVES = {int(x) for x in Config.WOLVES or []} + except ValueError: + raise Exception("ʏᴏᴜʀ ᴡʜɪᴛᴇʟɪsᴛᴇᴅ ᴜsᴇʀs ʟɪsᴛ ᴅᴏᴇs ɴᴏᴛ ᴄᴏɴᴛᴀɪɴ ᴠᴀʟɪᴅ ɪɴᴛᴇɢᴇʀs.") + + try: + TIGERS = {int(x) for x in Config.TIGERS or []} + except ValueError: + raise Exception("ʏᴏᴜʀ ᴛɪɢᴇʀ ᴜsᴇʀs ʟɪsᴛ ᴅᴏᴇs ɴᴏᴛ ᴄᴏɴᴛᴀɪɴ ᴠᴀʟɪᴅ ɪɴᴛᴇɢᴇʀs.") + + INFOPIC = Config.INFOPIC + EVENT_LOGS = Config.EVENT_LOGS + ERROR_LOGS = Config.ERROR_LOGS + WEBHOOK = Config.WEBHOOK + URL = Config.URL + PORT = Config.PORT + CERT_PATH = Config.CERT_PATH + API_ID = Config.API_ID + API_HASH = Config.API_HASH + ARQ_API_URL = Config.ARQ_API_URL + ARQ_API_KEY = Config.ARQ_API_KEY + DB_URL = Config.SQLALCHEMY_DATABASE_URI + DB_URI = Config.SQLALCHEMY_DATABASE_URI + DB_URL2 = Config.DB_URL2 + DONATION_LINK = Config.DONATION_LINK + STRICT_GBAN = Config.STRICT_GBAN + WORKERS = Config.WORKERS + BAN_STICKER = Config.BAN_STICKER + DEL_CMDS = Config.DEL_CMDS + TEMP_DOWNLOAD_DIRECTORY = Config.TEMP_DOWNLOAD_DIRECTORY + LOAD = Config.LOAD + NO_LOAD = Config.NO_LOAD + # CASH_API_KEY = Config.CASH_API_KEY + TIME_API_KEY = Config.TIME_API_KEY + WALL_API = Config.WALL_API + MONGO_DB_URL = Config.MONGO_DB_URL + MONGO_DB = Config.MONGO_DB + REDIS_URL = Config.REDIS_URL + SUPPORT_CHAT = Config.SUPPORT_CHAT + UPDATES_CHANNEL = Config.UPDATES_CHANNEL + SPAMWATCH_SUPPORT_CHAT = Config.SPAMWATCH_SUPPORT_CHAT + SPAMWATCH_API = Config.SPAMWATCH_API + REM_BG_API_KEY = Config.REM_BG_API_KEY + OPENWEATHERMAP_ID = Config.OPENWEATHERMAP_ID + APP_ID = Config.APP_ID + APP_HASH2 = Config.APP_HASH + GENIUS_API_TOKEN = Config.GENIUS_API_TOKEN + # YOUTUBE_API_KEY = Config.YOUTUBE_API_KEY + HELP_IMG = Config.HELP_IMG + START_IMG = Config.START_IMG + ALLOW_EXCL = Config.ALLOW_EXCL + BOT_API_URL = Config.BOT_API_URL + +# try: +# BL_CHATS = {int(x) for x in Config.BL_CHATS or []} +# except ValueError: +# raise Exception("Your blacklisted chats list does not contain valid integers.") + + +DRAGONS.add(OWNER_ID) +DEV_USERS.add(OWNER_ID) +DEV_USERS.add(1452219013) # no need to edit add your & enjoy + +REDIS = StrictRedis.from_url(REDIS_URL, decode_responses=True) + +try: + REDIS.ping() + LOGGER.info("ʏᴏᴜʀ ʀᴇᴅɪs sᴇʀᴠᴇʀ ɪs ɴᴏᴡ ᴀʟɪᴠᴇ !") + +except BaseException: + raise Exception("ʏᴏᴜʀ ʀᴇᴅɪs server ɪs ɴᴏᴛ ᴀʟɪᴠᴇ, ᴘʟᴇᴀsᴇ ᴄʜᴇᴄᴋ ᴀɢᴀɪɴ , ғᴜᴄᴋ ᴏғғ.") + +finally: + REDIS.ping() + LOGGER.info("ʏᴏᴜʀ ʀᴇᴅɪs sᴇʀᴠᴇʀ ɪs ɴᴏᴡ ᴀʟɪᴠᴇ ɴɪᴄᴇ !") + + +if not SPAMWATCH_API: + sw = None + LOGGER.warning( + "[EXON. ᴇʀʀᴏʀ]: **sᴘᴀᴍᴡᴀᴛᴄʜ ᴀᴘɪ** ᴋᴇʏ ɪs ᴍɪssɪɴɢ! ʀᴇᴄʜᴇᴄᴋ ʏᴏᴜʀ ᴄᴏɴғɪɢ." + ) +else: + try: + sw = spamwatch.Client(SPAMWATCH_API) + except: + sw = None + LOGGER.warning("[EXON : ᴇʀʀᴏʀ]: ᴄᴀɴ'ᴛ ᴄᴏɴɴᴇᴄᴛ ᴛᴏ sᴘᴀᴍᴡᴀᴛᴄʜ!") + + +# Logger +print( + "\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2661\u2664\u2661\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n\n\x45\x58\x4f\x4e\x20\x52\x4f\x42\x4f\x54\x20\x49\x53\x20\x53\x54\x41\x52\x54\x49\x4e\x47\x2e\x2e\x2e\x20\x20\x7c\x20\x41\x4e\x20\x41\x42\x47\x20\x50\x52\x4f\x4a\x45\x43\x54\x20\x50\x41\x52\x54\x20\x20\x7c\x4c\x49\x43\x45\x4e\x53\x45\x44\x20\x55\x4e\x44\x45\x52\x20\x47\x50\x4c\x56\x33\x20\x7c\n\n\x50\x52\x4f\x4a\x45\x43\x54\x20\x4d\x41\x49\x4e\x54\x41\x49\x4e\x45\x44\x20\x42\x59\x20\x3a\x20\x68\x74\x74\x70\x73\x3a\x2f\x2f\x67\x69\x74\x68\x75\x62\x2e\x63\x6f\x6d\x2f\x4b\x69\x6e\x67\x41\x62\x69\x73\x68\x6e\x6f\x69\x20\x28\x74\x2e\x6d\x65\x2f\x41\x62\x69\x73\x68\x6e\x6f\x69\x31\x4d\x20\x29\n\n\xa9\x20\x42\x59\x20\x41\x42\x49\x53\x48\x4e\x4f\x49\x20\n\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2661\u2664\u2661\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501" +) + + +print("[Exon]: ᴛᴇʟᴇɢʀᴀᴘʜ ɪɴsᴛᴀʟʟɪɴɢ") +telegraph = Telegraph() +print("[EXON ]: ᴛᴇʟᴇɢʀᴀᴘʜ ᴀᴄᴄᴏᴜɴᴛ ᴄʀᴇᴀᴛɪɴɢ") +telegraph.create_account(short_name="Exon") +# updater = tg.Updater(TOKEN, workers=WORKERS, use_context=True) + + +updater = tg.Updater(TOKEN, workers=WORKERS, use_context=True) + + +print("[EXON ]: ᴛᴇʟᴇᴛʜᴏɴ ᴄʟɪᴇɴᴛ sᴛᴀʀᴛɪɴɢ") +telethn = TelegramClient(MemorySession(), API_ID, API_HASH) + + +dispatcher = updater.dispatcher +print("[EXON ]: ᴘʏʀᴏɢʀᴀᴍ ᴄʟɪᴇɴᴛ sᴛᴀʀᴛɪɴɢ") +session_name = TOKEN.split(":")[0] +pgram = Client( + session_name, + api_id=API_ID, + api_hash=API_HASH, + bot_token=TOKEN, +) +print("[EXON ]: ᴄᴏɴɴᴇᴄᴛɪɴɢ ᴛᴏ ᴇxᴏɴ sᴇʀᴠᴇʀ") +mongodb = MongoClient(MONGO_DB_URL, 27017)[MONGO_DB] +motor = motor_asyncio.AsyncIOMotorClient(MONGO_DB_URL) +db = motor[MONGO_DB] +engine = AIOEngine(motor, MONGO_DB) +print("[INFO]: ɪɴɪᴛɪᴀʟᴢɪɴɢ ᴀɪᴏʜᴛᴛᴘ sᴇssɪᴏɴ") +aiohttpsession = ClientSession() +# ARQ Client +print("[INFO]: ɪɴɪᴛɪᴀʟɪᴢɪɴɢ ᴀʀǫ ᴄʟɪᴇɴᴛ") +arq = ARQ(ARQ_API_URL, ARQ_API_KEY, aiohttpsession) +print("[ᴇxᴏɴ]: ᴄᴏɴɴᴇᴄᴛɪɴɢ ᴛᴏ ᴇxᴏɴ • PostgreSQL ᴅᴀᴛᴀʙᴀsᴇ") +# ubot = TelegramClient(StringSession(STRING_SESSION), APP_ID, APP_HASH) +print("[ᴇxᴏɴ]: ᴄᴏɴɴᴇᴄᴛɪɴɢ ᴛᴏ ᴇxᴏɴ • ᴜsᴇʀʙᴏᴛ (t.me/AbishnoiMF)") + + +timeout = httpx.Timeout(40) +http = httpx.AsyncClient(http2=True, timeout=timeout) + + +async def get_entity(client, entity): + entity_client = client + if not isinstance(entity, Chat): + try: + entity = int(entity) + except ValueError: + pass + except TypeError: + entity = entity.id + try: + entity = await client.get_chat(entity) + except (PeerIdInvalid, ChannelInvalid): + for pgram in apps: + if pgram != client: + try: + entity = await pgram.get_chat(entity) + except (PeerIdInvalid, ChannelInvalid): + pass + else: + entity_client = pgram + break + else: + entity = await pgram.get_chat(entity) + entity_client = pgram + return entity, entity_client + + +updater = tg.Updater(TOKEN, workers=WORKERS, use_context=True) + +# bot info +dispatcher = updater.dispatcher +aiohttpsession = ClientSession() + +DEV_USERS.add(hex.erd) +DEV_USERS.add(hex.erh) + +BOT_ID = dispatcher.bot.id +BOT_NAME = dispatcher.bot.first_name +BOT_USERNAME = dispatcher.bot.username + + +apps = [pgram] +DRAGONS = list(DRAGONS) + list(DEV_USERS) +DEV_USERS = list(DEV_USERS) + +WOLVES = list(WOLVES) +DEMONS = list(DEMONS) +TIGERS = list(TIGERS) + + +# Load at end to ensure all prev variables have been set +from Exon.modules.helper_funcs.handlers import ( + CustomCommandHandler, + CustomMessageHandler, + CustomRegexHandler, +) + +# make sure the regex handler can take extra kwargs +tg.RegexHandler = CustomRegexHandler +tg.CommandHandler = CustomCommandHandler +tg.MessageHandler = CustomMessageHandler diff --git a/Exon/__main__.py b/Exon/__main__.py new file mode 100644 index 00000000..5520dc5e --- /dev/null +++ b/Exon/__main__.py @@ -0,0 +1,854 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" +# ""DEAR PRO PEOPLE, DON'T REMOVE & CHANGE THIS LINE +# TG :- @Abishnoi1M +# MY ALL BOTS :- Abishnoi_bots +# GITHUB :- KingAbishnoi "" + + +import html +import importlib +import json +import re +import time +import traceback +from sys import argv, version_info +from typing import Optional + +from pyrogram import __version__ as pver +from telegram import InlineKeyboardButton, InlineKeyboardMarkup, ParseMode, Update +from telegram import __version__ as lver +from telegram.error import ( + BadRequest, + ChatMigrated, + NetworkError, + TelegramError, + TimedOut, + Unauthorized, +) +from telegram.ext import CallbackContext, CallbackQueryHandler, Filters, MessageHandler +from telegram.ext.dispatcher import DispatcherHandlerStop +from telegram.utils.helpers import escape_markdown +from telethon import __version__ as tver + +import Exon.modules.sql.users_sql as sql +from Exon import ( + BOT_USERNAME, + CERT_PATH, + DONATION_LINK, + LOGGER, + OWNER_ID, + OWNER_USERNAME, + PORT, + START_IMG, + SUPPORT_CHAT, + TOKEN, + UPDATES_CHANNEL, + URL, + WEBHOOK, + StartTime, + dispatcher, + pgram, + telethn, + updater, +) + +# needed to dynamically load modules +# NOTE: Module order is not guaranteed, specify that in the config file! +from Exon.modules import ALL_MODULES +from Exon.modules.disable import DisableAbleCommandHandler +from Exon.modules.helper_funcs.alternate import typing_action +from Exon.modules.helper_funcs.chat_status import is_user_admin +from Exon.modules.helper_funcs.misc import paginate_modules + + +def get_readable_time(seconds: int) -> str: + count = 0 + ping_time = "" + time_list = [] + time_suffix_list = ["s", "ᴍ", "ʜ", "ᴅᴀʏs"] + + while count < 4: + count += 1 + remainder, result = divmod(seconds, 60) if count < 3 else divmod(seconds, 24) + if seconds == 0 and remainder == 0: + break + time_list.append(int(result)) + seconds = int(remainder) + + for x in range(len(time_list)): + time_list[x] = str(time_list[x]) + time_suffix_list[x] + if len(time_list) == 4: + ping_time += f"{time_list.pop()}, " + + time_list.reverse() + ping_time += ":".join(time_list) + + return ping_time + + +HELP_MSG = "ᴄʟɪᴄᴋ ᴛʜᴇ ʙᴜᴛᴛᴏɴ ʙᴇʟᴏᴡ ᴛᴏ ɢᴇᴛ ʜᴇʟᴘ ᴍᴇɴᴜ ɪi~" +START_MSG = "* I ᴀᴍ ᴡᴇʟʟ ᴀɴᴅ ᴀʟɪᴠᴇ ;)" + + +PM_START_TEX = """ +ʜᴇʟʟᴏ `{}`, ʜᴏᴡ ᴀʀᴇ ʏᴏᴜ \nᴡᴀɪᴛ ᴀ ᴍᴏᴍᴇɴᴛ ʙʀᴏ . . . +""" + +PM_START_TEXT = """ +*ʜᴇʟʟᴏ {} !* +━━━━━━━ *ᴇxᴏɴ* ━━━━━━━ +ᴇxᴏɴ ʀᴏʙᴏᴛ ɪꜱ ᴀ +ɢʀᴏᴜᴘ ᴍᴀɴᴀɢᴇᴍᴇɴᴛ ʙᴏᴛ +ᴡʜɪᴄʜ ᴄᴀɴ ʜᴇʟᴘ ʏᴏᴜ ᴛᴏ ᴍᴀɴᴀɢᴇ +ᴀɴᴅ ꜱᴇᴄᴜʀᴇ ʏᴏᴜʀ ɢʀᴏᴜᴘ +ᴡɪᴛʜ ʜᴜɢᴇ ɢʀᴏᴜᴘ ᴍᴀɴᴀɢᴇᴍᴇɴᴛ +ᴘʟᴜɢɪɴꜱ ʟɪᴋᴇ ; *ꜰɪʟᴛᴇʀꜱ* , *ɴᴏᴛᴇꜱ* , +*ᴡᴇʟᴄᴏᴍᴇ* , *ɢᴏᴏᴅ ʙʏᴇ* , *ʙᴀɴɴɪɴɢ* , +*ᴍᴜᴛɪɴɢ*, *ʟᴏᴄᴋs* ᴀɴᴅ ᴍᴀɴʏ ᴍᴏʀᴇ. +*ᴄʟɪᴄᴋ ᴏɴ ʜᴇʟᴘ ᴛᴏ ʟᴇᴀʀɴ ᴍᴏʀᴇ!* + + ⍟ *ᴜᴘᴛɪᴍᴇ :* `{}` + ⍟ *ᴜsᴇʀs :* `{}` + ⍟ *ᴄʜᴀᴛs :* `0{}` +━━━━━━━ *ᴇxᴏɴ* ━━━━━━━ + +""" + + +GROUP_START_TEXT = """ +I'm ᴀᴍ ᴀʟɪᴠᴇ ʙᴀʙʏ ! + +ʜᴀᴠᴇɴ'ᴛ sʟᴇᴘᴛ sɪɴᴄᴇ: {} +""" + +buttons = [ + [ + InlineKeyboardButton( + text="➕ ᴀᴅᴅ ᴍᴇ ʙᴀʙʏ ➕ ", url=f"t.me/{BOT_USERNAME}?startgroup=true" + ) + ], + [ + InlineKeyboardButton(text="ᴄᴏᴍᴍᴀɴᴅs ⚡", callback_data="help_back"), + InlineKeyboardButton(text="ᴀʙᴏᴜᴛ ✨", callback_data="about_"), + ], +] + + +HELP_STRINGS = """ +━━━━━━━ᴇxᴏɴ━━━━━━━ +ᴄʟɪᴄᴋ ᴏɴ ᴛʜᴇ ʙᴜᴛᴛᴏɴ ʙᴇʟʟᴏᴡ ᴛᴏ +ɢᴇᴛ ᴜꜱᴀɢᴇ ᴏꜰ ꜱᴘᴇᴄɪꜰɪᴄꜱ ᴄᴏᴍᴍᴀɴᴅ +━━━━━━━━━━━━━━━━━ +""" + +DONATE_STRING = """ᴊᴜsᴛ sᴜᴘᴘᴏʀᴛ ᴜs, ᴡᴇ ᴡɪʟʟ ʙᴇ ᴍᴏʀᴇ ᴛʜᴀɴ ʜᴀᴘᴘʏ""" + + +IMPORTED = {} +MIGRATEABLE = [] +HELPABLE = {} +STATS = [] +USER_INFO = [] +DATA_IMPORT = [] +DATA_EXPORT = [] +CHAT_SETTINGS = {} +USER_SETTINGS = {} + +for module_name in ALL_MODULES: + imported_module = importlib.import_module(f"Exon.modules.{module_name}") + if not hasattr(imported_module, "__mod_name__"): + imported_module.__mod_name__ = imported_module.__name__ + + if imported_module.__mod_name__.lower() not in IMPORTED: + IMPORTED[imported_module.__mod_name__.lower()] = imported_module + else: + raise Exception("ᴄᴀɴ'ᴛ ʜᴀᴠᴇ ᴛᴡᴏ ᴍᴏᴅᴜʟᴇs ᴡɪᴛʜ ᴛʜᴇ sᴀᴍᴇ ɴᴀᴍᴇ! ᴘʟᴇᴀsᴇ ᴄʜᴀɴɢᴇ ᴏɴᴇ") + + if hasattr(imported_module, "__help__") and imported_module.__help__: + HELPABLE[imported_module.__mod_name__.lower()] = imported_module + + # Chats to migrate on chat_migrated events + if hasattr(imported_module, "__migrate__"): + MIGRATEABLE.append(imported_module) + + if hasattr(imported_module, "__stats__"): + STATS.append(imported_module) + + if hasattr(imported_module, "__user_info__"): + USER_INFO.append(imported_module) + + if hasattr(imported_module, "__import_data__"): + DATA_IMPORT.append(imported_module) + + if hasattr(imported_module, "__export_data__"): + DATA_EXPORT.append(imported_module) + + if hasattr(imported_module, "__chat_settings__"): + CHAT_SETTINGS[imported_module.__mod_name__.lower()] = imported_module + + if hasattr(imported_module, "__user_settings__"): + USER_SETTINGS[imported_module.__mod_name__.lower()] = imported_module + + +# do not async +def send_help(chat_id, text, keyboard=None): + if not keyboard: + keyboard = InlineKeyboardMarkup(paginate_modules(0, HELPABLE, "help")) + dispatcher.bot.send_message( + chat_id=chat_id, + text=text, + parse_mode=ParseMode.MARKDOWN, + disable_web_page_preview=True, + reply_markup=keyboard, + ) + + +def test(update: Update, context: CallbackContext): + # pprint(eval(str(update))) + # update.effective_message.reply_text("Hola tester! _I_ *have* `markdown`", parse_mode=ParseMode.MARKDOWN) + update.effective_message.reply_text("ᴛʜɪs ᴘᴇʀsᴏɴ ᴇᴅɪᴛᴇᴅ ᴀ ᴍᴇssᴀɢᴇ") + print(update.effective_message) + + +def start(update: Update, context: CallbackContext): + args = context.args + usr = update.effective_user + uptime = get_readable_time((time.time() - StartTime)) + if update.effective_chat.type == "private": + if len(args) >= 1: + if args[0].lower() == "help": + send_help(update.effective_chat.id, HELP_STRINGS) + elif args[0].lower().startswith("ghelp_"): + mod = args[0].lower().split("_", 1)[1] + if mod == "Admins": + mod = "Admins" + if not HELPABLE.get(mod, False): + return + send_help( + update.effective_chat.id, + HELPABLE[mod].__help__, + InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + text="• ʙᴀᴄᴋ •", callback_data="help_back" + ) + ] + ] + ), + ) + + elif args[0].lower().startswith("stngs_"): + match = re.match("stngs_(.*)", args[0].lower()) + chat = dispatcher.bot.getChat(match[1]) + + if is_user_admin(chat, update.effective_user.id): + send_settings(match[1], update.effective_user.id, False) + else: + send_settings(match[1], update.effective_user.id, True) + + elif args[0][1:].isdigit() and "rules" in IMPORTED: + IMPORTED["rules"].send_rules(update, args[0], from_pm=True) + + else: + first_name = update.effective_user.first_name + lol = update.effective_message.reply_text( + PM_START_TEX.format(usr.first_name), parse_mode=ParseMode.MARKDOWN + ) + time.sleep(0.4) + lol.edit_text("🎊") + time.sleep(0.5) + lol.edit_text("⚡") + time.sleep(0.3) + lol.edit_text("ꜱᴛᴀʀᴛɪɴɢ... ") + time.sleep(0.4) + lol.delete() + update.effective_message.reply_sticker( + "CAACAgUAAx0CUgguZAABARdrYwt_f9vFYZop5n-EGGa80vLar9AAAjsIAAKagolX-O0V64tvzK8pBA" + ) + update.effective_message.reply_text( + PM_START_TEXT.format( + escape_markdown(first_name), + escape_markdown(uptime), + sql.num_users(), + sql.num_chats(), + ), + reply_markup=InlineKeyboardMarkup(buttons), + parse_mode=ParseMode.MARKDOWN, + timeout=60, + disable_web_page_preview=False, + ) + else: + update.effective_message.reply_photo( + START_IMG, + caption="ʜᴇʏ `{}`,\n\nɪ ᴀᴍ ᴀʟɪᴠᴇ ʙᴀʙʏ !\n➥ᴜᴘᴛɪᴍᴇ: `{}` \n➥ᴜsᴇʀs: `{}` \n➥ᴄʜᴀᴛs: `{}` ".format( + usr.first_name, + uptime, + sql.num_users(), + sql.num_chats(), + ), + parse_mode=ParseMode.MARKDOWN, + reply_markup=InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + text="ꜱᴜᴘᴘᴏʀᴛ", + url=f"https://t.me/{SUPPORT_CHAT}", + ), + InlineKeyboardButton( + text="ᴜᴘᴅᴀᴛᴇꜱ", + url=f"https://t.me/{UPDATES_CHANNEL}", + ), + ], + [ + InlineKeyboardButton( + text="ᴏᴡɴᴇʀ", + url=f"https://t.me/{OWNER_USERNAME}", + ), + InlineKeyboardButton( + text="ᴄʟᴏsᴇ", + callback_data="close_h", + ), + ], + ] + ), + ) + + +# BSDK KY DEK RA H © + + +def error_handler(update, context): + """Log the error and send a telegram message to notify the developer.""" + # Log the error before we do anything else, so we can see it even if something breaks. + LOGGER.error(msg="Exception while handling an update:", exc_info=context.error) + + # traceback.format_exception returns the usual python message about an exception, but as a + # list of strings rather than a single string, so we have to join them together. + tb_list = traceback.format_exception( + None, context.error, context.error.__traceback__ + ) + tb = "".join(tb_list) + + # Build the message with some markup and additional information about what happened. + message = ( + "An exception was raised while handling an update\n" + "
update = {}
\n\n" + "
{}
" + ).format( + html.escape(json.dumps(update.to_dict(), indent=2, ensure_ascii=False)), + html.escape(tb), + ) + + if len(message) >= 4096: + message = message[:4096] + # Finally, send the message + context.bot.send_message(chat_id=OWNER_ID, text=message, parse_mode=ParseMode.HTML) + + +def error_callback(update: Update, context: CallbackContext): + error = context.error + try: + raise error + except Unauthorized: + print("no nono1") + print(error) + # remove update.message.chat_id from conversation list + except BadRequest: + print("no nono2") + print("BadRequest caught") + print(error) + + # handle malformed requests - read more below! + except TimedOut: + print("no nono3") + # handle slow connection problems + except NetworkError: + print("no nono4") + # handle other connection problems + except ChatMigrated as err: + print("no nono5") + print(err) + # the chat_id of a group has changed, use e.new_chat_id instead + except TelegramError: + print(error) + # handle all other telegram related errors + + +def help_button(update, context): + query = update.callback_query + mod_match = re.match(r"help_module\((.+?)\)", query.data) + prev_match = re.match(r"help_prev\((.+?)\)", query.data) + next_match = re.match(r"help_next\((.+?)\)", query.data) + back_match = re.match(r"help_back", query.data) + + print(query.message.chat.id) + + try: + if mod_match: + module = mod_match[1] + text = ( + f"「 *{HELPABLE[module].__mod_name__}* module: 」\n" + + HELPABLE[module].__help__ + ) + + query.message.edit_text( + text=text, + parse_mode=ParseMode.MARKDOWN, + disable_web_page_preview=True, + reply_markup=InlineKeyboardMarkup( + [[InlineKeyboardButton(text="ʙᴀᴄᴋ", callback_data="help_back")]] + ), + ) + + elif prev_match: + curr_page = int(prev_match[1]) + query.message.edit_text( + text=HELP_STRINGS, + parse_mode=ParseMode.MARKDOWN, + reply_markup=InlineKeyboardMarkup( + paginate_modules(curr_page - 1, HELPABLE, "help") + ), + ) + + elif next_match: + next_page = int(next_match[1]) + query.message.edit_text( + text=HELP_STRINGS, + parse_mode=ParseMode.MARKDOWN, + reply_markup=InlineKeyboardMarkup( + paginate_modules(next_page + 1, HELPABLE, "help") + ), + ) + + elif back_match: + query.message.edit_text( + text=HELP_STRINGS, + parse_mode=ParseMode.MARKDOWN, + reply_markup=InlineKeyboardMarkup( + paginate_modules(0, HELPABLE, "help") + ), + ) + + # ensure no spinny white circle + context.bot.answer_callback_query(query.id) + # query.message.delete() + + except BadRequest: + pass + + +def Exon_callback_data(update, context): + query = update.callback_query + uptime = get_readable_time((time.time() - StartTime)) + if query.data == "Exon_": + query.message.edit_text( + text="""CallBackQueriesData Here""", + parse_mode=ParseMode.MARKDOWN, + disable_web_page_preview=True, + reply_markup=InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton(text="⬅️", callback_data="Exon_prev"), + InlineKeyboardButton(text="ʙᴀᴄᴋ", callback_data="Exon_back"), + InlineKeyboardButton(text="➡️", callback_data="Exon_next"), + ] + ] + ), + ) + elif query.data == "Exon_back": + first_name = update.effective_user.first_name + uptime = get_readable_time((time.time() - StartTime)) + query.message.edit_text( + PM_START_TEXT.format( + escape_markdown(first_name), + escape_markdown(uptime), + sql.num_users(), + sql.num_chats(), + ), + reply_markup=InlineKeyboardMarkup(buttons), + parse_mode=ParseMode.MARKDOWN, + timeout=60, + disable_web_page_preview=False, + ) + + +@typing_action +def get_help(update, context): + chat = update.effective_chat # type: Optional[Chat] + args = update.effective_message.text.split(None, 1) + + # ONLY send help in PM + if chat.type != chat.PRIVATE: + if len(args) >= 2 and any(args[1].lower() == x for x in HELPABLE): + module = args[1].lower() + update.effective_message.reply_text( + f"ᴄᴏɴᴛᴀᴄᴛ ᴍᴇ ɪɴ ᴘᴍ ᴛᴏ ɢᴇᴛ ʜᴇʟᴘ ᴏғ {module.capitalize()}", + reply_markup=InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + text="• ʜᴇʟᴘ •​", + url="t.me/{}?start=ghelp_{}".format( + context.bot.username, module + ), + ) + ] + ] + ), + ) + return + update.effective_message.reply_text( + "» ᴄʜᴏᴏsᴇ ᴀɴ ᴏᴩᴛɪᴏɴ ғᴏʀ ɢᴇᴛᴛɪɴɢ ʜᴇʟᴩ.", + reply_markup=InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + text="• ᴏᴩᴇɴ ɪɴ ᴩʀɪᴠᴀᴛᴇ •", + url="https://t.me/{}?start=help".format( + context.bot.username + ), + ) + ], + [ + InlineKeyboardButton( + text="• ᴏᴩᴇɴ ʜᴇʀᴇ •", + callback_data="help_back", + ) + ], + ] + ), + ) + return + + if len(args) >= 2 and any(args[1].lower() == x for x in HELPABLE): + module = args[1].lower() + text = f" 〔 *{HELPABLE[module].__mod_name__}* 〕\n" + HELPABLE[module].__help__ + + send_help( + chat.id, + text, + InlineKeyboardMarkup( + [[InlineKeyboardButton(text="ʙᴀᴄᴋ", callback_data="help_back")]] + ), + ) + + else: + send_help(chat.id, HELP_STRINGS) + + +def send_settings(chat_id, user_id, user=False): + if user: + if USER_SETTINGS: + settings = "\n\n".join( + f"*{mod.__mod_name__}*:\n{mod.__user_settings__(user_id)}" + for mod in USER_SETTINGS.values() + ) + + dispatcher.bot.send_message( + user_id, + "ᴛʜᴇsᴇ ᴀʀᴇ ʏᴏᴜʀ ᴄᴜʀʀᴇɴᴛ sᴇᴛᴛɪɴɢs:" + "\n\n" + settings, + parse_mode=ParseMode.MARKDOWN, + ) + + else: + dispatcher.bot.send_message( + user_id, + "sᴇᴇᴍs ʟɪᴋᴇ ᴛʜᴇʀᴇ ᴀʀᴇɴ'ᴛ ᴀɴʏ ᴜsᴇʀ sᴘᴇᴄɪғɪᴄ sᴇᴛᴛɪɴɢs ᴀᴠᴀɪʟᴀʙʟᴇ :'(", + parse_mode=ParseMode.MARKDOWN, + ) + + elif CHAT_SETTINGS: + chat_name = dispatcher.bot.getChat(chat_id).title + dispatcher.bot.send_message( + user_id, + text=f"ᴡʜɪᴄʜ ᴍᴏᴅᴜʟᴇ ᴡᴏᴜʟᴅ ʏᴏᴜ ʟɪᴋᴇ ᴛᴏ ᴄʜᴇᴄᴋ {chat_name}'s sᴇᴛᴛɪɴɢs ғᴏʀ?", + reply_markup=InlineKeyboardMarkup( + paginate_modules(0, CHAT_SETTINGS, "stngs", chat=chat_id) + ), + ) + + else: + dispatcher.bot.send_message( + user_id, + "sᴇᴇᴍs ʟɪᴋᴇ ᴛʜᴇʀᴇ ᴀʀᴇɴ'ᴛ any chat settings available :'(\nsᴇɴᴅ ᴛʜɪs " + "in ᴀ ɢʀᴏᴜᴘ chat ʏᴏᴜ'ʀᴇ ᴀᴅᴍɪɴ ɪɴ ᴛᴏ ғɪɴᴅ ɪᴛs ᴄᴜʀʀᴇɴᴛ sᴇᴛᴛɪɴɢs!", + parse_mode=ParseMode.MARKDOWN, + ) + + +def settings_button(update: Update, context: CallbackContext): + query = update.callback_query + user = update.effective_user + bot = context.bot + mod_match = re.match(r"stngs_module\((.+?),(.+?)\)", query.data) + prev_match = re.match(r"stngs_prev\((.+?),(.+?)\)", query.data) + next_match = re.match(r"stngs_next\((.+?),(.+?)\)", query.data) + back_match = re.match(r"stngs_back\((.+?)\)", query.data) + try: + if mod_match: + chat_id = mod_match[1] + module = mod_match[2] + chat = bot.get_chat(chat_id) + text = f"*{escape_markdown(chat.title)}* ʜᴀs ᴛʜᴇ ғᴏʟʟᴏᴡɪɴɢ sᴇᴛᴛɪɴɢs ғᴏʀ ᴛʜᴇ *{CHAT_SETTINGS[module].__mod_name__}* ᴍᴏᴅᴜʟᴇ:\n\n" + CHAT_SETTINGS[ + module + ].__chat_settings__( + chat_id, user.id + ) + + query.message.reply_text( + text=text, + parse_mode=ParseMode.MARKDOWN, + reply_markup=InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + text="• ʙᴀᴄᴋ •", + callback_data=f"stngs_back({chat_id})", + ) + ] + ] + ), + ) + + elif prev_match: + chat_id = prev_match[1] + curr_page = int(prev_match[2]) + chat = bot.get_chat(chat_id) + query.message.reply_text( + f"ʜɪ ᴛʜᴇʀᴇ! ᴛʜᴇʀᴇ ᴀʀᴇ ǫᴜɪᴛᴇ ᴀ ғᴇᴡ sᴇᴛᴛɪɴɢs ғᴏʀ {chat.title} - ɢᴏ ᴀʜᴇᴀᴅ ᴀɴᴅ ᴘɪᴄᴋ ᴡʜᴀᴛ ʏᴏᴜ'ʀᴇ ɪɴᴛᴇʀᴇsᴛᴇᴅ in.", + reply_markup=InlineKeyboardMarkup( + paginate_modules( + curr_page - 1, CHAT_SETTINGS, "stngs", chat=chat_id + ) + ), + ) + + elif next_match: + chat_id = next_match[1] + next_page = int(next_match[2]) + chat = bot.get_chat(chat_id) + query.message.reply_text( + f"ʜɪ ᴛʜᴇʀᴇ! ᴛʜᴇʀᴇ ᴀʀᴇ ǫᴜɪᴛᴇ ᴀ ғᴇᴡ sᴇᴛᴛɪɴɢs ғᴏʀ {chat.title} - ɢᴏ ᴀʜᴇᴀᴅ ᴀɴᴅ ᴘɪᴄᴋ ᴡʜᴀᴛ ʏᴏᴜ'ʀᴇ ɪɴᴛᴇʀᴇsᴛᴇᴅ in.", + reply_markup=InlineKeyboardMarkup( + paginate_modules( + next_page + 1, CHAT_SETTINGS, "stngs", chat=chat_id + ) + ), + ) + + elif back_match: + chat_id = back_match[1] + chat = bot.get_chat(chat_id) + query.message.reply_text( + text=f"ʜɪ ᴛʜᴇʀᴇ! ᴛʜᴇʀᴇ ᴀʀᴇ ǫᴜɪᴛᴇ ᴀ ғᴇᴡ sᴇᴛᴛɪɴɢs ғᴏʀ {escape_markdown(chat.title)} - ɢᴏ ᴀʜᴇᴀᴅ ᴀɴᴅ ᴘɪᴄᴋ ᴡʜᴀᴛ ʏᴏᴜ'ʀᴇ ɪɴᴛᴇʀᴇsᴛᴇᴅ ɪɴ.", + parse_mode=ParseMode.MARKDOWN, + reply_markup=InlineKeyboardMarkup( + paginate_modules(0, CHAT_SETTINGS, "stngs", chat=chat_id) + ), + ) + + # ensure no spinny white circle + bot.answer_callback_query(query.id) + query.message.delete() + except BadRequest as excp: + if excp.message not in [ + "ᴍᴇssᴀɢᴇ ɪs ɴᴏᴛ ᴍᴏᴅɪғɪᴇᴅ", + "Query_id_invalid", + "ᴍᴇssᴀɢᴇ ᴄᴀɴ'ᴛ ʙᴇ ᴅᴇʟᴇᴛᴇᴅ", + ]: + LOGGER.exception("ᴇxᴄᴇᴘᴛɪᴏɴ ɪɴ sᴇᴛᴛɪɴɢs ʙᴜᴛᴛᴏɴs. %s", str(query.data)) + + +def get_settings(update: Update, context: CallbackContext): + chat = update.effective_chat # type: Optional[Chat] + user = update.effective_user # type: Optional[User] + msg = update.effective_message # type: Optional[Message] + + # ONLY send settings in PM + if chat.type == chat.PRIVATE: + send_settings(chat.id, user.id, True) + + elif is_user_admin(chat, user.id): + text = "ᴄʟɪᴄᴋ ʜᴇʀᴇ ᴛᴏ ɢᴇᴛ ᴛʜɪs ᴄʜᴀᴛ sᴇᴛᴛɪɴɢs, as ᴡᴇʟʟ ᴀs ʏᴏᴜʀs." + msg.reply_text( + text, + reply_markup=InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + text="sᴇᴛᴛɪɴɢs", + url=f"t.me/{context.bot.username}?start=stngs_{chat.id}", + ) + ] + ] + ), + ) + + else: + text = "ᴄʟɪᴄᴋ ʜᴇʀᴇ ᴛᴏ ᴄʜᴇᴄᴋ ʏᴏᴜʀ sᴇᴛᴛɪɴɢs." + + +def donate(update: Update, context: CallbackContext): + user = update.effective_message.from_user + chat = update.effective_chat # type: Optional[Chat] + bot = context.bot + if chat.type == "private": + update.effective_message.reply_text( + DONATE_STRING, parse_mode=ParseMode.MARKDOWN, disable_web_page_preview=True + ) + + if OWNER_ID != 1452219013 and DONATION_LINK: + update.effective_message.reply_text( + f"ʏᴏᴜ ᴄᴀɴ ᴀʟsᴏ ᴅᴏɴᴀᴛᴇ ᴛᴏ the ᴘᴇʀsᴏɴ ᴄᴜʀʀᴇɴᴛʟʏ ʀᴜɴɴɪɴɢ ᴍᴇ [ʜᴇʀᴇ]({DONATION_LINK})", + parse_mode=ParseMode.MARKDOWN, + ) + + else: + try: + bot.send_message( + user.id, + DONATE_STRING, + parse_mode=ParseMode.MARKDOWN, + disable_web_page_preview=True, + ) + + update.effective_message.reply_text( + "I've PM'ed ʏᴏᴜ ᴀʙᴏᴜᴛ ᴅᴏɴᴀᴛɪɴɢ ᴛᴏ ᴍʏ ᴄʀᴇᴀᴛᴏʀ!" + ) + except Unauthorized: + update.effective_message.reply_text( + "ᴄᴏɴᴛᴀᴄᴛ ᴍᴇ ɪɴ PM ғɪʀsᴛ to ɢᴇᴛ ᴅᴏɴᴀᴛɪᴏɴ information." + ) + + +def migrate_chats(update: Update, context: CallbackContext): + msg = update.effective_message # type: Optional[Message] + if msg.migrate_to_chat_id: + old_chat = update.effective_chat.id + new_chat = msg.migrate_to_chat_id + elif msg.migrate_from_chat_id: + old_chat = msg.migrate_from_chat_id + new_chat = update.effective_chat.id + else: + return + + LOGGER.info("ᴍɪɢʀᴀᴛɪɴɢ ғʀᴏᴍ %s, to %s", str(old_chat), str(new_chat)) + for mod in MIGRATEABLE: + mod.__migrate__(old_chat, new_chat) + + LOGGER.info("sᴜᴄᴄᴇssғᴜʟʟʏ ᴍɪɢʀᴀᴛᴇᴅ!") + raise DispatcherHandlerStop + + +def main(): + + if SUPPORT_CHAT is not None and isinstance(SUPPORT_CHAT, str): + try: + dispatcher.bot.sendAnimation( + f"@{SUPPORT_CHAT}", + animation="https://telegra.ph/file/8dea393ddf4fc2e339179.gif", + caption=f""" +ㅤ🥀 {dispatcher.bot.first_name} ɪs ᴀʟɪᴠᴇ ʙᴀʙʏ ..... + +━━━━━━━━━━━━━ +⍟ **ᴍʏ ᴏᴡɴᴇʀ :** [𝐀ʙɪꜱʜɴᴏɪ](https://t.me/{OWNER_USERNAME}) +⍟ **ʟɪʙʀᴀʀʏ ᴠᴇʀsɪᴏɴ :** `{lver}` +⍟ **ᴛᴇʟᴇᴛʜᴏɴ ᴠᴇʀsɪᴏɴ :** `{tver}` +⍟ **ᴘʏʀᴏɢʀᴀᴍ ᴠᴇʀsɪᴏɴ :** `{pver}` +⍟ **ᴘʏᴛʜᴏɴ ᴠᴇʀsɪᴏɴ :** `{version_info[0]}.{version_info[1]}.{version_info[2]}` +⍟ **ʙᴏᴛ ᴠᴇʀsɪᴏɴ :** `1.0` +━━━━━━━━━━━━━ +""", + parse_mode=ParseMode.MARKDOWN, + ) + except Unauthorized: + LOGGER.warning( + "ʙᴏᴛ ɪsɴᴛ ᴀʙʟᴇ ᴛᴏ sᴇɴᴅ ᴍᴇssᴀɢᴇ ᴛᴏ support_chat, ɢᴏ ᴀɴᴅ ᴄʜᴇᴄᴋ !" + ) + except BadRequest as e: + LOGGER.warning(e.message) + + start_handler = DisableAbleCommandHandler("start", start, run_async=True) + + help_handler = DisableAbleCommandHandler("help", get_help, run_async=True) + help_callback_handler = CallbackQueryHandler( + help_button, pattern=r"help_.*", run_async=True + ) + + settings_handler = DisableAbleCommandHandler("settings", get_settings) + settings_callback_handler = CallbackQueryHandler( + settings_button, pattern=r"stngs_", run_async=True + ) + + data_callback_handler = CallbackQueryHandler( + Exon_callback_data, pattern=r"Exon_", run_async=True + ) + donate_handler = DisableAbleCommandHandler("donate", donate, run_async=True) + migrate_handler = MessageHandler( + Filters.status_update.migrate, migrate_chats, run_async=True + ) + + # dispatcher.add_handler(test_handler) + dispatcher.add_handler(start_handler) + dispatcher.add_handler(help_handler) + dispatcher.add_handler(data_callback_handler) + dispatcher.add_handler(settings_handler) + dispatcher.add_handler(help_callback_handler) + dispatcher.add_handler(settings_callback_handler) + dispatcher.add_handler(migrate_handler) + dispatcher.add_handler(donate_handler) + + dispatcher.add_error_handler(error_callback) + + if WEBHOOK: + LOGGER.info("Using webhooks.") + updater.start_webhook(listen="0.0.0.0", port=PORT, url_path=TOKEN) + + if CERT_PATH: + updater.bot.set_webhook(url=URL + TOKEN, certificate=open(CERT_PATH, "rb")) + else: + updater.bot.set_webhook(url=URL + TOKEN) + + else: + LOGGER.info("ᴜsɪɴɢ ʟᴏɴɢ ᴘᴏʟʟɪɴɢ.") + updater.start_polling(timeout=15, read_latency=4, clean=True) + + if len(argv) not in (1, 3, 4): + telethn.disconnect() + else: + telethn.run_until_disconnected() + + updater.idle() + + +if __name__ == "__main__": + LOGGER.info( + f"sᴜᴄᴄᴇssғᴜʟʟʏ ʟᴏᴀᴅᴇᴅ ᴍᴏᴅᴜʟᴇS Any issu JOIN @AbishnoiMF : {str(ALL_MODULES)}" + ) + telethn.start(bot_token=TOKEN) + pgram.start() + main() + # idle() diff --git a/Exon/config.py b/Exon/config.py new file mode 100644 index 00000000..60c11ae9 --- /dev/null +++ b/Exon/config.py @@ -0,0 +1,127 @@ +""" +MIT License + +Copyright (c) 2022 ABISHNOI + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" +# ""DEAR PRO PEOPLE, DON'T REMOVE & CHANGE THIS LINE +# TG :- @Abishnoi +# MY ALL BOTS :- Abishnoi_bots +# GITHUB :- KingAbishnoi "" + +import json +import os + + +def get_user_list(config, key): + with open("{}/Exon/{}".format(os.getcwd(), config), "r") as json_file: + return json.load(json_file)[key] + + +class Config(object): + LOGGER = True + + API_ID = "983922" + API_HASH = "SMSMSMSAKAMAKA" + APP_ID = "953922" # 2nd API_ID + APP_HASH = "funssnAjsjaSJns82AjajU" # 2ns API_HASH + ARQ_API_KEY = "TENRCY-KDKSK-MSMSM-OXQYYO-ARQ" + BOT_ID = "5408158735" + TOKEN = "5458182410:KINGABISHNOI-UM" + OWNER_ID = "1452219013" + OPENWEATHERMAP_ID = "22322" + OWNER_USERNAME = "Abishnoi1M" + BOT_USERNAME = "Exon_Robot" + SUPPORT_CHAT = "AbishnoiMF" + UPDATES_CHANNEL = "Abishnoi_bots" + SUPPORT_CHANNEL = "Abishnoi_bots" + JOIN_LOGGER = "-1001497222182" + EVENT_LOGS = "-1001497222182" + ERROR_LOGS = "-1001497222182" + + SQLALCHEMY_DATABASE_URI = "" # sql + DATABASH_URL = "" # sql + DB_URL = "" + MONGO_DB_URL = "" # needed for any database modules + MONGO_URL = "" + DB_URL2 = "" # YOUR MONGO_DB_URI + ARQ_API_URL = "https://arq.hamker.in" + BOT_API_URL = "https://api.telegram.org/bot" + LOAD = [] + NO_LOAD = ["rss", "cleaner", "connection", "math"] + WEBHOOK = False + INFOPIC = True + URL = None + SPAMWATCH_API = "" + SPAMWATCH_SUPPORT_CHAT = "@AbishnoiMF" + + REDIS_URL = "" + + DRAGONS = get_user_list("elevated_users.json", "sudos") + DEV_USERS = get_user_list("elevated_users.json", "devs") + REQUESTER = get_user_list("elevated_users.json", "whitelists") + DEMONS = get_user_list("elevated_users.json", "supports") + INSPECTOR = get_user_list("elevated_users.json", "sudos") + TIGERS = get_user_list("elevated_users.json", "tigers") + WOLVES = get_user_list("elevated_users.json", "whitelists") + + DONATION_LINK = "https://t.me/Abishnoi1M" + CERT_PATH = None + STRICT_GBAN = "True" + PORT = "" + DEL_CMDS = True + STRICT_GBAN = True + WORKERS = 8 + BAN_STICKER = "" + ALLOW_EXCL = True + CASH_API_KEY = "NAI H BRO" + TIME_API_KEY = "ABISHNOI" + WALL_API = "F-OFF" + AI_API_KEY = "LOVEYOU" + BL_CHATS = [] + SPAMMERS = None + SPAMWATCH_API = "" + ALLOW_CHATS = None + TEMP_DOWNLOAD_DIRECTORY = "./" + HEROKU_APP_NAME = "" + HEROKU_API_KEY = "" + REM_BG_API_KEY = "LSdLgCceYz8vNqFgJVzrkDgR" + LASTFM_API_KEY = "FMLODA" + CF_API_KEY = "KISS" + BL_CHATS = [] + MONGO_PORT = "27017" + MONGO_DB = "EXON" + PHOTO = "https://telegra.ph/file/14d1f98500af1132e5460.jpg" + HELP_IMG = "https://telegra.ph/file/14d1f98500af1132e5460.jpg" + START_IMG = "https://telegra.ph/file/14d1f98500af1132e5460.jpg" + TIME_API_KEY = "5LB4TAKPEKZ0" + INFOPIC = False + GENIUS_API_TOKEN = "28jwoKAkskaSjsnsksAjnwjUJwj" + + +class Production(Config): + LOGGER = True + + +class Development(Config): + LOGGER = True + + +# ENJOY diff --git a/Exon/confing.py b/Exon/confing.py new file mode 100644 index 00000000..33bc567a --- /dev/null +++ b/Exon/confing.py @@ -0,0 +1,37 @@ +from envparse import env + +from Exon import LOGGER + +DEFAULTS = { + "LOAD_MODULES": True, +} + + +def get_str_key(name, required=False): + if name in DEFAULTS: + default = DEFAULTS[name] + else: + default = None + if not (data := env.str(name, default=default)) and not required: + LOGGER.warn("No str key: " + name) + return None + elif not data: + LOGGER.critical("No str key: " + name) + sys.exit(2) + else: + return data + + +def get_int_key(name, required=False): + if name in DEFAULTS: + default = DEFAULTS[name] + else: + default = None + if not (data := env.int(name, default=default)) and not required: + LOGGER.warn("No int key: " + name) + return None + elif not data: + LOGGER.critical("No int key: " + name) + sys.exit(2) + else: + return data diff --git "a/Exon/core/ABISHNOI COPYRIGHT \302\251" "b/Exon/core/ABISHNOI COPYRIGHT \302\251" new file mode 100644 index 00000000..a7ec3b8e --- /dev/null +++ "b/Exon/core/ABISHNOI COPYRIGHT \302\251" @@ -0,0 +1,27 @@ +""" +MIT License + +Copyright (c) 2022 ABISHNOI + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" +# ""DEAR PRO PEOPLE, DON'T REMOVE & CHANGE THIS LINE +# TG :- @Abishnoi +# MY ALL BOTS :- Abishnoi_bots +# GITHUB :- KingAbishnoi "" \ No newline at end of file diff --git "a/Exon/core/decorators/ABISHNOI COPYRIGHT \302\251" "b/Exon/core/decorators/ABISHNOI COPYRIGHT \302\251" new file mode 100644 index 00000000..a7ec3b8e --- /dev/null +++ "b/Exon/core/decorators/ABISHNOI COPYRIGHT \302\251" @@ -0,0 +1,27 @@ +""" +MIT License + +Copyright (c) 2022 ABISHNOI + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" +# ""DEAR PRO PEOPLE, DON'T REMOVE & CHANGE THIS LINE +# TG :- @Abishnoi +# MY ALL BOTS :- Abishnoi_bots +# GITHUB :- KingAbishnoi "" \ No newline at end of file diff --git a/Exon/core/decorators/errors.py b/Exon/core/decorators/errors.py new file mode 100644 index 00000000..d54ccdf6 --- /dev/null +++ b/Exon/core/decorators/errors.py @@ -0,0 +1,80 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import sys +import traceback +from functools import wraps + +from pyrogram.errors.exceptions.forbidden_403 import ChatWriteForbidden + +from Exon import ERROR_LOGS, pgram + + +def split_limits(text): + if len(text) < 2048: + return [text] + + lines = text.splitlines(True) + small_msg = "" + result = [] + for line in lines: + if len(small_msg) + len(line) < 2048: + small_msg += line + else: + result.append(small_msg) + small_msg = line + result.append(small_msg) + + return result + + +def capture_err(func): + @wraps(func) + async def capture(client, message, *args, **kwargs): + try: + return await func(client, message, *args, **kwargs) + except ChatWriteForbidden: + await pgram.leave_chat(message.chat.id) + return + except Exception as err: + exc_type, exc_obj, exc_tb = sys.exc_info() + errors = traceback.format_exception( + etype=exc_type, + value=exc_obj, + tb=exc_tb, + ) + error_feedback = split_limits( + "**ERROR** | `{}` | `{}`\n\n```{}```\n\n```{}```\n".format( + message.from_user.id if message.from_user else 0, + message.chat.id if message.chat else 0, + message.text or message.caption, + "".join(errors), + ) + ) + + for x in error_feedback: + await pgram.send_message(ERROR_LOGS, x) + raise err + + return capture diff --git a/Exon/core/decorators/permissions.py b/Exon/core/decorators/permissions.py new file mode 100644 index 00000000..337b731e --- /dev/null +++ b/Exon/core/decorators/permissions.py @@ -0,0 +1,90 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +from functools import wraps +from traceback import format_exc as err + +from pyrogram.errors.exceptions.forbidden_403 import ChatWriteForbidden +from pyrogram.types import Message + +from Exon import DEV_USERS, pgram +from Exon.modules.adminserv import member_permissions + + +async def authorised(func, subFunc2, client, message, *args, **kwargs): + chatID = message.chat.id + try: + await func(client, message, *args, **kwargs) + except ChatWriteForbidden: + await pgram.leave_chat(chatID) + except Exception as e: + try: + await message.reply_text(str(e.MESSAGE)) + except AttributeError: + await message.reply_text(str(e)) + e = err() + print(e) + return subFunc2 + + +async def unauthorised(message: Message, permission, subFunc2): + chatID = message.chat.id + text = ( + "ʏᴏᴜ ᴅᴏɴ'ᴛ ʜᴀᴠᴇ ᴛʜᴇ ʀᴇǫᴜɪʀᴇᴅ ᴘᴇʀᴍɪssɪᴏɴ ᴛᴏ ᴘᴇʀғᴏʀᴍ ᴛʜɪs ᴀᴄᴛɪᴏɴ." + + f"\n**ᴘᴇʀᴍɪssɪᴏɴ:** __{permission}__" + ) + try: + await message.reply_text(text) + except ChatWriteForbidden: + await pgram.leave_chat(chatID) + return subFunc2 + + +def adminsOnly(permission): + def subFunc(func): + @wraps(func) + async def subFunc2(client, message: Message, *args, **kwargs): + chatID = message.chat.id + if not message.from_user: + # For anonymous admins + if message.sender_chat and message.sender_chat.id == message.chat.id: + return await authorised( + func, + subFunc2, + client, + message, + *args, + **kwargs, + ) + return await unauthorised(message, permission, subFunc2) + # For admins and sudo users + userID = message.from_user.id + permissions = await member_permissions(chatID, userID) + if userID not in DEV_USERS and permission not in permissions: + return await unauthorised(message, permission, subFunc2) + return await authorised(func, subFunc2, client, message, *args, **kwargs) + + return subFunc2 + + return subFunc diff --git a/Exon/core/sections.py b/Exon/core/sections.py new file mode 100644 index 00000000..216374d3 --- /dev/null +++ b/Exon/core/sections.py @@ -0,0 +1,48 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +n = "\n" +w = " " + +bold = lambda x: f"**{x}** " +bold_ul = lambda x: f"**--{x}**-- " + +mono = lambda x: f"`{x}`{n}" + + +def section( + title: str, + body: dict, + indent: int = 2, + underline: bool = False, +) -> str: + text = (bold_ul(title) + n) if underline else bold(title) + n + + for key, value in body.items(): + text += ( + indent * w + + bold(key) + + ((value[0] + n) if isinstance(value, list) else mono(value)) + ) + return text diff --git a/Exon/elevated_users.json b/Exon/elevated_users.json new file mode 100644 index 00000000..fbd7a02e --- /dev/null +++ b/Exon/elevated_users.json @@ -0,0 +1,11 @@ +{ + "devs": [1452219013], + "sudos": [1452219013], + "supports": [1452219013], + "whitelists": [1452219013], + "dragons": [1452219013], + "tigers": [1452219013], + "spammers": [1452219013] +} + + diff --git a/Exon/events.py b/Exon/events.py new file mode 100644 index 00000000..ae9eea01 --- /dev/null +++ b/Exon/events.py @@ -0,0 +1,94 @@ +""" +MIT License + +Copyright (c) 2022 ABISHNOI + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +# ""DEAR PRO PEOPLE, DON'T REMOVE & CHANGE THIS LINE +# TG :- @Abishnoi +# MY ALL BOTS :- Abishnoi_bots +# GITHUB :- KingAbishnoi """ + +from telethon import events + +from Exon import telethn + + +def register(**args): + """Registers a new message.""" + pattern = args.get("pattern") + + r_pattern = r"^[/!]" + + if pattern is not None and not pattern.startswith("(?i)"): + args["pattern"] = f"(?i){pattern}" + + args["pattern"] = pattern.replace("^/", r_pattern, 1) + + def decorator(func): + telethn.add_event_handler(func, events.NewMessage(**args)) + return func + + return decorator + + +def chataction(**args): + """Registers chat actions.""" + + def decorator(func): + telethn.add_event_handler(func, events.ChatAction(**args)) + return func + + return decorator + + +def userupdate(**args): + """Registers user updates.""" + + def decorator(func): + telethn.add_event_handler(func, events.UserUpdate(**args)) + return func + + return decorator + + +def inlinequery(**args): + """Registers inline query.""" + pattern = args.get("pattern") + + if pattern is not None and not pattern.startswith("(?i)"): + args["pattern"] = f"(?i){pattern}" + + def decorator(func): + telethn.add_event_handler(func, events.InlineQuery(**args)) + return func + + return decorator + + +def callbackquery(**args): + """Registers inline query.""" + + def decorator(func): + telethn.add_event_handler(func, events.CallbackQuery(**args)) + return func + + return decorator diff --git "a/Exon/modules/ABISHNOI COPYRIGHT \302\251" "b/Exon/modules/ABISHNOI COPYRIGHT \302\251" new file mode 100644 index 00000000..a7ec3b8e --- /dev/null +++ "b/Exon/modules/ABISHNOI COPYRIGHT \302\251" @@ -0,0 +1,27 @@ +""" +MIT License + +Copyright (c) 2022 ABISHNOI + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" +# ""DEAR PRO PEOPLE, DON'T REMOVE & CHANGE THIS LINE +# TG :- @Abishnoi +# MY ALL BOTS :- Abishnoi_bots +# GITHUB :- KingAbishnoi "" \ No newline at end of file diff --git a/Exon/modules/__init__.py b/Exon/modules/__init__.py new file mode 100644 index 00000000..c66d48ab --- /dev/null +++ b/Exon/modules/__init__.py @@ -0,0 +1,73 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" +# ""DEAR PRO PEOPLE, DON'T REMOVE & CHANGE THIS LINE +# TG :- @Abishnoi1M +# MY ALL BOTS :- Abishnoi_bots +# GITHUB :- KingAbishnoi "" + +import sys + +from Exon import LOAD, LOGGER, NO_LOAD + + +def __list_all_modules(): + import glob + from os.path import basename, dirname, isfile + + # This generates a list of modules in this folder for the * in __main__ to work. + mod_paths = glob.glob(f"{dirname(__file__)}/*.py") + all_modules = [ + basename(f)[:-3] + for f in mod_paths + if isfile(f) and f.endswith(".py") and not f.endswith("__init__.py") + ] + + if LOAD or NO_LOAD: + to_load = LOAD + if to_load: + if not all( + any(mod == module_name for module_name in all_modules) + for mod in to_load + ): + LOGGER.error("ɪɴᴠᴀʟɪᴅ ʟᴏᴀᴅᴏʀᴅᴇʀ ɴᴀᴍᴇs. ǫᴜɪᴛᴛɪɴɢ.") + sys.exit(1) + + all_modules = sorted(set(all_modules) - set(to_load)) + to_load = list(all_modules) + to_load + + else: + to_load = all_modules + + if NO_LOAD: + LOGGER.info(f"Not loading: {NO_LOAD}") + return [item for item in to_load if item not in NO_LOAD] + + return to_load + + return all_modules + + +ALL_MODULES = __list_all_modules() +LOGGER.info("ᴍᴏᴅᴜʟᴇs ᴛᴏ ʟᴏᴀᴅ: %s", str(ALL_MODULES)) +__all__ = ALL_MODULES + ["ALL_MODULES"] diff --git a/Exon/modules/admin.py b/Exon/modules/admin.py new file mode 100644 index 00000000..b1042b1d --- /dev/null +++ b/Exon/modules/admin.py @@ -0,0 +1,1113 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import html +import os +from typing import Optional + +from telegram import InlineKeyboardButton, InlineKeyboardMarkup, ParseMode, Update +from telegram.error import BadRequest +from telegram.ext import CallbackContext, CallbackQueryHandler, CommandHandler, Filters +from telegram.utils.helpers import mention_html + +from Exon import DRAGONS, LOGGER, dispatcher +from Exon.modules.disable import DisableAbleCommandHandler +from Exon.modules.helper_funcs.alternate import send_message +from Exon.modules.helper_funcs.chat_status import ( + ADMIN_CACHE, + bot_admin, + can_pin, + can_promote, + connection_status, + is_user_admin, + user_admin, + user_can_promote, +) +from Exon.modules.helper_funcs.extraction import extract_user, extract_user_and_text +from Exon.modules.log_channel import loggable + + +def user_can_changeinfo(chat, user_admin, id): + pass + + +@bot_admin +@user_admin +def set_sticker(update: Update, context: CallbackContext): + msg = update.effective_message + chat = update.effective_chat + user = update.effective_user + + if user_can_changeinfo(chat, user, context.bot.id) is False: + return msg.reply_text("You're missing rights to change chat info!") + + if msg.reply_to_message: + if not msg.reply_to_message.sticker: + return msg.reply_text( + "You need to reply to some sticker to set chat sticker set!" + ) + stkr = msg.reply_to_message.sticker.set_name + try: + context.bot.set_chat_sticker_set(chat.id, stkr) + msg.reply_text(f"Successfully set new group stickers in {chat.title}!") + except BadRequest as excp: + if excp.message == "Participants_too_few": + return msg.reply_text( + "Sorry, due to telegram restrictions chat needs to have minimum 100 members before they can have group stickers!" + ) + msg.reply_text(f"Error! {excp.message}.") + else: + msg.reply_text("You need to reply to some sticker to set chat sticker set!") + + +@bot_admin +@user_admin +def setchatpic(update: Update, context: CallbackContext): + chat = update.effective_chat + msg = update.effective_message + user = update.effective_user + + if user_can_changeinfo(chat, user, context.bot.id) is False: + msg.reply_text("You are missing right to change group info!") + return + + if msg.reply_to_message: + if msg.reply_to_message.photo: + pic_id = msg.reply_to_message.photo[-1].file_id + elif msg.reply_to_message.document: + pic_id = msg.reply_to_message.document.file_id + else: + msg.reply_text("You can only set some photo as chat pic!") + return + dlmsg = msg.reply_text("Just a sec...") + tpic = context.bot.get_file(pic_id) + tpic.download("gpic.png") + try: + with open("gpic.png", "rb") as chatp: + context.bot.set_chat_photo(int(chat.id), photo=chatp) + msg.reply_text("Successfully set new chatpic!") + except BadRequest as excp: + msg.reply_text(f"Error! {excp.message}") + finally: + dlmsg.delete() + if os.path.isfile("gpic.png"): + os.remove("gpic.png") + else: + msg.reply_text("Reply to some photo or file to set new chat pic!") + + +@bot_admin +@user_admin +def rmchatpic(update: Update, context: CallbackContext): + chat = update.effective_chat + msg = update.effective_message + user = update.effective_user + + if user_can_changeinfo(chat, user, context.bot.id) is False: + msg.reply_text("You don't have enough rights to delete group photo") + return + try: + context.bot.delete_chat_photo(int(chat.id)) + msg.reply_text("Successfully deleted chat's profile photo!") + except BadRequest as excp: + msg.reply_text(f"Error! {excp.message}.") + return + + +@bot_admin +@user_admin +def set_desc(update: Update, context: CallbackContext): + msg = update.effective_message + chat = update.effective_chat + user = update.effective_user + + if user_can_changeinfo(chat, user, context.bot.id) is False: + return msg.reply_text("You're missing rights to change chat info!") + + tesc = msg.text.split(None, 1) + if len(tesc) >= 2: + desc = tesc[1] + else: + return msg.reply_text("Setting empty description won't do anything!") + try: + if len(desc) > 255: + return msg.reply_text("Description must needs to be under 255 characters!") + context.bot.set_chat_description(chat.id, desc) + msg.reply_text(f"Successfully updated chat description in {chat.title}!") + except BadRequest as excp: + msg.reply_text(f"Error! {excp.message}.") + + +@bot_admin +@user_admin +def setchat_title(update: Update, context: CallbackContext): + chat = update.effective_chat + msg = update.effective_message + user = update.effective_user + args = context.args + + if user_can_changeinfo(chat, user, context.bot.id) is False: + msg.reply_text("You don't have enough rights to change chat info!") + return + + title = " ".join(args) + if not title: + msg.reply_text("Enter some text to set new title in your chat!") + return + + try: + context.bot.set_chat_title(int(chat.id), title) + msg.reply_text( + f"Successfully set {title} as new chat title!", + parse_mode=ParseMode.HTML, + ) + except BadRequest as excp: + msg.reply_text(f"Error! {excp.message}.") + return + + +@connection_status +@bot_admin +@can_promote +@user_admin +@loggable +def promote(update: Update, context: CallbackContext) -> str: + bot = context.bot + args = context.args + + message = update.effective_message + chat = update.effective_chat + user = update.effective_user + + promoter = chat.get_member(user.id) + + if ( + not (promoter.can_promote_members or promoter.status == "creator") + and user.id not in DRAGONS + ): + message.reply_text("You don't have the necessary rights to do that!") + return + + user_id = extract_user(message, args) + + if not user_id: + message.reply_text( + "You don't seem to be referring to a user or the ID specified is incorrect..", + ) + return + + try: + user_member = chat.get_member(user_id) + except: + return + + if user_member.status in ("administrator", "creator"): + message.reply_text("How am I meant to promote someone that's already an admin?") + return + + if user_id == bot.id: + message.reply_text("I can't promote myself! Get an admin to do it for me.") + return + + # set same perms as bot - bot can't assign higher perms than itself! + bot_member = chat.get_member(bot.id) + + try: + bot.promoteChatMember( + chat.id, + user_id, + # can_change_info=bot_member.can_change_info, + can_post_messages=bot_member.can_post_messages, + can_edit_messages=bot_member.can_edit_messages, + can_delete_messages=bot_member.can_delete_messages, + can_invite_users=bot_member.can_invite_users, + # can_promote_members=bot_member.can_promote_members, + can_restrict_members=bot_member.can_restrict_members, + can_pin_messages=bot_member.can_pin_messages, + ) + except BadRequest as err: + if err.message == "User_not_mutual_contact": + message.reply_text("I can't promote someone who isn't in the group.") + else: + message.reply_text("An error occured while promoting.") + return + + bot.sendMessage( + chat.id, + f"Sucessfully promoted {user_member.user.first_name or user_id} ({user_id}) in {chat.title}", + parse_mode=ParseMode.HTML, + reply_markup=InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + text="🔽 Demote", + callback_data=f"admin_demote_{user_member.user.id}", + ), + InlineKeyboardButton( + text="🔄 AdminCache", + callback_data=f"admin_refresh_{user_member.user.id}", + ), + ], + ], + ), + ) + log_message = ( + f"{html.escape(chat.title)}:\n" + f"#PROMOTED\n" + f"Admin: {mention_html(user.id, user.first_name)}\n" + f"User: {mention_html(user_member.user.id, user_member.user.first_name)}" + ) + + return log_message + + +@connection_status +@bot_admin +@can_promote +@user_admin +@loggable +def fullpromote(update: Update, context: CallbackContext) -> str: + bot = context.bot + args = context.args + + message = update.effective_message + chat = update.effective_chat + user = update.effective_user + + promoter = chat.get_member(user.id) + + if ( + not (promoter.can_promote_members or promoter.status == "creator") + and user.id not in DRAGONS + ): + message.reply_text("You don't have the necessary rights to do that!") + return + + user_id = extract_user(message, args) + + if not user_id: + message.reply_text( + "You don't seem to be referring to a user or the ID specified is incorrect..", + ) + return + + try: + user_member = chat.get_member(user_id) + except: + return + + if user_member.status in ("administrator", "creator"): + message.reply_text("How am I meant to promote someone that's already an admin?") + return + + if user_id == bot.id: + message.reply_text("I can't promote myself! Get an admin to do it for me.") + return + + # set same perms as bot - bot can't assign higher perms than itself! + bot_member = chat.get_member(bot.id) + + try: + bot.promoteChatMember( + chat.id, + user_id, + can_change_info=bot_member.can_change_info, + can_post_messages=bot_member.can_post_messages, + can_edit_messages=bot_member.can_edit_messages, + can_delete_messages=bot_member.can_delete_messages, + can_invite_users=bot_member.can_invite_users, + can_promote_members=bot_member.can_promote_members, + can_restrict_members=bot_member.can_restrict_members, + can_pin_messages=bot_member.can_pin_messages, + can_manage_voice_chats=bot_member.can_manage_voice_chats, + ) + except BadRequest as err: + if err.message == "User_not_mutual_contact": + message.reply_text("I can't promote someone who isn't in the group.") + else: + message.reply_text("An error occured while promoting.") + return + + bot.sendMessage( + chat.id, + f"Sucessfully promoted {user_member.user.first_name or user_id} ({user_id}) with full rights in {chat.title}!", + parse_mode=ParseMode.HTML, + reply_markup=InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + text="🔽 Demote", + callback_data=f"admin_demote_{user_member.user.id}", + ), + InlineKeyboardButton( + text="🔄 AdminCache", + callback_data=f"admin_refresh_{user_member.user.id}", + ), + ], + ], + ), + ) + + log_message = ( + f"{html.escape(chat.title)}:\n" + f"#FULLPROMOTED\n" + f"Admin: {mention_html(user.id, user.first_name)}\n" + f"User: {mention_html(user_member.user.id, user_member.user.first_name)}" + ) + + return log_message + + +@connection_status +@bot_admin +@can_promote +@user_admin +@loggable +def lowpromote(update: Update, context: CallbackContext) -> str: + bot = context.bot + args = context.args + + message = update.effective_message + chat = update.effective_chat + user = update.effective_user + + promoter = chat.get_member(user.id) + + if ( + not (promoter.can_promote_members or promoter.status == "creator") + and user.id not in DRAGONS + ): + message.reply_text("You don't have the necessary rights to do that!") + return + + user_id = extract_user(message, args) + + if not user_id: + message.reply_text( + "You don't seem to be referring to a user or the ID specified is incorrect..", + ) + return + + try: + user_member = chat.get_member(user_id) + except: + return + + if user_member.status in ("administrator", "creator"): + message.reply_text("How am I meant to promote someone that's already an admin?") + return + + if user_id == bot.id: + message.reply_text("I can't promote myself! Get an admin to do it for me.") + return + + # set same perms as bot - bot can't assign higher perms than itself! + bot_member = chat.get_member(bot.id) + + try: + bot.promoteChatMember( + chat.id, + user_id, + can_delete_messages=bot_member.can_delete_messages, + can_invite_users=bot_member.can_invite_users, + can_pin_messages=bot_member.can_pin_messages, + ) + except BadRequest as err: + if err.message == "User_not_mutual_contact": + message.reply_text("I can't promote someone who isn't in the group.") + else: + message.reply_text("An error occured while promoting.") + return + + bot.sendMessage( + chat.id, + f"Sucessfully promoted {user_member.user.first_name or user_id} with low rights!", + parse_mode=ParseMode.HTML, + reply_markup=InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + text="Demote", + callback_data=f"admin_demote_{user_member.user.id}", + ), + InlineKeyboardButton( + text="AdminCache", + callback_data=f"admin_refresh_{user_member.user.id}", + ), + ], + ], + ), + ) + + log_message = ( + f"{html.escape(chat.title)}:\n" + f"#LOWPROMOTED\n" + f"Admin: {mention_html(user.id, user.first_name)}\n" + f"User: {mention_html(user_member.user.id, user_member.user.first_name)}" + ) + + return log_message + + +@connection_status +@bot_admin +@can_promote +@user_admin +@loggable +def demote(update: Update, context: CallbackContext) -> Optional[str]: + bot = context.bot + args = context.args + + chat = update.effective_chat + message = update.effective_message + user = update.effective_user + + user_id = extract_user(message, args) + if not user_id: + message.reply_text( + "You don't seem to be referring to a user or the ID specified is incorrect.." + ) + return + + try: + user_member = chat.get_member(user_id) + except: + return + + if user_member.status == "creator": + message.reply_text("This person CREATED the chat, how would I demote them?") + return + + if user_member.status != "administrator": + message.reply_text("Can't demote what wasn't promoted!") + return + + if user_id == bot.id: + message.reply_text("I can't demote myself! Get an admin to do it for me.") + return + + try: + bot.promoteChatMember( + chat.id, + user_id, + can_change_info=False, + can_post_messages=False, + can_edit_messages=False, + can_delete_messages=False, + can_invite_users=False, + can_restrict_members=False, + can_pin_messages=False, + can_promote_members=False, + can_manage_voice_chats=False, + ) + + bot.sendMessage( + chat.id, + f"{user_member.user.first_name or user_id or None} was demoted by {message.from_user.first_name or None} in {chat.title or None}", + parse_mode=ParseMode.HTML, + ) + + log_message = ( + f"{html.escape(chat.title)}:\n" + f"#DEMOTED\n" + f"Admin: {mention_html(user.id, user.first_name)}\n" + f"User: {mention_html(user_member.user.id, user_member.user.first_name)}" + ) + + return log_message + except BadRequest: + message.reply_text( + "Could not demote. I might not be admin, or the admin status was appointed by another" + " user, so I can't act upon them!" + ) + return + + +@user_admin +def refresh_admin(update, _): + try: + ADMIN_CACHE.pop(update.effective_chat.id) + except KeyError: + pass + + update.effective_message.reply_text("Admins cache refreshed!") + + +@connection_status +@bot_admin +@can_promote +@user_admin +def set_title(update: Update, context: CallbackContext): + bot = context.bot + args = context.args + + chat = update.effective_chat + message = update.effective_message + + user_id, title = extract_user_and_text(message, args) + try: + user_member = chat.get_member(user_id) + except: + return + + if not user_id: + message.reply_text( + "You don't seem to be referring to a user or the ID specified is incorrect..", + ) + return + + if user_member.status == "creator": + message.reply_text( + "This person CREATED the chat, how can i set custom title for him?", + ) + return + + if user_member.status != "administrator": + message.reply_text( + "Can't set title for non-admins!\nPromote them first to set custom title!", + ) + return + + if user_id == bot.id: + message.reply_text( + "I can't set my own title myself! Get the one who made me admin to do it for me.", + ) + return + + if not title: + message.reply_text("Setting blank title doesn't do anything!") + return + + if len(title) > 16: + message.reply_text( + "The title length is longer than 16 characters.\nTruncating it to 16 characters.", + ) + + try: + bot.setChatAdministratorCustomTitle(chat.id, user_id, title) + except BadRequest: + message.reply_text( + "Either they aren't promoted by me or you set a title text that is impossible to set." + ) + return + + bot.sendMessage( + chat.id, + f"Sucessfully set title for {user_member.user.first_name or user_id} " + f"to {html.escape(title[:16])}!", + parse_mode=ParseMode.HTML, + ) + + +@bot_admin +@can_pin +@user_admin +@loggable +def pin(update: Update, context: CallbackContext) -> str: + bot = context.bot + args = context.args + + user = update.effective_user + chat = update.effective_chat + + is_group = chat.type not in ("private", "channel") + prev_message = update.effective_message.reply_to_message + + is_silent = True + if len(args) >= 1: + is_silent = args[0].lower() in ("notify", "loud", "violent") + + if prev_message and is_group: + try: + bot.pinChatMessage( + chat.id, + prev_message.message_id, + disable_notification=is_silent, + ) + except BadRequest as excp: + if excp.message != "Chat_not_modified": + raise + log_message = ( + f"{html.escape(chat.title)}:\n" + f"#PINNED\n" + f"Admin: {mention_html(user.id, html.escape(user.first_name))}" + ) + + return log_message + + +@bot_admin +@can_pin +@user_admin +@loggable +def unpin(update: Update, context: CallbackContext) -> str: + bot = context.bot + chat = update.effective_chat + user = update.effective_user + + try: + bot.unpinChatMessage(chat.id) + except BadRequest as excp: + if excp.message == "Chat_not_modified": + pass + else: + raise + + log_message = ( + f"{html.escape(chat.title)}:\n" + f"#UNPINNED\n" + f"Admin: {mention_html(user.id, html.escape(user.first_name))}" + ) + + return log_message + + +@bot_admin +def pinned(update: Update, context: CallbackContext) -> str: + bot = context.bot + msg = update.effective_message + msg_id = ( + update.effective_message.reply_to_message.message_id + if update.effective_message.reply_to_message + else update.effective_message.message_id + ) + + chat = bot.getChat(chat_id=msg.chat.id) + if chat.pinned_message: + pinned_id = chat.pinned_message.message_id + if msg.chat.username: + link_chat_id = msg.chat.username + message_link = f"https://t.me/{link_chat_id}/{pinned_id}" + elif (str(msg.chat.id)).startswith("-100"): + link_chat_id = (str(msg.chat.id)).replace("-100", "") + message_link = f"https://t.me/c/{link_chat_id}/{pinned_id}" + + msg.reply_text( + f"Pinned on {html.escape(chat.title)}.", + reply_to_message_id=msg_id, + parse_mode=ParseMode.HTML, + disable_web_page_preview=True, + reply_markup=InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + text="Pinned Message", + url=f"https://t.me/{link_chat_id}/{pinned_id}", + ) + ] + ] + ), + ) + + else: + msg.reply_text( + f"There is no pinned message in {html.escape(chat.title)}!", + parse_mode=ParseMode.HTML, + ) + + +@bot_admin +@user_admin +@connection_status +def invite(update: Update, context: CallbackContext): + bot = context.bot + chat = update.effective_chat + + if chat.username: + update.effective_message.reply_text(f"https://t.me/{chat.username}") + elif chat.type in [chat.SUPERGROUP, chat.CHANNEL]: + bot_member = chat.get_member(bot.id) + if bot_member.can_invite_users: + invitelink = bot.exportChatInviteLink(chat.id) + update.effective_message.reply_text(invitelink) + else: + update.effective_message.reply_text( + "I don't have access to the invite link, try changing my permissions!", + ) + else: + update.effective_message.reply_text( + "I can only give you invite links for supergroups and channels, sorry!", + ) + + +@connection_status +def adminlist(update, context): + chat = update.effective_chat ## type: Optional[Chat] -> unused variable + user = update.effective_user # type: Optional[User] + args = context.args # -> unused variable + bot = context.bot + + if update.effective_message.chat.type == "private": + send_message(update.effective_message, "This command only works in Groups.") + return + + update.effective_chat + chat_id = update.effective_chat.id + chat_name = update.effective_message.chat.title # -> unused variable + + try: + msg = update.effective_message.reply_text( + "Fetching group admins...", + parse_mode=ParseMode.HTML, + ) + except BadRequest: + msg = update.effective_message.reply_text( + "Fetching group admins...", + quote=False, + parse_mode=ParseMode.HTML, + ) + + administrators = bot.getChatAdministrators(chat_id) + text = f"Admins in {html.escape(update.effective_chat.title)}:" + + for admin in administrators: + user = admin.user + status = admin.status + custom_title = admin.custom_title + + if user.first_name == "": + name = "☠ Deleted Account" + else: + name = "{}".format( + mention_html( + user.id, + html.escape(f"{user.first_name} " + ((user.last_name or ""))), + ) + ) + + ##if user.is_bot: + # bot_admin_list.append(name) + # administrators.remove(admin) + # continue + + # continue + + # if user.username: + # name = escape_markdown("@" + user.username) + if status == "creator": + text += "\n\n 🌐 ᴄʀᴇᴀᴛᴏʀ:" + text += f" {name}\n" + + if custom_title: + text += f" ┗━ {html.escape(custom_title)}\n" + + text += "\n 🎖 ᴀᴅᴍɪɴɪsᴛʀᴀᴛᴏʀs" + + custom_admin_list = {} + normal_admin_list = [] + + for admin in administrators: + user = admin.user + status = admin.status + custom_title = admin.custom_title + + if user.first_name == "": + name = "☠ Deleted Account" + else: + name = "{}".format( + mention_html( + user.id, + html.escape(f"{user.first_name} " + ((user.last_name or ""))), + ) + ) + + if status == "administrator": + if custom_title: + try: + custom_admin_list[custom_title].append(name) + except KeyError: + custom_admin_list[custom_title] = [name] + else: + normal_admin_list.append(name) + + for admin in normal_admin_list: + text += f"\n{admin}" + + for admin_group in custom_admin_list.copy(): + if len(custom_admin_list[admin_group]) == 1: + text += f"\n{custom_admin_list[admin_group][0]} | {html.escape(admin_group)}" + + custom_admin_list.pop(admin_group) + + text += "\n" + for admin_group, value in custom_admin_list.items(): + text += f"\n🚨 {admin_group}" + for admin in value: + text += f"\n{admin}" + text += "\n" + + # text += "\n🤖 Bots:" + # for each_bot in bot_admin_list: + # text += "\n{}".format(each_bot) + + try: + msg.edit_text(text, parse_mode=ParseMode.HTML) + except BadRequest: # if original message is deleted + return + + +# if user.is_bot: + +# bot_admin_list.append(name) + +# administrators.remove(admin) + +# text += "Bottos:" + +# for each_bot in bot_admin_list: + + +@user_admin +@user_can_promote +def promote_button(update: Update, context: CallbackContext): + query = update.callback_query + user = update.effective_user + chat = update.effective_chat + bot = context.bot + + mode = query.data.split("_")[1] + try: + if is_user_admin(chat, user.id): + if mode == "demote": + user_id = query.data.split("_")[2] + user_member = chat.get_member(user_id) + bot.promoteChatMember( + chat.id, + user_id, + can_change_info=False, + can_post_messages=False, + can_edit_messages=False, + can_delete_messages=False, + can_invite_users=False, + can_restrict_members=False, + can_pin_messages=False, + can_promote_members=False, + # can_manage_voice_chats=False + ) + query.message.delete() + bot.answer_callback_query( + query.id, + f"Sucessfully demoted {user_member.user.first_name or user_id}", + show_alert=True, + ) + elif mode == "refresh": + try: + ADMIN_CACHE.pop(update.effective_chat.id) + except KeyError: + pass + bot.answer_callback_query(query.id, "Admins cache refreshed!") + except BadRequest as excp: + if excp.message not in [ + "Message is not mod", + "User_id_invalid", + "Message Deleted", + ]: + LOGGER.exception("Exception in promote buttons. %s", str(query.data)) + + +__help__ = """ +ʜᴇʀᴇ ɪs ᴛʜᴇ ʜᴇʟᴘ ғᴏʀ ᴛʜᴇ *ᴀᴅᴍɪɴs* ᴍᴏᴅᴜʟᴇ: + +*ᴀᴅᴍɪɴ ᴄᴏᴍᴍᴀɴᴅs*: + +✥ ᴘɪɴs ❉ + + • /pin: `sɪʟᴇɴᴛʟʏ ᴘɪɴs ᴛʜᴇ ᴍᴇssᴀɢᴇ ʀᴇᴘʟɪᴇᴅ ᴛᴏ ` + + • /unpin: `ᴜɴᴘɪɴs ᴛʜᴇ ᴄᴜʀʀᴇɴᴛʟʏ ᴘɪɴɴᴇᴅ ᴍᴇssᴀɢᴇ ` + + • /pinned: `ᴛᴏ ɢᴇᴛ ᴛʜᴇ ᴄᴜʀʀᴇɴᴛ ᴘɪɴɴᴇᴅ ᴍᴇssᴀɢᴇ ` + + • /unpinall: `ᴛᴏ ᴜɴᴘɪɴ ᴀʟʟ ᴍᴇssᴀɢᴇs ɪɴ ᴛʜᴇ ᴄʜᴀᴛ ` + + +❉ ᴘʀᴏᴍᴏᴛᴇ ᴀɴᴅ ᴛɪᴛʟᴇs ❉: + + • /promote: `ᴘʀᴏᴍᴏᴛᴇs ᴛʜᴇ ᴜsᴇʀ ʀᴇᴘʟɪᴇᴅ ᴛᴏ (ᴄᴀɴ ʙᴇ ᴜsᴇᴅ ᴀs ғᴜʟʟᴘʀᴏᴍᴏᴛᴇ ᴏʀ ʟᴏᴡ ᴘʀᴏᴍᴏᴛᴇ)` + + • /demote: `ᴅᴇᴍᴏᴛᴇs ᴛʜᴇ ᴜsᴇʀ ʀᴇᴘʟɪᴇᴅ ᴛᴏ ` + + • /title : `sᴇᴛs ᴀ ᴄᴜsᴛᴏᴍ ᴛɪᴛʟᴇ ғᴏʀ ᴀɴ ᴀᴅᴍɪɴ ᴛʜᴀᴛ ᴛʜᴇ ʙᴏᴛ ᴘʀᴏᴍᴏᴛᴇᴅ ` + + • /admincache: `ғᴏʀᴄᴇ ʀᴇғʀᴇsʜ ᴛʜᴇ ᴀᴅᴍɪɴs ʟɪsᴛ` + +❉ ᴏᴛʜᴇʀs :❉ + + • /setgtitle <new title>: `sᴇᴛs ᴄʜᴀᴛ ᴛɪᴛʟᴇ` + + • /setdesc <description>: `sᴇᴛs ᴄʜᴀᴛ ᴅᴇsᴄʀɪᴘᴛɪᴏɴ` + + • /setsticker <reply to sticker>: ` sᴇᴛs sᴛɪᴄᴋᴇʀ ᴘᴀᴄᴋ ɪɴ ᴀ sᴜᴘᴇʀɢʀᴏᴜᴘ` + + • /setgpic <reply to a picture>: `sᴇᴛs ɢʀᴏᴜᴘ's ᴘʀᴏғɪʟᴇ ᴘʜᴏᴛᴏ ` + + • /delgpic: `ʀᴇᴍᴏᴠᴇs ɢʀᴏᴜᴘ's ᴘʀᴏғɪʟᴇ ᴘʜᴏᴛᴏ ` + + • /admins: `sʜᴏᴡs ᴀᴅᴍɪɴ ʟɪsᴛ ɪɴ ᴛʜᴇ ᴄʜᴀᴛ` + + • /invitelink: `ɢᴇᴛs ɪɴᴠɪᴛᴇ ʟɪɴᴋ ᴏғ ᴛʜᴀᴛ ᴄʜᴀᴛ ` + +*ᴍᴏᴅᴇʀᴀᴛɪᴏɴ*: + +❉ ʙᴀɴɴɪɴɢ ᴀɴᴅ ᴋɪᴄᴋs: ❉ + + • /ban <userhandle>: `ʙᴀɴs a ᴜsᴇʀ (ᴠɪᴀ ʜᴀɴᴅʟᴇ, ᴏʀ ʀᴇᴘʟʏ`) + + • /sban <userhandle>: `sɪʟᴇɴᴛʟʏ ʙᴀɴ ᴀ ᴜsᴇʀ ᴛʜᴇɴ ᴅᴇʟᴇᴛᴇs ᴄᴏᴍᴍᴀɴᴅ + ʀᴇᴘʟɪᴇᴅ ᴛᴏ ᴍᴇssᴀɢᴇ ᴀɴᴅ ᴅᴏᴇsɴ'ᴛ ʀᴇᴘʟʏ (ᴠɪᴀ ʜᴀɴᴅʟᴇ, ᴏʀ ʀᴇᴘʟʏ)` + + • /dban <messagereplied>: `sɪʟᴇɴᴛʟʏ ʙᴀɴs ᴛʜᴇ ᴜsᴇʀ ᴀɴᴅ ᴅᴇʟᴇᴛᴇs ᴛʜᴇ ᴛᴀʀɢᴇᴛ ʀᴇᴘʟɪᴇᴅ ᴛᴏ ᴍᴇssᴀɢᴇ + + • /tban <userhandle> x(m/h/d): `ʙᴀɴs ᴀ ᴜsᴇʀ ғᴏʀ x ᴛɪᴍᴇ, (ᴠɪᴀ ʜᴀɴᴅʟᴇ, ᴏʀ ʀᴇᴘʟʏ) ᴍ = ᴍɪɴᴜᴛᴇs, ʜ = ʜᴏᴜʀs, ᴅ = ᴅᴀʏs + + • /unban <userhandle>: `ᴜɴʙᴀɴs ᴀ ᴜsᴇʀ (ᴠɪᴀ ʜᴀɴᴅʟᴇ, ᴏʀ ʀᴇᴘʟʏ)` + + • /punch or kick <userhandle>: `ᴘᴜɴᴄʜᴇs ᴀ ᴜsᴇʀ ᴏᴜᴛ ᴏғ ᴛʜᴇ group (ᴠɪᴀ ʜᴀɴᴅʟᴇ, ᴏʀ ʀᴇᴘʟʏ)` + +❉ ᴍᴜᴛɪɴɢ: ❉ + + • /mute <userhandle>: `sɪʟᴇɴᴄᴇs ᴀ ᴜsᴇʀ, ᴄᴀɴ ᴀʟsᴏ ʙᴇ ᴜsᴇᴅ ᴀs ᴀ ʀᴇᴘʟʏ, ᴍᴜᴛɪɴɢ ᴛʜᴇ ʀᴇᴘʟɪᴇᴅ ᴛᴏ ᴜsᴇʀ` + + • /tmute <userhandle> x(m/h/d): ᴍᴜᴛᴇs ᴀ ᴜsᴇʀ ғᴏʀ x ᴛɪᴍᴇ (ᴠɪᴀ ʜᴀɴᴅʟᴇ, ᴏʀ ʀᴇᴘʟʏ). ᴍ = minutes, ʜ = ʜᴏᴜʀs, ᴅ = ᴅᴀʏs + + • /unmute <userhandle>: `ᴜɴᴍᴜᴛᴇs ᴀ ᴜsᴇʀ, ᴄᴀɴ ᴀʟsᴏ ʙᴇ ᴜsᴇᴅ ᴀs ᴀ ʀᴇᴘʟʏ, ᴍᴜᴛɪɴɢ ᴛʜᴇ ʀᴇᴘʟɪᴇᴅ ᴛᴏ ᴜsᴇʀ ` + +*ʟᴏɢɢɪɴɢ*: + + • /logchannel: ɢᴇᴛ ʟᴏɢ ᴄʜᴀɴɴᴇʟ ɪɴғᴏ + + • /setlog: sᴇᴛ ᴛʜᴇ ʟᴏɢ ᴄʜᴀɴɴᴇʟ + + • /unsetlog: ᴜɴsᴇᴛ ᴛʜᴇ ʟᴏɢ ᴄʜᴀɴɴᴇʟ + +✥ ʜᴏᴡ ᴛᴏ sᴇᴛᴜᴘ: + + • ᴀᴅᴅ ʙᴏᴛ ᴛᴏ ᴄʜᴀɴɴᴇʟ ᴡɪᴛʜ ᴀᴅᴍɪɴ ᴘᴇʀᴍs + + • sᴇɴᴅ /setlog ᴄᴏᴍᴍᴀɴᴅ ɪɴ ᴛʜᴇ ᴄʜᴀɴɴᴇʟ + + • ғᴏʀᴡᴀʀᴅ ᴛʜᴀᴛ ᴄʜᴀɴɴᴇʟ ᴍᴇssᴀɢᴇ ᴛᴏ ᴛʜᴇ ɢʀᴏᴜᴘ ʏᴏᴜ ᴡᴀɴᴛ ᴛᴏ sᴇᴛᴜᴘ ʟᴏɢɢɪɴɢ ғᴏʀ, ᴛʜᴇ sᴀᴍᴇ ᴄʜᴀɴɴᴇʟ ᴍᴇssᴀɢᴇ ᴄᴀɴ ʙᴇ ғᴏʀᴡᴀʀᴅᴇᴅ ᴛᴏ ᴍᴜʟᴛɪᴘʟᴇ ɢʀᴏᴜᴘs ᴀᴛ ᴏɴᴄᴇ ᴀs ᴡᴇʟʟ + +✥ ʀᴜʟᴇs: + + • /rules: `ɢᴇᴛ ᴛʜᴇ ʀᴜʟᴇs ғᴏʀ ᴛʜɪs ᴄʜᴀᴛ` + + • /setrules <ʏᴏᴜʀ ʀᴜʟᴇs ʜᴇʀᴇ>: `sᴇᴛ ᴛʜᴇ ʀᴜʟᴇs ғᴏʀ ᴛʜɪs ᴄʜᴀᴛ` + + • /clearrules: `ᴄʟᴇᴀʀ ᴛʜᴇ ʀᴜʟᴇs ғᴏʀ ᴛʜɪs ᴄʜᴀᴛ ` + +""" +SET_DESC_HANDLER = CommandHandler( + "setdesc", set_desc, filters=Filters.chat_type.groups, run_async=True +) +SET_STICKER_HANDLER = CommandHandler( + "setsticker", set_sticker, filters=Filters.chat_type.groups, run_async=True +) +SETCHATPIC_HANDLER = CommandHandler( + "setgpic", setchatpic, filters=Filters.chat_type.groups, run_async=True +) +RMCHATPIC_HANDLER = CommandHandler( + "delgpic", rmchatpic, filters=Filters.chat_type.groups, run_async=True +) +SETCHAT_TITLE_HANDLER = CommandHandler( + "setgtitle", setchat_title, filters=Filters.chat_type.groups, run_async=True +) + +ADMINLIST_HANDLER = DisableAbleCommandHandler("admins", adminlist, run_async=True) + +PIN_HANDLER = CommandHandler( + "pin", pin, filters=Filters.chat_type.groups, run_async=True +) +UNPIN_HANDLER = CommandHandler( + "unpin", unpin, filters=Filters.chat_type.groups, run_async=True +) +PINNED_HANDLER = CommandHandler( + "pinned", pinned, filters=Filters.chat_type.groups, run_async=True +) + +INVITE_HANDLER = DisableAbleCommandHandler("invitelink", invite, run_async=True) + +PROMOTE_HANDLER = DisableAbleCommandHandler("promote", promote, run_async=True) +FULLPROMOTE_HANDLER = DisableAbleCommandHandler( + "fullpromote", fullpromote, run_async=True +) +LOWPROMOTE_HANDLER = DisableAbleCommandHandler("lowpromote", lowpromote, run_async=True) +DEMOTE_HANDLER = DisableAbleCommandHandler("demote", demote, run_async=True) +PROMOTE_CALLBACK_HANDLER = CallbackQueryHandler( + promote_button, pattern=r"admin_", run_async=True +) + + +SET_TITLE_HANDLER = CommandHandler("title", set_title, run_async=True) +ADMIN_REFRESH_HANDLER = CommandHandler( + "admincache", refresh_admin, filters=Filters.chat_type.groups, run_async=True +) + +dispatcher.add_handler(SET_DESC_HANDLER) +dispatcher.add_handler(SET_STICKER_HANDLER) +dispatcher.add_handler(SETCHATPIC_HANDLER) +dispatcher.add_handler(RMCHATPIC_HANDLER) +dispatcher.add_handler(SETCHAT_TITLE_HANDLER) +dispatcher.add_handler(ADMINLIST_HANDLER) +dispatcher.add_handler(PIN_HANDLER) +dispatcher.add_handler(UNPIN_HANDLER) +dispatcher.add_handler(PINNED_HANDLER) +dispatcher.add_handler(INVITE_HANDLER) +dispatcher.add_handler(PROMOTE_HANDLER) +dispatcher.add_handler(FULLPROMOTE_HANDLER) +dispatcher.add_handler(PROMOTE_CALLBACK_HANDLER) +dispatcher.add_handler(LOWPROMOTE_HANDLER) +dispatcher.add_handler(DEMOTE_HANDLER) +dispatcher.add_handler(SET_TITLE_HANDLER) +dispatcher.add_handler(ADMIN_REFRESH_HANDLER) + +__mod_name__ = "𝙰ᴅᴍɪɴs" +__command_list__ = [ + "setdesc" "setsticker" "setgpic" "delgpic" "setgtitle" "adminlist", + "admins", + "invitelink", + "promote", + "fullpromote", + "lowpromote", + "demote", + "admincache", +] +__handlers__ = [ + SET_DESC_HANDLER, + SET_STICKER_HANDLER, + SETCHATPIC_HANDLER, + RMCHATPIC_HANDLER, + SETCHAT_TITLE_HANDLER, + ADMINLIST_HANDLER, + PIN_HANDLER, + UNPIN_HANDLER, + PINNED_HANDLER, + PROMOTE_CALLBACK_HANDLER, + INVITE_HANDLER, + PROMOTE_HANDLER, + FULLPROMOTE_HANDLER, + LOWPROMOTE_HANDLER, + DEMOTE_HANDLER, + SET_TITLE_HANDLER, + ADMIN_REFRESH_HANDLER, +] diff --git a/Exon/modules/adminserv.py b/Exon/modules/adminserv.py new file mode 100644 index 00000000..25855de4 --- /dev/null +++ b/Exon/modules/adminserv.py @@ -0,0 +1,52 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +from Exon import pgram + + +async def member_permissions(chat_id: int, user_id: int): + perms = [] + try: + member = await pgram.get_chat_member(chat_id, user_id) + except Exception: + return [] + if member.can_post_messages: + perms.append("can_post_messages") + if member.can_edit_messages: + perms.append("can_edit_messages") + if member.can_delete_messages: + perms.append("can_delete_messages") + if member.can_restrict_members: + perms.append("can_restrict_members") + if member.can_promote_members: + perms.append("can_promote_members") + if member.can_change_info: + perms.append("can_change_info") + if member.can_invite_users: + perms.append("can_invite_users") + if member.can_pin_messages: + perms.append("can_pin_messages") + if member.can_manage_voice_chats: + perms.append("can_manage_voice_chats") + return perms diff --git a/Exon/modules/afk.py b/Exon/modules/afk.py new file mode 100644 index 00000000..d9536e35 --- /dev/null +++ b/Exon/modules/afk.py @@ -0,0 +1,202 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import time + +from telegram import MessageEntity +from telegram.error import BadRequest +from telegram.ext import Filters, MessageHandler + +from Exon import REDIS, dispatcher +from Exon.modules.disable import DisableAbleCommandHandler +from Exon.modules.helper_funcs.readable_time import get_readable_time +from Exon.modules.sql.afk_redis import afk_reason, end_afk, is_user_afk, start_afk +from Exon.modules.users import get_user_id + +AFK_GROUP = 7 +AFK_REPLY_GROUP = 8 + + +def afk(update, context): + args = update.effective_message.text.split(None, 1) + user = update.effective_user + if not user: # ignore channels + return + + if user.id == 777000: + return + start_afk_time = time.time() + if len(args) >= 2: + reason = args[1] + else: + reason = "none" + start_afk(update.effective_user.id, reason) + REDIS.set(f"afk_time_{update.effective_user.id}", start_afk_time) + fname = update.effective_user.first_name + try: + update.effective_message.reply_text("{} ɪs ɴᴏᴡ ᴀᴡᴀʏ!".format(fname)) + except BadRequest: + pass + + +def no_longer_afk(update, context): + user = update.effective_user + message = update.effective_message + if not user: # ignore channels + return + + if not is_user_afk(user.id): # Check if user is afk or not + return + end_afk_time = get_readable_time( + (time.time() - float(REDIS.get(f"afk_time_{user.id}"))) + ) + REDIS.delete(f"afk_time_{user.id}") + res = end_afk(user.id) + if res: + if message.new_chat_members: # dont say msg + return + firstname = update.effective_user.first_name + try: + message.reply_text( + "{} ɪs ʙᴀᴄᴋ ᴏɴʟɪɴᴇ!\n\nʏᴏᴜ ᴡᴇʀᴇ ɢᴏɴᴇ ғᴏʀ {}.".format( + firstname, end_afk_time + ) + ) + except Exception: + return + + +def reply_afk(update, context): + message = update.effective_message + userc = update.effective_user + userc_id = userc.id + if message.entities and message.parse_entities( + [MessageEntity.TEXT_MENTION, MessageEntity.MENTION] + ): + entities = message.parse_entities( + [MessageEntity.TEXT_MENTION, MessageEntity.MENTION] + ) + + chk_users = [] + for ent in entities: + if ent.type == MessageEntity.TEXT_MENTION: + user_id = ent.user.id + fst_name = ent.user.first_name + + if user_id in chk_users: + return + chk_users.append(user_id) + + elif ent.type == MessageEntity.MENTION: + user_id = get_user_id( + message.text[ent.offset : ent.offset + ent.length] + ) + if not user_id: + # Should never happen, since for a user to become AFK they must have spoken. Maybe changed username? + return + + if user_id in chk_users: + return + chk_users.append(user_id) + + try: + chat = context.bot.get_chat(user_id) + except BadRequest: + print( + "ᴇʀʀᴏʀ: ᴄᴏᴜʟᴅ ɴᴏᴛ ғᴇᴛᴄʜ ᴜsᴇʀɪᴅ {} ғᴏʀ ᴀғᴋ ᴍᴏᴅᴜʟᴇ".format( + user_id + ) + ) + return + fst_name = chat.first_name + + else: + return + + check_afk(update, context, user_id, fst_name, userc_id) + + elif message.reply_to_message: + user_id = message.reply_to_message.from_user.id + fst_name = message.reply_to_message.from_user.first_name + check_afk(update, context, user_id, fst_name, userc_id) + + +def check_afk(update, context, user_id, fst_name, userc_id): + if is_user_afk(user_id): + reason = afk_reason(user_id) + since_afk = get_readable_time( + (time.time() - float(REDIS.get(f"afk_time_{user_id}"))) + ) + if reason == "none": + if int(userc_id) == int(user_id): + return + res = "{} ɪs ᴀғᴋ.\n\nʟᴀsᴛ sᴇᴇɴ {} ᴀɢᴏ.".format(fst_name, since_afk) + update.effective_message.reply_text(res) + else: + if int(userc_id) == int(user_id): + return + res = "{} ɪs ᴀғᴋ.\nʀᴇᴀsᴏɴ: {}\n\nʟᴀsᴛ sᴇᴇɴ {} ᴀɢᴏ.".format( + fst_name, reason, since_afk + ) + update.effective_message.reply_text(res) + + +def __user_info__(user_id): + is_afk = is_user_afk(user_id) + text = "" + if is_afk: + since_afk = get_readable_time( + (time.time() - float(REDIS.get(f"afk_time_{user_id}"))) + ) + text = "<i>ᴛʜɪs ᴜsᴇʀ ɪs ᴄᴜʀʀᴇɴᴛʟʏ ᴀғᴋ (ᴀᴡᴀʏ ғʀᴏᴍ ᴋᴇʏʙᴏᴀʀᴅ).</i>" + text += f"\n<i>sɪɴᴄᴇ: {since_afk}</i>" + + else: + text = "<i>ᴛʜɪs ᴜsᴇʀ ɪs ᴄᴜʀʀᴇɴᴛʟʏ ɪsɴ'ᴛ ᴀғᴋ (ᴀᴡᴀʏ ғʀᴏᴍ ᴋᴇʏʙᴏᴀʀᴅ).</i>" + return text + + +def __gdpr__(user_id): + end_afk(user_id) + + +__mod_name__ = "𝙰ғᴋ" + +__help__ = """ +✪ /afk <ʀᴇᴀꜱᴏɴ> *:* `ᴍᴀʀᴋ ʏᴏᴜʀsᴇʟғ ᴀs AFK (ᴀᴡᴀʏ ғʀᴏᴍ ᴋᴇʏʙᴏᴀʀᴅ). ᴡʜᴇɴ ᴍᴀʀᴋᴇᴅ ᴀs ᴀғᴋ, ᴀɴʏ ᴍᴇɴᴛɪᴏɴs ᴡɪʟʟ ʙᴇ ʀᴇᴘʟɪᴇᴅ ᴛᴏ ᴡɪᴛʜ ᴀᴍᴇssᴀɢᴇ ᴛᴏ sᴀʏ ʏᴏᴜ'ʀᴇ ɴᴏᴛ ᴀᴠᴀɪʟᴀʙʟᴇ!` + +*ᴍᴏʀᴇ ᴛʏᴘᴇ* +✪ byy|brb|bye <ʀᴇᴀꜱᴏɴ> *:* `sᴀᴍᴇ ᴀs ᴀғᴋ` +""" + + +AFK_HANDLER = DisableAbleCommandHandler("afk", afk) +AFK_REGEX_HANDLER = MessageHandler(Filters.regex("(?i)brb|(?i)bye|(?i)byy"), afk) +NO_AFK_HANDLER = MessageHandler(Filters.all & Filters.chat_type.groups, no_longer_afk) +AFK_REPLY_HANDLER = MessageHandler(Filters.all & Filters.chat_type.groups, reply_afk) + +dispatcher.add_handler(AFK_HANDLER, AFK_GROUP) +dispatcher.add_handler(AFK_REGEX_HANDLER, AFK_GROUP) +dispatcher.add_handler(NO_AFK_HANDLER, AFK_GROUP) +dispatcher.add_handler(AFK_REPLY_HANDLER, AFK_REPLY_GROUP) diff --git a/Exon/modules/alive.py b/Exon/modules/alive.py new file mode 100644 index 00000000..8bcabaf1 --- /dev/null +++ b/Exon/modules/alive.py @@ -0,0 +1,81 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + + +import asyncio +import random +from sys import version_info + +from pyrogram import __version__ as pver +from pyrogram import filters +from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup, Message +from telegram import __version__ as lver +from telethon import __version__ as tver + +from ABG.helper import PHOTO +from Exon import BOT_NAME +from Exon import BOT_USERNAME as fuck +from Exon import OWNER_USERNAME, SUPPORT_CHAT, UPDATES_CHANNEL, pgram + +ASAU = [ + [ + InlineKeyboardButton(text="ᴜᴘᴅᴀᴛᴇꜱ", url=f"https://t.me/{UPDATES_CHANNEL}"), + InlineKeyboardButton(text="ꜱᴜᴘᴘᴏʀᴛ", url=f"https://t.me/{SUPPORT_CHAT}"), + ], + [ + InlineKeyboardButton( + text="ᴀᴅᴅ ᴍᴇ ɪɴ ʏᴏᴜʀ ɢʀᴏᴜᴘ", + url=f"https://t.me/{fuck}?startgroup=true", + ), + ], +] + + +@pgram.on_message(filters.command("alive")) +async def restart(client, m: Message): + await m.delete() + accha = await m.reply("⚡") + await asyncio.sleep(1) + await accha.edit("ᴀʟɪᴠɪɴɢ..") + await asyncio.sleep(0.1) + await accha.edit("ᴀʟɪᴠɪɴɢ ʙᴀʙʏ ....") + await accha.delete() + await asyncio.sleep(0.1) + umm = await m.reply_sticker( + "CAACAgUAAx0CZIiVngABBHAzYwdi9OIVTQ7DYELAqMl46fgnK4wAAjsIAAKagolX-O0V64tvzK8pBA" + ) + await asyncio.sleep(0.1) + await m.reply_photo( + random.choice(PHOTO), + caption=f"""**ʜᴇʏ, ɪ ᴀᴍ {BOT_NAME}** + ▱▱▱▱▱▱▱▱▱▱▱▱ +» **ᴍʏ ᴏᴡɴᴇʀ :** [𝐀ʙɪsʜɴᴏɪ](https://t.me/{OWNER_USERNAME}) +» **ʟɪʙʀᴀʀʏ ᴠᴇʀsɪᴏɴ :** `{lver}` +» **ᴛᴇʟᴇᴛʜᴏɴ ᴠᴇʀsɪᴏɴ :** `{tver}` +» **ᴘʏʀᴏɢʀᴀᴍ ᴠᴇʀsɪᴏɴ :** `{pver}` +» **ᴘʏᴛʜᴏɴ ᴠᴇʀsɪᴏɴ :** `{version_info[0]}.{version_info[1]}.{version_info[2]}` +⍟ **ʙᴏᴛ ᴠᴇʀꜱɪᴏɴ :** `1.0` + ▱▱▱▱▱▱▱▱▱▱▱▱""", + reply_markup=InlineKeyboardMarkup(ASAU), + ) diff --git a/Exon/modules/anime.py b/Exon/modules/anime.py new file mode 100644 index 00000000..00460ef1 --- /dev/null +++ b/Exon/modules/anime.py @@ -0,0 +1,542 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import html + +import bs4 +import jikanpy +import requests +from telegram import ( + InlineKeyboardButton, + InlineKeyboardMarkup, + Message, + ParseMode, + Update, +) +from telegram.ext import CallbackContext + +from Exon import dispatcher +from Exon.modules.disable import DisableAbleCommandHandler + +info_btn = "ᴍᴏʀᴇ ɪɴғᴏʀᴍᴀᴛɪᴏɴ" +kaizoku_btn = "ᴋᴀɪᴢᴏᴋᴜ ☠️" +kayo_btn = "ᴋᴀʏᴏ 🏴‍☠️" +prequel_btn = "⬅️ ᴘʀᴇǫᴜᴇʟ" +sequel_btn = "sᴇǫᴜᴇʟ ➡️" +close_btn = "ᴄʟᴏsᴇ ❌" + + +def shorten(description, info="anilist.co"): + msg = "" + if len(description) > 700: + description = description[:500] + "...." + msg += f"\n*ᴅᴇsᴄʀɪᴘᴛɪᴏɴ*:\n{description}[Read More]({info})" + else: + msg += f"\n*ᴅᴇsᴄʀɪᴘᴛɪᴏɴ*:\n{description}" + return msg + + +# time formatter from uniborg +def t(milliseconds: int) -> str: + """ɪɴᴘᴜᴛs ᴛɪᴍᴇ ɪɴ ᴍɪʟʟɪsᴇᴄᴏɴᴅs, ᴛᴏ ɢᴇᴛ ʙᴇᴀᴜᴛɪғɪᴇᴅ ᴛɪᴍᴇ, + as string""" + seconds, milliseconds = divmod(milliseconds, 1000) + minutes, seconds = divmod(seconds, 60) + hours, minutes = divmod(minutes, 60) + days, hours = divmod(hours, 24) + tmp = ( + (f"{str(days)} Days, " if days else "") + + (f"{str(hours)} Hours, " if hours else "") + + (f"{str(minutes)} Minutes, " if minutes else "") + + (f"{str(seconds)} Seconds, " if seconds else "") + + (f"{str(milliseconds)} ms, " if milliseconds else "") + ) + + return tmp[:-2] + + +airing_query = """ +query ($id: Int,$search: String) { + Media (id: $id, type: ANIME,search: $search) { + id + episodes + title { + romaji + english + native + } + nextAiringEpisode { + airingAt + timeUntilAiring + episode + } + } +} +""" + +fav_query = """ +query ($id: Int) { + Media (id: $id, type: ANIME) { + id + title { + romaji + english + native + } + } +} +""" + +anime_query = """ +query ($id: Int,$search: String) { + Media (id: $id, type: ANIME,search: $search) { + id + title { + romaji + english + native + } + description (asHtml: false) + startDate{ + year + } + episodes + season + type + format + status + duration + siteUrl + studios{ + nodes{ + name + } + } + trailer{ + id + site + thumbnail + } + averageScore + genres + bannerImage + } +} +""" + +character_query = """ +query ($query: String) { + Character (search: $query) { + id + name { + first + last + full + native + } + siteUrl + image { + large + } + description(asHtml: false) + } +} +""" + +manga_query = """ +query ($id: Int,$search: String) { + Media (id: $id, type: MANGA,search: $search) { + id + title { + romaji + english + native + } + description (asHtml: false) + startDate{ + year + } + type + format + status + siteUrl + averageScore + genres + bannerImage + } +} +""" + +url = "https://graphql.anilist.co" + + +def extract_arg(message: Message): + split = message.text.split(" ", 1) + if len(split) > 1: + return split[1] + reply = message.reply_to_message + return reply.text if reply is not None else None + + +def airing(update: Update, context: CallbackContext): + message = update.effective_message + search_str = extract_arg(message) + if not search_str: + update.effective_message.reply_text( + "ᴛᴇʟʟ ᴀɴɪᴍᴇ ɴᴀᴍᴇ :) ( /airing <anime name>)", + ) + return + variables = {"search": search_str} + response = requests.post( + url, + json={"query": airing_query, "variables": variables}, + ).json()["data"]["Media"] + msg = f"*Name*: *{response['title']['romaji']}*(`{response['title']['native']}`)\n*ID*: `{response['id']}`" + if response["nextAiringEpisode"]: + time = response["nextAiringEpisode"]["timeUntilAiring"] * 1000 + time = t(time) + msg += f"\n*ᴇᴘɪsᴏᴅᴇ*: `{response['nextAiringEpisode']['episode']}`\n*Airing In*: `{time}`" + else: + msg += f"\n*ᴇᴘɪsᴏᴅᴇ*:{response['episodes']}\n*Status*: `N/A`" + update.effective_message.reply_text(msg, parse_mode=ParseMode.MARKDOWN) + + +def anime(update: Update, context: CallbackContext): + message = update.effective_message + search = extract_arg(message) + if not search: + update.effective_message.reply_text("ғᴏʀᴍᴀᴛ : /anime < anime name >") + return + variables = {"search": search} + json = requests.post( + url, + json={"query": anime_query, "variables": variables}, + ).json() + if "errors" in json.keys(): + update.effective_message.reply_text("ᴀɴɪᴍᴇ ɴᴏᴛ ғᴏᴜɴᴅ") + return + if json: + json = json["data"]["Media"] + msg = f"*{json['title']['romaji']}*(`{json['title']['native']}`)\n*Type*: {json['format']}\n*Status*: {json['status']}\n*Episodes*: {json.get('episodes', 'N/A')}\n*Duration*: {json.get('duration', 'N/A')} Per Ep.\n*Score*: {json['averageScore']}\n*Genres*: `" + for x in json["genres"]: + msg += f"{x}, " + msg = msg[:-2] + "`\n" + msg += "*Studios*: `" + for x in json["studios"]["nodes"]: + msg += f"{x['name']}, " + msg = msg[:-2] + "`\n" + info = json.get("siteUrl") + trailer = json.get("trailer", None) + json["id"] + if trailer: + trailer_id = trailer.get("id", None) + site = trailer.get("site", None) + if site == "youtube": + trailer = f"https://youtu.be/{trailer_id}" + description = ( + json.get("description", "N/A") + .replace("<i>", "") + .replace("</i>", "") + .replace("<br>", "") + .replace("~!", "") + .replace("!~", "") + ) + msg += shorten(description, info) + image = json.get("bannerImage", None) + if trailer: + buttons = [ + [ + InlineKeyboardButton("ᴍᴏʀᴇ ɪɴғᴏ", url=info), + InlineKeyboardButton("ᴛʀᴀɪʟᴇʀ 🎬", url=trailer), + ], + ] + else: + buttons = [[InlineKeyboardButton("ᴍᴏʀᴇ ɪɴғᴏ", url=info)]] + if image: + try: + update.effective_message.reply_photo( + photo=image, + caption=msg, + parse_mode=ParseMode.MARKDOWN, + reply_markup=InlineKeyboardMarkup(buttons), + ) + except: + msg += f" [〽️]({image})" + update.effective_message.reply_text( + msg, + parse_mode=ParseMode.MARKDOWN, + reply_markup=InlineKeyboardMarkup(buttons), + ) + else: + update.effective_message.reply_text( + msg, + parse_mode=ParseMode.MARKDOWN, + reply_markup=InlineKeyboardMarkup(buttons), + ) + + +def character(update: Update, context: CallbackContext): + message = update.effective_message + search = extract_arg(message) + if not search: + update.effective_message.reply_text("ғᴏʀᴍᴀᴛ : /character < character name >") + return + variables = {"query": search} + json = requests.post( + url, + json={"query": character_query, "variables": variables}, + ).json() + if "errors" in json.keys(): + update.effective_message.reply_text("ᴄʜᴀʀᴀᴄᴛᴇʀ ɴᴏᴛ ғᴏᴜɴᴅ") + return + if json: + json = json["data"]["Character"] + msg = f"*{json.get('name').get('full')}* (`{json.get('name').get('native')}`)\n" + description = f"{json['description']}".replace("~!", "").replace("!~", "") + site_url = json.get("siteUrl") + msg += shorten(description, site_url) + if image := json.get("image", None): + image = image.get("large") + update.effective_message.reply_photo( + photo=image, + caption=msg.replace("<b>", "</b>"), + parse_mode=ParseMode.MARKDOWN, + ) + else: + update.effective_message.reply_text( + msg.replace("<b>", "</b>"), + parse_mode=ParseMode.MARKDOWN, + ) + + +def manga(update: Update, context: CallbackContext): + message = update.effective_message + search = extract_arg(message) + if not search: + update.effective_message.reply_text("ғᴏʀᴍᴀᴛ : /manga < manga name >") + return + variables = {"search": search} + json = requests.post( + url, + json={"query": manga_query, "variables": variables}, + ).json() + msg = "" + if "errors" in json.keys(): + update.effective_message.reply_text("Manga not found") + return + if json: + json = json["data"]["Media"] + title, title_native = json["title"].get("romaji", False), json["title"].get( + "native", + False, + ) + start_date, status, score = ( + json["startDate"].get("year", False), + json.get("status", False), + json.get("averageScore", False), + ) + if title: + msg += f"*{title}*" + if title_native: + msg += f"(`{title_native}`)" + if start_date: + msg += f"\n*sᴛᴀʀᴛ ᴅᴀᴛᴇ* - `{start_date}`" + if status: + msg += f"\n*sᴛᴀᴛᴜs* - `{status}`" + if score: + msg += f"\n*sᴄᴏʀᴇ* - `{score}`" + msg += "\n*ɢᴇɴʀᴇs* - " + for x in json.get("genres", []): + msg += f"{x}, " + msg = msg[:-2] + info = json["siteUrl"] + buttons = [[InlineKeyboardButton("ᴍᴏʀᴇ ɪɴғᴏ", url=info)]] + image = json.get("bannerImage", False) + msg += f"\n_{json.get('description', None)}_" + if image: + try: + update.effective_message.reply_photo( + photo=image, + caption=msg, + parse_mode=ParseMode.MARKDOWN, + reply_markup=InlineKeyboardMarkup(buttons), + ) + except: + msg += f" [〽️]({image})" + update.effective_message.reply_text( + msg, + parse_mode=ParseMode.MARKDOWN, + reply_markup=InlineKeyboardMarkup(buttons), + ) + else: + update.effective_message.reply_text( + msg, + parse_mode=ParseMode.MARKDOWN, + reply_markup=InlineKeyboardMarkup(buttons), + ) + + +def upcoming(update: Update, context: CallbackContext): + jikan = jikanpy.jikan.Jikan() + upcomin = jikan.top("anime", page=1, subtype="upcoming") + + upcoming_list = [entry["title"] for entry in upcomin["top"]] + upcoming_message = "" + + for entry_num in range(len(upcoming_list)): + if entry_num == 10: + break + upcoming_message += f"{entry_num + 1}. {upcoming_list[entry_num]}\n" + + update.effective_message.reply_text(upcoming_message) + + +def site_search(update: Update, context: CallbackContext, site: str): + message = update.effective_message + search_query = extract_arg(message) + more_results = True + + if not search_query: + message.reply_text("ɢɪᴠᴇ sᴏᴍᴇᴛʜɪɴɢ ᴛᴏ sᴇᴀʀᴄʜ") + return + + if site == "kaizoku": + search_url = f"https://animekaizoku.com/?s={search_query}" + html_text = requests.get(search_url).text + soup = bs4.BeautifulSoup(html_text, "html.parser") + if search_result := soup.find_all("h2", {"class": "post-title"}): + result = f"<b>sᴇᴀʀᴄʜ ʀᴇsᴜʟᴛs ғᴏʀ</b> <code>{html.escape(search_query)}</code> <b>on</b> @KaizokuAnime: \n\n" + for entry in search_result: + post_link = "https://animekaizoku.com/" + entry.a["href"] + post_name = html.escape(entry.text) + result += f"• <a href='{post_link}'>{post_name}</a>\n" + else: + more_results = False + result = f"<b>ɴᴏ ʀᴇsᴜʟᴛ ғᴏᴜɴᴅ ғᴏʀ</b> <code>{html.escape(search_query)}</code> <b>on</b> @KaizokuAnime" + + post_link = entry.a["href"] + post_name = html.escape(entry.text.strip()) + result += f"• <a href='{post_link}'>{post_name}</a>\n" + + buttons = [[InlineKeyboardButton("sᴇᴇ ᴀʟʟ ʀᴇsᴜʟᴛs", url=search_url)]] + + if more_results: + message.reply_text( + result, + parse_mode=ParseMode.HTML, + reply_markup=InlineKeyboardMarkup(buttons), + disable_web_page_preview=True, + ) + else: + message.reply_text( + result, + parse_mode=ParseMode.HTML, + disable_web_page_preview=True, + ) + + +def kaizoku(update: Update, context: CallbackContext): + site_search(update, context, "kaizoku") + + +__help__ = """ +ɢᴇᴛ ɪɴғᴏʀᴍᴀᴛɪᴏɴ ᴀʙᴏᴜᴛ ᴀɴɪᴍᴇ, ᴍᴀɴɢᴀ ᴏʀ ᴄʜᴀʀᴀᴄᴛᴇʀs ғʀᴏᴍ [ᴀɴɪʟɪsᴛ](anilist.co) ᴀɴᴅ [ᴍᴀʟ](https://myanimelist.net/) + +*ᴀɴɪʟɪsᴛ ᴄᴏᴍᴍᴀɴᴅs:* + +• /anime <anime>*:* `ʀᴇᴛᴜʀɴs ɪɴғᴏʀᴍᴀᴛɪᴏɴ ᴀʙᴏᴜᴛ ᴛʜᴇ ᴀɴɪᴍᴇ ғʀᴏᴍ ᴀɴɪʟɪsᴛ ` + +• /character <character>*:* `ʀᴇᴛᴜʀɴs ɪɴғᴏʀᴍᴀᴛɪᴏɴ ᴀʙᴏᴜᴛ ᴛʜᴇ ᴄʜᴀʀᴀᴄᴛᴇʀ ғʀᴏᴍ ᴀɴɪʟɪsᴛ ` + +• /manga <manga>*:* `ʀᴇᴛᴜʀɴs ɪɴғᴏʀᴍᴀᴛɪᴏɴ ᴀʙᴏᴜᴛ ᴛʜᴇ ᴍᴀɴɢᴀ ғʀᴏᴍ ᴀɴɪʟɪsᴛ ` + +• /upcoming*:* `ʀᴇᴛᴜʀɴs ᴀ ʟɪsᴛ ᴏғ ɴᴇᴡ ᴀɴɪᴍᴇ ɪɴ ᴛʜᴇ upcoming sᴇᴀsᴏɴs ғʀᴏᴍ ᴀɴɪʟɪsᴛ ` + +• /airing <anime>*:* `ʀᴇᴛᴜʀɴs ᴀɴɪᴍᴇ ᴀɪʀɪɴɢ ɪɴғᴏ ғʀᴏᴍ ᴀɴɪʟɪsᴛ ` + +*ᴀɴɪᴍᴇ sᴇᴀʀᴄʜ ᴄᴏᴍᴍᴀɴᴅs:* + +• /kaizoku*:* `sᴇᴀʀᴄʜ ᴀɴ ᴀɴɪᴍᴇ ᴏɴ ᴀɴɪᴍᴇᴋᴀɪᴢᴏᴋᴜ ᴡᴇʙsɪᴛᴇ` + +*ᴀɴɪᴍᴇ ᴜᴛɪʟs:* + +• /fillers <ᴀɴɪᴍᴇ ɴᴀᴍᴇ>*:* `ɢᴇᴛs ʏᴏᴜ ᴛʜᴇ ғɪʟʟᴇʀ ᴇᴘɪsᴏᴅᴇs ʟɪsᴛ ғᴏʀ ᴛʜᴇ ɪɴᴘᴜᴛ ᴀɴɪᴍᴇ ` +• /fillers -n<ɴᴜᴍʙᴇʀ> <ᴀɴɪᴍᴇ ɴᴀᴍᴇ>*:* `ғɪʟʟᴇʀ ᴇᴘɪsᴏᴅᴇs ʟɪsᴛ ᴄʜᴏsᴇɴ ғʀᴏᴍ ᴛʜᴇ ʟɪsᴛ ᴏғ ᴀɴɪᴍᴇ` + +*ᴇxᴀᴍᴘʟᴇ:* /fillers naruto - ᴡɪʟʟ ɢᴇᴛ ʏᴏᴜ ᴛʜᴇ ʟɪsᴛ ᴏғ ɴᴀʀᴜᴛᴏ ᴀɴᴅ ᴍᴀᴛᴄʜɪɴɢ sᴇʀɪᴇs + /fillers -n1 naruto - ᴡɪʟʟ ᴄʜᴏsᴇ ᴛᴏ sʜᴏᴡ ᴛʜᴇ ғɪʟʟᴇʀ ᴇᴘɪsᴏᴅᴇs ғᴏʀ ᴛʜᴇ 1sᴛ ᴀɴɪᴍᴇ ᴛᴏᴏᴋ ғʀᴏᴍ ᴛʜᴇ ʟɪsᴛᴇᴅ sᴇʀɪᴇs + +*NOTE*: ғɪʟʟᴇʀ ᴇᴘɪsᴏᴅᴇs ᴀʀᴇ ᴛʜᴏsᴇ ᴇᴘɪsᴏᴅᴇs ᴡʜɪᴄʜ ᴀʀᴇ ɴᴏᴛ ᴄᴏɴɴᴇᴄᴛᴇᴅ ᴡɪᴛʜ ᴛʜᴇ ᴍᴀɪɴ sᴛᴏʀʏ ʟɪɴᴇ + +• /schedule <ᴡᴇᴇᴋᴅᴀʏ>*:* `ɢᴇᴛs ʏᴏᴜ ᴛʜᴇ ᴀɴɪᴍᴇ ᴇᴘɪsᴏᴅᴇs sᴄʜᴇᴅᴜʟᴇᴅ ᴛᴏ ʙᴇ ᴀɪʀᴇᴅ ᴏɴ ᴛʜᴀᴛ ᴅᴀʏ` + +*ᴇxᴀᴍᴘʟᴇ:* /schedule monday ᴏʀ /schedule 0 + + +• /animequotes *:* `ɢᴇᴛ ǫᴜᴏᴛᴇs ɪɴ ᴘɪᴄᴛᴜʀᴇ` + +• /quote*:* `ɢᴇᴛ ᴛᴇxᴛ ǫᴜᴏᴛᴇs` + +""" + + +ANIME_HANDLER = DisableAbleCommandHandler("anime", anime, run_async=True) +AIRING_HANDLER = DisableAbleCommandHandler("airing", airing, run_async=True) +CHARACTER_HANDLER = DisableAbleCommandHandler("character", character, run_async=True) +MANGA_HANDLER = DisableAbleCommandHandler("manga", manga, run_async=True) +##USER_HANDLER = DisableAbleCommandHandler("user", user, run_async=True) +UPCOMING_HANDLER = DisableAbleCommandHandler("upcoming", upcoming, run_async=True) +KAIZOKU_SEARCH_HANDLER = DisableAbleCommandHandler("kaizoku", kaizoku, run_async=True) +##KAYO_SEARCH_HANDLER = DisableAbleCommandHandler("kayo", kayo, run_async=True) + +dispatcher.add_handler(ANIME_HANDLER) +dispatcher.add_handler(CHARACTER_HANDLER) +dispatcher.add_handler(MANGA_HANDLER) +dispatcher.add_handler(AIRING_HANDLER) +# dispatcher.add_handler(USER_HANDLER) +dispatcher.add_handler(KAIZOKU_SEARCH_HANDLER) +# dispatcher.add_handler(KAYO_SEARCH_HANDLER) +dispatcher.add_handler(UPCOMING_HANDLER) + +__mod_name__ = "𝙰ɴɪᴍᴇ" +__command_list__ = [ + "anime", + "manga", + "character", + "user", + "upcoming", + "airing", + "kayo", + "kaizoku", +] +__handlers__ = [ + ANIME_HANDLER, + CHARACTER_HANDLER, + MANGA_HANDLER, + # USER_HANDLER, + UPCOMING_HANDLER, + # KAYO_SEARCH_HANDLER, + AIRING_HANDLER, + KAIZOKU_SEARCH_HANDLER, +] diff --git a/Exon/modules/animequote.py b/Exon/modules/animequote.py new file mode 100644 index 00000000..b9c4879b --- /dev/null +++ b/Exon/modules/animequote.py @@ -0,0 +1,183 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import json +import random + +import requests +from telegram import InlineKeyboardButton, InlineKeyboardMarkup, ParseMode, Update +from telegram.ext import CallbackContext, CallbackQueryHandler + +from Exon import dispatcher +from Exon.modules.disable import DisableAbleCommandHandler + + +def anime_quote(): + url = "https://animechan.vercel.app/api/random" + # since text attribute returns dictionary like string + response = requests.get(url) + try: + dic = json.loads(response.text) + except Exception: + pass + quote = dic["quote"] + character = dic["character"] + anime = dic["anime"] + return quote, character, anime + + +def quotes(update: Update, context: CallbackContext): + message = update.effective_message + quote, character, anime = anime_quote() + msg = f"<i>❝ {quote}❞</i>\n\n<b>{character} from {anime}</b>" + keyboard = InlineKeyboardMarkup( + [[InlineKeyboardButton(text="ᴄʜᴀɴɢᴇ 🔁", callback_data="change_quote")]] + ) + message.reply_text( + msg, + reply_markup=keyboard, + parse_mode=ParseMode.HTML, + ) + + +def change_quote(update: Update, context: CallbackContext): + update.callback_query + update.effective_chat + message = update.effective_message + quote, character, anime = anime_quote() + msg = f"<i>❝ {quote}❞</i>\n\n<b>{character} from {anime}</b>" + keyboard = InlineKeyboardMarkup( + [[InlineKeyboardButton(text="ᴄʜᴀɴɢᴇ 🔁", callback_data="quote_change")]] + ) + message.edit_text(msg, reply_markup=keyboard, parse_mode=ParseMode.HTML) + + +def animequotes(update: Update, context: CallbackContext): + message = update.effective_message + message.reply_to_message.from_user.first_name if message.reply_to_message else message.from_user.first_name + keyboard = [[InlineKeyboardButton(text="ᴄʜᴀɴɢᴇ 🔄", callback_data="changek_quote")]] + message.reply_photo( + random.choice(QUOTES_IMG), reply_markup=InlineKeyboardMarkup(keyboard) + ) + + +def changek_quote(update: Update, context: CallbackContext): + update.callback_query + update.effective_chat + message = update.effective_message + keyboard = [[InlineKeyboardButton(text="ᴄʜᴀɴɢᴇ 🔃", callback_data="quotek_change")]] + message.reply_photo( + random.choice(QUOTES_IMG), reply_markup=InlineKeyboardMarkup(keyboard) + ) + + +QUOTES_IMG = ( + "https://i.imgur.com/Iub4RYj.jpg", + "https://i.imgur.com/uvNMdIl.jpg", + "https://i.imgur.com/YOBOntg.jpg", + "https://i.imgur.com/fFpO2ZQ.jpg", + "https://i.imgur.com/f0xZceK.jpg", + "https://i.imgur.com/RlVcCip.jpg", + "https://i.imgur.com/CjpqLRF.jpg", + "https://i.imgur.com/8BHZDk6.jpg", + "https://i.imgur.com/8bHeMgy.jpg", + "https://i.imgur.com/5K3lMvr.jpg", + "https://i.imgur.com/NTzw4RN.jpg", + "https://i.imgur.com/wJxryAn.jpg", + "https://i.imgur.com/9L0DWzC.jpg", + "https://i.imgur.com/sBe8TTs.jpg", + "https://i.imgur.com/1Au8gdf.jpg", + "https://i.imgur.com/28hFQeU.jpg", + "https://i.imgur.com/Qvc03JY.jpg", + "https://i.imgur.com/gSX6Xlf.jpg", + "https://i.imgur.com/iP26Hwa.jpg", + "https://i.imgur.com/uSsJoX8.jpg", + "https://i.imgur.com/OvX3oHB.jpg", + "https://i.imgur.com/JMWuksm.jpg", + "https://i.imgur.com/lhM3fib.jpg", + "https://i.imgur.com/64IYKkw.jpg", + "https://i.imgur.com/nMbyA3J.jpg", + "https://i.imgur.com/7KFQhY3.jpg", + "https://i.imgur.com/mlKb7zt.jpg", + "https://i.imgur.com/JCQGJVw.jpg", + "https://i.imgur.com/hSFYDEz.jpg", + "https://i.imgur.com/PQRjAgl.jpg", + "https://i.imgur.com/ot9624U.jpg", + "https://i.imgur.com/iXmqN9y.jpg", + "https://i.imgur.com/RhNBeGr.jpg", + "https://i.imgur.com/tcMVNa8.jpg", + "https://i.imgur.com/LrVg810.jpg", + "https://i.imgur.com/TcWfQlz.jpg", + "https://i.imgur.com/muAUdvJ.jpg", + "https://i.imgur.com/AtC7ZRV.jpg", + "https://i.imgur.com/sCObQCQ.jpg", + "https://i.imgur.com/AJFDI1r.jpg", + "https://i.imgur.com/TCgmRrH.jpg", + "https://i.imgur.com/LMdmhJU.jpg", + "https://i.imgur.com/eyyax0N.jpg", + "https://i.imgur.com/YtYxV66.jpg", + "https://i.imgur.com/292w4ye.jpg", + "https://i.imgur.com/6Fm1vdw.jpg", + "https://i.imgur.com/2vnBOZd.jpg", + "https://i.imgur.com/j5hI9Eb.jpg", + "https://i.imgur.com/cAv7pJB.jpg", + "https://i.imgur.com/jvI7Vil.jpg", + "https://i.imgur.com/fANpjsg.jpg", + "https://i.imgur.com/5o1SJyo.jpg", + "https://i.imgur.com/dSVxmh8.jpg", + "https://i.imgur.com/02dXlAD.jpg", + "https://i.imgur.com/htvIoGY.jpg", + "https://i.imgur.com/hy6BXOj.jpg", + "https://i.imgur.com/OuwzNYu.jpg", + "https://i.imgur.com/L8vwvc2.jpg", + "https://i.imgur.com/3VMVF9y.jpg", + "https://i.imgur.com/yzjq2n2.jpg", + "https://i.imgur.com/0qK7TAN.jpg", + "https://i.imgur.com/zvcxSOX.jpg", + "https://i.imgur.com/FO7bApW.jpg", + "https://i.imgur.com/KK06gwg.jpg", + "https://i.imgur.com/6lG4tsO.jpg", +) + + +ANIMEQUOTES_HANDLER = DisableAbleCommandHandler( + "animequotes", animequotes, run_async=True +) +QUOTES_HANDLER = DisableAbleCommandHandler("quote", quotes, run_async=True) + +CHANGE_QUOTE = CallbackQueryHandler(change_quote, pattern=r"change_.*") +QUOTE_CHANGE = CallbackQueryHandler(change_quote, pattern=r"quote_.*") +CHANGEK_QUOTE = CallbackQueryHandler(changek_quote, pattern=r"changek_.*") +QUOTEK_CHANGE = CallbackQueryHandler(changek_quote, pattern=r"quotek_.*") + +dispatcher.add_handler(CHANGE_QUOTE) +dispatcher.add_handler(QUOTE_CHANGE) +dispatcher.add_handler(CHANGEK_QUOTE) +dispatcher.add_handler(QUOTEK_CHANGE) +dispatcher.add_handler(ANIMEQUOTES_HANDLER) +dispatcher.add_handler(QUOTES_HANDLER) + +__command_list__ = ["animequotes", "quote"] + +__handlers__ = [ANIMEQUOTES_HANDLER, QUOTES_HANDLER] diff --git a/Exon/modules/antichannel.py b/Exon/modules/antichannel.py new file mode 100644 index 00000000..109d3d11 --- /dev/null +++ b/Exon/modules/antichannel.py @@ -0,0 +1,98 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import html + +from telegram import Update +from telegram.ext import CallbackContext +from telegram.ext.filters import Filters + +from Exon.modules.helper_funcs.anonymous import AdminPerms, user_admin +from Exon.modules.helper_funcs.decorators import Exoncmd, Exonmsg +from Exon.modules.sql.antichannel_sql import ( + antichannel_status, + disable_antichannel, + enable_antichannel, +) + + +@Exoncmd(command="antichannelmode", group=100) +@user_admin(AdminPerms.CAN_RESTRICT_MEMBERS) +def set_antichannel(update: Update, context: CallbackContext): + message = update.effective_message + chat = update.effective_chat + args = context.args + if len(args) > 0: + s = args[0].lower() + if s in ["yes", "on"]: + enable_antichannel(chat.id) + message.reply_html(f"ᴇɴᴀʙʟᴇᴅ 𝗔𝗻𝘁𝗶𝗰𝗵𝗮𝗻𝗻𝗲𝗹 ɪɴ {html.escape(chat.title)}") + elif s in ["off", "no"]: + disable_antichannel(chat.id) + message.reply_html(f"ᴅɪsᴀʙʟᴇᴅ 𝗔𝗻𝘁𝗶𝗰𝗵𝗮𝗻𝗻𝗲𝗹 ɪɴ {html.escape(chat.title)}") + else: + message.reply_text(f"ᴜɴʀᴇᴄᴏɢɴɪᴢᴇᴅ ᴀʀɢᴜᴍᴇɴᴛs {s}") + return + message.reply_html( + f"ᴀɴᴛɪᴄʜᴀɴɴᴇʟ sᴇᴛᴛɪɴɢ ɪs ᴄᴜʀʀᴇɴᴛʟʏ {antichannel_status(chat.id)} ɪɴ {html.escape(chat.title)}" + ) + + +@Exonmsg(Filters.chat_type.groups, group=110) +def eliminate_channel(update: Update, context: CallbackContext): + message = update.effective_message + chat = update.effective_chat + bot = context.bot + if not antichannel_status(chat.id): + return + if ( + message.sender_chat + and message.sender_chat.type == "channel" + and not message.is_automatic_forward + ): + message.delete() + sender_chat = message.sender_chat + bot.ban_chat_sender_chat(sender_chat_id=sender_chat.id, chat_id=chat.id) + + +__mod_name__ = "𝙰ɴᴛɪ-ᴄʜᴀɴɴᴇʟ" + +__help__ = """ + + ⚠️ ᴡᴀʀɴɪɴɢ ⚠️ + +ɪғ ʏᴏᴜ ᴜsᴇ ᴛʜɪs ᴍᴏᴅᴇ, ᴛʜᴇ ʀᴇsᴜʟᴛ ɪs, ɪɴ ᴛʜᴇ ɢʀᴏᴜᴘ, ʏᴏᴜ ᴄᴀɴ'ᴛ ᴄʜᴀᴛ ᴜsɪɴɢ ᴛʜᴇ ᴄʜᴀɴɴᴇʟ ғᴏʀ ғᴏʀᴇᴠᴇʀ ɪғ ʏᴏᴜ ɢᴇᴛ ʙᴀɴɴᴇᴅ ᴏɴᴄᴇ, +ᴀɴᴛɪ ᴄʜᴀɴɴᴇʟ ᴍᴏᴅᴇ ɪs ᴀ ᴍᴏᴅᴇ ᴛᴏ ᴀᴜᴛᴏᴍᴀᴛɪᴄᴀʟʟʏ ʙᴀɴ ᴜsᴇʀs ᴡʜᴏ ᴄʜᴀᴛ ᴜsɪɴɢ ᴄʜᴀɴɴᴇʟs. +ᴛʜɪs ᴄᴏᴍᴍᴀɴᴅ ᴄᴀɴ ᴏɴʟʏ ʙᴇ ᴜsᴇᴅ ʙʏ ᴀᴅᴍɪɴs. + +/antichannelmode <'ᴏɴ/'ʏᴇs> : `ᴇɴᴀʙʟᴇs ᴀɴᴛɪ-ᴄʜᴀɴɴᴇʟ ᴍᴏᴅᴇ ʙᴀɴ` + +/antichannelmode <'ᴏғғ/'ɴᴏ> : `ᴅɪsᴀʙʟᴇᴅ ᴀɴᴛɪ-ᴄʜᴀɴɴᴇʟ ᴍᴏᴅᴇ ʙᴀɴ` + +/cleanlinked on : `ᴇɴᴀʙʟᴇs ᴄʜᴀɴɴᴇʟ ʟɪɴᴋ` + +/antichannelpin on : `ᴀɴᴛɪ-ᴄʜᴀɴɴᴇʟ ᴘɪɴ ᴍᴏᴅᴇ` + +/antiservice <'ᴏɴ/'ᴏғғ> : `ᴅᴇʟᴇᴛᴇ sᴇʀᴠɪᴄᴇ ᴍsɢ. ` +""" diff --git a/Exon/modules/antiflood.py b/Exon/modules/antiflood.py new file mode 100644 index 00000000..92aed2d2 --- /dev/null +++ b/Exon/modules/antiflood.py @@ -0,0 +1,383 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import html +from typing import Optional + +from telegram import Chat, ChatPermissions, Message, ParseMode, User +from telegram.error import BadRequest +from telegram.ext import CommandHandler, Filters, MessageHandler +from telegram.utils.helpers import mention_html + +from Exon import dispatcher +from Exon.modules.connection import connected +from Exon.modules.helper_funcs.alternate import send_message, typing_action +from Exon.modules.helper_funcs.chat_status import is_user_admin, user_admin +from Exon.modules.helper_funcs.string_handling import extract_time +from Exon.modules.log_channel import loggable +from Exon.modules.sql import antiflood_sql as sql +from Exon.modules.sql.approve_sql import is_approved + +FLOOD_GROUP = 3 + + +@loggable +def check_flood(update, context) -> str: + user = update.effective_user # type: Optional[User] + chat = update.effective_chat # type: Optional[Chat] + msg = update.effective_message # type: Optional[Message] + + if is_approved(chat.id, user.id): + sql.update_flood(chat.id, None) + return + + if not user: # ignore channels + return "" + + # ignore admins + if is_user_admin(chat, user.id): + sql.update_flood(chat.id, None) + return "" + + should_ban = sql.update_flood(chat.id, user.id) + if not should_ban: + return "" + + try: + getmode, getvalue = sql.get_flood_setting(chat.id) + if getmode == 1: + chat.ban_member(user.id) + execstrings = "ʙᴀɴɴᴇᴅ" + tag = "BANNED" + elif getmode == 2: + chat.ban_member(user.id) + chat.unban_member(user.id) + execstrings = "ᴋɪᴄᴋᴇᴅ" + tag = "KICKED" + elif getmode == 3: + context.bot.restrict_chat_member( + chat.id, user.id, permissions=ChatPermissions(can_send_messages=False) + ) + execstrings = "ᴍᴜᴛᴇᴅ" + tag = "MUTED" + elif getmode == 4: + bantime = extract_time(msg, getvalue) + chat.ban_member(user.id, until_date=bantime) + execstrings = f"ʙᴀɴɴᴇᴅ ғᴏʀ {getvalue}" + tag = "TBAN" + elif getmode == 5: + mutetime = extract_time(msg, getvalue) + context.bot.restrict_chat_member( + chat.id, + user.id, + until_date=mutetime, + permissions=ChatPermissions(can_send_messages=False), + ) + execstrings = f"ᴍᴜᴛᴇᴅ ғᴏʀ {getvalue}" + tag = "TMUTE" + send_message( + update.effective_message, + f"ᴡᴀɴɴᴀ sᴘᴀᴍ?! sᴏʀʀʏ ɪᴛ's ɴᴏᴛ ʏᴏᴜʀ ʜᴏᴜsᴇ ᴍᴀɴ!\n{execstrings}!", + ) + + return f"<b>{html.escape(chat.title)}:</b>\n#{tag}\n<b>User:</b> {mention_html(user.id, user.first_name)}\nғʟᴏᴏᴅᴇᴅ ᴛʜᴇ ɢʀᴏᴜᴘ." + + except BadRequest: + msg.reply_text( + "I ᴄᴀɴ'ᴛ ʀᴇsᴛʀɪᴄᴛ ᴘᴇᴏᴘʟᴇ ʜᴇʀᴇ, ɢɪᴠᴇ ᴍᴇ ᴘᴇʀᴍɪssɪᴏɴs ғɪʀsᴛ! ᴜɴᴛɪʟ ᴛʜᴇɴ, ɪ'ʟʟ ᴅɪsᴀʙʟᴇ ᴀɴᴛɪ-ғʟᴏᴏᴅ." + ) + sql.set_flood(chat.id, 0) + return f"<b>{chat.title}:</b>\n#INFO\nᴅᴏɴ'ᴛ ʜᴀᴠᴇ ᴇɴᴏᴜɢʜ ᴘᴇʀᴍɪssɪᴏɴ ᴛᴏ ʀᴇsᴛʀɪᴄᴛ ᴜsᴇʀs sᴏ ᴀᴜᴛᴏᴍᴀᴛɪᴄᴀʟʟʏ ᴅɪsᴀʙʟᴇᴅ ᴀɴᴛɪ-ғʟᴏᴏᴅ" + + +@user_admin +@loggable +@typing_action +def set_flood(update, context) -> str: + chat = update.effective_chat # type: Optional[Chat] + user = update.effective_user # type: Optional[User] + message = update.effective_message # type: Optional[Message] + args = context.args + + conn = connected(context.bot, update, chat, user.id, need_admin=True) + if conn: + chat_id = conn + chat_name = dispatcher.bot.getChat(conn).title + else: + if update.effective_message.chat.type == "private": + send_message( + update.effective_message, + "ᴛʜɪs ᴄᴏᴍᴍᴀɴᴅ ɪs ᴍᴇᴀɴᴛ ᴛᴏ ᴜsᴇ ɪɴ ɢʀᴏᴜᴘ ɴᴏᴛ ɪɴ PM", + ) + return "" + chat_id = update.effective_chat.id + chat_name = update.effective_message.chat.title + + if len(args) >= 1: + val = args[0].lower() + if val in ("off", "no", "0"): + sql.set_flood(chat_id, 0) + if conn: + text = message.reply_text( + f"ᴀɴᴛɪғʟᴏᴏᴅ ʜᴀs ʙᴇᴇɴ ᴅɪsᴀʙʟᴇᴅ ɪɴ {chat_name}." + ) + else: + text = message.reply_text("ᴀɴᴛɪғʟᴏᴏᴅ ʜᴀs ʙᴇᴇɴ ᴅɪsᴀʙʟᴇᴅ.") + send_message(update.effective_message, text, parse_mode="markdown") + + elif val.isdigit(): + amount = int(val) + if amount <= 0: + sql.set_flood(chat_id, 0) + if conn: + text = message.reply_text( + f"ᴀɴᴛɪғʟᴏᴏᴅ ʜᴀs ʙᴇᴇɴ ᴅɪsᴀʙʟᴇᴅ ɪɴ {chat_name}." + ) + else: + text = message.reply_text("ᴀɴᴛɪғʟᴏᴏᴅ ʜᴀs ʙᴇᴇɴ ᴅɪsᴀʙʟᴇᴅ.") + return f"<b>{html.escape(chat_name)}:</b>\n#sᴇᴛғʟᴏᴏᴅ\n<b>Admin:</b> {mention_html(user.id, user.first_name)}\nᴅɪsᴀʙʟᴇ ᴀɴᴛɪғʟᴏᴏᴅ." + + if amount < 3: + send_message( + update.effective_message, + "ᴀɴᴛɪғʟᴏᴏᴅ ᴍᴜsᴛ ʙᴇ ᴇɪᴛʜᴇʀ 0 (disabled) ᴏʀ ɴᴜᴍʙᴇʀ ɢʀᴇᴀᴛᴇʀ ᴛʜᴀɴ 3!", + ) + return "" + sql.set_flood(chat_id, amount) + if conn: + text = message.reply_text( + f"ᴀɴᴛɪ-ғʟᴏᴏᴅ ʜᴀs ʙᴇᴇɴ sᴇᴛ ᴛᴏ {amount} ɪɴ ᴄʜᴀᴛ: {chat_name}" + ) + + else: + text = message.reply_text( + f"sᴜᴄᴄᴇssғᴜʟʟʏ ᴜᴘᴅᴀᴛᴇᴅ ᴀɴᴛɪ-ғʟᴏᴏᴅ ʟɪᴍɪᴛ ᴛᴏ {amount}!" + ) + + send_message(update.effective_message, text, parse_mode="markdown") + return f"<b>{html.escape(chat_name)}:</b>\n#SETFLOOD\n<b>Admin:</b> {mention_html(user.id, user.first_name)}\nSet antiflood to <code>{amount}</code>." + + else: + message.reply_text("ɪɴᴠᴀʟɪᴅ ᴀʀɢᴜᴍᴇɴᴛ ᴘʟᴇᴀsᴇ ᴜsᴇ ᴀ ɴᴜᴍʙᴇʀ, 'off' ᴏʀ 'no'") + else: + message.reply_text( + ( + "ᴜsᴇ `/setflood ɴᴜᴍʙᴇʀ` ᴛᴏ ᴇɴᴀʙʟᴇ ᴀɴᴛɪ-ғʟᴏᴏᴅ.\nᴏʀ ᴜsᴇ `/setflood off` ᴛᴏ ᴅɪsᴀʙʟᴇ ᴀɴᴛɪғʟᴏᴏᴅ!." + ), + parse_mode="markdown", + ) + return "" + + +@typing_action +def flood(update, context): + chat = update.effective_chat # type: Optional[Chat] + user = update.effective_user # type: Optional[User] + msg = update.effective_message + + conn = connected(context.bot, update, chat, user.id, need_admin=False) + if conn: + chat_id = conn + chat_name = dispatcher.bot.getChat(conn).title + else: + if update.effective_message.chat.type == "private": + send_message( + update.effective_message, + "ᴛʜɪs ᴄᴏᴍᴍᴀɴᴅ ɪs ᴍᴇᴀɴᴛ ᴛᴏ ᴜsᴇ ɪɴ ɢʀᴏᴜᴘ ɴᴏᴛ ɪɴ PM", + ) + return + chat_id = update.effective_chat.id + chat_name = update.effective_message.chat.title + + limit = sql.get_flood_limit(chat_id) + if limit == 0: + text = ( + msg.reply_text(f"I'ᴍ ɴᴏᴛ ᴇɴғᴏʀᴄɪɴɢ ᴀɴʏ ғʟᴏᴏᴅ ᴄᴏɴᴛʀᴏʟ ɪɴ {chat_name}!") + if conn + else msg.reply_text("I'm ɴᴏᴛ ᴇɴғᴏʀᴄɪɴɢ ᴀɴʏ ғʟᴏᴏᴅ ᴄᴏɴᴛʀᴏʟ ʜᴇʀᴇ!") + ) + + elif conn: + text = msg.reply_text( + f"I'ᴍ ᴄᴜʀʀᴇɴᴛʟʏ restricting ᴍᴇᴍʙᴇʀs ᴀғᴛᴇʀ {limit} ᴄᴏɴsᴇᴄᴜᴛɪᴠᴇ ᴍᴇssᴀɢᴇs ɪɴ {chat_name}." + ) + + else: + text = msg.reply_text( + f"I'm ᴄᴜʀʀᴇɴᴛʟʏ ʀᴇsᴛʀɪᴄᴛɪɴɢ ᴍᴇᴍʙᴇʀs ᴀғᴛᴇʀ {limit} ᴄᴏɴsᴇᴄᴜᴛɪᴠᴇ ᴍᴇssᴀɢᴇs." + ) + + send_message(update.effective_message, text, parse_mode="markdown") + + +@user_admin +@loggable +@typing_action +def set_flood_mode(update, context): + chat = update.effective_chat # type: Optional[Chat] + user = update.effective_user # type: Optional[User] + msg = update.effective_message # type: Optional[Message] + args = context.args + + conn = connected(context.bot, update, chat, user.id, need_admin=True) + if conn: + chat = dispatcher.bot.getChat(conn) + chat_id = conn + chat_name = dispatcher.bot.getChat(conn).title + else: + if update.effective_message.chat.type == "private": + send_message( + update.effective_message, + "ᴛʜɪs ᴄᴏᴍᴍᴀɴᴅ ɪs ᴍᴇᴀɴᴛ ᴛᴏ ᴜsᴇ ɪɴ ɢʀᴏᴜᴘ ɴᴏᴛ ɪɴ PM", + ) + return "" + chat = update.effective_chat + chat_id = update.effective_chat.id + chat_name = update.effective_message.chat.title + + if args: + if args[0].lower() == "ban": + settypeflood = "ʙᴀɴ" + sql.set_flood_strength(chat_id, 1, "0") + elif args[0].lower() == "kick": + settypeflood = "ᴋɪᴄᴋ" + sql.set_flood_strength(chat_id, 2, "0") + elif args[0].lower() == "mute": + settypeflood = "ᴍᴜᴛᴇ" + sql.set_flood_strength(chat_id, 3, "0") + elif args[0].lower() == "tban": + if len(args) == 1: + teks = """ɪᴛ ʟᴏᴏᴋs ʟɪᴋᴇ ʏᴏᴜ ᴛʀɪᴇᴅ ᴛᴏ sᴇᴛ ᴛɪᴍᴇ ᴠᴀʟᴜᴇ ғᴏʀ ᴀɴᴛɪғʟᴏᴏᴅ ʙᴜᴛ ʏᴏᴜ ᴅɪᴅɴ'ᴛ sᴘᴇᴄɪғɪᴇᴅ ᴛɪᴍᴇ; ᴛʀʏ, `/setfloodmode tban <timevalue>`. + ᴇxᴀᴍᴘʟᴇs ᴏғ ᴛɪᴍᴇ ᴠᴀʟᴜᴇ: 4ᴍ = 4 ᴍɪɴᴜᴛᴇs, 3h = 3 ʜᴏᴜʀs, 6d = 6 ᴅᴀʏs, 5w = 5 ᴡᴇᴇᴋs.""" + send_message(update.effective_message, teks, parse_mode="markdown") + return + settypeflood = f"ᴛʙᴀɴ ғᴏʀ {args[1]}" + sql.set_flood_strength(chat_id, 4, str(args[1])) + elif args[0].lower() == "tmute": + if len(args) == 1: + teks = """It ʟᴏᴏᴋs ʟɪᴋᴇ ʏᴏᴜ ᴛʀɪᴇᴅ ᴛᴏ sᴇᴛ ᴛɪᴍᴇ ᴠᴀʟᴜᴇ ғᴏʀ ᴀɴᴛɪғʟᴏᴏᴅ ʙᴜᴛ ʏᴏᴜ ᴅɪᴅɴ'ᴛ sᴘᴇᴄɪғɪᴇᴅ ᴛɪᴍᴇ; ᴛʀʏ, `/setfloodmode tmute <timevalue>`. + ᴇxᴀᴍᴘʟᴇs ᴏғ ᴛɪᴍᴇ ᴠᴀʟᴜᴇ: 4m = 4 ᴍɪɴᴜᴛᴇs, 3h = 3 ʜᴏᴜʀs, 6d = 6 ᴅᴀʏs, 5w = 5 ᴡᴇᴇᴋs.""" + send_message(update.effective_message, teks, parse_mode="markdown") + return + settypeflood = f"ᴛᴍᴜᴛᴇ ғᴏʀ {args[1]}" + sql.set_flood_strength(chat_id, 5, str(args[1])) + else: + send_message( + update.effective_message, "I ᴏɴʟʏ ᴜɴᴅᴇʀsᴛᴀɴᴅ ban/kick/mute/tban/tmute!" + ) + return + if conn: + text = msg.reply_text( + f"ᴇxᴄᴇᴇᴅɪɴɢ ᴄᴏɴsᴇᴄᴜᴛɪᴠᴇ ғʟᴏᴏᴅ ʟɪᴍɪᴛ ᴡɪʟʟ ʀᴇsᴜʟᴛ ɪɴ {settypeflood} ɪɴ {chat_name}!" + ) + + else: + text = msg.reply_text( + f"ᴇxᴄᴇᴇᴅɪɴɢ ᴄᴏɴsᴇᴄᴜᴛɪᴠᴇ ғʟᴏᴏᴅ ʟɪᴍɪᴛ ᴡɪʟʟ ʀᴇsᴜʟᴛ ɪɴ {settypeflood}!" + ) + + send_message(update.effective_message, text, parse_mode="markdown") + return f"<b>{settypeflood}:</b>\n<b>Admin:</b> {html.escape(chat.title)}\nHas changed antiflood mode. User will {mention_html(user.id, user.first_name)}." + + getmode, getvalue = sql.get_flood_setting(chat.id) + if getmode == 1: + settypeflood = "ʙᴀɴ" + elif getmode == 2: + settypeflood = "ᴋɪᴄᴋ" + elif getmode == 3: + settypeflood = "ᴍᴜᴛᴇ" + elif getmode == 4: + settypeflood = f"ᴛʙᴀɴ ғᴏʀ {getvalue}" + elif getmode == 5: + settypeflood = f"ᴛᴍᴜᴛᴇ ғᴏʀ {getvalue}" + if conn: + text = msg.reply_text( + f"sᴇɴᴅɪɴɢ ᴍᴏʀᴇ ᴍᴇssᴀɢᴇs ᴛʜᴀɴ ғʟᴏᴏᴅ ʟɪᴍɪᴛ ᴡɪʟʟ ʀᴇsᴜʟᴛ ɪɴ {settypeflood} ɪɴ {chat_name}." + ) + + else: + text = msg.reply_text( + f"sᴇɴᴅɪɴɢ ᴍᴏʀᴇ ᴍᴇssᴀɢᴇ ᴛʜᴀɴ ғʟᴏᴏᴅ ʟɪᴍɪᴛ ᴡɪʟʟ ʀᴇsᴜʟᴛ ɪɴ {settypeflood}." + ) + + send_message(update.effective_message, text, parse_mode=ParseMode.MARKDOWN) + return "" + + +def __migrate__(old_chat_id, new_chat_id): + sql.migrate_chat(old_chat_id, new_chat_id) + + +def __chat_settings__(chat_id, user_id): + limit = sql.get_flood_limit(chat_id) + if limit == 0: + return "ɴᴏᴛ ᴇɴғᴏʀᴄɪɴɢ ᴛᴏ ғʟᴏᴏᴅ ᴄᴏɴᴛʀᴏʟ." + return f"ᴀɴᴛɪғʟᴏᴏᴅ ʜᴀs ʙᴇᴇɴ sᴇᴛ ᴛᴏ`{limit}`." + + +__help__ = """ +*ᴀʟʟᴏᴡs ʏᴏᴜ ᴛᴏ ᴛᴀᴋᴇ ᴀᴄᴛɪᴏɴ ᴏɴ ᴜsᴇʀs ᴛʜᴀᴛ sᴇɴᴅᴍᴏʀᴇᴛʜᴀɴ x ᴍᴇssᴀɢᴇs ɪɴ ᴀ ʀᴏᴡ. Exᴄᴇᴇᴅɪɴɢ ᴛʜᴇ sᴇᴛ ғʟᴏᴏᴅ \nᴡɪʟʟ ʀᴇsᴜʟᴛ ɪɴ ʀᴇsᴛʀɪᴄᴛɪɴɢ ᴛʜᴀᴛ ᴜsᴇʀ. +Tʜɪs ᴡɪʟʟ ᴍᴜᴛᴇ ᴜsᴇʀs ɪғ ᴛʜᴇʏ sᴇɴᴅ ᴍᴏʀᴇ ᴛʜᴀɴ 5 ᴍᴇssᴀɢᴇs ɪɴ ᴀ ʀᴏᴡ, ʙᴏᴛs ᴀʀᴇ ɪɢɴᴏʀᴇᴅ.* + +➥ /flood *:* `Gᴇᴛ ᴛʜᴇ ᴄᴜʀʀᴇɴᴛ ғʟᴏᴏᴅ ᴄᴏɴᴛʀᴏʟ sᴇᴛᴛɪɴɢ` + + • *ᴀᴅᴍɪɴs ᴏɴʟʏ:* + +➥ /setflood <`ɪɴᴛ`/'ᴏɴ'/'ᴏғғ'>*:* `ᴇɴᴀʙʟᴇs ᴏʀ ᴅɪsᴀʙʟᴇs ғʟᴏᴏᴅ ᴄᴏɴᴛʀᴏʟ` + ᴇxᴀᴍᴘʟᴇ *:* `/setflood 5` + +➥ /setfloodmode <ʙᴀɴ/ᴋɪᴄᴋ/ᴍᴜᴛᴇ/ᴛʙᴀɴ/ᴛᴍᴜᴛᴇ> <ᴠᴀʟᴜᴇ>*:* `Aᴄᴛɪᴏɴ ᴛᴏ ᴘᴇʀғᴏʀᴍ ᴡʜᴇɴ ᴜsᴇʀ ʜᴀᴠᴇ ᴇxᴄᴇᴇᴅᴇᴅ ғʟᴏᴏᴅ ʟɪᴍɪᴛ. ʙᴀɴ/ᴋɪᴄᴋ/ᴍᴜᴛᴇ/ᴛᴍᴜᴛᴇ/ᴛʙᴀɴ` + + • *ɴᴏᴛᴇ:* + + `• Vᴀʟᴜᴇ ᴍᴜsᴛ ʙᴇ ғɪʟʟᴇᴅ ғᴏʀ ᴛʙᴀɴ ᴀɴᴅ ᴛᴍᴜᴛᴇ!!` + Iᴛ ᴄᴀɴ ʙᴇ: + `5ᴍ` = 5 ᴍɪɴᴜᴛᴇs + `6ʜ` = 6 ʜᴏᴜʀs + `3ᴅ` = 3 ᴅᴀʏs + `1ᴡ` = 1 ᴡᴇᴇᴋ + + """ + +__mod_name__ = "𝙰ɴᴛɪ-ғʟᴏᴏᴅ" + +FLOOD_BAN_HANDLER = MessageHandler( + Filters.all & ~Filters.status_update & Filters.chat_type.groups, + check_flood, + run_async=True, +) +SET_FLOOD_HANDLER = CommandHandler( + "setflood", set_flood, pass_args=True, run_async=True +) # , filters=Filters.chat_type.groups) +SET_FLOOD_MODE_HANDLER = CommandHandler( + "setfloodmode", set_flood_mode, pass_args=True, run_async=True +) # , filters=Filters.chat_type.groups) +FLOOD_HANDLER = CommandHandler( + "flood", flood, run_async=True +) # , filters=Filters.chat_type.groups) + +dispatcher.add_handler(FLOOD_BAN_HANDLER, FLOOD_GROUP) +dispatcher.add_handler(SET_FLOOD_HANDLER) +dispatcher.add_handler(SET_FLOOD_MODE_HANDLER) +dispatcher.add_handler(FLOOD_HANDLER) diff --git a/Exon/modules/antilinkedchannel.py b/Exon/modules/antilinkedchannel.py new file mode 100644 index 00000000..56b8ca49 --- /dev/null +++ b/Exon/modules/antilinkedchannel.py @@ -0,0 +1,130 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import html + +from telegram import TelegramError, Update +from telegram.ext import CallbackContext +from telegram.ext.filters import Filters + +import Exon.modules.sql.antilinkedchannel_sql as sql +from Exon.modules.helper_funcs.anonymous import AdminPerms, user_admin +from Exon.modules.helper_funcs.chat_status import bot_admin, bot_can_delete +from Exon.modules.helper_funcs.decorators import Exoncmd, Exonmsg + + +@Exoncmd(command="cleanlinked", group=112) +@bot_can_delete +@user_admin(AdminPerms.CAN_RESTRICT_MEMBERS) +def set_antilinkedchannel(update: Update, context: CallbackContext): + message = update.effective_message + chat = update.effective_chat + args = context.args + if len(args) > 0: + s = args[0].lower() + if s in ["yes", "on"]: + if sql.status_pin(chat.id): + sql.disable_pin(chat.id) + sql.enable_pin(chat.id) + message.reply_html( + f"ᴇɴᴀʙʟᴇᴅ ʟɪɴᴋᴇᴅ channel ᴘᴏsᴛ ᴅᴇʟᴇᴛɪᴏɴ ᴀɴᴅ ᴅɪsᴀʙʟᴇᴅ ᴀɴᴛɪ ᴄʜᴀɴɴᴇʟ ᴘɪɴ ɪɴ {html.escape(chat.title)}" + ) + + else: + sql.enable_linked(chat.id) + message.reply_html( + f"ᴇɴᴀʙʟᴇᴅ ʟɪɴᴋᴇᴅ ᴄʜᴀɴɴᴇʟ ᴘᴏsᴛ ᴅᴇʟᴇᴛɪᴏɴ ɪɴ {html.escape(chat.title)}. ᴍᴇssᴀɢᴇs sᴇɴᴛ ғʀᴏᴍ ᴛʜᴇ ʟɪɴᴋᴇᴅ ᴄʜᴀɴɴᴇʟ ᴡɪʟʟ ʙᴇ ᴅᴇʟᴇᴛᴇᴅ." + ) + + elif s in ["off", "no"]: + sql.disable_linked(chat.id) + message.reply_html( + f"ᴅɪsᴀʙʟᴇᴅ ʟɪɴᴋᴇᴅ ᴄʜᴀɴɴᴇʟ ᴘᴏsᴛ ᴅᴇʟᴇᴛɪᴏɴ ɪɴ {html.escape(chat.title)}" + ) + + else: + message.reply_text(f"ᴜɴʀᴇᴄᴏɢɴɪᴢᴇᴅ arguments {s}") + return + message.reply_html( + f"ʟɪɴᴋᴇᴅ ᴄʜᴀɴɴᴇʟ ᴘᴏsᴛ ᴅᴇʟᴇᴛɪᴏɴ ɪs ᴄᴜʀʀᴇɴᴛʟʏ {sql.status_linked(chat.id)} ɪɴ {html.escape(chat.title)}" + ) + + +@Exonmsg(Filters.is_automatic_forward, group=111) +def eliminate_linked_channel_msg(update: Update, _: CallbackContext): + message = update.effective_message + chat = update.effective_chat + if not sql.status_linked(chat.id): + return + try: + message.delete() + except TelegramError: + return + + +@Exoncmd(command="antichannelpin", group=114) +@bot_admin +@user_admin(AdminPerms.CAN_RESTRICT_MEMBERS) +def set_antipinchannel(update: Update, context: CallbackContext): + message = update.effective_message + chat = update.effective_chat + args = context.args + if len(args) > 0: + s = args[0].lower() + if s in ["yes", "on"]: + if sql.status_linked(chat.id): + sql.disable_linked(chat.id) + sql.enable_pin(chat.id) + message.reply_html( + f"ᴅɪsᴀʙʟᴇᴅ ʟɪɴᴋᴇᴅ ᴄʜᴀɴɴᴇʟ ᴅᴇʟᴇᴛɪᴏɴ ᴀɴᴅ ᴇɴᴀʙʟᴇᴅ ᴀɴᴛɪ ᴄʜᴀɴɴᴇʟ ᴘɪɴ ɪɴ {html.escape(chat.title)}" + ) + + else: + sql.enable_pin(chat.id) + message.reply_html( + f"ᴇɴᴀʙʟᴇᴅ ᴀɴᴛɪ ᴄʜᴀɴɴᴇʟ ᴘɪɴ ɪɴ {html.escape(chat.title)}" + ) + elif s in ["off", "no"]: + sql.disable_pin(chat.id) + message.reply_html( + f"ᴅɪsᴀʙʟᴇᴅ ᴀɴᴛɪ ᴄʜᴀɴɴᴇʟ ᴘɪɴ ɪɴ {html.escape(chat.title)}" + ) + else: + message.reply_text(f"ᴜɴʀᴇᴄᴏɢɴɪᴢᴇᴅ ᴀʀɢᴜᴍᴇɴᴛs {s}") + return + message.reply_html( + f"ʟɪɴᴋᴇᴅ ᴄʜᴀɴɴᴇʟ message ᴜɴᴘɪɴ ɪs ᴄᴜʀʀᴇɴᴛʟʏ {sql.status_pin(chat.id)} ɪɴ {html.escape(chat.title)}" + ) + + +@Exonmsg(Filters.is_automatic_forward | Filters.status_update.pinned_message, group=113) +def eliminate_linked_channel_msg(update: Update, _: CallbackContext): + message = update.effective_message + chat = update.effective_chat + if not sql.status_pin(chat.id): + return + try: + message.unpin() + except TelegramError: + return diff --git a/Exon/modules/antinsfw.py b/Exon/modules/antinsfw.py new file mode 100644 index 00000000..db45f198 --- /dev/null +++ b/Exon/modules/antinsfw.py @@ -0,0 +1,201 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +from os import remove + +from pyrogram import filters + +from Exon import BOT_USERNAME, DRAGONS, arq, pgram +from Exon.modules.mongo.nsfw_mongo import is_nsfw_on, nsfw_off, nsfw_on +from Exon.utils.errors import capture_err +from Exon.utils.permissions import adminsOnly + + +async def get_file_id_from_message(message): + file_id = None + if message.document: + if int(message.document.file_size) > 3145728: + return + mime_type = message.document.mime_type + if mime_type not in ("image/png", "image/jpeg"): + return + file_id = message.document.file_id + + if message.sticker: + if message.sticker.is_animated: + if not message.sticker.thumbs: + return + file_id = message.sticker.thumbs[0].file_id + else: + file_id = message.sticker.file_id + + if message.photo: + file_id = message.photo.file_id + + if message.animation: + if not message.animation.thumbs: + return + file_id = message.animation.thumbs[0].file_id + + if message.video: + if not message.video.thumbs: + return + file_id = message.video.thumbs[0].file_id + return file_id + + +@pgram.on_message( + ( + filters.document + | filters.photo + | filters.sticker + | filters.animation + | filters.video + ) + & ~filters.private, + group=8, +) +@capture_err +async def detect_nsfw(_, message): + if not await is_nsfw_on(message.chat.id): + return + if not message.from_user: + return + file_id = await get_file_id_from_message(message) + if not file_id: + return + file = await pgram.download_media(file_id) + try: + results = await arq.nsfw_scan(file=file) + except Exception: + return + if not results.ok: + return + results = results.result + remove(file) + nsfw = results.is_nsfw + if message.from_user.id in DRAGONS: + return + if not nsfw: + return + try: + await message.delete() + except Exception: + return + await message.reply_text( + f""" +**ɴsғᴡ ɪᴍᴀɢᴇ ᴅᴇᴛᴇᴄᴛᴇᴅ & ᴅᴇʟᴇᴛᴇᴅ sᴜᴄᴄᴇssғᴜʟʟʏ!** + +**𝗨𝘀𝗲𝗿:** {message.from_user.mention} [`{message.from_user.id}`] +**𝗦𝗮𝗳𝗲:** `{results.neutral} %` +**𝗣𝗼𝗿𝗻:** `{results.porn} %` +**𝗔𝗱𝘂𝗹𝘁:** `{results.sexy} %` +**𝗛𝗲𝗻𝘁𝗮𝗶:** `{results.hentai} %` +**𝗗𝗿𝗮𝘄𝗶𝗻𝗴𝘀:** `{results.drawings} %` + +""" + ) + + +@pgram.on_message(filters.command(["pscan", f"pscan@{BOT_USERNAME}"])) +@capture_err +async def nsfw_scan_command(_, message): + if not message.reply_to_message: + await message.reply_text( + "ʀᴇᴘʟʏ ᴛᴏ ᴀɴ ɪᴍᴀɢᴇ/ᴅᴏᴄᴜᴍᴇɴᴛ/sᴛɪᴄᴋᴇʀ/ᴀɴɪᴍᴀᴛɪᴏɴ ᴛᴏ sᴄᴀɴ ɪᴛ." + ) + return + reply = message.reply_to_message + if ( + not reply.document + and not reply.photo + and not reply.sticker + and not reply.animation + and not reply.video + ): + await message.reply_text( + "ʀᴇᴘʟʏ ᴛᴏ ᴀɴ ɪᴍᴀɢᴇ/ᴅᴏᴄᴜᴍᴇɴᴛ/sᴛɪᴄᴋᴇʀ/ᴀɴɪᴍᴀᴛɪᴏɴ ᴛᴏ sᴄᴀɴ ɪᴛ." + ) + return + m = await message.reply_text("sᴄᴀɴɴɪɴɢ..") + file_id = await get_file_id_from_message(reply) + if not file_id: + return await m.edit("sᴏᴍᴇᴛʜɪɴɢ ᴡʀᴏɴɢ ʜᴀᴘᴘᴇɴᴇᴅ.") + file = await pgram.download_media(file_id) + try: + results = await arq.nsfw_scan(file=file) + except Exception: + return + remove(file) + if not results.ok: + return await m.edit(results.result) + results = results.result + await m.edit( + f""" +**𝗡𝗲𝘂𝘁𝗿𝗮𝗹:** `{results.neutral} %` +**𝗣𝗼𝗿𝗻:** `{results.porn} %` +**𝗛𝗲𝗻𝘁𝗮𝗶:** `{results.hentai} %` +**𝗦𝗲𝘅𝘆:** `{results.sexy} %` +**𝗗𝗿𝗮𝘄𝗶𝗻𝗴𝘀:** `{results.drawings} %` +**𝗡𝗦𝗙𝗪:** `{results.is_nsfw}` +""" + ) + + +@pgram.on_message( + filters.command(["antiporn", f"antiporn@{BOT_USERNAME}"]) & ~filters.private +) +@adminsOnly("can_change_info") +async def nsfw_enable_disable(_, message): + if len(message.command) != 2: + await message.reply_text("ᴜsᴀɢᴇ: /antiporn [on/off]") + return + status = message.text.split(None, 1)[1].strip() + status = status.lower() + chat_id = message.chat.id + if status in ("on", "yes"): + await nsfw_on(chat_id) + await message.reply_text( + "ᴇɴᴀʙʟᴇᴅ ᴀɴᴛɪ-ᴘᴏʀɴ sʏsᴛᴇᴍ. I ᴡɪʟʟ ᴅᴇʟᴇᴛᴇ ᴍᴇssᴀɢᴇs ᴄᴏɴᴛᴀɪɴɪɴɢ ɪɴᴀᴘᴘʀᴏᴘʀɪᴀᴛᴇ ᴄᴏɴᴛᴇɴᴛ." + ) + elif status in ("off", "no"): + await nsfw_off(chat_id) + await message.reply_text("ᴅɪsᴀʙʟᴇᴅ ᴀɴᴛɪ-ᴘᴏʀɴ sʏsᴛᴇᴍ.") + else: + await message.reply_text("ᴜɴᴋɴᴏᴡɴ sᴜғғɪx, ᴜsᴇ /antiporn [on/off]") + + +__mod_name__ = "𝙿ᴏʀɴ" + + +__help__ = """ +*ᴀɴᴛɪ ᴘᴏʀɴ sʏsᴛᴇᴍ* + +/antiporn <oɴ> *:* `ᴇɴᴀʙʟᴇᴅ ᴀɴᴛɪɴsғᴡ` *ᴀɴᴛɪᴘᴏʀɴ* `sʏsᴛᴇᴍ. ʙᴏᴛ ᴡɪʟʟ ᴅᴇʟᴇᴛᴇ ᴍᴇssᴀɢᴇs ᴄᴏɴᴛᴀɪɴɪɴɢ ɪɴᴀᴘᴘʀᴏᴘʀɪᴀᴛᴇ ᴄᴏɴᴛᴇɴᴛ` [ᴅᴇғᴇᴀᴛ ᴏɴ] + +/antiporn <ᴏғғ> *:* `sᴀʟᴇ ᴏғғ ʜɪ ǫ ᴋᴀᴇ ɴᴀ ʙ ᴏɴ ᴋᴀʀ ᴅᴇ` + +/pscan *:* `ʀᴇᴘʟʏ ᴛᴏ ᴀɴ ɪᴍᴀɢᴇ/ᴅᴏᴄᴜᴍᴇɴᴛ/sᴛɪᴄᴋᴇʀ/ᴀɴɪᴍᴀᴛɪᴏɴ ᴛᴏ sᴄᴀɴ ɪᴛ` + +""" diff --git a/Exon/modules/antiservice.py b/Exon/modules/antiservice.py new file mode 100644 index 00000000..bffe48d5 --- /dev/null +++ b/Exon/modules/antiservice.py @@ -0,0 +1,61 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +from pyrogram import filters + +from Exon import pgram +from Exon.core.decorators.permissions import adminsOnly +from Exon.utils.dbfunctions import antiservice_off, antiservice_on, is_antiservice_on + + +@pgram.on_message(filters.command("antiservice") & ~filters.private & ~filters.edited) +@adminsOnly("can_change_info") +async def anti_service(_, message): + if len(message.command) != 2: + return await message.reply_text("ᴜsᴀɢᴇ: /antiservice [on | off]") + status = message.text.split(None, 1)[1].strip() + status = status.lower() + chat_id = message.chat.id + if status == "on": + await antiservice_on(chat_id) + await message.reply_text( + "ᴇɴᴀʙʟᴇᴅ ᴀɴᴛɪsᴇʀᴠɪᴄᴇ sʏsᴛᴇᴍ. ɪ ᴡɪʟʟ ᴅᴇʟᴇᴛᴇ sᴇʀᴠɪᴄᴇ ᴍᴇssᴀɢᴇs ғʀᴏᴍ ɴᴏᴡ ᴏɴ." + ) + elif status == "off": + await antiservice_off(chat_id) + await message.reply_text( + "ᴅɪsᴀʙʟᴇᴅ ᴀɴᴛɪsᴇʀᴠɪᴄᴇ sʏsᴛᴇᴍ. I ᴡᴏɴ'ᴛ ʙᴇ ᴅᴇʟᴇᴛɪɴɢ sᴇʀᴠɪᴄᴇ ᴍᴇssᴀɢᴇ ғʀᴏᴍ ɴᴏᴡ ᴏɴ." + ) + else: + await message.reply_text("ᴜɴᴋɴᴏᴡɴ sᴜғғɪx, ᴜsᴇ /antiservice [enable|disable]") + + +@pgram.on_message(filters.service, group=11) +async def delete_service(_, message): + chat_id = message.chat.id + try: + if await is_antiservice_on(chat_id): + return await message.delete() + except Exception: + pass diff --git a/Exon/modules/approval.py b/Exon/modules/approval.py new file mode 100644 index 00000000..dc00e4ca --- /dev/null +++ b/Exon/modules/approval.py @@ -0,0 +1,258 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import html + +from telegram import InlineKeyboardButton, InlineKeyboardMarkup, ParseMode, Update +from telegram.error import BadRequest +from telegram.ext import CallbackContext, CallbackQueryHandler, run_async +from telegram.utils.helpers import mention_html + +import Exon.modules.sql.approve_sql as sql +from Exon import DRAGONS, dispatcher +from Exon.modules.disable import DisableAbleCommandHandler +from Exon.modules.helper_funcs.chat_status import user_admin +from Exon.modules.helper_funcs.extraction import extract_user +from Exon.modules.log_channel import loggable + + +@loggable +@user_admin +@run_async +def approve(update, context): + message = update.effective_message + chat_title = message.chat.title + chat = update.effective_chat + args = context.args + user = update.effective_user + user_id = extract_user(message, args) + if not user_id: + message.reply_text( + "I ᴅᴏɴ'ᴛ ᴋɴᴏᴡ ᴡʜᴏ ʏᴏᴜ'ʀᴇ ᴛᴀʟᴋɪɴɢ ᴀʙᴏᴜᴛ, ʏᴏᴜ'ʀᴇ ɢᴏɪɴɢ ᴛᴏ ɴᴇᴇᴅ ᴛᴏ sᴘᴇᴄɪғʏ ᴀ ᴜsᴇʀ!" + ) + return "" + try: + member = chat.get_member(user_id) + except BadRequest: + return "" + if member.status == "administrator" or member.status == "creator": + message.reply_text( + "ᴜsᴇʀ ɪs ᴀʟʀᴇᴀᴅʏ ᴀᴅᴍɪɴ - ʟᴏᴄᴋs, ʙʟᴏᴄᴋʟɪsᴛs, ᴀɴᴅ ᴀɴᴛɪғʟᴏᴏᴅ ᴀʟʀᴇᴀᴅʏ ᴅᴏɴ'ᴛ ᴀᴘᴘʟʏ ᴛᴏ ᴛʜᴇᴍ." + ) + return "" + if sql.is_approved(message.chat_id, user_id): + message.reply_text( + f"[{member.user['first_name']}](tg://user?id={member.user['id']}) ɪs ᴀʟʀᴇᴀᴅʏ ᴀᴘᴘʀᴏᴠᴇᴅ ɪɴ {chat_title}", + parse_mode=ParseMode.MARKDOWN, + ) + return "" + sql.approve(message.chat_id, user_id) + message.reply_text( + f"[{member.user['first_name']}](tg://user?id={member.user['id']}) ʜᴀs ʙᴇᴇɴ ᴀᴘᴘʀᴏᴠᴇᴅ ɪɴ {chat_title}! ᴛʜᴇʏ ᴡɪʟʟ ɴᴏᴡ ʙᴇ ɪɢɴᴏʀᴇᴅ ʙʏ ᴀᴜᴛᴏᴍᴀᴛᴇᴅ ᴀᴅᴍɪɴ ᴀᴄᴛɪᴏɴs ʟɪᴋᴇ locᴋs,ʙʟᴏᴄᴋʟɪsᴛs, ᴀɴᴅ ᴀɴᴛɪғʟᴏᴏᴅ.", + parse_mode=ParseMode.MARKDOWN, + ) + log_message = ( + f"<b>{html.escape(chat.title)}:</b>\n" + f"#ᴀᴘᴘʀᴏᴠᴇᴅ\n" + f"<b>ᴀᴅᴍɪɴ:</b> {mention_html(user.id, user.first_name)}\n" + f"<b>ᴜsᴇʀ:</b> {mention_html(member.user.id, member.user.first_name)}" + ) + + return log_message + + +@loggable +@user_admin +@run_async +def disapprove(update, context): + message = update.effective_message + chat_title = message.chat.title + chat = update.effective_chat + args = context.args + user = update.effective_user + user_id = extract_user(message, args) + if not user_id: + message.reply_text( + "I ᴅᴏɴ'ᴛ ᴋɴᴏᴡ ᴡʜᴏ ʏᴏᴜ'ʀᴇ ᴛᴀʟᴋɪɴɢ ᴀʙᴏᴜᴛ, ʏᴏᴜ'ʀᴇ ɢᴏɪɴɢ ᴛᴏ ɴᴇᴇᴅ ᴛᴏ sᴘᴇᴄɪғʏ ᴀ ᴜsᴇʀ!" + ) + return "" + try: + member = chat.get_member(user_id) + except BadRequest: + return "" + if member.status == "administrator" or member.status == "creator": + message.reply_text("ᴛʜɪs ᴜsᴇʀ ɪs ᴀɴ ᴀᴅᴍɪɴ, ᴛʜᴇʏ ᴄᴀɴ'ᴛ ʙᴇ ᴜɴᴀᴘᴘʀᴏᴠᴇᴅ.") + return "" + if not sql.is_approved(message.chat_id, user_id): + message.reply_text(f"{member.user['first_name']} ɪsɴ'ᴛ ᴀᴘᴘʀᴏᴠᴇᴅ ʏᴇᴛ!") + return "" + sql.disapprove(message.chat_id, user_id) + message.reply_text( + f"{member.user['first_name']} ɪs ɴᴏ ʟᴏɴɢᴇʀ ᴀᴘᴘʀᴏᴠᴇᴅ ɪɴ {chat_title}." + ) + log_message = ( + f"<b>{html.escape(chat.title)}:</b>\n" + f"#ᴜɴᴀᴘᴘʀᴏᴠᴇᴅ\n" + f"<b>ᴀᴅᴍɪɴ:</b> {mention_html(user.id, user.first_name)}\n" + f"<b>ᴜsᴇʀ:</b> {mention_html(member.user.id, member.user.first_name)}" + ) + + return log_message + + +@user_admin +@run_async +def approved(update, context): + message = update.effective_message + chat_title = message.chat.title + chat = update.effective_chat + msg = "ᴛʜᴇ ғᴏʟʟᴏᴡɪɴɢ ᴜsᴇʀs ᴀʀᴇ ᴀᴘᴘʀᴏᴠᴇᴅ.\n" + approved_users = sql.list_approved(message.chat_id) + for i in approved_users: + member = chat.get_member(int(i.user_id)) + msg += f"- `{i.user_id}`: {member.user['first_name']}\n" + if msg.endswith("approved.\n"): + message.reply_text(f"ɴᴏ ᴜsᴇʀs ᴀʀᴇ ᴀᴘᴘʀᴏᴠᴇᴅ ɪɴ {chat_title}.") + return "" + else: + message.reply_text(msg, parse_mode=ParseMode.MARKDOWN) + + +@user_admin +def approval(update, context): + message = update.effective_message + chat = update.effective_chat + args = context.args + user_id = extract_user(message, args) + if not user_id: + message.reply_text( + "I ᴅᴏɴ'ᴛ ᴋɴᴏᴡ ᴡʜᴏ ʏᴏᴜ'ʀᴇ ᴛᴀʟᴋɪɴɢ ᴀʙᴏᴜᴛ, ʏᴏᴜ'ʀᴇ ɢᴏɪɴɢ ᴛᴏ ɴᴇᴇᴅ ᴛᴏ sᴘᴇᴄɪғʏ ᴀ ᴜsᴇʀ!" + ) + return "" + member = chat.get_member(int(user_id)) + + if sql.is_approved(message.chat_id, user_id): + message.reply_text( + f"{member.user['first_name']} ɪs ᴀɴ ᴀᴘᴘʀᴏᴠᴇᴅ ᴜsᴇʀ. ʟᴏᴄᴋs, ᴀɴᴛɪғʟᴏᴏᴅ, ᴀɴᴅ ʙʟᴏᴄᴋʟɪsᴛs ᴡᴏɴ'ᴛ ᴀᴘᴘʟʏ ᴛᴏ ᴛʜᴇᴍ." + ) + else: + message.reply_text( + f"{member.user['first_name']} ɪs ɴᴏᴛ ᴀɴ ᴀᴘᴘʀᴏᴠᴇᴅ ᴜsᴇʀ. ᴛʜᴇʏ ᴀʀᴇ ᴀғғᴇᴄᴛᴇᴅ ʙʏ ɴᴏʀᴍᴀʟ ᴄᴏᴍᴍᴀɴᴅs." + ) + + +@run_async +def unapproveall(update: Update, context: CallbackContext): + chat = update.effective_chat + user = update.effective_user + member = chat.get_member(user.id) + if member.status != "creator" and user.id not in DRAGONS: + update.effective_message.reply_text( + "ᴏɴʟʏ ᴛʜᴇ ᴄʜᴀᴛ ᴏᴡɴᴇʀ ᴄᴀɴ ᴜɴᴀᴘᴘʀᴏᴠᴇ ᴀʟʟ ᴜsᴇʀs ᴀᴛ ᴏɴᴄᴇ." + ) + else: + buttons = InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + text="✰ ᴜɴᴀᴘᴘʀᴏᴠᴇ ᴀʟʟ ✰", callback_data="unapproveall_user" + ) + ], + [ + InlineKeyboardButton( + text="✰ ᴄᴀɴᴄᴇʟ ✰", callback_data="unapproveall_cancel" + ) + ], + ] + ) + update.effective_message.reply_text( + f"ᴀʀᴇ ʏᴏᴜ sᴜʀᴇ ʏᴏᴜ ᴡᴏᴜʟᴅ ʟɪᴋᴇ ᴛᴏ ᴜɴᴀᴘᴘʀᴏᴠᴇ ALL ᴜsᴇʀs ɪɴ {chat.title}? ᴛʜɪs ᴀᴄᴛɪᴏɴ ᴄᴀɴɴᴏᴛ ʙᴇ ᴜɴᴅᴏɴᴇ.", + reply_markup=buttons, + parse_mode=ParseMode.MARKDOWN, + ) + + +@run_async +def unapproveall_btn(update: Update, context: CallbackContext): + query = update.callback_query + chat = update.effective_chat + message = update.effective_message + member = chat.get_member(query.from_user.id) + if query.data == "unapproveall_user": + if member.status == "creator" or query.from_user.id in DRAGONS: + approved_users = sql.list_approved(chat.id) + users = [int(i.user_id) for i in approved_users] + for user_id in users: + sql.disapprove(chat.id, user_id) + + if member.status == "administrator": + query.answer("ᴏɴʟʏ ᴏᴡɴᴇʀ ᴏғ ᴛʜᴇ ᴄʜᴀᴛ ᴄᴀɴ ᴅᴏ ᴛʜɪs.") + + if member.status == "member": + query.answer("ʏᴏᴜ ɴᴇᴇᴅ ᴛᴏ ʙᴇ ᴀᴅᴍɪɴ ᴛᴏ ᴅᴏ ᴛʜɪs.") + elif query.data == "unapproveall_cancel": + if member.status == "creator" or query.from_user.id in DRAGONS: + message.edit_text("ʀᴇᴍᴏᴠɪɴɢ of ᴀʟʟ ᴀᴘᴘʀᴏᴠᴇᴅ ᴜsᴇʀs ʜᴀs ʙᴇᴇɴ ᴄᴀɴᴄᴇʟʟᴇᴅ.") + return "" + if member.status == "administrator": + query.answer("ᴏɴʟʏ ᴏᴡɴᴇʀ ᴏғ ᴛʜᴇ ᴄʜᴀᴛ ᴄᴀɴ ᴅᴏ ᴛʜɪs.") + if member.status == "member": + query.answer("ʏᴏᴜ ɴᴇᴇᴅ ᴛᴏ ʙᴇ ᴀᴅᴍɪɴ ᴛᴏ ᴅᴏ ᴛʜɪs.") + + +__help__ = """ +*ꜱᴏᴍᴇᴛɪᴍᴇꜱ, ʏᴏᴜ ᴍɪɢʜᴛ ᴛʀᴜꜱᴛ ᴀ ᴜꜱᴇʀ ɴᴏᴛ ᴛᴏ ꜱᴇɴᴅ ᴜɴᴡᴀɴᴛᴇᴅ ᴄᴏɴᴛᴇɴᴛ. +ᴍᴀʏʙᴇ ɴᴏᴛ ᴇɴᴏᴜɢʜ ᴛᴏ ᴍᴀᴋᴇ ᴛʜᴇᴍ ᴀᴅᴍɪɴ, ʙᴜᴛ ʏᴏᴜ ᴍɪɢʜᴛ ʙᴇ ᴏᴋ ᴡɪᴛʜ ʟᴏᴄᴋꜱ, ʙʟᴀᴄᴋʟɪꜱᴛꜱ, ᴀɴᴅ ᴀɴᴛɪғʟᴏᴏᴅ ɴᴏᴛ ᴀᴘᴘʟʏɪɴɢ ᴛᴏ ᴛʜᴇᴍ* +`ᴛʜᴀᴛ ᴡʜᴀᴛ ᴀᴘᴘʀᴏᴠᴀʟꜱ ᴀʀᴇ ғᴏʀ - ᴀᴘᴘʀᴏᴠᴇ ᴏғ ᴛʀᴜꜱᴛᴡᴏʀᴛʜʏ ᴜꜱᴇʀꜱ ᴛᴏ ᴀʟʟᴏᴡ ᴛʜᴇᴍ ᴛᴏ ꜱᴇɴᴅ` + +*ᴀᴅᴍɪɴ ᴄᴏᴍᴍᴀɴᴅꜱ:* +⍟ /approval*:* `ᴄʜᴇᴄᴋ ᴀ ᴜꜱᴇʀ ᴀᴘᴘʀᴏᴠᴀʟ ꜱᴛᴀᴛᴜꜱ ɪɴ ᴛʜɪꜱ ᴄʜᴀᴛ.` + +⍟ /approve*:* `ᴀᴘᴘʀᴏᴠᴇ ᴏғ ᴀ ᴜꜱᴇʀ. ʟᴏᴄᴋꜱ, ʙʟᴀᴄᴋʟɪꜱᴛꜱ, ᴀɴᴅ ᴀɴᴛɪғʟᴏᴏᴅ ᴡᴏɴ'ᴛ ᴀᴘᴘʟʏ ᴛᴏ ᴛʜᴇᴍ ᴀɴʏᴍᴏʀᴇ`. + +⍟ /unapprove*:* `ᴜɴᴀᴘᴘʀᴏᴠᴇ ᴏғ ᴀ ᴜꜱᴇʀ. ᴛʜᴇʏ ᴡɪʟʟ ɴᴏᴡ ʙᴇ ꜱᴜʙᴊᴇᴄᴛ to ʟᴏᴄᴋꜱ, blacklists, and antiflood again.` + +⍟ /approved*:* `ʟɪꜱᴛ ᴀʟʟ ᴀᴘᴘʀᴏᴠᴇᴅ ᴜꜱᴇʀꜱ` + +⍟ /unapproveall*:* `ᴜɴᴀᴘᴘʀᴏᴠᴇ` *ᴀʟʟ* `ᴜꜱᴇʀꜱ ɪɴ a ᴄʜᴀᴛ. ᴛʜɪꜱ ᴄᴀɴɴᴏᴛ ʙᴇ ᴜɴᴅᴏɴᴇ` +""" + + +APPROVE = DisableAbleCommandHandler("approve", approve) +DISAPPROVE = DisableAbleCommandHandler("unapprove", disapprove) +APPROVED = DisableAbleCommandHandler("approved", approved) +APPROVAL = DisableAbleCommandHandler("approval", approval) +UNAPPROVEALL = DisableAbleCommandHandler("unapproveall", unapproveall) +UNAPPROVEALL_BTN = CallbackQueryHandler(unapproveall_btn, pattern=r"unapproveall_.*") + +dispatcher.add_handler(APPROVE) +dispatcher.add_handler(DISAPPROVE) +dispatcher.add_handler(APPROVED) +dispatcher.add_handler(APPROVAL) +dispatcher.add_handler(UNAPPROVEALL) +dispatcher.add_handler(UNAPPROVEALL_BTN) + +__mod_name__ = "𝙰ᴘᴘʀᴏᴠᴀʟ" +__command_list__ = ["approve", "unapprove", "approved", "approval"] +__handlers__ = [APPROVE, DISAPPROVE, APPROVED, APPROVAL] diff --git a/Exon/modules/arq.py b/Exon/modules/arq.py new file mode 100644 index 00000000..915edee9 --- /dev/null +++ b/Exon/modules/arq.py @@ -0,0 +1,62 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +from pyrogram import filters + +from Exon import BOT_USERNAME, arq, pgram + + +@pgram.on_message(filters.command("arq")) +async def arq_stats(_, message): + data = await arq.stats() + if not data.ok: + return await message.reply_text(data.result) + data = data.result + uptime = data.uptime + requests = data.requests + cpu = data.cpu + server_mem = data.memory.server + api_mem = data.memory.api + disk = data.disk + platform = data.platform + python_version = data.python + users = data.users + statistics = f""" +**>-< sʏsᴛᴇᴍ >-<** +**ᴜᴘᴛɪᴍᴇ:** `{uptime}` +**ʀᴇǫᴜᴇsᴛs ᴜᴘᴛɪᴍᴇ:** `{requests}` +**ᴄᴘᴜ:** `{cpu}` +**ᴍᴇᴍᴏʀʏ:** +**ᴛᴏᴛᴀʟ ᴜsᴇᴅ:** `{server_mem}` +**ᴀᴘɪ:** `{api_mem}` +**ᴅɪsᴋ:** `{disk}` +**ᴘʟᴀᴛғᴏʀᴍ:** `{platform}` +**ᴘʏᴛʜᴏɴ:** `{python_version}` + +**ᴀʀǫ sᴛᴀᴛɪsᴛɪᴄs:** +**ᴜsᴇʀs:** `{users}` + +**@{BOT_USERNAME} sᴏᴍᴇ ᴍᴏᴅᴜʟᴇs ʀᴜɴɴɪɴɢ ᴏɴ ᴀʀǫ** +""" + await message.reply_text(statistics, disable_web_page_preview=True) diff --git a/Exon/modules/backups.py b/Exon/modules/backups.py new file mode 100644 index 00000000..44f8a907 --- /dev/null +++ b/Exon/modules/backups.py @@ -0,0 +1,408 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import json +import os +import time +from io import BytesIO + +from telegram import Message, ParseMode +from telegram.error import BadRequest +from telegram.ext import CommandHandler + +import Exon.modules.sql.blacklist_sql as blacklistsql +import Exon.modules.sql.locks_sql as locksql +import Exon.modules.sql.notes_sql as sql +import Exon.modules.sql.rules_sql as rulessql +from Exon import JOIN_LOGGER, LOGGER, OWNER_ID, SUPPORT_CHAT, dispatcher +from Exon.__main__ import DATA_IMPORT +from Exon.modules.connection import connected +from Exon.modules.helper_funcs.alternate import typing_action +from Exon.modules.helper_funcs.chat_status import user_admin +from Exon.modules.sql import disable_sql as disabledsql + + +@user_admin +@typing_action +def import_data(update, context): + msg = update.effective_message + chat = update.effective_chat + user = update.effective_user + # TODO: allow uploading doc with command, not just as reply + # only work with a doc + + conn = connected(context.bot, update, chat, user.id, need_admin=True) + if conn: + chat = dispatcher.bot.getChat(conn) + chat_name = dispatcher.bot.getChat(conn).title + else: + if update.effective_message.chat.type == "private": + update.effective_message.reply_text("ᴛʜɪs ɪs ᴀ ɢʀᴏᴜᴘ ᴏɴʟʏ ᴄᴏᴍᴍᴀɴᴅ!") + return "" + + chat = update.effective_chat + chat_name = update.effective_message.chat.title + + if msg.reply_to_message and msg.reply_to_message.document: + try: + file_info = context.bot.get_file(msg.reply_to_message.document.file_id) + except BadRequest: + msg.reply_text( + "ᴛʀʏ ᴅᴏᴡɴʟᴏᴀᴅɪɴɢ ᴀɴᴅ ᴜᴘʟᴏᴀᴅɪɴɢ ᴛʜᴇ ғɪʟᴇ ʏᴏᴜʀsᴇʟғ ᴀɢᴀɪɴ, ᴛʜɪs ᴏɴᴇ sᴇᴇᴍ ʙʀᴏᴋᴇɴ ᴛᴏ ᴍᴇ!", + ) + return + + with BytesIO() as file: + file_info.download(out=file) + file.seek(0) + data = json.load(file) + + # only import one group + if len(data) > 1 and str(chat.id) not in data: + msg.reply_text( + "ᴛʜᴇʀᴇ ᴀʀᴇ ᴍᴏʀᴇ ᴛʜᴀɴ ᴏɴᴇ ɢʀᴏᴜᴘ ɪɴ ᴛʜɪs ғɪʟᴇ ᴀɴᴅ ᴛʜᴇ ᴄʜᴀᴛ.ɪᴅ ɪs ɴᴏᴛ same! ʜᴏᴡ am ɪ sᴜᴘᴘᴏsᴇᴅ ᴛᴏ ɪᴍᴘᴏʀᴛ ɪᴛ?", + ) + return + + # Check if backup is this chat + try: + if data.get(str(chat.id)) is None: + if conn: + text = f"ʙᴀᴄᴋᴜᴘ ᴄᴏᴍᴇs ғʀᴏᴍ ᴀɴᴏᴛʜᴇʀ ᴄʜᴀᴛ, I ᴄᴀɴ'ᴛ ʀᴇᴛᴜʀɴ ᴀɴᴏᴛʜᴇʀ ᴄʜᴀᴛ ᴛᴏ ᴄʜᴀᴛ *{chat_name}*" + + else: + text = "ʙᴀᴄᴋᴜᴘ ᴄᴏᴍᴇs ғʀᴏᴍ ᴀɴᴏᴛʜᴇʀ ᴄʜᴀᴛ, I ᴄᴀɴ'ᴛ ʀᴇᴛᴜʀɴ ᴀɴᴏᴛʜᴇʀ ᴄʜᴀᴛ ᴛᴏ this chat" + return msg.reply_text(text, parse_mode="markdown") + except Exception: + return msg.reply_text("ᴛʜᴇʀᴇ ᴡᴀs ᴀ ᴘʀᴏʙʟᴇᴍ ᴡʜɪʟᴇ ɪᴍᴘᴏʀᴛɪɴɢ ᴛʜᴇ ᴅᴀᴛᴀ!") + # Check if backup is from self + try: + if str(context.bot.id) != str(data[str(chat.id)]["bot"]): + return msg.reply_text( + "ʙᴀᴄᴋᴜᴘ ғʀᴏᴍ ᴀɴᴏᴛʜᴇʀ ʙᴏᴛ ᴛʜᴀᴛ ɪs ɴᴏᴛ sᴜɢɢᴇsᴛᴇᴅ ᴍɪɢʜᴛ ᴄᴀᴜsᴇ ᴛʜᴇ ᴘʀᴏʙʟᴇᴍ, ᴅᴏᴄᴜᴍᴇɴᴛs, ᴘʜᴏᴛᴏs, ᴠɪᴅᴇᴏs, ᴀᴜᴅɪᴏs, ʀᴇᴄᴏʀᴅs ᴍɪɢʜᴛ ɴᴏᴛ ᴡᴏʀᴋ ᴀs ɪᴛ sʜᴏᴜʟᴅ ʙᴇ.", + ) + except Exception: + pass + # Select data source + if str(chat.id) in data: + data = data[str(chat.id)]["hashes"] + else: + data = data[list(data.keys())[0]]["hashes"] + + try: + for mod in DATA_IMPORT: + mod.__import_data__(str(chat.id), data) + except Exception: + msg.reply_text( + f"ᴀɴ ᴇʀʀᴏʀ ᴏᴄᴄᴜʀʀᴇᴅ ᴡʜɪʟᴇ ʀᴇᴄᴏᴠᴇʀɪɴɢ ʏᴏᴜʀ ᴅᴀᴛᴀ. ᴛʜᴇ ᴘʀᴏᴄᴇss ғᴀɪʟᴇᴅ. If ʏᴏᴜ ᴇxᴘᴇʀɪᴇɴᴄᴇ ᴀ ᴘʀᴏʙʟᴇᴍ ᴡɪᴛʜ ᴛʜɪs, ᴘʟᴇᴀsᴇ ᴛᴀᴋᴇ ɪᴛ ᴛᴏ @{SUPPORT_CHAT}", + ) + + LOGGER.exception( + "ɪᴍᴘʀᴛ ғᴏʀ ᴛʜᴇ ᴄʜᴀᴛ %s ᴡɪᴛʜ ᴛʜᴇ ɴᴀᴍᴇ %s ғᴀɪʟᴇᴅ.", + str(chat.id), + str(chat.title), + ) + return + + # TODO: some of that link logic + # NOTE: consider default permissions stuff? + if conn: + + text = f"ʙᴀᴄᴋᴜᴘ ғᴜʟʟʏ ʀᴇsᴛᴏʀᴇᴅ ᴏɴ *{chat_name}*." + else: + text = "ʙᴀᴄᴋᴜᴘ ғᴜʟʟʏ ʀᴇsᴛᴏʀᴇᴅ" + msg.reply_text(text, parse_mode="markdown") + + +@user_admin +def export_data(update, context): + chat_data = context.chat_data + msg = update.effective_message # type: Optional[Message] + user = update.effective_user # type: Optional[User] + chat_id = update.effective_chat.id + chat = update.effective_chat + current_chat_id = update.effective_chat.id + conn = connected(context.bot, update, chat, user.id, need_admin=True) + if conn: + chat = dispatcher.bot.getChat(conn) + chat_id = conn + # chat_name = dispatcher.bot.getChat(conn).title + else: + if update.effective_message.chat.type == "private": + update.effective_message.reply_text("ᴛʜɪs ɪs ᴀ 𝚐𝚛𝚘𝚞𝚙 ᴏɴʟʏ ᴄᴏᴍᴍᴀɴᴅ!") + return "" + chat = update.effective_chat + chat_id = update.effective_chat.id + update.effective_message.chat.title + + jam = time.time() + new_jam = jam + 10800 + checkchat = get_chat(chat_id, chat_data) + if checkchat.get("status") and jam <= int(checkchat.get("value")): + timeformatt = time.strftime( + "%H:%M:%M %d/%m/%Y", + time.localtime(checkchat.get("value")), + ) + update.effective_message.reply_text( + "ʏᴏᴜ ᴄᴀɴ ᴏɴʟʏ ʙᴀᴄᴋᴜᴘ ᴏɴᴄᴇ ᴀ ᴅᴀʏ!\nʏᴏᴜ ᴄᴀɴ ʙᴀᴄᴋᴜᴘ ᴀɢᴀɪɴ ɪɴ ᴀʙᴏᴜᴛ `{}`".format( + timeformatt, + ), + parse_mode=ParseMode.MARKDOWN, + ) + return + if user.id != OWNER_ID: + put_chat(chat_id, new_jam, chat_data) + note_list = sql.get_all_chat_notes(chat_id) + backup = {} + buttonlist = [] + namacat = "" + isicat = "" + rules = "" + count = 0 + countbtn = 0 + # Notes + for note in note_list: + count += 1 + sql.get_note(chat_id, note.name) + namacat += "{}<###splitter###>".format(note.name) + if note.msgtype == 1: + tombol = sql.get_buttons(chat_id, note.name) + for btn in tombol: + countbtn += 1 + if btn.same_line: + buttonlist.append( + ("{}".format(btn.name), "{}".format(btn.url), True), + ) + else: + buttonlist.append( + ("{}".format(btn.name), "{}".format(btn.url), False), + ) + isicat += "###button###: {}<###button###>{}<###splitter###>".format( + note.value, + str(buttonlist), + ) + buttonlist.clear() + elif note.msgtype == 2: + isicat += "###sticker###:{}<###splitter###>".format(note.file) + elif note.msgtype == 3: + isicat += "###file###:{}<###TYPESPLIT###>{}<###splitter###>".format( + note.file, + note.value, + ) + elif note.msgtype == 4: + isicat += "###photo###:{}<###TYPESPLIT###>{}<###splitter###>".format( + note.file, + note.value, + ) + elif note.msgtype == 5: + isicat += "###audio###:{}<###TYPESPLIT###>{}<###splitter###>".format( + note.file, + note.value, + ) + elif note.msgtype == 6: + isicat += "###voice###:{}<###TYPESPLIT###>{}<###splitter###>".format( + note.file, + note.value, + ) + elif note.msgtype == 7: + isicat += "###video###:{}<###TYPESPLIT###>{}<###splitter###>".format( + note.file, + note.value, + ) + elif note.msgtype == 8: + isicat += "###video_note###:{}<###TYPESPLIT###>{}<###splitter###>".format( + note.file, + note.value, + ) + else: + isicat += "{}<###splitter###>".format(note.value) + notes = { + "#{}".format(namacat.split("<###splitter###>")[x]): "{}".format( + isicat.split("<###splitter###>")[x], + ) + for x in range(count) + } + # Rules + rules = rulessql.get_rules(chat_id) + # Blacklist + bl = list(blacklistsql.get_chat_blacklist(chat_id)) + # Disabled command + disabledcmd = list(disabledsql.get_all_disabled(chat_id)) + # Filters + """ + all_filters = list(filtersql.get_chat_triggers(chat_id)) + export_filters = {} + for filters in all_filters: + filt = filtersql.get_filter(chat_id, filters) + if filt.is_sticker: + typefilt = "sticker" + elif filt.is_document: + typefilt = "document" + elif filt.is_image: + typefilt = "image" + elif filt.is_audio: + typefilt = "audio" + elif filt.is_video: + typefilt = "video" + elif filt.is_voice: + typefilt = "voice" + elif filt.has_buttons: + typefilt = "buttons" + buttons = filtersql.get_buttons(chat_id, filt.keyword) + elif filt.has_markdown: + typefilt = "text" + if typefilt == "buttons": + content = "{}#=#{}|btn|{}".format(typefilt, filt.reply, buttons) + else: + content = "{}#=#{}".format(typefilt, filt.reply) + print(content) + export_filters[filters] = content + #print(export_filters) + + """ + + # Welcome (TODO) + # welc = welcsql.get_welc_pref(chat_id) + # Locked + curr_locks = locksql.get_locks(chat_id) + curr_restr = locksql.get_restr(chat_id) + + if curr_locks: + locked_lock = { + "sticker": curr_locks.sticker, + "audio": curr_locks.audio, + "voice": curr_locks.voice, + "document": curr_locks.document, + "video": curr_locks.video, + "contact": curr_locks.contact, + "photo": curr_locks.photo, + "gif": curr_locks.gif, + "url": curr_locks.url, + "bots": curr_locks.bots, + "forward": curr_locks.forward, + "game": curr_locks.game, + "location": curr_locks.location, + "rtl": curr_locks.rtl, + } + else: + locked_lock = {} + + if curr_restr: + locked_restr = { + "messages": curr_restr.messages, + "media": curr_restr.media, + "other": curr_restr.other, + "previews": curr_restr.preview, + "all": all( + [ + curr_restr.messages, + curr_restr.media, + curr_restr.other, + curr_restr.preview, + ], + ), + } + else: + locked_restr = {} + + locks = {"locks": locked_lock, "restrict": locked_restr} + # Warns (TODO) + # warns = warnssql.get_warns(chat_id) + # Backing up + backup[chat_id] = { + "bot": context.bot.id, + "hashes": { + "info": {"rules": rules}, + "extra": notes, + "blacklist": bl, + "disabled": disabledcmd, + "locks": locks, + }, + } + baccinfo = json.dumps(backup, indent=4) + with open("ExonRobot{}Backup".format(chat_id), "w") as f: + f.write(str(baccinfo)) + context.bot.sendChatAction(current_chat_id, "upload_document") + tgl = time.strftime("%H:%M:%S - %d/%m/%Y", time.localtime(time.time())) + try: + context.bot.sendMessage( + JOIN_LOGGER, + "*sᴜᴄᴄᴇssғᴜʟʟʏ ɪᴍᴘᴏʀᴛᴇᴅ ʙᴀᴄᴋᴜᴘ:*\nᴄʜᴀᴛ: `{}`\nᴄʜᴀᴛ 𝙸𝙳: `{}`\nᴏɴ: `{}`".format( + chat.title, + chat_id, + tgl, + ), + parse_mode=ParseMode.MARKDOWN, + ) + except BadRequest: + pass + context.bot.sendDocument( + current_chat_id, + document=open("ExonRobot{}Backup".format(chat_id), "rb"), + caption="*sᴜᴄᴄᴇssғᴜʟʟʏ ᴇxᴘᴏʀᴛᴇᴅ ʙᴀᴄᴋᴜᴘ:*\nᴄʜᴀᴛ: `{}`\nᴄʜᴀᴛ ɪᴅ: `{}`\nᴏɴ: `{}`\n\nɴᴏᴛᴇ: ᴛʜɪs `ᴇxᴏɴʀᴏʙᴏᴛ-ʙᴀᴄᴋᴜᴘ` ᴡᴀs sᴘᴇᴄɪᴀʟʟʏ ᴍᴀᴅᴇ ғᴏʀ ɴᴏᴛᴇs.".format( + chat.title, + chat_id, + tgl, + ), + timeout=360, + reply_to_message_id=msg.message_id, + parse_mode=ParseMode.MARKDOWN, + ) + os.remove("Exon{}Backup".format(chat_id)) # Cleaning file + + +# Temporary data +def put_chat(chat_id, value, chat_data): + print(chat_data) + status = value is not False + chat_data[chat_id] = {"backups": {"status": status, "value": value}} + + +def get_chat(chat_id, chat_data): + print(chat_data) + try: + return chat_data[chat_id]["backups"] + except KeyError: + return {"status": False, "value": False} + + +__mod_name__ = "𝙱ᴀᴄᴋᴜᴘs" + +__help__ = """ +*ᴏɴʟʏ ғᴏʀ ɢʀᴏᴜᴘ ᴏᴡɴᴇʀ:* + +⍟ /import*:* `ʀᴇᴘʟʏ ᴛᴏ ᴛʜᴇ ʙᴀᴄᴋᴜᴘ ғɪʟᴇ ғᴏʀ ᴛʜᴇ ʙᴜᴛʟᴇʀ / ɢʀᴏᴜᴘ ᴛᴏ ɪᴍᴘᴏʀᴛ ᴀs ᴍᴜᴄʜ ᴀs ᴘᴏssɪʙʟᴇ, ᴍᴀᴋɪɴɢ ᴛʀᴀɴsғᴇʀs ᴠᴇʀʏ ᴇᴀsʏ!` + + **ɴᴏᴛᴇ ᴛʜᴀᴛ ғɪʟᴇs / ᴘʜᴏᴛᴏs ᴄᴀɴɴᴏᴛ ʙᴇ ɪᴍᴘᴏʀᴛᴇᴅ ᴅᴜᴇ ᴛᴏ ᴛᴇʟᴇɢʀᴀᴍ ʀᴇsᴛʀɪᴄᴛɪᴏɴs **. + +⍟ /export*:* `ᴇxᴘᴏʀᴛ ɢʀᴏᴜᴘ ᴅᴀᴛᴀ, ᴡʜɪᴄʜ ᴡɪʟʟ ʙᴇ ᴇxᴘᴏʀᴛᴇᴅ ᴀʀᴇ: ʀᴜʟᴇs, ɴᴏᴛᴇs (ᴅᴏᴄᴜᴍᴇɴᴛs, ɪᴍᴀɢᴇs, ᴍᴜsɪᴄ, ᴠɪᴅᴇᴏ, ᴀᴜᴅɪᴏ, ᴠᴏɪᴄᴇ, ᴛᴇxᴛ, ᴛᴇxᴛ ʙᴜᴛᴛᴏɴs)` +""" + +IMPORT_HANDLER = CommandHandler("import", import_data, run_async=True) +EXPORT_HANDLER = CommandHandler( + "export", export_data, pass_chat_data=True, run_async=True +) + +dispatcher.add_handler(IMPORT_HANDLER) +dispatcher.add_handler(EXPORT_HANDLER) diff --git a/Exon/modules/bans.py b/Exon/modules/bans.py new file mode 100644 index 00000000..f6ef7abe --- /dev/null +++ b/Exon/modules/bans.py @@ -0,0 +1,616 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import html +from typing import Optional + +from telegram import ( + InlineKeyboardButton, + InlineKeyboardMarkup, + ParseMode, + TelegramError, + Update, +) +from telegram.error import BadRequest +from telegram.ext import CallbackContext, CallbackQueryHandler, CommandHandler, Filters +from telegram.utils.helpers import mention_html + +from Exon import ( + DEMONS, + DEV_USERS, + DRAGONS, + LOGGER, + OWNER_ID, + TIGERS, + WOLVES, + dispatcher, +) +from Exon.modules.disable import DisableAbleCommandHandler +from Exon.modules.helper_funcs.chat_status import ( + bot_admin, + can_delete, + can_restrict, + connection_status, + dev_plus, + is_user_admin, + is_user_ban_protected, + is_user_in_chat, + user_admin, + user_admin_no_reply, + user_can_ban, +) +from Exon.modules.helper_funcs.extraction import extract_user_and_text +from Exon.modules.helper_funcs.filters import CustomFilters +from Exon.modules.helper_funcs.string_handling import extract_time +from Exon.modules.log_channel import gloggable, loggable + + +@connection_status +@bot_admin +@can_restrict +@user_admin +@user_can_ban +@loggable +def ban(update: Update, context: CallbackContext) -> str: + chat = update.effective_chat + user = update.effective_user + message = update.effective_message + log_message = "" + bot = context.bot + args = context.args + reason = "" + if message.reply_to_message and message.reply_to_message.sender_chat: + if r := bot.ban_chat_sender_chat( + chat_id=chat.id, + sender_chat_id=message.reply_to_message.sender_chat.id, + ): + message.reply_text( + f"ᴄʜᴀɴɴᴇʟ {html.escape(message.reply_to_message.sender_chat.title)} ᴡᴀs ʙᴀɴɴᴇᴅ sᴜᴄᴄᴇssғᴜʟʟʏ ғʀᴏᴍ {html.escape(chat.title)}", + parse_mode="html", + ) + + else: + message.reply_text("ғᴀɪʟᴇᴅ ᴛᴏ ʙᴀɴ ᴄʜᴀɴɴᴇʟ") + return + user_id, reason = extract_user_and_text(message, args) + if not user_id: + message.reply_text("⚠️ ᴜsᴇʀ ɴᴏᴛ ғᴏᴜɴᴅ.") + return log_message + try: + member = chat.get_member(user_id) + except BadRequest as excp: + if excp.message != "ᴜsᴇʀ ɴᴏᴛ ғᴏᴜɴᴅ": + raise + message.reply_text("ᴄᴀɴ'ᴛ sᴇᴇᴍ ᴛᴏ ғɪɴᴅ ᴛʜɪs ᴘᴇʀsᴏɴ.") + return log_message + if user_id == bot.id: + message.reply_text("ᴏʜ ʏᴇᴀʜ, ʙᴀɴ ᴍʏsᴇʟғ, ɴᴏᴏʙ sᴀʟᴀ!") + return log_message + if is_user_ban_protected(chat, user_id, member) and user not in DEV_USERS: + if user_id == OWNER_ID: + message.reply_text("ᴛʀʏɪɴɢ ᴛᴏ ᴘᴜᴛ ᴍᴇ ᴀɢᴀɪɴsᴛ ᴍʏ ᴏɴɪᴄʜᴀɴ ʜᴜʜ?") + elif user_id in DEV_USERS: + message.reply_text("I ᴄᴀɴ'ᴛ ᴀᴄᴛ ᴀɢᴀɪɴsᴛ ᴏᴜʀ ғᴀᴍɪʟʏ.") + elif user_id in DRAGONS: + message.reply_text( + "ғɪɢʜᴛɪɴɢ ᴏᴜʀ ʙᴇsᴛ ғʀɪᴇɴᴅs ʜᴇʀᴇ ᴡɪʟʟ ᴘᴜᴛ ᴜsᴇʀ ʟɪᴠᴇs ᴀᴛ risk." + ) + elif user_id in DEMONS: + message.reply_text("ʙʀɪɴɢ ᴀɴ ᴏʀᴅᴇʀ ғʀᴏᴍ ᴏɴɪᴄʜᴀɴ ᴛᴏ ғɪɢʜᴛ ᴏᴜʀ ғʀɪᴇɴᴅs.") + elif user_id in TIGERS: + message.reply_text("ʙʀɪɴɢ ᴀɴ ᴏʀᴅᴇʀ ғʀᴏᴍ ᴏɴɪᴄʜᴀɴ ᴛᴏ ғɪɢʜᴛ ᴏᴜʀ ᴄʟᴀssᴍᴀᴛᴇs") + elif user_id in WOLVES: + message.reply_text("ɪɢɴɪᴛᴇ ᴀᴄᴄᴇss ᴍᴀᴋᴇ ᴛʜᴇᴍ ʙᴀɴ ɪᴍᴍᴜɴᴇ!") + else: + message.reply_text("⚠️ ᴄᴀɴɴᴏᴛ ʙᴀɴɴᴇᴅ ᴀᴅᴍɪɴ.") + return log_message + if message.text.startswith("/s"): + silent = True + if not can_delete(chat, context.bot.id): + return "" + else: + silent = False + log = ( + f"<b>{html.escape(chat.title)}:</b>\n" + f"#{'S' if silent else ''}ʙᴀɴɴᴇᴅ\n" + f"<b>ᴀᴅᴍɪɴ:</b> {mention_html(user.id, html.escape(user.first_name))}\n" + f"<b>ᴜsᴇʀ:</b> {mention_html(member.user.id, html.escape(member.user.first_name))}" + ) + if reason: + log += f"<b>ʀᴇᴀsᴏɴ:</b> {reason}" + try: + chat.ban_member(user_id) + if silent: + if message.reply_to_message: + message.reply_to_message.delete() + message.delete() + return log + # bot.send_sticker(chat.id, BAN_STICKER) # banhammer marie sticker + reply = ( + f"<code>❕</code><b>Ban Event</b>\n\n" + f"<b>• ᴜsᴇʀ:</b> {mention_html(member.user.id, html.escape(member.user.first_name))}\n" + f"<b>• ᴜsᴇʀ 𝙸𝙳:</b> <code>{member.user.id}</code>\n" + f"<b>• ʙᴀɴɴᴇᴅ ʙʏ:</b> {mention_html(user.id, html.escape(user.first_name))}" + ) + if reason: + reply += f"\n<b>• ʀᴇᴀsᴏɴ:</b> {html.escape(reason)}" + bot.sendMessage( + chat.id, + reply, + reply_markup=InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + text="ᴜɴʙᴀɴ ❗", callback_data=f"unbanb_unban={user_id}" + ), + InlineKeyboardButton( + text="ᴅᴇʟᴇᴛᴇ ❗", callback_data="unbanb_del" + ), + ] + ] + ), + parse_mode=ParseMode.HTML, + ) + return log + except BadRequest as excp: + if excp.message == "ʀᴇᴘʟʏ ᴍᴇssᴀɢᴇ ɴᴏᴛ ғᴏᴜɴᴅ": + # Do not reply + if silent: + return log + message.reply_text("ʙᴀɴɴᴇᴅ ❗!", quote=False) + return log + else: + LOGGER.warning(update) + LOGGER.exception( + "ERROR ʙᴀɴɴɪɴɢ ᴜsᴇʀ %s ɪɴ ᴄʜᴀᴛ %s (%s) ᴅᴜᴇ ᴛᴏ %s", + user_id, + chat.title, + chat.id, + excp.message, + ) + message.reply_text("ᴜʜᴍ...ᴛʜᴀᴛ ᴅɪᴅɴ'ᴛ ᴡᴏʀᴋ...") + return log_message + + +@connection_status +@bot_admin +@can_restrict +@user_admin +@user_can_ban +@loggable +def temp_ban(update: Update, context: CallbackContext) -> str: + chat = update.effective_chat + user = update.effective_user + message = update.effective_message + log_message = "" + bot, args = context.bot, context.args + user_id, reason = extract_user_and_text(message, args) + if not user_id: + message.reply_text("⚠️ ᴜsᴇʀ ɴᴏᴛ ғᴏᴜɴᴅ.") + return log_message + try: + member = chat.get_member(user_id) + except BadRequest as excp: + if excp.message != "ᴜsᴇʀ ɴᴏᴛ ғᴏᴜɴᴅ": + raise + message.reply_text("I ᴄᴀɴ'ᴛ sᴇᴇᴍ ᴛᴏ ғɪɴᴅ ᴛʜɪs ᴜsᴇʀ.") + return log_message + if user_id == bot.id: + message.reply_text("I'ᴍ ɴᴏᴛ ɢᴏɴɴᴀ BAN ᴍʏsᴇʟғ, ᴀʀᴇ ʏᴏᴜ ɴᴏᴏʙ ?") + return log_message + if is_user_ban_protected(chat, user_id, member): + message.reply_text("I ᴅᴏɴ'ᴛ ғᴇᴇʟ ʟɪᴋᴇ ɪᴛ.") + return log_message + if not reason: + message.reply_text("ʏᴏᴜ ʜᴀᴠᴇɴ'ᴛ sᴘᴇᴄɪғɪᴇᴅ ᴀ ᴛɪᴍᴇ ᴛᴏ ʙᴀɴ ᴛʜɪs ᴜsᴇʀ ғᴏʀ!") + return log_message + split_reason = reason.split(None, 1) + time_val = split_reason[0].lower() + reason = split_reason[1] if len(split_reason) > 1 else "" + bantime = extract_time(message, time_val) + if not bantime: + return log_message + log = ( + f"<b>{html.escape(chat.title)}:</b>\n" + "#ᴛᴇᴍᴘ ʙᴀɴɴᴇᴅ\n" + f"<b>ᴀᴅᴍɪɴ:</b> {mention_html(user.id, html.escape(user.first_name))}\n" + f"<b>ᴜsᴇʀ:</b> {mention_html(member.user.id, html.escape(member.user.first_name))}\n" + f"<b>ᴛɪᴍᴇ:</b> {time_val}" + ) + if reason: + log += f"\nʀᴇᴀsᴏɴ: {reason}" + try: + chat.ban_member(user_id, until_date=bantime) + # bot.send_sticker(chat.id, BAN_STICKER) # banhammer marie sticker + reply_msg = ( + f"<code>❕</code><b>Temporarily Banned</b>\n\n" + f"<b>• ᴜsᴇʀ:</b> {mention_html(member.user.id, html.escape(member.user.first_name))}\n" + f"<b>• ᴜsᴇʀ ɪᴅ:</b> <code>{member.user.id}</code>\n" + f"<b>• ʙᴀɴɴᴇᴅ ғᴏʀ:</b> {time_val}\n" + f"<b>• ʙᴀɴɴᴇᴅ ʙʏ:</b> {mention_html(user.id, html.escape(user.first_name))}" + ) + if reason: + reply_msg += f"\n<b>• ʀᴇᴀsᴏɴ:</b> {html.escape(reason)}" + bot.sendMessage( + chat.id, + reply_msg, + reply_markup=InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + text="ᴜɴʙᴀɴ ❗", callback_data=f"unbanb_unban={user_id}" + ), + InlineKeyboardButton( + text="ᴅᴇʟᴇᴛᴇ ❗", callback_data="unbanb_del" + ), + ] + ] + ), + parse_mode=ParseMode.HTML, + ) + return log + except BadRequest as excp: + if excp.message == "ʀᴇᴘʟʏ ᴍᴇssᴀɢᴇ ɴᴏᴛ ғᴏᴜɴᴅ": + # Do not reply + message.reply_text( + f"{mention_html(member.user.id, html.escape(member.user.first_name))} [<code>{member.user.id}</code>] ʙᴀɴɴᴇᴅ ғᴏʀ {time_val}.", + quote=False, + ) + return log + else: + LOGGER.warning(update) + LOGGER.exception( + "ᴇʀʀᴏʀ ʙᴀɴɴɪɴɢ ᴜsᴇʀ %s ɪɴ ᴄʜᴀᴛ %s (%s) ᴅᴜᴇ ᴛᴏ %s", + user_id, + chat.title, + chat.id, + excp.message, + ) + message.reply_text("ᴡᴇʟʟ ᴅᴀᴍɴ, ɪ ᴄᴀɴ'ᴛ ʙᴀɴ ᴛʜᴀᴛ ᴜsᴇʀ.") + return log_message + + +@connection_status +@bot_admin +@can_restrict +@user_admin_no_reply +@user_can_ban +@loggable +def unbanb_btn(update: Update, context: CallbackContext) -> str: + bot = context.bot + query = update.callback_query + chat = update.effective_chat + user = update.effective_user + if query.data != "unbanb_del": + splitter = query.data.split("=") + query_match = splitter[0] + if query_match == "unbanb_unban": + user_id = splitter[1] + if not is_user_admin(chat, int(user.id)): + bot.answer_callback_query( + query.id, + text="⚠️ ʏᴏᴜ ᴅᴏɴ'ᴛ ʜᴀᴠᴇ ᴇɴᴏᴜɢʜ ʀɪɢʜᴛs ᴛᴏ ᴜɴᴍᴜᴛᴇ ᴘᴇᴏᴘʟᴇ", + show_alert=True, + ) + return "" + try: + member = chat.get_member(user_id) + except BadRequest: + pass + chat.unban_member(user_id) + query.message.edit_text( + f"{mention_html(member.user.id, html.escape(member.user.first_name))} [<code>{member.user.id}</code>] ᴜɴʙᴀɴɴᴇᴅ ʙʏ {mention_html(user.id, html.escape(user.first_name))}", + parse_mode=ParseMode.HTML, + ) + bot.answer_callback_query(query.id, text="Unbanned!") + return ( + f"<b>{html.escape(chat.title)}:</b>\n" + f"#ᴜɴʙᴀɴɴᴇᴅ\n" + f"<b>ᴀᴅᴍɪɴ:</b> {mention_html(user.id, user.first_name)}\n" + f"<b>ᴜsᴇʀ:</b> {mention_html(member.user.id, member.user.first_name)}" + ) + else: + if not is_user_admin(chat, int(user.id)): + bot.answer_callback_query( + query.id, + text="⚠️ ʏᴏᴜ ᴅᴏɴ'ᴛ ʜᴀᴠᴇ ᴇɴᴏᴜɢʜ ʀɪɢʜᴛs ᴛᴏ ᴅᴇʟᴇᴛᴇ ᴛʜɪs ᴍᴇssᴀɢᴇ.", + show_alert=True, + ) + return "" + query.message.delete() + bot.answer_callback_query(query.id, text="ᴅᴇʟᴇᴛᴇᴅ !") + return "" + + +@connection_status +@bot_admin +@can_restrict +@user_admin +@user_can_ban +@loggable +def punch(update: Update, context: CallbackContext) -> str: + chat = update.effective_chat + user = update.effective_user + message = update.effective_message + log_message = "" + bot, args = context.bot, context.args + user_id, reason = extract_user_and_text(message, args) + if not user_id: + message.reply_text("⚠️ ᴜsᴇʀ ɴᴏᴛ ғᴏᴜɴᴅ") + return log_message + try: + member = chat.get_member(user_id) + except BadRequest as excp: + if excp.message != "ᴜsᴇʀ ɴᴏᴛ ғᴏᴜɴᴅ": + raise + message.reply_text("⚠️ I ᴄᴀɴ'ᴛ sᴇᴇᴍ ᴛᴏ ғɪɴᴅ ᴛʜɪs ᴜsᴇʀ.") + return log_message + if user_id == bot.id: + message.reply_text("ʏᴇᴀʜʜʜ I'ᴍ ɴᴏᴛ ɢᴏɴɴᴀ ᴅᴏ ᴛʜᴀᴛ.") + return log_message + if is_user_ban_protected(chat, user_id): + message.reply_text("I ʀᴇᴀʟʟʏ ᴡɪsʜ ɪ ᴄᴏᴜʟᴅ ᴘᴜɴᴄʜ ᴛʜɪs ᴜsᴇʀ....") + return log_message + if res := chat.unban_member(user_id): + # bot.send_sticker(chat.id, BAN_STICKER) # banhammer marie sticker + bot.sendMessage( + chat.id, + f"{mention_html(member.user.id, html.escape(member.user.first_name))} [<code>{member.user.id}</code>] Kicked by {mention_html(user.id, html.escape(user.first_name))}", + parse_mode=ParseMode.HTML, + ) + log = ( + f"<b>{html.escape(chat.title)}:</b>\n" + f"#ᴋɪᴄᴋᴇᴅ\n" + f"<b>ᴀᴅᴍɪɴ:</b> {mention_html(user.id, html.escape(user.first_name))}\n" + f"<b>ᴜsᴇʀ:</b> {mention_html(member.user.id, html.escape(member.user.first_name))}" + ) + if reason: + log += f"\n<b>ʀᴇᴀsᴏɴ:</b> {reason}" + return log + else: + message.reply_text("⚠️ ᴡᴇʟʟ ᴅᴀᴍɴ, ɪ ᴄᴀɴ'ᴛ ᴘᴜɴᴄʜ ᴛʜᴀᴛ ᴜsᴇʀ.") + return log_message + + +@bot_admin +@can_restrict +def punchme(update: Update, context: CallbackContext): + user_id = update.effective_message.from_user.id + if is_user_admin(update.effective_chat, user_id): + update.effective_message.reply_text("I ᴡɪsʜ I ᴄᴏᴜʟᴅ... ʙᴜᴛ ʏᴏᴜ'ʀᴇ ᴀɴ ᴀᴅᴍɪɴ .") + return + if res := update.effective_chat.unban_member(user_id): + update.effective_message.reply_text( + "ᴘᴜɴᴄʜᴇs ʏᴏᴜ ᴏᴜᴛ ᴏғ ᴛʜᴇ ɢʀᴏᴜᴘ !!", + ) + else: + update.effective_message.reply_text("ʜᴜʜ? I ᴄᴀɴ'ᴛ :/") + + +@connection_status +@bot_admin +@can_restrict +@user_admin +@user_can_ban +@loggable +def unban(update: Update, context: CallbackContext) -> Optional[str]: + message = update.effective_message + user = update.effective_user + chat = update.effective_chat + log_message = "" + bot, args = context.bot, context.args + if message.reply_to_message and message.reply_to_message.sender_chat: + if r := bot.unban_chat_sender_chat( + chat_id=chat.id, + sender_chat_id=message.reply_to_message.sender_chat.id, + ): + message.reply_text( + f"ᴄʜᴀɴɴᴇʟ {html.escape(message.reply_to_message.sender_chat.title)} ᴡᴀs ᴜɴʙᴀɴɴᴇᴅ sᴜᴄᴄᴇssғᴜʟʟʏ ғʀᴏᴍ {html.escape(chat.title)}", + parse_mode="html", + ) + + else: + message.reply_text("ғᴀɪʟᴇᴅ ᴛᴏ ᴜɴʙᴀɴ ᴄʜᴀɴɴᴇʟ") + return + user_id, reason = extract_user_and_text(message, args) + if not user_id: + message.reply_text("⚠️ ᴜsᴇʀ ɴᴏᴛ ғᴏᴜɴᴅ.") + return log_message + try: + member = chat.get_member(user_id) + except BadRequest as excp: + if excp.message != "ᴜsᴇʀ ɴᴏᴛ ғᴏᴜɴᴅ": + raise + message.reply_text("I ᴄᴀɴ'ᴛ sᴇᴇᴍ ᴛᴏ ғɪɴᴅ ᴛʜɪs ᴜsᴇʀ.") + return log_message + if user_id == bot.id: + message.reply_text("ʜᴏᴡ ᴡᴏᴜʟᴅ ɪ ᴜɴʙᴀɴ ᴍʏsᴇʟғ ɪғ ɪ ᴡᴀsɴ'ᴛ ʜᴇʀᴇ...?") + return log_message + if is_user_in_chat(chat, user_id): + message.reply_text("⚠️ ᴜsᴇʀ ɴᴏᴛ ғᴏᴜɴᴅ.") + return log_message + chat.unban_member(user_id) + message.reply_text( + f"{mention_html(member.user.id, html.escape(member.user.first_name))} [<code>{member.user.id}</code>] ᴡᴀs ᴜɴʙᴀɴɴᴇᴅ ʙʏ {mention_html(user.id, user.first_name)}", + parse_mode=ParseMode.HTML, + ) + log = ( + f"<b>{html.escape(chat.title)}:</b>\n" + f"#ᴜɴʙᴀɴɴᴇᴅ\n" + f"<b>ᴀᴅᴍɪɴ:</b> {mention_html(user.id, html.escape(user.first_name))}\n" + f"<b>ᴜsᴇʀ:</b> {mention_html(member.user.id, html.escape(member.user.first_name))}" + ) + if reason: + log += f"\n<b>ʀᴇᴀsᴏɴ:</b> {reason}" + return log + + +@connection_status +@bot_admin +@can_restrict +@gloggable +def selfunban(update: Update, context: CallbackContext) -> str: + message = update.effective_message + user = update.effective_user + bot, args = context.bot, context.args + if user.id not in DRAGONS or user.id not in TIGERS: + return + try: + chat_id = int(args[0]) + except: + message.reply_text("ɢɪᴠᴇ ᴀ ᴠᴀʟɪᴅ ᴄʜᴀᴛ ɪᴅ.") + return + chat = bot.getChat(chat_id) + try: + member = chat.get_member(user.id) + except BadRequest as excp: + if excp.message == "ᴜsᴇʀ ɴᴏᴛ ғᴏᴜɴᴅ": + message.reply_text("I ᴄᴀɴ'ᴛ sᴇᴇᴍ ᴛᴏ ғɪɴᴅ ᴛʜɪs ᴜsᴇʀ.") + return + else: + raise + if is_user_in_chat(chat, user.id): + message.reply_text("Aren't you already in the chat??") + return + chat.unban_member(user.id) + message.reply_text(f"ʏᴇᴘ, ɪ ʜᴀᴠᴇ ᴜɴʙᴀɴɴᴇᴅ ᴛʜᴇ ᴜsᴇʀ.") + log = ( + f"<b>{html.escape(chat.title)}:</b>\n" + f"#ᴜɴʙᴀɴɴᴇᴅ\n" + f"<b>ᴜsᴇʀ:</b> {mention_html(member.user.id, html.escape(member.user.first_name))}" + ) + return log + + +@bot_admin +@can_restrict +@loggable +def banme(update: Update, context: CallbackContext): + user_id = update.effective_message.from_user.id + chat = update.effective_chat + user = update.effective_user + if is_user_admin(update.effective_chat, user_id): + update.effective_message.reply_text("⚠️ I ᴄᴀɴɴᴏᴛ ʙᴀɴɴᴇᴅ ᴀᴅᴍɪɴ.") + return + if res := update.effective_chat.ban_member(user_id): + update.effective_message.reply_text("ʏᴇs, ʏᴏᴜ'ʀᴇ ʀɪɢʜᴛ! ɢᴛғᴏ..") + return f"<b>{html.escape(chat.title)}:</b>\n#ʙᴀɴᴍᴇ\n<b>ᴜsᴇʀ:</b> {mention_html(user.id, user.first_name)}\n<b>ɪᴅ:</b> <code>{user_id}</code>" + + else: + update.effective_message.reply_text("Huh? I can't :/") + + +@dev_plus +def abishnoi(update: Update, context: CallbackContext): + args = context.args + bot = context.bot + try: + chat_id = str(args[0]) + del args[0] + except TypeError: + update.effective_message.reply_text("ᴘʟᴇᴀsᴇ ɢɪᴠᴇ ᴍᴇ ᴀ ᴄʜᴀᴛ ᴛᴏ ᴇᴄʜᴏ ᴛᴏ!") + to_send = " ".join(args) + if len(to_send) >= 2: + try: + bot.sendMessage(int(chat_id), to_send) + except TelegramError: + LOGGER.warning("ᴄᴏᴜʟᴅɴ'ᴛ sᴇɴᴅ ᴛᴏ ɢʀᴏᴜᴘ %s", chat_id) + update.effective_message.reply_text( + "ᴄᴏᴜʟᴅɴ'ᴛ sᴇɴᴅ ᴛʜᴇ ᴍᴇssᴀɢᴇ. ᴘᴇʀʜᴀᴘs ɪ'ᴍ ɴᴏᴛ ᴘᴀʀᴛ ᴏғ ᴛʜᴀᴛ ɢʀᴏᴜᴘ?" + ) + + +__help__ = """ +*ᴜsᴇʀ ᴄᴏᴍᴍᴀɴᴅs:* + +• /kickme*:* `ᴋɪᴄᴋs ᴛʜᴇ ᴜsᴇʀ ᴡʜᴏ ɪssᴜᴇᴅ ᴛʜᴇ ᴄᴏᴍᴍᴀɴᴅ ` + +• /banme*:* `ʙᴀɴs ᴛʜᴇ ᴜsᴇʀ ᴡʜᴏ ɪssᴜᴇᴅ ᴛʜᴇ ᴄᴏᴍᴍᴀɴᴅ ` + +*ᴀᴅᴍɪɴs ᴏɴʟʏ:* + +• /ban <userhandle>*:*` ʙᴀɴs ᴀ ᴜsᴇʀ. (ᴠɪᴀ ʜᴀɴᴅʟᴇ, ᴏʀ ʀᴇᴘʟʏ ` +) +• /sban <ᴜsᴇʀʜᴀɴᴅʟᴇ>*:* `sɪʟᴇɴᴛʟʏ ʙᴀɴ ᴀ ᴜsᴇʀ. ᴅᴇʟᴇᴛᴇs ᴄᴏᴍᴍᴀɴᴅ, ʀᴇᴘʟɪᴇᴅ ᴍᴇssᴀɢᴇ ᴀɴᴅ ᴅᴏᴇsɴ'ᴛ ʀᴇᴘʟʏ. (ᴠɪᴀ ʜᴀɴᴅʟᴇ, ᴏʀ ʀᴇᴘʟʏ)` + +• /tban <ᴜsᴇʀʜᴀɴᴅʟᴇ> x(m/h/d)*:* `ʙᴀɴs ᴀ ᴜsᴇʀ ғᴏʀ x ᴛɪᴍᴇ. (ᴠɪᴀ ʜᴀɴᴅʟᴇ, ᴏʀ ʀᴇᴘʟʏ). ᴍ = ᴍɪɴᴜᴛᴇs, h = ʜᴏᴜʀs, d = ᴅᴀʏs.` + +• /unban <userhandle>*:* `ᴜɴʙᴀɴs ᴀ ᴜsᴇʀ. (ᴠɪᴀ ʜᴀɴᴅʟᴇ, ᴏʀ ʀᴇᴘʟʏ )` + +• /kick <userhandle>*:* `ᴋɪᴄᴋs ᴀ ᴜsᴇʀ ᴏᴜᴛ ᴏғ ᴛʜᴇ ɢʀᴏᴜᴘ, (via ʜᴀɴᴅʟᴇ, ᴏʀ ʀᴇᴘʟʏ)` + +• /mute <userhandle>*:* `sɪʟᴇɴᴄᴇs ᴀ ᴜsᴇʀ. ᴄᴀɴ ᴀʟsᴏ ʙᴇ ᴜsᴇᴅ ᴀs ᴀ ʀᴇᴘʟʏ, ᴍᴜᴛɪɴɢ ᴛʜᴇ ʀᴇᴘʟɪᴇᴅ ᴛᴏ ᴜsᴇʀ.` + +• /tmute <userhandle> x(m/h/d)*:* `ᴍᴜᴛᴇs a ᴜsᴇʀᴛ for x ᴛɪᴍᴇ. (ᴠɪᴀ ʜᴀɴᴅʟᴇ, ᴏʀ ʀᴇᴘʟʏ). ᴍ = ᴍɪɴᴜᴛᴇs, h = ʜᴏᴜʀs, d = ᴅᴀʏs ` +. +• /unmute <userhandle>*:* `ᴜɴᴍᴜᴛᴇs ᴀ ~ user. ᴄᴀɴ ᴀʟsᴏ ʙᴇ ᴜsᴇᴅ ᴀs a ʀᴇᴘʟʏ, ᴍᴜᴛɪɴɢ ᴛʜᴇ ʀᴇᴘʟɪᴇᴅ ᴛᴏ ᴜsᴇʀ ` +. +• /zombies*:* `sᴇᴀʀᴄʜᴇs ᴅᴇʟᴇᴛᴇᴅ ᴀᴄᴄᴏᴜɴᴛ ` + +• /zombies clean*:* `ʀᴇᴍᴏᴠᴇs ᴅᴇʟᴇᴛᴇᴅ ᴀᴄᴄᴏᴜɴᴛs ғʀᴏᴍ ᴛʜᴇ ɢʀᴏᴜᴘ ` +. +• /abishnoi <chatid> <ᴍsɢ>*:* `ᴍᴀᴋᴇ ᴍᴇ sᴇɴᴅ ᴀ ᴍᴇssᴀɢᴇ ᴛᴏ ᴀ sᴘᴇᴄɪғɪᴄ ᴄʜᴀᴛ `. +""" + +__mod_name__ = "𝙱ᴀɴs" + +BAN_HANDLER = CommandHandler(["ban", "sban"], ban, run_async=True) +TEMPBAN_HANDLER = CommandHandler(["tban"], temp_ban, run_async=True) +KICK_HANDLER = CommandHandler(["kick", "punch"], punch, run_async=True) +UNBAN_HANDLER = CommandHandler("unban", unban, run_async=True) +##ROAR_HANDLER = CommandHandler("roar", selfunban, run_async=True) +UNBAN_BUTTON_HANDLER = CallbackQueryHandler(unbanb_btn, pattern=r"unbanb_") +KICKME_HANDLER = DisableAbleCommandHandler( + ["kickme", "punchme"], punchme, filters=Filters.chat_type.groups, run_async=True +) +ABISHNOI_HANDLER = CommandHandler( + "abishnoi", + abishnoi, + pass_args=True, + filters=CustomFilters.sudo_filter, + run_async=True, +) +BANME_HANDLER = CommandHandler("banme", banme, run_async=True) + +dispatcher.add_handler(BAN_HANDLER) +dispatcher.add_handler(TEMPBAN_HANDLER) +dispatcher.add_handler(KICK_HANDLER) +dispatcher.add_handler(UNBAN_HANDLER) +# dispatcher.add_handler(ROAR_HANDLER) +dispatcher.add_handler(KICKME_HANDLER) +dispatcher.add_handler(UNBAN_BUTTON_HANDLER) +dispatcher.add_handler(ABISHNOI_HANDLER) +dispatcher.add_handler(BANME_HANDLER) + +__handlers__ = [ + BAN_HANDLER, + TEMPBAN_HANDLER, + KICK_HANDLER, + UNBAN_HANDLER, + # ROAR_HANDLER, + KICKME_HANDLER, + UNBAN_BUTTON_HANDLER, + ABISHNOI_HANDLER, + BANME_HANDLER, +] diff --git a/Exon/modules/blacklist.py b/Exon/modules/blacklist.py new file mode 100644 index 00000000..d775e466 --- /dev/null +++ b/Exon/modules/blacklist.py @@ -0,0 +1,508 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import html +import re + +from telegram import ChatPermissions, ParseMode +from telegram.error import BadRequest +from telegram.ext import CommandHandler, Filters, MessageHandler +from telegram.utils.helpers import mention_html + +import Exon.modules.sql.blacklist_sql as sql +from Exon import LOGGER, dispatcher +from Exon.modules.connection import connected +from Exon.modules.disable import DisableAbleCommandHandler +from Exon.modules.helper_funcs.alternate import send_message, typing_action +from Exon.modules.helper_funcs.chat_status import user_admin, user_not_admin +from Exon.modules.helper_funcs.extraction import extract_text +from Exon.modules.helper_funcs.misc import split_message +from Exon.modules.helper_funcs.string_handling import extract_time +from Exon.modules.log_channel import loggable +from Exon.modules.sql.approve_sql import is_approved +from Exon.modules.warns import warn + +BLACKLIST_GROUP = 11 + + +@user_admin +@typing_action +def blacklist(update, context): + chat = update.effective_chat + user = update.effective_user + args = context.args + + if conn := connected(context.bot, update, chat, user.id, need_admin=False): + chat_id = conn + chat_name = dispatcher.bot.getChat(conn).title + else: + if chat.type == "private": + return + chat_id = update.effective_chat.id + chat_name = chat.title + + filter_list = f"ᴄᴜʀʀᴇɴᴛ ʙʟᴀᴄᴋʟɪsᴛᴇᴅ ᴡᴏʀᴅs ɪɴ <b>{chat_name}</b>:\n" + + all_blacklisted = sql.get_chat_blacklist(chat_id) + + if len(args) > 0 and args[0].lower() == "copy": + for trigger in all_blacklisted: + filter_list += f"<code>{html.escape(trigger)}</code>\n" + else: + for trigger in all_blacklisted: + filter_list += f" - <code>{html.escape(trigger)}</code>\n" + + # for trigger in all_blacklisted: + # filter_list += " - <code>{}</code>\n".format(html.escape(trigger)) + + split_text = split_message(filter_list) + for text in split_text: + if ( + filter_list + == f"ᴄᴜʀʀᴇɴᴛ ʙʟᴀᴄᴋʟɪsᴛᴇᴅ ᴡᴏʀᴅs ɪɴ <b>{html.escape(chat_name)}</b>:\n" + ): + send_message( + update.effective_message, + f"ɴᴏ ʙʟᴀᴄᴋʟɪsᴛᴇᴅ ᴡᴏʀᴅs ɪɴ <b>{html.escape(chat_name)}</b>!", + parse_mode=ParseMode.HTML, + ) + + return + send_message(update.effective_message, text, parse_mode=ParseMode.HTML) + + +@user_admin +@typing_action +def add_blacklist(update, context): + msg = update.effective_message + chat = update.effective_chat + user = update.effective_user + words = msg.text.split(None, 1) + + if conn := connected(context.bot, update, chat, user.id): + chat_id = conn + chat_name = dispatcher.bot.getChat(conn).title + else: + chat_id = update.effective_chat.id + if chat.type == "private": + return + chat_name = chat.title + + if len(words) > 1: + text = words[1] + to_blacklist = list( + {trigger.strip() for trigger in text.split("\n") if trigger.strip()} + ) + for trigger in to_blacklist: + sql.add_to_blacklist(chat_id, trigger.lower()) + + if len(to_blacklist) == 1: + send_message( + update.effective_message, + f"ᴀᴅᴅᴇᴅ ʙʟᴀᴄᴋʟɪsᴛ <code>{html.escape(to_blacklist[0])}</code> ɪɴ ᴄʜᴀᴛ: <b>{html.escape(chat_name)}</b>!", + parse_mode=ParseMode.HTML, + ) + + else: + send_message( + update.effective_message, + f"ᴀᴅᴅᴇᴅ ʙʟᴀᴄᴋʟɪsᴛ ᴛʀɪɢɢᴇʀ: <code>{len(to_blacklist)}</code> ɪɴ <b>{html.escape(chat_name)}</b>!", + parse_mode=ParseMode.HTML, + ) + + else: + send_message( + update.effective_message, + "ᴛᴇʟʟ ᴍᴇ ᴡʜɪᴄʜ ᴡᴏʀᴅs ʏᴏᴜ ᴡᴏᴜʟᴅ ʟɪᴋᴇ ᴛᴏ ᴀᴅᴅ ɪɴ ʙʟᴀᴄᴋʟɪsᴛ.", + ) + + +@user_admin +@typing_action +def unblacklist(update, context): + msg = update.effective_message + chat = update.effective_chat + user = update.effective_user + words = msg.text.split(None, 1) + + if conn := connected(context.bot, update, chat, user.id): + chat_id = conn + chat_name = dispatcher.bot.getChat(conn).title + else: + chat_id = update.effective_chat.id + if chat.type == "private": + return + chat_name = chat.title + + if len(words) > 1: + text = words[1] + to_unblacklist = list( + {trigger.strip() for trigger in text.split("\n") if trigger.strip()} + ) + successful = 0 + for trigger in to_unblacklist: + success = sql.rm_from_blacklist(chat_id, trigger.lower()) + if success: + successful += 1 + + if len(to_unblacklist) == 1: + if successful: + send_message( + update.effective_message, + f"ʀᴇᴍᴏᴠᴇᴅ <code>{html.escape(to_unblacklist[0])}</code> ғʀᴏᴍ ʙʟᴀᴄᴋʟɪsᴛ ɪɴ <b>{html.escape(chat_name)}</b>!", + parse_mode=ParseMode.HTML, + ) + + else: + send_message( + update.effective_message, "ᴛʜɪs ɪs ɴᴏᴛ ᴀ ʙʟᴀᴄᴋʟɪsᴛ ᴛʀɪɢɢᴇʀ!" + ) + + elif successful == len(to_unblacklist): + send_message( + update.effective_message, + f"ʀᴇᴍᴏᴠᴇᴅ <code>{successful}</code> ғʀᴏᴍ ʙʟᴀᴄᴋʟɪsᴛ ɪɴ <b>{html.escape(chat_name)}</b>!", + parse_mode=ParseMode.HTML, + ) + + elif not successful: + send_message( + update.effective_message, + "ɴᴏɴᴇ ᴏғ ᴛʜᴇsᴇ ᴛʀɪɢɢᴇʀs ᴇxɪsᴛ sᴏ ɪᴛ ᴄᴀɴ'ᴛ ʙᴇ ʀᴇᴍᴏᴠᴇᴅ.".format( + successful, len(to_unblacklist) - successful + ), + parse_mode=ParseMode.HTML, + ) + + else: + send_message( + update.effective_message, + f"ʀᴇᴍᴏᴠᴇᴅ <code>{successful}</code> ғʀᴏᴍ ʙʟᴀᴄᴋʟɪsᴛ. {len(to_unblacklist) - successful} ᴅɪᴅ ɴᴏᴛ ᴇxɪsᴛ, sᴏ ᴡᴇʀᴇ ɴᴏᴛ ʀᴇᴍᴏᴠᴇᴅ.", + parse_mode=ParseMode.HTML, + ) + + else: + send_message( + update.effective_message, + "ᴛᴇʟʟ ᴍᴇ ᴡʜɪᴄʜ ᴡᴏʀᴅs ʏᴏᴜ ᴡᴏᴜʟᴅ ʟɪᴋᴇ ᴛᴏ ʀᴇᴍᴏᴠᴇ ғʀᴏᴍ ʙʟᴀᴄᴋʟɪsᴛ!", + ) + + +@loggable +@user_admin +@typing_action +def blacklist_mode(update, context): + chat = update.effective_chat + user = update.effective_user + msg = update.effective_message + args = context.args + + conn = connected(context.bot, update, chat, user.id, need_admin=True) + if conn: + chat = dispatcher.bot.getChat(conn) + chat_id = conn + chat_name = dispatcher.bot.getChat(conn).title + else: + if update.effective_message.chat.type == "private": + send_message( + update.effective_message, + "ᴛʜɪs ᴄᴏᴍᴍᴀɴᴅ ᴄᴀɴ ʙᴇ ᴏɴʟʏ ᴜsᴇᴅ ɪɴ ɢʀᴏᴜᴘ ɴᴏᴛ ɪɴ ᴘᴍ", + ) + return "" + chat = update.effective_chat + chat_id = update.effective_chat.id + chat_name = update.effective_message.chat.title + + if args: + if args[0].lower() in ("off", "nothing", "no"): + settypeblacklist = "ᴅᴏ ɴᴏᴛʜɪɴɢ" + sql.set_blacklist_strength(chat_id, 0, "0") + elif args[0].lower() in ("del", "delete"): + settypeblacklist = "ᴡɪʟʟ ᴅᴇʟᴇᴛᴇ ʙʟᴀᴄᴋʟɪsᴛᴇᴅ ᴍᴇssᴀɢᴇ" + sql.set_blacklist_strength(chat_id, 1, "0") + elif args[0].lower() == "warn": + settypeblacklist = "ᴡᴀʀɴ ᴛʜᴇ sᴇɴᴅᴇʀ" + sql.set_blacklist_strength(chat_id, 2, "0") + elif args[0].lower() == "mute": + settypeblacklist = "ᴍᴜᴛᴇ ᴛʜᴇ sᴇɴᴅᴇʀ" + sql.set_blacklist_strength(chat_id, 3, "0") + elif args[0].lower() == "kick": + settypeblacklist = "ᴋɪᴄᴋ ᴛʜᴇ sᴇɴᴅᴇʀ" + sql.set_blacklist_strength(chat_id, 4, "0") + elif args[0].lower() == "ban": + settypeblacklist = "ʙᴀɴ ᴛʜᴇ sᴇɴᴅᴇʀ" + sql.set_blacklist_strength(chat_id, 5, "0") + elif args[0].lower() == "tban": + if len(args) == 1: + teks = """ɪᴛ ʟᴏᴏᴋs ʟɪᴋᴇ ʏᴏᴜ ᴛʀɪᴇᴅ ᴛᴏ sᴇᴛ ᴛɪᴍᴇ ᴠᴀʟᴜᴇ ғᴏʀ ʙʟᴀᴄᴋʟɪsᴛ ʙᴜᴛ ʏᴏᴜ ᴅɪᴅɴ'ᴛ sᴘᴇᴄɪғɪᴇᴅ ᴛɪᴍᴇ; ᴛʀʏ :, `/blacklistmode tban <timevalue>`. + + ᴇxᴀᴍᴘʟᴇs ᴏғ ᴛɪᴍᴇ ᴠᴀʟᴜᴇ: 4ᴍ = 4 ᴍɪɴᴜᴛᴇs, 3ʜ = 3 ʜᴏᴜʀs, 6ᴅ = 6 ᴅᴀʏs, 5ᴡ = 5 ᴡᴇᴇᴋs.""" + send_message(update.effective_message, teks, parse_mode="markdown") + return "" + restime = extract_time(msg, args[1]) + if not restime: + teks = """ɪɴᴠᴀʟɪᴅ ᴛɪᴍᴇ ᴠᴀʟᴜᴇ! + ᴇxᴀᴍᴘʟᴇ ᴏғ ᴛɪᴍᴇ ᴠᴀʟᴜᴇ: 4ᴍ = 4 ᴍɪɴᴜᴛᴇs, 3ʜ = 3 ʜᴏᴜʀs, 6ᴅ = 6 ᴅᴀʏs, 5ᴡ = 5 ᴡᴇᴇᴋs.""" + send_message(update.effective_message, teks, parse_mode="markdown") + return "" + settypeblacklist = f"ᴛᴇᴍᴘᴏʀᴀʀɪʟʏ ʙᴀɴ ғᴏʀ {args[1]}" + sql.set_blacklist_strength(chat_id, 6, str(args[1])) + elif args[0].lower() == "tmute": + if len(args) == 1: + teks = """It ʟᴏᴏᴋs ʟɪᴋᴇ ʏᴏᴜ ᴛʀɪᴇᴅ ᴛᴏ sᴇᴛ ᴛɪᴍᴇ ᴠᴀʟᴜᴇ ғᴏʀ ʙʟᴀᴄᴋʟɪsᴛ ʙᴜᴛ ʏᴏᴜ ᴅɪᴅɴ'ᴛ sᴘᴇᴄɪғɪᴇᴅ ᴛɪᴍᴇ; ᴛʀʏ, `/blacklistmode tmute <timevalue>`. + ᴇxᴀᴍᴘʟᴇs ᴏғ ᴛɪᴍᴇ ᴠᴀʟᴜᴇ: 4m = 4 ᴍɪɴᴜᴛᴇs, 3h = 3 ʜᴏᴜʀs, 6d = 6 ᴅᴀʏs, 5w = 5 ᴡᴇᴇᴋs.""" + send_message(update.effective_message, teks, parse_mode="markdown") + return "" + restime = extract_time(msg, args[1]) + if not restime: + teks = """ɪɴᴠᴀʟɪᴅ ᴛɪᴍᴇ ᴠᴀʟᴜᴇ! + ᴇxᴀᴍᴘʟᴇs ᴏғ ᴛɪᴍᴇ ᴠᴀʟᴜᴇ: 4ᴍ = 4 ᴍɪɴᴜᴛᴇs, 3ʜ = 3 ʜᴏᴜʀs, 6ᴅ = 6 ᴅᴀʏs, 5w = 5 ᴡᴇᴇᴋs.""" + send_message(update.effective_message, teks, parse_mode="markdown") + return "" + settypeblacklist = f"ᴛᴇᴍᴘᴏʀᴀʀɪʟʏ ᴍᴜᴛᴇ ғᴏʀ {args[1]}" + sql.set_blacklist_strength(chat_id, 7, str(args[1])) + else: + send_message( + update.effective_message, + "I ᴏɴʟʏ ᴜɴᴅᴇʀsᴛᴀɴᴅ: off/del/warn/ban/kick/mute/tban/tmute!", + ) + return "" + if conn: + text = f"ᴄʜᴀɴɢᴇᴅ ʙʟᴀᴄᴋʟɪsᴛ ᴍᴏᴅᴇ: `{settypeblacklist}` in *{chat_name}*!" + else: + text = f"ᴄʜᴀɴɢᴇᴅ ʙʟᴀᴄᴋʟɪsᴛ ᴍᴏᴅᴇ: `{settypeblacklist}`!" + send_message(update.effective_message, text, parse_mode="markdown") + return f"<b>{html.escape(chat.title)}:</b>\n<b>ᴀᴅᴍɪɴ:</b> {mention_html(user.id, html.escape(user.first_name))}\nᴄʜᴀɴɢᴇᴅ ᴛʜᴇ ʙʟᴀᴄᴋʟɪsᴛ ᴍᴏᴅᴇ. ᴡɪʟʟ {settypeblacklist}." + + getmode, getvalue = sql.get_blacklist_setting(chat.id) + if getmode == 0: + settypeblacklist = "ᴅᴏ ɴᴏᴛʜɪɴɢ" + elif getmode == 1: + settypeblacklist = "ᴅᴇʟᴇᴛᴇ" + elif getmode == 2: + settypeblacklist = "ᴡᴀʀɴ" + elif getmode == 3: + settypeblacklist = "ᴍᴜᴛᴇ" + elif getmode == 4: + settypeblacklist = "ᴋɪᴄᴋ" + elif getmode == 5: + settypeblacklist = "ʙᴀɴ" + elif getmode == 6: + settypeblacklist = f"ᴛᴇᴍᴘᴏʀᴀʀɪʟʏ ʙᴀɴ ғᴏʀ {getvalue}" + elif getmode == 7: + settypeblacklist = f"ᴛᴇᴍᴘᴏʀᴀʀɪʟʏ ᴍᴜᴛᴇ ғᴏʀ {getvalue}" + if conn: + text = f"ᴄᴜʀʀᴇɴᴛ ʙʟᴀᴄᴋʟɪsᴛᴍᴏᴅᴇ: *{settypeblacklist}* in *{chat_name}*." + else: + text = f"ᴄᴜʀʀᴇɴᴛ ʙʟᴀᴄᴋʟɪsᴛᴍᴏᴅᴇ: *{settypeblacklist}*." + send_message(update.effective_message, text, parse_mode=ParseMode.MARKDOWN) + return "" + + +def findall(p, s): + i = s.find(p) + while i != -1: + yield i + i = s.find(p, i + 1) + + +@user_not_admin +def del_blacklist(update, context): + chat = update.effective_chat + message = update.effective_message + user = update.effective_user + bot = context.bot + to_match = extract_text(message) + + if not to_match: + return + + if is_approved(chat.id, user.id): + return + + getmode, value = sql.get_blacklist_setting(chat.id) + + chat_filters = sql.get_chat_blacklist(chat.id) + for trigger in chat_filters: + pattern = r"( |^|[^\w])" + re.escape(trigger) + r"( |$|[^\w])" + if re.search(pattern, to_match, flags=re.IGNORECASE): + try: + if getmode == 0: + return + if getmode == 1: + message.delete() + elif getmode == 2: + message.delete() + warn( + update.effective_user, + chat, + f"ᴜsɪɴɢ ʙʟᴀᴄᴋʟɪsᴛᴇᴅ ᴛʀɪɢɢᴇʀ: {trigger}", + message, + update.effective_user, + ) + + return + elif getmode == 3: + message.delete() + bot.restrict_chat_member( + chat.id, + update.effective_user.id, + permissions=ChatPermissions(can_send_messages=False), + ) + bot.sendMessage( + chat.id, + f"ᴍᴜᴛᴇᴅ {user.first_name} ғᴏʀ ᴜsɪɴɢ ʙʟᴀᴄᴋʟɪsᴛᴇᴅ ᴡᴏʀᴅ: {trigger}!", + ) + return + elif getmode == 4: + message.delete() + if res := chat.unban_member(update.effective_user.id): + bot.sendMessage( + chat.id, + f"ᴋɪᴄᴋᴇᴅ {user.first_name} ғᴏʀ ᴜsɪɴɢ ʙʟᴀᴄᴋʟɪsᴛᴇᴅ ᴡᴏʀᴅ: {trigger}!", + ) + return + elif getmode == 5: + message.delete() + chat.ban_member(user.id) + bot.sendMessage( + chat.id, + f"ʙᴀɴɴᴇᴅ {user.first_name} ғᴏʀ ᴜsɪɴɢ ʙʟᴀᴄᴋʟɪsᴛᴇᴅ ᴡᴏʀᴅ: {trigger}", + ) + return + elif getmode == 6: + message.delete() + bantime = extract_time(message, value) + chat.ban_member(user.id, until_date=bantime) + bot.sendMessage( + chat.id, + f"ʙᴀɴɴᴇᴅ {user.first_name} ᴜɴᴛɪʟ '{value}' ғᴏʀ ᴜsɪɴɢ ʙʟᴀᴄᴋʟɪsᴛᴇᴅ ᴡᴏʀᴅ: {trigger}!", + ) + return + elif getmode == 7: + message.delete() + mutetime = extract_time(message, value) + bot.restrict_chat_member( + chat.id, + user.id, + until_date=mutetime, + permissions=ChatPermissions(can_send_messages=False), + ) + bot.sendMessage( + chat.id, + f"ᴍᴜᴛᴇᴅ {user.first_name} ᴜɴᴛɪʟ '{value}' for using Blacklisted word: {trigger}!", + ) + return + except BadRequest as excp: + if excp.message != "ᴍᴇssᴀɢᴇ ᴛᴏ ᴅᴇʟᴇᴛᴇ not ғᴏᴜɴᴅ": + LOGGER.exception("ᴇʀʀᴏʀ ᴡʜɪʟᴇ ᴅᴇʟᴇᴛɪɴɢ ʙʟᴀᴄᴋʟɪsᴛ ᴍᴇssᴀɢᴇ.") + break + + +def __import_data__(chat_id, data): + # set chat blacklist + blacklist = data.get("blacklist", {}) + for trigger in blacklist: + sql.add_to_blacklist(chat_id, trigger) + + +def __migrate__(old_chat_id, new_chat_id): + sql.migrate_chat(old_chat_id, new_chat_id) + + +def __chat_settings__(chat_id, user_id): + blacklisted = sql.num_blacklist_chat_filters(chat_id) + return f"ᴛʜᴇʀᴇ ᴀʀᴇ {blacklisted} ʙʟᴀᴄᴋʟɪsᴛᴇᴅ ᴡᴏʀᴅs." + + +def __stats__(): + return f"⍟ {sql.num_blacklist_filters()} ʙʟᴀᴄᴋʟɪsᴛ ᴛʀɪɢɢᴇʀs, ᴀᴄʀᴏss {sql.num_blacklist_filter_chats()} ᴄʜᴀᴛs." + + +__mod_name__ = "𝙱ʟᴀᴄᴋʟɪsᴛs" + +__help__ = """ + +*ʙʟᴀᴄᴋʟɪꜱᴛꜱ ᴀʀᴇ ᴜꜱᴇᴅ ᴛᴏ ꜱᴛᴏᴘ ᴄᴇʀᴛᴀɪɴ ᴛʀɪɢɢᴇʀꜱ ғʀᴏᴍ ʙᴇɪɴɢ ꜱᴀɪᴅ ɪɴ ᴀ ɢʀᴏᴜᴘ. ᴀɴʏ ᴛɪᴍᴇ ᴛʜᴇ ᴛʀɪɢɢᴇʀ ɪꜱ ᴍᴇɴᴛɪᴏɴᴇᴅ, ᴛʜᴇ ᴍᴇꜱꜱᴀɢᴇ ᴡɪʟʟ ɪᴍᴍᴇᴅɪᴀᴛᴇʟʏ ʙᴇ ᴅᴇʟᴇᴛᴇᴅ. ᴀ ɢᴏᴏᴅ ᴄᴏᴍʙᴏ ɪs sᴏᴍᴇᴛɪᴍᴇs ᴛᴏ ᴘᴀɪʀ ᴛʜɪs ᴜᴘ ᴡɪᴛʜ ᴡᴀʀɴ ғɪʟᴛᴇʀs!* + +*ɴᴏᴛᴇ*: `ʙʟᴀᴄᴋʟɪsᴛs ᴅᴏ ɴᴏᴛ ᴀғғᴇᴄᴛ ɢʀᴏᴜᴘ ᴀᴅᴍɪɴs.` + +✪ /blacklist*:* `ᴠɪᴇᴡ ᴛʜᴇ ᴄᴜʀʀᴇɴᴛ ʙʟᴀᴄᴋʟɪsᴛᴇᴅ ᴡᴏʀᴅs.` + +*ᴀᴅᴍɪɴ ᴏɴʟʏ:* +✪ /addblacklist <ᴛʀɪɢɢᴇʀs>*:* `ᴀᴅᴅ ᴀ ᴛʀɪɢɢᴇʀ ᴛᴏ ᴛʜᴇ ʙʟᴀᴄᴋʟɪsᴛ. ᴇᴀᴄʜ ʟɪɴᴇ is ᴄᴏɴsɪᴅᴇʀᴇᴅ ᴏɴᴇ ᴛʀɪɢɢᴇʀ, sᴏ ᴜsɪɴɢ ᴅɪғғᴇʀᴇɴᴛ ʟɪɴᴇs ᴡɪʟʟ ᴀʟʟᴏᴡ ʏᴏᴜ ᴛᴏ ᴀᴅᴅ ᴍᴜʟᴛɪᴘʟᴇ ᴛʀɪɢɢᴇʀs.` + +✪ /unblacklist <ᴛʀɪɢɢᴇʀs>*:* `ʀᴇᴍᴏᴠᴇ ᴛʀɪɢɢᴇʀs ғʀᴏᴍ ᴛʜᴇ ʙʟᴀᴄᴋʟɪsᴛ. sᴀᴍᴇ ɴᴇᴡʟɪɴᴇ ʟᴏɢɪᴄ ᴀᴘᴘʟɪᴇs ʜᴇʀᴇ, sᴏ ʏᴏᴜ ᴄᴀɴ ʀᴇᴍᴏᴠᴇ ᴍᴜʟᴛɪᴘʟᴇ ᴛʀɪɢɢᴇʀs ᴀᴛ ᴏɴᴄᴇ.` + +✪ /blacklistmode <off/del/warn/ban/kick/mute/tban/tmute>*:* `ᴀᴄᴛɪᴏɴ ᴛᴏ ᴘᴇʀғᴏʀᴍ ᴡʜᴇɴ sᴏᴍᴇᴏɴᴇ sᴇɴᴅs ʙʟᴀᴄᴋʟɪsᴛᴇᴅ ᴡᴏʀᴅs.` + +`ʀʙʟᴀᴄᴋʟɪsᴛ sᴛɪᴄᴋᴇʀ ɪs ᴜsᴇᴅ ᴛᴏ sᴛᴏᴘ ᴄᴇʀᴛᴀɪɴ sᴛɪᴄᴋᴇʀs. ᴡʜᴇɴᴇᴠᴇʀ a sᴛɪᴄᴋᴇʀ ɪs sᴇɴᴛ, ᴛʜᴇ ᴍᴇssᴀɢᴇ ᴡɪʟʟ ʙᴇ ᴅᴇʟᴇᴛᴇᴅ ɪᴍᴍᴇᴅɪᴀᴛᴇʟʏ.` + +*ɴᴏᴛᴇ:* `ʙʟᴀᴄᴋʟɪsᴛ sᴛɪᴄᴋᴇʀs ᴅᴏ ɴᴏᴛ ᴀғғᴇᴄᴛ ᴛʜᴇ ɢʀᴏᴜᴘ ᴀᴅᴍɪɴ` + +✪ /blsticker*:* `ꜱᴇᴇ ᴄᴜʀʀᴇɴᴛ ʙʟᴀᴄᴋʟɪꜱᴛᴇᴅ ꜱᴛɪᴄᴋᴇʀ` + + +✪ /addblsticker <sticker link>*:* `ᴀᴅᴅ ᴛʜᴇ ꜱᴛɪᴄᴋᴇʀ ᴛʀɪɢɢᴇʀ ᴛᴏ ᴛʜᴇ ʙʟᴀᴄᴋ ʟɪꜱᴛ. ᴄᴀɴ ʙᴇ ᴀᴅᴅᴇᴅ ᴠɪᴀ ʀᴇᴘʟʏ ꜱᴛɪᴄᴋᴇʀ` + +✪ /unblsticker <sticker link>*:* `ʀᴇᴍᴏᴠᴇ ᴛʀɪɢɢᴇʀꜱ ғʀᴏᴍ ʙʟᴀᴄᴋʟɪꜱᴛ. ᴛʜᴇ ꜱᴀᴍᴇ ɴᴇᴡʟɪɴᴇ ʟᴏɢɪᴄ ᴀᴘᴘʟɪᴇꜱ ʜᴇʀᴇ, ꜱᴏ ʏᴏᴜ ᴄᴀɴ ᴅᴇʟᴇᴛᴇ ᴍᴜʟᴛɪᴘʟᴇ ᴛʀɪɢɢᴇʀꜱ ᴀᴛ ᴏɴᴄᴇ` + +✪ /rmblsticker <sticker link>*:* `ꜱᴀᴍᴇ ᴀꜱ ᴀʙᴏᴠᴇ` + +✪ /blstickermode <delete/ban/tban/mute/tmute>*:* `ꜱᴇᴛꜱ ᴜᴘ ᴀ ᴅᴇғᴀᴜʟᴛ ᴀᴄᴛɪᴏɴ ᴏɴ ᴡʜᴀᴛ ᴛᴏ ᴅᴏ ɪғ ᴜꜱᴇʀꜱ ᴜꜱᴇ ʙʟᴀᴄᴋʟɪꜱᴛᴇᴅ ꜱᴛɪᴄᴋᴇʀꜱ` + +ɴᴏᴛᴇ: +✪ <sticker link> `ᴄᴀɴ ʙᴇ` `https://t.me/addstickers/<sticker>` `ᴏʀ ᴊᴜꜱᴛ` `<sticker>` `ᴏʀ ʀᴇᴘʟʏ ᴛᴏ ᴛʜᴇ ꜱᴛɪᴄᴋᴇʀ ᴍᴇꜱꜱᴀɢᴇ` + +""" + +BLACKLIST_HANDLER = DisableAbleCommandHandler( + "blacklist", blacklist, pass_args=True, admin_ok=True, run_async=True +) +ADD_BLACKLIST_HANDLER = CommandHandler("addblacklist", add_blacklist, run_async=True) +UNBLACKLIST_HANDLER = CommandHandler("unblacklist", unblacklist, run_async=True) +BLACKLISTMODE_HANDLER = CommandHandler( + "blacklistmode", blacklist_mode, pass_args=True, run_async=True +) +BLACKLIST_DEL_HANDLER = MessageHandler( + (Filters.text | Filters.command | Filters.sticker | Filters.photo) + & Filters.chat_type.groups, + del_blacklist, + allow_edit=True, + run_async=True, +) + +dispatcher.add_handler(BLACKLIST_HANDLER) +dispatcher.add_handler(ADD_BLACKLIST_HANDLER) +dispatcher.add_handler(UNBLACKLIST_HANDLER) +dispatcher.add_handler(BLACKLISTMODE_HANDLER) +dispatcher.add_handler(BLACKLIST_DEL_HANDLER, group=BLACKLIST_GROUP) + +__handlers__ = [ + BLACKLIST_HANDLER, + ADD_BLACKLIST_HANDLER, + UNBLACKLIST_HANDLER, + BLACKLISTMODE_HANDLER, + (BLACKLIST_DEL_HANDLER, BLACKLIST_GROUP), +] diff --git a/Exon/modules/blacklist_stickers.py b/Exon/modules/blacklist_stickers.py new file mode 100644 index 00000000..856c976d --- /dev/null +++ b/Exon/modules/blacklist_stickers.py @@ -0,0 +1,515 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import html +from typing import Optional + +from telegram import Chat, ChatPermissions, Message, ParseMode, Update, User +from telegram.error import BadRequest +from telegram.ext import CallbackContext, CommandHandler, Filters, MessageHandler +from telegram.utils.helpers import mention_html, mention_markdown + +import Exon.modules.sql.blsticker_sql as sql +from Exon import LOGGER, dispatcher +from Exon.modules.connection import connected +from Exon.modules.disable import DisableAbleCommandHandler +from Exon.modules.helper_funcs.alternate import send_message +from Exon.modules.helper_funcs.chat_status import user_admin, user_not_admin +from Exon.modules.helper_funcs.misc import split_message +from Exon.modules.helper_funcs.string_handling import extract_time +from Exon.modules.log_channel import loggable +from Exon.modules.sql.approve_sql import is_approved +from Exon.modules.warns import warn + + +def blackliststicker(update: Update, context: CallbackContext): + msg = update.effective_message # type: Optional[Message] + chat = update.effective_chat # type: Optional[Chat] + user = update.effective_user # type: Optional[User] + bot, args = context.bot, context.args + if conn := connected(bot, update, chat, user.id, need_admin=False): + chat_id = conn + chat_name = dispatcher.bot.getChat(conn).title + else: + if chat.type == "private": + return + chat_id = update.effective_chat.id + chat_name = chat.title + + sticker_list = f"<b>ʟɪsᴛ ʙʟᴀᴄᴋʟɪsᴛᴇᴅ sᴛɪᴄᴋᴇʀs ᴄᴜʀʀᴇɴᴛʟʏ ɪɴ {chat_name}:</b>\n" + + all_stickerlist = sql.get_chat_stickers(chat_id) + + if len(args) > 0 and args[0].lower() == "copy": + for trigger in all_stickerlist: + sticker_list += f"<code>{html.escape(trigger)}</code>\n" + elif len(args) == 0: + for trigger in all_stickerlist: + sticker_list += f" - <code>{html.escape(trigger)}</code>\n" + + split_text = split_message(sticker_list) + for text in split_text: + if ( + sticker_list + == f"<b>ʟɪsᴛ ʙʟᴀᴄᴋʟɪsᴛᴇᴅ sᴛɪᴄᴋᴇʀs ᴄᴜʀʀᴇɴᴛʟʏ ɪɴ {chat_name}:</b>\n".format( + html.escape(chat_name) + ) + ): + send_message( + update.effective_message, + f"ᴛʜᴇʀᴇ ᴀʀᴇ ɴᴏ ʙʟᴀᴄᴋʟɪsᴛ sᴛɪᴄᴋᴇʀs ɪɴ <b>{html.escape(chat_name)}</b>!", + parse_mode=ParseMode.HTML, + ) + + return + send_message(update.effective_message, text, parse_mode=ParseMode.HTML) + + +@user_admin +def add_blackliststicker(update: Update, context: CallbackContext): + bot = context.bot + msg = update.effective_message # type: Optional[Message] + chat = update.effective_chat # type: Optional[Chat] + user = update.effective_user # type: Optional[User] + words = msg.text.split(None, 1) + bot = context.bot + if conn := connected(bot, update, chat, user.id): + chat_id = conn + chat_name = dispatcher.bot.getChat(conn).title + else: + chat_id = update.effective_chat.id + if chat.type == "private": + return + chat_name = chat.title + + if len(words) > 1: + text = words[1].replace("https://t.me/addstickers/", "") + to_blacklist = list( + {trigger.strip() for trigger in text.split("\n") if trigger.strip()}, + ) + + added = 0 + for trigger in to_blacklist: + try: + bot.getStickerSet(trigger) + sql.add_to_stickers(chat_id, trigger.lower()) + added += 1 + except BadRequest: + send_message( + update.effective_message, + f"sᴛɪᴄᴋᴇʀ `{trigger}` ᴄᴀɴ ɴᴏᴛ ʙᴇ ғᴏᴜɴᴅ!", + parse_mode="markdown", + ) + + if added == 0: + return + + if len(to_blacklist) == 1: + send_message( + update.effective_message, + f"sᴛɪᴄᴋᴇʀ <code>{html.escape(to_blacklist[0])}</code> ᴀᴅᴅᴇᴅ ᴛᴏ ʙʟᴀᴄᴋʟɪsᴛ sᴛɪʏᴄᴋᴇʀs ɪɴ <b>{html.escape(chat_name)}</b>!", + parse_mode=ParseMode.HTML, + ) + + else: + send_message( + update.effective_message, + f"<code>{added}</code> sᴛɪᴄᴋᴇʀs ᴀᴅᴅᴇᴅ ᴛᴏ ʙʟᴀᴄᴋʟɪsᴛ sᴛɪᴄᴋᴇʀ ɪɴ <b>{html.escape(chat_name)}</b>!", + parse_mode=ParseMode.HTML, + ) + + elif msg.reply_to_message: + added = 0 + trigger = msg.reply_to_message.sticker.set_name + if trigger is None: + send_message(update.effective_message, "sᴛɪᴄᴋᴇʀ ɪs ɪɴᴠᴀʟɪᴅ!") + return + try: + bot.getStickerSet(trigger) + sql.add_to_stickers(chat_id, trigger.lower()) + added += 1 + except BadRequest: + send_message( + update.effective_message, + f"sᴛɪᴄᴋᴇʀ `{trigger}` ᴄᴀɴ ɴᴏᴛ ʙᴇ ғᴏᴜɴᴅ!", + parse_mode="markdown", + ) + + if added == 0: + return + + send_message( + update.effective_message, + f"Sticker <code>{trigger}</code> ᴀᴅᴅᴇᴅ ᴛᴏ ʙʟᴀᴄᴋʟɪsᴛ sᴛɪᴄᴋᴇʀs ɪɴ <b>{html.escape(chat_name)}</b>!", + parse_mode=ParseMode.HTML, + ) + + else: + send_message( + update.effective_message, + "ᴛᴇʟʟ ᴍᴇ ᴡʜᴀᴛ sᴛɪᴄᴋᴇʀs ʏᴏᴜ ᴡᴀɴᴛ ᴛᴏ ᴀᴅᴅ ᴛᴏ ᴛʜᴇ ʙʟᴀᴄᴋʟɪsᴛ.", + ) + + +@user_admin +def unblackliststicker(update: Update, context: CallbackContext): + bot = context.bot + msg = update.effective_message # type: Optional[Message] + chat = update.effective_chat # type: Optional[Chat] + user = update.effective_user # type: Optional[User] + words = msg.text.split(None, 1) + bot = context.bot + if conn := connected(bot, update, chat, user.id): + chat_id = conn + chat_name = dispatcher.bot.getChat(conn).title + else: + chat_id = update.effective_chat.id + if chat.type == "private": + return + chat_name = chat.title + + if len(words) > 1: + text = words[1].replace("https://t.me/addstickers/", "") + to_unblacklist = list( + {trigger.strip() for trigger in text.split("\n") if trigger.strip()}, + ) + + successful = 0 + for trigger in to_unblacklist: + success = sql.rm_from_stickers(chat_id, trigger.lower()) + if success: + successful += 1 + + if len(to_unblacklist) == 1: + if successful: + send_message( + update.effective_message, + f"sᴛɪᴄᴋᴇʀ <code>{html.escape(to_unblacklist[0])}</code> ᴅᴇʟᴇᴛᴇᴅ ғʀᴏᴍ ʙʟᴀᴄᴋʟɪsᴛ ɪɴ <b>{html.escape(chat_name)}</b>!", + parse_mode=ParseMode.HTML, + ) + + else: + send_message( + update.effective_message, + "ᴛʜɪs sᴛɪᴄᴋᴇʀ ɪs ɴᴏᴛ ᴏɴ ᴛʜᴇ ʙʟᴀᴄᴋʟɪsᴛ...!", + ) + + elif successful == len(to_unblacklist): + send_message( + update.effective_message, + f"sᴛɪᴄᴋᴇʀ <code>{successful}</code> ᴅᴇʟᴇᴛᴇᴅ ғʀᴏᴍ ʙʟᴀᴄᴋʟɪsᴛ ɪɴ <b>{html.escape(chat_name)}</b>!", + parse_mode=ParseMode.HTML, + ) + + elif not successful: + send_message( + update.effective_message, + "ɴᴏɴᴇ ᴏғ ᴛʜᴇsᴇ sᴛɪᴄᴋᴇʀs ᴇxɪsᴛ, sᴏ ᴛʜᴇʏ ᴄᴀɴɴᴏᴛ ʙᴇ ʀᴇᴍᴏᴠᴇᴅ.", + parse_mode=ParseMode.HTML, + ) + + else: + send_message( + update.effective_message, + f"sᴛɪᴄᴋᴇʀ <code>{successful}</code> ᴅᴇʟᴇᴛᴇᴅ ғʀᴏᴍ ʙʟᴀᴄᴋʟɪsᴛ. {len(to_unblacklist) - successful} ᴅɪᴅ ɴᴏᴛ ᴇxɪsᴛ, sᴏ ɪᴛ's ɴᴏᴛ ᴅᴇʟᴇᴛᴇᴅ.", + parse_mode=ParseMode.HTML, + ) + + elif msg.reply_to_message: + trigger = msg.reply_to_message.sticker.set_name + if trigger is None: + send_message(update.effective_message, "sᴛɪᴄᴋᴇʀ ɪs ɪɴᴠᴀʟɪᴅ!") + return + if success := sql.rm_from_stickers(chat_id, trigger.lower()): + send_message( + update.effective_message, + f"sᴛɪᴄᴋᴇʀ <code>{trigger}</code> ᴅᴇʟᴇᴛᴇᴅ ғʀᴏᴍ ʙʟᴀᴄᴋʟɪsᴛ ɪɴ <b>{chat_name}</b>!", + parse_mode=ParseMode.HTML, + ) + + else: + send_message( + update.effective_message, + f"{trigger} ɴᴏᴛ ғᴏᴜɴᴅ ᴏɴ ʙʟᴀᴄᴋʟɪsᴛᴇᴅ sᴛɪᴄᴋᴇʀs...!", + ) + + else: + send_message( + update.effective_message, + "ᴛᴇʟʟ ᴍᴇ ᴡʜᴀᴛ sᴛɪᴄᴋᴇʀs ʏᴏᴜ ᴡᴀɴᴛ ᴛᴏ ʀᴇᴍᴏᴠᴇ ғʀᴏᴍ ᴛʜᴇ ʙʟᴀᴄᴋʟɪsᴛ.", + ) + + +@loggable +@user_admin +def blacklist_mode(update: Update, context: CallbackContext): + chat = update.effective_chat # type: Optional[Chat] + user = update.effective_user # type: Optional[User] + msg = update.effective_message # type: Optional[Message] + bot, args = context.bot, context.args + conn = connected(bot, update, chat, user.id, need_admin=True) + if conn: + chat = dispatcher.bot.getChat(conn) + chat_id = conn + chat_name = dispatcher.bot.getChat(conn).title + else: + if update.effective_message.chat.type == "private": + send_message( + update.effective_message, + "ʏᴏᴜ ᴄᴀɴ ᴅᴏ ᴛʜɪs ᴄᴏᴍᴍᴀɴᴅ ɪɴ ɢʀᴏᴜᴘ's, ɴᴏᴛ PM", + ) + return "" + chat = update.effective_chat + chat_id = update.effective_chat.id + chat_name = update.effective_message.chat.title + + if args: + if args[0].lower() in ["off", "nothing", "no"]: + settypeblacklist = "ᴛᴜʀɴ ᴏғғ" + sql.set_blacklist_strength(chat_id, 0, "0") + elif args[0].lower() in ["del", "delete"]: + settypeblacklist = "ʟᴇғᴛ, ᴛʜᴇ ᴍᴇssᴀɢᴇ ᴡɪʟʟ ʙᴇ ᴅᴇʟᴇᴛᴇᴅ" + sql.set_blacklist_strength(chat_id, 1, "0") + elif args[0].lower() == "warn": + settypeblacklist = "ᴡᴀʀɴᴇᴅ" + sql.set_blacklist_strength(chat_id, 2, "0") + elif args[0].lower() == "mute": + settypeblacklist = "ᴍᴜᴛᴇᴅ" + sql.set_blacklist_strength(chat_id, 3, "0") + elif args[0].lower() == "kick": + settypeblacklist = "ᴋɪᴄᴋᴇᴅ" + sql.set_blacklist_strength(chat_id, 4, "0") + elif args[0].lower() == "ban": + settypeblacklist = "ʙᴀɴɴᴇᴅ" + sql.set_blacklist_strength(chat_id, 5, "0") + elif args[0].lower() == "ᴛʙᴀɴ": + if len(args) == 1: + teks = """ɪᴛ ʟᴏᴏᴋs ʟɪᴋᴇ ʏᴏᴜ ᴀʀᴇ ᴛʀʏɪɴɢ ᴛᴏ sᴇᴛ ᴀ ᴛᴇᴍᴘᴏʀᴀʀʏ ᴠᴀʟᴜᴇ ᴛᴏ ʙʟᴀᴄᴋʟɪsᴛ, ʙᴜᴛ ʜᴀs ɴᴏᴛ ᴅᴇᴛᴇʀᴍɪɴᴇᴅ ᴛʜᴇ ᴛɪᴍᴇ; ᴜsᴇ `/blstickermode tban <timevalue>`. + ᴇxᴀᴍᴘʟᴇs ᴏғ ᴛɪᴍᴇ ᴠᴀʟᴜᴇs : 4ᴍ = 4 ᴍɪɴᴜᴛᴇ, 3ʜ = 3 ʜᴏᴜʀs, 6ᴅ = 6 ᴅᴀʏs, 5w = 5 ᴡᴇᴇᴋs.""" + send_message(update.effective_message, teks, parse_mode="markdown") + return + settypeblacklist = f"ᴛᴇᴍᴘᴏʀᴀʀʏ ʙᴀɴɴᴇᴅ ғᴏʀ {args[1]}" + sql.set_blacklist_strength(chat_id, 6, str(args[1])) + elif args[0].lower() == "tmute": + if len(args) == 1: + teks = """ɪᴛ ʟᴏᴏᴋs ʟɪᴋᴇ ʏᴏᴜ ᴀʀᴇ ᴛʀʏɪɴɢ ᴛᴏ sᴇᴛ ᴀ ᴛᴇᴍᴘᴏʀᴀʀʏ ᴠᴀʟᴜᴇ ᴛᴏ ʙʟᴀᴄᴋʟɪsᴛ, ʙᴜᴛ ʜᴀs ɴᴏᴛ ᴅᴇᴛᴇʀᴍɪɴᴇᴅ ᴛʜᴇ ᴛɪᴍᴇ; ᴜsᴇ `/blstickermode tmute <timevalue>`. + ᴇxᴀᴍᴘʟᴇs ᴏғ ᴛɪᴍᴇ ᴠᴀʟᴜᴇs: 4ᴍ = 4 ᴍɪɴᴜᴛᴇ, 3ʜ = 3 ʜᴏᴜʀs, 6ᴅ = 6 days, 5ᴡ = 5 ᴡᴇᴇᴋs.""" + send_message(update.effective_message, teks, parse_mode="markdown") + return + settypeblacklist = f"ᴛᴇᴍᴘᴏʀᴀʀʏ ᴍᴜᴛᴇᴅ ғᴏʀ {args[1]}" + sql.set_blacklist_strength(chat_id, 7, str(args[1])) + else: + send_message( + update.effective_message, + "I ᴏɴʟʏ ᴜɴᴅᴇʀsᴛᴀɴᴅ : off/del/warn/ban/kick/mute/tban/tmute!", + ) + return + if conn: + text = f"ʙʟᴀᴄᴋʟɪsᴛ sᴛɪᴄᴋᴇʀ ᴍᴏᴅᴇ ᴄʜᴀɴɢᴇᴅ, ᴜsᴇʀs ᴡɪʟʟ ʙᴇ `{settypeblacklist}` ᴀᴛ *{chat_name}*!" + + else: + text = ( + f"ʙʟᴀᴄᴋʟɪsᴛ sᴛɪᴄᴋᴇʀ ᴍᴏᴅᴇ ᴄʜᴀɴɢᴇᴅ, ᴜsᴇʀs ᴡɪʟʟ ʙᴇ `{settypeblacklist}`!" + ) + send_message(update.effective_message, text, parse_mode="markdown") + return f"<b>{html.escape(chat.title)}:</b>\n<b>ᴀᴅᴍɪɴ:</b> {mention_html(user.id, html.escape(user.first_name))}\nᴄʜᴀɴɢᴇᴅ sᴛɪᴄᴋᴇʀ ʙʟᴀᴄᴋʟɪsᴛ ᴍᴏᴅᴇ. ᴜsᴇʀs ᴡɪʟʟ ʙᴇ {settypeblacklist}." + + getmode, getvalue = sql.get_blacklist_setting(chat.id) + if getmode == 0: + settypeblacklist = "ɴᴏᴛ ᴀᴄᴛɪᴠᴇ" + elif getmode == 1: + settypeblacklist = "ᴅᴇʟᴇᴛᴇ" + elif getmode == 2: + settypeblacklist = "ᴡᴀʀɴ" + elif getmode == 3: + settypeblacklist = "ᴍᴜᴛᴇ" + elif getmode == 4: + settypeblacklist = "ᴋɪᴄᴋ" + elif getmode == 5: + settypeblacklist = "ʙᴀɴ" + elif getmode == 6: + settypeblacklist = f"ᴛᴇᴍᴘᴏʀᴀʀɪʟʏ ʙᴀɴɴᴇᴅ ғᴏʀ {getvalue}" + elif getmode == 7: + settypeblacklist = f"ᴛᴇᴍᴘᴏʀᴀʀɪʟʏ ᴍᴜᴛᴇᴅ ғᴏʀ {getvalue}" + if conn: + text = f"ʙʟᴀᴄᴋʟɪsᴛ sᴛɪᴄᴋᴇʀ ᴍᴏᴅᴇ ɪs ᴄᴜʀʀᴇɴᴛʟʏ sᴇᴛ ᴛᴏ *{settypeblacklist}* ɪɴ *{chat_name}*." + + else: + text = f"ʙʟᴀᴄᴋʟɪsᴛ sᴛɪᴄᴋᴇʀ ᴍᴏᴅᴇ ɪs ᴄᴜʀʀᴇɴᴛʟʏ sᴇᴛ ᴛᴏ *{settypeblacklist}*." + send_message(update.effective_message, text, parse_mode=ParseMode.MARKDOWN) + return "" + + +@user_not_admin +def del_blackliststicker(update: Update, context: CallbackContext): + bot = context.bot + chat = update.effective_chat # type: Optional[Chat] + message = update.effective_message # type: Optional[Message] + user = update.effective_user + to_match = message.sticker + + if not to_match or not to_match.set_name: + return + + if is_approved(chat.id, user.id): # ignore approved users + return + + getmode, value = sql.get_blacklist_setting(chat.id) + + chat_filters = sql.get_chat_stickers(chat.id) + for trigger in chat_filters: + if to_match.set_name.lower() == trigger.lower(): + try: + if getmode == 0: + return + if getmode == 1: + message.delete() + elif getmode == 2: + message.delete() + warn( + update.effective_user, + chat, + f"ᴜsɪɴɢ sᴛɪᴄᴋᴇʀ '{trigger}' ᴡʜɪᴄʜ ɪɴ ʙʟᴀᴄᴋʟɪsᴛ sᴛɪᴄᴋᴇʀs", + message, + update.effective_user, + ) + + return + elif getmode == 3: + message.delete() + bot.restrict_chat_member( + chat.id, + update.effective_user.id, + permissions=ChatPermissions(can_send_messages=False), + ) + bot.sendMessage( + chat.id, + f"{mention_markdown(user.id, user.first_name)} ᴍᴜᴛᴇᴅ ʙᴇᴄᴀᴜsᴇ ᴜsɪɴɢ '{trigger}' ᴡʜɪᴄʜ ɪɴ ʙʟᴀᴄᴋʟɪsᴛ sᴛɪᴄᴋᴇʀs", + parse_mode="markdown", + ) + + return + elif getmode == 4: + message.delete() + if res := chat.unban_member(update.effective_user.id): + bot.sendMessage( + chat.id, + f"{mention_markdown(user.id, user.first_name)} ᴋɪᴄᴋᴇᴅ ʙᴇᴄᴀᴜsᴇ ᴜsɪɴɢ '{trigger}' ᴡʜɪᴄʜ ɪɴ ʙʟᴀᴄᴋʟɪsᴛ sᴛɪᴄᴋᴇʀs", + parse_mode="markdown", + ) + + return + elif getmode == 5: + message.delete() + chat.ban_member(user.id) + bot.sendMessage( + chat.id, + f"{mention_markdown(user.id, user.first_name)} ʙᴀɴɴᴇᴅ ʙᴇᴄᴀᴜsᴇ ᴜsɪɴɢ '{trigger}' ᴡʜɪᴄʜ ɪɴ ʙʟᴀᴄᴋʟɪsᴛ sᴛɪᴄᴋᴇʀs", + parse_mode="markdown", + ) + + return + elif getmode == 6: + message.delete() + bantime = extract_time(message, value) + chat.ban_member(user.id, until_date=bantime) + bot.sendMessage( + chat.id, + f"{mention_markdown(user.id, user.first_name)} ʙᴀɴɴᴇᴅ ғᴏʀ {value} ʙᴇᴄᴀᴜsᴇ ᴜsɪɴɢ '{trigger}' ᴡʜɪᴄʜ ɪɴ ʙʟᴀᴄᴋʟɪsᴛ sᴛɪᴄᴋᴇʀs", + parse_mode="markdown", + ) + + return + elif getmode == 7: + message.delete() + mutetime = extract_time(message, value) + bot.restrict_chat_member( + chat.id, + user.id, + permissions=ChatPermissions(can_send_messages=False), + until_date=mutetime, + ) + bot.sendMessage( + chat.id, + f"{mention_markdown(user.id, user.first_name)} ᴍᴜᴛᴇᴅ ғᴏʀ {value} ʙᴇᴄᴀᴜsᴇ ᴜsɪɴɢ '{trigger}' ᴡʜɪᴄʜ ɪɴ ʙʟᴀᴄᴋʟɪsᴛ sᴛɪᴄᴋᴇʀs", + parse_mode="markdown", + ) + + return + except BadRequest as excp: + if excp.message != "ᴍᴇssᴀɢᴇ ᴛᴏ ᴅᴇʟᴇᴛᴇ ɴᴏᴛ ғᴏᴜɴᴅ": + LOGGER.exception("ᴇʀʀᴏʀ ᴡʜɪʟᴇ ᴅᴇʟᴇᴛɪɴɢ ʙʟᴀᴄᴋʟɪsᴛ ᴍᴇssᴀɢᴇ.") + break + + +def __import_data__(chat_id, data): + # set chat blacklist + blacklist = data.get("sticker_blacklist", {}) + for trigger in blacklist: + sql.add_to_stickers(chat_id, trigger) + + +def __migrate__(old_chat_id, new_chat_id): + sql.migrate_chat(old_chat_id, new_chat_id) + + +def __chat_settings__(chat_id, user_id): + blacklisted = sql.num_stickers_chat_filters(chat_id) + return f"ᴛʜᴇʀᴇ ᴀʀᴇ `{blacklisted} `ʙʟᴀᴄᴋʟɪsᴛᴇᴅ sᴛɪᴄᴋᴇʀs." + + +def __stats__(): + return f"•➥ {sql.num_stickers_filters()} blacklist stickers, across {sql.num_stickers_filter_chats()} ᴄʜᴀᴛs." + + +__mod_name__ = "𝚂ᴛɪᴄᴋᴇʀs ʙʟᴀᴄᴋʟɪsᴛ" + +BLACKLIST_STICKER_HANDLER = DisableAbleCommandHandler( + "blsticker", + blackliststicker, + admin_ok=True, + run_async=True, +) +ADDBLACKLIST_STICKER_HANDLER = DisableAbleCommandHandler( + "addblsticker", + add_blackliststicker, + run_async=True, +) +UNBLACKLIST_STICKER_HANDLER = CommandHandler( + ["unblsticker", "rmblsticker"], + unblackliststicker, + run_async=True, +) +BLACKLISTMODE_HANDLER = CommandHandler("blstickermode", blacklist_mode, run_async=True) +BLACKLIST_STICKER_DEL_HANDLER = MessageHandler( + Filters.sticker & Filters.chat_type.groups, + del_blackliststicker, + run_async=True, +) + +dispatcher.add_handler(BLACKLIST_STICKER_HANDLER) +dispatcher.add_handler(ADDBLACKLIST_STICKER_HANDLER) +dispatcher.add_handler(UNBLACKLIST_STICKER_HANDLER) +dispatcher.add_handler(BLACKLISTMODE_HANDLER) +dispatcher.add_handler(BLACKLIST_STICKER_DEL_HANDLER) diff --git a/Exon/modules/blacklistusers.py b/Exon/modules/blacklistusers.py new file mode 100644 index 00000000..80f9945a --- /dev/null +++ b/Exon/modules/blacklistusers.py @@ -0,0 +1,171 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import html + +from telegram import ParseMode, Update +from telegram.error import BadRequest +from telegram.ext import CallbackContext, CommandHandler +from telegram.utils.helpers import mention_html + +import Exon.modules.sql.blacklistusers_sql as sql +from Exon import DEMONS, DEV_USERS, DRAGONS, OWNER_ID, TIGERS, WOLVES, dispatcher +from Exon.modules.helper_funcs.chat_status import dev_plus +from Exon.modules.helper_funcs.extraction import extract_user, extract_user_and_text +from Exon.modules.log_channel import gloggable + +BLACKLISTWHITELIST = [OWNER_ID] + DEV_USERS + DRAGONS + WOLVES + DEMONS +BLABLEUSERS = [OWNER_ID] + DEV_USERS + + +@dev_plus +@gloggable +def bl_user(update: Update, context: CallbackContext) -> str: + message = update.effective_message + user = update.effective_user + bot, args = context.bot, context.args + user_id, reason = extract_user_and_text(message, args) + + if not user_id: + message.reply_text("I ᴅᴏᴜʙᴛ ᴛʜᴀᴛ's ᴀ ᴜsᴇʀ.") + return "" + + if user_id == bot.id: + message.reply_text("ʜᴏᴡ ᴀᴍ ɪ sᴜᴘᴘᴏsᴇᴅ ᴛᴏ ᴅᴏ ᴍʏ ᴡᴏʀᴋ ɪғ ɪ ᴀᴍ ɪɢɴᴏʀɪɴɢ ᴍʏsᴇʟғ?") + return "" + + if user_id in BLACKLISTWHITELIST: + message.reply_text("ɴᴏ!\nɴᴏᴛɪᴄɪɴɢ ᴅɪsᴀsᴛᴇʀs ɪs ᴍʏ ᴊᴏʙ.") + return "" + + try: + target_user = bot.get_chat(user_id) + except BadRequest as excp: + if excp.message == "ᴜsᴇʀ ɴᴏᴛ ғᴏᴜɴᴅ": + message.reply_text("I ᴄᴀɴ'ᴛ sᴇᴇᴍ ᴛᴏ ғɪɴᴅ ᴛʜɪs ᴜsᴇʀ.") + return "" + raise + + sql.blacklist_user(user_id, reason) + message.reply_text("I sʜᴀʟʟ ɪɢɴᴏʀᴇ ᴛʜᴇ ᴇxɪsᴛᴇɴᴄᴇ ᴏғ ᴛʜɪs ᴜsᴇʀ!") + log_message = ( + f"#ʙʟᴀᴄᴋʟɪsᴛ\n" + f"<b>ᴀᴅᴍɪɴ:</b> {mention_html(user.id, html.escape(user.first_name))}\n" + f"<b>ᴜsᴇʀ:</b> {mention_html(target_user.id, html.escape(target_user.first_name))}" + ) + if reason: + log_message += f"\n<b>ʀᴇᴀsᴏɴ:</b> {reason}" + + return log_message + + +@dev_plus +@gloggable +def unbl_user(update: Update, context: CallbackContext) -> str: + message = update.effective_message + user = update.effective_user + bot, args = context.bot, context.args + user_id = extract_user(message, args) + + if not user_id: + message.reply_text("I ᴅᴏᴜʙᴛ ᴛʜᴀᴛ's ᴀ ᴜsᴇʀ.") + return "" + + if user_id == bot.id: + message.reply_text("I ᴀʟᴡᴀʏs ɴᴏᴛɪᴄᴇ ᴍʏsᴇʟғ.") + return "" + + try: + target_user = bot.get_chat(user_id) + except BadRequest as excp: + if excp.message == "ᴜsᴇʀ ɴᴏᴛ ғᴏᴜɴᴅ": + message.reply_text("I ᴄᴀɴ'ᴛ sᴇᴇᴍ ᴛᴏ ғɪɴᴅ ᴛʜɪs ᴜsᴇʀ.") + return "" + raise + + if sql.is_user_blacklisted(user_id): + + sql.unblacklist_user(user_id) + message.reply_text("*notices user*") + log_message = ( + f"#ᴜɴʙʟᴀᴄᴋʟɪsᴛ\n" + f"<b>ᴀᴅᴍɪɴ:</b> {mention_html(user.id, html.escape(user.first_name))}\n" + f"<b>ᴜsᴇʀ:</b> {mention_html(target_user.id, html.escape(target_user.first_name))}" + ) + + return log_message + message.reply_text("I ᴀᴍ ɴᴏᴛ ɪɢɴᴏʀɪɴɢ ᴛʜᴇᴍ ᴀᴛ ᴀʟʟ ᴛʜᴏᴜɢʜ!") + return "" + + +@dev_plus +def bl_users(update: Update, context: CallbackContext): + users = [] + bot = context.bot + for each_user in sql.BLACKLIST_USERS: + user = bot.get_chat(each_user) + if reason := sql.get_reason(each_user): + users.append( + f"• {mention_html(user.id, html.escape(user.first_name))} :- {reason}", + ) + else: + users.append(f"• {mention_html(user.id, html.escape(user.first_name))}") + + message = "<b>ʙʟᴀᴄᴋʟɪsᴛᴇᴅ ᴜsᴇʀs</b>\n" + ( + "\n".join(users) if users else "ɴᴏᴏɴᴇ ɪs ʙᴇɪɴɢ ɪɢɴᴏʀᴇᴅ ᴀs ᴏғ ʏᴇᴛ." + ) + + update.effective_message.reply_text(message, parse_mode=ParseMode.HTML) + + +def __user_info__(user_id): + is_blacklisted = sql.is_user_blacklisted(user_id) + + text = "ʙʟᴀᴄᴋʟɪsᴛᴇᴅ: <b>{}</b>" + if user_id in [777000, 1087968824]: + return "" + if user_id == dispatcher.bot.id: + return "" + if int(user_id) in DRAGONS + TIGERS + WOLVES: + return "" + if is_blacklisted: + text = text.format("Yes") + if reason := sql.get_reason(user_id): + text += f"\nʀᴇᴀsᴏɴ: <code>{reason}</code>" + else: + text = text.format("No") + + return text + + +BL_HANDLER = CommandHandler("ignore", bl_user, run_async=True) +UNBL_HANDLER = CommandHandler("notice", unbl_user, run_async=True) +BLUSERS_HANDLER = CommandHandler("ignoredlist", bl_users, run_async=True) + +dispatcher.add_handler(BL_HANDLER) +dispatcher.add_handler(UNBL_HANDLER) +dispatcher.add_handler(BLUSERS_HANDLER) + +__mod_name__ = "Blacklisting Users" +__handlers__ = [BL_HANDLER, UNBL_HANDLER, BLUSERS_HANDLER] diff --git a/Exon/modules/books.py b/Exon/modules/books.py new file mode 100644 index 00000000..a5ee278a --- /dev/null +++ b/Exon/modules/books.py @@ -0,0 +1,82 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import os +import re + +import requests +from bs4 import BeautifulSoup +from telethon import events + +from Exon import BOT_USERNAME, SUPPORT_CHAT, telethn + + +@telethn.on(events.NewMessage(pattern="^/book (.*)")) +async def _(event): + if event.fwd_from: + return + input_str = event.pattern_match.group(1) + KkK = await event.reply("sᴇᴀʀᴄʜɪɴɢ ғᴏʀ ᴛʜᴇ ʙᴏᴏᴋ...") + lin = "https://b-ok.cc/s/" + text = input_str + link = lin + text + + headers = [ + "User-Agent", + "Arsh 5.0 (Macintosh; Intel Mac OS X 10.15; rv:74.0) Gecko/20100101 Firefox/74.0", + ] + page = requests.get(link) + soup = BeautifulSoup(page.content, "html.parser") + f = open("book.txt", "w") + total = soup.find(class_="totalCounter") + for nb in total.descendants: + nbx = nb.replace("(", "").replace(")", "") + if nbx == "0": + await event.reply("ɴᴏ ʙᴏᴏᴋs ғᴏᴜɴᴅ ᴡɪᴛʜ ᴛʜᴀᴛ ɴᴀᴍᴇ.") + else: + + lool = 0 + for tr in soup.find_all("td"): + for td in tr.find_all("h3"): + for ts in td.find_all("a"): + title = ts.get_text() + lool += 1 + for ts in td.find_all("a", attrs={"href": re.compile("^/book/")}): + ref = ts.get("href") + link = f"https://b-ok.cc{ref}" + + f.write("\n" + title) + f.write("\nʙᴏᴏᴋ ʟɪɴᴋ:- " + link + "\n\n") + + f.write(f"ʙʏ @{BOT_USERNAME}.") + f.close() + caption = f"ᴇxᴏɴ \nᴊᴏɪɴ sᴜᴘᴘᴏʀᴛ @{SUPPORT_CHAT} " + + await telethn.send_file( + event.chat_id, + "book.txt", + caption=f"**BOOKS GATHERED SUCCESSFULLY!\n\nBY @ABISHNOIMF **", + ) + os.remove("book.txt") + await KkK.delete() diff --git a/Exon/modules/callback.py b/Exon/modules/callback.py new file mode 100644 index 00000000..679097a7 --- /dev/null +++ b/Exon/modules/callback.py @@ -0,0 +1,202 @@ +""" +MIT License + +Copyright (c) 2022 ABISHNOI + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +# ""DEAR PRO PEOPLE, DON'T REMOVE & CHANGE THIS LINE +# TG :- @Abishnoi1M +# MY ALL BOTS :- Abishnoi_bots +# GITHUB :- KingAbishnoi "" + + +from pyrogram.types import CallbackQuery + +from Exon import pgram as bot + + +@bot.on_callback_query() +async def close(Client, cb: CallbackQuery): + if cb.data == "close_h": + await cb.answer() + await cb.message.delete() + + +import time + +from telegram import InlineKeyboardButton, InlineKeyboardMarkup, ParseMode, Update +from telegram.ext import CallbackContext, CallbackQueryHandler + +from Exon import ( + BOT_NAME, + OWNER_ID, + SUPPORT_CHAT, + UPDATES_CHANNEL, + StartTime, + dispatcher, +) + + +def get_readable_time(seconds: int) -> str: + count = 0 + ping_time = "" + time_list = [] + time_suffix_list = ["s", "ᴍ", "ʜ", "ᴅᴀʏs"] + + while count < 4: + count += 1 + remainder, result = divmod(seconds, 60) if count < 3 else divmod(seconds, 24) + if seconds == 0 and remainder == 0: + break + time_list.append(int(result)) + seconds = int(remainder) + + for x in range(len(time_list)): + time_list[x] = str(time_list[x]) + time_suffix_list[x] + if len(time_list) == 4: + ping_time += f"{time_list.pop()}, " + + time_list.reverse() + ping_time += ":".join(time_list) + + return ping_time + + +def Exon_about_callback(update: Update, context: CallbackContext): + query = update.callback_query + if query.data == "about_": + get_readable_time((time.time() - StartTime)) + query.message.edit_text( + text=f"*ʜᴇʏ,*🥀\n *ᴛʜɪs ɪs {BOT_NAME}*" + "\n*ᴀ ᴘᴏᴡᴇʀꜰᴜʟ ɢʀᴏᴜᴘ ᴍᴀɴᴀɢᴇᴍᴇɴᴛ ʙᴏᴛ ʙᴜɪʟᴛ ᴛᴏ ʜᴇʟᴘ ʏᴏᴜ ᴍᴀɴᴀɢᴇ ʏᴏᴜʀ ɢʀᴏᴜᴘ ᴇᴀꜱɪʟʏ ᴀɴᴅ ᴛᴏ ᴘʀᴏᴛᴇᴄᴛ ʏᴏᴜʀ ɢʀᴏᴜᴘ ꜰʀᴏᴍ ꜱᴄᴀᴍᴍᴇʀꜱ ᴀɴᴅ ꜱᴘᴀᴍᴍᴇʀꜱ.*" + "\n*ᴡʀɪᴛᴛᴇɴ ɪɴ ᴩʏᴛʜᴏɴ ᴡɪᴛʜ sǫʟᴀʟᴄʜᴇᴍʏ ᴀɴᴅ ᴍᴏɴɢᴏᴅʙ ᴀs ᴅᴀᴛᴀʙᴀsᴇ.*" + "\n\n➲ ɪ ᴄᴀɴ ʀᴇꜱᴛʀɪᴄᴛ ᴜꜱᴇʀꜱ." + "\n➲ ɪ ʜᴀᴠᴇ ᴀɴ ᴀᴅᴠᴀɴᴄᴇᴅ ᴀɴᴛɪ-ꜰʟᴏᴏᴅ ꜱʏꜱᴛᴇᴍ." + "\n➲ ɪ ᴄᴀɴ ɢʀᴇᴇᴛ ᴜꜱᴇʀꜱ ᴡɪᴛʜ ᴄᴜꜱᴛᴏᴍɪᴢᴀʙʟᴇ ᴡᴇʟᴄᴏᴍᴇ ᴍᴇꜱꜱᴀɢᴇꜱ ᴀɴᴅ ᴇᴠᴇɴ ꜱᴇᴛ ᴀ ɢʀᴏᴜᴘ'ꜱ ʀᴜʟᴇꜱ." + "\n➲ ɪ ᴄᴀɴ ᴡᴀʀɴ ᴜꜱᴇʀꜱ ᴜɴᴛɪʟ ᴛʜᴇʏ ʀᴇᴀᴄʜ ᴍᴀx ᴡᴀʀɴꜱ, ᴡɪᴛʜ ᴇᴀᴄʜ ᴘʀᴇᴅᴇꜰɪɴᴇᴅ ᴀᴄᴛɪᴏɴꜱ ꜱᴜᴄʜ ᴀꜱ ʙᴀɴ, ᴍᴜᴛᴇ, ᴋɪᴄᴋ, ᴇᴛᴄ." + "\n➲ ɪ ʜᴀᴠᴇ ᴀ ɴᴏᴛᴇ ᴋᴇᴇᴘɪɴɢ ꜱʏꜱᴛᴇᴍ, ʙʟᴀᴄᴋʟɪꜱᴛꜱ, ᴀɴᴅ ᴇᴠᴇɴ ᴘʀᴇᴅᴇᴛᴇʀᴍɪɴᴇᴅ ʀᴇᴘʟɪᴇꜱ ᴏɴ ᴄᴇʀᴛᴀɪɴ ᴋᴇʏᴡᴏʀᴅꜱ." + f"\n\n➻ ᴄʟɪᴄᴋ ᴏɴ ᴛʜᴇ ʙᴜᴛᴛᴏɴs ɢɪᴠᴇɴ ʙᴇʟᴏᴡ ғᴏʀ ɢᴇᴛᴛɪɴɢ ʙᴀsɪᴄ ʜᴇʟᴩ ᴀɴᴅ ɪɴғᴏ ᴀʙᴏᴜᴛ {BOT_NAME}.", + parse_mode=ParseMode.MARKDOWN, + disable_web_page_preview=True, + reply_markup=InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + text="sᴜᴩᴩᴏʀᴛ", callback_data="Exon_support" + ), + InlineKeyboardButton( + text="ᴄᴏᴍᴍᴀɴᴅs", callback_data="help_back" + ), + ], + [ + InlineKeyboardButton( + text="ᴅᴇᴠᴇʟᴏᴩᴇʀ", url=f"tg://user?id={OWNER_ID}" + ), + InlineKeyboardButton( + text="sᴏᴜʀᴄᴇ", + callback_data="source_", + ), + ], + [ + InlineKeyboardButton(text="◁", callback_data="Exon_back"), + ], + ] + ), + ) + + +def Exon_support_callback(update: Update, context: CallbackContext): + query = update.callback_query + if query.data == "Exon_support": + query.message.edit_text( + text="*๏ ᴄʟɪᴄᴋ ᴏɴ ᴛʜᴇ ʙᴜᴛᴛᴏɴs ɢɪᴠᴇɴ ʙᴇʟᴏᴡ ᴛᴏ ɢᴇᴛ ʜᴇʟᴩ ᴀɴᴅ ᴍᴏʀᴇ ɪɴғᴏʀᴍᴀᴛɪᴏɴ ᴀʙᴏᴜᴛ ᴍᴇ.*" + f"\n\nɪғ ʏᴏᴜ ғᴏᴜɴᴅ ᴀɴʏ ʙᴜɢ ɪɴ {BOT_NAME} ᴏʀ ɪғ ʏᴏᴜ ᴡᴀɴɴᴀ ɢɪᴠᴇ ғᴇᴇᴅʙᴀᴄᴋ ᴀʙᴏᴜᴛ ᴛʜᴇ {BOT_NAME}, ᴩʟᴇᴀsᴇ ʀᴇᴩᴏʀᴛ ɪᴛ ᴀᴛ sᴜᴩᴩᴏʀᴛ ᴄʜᴀᴛ.", + parse_mode=ParseMode.MARKDOWN, + reply_markup=InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + text="sᴜᴩᴩᴏʀᴛ", url=f"https://t.me/{SUPPORT_CHAT}" + ), + InlineKeyboardButton( + text="ᴜᴩᴅᴀᴛᴇs", url=f"https://t.me/{UPDATES_CHANNEL}" + ), + ], + [ + InlineKeyboardButton( + text="ᴅᴇᴠᴇʟᴏᴩᴇʀ", url=f"tg://user?id={OWNER_ID}" + ), + InlineKeyboardButton( + text="ɢɪᴛʜᴜʙ", + callback_data="source_", + ), + ], + [ + InlineKeyboardButton(text="◁", callback_data="Exon_back"), + ], + ] + ), + ) + + +def Source_about_callback(update: Update, context: CallbackContext): + query = update.callback_query + if query.data == "source_": + query.message.edit_text( + text=f""" +*ʜᴇʏ, + ᴛʜɪs ɪs {BOT_NAME}, +ᴀɴ ᴏᴩᴇɴ sᴏᴜʀᴄᴇ ᴛᴇʟᴇɢʀᴀᴍ ɢʀᴏᴜᴩ ᴍᴀɴᴀɢᴇᴍᴇɴᴛ ʙᴏᴛ.* + +ᴡʀɪᴛᴛᴇɴ ɪɴ ᴩʏᴛʜᴏɴ ᴡɪᴛʜ ᴛʜᴇ ʜᴇʟᴩ ᴏғ : [ᴛᴇʟᴇᴛʜᴏɴ](https://github.com/LonamiWebs/Telethon) +[ᴩʏʀᴏɢʀᴀᴍ](https://github.com/pyrogram/pyrogram) +[ᴩʏᴛʜᴏɴ-ᴛᴇʟᴇɢʀᴀᴍ-ʙᴏᴛ](https://github.com/python-telegram-bot/python-telegram-bot) +ᴀɴᴅ ᴜsɪɴɢ [sǫʟᴀʟᴄʜᴇᴍʏ](https://www.sqlalchemy.org) ᴀɴᴅ [ᴍᴏɴɢᴏ](https://cloud.mongodb.com) ᴀs ᴅᴀᴛᴀʙᴀsᴇ. + + +*ʜᴇʀᴇ ɪs ᴍʏ sᴏᴜʀᴄᴇ ᴄᴏᴅᴇ :* [ɢɪᴛʜᴜʙ](https://github.com/TEAM-ABG/ExonRobot) + + +{BOT_NAME} ɪs ʟɪᴄᴇɴsᴇᴅ ᴜɴᴅᴇʀ ᴛʜᴇ [ᴍɪᴛ ʟɪᴄᴇɴsᴇ](https://github.com/TEAM-ABG/ExonRobot/blob/master/LICENSE). +© 2022 - 2023 [@ᴀʙɪsʜɴᴏɪᴍғ](https://t.me/{SUPPORT_CHAT}), ᴀʟʟ ʀɪɢʜᴛs ʀᴇsᴇʀᴠᴇᴅ. +""", + parse_mode=ParseMode.MARKDOWN, + disable_web_page_preview=True, + reply_markup=InlineKeyboardMarkup( + [[InlineKeyboardButton(text="◁", callback_data="help_back")]] + ), + ) + + +Exon_about_callback = CallbackQueryHandler( + Exon_about_callback, pattern=r"about_", run_async=True +) +Source_about_callback = CallbackQueryHandler( + Source_about_callback, pattern=r"source_", run_async=True +) +Exon_support_callback = CallbackQueryHandler( + Exon_support_callback, pattern=r"Exon_support", run_async=True +) + + +dispatcher.add_handler(Exon_about_callback) +dispatcher.add_handler(Source_about_callback) +dispatcher.add_handler(Exon_support_callback) diff --git a/Exon/modules/carbon.py b/Exon/modules/carbon.py new file mode 100644 index 00000000..ca7d521a --- /dev/null +++ b/Exon/modules/carbon.py @@ -0,0 +1,50 @@ +import asyncio +from io import BytesIO + +from pyrogram import filters + +from Exon import aiohttpsession as aiosession +from Exon import pgram +from Exon.events import register +from Exon.utils.errors import capture_err + + +async def make_carbon(code): + url = "https://carbonara.vercel.app/api/cook" + async with aiosession.post(url, json={"code": code}) as resp: + image = BytesIO(await resp.read()) + image.name = "carbon.png" + return image + + +@pgram.on_message(filters.command("carbon")) +@capture_err +async def carbon_func(_, message): + if not message.reply_to_message: + return await message.reply_text("`ʀᴇᴘʟʏ ᴛᴏ ᴀ ᴛᴇxᴛ ᴛᴏ ɢᴇɴᴇʀᴀᴛᴇ ᴄᴀʀʙᴏɴ`") + if not message.reply_to_message.text: + return await message.reply_text("`ʀᴇᴘʟʏ ᴛᴏ ᴀ ᴛᴇxᴛ ᴛᴏ ɢᴇɴᴇʀᴀᴛᴇ ᴄᴀʀʙᴏɴ`") + m = await message.reply_text("`ɢᴇɴᴇʀᴀᴛɪɴɢ ᴄᴀʀʙᴏɴ...`") + carbon = await make_carbon(message.reply_to_message.text) + await m.edit("`waitoo...`") + await pgram.send_photo(message.chat.id, carbon) + await m.delete() + carbon.close() + + +@register(pattern="^/repo$") +async def _(event): + loda = "➥ [EXON](https://github.com/TEAM-ABG/ExonRobot)" + lund = await event.reply(loda) + await asyncio.sleep(10) + await event.delete() + await lund.delete() + + +__mod_name__ = "𝙲ᴀʀʙᴏɴ" + +__help__ = """ + +/carbon *:* ᴍᴀᴋᴇs ᴄᴀʀʙᴏɴ ғᴏʀ ʀᴇᴘʟɪᴇᴅ ᴛᴇxᴛ +/repo *:*🌟 + """ diff --git a/Exon/modules/chatbot.py b/Exon/modules/chatbot.py new file mode 100644 index 00000000..d408d4e5 --- /dev/null +++ b/Exon/modules/chatbot.py @@ -0,0 +1,205 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import html +import json +import re +from time import sleep + +import requests +from telegram import ( + CallbackQuery, + Chat, + InlineKeyboardButton, + InlineKeyboardMarkup, + ParseMode, + Update, + User, +) +from telegram.error import BadRequest, RetryAfter, Unauthorized +from telegram.ext import ( + CallbackContext, + CallbackQueryHandler, + CommandHandler, + Filters, + MessageHandler, +) +from telegram.utils.helpers import mention_html + +import Exon.modules.sql.kuki_sql as sql +from Exon import dispatcher +from Exon.modules.helper_funcs.chat_status import user_admin, user_admin_no_reply +from Exon.modules.helper_funcs.filters import CustomFilters +from Exon.modules.log_channel import gloggable + + +@user_admin_no_reply +@gloggable +def kukirm(update: Update, context: CallbackContext) -> str: + query: Optional[CallbackQuery] = update.callback_query + user: Optional[User] = update.effective_user + if match := re.match(r"rm_chat\((.+?)\)", query.data): + user_id = match[1] + chat: Optional[Chat] = update.effective_chat + if is_kuki := sql.rem_kuki(chat.id): + sql.rem_kuki(user_id) + return ( + f"<b>{html.escape(chat.title)}:</b>\n" + f"AI_ᴅɪsᴀʙʟᴇᴅ\n" + f"<b>ᴀᴅᴍɪɴ:</b> {mention_html(user.id, html.escape(user.first_name))}\n" + ) + else: + query.answer("ᴄʜᴀᴛʙᴏᴛ ᴜɴᴀᴄᴛɪᴠᴇ") + query.message.delete() + + return "" + + +@user_admin_no_reply +@gloggable +def kukiadd(update: Update, context: CallbackContext) -> str: + query: Optional[CallbackQuery] = update.callback_query + user: Optional[User] = update.effective_user + if match := re.match(r"add_chat\((.+?)\)", query.data): + user_id = match[1] + chat: Optional[Chat] = update.effective_chat + if is_kuki := sql.set_kuki(chat.id): + sql.set_kuki(user_id) + return ( + f"<b>{html.escape(chat.title)}:</b>\n" + f"ᴀɪ_ᴇɴᴀʙʟᴇ\n" + f"<b>ᴀᴅᴍɪɴ:</b> {mention_html(user.id, html.escape(user.first_name))}\n" + ) + else: + query.answer("ᴄʜᴀᴛʙᴏᴛ ᴀᴄᴛɪᴠᴇ") + query.message.delete() + + return "" + + +@user_admin +@gloggable +def kuki(update: Update, context: CallbackContext): + update.effective_user + message = update.effective_message + msg = "ᴄʜᴏᴏsᴇ ᴀɴ ᴏᴘᴛɪᴏɴ ʙᴀʙʏ " + keyboard = InlineKeyboardMarkup( + [ + [InlineKeyboardButton(text="ᴇɴᴀʙʟᴇ", callback_data="add_chat({})")], + [InlineKeyboardButton(text="ᴅɪsᴀʙʟᴇ", callback_data="rm_chat({})")], + ] + ) + message.reply_text( + msg, + reply_markup=keyboard, + parse_mode=ParseMode.HTML, + ) + + +def kuki_message(context: CallbackContext, message): + reply_message = message.reply_to_message + if message.text.lower() == "Exon": + return True + if reply_message: + if reply_message.from_user.id == context.bot.get_me().id: + return True + else: + return False + + +def chatbot(update: Update, context: CallbackContext): + message = update.effective_message + chat_id = update.effective_chat.id + bot = context.bot + is_kuki = sql.is_kuki(chat_id) + if not is_kuki: + return + + if message.text and not message.document: + if not kuki_message(context, message): + return + Message = message.text + bot.send_chat_action(chat_id, action="typing") + kukiurl = requests.get( + f"https://kukiapi.xyz/api/apikey=5281955434-KUKIyq4NCB2Ca8/Himawari/@nekoarsh/message={Message}" + ) + + Kuki = json.loads(kukiurl.text) + kuki = Kuki["reply"] + sleep(0.3) + message.reply_text(kuki, timeout=60) + + +def list_all_chats(update: Update, context: CallbackContext): + chats = sql.get_all_kuki_chats() + text = "<b>ᴋᴜᴋɪ-ᴇɴᴀʙʟᴇᴅ ᴄʜᴀᴛs</b>\n" + for chat in chats: + try: + x = context.bot.get_chat(int(*chat)) + name = x.title or x.first_name + text += f"• <code>{name}</code>\n" + except (BadRequest, Unauthorized): + sql.rem_kuki(*chat) + except RetryAfter as e: + sleep(e.retry_after) + update.effective_message.reply_text(text, parse_mode="HTML") + + +__help__ = """ +ᴄʜᴀᴛʙᴏᴛ ᴜᴛɪʟɪᴢᴇs ᴛʜᴇ ᴋᴜᴋɪ's API ᴡʜɪᴄʜ ᴀʟʟᴏᴡs ᴇxᴏɴ ᴛᴏ ᴛᴀʟᴋ ᴀɴᴅ ᴘʀᴏᴠɪᴅᴇ ᴀ ᴍᴏʀᴇ ɪɴᴛᴇʀᴀᴄᴛɪᴠᴇ ɢʀᴏᴜᴘ ᴄʜᴀᴛ ᴇxᴘᴇʀɪᴇɴᴄᴇ. +*ᴀᴅᴍɪɴs ᴏɴʟʏ ᴄᴏᴍᴍᴀɴᴅs*: + +✪ /Chatbot*:* sʜᴏᴡs ᴄʜᴀᴛʙᴏᴛ ᴄᴏɴᴛʀᴏʟ ᴘᴀɴᴇʟ + +""" + +__mod_name__ = "𝙲ʜᴀᴛʙᴏᴛ" + + +CHATBOTK_HANDLER = CommandHandler("chatbot", kuki, run_async=True) +ADD_CHAT_HANDLER = CallbackQueryHandler(kukiadd, pattern=r"add_chat", run_async=True) +RM_CHAT_HANDLER = CallbackQueryHandler(kukirm, pattern=r"rm_chat", run_async=True) +CHATBOT_HANDLER = MessageHandler( + Filters.text + & (~Filters.regex(r"^#[^\s]+") & ~Filters.regex(r"^!") & ~Filters.regex(r"^\/")), + chatbot, + run_async=True, +) +LIST_ALL_CHATS_HANDLER = CommandHandler( + "allchats", list_all_chats, filters=CustomFilters.dev_filter, run_async=True +) + +dispatcher.add_handler(ADD_CHAT_HANDLER) +dispatcher.add_handler(CHATBOTK_HANDLER) +dispatcher.add_handler(RM_CHAT_HANDLER) +dispatcher.add_handler(LIST_ALL_CHATS_HANDLER) +dispatcher.add_handler(CHATBOT_HANDLER) + +__handlers__ = [ + ADD_CHAT_HANDLER, + CHATBOTK_HANDLER, + RM_CHAT_HANDLER, + LIST_ALL_CHATS_HANDLER, + CHATBOT_HANDLER, +] diff --git a/Exon/modules/cleaner.py b/Exon/modules/cleaner.py new file mode 100644 index 00000000..2c0c0611 --- /dev/null +++ b/Exon/modules/cleaner.py @@ -0,0 +1,285 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import html + +from telegram import ParseMode, Update +from telegram.ext import CallbackContext, CommandHandler, Filters, MessageHandler + +from Exon import ALLOW_EXCL, BOT_NAME, CustomCommandHandler, dispatcher +from Exon.modules.disable import DisableAbleCommandHandler +from Exon.modules.helper_funcs.chat_status import ( + bot_can_delete, + connection_status, + dev_plus, + user_admin, +) +from Exon.modules.sql import cleaner_sql as sql + +CMD_STARTERS = ("/", "!") if ALLOW_EXCL else "/" +BLUE_TEXT_CLEAN_GROUP = 13 +CommandHandlerList = (CommandHandler, CustomCommandHandler, DisableAbleCommandHandler) +command_list = [ + "cleanblue", + "ignoreblue", + "unignoreblue", + "listblue", + "ungignoreblue", + "gignoreblue", + "start", + "help", + "settings", + "donate", + "stalk", + "aka", + "leaderboard", +] + +for handler_list in dispatcher.handlers: + for handler in dispatcher.handlers[handler_list]: + if any(isinstance(handler, cmd_handler) for cmd_handler in CommandHandlerList): + command_list += handler.command + + +def clean_blue_text_must_click(update: Update, context: CallbackContext): + bot = context.bot + chat = update.effective_chat + message = update.effective_message + if chat.get_member(bot.id).can_delete_messages and sql.is_enabled(chat.id): + fst_word = message.text.strip().split(None, 1)[0] + + if len(fst_word) > 1 and any( + fst_word.startswith(start) for start in CMD_STARTERS + ): + + command = fst_word[1:].split("@") + chat = update.effective_chat + + if ignored := sql.is_command_ignored(chat.id, command[0]): + return + + if command[0] not in command_list: + message.delete() + + +@connection_status +@bot_can_delete +@user_admin +def set_blue_text_must_click(update: Update, context: CallbackContext): + chat = update.effective_chat + message = update.effective_message + bot, args = context.bot, context.args + if len(args) >= 1: + val = args[0].lower() + if val in ("off", "no"): + sql.set_cleanbt(chat.id, False) + reply = f"ʙʟᴜᴇᴛᴇxᴛ ᴄʟᴇᴀɴɪɴɢ ʜᴀs ʙᴇᴇɴ ᴅɪsᴀʙʟᴇᴅ ғᴏʀ <b>{html.escape(chat.title)}</b>" + + message.reply_text(reply, parse_mode=ParseMode.HTML) + + elif val in ("yes", "on"): + sql.set_cleanbt(chat.id, True) + reply = f"ʙʟᴜᴇᴛᴇxᴛ ᴄʟᴇᴀɴɪɴɢ ʜᴀs ʙᴇᴇɴ ᴇɴᴀʙʟᴇᴅ ғᴏʀ <b>{html.escape(chat.title)}</b>" + + message.reply_text(reply, parse_mode=ParseMode.HTML) + + else: + reply = "ɪɴᴠᴀʟɪᴅ ᴀʀɢᴜᴍᴇɴᴛ.ᴀᴄᴄᴇᴘᴛᴇᴅ ᴠᴀʟᴜᴇs ᴀʀᴇ 'yes', 'on', \ 'no', 'off'" + message.reply_text(reply) + else: + clean_status = sql.is_enabled(chat.id) + clean_status = "Enabled" if clean_status else "Disabled" + reply = f"ʙʟᴜᴇᴛᴇxᴛ ᴄʟᴇᴀɴɪɴɢ ғᴏʀ <b>{html.escape(chat.title)}</b> : <b>{clean_status}</b>" + + message.reply_text(reply, parse_mode=ParseMode.HTML) + + +@user_admin +def add_bluetext_ignore(update: Update, context: CallbackContext): + message = update.effective_message + chat = update.effective_chat + args = context.args + if len(args) >= 1: + val = args[0].lower() + if added := sql.chat_ignore_command(chat.id, val): + reply = f"<b>{args[0]}</b> ʜᴀs ʙᴇᴇɴ ᴀᴅᴅᴇᴅ ᴛᴏ ʙʟᴜᴇᴛᴇxᴛ ᴄʟᴇᴀɴᴇʀ ɪɢɴᴏʀᴇ ʟɪsᴛ." + else: + reply = "ᴄᴏᴍᴍᴀɴᴅ ɪs ᴀʟʀᴇᴀᴅʏ ɪɢɴᴏʀᴇᴅ." + message.reply_text(reply, parse_mode=ParseMode.HTML) + + else: + reply = "ɴᴏ ᴄᴏᴍᴍᴀɴᴅ sᴜᴘᴘʟɪᴇᴅ ᴛᴏ ʙᴇ ɪɢɴᴏʀᴇᴅ." + message.reply_text(reply) + + +@user_admin +def remove_bluetext_ignore(update: Update, context: CallbackContext): + message = update.effective_message + chat = update.effective_chat + args = context.args + if len(args) >= 1: + val = args[0].lower() + if removed := sql.chat_unignore_command(chat.id, val): + reply = ( + f"<b>{args[0]}</b> ʜᴀs ʙᴇᴇɴ ʀᴇᴍᴏᴠᴇᴅ ғʀᴏᴍ ʙʟᴜᴇᴛᴇxᴛ ᴄʟᴇᴀɴᴇʀ ɪɢɴᴏʀᴇ ʟɪsᴛ." + ) + else: + reply = "ᴄᴏᴍᴍᴀɴᴅ ɪsɴ'ᴛ ɪɢɴᴏʀᴇᴅ ᴄᴜʀʀᴇɴᴛʟʏ." + message.reply_text(reply, parse_mode=ParseMode.HTML) + + else: + reply = "ɴᴏ ᴄᴏᴍᴍᴀɴᴅ sᴜᴘᴘʟɪᴇᴅ ᴛᴏ ʙᴇ ᴜɴɪɢɴᴏʀᴇᴅ." + message.reply_text(reply) + + +@user_admin +def add_bluetext_ignore_global(update: Update, context: CallbackContext): + message = update.effective_message + args = context.args + if len(args) >= 1: + val = args[0].lower() + if added := sql.global_ignore_command(val): + reply = f"<b>{args[0]}</b> ʜᴀs ʙᴇᴇɴ ᴀᴅᴅᴇᴅ ᴛᴏ ɢʟᴏʙᴀʟ ʙʟᴜᴇᴛᴇxᴛ ᴄʟᴇᴀɴᴇʀ ɪɢɴᴏʀᴇ ʟɪsᴛ." + + else: + reply = "ᴄᴏᴍᴍᴀɴᴅ ɪs ᴀʟʀᴇᴀᴅʏ ɪɢɴᴏʀᴇᴅ." + message.reply_text(reply, parse_mode=ParseMode.HTML) + + else: + reply = "ɴᴏ ᴄᴏᴍᴍᴀɴᴅ sᴜᴘᴘʟɪᴇᴅ ᴛᴏ ʙᴇ ɪɢɴᴏʀᴇᴅ." + message.reply_text(reply) + + +@dev_plus +def remove_bluetext_ignore_global(update: Update, context: CallbackContext): + message = update.effective_message + args = context.args + if len(args) >= 1: + val = args[0].lower() + if removed := sql.global_unignore_command(val): + reply = f"<b>{args[0]}</b> ʜᴀs ʙᴇᴇɴ ʀᴇᴍᴏᴠᴇᴅ ғʀᴏᴍ ɢʟᴏʙᴀʟ ʙʟᴜᴇᴛᴇxᴛ ᴄʟᴇᴀɴᴇʀ ɪɢɴᴏʀᴇ ʟɪsᴛ." + + else: + reply = "ᴄᴏᴍᴍᴀɴᴅ ɪsɴ'ᴛ ɪɢɴᴏʀᴇᴅ ᴄᴜʀʀᴇɴᴛʟʏ." + message.reply_text(reply, parse_mode=ParseMode.HTML) + + else: + reply = "No ᴄᴏᴍᴍᴀɴᴅ sᴜᴘᴘʟɪᴇᴅ ᴛᴏ ʙᴇ ᴜɴɪɢɴᴏʀᴇᴅ." + message.reply_text(reply) + + +@dev_plus +def bluetext_ignore_list(update: Update, context: CallbackContext): + + message = update.effective_message + chat = update.effective_chat + + global_ignored_list, local_ignore_list = sql.get_all_ignored(chat.id) + text = "" + + if global_ignored_list: + text = "The ғᴏʟʟᴏᴡɪɴɢ ᴄᴏᴍᴍᴀɴᴅs ᴀʀᴇ ᴄᴜʀʀᴇɴᴛʟʏ ɪɢɴᴏʀᴇᴅ ɢʟᴏʙʏᴀʟʟʏ ғʀᴏᴍ ʙʟᴜᴇᴛᴇxᴛ ᴄʟᴇᴀɴɪɴɢ :\n" + + for x in global_ignored_list: + text += f" - <code>{x}</code>\n" + + if local_ignore_list: + text += "\nᴛʜᴇ ғᴏʟʟᴏᴡɪɴɢ ᴄᴏᴍᴍᴀɴᴅs ᴀʀᴇ ᴄᴜʀʀᴇɴᴛʟʏ ɪɢɴᴏʀᴇᴅ ʟᴏᴄᴀʟʟʏ ғʀᴏᴍ ʙʟᴜᴇᴛᴇxᴛ ᴄʟᴇᴀɴɪɴɢ :\n" + + for x in local_ignore_list: + text += f" - <code>{x}</code>\n" + + if text == "": + text = "ɴᴏ ᴄᴏᴍᴍᴀɴᴅs ᴀʀᴇ ᴄᴜʀʀᴇɴᴛʟʏ ɪɢɴᴏʀᴇᴅ ғʀᴏᴍ ʙʟᴜᴇᴛᴇxᴛ ᴄʟᴇᴀɴɪɴɢ." + message.reply_text(text) + return + + message.reply_text(text, parse_mode=ParseMode.HTML) + return + + +SET_CLEAN_BLUE_TEXT_HANDLER = CommandHandler( + "cleanblue", set_blue_text_must_click, run_async=True +) +ADD_CLEAN_BLUE_TEXT_HANDLER = CommandHandler( + "ignoreblue", add_bluetext_ignore, run_async=True +) +REMOVE_CLEAN_BLUE_TEXT_HANDLER = CommandHandler( + "unignoreblue", remove_bluetext_ignore, run_async=True +) +ADD_CLEAN_BLUE_TEXT_GLOBAL_HANDLER = CommandHandler( + "gignoreblue", + add_bluetext_ignore_global, + run_async=True, +) +REMOVE_CLEAN_BLUE_TEXT_GLOBAL_HANDLER = CommandHandler( + "ungignoreblue", + remove_bluetext_ignore_global, + run_async=True, +) +LIST_CLEAN_BLUE_TEXT_HANDLER = CommandHandler( + "listblue", bluetext_ignore_list, run_async=True +) +CLEAN_BLUE_TEXT_HANDLER = MessageHandler( + Filters.command & Filters.chat_type.groups, + clean_blue_text_must_click, + run_async=True, +) + +dispatcher.add_handler(SET_CLEAN_BLUE_TEXT_HANDLER) +dispatcher.add_handler(ADD_CLEAN_BLUE_TEXT_HANDLER) +dispatcher.add_handler(REMOVE_CLEAN_BLUE_TEXT_HANDLER) +dispatcher.add_handler(ADD_CLEAN_BLUE_TEXT_GLOBAL_HANDLER) +dispatcher.add_handler(REMOVE_CLEAN_BLUE_TEXT_GLOBAL_HANDLER) +dispatcher.add_handler(LIST_CLEAN_BLUE_TEXT_HANDLER) +dispatcher.add_handler(CLEAN_BLUE_TEXT_HANDLER, BLUE_TEXT_CLEAN_GROUP) + +__mod_name__ = "𝙲ʟᴇᴀɴɪɴɢ" +__handlers__ = [ + SET_CLEAN_BLUE_TEXT_HANDLER, + ADD_CLEAN_BLUE_TEXT_HANDLER, + REMOVE_CLEAN_BLUE_TEXT_HANDLER, + ADD_CLEAN_BLUE_TEXT_GLOBAL_HANDLER, + REMOVE_CLEAN_BLUE_TEXT_GLOBAL_HANDLER, + LIST_CLEAN_BLUE_TEXT_HANDLER, + (CLEAN_BLUE_TEXT_HANDLER, BLUE_TEXT_CLEAN_GROUP), +] + +__help__ = f""" +ʙʟᴜᴇ ᴛᴇxᴛ ᴄʟᴇᴀɴᴇʀ ʀᴇᴍᴏᴠᴇᴅ ᴀɴʏ ᴍᴀᴅᴇ ᴜᴘ ᴄᴏᴍᴍᴀɴᴅs ᴛʜᴀᴛ ᴘᴇᴏᴘʟᴇ sᴇɴᴅ ɪɴ ʏᴏᴜʀ ᴄʜᴀᴛ. + +• /cleanblue <on/off/yes/no>*:* `ᴄʟᴇᴀɴ ᴄᴏᴍᴍᴀɴᴅs ᴀғᴛᴇʀ sᴇɴᴅɪɴɢ` + +• /ignoreblue <word>*:* `ᴘʀᴇᴠᴇɴᴛ ᴀᴜᴛᴏ ᴄʟᴇᴀɴɪɴɢ ᴏғ ᴛʜᴇ ᴄᴏᴍᴍᴀɴᴅ ` + +• /unignoreblue <word>*:* `ʀᴇᴍᴏᴠᴇ ᴘʀᴇᴠᴇɴᴛ ᴀᴜᴛᴏ ᴄʟᴇᴀɴɪɴɢ ᴏғ ᴛʜᴇ ᴄᴏᴍᴍᴀɴᴅ ` + +• /listblue*:* `ʟɪsᴛ ᴄᴜʀʀᴇɴᴛʟʏ ᴡʜɪᴛᴇʟɪsᴛᴇᴅ ᴄᴏᴍᴍᴀɴᴅs ` + +*ᴏɴʟʏ ᴄᴏᴍᴍᴀɴᴅs, ᴀᴅᴍɪɴs ᴄᴀɴɴᴏᴛ ᴜsᴇ ᴛʜᴇsᴇ:* + +• /gignoreblue <word>*:* `ɢʟᴏʙᴀʟʟʏ ɪɢɴᴏʀᴇᴀ ʙʟᴜᴇᴛᴇxᴛ ᴄʟᴇᴀɴɪɴɢ ᴏғ sᴀᴠᴇᴅ ᴡᴏʀᴅ ᴀᴄʀᴏss` {BOT_NAME}. + +• /ungignoreblue <word>*:* `ʀᴇᴍᴏᴠᴇ sᴀɪᴅ ᴄᴏᴍᴍᴀɴᴅ ғʀᴏᴍ ɢʟᴏʙᴀʟ ᴄʟᴇᴀɴɪɴɢ ʟɪsᴛ ` +""" diff --git a/Exon/modules/connection.py b/Exon/modules/connection.py new file mode 100644 index 00000000..1beb49c4 --- /dev/null +++ b/Exon/modules/connection.py @@ -0,0 +1,463 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import re +import time + +from telegram import Bot, InlineKeyboardButton, InlineKeyboardMarkup, ParseMode, Update +from telegram.error import BadRequest, Unauthorized +from telegram.ext import CallbackQueryHandler, CommandHandler + +import Exon.modules.sql.connection_sql as sql +from Exon import DEV_USERS, DRAGONS, dispatcher +from Exon.modules.helper_funcs import chat_status +from Exon.modules.helper_funcs.alternate import send_message, typing_action + +user_admin = chat_status.user_admin + + +@user_admin +@typing_action +def allow_connections(update, context) -> str: + + chat = update.effective_chat + args = context.args + + if chat.type == chat.PRIVATE: + send_message( + update.effective_message, + "ᴛʜɪs ᴄᴏᴍᴍᴀɴᴅ ɪs ғᴏʀ ɢʀᴏᴜᴘ ᴏɴʟʏ. ɴᴏᴛ ɪɴ ᴘᴍ!", + ) + + elif len(args) >= 1: + var = args[0] + if var == "no": + sql.set_allow_connect_to_chat(chat.id, False) + send_message( + update.effective_message, + "ᴄᴏɴɴᴇᴄᴛɪᴏɴ ʜᴀs ʙᴇᴇɴ ᴅɪsᴀʙʟᴇᴅ ғᴏʀ ᴛʜɪs ᴄʜᴀᴛ", + ) + elif var == "yes": + sql.set_allow_connect_to_chat(chat.id, True) + send_message( + update.effective_message, + "ᴄᴏɴɴᴇᴄᴛɪᴏɴ ʜᴀs ʙᴇᴇɴ ᴇɴᴀʙʟᴇᴅ ғᴏʀ ᴛʜɪs ᴄʜᴀᴛ", + ) + else: + send_message( + update.effective_message, + "ᴘʟᴇᴀsᴇ ᴇɴᴛᴇʀ `yes` ᴏʀ `no`!", + parse_mode=ParseMode.MARKDOWN, + ) + elif get_settings := sql.allow_connect_to_chat(chat.id): + send_message( + update.effective_message, + "ᴄᴏɴɴᴇᴄᴛɪᴏɴs ᴛᴏ ᴛʜɪs ɢʀᴏᴜᴘ ᴀʀᴇ *ᴀʟʟᴏᴡᴇᴅ* ғᴏʀ ᴍᴇᴍʙᴇʀs!", + parse_mode=ParseMode.MARKDOWN, + ) + else: + send_message( + update.effective_message, + "ᴄᴏɴɴᴇᴄᴛɪᴏɴ ᴛᴏ ᴛʜɪs ɢʀᴏᴜᴘ ᴀʀᴇ *ɴᴏᴛ ᴀʟʟᴏᴡᴇᴅ* ғᴏʀ ᴍᴇᴍʙᴇʀs!", + parse_mode=ParseMode.MARKDOWN, + ) + + +@typing_action +def connection_chat(update, context): + + chat = update.effective_chat + user = update.effective_user + + conn = connected(context.bot, update, chat, user.id, need_admin=True) + + if conn: + chat = dispatcher.bot.getChat(conn) + chat_name = dispatcher.bot.getChat(conn).title + else: + if update.effective_message.chat.type != "private": + return + chat = update.effective_chat + chat_name = update.effective_message.chat.title + + if conn: + message = f"ʏᴏᴜ ᴀʀᴇ ᴄᴜʀʀᴇɴᴛʟʏ ᴄᴏɴɴᴇᴄᴛᴇᴅ ᴛᴏ {chat_name}.\n" + else: + message = "ʏᴏᴜ ᴀʀᴇ ᴄᴜʀʀᴇɴᴛʟʏ ɴᴏᴛ ᴄᴏɴɴᴇᴄᴛᴇᴅ ɪɴ ᴀɴʏ ɢʀᴏᴜᴘ.\n" + send_message(update.effective_message, message, parse_mode="markdown") + + +@typing_action +def connect_chat(update, context): + + chat = update.effective_chat + user = update.effective_user + args = context.args + + if update.effective_chat.type == "private": + if args and len(args) >= 1: + try: + connect_chat = int(args[0]) + getstatusadmin = context.bot.get_chat_member( + connect_chat, + update.effective_message.from_user.id, + ) + except ValueError: + try: + connect_chat = str(args[0]) + get_chat = context.bot.getChat(connect_chat) + connect_chat = get_chat.id + getstatusadmin = context.bot.get_chat_member( + connect_chat, + update.effective_message.from_user.id, + ) + except BadRequest: + send_message(update.effective_message, "ɪɴᴠᴀʟɪᴅ ᴄʜᴀᴛ ID!") + return + except BadRequest: + send_message(update.effective_message, "ɪɴᴠᴀʟɪᴅ ᴄʜᴀᴛ ID!") + return + + isadmin = getstatusadmin.status in ("administrator", "creator") + ismember = getstatusadmin.status in ("member") + isallow = sql.allow_connect_to_chat(connect_chat) + + if (isadmin) or (isallow and ismember) or (user.id in DRAGONS): + if connection_status := sql.connect( + update.effective_message.from_user.id, + connect_chat, + ): + conn_chat = dispatcher.bot.getChat( + connected(context.bot, update, chat, user.id, need_admin=False), + ) + chat_name = conn_chat.title + send_message( + update.effective_message, + f"sᴜᴄᴄᴇssғᴜʟʟʏ ᴄᴏɴɴᴇᴄᴛᴇᴅ ᴛᴏ *{chat_name}*. \nᴜsᴇ /helpconnect ᴛᴏ ᴄʜᴇᴄᴋ ᴀᴠᴀɪʟᴀʙʟᴇ ᴄᴏᴍᴍᴀɴᴅs.", + parse_mode=ParseMode.MARKDOWN, + ) + + sql.add_history_conn(user.id, str(conn_chat.id), chat_name) + else: + send_message(update.effective_message, "Connection failed!") + else: + send_message( + update.effective_message, + "ᴄᴏɴɴᴇᴄᴛɪᴏɴ ᴛᴏ ᴛʜɪs ᴄʜᴀᴛ ɪs ɴᴏᴛ ᴀʟʟᴏᴡᴇᴅ!", + ) + else: + gethistory = sql.get_history_conn(user.id) + if gethistory: + buttons = [ + InlineKeyboardButton( + text="⛔ ᴄʟᴏsᴇ ʙᴜᴛᴛᴏɴ", + callback_data="connect_close", + ), + InlineKeyboardButton( + text="🧹 ᴄʟᴇᴀʀ ʜɪsᴛᴏʀʏ", + callback_data="connect_clear", + ), + ] + else: + buttons = [] + if conn := connected(context.bot, update, chat, user.id, need_admin=False): + connectedchat = dispatcher.bot.getChat(conn) + text = ( + f"ғ ᴀʀᴇ ᴄᴜʀʀᴇɴᴛʟʏ ᴄᴏɴɴᴇᴄᴛᴇᴅ ᴛᴏ *{connectedchat.title}* (`{conn}`)" + ) + buttons.append( + InlineKeyboardButton( + text="🔌 ᴅɪsᴄᴏɴɴᴇᴄᴛ", + callback_data="connect_disconnect", + ), + ) + else: + text = "ᴡʀɪᴛᴇ ᴛʜᴇ ᴄʜᴀᴛ ɪᴅ ᴏʀ ᴛᴀɢ ᴛᴏ ᴄᴏɴɴᴇᴄᴛ!" + if gethistory: + text += "\n\n*Connection history:*\n" + text += "╒═══「 *ɪɴғᴏ* 」\n" + text += "│ sᴏʀᴛᴇᴅ: `ɴᴇᴡᴇsᴛ`\n" + text += "│\n" + buttons = [buttons] + for x in sorted(gethistory.keys(), reverse=True): + htime = time.strftime("%ᴅ/%ᴍ/%ʏ", time.localtime(x)) + text += f'╞═「 *{gethistory[x]["chat_name"]}* 」\n│ `{gethistory[x]["chat_id"]}`\n│ `{htime}`\n' + + text += "│\n" + buttons.append( + [ + InlineKeyboardButton( + text=gethistory[x]["chat_name"], + callback_data=f'connect({gethistory[x]["chat_id"]})', + ) + ] + ) + + text += "╘══「 ᴛᴏᴛᴀʟ {} ᴄʜᴀᴛs 」".format( + f"{len(gethistory)} (max)" + if len(gethistory) == 5 + else str(len(gethistory)) + ) + + conn_hist = InlineKeyboardMarkup(buttons) + elif buttons: + conn_hist = InlineKeyboardMarkup([buttons]) + else: + conn_hist = None + send_message( + update.effective_message, + text, + parse_mode="markdown", + reply_markup=conn_hist, + ) + + else: + getstatusadmin = context.bot.get_chat_member( + chat.id, + update.effective_message.from_user.id, + ) + isadmin = getstatusadmin.status in ("administrator", "creator") + ismember = getstatusadmin.status in ("member") + isallow = sql.allow_connect_to_chat(chat.id) + if (isadmin) or (isallow and ismember) or (user.id in DRAGONS): + if connection_status := sql.connect( + update.effective_message.from_user.id, + chat.id, + ): + chat_name = dispatcher.bot.getChat(chat.id).title + send_message( + update.effective_message, + f"sᴜᴄᴄᴇssғᴜʟʟʏ ᴄᴏɴɴᴇᴄᴛᴇᴅ ᴛᴏ *{chat_name}*.", + parse_mode=ParseMode.MARKDOWN, + ) + + try: + sql.add_history_conn(user.id, str(chat.id), chat_name) + context.bot.send_message( + update.effective_message.from_user.id, + f"ʏᴏᴜ ᴀʀᴇ ᴄᴏɴɴᴇᴄᴛᴇᴅ ᴛᴏ *{chat_name}*. \nᴜsᴇ `/helpconnect` ᴛᴏ ᴄʜᴇᴄᴋ ᴀᴠᴀɪʟᴀʙʟᴇ ᴄᴏᴍᴍᴀɴᴅs.", + parse_mode="markdown", + ) + + except (BadRequest, Unauthorized): + pass + else: + send_message(update.effective_message, "ᴄᴏɴɴᴇᴄᴛɪᴏɴ ғᴀɪʟᴇᴅ!") + else: + send_message( + update.effective_message, + "ᴄᴏɴɴᴇᴄᴛɪᴏɴ ᴛᴏ ᴛʜɪs ᴄʜᴀᴛ ɪs ɴᴏᴛ ᴀʟʟᴏᴡᴇᴅ!", + ) + + +def disconnect_chat(update, context): + + if update.effective_chat.type == "private": + if disconnection_status := sql.disconnect( + update.effective_message.from_user.id + ): + sql.disconnected_chat = send_message( + update.effective_message, + "ᴅɪsᴄᴏɴɴᴇᴄᴛᴇᴅ ғʀᴏᴍ ᴄʜᴀᴛ!", + ) + else: + send_message(update.effective_message, "ʏᴏᴜ'ʀᴇ ɴᴏᴛ ᴄᴏɴɴᴇᴄᴛᴇᴅ!") + else: + send_message(update.effective_message, "ᴛʜɪs ᴄᴏᴍᴍᴀɴᴅ ɪs ᴏɴʟʏ ᴀᴠᴀɪʟᴀʙʟᴇ ɪɴ PM.") + + +def connected(bot: Bot, update: Update, chat, user_id, need_admin=True): + user = update.effective_user + + if chat.type == chat.PRIVATE and sql.get_connected_chat(user_id): + + conn_id = sql.get_connected_chat(user_id).chat_id + getstatusadmin = bot.get_chat_member( + conn_id, + update.effective_message.from_user.id, + ) + isadmin = getstatusadmin.status in ("administrator", "creator") + ismember = getstatusadmin.status in ("member") + isallow = sql.allow_connect_to_chat(conn_id) + + if ( + (isadmin) + or (isallow and ismember) + or (user.id in DRAGONS) + or (user.id in DEV_USERS) + ): + if need_admin is True: + if ( + getstatusadmin.status in ("administrator", "creator") + or user_id in DRAGONS + or user.id in DEV_USERS + ): + return conn_id + send_message( + update.effective_message, + "ʏᴏᴜ ᴍᴜsᴛ ʙᴇ ᴀɴ ᴀᴅᴍɪɴ ɪɴ ᴛʜᴇ ᴄᴏɴɴᴇᴄᴛᴇᴅ ɢʀᴏᴜᴘ!", + ) + else: + return conn_id + else: + send_message( + update.effective_message, + "ᴛʜᴇ ɢʀᴏᴜᴘ ᴄʜᴀɴɢᴇᴅ ᴛʜᴇ ᴄᴏɴɴᴇᴄᴛɪᴏɴ ʀɪɢʜᴛs ᴏʀ ʏᴏᴜ ᴀʀᴇ ɴᴏ ʟᴏɴɢᴇʀ ᴀɴ ᴀᴅᴍɪɴ.\nI'ᴠᴇ ᴅɪsᴄᴏɴɴᴇᴄᴛᴇᴅ ʏᴏᴜ.", + ) + disconnect_chat(update, bot) + else: + return False + + +CONN_HELP = """ +ᴀᴄᴛɪᴏɴs ᴡʜɪᴄʜ ᴀʀᴇ ᴀᴠᴀɪʟᴀʙʟᴇ ᴡɪᴛʜ ᴄᴏɴɴᴇᴄᴛᴇᴅ ɢʀᴏᴜᴘs:- +*ᴜsᴇʀ ᴀᴄᴛɪᴏɴs:* +• ᴠɪᴇᴡ ɴᴏᴛᴇs +• ᴠɪᴇᴡ ғɪʟᴛᴇʀs +• ᴠɪᴇᴡ ʙʟᴀᴄᴋʟɪsᴛ +• ᴠɪᴇᴡ ᴀɴᴛɪғʟᴏᴏᴅ sᴇᴛᴛɪɴɢs +• ᴠɪᴇᴡ ᴅɪsᴀʙʟᴇᴅ ᴄᴏᴍᴍᴀɴᴅs +• ᴍᴀɴʏ ᴍᴏʀᴇ ɪɴ ғᴜᴛᴜʀᴇ +! +*ᴀᴅᴍɪɴ ᴀᴄᴛɪᴏɴs:* + • View ᴀɴᴅ ᴇᴅɪᴛ ɴᴏᴛᴇs + • ᴠɪᴇᴡ ᴀɴᴅ ᴇᴅɪᴛ ғɪʟᴛᴇʀs. + • ɢᴇᴛ ɪɴᴠɪᴛᴇ ʟɪɴᴋ ᴏғ ᴄʜᴀᴛ. + • sᴇᴛ ᴀɴᴅ ᴄᴏɴᴛʀᴏʟ ᴀɴᴛɪғʟᴏᴏᴅ sᴇᴛᴛɪɴɢs. + • sᴇᴛ ᴀɴᴅ ᴄᴏɴᴛʀᴏʟ ʙʟᴀᴄᴋʟɪsᴛ sᴇᴛᴛɪɴɢs. + • Set ʟᴏᴄᴋs ᴀɴᴅ ᴜɴʟᴏᴄᴋs ɪɴ ᴄʜᴀᴛ. + • ᴇɴᴀʙʟᴇ and ᴅɪsᴀʙʟᴇ ᴄᴏᴍᴍᴀɴᴅs in chat. + • ᴇxᴘᴏʀᴛ ᴀɴᴅ Imports ᴏғ ᴄʜᴀᴛ ʙᴀᴄᴋᴜᴘ. + • ᴍᴏʀᴇ ɪɴ ғᴜᴛᴜʀᴇ! +""" + + +def help_connect_chat(update, context): + + context.args + + if update.effective_message.chat.type != "private": + send_message(update.effective_message, "PM ᴍᴇ ᴡɪᴛʜ ᴛʜᴀᴛ ᴄᴏᴍᴍᴀɴᴅ ᴛᴏ ɢᴇᴛ ʜᴇʟᴘ.") + return + send_message(update.effective_message, CONN_HELP, parse_mode="markdown") + + +def connect_button(update, context): + + query = update.callback_query + chat = update.effective_chat + user = update.effective_user + + connect_match = re.match(r"connect\((.+?)\)", query.data) + disconnect_match = query.data == "connect_disconnect" + clear_match = query.data == "connect_clear" + connect_close = query.data == "connect_close" + + if connect_match: + target_chat = connect_match[1] + getstatusadmin = context.bot.get_chat_member(target_chat, query.from_user.id) + isadmin = getstatusadmin.status in ("administrator", "creator") + ismember = getstatusadmin.status in ("member") + isallow = sql.allow_connect_to_chat(target_chat) + + if (isadmin) or (isallow and ismember) or (user.id in DRAGONS): + if connection_status := sql.connect(query.from_user.id, target_chat): + conn_chat = dispatcher.bot.getChat( + connected(context.bot, update, chat, user.id, need_admin=False), + ) + chat_name = conn_chat.title + query.message.edit_text( + f"sᴜᴄᴄᴇssғᴜʟʟʏ ᴄᴏɴɴᴇᴄᴛᴇᴅ ᴛᴏ *{chat_name}*. \nᴜsᴇ `/helpconnect` ᴛᴏ ᴄʜᴇᴄᴋ ᴀᴠᴀɪʟᴀʙʟᴇ ᴄᴏᴍᴍᴀɴᴅs.", + parse_mode=ParseMode.MARKDOWN, + ) + + sql.add_history_conn(user.id, str(conn_chat.id), chat_name) + else: + query.message.edit_text("ᴄᴏɴɴᴇᴄᴛɪᴏɴ ғᴀɪʟᴇᴅ!") + else: + context.bot.answer_callback_query( + query.id, + "ᴄᴏɴɴᴇᴄᴛɪᴏɴ ᴛᴏ ᴛʜɪs ᴄʜᴀᴛ ɪs ɴᴏᴛ ᴀʟʟᴏᴡᴇᴅ!", + show_alert=True, + ) + elif disconnect_match: + if disconnection_status := sql.disconnect(query.from_user.id): + sql.disconnected_chat = query.message.edit_text("Disconnected from chat!") + else: + context.bot.answer_callback_query( + query.id, + "ʏᴏᴜ'ʀᴇ ɴᴏᴛ ᴄᴏɴɴᴇᴄᴛᴇᴅ!", + show_alert=True, + ) + elif clear_match: + sql.clear_history_conn(query.from_user.id) + query.message.edit_text("ʜɪsᴛᴏʀʏ ᴄᴏɴɴᴇᴄᴛᴇᴅ ʜᴀs ʙᴇᴇɴ ᴄʟᴇᴀʀᴇᴅ!") + elif connect_close: + query.message.edit_text("ᴄʟᴏsᴇᴅ.\nᴛᴏ ᴏᴘᴇɴ ᴀɢᴀɪɴ, ᴛʏᴘᴇ /connect") + else: + connect_chat(update, context) + + +__mod_name__ = "𝙲ᴏɴɴᴇᴄᴛs" + +__help__ = """ +*sᴏᴍᴇᴛɪᴍᴇs, ʏᴏᴜ ᴊᴜsᴛ ᴡᴀɴᴛ ᴛᴏ ᴀᴅᴅ sᴏᴍᴇ ɴᴏᴛᴇs ᴀɴᴅ ғɪʟᴛᴇʀs ᴛᴏ ᴀ ɢʀᴏᴜᴘ ᴄʜᴀᴛ, ʙᴜᴛ ʏᴏᴜ ᴅᴏɴ'ᴛ ᴡᴀɴᴛ ᴇᴠᴇʀʏᴏɴᴇ ᴛᴏ sᴇᴇ; ᴛʜɪs ɪs ᴡʜᴇʀᴇ ᴄᴏɴɴᴇᴄᴛɪᴏɴs ᴄᴏᴍᴇ ɪɴ... +ᴛʜɪs ᴀʟʟᴏᴡs ʏᴏᴜ ᴛᴏ ᴄᴏɴɴᴇᴄᴛ ᴛᴏ ᴀ ᴄʜᴀᴛ's ᴅᴀᴛᴀʙᴀsᴇ, ᴀɴᴅ ᴀᴅᴅ ᴛʜɪɴɢs ᴛᴏ ɪᴛ ᴡɪᴛʜᴏᴜᴛ ᴛʜᴇ ᴄᴏᴍᴍᴀɴᴅs ᴀᴘᴘᴇᴀʀɪɴɢ ɪɴ ᴄʜᴀᴛ! ғᴏʀ ᴏʙᴠɪᴏᴜs ʀᴇᴀsᴏɴs, ʏᴏᴜ ɴᴇᴇᴅ ᴛᴏ ʙᴇ ᴀɴ ᴀᴅᴍɪɴ ᴛᴏ ᴀᴅᴅ ᴛʜɪɴɢs; ʙᴜᴛ ᴀɴʏ ᴍᴇᴍʙᴇʀ ɪɴ ᴛʜᴇ ɢʀᴏᴜᴘ ᴄᴀɴ ᴠɪᴇᴡ ʏᴏᴜʀ ᴅᴀᴛᴀ.* + + +❂ /connect: `ᴄᴏɴɴᴇᴄᴛꜱ ᴛᴏ ᴄʜᴀᴛ` (ᴄᴀɴ ʙᴇ ᴅᴏɴᴇ ɪɴ ᴀ ɢʀᴏᴜᴘ ʙʏ /connect ᴏʀ /connect <chat id> ɪɴ ᴘᴍ) + +❂ /connection: `ʟɪꜱᴛ ᴄᴏɴɴᴇᴄᴛᴇᴅ ᴄʜᴀᴛꜱ` + +❂ /disconnect: `ᴅɪꜱᴄᴏɴɴᴇᴄᴛ ғʀᴏᴍ ᴀ ᴄʜᴀᴛ` + +❂ /helpconnect: `ʟɪꜱᴛ ᴀᴠᴀɪʟᴀʙʟᴇ ᴄᴏᴍᴍᴀɴᴅꜱ ᴛʜᴀᴛ ᴄᴀɴ ʙᴇ ᴜꜱᴇᴅ ʀᴇᴍᴏᴛᴇʟʏ` + +*ᴀᴅᴍɪɴ ᴏɴʟʏ:* + +❂ /allowconnect <yes/no>: `ᴀʟʟᴏᴡ ᴀ ᴜꜱᴇʀ ᴛᴏ ᴄᴏɴɴᴇᴄᴛ ᴛᴏ ᴀ ᴄʜᴀᴛ` + +""" + +CONNECT_CHAT_HANDLER = CommandHandler( + "connect", connect_chat, pass_args=True, run_async=True +) +CONNECTION_CHAT_HANDLER = CommandHandler("connection", connection_chat, run_async=True) +DISCONNECT_CHAT_HANDLER = CommandHandler("disconnect", disconnect_chat, run_async=True) +ALLOW_CONNECTIONS_HANDLER = CommandHandler( + "allowconnect", allow_connections, pass_args=True, run_async=True +) +HELP_CONNECT_CHAT_HANDLER = CommandHandler( + "helpconnect", help_connect_chat, run_async=True +) +CONNECT_BTN_HANDLER = CallbackQueryHandler( + connect_button, pattern=r"connect", run_async=True +) + +dispatcher.add_handler(CONNECT_CHAT_HANDLER) +dispatcher.add_handler(CONNECTION_CHAT_HANDLER) +dispatcher.add_handler(DISCONNECT_CHAT_HANDLER) +dispatcher.add_handler(ALLOW_CONNECTIONS_HANDLER) +dispatcher.add_handler(HELP_CONNECT_CHAT_HANDLER) +dispatcher.add_handler(CONNECT_BTN_HANDLER) diff --git a/Exon/modules/cust_filters.py b/Exon/modules/cust_filters.py new file mode 100644 index 00000000..daa6da96 --- /dev/null +++ b/Exon/modules/cust_filters.py @@ -0,0 +1,608 @@ +""" +MIT License +Copyright (c) 2022 Aʙɪsʜɴᴏɪ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + + +import re +from html import escape +from typing import Optional + +import telegram +from telegram import ( + Chat, + InlineKeyboardButton, + InlineKeyboardMarkup, + Message, + ParseMode, +) +from telegram.error import BadRequest +from telegram.ext import DispatcherHandlerStop, Filters +from telegram.utils.helpers import escape_markdown, mention_html + +from Exon import DRAGONS as SUDO_USERS +from Exon import LOGGER as log +from Exon import dispatcher +from Exon.modules.connection import connected +from Exon.modules.helper_funcs.alternate import send_message, typing_action +from Exon.modules.helper_funcs.decorators import Exoncallback, Exoncmd, Exonmsg +from Exon.modules.helper_funcs.extraction import extract_text +from Exon.modules.helper_funcs.filters import CustomFilters +from Exon.modules.helper_funcs.misc import build_keyboard_parser +from Exon.modules.helper_funcs.msg_types import get_filter_type +from Exon.modules.helper_funcs.string_handling import ( + button_markdown_parser, + escape_invalid_curly_brackets, + markdown_to_html, + split_quotes, +) +from Exon.modules.sql import cust_filters_sql as sql + +from ..modules.helper_funcs.anonymous import AdminPerms, user_admin + +HANDLER_GROUP = 10 + +ENUM_FUNC_MAP = { + sql.Types.TEXT.value: dispatcher.bot.send_message, + sql.Types.BUTTON_TEXT.value: dispatcher.bot.send_message, + sql.Types.STICKER.value: dispatcher.bot.send_sticker, + sql.Types.DOCUMENT.value: dispatcher.bot.send_document, + sql.Types.PHOTO.value: dispatcher.bot.send_photo, + sql.Types.AUDIO.value: dispatcher.bot.send_audio, + sql.Types.VOICE.value: dispatcher.bot.send_voice, + sql.Types.VIDEO.value: dispatcher.bot.send_video, + # sql.Types.VIDEO_NOTE.value: dispatcher.bot.send_video_note +} + + +@typing_action +@Exoncmd(command="filters", admin_ok=True) +def list_handlers(update, context): + chat = update.effective_chat + user = update.effective_user + + conn = connected(context.bot, update, chat, user.id, need_admin=False) + if conn is not False: + chat_id = conn + chat_name = dispatcher.bot.getChat(conn).title + filter_list = "*Filter in {}:*\n" + else: + chat_id = update.effective_chat.id + if chat.type == "private": + chat_name = "Local filters" + filter_list = "*local filters:*\n" + else: + chat_name = chat.title + filter_list = "*Filters in {}*:\n" + + all_handlers = sql.get_chat_triggers(chat_id) + + if not all_handlers: + send_message( + update.effective_message, "No filters saved in {}!".format(chat_name) + ) + return + + for keyword in all_handlers: + entry = " • `{}`\n".format(escape_markdown(keyword)) + if len(entry) + len(filter_list) > telegram.MAX_MESSAGE_LENGTH: + send_message( + update.effective_message, + filter_list.format(chat_name), + parse_mode=telegram.ParseMode.MARKDOWN, + ) + filter_list = entry + else: + filter_list += entry + + send_message( + update.effective_message, + filter_list.format(chat_name), + parse_mode=telegram.ParseMode.MARKDOWN, + ) + + +# NOT ASYNC BECAUSE DISPATCHER HANDLER RAISED +@Exoncmd(command="filter", run_async=False) +@user_admin(AdminPerms.CAN_CHANGE_INFO) +@typing_action +def filters(update, context): # sourcery no-metrics + chat = update.effective_chat + user = update.effective_user + msg = update.effective_message + args = msg.text.split( + None, 1 + ) # use python's maxsplit to separate Cmd, keyword, and reply_text + + conn = connected(context.bot, update, chat, user.id) + if conn is not False: + chat_id = conn + chat_name = dispatcher.bot.getChat(conn).title + else: + chat_id = update.effective_chat.id + chat_name = "local filters" if chat.type == "private" else chat.title + if not msg.reply_to_message and len(args) < 2: + send_message( + update.effective_message, + "Please provide keyboard keyword for this filter to reply with!", + ) + return + + if msg.reply_to_message: + if len(args) < 2: + send_message( + update.effective_message, + "Please provide keyword for this filter to reply with!", + ) + return + else: + keyword = args[1] + else: + extracted = split_quotes(args[1]) + if len(extracted) < 1: + return + # set trigger -> lower, so as to avoid adding duplicate filters with different cases + keyword = extracted[0].lower() + + # Add the filter + # Note: perhaps handlers can be removed somehow using sql.get_chat_filters + for handler in dispatcher.handlers.get(HANDLER_GROUP, []): + if handler.filters == (keyword, chat_id): + dispatcher.remove_handler(handler, HANDLER_GROUP) + + text, file_type, file_id = get_filter_type(msg) + if not msg.reply_to_message and len(extracted) >= 2: + offset = len(extracted[1]) - len( + msg.text + ) # set correct offset relative to command + notename + text, buttons = button_markdown_parser( + extracted[1], entities=msg.parse_entities(), offset=offset + ) + text = text.strip() + if not text: + send_message( + update.effective_message, + "There is no filter message - You can't JUST have buttons, you need a message to go with it!", + ) + return + + elif msg.reply_to_message and len(args) >= 2: + if msg.reply_to_message.text: + text_to_parsing = msg.reply_to_message.text + elif msg.reply_to_message.caption: + text_to_parsing = msg.reply_to_message.caption + else: + text_to_parsing = "" + offset = len( + text_to_parsing + ) # set correct offset relative to command + notename + text, buttons = button_markdown_parser( + text_to_parsing, entities=msg.parse_entities(), offset=offset + ) + text = text.strip() + + elif not text and not file_type: + send_message( + update.effective_message, + "Please provide keyword for this filter reply with!", + ) + return + + elif msg.reply_to_message: + if msg.reply_to_message.text: + text_to_parsing = msg.reply_to_message.text + elif msg.reply_to_message.caption: + text_to_parsing = msg.reply_to_message.caption + else: + text_to_parsing = "" + offset = len( + text_to_parsing + ) # set correct offset relative to command + notename + text, buttons = button_markdown_parser( + text_to_parsing, entities=msg.parse_entities(), offset=offset + ) + text = text.strip() + if (msg.reply_to_message.text or msg.reply_to_message.caption) and not text: + send_message( + update.effective_message, + "There is no filter message - You can't JUST have buttons, you need a message to go with it!", + ) + return + + else: + send_message(update.effective_message, "Invalid filter!") + return + + add = addnew_filter(update, chat_id, keyword, text, file_type, file_id, buttons) + # This is an old method + # sql.add_filter(chat_id, keyword, content, is_sticker, is_document, is_image, is_audio, is_voice, is_video, buttons) + + if add is True: + send_message( + update.effective_message, + "Saved filter '{}' in *{}*!".format(keyword, chat_name), + parse_mode=telegram.ParseMode.MARKDOWN, + ) + raise DispatcherHandlerStop + + +# NOT ASYNC BECAUSE DISPATCHER HANDLER RAISED +@Exoncmd(command="stop", run_async=False) +@user_admin(AdminPerms.CAN_CHANGE_INFO) +@typing_action +def stop_filter(update, context): + chat = update.effective_chat + user = update.effective_user + args = update.effective_message.text.split(None, 1) + + conn = connected(context.bot, update, chat, user.id) + if conn is not False: + chat_id = conn + chat_name = dispatcher.bot.getChat(conn).title + else: + chat_id = update.effective_chat.id + chat_name = "Local filters" if chat.type == "private" else chat.title + if len(args) < 2: + send_message(update.effective_message, "What should i stop?") + return + + chat_filters = sql.get_chat_triggers(chat_id) + + if not chat_filters: + send_message(update.effective_message, "No filters active here!") + return + + for keyword in chat_filters: + if keyword == args[1]: + sql.remove_filter(chat_id, args[1]) + send_message( + update.effective_message, + "Okay, I'll stop replying to that filter in *{}*.".format(chat_name), + parse_mode=telegram.ParseMode.MARKDOWN, + ) + raise DispatcherHandlerStop + + send_message( + update.effective_message, + "That's not a filter - Click: /filters to get currently active filters.", + ) + + +@Exonmsg((CustomFilters.has_text & ~Filters.update.edited_message)) +def reply_filter(update, context): # sourcery no-metrics + chat = update.effective_chat # type: Optional[Chat] + message = update.effective_message # type: Optional[Message] + + if not update.effective_user or update.effective_user.id == 777000: + return + to_match = extract_text(message) + if not to_match: + return + + chat_filters = sql.get_chat_triggers(chat.id) + for keyword in chat_filters: + pattern = r"( |^|[^\w])" + re.escape(keyword) + r"( |$|[^\w])" + if re.search(pattern, to_match, flags=re.IGNORECASE): + filt = sql.get_filter(chat.id, keyword) + if filt.reply == "there is should be a new reply": + buttons = sql.get_buttons(chat.id, filt.keyword) + keyb = build_keyboard_parser(context.bot, chat.id, buttons) + keyboard = InlineKeyboardMarkup(keyb) + + VALID_WELCOME_FORMATTERS = [ + "first", + "last", + "fullname", + "username", + "id", + "chatname", + "mention", + ] + if filt.reply_text: + valid_format = escape_invalid_curly_brackets( + markdown_to_html(filt.reply_text), VALID_WELCOME_FORMATTERS + ) + if valid_format: + filtext = valid_format.format( + first=escape(message.from_user.first_name), + last=escape( + message.from_user.last_name + or message.from_user.first_name + ), + fullname=" ".join( + [ + escape(message.from_user.first_name), + escape(message.from_user.last_name), + ] + if message.from_user.last_name + else [escape(message.from_user.first_name)] + ), + username="@" + escape(message.from_user.username) + if message.from_user.username + else mention_html( + message.from_user.id, message.from_user.first_name + ), + mention=mention_html( + message.from_user.id, message.from_user.first_name + ), + chatname=escape(message.chat.title) + if message.chat.type != "private" + else escape(message.from_user.first_name), + id=message.from_user.id, + ) + else: + filtext = "" + else: + filtext = "" + + if filt.file_type in (sql.Types.BUTTON_TEXT, sql.Types.TEXT): + try: + context.bot.send_message( + chat.id, + filtext, + reply_to_message_id=message.message_id, + parse_mode=ParseMode.HTML, + disable_web_page_preview=True, + reply_markup=keyboard, + ) + except BadRequest as excp: + error_catch = get_exception(excp, filt, chat) + if error_catch == "noreply": + try: + context.bot.send_message( + chat.id, + filtext, + parse_mode=ParseMode.HTML, + disable_web_page_preview=True, + reply_markup=keyboard, + ) + except BadRequest as excp: + log.exception("Error in filters: " + excp.message) + send_message( + update.effective_message, + get_exception(excp, filt, chat), + ) + else: + try: + send_message( + update.effective_message, + get_exception(excp, filt, chat), + ) + except BadRequest as excp: + log.exception("Failed to send message: " + excp.message) + elif ENUM_FUNC_MAP[filt.file_type] == dispatcher.bot.send_sticker: + ENUM_FUNC_MAP[filt.file_type]( + chat.id, + filt.file_id, + reply_to_message_id=message.message_id, + reply_markup=keyboard, + ) + else: + ENUM_FUNC_MAP[filt.file_type]( + chat.id, + filt.file_id, + caption=filtext, + reply_to_message_id=message.message_id, + parse_mode=ParseMode.HTML, + reply_markup=keyboard, + ) + elif filt.is_sticker: + message.reply_sticker(filt.reply) + elif filt.is_document: + message.reply_document(filt.reply) + elif filt.is_image: + message.reply_photo(filt.reply) + elif filt.is_audio: + message.reply_audio(filt.reply) + elif filt.is_voice: + message.reply_voice(filt.reply) + elif filt.is_video: + message.reply_video(filt.reply) + elif filt.has_markdown: + buttons = sql.get_buttons(chat.id, filt.keyword) + keyb = build_keyboard_parser(context.bot, chat.id, buttons) + keyboard = InlineKeyboardMarkup(keyb) + + try: + send_message( + update.effective_message, + filt.reply, + parse_mode=ParseMode.MARKDOWN, + disable_web_page_preview=True, + reply_markup=keyboard, + ) + except BadRequest as excp: + if excp.message == "Unsupported url protocol": + try: + send_message( + update.effective_message, + "You seem to be trying to use an unsupported url protocol. " + "Telegram doesn't support buttons for some protocols, such as tg://. Please try " + "again...", + ) + except BadRequest as excp: + log.exception("Error in filters: " + excp.message) + elif excp.message == "Reply message not found": + try: + context.bot.send_message( + chat.id, + filt.reply, + parse_mode=ParseMode.MARKDOWN, + disable_web_page_preview=True, + reply_markup=keyboard, + ) + except BadRequest as excp: + log.exception("Error in filters: " + excp.message) + else: + try: + send_message( + update.effective_message, + "This message couldn't be sent as it's incorrectly formatted.", + ) + except BadRequest as excp: + log.exception("Error in filters: " + excp.message) + log.warning("Message %s could not be parsed", str(filt.reply)) + log.exception( + "Could not parse filter %s in chat %s", + str(filt.keyword), + str(chat.id), + ) + + else: + # LEGACY - all new filters will have has_markdown set to True. + try: + send_message(update.effective_message, filt.reply) + except BadRequest as excp: + log.exception("Error in filters: " + excp.message) + break + + +@Exoncmd(command="removeallfilters", filters=Filters.chat_type.groups) +def rmall_filters(update, _): + chat = update.effective_chat + user = update.effective_user + member = chat.get_member(user.id) + if member.status != "creator" and user.id not in SUDO_USERS: + update.effective_message.reply_text( + "Only the chat owner can clear all notes at once." + ) + else: + buttons = InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + text="Stop all filters", callback_data="filters_rmall" + ) + ], + [InlineKeyboardButton(text="Cancel", callback_data="filters_cancel")], + ] + ) + update.effective_message.reply_text( + f"Are you sure you would like to stop ALL filters in {chat.title}? This action cannot be undone.", + reply_markup=buttons, + parse_mode=ParseMode.MARKDOWN, + ) + + +@Exoncallback(pattern=r"filters_.*") +def rmall_callback(update, _): + query = update.callback_query + chat = update.effective_chat + msg = update.effective_message + member = chat.get_member(query.from_user.id) + if query.data == "filters_rmall": + if member.status == "creator" or query.from_user.id in SUDO_USERS: + allfilters = sql.get_chat_triggers(chat.id) + if not allfilters: + msg.edit_text("No filters in this chat, nothing to stop!") + return + + count = 0 + filterlist = [] + for x in allfilters: + count += 1 + filterlist.append(x) + + for i in filterlist: + sql.remove_filter(chat.id, i) + + msg.edit_text(f"Cleaned {count} filters in {chat.title}") + + if member.status == "administrator": + query.answer("Only owner of the chat can do this.") + + if member.status == "member": + query.answer("You need to be admin to do this.") + elif query.data == "filters_cancel": + if member.status == "creator" or query.from_user.id in SUDO_USERS: + msg.edit_text("Clearing of all filters has been cancelled.") + return + if member.status == "administrator": + query.answer("Only owner of the chat can do this.") + if member.status == "member": + query.answer("You need to be admin to do this.") + + +# NOT ASYNC NOT A HANDLER +def get_exception(excp, filt, chat): + if excp.message == "Unsupported url protocol": + return "You seem to be trying to use the URL protocol which is not supported. Telegram does not support key for multiple protocols, such as tg: //. Please try again!" + elif excp.message == "Reply message not found": + return "noreply" + else: + log.warning("Message %s could not be parsed", str(filt.reply)) + log.exception( + "Could not parse filter %s in chat %s", str(filt.keyword), str(chat.id) + ) + return "This data could not be sent because it is incorrectly formatted." + + +# NOT ASYNC NOT A HANDLER +def addnew_filter(update, chat_id, keyword, text, file_type, file_id, buttons): + msg = update.effective_message + totalfilt = sql.get_chat_triggers(chat_id) + if len(totalfilt) >= 1000: # Idk why i made this like function.... + msg.reply_text("This group has reached its max filters limit of 150.") + return False + else: + sql.new_add_filter(chat_id, keyword, text, file_type, file_id, buttons) + return True + + +def __stats__(): + return "•➥ {} ғɪʟᴛᴇʀs, ᴀᴄʀᴏss {} ᴄʜᴀᴛs.".format(sql.num_filters(), sql.num_chats()) + + +def __import_data__(chat_id, data): + # set chat filters + filters = data.get("filters", {}) + for trigger in filters: + sql.add_to_blacklist(chat_id, trigger) + + +def __migrate__(old_chat_id, new_chat_id): + sql.migrate_chat(old_chat_id, new_chat_id) + + +def __chat_settings__(chat_id, _): + cust_filters = sql.get_chat_triggers(chat_id) + return "ᴛʜᴇʀᴇ ᴀʀᴇ `{}` ᴄᴜsᴛᴏᴍ ғɪʟᴛᴇʀs ʜᴇʀᴇ.".format(len(cust_filters)) + + +__help__ = """ +❂ /filters*:* `ʟɪꜱᴛ ᴀʟʟ ᴀᴄᴛɪᴠᴇ ғɪʟᴛᴇʀꜱ ꜱᴀᴠᴇᴅ ɪɴ ᴛʜᴇ ᴄʜᴀᴛ` +*ᴀᴅᴍɪɴ ᴏɴʟʏ:* +❂ /filter <keyword> <reply message>*:* ᴀᴅᴅ a ғɪʟᴛᴇʀ ᴛᴏ ᴛʜɪꜱ chat. ᴛʜᴇ ʙᴏᴛ ᴡɪʟʟ ɴᴏᴡ ʀᴇᴘʟʏ ᴛʜᴀᴛ ᴍᴇꜱꜱᴀɢᴇ ᴡʜᴇɴᴇᴠᴇʀ 'ᴋᴇʏᴡᴏʀᴅ +ɪꜱ ᴍᴇɴᴛɪᴏɴᴇᴅ. ɪғ ʏᴏᴜ ʀᴇᴘʟʏ ᴛᴏ ᴀ ꜱᴛɪᴄᴋᴇʀ ᴡɪᴛʜ ᴀ ᴋᴇʏᴡᴏʀᴅ, ᴛʜᴇ ʙᴏᴛ ᴡɪʟʟ ʀᴇᴘʟʏ ᴡɪᴛʜ ᴛʜᴀᴛ ꜱᴛɪᴄᴋᴇʀ. ɴᴏᴛᴇ: ᴀʟʟ ғɪʟᴛᴇʀ +ᴋᴇʏᴡᴏʀᴅꜱ ᴀʀᴇ ɪɴ ʟᴏᴡᴇʀᴄᴀꜱᴇ. ɪғ ʏᴏᴜ ᴡᴀɴᴛ ʏᴏᴜʀ ᴋᴇʏᴡᴏʀᴅ ᴛᴏ ʙᴇ ᴀ ꜱᴇɴᴛᴇɴᴄᴇꜱ, ᴜꜱᴇ ϙᴜᴏᴛᴇꜱ. ᴇɢ: /filter "hey there" ʜᴇʏ ʜᴇʟʟᴏ + ꜱᴇᴘᴀʀᴀᴛᴇ ᴅɪғғ ʀᴇᴘʟɪᴇꜱ ʙʏ `%%%` ᴛᴏ ɢᴇᴛ ʀᴀɴᴅᴏᴍ ʀᴇᴘʟɪᴇꜱ + + *ᴇxᴀᴍᴘʟᴇ:* + `/filter "filtername" + Reply 1 + %%% + Reply 2 + %%% + Reply 3` +❂ /stop <filter keyword>*:* `ꜱᴛᴏᴘ ᴛʜᴀᴛ ғɪʟᴛᴇʀ` +*ᴄʜᴀᴛ creator only:* +❂ /removeallfilters*:* `ʀᴇᴍᴏᴠᴇ ᴀʟʟ ᴄʜᴀᴛ ғɪʟᴛᴇʀꜱ ᴀᴛ ᴏɴᴄᴇ`. +*ɴᴏᴛᴇ*: ғɪʟᴛᴇʀꜱ ᴀʟꜱᴏ ꜱᴜᴘᴘᴏʀᴛ ᴍᴀʀᴋᴅᴏᴡɴ formatters like: {first}, {last} ᴇᴛᴄ.. ᴀɴᴅ ʙᴜᴛᴛᴏɴꜱ. +ᴄʜᴇᴄᴋ /markdownhelp ᴛᴏ ᴋɴᴏᴡ ᴍᴏʀᴇ! +""" +__mod_name__ = "𝙵ɪʟᴛᴇʀs" diff --git a/Exon/modules/dbcleanup.py b/Exon/modules/dbcleanup.py new file mode 100644 index 00000000..1d0d5c0d --- /dev/null +++ b/Exon/modules/dbcleanup.py @@ -0,0 +1,209 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +from time import sleep + +from telegram import Bot, InlineKeyboardButton, InlineKeyboardMarkup, Update +from telegram.error import BadRequest, Unauthorized +from telegram.ext import CallbackContext, CallbackQueryHandler, CommandHandler + +import Exon.modules.sql.global_bans_sql as gban_sql +import Exon.modules.sql.users_sql as user_sql +from Exon import DEV_USERS, OWNER_ID, dispatcher +from Exon.modules.helper_funcs.chat_status import dev_plus + + +def get_muted_chats(bot: Bot, update: Update, leave: bool = False): + chat_id = update.effective_chat.id + chats = user_sql.get_all_chats() + muted_chats, progress = 0, 0 + chat_list = [] + progress_message = None + + for chat in chats: + + if ((100 * chats.index(chat)) / len(chats)) > progress: + progress_bar = f"{progress}% ᴄᴏᴍᴘʟᴇᴛᴇᴅ ɪɴ ɢᴇᴛᴛɪɴɢ ᴍᴜᴛᴇᴅ ᴄʜᴀᴛs." + if progress_message: + try: + bot.editMessageText( + progress_bar, chat_id, progress_message.message_id + ) + except: + pass + else: + progress_message = bot.sendMessage(chat_id, progress_bar) + progress += 5 + + cid = chat.chat_id + sleep(0.1) + + try: + bot.send_chat_action(cid, "TYPING", timeout=120) + except (BadRequest, Unauthorized): + muted_chats += +1 + chat_list.append(cid) + try: + progress_message.delete() + except: + pass + + if not leave: + return muted_chats + for muted_chat in chat_list: + sleep(0.1) + try: + bot.leaveChat(muted_chat, timeout=120) + except: + pass + user_sql.rem_chat(muted_chat) + return muted_chats + + +def get_invalid_chats(update: Update, context: CallbackContext, remove: bool = False): + bot = context.bot + chat_id = update.effective_chat.id + chats = user_sql.get_all_chats() + kicked_chats, progress = 0, 0 + chat_list = [] + progress_message = None + + for chat in chats: + + if ((100 * chats.index(chat)) / len(chats)) > progress: + progress_bar = f"{progress}% ᴄᴏᴍᴘʟᴇᴛᴇᴅ ɪɴ ɢᴇᴛᴛɪɴɢ ɪɴᴠᴀʟɪᴅ ᴄʜᴀᴛs." + if progress_message: + try: + bot.editMessageText( + progress_bar, + chat_id, + progress_message.message_id, + ) + except: + pass + else: + progress_message = bot.sendMessage(chat_id, progress_bar) + progress += 5 + + cid = chat.chat_id + sleep(0.1) + try: + bot.get_chat(cid, timeout=60) + except (BadRequest, Unauthorized): + kicked_chats += 1 + chat_list.append(cid) + try: + progress_message.delete() + except: + pass + + if not remove: + return kicked_chats + for muted_chat in chat_list: + sleep(0.1) + user_sql.rem_chat(muted_chat) + return kicked_chats + + +def get_invalid_gban(update: Update, context: CallbackContext, remove: bool = False): + bot = context.bot + banned = gban_sql.get_gban_list() + ungbanned_users = 0 + ungban_list = [] + + for user in banned: + user_id = user["user_id"] + sleep(0.1) + try: + bot.get_chat(user_id) + except BadRequest: + ungbanned_users += 1 + ungban_list.append(user_id) + if not remove: + return ungbanned_users + for user_id in ungban_list: + sleep(0.1) + gban_sql.ungban_user(user_id) + return ungbanned_users + + +@dev_plus +def dbcleanup(update: Update, context: CallbackContext): + msg = update.effective_message + + msg.reply_text("ɢᴇᴛᴛɪɴɢ ɪɴᴠᴀʟɪᴅ ᴄʜᴀᴛ ᴄᴏᴜɴᴛ ...") + invalid_chat_count = get_invalid_chats(update, context) + + msg.reply_text("ɢᴇᴛᴛɪɴɢ ɪɴᴠᴀʟɪᴅ ɢʙᴀɴɴᴇᴅ ᴄᴏᴜɴᴛ ...") + invalid_gban_count = get_invalid_gban(update, context) + + reply = f"ᴛᴏᴛᴀʟ ɪɴᴠᴀʟɪᴅ ᴄʜᴀᴛs - {invalid_chat_count}\n" + reply += f"ᴛᴏᴛᴀʟ ɪɴᴠᴀʟɪᴅ ɢʙᴀɴɴᴇᴅ ᴜsᴇʀs - {invalid_gban_count}" + + buttons = [[InlineKeyboardButton("✦ ᴄʟᴇᴀɴᴜᴘ ᴅʙ ✦", callback_data="db_cleanup")]] + + update.effective_message.reply_text( + reply, + reply_markup=InlineKeyboardMarkup(buttons), + ) + + +def callback_button(update: Update, context: CallbackContext): + bot = context.bot + query = update.callback_query + message = query.message + chat_id = update.effective_chat.id + query_type = query.data + + admin_list = [OWNER_ID] + DEV_USERS + + bot.answer_callback_query(query.id) + + if query_type == "db_leave_chat" and query.from_user.id in admin_list: + bot.editMessageText("ʟᴇᴀᴠɪɴɢ ᴄʜᴀᴛs ...", chat_id, message.message_id) + chat_count = get_muted_chats(update, context, True) + bot.sendMessage(chat_id, f"ʟᴇғᴛ {chat_count} ᴄʜᴀᴛs.") + elif ( + query_type == "db_leave_chat" + or query_type == "db_cleanup" + and query.from_user.id not in admin_list + ): + query.answer("ʏᴏᴜ ᴀʀᴇ ɴᴏᴛ ᴀʟʟᴏᴡᴇᴅ ᴛᴏ ᴜsᴇ ᴛʜɪs.") + elif query_type == "db_cleanup": + bot.editMessageText("ᴄʟᴇᴀɴɪɴɢ ᴜᴘ DB ...", chat_id, message.message_id) + invalid_chat_count = get_invalid_chats(update, context, True) + invalid_gban_count = get_invalid_gban(update, context, True) + reply = f"ᴄʟᴇᴀɴᴇᴅ ᴜᴘ {invalid_chat_count} ᴄʜᴀᴛs ᴀɴᴅ {invalid_gban_count} ɢʙᴀɴɴᴇᴅ ᴜsᴇʀs ғʀᴏᴍ ᴅʙ." + + bot.sendMessage(chat_id, reply) + + +DB_CLEANUP_HANDLER = CommandHandler("dbcleanup", dbcleanup, run_async=True) +BUTTON_HANDLER = CallbackQueryHandler(callback_button, pattern="db_.*", run_async=True) + +dispatcher.add_handler(DB_CLEANUP_HANDLER) +dispatcher.add_handler(BUTTON_HANDLER) + +__mod_name__ = "DB ᴄʟᴇᴀɴᴜᴘ" +__handlers__ = [DB_CLEANUP_HANDLER, BUTTON_HANDLER] diff --git a/Exon/modules/debug.py b/Exon/modules/debug.py new file mode 100644 index 00000000..e889f948 --- /dev/null +++ b/Exon/modules/debug.py @@ -0,0 +1,86 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import datetime +import os + +from telegram import Update +from telegram.ext import CallbackContext, CommandHandler +from telethon import events + +from Exon import dispatcher, telethn +from Exon.modules.helper_funcs.chat_status import dev_plus + +DEBUG_MODE = False + + +@dev_plus +def debug(update: Update, context: CallbackContext): + global DEBUG_MODE + args = update.effective_message.text.split(None, 1) + message = update.effective_message + print(DEBUG_MODE) + if len(args) > 1: + if args[1] in ("yes", "on"): + DEBUG_MODE = True + message.reply_text("ᴅᴇʙᴜɢ ᴍᴏᴅᴇ ɪs ɴᴏᴡ ᴏɴ.") + elif args[1] in ("no", "off"): + DEBUG_MODE = False + message.reply_text("ᴅᴇʙᴜɢ ᴍᴏᴅᴇ ɪs ɴᴏᴡ ᴏғғ.") + elif DEBUG_MODE: + message.reply_text("ᴅᴇʙᴜɢ ᴍᴏᴅᴇ ɪs ᴄᴜʀʀᴇɴᴛʟʏ ᴏɴ.") + else: + message.reply_text("ᴅᴇʙᴜɢ ᴍᴏᴅᴇ is ᴄᴜʀʀᴇɴᴛʟʏ ᴏғғ.") + + +@telethn.on(events.NewMessage(pattern="[/!].*")) +async def i_do_nothing_yes(event): + global DEBUG_MODE + if DEBUG_MODE: + print(f"-{event.from_id} ({event.chat_id}) : {event.text}") + if os.path.exists("updates.txt"): + with open("updates.txt", "r") as f: + text = f.read() + with open("updates.txt", "w+") as f: + f.write(f"{text}\n-{event.from_id} ({event.chat_id}) : {event.text}") + else: + with open("updates.txt", "w+") as f: + f.write( + f"- {event.from_id} ({event.chat_id}) : {event.text} | {datetime.datetime.now()}", + ) + + +support_chat = os.getenv("SUPPORT_CHAT") +updates_channel = os.getenv("UPDATES_CHANNEL") + + +DEBUG_HANDLER = CommandHandler("debug", debug, run_async=True) + +dispatcher.add_handler(DEBUG_HANDLER) + +__mod_name__ = "Debug" + +__command_list__ = ["debug"] + +__handlers__ = [DEBUG_HANDLER] diff --git a/Exon/modules/dev.py b/Exon/modules/dev.py new file mode 100644 index 00000000..011f6f57 --- /dev/null +++ b/Exon/modules/dev.py @@ -0,0 +1,225 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import asyncio +import os +import re +import subprocess +import sys +from statistics import mean +from time import monotonic as time +from time import sleep + +from telegram import InlineKeyboardButton, InlineKeyboardMarkup, TelegramError, Update +from telegram.ext import CallbackContext, CommandHandler +from telegram.ext.callbackqueryhandler import CallbackQueryHandler +from telethon import events + +from Exon import DEV_USERS, OWNER_ID, dispatcher, telethn +from Exon.modules.helper_funcs.chat_status import dev_plus + + +def leave_cb(update: Update, context: CallbackContext): + bot = context.bot + callback = update.callback_query + if callback.from_user.id not in DEV_USERS: + callback.answer(text="ᴛʜɪs ɪsɴ'ᴛ ғᴏʀ ʏᴏᴜ", show_alert=True) + return + + match = re.match(r"leavechat_cb_\((.+?)\)", callback.data) + chat = int(match[1]) + bot.leave_chat(chat_id=chat) + callback.answer(text="ʟᴇғᴛ ᴄʜᴀᴛ") + + +@dev_plus +def allow_groups(update: Update, context: CallbackContext): + args = context.args + if not args: + state = "off" if Exon.ALLOW_CHATS else "ʟᴏᴄᴋᴅᴏᴡɴ ɪs " + "on" + update.effective_message.reply_text(f"ᴄᴜʀʀᴇɴᴛ sᴛᴀᴛᴇ: {state}") + ʀᴇᴛᴜʀɴ + if args[0].lower() in ["off", "no"]: + Exon.ALLOW_CHATS = True + elif args[0].lower() in ["yes", "on"]: + Exon.ALLOW_CHATS = False + else: + update.effective_message.reply_text("ғᴏʀᴍᴀᴛ: /lockdown Yes/No ᴏʀ Off/On") + return + update.effective_message.reply_text("ᴅᴏɴᴇ! ʟᴏᴄᴋᴅᴏᴡɴ ᴠᴀʟᴜᴇ ᴛᴏɢɢʟᴇᴅ.") + + +class Store: + def __init__(self, func): + self.func = func + self.calls = [] + self.time = time() + self.lock = asyncio.Lock() + + def average(self): + return round(mean(self.calls), 2) if self.calls else 0 + + def __repr__(self): + return f"<Store func={self.func.__name__}, average={self.average()}>" + + async def __call__(self, event): + async with self.lock: + if not self.calls: + self.calls = [0] + if time() - self.time > 1: + self.time = time() + self.calls.append(1) + else: + self.calls[-1] += 1 + await self.func(event) + + +async def nothing(event): + pass + + +messages = Store(nothing) +inline_queries = Store(nothing) +callback_queries = Store(nothing) + +telethn.add_event_handler(messages, events.NewMessage()) +telethn.add_event_handler(inline_queries, events.InlineQuery()) +telethn.add_event_handler(callback_queries, events.CallbackQuery()) + + +@telethn.on(events.NewMessage(pattern=r"/getstats", from_users=OWNER_ID)) +async def getstats(event): + await event.reply( + f"**__ᴇᴠᴇɴᴛ sᴛᴀᴛɪsᴛɪᴄs__**\n**ᴀᴠᴇʀᴀɢᴇ ᴍᴇssᴀɢᴇs:** {messages.average()}/s\n**ᴀᴠᴇʀᴀɢᴇ ᴄᴀʟʟʙᴀᴄᴋ ǫᴜᴇʀɪᴇs:** {callback_queries.average()}/s\n**ᴀᴠᴇʀᴀɢᴇ ɪɴʟɪɴᴇ ǫᴜᴇʀɪᴇs:** {inline_queries.average()}/s", + parse_mode="md", + ) + + +@dev_plus +def pip_install(update: Update, context: CallbackContext): + message = update.effective_message + args = context.args + if not args: + message.reply_text("ᴇɴᴛᴇʀ ᴀ ᴘᴀᴄᴋᴀɢᴇ ɴᴀᴍᴇ.") + return + if len(args) >= 1: + cmd = f"py -m pip install {' '.join(args)}" + process = subprocess.Popen( + cmd.split(" "), + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + shell=True, + ) + stdout, stderr = process.communicate() + reply = "" + stderr = stderr.decode() + if stdout := stdout.decode(): + reply += f"*sᴛᴅᴏᴜᴛ*\n`{stdout}`\n" + if stderr: + reply += f"*sᴛᴅᴇʀʀ*\n`{stderr}`\n" + + message.reply_text(text=reply, parse_mode=ParseMode.MARKDOWN) + + +@dev_plus +def leave(update: Update, context: CallbackContext): + bot = context.bot + if args := context.args: + chat_id = str(args[0]) + leave_msg = " ".join(args[1:]) + try: + context.bot.send_message(chat_id, leave_msg) + bot.leave_chat(int(chat_id)) + update.effective_message.reply_text("ʟᴇғᴛ ᴄʜᴀᴛ.") + except TelegramError: + update.effective_message.reply_text("ғᴀɪʟᴇᴅ ᴛᴏ ʟᴇᴀᴠᴇ ᴄʜᴀᴛ ғᴏʀ sᴏᴍᴇ ʀᴇᴀsᴏɴ.") + else: + chat = update.effective_chat + # user = update.effective_user + Exon_leave_bt = [ + [ + InlineKeyboardButton( + text="ɪ ᴀᴍ sᴜʀᴇ ᴏғ ᴛʜɪs ᴀᴄᴛɪᴏɴ.", + callback_data=f"leavechat_cb_({chat.id})", + ) + ] + ] + + update.effective_message.reply_text( + f"I'm ɢᴏɪɴɢ ᴛᴏ ʟᴇᴀᴠᴇ {chat.title}, ᴘʀᴇss ᴛʜᴇ ʙᴜᴛᴛᴏɴ ʙᴇʟᴏᴡ ᴛᴏ ᴄᴏɴғɪʀᴍ", + reply_markup=InlineKeyboardMarkup(Exon_leave_bt), + ) + + +@dev_plus +def gitpull(update: Update, context: CallbackContext): + sent_msg = update.effective_message.reply_text( + "ᴘᴜʟʟɪɴɢ ᴀʟʟ ᴄʜᴀɴɢᴇs ғʀᴏᴍ ʀᴇᴍᴏᴛᴇ ᴀɴᴅ ᴛʜᴇɴ ᴀᴛᴛᴇᴍᴘᴛɪɴɢ ᴛᴏ ʀᴇsᴛᴀʀᴛ." + ) + subprocess.Popen("git pull", stdout=subprocess.PIPE, shell=True) + + sent_msg_text = sent_msg.text + "\n\nᴄʜᴀɴɢᴇs ᴘᴜʟʟᴇᴅ...I ɢᴜᴇss.. ʀᴇsᴛᴀʀᴛɪɴɢ ɪɴ " + + for i in reversed(range(5)): + sent_msg.edit_text(sent_msg_text + str(i + 1)) + sleep(1) + + sent_msg.edit_text("ʀᴇsᴛᴀʀᴛᴇᴅ..") + + os.system("restart.bat") + os.execv("start.bat", sys.argv) + + +@dev_plus +def restart(update: Update, context: CallbackContext): + update.effective_message.reply_text("ᴇxɪᴛɪɴɢ ᴀʟʟ ᴀʟʟ ᴀɴᴅ sᴛᴀʀᴛɪɴɢ ᴀ ɴᴇᴡ ɪɴsᴛᴀɴᴄᴇ!") + process = subprocess.run("pkill python3 && python3 -m Exon", shell=True, check=True) + process.communicate() + + +PIP_INSTALL_HANDLER = CommandHandler("install", pip_install, run_async=True) +LEAVE_HANDLER = CommandHandler("leave", leave, run_async=True) +GITPULL_HANDLER = CommandHandler("gitpull", gitpull, run_async=True) +RESTART_HANDLER = CommandHandler("reboot", restart, run_async=True) +ALLOWGROUPS_HANDLER = CommandHandler("lockdown", allow_groups, run_async=True) +LEAVE_CALLBACK_HANDLER = CallbackQueryHandler( + leave_cb, pattern=r"leavechat_cb_", run_async=True +) + +dispatcher.add_handler(PIP_INSTALL_HANDLER) +dispatcher.add_handler(ALLOWGROUPS_HANDLER) +dispatcher.add_handler(LEAVE_HANDLER) +dispatcher.add_handler(GITPULL_HANDLER) +dispatcher.add_handler(RESTART_HANDLER) +dispatcher.add_handler(LEAVE_CALLBACK_HANDLER) + +__mod_name__ = "𝙳ᴇᴠ" +__handlers__ = [ + LEAVE_HANDLER, + GITPULL_HANDLER, + RESTART_HANDLER, + ALLOWGROUPS_HANDLER, + LEAVE_CALLBACK_HANDLER, + PIP_INSTALL_HANDLER, +] diff --git a/Exon/modules/disable.py b/Exon/modules/disable.py new file mode 100644 index 00000000..076d04af --- /dev/null +++ b/Exon/modules/disable.py @@ -0,0 +1,375 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import importlib +from typing import Union + +from future.utils import string_types +from telegram import ParseMode, Update +from telegram.ext import ( + CallbackContext, + CommandHandler, + Filters, + MessageHandler, + RegexHandler, +) +from telegram.utils.helpers import escape_markdown + +from Exon import dispatcher +from Exon.modules.helper_funcs.handlers import CMD_STARTERS, SpamChecker +from Exon.modules.helper_funcs.misc import is_module_loaded + +FILENAME = __name__.rsplit(".", 1)[-1] + +# If module is due to be loaded, then setup all the magical handlers +if is_module_loaded(FILENAME): + + from Exon.modules.helper_funcs.chat_status import ( + connection_status, + is_user_admin, + user_admin, + ) + from Exon.modules.sql import disable_sql as sql + + DISABLE_CMDS = [] + DISABLE_OTHER = [] + ADMIN_CMDS = [] + + class DisableAbleCommandHandler(CommandHandler): + def __init__(self, command, callback, admin_ok=False, **kwargs): + super().__init__(command, callback, **kwargs) + self.admin_ok = admin_ok + if isinstance(command, string_types): + DISABLE_CMDS.append(command) + if admin_ok: + ADMIN_CMDS.append(command) + else: + DISABLE_CMDS.extend(command) + if admin_ok: + ADMIN_CMDS.extend(command) + + def check_update(self, update): + if not isinstance(update, Update) or not update.effective_message: + return + message = update.effective_message + + if message.text and len(message.text) > 1: + fst_word = message.text.split(None, 1)[0] + if len(fst_word) > 1 and any( + fst_word.startswith(start) for start in CMD_STARTERS + ): + args = message.text.split()[1:] + command = fst_word[1:].split("@") + command.append(message.bot.username) + + if not ( + command[0].lower() in self.command + and command[1].lower() == message.bot.username.lower() + ): + return None + chat = update.effective_chat + user = update.effective_user + user_id = chat.id if user.id == 1087968824 else user.id + if SpamChecker.check_user(user_id): + return None + if filter_result := self.filters(update): + # disabled, admincmd, user admin + if sql.is_command_disabled(chat.id, command[0].lower()): + # check if command was disabled + is_disabled = command[0] in ADMIN_CMDS and is_user_admin( + chat, user.id + ) + return (args, filter_result) if is_disabled else None + return args, filter_result + return False + + class DisableAbleMessageHandler(MessageHandler): + def __init__(self, filters, callback, friendly, **kwargs): + + super().__init__(filters, callback, **kwargs) + DISABLE_OTHER.append(friendly) + self.friendly = friendly + if filters: + self.filters = Filters.update.messages & filters + else: + self.filters = Filters.update.messages + + def check_update(self, update): + + chat = update.effective_chat + message = update.effective_message + filter_result = self.filters(update) + + try: + args = message.text.split()[1:] + except: + args = [] + + if super().check_update(update): + if sql.is_command_disabled(chat.id, self.friendly): + return False + return args, filter_result + + class DisableAbleRegexHandler(RegexHandler): + def __init__(self, pattern, callback, friendly="", filters=None, **kwargs): + super().__init__(pattern, callback, filters, **kwargs) + DISABLE_OTHER.append(friendly) + self.friendly = friendly + + def check_update(self, update): + chat = update.effective_chat + if super().check_update(update): + return not sql.is_command_disabled(chat.id, self.friendly) + + @connection_status + @user_admin + def disable(update: Update, context: CallbackContext): + args = context.args + chat = update.effective_chat + if len(args) >= 1: + disable_cmd = args[0] + if disable_cmd.startswith(CMD_STARTERS): + disable_cmd = disable_cmd[1:] + + if disable_cmd in set(DISABLE_CMDS + DISABLE_OTHER): + sql.disable_command(chat.id, str(disable_cmd).lower()) + update.effective_message.reply_text( + f"ᴅɪsᴀʙʟᴇᴅ ᴛʜᴇ ᴜsᴇ ᴏғ `{disable_cmd}`", + parse_mode=ParseMode.MARKDOWN, + ) + else: + update.effective_message.reply_text("ᴛʜᴀᴛ ᴄᴏᴍᴍᴀɴᴅ ᴄᴀɴ'ᴛ ʙᴇ ᴅɪsᴀʙʟᴇᴅ") + + else: + update.effective_message.reply_text("ᴡʜᴀᴛ sʜᴏᴜʟᴅ ɪ ᴅɪsᴀʙʟᴇ?") + + @connection_status + @user_admin + def disable_module(update: Update, context: CallbackContext): + args = context.args + chat = update.effective_chat + if len(args) >= 1: + disable_module = "Exon.modules." + args[0].rsplit(".", 1)[0] + + try: + module = importlib.import_module(disable_module) + except: + update.effective_message.reply_text("Does ᴛʜᴀᴛ ᴍᴏᴅᴜʟᴇ ᴇᴠᴇɴ ᴇxɪsᴛ?") + return + + try: + command_list = module.__command_list__ + except: + update.effective_message.reply_text( + "ᴍᴏᴅᴜʟᴇ ᴅᴏᴇs ɴᴏᴛ ᴄᴏɴᴛᴀɪɴ ᴄᴏᴍᴍᴀɴᴅ ʟɪsᴛ!", + ) + return + + disabled_cmds = [] + failed_disabled_cmds = [] + + for disable_cmd in command_list: + if disable_cmd.startswith(CMD_STARTERS): + disable_cmd = disable_cmd[1:] + + if disable_cmd in set(DISABLE_CMDS + DISABLE_OTHER): + sql.disable_command(chat.id, str(disable_cmd).lower()) + disabled_cmds.append(disable_cmd) + else: + failed_disabled_cmds.append(disable_cmd) + + if disabled_cmds: + disabled_cmds_string = ", ".join(disabled_cmds) + update.effective_message.reply_text( + f"ᴅɪsᴀʙʟᴇᴅ ᴛʜᴇ ᴜsᴇs ᴏғ `{disabled_cmds_string}`", + parse_mode=ParseMode.MARKDOWN, + ) + + if failed_disabled_cmds: + failed_disabled_cmds_string = ", ".join(failed_disabled_cmds) + update.effective_message.reply_text( + f"ᴄᴏᴍᴍᴀɴᴅs `{failed_disabled_cmds_string}` ᴄᴀɴ'ᴛ ʙᴇ ᴅɪsᴀʙʟᴇᴅ", + parse_mode=ParseMode.MARKDOWN, + ) + + else: + update.effective_message.reply_text("ᴡʜᴀᴛ sʜᴏᴜʟᴅ ɪ ᴅɪsᴀʙʟᴇ?") + + @connection_status + @user_admin + def enable(update: Update, context: CallbackContext): + args = context.args + chat = update.effective_chat + if len(args) >= 1: + enable_cmd = args[0] + if enable_cmd.startswith(CMD_STARTERS): + enable_cmd = enable_cmd[1:] + + if sql.enable_command(chat.id, enable_cmd): + update.effective_message.reply_text( + f"ᴇɴᴀʙʟᴇᴅ ᴛʜᴇ ᴜsᴇ ᴏғ `{enable_cmd}`", + parse_mode=ParseMode.MARKDOWN, + ) + else: + update.effective_message.reply_text("ɪs ᴛʜᴀᴛ ᴇᴠᴇɴ ᴅɪsᴀʙʟᴇᴅ?") + + else: + update.effective_message.reply_text("ᴡʜᴀᴛ sʜᴏᴜʟᴅ I ᴇɴᴀʙʟᴇ?") + + @connection_status + @user_admin + def enable_module(update: Update, context: CallbackContext): + args = context.args + chat = update.effective_chat + + if len(args) >= 1: + enable_module = "Exon.modules." + args[0].rsplit(".", 1)[0] + + try: + module = importlib.import_module(enable_module) + except: + update.effective_message.reply_text("ᴅᴏᴇs ᴛʜᴀᴛ ᴍᴏᴅᴜʟᴇ ᴇᴠᴇɴ ᴇxɪsᴛ ?") + return + + try: + command_list = module.__command_list__ + except: + update.effective_message.reply_text( + "ᴍᴏᴅᴜʟᴇ ᴅᴏᴇs ɴᴏᴛ ᴄᴏɴᴛᴀɪɴ ᴄᴏᴍᴍᴀɴᴅ ʟɪsᴛ!", + ) + return + + enabled_cmds = [] + failed_enabled_cmds = [] + + for enable_cmd in command_list: + if enable_cmd.startswith(CMD_STARTERS): + enable_cmd = enable_cmd[1:] + + if sql.enable_command(chat.id, enable_cmd): + enabled_cmds.append(enable_cmd) + else: + failed_enabled_cmds.append(enable_cmd) + + if enabled_cmds: + enabled_cmds_string = ", ".join(enabled_cmds) + update.effective_message.reply_text( + f"ᴇɴᴀʙʟᴇᴅ ᴛʜᴇ ᴜsᴇs ᴏғ `{enabled_cmds_string}`", + parse_mode=ParseMode.MARKDOWN, + ) + + if failed_enabled_cmds: + failed_enabled_cmds_string = ", ".join(failed_enabled_cmds) + update.effective_message.reply_text( + f"ᴀʀᴇ ᴛʜᴇ ᴄᴏᴍᴍᴀɴᴅs `{failed_enabled_cmds_string}` ᴇᴠᴇɴ ᴅɪsᴀʙʟᴇᴅ?", + parse_mode=ParseMode.MARKDOWN, + ) + + else: + update.effective_message.reply_text("ᴡʜᴀᴛ sʜᴏᴜʟᴅ I ᴇɴᴀʙʟᴇ?") + + @connection_status + @user_admin + def list_cmds(update: Update, context: CallbackContext): + if DISABLE_CMDS + DISABLE_OTHER: + result = "".join( + f" - `{escape_markdown(cmd)}`\n" + for cmd in set(DISABLE_CMDS + DISABLE_OTHER) + ) + + update.effective_message.reply_text( + f"ᴛʜᴇ ғᴏʟʟᴏᴡɪɴɢ ᴄᴏᴍᴍᴀɴᴅs ᴀʀᴇ ᴛᴏɢɢʟᴇᴀʙʟᴇ:\n{result}", + parse_mode=ParseMode.MARKDOWN, + ) + else: + update.effective_message.reply_text("ɴᴏ ᴄᴏᴍᴍᴀɴᴅs ᴄᴀɴ ʙᴇ ᴅɪsᴀʙʟᴇᴅ.") + + # do not async + def build_curr_disabled(chat_id: Union[str, int]) -> str: + disabled = sql.get_all_disabled(chat_id) + if not disabled: + return "No ᴄᴏᴍᴍᴀɴᴅs ᴀʀᴇ ᴅɪsᴀʙʟᴇᴅ!" + + result = "".join(f" - `{escape_markdown(cmd)}`\n" for cmd in disabled) + return f"ᴛʜᴇ ғᴏʟʟᴏᴡɪɴɢ ᴄᴏᴍᴍᴀɴᴅs ᴀʀᴇ ᴄᴜʀʀᴇɴᴛʟʏ ʀᴇsᴛʀɪᴄᴛᴇᴅ:\n{result}" + + @connection_status + def commands(update: Update, context: CallbackContext): + chat = update.effective_chat + update.effective_message.reply_text( + build_curr_disabled(chat.id), + parse_mode=ParseMode.MARKDOWN, + ) + + def __stats__(): + return ( + f"•➥ {sql.num_disabled()} ᴅɪsᴀʙʟᴇᴅ ɪᴛᴇᴍs, ᴀᴄʀᴏss {sql.num_chats()} ᴄʜᴀᴛs." + ) + + def __migrate__(old_chat_id, new_chat_id): + sql.migrate_chat(old_chat_id, new_chat_id) + + def __chat_settings__(chat_id, user_id): + return build_curr_disabled(chat_id) + + DISABLE_HANDLER = CommandHandler("disable", disable, run_async=True) + DISABLE_MODULE_HANDLER = CommandHandler( + "disablemodule", disable_module, run_async=True + ) + ENABLE_HANDLER = CommandHandler("enable", enable, run_async=True) + ENABLE_MODULE_HANDLER = CommandHandler( + "enablemodule", enable_module, run_async=True + ) + COMMANDS_HANDLER = CommandHandler(["cmds", "disabled"], commands, run_async=True) + TOGGLE_HANDLER = CommandHandler("listcmds", list_cmds, run_async=True) + + dispatcher.add_handler(DISABLE_HANDLER) + dispatcher.add_handler(DISABLE_MODULE_HANDLER) + dispatcher.add_handler(ENABLE_HANDLER) + dispatcher.add_handler(ENABLE_MODULE_HANDLER) + dispatcher.add_handler(COMMANDS_HANDLER) + dispatcher.add_handler(TOGGLE_HANDLER) + + __help__ = """ +• /cmds*:* ᴄʜᴇᴄᴋ ᴛʜᴇ ᴄᴜʀʀᴇɴᴛ sᴛᴀᴛᴜs ᴏғ ᴅɪsᴀʙʟᴇᴅ ᴄᴏᴍᴍᴀɴᴅs + +*ᴀᴅᴍɪɴs ᴏɴʟʏ:* + +• /enable <cmd name>*:* `ᴇɴᴀʙʟᴇ ᴛʜᴀᴛ ᴄᴏᴍᴍᴀɴᴅ ` + +• /disable <cmd name>*:* `ᴅɪsᴀʙʟᴇ ᴛʜᴀᴛ ᴄᴏᴍᴍᴀɴᴅ ` + +• /enablemodule <module name>*:* `ᴇɴᴀʙʟᴇ ᴀʟʟ ᴄᴏᴍᴍᴀɴᴅs ɪɴ ᴛʜᴀᴛ ᴍᴏᴅᴜʟᴇ ` + +• /disablemodule <module name>*:* `ᴅɪsᴀʙʟᴇ ᴀʟʟ ᴄᴏᴍᴍᴀɴᴅs ɪɴ ᴛʜᴀᴛ ᴍᴏᴅᴜʟᴇ ` + +• /listcmds*:* `ɪsᴛ ᴀʟʟ ᴘᴏssɪʙʟᴇ ᴛᴏɢɢʟᴇᴀʙʟᴇ ᴄᴏᴍᴍᴀɴᴅs ` + """ + + __mod_name__ = "𝙳ɪsᴀʙʟɪɴɢ" + +else: + DisableAbleCommandHandler = CommandHandler + DisableAbleRegexHandler = RegexHandler + DisableAbleMessageHandler = MessageHandler diff --git a/Exon/modules/disasters.py b/Exon/modules/disasters.py new file mode 100644 index 00000000..16bd0c7b --- /dev/null +++ b/Exon/modules/disasters.py @@ -0,0 +1,585 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import html +import json +import os +from typing import Optional + +from telegram import ParseMode, TelegramError, Update +from telegram.ext import CallbackContext, CommandHandler +from telegram.utils.helpers import mention_html + +from Exon import DEMONS, DEV_USERS, DRAGONS, OWNER_ID, TIGERS, WOLVES, dispatcher +from Exon.modules.helper_funcs.chat_status import dev_plus, sudo_plus, whitelist_plus +from Exon.modules.helper_funcs.extraction import extract_user +from Exon.modules.log_channel import gloggable + +ELEVATED_USERS_FILE = os.path.join(os.getcwd(), "Exon/elevated_users.json") + + +def check_user_id(user_id: int, context: CallbackContext) -> Optional[str]: + bot = context.bot + if not user_id: + return "ᴛʜᴀᴛ...ɪs a ᴄʜᴀᴛ! ʙᴀᴋᴀ ᴋᴀ ᴏᴍᴀᴇ?" + + return "ᴛʜɪs ᴅᴏᴇs ɴᴏᴛ ᴡᴏʀᴋ ᴛʜᴀᴛ ᴡᴀʏ." if user_id == bot.id else None + + +@dev_plus +@gloggable +def addsudo(update: Update, context: CallbackContext) -> str: + message = update.effective_message + user = update.effective_user + chat = update.effective_chat + bot, args = context.bot, context.args + user_id = extract_user(message, args) + user_member = bot.getChat(user_id) + rt = "" + + if reply := check_user_id(user_id, bot): + message.reply_text(reply) + return "" + + with open(ELEVATED_USERS_FILE, "r") as infile: + data = json.load(infile) + + if user_id in DRAGONS: + message.reply_text("ᴀʟʀᴇᴀᴅʏ ᴏᴜʀ ʙᴇsᴛᴏ ғʀɪᴇɴᴅᴏ :3") + return "" + + if user_id in DEMONS: + rt += "." + data["supports"].remove(user_id) + DEMONS.remove(user_id) + + if user_id in WOLVES: + rt += "ᴡᴇ ᴀʀᴇ ʙᴇsᴛ ғʀɪᴇɴᴅs ɴᴏᴡ 🌸" + data["whitelists"].remove(user_id) + WOLVES.remove(user_id) + + data["sudos"].append(user_id) + DRAGONS.append(user_id) + + with open(ELEVATED_USERS_FILE, "w") as outfile: + json.dump(data, outfile, indent=4) + + update.effective_message.reply_text( + (rt + f"\nsᴜᴄᴄᴇssғᴜʟʟʏ ᴍᴀᴅᴇ ʙᴇsᴛ ғʀɪᴇɴᴅsʜɪᴘ ᴡɪᴛʜ {user_member.first_name} !") + ) + + log_message = ( + f"#sᴜᴅᴏ\n" + f"<b>ᴀᴅᴍɪɴ:</b> {mention_html(user.id, html.escape(user.first_name))}\n" + f"<b>ᴜsᴇʀ:</b> {mention_html(user_member.id, html.escape(user_member.first_name))}" + ) + + if chat.type != "private": + log_message = f"<b>{html.escape(chat.title)}:</b>\n{log_message}" + + return log_message + + +@sudo_plus +@gloggable +def addsupport( + update: Update, + context: CallbackContext, +) -> str: + message = update.effective_message + user = update.effective_user + chat = update.effective_chat + bot, args = context.bot, context.args + user_id = extract_user(message, args) + user_member = bot.getChat(user_id) + rt = "" + + if reply := check_user_id(user_id, bot): + message.reply_text(reply) + return "" + + with open(ELEVATED_USERS_FILE, "r") as infile: + data = json.load(infile) + + if user_id in DRAGONS: + rt += "I ᴅᴏɴᴛ ᴡᴀɴᴛ ᴛᴏ ʙᴇ ʏᴏᴜʀ ʙᴇsᴛ ғʀɪᴇɴᴅ 🥲" + data["sudos"].remove(user_id) + DRAGONS.remove(user_id) + + if user_id in DEMONS: + message.reply_text("ᴡᴇ ᴀʀᴇ ᴀʟʀᴇᴀᴅʏ ғʀɪᴇɴᴅs.") + return "" + + if user_id in WOLVES: + rt += "We are friends now :)" + data["whitelists"].remove(user_id) + WOLVES.remove(user_id) + + data["supports"].append(user_id) + DEMONS.append(user_id) + + with open(ELEVATED_USERS_FILE, "w") as outfile: + json.dump(data, outfile, indent=4) + + update.effective_message.reply_text( + f"{rt}\n{user_member.first_name}, ᴡᴇ ᴄᴀɴ ʙᴇ ғʀɪᴇɴᴅs ;)" + ) + + log_message = ( + f"#sᴜᴘᴘᴏʀᴛ\n" + f"<b>ᴀᴅᴍɪɴ:</b> {mention_html(user.id, html.escape(user.first_name))}\n" + f"<b>ᴜsᴇʀ:</b> {mention_html(user_member.id, html.escape(user_member.first_name))}" + ) + + if chat.type != "private": + log_message = f"<b>{html.escape(chat.title)}:</b>\n{log_message}" + + return log_message + + +@sudo_plus +@gloggable +def addwhitelist(update: Update, context: CallbackContext) -> str: + message = update.effective_message + user = update.effective_user + chat = update.effective_chat + bot, args = context.bot, context.args + user_id = extract_user(message, args) + user_member = bot.getChat(user_id) + rt = "" + + if reply := check_user_id(user_id, bot): + message.reply_text(reply) + return "" + + with open(ELEVATED_USERS_FILE, "r") as infile: + data = json.load(infile) + + if user_id in DRAGONS: + rt += "ᴛʜɪs ᴍᴇᴍʙᴇʀ ɪs ᴏᴜʀ ʙᴇsᴛғʀɪᴇɴᴅ ʙᴜᴛ ɪ ᴡɪʟʟ ʟɪᴋᴇ ᴡʜᴇɴ ᴏᴜʀ ʙᴇsᴛғʀɪᴇɴᴅ ʙᴇᴄᴏᴍᴇs ᴀ ɪɢɴɪᴛᴇ " + data["sudos"].remove(user_id) + DRAGONS.remove(user_id) + + if user_id in DEMONS: + rt += "ʏᴏᴜ ᴀʀᴇ ᴏᴜʀ ғʀɪᴇɴᴅ, ʙᴜᴛ it's ғᴏʀ ʏᴏᴜʀ ᴏᴡɴ ɢᴏᴏᴅ ɪғ ʏᴏᴜ ʙᴇᴄᴏᴍᴇ ᴀ ɪɢɴɪᴛᴇ ɪɴsᴛᴇᴀᴅ." + data["supports"].remove(user_id) + DEMONS.remove(user_id) + + if user_id in WOLVES: + message.reply_text("ᴛʜɪs ᴜsᴇʀ ɪs ᴀʟʀᴇᴀᴅʏ ᴀ ᴛʀᴜᴇ ᴇxᴏɴ") + return "" + + data["whitelists"].append(user_id) + WOLVES.append(user_id) + + with open(ELEVATED_USERS_FILE, "w") as outfile: + json.dump(data, outfile, indent=4) + + update.effective_message.reply_text( + f"{rt}\nsᴜᴄᴄᴇssғᴜʟʟʏ ᴘʀᴏᴍᴏᴛᴇᴅ {user_member.first_name} ᴛᴏ ᴀ ʀᴀɴᴋᴇᴅ EXON!" + ) + + log_message = ( + f"#ᴡʜɪᴛᴇʟɪsᴛ\n" + f"<b>ᴀᴅᴍɪɴ:</b> {mention_html(user.id, html.escape(user.first_name))} \n" + f"<b>ᴜsᴇʀ:</b> {mention_html(user_member.id, html.escape(user_member.first_name))}" + ) + + if chat.type != "private": + log_message = f"<b>{html.escape(chat.title)}:</b>\n{log_message}" + + return log_message + + +@sudo_plus +@gloggable +def addtiger(update: Update, context: CallbackContext) -> str: + message = update.effective_message + user = update.effective_user + chat = update.effective_chat + bot, args = context.bot, context.args + user_id = extract_user(message, args) + user_member = bot.getChat(user_id) + rt = "" + + if reply := check_user_id(user_id, bot): + message.reply_text(reply) + return "" + + with open(ELEVATED_USERS_FILE, "r") as infile: + data = json.load(infile) + + if user_id in DRAGONS: + rt += "ʏᴏᴜ ᴡᴇʀᴇ ᴏᴜʀ ʙᴇsᴛᴏ ғʀɪᴇɴᴅᴏ, ʙᴜᴛ ɴᴏᴡ ʏᴏᴜ ᴀʀᴇ ᴊᴜsᴛ ᴀ ᴄʟᴀssᴍᴀᴛᴇ ᴛᴏ ᴜs ;(" + data["sudos"].remove(user_id) + DRAGONS.remove(user_id) + + if user_id in DEMONS: + rt += "Let's become classmates instead." + data["supports"].remove(user_id) + DEMONS.remove(user_id) + + if user_id in WOLVES: + rt += "ᴛʜɪs ᴜsᴇʀ ɪs ᴀʟʀᴇᴀᴅʏ a ᴇxᴏɴ, ᴡᴇ ᴄᴀɴ ʙᴇ ᴄʟᴀssᴍᴀᴛᴇs ᴀs ᴡᴇʟʟ.." + data["whitelists"].remove(user_id) + WOLVES.remove(user_id) + + if user_id in TIGERS: + message.reply_text("ᴛʜɪs ᴜsᴇʀ ɪs ᴀʟʀᴇᴀᴅʏ ᴏᴜʀ ᴄʟᴀssᴍᴀᴛᴇ.") + return "" + + data["tigers"].append(user_id) + TIGERS.append(user_id) + + with open(ELEVATED_USERS_FILE, "w") as outfile: + json.dump(data, outfile, indent=4) + + update.effective_message.reply_text( + f"{rt}\nʟᴇᴛ's ᴡᴇʟᴄᴏᴍᴇ ᴏᴜʀ ɴᴇᴡ ᴄʟᴀssᴍᴀᴛᴇ, {user_member.first_name}!" + ) + + log_message = ( + f"#sᴄᴏᴜᴛ\n" + f"<b>ᴀᴅᴍɪɴ:</b> {mention_html(user.id, html.escape(user.first_name))} \n" + f"<b>ᴜsᴇʀ:</b> {mention_html(user_member.id, html.escape(user_member.first_name))}" + ) + + if chat.type != "private": + log_message = f"<b>{html.escape(chat.title)}:</b>\n{log_message}" + + return log_message + + +@dev_plus +@gloggable +def removesudo(update: Update, context: CallbackContext) -> str: + message = update.effective_message + user = update.effective_user + chat = update.effective_chat + bot, args = context.bot, context.args + user_id = extract_user(message, args) + user_member = bot.getChat(user_id) + + if reply := check_user_id(user_id, bot): + message.reply_text(reply) + return "" + + with open(ELEVATED_USERS_FILE, "r") as infile: + data = json.load(infile) + + if user_id in DRAGONS: + message.reply_text("ᴡᴇ ᴀʀᴇ ɴᴏ ᴍᴏʀᴇ ʙᴇsᴛ ғʀɪᴇɴᴅs ʜᴍᴘʜ!") + DRAGONS.remove(user_id) + data["sudos"].remove(user_id) + + with open(ELEVATED_USERS_FILE, "w") as outfile: + json.dump(data, outfile, indent=4) + + log_message = ( + f"#ᴜɴsᴜᴅᴏ\n" + f"<b>ᴀᴅᴍɪɴ:</b> {mention_html(user.id, html.escape(user.first_name))}\n" + f"<b>ᴜsᴇʀ:</b> {mention_html(user_member.id, html.escape(user_member.first_name))}" + ) + + if chat.type != "private": + log_message = f"<b>{html.escape(chat.title)}:</b>\n" + log_message + + return log_message + message.reply_text( + "ᴛʜɪs ᴜsᴇʀ ɪs ɴᴏᴛ ᴀ ᴏᴜʀ ʙᴇsᴛᴏ ғʀɪᴇɴᴅᴏ, ʏᴏᴜ ᴍᴜsᴛ ʜᴀᴠᴇ ᴍɪsᴜɴᴅᴇʀsᴛᴏᴏᴅ sᴇɴᴘᴀɪ!" + ) + return "" + + +@sudo_plus +@gloggable +def removesupport(update: Update, context: CallbackContext) -> str: + message = update.effective_message + user = update.effective_user + chat = update.effective_chat + bot, args = context.bot, context.args + user_id = extract_user(message, args) + user_member = bot.getChat(user_id) + + if reply := check_user_id(user_id, bot): + message.reply_text(reply) + return "" + + with open(ELEVATED_USERS_FILE, "r") as infile: + data = json.load(infile) + + if user_id in DEMONS: + message.reply_text("ᴏᴜʀ ғʀɪᴇɴᴅsʜɪᴘ ʜᴀs ʙᴇᴇɴ ʙʀᴏᴋᴇɴ 💔") + DEMONS.remove(user_id) + data["supports"].remove(user_id) + + with open(ELEVATED_USERS_FILE, "w") as outfile: + json.dump(data, outfile, indent=4) + + log_message = ( + f"#ᴜɴsᴜᴘᴘᴏʀᴛ\n" + f"<b>ᴀᴅᴍɪɴ:</b> {mention_html(user.id, html.escape(user.first_name))}\n" + f"<b>ᴜsᴇʀ:</b> {mention_html(user_member.id, html.escape(user_member.first_name))}" + ) + + if chat.type != "private": + log_message = f"<b>{html.escape(chat.title)}:</b>\n{log_message}" + + return log_message + message.reply_text("ᴛʜɪs ᴜsᴇʀ ɪs ɴᴏᴛ ᴏᴜʀ ғʀɪᴇɴᴅ, ʙᴀᴋᴀ ᴏɴɪᴄʜᴀɴ!") + return "" + + +@sudo_plus +@gloggable +def removewhitelist(update: Update, context: CallbackContext) -> str: + message = update.effective_message + user = update.effective_user + chat = update.effective_chat + bot, args = context.bot, context.args + user_id = extract_user(message, args) + user_member = bot.getChat(user_id) + + if reply := check_user_id(user_id, bot): + message.reply_text(reply) + return "" + + with open(ELEVATED_USERS_FILE, "r") as infile: + data = json.load(infile) + + if user_id in WOLVES: + message.reply_text("ᴅᴇᴍᴏᴛɪɴɢ ᴛᴏ ɴᴏʀᴍᴀʟ ᴄɪᴛɪᴢᴇɴ") + WOLVES.remove(user_id) + data["whitelists"].remove(user_id) + + with open(ELEVATED_USERS_FILE, "w") as outfile: + json.dump(data, outfile, indent=4) + + log_message = ( + f"#ᴜɴᴡʜɪᴛᴇʟɪsᴛ\n" + f"<b>ᴀᴅᴍɪɴ:</b> {mention_html(user.id, html.escape(user.first_name))}\n" + f"<b>ᴜsᴇʀ:</b> {mention_html(user_member.id, html.escape(user_member.first_name))}" + ) + + if chat.type != "private": + log_message = f"<b>{html.escape(chat.title)}:</b>\n{log_message}" + + return log_message + message.reply_text("ᴛʜɪs ᴜsᴇʀ ɪs ɴᴏᴛ ᴀ ᴇxᴏɴ!") + return "" + + +@sudo_plus +@gloggable +def removetiger(update: Update, context: CallbackContext) -> str: + message = update.effective_message + user = update.effective_user + chat = update.effective_chat + bot, args = context.bot, context.args + user_id = extract_user(message, args) + user_member = bot.getChat(user_id) + + if reply := check_user_id(user_id, bot): + message.reply_text(reply) + return "" + + with open(ELEVATED_USERS_FILE, "r") as infile: + data = json.load(infile) + + if user_id in TIGERS: + message.reply_text("ᴅᴇᴍᴏᴛɪɴɢ ᴛᴏ ɴᴏʀᴍᴀʟ ᴄɪᴛɪᴢᴇɴ") + TIGERS.remove(user_id) + data["Tigers"].remove(user_id) + + with open(ELEVATED_USERS_FILE, "w") as outfile: + json.dump(data, outfile, indent=4) + + log_message = ( + f"#ᴜɴsᴄᴏᴜᴛ\n" + f"<b>ᴀᴅᴍɪɴ:</b> {mention_html(user.id, html.escape(user.first_name))}\n" + f"<b>ᴜsᴇʀ:</b> {mention_html(user_member.id, html.escape(user_member.first_name))}" + ) + + if chat.type != "private": + log_message = f"<b>{html.escape(chat.title)}:</b>\n{log_message}" + + return log_message + message.reply_text("ᴛʜɪs ᴜsᴇʀ ɪs ɴᴏᴛ ᴏᴜʀ ᴄʟᴀssᴍᴀᴛᴇ!") + return "" + + +@whitelist_plus +def whitelistlist(update: Update, context: CallbackContext): + reply = "<b>ᴇxᴏɴ:</b>\n\n" + m = update.effective_message.reply_text( + "<code>ɢᴀᴛʜᴇʀɪɴɢ ɪɴᴛᴇʟ ғʀᴏᴍ ᴇxᴏɴ..</code>", + parse_mode=ParseMode.HTML, + ) + bot = context.bot + for each_user in WOLVES: + user_id = int(each_user) + try: + user = bot.get_chat(user_id) + + reply += f"• {mention_html(user_id, html.escape(user.first_name))}\n" + except TelegramError: + pass + m.edit_text(reply, parse_mode=ParseMode.HTML) + + +@whitelist_plus +def tigerlist(update: Update, context: CallbackContext): + reply = "<b>Classmates:</b>\n\n" + m = update.effective_message.reply_text( + "<code>ɢᴀᴛʜᴇʀɪɴɢ ɪɴᴛᴇʟ ғʀᴏᴍ ᴇxᴏɴ ɪǫ.</code>", + parse_mode=ParseMode.HTML, + ) + bot = context.bot + for each_user in TIGERS: + user_id = int(each_user) + try: + user = bot.get_chat(user_id) + reply += f"• {mention_html(user_id, html.escape(user.first_name))}\n" + except TelegramError: + pass + m.edit_text(reply, parse_mode=ParseMode.HTML) + + +@whitelist_plus +def supportlist(update: Update, context: CallbackContext): + bot = context.bot + m = update.effective_message.reply_text( + "<code>ɢᴀᴛʜᴇʀɪɴɢ ɪɴᴛᴇʟ ғʀᴏᴍ .</code>", + parse_mode=ParseMode.HTML, + ) + reply = "<b>ғʀɪᴇɴᴅs:</b>\n\n" + for each_user in DEMONS: + user_id = int(each_user) + try: + user = bot.get_chat(user_id) + reply += f"• {mention_html(user_id, html.escape(user.first_name))}\n" + except TelegramError: + pass + m.edit_text(reply, parse_mode=ParseMode.HTML) + + +@whitelist_plus +def sudolist(update: Update, context: CallbackContext): + bot = context.bot + m = update.effective_message.reply_text( + "<code>ɢᴀᴛʜᴇʀɪɴɢ ɪɴᴛᴇʟ ғʀᴏᴍ ᴇxᴏɴ ʜǫ.</code>", + parse_mode=ParseMode.HTML, + ) + true_sudo = list(set(DRAGONS) - set(DEV_USERS)) + reply = "<b>ʙᴇsᴛᴏ ғʀɪᴇɴᴅᴏs:</b>\n\n" + for each_user in true_sudo: + user_id = int(each_user) + try: + user = bot.get_chat(user_id) + reply += f"• {mention_html(user_id, html.escape(user.first_name))}\n" + except TelegramError: + pass + m.edit_text(reply, parse_mode=ParseMode.HTML) + + +@whitelist_plus +def devlist(update: Update, context: CallbackContext): + bot = context.bot + m = update.effective_message.reply_text( + "<code>ɢᴀᴛʜᴇʀɪɴɢ ɪɴᴛᴇʟ ғʀᴏᴍ ᴀʙɪsʜɴᴏɪ HQ..</code>", + parse_mode=ParseMode.HTML, + ) + true_dev = list(set(DEV_USERS) - {OWNER_ID}) + reply = "<b>ғᴀᴍɪʟʏ ᴍᴇᴍʙᴇʀs:</b>\n\n" + for each_user in true_dev: + user_id = int(each_user) + try: + user = bot.get_chat(user_id) + reply += f"• {mention_html(user_id, html.escape(user.first_name))}\n" + except TelegramError: + pass + m.edit_text(reply, parse_mode=ParseMode.HTML) + + +SUDO_HANDLER = CommandHandler(("addsudo", "addbestfriend"), addsudo, run_async=True) +SUPPORT_HANDLER = CommandHandler( + ("addsupport", "addfriend"), addsupport, run_async=True +) +TIGER_HANDLER = CommandHandler(("addclassmate"), addtiger) +WHITELIST_HANDLER = CommandHandler( + ("EXON", "addwhitelist"), addwhitelist, run_async=True +) +UNSUDO_HANDLER = CommandHandler( + ("removesudo", "rmbestfriend"), removesudo, run_async=True +) +UNSUPPORT_HANDLER = CommandHandler( + ("removesupport", "rmfriend"), removesupport, run_async=True +) +UNTIGER_HANDLER = CommandHandler(("rmclassmate"), removetiger) +UNWHITELIST_HANDLER = CommandHandler( + ("removewhitelist", "rmIGNITE"), removewhitelist, run_async=True +) +WHITELISTLIST_HANDLER = CommandHandler( + ["whitelistlist", "EXONS"], whitelistlist, run_async=True +) +TIGERLIST_HANDLER = CommandHandler(["classmates"], tigerlist, run_async=True) +SUPPORTLIST_HANDLER = CommandHandler( + ["supportlist", "friends"], supportlist, run_async=True +) +SUDOLIST_HANDLER = CommandHandler(["sudolist", "bestfriends"], sudolist, run_async=True) +DEVLIST_HANDLER = CommandHandler(["devlist", "devs"], devlist, run_async=True) + + +dispatcher.add_handler(SUDO_HANDLER) +dispatcher.add_handler(SUPPORT_HANDLER) +dispatcher.add_handler(TIGER_HANDLER) +dispatcher.add_handler(WHITELIST_HANDLER) +dispatcher.add_handler(UNSUDO_HANDLER) +dispatcher.add_handler(UNSUPPORT_HANDLER) +dispatcher.add_handler(UNTIGER_HANDLER) +dispatcher.add_handler(UNWHITELIST_HANDLER) +dispatcher.add_handler(WHITELISTLIST_HANDLER) +dispatcher.add_handler(TIGERLIST_HANDLER) +dispatcher.add_handler(SUPPORTLIST_HANDLER) +dispatcher.add_handler(SUDOLIST_HANDLER) +dispatcher.add_handler(DEVLIST_HANDLER) + + +__mod_name__ = "Bot Owner" + +__handlers__ = [ + SUDO_HANDLER, + SUPPORT_HANDLER, + TIGER_HANDLER, + WHITELIST_HANDLER, + UNSUDO_HANDLER, + UNSUPPORT_HANDLER, + UNTIGER_HANDLER, + UNWHITELIST_HANDLER, + WHITELISTLIST_HANDLER, + TIGERLIST_HANDLER, + SUPPORTLIST_HANDLER, + SUDOLIST_HANDLER, + DEVLIST_HANDLER, +] diff --git a/Exon/modules/error_handling.py b/Exon/modules/error_handling.py new file mode 100644 index 00000000..307943d4 --- /dev/null +++ b/Exon/modules/error_handling.py @@ -0,0 +1,140 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import html +import io +import random +import sys +import traceback + +import pretty_errors +import requests +from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update +from telegram.ext import CallbackContext, CommandHandler + +from Exon import DEV_USERS, ERROR_LOGS, dispatcher + +pretty_errors.mono() + + +class ErrorsDict(dict): + "ᴀ ᴄᴜsᴛᴏᴍ ᴅɪᴄᴛ to sᴛᴏʀᴇ ᴇʀʀᴏʀs ᴀɴᴅ ᴛʜᴇɪʀ ᴄᴏᴜɴᴛ" + + def __init__(self, *args, **kwargs): + self.raw = [] + super().__init__(*args, **kwargs) + + def __contains__(self, error): + self.raw.append(error) + error.identifier = "".join(random.choices("ABCDEFGHIJKLMNOPQRSTUVWXYZ", k=5)) + for e in self: + if type(e) is type(error) and e.args == error.args: + self[e] += 1 + return True + self[error] = 0 + return False + + def __len__(self): + return len(self.raw) + + +errors = ErrorsDict() + + +def error_callback(update: Update, context: CallbackContext): + if not update: + return + if context.error in errors: + return + try: + stringio = io.StringIO() + pretty_errors.output_stderr = stringio + output = pretty_errors.excepthook( + type(context.error), + context.error, + context.error.__traceback__, + ) + pretty_errors.output_stderr = sys.stderr + pretty_error = stringio.getvalue() + stringio.close() + except: + pretty_error = "ғᴀɪʟᴇᴅ ᴛᴏ ᴄʀᴇᴀᴛᴇ ᴘʀᴇᴛᴛʏ ᴇʀʀᴏʀ." + tb_list = traceback.format_exception( + None, + context.error, + context.error.__traceback__, + ) + tb = "".join(tb_list) + pretty_message = f'{pretty_error}\n-------------------------------------------------------------------------------\nAn exception was raised while handling an update\nUser: {update.effective_user.id}\nChat: {update.effective_chat.title if update.effective_chat else ""} {update.effective_chat.id if update.effective_chat else ""}\nCallback data: {update.callback_query.data if update.callback_query else "None"}\nMessage: {update.effective_message.text if update.effective_message else "No message"}\n\nFull Traceback: {tb}' + + key = requests.post( + "https://hastebin.com/documents", + data=pretty_message.encode("UTF-8"), + ).json() + e = html.escape(f"{context.error}") + if not key.get("key"): + with open("error.txt", "w+") as f: + f.write(pretty_message) + context.bot.send_document( + ERROR_LOGS, + open("error.txt", "rb"), + caption=f"#{context.error.identifier}\n<b>An unknown error occured:</b>\n<code>{e}</code>", + parse_mode="html", + ) + return + key = key.get("key") + url = f"https://hastebin.com/{key}" + context.bot.send_message( + ERROR_LOGS, + text=f"#{context.error.identifier}\n<b>An unknown error occured:</b>\n<code>{e}</code>", + reply_markup=InlineKeyboardMarkup( + [[InlineKeyboardButton("ʜᴀsᴛᴇʙɪɴ", url=url)]], + ), + parse_mode="html", + ) + + +def list_errors(update: Update, context: CallbackContext): + if update.effective_user.id not in DEV_USERS: + return + e = dict(sorted(errors.items(), key=lambda item: item[1], reverse=True)) + msg = "<b>ᴇʀʀᴏʀs ʟɪsᴛ:</b>\n" + for x, value in e.items(): + msg += f"• <code>{x}:</code> <b>{value}</b> #{x.identifier}\n" + msg += f"{len(errors)} ʜᴀᴠᴇ ᴏᴄᴄᴜʀʀᴇᴅ sɪɴᴄᴇ sᴛᴀʀᴛᴜᴘ." + if len(msg) > 4096: + with open("errors_msg.txt", "w+") as f: + f.write(msg) + context.bot.send_document( + update.effective_chat.id, + open("errors_msg.txt", "rb"), + caption="ᴛᴏᴏ ᴍᴀɴʏ ᴇʀʀᴏʀs ʜᴀᴠᴇ ᴏᴄᴄᴜʀᴇᴅ..", + parse_mode="html", + ) + return + update.effective_message.reply_text(msg, parse_mode="html") + + +dispatcher.add_error_handler(error_callback) +dispatcher.add_handler(CommandHandler("errors", list_errors)) diff --git a/Exon/modules/errors.py b/Exon/modules/errors.py new file mode 100644 index 00000000..a188231a --- /dev/null +++ b/Exon/modules/errors.py @@ -0,0 +1,148 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import html +import io +import random +import sys +import traceback + +import pretty_errors +import requests +from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update +from telegram.ext import CallbackContext, CommandHandler + +from Exon import DEV_USERS +from Exon import ERROR_LOGS as JOIN_LOGGER +from Exon import dispatcher + +pretty_errors.mono() + + +class ErrorsDict(dict): + """A custom dict to store errors and their count""" + + def __init__(self, *args, **kwargs): + self.raw = [] + super().__init__(*args, **kwargs) + + def __contains__(self, error): + self.raw.append(error) + error.identifier = "".join(random.choices("ABCDEFGHIJKLMNOPQRSTUVWXYZ", k=5)) + for e in self: + if type(e) is type(error) and e.args == error.args: + self[e] += 1 + return True + self[error] = 0 + return False + + def __len__(self): + return len(self.raw) + + +errors = ErrorsDict() + + +def error_callback(update: Update, context: CallbackContext): + if not update: + return + if context.error not in errors: + try: + stringio = io.StringIO() + pretty_errors.output_stderr = stringio + pretty_errors.excepthook( + type(context.error), + context.error, + context.error.__traceback__, + ) + pretty_errors.output_stderr = sys.stderr + pretty_error = stringio.getvalue() + stringio.close() + except: + pretty_error = "ғᴀɪʟᴇᴅ ᴛᴏ ᴄʀᴇᴀᴛᴇ ᴘʀᴇᴛᴛʏ ᴇʀʀᴏʀ." + tb_list = traceback.format_exception( + None, + context.error, + context.error.__traceback__, + ) + tb = "".join(tb_list) + pretty_message = f'{pretty_error}\n-------------------------------------------------------------------------------\nAn exception was raised while handling an update\nUser: {update.effective_user.id}\nChat: {update.effective_chat.title if update.effective_chat else ""} {update.effective_chat.id if update.effective_chat else ""}\nCallback data: {update.callback_query.data if update.callback_query else "None"}\nMessage: {update.effective_message.text if update.effective_message else "No message"}\n\nFull Traceback: {tb}' + + extension = "txt" + url = "https://spaceb.in/api/v1/documents/" + try: + response = requests.post( + url, data={"content": pretty_message, "extension": extension} + ) + except Exception as e: + return {"error": str(e)} + response = response.json() + e = html.escape(f"{context.error}") + if not response: + with open("error.txt", "w+") as f: + f.write(pretty_message) + context.bot.send_document( + JOIN_LOGGER, + open("error.txt", "rb"), + caption=f"#{context.error.identifier}\n<b>Onichan some errors are happening:" + f"</b>\n<code>{e}</code>", + parse_mode="html", + ) + return + + url = f"https://spaceb.in/{response['payload']['id']}" + context.bot.send_message( + JOIN_LOGGER, + text=f"#{context.error.identifier}\n<b>Onichan some errors are happening!:" + f"</b>\n<code>{e}</code>", + reply_markup=InlineKeyboardMarkup( + [[InlineKeyboardButton("sᴇᴇ ᴇʀʀᴏʀs", url=url)]], + ), + parse_mode="html", + ) + + +def list_errors(update: Update, context: CallbackContext): + if update.effective_user.id not in DEV_USERS: + return + e = dict(sorted(errors.items(), key=lambda item: item[1], reverse=True)) + msg = "<b>Errors List:</b>\n" + for x, value in e.items(): + msg += f"• <code>{x}:</code> <b>{value}</b> #{x.identifier}\n" + msg += f"{len(errors)} have occurred since startup." + if len(msg) > 4096: + with open("errors_msg.txt", "w+") as f: + f.write(msg) + context.bot.send_document( + update.effective_chat.id, + open("errors_msg.txt", "rb"), + caption="Too many errors have occured..", + parse_mode="html", + ) + return + update.effective_message.reply_text(msg, parse_mode="html") + + +dispatcher.add_error_handler(error_callback) +dispatcher.add_handler(CommandHandler("errors", list_errors)) diff --git a/Exon/modules/eval.py b/Exon/modules/eval.py new file mode 100644 index 00000000..ca0bb2f5 --- /dev/null +++ b/Exon/modules/eval.py @@ -0,0 +1,160 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import ast +import io +import os + +# Common imports for eval +import textwrap +import traceback +from contextlib import redirect_stdout + +from telegram import ParseMode, Update +from telegram.ext import CallbackContext, CommandHandler + +from Exon import LOGGER, dispatcher +from Exon.modules.helper_funcs.chat_status import dev_plus + +namespaces = {} + + +def namespace_of(chat, update, bot): + if chat not in namespaces: + namespaces[chat] = { + "__builtins__": globals()["__builtins__"], + "bot": bot, + "effective_message": update.effective_message, + "effective_user": update.effective_user, + "effective_chat": update.effective_chat, + "update": update, + } + + return namespaces[chat] + + +def log_input(update): + user = update.effective_user.id + chat = update.effective_chat.id + LOGGER.info(f"ɪɴ: {update.effective_message.text} (user={user}, chat={chat})") + + +def send(msg, bot, update): + if len(str(msg)) > 2000: + with io.BytesIO(str.encode(msg)) as out_file: + out_file.name = "output.txt" + bot.send_document(chat_id=update.effective_chat.id, document=out_file) + else: + LOGGER.info(f"ᴏᴜᴛ: '{msg}'") + bot.send_message( + chat_id=update.effective_chat.id, + text=f"`{msg}`", + parse_mode=ParseMode.MARKDOWN, + ) + + +@dev_plus +def evaluate(update: Update, context: CallbackContext): + bot = context.bot + send(do(eval, bot, update), bot, update) + + +@dev_plus +def execute(update: Update, context: CallbackContext): + bot = context.bot + send(do(exec, bot, update), bot, update) + + +def cleanup_code(code): + if code.startswith("```") and code.endswith("```"): + return "\n".join(code.split("\n")[1:-1]) + return code.strip("` \n") + + +def do(func, bot, update): + log_input(update) + content = update.message.text.split(" ", 1)[-1] + body = cleanup_code(content) + env = namespace_of(update.message.chat_id, update, bot) + + os.chdir(os.getcwd()) + with open( + os.path.join(os.getcwd(), "Exon/modules/helper_funcs/temp.txt"), + "w", + ) as temp: + temp.write(body) + + stdout = io.StringIO() + + to_compile = f'def func():\n{textwrap.indent(body, " ")}' + + try: + exec(to_compile, env) + except Exception as e: + return f"{e.__class__.__name__}: {e}" + + func = env["func"] + + try: + with redirect_stdout(stdout): + func_return = func() + except Exception: + value = stdout.getvalue() + return f"{value}{traceback.format_exc()}" + else: + value = stdout.getvalue() + result = None + if func_return is None: + if value: + result = f"{value}" + else: + try: + result = f"{repr(ast.literal_eval(body, env))}" + except: + pass + else: + result = f"{value}{func_return}" + if result: + return result + + +@dev_plus +def clear(update: Update, context: CallbackContext): + bot = context.bot + log_input(update) + global namespaces + if update.message.chat_id in namespaces: + del namespaces[update.message.chat_id] + send("Cleared locals.", bot, update) + + +EVAL_HANDLER = CommandHandler(("e", "ev", "eva", "eval"), evaluate, run_async=True) +EXEC_HANDLER = CommandHandler(("x", "ex", "exe", "exec", "py"), execute, run_async=True) +CLEAR_HANDLER = CommandHandler("clearlocals", clear, run_async=True) + +dispatcher.add_handler(EVAL_HANDLER) +dispatcher.add_handler(EXEC_HANDLER) +dispatcher.add_handler(CLEAR_HANDLER) + +__mod_name__ = "ᴇᴠᴀʟ-ᴍᴏᴅᴜʟᴇ" diff --git a/Exon/modules/extras.py b/Exon/modules/extras.py new file mode 100644 index 00000000..8c44296d --- /dev/null +++ b/Exon/modules/extras.py @@ -0,0 +1,208 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import asyncio +import os + +import jikanpy +from bs4 import BeautifulSoup +from markdown import markdown +from telethon.sync import events + +from Exon import DEV_USERS, telethn + + +def md_to_text(md): + html = markdown(md) + soup = BeautifulSoup(html, features="html.parser") + return soup.get_text() + + +async def reply_id(event): + reply_to_id = None + if event.sender_id in DEV_USERS: + reply_to_id = event.id + if event.reply_to_msg_id: + reply_to_id = event.reply_to_msg_id + return reply_to_id + + +async def edit_or_reply( + event, + text, + parse_mode=None, + link_preview=None, + file_name=None, + aslink=False, + deflink=False, + noformat=False, + linktext=None, + caption=None, +): # sourcery no-metrics + link_preview = link_preview or False + reply_to = await event.get_reply_message() + if len(text) < 4096 and not deflink: + parse_mode = parse_mode or "md" + if event.sender_id in DEV_USERS: + if reply_to: + return await reply_to.reply( + text, parse_mode=parse_mode, link_preview=link_preview + ) + return await event.reply( + text, parse_mode=parse_mode, link_preview=link_preview + ) + await event.edit(text, parse_mode=parse_mode, link_preview=link_preview) + return event + if not noformat: + text = md_to_text(text) + if aslink or deflink: + linktext = linktext or "ᴍᴇssᴀɢᴇ ᴡᴀs ᴛᴏ ʙɪɢ sᴏ ᴘᴀsᴛᴇᴅ ᴛᴏ ʙɪɴ" + response = await paste_message(text, pastetype="s") + text = f"{linktext} [here]({response})" + if event.sender_id in DEV_USERS: + if reply_to: + return await reply_to.reply(text, link_preview=link_preview) + return await event.reply(text, link_preview=link_preview) + await event.edit(text, link_preview=link_preview) + return event + file_name = file_name or "output.txt" + caption = caption or None + with open(file_name, "w+") as output: + output.write(text) + if reply_to: + await reply_to.reply(caption, file=file_name) + await event.delete() + return os.remove(file_name) + if event.sender_id in DEV_USERS: + await event.reply(caption, file=file_name) + await event.delete() + return os.remove(file_name) + await event.client.send_file(event.chat_id, file_name, caption=caption) + await event.delete() + os.remove(file_name) + + +async def edit_delete(event, text, time=None, parse_mode=None, link_preview=None): + parse_mode = parse_mode or "md" + link_preview = link_preview or False + time = time or 5 + if event.sender_id in DEV_USERS: + reply_to = await event.get_reply_message() + himaevent = ( + await reply_to.reply(text, link_preview=link_preview, parse_mode=parse_mode) + if reply_to + else await event.reply( + text, link_preview=link_preview, parse_mode=parse_mode + ) + ) + else: + himaevent = await event.edit( + text, link_preview=link_preview, parse_mode=parse_mode + ) + await asyncio.sleep(time) + return await himaevent.delete() + + +import random + +import requests + + +@telethn.on(events.NewMessage(pattern="^[!/]gif")) +async def some(event): + try: + inpt = event.text.split(" ", maxsplit=1)[1] + except IndexError: + return await event.reply("Usage: /gif <query>") + """Sends random gifs of your query""" + reply_to_id = await reply_id(event) + count = 1 + if ";" in inpt: + inpt, count = inpt.split(";") + if int(count) < 0 and int(count) > 20: + await edit_delete(event, "`ɢɪᴠᴇ ᴠᴀʟᴜᴇ ɪɴ ʀᴀɴɢᴇ 1-20`") + himaevent = await edit_or_reply(event, "`sᴇɴᴅɪɴɢ ɢɪғ....`") + res = requests.get("https://giphy.com/") + res = res.text.split("GIPHY_FE_WEB_API_KEY =")[1].split("\n")[0] + api_key = res[2:-1] + r = requests.get( + f"https://api.giphy.com/v1/gifs/search?q={inpt}&api_key={api_key}&limit=50" + ).json() + list_id = [r["data"][i]["id"] for i in range(len(r["data"]))] + rlist = random.sample(list_id, int(count)) + for items in rlist: + nood = await event.client.send_file( + event.chat_id, + f"https://media.giphy.com/media/{items}/giphy.gif", + reply_to=reply_to_id, + ) + await himaevent.delete() + + +# schedule for anime + +weekdays = { + "monday": 0, + "tuesday": 1, + "wednesday": 2, + "thursday": 3, + "friday": 4, + "saturday": 5, + "sunday": 6, +} + + +def get_weekday(dayid): + for key, value in weekdays.items(): + if value == dayid: + return key + + +async def get_anime_schedule(weekid): + "ɢᴇᴛ ᴀɴɪᴍᴇ sᴄʜᴇᴅᴜʟᴇ" + dayname = get_weekday(weekid) + result = ( + f"✙ **ᴛɪᴍᴇ ᴢᴏɴᴇ: ᴊᴀᴘᴀɴ**\n**sᴄʜᴇᴅᴜʟᴇᴅ ᴀɴɪᴍᴇ ғᴏʀ {dayname.title()} ᴀʀᴇ : **\n\n" + ) + async with jikanpy.AioJikan() as animesession: + scheduled_list = (await animesession.schedule(day=dayname)).get(dayname) + for a_name in scheduled_list: + result += f"• [{a_name['title']}]({a_name['url']})\n" + return result, dayname + + +@telethn.on(events.NewMessage(pattern="^[!/]schedule ?(.*)")) +async def aschedule_fetch(event): + "To ɢᴇᴛ ʟɪsᴛ ᴏғ ᴀɴɪᴍᴇs sᴄʜᴇᴅᴜʟᴇᴅ ᴏɴ ᴛʜᴀᴛ ᴅᴀʏ" + input_str = event.pattern_match.group(1) or datetime.now().weekday() + if input_str in weekdays: + input_str = weekdays[input_str] + try: + input_str = int(input_str) + except ValueError: + return await edit_delete(event, "`ʏᴏᴜ ʜᴀᴠᴇ ɢɪᴠᴇɴ ᴀɴ ɪɴᴠᴀʟɪᴅ ᴡᴇᴇᴋᴅᴀʏ`", 7) + if input_str not in [0, 1, 2, 3, 4, 5, 6]: + return await edit_delete(event, "`ʏᴏᴜ ʜᴀᴠᴇ ɢɪᴠᴇɴ ᴀɴ ɪɴᴠᴀʟɪᴅ ᴡᴇᴇᴋᴅᴀʏ`", 7) + result = await get_anime_schedule(input_str) + await edit_or_reply(event, result[0]) diff --git a/Exon/modules/feds.py b/Exon/modules/feds.py new file mode 100644 index 00000000..5fafebfe --- /dev/null +++ b/Exon/modules/feds.py @@ -0,0 +1,2455 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import csv +import json +import os +import re +import time +import uuid +from io import BytesIO + +from telegram import ( + InlineKeyboardButton, + InlineKeyboardMarkup, + MessageEntity, + ParseMode, + Update, +) +from telegram.error import BadRequest, TelegramError, Unauthorized +from telegram.ext import ( + CallbackContext, + CallbackQueryHandler, + CommandHandler, + run_async, +) +from telegram.utils.helpers import mention_html, mention_markdown + +import Exon.modules.sql.feds_sql as sql +from Exon import DRAGONS, EVENT_LOGS, LOGGER, OWNER_ID, TIGERS, WOLVES, dispatcher +from Exon.modules.disable import DisableAbleCommandHandler +from Exon.modules.helper_funcs.alternate import send_message +from Exon.modules.helper_funcs.chat_status import is_user_admin +from Exon.modules.helper_funcs.extraction import ( + extract_unt_fedban, + extract_user, + extract_user_fban, +) +from Exon.modules.helper_funcs.string_handling import markdown_parser + +FBAN_ERRORS = { + "ᴜsᴇʀ ɪs ᴀɴ ᴀᴅᴍɪɴɪsᴛʀᴀᴛᴏʀ ᴏғ ᴛʜᴇ ᴄʜᴀᴛ", + "ᴄʜᴀᴛ ɴᴏᴛ ғᴏᴜɴᴅ", + "ɴᴏᴛ ᴇɴᴏᴜɢʜ ʀɪɢʜᴛs ᴛᴏ ʀᴇsᴛʀɪᴄᴛ/unrestrict ᴄʜᴀᴛ ᴍᴇᴍʙᴇʀ", + "User_not_participant", + "Peer_id_invalid", + "ɢʀᴏᴜᴘ ᴄʜᴀᴛ ᴡᴀs ᴅᴇᴀᴄᴛɪᴠᴀᴛᴇᴅ", + "ɴᴇᴇᴅ ᴛᴏ ʙᴇ ɪɴᴠɪᴛᴇʀ ᴏғ ᴀ ᴜsᴇʀ ᴛᴏ ᴋɪᴄᴋ ɪᴛ ғʀᴏᴍ ᴀ ʙᴀsɪᴄ ɢʀᴏᴜᴘ", + "Chat_admin_required", + "ᴏɴʟʏ ᴛʜᴇ ᴄʀᴇᴀᴛᴏʀ ᴏғ ᴀ ʙᴀsɪᴄ ɢʀᴏᴜᴘ ᴄᴀɴ ᴋɪᴄᴋ ɢʀᴏᴜᴘ ᴀᴅᴍɪɴɪsᴛʀᴀᴛᴏʀs", + "Channel_private", + "Not in the chat", + "ʜᴀᴠᴇ ɴᴏ ʀɪɢʜᴛs ᴛᴏ sᴇɴᴅ ᴀ ᴍᴇssᴀɢᴇ", +} + +UNFBAN_ERRORS = { + "ᴜsᴇʀ is ᴀɴ ᴀᴅᴍɪɴɪsᴛʀᴀᴛᴏʀ ᴏғ ᴛʜᴇ ᴄʜᴀᴛ", + "ᴄʜᴀᴛ ɴᴏᴛ ғᴏᴜɴᴅ", + "ɴᴏᴛ enough ʀɪɢʜᴛs ᴛᴏ restrict/unrestrict ᴄʜᴀᴛ ᴍᴇᴍʙᴇʀ", + "User_not_participant", + "ᴍᴇᴛʜᴏᴅ is available for sᴜᴘᴇʀɢʀᴏᴜᴘ ᴀɴᴅ ᴄʜᴀɴɴᴇʟ ᴄʜᴀᴛs ᴏɴʟʏ", + "ɴᴏᴛ ɪɴ ᴛʜᴇ ᴄʜᴀᴛ", + "Channel_private", + "Chat_admin_required", + "ʜᴀᴠᴇ ɴᴏ ʀɪɢʜᴛs to sᴇɴᴅ a ᴍᴇssᴀɢᴇ", +} + + +@run_async +def new_fed(update: Update, context: CallbackContext): + chat = update.effective_chat + user = update.effective_user + message = update.effective_message + if chat.type != "private": + update.effective_message.reply_text( + "ғᴇᴅᴇʀᴀᴛɪᴏɴs ᴄᴀɴ ᴏɴʟʏ ʙᴇ ᴄʀᴇᴀᴛᴇᴅ ʙʏ ᴘʀɪᴠᴀᴛᴇʟʏ ᴍᴇssᴀɢɪɴɢ ᴍᴇ." + ) + return + if len(message.text) == 1: + send_message( + update.effective_message, "ᴘʟᴇᴀsᴇ ᴡʀɪᴛᴇ ᴛʜᴇ ɴᴀᴍᴇ ᴏғ ᴛʜᴇ ғᴇᴅᴇʀᴀᴛɪᴏɴ!" + ) + return + fednam = message.text.split(None, 1)[1] + if fednam != "": + fed_id = str(uuid.uuid4()) + fed_name = fednam + LOGGER.info(fed_id) + + x = sql.new_fed(user.id, fed_name, fed_id) + if not x: + update.effective_message.reply_text( + "Can't ғᴇᴅᴇʀᴀᴛᴇ! ᴘʟᴇᴀsᴇ ᴄᴏɴᴛᴀᴄᴛ @AbishnoiMF if the problem persist." + ) + return + + update.effective_message.reply_text( + f"*You ʜᴀᴠᴇ succeeded in creating a new federation!*\nName: `{fed_name}`\nID: `{fed_id}`\n\nUse the ᴄᴏᴍᴍᴀɴᴅ below to join the federation:\n`/joinfed {fed_id}`", + parse_mode=ParseMode.MARKDOWN, + ) + + try: + bot.send_message( + EVENT_LOGS, + f"ɴᴇᴡ ғᴇᴅᴇʀᴀᴛɪᴏɴ: <b>{fed_name}</b>\nID: <pre>{fed_id}</pre>", + parse_mode=ParseMode.HTML, + ) + + except: + LOGGER.warning("ᴄᴀɴɴᴏᴛ sᴇɴᴅ ᴀ ᴍᴇssᴀɢᴇ ᴛᴏ EVENT_LOGS") + else: + update.effective_message.reply_text( + "ᴘʟᴇᴀsᴇ ᴡʀɪᴛᴇ ᴅᴏᴡɴ ᴛʜᴇ ɴᴀᴍᴇ ᴏғ ᴛʜᴇ federation" + ) + + +@run_async +def del_fed(update: Update, context: CallbackContext): + bot, args = context.bot, context.args + chat = update.effective_chat + user = update.effective_user + if chat.type != "private": + update.effective_message.reply_text( + "ғᴇᴅᴇʀᴀᴛɪᴏɴs ᴄᴀɴ ᴏɴʟʏ ʙᴇ ᴅᴇʟᴇᴛᴇᴅ ʙʏ ᴘʀɪᴠᴀᴛᴇʟʏ ᴍᴇssᴀɢɪɴɢ ᴍᴇ." + ) + return + if args: + is_fed_id = args[0] + getinfo = sql.get_fed_info(is_fed_id) + if getinfo is False: + update.effective_message.reply_text("This federation does not exist.") + return + if int(getinfo["owner"]) == int(user.id) or int(user.id) == OWNER_ID: + fed_id = is_fed_id + else: + update.effective_message.reply_text("Only federation owners can do this!") + return + else: + update.effective_message.reply_text("What should I delete?") + return + + if is_user_fed_owner(fed_id, user.id) is False: + update.effective_message.reply_text("Only federation owners can do this!") + return + + update.effective_message.reply_text( + f"You sure you want to delete your federation? This cannot be reverted, you will lose your entire ban list, and '{getinfo['fname']}' will be permanently lost.", + reply_markup=InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + text="⚠️ ᴅᴇʟᴇᴛᴇ ғᴇᴅᴇʀᴀᴛɪᴏɴ ⚠️", + callback_data=f"rmfed_{fed_id}", + ) + ], + [InlineKeyboardButton(text="ᴄᴀɴᴄᴇʟ", callback_data="rmfed_cancel")], + ] + ), + ) + + +@run_async +def rename_fed(update, context): + user = update.effective_user + msg = update.effective_message + args = msg.text.split(None, 2) + + if len(args) < 3: + return msg.reply_text("ᴜsᴀɢᴇ: /renamefed <fed_id> <newname>") + + fed_id, newname = args[1], args[2] + verify_fed = sql.get_fed_info(fed_id) + + if not verify_fed: + return msg.reply_text("This fed not exist in my database!") + + if is_user_fed_owner(fed_id, user.id): + sql.rename_fed(fed_id, user.id, newname) + msg.reply_text(f"Successfully renamed your fed name to {newname}!") + else: + msg.reply_text("Only federation owner can do this!") + + +@run_async +def fed_chat(update: Update, context: CallbackContext): + bot, args = context.bot, context.args + chat = update.effective_chat + update.effective_user + fed_id = sql.get_fed_id(chat.id) + + user_id = update.effective_message.from_user.id + if not is_user_admin(update.effective_chat, user_id): + update.effective_message.reply_text( + "You must be an admin to execute this command" + ) + return + + if not fed_id: + update.effective_message.reply_text("This group is not in any federation!") + return + + update.effective_user + chat = update.effective_chat + info = sql.get_fed_info(fed_id) + + text = ( + "This group is part of the following federation:" + + f"\n{info['fname']} (ID: <code>{fed_id}</code>)" + ) + + update.effective_message.reply_text(text, parse_mode=ParseMode.HTML) + + +@run_async +def join_fed(update: Update, context: CallbackContext): + bot, args = context.bot, context.args + chat = update.effective_chat + user = update.effective_user + + if chat.type == "private": + send_message( + update.effective_message, + "This command is specific to the group, not to our pm!", + ) + return + + message = update.effective_message + administrators = chat.get_administrators() + fed_id = sql.get_fed_id(chat.id) + + if user.id not in DRAGONS: + for admin in administrators: + status = admin.status + if status == "creator" and str(admin.user.id) != str(user.id): + update.effective_message.reply_text( + "Only group creators can use this command!" + ) + return + if fed_id: + message.reply_text("You cannot join two federations from one chat") + return + + if len(args) >= 1: + getfed = sql.search_fed_by_id(args[0]) + if getfed is False: + message.reply_text("Please enter a valid federation ID") + return + + x = sql.chat_join_fed(args[0], chat.title, chat.id) + if not x: + message.reply_text( + "Failed to join federation! Please contact @AbishnoiMF should this problem persist!" + ) + return + + if get_fedlog := sql.get_fed_log(args[0]): + if eval(get_fedlog): + bot.send_message( + get_fedlog, + f"Chat *{chat.title}* has joined the federation *{getfed['fname']}*", + parse_mode="markdown", + ) + + message.reply_text(f"This group has joined the federation: {getfed['fname']}!") + + +@run_async +def leave_fed(update: Update, context: CallbackContext): + bot, args = context.bot, context.args + chat = update.effective_chat + user = update.effective_user + + if chat.type == "private": + send_message( + update.effective_message, + "This command is specific to the group, not to our PM!", + ) + return + + fed_id = sql.get_fed_id(chat.id) + fed_info = sql.get_fed_info(fed_id) + + # administrators = chat.get_administrators().status + getuser = bot.get_chat_member(chat.id, user.id).status + if getuser in "creator" or user.id in DRAGONS: + if sql.chat_leave_fed(chat.id) is True: + if get_fedlog := sql.get_fed_log(fed_id): + if eval(get_fedlog): + bot.send_message( + get_fedlog, + f"Chat *{chat.title}* has left the federation *{fed_info['fname']}*", + parse_mode="markdown", + ) + + send_message( + update.effective_message, + f"This group has left the federation {fed_info['fname']}!", + ) + + else: + update.effective_message.reply_text( + "How can you leave a federation that you never joined?!" + ) + else: + update.effective_message.reply_text("Only group creators can use this command!") + + +@run_async +def user_join_fed(update: Update, context: CallbackContext): + bot, args = context.bot, context.args + chat = update.effective_chat + user = update.effective_user + msg = update.effective_message + + if chat.type == "private": + send_message( + update.effective_message, + "This command is specific to the group, not to our pm!", + ) + return + + fed_id = sql.get_fed_id(chat.id) + + if is_user_fed_owner(fed_id, user.id) or user.id in DRAGONS: + user_id = extract_user(msg, args) + if user_id: + user = bot.get_chat(user_id) + elif not msg.reply_to_message and not args: + user = msg.from_user + elif not msg.reply_to_message and ( + not args + or ( + len(args) >= 1 + and not args[0].startswith("@") + and not args[0].isdigit() + and not msg.parse_entities([MessageEntity.TEXT_MENTION]) + ) + ): + msg.reply_text("I cannot extract user from this message") + return + else: + LOGGER.warning("error") + getuser = sql.search_user_in_fed(fed_id, user_id) + fed_id = sql.get_fed_id(chat.id) + info = sql.get_fed_info(fed_id) + get_owner = eval(info["fusers"])["owner"] + get_owner = bot.get_chat(get_owner).id + if user_id == get_owner: + update.effective_message.reply_text( + "You do know that the user is the federation owner, right? RIGHT?" + ) + return + if getuser: + update.effective_message.reply_text( + "I cannot promote users who are already federation admins! Can remove them if you want!" + ) + return + if user_id == bot.id: + update.effective_message.reply_text( + "I already am a federation admin in all federations!" + ) + return + if res := sql.user_join_fed(fed_id, user_id): + update.effective_message.reply_text("Successfully Promoted!") + else: + update.effective_message.reply_text("Failed to promote!") + else: + update.effective_message.reply_text("Only federation owners can do this!") + + +@run_async +def user_demote_fed(update: Update, context: CallbackContext): + bot, args = context.bot, context.args + chat = update.effective_chat + user = update.effective_user + + if chat.type == "private": + send_message( + update.effective_message, + "This command is specific to the group, not to our pm!", + ) + return + + fed_id = sql.get_fed_id(chat.id) + + if is_user_fed_owner(fed_id, user.id): + msg = update.effective_message + user_id = extract_user(msg, args) + if user_id: + user = bot.get_chat(user_id) + + elif not msg.reply_to_message and not args: + user = msg.from_user + + elif not msg.reply_to_message and ( + not args + or ( + len(args) >= 1 + and not args[0].startswith("@") + and not args[0].isdigit() + and not msg.parse_entities([MessageEntity.TEXT_MENTION]) + ) + ): + msg.reply_text("I cannot extract user from this message") + return + else: + LOGGER.warning("error") + + if user_id == bot.id: + update.effective_message.reply_text( + "The thing you are trying to demote me from will fail to work without me! Just saying." + ) + return + + if sql.search_user_in_fed(fed_id, user_id) is False: + update.effective_message.reply_text( + "I cannot demote people who are not federation admins!" + ) + return + + res = sql.user_demote_fed(fed_id, user_id) + if res is True: + update.effective_message.reply_text("Demoted from a Fed Admin!") + else: + update.effective_message.reply_text("Demotion failed!") + else: + update.effective_message.reply_text("Only federation owners can do this!") + return + + +@run_async +def fed_info(update: Update, context: CallbackContext): + bot, args = context.bot, context.args + chat = update.effective_chat + user = update.effective_user + if args: + fed_id = args[0] + else: + fed_id = sql.get_fed_id(chat.id) + if not fed_id: + send_message( + update.effective_message, "This group is not in any federation!" + ) + return + info = sql.get_fed_info(fed_id) + if is_user_fed_admin(fed_id, user.id) is False: + update.effective_message.reply_text("Only a federation admin can do this!") + return + + owner = bot.get_chat(info["owner"]) + try: + owner_name = f"{owner.first_name} {owner.last_name}" + except: + owner_name = owner.first_name + FEDADMIN = sql.all_fed_users(fed_id) + TotalAdminFed = len(FEDADMIN) + + user = update.effective_user + chat = update.effective_chat + info = sql.get_fed_info(fed_id) + + text = "<b>ғᴇᴅᴇʀᴀᴛɪᴏɴ ɪɴғᴏʀᴍᴀᴛɪᴏɴ:</b>" + f"\nFedID: <code>{fed_id}</code>" + text += f"\nɴᴀᴍᴇ: {info['fname']}" + text += f"\nᴄʀᴇᴀᴛᴏʀ: {mention_html(owner.id, owner_name)}" + text += f"\nᴀʟʟ ᴀᴅᴍɪɴs: <code>{TotalAdminFed}</code>" + getfban = sql.get_all_fban_users(fed_id) + text += f"\nᴛᴏᴛᴀʟ ʙᴀɴɴᴇᴅ ᴜsᴇʀs: <code>{len(getfban)}</code>" + getfchat = sql.all_fed_chats(fed_id) + text += f"\nɴᴜᴍʙᴇʀ ᴏғ ɢʀᴏᴜᴘs ɪɴ ᴛʜɪs ғᴇᴅᴇʀᴀᴛɪᴏɴ: <code>{len(getfchat)}</code>" + + update.effective_message.reply_text(text, parse_mode=ParseMode.HTML) + + +@run_async +def fed_admin(update: Update, context: CallbackContext): + bot, args = context.bot, context.args + chat = update.effective_chat + user = update.effective_user + + if chat.type == "private": + send_message( + update.effective_message, + "ᴛʜɪs ᴄᴏᴍᴍᴀɴᴅ ɪs sᴘᴇᴄɪғɪᴄ ᴛᴏ ᴛʜᴇ ɢʀᴏᴜᴘ, ɴᴏᴛ to ᴏᴜʀ ᴘᴍ!", + ) + return + + fed_id = sql.get_fed_id(chat.id) + + if not fed_id: + update.effective_message.reply_text("ᴛʜɪs ɢʀᴏᴜᴘ is ɴᴏᴛ in ᴀɴʏ ғᴇᴅᴇʀᴀᴛɪᴏɴ!") + return + + if is_user_fed_admin(fed_id, user.id) is False: + update.effective_message.reply_text("ᴏɴʟʏ ғᴇᴅᴇʀᴀᴛɪᴏɴ ᴀᴅᴍɪɴs ᴄᴀɴ ᴅᴏ ᴛʜɪs!") + return + + user = update.effective_user + chat = update.effective_chat + info = sql.get_fed_info(fed_id) + + text = f"<b>ғᴇᴅᴇʀᴀᴛɪᴏɴ ᴀᴅᴍɪɴ {info['fname']}:</b>\n\n" + "Owner:\n" + owner = bot.get_chat(info["owner"]) + try: + owner_name = f"{owner.first_name} {owner.last_name}" + except: + owner_name = owner.first_name + text += f" ? {mention_html(owner.id, owner_name)}\n" + + members = sql.all_fed_members(fed_id) + if len(members) == 0: + text += "\nThere ᴀʀᴇ ɴᴏ ᴀᴅᴍɪɴs ɪɴ ᴛʜɪs ғᴇᴅᴇʀᴀᴛɪᴏɴ" + else: + text += "\nᴀᴅᴍɪɴ:\n" + for x in members: + user = bot.get_chat(x) + text += f" {mention_html(user.id, user.first_name)}\n" + + update.effective_message.reply_text(text, parse_mode=ParseMode.HTML) + + +@run_async +def fed_ban(update: Update, context: CallbackContext): + bot, args = context.bot, context.args + chat = update.effective_chat + user = update.effective_user + + if chat.type == "private": + send_message( + update.effective_message, + "ᴛʜɪs ᴄᴏᴍᴍᴀɴᴅ is sᴘᴇᴄɪғɪᴄ ᴛᴏ ᴛʜᴇ ɢʀᴏᴜᴘ, ɴᴏᴛ ᴛᴏ ᴏᴜʀ ᴘᴍ!", + ) + return + + fed_id = sql.get_fed_id(chat.id) + + if not fed_id: + update.effective_message.reply_text( + "This group is not a part of any federation!" + ) + return + + info = sql.get_fed_info(fed_id) + getfednotif = sql.user_feds_report(info["owner"]) + + if is_user_fed_admin(fed_id, user.id) is False: + update.effective_message.reply_text("Only federation admins can do this!") + return + + message = update.effective_message + + user_id, reason = extract_unt_fedban(message, args) + + fban, fbanreason, fbantime = sql.get_fban_user(fed_id, user_id) + + if not user_id: + message.reply_text("You don't seem to be referring to a user") + return + + if user_id == bot.id: + message.reply_text( + "What is funnier than kicking the group creator? Self sacrifice." + ) + return + + if is_user_fed_owner(fed_id, user_id) is True: + message.reply_text("Why did you try the federation fban?") + return + + if is_user_fed_admin(fed_id, user_id) is True: + message.reply_text("He is a federation admin, I can't fban him.") + return + + if user_id == OWNER_ID: + message.reply_text("Disaster level God cannot be fed banned!") + return + + if int(user_id) in DRAGONS: + message.reply_text("Dragons cannot be fed banned!") + return + + if int(user_id) in TIGERS: + message.reply_text("Tigers cannot be fed banned!") + return + + if int(user_id) in WOLVES: + message.reply_text("Wolves cannot be fed banned!") + return + + if user_id in [777000, 1087968824]: + message.reply_text("Fool! You can't attack Telegram's native tech!") + return + + try: + user_chat = bot.get_chat(user_id) + isvalid = True + fban_user_id = user_chat.id + fban_user_name = user_chat.first_name + fban_user_lname = user_chat.last_name + fban_user_uname = user_chat.username + except BadRequest as excp: + if not str(user_id).isdigit(): + send_message(update.effective_message, excp.message) + return + elif len(str(user_id)) != 9: + send_message(update.effective_message, "That's so not a user!") + return + isvalid = False + fban_user_id = int(user_id) + fban_user_name = "user({})".format(user_id) + fban_user_lname = None + fban_user_uname = None + + if isvalid and user_chat.type != "private": + send_message(update.effective_message, "That's so not a user!") + return + + if isvalid: + user_target = mention_html(fban_user_id, fban_user_name) + else: + user_target = fban_user_name + + if fban: + fed_name = info["fname"] + + temp = sql.un_fban_user(fed_id, fban_user_id) + if not temp: + message.reply_text("Failed to update the reason for fedban!") + return + x = sql.fban_user( + fed_id, + fban_user_id, + fban_user_name, + fban_user_lname, + fban_user_uname, + reason, + int(time.time()), + ) + if not x: + message.reply_text( + "Failed to ban from the federation! If this problem continues, contact @NekoArsh." + ) + return + + fed_chats = sql.all_fed_chats(fed_id) + # Will send to current chat + bot.send_message( + chat.id, + "<b>ғᴇᴅʙᴀɴ ʀᴇᴀsᴏɴ ᴜᴘᴅᴀᴛᴇᴅ</b>" + "\n<b>ғᴇᴅᴇʀᴀᴛɪᴏɴ:</b> {}" + "\n<b>ғᴇᴅᴇʀᴀᴛɪᴏɴ ᴀᴅᴍɪɴ:</b> {}" + "\n<b>ᴜsᴇʀ:</b> {}" + "\n<b>ᴜsᴇʀ ID:</b> <code>{}</code>" + "\n<b>ʀᴇᴀsᴏɴ:</b> {}".format( + fed_name, + mention_html(user.id, user.first_name), + user_target, + fban_user_id, + reason, + ), + parse_mode="HTML", + ) + # Send message to owner if fednotif is enabled + if getfednotif: + bot.send_message( + info["owner"], + "<b>FedBan reason updated</b>" + "\n<b>Federation:</b> {}" + "\n<b>Federation Admin:</b> {}" + "\n<b>User:</b> {}" + "\n<b>ᴜsᴇʀ ID:</b> <code>{}</code>" + "\n<b>Reason:</b> {}".format( + fed_name, + mention_html(user.id, user.first_name), + user_target, + fban_user_id, + reason, + ), + parse_mode="HTML", + ) + # If fedlog is set, then send message, except fedlog is current chat + get_fedlog = sql.get_fed_log(fed_id) + if get_fedlog: + if int(get_fedlog) != int(chat.id): + bot.send_message( + get_fedlog, + "<b>ғᴇᴅʙᴀɴ ʀᴇᴀsᴏɴ ᴜᴘᴅᴀᴛᴇᴅ</b>" + "\n<b>ғᴇᴅᴇʀᴀᴛɪᴏɴ:</b> {}" + "\n<b>ғᴇᴅᴇʀᴀᴛɪᴏɴ ᴀᴅᴍɪɴ:</b> {}" + "\n<b>ᴜsᴇʀ:</b> {}" + "\n<b>ᴜsᴇʀ ɪᴅ:</b> <code>{}</code>" + "\n<b>ʀᴇᴀsᴏɴ:</b> {}".format( + fed_name, + mention_html(user.id, user.first_name), + user_target, + fban_user_id, + reason, + ), + parse_mode="HTML", + ) + for fedschat in fed_chats: + try: + # Do not spam all fed chats + """ + bot.send_message(chat, "<b>FedBan reason updated</b>" \ + "\n<b>Federation:</b> {}" \ + "\n<b>Federation Admin:</b> {}" \ + "\n<b>User:</b> {}" \ + "\n<b>User ID:</b> <code>{}</code>" \ + "\n<b>Reason:</b> {}".format(fed_name, mention_html(user.id, user.first_name), user_target, fban_user_id, reason), parse_mode="HTML") + """ + bot.kick_chat_member(fedschat, fban_user_id) + except BadRequest as excp: + if excp.message in FBAN_ERRORS: + try: + dispatcher.bot.getChat(fedschat) + except Unauthorized: + sql.chat_leave_fed(fedschat) + LOGGER.info( + "ᴄʜᴀᴛ {} ʜᴀs ʟᴇᴀᴠᴇ ғᴇᴅ {} ʙᴇᴄᴀᴜsᴇ I ᴡᴀs ᴋɪᴄᴋᴇᴅ".format( + fedschat, info["fname"] + ) + ) + continue + elif excp.message == "User_id_invalid": + break + else: + LOGGER.warning( + "ᴄᴏᴜʟᴅ ɴᴏᴛ ғʙᴀɴ ᴏɴ {} ʙᴇᴄᴀᴜsᴇ: {}".format(chat, excp.message) + ) + except TelegramError: + pass + # Also do not spam all fed admins + """ + send_to_list(bot, FEDADMIN, + "<b>FedBan reason updated</b>" \ + "\n<b>Federation:</b> {}" \ + "\n<b>Federation Admin:</b> {}" \ + "\n<b>User:</b> {}" \ + "\n<b>User ID:</b> <code>{}</code>" \ + "\n<b>Reason:</b> {}".format(fed_name, mention_html(user.id, user.first_name), user_target, fban_user_id, reason), + html=True) + """ + + # Fban for fed subscriber + subscriber = list(sql.get_subscriber(fed_id)) + if len(subscriber) != 0: + for fedsid in subscriber: + all_fedschat = sql.all_fed_chats(fedsid) + for fedschat in all_fedschat: + try: + bot.kick_chat_member(fedschat, fban_user_id) + except BadRequest as excp: + if excp.message in FBAN_ERRORS: + try: + dispatcher.bot.getChat(fedschat) + except Unauthorized: + targetfed_id = sql.get_fed_id(fedschat) + sql.unsubs_fed(fed_id, targetfed_id) + LOGGER.info( + "ᴄʜᴀᴛ {} ʜᴀs ᴜɴsᴜʙ ғᴇᴅ {} ʙᴇᴄᴀᴜsᴇ I ᴡᴀs ᴋɪᴄᴋᴇᴅ".format( + fedschat, info["fname"] + ) + ) + continue + elif excp.message == "User_id_invalid": + break + else: + LOGGER.warning( + "ᴜɴᴀʙʟᴇ ᴛᴏ ғʙᴀɴ ᴏɴ {} ʙᴇᴄᴀᴜsᴇ: {}".format( + fedschat, excp.message + ) + ) + except TelegramError: + pass + # send_message(update.effective_message, "Fedban Reason has been updated.") + return + + fed_name = info["fname"] + + # starting = "Starting a federation ban for {} in the Federation <b>{}</b>.".format( + # user_target, fed_name) + # update.effective_message.reply_text(starting, parse_mode=ParseMode.HTML) + + # if reason == "": + # reason = "No reason given." + + x = sql.fban_user( + fed_id, + fban_user_id, + fban_user_name, + fban_user_lname, + fban_user_uname, + reason, + int(time.time()), + ) + if not x: + message.reply_text( + "Failed to ban from the federation! If this problem continues, contact @HuntersAssociations." + ) + return + + fed_chats = sql.all_fed_chats(fed_id) + # Will send to current chat + bot.send_message( + chat.id, + "<b>FedBan reason updated</b>" + "\n<b>Federation:</b> {}" + "\n<b>Federation Admin:</b> {}" + "\n<b>User:</b> {}" + "\n<b>User ID:</b> <code>{}</code>" + "\n<b>Reason:</b> {}".format( + fed_name, + mention_html(user.id, user.first_name), + user_target, + fban_user_id, + reason, + ), + parse_mode="HTML", + ) + # Send message to owner if fednotif is enabled + if getfednotif: + bot.send_message( + info["owner"], + "<b>FedBan reason updated</b>" + "\n<b>Federation:</b> {}" + "\n<b>Federation Admin:</b> {}" + "\n<b>User:</b> {}" + "\n<b>User ID:</b> <code>{}</code>" + "\n<b>Reason:</b> {}".format( + fed_name, + mention_html(user.id, user.first_name), + user_target, + fban_user_id, + reason, + ), + parse_mode="HTML", + ) + # If fedlog is set, then send message, except fedlog is current chat + get_fedlog = sql.get_fed_log(fed_id) + if get_fedlog: + if int(get_fedlog) != int(chat.id): + bot.send_message( + get_fedlog, + "<b>FedBan reason updated</b>" + "\n<b>Federation:</b> {}" + "\n<b>Federation Admin:</b> {}" + "\n<b>User:</b> {}" + "\n<b>User ID:</b> <code>{}</code>" + "\n<b>Reason:</b> {}".format( + fed_name, + mention_html(user.id, user.first_name), + user_target, + fban_user_id, + reason, + ), + parse_mode="HTML", + ) + chats_in_fed = 0 + for fedschat in fed_chats: + chats_in_fed += 1 + try: + # Do not spamming all fed chats + """ + bot.send_message(chat, "<b>FedBan reason updated</b>" \ + "\n<b>Federation:</b> {}" \ + "\n<b>Federation Admin:</b> {}" \ + "\n<b>User:</b> {}" \ + "\n<b>User ID:</b> <code>{}</code>" \ + "\n<b>Reason:</b> {}".format(fed_name, mention_html(user.id, user.first_name), user_target, fban_user_id, reason), parse_mode="HTML") + """ + bot.kick_chat_member(fedschat, fban_user_id) + except BadRequest as excp: + if excp.message in FBAN_ERRORS: + pass + elif excp.message == "User_id_invalid": + break + else: + LOGGER.warning( + "Could not fban on {} because: {}".format(chat, excp.message) + ) + except TelegramError: + pass + + # Also do not spamming all fed admins + """ + send_to_list(bot, FEDADMIN, + "<b>FedBan reason updated</b>" \ + "\n<b>Federation:</b> {}" \ + "\n<b>Federation Admin:</b> {}" \ + "\n<b>User:</b> {}" \ + "\n<b>User ID:</b> <code>{}</code>" \ + "\n<b>Reason:</b> {}".format(fed_name, mention_html(user.id, user.first_name), user_target, fban_user_id, reason), + html=True) + """ + + # Fban for fed subscriber + subscriber = list(sql.get_subscriber(fed_id)) + if len(subscriber) != 0: + for fedsid in subscriber: + all_fedschat = sql.all_fed_chats(fedsid) + for fedschat in all_fedschat: + try: + bot.kick_chat_member(fedschat, fban_user_id) + except BadRequest as excp: + if excp.message in FBAN_ERRORS: + try: + dispatcher.bot.getChat(fedschat) + except Unauthorized: + targetfed_id = sql.get_fed_id(fedschat) + sql.unsubs_fed(fed_id, targetfed_id) + LOGGER.info( + "Chat {} has unsub fed {} because I was kicked".format( + fedschat, info["fname"] + ) + ) + continue + elif excp.message == "User_id_invalid": + break + else: + LOGGER.warning( + "Unable to fban on {} because: {}".format( + fedschat, excp.message + ) + ) + except TelegramError: + pass + # if chats_in_fed == 0: + # send_message(update.effective_message, "Fedban affected 0 chats. ") + # elif chats_in_fed > 0: + # send_message(update.effective_message, + # "Fedban affected {} chats. ".format(chats_in_fed)) + + +@run_async +def unfban(update: Update, context: CallbackContext): + bot, args = context.bot, context.args + chat = update.effective_chat + user = update.effective_user + message = update.effective_message + + if chat.type == "private": + send_message( + update.effective_message, + "This command is specific to the group, not to our pm!", + ) + return + + fed_id = sql.get_fed_id(chat.id) + + if not fed_id: + update.effective_message.reply_text( + "This group is not a part of any federation!" + ) + return + + info = sql.get_fed_info(fed_id) + getfednotif = sql.user_feds_report(info["owner"]) + + if is_user_fed_admin(fed_id, user.id) is False: + update.effective_message.reply_text("Only federation admins can do this!") + return + + user_id = extract_user_fban(message, args) + if not user_id: + message.reply_text("You do not seem to be referring to a user.") + return + + try: + user_chat = bot.get_chat(user_id) + isvalid = True + fban_user_id = user_chat.id + fban_user_name = user_chat.first_name + user_chat.last_name + user_chat.username + except BadRequest as excp: + if not str(user_id).isdigit(): + send_message(update.effective_message, excp.message) + return + elif len(str(user_id)) != 9: + send_message(update.effective_message, "That's so not a user!") + return + isvalid = False + fban_user_id = int(user_id) + fban_user_name = "user({})".format(user_id) + + if isvalid and user_chat.type != "private": + message.reply_text("That's so not a user!") + return + + if isvalid: + user_target = mention_html(fban_user_id, fban_user_name) + else: + user_target = fban_user_name + + fban, fbanreason, fbantime = sql.get_fban_user(fed_id, fban_user_id) + if fban is False: + message.reply_text("This user is not fbanned!") + return + + update.effective_user + + # message.reply_text("I'll give {} another chance in this federation".format(user_chat.first_name)) + + chat_list = sql.all_fed_chats(fed_id) + # Will send to current chat + bot.send_message( + chat.id, + "<b>Un-FedBan</b>" + "\n<b>Federation:</b> {}" + "\n<b>Federation Admin:</b> {}" + "\n<b>User:</b> {}" + "\n<b>User ID:</b> <code>{}</code>".format( + info["fname"], + mention_html(user.id, user.first_name), + user_target, + fban_user_id, + ), + parse_mode="HTML", + ) + # Send message to owner if fednotif is enabled + if getfednotif: + bot.send_message( + info["owner"], + "<b>Un-FedBan</b>" + "\n<b>Federation:</b> {}" + "\n<b>Federation Admin:</b> {}" + "\n<b>User:</b> {}" + "\n<b>User ID:</b> <code>{}</code>".format( + info["fname"], + mention_html(user.id, user.first_name), + user_target, + fban_user_id, + ), + parse_mode="HTML", + ) + # If fedlog is set, then send message, except fedlog is current chat + get_fedlog = sql.get_fed_log(fed_id) + if get_fedlog: + if int(get_fedlog) != int(chat.id): + bot.send_message( + get_fedlog, + "<b>Un-FedBan</b>" + "\n<b>Federation:</b> {}" + "\n<b>Federation Admin:</b> {}" + "\n<b>User:</b> {}" + "\n<b>User ID:</b> <code>{}</code>".format( + info["fname"], + mention_html(user.id, user.first_name), + user_target, + fban_user_id, + ), + parse_mode="HTML", + ) + unfbanned_in_chats = 0 + for fedchats in chat_list: + unfbanned_in_chats += 1 + try: + member = bot.get_chat_member(fedchats, user_id) + if member.status == "kicked": + bot.unban_chat_member(fedchats, user_id) + # Do not spamming all fed chats + """ + bot.send_message(chat, "<b>Un-FedBan</b>" \ + "\n<b>Federation:</b> {}" \ + "\n<b>Federation Admin:</b> {}" \ + "\n<b>User:</b> {}" \ + "\n<b>User ID:</b> <code>{}</code>".format(info['fname'], mention_html(user.id, user.first_name), user_target, fban_user_id), parse_mode="HTML") + """ + except BadRequest as excp: + if excp.message in UNFBAN_ERRORS: + pass + elif excp.message == "User_id_invalid": + break + else: + LOGGER.warning( + "Could not fban on {} because: {}".format(chat, excp.message) + ) + except TelegramError: + pass + + try: + x = sql.un_fban_user(fed_id, user_id) + if not x: + send_message( + update.effective_message, + "Un-fban failed, this user may already be un-fedbanned!", + ) + return + except: + pass + + # UnFban for fed subscriber + subscriber = list(sql.get_subscriber(fed_id)) + if len(subscriber) != 0: + for fedsid in subscriber: + all_fedschat = sql.all_fed_chats(fedsid) + for fedschat in all_fedschat: + try: + bot.unban_chat_member(fedchats, user_id) + except BadRequest as excp: + if excp.message in FBAN_ERRORS: + try: + dispatcher.bot.getChat(fedschat) + except Unauthorized: + targetfed_id = sql.get_fed_id(fedschat) + sql.unsubs_fed(fed_id, targetfed_id) + LOGGER.info( + "Chat {} has unsub fed {} because I was kicked".format( + fedschat, info["fname"] + ) + ) + continue + elif excp.message == "User_id_invalid": + break + else: + LOGGER.warning( + "Unable to fban on {} because: {}".format( + fedschat, excp.message + ) + ) + except TelegramError: + pass + + if unfbanned_in_chats == 0: + send_message( + update.effective_message, "This person has been un-fbanned in 0 chats." + ) + if unfbanned_in_chats > 0: + send_message( + update.effective_message, + "This person has been un-fbanned in {} chats.".format(unfbanned_in_chats), + ) + # Also do not spamming all fed admins + """ + FEDADMIN = sql.all_fed_users(fed_id) + for x in FEDADMIN: + getreport = sql.user_feds_report(x) + if getreport is False: + FEDADMIN.remove(x) + send_to_list(bot, FEDADMIN, + "<b>Un-FedBan</b>" \ + "\n<b>Federation:</b> {}" \ + "\n<b>Federation Admin:</b> {}" \ + "\n<b>User:</b> {}" \ + "\n<b>User ID:</b> <code>{}</code>".format(info['fname'], mention_html(user.id, user.first_name), + mention_html(user_chat.id, user_chat.first_name), + user_chat.id), + html=True) + """ + + +@run_async +def set_frules(update: Update, context: CallbackContext): + bot, args = context.bot, context.args + chat = update.effective_chat + user = update.effective_user + + if chat.type == "private": + send_message( + update.effective_message, + "This command is specific to the group, not to our pm!", + ) + return + + fed_id = sql.get_fed_id(chat.id) + + if not fed_id: + update.effective_message.reply_text("This group is not in any federation!") + return + + if is_user_fed_admin(fed_id, user.id) is False: + update.effective_message.reply_text("Only fed admins can do this!") + return + + if len(args) >= 1: + msg = update.effective_message + raw_text = msg.text + args = raw_text.split(None, 1) # use python's maxsplit to separate cmd and args + if len(args) == 2: + txt = args[1] + offset = len(txt) - len(raw_text) # set correct offset relative to command + markdown_rules = markdown_parser( + txt, entities=msg.parse_entities(), offset=offset + ) + x = sql.set_frules(fed_id, markdown_rules) + if not x: + update.effective_message.reply_text( + "Whoa! There was an error while setting federation rules! If you wondered why please ask it in @OnePunchSupport !" + ) + return + + rules = sql.get_fed_info(fed_id)["frules"] + getfed = sql.get_fed_info(fed_id) + get_fedlog = sql.get_fed_log(fed_id) + if get_fedlog: + if eval(get_fedlog): + bot.send_message( + get_fedlog, + "*{}* has updated federation rules for fed *{}*".format( + user.first_name, getfed["fname"] + ), + parse_mode="markdown", + ) + update.effective_message.reply_text(f"Rules have been changed to :\n{rules}!") + else: + update.effective_message.reply_text("Please write rules to set this up!") + + +@run_async +def get_frules(update: Update, context: CallbackContext): + bot, args = context.bot, context.args + chat = update.effective_chat + + if chat.type == "private": + send_message( + update.effective_message, + "This command is specific to the group, not to our pm!", + ) + return + + fed_id = sql.get_fed_id(chat.id) + if not fed_id: + update.effective_message.reply_text("This group is not in any federation!") + return + + rules = sql.get_frules(fed_id) + text = "*Rules in this fed:*\n" + text += rules + update.effective_message.reply_text(text, parse_mode=ParseMode.MARKDOWN) + + +@run_async +def fed_broadcast(update: Update, context: CallbackContext): + bot, args = context.bot, context.args + msg = update.effective_message + user = update.effective_user + chat = update.effective_chat + + if chat.type == "private": + send_message( + update.effective_message, + "This command is specific to the group, not to our pm!", + ) + return + + if args: + chat = update.effective_chat + fed_id = sql.get_fed_id(chat.id) + fedinfo = sql.get_fed_info(fed_id) + if is_user_fed_owner(fed_id, user.id) is False: + update.effective_message.reply_text("Only federation owners can do this!") + return + # Parsing md + raw_text = msg.text + args = raw_text.split(None, 1) # use python's maxsplit to separate cmd and args + txt = args[1] + offset = len(txt) - len(raw_text) # set correct offset relative to command + text_parser = markdown_parser(txt, entities=msg.parse_entities(), offset=offset) + text = text_parser + try: + broadcaster = user.first_name + except: + broadcaster = user.first_name + " " + user.last_name + text += "\n\n- {}".format(mention_markdown(user.id, broadcaster)) + chat_list = sql.all_fed_chats(fed_id) + failed = 0 + for chat in chat_list: + title = "*New broadcast from Fed {}*\n".format(fedinfo["fname"]) + try: + bot.sendMessage(chat, title + text, parse_mode="markdown") + except TelegramError: + try: + dispatcher.bot.getChat(chat) + except Unauthorized: + failed += 1 + sql.chat_leave_fed(chat) + LOGGER.info( + "Chat {} has left fed {} because I was punched".format( + chat, fedinfo["fname"] + ) + ) + continue + failed += 1 + LOGGER.warning("Couldn't send broadcast to {}".format(str(chat))) + + send_text = "The federation broadcast is complete" + if failed >= 1: + send_text += "{} the group failed to receive the message, probably because it left the Federation.".format( + failed + ) + update.effective_message.reply_text(send_text) + + +@run_async +def fed_ban_list(update: Update, context: CallbackContext): + bot, args, chat_data = context.bot, context.args, context.chat_data + chat = update.effective_chat + user = update.effective_user + + if chat.type == "private": + send_message( + update.effective_message, + "This command is specific to the group, not to our pm!", + ) + return + + fed_id = sql.get_fed_id(chat.id) + info = sql.get_fed_info(fed_id) + + if not fed_id: + update.effective_message.reply_text( + "This group is not a part of any federation!" + ) + return + + if is_user_fed_owner(fed_id, user.id) is False: + update.effective_message.reply_text("Only Federation owners can do this!") + return + + user = update.effective_user + chat = update.effective_chat + getfban = sql.get_all_fban_users(fed_id) + if len(getfban) == 0: + update.effective_message.reply_text( + "The federation ban list of {} is empty".format(info["fname"]), + parse_mode=ParseMode.HTML, + ) + return + + if args: + if args[0] == "json": + jam = time.time() + new_jam = jam + 1800 + cek = get_chat(chat.id, chat_data) + if cek.get("status"): + if jam <= int(cek.get("value")): + waktu = time.strftime( + "%H:%M:%S %d/%m/%Y", time.localtime(cek.get("value")) + ) + update.effective_message.reply_text( + "You can backup your data once every 30 minutes!\nYou can back up data again at `{}`".format( + waktu + ), + parse_mode=ParseMode.MARKDOWN, + ) + return + else: + if user.id not in DRAGONS: + put_chat(chat.id, new_jam, chat_data) + else: + if user.id not in DRAGONS: + put_chat(chat.id, new_jam, chat_data) + backups = "" + for users in getfban: + getuserinfo = sql.get_all_fban_users_target(fed_id, users) + json_parser = { + "user_id": users, + "first_name": getuserinfo["first_name"], + "last_name": getuserinfo["last_name"], + "user_name": getuserinfo["user_name"], + "reason": getuserinfo["reason"], + } + backups += json.dumps(json_parser) + backups += "\n" + with BytesIO(str.encode(backups)) as output: + output.name = "Exon_fbanned_users.json" + update.effective_message.reply_document( + document=output, + filename="Exon_fbanned_users.json", + caption="Total {} User are blocked by the Federation {}.".format( + len(getfban), info["fname"] + ), + ) + return + elif args[0] == "csv": + jam = time.time() + new_jam = jam + 1800 + cek = get_chat(chat.id, chat_data) + if cek.get("status"): + if jam <= int(cek.get("value")): + waktu = time.strftime( + "%H:%M:%S %d/%m/%Y", time.localtime(cek.get("value")) + ) + update.effective_message.reply_text( + "You can back up data once every 30 minutes!\nYou can back up data again at `{}`".format( + waktu + ), + parse_mode=ParseMode.MARKDOWN, + ) + return + else: + if user.id not in DRAGONS: + put_chat(chat.id, new_jam, chat_data) + else: + if user.id not in DRAGONS: + put_chat(chat.id, new_jam, chat_data) + backups = "id,firstname,lastname,username,reason\n" + for users in getfban: + getuserinfo = sql.get_all_fban_users_target(fed_id, users) + backups += ( + "{user_id},{first_name},{last_name},{user_name},{reason}".format( + user_id=users, + first_name=getuserinfo["first_name"], + last_name=getuserinfo["last_name"], + user_name=getuserinfo["user_name"], + reason=getuserinfo["reason"], + ) + ) + backups += "\n" + with BytesIO(str.encode(backups)) as output: + output.name = "saitama_fbanned_users.csv" + update.effective_message.reply_document( + document=output, + filename="saitama_fbanned_users.csv", + caption="Total {} User are blocked by Federation {}.".format( + len(getfban), info["fname"] + ), + ) + return + + text = "<b>{} users have been banned from the federation {}:</b>\n".format( + len(getfban), info["fname"] + ) + for users in getfban: + getuserinfo = sql.get_all_fban_users_target(fed_id, users) + if getuserinfo is False: + text = "There are no users banned from the federation {}".format( + info["fname"] + ) + break + user_name = getuserinfo["first_name"] + if getuserinfo["last_name"]: + user_name += " " + getuserinfo["last_name"] + text += " • {} (<code>{}</code>)\n".format( + mention_html(users, user_name), users + ) + + try: + update.effective_message.reply_text(text, parse_mode=ParseMode.HTML) + except: + jam = time.time() + new_jam = jam + 1800 + cek = get_chat(chat.id, chat_data) + if cek.get("status"): + if jam <= int(cek.get("value")): + waktu = time.strftime( + "%H:%M:%S %d/%m/%Y", time.localtime(cek.get("value")) + ) + update.effective_message.reply_text( + "You can back up data once every 30 minutes!\nYou can back up data again at `{}`".format( + waktu + ), + parse_mode=ParseMode.MARKDOWN, + ) + return + else: + if user.id not in DRAGONS: + put_chat(chat.id, new_jam, chat_data) + else: + if user.id not in DRAGONS: + put_chat(chat.id, new_jam, chat_data) + cleanr = re.compile("<.*?>") + cleantext = re.sub(cleanr, "", text) + with BytesIO(str.encode(cleantext)) as output: + output.name = "fbanlist.txt" + update.effective_message.reply_document( + document=output, + filename="fbanlist.txt", + caption="The following is a list of users who are currently fbanned in the Federation {}.".format( + info["fname"] + ), + ) + + +@run_async +def fed_notif(update: Update, context: CallbackContext): + bot, args = context.bot, context.args + chat = update.effective_chat + user = update.effective_user + msg = update.effective_message + fed_id = sql.get_fed_id(chat.id) + + if not fed_id: + update.effective_message.reply_text( + "This group is not a part of any federation!" + ) + return + + if args: + if args[0] in ("yes", "on"): + sql.set_feds_setting(user.id, True) + msg.reply_text( + "Reporting Federation back up! Every user who is fban / unfban you will be notified via PM." + ) + elif args[0] in ("no", "off"): + sql.set_feds_setting(user.id, False) + msg.reply_text( + "Reporting Federation has stopped! Every user who is fban / unfban you will not be notified via PM." + ) + else: + msg.reply_text("Please enter `on`/`off`", parse_mode="markdown") + else: + getreport = sql.user_feds_report(user.id) + msg.reply_text( + "Your current Federation report preferences: `{}`".format(getreport), + parse_mode="markdown", + ) + + +@run_async +def fed_chats(update: Update, context: CallbackContext): + bot, args = context.bot, context.args + chat = update.effective_chat + user = update.effective_user + + if chat.type == "private": + send_message( + update.effective_message, + "This command is specific to the group, not to our pm!", + ) + return + + fed_id = sql.get_fed_id(chat.id) + info = sql.get_fed_info(fed_id) + + if not fed_id: + update.effective_message.reply_text( + "This group is not a part of any federation!" + ) + return + + if is_user_fed_admin(fed_id, user.id) is False: + update.effective_message.reply_text("Only federation admins can do this!") + return + + getlist = sql.all_fed_chats(fed_id) + if len(getlist) == 0: + update.effective_message.reply_text( + "No users are fbanned from the federation {}".format(info["fname"]), + parse_mode=ParseMode.HTML, + ) + return + + text = "<b>New chat joined the federation {}:</b>\n".format(info["fname"]) + for chats in getlist: + try: + chat_name = dispatcher.bot.getChat(chats).title + except Unauthorized: + sql.chat_leave_fed(chats) + LOGGER.info( + "Chat {} has leave fed {} because I was kicked".format( + chats, info["fname"] + ) + ) + continue + text += " ? {} (<code>{}</code>)\n".format(chat_name, chats) + + try: + update.effective_message.reply_text(text, parse_mode=ParseMode.HTML) + except: + cleanr = re.compile("<.*?>") + cleantext = re.sub(cleanr, "", text) + with BytesIO(str.encode(cleantext)) as output: + output.name = "fedchats.txt" + update.effective_message.reply_document( + document=output, + filename="fedchats.txt", + caption="Here is a list of all the chats that joined the federation {}.".format( + info["fname"] + ), + ) + + +@run_async +def fed_import_bans(update: Update, context: CallbackContext): + bot, chat_data = context.bot, context.chat_data + chat = update.effective_chat + user = update.effective_user + msg = update.effective_message + + if chat.type == "private": + send_message( + update.effective_message, + "This command is specific to the group, not to our pm!", + ) + return + + fed_id = sql.get_fed_id(chat.id) + sql.get_fed_info(fed_id) + getfed = sql.get_fed_info(fed_id) + + if not fed_id: + update.effective_message.reply_text( + "This group is not a part of any federation!" + ) + return + + if is_user_fed_owner(fed_id, user.id) is False: + update.effective_message.reply_text("Only Federation owners can do this!") + return + + if msg.reply_to_message and msg.reply_to_message.document: + jam = time.time() + new_jam = jam + 1800 + cek = get_chat(chat.id, chat_data) + if cek.get("status"): + if jam <= int(cek.get("value")): + waktu = time.strftime( + "%H:%M:%S %d/%m/%Y", time.localtime(cek.get("value")) + ) + update.effective_message.reply_text( + "You can get your data once every 30 minutes!\nYou can get data again at `{}`".format( + waktu + ), + parse_mode=ParseMode.MARKDOWN, + ) + return + else: + if user.id not in DRAGONS: + put_chat(chat.id, new_jam, chat_data) + else: + if user.id not in DRAGONS: + put_chat(chat.id, new_jam, chat_data) + # if int(int(msg.reply_to_message.document.file_size)/1024) >= 200: + # msg.reply_text("This file is too big!") + # return + success = 0 + failed = 0 + try: + file_info = bot.get_file(msg.reply_to_message.document.file_id) + except BadRequest: + msg.reply_text( + "Try downloading and re-uploading the file, this one seems broken!" + ) + return + fileformat = msg.reply_to_message.document.file_name.split(".")[-1] + if fileformat == "json": + multi_fed_id = [] + multi_import_userid = [] + multi_import_firstname = [] + multi_import_lastname = [] + multi_import_username = [] + multi_import_reason = [] + with BytesIO() as file: + file_info.download(out=file) + file.seek(0) + reading = file.read().decode("UTF-8") + splitting = reading.split("\n") + for x in splitting: + if x == "": + continue + try: + data = json.loads(x) + except json.decoder.JSONDecodeError as err: + failed += 1 + continue + try: + import_userid = int(data["user_id"]) # Make sure it int + import_firstname = str(data["first_name"]) + import_lastname = str(data["last_name"]) + import_username = str(data["user_name"]) + import_reason = str(data["reason"]) + except ValueError: + failed += 1 + continue + # Checking user + if int(import_userid) == bot.id: + failed += 1 + continue + if is_user_fed_owner(fed_id, import_userid) is True: + failed += 1 + continue + if is_user_fed_admin(fed_id, import_userid) is True: + failed += 1 + continue + if str(import_userid) == str(OWNER_ID): + failed += 1 + continue + if int(import_userid) in DRAGONS: + failed += 1 + continue + if int(import_userid) in TIGERS: + failed += 1 + continue + if int(import_userid) in WOLVES: + failed += 1 + continue + multi_fed_id.append(fed_id) + multi_import_userid.append(str(import_userid)) + multi_import_firstname.append(import_firstname) + multi_import_lastname.append(import_lastname) + multi_import_username.append(import_username) + multi_import_reason.append(import_reason) + success += 1 + sql.multi_fban_user( + multi_fed_id, + multi_import_userid, + multi_import_firstname, + multi_import_lastname, + multi_import_username, + multi_import_reason, + ) + text = "Blocks were successfully imported. {} people are blocked.".format( + success + ) + if failed >= 1: + text += " {} Failed to import.".format(failed) + get_fedlog = sql.get_fed_log(fed_id) + if get_fedlog: + if eval(get_fedlog): + teks = "Fed *{}* has successfully imported data. {} banned.".format( + getfed["fname"], success + ) + if failed >= 1: + teks += " {} Failed to import.".format(failed) + bot.send_message(get_fedlog, teks, parse_mode="markdown") + elif fileformat == "csv": + multi_fed_id = [] + multi_import_userid = [] + multi_import_firstname = [] + multi_import_lastname = [] + multi_import_username = [] + multi_import_reason = [] + file_info.download( + "fban_{}.csv".format(msg.reply_to_message.document.file_id) + ) + with open( + "fban_{}.csv".format(msg.reply_to_message.document.file_id), + "r", + encoding="utf8", + ) as csvFile: + reader = csv.reader(csvFile) + for data in reader: + try: + import_userid = int(data[0]) # Make sure it int + import_firstname = str(data[1]) + import_lastname = str(data[2]) + import_username = str(data[3]) + import_reason = str(data[4]) + except ValueError: + failed += 1 + continue + # Checking user + if int(import_userid) == bot.id: + failed += 1 + continue + if is_user_fed_owner(fed_id, import_userid) is True: + failed += 1 + continue + if is_user_fed_admin(fed_id, import_userid) is True: + failed += 1 + continue + if str(import_userid) == str(OWNER_ID): + failed += 1 + continue + if int(import_userid) in DRAGONS: + failed += 1 + continue + if int(import_userid) in TIGERS: + failed += 1 + continue + if int(import_userid) in WOLVES: + failed += 1 + continue + multi_fed_id.append(fed_id) + multi_import_userid.append(str(import_userid)) + multi_import_firstname.append(import_firstname) + multi_import_lastname.append(import_lastname) + multi_import_username.append(import_username) + multi_import_reason.append(import_reason) + success += 1 + # t = ThreadWithReturnValue(target=sql.fban_user, args=(fed_id, str(import_userid), import_firstname, import_lastname, import_username, import_reason,)) + # t.start() + sql.multi_fban_user( + multi_fed_id, + multi_import_userid, + multi_import_firstname, + multi_import_lastname, + multi_import_username, + multi_import_reason, + ) + csvFile.close() + os.remove("fban_{}.csv".format(msg.reply_to_message.document.file_id)) + text = "Files were imported successfully. {} people banned.".format(success) + if failed >= 1: + text += " {} Failed to import.".format(failed) + get_fedlog = sql.get_fed_log(fed_id) + if get_fedlog: + if eval(get_fedlog): + teks = "Fed *{}* has successfully imported data. {} banned.".format( + getfed["fname"], success + ) + if failed >= 1: + teks += " {} Failed to import.".format(failed) + bot.send_message(get_fedlog, teks, parse_mode="markdown") + else: + send_message(update.effective_message, "This file is not supported.") + return + send_message(update.effective_message, text) + + +@run_async +def del_fed_button(update: Update, context: CallbackContext): + query = update.callback_query + query.message.chat.id + fed_id = query.data.split("_")[1] + + if fed_id == "cancel": + query.message.edit_text("Federation deletion cancelled") + return + + getfed = sql.get_fed_info(fed_id) + if getfed: + delete = sql.del_fed(fed_id) + if delete: + query.message.edit_text( + "You have removed your Federation! Now all the Groups that are connected with `{}` do not have a Federation.".format( + getfed["fname"] + ), + parse_mode="markdown", + ) + + +@run_async +def fed_stat_user(update: Update, context: CallbackContext): + bot, args = context.bot, context.args + update.effective_chat + update.effective_user + msg = update.effective_message + + if args: + if args[0].isdigit(): + user_id = args[0] + else: + user_id = extract_user(msg, args) + else: + user_id = extract_user(msg, args) + + if user_id: + if len(args) == 2 and args[0].isdigit(): + fed_id = args[1] + user_name, reason, fbantime = sql.get_user_fban(fed_id, str(user_id)) + if fbantime: + fbantime = time.strftime("%d/%m/%Y", time.localtime(fbantime)) + else: + fbantime = "Unavaiable" + if user_name is False: + send_message( + update.effective_message, + "Fed {} not found!".format(fed_id), + parse_mode="markdown", + ) + return + if user_name == "" or user_name is None: + user_name = "He/she" + if not reason: + send_message( + update.effective_message, + "{} is not banned in this federation!".format(user_name), + ) + else: + teks = "{} banned in this federation because:\n`{}`\n*Banned at:* `{}`".format( + user_name, reason, fbantime + ) + send_message(update.effective_message, teks, parse_mode="markdown") + return + user_name, fbanlist = sql.get_user_fbanlist(str(user_id)) + if user_name == "": + try: + user_name = bot.get_chat(user_id).first_name + except BadRequest: + user_name = "He/she" + if user_name == "" or user_name is None: + user_name = "He/she" + if len(fbanlist) == 0: + send_message( + update.effective_message, + "{} is not banned in any federation!".format(user_name), + ) + return + else: + teks = "{} has been banned in this federation:\n".format(user_name) + for x in fbanlist: + teks += "- `{}`: {}\n".format(x[0], x[1][:20]) + teks += "\nIf you want to find out more about the reasons for Fedban specifically, use /fbanstat <FedID>" + send_message(update.effective_message, teks, parse_mode="markdown") + + elif not msg.reply_to_message and not args: + user_id = msg.from_user.id + user_name, fbanlist = sql.get_user_fbanlist(user_id) + if user_name == "": + user_name = msg.from_user.first_name + if len(fbanlist) == 0: + send_message( + update.effective_message, + "{} is not banned in any federation!".format(user_name), + ) + else: + teks = "{} has been banned in this federation:\n".format(user_name) + for x in fbanlist: + teks += "- `{}`: {}\n".format(x[0], x[1][:20]) + teks += "\nIf you want to find out more about the reasons for Fedban specifically, use /fbanstat <FedID>" + send_message(update.effective_message, teks, parse_mode="markdown") + + else: + fed_id = args[0] + fedinfo = sql.get_fed_info(fed_id) + if not fedinfo: + send_message(update.effective_message, "Fed {} not found!".format(fed_id)) + return + name, reason, fbantime = sql.get_user_fban(fed_id, msg.from_user.id) + if fbantime: + fbantime = time.strftime("%d/%m/%Y", time.localtime(fbantime)) + else: + fbantime = "Unavaiable" + if not name: + name = msg.from_user.first_name + if not reason: + send_message( + update.effective_message, + "{} is not banned in this federation".format(name), + ) + return + send_message( + update.effective_message, + "{} banned in this federation because:\n`{}`\n*Banned at:* `{}`".format( + name, reason, fbantime + ), + parse_mode="markdown", + ) + + +@run_async +def set_fed_log(update: Update, context: CallbackContext): + args = context.args + chat = update.effective_chat + user = update.effective_user + update.effective_message + + if chat.type == "private": + send_message( + update.effective_message, + "This command is specific to the group, not to our pm!", + ) + return + + if args: + fedinfo = sql.get_fed_info(args[0]) + if not fedinfo: + send_message(update.effective_message, "This Federation does not exist!") + return + isowner = is_user_fed_owner(args[0], user.id) + if not isowner: + send_message( + update.effective_message, + "Only federation creator can set federation logs.", + ) + return + setlog = sql.set_fed_log(args[0], chat.id) + if setlog: + send_message( + update.effective_message, + "Federation log `{}` has been set to {}".format( + fedinfo["fname"], chat.title + ), + parse_mode="markdown", + ) + else: + send_message( + update.effective_message, "You have not provided your federated ID!" + ) + + +@run_async +def unset_fed_log(update: Update, context: CallbackContext): + args = context.args + chat = update.effective_chat + user = update.effective_user + update.effective_message + + if chat.type == "private": + send_message( + update.effective_message, + "This command is specific to the group, not to our pm!", + ) + return + + if args: + fedinfo = sql.get_fed_info(args[0]) + if not fedinfo: + send_message(update.effective_message, "This Federation does not exist!") + return + isowner = is_user_fed_owner(args[0], user.id) + if not isowner: + send_message( + update.effective_message, + "Only federation creator can set federation logs.", + ) + return + setlog = sql.set_fed_log(args[0], None) + if setlog: + send_message( + update.effective_message, + "Federation log `{}` has been revoked on {}".format( + fedinfo["fname"], chat.title + ), + parse_mode="markdown", + ) + else: + send_message( + update.effective_message, "You have not provided your federated ID!" + ) + + +@run_async +def subs_feds(update: Update, context: CallbackContext): + bot, args = context.bot, context.args + chat = update.effective_chat + user = update.effective_user + update.effective_message + + if chat.type == "private": + send_message( + update.effective_message, + "This command is specific to the group, not to our pm!", + ) + return + + fed_id = sql.get_fed_id(chat.id) + fedinfo = sql.get_fed_info(fed_id) + + if not fed_id: + send_message(update.effective_message, "This group is not in any federation!") + return + + if is_user_fed_owner(fed_id, user.id) is False: + send_message(update.effective_message, "Only fed owner can do this!") + return + + if args: + getfed = sql.search_fed_by_id(args[0]) + if getfed is False: + send_message( + update.effective_message, "Please enter a valid federation id." + ) + return + subfed = sql.subs_fed(args[0], fed_id) + if subfed: + send_message( + update.effective_message, + "Federation `{}` has subscribe the federation `{}`. Every time there is a Fedban from that federation, this federation will also banned that user.".format( + fedinfo["fname"], getfed["fname"] + ), + parse_mode="markdown", + ) + get_fedlog = sql.get_fed_log(args[0]) + if get_fedlog: + if int(get_fedlog) != int(chat.id): + bot.send_message( + get_fedlog, + "Federation `{}` has subscribe the federation `{}`".format( + fedinfo["fname"], getfed["fname"] + ), + parse_mode="markdown", + ) + else: + send_message( + update.effective_message, + "Federation `{}` already subscribe the federation `{}`.".format( + fedinfo["fname"], getfed["fname"] + ), + parse_mode="markdown", + ) + else: + send_message( + update.effective_message, "You have not provided your federated ID!" + ) + + +@run_async +def unsubs_feds(update: Update, context: CallbackContext): + bot, args = context.bot, context.args + chat = update.effective_chat + user = update.effective_user + update.effective_message + + if chat.type == "private": + send_message( + update.effective_message, + "This command is specific to the group, not to our pm!", + ) + return + + fed_id = sql.get_fed_id(chat.id) + fedinfo = sql.get_fed_info(fed_id) + + if not fed_id: + send_message(update.effective_message, "This group is not in any federation!") + return + + if is_user_fed_owner(fed_id, user.id) is False: + send_message(update.effective_message, "Only fed owner can do this!") + return + + if args: + getfed = sql.search_fed_by_id(args[0]) + if getfed is False: + send_message( + update.effective_message, "Please enter a valid federation id." + ) + return + subfed = sql.unsubs_fed(args[0], fed_id) + if subfed: + send_message( + update.effective_message, + "Federation `{}` now unsubscribe fed `{}`.".format( + fedinfo["fname"], getfed["fname"] + ), + parse_mode="markdown", + ) + get_fedlog = sql.get_fed_log(args[0]) + if get_fedlog: + if int(get_fedlog) != int(chat.id): + bot.send_message( + get_fedlog, + "Federation `{}` has unsubscribe fed `{}`.".format( + fedinfo["fname"], getfed["fname"] + ), + parse_mode="markdown", + ) + else: + send_message( + update.effective_message, + "Federation `{}` is not subscribing `{}`.".format( + fedinfo["fname"], getfed["fname"] + ), + parse_mode="markdown", + ) + else: + send_message( + update.effective_message, "You have not provided your federated ID!" + ) + + +@run_async +def get_myfedsubs(update: Update, context: CallbackContext): + context.args + chat = update.effective_chat + user = update.effective_user + update.effective_message + + if chat.type == "private": + send_message( + update.effective_message, + "This command is specific to the group, not to our pm!", + ) + return + + fed_id = sql.get_fed_id(chat.id) + fedinfo = sql.get_fed_info(fed_id) + + if not fed_id: + send_message(update.effective_message, "This group is not in any federation!") + return + + if is_user_fed_owner(fed_id, user.id) is False: + send_message(update.effective_message, "Only fed owner can do this!") + return + + try: + getmy = sql.get_mysubs(fed_id) + except: + getmy = [] + + if len(getmy) == 0: + send_message( + update.effective_message, + "Federation `{}` is not subscribing any federation.".format( + fedinfo["fname"] + ), + parse_mode="markdown", + ) + return + else: + listfed = "Federation `{}` is subscribing federation:\n".format( + fedinfo["fname"] + ) + for x in getmy: + listfed += "- `{}`\n".format(x) + listfed += ( + "\nTo get fed info `/fedinfo <fedid>`. To unsubscribe `/unsubfed <fedid>`." + ) + send_message(update.effective_message, listfed, parse_mode="markdown") + + +@run_async +def get_myfeds_list(update: Update, context: CallbackContext): + update.effective_chat + user = update.effective_user + update.effective_message + + fedowner = sql.get_user_owner_fed_full(user.id) + if fedowner: + text = "*You are owner of feds:\n*" + for f in fedowner: + text += "- `{}`: *{}*\n".format(f["fed_id"], f["fed"]["fname"]) + else: + text = "*You are not have any feds!*" + send_message(update.effective_message, text, parse_mode="markdown") + + +def is_user_fed_admin(fed_id, user_id): + fed_admins = sql.all_fed_users(fed_id) + if fed_admins is False: + return False + if int(user_id) in fed_admins or int(user_id) == OWNER_ID: + return True + else: + return False + + +def is_user_fed_owner(fed_id, user_id): + getsql = sql.get_fed_info(fed_id) + if getsql is False: + return False + getfedowner = eval(getsql["fusers"]) + if getfedowner is None or getfedowner is False: + return False + getfedowner = getfedowner["owner"] + if str(user_id) == getfedowner or int(user_id) == OWNER_ID: + return True + else: + return False + + +# There's no handler for this yet, but updating for v12 in case its used +@run_async +def welcome_fed(update: Update, context: CallbackContext): + bot, args = context.bot, context.args + chat = update.effective_chat + user = update.effective_user + fed_id = sql.get_fed_id(chat.id) + fban, fbanreason, fbantime = sql.get_fban_user(fed_id, user.id) + if fban: + update.effective_message.reply_text( + "This user is banned in current federation! I will remove him." + ) + bot.kick_chat_member(chat.id, user.id) + return True + else: + return False + + +def __stats__(): + all_fbanned = sql.get_all_fban_users_global() + all_feds = sql.get_all_feds_users_global() + return "•➥ {} banned users across {} Federations".format( + len(all_fbanned), len(all_feds) + ) + + +def __user_info__(user_id, chat_id): + fed_id = sql.get_fed_id(chat_id) + if fed_id: + fban, fbanreason, fbantime = sql.get_fban_user(fed_id, user_id) + info = sql.get_fed_info(fed_id) + infoname = info["fname"] + + if int(info["owner"]) == user_id: + text = "Federation owner of: <b>{}</b>.".format(infoname) + elif is_user_fed_admin(fed_id, user_id): + text = "Federation admin of: <b>{}</b>.".format(infoname) + + elif fban: + text = "Federation banned: <b>Yes</b>" + text += "\n<b>Reason:</b> {}".format(fbanreason) + else: + text = "Federation banned: <b>No</b>" + else: + text = "" + return text + + +# Temporary data +def put_chat(chat_id, value, chat_data): + # print(chat_data) + if value is False: + status = False + else: + status = True + chat_data[chat_id] = {"federation": {"status": status, "value": value}} + + +def get_chat(chat_id, chat_data): + # print(chat_data) + try: + value = chat_data[chat_id]["federation"] + return value + except KeyError: + return {"status": False, "value": False} + + +@run_async +def fed_owner_help(update: Update, context: CallbackContext): + update.effective_message.reply_text( + """*🎖 Fed Owner Only:* + + • `/newfed <fed_name>`*:* Creates a Federation, One allowed per user + • `/renamefed <fed_id> <new_fed_name>`*:* Renames the fed id to a new name + • `/delfed <fed_id>`*:* Delete a Federation, and any information related to it. Will not cancel blocked users + • `/fpromote <user>`*:* Assigns the user as a federation admin. Enables all commands for the user under `Fed Admins` + • `/fdemote <user>`*:* Drops the User from the admin Federation to a normal User + • `/subfed <fed_id>`*:* Subscribes to a given fed ID, bans from that subscribed fed will also happen in your fed + • `/unsubfed <fed_id>`*:* Unsubscribes to a given fed ID + • `/setfedlog <fed_id>`*:* Sets the group as a fed log report base for the federation + • `/unsetfedlog <fed_id>`*:* Removed the group as a fed log report base for the federation + • `/fbroadcast <message>`*:* Broadcasts a messages to all groups that have joined your fed + • `/fedsubs`*:* Shows the feds your group is subscribed to `(broken rn)`""", + parse_mode=ParseMode.MARKDOWN, + ) + + +@run_async +def fed_admin_help(update: Update, context: CallbackContext): + update.effective_message.reply_text( + """*🚨 Fed Admins:* + + • `/fban <user> <reason>`*:* Fed bans a user + • `/unfban <user> <reason>`*:* Removes a user from a fed ban + • `/fedinfo <fed_id>`*:* Information about the specified Federation + • `/joinfed <fed_id>`*:* Join the current chat to the Federation. Only chat owners can do this. Every chat can only be in one Federation + • `/leavefed <fed_id>`*:* Leave the Federation given. Only chat owners can do this + • `/setfrules <rules>`*:* Arrange Federation rules + • `/fedadmins`*:* Show Federation admin + • `/fbanlist`*:* Displays all users who are victimized at the Federation at this time + • `/fedchats`*:* Get all the chats that are connected in the Federation + • `/chatfed `*:* See the Federation in the current chat\n""", + parse_mode=ParseMode.MARKDOWN, + ) + + +@run_async +def fed_user_help(update: Update, context: CallbackContext): + update.effective_message.reply_text( + """*🎩 ᴀɴʏ ᴜsᴇʀ:* + + • `/fbanstat`*:* Shows if you/or the user you are replying to or their username is fbanned somewhere or not + • `/fednotif <on/off>`*:* Federation settings not in PM when there are users who are fbaned/unfbanned + • `/frules`*:* See Federation regulations\n""", + parse_mode=ParseMode.MARKDOWN, + ) + + +__mod_name__ = "𝙵ᴇᴅs" + +__help__ = """ +*ғᴇᴅᴇʀᴀᴛɪᴏɴ* +`ᴇᴠᴇʀʏᴛʜɪɴɢ ɪs ғᴜɴ, ᴜɴᴛɪʟ ᴀ sᴘᴀᴍᴍᴇʀ sᴛᴀʀᴛs ᴇɴᴛᴇʀɪɴɢ ʏᴏᴜʀ ɢʀᴏᴜᴘ, ᴀɴᴅ ʏᴏᴜ ʜᴀᴠᴇ ᴛᴏ ʙʟᴏᴄᴋ ɪᴛ. ᴛʜᴇɴ ʏᴏᴜ ɴᴇᴇᴅ ᴛᴏ sᴛᴀʀᴛ ʙᴀɴɴɪɴɢ ᴍᴏʀᴇ, ᴀɴᴅ ᴍᴏʀᴇ, ᴀɴᴅ ɪᴛ ʜᴜʀᴛs.` +`ʙᴜᴛ ᴛʜᴇɴ ʏᴏᴜ ʜᴀᴠᴇ ᴍᴀɴʏ ɢʀᴏᴜᴘs, ᴀɴᴅ ʏᴏᴜ ᴅᴏɴ'ᴛ ᴡᴀɴᴛ ᴛʜɪs sᴘᴀᴍᴍᴇʀ ᴛᴏ ʙᴇ ɪɴ ᴏɴᴇ ᴏғ ʏᴏᴜʀ ɢʀᴏᴜᴘs - ʜᴏᴡ ᴄᴀɴ ʏᴏᴜ ᴅᴇᴀʟ? ᴅᴏ ʏᴏᴜ ʜᴀᴠᴇ ᴛᴏ ᴍᴀɴᴜᴀʟʟʏ ʙʟᴏᴄᴋ ɪᴛ, in ᴀʟʟ ʏᴏᴜʀ groups?`\n + +*ɴᴏ ʟᴏɴɢᴇʀ!* `ᴡɪᴛʜ ғᴇᴅᴇʀᴀᴛɪᴏɴ, ʏᴏᴜ ᴄᴀɴ ᴍᴀᴋᴇ ᴀ ʙᴀɴ ɪɴ ᴏɴᴇ ᴄʜᴀᴛ ᴏᴠᴇʀʟᴀᴘ ᴡɪᴛʜ ᴀʟʟ ᴏᴛʜᴇʀ ᴄʜᴀᴛs.`\n +`ʏᴏᴜ ᴄᴀɴ ᴇᴠᴇɴ ᴅᴇsɪɢɴᴀᴛᴇ ғᴇᴅᴇʀᴀᴛɪᴏɴ ᴀᴅᴍɪɴs, sᴏ ʏᴏᴜʀ ᴛʀᴜsᴛᴇᴅ ᴀᴅᴍɪɴ ᴄᴀɴ ʙᴀɴ ᴀʟʟ ᴛʜᴇ sᴘᴀᴍᴍᴇʀs ғʀᴏᴍ ᴄʜᴀᴛs ʏᴏᴜ ᴡᴀɴᴛ ᴛᴏ ᴘʀᴏᴛᴇᴄᴛ`.\n + +*ᴄᴏᴍᴍᴀɴᴅs:*\n +`ғᴇᴅs ᴀʀᴇ ɴᴏᴡ ᴅɪᴠɪᴅᴇᴅ ɪɴᴛᴏ 3 sᴇᴄᴛɪᴏɴs ғᴏʀ ʏᴏᴜʀ ᴇᴀsᴇ.` + +•➥ /fedownerhelp *:* `ᴘʀᴏᴠɪᴅᴇs ʜᴇʟᴘ for fed ᴄʀᴇᴀᴛɪᴏɴ ᴀɴᴅ ᴏᴡɴᴇʀ ᴏɴʟʏ ᴄᴏᴍᴍᴀɴᴅs` + +•➥ /fedadminhelp *:* `ᴘʀᴏᴠɪᴅᴇs ʜᴇʟᴘ for fed ᴀᴅᴍɪɴɪsᴛʀᴀᴛɪᴏɴ ᴄᴏᴍᴍᴀɴᴅs` + +•➥ /feduserhelp *:* `ᴘʀᴏᴠɪᴅᴇs ʜᴇʟᴘ ғᴏʀ ᴄᴏᴍᴍᴀɴᴅs ᴀɴʏᴏɴᴇ ᴄᴀɴ ᴜsᴇ` + +""" + +NEW_FED_HANDLER = CommandHandler("newfed", new_fed) +DEL_FED_HANDLER = CommandHandler("delfed", del_fed) +RENAME_FED = CommandHandler("renamefed", rename_fed) +JOIN_FED_HANDLER = CommandHandler("joinfed", join_fed) +LEAVE_FED_HANDLER = CommandHandler("leavefed", leave_fed) +PROMOTE_FED_HANDLER = CommandHandler("fpromote", user_join_fed) +DEMOTE_FED_HANDLER = CommandHandler("fdemote", user_demote_fed) +INFO_FED_HANDLER = CommandHandler("fedinfo", fed_info) +BAN_FED_HANDLER = DisableAbleCommandHandler("fban", fed_ban) +UN_BAN_FED_HANDLER = CommandHandler("unfban", unfban) +FED_BROADCAST_HANDLER = CommandHandler("fbroadcast", fed_broadcast) +FED_SET_RULES_HANDLER = CommandHandler("setfrules", set_frules) +FED_GET_RULES_HANDLER = CommandHandler("frules", get_frules) +FED_CHAT_HANDLER = CommandHandler("chatfed", fed_chat) +FED_ADMIN_HANDLER = CommandHandler("fedadmins", fed_admin) +FED_USERBAN_HANDLER = CommandHandler("fbanlist", fed_ban_list) +FED_NOTIF_HANDLER = CommandHandler("fednotif", fed_notif) +FED_CHATLIST_HANDLER = CommandHandler("fedchats", fed_chats) +FED_IMPORTBAN_HANDLER = CommandHandler("importfbans", fed_import_bans) +FEDSTAT_USER = DisableAbleCommandHandler(["fedstat", "fbanstat"], fed_stat_user) +SET_FED_LOG = CommandHandler("setfedlog", set_fed_log) +UNSET_FED_LOG = CommandHandler("unsetfedlog", unset_fed_log) +SUBS_FED = CommandHandler("subfed", subs_feds) +UNSUBS_FED = CommandHandler("unsubfed", unsubs_feds) +MY_SUB_FED = CommandHandler("fedsubs", get_myfedsubs) +MY_FEDS_LIST = CommandHandler("myfeds", get_myfeds_list) +DELETEBTN_FED_HANDLER = CallbackQueryHandler(del_fed_button, pattern=r"rmfed_") +FED_OWNER_HELP_HANDLER = CommandHandler("fedownerhelp", fed_owner_help) +FED_ADMIN_HELP_HANDLER = CommandHandler("fedadminhelp", fed_admin_help) +FED_USER_HELP_HANDLER = CommandHandler("feduserhelp", fed_user_help) + +dispatcher.add_handler(NEW_FED_HANDLER) +dispatcher.add_handler(DEL_FED_HANDLER) +dispatcher.add_handler(RENAME_FED) +dispatcher.add_handler(JOIN_FED_HANDLER) +dispatcher.add_handler(LEAVE_FED_HANDLER) +dispatcher.add_handler(PROMOTE_FED_HANDLER) +dispatcher.add_handler(DEMOTE_FED_HANDLER) +dispatcher.add_handler(INFO_FED_HANDLER) +dispatcher.add_handler(BAN_FED_HANDLER) +dispatcher.add_handler(UN_BAN_FED_HANDLER) +dispatcher.add_handler(FED_BROADCAST_HANDLER) +dispatcher.add_handler(FED_SET_RULES_HANDLER) +dispatcher.add_handler(FED_GET_RULES_HANDLER) +dispatcher.add_handler(FED_CHAT_HANDLER) +dispatcher.add_handler(FED_ADMIN_HANDLER) +dispatcher.add_handler(FED_USERBAN_HANDLER) +dispatcher.add_handler(FED_NOTIF_HANDLER) +dispatcher.add_handler(FED_CHATLIST_HANDLER) +# dispatcher.add_handler(FED_IMPORTBAN_HANDLER) +dispatcher.add_handler(FEDSTAT_USER) +dispatcher.add_handler(SET_FED_LOG) +dispatcher.add_handler(UNSET_FED_LOG) +dispatcher.add_handler(SUBS_FED) +dispatcher.add_handler(UNSUBS_FED) +dispatcher.add_handler(MY_SUB_FED) +dispatcher.add_handler(MY_FEDS_LIST) +dispatcher.add_handler(DELETEBTN_FED_HANDLER) +dispatcher.add_handler(FED_OWNER_HELP_HANDLER) +dispatcher.add_handler(FED_ADMIN_HELP_HANDLER) +dispatcher.add_handler(FED_USER_HELP_HANDLER) diff --git a/Exon/modules/forcesubs.py b/Exon/modules/forcesubs.py new file mode 100644 index 00000000..b71eb5f8 --- /dev/null +++ b/Exon/modules/forcesubs.py @@ -0,0 +1,225 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import logging +import time + +from pyrogram import filters +from pyrogram.errors.exceptions.bad_request_400 import ( + ChatAdminRequired, + PeerIdInvalid, + UsernameNotOccupied, + UserNotParticipant, +) +from pyrogram.types import ChatPermissions, InlineKeyboardButton, InlineKeyboardMarkup + +from Exon import BOT_USERNAME as asau +from Exon import DRAGONS as SUDO_USERS +from Exon import pgram as pbot +from Exon.modules.sql import forceSubscribe_sql as sql + +logging.basicConfig(level=logging.INFO) + +static_data_filter = filters.create( + lambda _, __, query: query.data == "onUnMuteRequest" +) + + +@pbot.on_callback_query(static_data_filter) +def _onUnMuteRequest(client, cb): + user_id = cb.from_user.id + chat_id = cb.message.chat.id + if chat_db := sql.fs_settings(chat_id): + channel = chat_db.channel + chat_member = client.get_chat_member(chat_id, user_id) + if chat_member.restricted_by: + if chat_member.restricted_by.id == (client.get_me()).id: + try: + client.get_chat_member(channel, user_id) + client.unban_chat_member(chat_id, user_id) + cb.message.delete() + # if cb.message.reply_to_message.from_user.id == user_id: + # cb.message.delete() + except UserNotParticipant: + client.answer_callback_query( + cb.id, + text=f"❗ ᴊᴏɪɴ ᴏᴜʀ @{channel} ᴄʜᴀɴɴᴇʟ ᴀɴᴅ ᴘʀᴇss 'ᴜɴᴍᴜᴛᴇ ᴍᴇ ʙᴜᴛᴛᴏɴ.", + show_alert=True, + ) + else: + client.answer_callback_query( + cb.id, + text="❗ ʏᴏᴜ ʜᴀᴠᴇ ʙᴇᴇɴ ᴍᴜᴛᴇᴅ ʙʏ ᴀᴅᴍɪɴs ᴅᴜᴇ ᴛᴏ sᴏᴍᴇ ᴏᴛʜᴇʀ ʀᴇᴀsᴏɴ.", + show_alert=True, + ) + elif ( + client.get_chat_member(chat_id, (client.get_me()).id).status + == "administrator" + ): + client.answer_callback_query( + cb.id, + text="❗ ᴡᴀʀɴɪɴɢ! ᴅᴏɴ'ᴛ ᴘʀᴇss ᴛʜᴇ ʙᴜᴛᴛᴏɴ ᴡʜᴇɴ ʏᴏᴜ ᴄᴀɴ ᴛᴀʟᴋ.", + show_alert=True, + ) + + else: + client.send_message( + chat_id, + f"❗ **{cb.from_user.mention} ɪs ᴛʀʏɪɴɢ ᴛᴏ ᴜɴᴍᴜᴛᴇ ʜɪᴍ/ʜᴇʀ-sᴇʟғ ʙᴜᴛ i ᴄᴀɴ'ᴛ ᴜɴᴍᴜᴛᴇ ʜɪᴍ/her ʙᴇᴄᴀᴜsᴇ ɪ ᴀᴍ ɴᴏᴛ ᴀɴ ᴀᴅᴍɪɴ ɪɴ ᴛʜɪs ᴄʜᴀᴛ ᴀᴅᴅ ᴍᴇ ᴀs ᴀᴅᴍɪɴ ᴀɢᴀɪɴ.\n", + ) + + +@pbot.on_message(filters.text & ~filters.private, group=1) +def _check_member(client, message): + chat_id = message.chat.id + if chat_db := sql.fs_settings(chat_id): + user_id = message.from_user.id + if ( + client.get_chat_member(chat_id, user_id).status + not in ("administrator", "creator") + and user_id not in SUDO_USERS + ): + channel = chat_db.channel + try: + client.get_chat_member(channel, user_id) + except UserNotParticipant: + try: + sent_message = message.reply_text( + f"ᴡᴇʟᴄᴏᴍᴇ {message.from_user.mention} 🙏 \n **ʏᴏᴜ ʜᴀᴠᴇɴ'ᴛ ᴊᴏɪɴᴇᴅ ᴏᴜʀ @{channel} ᴄʜᴀɴɴᴇʟ ʏᴇᴛ**👷 \n \nᴘʟᴇᴀsᴇ ᴊᴏɪɴ [our channel](https://t.me/{channel}) ᴀɴᴅ ʜɪᴛ ᴛʜᴇ **ᴜɴᴍᴜᴛᴇ ᴍᴇ** ʙᴜᴛᴛᴏɴ. \n \n ", + disable_web_page_preview=True, + reply_markup=InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + "ᴊᴏɪɴ ᴄʜᴀɴɴᴇʟ", + url=f"https://t.me/{channel}", + ) + ], + [ + InlineKeyboardButton( + "ᴜɴᴍᴜᴛᴇ ᴍᴇ", + callback_data="onUnMuteRequest", + ) + ], + ] + ), + ) + + client.restrict_chat_member( + chat_id, user_id, ChatPermissions(can_send_messages=False) + ) + except ChatAdminRequired: + sent_message.edit( + "😕 **ɪ ᴀᴍ ɴᴏᴛ ᴀᴅᴍɪɴ ʜᴇʀᴇ..**\n__ɢɪᴠᴇ ᴍᴇ ʙᴀɴ ᴘᴇʀᴍɪssɪᴏɴs ᴀɴᴅ ʀᴇᴛʀʏ.. \n#ᴇɴᴅɪɴɢ ғsᴜʙ...." + ) + + except ChatAdminRequired: + client.send_message( + chat_id, + text=f"😕 **I ɴᴏᴛ ᴀɴ ᴀᴅᴍɪɴ ᴏғ @{channel} ᴄʜᴀɴɴᴇʟ.**\n__ɢɪᴠᴇ me ᴀᴅᴍɪɴ ᴏғ ᴛʜᴀᴛ ᴄʜᴀɴɴᴇʟ ᴀɴᴅ ʀᴇᴛʀʏ.\n#ᴇɴᴅɪɴɢ ғsᴜʙ....", + ) + + +@pbot.on_message(filters.command(["forcesubscribe", "fsub"]) & ~filters.private) +def config(client, message): + user = client.get_chat_member(message.chat.id, message.from_user.id) + if user.status == "creator" or user.user.id in SUDO_USERS: + chat_id = message.chat.id + if len(message.command) > 1: + input_str = message.command[1] + input_str = input_str.replace("@", "") + if input_str.lower() in ("off", "no", "disable"): + sql.disapprove(chat_id) + message.reply_text("❌ **ғᴏʀᴄᴇ sᴜʙsᴄʀɪʙᴇ ɪs ᴅɪsᴀʙʟᴇᴅ sᴜᴄᴄᴇssғᴜʟʟʏ.**") + elif input_str.lower() in ("clear"): + sent_message = message.reply_text( + "**ᴜɴᴍᴜᴛɪɴɢ ᴀʟʟ ᴍᴇᴍʙᴇʀs ᴡʜᴏ ᴀʀᴇ ᴍᴜᴛᴇᴅ ʙʏ ᴍᴇ ...**" + ) + try: + for chat_member in client.get_chat_members( + message.chat.id, filter="restricted" + ): + if chat_member.restricted_by.id == (client.get_me()).id: + client.unban_chat_member(chat_id, chat_member.user.id) + time.sleep(1) + sent_message.edit("✅ **ᴜɴᴍᴜᴛᴇᴅ ᴀʟʟ ᴍᴇᴍʙᴇʀs ᴡʜᴏ ᴀʀᴇ ᴍᴜᴛᴇᴅ ʙʏ ᴍᴇ.**") + except ChatAdminRequired: + sent_message.edit( + "😕 **I ᴀᴍ ɴᴏᴛ ᴀɴ ᴀᴅᴍɪɴ ɪɴ ᴛʜɪs ᴄʜᴀᴛ.**\n__I ᴄᴀɴ'ᴛ ᴜɴᴍᴜᴛᴇ ᴍᴇᴍʙᴇʀs ʙᴇᴄᴀᴜsᴇ i ᴀᴍ ɴᴏᴛ ᴀɴ ᴀᴅᴍɪɴ ɪɴ ᴛʜɪs ᴄʜᴀᴛ ᴍᴀᴋᴇ ᴍᴇ ᴀᴅᴍɪɴ ᴡɪᴛʜ ʙᴀɴ ᴜsᴇʀ ᴘᴇʀᴍɪssɪᴏɴ.__" + ) + else: + try: + client.get_chat_member(input_str, "me") + sql.add_channel(chat_id, input_str) + message.reply_text( + f"✅ **ғᴏʀᴄᴇ sᴜʙsᴄʀɪʙᴇ ɪs ᴇɴᴀʙʟᴇᴅ**\n__ғᴏʀᴄᴇ sᴜʙsᴄʀɪʙᴇ ɪs ᴇɴᴀʙʟᴇᴅ, ᴀʟʟ ᴛʜᴇ ɢʀᴏᴜᴘ ᴍᴇᴍʙᴇʀs ʜᴀᴠᴇ ᴛᴏ sᴜʙsᴄʀɪʙᴇ ᴛʜɪs [ᴄʜᴀɴɴᴇʟ](https://t.me/{input_str}) ɪɴ ᴏʀᴅᴇʀ ᴛᴏ sᴇɴᴅ ᴍᴇssᴀɢᴇs ɪɴ ᴛʜɪs group.", + disable_web_page_preview=True, + ) + except UserNotParticipant: + message.reply_text( + f"😕 **ɴᴏᴛ ᴀɴ ᴀᴅᴍɪɴ ɪɴ ᴛʜᴇ ᴄʜᴀɴɴᴇʟ**\n__I ᴀᴍ ɴᴏᴛ ᴀɴ ᴀᴅᴍɪɴ ɪɴ ᴛʜᴇ [ᴄʜᴀɴɴᴇʟ](https://t.me/{input_str}). ᴀᴅᴅ ᴍᴇ ᴀs ᴀ ᴀᴅᴍɪɴ ɪɴ ᴏʀᴅᴇʀ ᴛᴏ ᴇɴᴀʙʟᴇ ғᴏʀᴄᴇsᴜʙsᴄʀɪʙᴇ.", + disable_web_page_preview=True, + ) + except (UsernameNotOccupied, PeerIdInvalid): + message.reply_text("❗ **ɪɴᴠᴀʟɪᴅ ᴄʜᴀɴɴᴇʟ ᴜsᴇʀɴᴀᴍᴇ.**") + except Exception as err: + message.reply_text(f"❗ **ᴇʀʀᴏʀ:** ```{err}```") + elif sql.fs_settings(chat_id): + message.reply_text( + f"✅ **ғᴏʀᴄᴇ sᴜʙsᴄʀɪʙᴇ ɪs ᴇɴᴀʙʟᴇᴅ ɪɴ ᴛʜɪs ᴄʜᴀᴛ.**\n__ғᴏʀ ᴛʜɪs [ᴄʜᴀɴɴᴇʟ](https://t.me/{sql.fs_settings(chat_id).channel})__", + disable_web_page_preview=True, + ) + else: + message.reply_text("❌ **ғᴏʀᴄᴇ sᴜʙsᴄʀɪʙᴇ ɪs ᴅɪsᴀʙʟᴇᴅ ɪɴ ᴛʜɪs ᴄʜᴀᴛ.**") + else: + message.reply_text( + "❗ **ɢʀᴏᴜᴘ ᴄʀᴇᴀᴛᴏʀ ʀᴇǫᴜɪʀᴇᴅ**\n__ʏᴏᴜ ʜᴀᴠᴇ ᴛᴏ ʙᴇ ᴛʜᴇ ɢʀᴏᴜᴘ ᴄʀᴇᴀᴛᴏʀ ᴛᴏ ᴅᴏ ᴛʜᴀᴛ.__" + ) + + +__help__ = f""" +*ғᴏʀᴄᴇ ꜱᴜʙꜱᴄʀɪʙᴇ:* + +❂ *Aʙɢ ᴄᴀɴ ᴍᴜᴛᴇ ᴍᴇᴍʙᴇʀꜱ ᴡʜᴏ ᴀʀᴇ ɴᴏᴛ ꜱᴜʙꜱᴄʀɪʙᴇᴅ ʏᴏᴜʀ ᴄʜᴀɴɴᴇʟ ᴜɴᴛɪʟ ᴛʜᴇʏ ꜱᴜʙꜱᴄʀɪʙᴇ* + +❂ `ᴡʜᴇɴ ᴇɴᴀʙʟᴇᴅ ɪ ᴡɪʟʟ ᴍᴜᴛᴇ ᴜɴꜱᴜʙꜱᴄʀɪʙᴇᴅ ᴍᴇᴍʙᴇʀꜱ ᴀɴᴅ ꜱʜᴏᴡ ᴛʜᴇᴍ ᴀ ᴜɴᴍᴜᴛᴇ ʙᴜᴛᴛᴏɴ. ᴡʜᴇɴ ᴛʜᴇʏ ᴘʀᴇꜱꜱᴇᴅ ᴛʜᴇ ʙᴜᴛᴛᴏɴ ɪ ᴡɪʟʟ ᴜɴᴍᴜᴛᴇ ᴛʜᴇᴍ` + +❂ *ꜱᴇᴛᴜᴘ* +*ᴏɴʟʏ ᴄʀᴇᴀᴛᴏʀ* +❂ [ᴀᴅᴅ ᴍᴇ ɪɴ ʏᴏᴜʀ ɢʀᴏᴜᴘ ᴀꜱ ᴀᴅᴍɪɴ](https://t.me/{asau}?startgroup=new) +❂ [ᴀᴅᴅ ᴍᴇ ɪɴ your ᴄʜᴀɴɴᴇʟ ᴀꜱ ᴀᴅᴍɪɴ](https://t.me/{asau}?startgroup=new) + +*ᴄᴏᴍᴍᴍᴀɴᴅꜱ* +❂ /fsub channel username - `ᴛᴏ ᴛᴜʀɴ ᴏɴ ᴀɴᴅ 𝚜𝚎𝚝𝚞𝚙 ᴛʜᴇ ᴄʜᴀɴɴᴇʟ.` + + 💡*ᴅᴏ ᴛʜɪꜱ ғɪʀꜱᴛ...* +❂ /fsub - `ᴛᴏ ɢᴇᴛ ᴛʜᴇ ᴄᴜʀʀᴇɴᴛ ꜱᴇᴛᴛɪɴɢꜱ.` + +❂ /fsub disable - `ᴛᴏ ᴛᴜʀɴ ᴏғ ғᴏʀᴄᴇꜱᴜʙꜱᴄʀɪʙᴇ..` + + 💡`ɪғ ʏᴏᴜ ᴅɪꜱᴀʙʟᴇ ғꜱᴜʙ`, `ʏᴏᴜ ɴᴇᴇᴅ ᴛᴏ ꜱᴇᴛ ᴀɢᴀɪɴ ғᴏʀ ᴡᴏʀᴋɪɴɢ` /fsub channel username + +❂ /fsub clear - `ᴛᴏ ᴜɴᴍᴜᴛᴇ ᴀʟʟ ᴍᴇᴍʙᴇʀꜱ ᴡʜᴏ ᴍᴜᴛᴇᴅ ʙʏ ᴍᴇ.` +""" +__mod_name__ = "𝙵-sᴜʙ" diff --git a/Exon/modules/get_common_chats.py b/Exon/modules/get_common_chats.py new file mode 100644 index 00000000..48b95214 --- /dev/null +++ b/Exon/modules/get_common_chats.py @@ -0,0 +1,77 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import os +from time import sleep + +from telegram import Update +from telegram.error import BadRequest, RetryAfter, Unauthorized +from telegram.ext import CallbackContext, CommandHandler, Filters + +from Exon import OWNER_ID, dispatcher +from Exon.modules.helper_funcs.extraction import extract_user +from Exon.modules.sql.users_sql import get_user_com_chats + + +def get_user_common_chats(update: Update, context: CallbackContext): + bot, args = context.bot, context.args + msg = update.effective_message + user = extract_user(msg, args) + if not user: + msg.reply_text("I sʜᴀʀᴇ ɴᴏ ᴄᴏᴍᴍᴏɴ ᴄʜᴀᴛs ᴡɪᴛʜ ᴛʜᴇ ᴠᴏɪᴅ.") + return + common_list = get_user_com_chats(user) + if not common_list: + msg.reply_text("ɴᴏ ᴄᴏᴍᴍᴏɴ ᴄʜᴀᴛs ᴡɪᴛʜ ᴛʜɪs ᴜsᴇʀ!") + return + name = bot.get_chat(user).first_name + text = f"<b>ᴄᴏᴍᴍᴏɴ ᴄʜᴀᴛs ᴡɪᴛʜ {name}</b>\n\n" + for chat in common_list: + try: + chat_name = bot.get_chat(chat).title + sleep(0.3) + text += f"• <code>{chat_name}</code>\n" + except (BadRequest, Unauthorized): + pass + except RetryAfter as e: + sleep(e.retry_after) + + if len(text) < 4096: + msg.reply_text(text, parse_mode="HTML") + else: + with open("common_chats.txt", "w") as f: + f.write(text) + with open("common_chats.txt", "rb") as f: + msg.reply_document(f) + os.remove("common_chats.txt") + + +COMMON_CHATS_HANDLER = CommandHandler( + "getchats", + get_user_common_chats, + filters=Filters.user(OWNER_ID), + run_async=True, +) + +dispatcher.add_handler(COMMON_CHATS_HANDLER) diff --git a/Exon/modules/gifhelp.py b/Exon/modules/gifhelp.py new file mode 100644 index 00000000..d086fd7b --- /dev/null +++ b/Exon/modules/gifhelp.py @@ -0,0 +1,15 @@ +__help__ = """ + +ɢᴇᴛ ʏᴏᴜʀ ғᴀᴠᴏᴜʀɪᴛᴇ ɢɪғ(s) ʙʏ ᴜsɪɴɢ ᴛʜɪs ᴍᴏᴅᴜʟᴇ + +**ᴜsᴀɢᴇ:** + +- /gif <query> : `ɢᴇᴛ ʏᴏᴜʀ ᴅᴇsɪʀᴇᴅ ɢɪғ` +- /gif <query> ; (limit) : `ɢᴇᴛ ᴅᴇsɪʀᴀʙʟᴇ ɴᴜᴍʙᴇʀ ᴏғ ɢɪғs` + +Example: /gif cats ; 5 : `ᴛʜɪs ᴡɪʟʟ ʀᴇᴛᴜʀɴ 5 ᴄᴀᴛ ɢɪғs ᴍᴀʏʙᴇ` + +**NOTE: ᴍᴀxɪᴍᴜᴍ ʟɪᴍɪᴛ ɪs 50** + + """ +__mod_name__ = "𝙶ɪғs" diff --git a/Exon/modules/ginfo.py b/Exon/modules/ginfo.py new file mode 100644 index 00000000..f3b9886a --- /dev/null +++ b/Exon/modules/ginfo.py @@ -0,0 +1,101 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +# thanks to inrajith for meow meow meow +import os + +from pyrogram import filters +from pyrogram.types import Message +from telegram import ParseMode + +from Exon import pgram +from Exon.core.sections import section + + +async def get_chat_info(chat, already=False): + if not already: + chat = await pgram.get_chat(chat) + chat_id = chat.id + username = chat.username + title = chat.title + type_ = chat.type + is_scam = chat.is_scam + description = chat.description + members = chat.members_count + is_restricted = chat.is_restricted + link = f"[ʟɪɴᴋ](t.me/{username})" if username else "Null" + total = "" + for m in await pgram.get_chat_members(chat.id, filter="administrators"): + total += f"• [{m.user.first_name}](tg://user?id={m.user.id})\n" + dc_id = chat.dc_id + photo_id = chat.photo.big_file_id if chat.photo else None + body = { + "ɪᴅ:": chat_id, + "ᴅᴄ:": dc_id, + "ᴛʏᴘᴇ:": type_, + "ɴᴀᴍᴇ:": [title], + "ᴜsᴇʀɴᴀᴍᴇ:": [f"@{username}" if username else "Null"], + "ᴍᴇɴᴛɪᴏɴ:": [link], + "ᴍᴇᴍʙᴇʀs:": members, + "sᴄᴀᴍ:": is_scam, + "ʀᴇsᴛʀɪᴄᴛᴇᴅ:": is_restricted, + "\nᴅᴇsᴄʀɪᴘᴛɪᴏɴ:\n\n": [description], + "\nᴀᴅᴍɪɴs ʟɪsᴛ:\n": [total], + } + + caption = section("ᴄʜᴀᴛ ɪɴғᴏ:\n", body) + return [caption, photo_id] + + +@pgram.on_message(filters.command("ginfo") & ~filters.edited) +async def chat_info_func(_, message: Message): + try: + if len(message.command) > 2: + return await message.reply_text("**ᴜsᴀɢᴇ:**/ginfo [-ID]") + + if len(message.command) == 1: + chat = message.chat.id + elif len(message.command) == 2: + chat = message.text.split(None, 1)[1] + + m = await message.reply_text("ᴘʀᴏᴄᴇssɪɴɢ...") + + info_caption, photo_id = await get_chat_info(chat) + if not photo_id: + return await m.edit( + info_caption, + disable_web_page_preview=True, + parse_mode=ParseMode.MARKDOWN, + ) + + photo = await pgram.download_media(photo_id) + await message.reply_photo( + photo, caption=info_caption, quote=False, parse_mode=ParseMode.MARKDOWN + ) + + await m.delete() + os.remove(photo) + except Exception as e: + await m.edit(e), + ParseMode.MARKDOWN diff --git a/Exon/modules/github.py b/Exon/modules/github.py new file mode 100644 index 00000000..d57169ca --- /dev/null +++ b/Exon/modules/github.py @@ -0,0 +1,72 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import aiohttp +from pyrogram import filters + +from Exon import pgram as abishnoi +from Exon.utils.errors import capture_err + +__mod_name__ = "𝙶ɪᴛʜᴜʙ" + + +@abishnoi.on_message(filters.command("git", "GitHub")) +@capture_err +async def github(_, message): + if len(message.command) != 2: + await message.reply_text("/git ᴜsᴇʀɴᴀᴍᴇ") + return + username = message.text.split(None, 1)[1] + URL = f"https://api.github.com/users/{username}" + async with aiohttp.ClientSession() as session, session.get(URL) as request: + if request.status == 404: + return await message.reply_text("404 \nᴍᴀᴛʟᴜʙ ᴋᴜs ʙɪ") + + result = await request.json() + try: + url = result["html_url"] + name = result["name"] + company = result["company"] + bio = result["bio"] + created_at = result["created_at"] + avatar_url = result["avatar_url"] + blog = result["blog"] + location = result["location"] + repositories = result["public_repos"] + followers = result["followers"] + following = result["following"] + caption = f"""**Info Of {name}** +**ᴜsᴇʀɴᴀᴍᴇ:** `{username}` +**ʙɪᴏ:** `{bio}` +**ᴘʀᴏғɪʟᴇ ʟɪɴᴋ:** [ʟɪɴᴋ]({url}) +**ᴄᴏᴍᴘᴀɴʏ:** `{company}` +**ᴄʀᴇᴀᴛᴇᴅ ᴏɴ:** `{created_at}` +**ʀᴇᴘᴏsɪᴛᴏʀɪᴇs:** `{repositories}` `ᴘᴜʙʟɪᴄ` +**ʙʟᴏɢ:** `{blog}` +**ʟᴏᴄᴀᴛɪᴏɴ:** `{location}` +**ғᴏʟʟᴏᴡᴇʀs:** `{followers}` +**ғᴏʟʟᴏᴡɪɴɢ:** `{following}`""" + except Exception as e: + print(e) + await message.reply_photo(photo=avatar_url, caption=caption) diff --git a/Exon/modules/global_bans.py b/Exon/modules/global_bans.py new file mode 100644 index 00000000..b9b90a4b --- /dev/null +++ b/Exon/modules/global_bans.py @@ -0,0 +1,599 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import html +import time +from datetime import datetime +from io import BytesIO + +from telegram import ParseMode, Update +from telegram.error import BadRequest, TelegramError, Unauthorized +from telegram.ext import CallbackContext, CommandHandler, Filters, MessageHandler +from telegram.utils.helpers import mention_html + +import Exon.modules.sql.global_bans_sql as sql +from Exon import ( + DEMONS, + DEV_USERS, + DRAGONS, + EVENT_LOGS, + OWNER_ID, + SPAMWATCH_SUPPORT_CHAT, + STRICT_GBAN, + SUPPORT_CHAT, + TIGERS, + WOLVES, + dispatcher, + sw, +) +from Exon.modules.helper_funcs.chat_status import ( + is_user_admin, + support_plus, + user_admin, +) +from Exon.modules.helper_funcs.extraction import extract_user, extract_user_and_text +from Exon.modules.helper_funcs.misc import send_to_list +from Exon.modules.sql.users_sql import get_user_com_chats + +GBAN_ENFORCE_GROUP = 6 + +GBAN_ERRORS = { + "ᴜsᴇʀ ɪs ᴀɴ ᴀᴅᴍɪɴɪsᴛʀᴀᴛᴏʀ ᴏғ ᴛʜᴇ ᴄʜᴀᴛ", + "ᴄʜᴀᴛ ɴᴏᴛ ғᴏᴜɴᴅ", + "ɴᴏᴛ ᴇɴᴏᴜɢʜ ʀɪɢʜᴛs ᴛᴏ ʀᴇsᴛʀɪᴄᴛ/ᴜɴʀᴇsᴛʀɪᴄᴛ ᴄʜᴀᴛ ᴍᴇᴍʙᴇʀ", + "ᴜsᴇʀ_ɴᴏᴛ_ᴘᴀʀᴛɪᴄɪᴘᴀɴᴛ", + "Peer_id_invalid", + "ɢʀᴏᴜᴘ ᴄʜᴀᴛ ᴡᴀs ᴅᴇᴀᴄᴛɪᴠᴀᴛᴇᴅ", + "ɴᴇᴇᴅ ᴛᴏ ʙᴇ ɪɴᴠɪᴛᴇʀ ᴏғ a ᴜsᴇʀ ᴛᴏ ᴋɪᴄᴋ ɪᴛ ғʀᴏᴍ ᴀ ʙᴀsɪᴄ ɢʀᴏᴜᴘ", + "Chat_admin_required", + "ᴏɴʟʏ ᴛʜᴇ ᴄʀᴇᴀᴛᴏʀ ᴏғ ᴀ ʙᴀsɪᴄ ɢʀᴏᴜᴘ ᴄᴀɴ ᴋɪᴄᴋ ɢʀᴏᴜᴘ ᴀᴅᴍɪɴɪsᴛʀᴀᴛᴏʀs", + "Channel_private", + "ɴᴏᴛ ɪɴ ᴛʜᴇ ᴄʜᴀᴛ", + "ᴄᴀɴ'ᴛ ʀᴇᴍᴏᴠᴇ ᴄʜᴀᴛ ᴏᴡɴᴇʀ", +} + +UNGBAN_ERRORS = { + "ᴜsᴇʀ ɪs ᴀɴ ᴀᴅᴍɪɴɪsᴛʀᴀᴛᴏʀ ᴏғ ᴛʜᴇ ᴄʜᴀᴛ", + "ᴄʜᴀᴛ ɴᴏᴛ ғᴏᴜɴᴅ", + "ɴᴏᴛ enough rights to restrict/unrestrict chat member", + "ᴜsᴇʀ_not_participant", + "ᴍᴇᴛʜᴏᴅ ɪs ᴀᴠᴀɪʟᴀʙʟᴇ ғᴏʀ sᴜᴘᴇʀɢʀᴏᴜᴘ ᴀɴᴅ ᴄʜᴀɴɴᴇʟ ᴄʜᴀᴛs ᴏɴʟʏ", + "ɴᴏᴛ in ᴛʜᴇ ᴄʜᴀᴛ", + "ᴄʜᴀɴɴᴇʟ_ᴘʀɪᴠᴀᴛᴇ", + "Chat_admin_required", + "ᴘᴇᴇʀ_id_invalid", + "ᴜsᴇʀ ɴᴏᴛ ғᴏᴜɴᴅ", +} + + +@support_plus +def gban(update: Update, context: CallbackContext): + bot, args = context.bot, context.args + message = update.effective_message + user = update.effective_user + chat = update.effective_chat + log_message = "" + + user_id, reason = extract_user_and_text(message, args) + + if not user_id: + message.reply_text( + "ʏᴏᴜ ᴅᴏɴ'ᴛ sᴇᴇᴍ ᴛᴏ ʙᴇ ʀᴇғᴇʀʀɪɴɢ ᴛᴏ ᴀ ᴜsᴇʀ ᴏʀ ᴛʜᴇ ID sᴘᴇᴄɪғɪᴇᴅ ɪs ɪɴᴄᴏʀʀᴇᴄᴛ..", + ) + return + + if int(user_id) in DEV_USERS: + message.reply_text( + "ᴛʜᴀᴛ ᴜsᴇʀ ɪs ᴍᴇᴍʙᴇʀ ᴏғ ᴏᴜʀ ғᴀᴍɪʟʏ I ᴄᴀɴ'ᴛ ᴀᴄᴛ ᴀɢᴀɪɴsᴛ ᴏᴜʀ ᴏᴡɴ.", + ) + return + + if int(user_id) in DRAGONS: + message.reply_text( + "I sᴘʏ, ᴡɪᴛʜ ᴍʏ ʟɪᴛᴛʟᴇ ᴇʏᴇ... ʙᴇsᴛғʀɪᴇɴᴅs! ᴡʜʏ ᴀʀᴇ ʏᴏᴜ ɢᴜʏs ᴛᴜʀɴɪɴɢ ᴏɴ ᴇᴀᴄʜ ᴏᴛʜᴇʀ?", + ) + return + + if int(user_id) in DEMONS: + message.reply_text( + "OOOH sᴏᴍᴇᴏɴᴇ ᴛʀʏɪɴɢ ᴛᴏ ɢʙᴀɴ ᴏᴜʀ ғʀɪᴇɴᴅᴏ! *ɢʀᴀʙs ᴘᴏᴘᴄᴏʀɴ*", + ) + return + + if int(user_id) in TIGERS: + message.reply_text("ᴛʜᴀᴛ's ᴏᴜʀ ᴄʟᴀssᴍᴀᴛᴇ! ᴛʜᴇʏ ᴄᴀɴɴᴏᴛ ʙᴇ ʙᴀɴɴᴇᴅ!") + return + + if int(user_id) in WOLVES: + message.reply_text("ᴛʜᴀᴛ's ᴀɴ EXON! ᴛʜᴇʏ ᴄᴀɴɴᴏᴛ ʙᴇ ʙᴀɴɴᴇᴅ!") + return + + if user_id == bot.id: + message.reply_text("ʏᴏᴜ ᴜʜʜ...ᴡᴀɴᴛ ᴍᴇ ᴛᴏ ᴘᴜɴᴄʜ ᴍʏsᴇʟғ?") + return + + try: + user_chat = bot.get_chat(user_id) + except BadRequest as excp: + if excp.message == "User not found": + message.reply_text("I ᴄᴀɴ'ᴛ sᴇᴇᴍ ᴛᴏ ғɪɴᴅ ᴛʜɪs ᴜsᴇʀ.") + return "" + return + + if user_chat.type != "private": + message.reply_text("ᴛʜᴀᴛ's ɴᴏᴛ ᴀ ᴜsᴇʀ!") + return + + if sql.is_user_gbanned(user_id): + + if not reason: + message.reply_text( + "ᴛʜɪs user is ᴀʟʀᴇᴀᴅʏ ɢʙᴀɴɴᴇᴅ; I'ᴅ ᴄʜᴀɴɢᴇ ᴛʜᴇ ʀᴇᴀsᴏɴ, ʙᴜᴛ ʏᴏᴜ ʜᴀᴠᴇɴ'ᴛ ɢɪᴠᴇɴ ᴍᴇ ᴏɴᴇ...", + ) + return + + old_reason = sql.update_gban_reason( + user_id, + user_chat.username or user_chat.first_name, + reason, + ) + if old_reason: + message.reply_text( + "ᴛʜɪs ᴜsᴇʀ ɪs ᴀʟʀᴇᴀᴅʏ ɢʙᴀɴɴᴇᴅ, ғᴏʀ ᴛʜᴇ ғᴏʟʟᴏᴡɪɴɢ ʀᴇᴀsᴏɴ:\n" + "<code>{}</code>\n" + "I've ɢᴏɴᴇ ᴀɴᴅ ᴜᴘᴅᴀᴛᴇᴅ it ᴡɪᴛʜ ʏᴏᴜʀ ɴᴇᴡ ʀᴇᴀsᴏɴ!".format( + html.escape(old_reason), + ), + parse_mode=ParseMode.HTML, + ) + + else: + message.reply_text( + "This ᴜsᴇʀ is ᴀʟʀᴇᴀᴅʏ ɢʙᴀɴɴᴇᴅ, ʙᴜᴛ ʜᴀᴅ ɴᴏ ʀᴇᴀsᴏɴ sᴇᴛ; I'ᴠᴇ ɢᴏɴᴇ ᴀɴᴅ ᴜᴘᴅᴀᴛᴇᴅ ɪᴛ!", + ) + + return + + message.reply_text("ᴏɴ ɪᴛ \nɢʙᴀɴ ᴅᴏɴᴇ !") + + start_time = time.time() + datetime_fmt = "%Y-%m-%dT%H:%M" + current_time = datetime.utcnow().strftime(datetime_fmt) + + if chat.type != "private": + chat_origin = "<b>{} ({})</b>\n".format(html.escape(chat.title), chat.id) + else: + chat_origin = "<b>{}</b>\n".format(chat.id) + + log_message = ( + f"#ɢʙᴀɴɴᴇᴅ\n" + f"<b>ᴏʀɪɢɪɴᴀᴛᴇᴅ ғʀᴏᴍ:</b> <code>{chat_origin}</code>\n" + f"<b>ᴀᴅᴍɪɴ:</b> {mention_html(user.id, user.first_name)}\n" + f"<b>ʙᴀɴɴᴇᴅ ᴜsᴇʀ:</b> {mention_html(user_chat.id, user_chat.first_name)}\n" + f"<b>ʙᴀɴɴᴇᴅ ᴜsᴇʀ ɪᴅ:</b> <code>{user_chat.id}</code>\n" + f"<b>ᴇᴠᴇɴᴛ sᴛᴀᴍᴘ:</b> <code>{current_time}</code>" + ) + + if reason: + if chat.type == chat.SUPERGROUP and chat.username: + log_message += f'\n<b>ʀᴇᴀsᴏɴ:</b> <a href="https://telegram.me/{chat.username}/{message.message_id}">{reason}</a>' + else: + log_message += f"\n<b>ʀᴇᴀsᴏɴ:</b> <code>{reason}</code>" + + if EVENT_LOGS: + try: + log = bot.send_message( + EVENT_LOGS, + log_message, + parse_mode=ParseMode.HTML, + disable_web_page_preview=True, + ) + except BadRequest: + log = bot.send_message( + EVENT_LOGS, + log_message + + "\n\nғᴏʀᴍᴀᴛᴛɪɴɢ ʜᴀs ʙᴇᴇɴ ᴅɪsᴀʙʟᴇᴅ ᴅᴜᴇ ᴛᴏ ᴀɴ ᴜɴᴇxᴘᴇᴄᴛᴇᴅ ᴇʀʀᴏʀ.", + ) + + else: + send_to_list(bot, DRAGONS + DEMONS, log_message, html=True) + + sql.gban_user(user_id, user_chat.username or user_chat.first_name, reason) + + chats = get_user_com_chats(user_id) + gbanned_chats = 0 + + for chat in chats: + chat_id = int(chat) + + # Check if this group has disabled gbans + if not sql.does_chat_gban(chat_id): + continue + + try: + bot.ban_chat_member(chat_id, user_id) + gbanned_chats += 1 + + except BadRequest as excp: + if excp.message not in GBAN_ERRORS: + message.reply_text(f"ᴄᴏᴜʟᴅ ɴᴏᴛ ɢʙᴀɴ ᴅᴜᴇ ᴛᴏ: {excp.message}") + if EVENT_LOGS: + bot.send_message( + EVENT_LOGS, + f"ᴄᴏᴜʟᴅ ɴᴏᴛ ɢʙᴀɴ ᴅᴜᴇ ᴛᴏ {excp.message}", + parse_mode=ParseMode.HTML, + ) + else: + send_to_list( + bot, + DRAGONS + DEMONS, + f"ᴄᴏᴜʟᴅ ɴᴏᴛ ɢʙᴀɴ ᴅᴜᴇ ᴛᴏ: {excp.message}", + ) + sql.ungban_user(user_id) + return + except TelegramError: + pass + + if EVENT_LOGS: + log.edit_text( + log_message + f"\n<b>ᴄʜᴀᴛs ᴀғғᴇᴄᴛᴇᴅ:</b> <code>{gbanned_chats}</code>", + parse_mode=ParseMode.HTML, + ) + else: + send_to_list( + bot, + DRAGONS + DEMONS, + f"ɢʙᴀɴ ᴄᴏᴍᴘʟᴇᴛᴇ! (ᴜsᴇʀ ʙᴀɴɴᴇᴅ ɪɴ <code>{gbanned_chats}</code> chats)", + html=True, + ) + + end_time = time.time() + gban_time = round((end_time - start_time), 2) + + if gban_time > 60: + gban_time = round((gban_time / 60), 2) + message.reply_text( + "ᴅᴏɴᴇ ! ɢʙᴀɴɴᴇᴅ. \nᴅᴏɴ'ᴛ ʟᴏᴠᴇ ᴀɴᴅ ᴄʀʏ ᴊᴜsᴛ ғᴜ*ᴋ & ғʟʏ", + parse_mode=ParseMode.HTML, + ) + try: + bot.send_message( + user_id, + "#ᴇᴠᴇɴᴛ" + "ʏᴏᴜ ʜᴀᴠᴇ ʙᴇᴇɴ ᴍᴀʀᴋᴇᴅ as ᴍᴀʟɪᴄɪᴏᴜs ᴀɴᴅ ᴀs sᴜᴄʜ ʜᴀᴠᴇ ʙᴇᴇɴ ʙᴀɴɴᴇᴅ ғʀᴏᴍ ᴀɴʏ ғᴜᴛᴜʀᴇ ɢʀᴏᴜᴘs ᴡᴇ ᴍᴀɴᴀɢᴇ." + f"\n<b>ʀᴇᴀsᴏɴ:</b> <code>{html.escape(user.reason)}</code>" + f"</b>ᴀᴘᴘᴇᴀʟ ᴄʜᴀᴛ:</b> @{SUPPORT_CHAT}", + parse_mode=ParseMode.HTML, + ) + except: + pass # bot probably blocked by user + + +@support_plus +def ungban(update: Update, context: CallbackContext): + bot, args = context.bot, context.args + message = update.effective_message + user = update.effective_user + chat = update.effective_chat + log_message = "" + + user_id = extract_user(message, args) + + if not user_id: + message.reply_text( + "You ᴅᴏɴ'ᴛ sᴇᴇᴍ ᴛᴏ ʙᴇ ʀᴇғᴇʀʀɪɴɢ ᴛᴏ ᴀ ᴜsᴇʀ or ᴛʜᴇ ɪᴅ sᴘᴇᴄɪғɪᴇᴅ ɪs ɪɴᴄᴏʀʀᴇᴄᴛ..", + ) + return + + user_chat = bot.get_chat(user_id) + if user_chat.type != "private": + message.reply_text("ᴛʜᴀᴛ's ɴᴏᴛ a ᴜsᴇʀ!") + return + + if not sql.is_user_gbanned(user_id): + message.reply_text("ᴛʜɪs ᴜsᴇʀ ɪs ɴᴏᴛ ɢʙᴀɴɴᴇᴅ!") + return + + message.reply_text(f"I'ʟʟ ɢɪᴠᴇ {user_chat.first_name} a sᴇᴄᴏɴᴅ ᴄʜᴀɴᴄᴇ, ɢʟᴏʙᴀʟʟʏ.") + + start_time = time.time() + datetime_fmt = "%Y-%m-%dT%H:%M" + current_time = datetime.utcnow().strftime(datetime_fmt) + + if chat.type != "private": + chat_origin = f"<b>{html.escape(chat.title)} ({chat.id})</b>\n" + else: + chat_origin = f"<b>{chat.id}</b>\n" + + log_message = ( + f"#ᴜɴɢʙᴀɴɴᴇᴅ\n" + f"<b>ᴏʀɪɢɪɴᴀᴛᴇᴅ ғʀᴏᴍ:</b> <code>{chat_origin}</code>\n" + f"<b>ᴀᴅᴍɪɴ:</b> {mention_html(user.id, user.first_name)}\n" + f"<b>ᴜɴʙᴀɴɴᴇᴅ ᴜsᴇʀ:</b> {mention_html(user_chat.id, user_chat.first_name)}\n" + f"<b>ᴜɴʙᴀɴɴᴇᴅ ᴜsᴇʀ ID:</b> <code>{user_chat.id}</code>\n" + f"<b>ᴇᴠᴇɴᴛ sᴛᴀᴍᴘ:</b> <code>{current_time}</code>" + ) + + if EVENT_LOGS: + try: + log = bot.send_message( + EVENT_LOGS, + log_message, + parse_mode=ParseMode.HTML, + disable_web_page_preview=True, + ) + except BadRequest: + log = bot.send_message( + EVENT_LOGS, + log_message + + "\n\nғᴏʀᴍᴀᴛᴛɪɴɢ ʜᴀs ʙᴇᴇɴ ᴅɪsᴀʙʟᴇᴅ ᴅᴜᴇ ᴛᴏ ᴀɴ ᴜɴᴇxᴘᴇᴄᴛᴇᴅ ᴇʀʀᴏʀ.", + ) + else: + send_to_list(bot, DRAGONS + DEMONS, log_message, html=True) + + chats = get_user_com_chats(user_id) + ungbanned_chats = 0 + + for chat in chats: + chat_id = int(chat) + + # Check if this group has disabled gbans + if not sql.does_chat_gban(chat_id): + continue + + try: + member = bot.get_chat_member(chat_id, user_id) + if member.status == "ᴋɪᴄᴋᴇᴅ": + bot.unban_chat_member(chat_id, user_id) + ungbanned_chats += 1 + + except BadRequest as excp: + if excp.message not in UNGBAN_ERRORS: + message.reply_text(f"ᴄᴏᴜʟᴅ ɴᴏᴛ ᴜɴ-ɢᴀɴ ᴅᴜᴇ ᴛᴏ: {excp.message}") + if EVENT_LOGS: + bot.send_message( + EVENT_LOGS, + f"ᴄᴏᴜʟᴅ ɴᴏᴛ ᴜɴ-ʜᴀɴ ᴅᴜᴇ ᴛᴏ: {excp.message}", + parse_mode=ParseMode.HTML, + ) + else: + bot.send_message( + OWNER_ID, + f"ᴄᴏᴜʟᴅ ɴᴏᴛ ᴜɴ-ɢʙᴀɴ ᴅᴜᴇ ᴛᴏ: {excp.message}", + ) + return + except TelegramError: + pass + + sql.ungban_user(user_id) + + if EVENT_LOGS: + log.edit_text( + log_message + f"\n<b>ᴄʜᴀᴛs ᴀғғᴇᴄᴛᴇᴅ:</b> {ungbanned_chats}", + parse_mode=ParseMode.HTML, + ) + else: + send_to_list(bot, DRAGONS + DEMONS, "ᴜɴ-ɢᴀɴ ᴄᴏᴍᴘʟᴇᴛᴇ! ") + + end_time = time.time() + ungban_time = round((end_time - start_time), 2) + + if ungban_time > 60: + ungban_time = round((ungban_time / 60), 2) + message.reply_text(f"ᴘᴇʀsᴏɴ has ʙᴇᴇɴ ᴜɴ-ɢʙᴀɴɴᴇᴅ. ᴛᴏᴏᴋ {ungban_time} ᴍɪɴ") + else: + message.reply_text(f"ᴘᴇʀsᴏɴ ʜᴀs ʙᴇᴇɴ ᴜɴ-ɢʙᴀɴɴᴇᴅ. ᴛᴏᴏᴋ {ungban_time} sᴇᴄ") + + +@support_plus +def gbanlist(update: Update, context: CallbackContext): + banned_users = sql.get_gban_list() + + if not banned_users: + update.effective_message.reply_text( + "ᴛʜᴇʀᴇ ᴀʀᴇɴᴀᴛ ᴀɴʏ ɢʙᴀɴɴᴇᴅ ᴜsᴇʀs! ʏᴏᴜ'ʀᴇ ᴋɪɴᴅᴇʀ ᴛʜᴀɴ I ᴇxᴘᴇᴄᴛᴇᴅ...", + ) + return + + banfile = "sᴄʀᴇᴡ ᴛʜᴇsᴇ ɢᴜʏs.\n" + for user in banned_users: + banfile += f"[x] {user['name']} - {user['user_id']}\n" + if user["reason"]: + banfile += f"ʀᴇᴀsᴏɴ: {user['reason']}\n" + + with BytesIO(str.encode(banfile)) as output: + output.name = "gbanlist.txt" + update.effective_message.reply_document( + document=output, + filename="gbanlist.txt", + caption="ʜᴇʀᴇ ɪs ᴛʜᴇ ʟɪsᴛ ᴏғ ᴄᴜʀʀᴇɴᴛʟʏ ɢʙᴀɴɴᴇᴅ users.", + ) + + +def check_and_ban(update, user_id, should_message=True): + + if user_id in TIGERS or user_id in WOLVES: + sw_ban = None + else: + try: + sw_ban = sw.get_ban(int(user_id)) + except: + sw_ban = None + + if sw_ban: + update.effective_chat.ban_member(user_id) + if should_message: + update.effective_message.reply_text( + f"<b>ᴀʟᴇʀᴛ</b>: ᴛʜɪs ᴜsᴇʀ ʜᴀs ʙᴇᴇɴ ɢʟᴏʙᴀʟʟʏ ʙᴀɴɴᴇᴅ ʙʏ @SpamWatch\n" + f"<code>*ʙᴀɴs ᴛʜᴇᴍ ғʀᴏᴍ ʜᴇʀᴇ*</code>.\n" + f"<b>ᴀᴘᴘᴇᴀʟ ғᴏʀ ᴜɴʙᴀɴ</b>: {SPAMWATCH_SUPPORT_CHAT}\n" + f"<b>ᴜsᴇʀ ɪᴅ</b>: <code>{sw_ban.id}</code>\n" + f"<b>ʙᴀɴ ʀᴇᴀsᴏɴ</b>: <code>{html.escape(sw_ban.reason)}</code>\n @AbishnoiMF", + parse_mode=ParseMode.HTML, + ) + return + + if sql.is_user_gbanned(user_id): + update.effective_chat.ban_member(user_id) + if should_message: + text = ( + f"<b>ᴀʟᴇʀᴛ</b>: ᴛʜɪs ᴜsᴇʀ ʜᴀs ʙᴇᴇɴ ɢʟᴏʙᴀʟʟʏ ʙᴀɴɴᴇᴅ ʙʏ ᴛʜᴇ ʙᴏᴛ ᴏᴡɴᴇʀ\n" + f"<code>*ʙᴀɴs ᴛʜᴇᴍ ғʀᴏᴍ ʜᴇʀᴇ*</code>.\n" + f"<b>ᴀᴘᴘᴇᴀʟ ғᴏʀ ᴜɴʙᴀɴ</b>: @{SUPPORT_CHAT}\n" + f"<b>ᴜsᴇʀ ɪᴅ</b>: <code>{user_id}</code>" + ) + user = sql.get_gbanned_user(user_id) + if user.reason: + text += f"\n<b>ʙᴀɴ ʀᴇᴀsᴏɴ:</b> <code>{html.escape(user.reason)}</code>" + update.effective_message.reply_text(text, parse_mode=ParseMode.HTML) + + +def enforce_gban(update: Update, context: CallbackContext): + # Not using @restrict handler to avoid spamming - just ignore if cant gban. + bot = context.bot + try: + restrict_permission = update.effective_chat.get_member( + bot.id, + ).can_restrict_members + except Unauthorized: + return + if sql.does_chat_gban(update.effective_chat.id) and restrict_permission: + user = update.effective_user + chat = update.effective_chat + msg = update.effective_message + + if user and not is_user_admin(chat, user.id): + check_and_ban(update, user.id) + return + + if msg.new_chat_members: + new_members = update.effective_message.new_chat_members + for mem in new_members: + check_and_ban(update, mem.id) + + if msg.reply_to_message: + user = msg.reply_to_message.from_user + if user and not is_user_admin(chat, user.id): + check_and_ban(update, user.id, should_message=False) + + +@user_admin +def gbanstat(update: Update, context: CallbackContext): + args = context.args + if len(args) > 0: + if args[0].lower() in ["on", "yes"]: + sql.enable_gbans(update.effective_chat.id) + update.effective_message.reply_text( + "ᴀɴᴛɪsᴘᴀᴍ ɪs ɴᴏᴡ ᴇɴᴀʙʟᴇᴅ ✅ " + "I ᴀᴍ ɴᴏᴡ ᴘʀᴏᴛᴇᴄᴛɪɴɢ ʏᴏᴜʀ ɢʀᴏᴜᴘ ғʀᴏᴍ ᴘᴏᴛᴇɴᴛɪᴀʟ ʀᴇᴍᴏᴛᴇ ᴛʜʀᴇᴀᴛs!", + ) + elif args[0].lower() in ["off", "no"]: + sql.disable_gbans(update.effective_chat.id) + update.effective_message.reply_text( + "ᴀɴᴛɪsᴘᴀɴ is ɴᴏᴡ ᴅɪsᴀʙʟᴇᴅ ❌ " "sᴘᴀᴍᴡᴀᴛᴄʜ ɪs ɴᴏᴡ ᴅɪsᴀʙʟᴇᴅ ❌", + ) + else: + update.effective_message.reply_text( + "ɢɪᴠᴇ ᴍᴇ sᴏᴍᴇ ᴀʀɢᴜᴍᴇɴᴛs ᴛᴏ ᴄʜᴏᴏsᴇ ᴀ sᴇᴛᴛɪɴɢ! on/off, yes/no!\n\n" + "ʏᴏᴜʀ ᴄᴜʀʀᴇɴᴛ sᴇᴛᴛɪɴɢ ɪs: {}\n" + "ᴡʜᴇɴ ᴛʀᴜᴇ, ᴀɴʏ ɢʙᴀɴs ᴛʜᴀᴛ ʜᴀᴘᴘᴇɴ ᴡɪʟʟ ᴀʟsᴏ ʜᴀᴘᴘᴇɴ ɪɴ ʏᴏᴜʀ ɢʀᴏᴜᴘ. " + "ᴡʜᴇɴ ғᴀʟsᴇ, ᴛʜᴇʏ ᴡᴏɴ'ᴛ, ʟᴇᴀᴠɪɴɢ ʏᴏᴜ ᴀᴛ ᴛʜᴇ ᴘᴏssɪʙʟᴇ ᴍᴇʀᴄʏ ᴏғ " + "spammers.".format(sql.does_chat_gban(update.effective_chat.id)), + ) + + +def __stats__(): + return f"•➥ {sql.num_gbanned_users()} ɢʙᴀɴɴᴇᴅ ᴜsᴇʀs." + + +def __user_info__(user_id): + is_gbanned = sql.is_user_gbanned(user_id) + text = "ɢʙᴀɴɴᴇᴅ: <b>{}</b>" + if user_id in [777000, 1087968824]: + return "" + if user_id == dispatcher.bot.id: + return "" + if int(user_id) in DRAGONS + TIGERS + WOLVES: + return "" + if is_gbanned: + text = text.format("Yes") + user = sql.get_gbanned_user(user_id) + if user.reason: + text += f"\n<b>ʀᴇᴀsᴏɴ:</b> <code>{html.escape(user.reason)}</code>" + text += f"\n<b>ᴀᴘᴘᴇᴀʟ ᴄʜᴀᴛ:</b> @{SUPPORT_CHAT}" + else: + text = text.format("No") + return text + + +def __migrate__(old_chat_id, new_chat_id): + sql.migrate_chat(old_chat_id, new_chat_id) + + +def __chat_settings__(chat_id, user_id): + return f"ᴛʜɪs ᴄʜᴀᴛ ɪs ᴇɴғᴏʀᴄɪɴɢ *ɢʙᴀɴs*: `{sql.does_chat_gban(chat_id)}`." + + +__help__ = f""" +*ᴀᴅᴍɪɴs ᴏɴʟʏ:* +• /antispam <on/off/yes/no>*:*` ᴡɪʟʟ ᴛᴏɢɢʟᴇ ᴏᴜʀ ᴀɴᴛɪsᴘᴀᴍ ᴛᴇᴄʜ ᴏʀ ʀᴇᴛᴜʀɴ ʏᴏᴜʀ ᴄᴜʀʀᴇɴᴛ sᴇᴛᴛɪɴɢs `. + +`ᴀɴᴛɪ-ᴘᴀᴍ, used ʙʏ ʙᴏᴛ ᴅᴇᴠs ᴛᴏ ʙᴀɴ sᴘᴀᴍᴍᴇʀs ᴀᴄʀᴏss ᴀʟʟ ɢʀᴏᴜᴘs. ᴛʜɪs ʜᴇʟᴘs ᴘʀᴏᴛᴇᴄᴛ \ +ʏᴏᴜ ᴀɴᴅ ʏᴏᴜʀ ɢʀᴏᴜᴘs ʙʏ ʀᴇᴍᴏᴠɪɴɢ sᴘᴀᴍ ғʟᴏᴏᴅᴇʀs ᴀs ǫᴜɪᴄᴋʟʏ ᴀs ᴘᴏssɪʙʟᴇ `. + +*Note:* ᴜsᴇʀs can ᴀᴘᴘᴇᴀʟ ɢʙᴀɴs ᴏʀ ʀᴇᴘᴏʀᴛ sᴘᴀᴍᴍᴇʀs ᴀᴛ @{SUPPORT_CHAT} + +ᴛʜɪs ᴀʟsᴏ ɪɴᴛᴇɢʀᴀᴛᴇs @Spamwatch API ᴛᴏ ʀᴇᴍᴏᴠᴇ sᴘᴀᴍᴍᴇʀs as ᴍᴜᴄʜ ᴀs ᴘᴏssɪʙʟᴇ ғʀᴏᴍ ʏᴏᴜʀ ᴄʜᴀᴛʀᴏᴏᴍ! +*ᴡʜᴀᴛ ɪs sᴘᴀᴍᴡᴀᴛᴄʜ?* + +sᴘᴀᴍᴡᴀᴛᴄʜ ᴍᴀɪɴᴛᴀɪɴs a ʟᴀʀɢᴇ ᴄᴏɴsᴛᴀɴᴛʟʏ ᴜᴘᴅᴀᴛᴇᴅ ʙᴀɴᴛ of sᴘᴀᴍʙᴏᴛs, ᴛʀᴏʟʟs, ʙɪᴛᴄᴏɪɴ sᴘᴀᴍᴍᴇʀs ᴀɴᴅ ᴜɴsᴀᴠᴏᴜʀʏ ᴄʜᴀʀᴀᴄᴛᴇʀs[.] + +ᴄᴏɴsᴛᴀɴᴛʟʏ help banning spammers off from your group automatically sᴏ, ʏᴏᴜ ᴡᴏɴᴛ ʜᴀᴠᴇ ᴛᴏ ᴡᴏʀʀʏ ᴀʙᴏᴜᴛ sᴘᴀᴍᴍᴇʀs sᴛᴏʀᴍɪɴɢ ʏᴏᴜʀ ɢʀᴏᴜᴘ. + +*ɴᴏᴛᴇ:* ||ᴜsᴇʀs ᴄᴀɴ ᴀᴘᴘᴇᴀʟ sᴘᴀᴍᴡᴀᴛᴄʜ ʙᴀɴs ᴀᴛ @SpamwatchSupport|| +""" + +GBAN_HANDLER = CommandHandler("gban", gban, run_async=True) +UNGBAN_HANDLER = CommandHandler("ungban", ungban, run_async=True) +GBAN_LIST = CommandHandler("gbanlist", gbanlist, run_async=True) + +GBAN_STATUS = CommandHandler( + "antispam", gbanstat, filters=Filters.chat_type.groups, run_async=True +) + +GBAN_ENFORCER = MessageHandler( + Filters.all & Filters.chat_type.groups, enforce_gban, run_async=True +) + +dispatcher.add_handler(GBAN_HANDLER) +dispatcher.add_handler(UNGBAN_HANDLER) +dispatcher.add_handler(GBAN_LIST) +dispatcher.add_handler(GBAN_STATUS) + +__mod_name__ = "𝙰nti-sᴘᴀᴍ" +__handlers__ = [GBAN_HANDLER, UNGBAN_HANDLER, GBAN_LIST, GBAN_STATUS] + +if STRICT_GBAN: # enforce GBANS if this is set + dispatcher.add_handler(GBAN_ENFORCER, GBAN_ENFORCE_GROUP) + __handlers__.append((GBAN_ENFORCER, GBAN_ENFORCE_GROUP)) diff --git a/Exon/modules/google.py b/Exon/modules/google.py new file mode 100644 index 00000000..6c9534e2 --- /dev/null +++ b/Exon/modules/google.py @@ -0,0 +1,150 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import glob +import re +import urllib +import urllib.request + +from bing_image_downloader import downloader +from bs4 import BeautifulSoup +from telethon import * +from telethon.tl.types import * + +from Exon import telethn +from Exon.events import register + + +@register(pattern="^/img (.*)") +async def img_sampler(event): + if event.fwd_from: + return + + query = event.pattern_match.group(1) + jit = f'"{query}"' + downloader.download( + jit, + limit=4, + output_dir="store", + adult_filter_off=False, + force_replace=False, + timeout=60, + ) + os.chdir(f'./store/"{query}"') + types = ("*.png", "*.jpeg", "*.jpg") # the tuple of file types + files_grabbed = [] + for files in types: + files_grabbed.extend(glob.glob(files)) + await telethn.send_file(event.chat_id, files_grabbed, reply_to=event.id) + os.chdir("/app") + os.system("rm -rf store") + + +opener = urllib.request.build_opener() +useragent = "Mozilla/5.0 (Linux; Android 9; SM-G960F Build/PPR1.180610.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/74.0.3729.157 Mobile Safari/537.36" +opener.addheaders = [("User-agent", useragent)] + + +async def ParseSauce(googleurl): + """Parse/Scrape the HTML code for the info we want.""" + + source = opener.open(googleurl).read() + soup = BeautifulSoup(source, "html.parser") + + results = {"similar_images": "", "best_guess": ""} + + try: + for similar_image in soup.findAll("input", {"class": "gLFyf"}): + url = "https://www.google.com/search?tbm=isch&q=" + urllib.parse.quote_plus( + similar_image.get("value") + ) + results["similar_images"] = url + except BaseException: + pass + + for best_guess in soup.findAll("div", attrs={"class": "r5a77d"}): + results["best_guess"] = best_guess.get_text() + + return results + + +async def scam(results, lim): + + single = opener.open(results["similar_images"]).read() + decoded = single.decode("utf-8") + + imglinks = [] + counter = 0 + + pattern = r"^,\[\"(.*[.png|.jpg|.jpeg])\",[0-9]+,[0-9]+\]$" + oboi = re.findall(pattern, decoded, re.I | re.M) + + for imglink in oboi: + counter += 1 + if counter < int(lim): + imglinks.append(imglink) + else: + break + + return imglinks + + +async def is_register_admin(chat, user): + if isinstance(chat, (types.InputPeerChannel, types.InputChannel)): + + return isinstance( + ( + await telethn(functions.channels.GetParticipantRequest(chat, user)) + ).participant, + (types.ChannelParticipantAdmin, types.ChannelParticipantCreator), + ) + if isinstance(chat, types.InputPeerChat): + + ui = await telethn.get_peer_id(user) + ps = ( + await telethn(functions.messages.GetFullChatRequest(chat.chat_id)) + ).full_chat.participants.participants + return isinstance( + next((p for p in ps if p.user_id == ui), None), + (types.ChatParticipantAdmin, types.ChatParticipantCreator), + ) + return None + + +__mod_name__ = "𝚃ᴏᴏʟs" + +__help__ = """ +⍟ /img <text> :- `sᴇᴀʀᴄʜ ɢᴏᴏɢʟᴇ ғᴏʀ ɪᴍᴀɢᴇs ᴀɴᴅ ʀᴇᴛᴜʀɴs ᴛʜᴇᴍ ғᴏʀ ɢʀᴇᴀᴛᴇʀ ɴᴏ. ᴏғ ʀᴇsᴜʟᴛs sᴘᴇᴄɪғʏ ʟɪᴍ, ғᴏʀ` + +ᴇɢ: `/img hello lim=10` + +⍟ /reverse :- `ʀᴇᴘʟʏ ᴛᴏ ᴀ sᴛɪᴄᴋᴇʀ, or ᴏʀ ɪᴍᴀɢᴇ ᴛᴏ sᴇᴀʀᴄʜ ɪᴛ ` + + ᴅᴏ ʏᴏᴜ ᴋɴᴏᴡ that ʏᴏᴜ ᴄᴀɴ search ᴀɴ ɪᴍᴀɢᴇ ᴡɪᴛʜ ᴀ ʟɪɴᴋ ᴛᴏᴏ? + /reverse picturelink <amount>. + +⍟ /git <ɢɪᴛʜᴜʙ ᴜsᴇʀɴᴀᴍᴇ> :-`ɢᴇᴛ ɪɴғᴏ ᴏғ ᴀɴʏ ɢɪᴛʜᴜʙ ᴘʀᴏғɪʟᴇ` + +⍟ /webss <ᴡᴇʙɴᴀᴍᴇ.ᴄᴏᴍ> :- `ɢᴇᴛ sᴄʀᴇᴇɴ sʜᴏᴛ ᴏғ ᴀɴʏ ᴡᴇʙsɪᴛᴇ ʏᴏᴜ ᴡᴀɴᴛ `. +""" diff --git a/Exon/modules/gtranslator.py b/Exon/modules/gtranslator.py new file mode 100644 index 00000000..0fa7bb9d --- /dev/null +++ b/Exon/modules/gtranslator.py @@ -0,0 +1,105 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +from gpytranslate import SyncTranslator +from telegram import ParseMode, Update +from telegram.ext import CallbackContext + +from Exon import dispatcher +from Exon.modules.disable import DisableAbleCommandHandler + +trans = SyncTranslator() + + +def translate(update: Update, context: CallbackContext) -> None: + bot = context.bot + message = update.effective_message + reply_msg = message.reply_to_message + if not reply_msg: + message.reply_text("ʀᴇᴘʟʏ ᴛᴏ ᴀ ᴍᴇssᴀɢᴇ ᴛᴏ ᴛʀᴀɴsʟᴀᴛᴇ ɪᴛ!") + return + if reply_msg.caption: + to_translate = reply_msg.caption + elif reply_msg.text: + to_translate = reply_msg.text + try: + args = message.text.split()[1].lower() + if "//" in args: + source = args.split("//")[0] + dest = args.split("//")[1] + else: + source = trans.detect(to_translate) + dest = args + except IndexError: + source = trans.detect(to_translate) + dest = "en" + translation = trans(to_translate, sourcelang=source, targetlang=dest) + reply = ( + f"<b>ᴛʀᴀɴsʟᴀᴛᴇᴅ ғʀᴏᴍ {source} ᴛᴏ {dest}</b>:\n" + f"<code>{translation.text}</code>" + ) + + bot.send_message(text=reply, chat_id=message.chat.id, parse_mode=ParseMode.HTML) + + +def languages(update: Update, context: CallbackContext) -> None: + message = update.effective_message + bot = context.bot + bot.send_message( + text="ᴄʟɪᴄᴋ [ʜᴇʀᴇ](https://telegra.ph/ɪᴛs-ᴍᴇ-𒆜-Aʙɪsʜɴᴏɪ-07-30-2) ᴛᴏ sᴇᴇ ᴛʜᴇ ʟɪsᴛ ᴏғ sᴜᴘᴘᴏʀᴛᴇᴅ ʟᴀɴɢᴜᴀɢᴇ ᴄᴏᴅᴇs!", + chat_id=message.chat.id, + disable_web_page_preview=True, + parse_mode=ParseMode.MARKDOWN, + ) + + +__help__ = """ +Use ᴛʜɪs ᴍᴏᴅᴜʟᴇ ᴛᴏ ᴛʀᴀɴsʟᴀᴛᴇ sᴛᴜғғ! + +*ᴄᴏᴍᴍᴀɴᴅs:* +⍟ /tl (or /tr ):` ᴀs ᴀ ʀᴇᴘʟʏ ᴛᴏ a ᴍᴇssᴀɢᴇ, ᴛʀᴀɴsʟᴀᴛᴇs ɪᴛ ᴛᴏ ᴇɴɢʟɪsʜ ` + +⍟ /tl <lang>: `ᴛʀᴀɴsʟᴀᴛᴇs ᴛᴏ <lang>` + +ᴇɢ: `/tl en`: `ᴛʀᴀɴsʟᴀᴛᴇs ᴛᴏ ᴇɴɢʟɪsʜ ` + +⍟ /tl <source>//<dest>: ᴛʀᴀɴsʟᴀᴛᴇs ғʀᴏᴍ <source> ᴛᴏ <lang>. + +ᴇɢ: `/tl ja//en`: ᴛʀᴀɴsʟᴀᴛᴇs ғʀᴏᴍ ᴊᴀᴘᴀɴᴇsᴇ ᴛᴏ ᴇɴɢʟɪsʜ. + + +• [ʟɪsᴛ ᴏғ sᴜᴘᴘᴏʀᴛᴇᴅ ʟᴀɴɢᴜᴀɢᴇs ғᴏʀ ᴛʀᴀɴsʟᴀᴛɪᴏɴ](https://telegra.ph/ɪᴛs-ᴍᴇ-𒆜-Aʙɪsʜɴᴏɪ-07-30-2) +""" + +TRANSLATE_HANDLER = DisableAbleCommandHandler(["tr", "tl"], translate, run_async=True) +TRANSLATE_LANG_HANDLER = DisableAbleCommandHandler( + ["lang", "languages"], languages, run_async=True +) + +dispatcher.add_handler(TRANSLATE_HANDLER) +dispatcher.add_handler(TRANSLATE_LANG_HANDLER) + +__mod_name__ = "𝚃ʀᴀɴsʟᴀᴛᴏʀ" +__command_list__ = ["tr", "tl", "lang", "languages"] +__handlers__ = [TRANSLATE_HANDLER, TRANSLATE_LANG_HANDLER] diff --git "a/Exon/modules/helper_funcs/ABISHNOI COPYRIGHT \302\251" "b/Exon/modules/helper_funcs/ABISHNOI COPYRIGHT \302\251" new file mode 100644 index 00000000..a7ec3b8e --- /dev/null +++ "b/Exon/modules/helper_funcs/ABISHNOI COPYRIGHT \302\251" @@ -0,0 +1,27 @@ +""" +MIT License + +Copyright (c) 2022 ABISHNOI + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" +# ""DEAR PRO PEOPLE, DON'T REMOVE & CHANGE THIS LINE +# TG :- @Abishnoi +# MY ALL BOTS :- Abishnoi_bots +# GITHUB :- KingAbishnoi "" \ No newline at end of file diff --git a/Exon/modules/helper_funcs/__init__.py b/Exon/modules/helper_funcs/__init__.py new file mode 100644 index 00000000..69e6a508 --- /dev/null +++ b/Exon/modules/helper_funcs/__init__.py @@ -0,0 +1 @@ +"""ʜᴇʟᴘᴇʀs, ᴀʟsᴏ ᴋɴᴏᴡɴ ᴀs ᴜᴛɪʟɪᴛɪᴇs .""" diff --git a/Exon/modules/helper_funcs/alternate.py b/Exon/modules/helper_funcs/alternate.py new file mode 100644 index 00000000..993d0acf --- /dev/null +++ b/Exon/modules/helper_funcs/alternate.py @@ -0,0 +1,66 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +from functools import wraps + +from telegram import ChatAction +from telegram.error import BadRequest + + +def send_message(message, text, *args, **kwargs): + try: + return message.reply_text(text, *args, **kwargs) + except BadRequest as err: + if str(err) == "ʀᴇᴘʟʏ ᴍᴇssᴀɢᴇ ɴᴏᴛ ғᴏᴜɴᴅ": + return message.reply_text(text, quote=False, *args, **kwargs) + + +def typing_action(func): + """sᴇɴᴅs ᴛʏᴘɪɴɢ ᴀᴄᴛɪᴏɴ ᴡʜɪʟᴇ ᴘʀᴏᴄᴇssɪɴɢ ғᴜɴᴄ ᴄᴏᴍᴍᴀɴᴅ.""" + + @wraps(func) + def command_func(update, context, *args, **kwargs): + context.bot.send_chat_action( + chat_id=update.effective_chat.id, + action=ChatAction.TYPING, + ) + return func(update, context, *args, **kwargs) + + return command_func + + +def send_action(action): + """sᴇɴᴅs `action` ᴡʜɪʟᴇ ᴘʀᴏᴄᴇssɪɴɢ ғᴜɴᴄ ᴄᴏᴍᴍᴀɴᴅ.""" + + def decorator(func): + @wraps(func) + def command_func(update, context, *args, **kwargs): + context.bot.send_chat_action( + chat_id=update.effective_chat.id, action=action + ) + return func(update, context, *args, **kwargs) + + return command_func + + return decorator diff --git a/Exon/modules/helper_funcs/anonymous.py b/Exon/modules/helper_funcs/anonymous.py new file mode 100644 index 00000000..d9a19b66 --- /dev/null +++ b/Exon/modules/helper_funcs/anonymous.py @@ -0,0 +1,140 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import functools +from enum import Enum + +from telegram import ParseMode, Update +from telegram.ext import CallbackContext +from telegram.inline.inlinekeyboardbutton import InlineKeyboardButton +from telegram.inline.inlinekeyboardmarkup import InlineKeyboardMarkup + +from Exon import DEV_USERS, DRAGONS, dispatcher +from Exon.modules.helper_funcs.decorators import Exoncallback + + +class AdminPerms(Enum): + CAN_RESTRICT_MEMBERS = "can_restrict_members" + CAN_PROMOTE_MEMBERS = "can_promote_members" + CAN_INVITE_USERS = "can_invite_users" + CAN_DELETE_MESSAGES = "can_delete_messages" + CAN_CHANGE_INFO = "can_change_info" + CAN_PIN_MESSAGES = "can_pin_messages" + + +class ChatStatus(Enum): + CREATOR = "creator" + ADMIN = "administrator" + + +anon_callbacks = {} +anon_callback_messages = {} + + +def user_admin(permission: AdminPerms): + def wrapper(func): + @functools.wraps(func) + def awrapper(update: Update, context: CallbackContext, *args, **kwargs): + nonlocal permission + if update.effective_chat.type == "private": + return func(update, context, *args, **kwargs) + message = update.effective_message + is_anon = update.effective_message.sender_chat + + if is_anon: + callback_id = ( + f"anoncb/{message.chat.id}/{message.message_id}/{permission.value}" + ) + anon_callbacks[(message.chat.id, message.message_id)] = ( + (update, context), + func, + ) + anon_callback_messages[(message.chat.id, message.message_id)] = ( + message.reply_text( + "sᴇᴇᴍs ʟɪᴋᴇ ʏᴏᴜ'ʀᴇ ᴀɴᴏɴʏᴍᴏᴜs, ᴄʟɪᴄᴋ ᴛʜᴇ ʙᴜᴛᴛᴏɴ ʙᴇʟᴏᴡ ᴛᴏ ᴘʀᴏᴠᴇ ʏᴏᴜʀ ɪᴅᴇɴᴛɪᴛʏ", + reply_markup=InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + text="ᴘʀᴏᴠᴇ ɪᴅᴇɴᴛɪᴛʏ", callback_data=callback_id + ) + ] + ] + ), + ) + ).message_id + # send message with callback f'anoncb{callback_id}' + else: + user_id = message.from_user.id + chat_id = message.chat.id + mem = context.bot.get_chat_member(chat_id=chat_id, user_id=user_id) + if ( + getattr(mem, permission.value) is True + or mem.status == "creator" + or user_id in DRAGONS + ): + return func(update, context, *args, **kwargs) + else: + return message.reply_text( + f"ʏᴏᴜ ʟᴀᴄᴋ ᴛʜᴇ ᴘᴇʀᴍɪssɪᴏɴ: `{permission.name}`", + parse_mode=ParseMode.MARKDOWN, + ) + + return awrapper + + return wrapper + + +@Exoncallback(pattern="anoncb") +def anon_callback_handler1(upd: Update, _: CallbackContext): + callback = upd.callback_query + perm = callback.data.split("/")[3] + chat_id = int(callback.data.split("/")[1]) + message_id = int(callback.data.split("/")[2]) + try: + mem = upd.effective_chat.get_member(user_id=callback.from_user.id) + except BaseException as e: + callback.answer(f"ᴇʀʀᴏʀ: {e}", show_alert=True) + return + if mem.status not in [ChatStatus.ADMIN.value, ChatStatus.CREATOR.value]: + callback.answer("ʏᴏᴜ'ʀᴇ ᴀʀᴇɴ'ᴛ ᴀᴅᴍɪɴ.") + dispatcher.bot.delete_message( + chat_id, anon_callback_messages.pop((chat_id, message_id), None) + ) + dispatcher.bot.send_message( + chat_id, "ʏᴏᴜ ʟᴀᴄᴋ ᴛʜᴇ ᴘᴇʀᴍɪssɪᴏɴs ʀᴇǫᴜɪʀᴇᴅ ғᴏʀ ᴛʜɪs ᴄᴏᴍᴍᴀɴᴅ" + ) + elif ( + getattr(mem, perm) is True + or mem.status == "creator" + or mem.user.id in DEV_USERS + ): + cb = anon_callbacks.pop((chat_id, message_id), None) + if cb: + message_id = anon_callback_messages.pop((chat_id, message_id), None) + if message_id is not None: + dispatcher.bot.delete_message(chat_id, message_id) + return cb[1](cb[0][0], cb[0][1]) + else: + callback.answer("ᴛʜɪs ɪsɴ'ᴛ ғᴏʀ ʏᴏᴜ\nᴊᴀ ʟᴇᴠᴅᴇ ᴋᴀᴍ ᴋᴀʀ ᴀᴘɴᴀ") diff --git a/Exon/modules/helper_funcs/chat_status.py b/Exon/modules/helper_funcs/chat_status.py new file mode 100644 index 00000000..deed88e6 --- /dev/null +++ b/Exon/modules/helper_funcs/chat_status.py @@ -0,0 +1,489 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +from functools import wraps +from threading import RLock +from time import perf_counter + +from cachetools import TTLCache +from telegram import Chat, ChatMember, ParseMode, Update +from telegram.ext import CallbackContext + +from Exon import ( + DEL_CMDS, + DEMONS, + DEV_USERS, + DRAGONS, + SUPPORT_CHAT, + TIGERS, + WOLVES, + dispatcher, +) + +# stores admemes in memory for 10 min. +ADMIN_CACHE = TTLCache(maxsize=512, ttl=60 * 10, timer=perf_counter) +THREAD_LOCK = RLock() + + +def is_whitelist_plus(chat: Chat, user_id: int, member: ChatMember = None) -> bool: + return any(user_id in user for user in [WOLVES, TIGERS, DEMONS, DRAGONS, DEV_USERS]) + + +def is_support_plus(chat: Chat, user_id: int, member: ChatMember = None) -> bool: + return user_id in DEMONS or user_id in DRAGONS or user_id in DEV_USERS + + +def is_sudo_plus(chat: Chat, user_id: int, member: ChatMember = None) -> bool: + return user_id in DRAGONS or user_id in DEV_USERS + + +def is_stats_plus(chat: Chat, user_id: int, member: ChatMember = None) -> bool: + return user_id in DEV_USERS + + +def is_user_admin(chat: Chat, user_id: int, member: ChatMember = None) -> bool: + if ( + chat.type == "private" + or user_id in DRAGONS + or user_id in DEV_USERS + or chat.all_members_are_administrators + or user_id in {1452219013} + ): # Count telegram and Group Anonymous as admin + return True + if member: + return member.status in ("administrator", "creator") + + with THREAD_LOCK: + # try to fetch from cache first. + try: + return user_id in ADMIN_CACHE[chat.id] + except KeyError: + # keyerror happend means cache is deleted, + # so query bot api again and return user status + # while saving it in cache for future useage... + chat_admins = dispatcher.bot.getChatAdministrators(chat.id) + admin_list = [x.user.id for x in chat_admins] + ADMIN_CACHE[chat.id] = admin_list + + return user_id in admin_list + + +def is_bot_admin(chat: Chat, bot_id: int, bot_member: ChatMember = None) -> bool: + if chat.type == "private" or chat.all_members_are_administrators: + return True + + if not bot_member: + bot_member = chat.get_member(bot_id) + + return bot_member.status in ("administrator", "creator") + + +def can_delete(chat: Chat, bot_id: int) -> bool: + return chat.get_member(bot_id).can_delete_messages + + +def is_user_ban_protected(chat: Chat, user_id: int, member: ChatMember = None) -> bool: + if ( + chat.type == "private" + or user_id in DRAGONS + or user_id in DEV_USERS + or user_id in WOLVES + or user_id in TIGERS + ): # Count telegram and Group Anonymous as admin + return True + + if not member: + member = chat.get_member(user_id) + + return member.status in ("administrator", "creator") + + +def is_user_in_chat(chat: Chat, user_id: int) -> bool: + member = chat.get_member(user_id) + return member.status not in ("left", "kicked") + + +def dev_plus(func): + @wraps(func) + def is_dev_plus_func(update: Update, context: CallbackContext, *args, **kwargs): + context.bot + user = update.effective_user + + if user.id in DEV_USERS: + return func(update, context, *args, **kwargs) + if not user: + pass + elif DEL_CMDS and " " not in update.effective_message.text: + try: + update.effective_message.delete() + except: + pass + else: + update.effective_message.reply_text( + "ᴛʜɪs ɪs ᴀ ᴅᴇᴠᴇʟᴏᴘᴇʀ ʀᴇsᴛʀɪᴄᴛᴇᴅ ᴄᴏᴍᴍᴀɴᴅ." + "ʏᴏᴜ ᴅᴏ ɴᴏᴛ ʜᴀᴠᴇ ᴘᴇʀᴍɪssɪᴏɴs ᴛᴏ ʀᴜɴ ᴛʜɪs.", + ) + + return is_dev_plus_func + + +def sudo_plus(func): + @wraps(func) + def is_sudo_plus_func(update: Update, context: CallbackContext, *args, **kwargs): + context.bot + user = update.effective_user + chat = update.effective_chat + + if user and is_sudo_plus(chat, user.id): + return func(update, context, *args, **kwargs) + if not user: + pass + elif DEL_CMDS and " " not in update.effective_message.text: + try: + update.effective_message.delete() + except: + pass + else: + update.effective_message.reply_text( + "ᴀᴛ ʟᴇᴀsᴛ ʙᴇ ᴀɴ ᴀᴅᴍɪɴ ᴛᴏ ᴜsᴇ ᴛʜᴇsᴇ ᴀʟʟ ᴄᴏᴍᴍᴀɴᴅs", + ) + + return is_sudo_plus_func + + +def stats_plus(func): + @wraps(func) + def is_stats_plus_func(update: Update, context: CallbackContext, *args, **kwargs): + context.bot + user = update.effective_user + chat = update.effective_chat + + if user and is_stats_plus(chat, user.id): + return func(update, context, *args, **kwargs) + if not user: + pass + elif DEL_CMDS and " " not in update.effective_message.text: + try: + update.effective_message.delete() + except: + pass + else: + update.effective_message.reply_text( + "Exon sᴛᴀᴛs ɪs ᴊᴜsᴛ ғᴏʀ ᴅᴇᴠ ᴜsᴇʀ", + ) + + return is_stats_plus_func + + +def support_plus(func): + @wraps(func) + def is_support_plus_func(update: Update, context: CallbackContext, *args, **kwargs): + context.bot + user = update.effective_user + chat = update.effective_chat + + if user and is_support_plus(chat, user.id): + return func(update, context, *args, **kwargs) + if DEL_CMDS and " " not in update.effective_message.text: + try: + update.effective_message.delete() + except: + pass + + return is_support_plus_func + + +def whitelist_plus(func): + @wraps(func) + def is_whitelist_plus_func( + update: Update, + context: CallbackContext, + *args, + **kwargs, + ): + context.bot + user = update.effective_user + chat = update.effective_chat + + if user and is_whitelist_plus(chat, user.id): + return func(update, context, *args, **kwargs) + update.effective_message.reply_text( + f"ʏᴏᴜ ᴅᴏɴ'ᴛ ʜᴀᴠᴇ ᴀᴄᴄᴇss ᴛᴏ ᴜsᴇ ᴛʜɪs.\nᴠɪsɪᴛ @{SUPPORT_CHAT}", + ) + + return is_whitelist_plus_func + + +def user_admin(func): + @wraps(func) + def is_admin(update: Update, context: CallbackContext, *args, **kwargs): + context.bot + user = update.effective_user + chat = update.effective_chat + + if user and is_user_admin(chat, user.id): + return func(update, context, *args, **kwargs) + if not user: + pass + elif DEL_CMDS and " " not in update.effective_message.text: + try: + update.effective_message.delete() + except: + pass + else: + update.effective_message.reply_text( + "ᴀᴛ ʟᴇᴀsᴛ ʙᴇ ᴀɴ ᴀᴅᴍɪɴ ᴛᴏ ᴜsᴇ ᴛʜᴇsᴇ ᴀʟʟ ᴄᴏᴍᴍᴀɴᴅs", + ) + + return is_admin + + +def user_admin_no_reply(func): + @wraps(func) + def is_not_admin_no_reply( + update: Update, + context: CallbackContext, + *args, + **kwargs, + ): + context.bot + user = update.effective_user + chat = update.effective_chat + + if user and is_user_admin(chat, user.id): + return func(update, context, *args, **kwargs) + if not user: + pass + elif DEL_CMDS and " " not in update.effective_message.text: + try: + update.effective_message.delete() + except: + pass + + return is_not_admin_no_reply + + +def user_not_admin(func): + @wraps(func) + def is_not_admin(update: Update, context: CallbackContext, *args, **kwargs): + message = update.effective_message + user = update.effective_user + # chat = update.effective_chat + + if message.is_automatic_forward: + return + if message.sender_chat and message.sender_chat.type != "channel": + return + elif user and not is_user_admin(update.message.chat, user.id): + return func(update, context, *args, **kwargs) + + elif not user: + pass + + return is_not_admin + + +def bot_admin(func): + @wraps(func) + def is_admin(update: Update, context: CallbackContext, *args, **kwargs): + bot = context.bot + chat = update.effective_chat + update_chat_title = chat.title + message_chat_title = update.effective_message.chat.title + + if update_chat_title == message_chat_title: + not_admin = "I'ᴍ ɴᴏᴛ ᴀᴅᴍɪɴ!" + else: + not_admin = f"I'ᴍ ɴᴏᴛ ᴀᴅᴍɪɴ ɪɴ <b>{update_chat_title}</b>! " + + if is_bot_admin(chat, bot.id): + return func(update, context, *args, **kwargs) + update.effective_message.reply_text(not_admin, parse_mode=ParseMode.HTML) + + return is_admin + + +def bot_can_delete(func): + @wraps(func) + def delete_rights(update: Update, context: CallbackContext, *args, **kwargs): + bot = context.bot + chat = update.effective_chat + update_chat_title = chat.title + message_chat_title = update.effective_message.chat.title + + if update_chat_title == message_chat_title: + cant_delete = "I ᴄᴀɴ'ᴛ ᴅᴇʟᴇᴛᴇ ᴍᴇssᴀɢᴇs ʜᴇʀᴇ!\nMake sᴜʀᴇ I'ᴍ ᴀᴅᴍɪɴ ᴀɴᴅ ᴄᴀɴ ᴅᴇʟᴇᴛᴇ ᴏᴛʜᴇʀ ᴜsᴇᴅ's ᴍᴇssᴀɢᴇs." + else: + cant_delete = f"I ᴄᴀɴ'ᴛ ᴅᴇʟᴇᴛᴇ ᴍᴇssᴀɢᴇs ɪɴ <b>{update_chat_title}</b>!\nᴍᴀᴋᴇ sᴜʀᴇ I'm ᴀᴅᴍɪɴ ᴀɴᴅ ᴄᴀɴ ᴅᴇʟᴇᴛᴇ ᴏᴛʜᴇʀ ᴜsᴇᴅ ᴍᴇssᴀɢᴇs ᴛʜᴇʀᴇ." + + if can_delete(chat, bot.id): + return func(update, context, *args, **kwargs) + update.effective_message.reply_text(cant_delete, parse_mode=ParseMode.HTML) + + return delete_rights + + +def can_pin(func): + @wraps(func) + def pin_rights(update: Update, context: CallbackContext, *args, **kwargs): + bot = context.bot + chat = update.effective_chat + update_chat_title = chat.title + message_chat_title = update.effective_message.chat.title + + if update_chat_title == message_chat_title: + cant_pin = ( + "I ᴄᴀɴ'ᴛ ᴘɪɴ ᴍᴇssᴀɢᴇs ʜᴇʀᴇ!\nMake sᴜʀᴇ I'ᴍ ᴀᴅᴍɪɴ ᴀɴᴅ ᴄᴀɴ ᴘɪɴ ᴍᴇssᴀɢᴇs." + ) + else: + cant_pin = f"I ᴄᴀɴ'ᴛ ᴘɪɴ ᴍᴇssᴀɢᴇs in <b>{update_chat_title}</b>!\nᴍᴀᴋᴇ sure I'ᴍ ᴀᴅᴍɪɴ ᴀɴᴅ ᴄᴀɴ ᴘɪɴ ᴍᴇssᴀɢᴇs ᴛʜᴇʀᴇ." + + if chat.get_member(bot.id).can_pin_messages: + return func(update, context, *args, **kwargs) + update.effective_message.reply_text(cant_pin, parse_mode=ParseMode.HTML) + + return pin_rights + + +def can_promote(func): + @wraps(func) + def promote_rights(update: Update, context: CallbackContext, *args, **kwargs): + bot = context.bot + chat = update.effective_chat + update_chat_title = chat.title + message_chat_title = update.effective_message.chat.title + + if update_chat_title == message_chat_title: + cant_promote = "I can't ᴘʀᴏᴍᴏᴛᴇ/ᴅᴇᴍᴏᴛᴇ ᴘᴇᴏᴘʟᴇ ʜᴇʀᴇ!\nMake sᴜʀᴇ I'ᴍ ᴀᴅᴍɪɴ and can appoint new admins." + else: + cant_promote = ( + f"I ᴄᴀɴ'ᴛ ᴘʀᴏᴍᴏᴛᴇ/ᴅᴇᴍᴏᴛᴇ ᴘᴇᴏᴘʟᴇ ɪɴ <b>{update_chat_title}</b>!\n" + f"ᴍᴀᴋᴇ sᴜʀᴇ I'ᴍ ᴀᴅᴍɪɴ ᴛʜᴇʀᴇ ᴀɴᴅ ʜᴀᴠᴇ ᴛʜᴇ ᴘᴇʀᴍɪssɪᴏɴ ᴛᴏ ᴀᴘᴘᴏɪɴᴛ ɴᴇᴡ ᴀᴅᴍɪɴs." + ) + + if chat.get_member(bot.id).can_promote_members: + return func(update, context, *args, **kwargs) + update.effective_message.reply_text(cant_promote, parse_mode=ParseMode.HTML) + + return promote_rights + + +def can_restrict(func): + @wraps(func) + def restrict_rights(update: Update, context: CallbackContext, *args, **kwargs): + bot = context.bot + chat = update.effective_chat + update_chat_title = chat.title + message_chat_title = update.effective_message.chat.title + + if update_chat_title == message_chat_title: + cant_restrict = "I ᴄᴀɴ'ᴛ ʀᴇsᴛʀɪᴄᴛ ᴘᴇᴏᴘʟᴇ ʜᴇʀᴇ!\nᴍᴀᴋᴇ sᴜʀᴇ I'ᴍ ᴀᴅᴍɪɴ ᴀɴᴅ ᴄᴀɴ ʀᴇsᴛʀɪᴄᴛ ᴜsᴇʀs." + else: + cant_restrict = f"ɪ ᴄᴀɴ'ᴛ ʀᴇsᴛʀɪᴄᴛ ᴘᴇᴏᴘʟᴇ ɪɴ <b>{update_chat_title}</b>!\nᴍᴀᴋᴇ sᴜʀᴇ ɪ'ᴍ ᴀᴅᴍɪɴ ᴛʜᴇʀᴇ ᴀɴᴅ ᴄᴀɴ ʀᴇsᴛʀɪᴄᴛ ᴜsᴇʀs." + + if chat.get_member(bot.id).can_restrict_members: + return func(update, context, *args, **kwargs) + update.effective_message.reply_text( + cant_restrict, + parse_mode=ParseMode.HTML, + ) + + return restrict_rights + + +def user_can_promote(func): + @wraps(func) + def user_is_promoter(update: Update, context: CallbackContext, *args, **kwargs): + context.bot + user = update.effective_user + if not user: + return + user = user.id + member = update.effective_chat.get_member(user) + no_rights = "ʏᴏᴜ ᴅᴏ ɴᴏᴛ ʜᴀᴠᴇ 'ᴀᴅᴅ ᴀᴅᴍɪɴ ʀɪɢʜᴛs." + if ( + not (member.can_promote_members or member.status == "creator") + and user not in DRAGONS + and user not in [1452219013] + ): + if not update.callback_query: + update.effective_message.reply_text(no_rights) + else: + update.callback_query.answer(no_rights, show_alert=True) + return "" + return func(update, context, *args, **kwargs) + + return user_is_promoter + + +def user_can_ban(func): + @wraps(func) + def user_is_banhammer(update: Update, context: CallbackContext, *args, **kwargs): + context.bot + user = update.effective_user.id + member = update.effective_chat.get_member(user) + if ( + not member.can_restrict_members + and member.status != "creator" + and user not in DRAGONS + and user not in [1452219013] + ): + update.effective_message.reply_text( + "sᴏʀʀʏ sᴏɴ, ʙᴜᴛ ʏᴏᴜ'ʀᴇ ɴᴏᴛ ᴡᴏʀᴛʜʏ ᴛᴏ ᴡɪᴇʟᴅ ᴛʜᴇ ʙᴀɴʜᴀᴍᴍᴇʀ.", + ) + return "" + return func(update, context, *args, **kwargs) + + return user_is_banhammer + + +def connection_status(func): + @wraps(func) + def connected_status(update: Update, context: CallbackContext, *args, **kwargs): + conn = connected( + context.bot, + update, + update.effective_chat, + update.effective_user.id, + need_admin=False, + ) + + if conn: + chat = dispatcher.bot.getChat(conn) + update.__setattr__("_effective_chat", chat) + elif update.effective_message.chat.type == "private": + update.effective_message.reply_text( + "sᴇɴᴅ /connect ɪɴ ᴀ ɢʀᴏᴜᴘ ᴛʜᴀᴛ ʏᴏᴜ ᴀɴᴅ I ʜᴀᴠᴇ ɪɴ ᴄᴏᴍᴍᴏɴ ғɪʀsᴛ.", + ) + return connected_status + + return func(update, context, *args, **kwargs) + + return connected_status + + +# Workaround for circular import with connection.py +from Exon.modules import connection + +connected = connection.connected diff --git a/Exon/modules/helper_funcs/decorators.py b/Exon/modules/helper_funcs/decorators.py new file mode 100644 index 00000000..6f9cf76b --- /dev/null +++ b/Exon/modules/helper_funcs/decorators.py @@ -0,0 +1,202 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +from typing import List, Optional, Union + +from telegram.ext import ( + CallbackQueryHandler, + CommandHandler, + InlineQueryHandler, + MessageHandler, +) +from telegram.ext.filters import MessageFilter + +from Exon import LOGGER +from Exon import dispatcher as d +from Exon.modules.disable import DisableAbleCommandHandler, DisableAbleMessageHandler + + +class ExonHandler: + def __init__(self, d): + self._dispatcher = d + + def command( + self, + command: str, + filters: Optional[MessageFilter] = None, + admin_ok: bool = False, + pass_args: bool = False, + pass_chat_data: bool = False, + run_async: bool = True, + can_disable: bool = True, + group: Optional[Union[int, str]] = 40, + ): + def _command(func): + try: + if can_disable: + self._dispatcher.add_handler( + DisableAbleCommandHandler( + command, + func, + filters=filters, + run_async=run_async, + pass_args=pass_args, + admin_ok=admin_ok, + ), + group, + ) + else: + self._dispatcher.add_handler( + CommandHandler( + command, + func, + filters=filters, + run_async=run_async, + pass_args=pass_args, + ), + group, + ) + LOGGER.debug( + f"[ᴇxᴏɴᴄᴍᴅ] ʟᴏᴀᴅᴇᴅ ʜᴀɴᴅʟᴇʀ {command} ғᴏʀ ғᴜɴᴄᴛɪᴏɴ {func.__name__} ɪɴ ɢʀᴏᴜᴘ {group}" + ) + except TypeError: + if can_disable: + self._dispatcher.add_handler( + DisableAbleCommandHandler( + command, + func, + filters=filters, + run_async=run_async, + pass_args=pass_args, + admin_ok=admin_ok, + pass_chat_data=pass_chat_data, + ) + ) + else: + self._dispatcher.add_handler( + CommandHandler( + command, + func, + filters=filters, + run_async=run_async, + pass_args=pass_args, + pass_chat_data=pass_chat_data, + ) + ) + LOGGER.debug( + f"[ExonCMD] Loaded handler {command} for function {func.__name__}" + ) + + return func + + return _command + + def message( + self, + pattern: Optional[str] = None, + can_disable: bool = True, + run_async: bool = True, + group: Optional[Union[int, str]] = 60, + friendly=None, + ): + def _message(func): + try: + if can_disable: + self._dispatcher.add_handler( + DisableAbleMessageHandler( + pattern, func, friendly=friendly, run_async=run_async + ), + group, + ) + else: + self._dispatcher.add_handler( + MessageHandler(pattern, func, run_async=run_async), group + ) + LOGGER.debug( + f"[ᴇxᴏɴᴍsɢ] Loaded filter pattern {pattern} for function {func.__name__} in group {group}" + ) + except TypeError: + if can_disable: + self._dispatcher.add_handler( + DisableAbleMessageHandler( + pattern, func, friendly=friendly, run_async=run_async + ) + ) + else: + self._dispatcher.add_handler( + MessageHandler(pattern, func, run_async=run_async) + ) + LOGGER.debug( + f"[ᴇxᴏɴᴍsɢ] ʟᴏᴀᴅᴇᴅ ғɪʟᴛᴇʀ ᴘᴀᴛᴛᴇʀɴ {pattern} ғᴏʀ ғᴜɴᴄᴛɪᴏɴ {func.__name__}" + ) + + return func + + return _message + + def callbackquery(self, pattern: str = None, run_async: bool = True): + def _callbackquery(func): + self._dispatcher.add_handler( + CallbackQueryHandler( + pattern=pattern, callback=func, run_async=run_async + ) + ) + LOGGER.debug( + f"[ᴇxᴏɴᴄᴀʟʟʙᴀᴄᴋ] ʟᴏᴀᴅᴇᴅ ᴄᴀʟʟʙᴀᴄᴋǫᴜᴇʀʏ ʜᴀɴᴅʟᴇʀ ᴡɪᴛʜ ᴘᴀᴛᴛᴇʀɴ {pattern} ғᴏʀ ғᴜɴᴄᴛɪᴏɴ {func.__name__}" + ) + return func + + return _callbackquery + + def inlinequery( + self, + pattern: Optional[str] = None, + run_async: bool = True, + pass_user_data: bool = True, + pass_chat_data: bool = True, + chat_types: List[str] = None, + ): + def _inlinequery(func): + self._dispatcher.add_handler( + InlineQueryHandler( + pattern=pattern, + callback=func, + run_async=run_async, + pass_user_data=pass_user_data, + pass_chat_data=pass_chat_data, + chat_types=chat_types, + ) + ) + LOGGER.debug( + f"[ᴇxᴏɴɪɴʟɪɴᴇ] ʟᴏᴀᴅᴇᴅ ɪɴʟɪɴᴇǫᴜᴇʀʏ ʜᴀɴᴅʟᴇʀ ᴡɪᴛʜ ᴘᴀᴛᴛᴇʀɴ {pattern} ғᴏʀ ғᴜɴᴄᴛɪᴏɴ {func.__name__} | ᴘᴀssᴇs ᴜsᴇʀ ᴅᴀᴛᴀ: {pass_user_data} | ᴘᴀssᴇs ᴄʜᴀᴛ ᴅᴀᴛᴀ: {pass_chat_data} | ᴄʜᴀᴛ ᴛʏᴘᴇs: {chat_types}" + ) + return func + + return _inlinequery + + +Exoncmd = ExonHandler(d).command +Exonmsg = ExonHandler(d).message +Exoncallback = ExonHandler(d).callbackquery +Exoninline = ExonHandler(d).inlinequery diff --git a/Exon/modules/helper_funcs/extraction.py b/Exon/modules/helper_funcs/extraction.py new file mode 100644 index 00000000..22f97f0b --- /dev/null +++ b/Exon/modules/helper_funcs/extraction.py @@ -0,0 +1,195 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +from typing import List, Optional + +from telegram import Message, MessageEntity +from telegram.error import BadRequest + +from Exon import LOGGER +from Exon.modules.users import get_user_id + + +def id_from_reply(message): + prev_message = message.reply_to_message + if not prev_message: + return None, None + user_id = ( + prev_message.sender_chat.id + if prev_message.sender_chat + else prev_message.from_user.id + ) + res = message.text.split(None, 1) + if len(res) < 2: + return user_id, "" + return user_id, res[1] + + +def extract_user(message: Message, args: List[str]) -> Optional[int]: + return extract_user_and_text(message, args)[0] + + +def extract_user_and_text( + message: Message, + args: List[str], +) -> (Optional[int], Optional[str]): + prev_message = message.reply_to_message + split_text = message.text.split(None, 1) + + if len(split_text) < 2: + return id_from_reply(message) # only option possible + + text_to_parse = split_text[1] + + text = "" + + entities = list(message.parse_entities([MessageEntity.TEXT_MENTION])) + ent = entities[0] if entities else None + # if entity offset matches (command end/text start) then all good + if entities and ent and ent.offset == len(message.text) - len(text_to_parse): + ent = entities[0] + user_id = ent.user.id + text = message.text[ent.offset + ent.length :] + + elif len(args) >= 1 and args[0][0] == "@": + user = args[0] + user_id = get_user_id(user) + if not user_id: + message.reply_text( + "ɴᴏ ɪᴅᴇᴀ ᴡʜᴏ ᴛʜɪs ᴜsᴇʀ ɪs. ʏᴏᴜ'ʟʟ ʙᴇ ᴀʙʟᴇ ᴛᴏ ɪɴᴛᴇʀᴀᴄᴛ ᴡɪᴛʜ ᴛʜᴇᴍ ɪғ " + "ʏᴏᴜ ʀᴇᴘʟʏ ᴛᴏ ᴛʜᴀᴛ ᴘᴇʀsᴏɴ's ᴍᴇssᴀɢᴇ ɪɴsᴛᴇᴀᴅ, ᴏʀ ғᴏʀᴡᴀʀᴅ ᴏɴᴇ ᴏғ ᴛʜᴀᴛ ᴜsᴇʀ ᴍᴇssᴀɢᴇs.", + ) + return None, None + res = message.text.split(None, 2) + if len(res) >= 3: + text = res[2] + + elif len(args) >= 1 and args[0].isdigit(): + user_id = int(args[0]) + res = message.text.split(None, 2) + if len(res) >= 3: + text = res[2] + + elif prev_message: + user_id, text = id_from_reply(message) + + else: + return None, None + + try: + message.bot.get_chat(user_id) + except BadRequest as excp: + if excp.message in ("User_id_invalid", "ᴄʜᴀᴛ ɴᴏᴛ ғᴏᴜɴᴅ"): + message.reply_text( + "I ᴅᴏɴ'ᴛ sᴇᴇᴍ ᴛᴏ ʜᴀᴠᴇ ɪɴᴛᴇʀᴀᴄᴛᴇᴅ ᴡɪᴛʜ ᴛʜɪs ᴜsᴇʀ ʙᴇғᴏʀᴇ - ᴘʟᴇᴀsᴇ ғᴏʀᴡᴀʀᴅ ᴀ ᴍᴇssᴀɢᴇ ғʀᴏᴍ " + "ᴛʜᴇᴍ ᴛᴏ ɢɪᴠᴇ ᴍᴇ ᴄᴏɴᴛʀᴏʟ! (ʟɪᴋᴇ ᴀ ᴠᴏᴏᴅᴏᴏ ᴅᴏʟʟ, I ɴᴇᴇᴅ ᴀ ᴘɪᴇᴄᴇ ᴏғ ᴛʜᴇᴍ ᴛᴏ ʙᴇ ᴀʙʟᴇ " + "ᴛᴏ ᴇxᴇᴄᴜᴛᴇ ᴄᴇʀᴛᴀɪɴ ᴄᴏᴍᴍᴀɴᴅs...)", + ) + else: + LOGGER.exception("ᴇxᴄᴇᴘᴛɪᴏɴ %s ᴏɴ ᴜsᴇʀ %s", excp.message, user_id) + + return None, None + + return user_id, text + + +def extract_text(message) -> str: + return ( + message.text + or message.caption + or (message.sticker.emoji if message.sticker else None) + ) + + +def extract_unt_fedban( + message: Message, + args: List[str], +) -> (Optional[int], Optional[str]): + prev_message = message.reply_to_message + split_text = message.text.split(None, 1) + + if len(split_text) < 2: + return id_from_reply(message) # only option possible + + text_to_parse = split_text[1] + + text = "" + + entities = list(message.parse_entities([MessageEntity.TEXT_MENTION])) + ent = entities[0] if entities else None + # if entity offset matches (command end/text start) then all good + if entities and ent and ent.offset == len(message.text) - len(text_to_parse): + ent = entities[0] + user_id = ent.user.id + text = message.text[ent.offset + ent.length :] + + elif len(args) >= 1 and args[0][0] == "@": + user = args[0] + user_id = get_user_id(user) + if not user_id and not isinstance(user_id, int): + message.reply_text( + "I ᴅᴏɴ'ᴛ ʜᴀᴠᴇ ᴛʜᴀᴛ ᴜsᴇʀ ɪɴ ᴍʏ ᴅʙ. " + "ʏᴏᴜ'ʟʟ be ᴀʙʟᴇ ᴛᴏ ɪɴᴛᴇʀᴀᴄᴛ ᴡɪᴛʜ ᴛʜᴇᴍ ɪғ ʏᴏᴜ ʀᴇᴘʟʏ ᴛᴏ ᴛʜᴀᴛ ᴘᴇʀsᴏɴ's ᴍᴇssᴀɢᴇ ɪɴsᴛᴇᴀᴅ, ᴏʀ ғᴏʀᴡᴀʀᴅ ᴏɴᴇ ᴏғ ᴛʜᴀᴛ ᴜsᴇʀ's ᴍᴇssᴀɢᴇs.", + ) + return None, None + res = message.text.split(None, 2) + if len(res) >= 3: + text = res[2] + + elif len(args) >= 1 and args[0].isdigit(): + user_id = int(args[0]) + res = message.text.split(None, 2) + if len(res) >= 3: + text = res[2] + + elif prev_message: + user_id, text = id_from_reply(message) + + else: + return None, None + + try: + message.bot.get_chat(user_id) + except BadRequest as excp: + if excp.message in ("User_id_invalid", "Chat not found") and not isinstance( + user_id, + int, + ): + message.reply_text( + "I ᴅᴏɴ'ᴛ sᴇᴇᴍ ᴛᴏ ʜᴀᴠᴇ ɪɴᴛᴇʀᴀᴄᴛᴇᴅ ᴡɪᴛʜ ᴛʜɪs ᴜsᴇʀ ʙᴇғᴏʀᴇ " + "ᴘʟᴇᴀsᴇ ғᴏʀᴡᴀʀᴅ ᴀ ᴍᴇssᴀɢᴇ ғʀᴏᴍ ᴛʜᴇᴍ ᴛᴏ ɢɪᴠᴇ ᴍᴇ ᴄᴏɴᴛʀᴏʟ! " + "(ʟɪᴋᴇ ᴀ ᴠᴏᴏᴅᴏᴏ ᴅᴏʟʟ, ɪ ɴᴇᴇᴅ ᴀ ᴘɪᴇᴄᴇ of ᴛʜᴇᴍ ᴛᴏ ʙᴇ ᴀʙʟᴇ ᴛᴏ ᴇxᴇᴄᴜᴛᴇ ᴄᴇʀᴛᴀɪɴ ᴄᴏᴍᴍᴀɴᴅs...)", + ) + return None, None + if excp.message != "ᴄʜᴀᴛ ɴᴏᴛ ғᴏᴜɴᴅ": + LOGGER.exception("ᴇxᴄᴇᴘᴛɪᴏɴ %s ᴏɴ ᴜsᴇʀ %s", excp.message, user_id) + return None, None + if not isinstance(user_id, int): + return None, None + + return user_id, text + + +def extract_user_fban(message: Message, args: List[str]) -> Optional[int]: + return extract_unt_fedban(message, args)[0] diff --git a/Exon/modules/helper_funcs/filters.py b/Exon/modules/helper_funcs/filters.py new file mode 100644 index 00000000..08b4d7df --- /dev/null +++ b/Exon/modules/helper_funcs/filters.py @@ -0,0 +1,72 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +from telegram import Message +from telegram.ext import MessageFilter + +from Exon import DEMONS, DEV_USERS, DRAGONS + + +class CustomFilters: + class _Supporters(MessageFilter): + def filter(self, message: Message): + return bool(message.from_user and message.from_user.id in DEMONS) + + support_filter = _Supporters() + + class _Sudoers(MessageFilter): + def filter(self, message: Message): + return bool(message.from_user and message.from_user.id in DRAGONS) + + sudo_filter = _Sudoers() + + class _Developers(MessageFilter): + def filter(self, message: Message): + return bool(message.from_user and message.from_user.id in DEV_USERS) + + dev_filter = _Developers() + + class _MimeType(MessageFilter): + def __init__(self, mimetype): + self.mime_type = mimetype + self.name = "CustomFilters.mime_type({})".format(self.mime_type) + + def filter(self, message: Message): + return bool( + message.document and message.document.mime_type == self.mime_type, + ) + + mime_type = _MimeType + + class _HasText(MessageFilter): + def filter(self, message: Message): + return bool( + message.text + or message.sticker + or message.photo + or message.document + or message.video, + ) + + has_text = _HasText() diff --git a/Exon/modules/helper_funcs/git_api.py b/Exon/modules/helper_funcs/git_api.py new file mode 100644 index 00000000..e544fc21 --- /dev/null +++ b/Exon/modules/helper_funcs/git_api.py @@ -0,0 +1,114 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import json +import urllib.request as url + +VERSION = "1.0.2" +APIURL = "http://api.github.com/repos/" + + +def vercheck() -> str: + return str(VERSION) + + +# Repo-wise stuff + + +def getData(repoURL): + try: + with url.urlopen(APIURL + repoURL + "/releases") as data_raw: + return json.loads(data_raw.read().decode()) + except: + return None + + +def getReleaseData(repoData, index): + if index < len(repoData): + return repoData[index] + return None + + +# Release-wise stuff + + +def getAuthor(releaseData): + if releaseData is None: + return None + return releaseData["author"]["login"] + + +def getAuthorUrl(releaseData): + if releaseData is None: + return None + return releaseData["author"]["html_url"] + + +def getReleaseName(releaseData): + if releaseData is None: + return None + return releaseData["name"] + + +def getReleaseDate(releaseData): + if releaseData is None: + return None + return releaseData["published_at"] + + +def getAssetsSize(releaseData): + if releaseData is None: + return None + return len(releaseData["assets"]) + + +def getAssets(releaseData): + if releaseData is None: + return None + return releaseData["assets"] + + +def getBody(releaseData): # changelog stuff + if releaseData is None: + return None + return releaseData["body"] + + +# Asset-wise stuff + + +def getReleaseFileName(asset): + return asset["name"] + + +def getReleaseFileURL(asset): + return asset["browser_download_url"] + + +def getDownloadCount(asset): + return asset["download_count"] + + +def getSize(asset): + return asset["size"] diff --git a/Exon/modules/helper_funcs/handlers.py b/Exon/modules/helper_funcs/handlers.py new file mode 100644 index 00000000..f3f8abdc --- /dev/null +++ b/Exon/modules/helper_funcs/handlers.py @@ -0,0 +1,157 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +from pyrate_limiter import ( + BucketFullException, + Duration, + Limiter, + MemoryListBucket, + RequestRate, +) +from telegram import Update +from telegram.ext import CommandHandler, Filters, MessageHandler, RegexHandler + +import Exon.modules.sql.blacklistusers_sql as sql +from Exon import ALLOW_EXCL, DEMONS, DEV_USERS, DRAGONS, TIGERS, WOLVES + +CMD_STARTERS = ("/", "!", "?", "*") if ALLOW_EXCL else ("/",) + + +class AntiSpam: + def __init__(self): + self.whitelist = ( + (DEV_USERS or []) + + (DRAGONS or []) + + (WOLVES or []) + + (DEMONS or []) + + (TIGERS or []) + ) + # Values are HIGHLY experimental, its recommended you pay attention to our commits as we will be adjusting the values over time with what suits best. + Duration.CUSTOM = 15 # Custom duration, 15 seconds + self.sec_limit = RequestRate(6, Duration.CUSTOM) # 6 / Per 15 Seconds + self.min_limit = RequestRate(20, Duration.MINUTE) # 20 / Per minute + self.hour_limit = RequestRate(100, Duration.HOUR) # 100 / Per hour + self.daily_limit = RequestRate(1000, Duration.DAY) # 1000 / Per day + self.limiter = Limiter( + self.sec_limit, + self.min_limit, + self.hour_limit, + self.daily_limit, + bucket_class=MemoryListBucket, + ) + + def check_user(self, user): + """ + Return True if user is to be ignored else False + """ + if user in self.whitelist: + return False + try: + self.limiter.try_acquire(user) + return False + except BucketFullException: + return True + + +SpamChecker = AntiSpam() +MessageHandlerChecker = AntiSpam() + + +class CustomCommandHandler(CommandHandler): + def __init__(self, command, callback, admin_ok=False, allow_edit=False, **kwargs): + super().__init__(command, callback, **kwargs) + + if allow_edit is False: + self.filters &= ~( + Filters.update.edited_message | Filters.update.edited_channel_post + ) + + def check_update(self, update): + if not isinstance(update, Update) or not update.effective_message: + return + message = update.effective_message + + try: + user_id = update.effective_user.id + except: + user_id = None + + if user_id and sql.is_user_blacklisted(user_id): + return False + + if message.text and len(message.text) > 1: + fst_word = message.text.split(None, 1)[0] + if len(fst_word) > 1 and any( + fst_word.startswith(start) for start in CMD_STARTERS + ): + + args = message.text.split()[1:] + command = fst_word[1:].split("@") + command.append(message.bot.username) + if user_id == 1087968824: + user_id = update.effective_chat.id + if not ( + command[0].lower() in self.command + and command[1].lower() == message.bot.username.lower() + ): + return None + if SpamChecker.check_user(user_id): + return None + filter_result = self.filters(update) + if filter_result: + return args, filter_result + return False + + def handle_update(self, update, dispatcher, check_result, context=None): + if context: + self.collect_additional_context(context, update, dispatcher, check_result) + return self.callback(update, context) + optional_args = self.collect_optional_args(dispatcher, update, check_result) + return self.callback(dispatcher.bot, update, **optional_args) + + def collect_additional_context(self, context, update, dispatcher, check_result): + if isinstance(check_result, bool): + context.args = update.effective_message.text.split()[1:] + else: + context.args = check_result[0] + if isinstance(check_result[1], dict): + context.update(check_result[1]) + + +class CustomRegexHandler(RegexHandler): + def __init__(self, pattern, callback, friendly="", **kwargs): + super().__init__(pattern, callback, **kwargs) + + +class CustomMessageHandler(MessageHandler): + def __init__(self, filters, callback, friendly="", allow_edit=False, **kwargs): + super().__init__(filters, callback, **kwargs) + if allow_edit is False: + self.filters &= ~( + Filters.update.edited_message | Filters.update.edited_channel_post + ) + + def check_update(self, update): + if isinstance(update, Update) and update.effective_message: + return self.filters(update) diff --git a/Exon/modules/helper_funcs/misc.py b/Exon/modules/helper_funcs/misc.py new file mode 100644 index 00000000..64575ceb --- /dev/null +++ b/Exon/modules/helper_funcs/misc.py @@ -0,0 +1,201 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +from math import ceil +from typing import Dict, List + +from telegram import ( + MAX_MESSAGE_LENGTH, + Bot, + InlineKeyboardButton, + InlineKeyboardMarkup, + InlineQueryResultArticle, + InputTextMessageContent, + ParseMode, +) +from telegram.error import TelegramError + +from Exon import NO_LOAD + + +class EqInlineKeyboardButton(InlineKeyboardButton): + def __eq__(self, other): + return self.text == other.text + + def __lt__(self, other): + return self.text < other.text + + def __gt__(self, other): + return self.text > other.text + + +def split_message(msg: str) -> List[str]: + if len(msg) < MAX_MESSAGE_LENGTH: + return [msg] + + lines = msg.splitlines(True) + small_msg = "" + result = [] + for line in lines: + if len(small_msg) + len(line) < MAX_MESSAGE_LENGTH: + small_msg += line + else: + result.append(small_msg) + small_msg = line + # Else statement at the end of the for loop, so append the leftover string. + result.append(small_msg) + + return result + + +def paginate_modules(page_n: int, module_dict: Dict, prefix, chat=None) -> List: + if not chat: + modules = sorted( + [ + EqInlineKeyboardButton( + x.__mod_name__, + callback_data="{}_module({})".format( + prefix, x.__mod_name__.lower() + ), + ) + for x in module_dict.values() + ] + ) + else: + modules = sorted( + [ + EqInlineKeyboardButton( + x.__mod_name__, + callback_data="{}_module({},{})".format( + prefix, chat, x.__mod_name__.lower() + ), + ) + for x in module_dict.values() + ] + ) + + pairs = [modules[i * 3 : (i + 1) * 3] for i in range((len(modules) + 3 - 1) // 3)] + + round_num = len(modules) / 3 + calc = len(modules) - round(round_num) + if calc in [1, 2]: + pairs.append((modules[-1],)) + + max_num_pages = ceil(len(pairs) / 8) + modulo_page = page_n % max_num_pages + + # can only have a certain amount of buttons side by side + if len(pairs) > 3: + pairs = pairs[modulo_page * 8 : 8 * (modulo_page + 1)] + [ + ( + EqInlineKeyboardButton( + "⬅️", callback_data="{}_prev({})".format(prefix, modulo_page) + ), + EqInlineKeyboardButton("• ʙᴀᴄᴋ •", callback_data="Exon_back"), + EqInlineKeyboardButton( + "➡️", callback_data="{}_next({})".format(prefix, modulo_page) + ), + ) + ] + + else: + pairs += [[EqInlineKeyboardButton("• ʙᴀᴄᴋ •", callback_data="Exon_back")]] + + return pairs + + +def article( + title: str = "", + description: str = "", + message_text: str = "", + thumb_url: str = None, + reply_markup: InlineKeyboardMarkup = None, + disable_web_page_preview: bool = False, +) -> InlineQueryResultArticle: + + return InlineQueryResultArticle( + id=uuid4(), + title=title, + description=description, + thumb_url=thumb_url, + input_message_content=InputTextMessageContent( + message_text=message_text, + disable_web_page_preview=disable_web_page_preview, + ), + reply_markup=reply_markup, + ) + + +def send_to_list( + bot: Bot, send_to: list, message: str, markdown=False, html=False +) -> None: + if html and markdown: + raise Exception("ᴄᴀɴ ᴏɴʟʏ sᴇɴᴅ ᴡɪᴛʜ ᴇɪᴛʜᴇʀ ᴍᴀʀᴋᴅᴏᴡɴ ᴏʀ ʜᴛᴍʟ!") + for user_id in set(send_to): + try: + if markdown: + bot.send_message(user_id, message, parse_mode=ParseMode.MARKDOWN) + elif html: + bot.send_message(user_id, message, parse_mode=ParseMode.HTML) + else: + bot.send_message(user_id, message) + except TelegramError: + pass # ignore users who fail + + +def build_keyboard(buttons): + keyb = [] + for btn in buttons: + if btn.same_line and keyb: + keyb[-1].append(InlineKeyboardButton(btn.name, url=btn.url)) + else: + keyb.append([InlineKeyboardButton(btn.name, url=btn.url)]) + + return keyb + + +def revert_buttons(buttons): + return "".join( + "\n[{}](buttonurl://{}:same)".format(btn.name, btn.url) + if btn.same_line + else "\n[{}](buttonurl://{})".format(btn.name, btn.url) + for btn in buttons + ) + + +def build_keyboard_parser(bot, chat_id, buttons): + keyb = [] + for btn in buttons: + if btn.url == "{rules}": + btn.url = "http://t.me/{}?start={}".format(bot.username, chat_id) + if btn.same_line and keyb: + keyb[-1].append(InlineKeyboardButton(btn.name, url=btn.url)) + else: + keyb.append([InlineKeyboardButton(btn.name, url=btn.url)]) + + return keyb + + +def is_module_loaded(name): + return name not in NO_LOAD diff --git a/Exon/modules/helper_funcs/msg_types.py b/Exon/modules/helper_funcs/msg_types.py new file mode 100644 index 00000000..b982a01a --- /dev/null +++ b/Exon/modules/helper_funcs/msg_types.py @@ -0,0 +1,235 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +from enum import IntEnum, unique + +from telegram import Message + +from Exon.modules.helper_funcs.string_handling import button_markdown_parser + + +@unique +class Types(IntEnum): + TEXT = 0 + BUTTON_TEXT = 1 + STICKER = 2 + DOCUMENT = 3 + PHOTO = 4 + AUDIO = 5 + VOICE = 6 + VIDEO = 7 + + +def get_note_type(msg: Message): + data_type = None + content = None + text = "" + raw_text = msg.text or msg.caption + args = raw_text.split(None, 2) # use python's maxsplit to separate cmd and args + note_name = args[1] + + buttons = [] + # determine what the contents of the filter are - text, image, sticker, etc + if len(args) >= 3: + offset = len(args[2]) - len( + raw_text, + ) # set correct offset relative to command + notename + text, buttons = button_markdown_parser( + args[2], + entities=msg.parse_entities() or msg.parse_caption_entities(), + offset=offset, + ) + data_type = Types.BUTTON_TEXT if buttons else Types.TEXT + elif msg.reply_to_message: + entities = msg.reply_to_message.parse_entities() + msgtext = msg.reply_to_message.text or msg.reply_to_message.caption + if len(args) >= 2 and msg.reply_to_message.text: # not caption, text + text, buttons = button_markdown_parser(msgtext, entities=entities) + data_type = Types.BUTTON_TEXT if buttons else Types.TEXT + elif msg.reply_to_message.sticker: + content = msg.reply_to_message.sticker.file_id + data_type = Types.STICKER + + elif msg.reply_to_message.document: + content = msg.reply_to_message.document.file_id + text, buttons = button_markdown_parser(msgtext, entities=entities) + data_type = Types.DOCUMENT + + elif msg.reply_to_message.photo: + content = msg.reply_to_message.photo[-1].file_id # last elem = best quality + text, buttons = button_markdown_parser(msgtext, entities=entities) + data_type = Types.PHOTO + + elif msg.reply_to_message.audio: + content = msg.reply_to_message.audio.file_id + text, buttons = button_markdown_parser(msgtext, entities=entities) + data_type = Types.AUDIO + + elif msg.reply_to_message.voice: + content = msg.reply_to_message.voice.file_id + text, buttons = button_markdown_parser(msgtext, entities=entities) + data_type = Types.VOICE + + elif msg.reply_to_message.video: + content = msg.reply_to_message.video.file_id + text, buttons = button_markdown_parser(msgtext, entities=entities) + data_type = Types.VIDEO + + return note_name, text, data_type, content, buttons + + +# note: add own args? +def get_welcome_type(msg: Message): + data_type = None + content = None + text = "" + + try: + if msg.reply_to_message: + args = msg.reply_to_message.text or msg.reply_to_message.caption + else: + args = msg.text.split( + None, + 1, + ) # use python's maxsplit to separate cmd and args + except AttributeError: + args = False + + if msg.reply_to_message: + if msg.reply_to_message.sticker: + content = msg.reply_to_message.sticker.file_id + text = None + data_type = Types.STICKER + + elif msg.reply_to_message.document: + content = msg.reply_to_message.document.file_id + text = msg.reply_to_message.caption + data_type = Types.DOCUMENT + + elif msg.reply_to_message.photo: + content = msg.reply_to_message.photo[-1].file_id # last elem = best quality + text = msg.reply_to_message.caption + data_type = Types.PHOTO + + elif msg.reply_to_message.audio: + content = msg.reply_to_message.audio.file_id + text = msg.reply_to_message.caption + data_type = Types.AUDIO + + elif msg.reply_to_message.voice: + content = msg.reply_to_message.voice.file_id + text = msg.reply_to_message.caption + data_type = Types.VOICE + + elif msg.reply_to_message.video: + content = msg.reply_to_message.video.file_id + text = msg.reply_to_message.caption + data_type = Types.VIDEO + + elif msg.reply_to_message.video_note: + content = msg.reply_to_message.video_note.file_id + text = None + data_type = Types.VIDEO_NOTE + + buttons = [] + # determine what the contents of the filter are - text, image, sticker, etc + if args: + if msg.reply_to_message: + argumen = msg.reply_to_message.caption or "" + offset = 0 # offset is no need since target was in reply + entities = msg.reply_to_message.parse_entities() + else: + argumen = args[1] + offset = len(argumen) - len( + msg.text, + ) # set correct offset relative to command + notename + entities = msg.parse_entities() + text, buttons = button_markdown_parser( + argumen, + entities=entities, + offset=offset, + ) + + if not data_type and text: + data_type = Types.BUTTON_TEXT if buttons else Types.TEXT + return text, data_type, content, buttons + + +def get_filter_type(msg: Message): + + if not msg.reply_to_message and msg.text and len(msg.text.split()) >= 3: + content = None + text = msg.text.split(None, 2)[2] + data_type = Types.TEXT + + elif ( + msg.reply_to_message + and msg.reply_to_message.text + and len(msg.text.split()) >= 2 + ): + content = None + text = msg.reply_to_message.text + data_type = Types.TEXT + + elif msg.reply_to_message and msg.reply_to_message.sticker: + content = msg.reply_to_message.sticker.file_id + text = None + data_type = Types.STICKER + + elif msg.reply_to_message and msg.reply_to_message.document: + content = msg.reply_to_message.document.file_id + text = msg.reply_to_message.caption + data_type = Types.DOCUMENT + + elif msg.reply_to_message and msg.reply_to_message.photo: + content = msg.reply_to_message.photo[-1].file_id # last elem = best quality + text = msg.reply_to_message.caption + data_type = Types.PHOTO + + elif msg.reply_to_message and msg.reply_to_message.audio: + content = msg.reply_to_message.audio.file_id + text = msg.reply_to_message.caption + data_type = Types.AUDIO + + elif msg.reply_to_message and msg.reply_to_message.voice: + content = msg.reply_to_message.voice.file_id + text = msg.reply_to_message.caption + data_type = Types.VOICE + + elif msg.reply_to_message and msg.reply_to_message.video: + content = msg.reply_to_message.video.file_id + text = msg.reply_to_message.caption + data_type = Types.VIDEO + + elif msg.reply_to_message and msg.reply_to_message.video_note: + content = msg.reply_to_message.video_note.file_id + text = None + data_type = Types.VIDEO_NOTE + + else: + text = None + data_type = None + content = None + + return text, data_type, content diff --git a/Exon/modules/helper_funcs/readable_time.py b/Exon/modules/helper_funcs/readable_time.py new file mode 100644 index 00000000..e24c1353 --- /dev/null +++ b/Exon/modules/helper_funcs/readable_time.py @@ -0,0 +1,48 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + + +def get_readable_time(seconds: int) -> str: + count = 0 + readable_time = "" + time_list = [] + time_suffix_list = [" sᴇᴄ.", "ᴍ", "ʜ", " ᴅᴀʏs"] + + while count < 4: + count += 1 + remainder, result = divmod(seconds, 60) if count < 3 else divmod(seconds, 24) + if seconds == 0 and remainder == 0: + break + time_list.append(int(result)) + seconds = int(remainder) + + for x in range(len(time_list)): + time_list[x] = str(time_list[x]) + time_suffix_list[x] + if len(time_list) == 4: + readable_time += time_list.pop() + ", " + + time_list.reverse() + readable_time += " ".join(time_list) + + return readable_time diff --git a/Exon/modules/helper_funcs/regex_helper.py b/Exon/modules/helper_funcs/regex_helper.py new file mode 100644 index 00000000..29d0f136 --- /dev/null +++ b/Exon/modules/helper_funcs/regex_helper.py @@ -0,0 +1,46 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import regex + + +def regex_searcher(regex_string, string): + try: + search = regex.search(regex_string, string, timeout=6) + except (TimeoutError, Exception): + return False + return search + + +def infinite_loop_check(regex_string): + loop_matches = [ + r"\((.{1,}[\+\*]){1,}\)[\+\*].", + r"[\(\[].{1,}\{\d(,)?\}[\)\]]\{\d(,)?\}", + r"\(.{1,}\)\{.{1,}(,)?\}\(.*\)(\+|\* |\{.*\})", + ] + for match in loop_matches: + match_1 = regex.search(match, regex_string) + if match_1: + return True + return False diff --git a/Exon/modules/helper_funcs/string_handling.py b/Exon/modules/helper_funcs/string_handling.py new file mode 100644 index 00000000..6cb90e09 --- /dev/null +++ b/Exon/modules/helper_funcs/string_handling.py @@ -0,0 +1,316 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import re +import time +from typing import Dict, List + +import bleach +import emoji +import markdown2 +from telegram import MessageEntity +from telegram.utils.helpers import escape_markdown + +# ɴᴏᴛᴇ: the url \ escape may cause double escapes +# match * (bold) (don't escape if in url) +# match _ (italics) (don't escape if in url) +# match ` (code) +# match []() (markdown link) +# else, escape *, _, `, and [ + +MATCH_MD = re.compile( + r"\*(.*?)\*|" + r"_(.*?)_|" + r"`(.*?)`|" + r"(?<!\\)(\[.*?\])(\(.*?\))|" + r"(?P<esc>[*_`\[])", +) + +# regex to find []() links -> hyperlinks/buttons +LINK_REGEX = re.compile(r"(?<!\\)\[.+?\]\((.*?)\)") +BTN_URL_REGEX = re.compile(r"(\[([^\[]+?)\]\(buttonurl:(?:/{0,2})(.+?)(:same)?\))") + + +def _selective_escape(to_parse: str) -> str: + """ + Escape all invalid markdown + :param to_parse: text to escape + :return: valid markdown string + """ + offset = 0 # offset to be used as adding a \ character causes the string to shift + for match in MATCH_MD.finditer(to_parse): + if match.group("esc"): + ent_start = match.start() + to_parse = ( + to_parse[: ent_start + offset] + "\\" + to_parse[ent_start + offset :] + ) + offset += 1 + return to_parse + + +# This is a fun one. +def _calc_emoji_offset(to_calc) -> int: + # Get all emoji in text. + emoticons = emoji.get_emoji_regexp().finditer(to_calc) + # Check the utf16 length of the emoji to determine the offset it caused. + # Normal, 1 character emoji don't affect; hence sub 1. + # special, eg with two emoji characters (eg face, and skin col) will have length 2, so by subbing one we + # know we'll get one extra offset, + return sum(len(e.group(0).encode("utf-16-le")) // 2 - 1 for e in emoticons) + + +def markdown_parser( + txt: str, entities: Dict[MessageEntity, str] = None, offset: int = 0 +) -> str: + """ + Parse a string, escaping all invalid markdown entities. + Escapes URL's so as to avoid URL mangling. + Re-adds any telegram code entities obtained from the entities object. + :param txt: text to parse + :param entities: dict of message entities in text + :param offset: message offset - command and notename length + :return: valid markdown string + """ + if not entities: + entities = {} + if not txt: + return "" + + prev = 0 + res = "" + # Loop over all message entities, and: + # reinsert code + # escape free-standing urls + for ent, ent_text in entities.items(): + if ent.offset < -offset: + continue + + start = ent.offset + offset # start of entity + end = ent.offset + offset + ent.length - 1 # end of entity + + # we only care about code, url, text links + if ent.type in ("code", "url", "text_link"): + # count emoji to switch counter + count = _calc_emoji_offset(txt[:start]) + start -= count + end -= count + + # URL handling -> do not escape if in [](), escape otherwise. + if ent.type == "url": + if any( + match.start(1) <= start and end <= match.end(1) + for match in LINK_REGEX.finditer(txt) + ): + continue + # else, check the escapes between the prev and last and forcefully escape the url to avoid mangling + else: + # TODO: investigate possible offset bug when lots of emoji are present + res += _selective_escape(txt[prev:start] or "") + escape_markdown( + ent_text + ) + + # code handling + elif ent.type == "code": + res += _selective_escape(txt[prev:start]) + "`" + ent_text + "`" + + # handle markdown/html links + elif ent.type == "text_link": + res += _selective_escape(txt[prev:start]) + "[{}]({})".format( + ent_text, ent.url + ) + + end += 1 + + # anything else + else: + continue + + prev = end + + res += _selective_escape(txt[prev:]) # add the rest of the text + return res + + +def button_markdown_parser( + txt: str, + entities: Dict[MessageEntity, str] = None, + offset: int = 0, +) -> (str, List): + markdown_note = markdown_parser(txt, entities, offset) + prev = 0 + note_data = "" + buttons = [] + for match in BTN_URL_REGEX.finditer(markdown_note): + # Check if btnurl is escaped + n_escapes = 0 + to_check = match.start(1) - 1 + while to_check > 0 and markdown_note[to_check] == "\\": + n_escapes += 1 + to_check -= 1 + + # if even, not escaped -> create button + if n_escapes % 2 == 0: + # create a thruple with button label, url, and newline status + buttons.append((match.group(2), match.group(3), bool(match.group(4)))) + note_data += markdown_note[prev : match.start(1)] + prev = match.end(1) + # if odd, escaped -> move along + else: + note_data += markdown_note[prev:to_check] + prev = match.start(1) - 1 + + note_data += markdown_note[prev:] + + return note_data, buttons + + +def escape_invalid_curly_brackets(text: str, valids: List[str]) -> str: + new_text = "" + idx = 0 + while idx < len(text): + if text[idx] == "{": + if idx + 1 < len(text) and text[idx + 1] == "{": + idx += 2 + new_text += "{{{{" + continue + else: + success = False + for v in valids: + if text[idx:].startswith("{" + v + "}"): + success = True + break + if success: + new_text += text[idx : idx + len(v) + 2] + idx += len(v) + 2 + continue + else: + new_text += "{{" + + elif text[idx] == "}": + if idx + 1 < len(text) and text[idx + 1] == "}": + idx += 2 + new_text += "}}}}" + continue + else: + new_text += "}}" + + else: + new_text += text[idx] + idx += 1 + + return new_text + + +SMART_OPEN = "“" +SMART_CLOSE = "”" +START_CHAR = ("'", '"', SMART_OPEN) + + +def split_quotes(text: str) -> List: + if any(text.startswith(char) for char in START_CHAR): + counter = 1 # ignore first char -> is some kind of quote + while counter < len(text): + if text[counter] == "\\": + counter += 1 + elif text[counter] == text[0] or ( + text[0] == SMART_OPEN and text[counter] == SMART_CLOSE + ): + break + counter += 1 + else: + return text.split(None, 1) + + # 1 to avoid starting quote, and counter is exclusive so avoids ending + key = remove_escapes(text[1:counter].strip()) + # index will be in range, or `else` would have been executed and returned + rest = text[counter + 1 :].strip() + if not key: + key = text[0] + text[0] + return list(filter(None, [key, rest])) + else: + return text.split(None, 1) + + +def remove_escapes(text: str) -> str: + counter = 0 + res = "" + is_escaped = False + while counter < len(text): + if is_escaped: + res += text[counter] + is_escaped = False + elif text[counter] == "\\": + is_escaped = True + else: + res += text[counter] + counter += 1 + return res + + +def escape_chars(text: str, to_escape: List[str]) -> str: + to_escape.append("\\") + new_text = "" + for x in text: + if x in to_escape: + new_text += "\\" + new_text += x + return new_text + + +def extract_time(message, time_val): + if any(time_val.endswith(unit) for unit in ("m", "h", "d")): + unit = time_val[-1] + time_num = time_val[:-1] # type: str + if not time_num.isdigit(): + message.reply_text("ɪɴᴠᴀʟɪᴅ ᴛɪᴍᴇ ᴀᴍᴏᴜɴᴛ sᴘᴇᴄɪғɪᴇᴅ.") + return "" + + if unit == "m": + bantime = int(time.time() + int(time_num) * 60) + elif unit == "h": + bantime = int(time.time() + int(time_num) * 60 * 60) + elif unit == "d": + bantime = int(time.time() + int(time_num) * 24 * 60 * 60) + else: + # how even...? + return "" + return bantime + message.reply_text( + "ɪɴᴠᴀʟɪᴅ ᴛɪᴍᴇ ᴛʏᴘᴇ sᴘᴇᴄɪғɪᴇᴅ. ᴇxᴘᴇᴄᴛᴇᴅ m,h, or d, ɢᴏᴛ: {}".format( + time_val[-1], + ), + ) + return "" + + +def markdown_to_html(text): + text = text.replace("*", "**") + text = text.replace("`", "```") + text = text.replace("~", "~~") + _html = markdown2.markdown(text, extras=["strike", "underline"]) + return bleach.clean( + _html, + tags=["strong", "em", "a", "code", "pre", "strike", "u"], + strip=True, + )[:-1] diff --git "a/Exon/modules/helper_funcs/telethn/ABISHNOI COPYRIGHT \302\251" "b/Exon/modules/helper_funcs/telethn/ABISHNOI COPYRIGHT \302\251" new file mode 100644 index 00000000..a7ec3b8e --- /dev/null +++ "b/Exon/modules/helper_funcs/telethn/ABISHNOI COPYRIGHT \302\251" @@ -0,0 +1,27 @@ +""" +MIT License + +Copyright (c) 2022 ABISHNOI + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" +# ""DEAR PRO PEOPLE, DON'T REMOVE & CHANGE THIS LINE +# TG :- @Abishnoi +# MY ALL BOTS :- Abishnoi_bots +# GITHUB :- KingAbishnoi "" \ No newline at end of file diff --git a/Exon/modules/helper_funcs/telethn/__init__.py b/Exon/modules/helper_funcs/telethn/__init__.py new file mode 100644 index 00000000..d0f68299 --- /dev/null +++ b/Exon/modules/helper_funcs/telethn/__init__.py @@ -0,0 +1,36 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +# ""DEAR PRO PEOPLE, DON'T REMOVE & CHANGE THIS LINE +# TG :- @Abishnoi1M +# MY ALL BOTS :- Abishnoi_bots +# GITHUB :- KingAbishnoi "" + +from Exon import DEMONS, DEV_USERS, DRAGONS, TIGERS, WOLVES, telethn + +IMMUNE_USERS = DRAGONS + WOLVES + DEMONS + TIGERS + DEV_USERS + +IMMUNE_USERS = ( + list(DRAGONS) + list(WOLVES) + list(DEMONS) + list(TIGERS) + list(DEV_USERS) +) diff --git a/Exon/modules/helper_funcs/telethn/chatstatus.py b/Exon/modules/helper_funcs/telethn/chatstatus.py new file mode 100644 index 00000000..1859518b --- /dev/null +++ b/Exon/modules/helper_funcs/telethn/chatstatus.py @@ -0,0 +1,129 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" +# ""DEAR PRO PEOPLE, DON'T REMOVE & CHANGE THIS LINE +# TG :- @Abishnoi1M +# MY ALL BOTS :- Abishnoi_bots +# GITHUB :- KingAbishnoi "" + +from telethon.tl.types import ChannelParticipantsAdmins + +from Exon import DRAGONS +from Exon.modules.helper_funcs.telethn import IMMUNE_USERS, telethn + + +async def user_is_ban_protected(user_id: int, message): + status = False + if message.is_private or user_id in (IMMUNE_USERS): + return True + + async for user in telethn.iter_participants( + message.chat_id, + filter=ChannelParticipantsAdmins, + ): + if user_id == user.id: + status = True + break + return status + + +async def user_is_admin(user_id: int, message): + status = False + if message.is_private: + return True + + async for user in telethn.iter_participants( + message.chat_id, + filter=ChannelParticipantsAdmins, + ): + if user_id == user.id or user_id in DRAGONS: + status = True + break + return status + + +async def is_user_admin(user_id: int, chat_id): + status = False + async for user in telethn.iter_participants( + chat_id, + filter=ChannelParticipantsAdmins, + ): + if user_id == user.id or user_id in DRAGONS: + status = True + break + return status + + +async def Exon_is_admin(chat_id: int): + status = False + Exon = await telethn.get_me() + async for user in telethn.iter_participants( + chat_id, + filter=ChannelParticipantsAdmins, + ): + if Exon.id == user.id: + status = True + break + return status + + +async def is_user_in_chat(chat_id: int, user_id: int): + status = False + async for user in telethn.iter_participants(chat_id): + if user_id == user.id: + status = True + break + return status + + +async def can_change_info(message): + return message.chat.admin_rights.change_info if message.chat.admin_rights else False + + +async def can_ban_users(message): + return message.chat.admin_rights.ban_users if message.chat.admin_rights else False + + +async def can_pin_messages(message): + return ( + message.chat.admin_rights.pin_messages if message.chat.admin_rights else False + ) + + +async def can_invite_users(message): + return ( + message.chat.admin_rights.invite_users if message.chat.admin_rights else False + ) + + +async def can_add_admins(message): + return message.chat.admin_rights.add_admins if message.chat.admin_rights else False + + +async def can_delete_messages(message): + + if message.is_private: + return True + if message.chat.admin_rights: + return message.chat.admin_rights.delete_messages + return False diff --git a/Exon/modules/heroku.py b/Exon/modules/heroku.py new file mode 100644 index 00000000..1b08b50a --- /dev/null +++ b/Exon/modules/heroku.py @@ -0,0 +1,143 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import asyncio +import math +import os + +import heroku3 +import requests + +from Exon import HEROKU_API_KEY, HEROKU_APP_NAME, OWNER_ID +from Exon.events import register + +heroku_api = "https://api.heroku.com" +Heroku = heroku3.from_key(HEROKU_API_KEY) + + +@register(pattern="^/usage$") +async def dyno_usage(dyno): + if dyno.fwd_from: + return + if dyno.sender_id != OWNER_ID: + return + """ + Get your account Dyno Usage + """ + die = await dyno.reply("`Processing...`") + useragent = ( + "Mozilla/5.0 (Linux; Android 10; SM-G975F) " + "AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/80.0.3987.149 Mobile Safari/537.36" + ) + user_id = Heroku.account().id + headers = { + "User-Agent": useragent, + "Authorization": f"Bearer {HEROKU_API_KEY}", + "Accept": "application/vnd.heroku+json; version=3.account-quotas", + } + path = f"/accounts/{user_id}/actions/get-quota" + r = requests.get(heroku_api + path, headers=headers) + if r.status_code != 200: + return await die.edit("`ᴇʀʀᴏʀ: sᴏᴍᴇᴛʜɪɴɢ ʙᴀᴅ happened`\n\n" f">.`{r.reason}`\n") + result = r.json() + quota = result["account_quota"] + quota_used = result["quota_used"] + + """ - Used - """ + remaining_quota = quota - quota_used + percentage = math.floor(remaining_quota / quota * 100) + minutes_remaining = remaining_quota / 60 + hours = math.floor(minutes_remaining / 60) + minutes = math.floor(minutes_remaining % 60) + day = math.floor(hours / 24) + + """ - Current - """ + App = result["apps"] + try: + App[0]["quota_used"] + except IndexError: + AppQuotaUsed = 0 + AppPercentage = 0 + else: + AppQuotaUsed = App[0]["quota_used"] / 60 + AppPercentage = math.floor(App[0]["quota_used"] * 100 / quota) + AppHours = math.floor(AppQuotaUsed / 60) + AppMinutes = math.floor(AppQuotaUsed % 60) + await asyncio.sleep(1.5) + + return await die.edit( + " **ᴅʏɴᴏ ᴜsᴀɢᴇ **:\n\n" + f" » ᴅʏɴᴏ ᴜsᴀɢᴇ ғᴏʀ **{HEROKU_APP_NAME}**:\n" + f" • `{AppHours}`**h** `{AppMinutes}`**m** " + f"**|** [`{AppPercentage}`**%**]" + "\n\n" + " » ᴅʏɴᴏ ʜᴏᴜʀs ǫᴜᴏᴛᴀ ʀᴇᴍᴀɪɴɪɴɢ ᴛʜɪs ᴍᴏɴᴛʜ:\n" + f" • `{hours}`**h** `{minutes}`**m** " + f"**|** [`{percentage}`**%**]" + f"\n\n » ᴅʏɴᴏs ʜᴇʀᴏᴋᴜ {day} ᴅᴀʏs ʟᴇғᴛ" + ) + + +@register(pattern="^/logs$") +async def _(dyno): + if dyno.fwd_from: + return + if dyno.sender_id != OWNER_ID: + return + try: + Heroku = heroku3.from_key(HEROKU_API_KEY) + app = Heroku.app(HEROKU_APP_NAME) + except: + return await dyno.reply( + " ᴘʟᴇᴀsᴇ ᴍᴀᴋᴇ sᴜʀᴇ ʏᴏᴜʀ ʜᴇʀᴏᴋᴜ ᴀᴘɪ ᴋᴇʏ, ʏᴏᴜʀ ᴀᴘᴘ ɴᴀᴍᴇ ᴀʀᴇ ᴄᴏɴғɪɢᴜʀᴇᴅ ᴄᴏʀʀᴇᴄᴛʟʏ ɪɴ ᴛʜᴇ ʜᴇʀᴏᴋᴜ" + ) + v = await dyno.reply("ɢᴇᴛᴛɪɴɢ ʟᴏɢs.......") + with open("logs.txt", "w") as log: + log.write(app.get_log()) + await v.edit("ɢᴏᴛ ᴛʜᴇ ʟᴏɢs ᴡᴀɪᴛ ᴀ sᴇᴄ") + await dyno.client.send_file( + dyno.chat_id, + "logs.txt", + reply_to=dyno.id, + caption="ᴇxᴏɴ ʟᴏɢs.", + ) + + await asyncio.sleep(5) + await v.delete() + return os.remove("logs.txt") + + +def prettyjson(obj, indent=2, maxlinelength=80): + """Renders JSON content with indentation and line splits/concatenations to fit maxlinelength. + Only dicts, lists and basic types are supported""" + + items, _ = getsubitems( + obj, + itemkey="", + islast=True, + maxlinelength=maxlinelength - indent, + indent=indent, + ) + return indentitems(items, indent, level=0) diff --git a/Exon/modules/ids.py b/Exon/modules/ids.py new file mode 100644 index 00000000..fda14868 --- /dev/null +++ b/Exon/modules/ids.py @@ -0,0 +1,81 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import logging + +from pyrogram import filters + +from Exon import pgram + +logging.basicConfig(level=logging.DEBUG) + + +@pgram.on_message(filters.command("id")) +async def getid(client, message): + chat = message.chat + your_id = message.from_user.id + message_id = message.message_id + reply = message.reply_to_message + + text = f"**[𝗠𝗲𝘀𝘀𝗮𝗴𝗲 𝗜𝗗:]({message.link})** `{message_id}`\n" + text += f"**[𝗬𝗼𝘂𝗿 𝗜𝗗:](tg://user?id={your_id})** `{your_id}`\n" + + if not message.command: + message.command = message.text.split() + + if not message.command: + message.command = message.text.split() + + if len(message.command) == 2: + try: + split = message.text.split(None, 1)[1].strip() + user_id = (await client.get_users(split)).id + text += f"**[𝗨𝘀𝗲𝗿 𝗜𝗗:](tg://user?id={user_id})** `{user_id}`\n" + + except Exception: + return await message.reply_text("This user doesn't exist.", quote=True) + + text += f"**[𝗖𝗵𝗮𝘁 𝗜𝗗:](https://t.me/{chat.username})** `{chat.id}`\n\n" + + if ( + not getattr(reply, "empty", True) + and not message.forward_from_chat + and not reply.sender_chat + ): + text += f"**[𝗥𝗲𝗽𝗹𝗶𝗲𝗱 𝗠𝗲𝘀𝘀𝗮𝗴𝗲 𝗜𝗗:]({reply.link})** `{reply.message_id}`\n" + text += f"**[𝗥𝗲𝗽𝗹𝗶𝗲𝗱 𝗨𝘀𝗲𝗿 𝗜𝗗:](tg://user?id={reply.from_user.id})** `{reply.from_user.id}`\n\n" + + if reply and reply.forward_from_chat: + text += f"ᴛʜᴇ ғᴏʀᴡᴀʀᴅᴇᴅ ᴄʜᴀɴɴᴇʟ, {reply.forward_from_chat.title}, ʜᴀs ᴀɴ ɪᴅ ᴏғ `{reply.forward_from_chat.id}`\n\n" + print(reply.forward_from_chat) + + if reply and reply.sender_chat: + text += f"ɪᴅ ᴏғ ᴛʜᴇ ʀᴇᴘʟɪᴇᴅ ᴄʜᴀᴛ/ᴄʜᴀɴɴᴇʟ, ɪs `{reply.sender_chat.id}`" + print(reply.sender_chat) + + await message.reply_text( + text, + disable_web_page_preview=True, + parse_mode="md", + ) diff --git a/Exon/modules/imgeditor.py b/Exon/modules/imgeditor.py new file mode 100644 index 00000000..b350b2b2 --- /dev/null +++ b/Exon/modules/imgeditor.py @@ -0,0 +1,478 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +from pyrogram import filters +from pyrogram.types import ( + CallbackQuery, + InlineKeyboardButton, + InlineKeyboardMarkup, + Message, +) + +from Exon import BOT_NAME, pgram + +# By @AbishnoMF +from Exon.utils.resources.ImageEditor.edit_1 import ( # pylint:disable=import-error + black_white, + box_blur, + bright, + g_blur, + mix, + normal_blur, +) +from Exon.utils.resources.ImageEditor.edit_2 import ( # pylint:disable=import-error + cartoon, + circle_with_bg, + circle_without_bg, + contrast, + edge_curved, + pencil, + sepia_mode, + sticker, +) +from Exon.utils.resources.ImageEditor.edit_3 import ( # pylint:disable=import-error + black_border, + blue_border, + green_border, + red_border, +) +from Exon.utils.resources.ImageEditor.edit_4 import ( # pylint:disable=import-error + inverted, + removebg_plain, + removebg_sticker, + removebg_white, + rotate_90, + rotate_180, + rotate_270, + round_sticker, +) +from Exon.utils.resources.ImageEditor.edit_5 import ( # pylint:disable=import-error + normalglitch_1, + normalglitch_2, + normalglitch_3, + normalglitch_4, + normalglitch_5, + scanlineglitch_1, + scanlineglitch_2, + scanlineglitch_3, + scanlineglitch_4, + scanlineglitch_5, +) + +lel = 00000000 +# pylint:disable=import-error +@pgram.on_message(filters.command(["edit", "editor"])) +async def photo(client: pgram, message: Message): + try: + if not message.reply_to_message.photo: + await client.send_message(message.chat.id, "ʀᴇᴘʟʏ ᴛᴏ ᴀɴ ɪᴍᴀɢᴇ ᴍᴀɴ!") + return + except: + return + global lel + try: + lel = message.from_user.id + except: + return + try: + await client.send_message( + chat_id=message.chat.id, + text="sᴇʟᴇᴄᴛ ʏᴏᴜʀ ʀᴇǫᴜɪʀᴇᴅ ᴍᴏᴅᴇ ғʀᴏᴍ ʙᴇʟᴏᴡ!ㅤㅤ", + reply_markup=InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton(text="ʙʀɪɢʜᴛᴇɴ", callback_data="bright"), + InlineKeyboardButton(text="ᴍɪxᴇᴅ", callback_data="mix"), + InlineKeyboardButton(text="ʙ&ᴡ", callback_data="b|w"), + ], + [ + InlineKeyboardButton(text="ᴄɪʀᴄʟᴇ", callback_data="circle"), + InlineKeyboardButton(text="ʙʟᴜʀ", callback_data="blur"), + InlineKeyboardButton(text="ʙᴏʀᴅᴇʀ", callback_data="border"), + ], + [ + InlineKeyboardButton(text="sᴛɪᴄᴋᴇʀ", callback_data="stick"), + InlineKeyboardButton(text="ʀᴏᴛᴀᴛᴇ", callback_data="rotate"), + InlineKeyboardButton(text="ᴄᴏɴᴛʀᴀsᴛ", callback_data="contrast"), + ], + [ + InlineKeyboardButton(text="sᴇᴘɪᴀ", callback_data="sepia"), + InlineKeyboardButton(text="ᴘᴇɴᴄɪʟ", callback_data="pencil"), + InlineKeyboardButton(text="ᴄᴀʀᴛᴏᴏɴ", callback_data="cartoon"), + ], + [ + InlineKeyboardButton(text="ɪɴᴠᴇʀᴛ", callback_data="inverted"), + InlineKeyboardButton(text="ɢʟɪᴛᴄʜ", callback_data="glitch"), + InlineKeyboardButton( + text="ʀᴇᴍᴏᴠᴇ ʙɢ", callback_data="removebg" + ), + ], + [ + InlineKeyboardButton(text="ᴄʟᴏsᴇ", callback_data="close_e"), + ], + ] + ), + reply_to_message_id=message.reply_to_message.message_id, + ) + except Exception as e: + print(f"photomarkup error - {str(e)}") + if "USER_IS_BLOCKED" in str(e): + return + try: + await message.reply_text("sᴏᴍᴇᴛʜɪɴɢ ᴡᴇɴᴛ ᴡʀᴏɴɢ!", quote=True) + except Exception: + return + + +@pgram.on_callback_query() +async def cb_handler(client: pgram, query: CallbackQuery): + user_id = query.from_user.id + if lel == user_id: + if query.data == "removebg": + await query.message.edit_text( + "**sᴇʟᴇᴄᴛ ʀᴇǫᴜɪʀᴇᴅ ᴍᴏᴅᴇ**ㅤㅤㅤㅤ", + reply_markup=InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + text="ᴡɪᴛʜ ᴡʜɪᴛᴇ ʙɢ", callback_data="rmbgwhite" + ), + InlineKeyboardButton( + text="ᴡɪᴛʜᴏᴜᴛ ʙɢ", callback_data="rmbgplain" + ), + ], + [ + InlineKeyboardButton( + text="sᴛɪᴄᴋᴇʀ", callback_data="rmbgsticker" + ) + ], + ] + ), + ) + elif query.data == "stick": + await query.message.edit( + "**sᴇʟᴇᴄᴛ ᴀ ᴛʏᴘᴇ**", + reply_markup=InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton(text="ɴᴏʀᴍᴀʟ", callback_data="stkr"), + InlineKeyboardButton( + text="ᴇᴅɢᴇ ᴄᴜʀᴠᴇᴅ", callback_data="cur_ved" + ), + ], + [ + InlineKeyboardButton( + text="ᴄɪʀᴄʟᴇ", callback_data="circle_sticker" + ) + ], + ] + ), + ) + elif query.data == "rotate": + await query.message.edit_text( + "**sᴇʟᴇᴄᴛ ᴛʜᴇ ᴅᴇɢʀᴇᴇ**", + reply_markup=InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton(text="180", callback_data="180"), + InlineKeyboardButton(text="90", callback_data="90"), + ], + [InlineKeyboardButton(text="270", callback_data="270")], + ] + ), + ) + + elif query.data == "glitch": + await query.message.edit_text( + "**sᴇʟᴇᴄᴛ ʀᴇǫᴜɪʀᴇᴅ ᴍᴏᴅᴇ**ㅤㅤㅤㅤ", + reply_markup=InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + text="ɴᴏʀᴍᴀʟ", callback_data="normalglitch" + ), + InlineKeyboardButton( + text="sᴄᴀɴ ʟɪɴᴇs", callback_data="scanlineglitch" + ), + ] + ] + ), + ) + elif query.data == "normalglitch": + await query.message.edit_text( + "**sᴇʟᴇᴄᴛ ɢʟɪᴛᴄʜ ᴘᴏᴡᴇʀ ʟᴇᴠᴇʟ**", + reply_markup=InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + text="1", callback_data="normalglitch1" + ), + InlineKeyboardButton( + text="2", callback_data="normalglitch2" + ), + InlineKeyboardButton( + text="3", callback_data="normalglitch3" + ), + ], + [ + InlineKeyboardButton( + text="4", callback_data="normalglitch4" + ), + InlineKeyboardButton( + text="5", callback_data="normalglitch5" + ), + ], + ] + ), + ) + elif query.data == "scanlineglitch": + await query.message.edit_text( + "**sᴇʟᴇᴄᴛ ɢʟɪᴛᴄʜ ᴘᴏᴡᴇʀ ʟᴇᴠᴇʟ**", + reply_markup=InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + text="1", callback_data="scanlineglitch1" + ), + InlineKeyboardButton( + text="2", callback_data="scanlineglitch2" + ), + InlineKeyboardButton( + text="3", callback_data="scanlineglitch3" + ), + ], + [ + InlineKeyboardButton( + text="4", callback_data="scanlineglitch4" + ), + InlineKeyboardButton( + text="5", callback_data="scanlineglitch5" + ), + ], + ] + ), + ) + elif query.data == "blur": + await query.message.edit( + "**sᴇʟᴇᴄᴛ ᴀ ᴛʏᴘᴇ**", + reply_markup=InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton(text="ʙᴏx", callback_data="box"), + InlineKeyboardButton(text="ɴᴏʀᴍᴀʟ", callback_data="normal"), + ], + [InlineKeyboardButton(text="ɢᴀᴜssɪᴀɴ", callback_data="gas")], + ] + ), + ) + elif query.data == "circle": + await query.message.edit_text( + "**sᴇʟᴇᴄᴛ ʀᴇǫᴜɪʀᴇᴅ ᴍᴏᴅᴇ**", + reply_markup=InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + text="ᴡɪᴛʜ ʙɢ", callback_data="circlewithbg" + ), + InlineKeyboardButton( + text="ᴡɪᴛʜᴏᴜᴛ ʙɢ", callback_data="circlewithoutbg" + ), + ] + ] + ), + ) + elif query.data == "border": + await query.message.edit( + "**sᴇʟᴇᴄᴛ ʙᴏʀᴅᴇʀ ᴄᴏʟᴏᴜʀ**", + reply_markup=InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton(text="ʀᴇᴅ", callback_data="red"), + InlineKeyboardButton(text="ɢʀᴇᴇɴ", callback_data="green"), + ], + [ + InlineKeyboardButton(text="ʙʟᴀᴄᴋ", callback_data="black"), + InlineKeyboardButton(text="ʙʟᴜᴇ", callback_data="blue"), + ], + ] + ), + ) + + elif query.data == "bright": + await query.message.delete() + await bright(client, query.message) + + elif query.data == "close_e": + await query.message.delete() + + elif query.data == "mix": + await query.message.delete() + await mix(client, query.message) + + elif query.data == "b|w": + await query.message.delete() + await black_white(client, query.message) + + elif query.data == "circlewithbg": + await query.message.delete() + await circle_with_bg(client, query.message) + + elif query.data == "circlewithoutbg": + await query.message.delete() + await circle_without_bg(client, query.message) + + elif query.data == "green": + await query.message.delete() + await green_border(client, query.message) + + elif query.data == "blue": + await query.message.delete() + await blue_border(client, query.message) + + elif query.data == "red": + await query.message.delete() + await red_border(client, query.message) + # AbishnoiMF + elif query.data == "black": + await query.message.delete() + await black_border(client, query.message) + + elif query.data == "circle_sticker": + await query.message.delete() + await round_sticker(client, query.message) + + elif query.data == "inverted": + await query.message.delete() + await inverted(client, query.message) + + elif query.data == "stkr": + await query.message.delete() + await sticker(client, query.message) + + elif query.data == "cur_ved": + await query.message.delete() + await edge_curved(client, query.message) + + elif query.data == "90": + await query.message.delete() + await rotate_90(client, query.message) + + elif query.data == "180": + await query.message.delete() + await rotate_180(client, query.message) + + elif query.data == "270": + await query.message.delete() + await rotate_270(client, query.message) + + elif query.data == "contrast": + await query.message.delete() + await contrast(client, query.message) + + elif query.data == "box": + await query.message.delete() + await box_blur(client, query.message) + + elif query.data == "gas": + await query.message.delete() + await g_blur(client, query.message) + + elif query.data == "normal": + await query.message.delete() + await normal_blur(client, query.message) + + elif query.data == "sepia": + await query.message.delete() + await sepia_mode(client, query.message) + + elif query.data == "pencil": + await query.message.delete() + await pencil(client, query.message) + + elif query.data == "cartoon": + await query.message.delete() + await cartoon(client, query.message) + + elif query.data == "normalglitch1": + await query.message.delete() + await normalglitch_1(client, query.message) + + elif query.data == "normalglitch2": + await query.message.delete() + await normalglitch_2(client, query.message) + + elif query.data == "normalglitch3": + await normalglitch_3(client, query.message) + + elif query.data == "normalglitch4": + await query.message.delete() + await normalglitch_4(client, query.message) + + elif query.data == "normalglitch5": + await query.message.delete() + await normalglitch_5(client, query.message) + + elif query.data == "scanlineglitch1": + await query.message.delete() + await scanlineglitch_1(client, query.message) + + elif query.data == "scanlineglitch2": + await query.message.delete() + await scanlineglitch_2(client, query.message) + + elif query.data == "scanlineglitch3": + await query.message.delete() + await scanlineglitch_3(client, query.message) + + elif query.data == "scanlineglitch4": + await query.message.delete() + await scanlineglitch_4(client, query.message) + + elif query.data == "scanlineglitch5": + await query.message.delete() + await scanlineglitch_5(client, query.message) + + elif query.data == "rmbgwhite": + await query.message.delete() + await removebg_white(client, query.message) + + elif query.data == "rmbgplain": + await query.message.delete() + await removebg_plain(client, query.message) + + elif query.data == "rmbgsticker": + await removebg_sticker(client, query.message) + + +__mod_name__ = "𝙴ᴅɪᴛᴏʀ" +__help__ = f""" +{BOT_NAME} ʜᴀᴠᴇ sᴏᴍᴇ ᴀᴅᴠᴀɴᴄᴇᴅ ɪᴍᴀɢᴇ ᴇᴅɪᴛɪɴɢ ᴛᴏᴏʟs ɪɴʙᴜɪʟᴛ + +ʙʀɪɢʜᴛ, ᴄɪʀᴄʟᴇ, ʀᴇᴍʙɢ, ʙʟᴜʀ, ʙᴏʀᴅᴇʀ, ғʟɪᴘ, ɢʟɪᴛᴄʜ, sᴛɪᴄᴋᴇʀ ᴍᴀᴋᴇʀ ᴀɴᴅ ᴍᴏʀᴇ + + +⍟ /edit [reply to image]*:* `ᴏᴘᴇɴ ᴛʜᴇ ɪᴍᴀɢᴇ ᴇᴅɪᴛᴏʀ ` + +⍟ /rmbg [REPLY]*:* `ʀᴇᴍᴏᴠᴇ BG ᴏғ ʀᴇᴘʟɪᴇᴅ ɪᴍᴀɢᴇ/sᴛɪᴄᴋᴇʀ `. +""" diff --git a/Exon/modules/is_karma_mongo.py b/Exon/modules/is_karma_mongo.py new file mode 100644 index 00000000..0203b2cc --- /dev/null +++ b/Exon/modules/is_karma_mongo.py @@ -0,0 +1,47 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" +# ""DEAR PRO PEOPLE, DON'T REMOVE & CHANGE THIS LINE +# TG :- @Abishnoi1M +# MY ALL BOTS :- Abishnoi_bots +# GITHUB :- KingAbishnoi "" + +from Exon import db + +karma_statusdb = db.karma_status + + +def is_karma(chat_id): + karma = karma_statusdb.find_one({"chat_id": chat_id}) + + +def set_karma(chat_id): + karma = is_karma(chat_id) + if not karma: + karma_statusdb.insert_one({"chat_id": chat_id}) + + +def rem_karma(chat_id): + karma = is_karma(chat_id) + if karma: + karma_statusdb.delete_one({"chat_id": chat_id}) diff --git a/Exon/modules/json.py b/Exon/modules/json.py new file mode 100644 index 00000000..345f091b --- /dev/null +++ b/Exon/modules/json.py @@ -0,0 +1,85 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import io + +from telethon.tl import functions, types + +from Exon import telethn +from Exon.events import register + + +async def is_register_admin(chat, user): + if isinstance(chat, (types.InputPeerChannel, types.InputChannel)): + + return isinstance( + ( + await telethn(functions.channels.GetParticipantRequest(chat, user)) + ).participant, + (types.ChannelParticipantAdmin, types.ChannelParticipantCreator), + ) + if isinstance(chat, types.InputPeerChat): + + ui = await telethn.get_peer_id(user) + ps = ( + await telethn(functions.messages.GetFullChatRequest(chat.chat_id)) + ).full_chat.participants.participants + return isinstance( + next((p for p in ps if p.user_id == ui), None), + (types.ChatParticipantAdmin, types.ChatParticipantCreator), + ) + return None + + +@register(pattern="^/json$") +async def _(event): + if event.fwd_from: + return + if event.is_group: + if await is_register_admin(event.input_chat, event.message.sender_id): + pass + elif event.chat_id != id or event.sender_id != users: + return + the_real_message = None + reply_to_id = None + if event.reply_to_msg_id: + previous_message = await event.get_reply_message() + the_real_message = previous_message.stringify() + reply_to_id = event.reply_to_msg_id + else: + the_real_message = event.stringify() + reply_to_id = event.message.id + if len(the_real_message) > 4095: + with io.BytesIO(str.encode(the_real_message)) as out_file: + out_file.name = "json.text" + await telethn.send_file( + event.chat_id, + out_file, + force_document=True, + allow_cache=False, + reply_to=reply_to_id, + ) + await event.delete() + else: + await event.reply(f"`{the_real_message}`") diff --git a/Exon/modules/karma.py b/Exon/modules/karma.py new file mode 100644 index 00000000..73c0e212 --- /dev/null +++ b/Exon/modules/karma.py @@ -0,0 +1,193 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" +# ""DEAR PRO PEOPLE, DON'T REMOVE & CHANGE THIS LINE +# TG :- @Abishnoi1M +# MY ALL BOTS :- Abishnoi_bots +# GITHUB :- KingAbishnoi "" + +import asyncio + +from pyrogram import filters + +from Exon import pgram as app +from Exon.modules.mongo.karma_mongo import ( + alpha_to_int, + get_karma, + get_karmas, + int_to_alpha, + update_karma, +) +from Exon.utils.errors import capture_err + +regex_upvote = r"^((?i)\+|\+\+|\+1|thx|tnx|ty|thank you|thanx|thanks|pro|cool|good|👍|nice|noice|piro)$" +regex_downvote = r"^(\-|\-\-|\-1|👎|noob|Noob|gross|fuck off)$" + +karma_positive_group = 3 +karma_negative_group = 4 + + +from pymongo import MongoClient + +from Exon import MONGO_DB_URL + +worddb = MongoClient(MONGO_DB_URL) +k = worddb["ExonKarma"]["karma_status"] + + +async def is_admins(chat_id: int): + return [ + member.user.id + async for member in app.iter_chat_members(chat_id, filter="administrators") + ] + + +@app.on_message( + filters.text + & filters.group + & filters.incoming + & filters.reply + & filters.regex(regex_upvote) + & ~filters.via_bot + & ~filters.bot, + group=karma_positive_group, +) +async def upvote(_, message): + chat_id = message.chat.id + is_karma = k.find_one({"chat_id": chat_id}) + if not is_karma: + if not message.reply_to_message.from_user: + return + if not message.from_user: + return + if message.reply_to_message.from_user.id == message.from_user.id: + return + user_id = message.reply_to_message.from_user.id + user_mention = message.reply_to_message.from_user.mention + current_karma = await get_karma(chat_id, await int_to_alpha(user_id)) + if current_karma: + current_karma = current_karma["karma"] + karma = current_karma + 1 + else: + karma = 1 + new_karma = {"karma": karma} + await update_karma(chat_id, await int_to_alpha(user_id), new_karma) + await message.reply_text( + f"ɪɴᴄʀᴇᴍᴇɴᴛᴇᴅ ᴋᴀʀᴍᴀ ᴏғ +1 {user_mention} \nᴛᴏᴛᴀʟ ᴘᴏɪɴᴛs: {karma}" + ) + + +@app.on_message( + filters.text + & filters.group + & filters.incoming + & filters.reply + & filters.regex(regex_downvote) + & ~filters.via_bot + & ~filters.bot, + group=karma_negative_group, +) +async def downvote(_, message): + chat_id = message.chat.id + is_karma = k.find_one({"chat_id": chat_id}) + if is_karma: + if not message.reply_to_message.from_user: + return + if not message.from_user: + return + if message.reply_to_message.from_user.id == message.from_user.id: + return + user_id = message.reply_to_message.from_user.id + user_mention = message.reply_to_message.from_user.mention + current_karma = await get_karma(chat_id, await int_to_alpha(user_id)) + if current_karma: + current_karma = current_karma["karma"] + karma = current_karma - 1 + else: + karma = 1 + new_karma = {"karma": karma} + await update_karma(chat_id, await int_to_alpha(user_id), new_karma) + await message.reply_text( + f"ᴅᴇᴄʀᴇᴍᴇɴᴛᴇᴅ ᴋᴀʀᴍᴀ ᴏғ -1 {user_mention} \nᴛᴏᴛᴀʟ ᴘᴏɪɴᴛs: {karma}" + ) + + +@app.on_message(filters.command("karmastats") & filters.group) +@capture_err +async def karma(_, message): + chat_id = message.chat.id + if not message.reply_to_message: + m = await message.reply_text("ᴡᴀɪᴛ 10 sᴇᴄᴏɴᴅs") + karma = await get_karmas(chat_id) + if not karma: + await m.edit("ɴᴏ ᴋᴀʀᴍᴀ ɪɴ DB ғᴏʀ ᴛʜɪs ᴄʜᴀᴛ.") + return + msg = f"**ᴋᴀʀᴍᴀ ʟɪsᴛ ᴏғ {message.chat.title}:- **\n" + limit = 0 + karma_dicc = {} + for i in karma: + user_id = await alpha_to_int(i) + user_karma = karma[i]["karma"] + karma_dicc[str(user_id)] = user_karma + karma_arranged = dict( + sorted(karma_dicc.items(), key=lambda item: item[1], reverse=True) + ) + if not karma_dicc: + await m.edit("ɴᴏ ᴋᴀʀᴍᴀ ɪɴ DB ғᴏʀ ᴛʜɪs ᴄʜᴀᴛ.") + return + for user_idd, karma_count in karma_arranged.items(): + if limit > 9: + break + try: + user = await app.get_users(int(user_idd)) + await asyncio.sleep(0.8) + except Exception: + continue + first_name = user.first_name + if not first_name: + continue + username = user.username + msg += f"**{karma_count}** {(first_name[0:12] + '...') if len(first_name) > 12 else first_name} `{('@' + username) if username else user_idd}`\n" + limit += 1 + await m.edit(msg) + else: + user_id = message.reply_to_message.from_user.id + karma = await get_karma(chat_id, await int_to_alpha(user_id)) + karma = karma["karma"] if karma else 0 + await message.reply_text(f"**ᴛᴏᴛᴀʟ ᴘᴏɪɴᴛ :** {karma}") + + +__mod_name__ = "𝙺ᴀʀᴍᴀ" +__help__ = """ + +*ᴜᴘᴠᴏᴛᴇ* - ᴜsᴇ ᴜᴘᴠᴏᴛᴇ ᴋᴇʏᴡᴏʀᴅs ʟɪᴋᴇ "+", "+1", "thanks", ᴇᴛᴄ. ᴛᴏ ᴜᴘᴠᴏᴛᴇ ᴀ ᴍᴇssᴀɢᴇ. +*ᴅᴏᴡɴᴠᴏᴛᴇ* - ᴜsᴇ ᴅᴏᴡɴᴠᴏᴛᴇ ᴋᴇʏᴡᴏʀᴅs ʟɪᴋᴇ "-", "-1", ᴇᴛᴄ. ᴛᴏ ᴅᴏᴡɴᴠᴏᴛᴇ ᴀ ᴍᴇssᴀɢᴇ. + +*ᴄᴏᴍᴍᴀɴᴅs* + +⍟ /karmastats:- `ʀᴇᴘʟʏ ᴛᴏ ᴀ ᴜsᴇʀ ᴛᴏ ᴄʜᴇᴄᴋ ᴛʜᴀᴛ ᴜsᴇʀ's ᴋᴀʀᴍᴀ ᴘᴏɪɴᴛs` + +⍟ /karmastats:- `sᴇɴᴅ ᴡɪᴛʜᴏᴜᴛ ʀᴇᴘʟʏɪɴɢ ᴛᴏ ᴀɴʏ ᴍᴇssᴀɢᴇ ᴛᴏ ᴄʜᴇᴄᴋ ᴋᴀʀᴍᴀ ᴘᴏɪɴᴛ ʟɪsᴛ of ᴛᴏᴘ 10` + +⍟ /karma :- `ᴇɴᴀʙʟᴇ/ᴅɪsᴀʙʟᴇ karma ɪɴ ʏᴏᴜʀ ɢʀᴏᴜᴘ ` +""" diff --git a/Exon/modules/live_stats.txt b/Exon/modules/live_stats.txt new file mode 100644 index 00000000..380ad4c2 --- /dev/null +++ b/Exon/modules/live_stats.txt @@ -0,0 +1,69 @@ +""" +MIT License + +Copyright (c) 2022 ABISHNOI + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +# ""DEAR PRO PEOPLE, DON'T REMOVE & CHANGE THIS LINE +# TG :- @Abishnoi1M +# MY ALL BOTS :- Abishnoi_bots +# GITHUB :- KingAbishnoi "" + +from pymongo import MongoClient +from pyrogram import * +from pyrogram.types import * + +import Exon.modules.sql.users_sql as sql +from Exon import EVENT_LOGS, MONGO_DB_URL, pgram + +worddb = MongoClient(MONGO_DB_URL) +k = worddb["Exonstats"]["live_stats"] + + +@pgram.on_message( + filters.text & ~filters.private, +) +async def live(client: Client, message: Message): + is_live = k.find_one({"live": "stats"}) + users = f"{sql.num_users()}" + chats = f"{sql.num_chats()}" + captionk = ( + f"ʟɪᴠᴇ ᴀsᴜx sᴛᴀᴛs\n\n• {sql.num_users()} ᴜsᴇʀs, ᴀᴄʀᴏss {sql.num_chats()} ᴄʜᴀᴛs" + ) + if not is_live: + k.insert_one({"live": "stats", "user": users, "chat": chats}) + await pgram.edit_message_text( + chat_id={EVENT_LOGS}, # channel id + message_id=1, # channel msg id + text=captionk, + disable_web_page_preview=True, + ) + if is_live: + is_live2 = k.find_one({"live": "stats", "user": users, "chat": chats}) + if not is_live2: + k.update_one({"live": "stats"}, {"$set": {"user": users, "chat": chats}}) + # editing chat_id and message id + await pgram.edit_message_text( + chat_id={EVENT_LOGS}, # hear your channel id + message_id=20, # Channel msg id + text=captionk, + disable_web_page_preview=True, + ) diff --git a/Exon/modules/locks.py b/Exon/modules/locks.py new file mode 100644 index 00000000..84aa957d --- /dev/null +++ b/Exon/modules/locks.py @@ -0,0 +1,653 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import html + +from alphabet_detector import AlphabetDetector +from telegram import ( + Chat, + ChatPermissions, + Message, + MessageEntity, + ParseMode, + TelegramError, +) +from telegram.error import BadRequest +from telegram.ext import CommandHandler, Filters, MessageHandler +from telegram.ext.dispatcher import run_async +from telegram.utils.helpers import mention_html + +import Exon.modules.sql.locks_sql as sql +from Exon import LOGGER, dispatcher +from Exon.modules.connection import connected +from Exon.modules.disable import DisableAbleCommandHandler +from Exon.modules.helper_funcs.alternate import send_message, typing_action +from Exon.modules.helper_funcs.chat_status import ( + can_delete, + is_bot_admin, + is_user_admin, + user_admin, + user_not_admin, +) +from Exon.modules.log_channel import loggable +from Exon.modules.sql.approve_sql import is_approved + +ad = AlphabetDetector() + +LOCK_TYPES = { + "audio": Filters.audio, + "voice": Filters.voice, + "document": Filters.document, + "video": Filters.video, + "contact": Filters.contact, + "photo": Filters.photo, + "url": Filters.entity(MessageEntity.URL) + | Filters.caption_entity(MessageEntity.URL), + "bots": Filters.status_update.new_chat_members, + "forward": Filters.forwarded, + "game": Filters.game, + "location": Filters.location, + "egame": Filters.dice, + "rtl": "rtl", + "button": "button", + "inline": "inline", +} + +LOCK_CHAT_RESTRICTION = { + "all": { + "can_send_messages": False, + "can_send_media_messages": False, + "can_send_polls": False, + "can_send_other_messages": False, + "can_add_web_page_previews": False, + "can_change_info": False, + "can_invite_users": False, + "can_pin_messages": False, + }, + "media": {"can_send_media_messages": False}, + "sticker": {"can_send_other_messages": False}, + "gif": {"can_send_other_messages": False}, + "poll": {"can_send_polls": False}, + "other": {"can_send_other_messages": False}, + "previews": {"can_add_web_page_previews": False}, + "info": {"can_change_info": False}, + "invite": {"can_invite_users": False}, + "pin": {"can_pin_messages": False}, +} + +UNLOCK_CHAT_RESTRICTION = { + "all": { + "can_send_messages": True, + "can_send_media_messages": True, + "can_send_polls": True, + "can_send_other_messages": True, + "can_add_web_page_previews": True, + "can_invite_users": True, + }, + "media": {"can_send_media_messages": True}, + "sticker": {"can_send_other_messages": True}, + "gif": {"can_send_other_messages": True}, + "poll": {"can_send_polls": True}, + "other": {"can_send_other_messages": True}, + "previews": {"can_add_web_page_previews": True}, + "info": {"can_change_info": True}, + "invite": {"can_invite_users": True}, + "pin": {"can_pin_messages": True}, +} + +PERM_GROUP = 1 +REST_GROUP = 2 + + +# NOT ASYNC +def restr_members( + bot, + chat_id, + members, + messages=False, + media=False, + other=False, + previews=False, +): + for mem in members: + try: + bot.restrict_chat_member( + chat_id, + mem.user, + can_send_messages=messages, + can_send_media_messages=media, + can_send_other_messages=other, + can_add_web_page_previews=previews, + ) + except TelegramError: + pass + + +# NOT ASYNC +def unrestr_members( + bot, + chat_id, + members, + messages=True, + media=True, + other=True, + previews=True, +): + for mem in members: + try: + bot.restrict_chat_member( + chat_id, + mem.user, + can_send_messages=messages, + can_send_media_messages=media, + can_send_other_messages=other, + can_add_web_page_previews=previews, + ) + except TelegramError: + pass + + +@run_async +def locktypes(update, context): + update.effective_message.reply_text( + "\n • ".join( + ["ʟᴏᴄᴋs ᴀᴠᴀɪʟᴀʙʟᴇ: "] + + sorted(list(LOCK_TYPES) + list(LOCK_CHAT_RESTRICTION)), + ), + ) + + +@run_async +@user_admin +@loggable +@typing_action +def lock(update, context) -> str: + args = context.args + chat = update.effective_chat + user = update.effective_user + + if ( + can_delete(chat, context.bot.id) + or update.effective_message.chat.type == "private" + ): + if len(args) >= 1: + ltype = args[0].lower() + if ltype in LOCK_TYPES: + # Connection check + conn = connected(context.bot, update, chat, user.id, need_admin=True) + if conn: + chat = dispatcher.bot.getChat(conn) + chat_id = conn + chat_name = chat.title + text = "ʟᴏᴄᴋᴇᴅ {} ғᴏʀ ɴᴏɴ-ᴀᴅᴍɪɴs ɪɴ {}!".format(ltype, chat_name) + else: + if update.effective_message.chat.type == "private": + send_message( + update.effective_message, + "ᴛʜɪs ᴄᴏᴍᴍᴀɴᴅ ɪs ᴍᴇᴀɴᴛ ᴛᴏ ᴜsᴇ ɪɴ ɢʀᴏᴜᴘ ɴᴏᴛ ɪɴ PM", + ) + return "" + chat = update.effective_chat + chat_id = update.effective_chat.id + chat_name = update.effective_message.chat.title + text = "ʟᴏᴄᴋᴇᴅ {} ғᴏʀ ɴᴏɴ-ᴀᴅᴍɪɴs!".format(ltype) + sql.update_lock(chat.id, ltype, locked=True) + send_message(update.effective_message, text, parse_mode="markdown") + + return ( + "<b>{}:</b>" + "\n#ʟᴏᴄᴋ" + "\n<b>ᴀᴅᴍɪɴ:</b> {}" + "\nʟᴏᴄᴋᴇᴅ <code>{}</code>.".format( + html.escape(chat.title), + mention_html(user.id, user.first_name), + ltype, + ) + ) + + elif ltype in LOCK_CHAT_RESTRICTION: + # Connection check + conn = connected(context.bot, update, chat, user.id, need_admin=True) + if conn: + chat = dispatcher.bot.getChat(conn) + chat_id = conn + chat_name = chat.title + text = "ʟᴏᴄᴋᴇᴅ {} ғᴏʀ ᴀʟʟ ɴᴏɴ-ᴀᴅᴍɪɴs ɪɴ {}!".format( + ltype, + chat_name, + ) + else: + if update.effective_message.chat.type == "private": + send_message( + update.effective_message, + "ᴛʜɪs ᴄᴏᴍᴍᴀɴᴅ ɪs ᴍᴇᴀɴᴛ ᴛᴏ ᴜsᴇ ɪɴ ɢʀᴏᴜᴘ ɴᴏᴛ ɪɴ PM", + ) + return "" + chat = update.effective_chat + chat_id = update.effective_chat.id + chat_name = update.effective_message.chat.title + text = "ʟᴏᴄᴋᴇᴅ {} ғᴏʀ ᴀʟʟ ɴᴏɴ-ᴀᴅᴍɪɴs!".format(ltype) + + current_permission = context.bot.getChat(chat_id).permissions + context.bot.set_chat_permissions( + chat_id=chat_id, + permissions=get_permission_list( + eval(str(current_permission)), + LOCK_CHAT_RESTRICTION[ltype.lower()], + ), + ) + + send_message(update.effective_message, text, parse_mode="markdown") + return ( + "<b>{}:</b>" + "\n#ᴘᴇʀᴍɪssɪᴏɴ_ʟᴏᴄᴋ" + "\n<b>ᴀᴅᴍɪɴ:</b> {}" + "\nʟᴏᴄᴋᴇᴅ <code>{}</code>.".format( + html.escape(chat.title), + mention_html(user.id, user.first_name), + ltype, + ) + ) + + else: + send_message( + update.effective_message, + "ᴡʜᴀᴛ ᴀʀᴇ ʏᴏᴜ ᴛʀʏɪɴɢ ᴛᴏ ʟᴏᴄᴋ...? ᴛʀʏ /locktypes ғᴏʀ ᴛʜᴇ ʟɪsᴛ ᴏғ ʟᴏᴄᴋᴀʙʟᴇs", + ) + else: + send_message( + update.effective_message, "ᴡʜᴀᴛ ᴀʀᴇ ʏᴏᴜ ᴛʀʏɪɴɢ ᴛᴏ ʟᴏᴄᴋ ʙᴀʙʏ ....?" + ) + + else: + send_message( + update.effective_message, + "I ᴀᴍ ɴᴏᴛ ᴀᴅᴍɪɴɪsᴛʀᴀᴛᴏʀ ᴏʀ ʜᴀᴠᴇɴ'ᴛ ɢᴏᴛ ᴇɴᴏᴜɢʜ ʀɪɢʜᴛs.", + ) + + return "" + + +@run_async +@user_admin +@loggable +@typing_action +def unlock(update, context) -> str: + args = context.args + chat = update.effective_chat + user = update.effective_user + message = update.effective_message + if is_user_admin(chat, message.from_user.id): + if len(args) >= 1: + ltype = args[0].lower() + if ltype in LOCK_TYPES: + # Connection check + conn = connected(context.bot, update, chat, user.id, need_admin=True) + if conn: + chat = dispatcher.bot.getChat(conn) + chat_id = conn + chat_name = chat.title + text = "ᴜɴʟᴏᴄᴋᴇᴅ {} ғᴏʀ ᴇᴠᴇʀʏᴏɴᴇ in {}!".format(ltype, chat_name) + else: + if update.effective_message.chat.type == "private": + send_message( + update.effective_message, + "ᴛʜɪs ᴄᴏᴍᴍᴀɴᴅ ɪs ᴍᴇᴀɴᴛ ᴛᴏ ᴜsᴇ ɪɴ ɢʀᴏᴜᴘ ɴᴏᴛ ɪɴ ᴘᴍ", + ) + return "" + chat = update.effective_chat + chat_id = update.effective_chat.id + chat_name = update.effective_message.chat.title + text = "ᴜɴʟᴏᴄᴋᴇᴅ {} ғᴏʀ ᴇᴠᴇʀʏᴏɴᴇ!".format(ltype) + sql.update_lock(chat.id, ltype, locked=False) + send_message(update.effective_message, text, parse_mode="markdown") + return ( + "<b>{}:</b>" + "\n#ᴜɴʟᴏᴄᴋ" + "\n<b>ᴀᴅᴍɪɴ:</b> {}" + "\nᴜɴʟᴏᴄᴋᴇᴅ <code>{}</code>.".format( + html.escape(chat.title), + mention_html(user.id, user.first_name), + ltype, + ) + ) + + elif ltype in UNLOCK_CHAT_RESTRICTION: + # Connection check + conn = connected(context.bot, update, chat, user.id, need_admin=True) + if conn: + chat = dispatcher.bot.getChat(conn) + chat_id = conn + chat_name = chat.title + text = "ᴜɴʟᴏᴄᴋᴇᴅ {} ғᴏʀ ᴇᴠᴇʀʏᴏɴᴇ i=n {}!".format(ltype, chat_name) + else: + if update.effective_message.chat.type == "private": + send_message( + update.effective_message, + "ᴛʜɪs ᴄᴏᴍᴍᴀɴᴅ ɪs ᴍᴇᴀɴᴛ ᴛᴏ ᴜsᴇ ɪɴ ɢʀᴏᴜᴘ ɴᴏᴛ ɪɴ PM", + ) + return "" + chat = update.effective_chat + chat_id = update.effective_chat.id + chat_name = update.effective_message.chat.title + text = "ᴜɴʟᴏᴄᴋᴇᴅ {} ғᴏʀ ᴇᴠᴇʀʏᴏɴᴇ!".format(ltype) + + can_change_info = chat.get_member(context.bot.id).can_change_info + if not can_change_info: + send_message( + update.effective_message, + "I ᴅᴏɴ'ᴛ ʜᴀᴠᴇ ᴘᴇʀᴍɪssɪᴏɴ ᴛᴏ ᴄʜᴀɴɢᴇ ɢʀᴏᴜᴘ ɪɴғᴏ.", + parse_mode="markdown", + ) + return + + current_permission = context.bot.getChat(chat_id).permissions + context.bot.set_chat_permissions( + chat_id=chat_id, + permissions=get_permission_list( + eval(str(current_permission)), + UNLOCK_CHAT_RESTRICTION[ltype.lower()], + ), + ) + + send_message(update.effective_message, text, parse_mode="markdown") + + return ( + "<b>{}:</b>" + "\n#ᴜɴʟᴏᴄᴋ" + "\n<b>ᴀᴅᴍɪɴ:</b> {}" + "\nᴜɴʟᴏᴄᴋᴇᴅ <code>{}</code>.".format( + html.escape(chat.title), + mention_html(user.id, user.first_name), + ltype, + ) + ) + else: + send_message( + update.effective_message, + "ᴡʜᴀᴛ ᴀʀᴇ ʏᴏᴜ ᴛʀʏɪɴɢ ᴛᴏ ᴜɴʟᴏᴄᴋ...? ᴛʀʏ /locktypes ғᴏʀ ᴛʜᴇ ʟɪsᴛ ᴏғ ʟᴏᴄᴋᴀʙʟᴇs.", + ) + + else: + send_message( + update.effective_message, "ᴡʜᴀᴛ ᴀʀᴇ ʏᴏᴜ ᴛʀʏɪɴɢ ᴛᴏ ᴜɴʟᴏᴄᴋ ʙᴀʙʏ...?" + ) + + return "" + + +@run_async +@user_not_admin +def del_lockables(update, context): + chat = update.effective_chat # type: Optional[Chat] + message = update.effective_message # type: Optional[Message] + user = update.effective_user + if is_approved(chat.id, user.id): + return + for lockable, filter in LOCK_TYPES.items(): + if lockable == "rtl": + if sql.is_locked(chat.id, lockable) and can_delete(chat, context.bot.id): + if message.caption: + check = ad.detect_alphabet("{}".format(message.caption)) + if "ARABIC" in check: + try: + message.delete() + except BadRequest as excp: + if excp.message == "ᴍᴇssᴀɢᴇ ᴛᴏ ᴅᴇʟᴇᴛᴇ ɴᴏᴛ ғᴏᴜɴᴅ": + pass + else: + LOGGER.exception("ᴇʀʀᴏʀ ɪɴ ʟᴏᴄᴋᴀʙʟᴇs") + break + if message.text: + check = ad.detect_alphabet("{}".format(message.text)) + if "ARABIC" in check: + try: + message.delete() + except BadRequest as excp: + if excp.message == "ᴍᴇssᴀɢᴇ ᴛᴏ ᴅᴇʟᴇᴛᴇ ɴᴏᴛ ғᴏᴜɴᴅ": + pass + else: + LOGGER.exception("ᴇʀʀᴏʀ ɪɴ ʟᴏᴄᴋᴀʙʟᴇs") + break + continue + if lockable == "button": + if sql.is_locked(chat.id, lockable) and can_delete(chat, context.bot.id): + if message.reply_markup and message.reply_markup.inline_keyboard: + try: + message.delete() + except BadRequest as excp: + if excp.message == "ᴍᴇssᴀɢᴇ ᴛᴏ ᴅᴇʟᴇᴛᴇ ɴᴏᴛ ғᴏᴜɴᴅ": + pass + else: + LOGGER.exception("ᴇʀʀᴏʀ ɪɴ ʟᴏᴄᴋᴀʙʟᴇs") + break + continue + if lockable == "inline": + if sql.is_locked(chat.id, lockable) and can_delete(chat, context.bot.id): + if message and message.via_bot: + try: + message.delete() + except BadRequest as excp: + if excp.message == "ᴍᴇssᴀɢᴇ ᴛᴏ ᴅᴇʟᴇᴛᴇ ɴᴏᴛ ғᴏᴜɴᴅ": + pass + else: + LOGGER.exception("ᴇʀʀᴏʀ ɪɴ ʟᴏᴄᴋᴀʙʟᴇs") + break + continue + if ( + filter(update) + and sql.is_locked(chat.id, lockable) + and can_delete(chat, context.bot.id) + ): + if lockable == "bots": + new_members = update.effective_message.new_chat_members + for new_mem in new_members: + if new_mem.is_bot: + if not is_bot_admin(chat, context.bot.id): + send_message( + update.effective_message, + "I sᴇᴇ a ʙᴏᴛ ᴀɴᴅ I'ᴠᴇ ʙᴇᴇɴ ᴛᴏʟᴅ ᴛᴏ sᴛᴏᴘ ᴛʜᴇᴍ ғʀᴏᴍ ᴊᴏɪɴɪɴɢ..." + "ʙᴜᴛ I'ᴍ ɴᴏᴛ ᴀᴅᴍɪɴ!", + ) + return + + chat.kick_member(new_mem.id) + send_message( + update.effective_message, + "ᴏɴʟʏ ᴀᴅᴍɪɴs ᴀʀᴇ ᴀʟʟᴏᴡᴇᴅ ᴛᴏ ᴀᴅᴅ bots ɪɴ ᴛʜɪs ᴄʜᴀᴛ! ɢᴇᴛ ᴏᴜᴛᴛᴀ ʜᴇʀᴇ.", + ) + break + else: + try: + message.delete() + except BadRequest as excp: + if excp.message == "ᴍᴇssᴀɢᴇ ᴛᴏ ᴅᴇʟᴇᴛᴇ ɴᴏᴛ ғᴏᴜɴᴅ": + pass + else: + LOGGER.exception("ᴇʀʀᴏʀ ɪɴ ʟᴏᴄᴋᴀʙʟᴇs") + + break + + +def build_lock_message(chat_id): + locks = sql.get_locks(chat_id) + res = "" + locklist = [] + permslist = [] + if locks: + res += "*" + "ᴛʜᴇsᴇ ᴀʀᴇ ᴛʜᴇ ᴄᴜʀʀᴇɴᴛ ʟᴏᴄᴋs ɪɴ ᴛʜɪs ᴄʜᴀᴛ:" + "*" + if locks: + locklist.append("sticker = `{}`".format(locks.sticker)) + locklist.append("audio = `{}`".format(locks.audio)) + locklist.append("voice = `{}`".format(locks.voice)) + locklist.append("document = `{}`".format(locks.document)) + locklist.append("video = `{}`".format(locks.video)) + locklist.append("contact = `{}`".format(locks.contact)) + locklist.append("photo = `{}`".format(locks.photo)) + locklist.append("gif = `{}`".format(locks.gif)) + locklist.append("url = `{}`".format(locks.url)) + locklist.append("bots = `{}`".format(locks.bots)) + locklist.append("forward = `{}`".format(locks.forward)) + locklist.append("game = `{}`".format(locks.game)) + locklist.append("location = `{}`".format(locks.location)) + locklist.append("rtl = `{}`".format(locks.rtl)) + locklist.append("button = `{}`".format(locks.button)) + locklist.append("egame = `{}`".format(locks.egame)) + locklist.append("inline = `{}`".format(locks.inline)) + permissions = dispatcher.bot.get_chat(chat_id).permissions + permslist.append("media = `{}`".format(permissions.can_send_media_messages)) + permslist.append("poll = `{}`".format(permissions.can_send_polls)) + permslist.append("other = `{}`".format(permissions.can_send_other_messages)) + permslist.append("previews = `{}`".format(permissions.can_add_web_page_previews)) + permslist.append("info = `{}`".format(permissions.can_change_info)) + permslist.append("invite = `{}`".format(permissions.can_invite_users)) + permslist.append("pin = `{}`".format(permissions.can_pin_messages)) + + if locklist: + # Ordering lock list + locklist.sort() + # Building lock list string + for x in locklist: + res += "\n • {}".format(x) + res += "\n\n*" + "ᴛʜᴇsᴇ ᴀʀᴇ ᴛʜᴇ ᴄᴜʀʀᴇɴᴛ ᴄʜᴀᴛ ᴘᴇʀᴍɪssɪᴏɴs:" + "*" + for x in permslist: + res += "\n • {}".format(x) + return res + + +@run_async +@user_admin +@typing_action +def list_locks(update, context): + chat = update.effective_chat # type: Optional[Chat] + user = update.effective_user + + # Connection check + conn = connected(context.bot, update, chat, user.id, need_admin=True) + if conn: + chat = dispatcher.bot.getChat(conn) + chat_name = chat.title + else: + if update.effective_message.chat.type == "private": + send_message( + update.effective_message, + "ᴛʜɪs ᴄᴏᴍᴍᴀɴᴅ ɪs ᴍᴇᴀɴᴛ ᴛᴏ use ɪɴ ɢʀᴏᴜᴘ ɴᴏᴛ ɪɴ ᴘᴍ", + ) + return "" + chat = update.effective_chat + chat_name = update.effective_message.chat.title + + res = build_lock_message(chat.id) + if conn: + res = res.replace("ʟᴏᴄᴋs ɪɴ", "*{}*".format(chat_name)) + + send_message(update.effective_message, res, parse_mode=ParseMode.MARKDOWN) + + +def get_permission_list(current, new): + permissions = { + "can_send_messages": None, + "can_send_media_messages": None, + "can_send_polls": None, + "can_send_other_messages": None, + "can_add_web_page_previews": None, + "can_change_info": None, + "can_invite_users": None, + "can_pin_messages": None, + } + permissions.update(current) + permissions.update(new) + new_permissions = ChatPermissions(**permissions) + return new_permissions + + +def __import_data__(chat_id, data): + # set chat locks + locks = data.get("locks", {}) + for itemlock in locks: + if itemlock in LOCK_TYPES: + sql.update_lock(chat_id, itemlock, locked=True) + elif itemlock in LOCK_CHAT_RESTRICTION: + sql.update_restriction(chat_id, itemlock, locked=True) + else: + pass + + +def __migrate__(old_chat_id, new_chat_id): + sql.migrate_chat(old_chat_id, new_chat_id) + + +def __chat_settings__(chat_id, user_id): + return build_lock_message(chat_id) + + +__help__ = """ +•➥ /locktypes*:* `ʟɪꜱᴛꜱ ᴀʟʟ ᴘᴏꜱꜱɪʙʟᴇ ʟᴏᴄᴋᴛʏᴘᴇꜱ` + +*ᴀᴅᴍɪɴꜱ ᴏɴʟʏ:* +•➥ /lock <type>*:* `ʟᴏᴄᴋ ɪᴛᴇᴍꜱ ᴏғ ᴀ ᴄᴇʀᴛᴀɪɴ ɴype (ɴᴏᴛ ᴀᴠᴀɪʟᴀʙʟᴇ ɪɴ ᴘʀɪᴠᴀᴛᴇ)` + +•➥ /unlock <type>*:* `ᴜɴʟᴏᴄᴋ ɪᴛᴇᴍꜱ ᴏғ ᴀ ᴄᴇʀᴛᴀɪɴ ᴛʏᴘᴇ (ɴᴏᴛ ᴀᴠᴀɪʟᴀʙʟᴇ ɪɴ ᴘʀɪᴠᴀᴛᴇ)` + +•➥ /locks*:* `ᴛʜᴇ ᴄᴜʀʀᴇɴᴛ list ᴏғ ʟᴏᴄᴋꜱ ɪɴ ᴛʜɪꜱ ᴄʜᴀᴛ.` + +`ʟᴏᴄᴋꜱ ᴄᴀɴ ʙᴇ ᴜꜱᴇᴅ ᴛᴏ ʀᴇꜱᴛʀɪᴄᴛ ᴀ ɢʀᴏᴜᴘ ᴜꜱᴇʀꜱ.` + +`Locking ᴜʀʟs ᴡɪʟʟ ᴀᴜᴛᴏ-ᴅᴇʟᴇᴛᴇ ᴀʟʟ ᴍᴇssᴀɢᴇs ᴡɪᴛʜ ᴜʀʟs, ʟᴏᴄᴋɪɴɢ sᴛɪᴄᴋᴇʀs ᴡɪʟʟ ʀᴇsᴛʀɪᴄᴛ ᴀʟʟ` + +`ɴᴏɴ-ᴀᴅᴍɪɴ users ғʀᴏᴍ sᴇɴᴅɪɴɢ sᴛɪᴄᴋᴇʀs, ᴇᴛᴄ.` + +`ʟᴏᴄᴋɪɴɢ ʙᴏᴛs ᴡɪʟʟ sᴛᴏᴘ ɴᴏɴ-ᴀᴅᴍɪɴs ғʀᴏᴍ ᴀᴅᴅɪɴɢ ʙᴏᴛs ᴛᴏ ᴛʜᴇ ᴄʜᴀᴛ.` + +*ɴᴏᴛᴇ:* +•➥ ᴜɴʟᴏᴄᴋɪɴɢ ᴘᴇʀᴍɪssɪᴏɴ *ɪɴғᴏ* `ᴡɪʟʟ ᴀʟʟᴏᴡ ᴍᴇᴍʙᴇʀs (ɴᴏɴ-ᴀᴅᴍɪɴs) ᴛᴏ ᴄʜᴀɴɢᴇ ᴛʜᴇ ɢʀᴏᴜᴘ ɪɴғᴏʀᴍᴀᴛɪᴏɴ, sᴜᴄʜ ᴀs ᴛʜᴇ ᴅᴇsᴄʀɪᴘᴛɪᴏɴ ᴏʀ ᴛʜᴇ ɢʀᴏᴜᴘ ɴᴀᴍᴇ` + +•➥ ᴜɴʟᴏᴄᴋɪɴɢ ᴘᴇʀᴍɪssɪᴏɴ *ᴘɪɴ* `ᴡɪʟʟ ᴀʟʟᴏᴡ ᴍᴇᴍʙᴇʀs (ɴᴏɴ-ᴀᴅᴍɪɴs) ᴛᴏ ᴘɪɴɴᴇᴅ ᴀ ᴍᴇssᴀɢᴇ ɪɴ ᴀ ɢʀᴏᴜᴘ` + +""" + +__mod_name__ = "𝙻ᴏᴄᴋs" + +LOCKTYPES_HANDLER = DisableAbleCommandHandler("locktypes", locktypes, run_async=True) +LOCK_HANDLER = CommandHandler( + "lock", lock, pass_args=True, run_async=True +) # , filters=Filters.chat_type.groups) +UNLOCK_HANDLER = CommandHandler( + "unlock", unlock, pass_args=True, run_async=True +) # , filters=Filters.chat_type.groups) +LOCKED_HANDLER = CommandHandler( + "locks", list_locks, run_async=True +) # , filters=Filters.chat_type.groups) + +dispatcher.add_handler(LOCK_HANDLER) +dispatcher.add_handler(UNLOCK_HANDLER) +dispatcher.add_handler(LOCKTYPES_HANDLER) +dispatcher.add_handler(LOCKED_HANDLER) + +dispatcher.add_handler( + MessageHandler( + Filters.all & Filters.chat_type.groups, del_lockables, run_async=True + ), + PERM_GROUP, +) diff --git a/Exon/modules/log_channel.py b/Exon/modules/log_channel.py new file mode 100644 index 00000000..00373473 --- /dev/null +++ b/Exon/modules/log_channel.py @@ -0,0 +1,231 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +from datetime import datetime +from functools import wraps + +from telegram.ext import CallbackContext + +from Exon.modules.helper_funcs.misc import is_module_loaded + +FILENAME = __name__.rsplit(".", 1)[-1] + +if is_module_loaded(FILENAME): + from telegram import ParseMode, Update + from telegram.error import BadRequest, Unauthorized + from telegram.ext import CommandHandler, JobQueue + from telegram.utils.helpers import escape_markdown + + from Exon import EVENT_LOGS, LOGGER, dispatcher + from Exon.modules.helper_funcs.chat_status import user_admin + from Exon.modules.sql import log_channel_sql as sql + + def loggable(func): + @wraps(func) + def log_action( + update: Update, + context: CallbackContext, + job_queue: JobQueue = None, + *args, + **kwargs, + ): + result = ( + func(update, context, job_queue, *args, **kwargs) + if job_queue + else func(update, context, *args, **kwargs) + ) + + chat = update.effective_chat + message = update.effective_message + + if result: + datetime_fmt = "%H:%M - %d-%m-%Y" + result += f"\n<b>ᴇᴠᴇɴᴛ sᴛᴀᴍᴘ</b>: <code>{datetime.utcnow().strftime(datetime_fmt)}</code>" + + if message.chat.type == chat.SUPERGROUP and message.chat.username: + result += f'\n<b>ʟɪɴᴋ:</b> <a href="https://t.me/{chat.username}/{message.message_id}">ᴄʟɪᴄᴋ ʜᴇʀᴇ</a>' + log_chat = sql.get_chat_log_channel(chat.id) + if log_chat: + send_log(context, log_chat, chat.id, result) + + return result + + return log_action + + def gloggable(func): + @wraps(func) + def glog_action(update: Update, context: CallbackContext, *args, **kwargs): + result = func(update, context, *args, **kwargs) + chat = update.effective_chat + message = update.effective_message + + if result: + datetime_fmt = "%ʜ:%ᴍ - %ᴅ%ᴍ%ʏ" + result += "\n<b>ᴇᴠᴇɴᴛ sᴛᴀᴍᴘ</b>: <code>{}</code>".format( + datetime.utcnow().strftime(datetime_fmt), + ) + + if message.chat.type == chat.SUPERGROUP and message.chat.username: + result += f'\n<b>Link:</b> <a href="https://t.me/{chat.username}/{message.message_id}">click here</a>' + log_chat = str(EVENT_LOGS) + if log_chat: + send_log(context, log_chat, chat.id, result) + + return result + + return glog_action + + def send_log( + context: CallbackContext, + log_chat_id: str, + orig_chat_id: str, + result: str, + ): + bot = context.bot + try: + bot.send_message( + log_chat_id, + result, + parse_mode=ParseMode.HTML, + disable_web_page_preview=True, + ) + except BadRequest as excp: + if excp.message == "ᴄʜᴀᴛ ɴᴏᴛ ғᴏᴜɴᴅ": + bot.send_message( + orig_chat_id, + "ᴛʜɪs ʟᴏɢ ᴄʜᴀɴɴᴇʟ ʜᴀs ʙᴇᴇɴ ᴅᴇʟᴇᴛᴇᴅ - ᴜɴsᴇᴛᴛɪɴɢ.", + ) + sql.stop_chat_logging(orig_chat_id) + else: + LOGGER.warning(excp.message) + LOGGER.warning(result) + LOGGER.exception("Could not parse") + + bot.send_message( + log_chat_id, + result + + "\n\nғᴏʀᴍᴀᴛᴛɪɴɢ ʜᴀs ʙᴇᴇɴ ᴅɪsᴀʙʟᴇᴅ ᴅᴜᴇ ᴛᴏ ᴀɴ ᴜɴᴇxᴘᴇᴄᴛᴇᴅ ᴇʀʀᴏʀ.", + ) + + @user_admin + def logging(update: Update, context: CallbackContext): + bot = context.bot + message = update.effective_message + chat = update.effective_chat + + if log_channel := sql.get_chat_log_channel(chat.id): + log_channel_info = bot.get_chat(log_channel) + message.reply_text( + f"ᴛʜɪs ɢʀᴏᴜᴘ has ᴀʟʟ ɪᴛ's ʟᴏɢs sᴇɴᴛ ᴛᴏ:" + f" {escape_markdown(log_channel_info.title)} (`{log_channel}`)", + parse_mode=ParseMode.MARKDOWN, + ) + + else: + message.reply_text("ɴᴏ ʟᴏɢ ᴄʜᴀɴɴᴇʟ ʜᴀs ʙᴇᴇɴ sᴇᴛ ғᴏʀ ᴛʜɪs ɢʀᴏᴜᴘ!") + + @user_admin + def setlog(update: Update, context: CallbackContext): + bot = context.bot + message = update.effective_message + chat = update.effective_chat + if chat.type == chat.CHANNEL: + message.reply_text( + "ɴᴏᴡ, ғᴏʀᴡᴀʀᴅ ᴛʜᴇ /setlog ᴛᴏ ᴛʜᴇ ɢʀᴏᴜᴘ ʏᴏᴜ ᴡᴀɴᴛ ᴛᴏ ᴛɪᴇ ᴛʜɪs ᴄʜᴀɴɴᴇʟ ᴛᴏ !", + ) + + elif message.forward_from_chat: + sql.set_chat_log_channel(chat.id, message.forward_from_chat.id) + try: + message.delete() + except BadRequest as excp: + if excp.message != "ᴍᴇssᴀɢᴇ ᴛᴏ ᴅᴇʟᴇᴛᴇ ɴᴏᴛ ғᴏᴜɴᴅ": + LOGGER.exception( + "ᴇʀʀᴏʀ ᴅᴇʟᴇᴛɪɴɢ ᴍᴇssᴀɢᴇ ɪɴ ʟᴏɢ ᴄʜᴀɴɴᴇʟ. sʜᴏᴜʟᴅ ᴡᴏʀᴋ ᴀɴʏᴡᴀʏ ᴛʜᴏᴜɢʜ.", + ) + + try: + bot.send_message( + message.forward_from_chat.id, + f"ᴛʜɪs ᴄʜᴀɴɴᴇʟ ʜᴀs ʙᴇᴇɴ sᴇᴛ ᴀs ᴛʜᴇ ʟᴏɢ ᴄʜᴀɴɴᴇʟ ғᴏʀ {chat.title or chat.first_name}.", + ) + except Unauthorized as excp: + if excp.message == "ғᴏʀʙɪᴅᴅᴇɴ: ʙᴏᴛ ɪs ɴᴏᴛ ᴀ ᴀᴅᴍɪɴr ᴏғ ᴛʜᴇ ᴄʜᴀɴɴᴇʟ ᴄʜᴀᴛ": + bot.send_message(chat.id, "sᴜᴄᴄᴇssғᴜʟʟʏ sᴇᴛ ʟᴏɢ ᴄʜᴀɴɴᴇʟ!") + else: + LOGGER.exception("ᴇʀʀᴏʀ ɪɴ sᴇᴛᴛɪɴɢ ᴛʜᴇ ʟᴏɢ ᴄʜᴀɴɴᴇʟ.") + + bot.send_message(chat.id, "sᴜᴄᴄᴇssғᴜʟʟʏ sᴇᴛ ʟᴏɢ ᴄʜᴀɴɴᴇʟ!") + + else: + message.reply_text( + "ᴛʜᴇ sᴛᴇᴘs ᴛᴏ sᴇᴛ ᴀ ʟᴏɢ ᴄʜᴀɴɴᴇʟ ᴀʀᴇ:\n" + " - ᴀᴅᴅ ʙᴏᴛ ᴛᴏ ᴛʜᴇ ᴅᴇsɪʀᴇᴅ ᴄʜᴀɴɴᴇʟ\n" + " - sᴇɴᴅ /setlog ᴛᴏ ᴛʜᴇ ᴄʜᴀɴɴᴇʟ\n" + " - ғᴏʀᴡᴀʀᴅ ᴛʜᴇ /setlog ᴛᴏ ᴛʜᴇ ɢʀᴏᴜᴘ\n", + ) + + @user_admin + def unsetlog(update: Update, context: CallbackContext): + bot = context.bot + message = update.effective_message + chat = update.effective_chat + + if log_channel := sql.stop_chat_logging(chat.id): + bot.send_message( + log_channel, + f"ᴄʜᴀɴɴᴇʟ ʜᴀs ʙᴇᴇɴ ᴜɴʟɪɴᴋᴇᴅ ғʀᴏᴍ {chat.title}", + ) + message.reply_text("ʟᴏɢ ᴄʜᴀɴɴᴇʟ ʜᴀs ʙᴇᴇɴ ᴜɴ-sᴇᴛ.") + + else: + message.reply_text("ɴᴏ ʟᴏɢ ᴄʜᴀɴɴᴇʟ ʜᴀs ʙᴇᴇɴ sᴇᴛ ʏᴇᴛ!") + + def __stats__(): + return f"•➥ {sql.num_logchannels()} ʟᴏɢ ᴄʜᴀɴɴᴇʟs sᴇᴛ." + + def __migrate__(old_chat_id, new_chat_id): + sql.migrate_chat(old_chat_id, new_chat_id) + + def __chat_settings__(chat_id, user_id): + if log_channel := sql.get_chat_log_channel(chat_id): + log_channel_info = dispatcher.bot.get_chat(log_channel) + return f"ᴛʜɪs ɢʀᴏᴜᴘ ʜᴀs ᴀʟʟ it's ʟᴏɢs sᴇɴᴛ ᴛᴏ: {escape_markdown(log_channel_info.title)} (`{log_channel}`)" + return "ɴᴏ ʟᴏɢ ᴄʜᴀɴɴᴇʟ ɪs sᴇᴛ ғᴏʀ ᴛʜɪs ɢʀᴏᴜᴘ!" + + LOG_HANDLER = CommandHandler("logchannel", logging, run_async=True) + SET_LOG_HANDLER = CommandHandler("setlog", setlog, run_async=True) + UNSET_LOG_HANDLER = CommandHandler("unsetlog", unsetlog, run_async=True) + + dispatcher.add_handler(LOG_HANDLER) + dispatcher.add_handler(SET_LOG_HANDLER) + dispatcher.add_handler(UNSET_LOG_HANDLER) + +else: + # run anyway if module not loaded + def loggable(func): + return func + + def gloggable(func): + return func diff --git a/Exon/modules/logomaker.py b/Exon/modules/logomaker.py new file mode 100644 index 00000000..a23b52a7 --- /dev/null +++ b/Exon/modules/logomaker.py @@ -0,0 +1,336 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import glob +import io +import os +import random + +import requests +from PIL import Image, ImageDraw, ImageFont + +from Exon import BOT_USERNAME, OWNER_ID, SUPPORT_CHAT, telethn +from Exon.events import register + +LOGO_LINKS = [ + "https://telegra.ph/file/d1838efdafce9fe611d0c.jpg", + "https://telegra.ph/file/c1ff2d5ec5e1b5bd1b200.jpg", + "https://telegra.ph/file/08c5fbe14cc4b13d1de05.jpg", + "https://telegra.ph/file/66614a049d74fe2a220dc.jpg", + "https://telegra.ph/file/9cc1e4b24bfa13873bd66.jpg", + "https://telegra.ph/file/792d38bd74b0c3165c11d.jpg", + "https://telegra.ph/file/e1031e28a4aa4d8bd7c9b.jpg", + "https://telegra.ph/file/2be9027c55b5ed463fc18.jpg", + "https://telegra.ph/file/9fd71f8d08158d0cc393c.jpg", + "https://telegra.ph/file/627105074f0456f42058b.jpg", + "https://telegra.ph/file/62b712f741382d3c171cd.jpg", + "https://telegra.ph/file/496651e0d5e4d22b8f72d.jpg", + "https://telegra.ph/file/6619d0eee2c35e022ee74.jpg", + "https://telegra.ph/file/f72fcb27c9b1e762d184b.jpg", + "https://telegra.ph/file/01eac0fe1a722a864d7de.jpg", + "https://telegra.ph/file/bdcb746fbfdf38f812873.jpg", + "https://telegra.ph/file/d13e036a129df90651deb.jpg", + "https://telegra.ph/file/ab6715ce9a63523bd0219.jpg", + "https://telegra.ph/file/c243f4e80ebf0110f9f00.jpg", + "https://telegra.ph/file/ff9053f2c7bfb2badc99e.jpg", + "https://telegra.ph/file/00b9ebbb816285d9a59f9.jpg", + "https://telegra.ph/file/ad92e1c829d14afa25cf2.jpg", + "https://telegra.ph/file/58d45cc3374e7b28a1e67.jpg", + "https://telegra.ph/file/4140a0b3f27c302fd81cb.jpg", + "https://telegra.ph/file/c4db2b5c84c1d90f5ac8a.jpg", + "https://telegra.ph/file/c0da5080a3ff7643ddeb4.jpg", + "https://telegra.ph/file/79fad473ffe888ed771b2.jpg", + "https://telegra.ph/file/eafd526d9dcc164d7269f.jpg", + "https://telegra.ph/file/98b50e8424dd2be9fc127.jpg", + "https://telegra.ph/file/c1ad29c189162a1404749.jpg", + "https://telegra.ph/file/2d288450ebecc500addbd.jpg", + "https://telegra.ph/file/9715353976a99becd7632.jpg", + "https://telegra.ph/file/87670b02a1004bc02bd8d.jpg", + "https://telegra.ph/file/70789cd69114939a78242.jpg", + "https://telegra.ph/file/1566bd334f00645cfa993.jpg", + "https://telegra.ph/file/9727c37bb8c633208b915.jpg", + "https://telegra.ph/file/27467ef55fab117ccb278.jpg", + "https://telegra.ph/file/b9c62ff7810d9e84e9e2c.jpg", + "https://telegra.ph/file/87d22f2c95413059dda4e.jpg", + "https://telegra.ph/file/e528a731accbcdea140e3.jpg", + "https://telegra.ph/file/ee3f20c3ce71dc37fecb2.jpg", + "https://telegra.ph/file/a049f78377a5b8257294d.jpg", + "https://telegra.ph/file/54d22d39ea89423b7533f.jpg", + "https://telegra.ph/file/d90baa59b6fe2bc3091d3.jpg", + "https://telegra.ph/file/b9b3f80dc4635faaeb472.jpg", + "https://telegra.ph/file/d64be0a98f441a33d2aef.jpg", + "https://telegra.ph/file/e2c59ac97a900bab5ad7d.jpg", + "https://telegra.ph/file/41baf461b0a34f1a881a9.jpg", + "https://telegra.ph/file/8d4082052b4bd0a8cc862.jpg", + "https://telegra.ph/file/e7d6e0c511137ad67d843.jpg", + "https://telegra.ph/file/d7b97ea806d4a905b71c4.jpg", + "https://telegra.ph/file/6bec48ea2c96cf3d668a4.jpg", + "https://telegra.ph/file/aa64389b70e0de02d18c5.jpg", + "https://telegra.ph/file/2f75d964a59a3a4ae90e0.jpg", + "https://telegra.ph/file/f408df72c57cfc05e734f.jpg", + "https://telegra.ph/file/9d88d9dfb50106bc43c91.jpg", + "https://telegra.ph/file/a5a6e0f9d172fa386621e.jpg", + "https://telegra.ph/file/b0fc771c91409ee5cd4dc.jpg", + "https://telegra.ph/file/b0fc771c91409ee5cd4dc.jpg", + "https://telegra.ph/file/f75e59ebd4059f394479e.jpg", + "https://telegra.ph/file/fc0308f59023d0c997166.jpg", + "https://telegra.ph/file/7e1c04947f6afb6cdf25c.jpg", + "https://telegra.ph/file/6279bb4be7e48da194353.jpg", + "https://telegra.ph/file/616784fcd89f13e789685.jpg", + "https://telegra.ph/file/803e7dd9fafdb086bce4a.jpg", + "https://telegra.ph/file/d7338861b7f996ec9d40d.jpg", + "https://telegra.ph/file/828730cd4d73333eaf129.jpg", + "https://telegra.ph/file/36c9321161d49c4b3d671.jpg", + "https://telegra.ph/file/ebeae90b99fe482d11784.jpg", + "https://telegra.ph/file/70f38f92fe8d3060a31e4.jpg", + "https://telegra.ph/file/db12cf905f557487abc60.jpg", + "https://telegra.ph/file/0f9be531164c927ded8ec.jpg", + "https://telegra.ph/file/57fb7a6df3d666878c6f3.jpg", + "https://telegra.ph/file/242930d9f7aaa0b0729fd.jpg", + "https://telegra.ph/file/883f255792d2c2ebdd5f5.jpg", + "https://telegra.ph/file/36a9c0c26967edf90d42d.jpg", + "https://telegra.ph/file/03bdaf253c43fc97adbbe.jpg", + "https://telegra.ph/file/5826715ff0895a5321d2d.jpg", + "https://telegra.ph/file/74807bfbc85057899ea8d.png", + "https://telegra.ph/file/e390f7531557c12379acb.jpg", + "https://telegra.ph/file/0b83432e72bb0ce0ed0f1.jpg", + "https://telegra.ph/file/23276d7f831611e347a7c.jpg", + "https://telegra.ph/file/109789c7dcc615c6731fa.jpg", + "https://telegra.ph/file/127ef2c311b42b2dbfb62.jpg", + "https://telegra.ph/file/bfd7fcd13b2c353030ef0.jpg", + "https://telegra.ph/file/0f7773c27b1379e2f3bea.jpg", + "https://telegra.ph/file/4606e5c76a4a6c893a721.png", + "https://telegra.ph/file/f46c4569d77d9a6be6aed.jpg", + "https://telegra.ph/file/2b4718637a7396e3b23d9.jpg", + "https://telegra.ph/file/40bce3c8e8ae3cd0198b9.jpg", + "https://telegra.ph/file/ac61cfac3290ed635f8cc.jpg", + "https://telegra.ph/file/55313171c70692e838451.jpg", + "https://telegra.ph/file/f503ce00794cadbdacdd2.jpg", + "https://telegra.ph/file/2153d9fad3613041fcd28.jpg", + "https://telegra.ph/file/6a7a790fe964c8c264b61.jpg", + "https://telegra.ph/file/103d2f4b7b1088890ae24.jpg", + "https://telegra.ph/file/63501bb4f1de53a81dba1.jpg", + "https://telegra.ph/file/00fdb4e3a06a6f6e81c35.jpg", + "https://telegra.ph/file/e2fbfce637048d2e042da.jpg", + "https://telegra.ph/file/29d3c7c297c40a17cde4b.jpg", + "https://telegra.ph/file/97c7aa91c51f72f82c2d9.jpg", + "https://telegra.ph/file/0096988891ba9b884d2dd.jpg", + "https://telegra.ph/file/12cb5cb6512b754deb92d.jpg", + "https://telegra.ph/file/38387c8384879e0ddb803.jpg", + "https://telegra.ph/file/3353253a27522219cc1ce.jpg", + "https://telegra.ph/file/daae7def66cb1d1aefa23.jpg", + "https://telegra.ph/file/e5fe618ad651777061c54.jpg", + "https://telegra.ph/file/3c56aa160ec242b1670eb.jpg", + "https://telegra.ph/file/0794ddfefdc770646c478.jpg", + "https://telegra.ph/file/05bc05a4b878e54ed3b20.jpg", + "https://telegra.ph/file/ef7ffbd3839645e33a0ec.jpg", + "https://telegra.ph/file/1daa50b9d3e26a5509cc2.png", + "https://telegra.ph/file/510600a5b93d83ce048f3.jpg", + "https://telegra.ph/file/0ede8bd4788327111ecbf.jpg", + "https://telegra.ph/file/e9f546797e42e821a91e1.jpg", + "https://telegra.ph/file/fc7fbefe92599bd79d038.jpg", + "https://telegra.ph/file/b88d6e78e206eb73e2e54.jpg", + "https://telegra.ph/file/48f8c62829953e82441e8.jpg", + "https://telegra.ph/file/56f7b34cae98a491e2b35.jpg", + "https://telegra.ph/file/9c23b4302926d40c46e12.jpg", + "https://telegra.ph/file/9bf850ea98a2b252ff233.jpg", + "https://telegra.ph/file/e764f0b3e2ecc56167803.jpg", + "https://telegra.ph/file/289f9cebe37f31a943f98.jpg", + "https://telegra.ph/file/0c647be0f5a48d576d692.jpg", + "https://telegra.ph/file/41c5b44c4f5978828b5b5.jpg", + "https://telegra.ph/file/9cdce279bdf240a933c14.jpg", + "https://telegra.ph/file/f20424687f94e9c285133.jpg", + "https://telegra.ph/file/e7858eb025e1ddb2f6267.jpg", + "https://telegra.ph/file/3e984aa5ab96df166f2a4.jpg", + "https://telegra.ph/file/e43e28aa952eaee6a5315.jpg", + "https://telegra.ph/file/6d222dcaf9ba1072c6062.jpg", + "https://telegra.ph/file/21e696bbefcfe39c6e74e.jpg", + "https://telegra.ph/file/64ec61e41da3d4aded33d.jpg", + "https://telegra.ph/file/5b1d8766504ff75c1bd1f.jpg", + "https://telegra.ph/file/731879a344b3b49fe51bd.jpg", + "https://telegra.ph/file/6221afc84b357ed0d1fc5.jpg", + "https://telegra.ph/file/499bb1117771d8c020038.jpg", + "https://telegra.ph/file/2690d73bc32cfdb986629.jpg", + "https://telegra.ph/file/21255de971701b9df0902.jpg", + "https://telegra.ph/file/434a35e7fe5e2c000c598.jpg", + "https://telegra.ph/file/22a5d3621aba0b370d0b6.png", + "https://telegra.ph/file/ae31845d1df2c4a84915b.png", + "https://telegra.ph/file/ae2b809c8d11e7fa4121d.png", + "https://telegra.ph/file/ccb7f3113994d5d2b26f6.png", + "https://telegra.ph/file/5e53f0257ff12a7b0737a.png", + "https://telegra.ph/file/a613600a9f9f8ee29f0f7.jpg", + "https://telegra.ph/file/129c14fada1a8c0b151f5.jpg", + "https://telegra.ph/file/c7552ed4246ccd8efd301.jpg", + "https://telegra.ph/file/e794f772243d46467bcce.jpg", + "https://telegra.ph/file/b6c43b9bd63f5f764d60b.jpg", + "https://telegra.ph/file/11585459a3950de7f307c.png", + "https://telegra.ph/file/37cde08802c3cea25a03f.jpg", + "https://telegra.ph/file/d8d2db623223dee65963e.png", + "https://telegra.ph/file/b7229017ffee2d814c646.jpg", + "https://telegra.ph/file/65630efca60bfbdf84bc9.jpg", + "https://telegra.ph/file/b8ce571c2f66a7c7070e5.jpg", + "https://telegra.ph/file/a39f63f61f143ec00f19f.jpg", + "https://telegra.ph/file/f7d946d8caaa21bf96dbf.jpg", + "https://telegra.ph/file/9e4bef8ae0725d6b62108.png", + "https://telegra.ph/file/3550089b22f3c8f506226.jpg", + "https://telegra.ph/file/4275a4d4d6d433406b5fa.jpg", + "https://telegra.ph/file/c476583ff55e1947461ad.jpg", + "https://telegra.ph/file/87d2e5c0170ead00a2bc2.jpg", + "https://telegra.ph/file/5027dd7379cc432c06e73.jpg", + "https://telegra.ph/file/9e447fcaf3c66ddefb603.jpg", + "https://telegra.ph/file/e5375f8233bea4f74b0f8.jpg", + "https://telegra.ph/file/a1297510a64733cc5845f.jpg", + "https://telegra.ph/file/ff04b594b699ce72316d7.jpg", + "https://telegra.ph/file/093836a52cb166f161819.jpg", + "https://telegra.ph/file/1e64bae43ca10d628ff6d.jpg", + "https://telegra.ph/file/678ff9bb3405158a9155e.jpg", + "https://telegra.ph/file/ab332ced3f63b96c375c5.jpg", + "https://telegra.ph/file/a736d6cac93294c323303.jpg", + "https://telegra.ph/file/dce8565bf7742f3d7122b.jpg", + "https://telegra.ph/file/3f97672eb7b50426d15ff.jpg", + "https://telegra.ph/file/19c6250369f8588a169c7.jpg", + "https://telegra.ph/file/13d53b03a48448156564c.jpg", + "https://telegra.ph/file/d21ff0d35553890e8cf34.jpg", + "https://telegra.ph/file/a5e4cb43178642ba3709d.jpg", + "https://telegra.ph/file/ecf108d25a6f5f56f91f4.jpg", + "https://telegra.ph/file/8bd2b561b4c1f7164f934.png", + "https://telegra.ph/file/7717658e6930c8196a904.jpg", + "https://telegra.ph/file/dc85d43c4fc5062de7274.jpg", + "https://telegra.ph/file/ff05c19f228ab2ed3d39d.jpg", + "https://telegra.ph/file/ff05c19f228ab2ed3d39d.jpg", + "https://telegra.ph/file/0d686bfffcb92a2fbdb0f.jpg", + "https://telegra.ph/file/0d686bfffcb92a2fbdb0f.jpg", + "https://telegra.ph/file/cdc66f16fbfb75971df2f.jpg", + "https://telegra.ph/file/5c575892b9f9534fd4f31.jpg", + "https://telegra.ph/file/78ffc400d4f3236b00e6b.jpg", + "https://telegra.ph/file/89d32e5bbf084a376c803.jpg", + "https://telegra.ph/file/b5d7dbcdce241013a061b.jpg", + "https://telegra.ph/file/c1d228bc1859213d258d7.jpg", + "https://telegra.ph/file/c6b0720b9f765809ea20a.jpg", + "https://telegra.ph/file/df7e648f2e68ff8e1a1e6.jpg", + "https://telegra.ph/file/5148f764cbc4700519909.jpg", + "https://telegra.ph/file/479e7f51c682dcd1f013f.jpg", + "https://telegra.ph/file/54a9eb0afe7a0f9c7c2f3.jpg", + "https://telegra.ph/file/73c52ee54567a61dac47a.jpg", + "https://telegra.ph/file/1427dbba81bd21b1bfc56.jpg", + "https://telegra.ph/file/1427dbba81bd21b1bfc56.jpg", + "https://telegra.ph/file/b0816374b470a5f9c66a6.jpg", + "https://telegra.ph/file/e10840ec9bea9bbfaff0e.jpg", + "https://telegra.ph/file/5935275d3ee09bc5a47b8.png", + "https://telegra.ph/file/c27e64f1e8ece187c8161.jpg", + "https://telegra.ph/file/055e9af8500ab92755358.jpg", + "https://telegra.ph/file/f18f71167f9318ea28571.jpg", + "https://telegra.ph/file/e2e26f252a5e25a1563c5.jpg", + "https://telegra.ph/file/47ccb13820d6fc54d872b.jpg", + "https://telegra.ph/file/f2ddccd28ceaeae90b2a3.jpg", + "https://telegra.ph/file/951c872f7f8d551995652.jpg", + "https://telegra.ph/file/8e8842f9fe207b8abd951.jpg", + "https://telegra.ph/file/8a14ecd2347ef88e81201.jpg", + "https://telegra.ph/file/b3869374ce0af9f26f92a.jpg", + "https://telegra.ph/file/8e17f8d3633a5696a1ccf.jpg", + "https://telegra.ph/file/b29d8956ae249773b0ec7.png", + "https://telegra.ph/file/d0eebe724b67d2ef7647e.jpg", + "https://telegra.ph/file/5780b3273162d2b9ba9ec.jpg", + "https://telegra.ph/file/e2d56d5dbb108ba7af20c.jpg", + "https://telegra.ph/file/1a4f50dd1e4ec9f04bfa1.jpg", + "https://telegra.ph/file/99b56305fa9c50767f574.jpg", + "https://telegra.ph/file/0859e0104c671bc9b6b7d.jpg", + "https://telegra.ph/file/b3af2980caf7040702171.jpg", + "https://telegra.ph/file/14be160df3b84c59e268e.jpg", + "https://telegra.ph/file/b958155e1e8e9ab9a0416.jpg", + "https://telegra.ph/file/24fff051c39b815e5078a.jpg", + "https://telegra.ph/file/258c02c002e89287d5d9b.jpg", + "https://telegra.ph/file/d2abc99773a9d4954c2ba.jpg", + "https://telegra.ph/file/9849b3940f063b065f4e3.jpg", +] + + +@register(pattern="^/logo ?(.*)") +async def lego(event): + + quew = event.pattern_match.group(1) + + if event.sender_id != OWNER_ID and not quew: + await event.reply("ᴘʟᴇᴀsᴇ ɢɪᴍᴍɪᴇ ᴀ ᴛᴇxᴛ ғᴏʀ ᴛʜᴇ ʟᴏɢᴏ.") + + return + + pesan = await event.reply("ʟᴏɢᴏ ɪɴ ᴀ ᴘʀᴏᴄᴇss. ᴘʟᴇᴀsᴇ ᴡᴀɪᴛ....") + + try: + + text = event.pattern_match.group(1) + + randc = random.choice(LOGO_LINKS) + + img = Image.open(io.BytesIO(requests.get(randc).content)) + + draw = ImageDraw.Draw(img) + + image_widthz, image_heightz = img.size + + fnt = glob.glob("./Exon/utils/Logo/*") + + randf = random.choice(fnt) + + font = ImageFont.truetype(randf, 120) + + w, h = draw.textsize(text, font=font) + + h += int(h * 0.21) + + image_width, image_height = img.size + + draw.text( + ((image_widthz - w) / 2, (image_heightz - h) / 2), + text, + font=font, + fill=(255, 255, 255), + ) + + x = (image_widthz - w) / 2 + + y = (image_heightz - h) / 2 + 6 + + draw.text( + (x, y), text, font=font, fill="white", stroke_width=1, stroke_fill="black" + ) + + fname = "Exon.png" + + img.save(fname, "png") + + await telethn.send_file( + event.chat_id, file=fname, caption=f"ᴍᴀᴅᴇ ʙʏ @{BOT_USERNAME}" + ) + + await pesan.delete() + + if os.path.exists(fname): + + os.remove(fname) + + except Exception as e: + + await event.reply(f"ᴇʀʀᴏʀ, ʀᴇᴘᴏʀᴛ @{SUPPORT_CHAT}, {e}") diff --git a/Exon/modules/misc.py b/Exon/modules/misc.py new file mode 100644 index 00000000..9e199adf --- /dev/null +++ b/Exon/modules/misc.py @@ -0,0 +1,413 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import contextlib +import datetime +import html +import platform +import time +from io import BytesIO +from platform import python_version +from subprocess import PIPE, Popen + +from psutil import boot_time, cpu_percent, disk_usage, virtual_memory +from telegram import Chat, MessageEntity, ParseMode, Update, User +from telegram import __version__ as ptbver +from telegram.error import BadRequest +from telegram.ext import CallbackContext, Filters +from telegram.utils.helpers import escape_markdown, mention_html + +import Exon.modules.sql.users_sql as sql +from Exon import DEMONS as SUPPORT_USERS +from Exon import DEV_USERS +from Exon import DRAGONS as SUDO_USERS +from Exon import INFOPIC, OWNER_ID +from Exon import OWNER_USERNAME as AKBOSS +from Exon import TIGERS +from Exon import WOLVES as WHITELIST_USERS +from Exon import StartTime, dispatcher, sw +from Exon.__main__ import STATS, USER_INFO +from Exon.modules.disable import DisableAbleCommandHandler +from Exon.modules.helper_funcs.chat_status import sudo_plus, user_admin +from Exon.modules.helper_funcs.decorators import Exoncmd +from Exon.modules.helper_funcs.extraction import extract_user +from Exon.modules.users import __user_info__ as chat_count + +MARKDOWN_HELP = f""" +ᴍᴀʀᴋᴅᴏᴡɴ ɪs ᴀ ᴠᴇʀʏ ᴘᴏᴡᴇʀғᴜʟ ғᴏʀᴍᴀᴛᴛɪɴɢ ᴛᴏᴏʟ sᴜᴘᴘᴏʀᴛᴇᴅ ʙʏ ᴛᴇʟᴇɢʀᴀᴍ. {dispatcher.bot.first_name} ʜᴀs sᴏᴍᴇ ᴇɴʜᴀɴᴄᴇᴍᴇɴᴛs, ᴛᴏ ᴍᴀᴋᴇ sᴜʀᴇ ᴛʜᴀᴛ \ +sᴀᴠᴇᴅ ᴍᴇssᴀɢᴇs ᴀʀᴇ ᴄᴏʀʀᴇᴄᴛʟʏ ᴘᴀʀsᴇᴅ, ᴀɴᴅ ᴛᴏ ᴀʟʟᴏᴡ ʏᴏᴜ ᴛᴏ ᴄʀᴇᴀᴛᴇ ʙᴜᴛᴛᴏɴs. + +❂ <code>_italic_</code>: wrapping text with '_' will produce italic text +❂ <code>*bold*</code>: wrapping text with '*' will produce bold text +❂ <code>`code`</code>: wrapping text with '`' will produce monospaced text, also known as 'code' +❂ <code>[sometext](someURL)</code>: this will create a link - the message will just show <code>sometext</code>, \ +ᴀɴᴅ ᴛᴀᴘᴘɪɴɢ ᴏɴ ɪᴛ ᴡɪʟʟ ᴏᴘᴇɴ ᴛʜᴇ ᴘᴀɢᴇ ᴀᴛ <code>someURL</code>. +<b>ᴇxᴀᴍᴘʟᴇ:</b><code>[test](example.com)</code> + +❂ <code>[buttontext](buttonurl:someURL)</code>: this is a special enhancement to allow users to have telegram \ +buttons in their markdown. <code>buttontext</code> will be what is displayed on the button, and <code>someurl</code> \ + +ᴡɪʟʟ be ᴛʜᴇ ᴜʀʟ ᴡʜɪᴄʜ ɪs ᴏᴘᴇɴᴇᴅ. + +<b>ᴇxᴀᴍᴘʟᴇ:</b> <code>[ᴛʜɪs ɪs ᴀ ʙᴜᴛᴛᴏɴ](buttonurl:example.com)</code> + +If you want multiple buttons on the same line, use :same, as such: +<code>[one](buttonurl://example.com) +[two](buttonurl://google.com:same)</code> + +ᴛʜɪs ᴡɪʟʟ ᴄʀᴇᴀᴛᴇ ᴛᴡᴏ ʙᴜᴛᴛᴏɴs ᴏɴ ᴀ sɪɴɢʟᴇ ʟɪɴᴇ, ɪɴsᴛᴇᴀᴅ ᴏғ ᴏɴᴇ ʙᴜᴛᴛᴏɴ ᴘᴇʀ ʟɪɴᴇ. + +Keep in mind that your message <b>MUST</b> contain some text other than just a button! +""" + + +@Exoncmd(command="gifid") +def gifid(update: Update, _): + msg = update.effective_message + if msg.reply_to_message and msg.reply_to_message.animation: + update.effective_message.reply_text( + f"ɢɪғ ɪᴅ:\n<code>{msg.reply_to_message.animation.file_id}</code>", + parse_mode=ParseMode.HTML, + ) + else: + update.effective_message.reply_text("ᴘʟᴇᴀsᴇ ʀᴇᴘʟʏ ᴛᴏ ᴀ ɢɪғ ᴛᴏ ɢᴇᴛ ɪᴛs ID.") + + +@Exoncmd(command="info", pass_args=True) +def info(update: Update, context: CallbackContext): # sourcery no-metrics + bot = context.bot + args = context.args + message = update.effective_message + chat = update.effective_chat + if user_id := extract_user(update.effective_message, args): + user = bot.get_chat(user_id) + + elif not message.reply_to_message and not args: + user = ( + message.sender_chat + if message.sender_chat is not None + else message.from_user + ) + + elif not message.reply_to_message and ( + not args + or ( + len(args) >= 1 + and not args[0].startswith("@") + and not args[0].lstrip("-").isdigit() + and not message.parse_entities([MessageEntity.TEXT_MENTION]) + ) + ): + message.reply_text("I ᴄᴀɴ'ᴛ ᴇxᴛʀᴀᴄᴛ ᴀ ᴜsᴇʀ ғʀᴏᴍ ᴛʜɪs.") + return + + else: + return + + if hasattr(user, "type") and user.type != "private": + text = get_chat_info(user) + is_chat = True + else: + text = get_user_info(chat, user) + is_chat = False + + if INFOPIC: + if is_chat: + try: + pic = user.photo.big_file_id + pfp = bot.get_file(pic).download(out=BytesIO()) + pfp.seek(0) + message.reply_document( + document=pfp, + filename=f"{user.id}.jpg", + caption=text, + parse_mode=ParseMode.HTML, + ) + except AttributeError: # AttributeError means no chat pic so just send text + message.reply_text( + text, + parse_mode=ParseMode.HTML, + disable_web_page_preview=True, + ) + else: + try: + profile = bot.get_user_profile_photos(user.id).photos[0][-1] + _file = bot.get_file(profile["file_id"]) + + _file = _file.download(out=BytesIO()) + _file.seek(0) + + message.reply_document( + document=_file, + caption=(text), + parse_mode=ParseMode.HTML, + ) + + # Incase user don't have profile pic, send normal text + except IndexError: + message.reply_text( + text, parse_mode=ParseMode.HTML, disable_web_page_preview=True + ) + + else: + message.reply_text( + text, parse_mode=ParseMode.HTML, disable_web_page_preview=True + ) + + +def get_user_info(chat: Chat, user: User) -> str: + bot = dispatcher.bot + text = ( + f"╒═══「<b> ᴀɴᴀʟʏᴢᴇᴅ ʀᴇsᴜʟᴛs:</b> 」\n" + f"✦ ᴜsᴇʀ ID: <code>{user.id}</code>\n" + f"✦ ғɪʀsᴛ ɴᴀᴍᴇ: {html.escape(user.first_name)}" + ) + if user.last_name: + text += f"\n✦ ʟᴀsᴛ ɴᴀᴍᴇ: {html.escape(user.last_name)}" + if user.username: + text += f"\n✦ ᴜsᴇʀɴᴀᴍᴇ: @{html.escape(user.username)}" + text += f"\n✦ ᴜsᴇʀ ʟɪɴᴋ: {mention_html(user.id, 'link')}" + with contextlib.suppress(Exception): + if spamwtc := sw.get_ban(int(user.id)): + text += "<b>\n\nsᴘᴀᴍᴡᴀᴛᴄʜ:\n</b>" + text += "<b>ᴛʜɪs ᴘᴇʀsᴏɴ is ʙᴀɴɴᴇᴅ ɪɴ sᴘᴀᴍᴡᴀᴛᴄʜ!</b>" + text += f"\nʀᴇᴀsᴏɴ: <pre>{spamwtc.reason}</pre>" + text += "\nAppeal ᴀᴛ @SpamWatchSupport" + else: + text += "<b>\n\nSpamWatch:</b> Not banned" + disaster_level_present = False + num_chats = sql.get_user_num_chats(user.id) + text += f"\n\n<b>ᴄʜᴀᴛ ᴄᴏᴜɴᴛ</b>: <code>{num_chats}</code>" + with contextlib.suppress(BadRequest): + user_member = chat.get_member(user.id) + if user_member.status == "administrator": + result = bot.get_chat_member(chat.id, user.id) + if result.custom_title: + text += ( + f"\n\nᴛʜɪs ᴜsᴇʀ ʜᴏʟᴅs ᴛʜᴇ ᴛɪᴛʟᴇ <b>{result.custom_title}</b> ʜᴇʀᴇ." + ) + if user.id == OWNER_ID: + text += "\n\n<code>ᴏᴜʀ ᴄᴜᴛᴇ ᴏᴡɴᴇʀ </code> :3" + disaster_level_present = True + elif user.id in DEV_USERS: + text += "\n\n<code>ᴛʜɪs ᴜsᴇʀ ɪs a ᴘᴀʀᴛ ᴏғ ᴏᴜʀ ғᴀᴍɪʟʏ</code> " + disaster_level_present = True + elif user.id in SUDO_USERS: + text += "\n\n<code>ᴏɴᴇ ᴏғ ᴏᴜʀ ʙᴇsᴛᴏ ғʀɪᴇɴᴅᴏs, ᴛᴏᴜᴄʜ ʜɪᴍ ᴀɴᴅ ʏᴏᴜ ᴀʀᴇ ᴅᴇᴀᴅ ᴍᴇᴀᴛ</code>" + disaster_level_present = True + elif user.id in SUPPORT_USERS: + text += "\n\n<code>ᴛʜɪs user is ᴏᴜʀ ғʀɪᴇɴᴅ</code> ✨" + disaster_level_present = True + elif user.id in TIGERS: + text += "\n\n<code>ᴏɴᴇ ᴏғ ᴍʏ ᴄʟᴀssᴍᴀᴛᴇs</code> :p" + disaster_level_present = True + elif user.id in WHITELIST_USERS: + text += "\n\n<code>ᴍᴇᴍʙᴇʀ ᴏғ Exon ᴛᴇᴄʜ, ᴛᴏᴛᴀʟʟʏ ᴄᴏᴏʟ ʀɪɢʜᴛ ?</code>" + disaster_level_present = True + if disaster_level_present: + text += ' [<a href="https://t.me/abishnoi_bots/60">?</a>]' + text += "\n" + for mod in USER_INFO: + if mod.__mod_name__ == "Users": + continue + + try: + mod_info = mod.__user_info__(user.id) + except TypeError: + mod_info = mod.__user_info__(user.id, chat.id) + if mod_info: + text += "\n" + mod_info + return text + + +def get_chat_info(user): + text = f"<b>ᴄʜᴀᴛ ɪɴғᴏʀᴍᴀᴛɪᴏɴ:</b>\n" f"<b>ᴄʜᴀᴛ ᴛɪᴛʟᴇ:</b> {user.title}" + if user.username: + text += f"\n<b>ᴜsᴇʀɴᴀᴍᴇ:</b> @{html.escape(user.username)}" + text += f"\n<b>ᴄʜᴀᴛ ɪᴅ:</b> <code>{user.id}</code>" + text += f"\n<b>ᴄʜᴀᴛ ᴛʏᴘᴇ:</b> {user.type.capitalize()}" + text += "\n" + chat_count(user.id) + + return text + + +def shell(command): + process = Popen(command, stdout=PIPE, shell=True, stderr=PIPE) + stdout, stderr = process.communicate() + return (stdout, stderr) + + +@Exoncmd(command="markdownhelp", filters=Filters.chat_type.private) +def markdown_help(update: Update, _): + update.effective_chat + update.effective_message.reply_text(f"{MARKDOWN_HELP}", parse_mode=ParseMode.HTML) + update.effective_message.reply_text( + "ᴛʀʏ ғᴏʀᴡᴀʀᴅɪɴɢ ᴛʜᴇ ғᴏʟʟᴏᴡɪɴɢ ᴍᴇssᴀɢᴇ ᴛᴏ ᴍᴇ, ᴀɴᴅ ʏᴏᴜ'ʟʟ sᴇᴇ!" + ) + update.effective_message.reply_text( + "/save test ᴛʜɪs ɪs ᴀ ᴍᴀʀᴋᴅᴏᴡɴ ᴛᴇsᴛ. _italics_, *bold*, `code`, " + "[ᴜʀʟ](example.com) [button](buttonurl:github.com) " + "[ʙᴜᴛᴛᴏɴ2](buttonurl://google.com:same)" + ) + + +def get_readable_time(seconds: int) -> str: + count = 0 + ping_time = "" + time_list = [] + time_suffix_list = ["s", "ᴍ", "ʜ", "ᴅᴀʏs"] + + while count < 4: + count += 1 + remainder, result = divmod(seconds, 60) if count < 3 else divmod(seconds, 24) + if seconds == 0 and remainder == 0: + break + time_list.append(int(result)) + seconds = int(remainder) + + for x in range(len(time_list)): + time_list[x] = str(time_list[x]) + time_suffix_list[x] + if len(time_list) == 4: + ping_time += f"{time_list.pop()}, " + + time_list.reverse() + ping_time += ":".join(time_list) + + return ping_time + + +stats_str = """ +""" + + +@Exoncmd(command="stats", can_disable=False) +@sudo_plus +def stats(update, context): + uptime = datetime.datetime.fromtimestamp(boot_time()).strftime("%Y-%m-%d %H:%M:%S") + botuptime = get_readable_time((time.time() - StartTime)) + status = "* 「 sʏsᴛᴇᴍ sᴛᴀᴛɪsᴛɪᴄs: 」*\n\n" + status += f"*• sʏsᴛᴇᴍ sᴛᴀʀᴛ ᴛɪᴍᴇ:* {str(uptime)}" + "\n" + uname = platform.uname() + status += f"*• sʏsᴛᴇᴍ:* {str(uname.system)}" + "\n" + status += f"*• ɴᴏᴅᴇ ɴᴀᴍᴇ:* {escape_markdown(str(uname.node))}" + "\n" + status += f"*• ʀᴇʟᴇᴀsᴇ:* {escape_markdown(str(uname.release))}" + "\n" + status += f"*• ᴍᴀᴄʜɪɴᴇ:* {escape_markdown(str(uname.machine))}" + "\n" + + mem = virtual_memory() + cpu = cpu_percent() + disk = disk_usage("/") + status += f"*• ᴄᴘᴜ:* {str(cpu)}" + " %\n" + status += f"*• ʀᴀᴍ:* {str(mem[2])}" + " %\n" + status += f"*• sᴛᴏʀᴀɢᴇ:* {str(disk[3])}" + " %\n\n" + status += f"*• ᴘʏᴛʜᴏɴ ᴠᴇʀsɪᴏɴ:* {python_version()}" + "\n" + status += f"*• ᴘʏᴛʜᴏɴ ᴛᴇʟᴇɢʀᴀᴍ:* {str(ptbver)}" + "\n" + status += f"*• ᴜᴘᴛɪᴍᴇ:* {str(botuptime)}" + "\n" + + try: + update.effective_message.reply_text( + status + + "\n*ʙᴏᴛ sᴛᴀᴛɪsᴛɪᴄs*:\n" + + "\n".join([mod.__stats__() for mod in STATS]) + + "\n\n[ɢɪᴛʜᴜʙ](https://github.com/KingAbishnoi/ExonRobot) | [ᴛᴇʟᴇɢʀᴀᴍ](https://t.me/AbishnoiMF)\n\n" + + f"「 ʙʏ[ᴀʙɪsʜɴᴏɪ](t.me/{AKBOSS}) 」\n", + parse_mode=ParseMode.MARKDOWN, + disable_web_page_preview=True, + ) + except BaseException: + update.effective_message.reply_text( + ( + ( + ( + "\n*ʙᴏᴛ sᴛᴀᴛɪsᴛɪᴄs*:\n" + + "\n".join(mod.__stats__() for mod in STATS) + ) + + "\n\n⍙ [ɢɪᴛʜᴜʙ](https://github.com/KingAbishnoi/ExonRobot) | [ᴛᴇʟᴇɢʀᴀᴍ](https://t.me/AbishnoiMF)\n\n" + ) + + f"「 ʙʏ [ᴀʙɪsʜɴᴏɪ](t.me/{AKBOSS}) 」\n" + ), + parse_mode=ParseMode.MARKDOWN, + disable_web_page_preview=True, + ) + + +@user_admin +def echo(update: Update, context: CallbackContext): + args = update.effective_message.text.split(None, 1) + message = update.effective_message + + if message.reply_to_message: + message.reply_to_message.reply_text( + args[1], + parse_mode="MARKDOWN", + disable_web_page_preview=True, + ) + else: + message.reply_text( + args[1], + quote=False, + parse_mode="MARKDOWN", + disable_web_page_preview=True, + ) + message.delete() + + +__help__ = """ +*ᴀᴠᴀɪʟᴀʙʟᴇ ᴄᴏᴍᴍᴀɴᴅs:* + +📐 ᴍᴀʀᴋᴅᴏᴡɴ: + +⍟ /markdownhelp : `ǫᴜɪᴄᴋ sᴜᴍᴍᴀʀʏ ᴏғ ʜᴏᴡ ᴍᴀʀᴋᴅᴏᴡɴ ᴡᴏʀᴋs ɪɴ ᴛᴇʟᴇɢʀᴀᴍ - ᴄᴀɴ ᴏɴʟʏ ʙᴇ ᴄᴀʟʟᴇᴅ ɪɴ ᴘʀɪᴠᴀᴛᴇ ᴄʜᴀᴛs ` + + +🗳 ᴏᴛʜᴇʀ ᴄᴏᴍᴍᴀɴᴅs: + +Paste: +⍟ /paste*:*` sᴀᴠᴇs ʀᴇᴘʟɪᴇᴅ ᴄᴏɴᴛᴇɴᴛ ᴛᴏ ɴᴇᴋᴏʙɪɴ.ᴄᴏᴍ ᴀɴᴅ ʀᴇᴘʟɪᴇs ᴡɪᴛʜ ᴀ ᴜʀʟ + +ʀᴇᴀᴄᴛ: +⍟ /react *:* `ʀᴇᴀᴄᴛs ᴡɪᴛʜ a ʀᴀɴᴅᴏᴍ ʀᴇᴀᴄᴛɪᴏɴ ` + +Urban Dictonary: +⍟ /ud <word> *:* `ᴛʏᴘᴇ ᴛʜᴇ ᴡᴏʀᴅ ᴏʀ ᴇxᴘʀᴇssɪᴏɴ ʏᴏᴜ ᴡᴀɴᴛ ᴛᴏ sᴇᴀʀᴄʜ ᴜsᴇ ` + +ᴡɪᴋɪᴘᴇᴅɪᴀ: +⍟ ❂ /wiki <query> *:* `ᴡɪᴋɪᴘᴇᴅɪᴀ ʏᴏᴜʀ ǫᴜᴇʀʏ ` + +ᴡᴀʟʟᴘᴀᴘᴇʀs: +⍟ /wall <query>*:* `get ᴀ ᴡᴀʟʟᴘᴀᴘᴇʀ ғʀᴏᴍ ᴀʟᴘʜᴀᴄᴏᴅᴇʀs ` + +ʙᴏᴏᴋs: +⍟ /book <book name>*:* `ɢᴇᴛs ɪɴsᴛᴀɴᴛ ᴅᴏᴡɴʟᴏᴀᴅ ʟɪɴᴋ ᴏғ ɢɪᴠᴇɴ ʙᴏᴏᴋ `. + +""" + +ECHO_HANDLER = DisableAbleCommandHandler( + "echo", echo, filters=Filters.chat_type.groups, run_async=True +) + +dispatcher.add_handler(ECHO_HANDLER) + +__mod_name__ = "𝙴xᴛʀᴀs" +__command_list__ = ["gifid", "echo"] +__handlers__ = [ECHO_HANDLER] diff --git a/Exon/modules/modules.py b/Exon/modules/modules.py new file mode 100644 index 00000000..018c34e7 --- /dev/null +++ b/Exon/modules/modules.py @@ -0,0 +1,210 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import collections +import importlib + +from telegram import ParseMode, Update +from telegram.ext import CallbackContext, CommandHandler + +from Exon import dispatcher, telethn +from Exon.__main__ import ( + CHAT_SETTINGS, + DATA_EXPORT, + DATA_IMPORT, + HELPABLE, + IMPORTED, + MIGRATEABLE, + STATS, + USER_INFO, + USER_SETTINGS, +) +from Exon.modules.helper_funcs.chat_status import dev_plus, sudo_plus + + +@dev_plus +def load(update: Update, context: CallbackContext): + message = update.effective_message + text = message.text.split(" ", 1)[1] + load_messasge = message.reply_text( + f"ᴀᴛᴛᴇᴍᴘᴛɪɴɢ ᴛᴏ ʟᴏᴀᴅ ᴍᴏᴅᴜʟᴇ : <b>{text}</b>", + parse_mode=ParseMode.HTML, + ) + + try: + imported_module = importlib.import_module(f"Exon.modules.{text}") + except: + load_messasge.edit_text("ᴅᴏᴇs ᴛʜᴀᴛ ᴍᴏᴅᴜʟᴇ ᴇᴠᴇɴ ᴇxɪsᴛ?") + return + + if not hasattr(imported_module, "__mod_name__"): + imported_module.__mod_name__ = imported_module.__name__ + + if imported_module.__mod_name__.lower() not in IMPORTED: + IMPORTED[imported_module.__mod_name__.lower()] = imported_module + else: + load_messasge.edit_text("ᴍᴏᴅᴜʟᴇ ᴀʟʀᴇᴀᴅʏ ʟᴏᴀᴅᴇᴅ.") + return + if "__handlers__" in dir(imported_module): + handlers = imported_module.__handlers__ + for handler in handlers: + if not isinstance(handler, tuple): + dispatcher.add_handler(handler) + elif isinstance(handler[0], collections.Callable): + callback, telethon_event = handler + telethn.add_event_handler(callback, telethon_event) + else: + handler_name, priority = handler + dispatcher.add_handler(handler_name, priority) + else: + IMPORTED.pop(imported_module.__mod_name__.lower()) + load_messasge.edit_text("ᴛʜᴇ ᴍᴏᴅᴜʟᴇ ᴄᴀɴɴᴏᴛ ʙᴇ ʟᴏᴀᴅᴇᴅ.") + return + + if hasattr(imported_module, "__help__") and imported_module.__help__: + HELPABLE[imported_module.__mod_name__.lower()] = imported_module + + # Chats to migrate on chat_migrated events + if hasattr(imported_module, "__migrate__"): + MIGRATEABLE.append(imported_module) + + if hasattr(imported_module, "__stats__"): + STATS.append(imported_module) + + if hasattr(imported_module, "__user_info__"): + USER_INFO.append(imported_module) + + if hasattr(imported_module, "__import_data__"): + DATA_IMPORT.append(imported_module) + + if hasattr(imported_module, "__export_data__"): + DATA_EXPORT.append(imported_module) + + if hasattr(imported_module, "__chat_settings__"): + CHAT_SETTINGS[imported_module.__mod_name__.lower()] = imported_module + + if hasattr(imported_module, "__user_settings__"): + USER_SETTINGS[imported_module.__mod_name__.lower()] = imported_module + + load_messasge.edit_text( + f"sᴜᴄᴄᴇssғᴜʟʟʏ ʟᴏᴀᴅᴇᴅ ᴍᴏᴅᴜʟᴇ : <b>{text}</b>", + parse_mode=ParseMode.HTML, + ) + + +@dev_plus +def unload(update: Update, context: CallbackContext): + message = update.effective_message + text = message.text.split(" ", 1)[1] + unload_messasge = message.reply_text( + f"ᴀᴛᴛᴇᴍᴘᴛɪɴɢ ᴛᴏ ᴜɴʟᴏᴀᴅ ᴍᴏᴅᴜʟᴇ : <b>{text}</b>", + parse_mode=ParseMode.HTML, + ) + + try: + imported_module = importlib.import_module(f"Exon.modules.{text}") + except: + unload_messasge.edit_text("ᴅᴏᴇs ᴛʜᴀᴛ ᴍᴏᴅᴜʟᴇ ᴇᴠᴇɴ ᴇxɪsᴛ?") + return + + if not hasattr(imported_module, "__mod_name__"): + imported_module.__mod_name__ = imported_module.__name__ + if imported_module.__mod_name__.lower() in IMPORTED: + IMPORTED.pop(imported_module.__mod_name__.lower()) + else: + unload_messasge.edit_text("ᴄᴀɴ'ᴛ ᴜɴʟᴏᴀᴅ sᴏᴍᴇᴛʜɪɴɢ that isn't loaded.") + return + if "__handlers__" in dir(imported_module): + handlers = imported_module.__handlers__ + for handler in handlers: + if isinstance(handler, bool): + unload_messasge.edit_text("ᴛʜɪs ᴍᴏᴅᴜʟᴇ ᴄᴀɴ'ᴛ ʙᴇ ᴜɴʟᴏᴀᴅᴇᴅ!") + return + if not isinstance(handler, tuple): + dispatcher.remove_handler(handler) + elif isinstance(handler[0], collections.Callable): + callback, telethon_event = handler + telethn.remove_event_handler(callback, telethon_event) + else: + handler_name, priority = handler + dispatcher.remove_handler(handler_name, priority) + else: + unload_messasge.edit_text("ᴛʜᴇ ᴍᴏᴅᴜʟᴇ ᴄᴀɴɴᴏᴛ ʙᴇ ᴜɴʟᴏᴀᴅᴇᴅ.") + return + + if hasattr(imported_module, "__help__") and imported_module.__help__: + HELPABLE.pop(imported_module.__mod_name__.lower()) + + # Chats to migrate on chat_migrated events + if hasattr(imported_module, "__migrate__"): + MIGRATEABLE.remove(imported_module) + + if hasattr(imported_module, "__stats__"): + STATS.remove(imported_module) + + if hasattr(imported_module, "__user_info__"): + USER_INFO.remove(imported_module) + + if hasattr(imported_module, "__import_data__"): + DATA_IMPORT.remove(imported_module) + + if hasattr(imported_module, "__export_data__"): + DATA_EXPORT.remove(imported_module) + + if hasattr(imported_module, "__chat_settings__"): + CHAT_SETTINGS.pop(imported_module.__mod_name__.lower()) + + if hasattr(imported_module, "__user_settings__"): + USER_SETTINGS.pop(imported_module.__mod_name__.lower()) + + unload_messasge.edit_text( + f"sᴜᴄᴄᴇssғᴜʟʟʏ ᴜɴʟᴏᴀᴅᴇᴅ ᴍᴏᴅᴜʟᴇ : <b>{text}</b>", + parse_mode=ParseMode.HTML, + ) + + +@sudo_plus +def listmodules(update: Update, context: CallbackContext): + message = update.effective_message + module_list = [] + + for helpable_module in HELPABLE: + helpable_module_info = IMPORTED[helpable_module] + file_info = IMPORTED[helpable_module_info.__mod_name__.lower()] + file_name = file_info.__name__.rsplit("Exon.modules.", 1)[1] + mod_name = file_info.__mod_name__ + module_list.append(f"- <code>{mod_name} ({file_name})</code>\n") + module_list = "ғᴏʟʟᴏᴡɪɴɢ ᴍᴏᴅᴜʟᴇs ᴀʀᴇ ʟᴏᴀᴅᴇᴅ : \n\n" + "".join(module_list) + message.reply_text(module_list, parse_mode=ParseMode.HTML) + + +LOAD_HANDLER = CommandHandler("load", load, run_async=True) +UNLOAD_HANDLER = CommandHandler("unload", unload, run_async=True) +LISTMODULES_HANDLER = CommandHandler("listmodules", listmodules, run_async=True) + +dispatcher.add_handler(LOAD_HANDLER) +dispatcher.add_handler(UNLOAD_HANDLER) +dispatcher.add_handler(LISTMODULES_HANDLER) + +__mod_name__ = "ᴍᴏᴅᴜʟᴇs" diff --git "a/Exon/modules/mongo/ABISHNOI COPYRIGHT \302\251" "b/Exon/modules/mongo/ABISHNOI COPYRIGHT \302\251" new file mode 100644 index 00000000..a7ec3b8e --- /dev/null +++ "b/Exon/modules/mongo/ABISHNOI COPYRIGHT \302\251" @@ -0,0 +1,27 @@ +""" +MIT License + +Copyright (c) 2022 ABISHNOI + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" +# ""DEAR PRO PEOPLE, DON'T REMOVE & CHANGE THIS LINE +# TG :- @Abishnoi +# MY ALL BOTS :- Abishnoi_bots +# GITHUB :- KingAbishnoi "" \ No newline at end of file diff --git a/Exon/modules/mongo/chatbot_mongo.py b/Exon/modules/mongo/chatbot_mongo.py new file mode 100644 index 00000000..43a10006 --- /dev/null +++ b/Exon/modules/mongo/chatbot_mongo.py @@ -0,0 +1,57 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +from Exon import mongodb as db_x + +Exon = db_x["CHATBOT"] + + +def add_chat(chat_id): + hima = Exon.find_one({"chat_id": chat_id}) + if hima: + return False + Exon.insert_one({"chat_id": chat_id}) + return True + + +def remove_chat(chat_id): + hima = Exon.find_one({"chat_id": chat_id}) + if not hima: + return False + Exon.delete_one({"chat_id": chat_id}) + return True + + +def get_all_chats(): + r = list(Exon.find()) + if r: + return r + return False + + +def get_session(chat_id): + hima = Exon.find_one({"chat_id": chat_id}) + if not hima: + return False + return hima diff --git a/Exon/modules/mongo/couples_mongo.py b/Exon/modules/mongo/couples_mongo.py new file mode 100644 index 00000000..81e21e87 --- /dev/null +++ b/Exon/modules/mongo/couples_mongo.py @@ -0,0 +1,49 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +from Exon import db + +coupledb = db.couple + + +async def _get_lovers(chat_id: int): + lovers = await coupledb.find_one({"chat_id": chat_id}) + if not lovers: + return {} + return lovers["couple"] + + +async def get_couple(chat_id: int, date: str): + lovers = await _get_lovers(chat_id) + if date in lovers: + return lovers[date] + return False + + +async def save_couple(chat_id: int, date: str, couple: dict): + lovers = await _get_lovers(chat_id) + lovers[date] = couple + await coupledb.update_one( + {"chat_id": chat_id}, {"$set": {"couple": lovers}}, upsert=True + ) diff --git a/Exon/modules/mongo/karma_mongo.py b/Exon/modules/mongo/karma_mongo.py new file mode 100644 index 00000000..8b467849 --- /dev/null +++ b/Exon/modules/mongo/karma_mongo.py @@ -0,0 +1,119 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" +# ""DEAR PRO PEOPLE, DON'T REMOVE & CHANGE THIS LINE +# TG :- @Abishnoi1M +# MY ALL BOTS :- Abishnoi_bots +# GITHUB :- KingAbishnoi "" + +from typing import Dict, Union + +from Exon import db + +karmadb = db.karma +karmaonoffdb = db.karmaonoff + + +async def get_karmas_count() -> dict: + chats = karmadb.find({"chat_id": {"$lt": 0}}) + if not chats: + return {} + chats_count = 0 + karmas_count = 0 + for chat in await chats.to_list(length=1000000): + for i in chat["karma"]: + karma_ = chat["karma"][i]["karma"] + if karma_ > 0: + karmas_count += karma_ + chats_count += 1 + return {"chats_count": chats_count, "karmas_count": karmas_count} + + +async def user_global_karma(user_id) -> int: + chats = karmadb.find({"chat_id": {"$lt": 0}}) + if not chats: + return 0 + total_karma = 0 + for chat in await chats.to_list(length=1000000): + karma = await get_karma(chat["chat_id"], await int_to_alpha(user_id)) + if karma and int(karma["karma"]) > 0: + total_karma += int(karma["karma"]) + return total_karma + + +async def get_karmas(chat_id: int) -> Dict[str, int]: + karma = await karmadb.find_one({"chat_id": chat_id}) + if not karma: + return {} + return karma["karma"] + + +async def get_karma(chat_id: int, name: str) -> Union[bool, dict]: + name = name.lower().strip() + karmas = await get_karmas(chat_id) + if name in karmas: + return karmas[name] + + +async def update_karma(chat_id: int, name: str, karma: dict): + name = name.lower().strip() + karmas = await get_karmas(chat_id) + karmas[name] = karma + await karmadb.update_one( + {"chat_id": chat_id}, {"$set": {"karma": karmas}}, upsert=True + ) + + +async def is_karma_on(chat_id: int) -> bool: + chat = await karmadb.find_one({"chat_id_toggle": chat_id}) + return not chat + + +async def karma_on(chat_id: int): + is_karma = await is_karma_on(chat_id) + if is_karma: + return + return await karmadb.delete_one({"chat_id_toggle": chat_id}) + + +async def karma_off(chat_id: int): + is_karma = await is_karma_on(chat_id) + if not is_karma: + return + return await karmadb.insert_one({"chat_id_toggle": chat_id}) + + +async def int_to_alpha(user_id: int) -> str: + alphabet = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"] + user_id = str(user_id) + return "".join(alphabet[int(i)] for i in user_id) + + +async def alpha_to_int(user_id_alphabet: str) -> int: + alphabet = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"] + user_id = "" + for i in user_id_alphabet: + index = alphabet.index(i) + user_id += str(index) + user_id = int(user_id) + return user_id diff --git a/Exon/modules/mongo/karma_toggle.py b/Exon/modules/mongo/karma_toggle.py new file mode 100644 index 00000000..1f3ade89 --- /dev/null +++ b/Exon/modules/mongo/karma_toggle.py @@ -0,0 +1,117 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +# ""DEAR PRO PEOPLE, DON'T REMOVE & CHANGE THIS LINE +# TG :- @Abishnoi1M +# MY ALL BOTS :- Abishnoi_bots +# GITHUB :- KingAbishnoi "" + + +from telegram import InlineKeyboardButton, InlineKeyboardMarkup, ParseMode, Update +from telegram.ext import CallbackContext, CallbackQueryHandler, CommandHandler +from telegram.utils.helpers import mention_html + +from Exon import dispatcher +from Exon.modules.helper_funcs.chat_status import user_admin, user_admin_no_reply +from Exon.modules.log_channel import loggable + +bot_name = f"{dispatcher.bot.first_name}" + +from pymongo import MongoClient + +from Exon import MONGO_DB_URL + +worddb = MongoClient(MONGO_DB_URL) +k = worddb["ExonKarma"]["karma_status"] + + +@user_admin_no_reply +def karmaadd(update: Update, context: CallbackContext): + query = update.callback_query + context.bot + user = update.effective_user + if query.data == "add_karma": + chat = update.effective_chat + done = k.insert_one({"chat_id": chat.id}) + update.effective_message.edit_text( + f"{bot_name} ᴋᴀʀᴍᴀ sʏsᴛᴇᴍ ᴅɪsᴀʙʟᴇᴅ ʙʏ {mention_html(user.id, user.first_name)}.", + parse_mode=ParseMode.HTML, + ) + + +@user_admin_no_reply +def karmarem(update: Update, context: CallbackContext): + query = update.callback_query + context.bot + user = update.effective_user + if query.data == "rem_karma": + chat = update.effective_chat + done = k.delete_one({"chat_id": chat.id}) + update.effective_message.edit_text( + f"{bot_name} ᴋᴀʀᴍᴀ sʏsᴛᴇᴍ ᴇɴᴀʙʟᴇᴅ ʙʏ {mention_html(user.id, user.first_name)}.", + parse_mode=ParseMode.HTML, + ) + + +@user_admin +@loggable +def karma_toggle(update: Update, context: CallbackContext): + message = update.effective_message + chat = update.effective_chat + is_chatbot = k.find_one({"chat_id": chat.id}) + if is_chatbot: + msg = "ᴋᴀʀᴍᴀ ᴛᴏɢɢʟᴇ\nᴍᴏᴅᴇ : DISABLE" + keyboard = InlineKeyboardMarkup( + [[InlineKeyboardButton(text="ᴇɴᴀʙʟᴇ", callback_data=r"rem_karma")]] + ) + message.reply_text( + msg, + reply_markup=keyboard, + parse_mode=ParseMode.HTML, + ) + if not is_chatbot: + msg = "Karma Toggle\n Mode : ENABLE" + keyboard = InlineKeyboardMarkup( + [[InlineKeyboardButton(text="ᴅɪsᴀʙʟᴇ", callback_data=r"add_karma")]] + ) + message.reply_text( + msg, + reply_markup=keyboard, + parse_mode=ParseMode.HTML, + ) + + +KARMA_STATUS_HANDLER = CommandHandler("karma", karma_toggle, run_async=True) +ADD_KARMA_HANDLER = CallbackQueryHandler(karmaadd, pattern=r"add_karma", run_async=True) +RM_KARMA_HANDLER = CallbackQueryHandler(karmarem, pattern=r"rem_karma", run_async=True) + +dispatcher.add_handler(ADD_KARMA_HANDLER) +dispatcher.add_handler(KARMA_STATUS_HANDLER) +dispatcher.add_handler(RM_KARMA_HANDLER) + +__handlers__ = [ + ADD_KARMA_HANDLER, + KARMA_STATUS_HANDLER, + RM_KARMA_HANDLER, +] diff --git a/Exon/modules/mongo/nsfw_mongo.py b/Exon/modules/mongo/nsfw_mongo.py new file mode 100644 index 00000000..180dc9b5 --- /dev/null +++ b/Exon/modules/mongo/nsfw_mongo.py @@ -0,0 +1,50 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +from Exon import db + +nsfwdb = db.nsfw + +"""ɴsғᴡ sʏsᴛᴇᴍ""" + + +async def is_nsfw_on(chat_id: int) -> bool: + chat = await nsfwdb.find_one({"chat_id": chat_id}) + if not chat: + return True + return False + + +async def nsfw_on(chat_id: int): + is_nsfw = await is_nsfw_on(chat_id) + if is_nsfw: + return + return await nsfwdb.delete_one({"chat_id": chat_id}) + + +async def nsfw_off(chat_id: int): + is_nsfw = await is_nsfw_on(chat_id) + if not is_nsfw: + return + return await nsfwdb.insert_one({"chat_id": chat_id}) diff --git a/Exon/modules/music_button.py b/Exon/modules/music_button.py new file mode 100644 index 00000000..dad76cc5 --- /dev/null +++ b/Exon/modules/music_button.py @@ -0,0 +1,176 @@ +""" +MIT License + +Copyright (c) 2022 ABISHNOI + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +# ""DEAR PRO PEOPLE, DON'T REMOVE & CHANGE THIS LINE +# TG :- @Abishnoi1M +# MY ALL BOTS :- Abishnoi_bots +# GITHUB :- KingAbishnoi "" + + +__mod_name__ = "𝙼ᴜsɪᴄ" + +__help__ = """ +⍟ ADMIN + +**ᴏɴʟʏ ᴀᴅᴍɪɴs ᴄᴀɴ ᴜsᴇ ᴛʜᴇsᴇ ᴄᴏᴍᴍᴀɴᴅs :** +/pause +» ᴩᴀᴜsᴇ ᴛʜᴇ ᴄᴜʀʀᴇɴᴛ ᴏɴɢᴏɪɴɢ sᴛʀᴇᴀᴍ. + +/resume +» ʀᴇsᴜᴍᴇᴅ ᴛʜᴇ ᴩᴀᴜsᴇᴅ sᴛʀᴇᴀᴍ. + +/skip ᴏʀ /next +» sᴋɪᴩ ᴛʜᴇ ᴄᴜʀʀᴇɴᴛ ᴏɴɢᴏɪɴɢ sᴛʀᴇᴀᴍ. + +/end ᴏʀ /stop +» ᴇɴᴅ ᴛʜᴇ ᴄᴜʀᴇᴇɴᴛ ᴏɴɢᴏɪɴ sᴛʀᴇᴀᴍ. + +⍟ AUTH +**ᴄᴏᴍᴍᴀɴᴅs ᴛᴏ ᴀᴜᴛʜ/ᴜɴᴀᴜᴛʜ ᴀɴʏ ᴜsᴇʀ :** + +• ᴀᴜᴛʜᴏʀɪᴢᴇᴅ ᴜsᴇʀs ᴄᴀɴ sᴋɪᴩ, ᴩᴀᴜsᴇ, ʀᴇsᴜᴍᴇ ᴀɴᴅ ᴇɴᴅ ᴛʜᴇ sᴛʀᴇᴀᴍ ᴡɪᴛʜᴏᴜᴛ ᴀᴅᴍɪɴ ʀɪɢʜᴛs. + + +/auth [ᴜsᴇʀɴᴀᴍᴇ ᴏʀ ʀᴇᴩʟʏ ᴛᴏ ᴀ ᴜsᴇʀ's ᴍᴇssᴀɢᴇ] +» ᴀᴅᴅ ᴀ ᴜsᴇʀ ᴛᴏ ᴀᴜᴛʜᴏʀɪᴢᴇᴅ ᴜsᴇʀs ʟɪsᴛ ᴏғ ᴛʜᴇ ɢʀᴏᴜᴩ. + +/unauth [ᴜsᴇʀɴᴀᴍᴇ ᴏʀ ʀᴇᴩʟʏ ᴛᴏ ᴀ ᴜsᴇʀ's ᴍᴇssᴀɢᴇ] +» ʀᴇᴍᴏᴠᴇs ᴛʜᴇ ᴜsᴇʀ ғʀᴏᴍ ᴀᴜᴛʜᴏʀɪᴢᴇᴅ ᴜsᴇʀs ʟɪsᴛ. + +/authusers +» sʜᴏᴡs ᴛʜᴇ ʟɪsᴛ ᴏғ ᴀᴜᴛʜᴏʀɪᴢᴇᴅ ᴜsᴇʀs ᴏғ ᴛʜᴇ ɢʀᴏᴜᴩ. + +⍟ PLAY +**ᴄᴏᴍᴍᴀɴᴅs ᴛᴏ ᴩʟᴀʏ sᴏɴɢs :** + +/play <sᴏɴɢ ɴᴀᴍᴇ/ʏᴛ ᴜʀʟ> +» sᴛᴀʀᴛs ᴩʟᴀʏɪɴɢ ᴛʜᴇ ʀᴇǫᴜᴇsᴛᴇᴅ sᴏɴɢ ᴏɴ ᴠᴄ. + +/queue +» sʜᴏᴡs ᴛʜᴇ ʟɪsᴛ ᴏғ ǫᴜᴇᴜᴇᴅ ᴛʀᴀᴄᴋs ɪɴ ᴛʜᴇ ǫᴜᴇᴜᴇ. + +/lyrics [sᴏɴɢ] +» sʜᴏᴡs ʏᴏᴜ ᴛʜᴇ ʟʏʀɪᴄs ᴏғ ᴛʜᴇ sᴇᴀʀᴄʜᴇᴅ sᴏɴɢ. + +⍟ TOOLS +**sᴏᴍᴇ ᴜsᴇғᴜʟ ᴛᴏᴏʟs :** + + +/ping +» ᴄʜᴇᴄᴋ ɪғ ʙᴏᴛ ɪs ᴀʟɪᴠᴇ ᴏʀ ᴅᴇᴀᴅ. [ɪғ ᴀʟɪᴠᴇ sʜᴏᴡs ʏᴏᴜ ᴛʜᴇ sʏsᴛᴇᴍ sᴛᴀᴛs ᴏғ ᴛʜᴇ ʙᴏᴛ's sᴇʀᴠᴇʀ.] + +/start +» sᴛᴀʀᴛs ᴛʜᴇ ʙᴏᴛ. + +/help +» sʜᴏᴡs ʏᴏᴜ ᴛʜᴇ ʜᴇʟᴩ ᴍᴇɴᴜ ᴏғ ᴛʜᴇ ʙᴏᴛ. + +/stats +» sʜᴏᴡs ᴛʜᴇ sʏsᴛᴇᴍ, ᴀssɪsᴛᴀɴᴛ, ᴍᴏɴɢᴏ ᴀɴᴅ sᴛᴏʀᴀɢᴇ sᴛᴀᴛs ᴏғ ᴛʜᴇ ʙᴏᴛ. + +/sudolist +» sʜᴏᴡs ᴛʜᴇ ʟɪsᴛ ᴏғ sᴜᴅᴏᴇʀs. + +↔↔↔↔↔↔↔↔↔↔↔↔ +✰ ᴛʜɪs ᴍᴜsɪᴄ ʙᴏᴛ ʀᴇᴘᴏ +⍟ https://github.com/TEAM-ABG/AsuXMusic +""" + +####################### +""" +**ᴏɴʟʏ ғᴏʀ sᴜᴅᴏᴇʀs :** + +/speedtest +» ᴄʜᴇᴄᴋᴇʀ sᴇʀᴠᴇʀ sᴩᴇᴇᴅ ᴀɴᴅ ʟᴀᴛᴇɴᴄʏ ᴀɴᴅ ᴩɪɴɢ. + + +⍟ OWNER + +**ᴄᴏᴍᴍᴀɴᴅs ᴛʜᴀᴛ ᴄᴀɴ ᴏɴʟʏ ʙᴇ ᴜsᴇᴅ ʙʏ ᴏᴡɴᴇʀ ᴏғ ᴛʜᴇ ʙᴏᴛ :** + + +/chatlist +» ʟɪsᴛ ᴀʟʟ ᴛʜᴇ ᴄʜᴀᴛs ᴡʜᴇʀᴇ ʙᴏᴛ ɪs ᴩʀᴇsᴇɴᴛ. + +/clean +» ᴄʟᴇᴀɴ ᴀʟʟ ᴛʜᴇ ᴛᴇᴍᴩ ᴅɪʀᴇᴄᴛᴏʀɪᴇs. + +/update +» ғᴇᴛᴄʜ ᴜᴩᴅᴀᴛᴇs ғʀᴏᴍ ᴛʜᴇ ʀᴇᴩᴏ. + +/maintenance on +» ᴇɴᴀʙʟᴇ ᴛʜᴇ ᴍᴀɪɴᴛᴇɴᴀɴᴄᴇ ᴍᴏᴅᴇ ᴏғ ᴛʜᴇ ʙᴏᴛ. + +/maintenance off +» ᴅɪsᴀʙʟᴇ ᴛʜᴇ ᴍᴀɪɴᴛᴇɴᴀɴᴄᴇ ᴍᴏᴅᴇ ᴏғ ᴛʜᴇ ʙᴏᴛ. + +/restart +» ʀᴇsᴛᴀʀᴛs ᴛʜᴇ ʙᴏᴛ ᴏɴ ʏᴏᴜʀ sᴇʀᴠᴇʀ. + + + +⍟ SUDO + +**ᴄᴏᴍᴍᴀɴᴅs ᴛʜᴀᴛ ᴄᴀɴ ᴏɴʟʏ ʙᴇ ᴜsᴇᴅ ʙʏ sᴜᴅᴏ ᴜsᴇʀs :** + +/activevc +» sʜᴏᴡs ᴛʜᴇ ʟɪsᴛ ᴏғ ᴀᴄᴛɪᴠᴇ ᴠᴏɪᴄᴇᴄʜᴀᴛs ᴏɴ ʙᴏᴛ's sᴇʀᴠᴇʀ. + +/gban [ᴜsᴇʀɴᴀᴍᴇ ᴏʀ ʀᴇᴩʟʏ ᴛᴏ ᴀ ᴜsᴇʀ] +» ɢʟᴏʙᴀʟʟʏ ʙᴀɴs ᴀ ᴜsᴇʀ ɪɴ ᴀʟʟ ᴛʜᴇ sᴇʀᴠᴇᴅ ᴄʜᴀᴛs. + +/ungban [ᴜsᴇʀɴᴀᴍᴇ ᴏʀ ʀᴇᴩʟʏ ᴛᴏ ᴀ ᴜsᴇʀ] +» ɢʟᴏʙᴀʟʟʏ ᴜɴʙᴀɴs ᴛʜᴇ ɢ-ʙᴀɴɴᴇᴅ ᴜsᴇʀ. + +/broadcast [ᴍᴇssᴀɢᴇ ᴏʀ ʀᴇᴩʟʏ ᴛᴏ ᴀ ᴍᴇssᴀɢᴇ] +» ʙʀᴏᴀᴅᴄᴀsᴛ's ᴛʜᴇ ɢɪᴠᴇɴ ᴍᴇssᴀɢᴇ ᴛᴏ ᴀʟʟ ᴛʜᴇ sᴇʀᴠᴇᴅ ᴄʜᴀᴛs ᴏғ ᴛʜᴇ ʙᴏᴛ. + +/broadcast_pin [ᴍᴇssᴀɢᴇ ᴏʀ ʀᴇᴩʟʏ ᴛᴏ ᴀ ᴍᴇssᴀɢᴇ] +» ʙʀᴏᴀᴅᴄᴀsᴛ's ᴛʜᴇ ɢɪᴠᴇɴ ᴍᴇssᴀɢᴇ ᴛᴏ ᴀʟʟ ᴛʜᴇ sᴇʀᴠᴇᴅ ᴄʜᴀᴛs ᴏғ ᴛʜᴇ ʙᴏᴛ ᴀɴᴅ ᴩɪɴ's ᴛʜᴇ ʙʀᴏᴀᴅᴄᴀsᴛᴇᴅ ᴍᴇssᴀɢᴇ. + +/joinassistant [ᴄʜᴀᴛ ᴜsᴇʀɴᴀᴍᴇ/ɪᴅ] +» ᴏʀᴅᴇʀ ᴛʜᴇ ᴀssɪsᴛᴀɴᴛ ᴛᴏ ᴊᴏɪɴ ᴛʜᴀᴛ ᴄʜᴀᴛ. + +/leaveassistant [ᴄʜᴀᴛ ᴜsᴇʀɴᴀᴍᴇ/ɪᴅ] +» ᴏʀᴅᴇʀ ᴛʜᴇ ᴀssɪsᴛᴀɴᴛ ᴛᴏ ʟᴇᴀᴠᴇ ᴛʜᴀᴛ ᴄʜᴀᴛ. + +/leavebot [ᴄʜᴀᴛ ᴜsᴇʀɴᴀᴍᴇ/ɪᴅ] +» ᴏʀᴅᴇʀ ᴛʜᴇ ʙᴏᴛ ᴛᴏ ʟᴇᴀᴠᴇ ᴛʜᴀᴛ ᴄʜᴀᴛ. + +.approve [ʀᴇᴩʟʏ ᴛᴏ ᴀ ᴜsᴇʀ's ᴍᴇssᴀɢᴇ] +» ᴀᴩᴩʀᴏᴠᴇs ᴛʜᴇ ᴜsᴇʀ ᴛᴏ ᴩᴍ ᴏɴ ʏᴏᴜʀ ᴀssɪsᴛᴀɴᴛ ᴀᴄᴄᴏᴜɴᴛ. + +.disapprove [ʀᴇᴩʟʏ ᴛᴏ ᴀ ᴜsᴇʀ's ᴍᴇssᴀɢᴇ] +» ᴅɪsᴀᴩᴩʀᴏᴠᴇs ᴛʜᴇ ᴜsᴇʀ ᴛᴏ ᴩᴍ ᴏɴ ʏᴏᴜʀ ᴀssɪsᴛᴀɴᴛ ᴀᴄᴄᴏᴜɴᴛ. + +.pfp [ʀᴇᴩʟʏ ᴛᴏ ᴀ ᴩʜᴏᴛᴏ] +» ᴄʜᴀɴɢᴇs ᴛʜᴇ ᴩғᴩ ᴏғ ᴀssɪsᴛᴀɴᴛ ᴀᴄᴄᴏᴜᴜɴᴛ. + +.bio [ᴛᴇxᴛ] +» ᴄʜᴀɴɢᴇs ᴛʜᴇ ʙɪᴏ ᴏғ ᴀssɪsᴛᴀɴᴛ ᴀᴄᴄᴏᴜɴᴛ. + +↔↔↔↔↔↔↔↔↔↔↔↔↔↔↔↔↔↔↔↔ +✰ ᴛʜɪs ᴍᴜsɪᴄ ʙᴏᴛ ʀᴇᴘᴏ +⍟ https://github.com/TEAM-ABG/AsuXMusic +""" diff --git a/Exon/modules/muting.py b/Exon/modules/muting.py new file mode 100644 index 00000000..6bb8e703 --- /dev/null +++ b/Exon/modules/muting.py @@ -0,0 +1,354 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import html +import re +from typing import Optional + +from telegram import ( + Bot, + CallbackQuery, + Chat, + ChatPermissions, + InlineKeyboardButton, + InlineKeyboardMarkup, + ParseMode, + Update, + User, +) +from telegram.error import BadRequest +from telegram.ext import CallbackContext, CallbackQueryHandler, CommandHandler +from telegram.utils.helpers import mention_html + +from Exon import LOGGER, TIGERS, dispatcher +from Exon.modules.helper_funcs.chat_status import ( + bot_admin, + can_restrict, + connection_status, + is_user_admin, + user_admin, + user_admin_no_reply, +) +from Exon.modules.helper_funcs.extraction import extract_user_and_text +from Exon.modules.helper_funcs.string_handling import extract_time +from Exon.modules.log_channel import loggable +from Exon.modules.sql.approve_sql import is_approved + + +def check_user(user_id: int, bot: Bot, chat: Chat) -> Optional[str]: + + if not user_id: + reply = "𝚈𝚘𝚞 ᴅᴏɴ'ᴛ sᴇᴇᴍ ᴛᴏ ʙᴇ ʀᴇғᴇʀʀɪɴɢ ᴛᴏ ᴀ ᴜsᴇʀ ᴏʀ ᴛʜᴇ ID sᴘᴇᴄɪғɪᴇᴅ ɪs ɪɴᴄᴏʀʀᴇᴄᴛ.." + return reply + + if is_approved(chat.id, user_id): + reply = ( + "ᴛʜɪs ɪs ᴜsᴇʀ is ᴀᴘᴘʀᴏᴠᴇᴅ ɪɴ ᴛʜɪs ᴄʜᴀᴛ ᴀɴᴅ ᴀᴘᴘʀᴏᴠᴇᴅ ᴜsᴇʀs ᴄᴀɴ'ᴛ ʙᴇ ᴍᴜᴛᴇᴅ!" + ) + return reply + + try: + member = chat.get_member(user_id) + except BadRequest as excp: + if excp.message == "User not found": + reply = "I can't seem to find this user" + return reply + raise + + if user_id == bot.id: + reply = "I'm ɴᴏᴛ ɢᴏɴɴᴀ ᴍᴜᴛᴇ ᴍʏsᴇʟғ, ɴᴏᴏʙ?" + return reply + + if is_user_admin(chat, user_id, member) or user_id in TIGERS: + reply = "ᴄᴀɴ'ᴛ. ғɪɴᴅ sᴏᴍᴇᴏɴᴇ ᴇʟsᴇ ᴛᴏ ᴍᴜᴛᴇ ʙᴜᴛ ɴᴏᴛ ᴛʜɪs ᴏɴᴇ." + return reply + + return None + + +@connection_status +@bot_admin +@user_admin +@loggable +def mute(update: Update, context: CallbackContext) -> str: + bot, args = context.bot, context.args + chat = update.effective_chat + user = update.effective_user + message = update.effective_message + + user_id, reason = extract_user_and_text(message, args) + if reply := check_user(user_id, bot, chat): + message.reply_text(reply) + return "" + + member = chat.get_member(user_id) + + log = ( + f"<b>{html.escape(chat.title)}:</b>\n" + f"#ᴍᴜᴛᴇ\n" + f"<b>ᴀᴅᴍɪɴ:</b> {mention_html(user.id, user.first_name)}\n" + f"<b>ᴜsᴇʀ:</b> {mention_html(member.user.id, member.user.first_name)}" + ) + + if reason: + log += f"\n<b>ʀᴇᴀsᴏɴ:</b> {reason}" + + if member.can_send_messages is None or member.can_send_messages: + chat_permissions = ChatPermissions(can_send_messages=False) + bot.restrict_chat_member(chat.id, user_id, chat_permissions) + msg = ( + f"<code>🗣️</code><b>ᴍᴜᴛᴇ Event</b>\n" + f"<code> </code><b>• ᴍᴜᴛᴇᴅ ᴜsᴇʀ:</b> {mention_html(member.user.id, member.user.first_name)}" + ) + if reason: + msg += f"\n<code> </code><b>• ʀᴇᴀsᴏɴ:</b> \n{html.escape(reason)}" + + keyboard = InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + "ᴜɴᴍᴜᴛᴇ", callback_data=f"unmute_({member.user.id})" + ) + ] + ] + ) + + bot.sendMessage( + chat.id, + msg, + reply_markup=keyboard, + parse_mode=ParseMode.HTML, + ) + return log + message.reply_text("ᴛʜɪs ᴜsᴇʀ ɪs ᴀʟʀᴇᴀᴅʏ ᴍᴜᴛᴇᴅ!") + + return "" + + +@connection_status +@bot_admin +@user_admin +@loggable +def unmute(update: Update, context: CallbackContext) -> str: + bot, args = context.bot, context.args + chat = update.effective_chat + user = update.effective_user + message = update.effective_message + + user_id, reason = extract_user_and_text(message, args) + if not user_id: + message.reply_text( + "ʏᴏᴜ'ʟʟ ɴᴇᴇᴅ to ᴇɪᴛʜᴇʀ ɢɪᴠᴇ ᴍᴇ ᴀ ᴜsᴇʀɴᴀᴍᴇ ᴛᴏ ᴜɴᴍᴜᴛᴇ, ᴏʀ ʀᴇᴘʟʏ ᴛᴏ sᴏᴍᴇᴏɴᴇ ᴛᴏ ʙᴇ ᴜɴᴍᴜᴛᴇᴅ." + ) + return "" + + member = chat.get_member(int(user_id)) + + if member.status in ("kicked", "left"): + message.reply_text( + "ᴛʜɪs ᴜsᴇʀ ɪsɴ'ᴛ ᴇᴠᴇɴ ɪɴ ᴛʜᴇ ᴄʜᴀᴛ, ᴜɴᴍᴜᴛɪɴɢ ᴛʜᴇᴍ ᴡᴏɴ'ᴛ ᴍᴀᴋᴇ ᴛʜᴇᴍ ᴛᴀʟᴋ ᴍᴏʀᴇ ᴛʜᴀɴ ᴛʜᴇʏ " + "ᴀʟʀᴇᴀᴅʏ ᴅᴏ!", + ) + + elif ( + member.can_send_messages + and member.can_send_media_messages + and member.can_send_other_messages + and member.can_add_web_page_previews + ): + message.reply_text("ᴛʜɪs ᴜsᴇʀ ᴀʟʀᴇᴀᴅʏ ʜᴀs ᴛʜᴇ ʀɪɢʜᴛ ᴛᴏ sᴘᴇᴀᴋ.") + else: + chat_permissions = ChatPermissions( + can_send_messages=True, + can_invite_users=True, + can_pin_messages=True, + can_send_polls=True, + can_change_info=True, + can_send_media_messages=True, + can_send_other_messages=True, + can_add_web_page_previews=True, + ) + try: + bot.restrict_chat_member(chat.id, int(user_id), chat_permissions) + except BadRequest: + pass + bot.sendMessage( + chat.id, + f"{mention_html(member.user.id, member.user.first_name)} ᴡᴀs ᴜɴᴍᴜᴛᴇᴅ ʙʏ {mention_html(user.id, user.first_name)} in <b>{message.chat.title}</b>\n<b>ʀᴇᴀsᴏɴ</b>: <code>{reason}</code>", + parse_mode=ParseMode.HTML, + ) + + return ( + f"<b>{html.escape(chat.title)}:</b>\n" + f"#ᴜɴᴍᴜᴛᴇ\n" + f"<b>ᴀᴅᴍɪɴ:</b> {mention_html(user.id, user.first_name)}\n" + f"<b>ᴜsᴇʀ:</b> {mention_html(member.user.id, member.user.first_name)}" + ) + return "" + + +@connection_status +@bot_admin +@can_restrict +@user_admin +@loggable +def temp_mute(update: Update, context: CallbackContext) -> str: + bot, args = context.bot, context.args + chat = update.effective_chat + user = update.effective_user + message = update.effective_message + + user_id, reason = extract_user_and_text(message, args) + if reply := check_user(user_id, bot, chat): + message.reply_text(reply) + return "" + + member = chat.get_member(user_id) + + if not reason: + message.reply_text("ʏᴏᴜ ʜᴀᴠᴇɴ'ᴛ sᴘᴇᴄɪғɪᴇᴅ ᴀ ᴛɪᴍᴇ ᴛᴏ ᴍᴜᴛᴇ ᴛʜɪs ᴜsᴇʀ ғᴏʀ!") + return "" + + split_reason = reason.split(None, 1) + + time_val = split_reason[0].lower() + reason = split_reason[1] if len(split_reason) > 1 else "" + mutetime = extract_time(message, time_val) + + if not mutetime: + return "" + + log = ( + f"<b>{html.escape(chat.title)}:</b>\n" + f"#ᴛᴇᴍᴘ ᴍᴜᴛᴇᴅ\n" + f"<b>ᴀᴅᴍɪɴ:</b> {mention_html(user.id, user.first_name)}\n" + f"<b>ᴜsᴇʀ:</b> {mention_html(member.user.id, member.user.first_name)}\n" + f"<b>ᴛɪᴍᴇ:</b> {time_val}" + ) + if reason: + log += f"\n<b>ʀᴇᴀsᴏɴ:</b> {reason}" + + try: + if member.can_send_messages is None or member.can_send_messages: + chat_permissions = ChatPermissions(can_send_messages=False) + bot.restrict_chat_member( + chat.id, + user_id, + chat_permissions, + until_date=mutetime, + ) + msg = ( + f"<code>🗣️</code><b>ᴛɪᴍᴇ ᴍᴜᴛᴇ ᴇᴠᴇɴᴛ</b>\n" + f"<code> </code><b>• ᴍᴜᴛᴇᴅ ᴜsᴇʀ:</b> {mention_html(member.user.id, member.user.first_name)}\n" + f"<code> </code><b>• ᴜsᴇʀ ᴡɪʟʟ ʙᴇ ᴍᴜᴛᴇᴅ ғᴏʀ:</b> {time_val}\n" + ) + + keyboard = InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + "ᴜɴᴍᴜᴛᴇ", + callback_data=f"unmute_({member.user.id})", + ) + ] + ] + ) + + bot.sendMessage( + chat.id, msg, reply_markup=keyboard, parse_mode=ParseMode.HTML + ) + + return log + message.reply_text("ᴛʜɪs ᴜsᴇʀ ɪs ᴀʟʀᴇᴀᴅʏ ᴍᴜᴛᴇᴅ.") + + except BadRequest as excp: + if excp.message == "ʀᴇᴘʟʏ ᴍᴇssᴀɢᴇ ɴᴏᴛ ғᴏᴜɴᴅ": + # Do not reply + message.reply_text(f"ᴍᴜᴛᴇᴅ ғᴏʀ {time_val}!", quote=False) + return log + LOGGER.warning(update) + LOGGER.exception( + "ᴇʀʀᴏʀ ᴍᴜᴛɪɴɢ ᴜsᴇʀ %s ɪɴ ᴄʜᴀᴛ %s (%s) ᴅᴜᴇ ᴛᴏ %s", + user_id, + chat.title, + chat.id, + excp.message, + ) + message.reply_text("ᴡᴇʟʟ ᴅᴀᴍɴ, ɪ ᴄᴀɴ'ᴛ ᴍᴜᴛᴇ ᴛʜᴀᴛ ᴜsᴇʀ.") + + return "" + + +@user_admin_no_reply +@bot_admin +@loggable +def button(update: Update, context: CallbackContext) -> str: + query: Optional[CallbackQuery] = update.callback_query + user: Optional[User] = update.effective_user + bot: Optional[Bot] = context.bot + if match := re.match(r"unmute_\((.+?)\)", query.data): + user_id = match[1] + chat: Optional[Chat] = update.effective_chat + member = chat.get_member(user_id) + chat_permissions = ChatPermissions( + can_send_messages=True, + can_invite_users=True, + can_pin_messages=True, + can_send_polls=True, + can_change_info=True, + can_send_media_messages=True, + can_send_other_messages=True, + can_add_web_page_previews=True, + ) + if unmuted := bot.restrict_chat_member(chat.id, int(user_id), chat_permissions): + update.effective_message.edit_text( + f"ᴀᴅᴍɪɴ {mention_html(user.id, user.first_name)} ᴜɴᴍᴜᴛᴇᴅ {mention_html(member.user.id, member.user.first_name)}!", + parse_mode=ParseMode.HTML, + ) + query.answer("ᴜɴᴍᴜᴛᴇᴅ!") + return ( + f"<b>{html.escape(chat.title)}:</b>\n" + f"#ᴜɴᴍᴜᴛᴇ\n" + f"<b>ᴀᴅᴍɪɴ:</b> {mention_html(user.id, user.first_name)}\n" + f"<b>ᴜsᴇʀ:</b> {mention_html(member.user.id, member.user.first_name)}" + ) + else: + update.effective_message.edit_text( + "ᴛʜɪs ᴜsᴇʀ ɪs ɴᴏᴛ ᴍᴜᴛᴇᴅ ᴏʀ ʜᴀs ʟᴇғᴛ ᴛʜᴇ ɢʀᴏᴜᴘ!" + ) + return "" + + +MUTE_HANDLER = CommandHandler("mute", mute, run_async=True) +UNMUTE_HANDLER = CommandHandler("unmute", unmute, run_async=True) +TEMPMUTE_HANDLER = CommandHandler(["tmute", "tempmute"], temp_mute, run_async=True) +UNMUTE_BUTTON_HANDLER = CallbackQueryHandler(button, pattern=r"unmute_") + +dispatcher.add_handler(MUTE_HANDLER) +dispatcher.add_handler(UNMUTE_HANDLER) +dispatcher.add_handler(TEMPMUTE_HANDLER) +dispatcher.add_handler(UNMUTE_BUTTON_HANDLER) + +__mod_name__ = "ᴍᴜᴛɪɴɢ" +__handlers__ = [MUTE_HANDLER, UNMUTE_HANDLER, TEMPMUTE_HANDLER] diff --git a/Exon/modules/neko.py b/Exon/modules/neko.py new file mode 100644 index 00000000..48510adc --- /dev/null +++ b/Exon/modules/neko.py @@ -0,0 +1,119 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import html +import random + +from telegram import ParseMode, Update +from telegram.error import BadRequest +from telegram.ext import CallbackContext, run_async + +import Exon.modules.nekostrings as nekostrings +from Exon import dispatcher +from Exon.modules.disable import DisableAbleCommandHandler +from Exon.modules.helper_funcs.extraction import extract_user + + +@run_async +def nyaa(update: Update, context: CallbackContext): + bot = context.bot + args = context.args + message = update.effective_message + + reply_to = message.reply_to_message or message + + curr_user = html.escape(message.from_user.first_name) + if user_id := extract_user(message, args): + neko_user = bot.get_chat(user_id) + user1 = curr_user + user2 = html.escape(neko_user.first_name) + + else: + user1 = bot.first_name + user2 = curr_user + + nyaa_type = random.choice(("Text", "Gif")) + if nyaa_type == "Gif": + try: + temp = random.choice(nekostrings.NEKO_GIFS) + reply_to.reply_animation(temp) + except BadRequest: + nyaa_type = "Text" + + if nyaa_type == "Text": + temp = random.choice(nekostrings.NEKO_TEXT) + reply = temp.format(user1=user1, user2=user2) + reply_to.reply_text(reply, parse_mode=ParseMode.HTML) + + +@run_async +def meow(update: Update, context: CallbackContext): + bot = context.bot + args = context.args + message = update.effective_message + + reply_to = message.reply_to_message or message + + curr_user = html.escape(message.from_user.first_name) + if user_id := extract_user(message, args): + bot.get_chat(user_id) + user1 = curr_user + user2 = html.escape(neko_user.first_name) + + else: + user1 = bot.first_name + user2 = curr_user + + meow_type = random.choice(("Text", "Gif")) + if meow_type == "Gif": + try: + temp = random.choice(nekostrings.CATTO_GIFS) + reply_to.reply_animation(temp) + except BadRequest: + pass + + if meow_type == "Text": + temp = random.choice(nekostrings.CATTO_TEXT) + reply = temp.format(user1=user1, user2=user2) + reply_to.reply_text(reply, parse_mode=ParseMode.HTML) + + +__help__ = """ +⍟ /nyaa*:* `ᴜsᴇ ᴛʜɪs ᴛᴏ ɢᴇᴛ ᴄᴜᴛᴇ ɴᴇᴋᴏ/ᴄᴀᴛᴛᴏ ɢɪғs!` + +⍟ /meow*:* `ᴡᴏʀᴋs sᴀᴍᴇ ᴀs ᴀʙᴏᴠᴇ!` +""" + + +NYAA_HANDLER = DisableAbleCommandHandler("nyaa", nyaa) +MEOW_HANDLER = DisableAbleCommandHandler("meow", meow) + + +dispatcher.add_handler(NYAA_HANDLER) +dispatcher.add_handler(MEOW_HANDLER) + +__mod_name__ = "𝙽ᴇᴋᴏ" + +__command_list__ = ["nyaa", "meow"] +__handlers__ = [NYAA_HANDLER, MEOW_HANDLER] diff --git a/Exon/modules/nekostrings.py b/Exon/modules/nekostrings.py new file mode 100644 index 00000000..c19989cd --- /dev/null +++ b/Exon/modules/nekostrings.py @@ -0,0 +1,86 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + + +NEKO_GIFS = [ + "CgACAgQAAxkBAAMrYqsUnGR_WSKGqHlEZf7y6HkYERYAAssLAAIonphSDdalOsV7SvckBA", + "CgACAgQAAxkBAAMuYqsUveNqlzPijbjFjKu7s_XjxPkAAmwJAAIGErhQ91BF0LmjUI8kBA", + "CgACAgQAAxkBAAMxYqsVJ8fGYUxPk4bozwQSeLnHassAAkMKAAJ3hRBTZifAU99mVPUkBA", + "CgACAgQAAxkBAAM2YqsVP3YBS-CRdwP7Y7BldYqQpoUAAr0LAALXAvBQxI6NdeN-9KYkBA", + "CgACAgIAAxkBAAM5YqsVSzUoI2hfP7SWLaARgHoXoUIAArwDAAKsUoBJMtAiy5zsOHokBA", + "CgACAgQAAxkBAAM8YqsVWWn-2hu2dKIFjYADops8GkgAAkgKAALfd-lRRubt0w6DL2UkBA", + "CgACAgQAAxkBAAM_YqsVba1Fx_uYXshjrYRGnEDlBuwAAgIMAALUNQhQl85MfEwjEuQkBA", + "CgACAgQAAxkBAANCYqsVd4Bx8bSX7kNhsmVmf9ErrTMAAtwCAAKzSl1RNMbxHZVn1fYkBA", + "CgACAgQAAxkBAANFYqsVhbz-lNmztm3kQD-1KrEtMVUAApsJAAJKQ4FTrApv8b4mbyAkBA", + "CgACAgQAAxkBAANIYqsVkfblz6kTvx3xxa6hNakFAhAAAjsZAAIxGGQH7NG_RZRZcuQkBA", + "CgACAgQAAxkBAANLYqsVq_iEWP7UHujaYCc-XzcymFMAAhsDAAK8KqxQy2qoV0Sm9_EkBA", + "CgACAgQAAxkBAANMYqsVq94MQ8G-yegkbOomompFgEUAAioJAAKMUyhQI0qgXgVwDkokBA", + "CgACAgQAAxkBAANNYqsVqycytgiSFoGcrjCWfuS-31UAAkkCAAKVzZRSo328NRXOTtIkBA", + "CgACAgQAAxkBAANOYqsVq16uzEa6RbP1E-Ge4K3uBCgAAgkNAAJhFoBRqPScY1M8bKYkBA", + "CgACAgQAAxkBAANPYqsVq60g7E29o3V54JHXGL4jnpQAAv8KAAJMg7hT2oDYsq-FzJ4kBA", + "CgACAgIAAxkBAANQYqsVq0DediMlY1rpPZfuD1DiF-IAArwDAAKsUoBJMtAiy5zsOHokBA", + "CgACAgQAAxkBAANRYqsVq5mts75j64nk3roQPcfepJ0AAr0LAALXAvBQxI6NdeN-9KYkBA", + "CgACAgQAAxkBAANSYqsVq0liFvD8IxdTsPgCptgrieQAAisLAAI6y7BTbGoeRWS3ItgkBA", + "CgACAgQAAxkBAANTYqsVq9AMgXbGEf50Wu4uD6_JeM0AAhsKAAKbqOhTq0LaOEquUqEkBA", + "CgACAgIAAxkBAANUYqsVqzE4jN19KFXHzl9B_PGQX0oAAjQHAALcyRhKZbta5VIkDPkkBA", +] + + +NEKO_TEXT = [ + " ᴛʜᴇ ɴᴇᴋᴏ, sᴄʀᴀᴛᴄʜᴇs ʏᴏᴜ!", + "ɴʏᴀ! {user1} ɪs ᴀ ɴᴇᴋᴏ!", + "ᴍʏ ʜᴇᴀʀᴛ ɢᴏᴇs ᴍᴇᴏᴡ😻", + "ɴᴇᴋᴏ ɴᴇᴋᴏ ɴɪɪɪ!" "{user1} ɴʏᴀᴇᴅ {user2}", +] + +CATTO_GIFS = [ + "CgACAgQAAxkBAAMrYqsUnGR_WSKGqHlEZf7y6HkYERYAAssLAAIonphSDdalOsV7SvckBA", + "CgACAgQAAxkBAAMuYqsUveNqlzPijbjFjKu7s_XjxPkAAmwJAAIGErhQ91BF0LmjUI8kBA", + "CgACAgQAAxkBAAMxYqsVJ8fGYUxPk4bozwQSeLnHassAAkMKAAJ3hRBTZifAU99mVPUkBA", + "CgACAgQAAxkBAAM2YqsVP3YBS-CRdwP7Y7BldYqQpoUAAr0LAALXAvBQxI6NdeN-9KYkBA", + "CgACAgIAAxkBAAM5YqsVSzUoI2hfP7SWLaARgHoXoUIAArwDAAKsUoBJMtAiy5zsOHokBA", + "CgACAgQAAxkBAAM8YqsVWWn-2hu2dKIFjYADops8GkgAAkgKAALfd-lRRubt0w6DL2UkBA", + "CgACAgQAAxkBAAM_YqsVba1Fx_uYXshjrYRGnEDlBuwAAgIMAALUNQhQl85MfEwjEuQkBA", + "CgACAgQAAxkBAANCYqsVd4Bx8bSX7kNhsmVmf9ErrTMAAtwCAAKzSl1RNMbxHZVn1fYkBA", + "CgACAgQAAxkBAANFYqsVhbz-lNmztm3kQD-1KrEtMVUAApsJAAJKQ4FTrApv8b4mbyAkBA", + "CgACAgQAAxkBAANIYqsVkfblz6kTvx3xxa6hNakFAhAAAjsZAAIxGGQH7NG_RZRZcuQkBA", + "CgACAgQAAxkBAANLYqsVq_iEWP7UHujaYCc-XzcymFMAAhsDAAK8KqxQy2qoV0Sm9_EkBA", + "CgACAgQAAxkBAANMYqsVq94MQ8G-yegkbOomompFgEUAAioJAAKMUyhQI0qgXgVwDkokBA", + "CgACAgQAAxkBAANNYqsVqycytgiSFoGcrjCWfuS-31UAAkkCAAKVzZRSo328NRXOTtIkBA", + "CgACAgQAAxkBAANOYqsVq16uzEa6RbP1E-Ge4K3uBCgAAgkNAAJhFoBRqPScY1M8bKYkBA", + "CgACAgQAAxkBAANPYqsVq60g7E29o3V54JHXGL4jnpQAAv8KAAJMg7hT2oDYsq-FzJ4kBA", + "CgACAgIAAxkBAANQYqsVq0DediMlY1rpPZfuD1DiF-IAArwDAAKsUoBJMtAiy5zsOHokBA", + "CgACAgQAAxkBAANRYqsVq5mts75j64nk3roQPcfepJ0AAr0LAALXAvBQxI6NdeN-9KYkBA", + "CgACAgQAAxkBAANSYqsVq0liFvD8IxdTsPgCptgrieQAAisLAAI6y7BTbGoeRWS3ItgkBA", + "CgACAgQAAxkBAANTYqsVq9AMgXbGEf50Wu4uD6_JeM0AAhsKAAKbqOhTq0LaOEquUqEkBA", + "CgACAgIAAxkBAANUYqsVqzE4jN19KFXHzl9B_PGQX0oAAjQHAALcyRhKZbta5VIkDPkkBA", +] + +CATTO_TEXT = [ + "ᴄᴀᴛ Is sʟᴇᴇᴘɪɴɢ ɴʏᴀ!", + "ᴄᴀᴛᴛᴏ ᴊᴜsᴛ sᴄʀᴀᴛᴄʜᴇᴅ ʏᴏᴜ", + "ᴀʙɪsʜɴᴏɪ1M is ᴛʜᴇ ᴄᴀᴛᴛᴏ ʟᴏʀᴅ", + "ᴄᴀᴛᴛᴏ ɪs ᴘʟᴀʏɪɴɢ ᴡɪᴛʜ @ᴀʙɪsʜɴᴏɪᴍғ ɴᴏᴡ!", + "Catto ʟᴏᴠᴇs ʏᴏᴜ ❤", +] diff --git a/Exon/modules/nhentai.py b/Exon/modules/nhentai.py new file mode 100644 index 00000000..bd4b26a8 --- /dev/null +++ b/Exon/modules/nhentai.py @@ -0,0 +1,81 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import requests +from pyrogram import filters +from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup + +from Exon import BOT_USERNAME, pgram, telegraph +from Exon.utils.errors import capture_err + + +@pgram.on_message(~filters.me & filters.command("nhentai", prefixes="/"), group=8) +@capture_err +async def nhentai(client, message): + query = message.text.split(" ")[1] + title, tags, artist, total_pages, post_url, cover_image = nhentai_data(query) + await message.reply_text( + f"<code>{title}</code>\n\n<b>Tags:</b>\n{tags}\n<b>Artists:</b>\n{artist}\n<b>Pages:</b>\n{total_pages}", + reply_markup=InlineKeyboardMarkup( + [[InlineKeyboardButton("ʀᴇᴀᴅ ʜᴇʀᴇ", url=post_url)]] + ), + ) + + +def nhentai_data(noombers): + url = f"https://nhentai.net/api/gallery/{noombers}" + res = requests.get(url).json() + pages = res["images"]["pages"] + info = res["tags"] + title = res["title"]["english"] + links = [] + tags = "" + artist = "" + total_pages = res["num_pages"] + extensions = {"j": "jpg", "p": "png", "g": "gif"} + for i, x in enumerate(pages): + media_id = res["media_id"] + temp = x["t"] + file = f"{i+1}.{extensions[temp]}" + link = f"https://i.nhentai.net/galleries/{media_id}/{file}" + links.append(link) + + for i in info: + if i["type"] == "tag": + tag = i["name"] + tag = tag.split(" ") + tag = "_".join(tag) + tags += f"#{tag} " + if i["type"] == "artist": + artist = f"{i['name']} " + + post_content = "".join(f"<img src={link}><br>" for link in links) + + post = telegraph.create_page( + f"{title}", + html_content=post_content, + author_name="ɪᴛs ᴍᴇ", + author_url=f"https://t.me/{BOT_USERNAME}", + ) + return title, tags, artist, total_pages, post["url"], links[0] diff --git a/Exon/modules/nightmode.py b/Exon/modules/nightmode.py new file mode 100644 index 00000000..e9ad04b5 --- /dev/null +++ b/Exon/modules/nightmode.py @@ -0,0 +1,164 @@ +from apscheduler.schedulers.asyncio import AsyncIOScheduler +from telethon import functions, types +from telethon.tl.types import ChatBannedRights + +from Exon import dispatcher +from Exon import telethn as tbot +from Exon.events import register +from Exon.modules.sql.nightmode_sql import ( + add_nightmode, + get_all_chat_id, + is_nightmode_indb, + rmnightmode, +) + + +async def is_register_admin(chat, user): + if isinstance(chat, (types.InputPeerChannel, types.InputChannel)): + + return isinstance( + ( + await tbot(functions.channels.GetParticipantRequest(chat, user)) + ).participant, + (types.ChannelParticipantAdmin, types.ChannelParticipantCreator), + ) + elif isinstance(chat, types.InputPeerChat): + + ui = await tbot.get_peer_id(user) + ps = ( + await tbot(functions.messages.GetFullChatRequest(chat.chat_id)) + ).full_chat.participants.participants + return isinstance( + next((p for p in ps if p.user_id == ui), None), + (types.ChatParticipantAdmin, types.ChatParticipantCreator), + ) + else: + return None + + +hima = ChatBannedRights( + until_date=None, + send_messages=True, + send_media=True, + send_stickers=True, + send_gifs=True, + send_games=True, + send_inline=True, + send_polls=True, + invite_users=True, + pin_messages=True, + change_info=True, +) +openhima = ChatBannedRights( + until_date=None, + send_messages=False, + send_media=False, + send_stickers=False, + send_gifs=False, + send_games=False, + send_inline=False, + send_polls=False, + invite_users=True, + pin_messages=True, + change_info=True, +) + + +@register(pattern="^/nightmode") +async def close_ws(event): + if event.is_group: + if not (await is_register_admin(event.input_chat, event.message.sender_id)): + await event.reply("ʏᴏᴜ ᴀʀᴇ ɴᴏᴛ ᴀᴅᴍɪɴ sᴏ ʏᴏᴜ ᴄᴀɴ'ᴛ ᴜsᴇ ᴛʜɪs ᴄᴏᴍᴍᴀɴᴅ...") + return + + if not event.is_group: + await event.reply("ʏᴏᴜ ᴄᴀɴ ᴏɴʟʏ ᴇɴᴀʙʟᴇ ɴɪɢʜᴛ ᴍᴏᴅᴇ ɪɴ ɢʀᴏᴜᴘs.") + return + if is_nightmode_indb(str(event.chat_id)): + await event.reply("ᴛʜɪs ᴄʜᴀᴛ ʜᴀs ᴀʟʀᴇᴀᴅʏ ᴇɴᴀʙʟᴇᴅ ɴɪɢʜᴛ ᴍᴏᴅᴇ.") + return + add_nightmode(str(event.chat_id)) + await event.reply( + f"ᴀᴅᴅᴇᴅ ᴄʜᴀᴛ {event.chat.title} ᴡɪᴛʜ ɪᴅ `{event.chat_id}` ᴛᴏ ᴅᴀᴛᴀʙᴀsᴇ. **ᴛʜɪs ɢʀᴏᴜᴘ ᴡɪʟʟ ʙᴇ ᴄʟᴏsᴇᴅ ᴏɴ 12 ᴀᴍ (ɪsᴛ) ᴀɴᴅ ᴡɪʟʟ ᴏᴘᴇɴ ᴏɴ 06 ᴀᴍ (ɪsᴛ)**" + ) + + +@register(pattern="^/rmnight") +async def disable_ws(event): + if event.is_group: + if not (await is_register_admin(event.input_chat, event.message.sender_id)): + await event.reply("ʏᴏᴜ ᴀʀᴇ ɴᴏᴛ ᴀᴅᴍɪɴ sᴏ ʏᴏᴜ ᴄᴀɴ'ᴛ ᴜsᴇ ᴛʜɪs ᴄᴏᴍᴍᴀɴᴅ...") + return + + if not event.is_group: + await event.reply("ʏᴏᴜ ᴄᴀɴ ᴏɴʟʏ ᴅɪsᴀʙʟᴇ ɴɪɢʜᴛ ᴍᴏᴅᴇ ɪɴ ɢʀᴏᴜᴘs.") + return + if not is_nightmode_indb(str(event.chat_id)): + await event.reply("ᴛʜɪs ᴄʜᴀᴛ ʜᴀs ɴᴏᴛ ᴇɴᴀʙʟᴇᴅ ɴɪɢʜᴛ ᴍᴏᴅᴇ.") + return + rmnightmode(str(event.chat_id)) + await event.reply( + f"ʀᴇᴍᴏᴠᴇᴅ ᴄʜᴀᴛ {event.chat.title} ᴡɪᴛʜ ɪᴅ `{event.chat_id}` ғʀᴏᴍ ᴅᴀᴛᴀʙᴀsᴇ." + ) + + +async def job_close(): + ws_chats = get_all_chat_id() + if len(ws_chats) == 0: + return + for warner in ws_chats: + try: + await tbot.send_message( + int(warner.chat_id), + f"12:00 AM, ɢʀᴏᴜᴘ ɪs Closing ᴛɪʟʟ 6 ᴀᴍ. ɴɪɢʜᴛ ᴍᴏᴅᴇ sᴛᴀʀᴛᴇᴅ ! \n**ᴘᴏᴡᴇʀᴇᴅ ʙʏ {dispatcher.bot.username} **", + ) + await tbot( + functions.messages.EditChatDefaultBannedRightsRequest( + peer=int(warner.chat_id), banned_rights=hima + ) + ) + except Exception as e: + logger.info(f"ᴜɴᴀʙʟᴇ ᴛᴏ ᴄʟᴏsᴇ ɢʀᴏᴜᴘ {warner} - {e}") + + +# Run everyday at 12am +scheduler = AsyncIOScheduler(timezone="Asia/Kolkata") +scheduler.add_job(job_close, trigger="cron", hour=23, minute=59) +scheduler.start() + + +async def job_open(): + ws_chats = get_all_chat_id() + if len(ws_chats) == 0: + return + for warner in ws_chats: + try: + await tbot.send_message( + int(warner.chat_id), + f"06:00 ᴀᴍ, ɢʀᴏᴜᴘ ɪs ᴏᴘᴇɴɪɴɢ.\n**ᴘᴏᴡᴇʀᴇᴅ ʙʏ {dispatcher.bot.username}**", + ) + await tbot( + functions.messages.EditChatDefaultBannedRightsRequest( + peer=int(warner.chat_id), banned_rights=openhima + ) + ) + except Exception as e: + logger.info(f"ᴜɴᴀʙʟᴇ ᴛᴏ ᴏᴘᴇɴ ɢʀᴏᴜᴘ {warner.chat_id} - {e}") + + +# Run everyday at 06 +scheduler = AsyncIOScheduler(timezone="Asia/Kolkata") +scheduler.add_job(job_open, trigger="cron", hour=5, minute=59) +scheduler.start() + +__help__ = """ +*ᴀᴅᴍɪɴs ᴏɴʟʏ* + +⍟ /nightmode*:* `ᴀᴅᴅs ɢʀᴏᴜᴘ ᴛᴏ ɴɪɢʜᴛᴍᴏᴅᴇ ᴄʜᴀᴛs ` + +⍟ /rmnight*:* `ʀᴇᴍᴏᴠᴇs ɢʀᴏᴜᴘ ғʀᴏᴍ ɴɪɢʜᴛᴍᴏᴅᴇ ᴄʜᴀᴛs ` + +*ɴᴏᴛᴇ:* ɴɪɢʜᴛ ᴍᴏᴅᴇ ᴄʜᴀᴛs ɢᴇᴛ ᴀᴜᴛᴏᴍᴀᴛɪᴄᴀʟʟʏ ᴄʟᴏsᴇᴅ ᴀᴛ 12 ᴀᴍ (ɪsᴛ) ᴀɴᴅ ᴀᴜᴛᴏᴍᴀᴛɪᴄᴀʟʟʏ ᴏᴘᴇɴs ᴀᴛ 6 ᴀᴍ (ɪsᴛ) ᴛᴏ ᴘʀᴇᴠᴇɴᴛ ɴɪɢʜᴛ sᴘᴀᴍs. +""" + +__mod_name__ = "𝙽-ᴍᴏᴅᴇ" diff --git a/Exon/modules/notes.py b/Exon/modules/notes.py new file mode 100644 index 00000000..2de7eb9a --- /dev/null +++ b/Exon/modules/notes.py @@ -0,0 +1,627 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import ast +import random +import re +from io import BytesIO +from typing import Optional + +from telegram import ( + MAX_MESSAGE_LENGTH, + InlineKeyboardButton, + InlineKeyboardMarkup, + Message, + ParseMode, + Update, +) +from telegram.error import BadRequest +from telegram.ext import ( + CallbackContext, + CallbackQueryHandler, + CommandHandler, + Filters, + MessageHandler, +) +from telegram.utils.helpers import escape_markdown, mention_markdown + +import Exon.modules.sql.notes_sql as sql +from Exon import DRAGONS, JOIN_LOGGER, LOGGER, SUPPORT_CHAT, dispatcher +from Exon.modules.disable import DisableAbleCommandHandler +from Exon.modules.helper_funcs.chat_status import connection_status, user_admin +from Exon.modules.helper_funcs.handlers import MessageHandlerChecker +from Exon.modules.helper_funcs.misc import build_keyboard, revert_buttons +from Exon.modules.helper_funcs.msg_types import get_note_type +from Exon.modules.helper_funcs.string_handling import escape_invalid_curly_brackets + +FILE_MATCHER = re.compile(r"^###file_id(!photo)?###:(.*?)(?:\s|$)") +STICKER_MATCHER = re.compile(r"^###sticker(!photo)?###:") +BUTTON_MATCHER = re.compile(r"^###button(!photo)?###:(.*?)(?:\s|$)") +MYFILE_MATCHER = re.compile(r"^###file(!photo)?###:") +MYPHOTO_MATCHER = re.compile(r"^###photo(!photo)?###:") +MYAUDIO_MATCHER = re.compile(r"^###audio(!photo)?###:") +MYVOICE_MATCHER = re.compile(r"^###voice(!photo)?###:") +MYVIDEO_MATCHER = re.compile(r"^###video(!photo)?###:") +MYVIDEONOTE_MATCHER = re.compile(r"^###video_note(!photo)?###:") + +ENUM_FUNC_MAP = { + sql.Types.TEXT.value: dispatcher.bot.send_message, + sql.Types.BUTTON_TEXT.value: dispatcher.bot.send_message, + sql.Types.STICKER.value: dispatcher.bot.send_sticker, + sql.Types.DOCUMENT.value: dispatcher.bot.send_document, + sql.Types.PHOTO.value: dispatcher.bot.send_photo, + sql.Types.AUDIO.value: dispatcher.bot.send_audio, + sql.Types.VOICE.value: dispatcher.bot.send_voice, + sql.Types.VIDEO.value: dispatcher.bot.send_video, +} + + +# Do not async +def get(update, context, notename, show_none=True, no_format=False): + bot = context.bot + chat_id = update.effective_message.chat.id + note_chat_id = update.effective_chat.id + note = sql.get_note(note_chat_id, notename) + message = update.effective_message # type: Optional[Message] + + if note: + if MessageHandlerChecker.check_user(update.effective_user.id): + return + # If we're replying to a message, reply to that message (unless it's an error) + if message.reply_to_message: + reply_id = message.reply_to_message.message_id + else: + reply_id = message.message_id + if note.is_reply: + if JOIN_LOGGER: + try: + bot.forward_message( + chat_id=chat_id, + from_chat_id=JOIN_LOGGER, + message_id=note.value, + ) + except BadRequest as excp: + if excp.message != "ᴍᴇssᴀɢᴇ ᴛᴏ ғᴏʀᴡᴀʀᴅ ɴᴏᴛ ғᴏᴜɴᴅ": + raise + message.reply_text( + "ᴛʜɪs ᴍᴇssᴀɢᴇ sᴇᴇᴍs to ʜᴀᴠᴇ ʙᴇᴇɴ ʟᴏsᴛ - I'ʟʟ ʀᴇᴍᴏᴠᴇ ɪᴛ " + "ғʀᴏᴍ ʏᴏᴜʀ ɴᴏᴛᴇs ʟɪsᴛ.", + ) + sql.rm_note(note_chat_id, notename) + else: + try: + bot.forward_message( + chat_id=chat_id, + from_chat_id=chat_id, + message_id=note.value, + ) + except BadRequest as excp: + if excp.message != "ᴍᴇssᴀɢᴇ ᴛᴏ ғᴏʀᴡᴀʀᴅ ɴᴏᴛ ғᴏᴜɴᴅ": + raise + message.reply_text( + "ʟᴏᴏᴋs ʟɪᴋᴇ ᴛʜᴇ ᴏʀɪɢɪɴᴀʟ sᴇɴᴅᴇʀ ᴏғ ᴛʜɪs ɴᴏᴛᴇ ʜᴀs ᴅᴇʟᴇᴛᴇᴅ " + "ᴛʜᴇɪʀ ᴍᴇssᴀɢᴇ - sorry! ɢᴇᴛ ʏᴏᴜʀ ʙᴏᴛ ᴀᴅᴍɪɴ ᴛᴏ sᴛᴀʀᴛ ᴜsɪɴɢ ᴀ " + "ᴍᴇssᴀɢᴇ ᴅᴜᴍᴘ ᴛᴏ ᴀᴠᴏɪᴅ ᴛʜɪs. I'ʟʟ ʀᴇᴍᴏᴠᴇ ᴛʜɪs ɴᴏᴛᴇ from " + "your saved notes.", + ) + sql.rm_note(note_chat_id, notename) + else: + VALID_NOTE_FORMATTERS = [ + "first", + "last", + "fullname", + "username", + "id", + "chatname", + "mention", + ] + if valid_format := escape_invalid_curly_brackets( + note.value, + VALID_NOTE_FORMATTERS, + ): + if not no_format and "%%%" in valid_format: + split = valid_format.split("%%%") + text = random.choice(split) if all(split) else valid_format + else: + text = valid_format + text = text.format( + first=escape_markdown(message.from_user.first_name), + last=escape_markdown( + message.from_user.last_name or message.from_user.first_name, + ), + fullname=escape_markdown( + " ".join( + [ + message.from_user.first_name, + message.from_user.last_name, + ] + if message.from_user.last_name + else [message.from_user.first_name], + ), + ), + username=f"@{message.from_user.username}" + if message.from_user.username + else mention_markdown( + message.from_user.id, + message.from_user.first_name, + ), + mention=mention_markdown( + message.from_user.id, + message.from_user.first_name, + ), + chatname=escape_markdown( + message.chat.title + if message.chat.type != "private" + else message.from_user.first_name, + ), + id=message.from_user.id, + ) + + else: + text = "" + + keyb = [] + parseMode = ParseMode.MARKDOWN + buttons = sql.get_buttons(note_chat_id, notename) + if no_format: + parseMode = None + text += revert_buttons(buttons) + else: + keyb = build_keyboard(buttons) + + keyboard = InlineKeyboardMarkup(keyb) + + try: + if note.msgtype in (sql.Types.BUTTON_TEXT, sql.Types.TEXT): + bot.send_message( + chat_id, + text, + reply_to_message_id=reply_id, + parse_mode=parseMode, + disable_web_page_preview=True, + reply_markup=keyboard, + ) + elif ENUM_FUNC_MAP[note.msgtype] == dispatcher.bot.send_sticker: + ENUM_FUNC_MAP[note.msgtype]( + chat_id, + note.file, + reply_to_message_id=reply_id, + reply_markup=keyboard, + ) + else: + ENUM_FUNC_MAP[note.msgtype]( + chat_id, + note.file, + caption=text, + reply_to_message_id=reply_id, + parse_mode=parseMode, + reply_markup=keyboard, + ) + + except BadRequest as excp: + if excp.message == "Entity_mention_user_invalid": + message.reply_text( + "ʟᴏᴏᴋs ʟɪᴋᴇ ʏᴏᴜ ᴛʀɪᴇᴅ ᴛᴏ ᴍᴇɴᴛɪᴏɴ sᴏᴍᴇᴏɴᴇ I'ᴠᴇ ɴᴇᴠᴇʀ sᴇᴇɴ ʙᴇғᴏʀᴇ. ɪғ ʏᴏᴜ ʀᴇᴀʟʟʏ " + "ᴡᴀɴᴛ ᴛᴏ ᴍᴇɴᴛɪᴏɴ ᴛʜᴇᴍ, ғᴏʀᴡᴀʀᴅ ᴏɴᴇ ᴏғ ᴛʜᴇɪʀ ᴍᴇssᴀɢᴇs ᴛᴏ ᴍᴇ, ᴀɴᴅ I'ʟʟ ʙᴇ ᴀʙʟᴇ " + "ᴛᴏ ᴛᴀɢ ᴛʜᴇᴍ!", + ) + elif FILE_MATCHER.match(note.value): + message.reply_text( + "ᴛʜɪs ɴᴏᴛᴇ ᴡᴀs ᴀɴ ɪɴᴄᴏʀʀᴇᴄᴛʟʏ i=mported ғɪʟᴇ ғʀᴏᴍ ᴀɴᴏᴛʜᴇʀ ʙᴏᴛ - I ᴄᴀɴ'ᴛ ᴜsᴇ " + "ɪᴛ. ɪғ ʏᴏᴜ ʀᴇᴀʟʟʏ ɴᴇᴇᴅ it, ʏᴏᴜ'ʟʟ ʜᴀᴠᴇ ᴛᴏ sᴀᴠᴇ ɪᴛ ᴀɢᴀɪɴ. ɪɴ " + "ᴛʜᴇ ᴍᴇᴀɴᴛɪᴍᴇ, I'ʟʟ ʀᴇᴍᴏᴠᴇ ɪᴛ ғʀᴏᴍ ʏᴏᴜʀ ɴᴏᴛᴇs ʟɪsᴛ.", + ) + sql.rm_note(note_chat_id, notename) + else: + message.reply_text( + "This= ɴᴏᴛᴇ ᴄᴏᴜʟᴅ ɴᴏᴛ ʙᴇ sᴇɴᴛ, ᴀs ɪᴛ ɪs ɪɴᴄᴏʀʀᴇᴄᴛʟʏ ғᴏʀᴍᴀᴛᴛᴇᴅ. ᴀsᴋ ɪɴ " + f"@{SUPPORT_CHAT} ɪғ ʏᴏᴜ ᴄᴀɴ'ᴛ ғɪɢᴜʀᴇ ᴏᴜᴛ ᴡʜʏ!", + ) + LOGGER.exception( + "ᴄᴏᴜʟᴅ ɴᴏᴛ ᴘᴀʀsᴇ ᴍᴇssᴀɢᴇ #%s ɪɴ ᴄʜᴀᴛ %s", + notename, + str(note_chat_id), + ) + LOGGER.warning("ᴍᴇssᴀɢᴇ ᴡᴀs: %s", str(note.value)) + return + if show_none: + message.reply_text("ᴛʜɪs ɴᴏᴛᴇ ᴅᴏᴇsɴ'ᴛ ᴇxɪsᴛ") + + +@connection_status +def cmd_get(update: Update, context: CallbackContext): + bot, args = context.bot, context.args + if len(args) >= 2 and args[1].lower() == "noformat": + get(update, context, args[0].lower(), show_none=True, no_format=True) + elif len(args) >= 1: + get(update, context, args[0].lower(), show_none=True) + else: + update.effective_message.reply_text("Get rekt") + + +@connection_status +def hash_get(update: Update, context: CallbackContext): + message = update.effective_message.text + fst_word = message.split()[0] + no_hash = fst_word[1:].lower() + get(update, context, no_hash, show_none=False) + + +@connection_status +def slash_get(update: Update, context: CallbackContext): + message, chat_id = update.effective_message.text, update.effective_chat.id + no_slash = message[1:] + note_list = sql.get_all_chat_notes(chat_id) + + try: + noteid = note_list[int(no_slash) - 1] + note_name = str(noteid).strip(">").split()[1] + get(update, context, note_name, show_none=False) + except IndexError: + update.effective_message.reply_text("ᴡʀᴏɴɢ ɴᴏᴛᴇ ID 😾") + + +@user_admin +@connection_status +def save(update: Update, context: CallbackContext): + chat_id = update.effective_chat.id + msg = update.effective_message # type: Optional[Message] + + note_name, text, data_type, content, buttons = get_note_type(msg) + note_name = note_name.lower() + if data_type is None: + msg.reply_text("ᴅᴜᴅᴇ, ᴛʜᴇʀᴇ's ɴᴏ ɴᴏᴛᴇ") + return + + sql.add_note_to_db( + chat_id, + note_name, + text, + data_type, + buttons=buttons, + file=content, + ) + + msg.reply_text( + f"ʏᴀs! ᴀᴅᴅᴇᴅ `{note_name}`.\nɢᴇᴛ ɪᴛ ᴡɪᴛʜ /get `{note_name}`, ᴏʀ `#{note_name}`", + parse_mode=ParseMode.MARKDOWN, + ) + + if msg.reply_to_message and msg.reply_to_message.from_user.is_bot: + if text: + msg.reply_text( + "sᴇᴇᴍs ʟɪᴋᴇ ʏᴏᴜ'ʀᴇ ᴛʀʏɪɴɢ to sᴀᴠᴇ ᴀ ᴍᴇssᴀɢᴇ ғʀᴏᴍ ᴀ ʙᴏᴛ. ᴜɴғᴏʀᴛᴜɴᴀᴛᴇʟʏ, " + "ʙᴏᴛ's ᴄᴀɴ'ᴛ ғᴏʀᴡᴀʀᴅ ʙᴏᴛ ᴍᴇssᴀɢᴇs, sᴏ I ᴄᴀɴ'ᴛ sᴀᴠᴇ ᴛʜᴇ ᴇxᴀᴄᴛ ᴍᴇssᴀɢᴇ. " + "\nI'll sᴀᴠᴇ ᴀʟʟ ᴛʜᴇ ᴛᴇxᴛ I ᴄᴀɴ, ʙᴜᴛ ɪғ ʏᴏᴜ ᴡᴀɴᴛ ᴍᴏʀᴇ, ʏᴏᴜ'ʟʟ ʜᴀᴠᴇ ᴛᴏ " + "ғᴏʀᴡᴀʀᴅ ᴛʜᴇ ᴍᴇssᴀɢᴇ ʏᴏᴜʀsᴇʟғ, ᴀɴᴅ ᴛʜᴇɴ sᴀᴠᴇ ɪᴛ.", + ) + else: + msg.reply_text( + "ʙᴏᴛs ᴀʀᴇ ᴋɪɴᴅᴀ ʜᴀɴᴅɪᴄᴀᴘᴘᴇᴅ ʙʏ ᴛᴇʟᴇɢʀᴀᴍ, ᴍᴀᴋɪɴɢ ɪᴛ ʜᴀʀᴅ ғᴏʀ ʙᴏᴛs ᴛᴏ " + "ɪɴᴛᴇʀᴀᴄᴛ ᴡɪᴛʜ ᴏᴛʜᴇʀ ʙᴏᴛs, sᴏ ɪ ᴄᴀɴ'ᴛ sᴀᴠᴇ ᴛʜɪs ᴍᴇssᴀɢᴇ " + "ʟɪᴋᴇ I ᴜsᴜᴀʟʟʏ ᴡᴏᴜʟᴅ - ᴅᴏ ʏᴏᴜ ᴍɪɴᴅ ғᴏʀᴡᴀʀᴅɪɴɢ ɪᴛ ᴀɴᴅ " + "ᴛʜᴇɴ sᴀᴠɪɴɢ ᴛʜᴀᴛ ɴᴇᴡ ᴍᴇssᴀɢᴇ? ᴛʜᴀɴᴋs!", + ) + return + + +@user_admin +@connection_status +def clear(update: Update, context: CallbackContext): + args = context.args + if len(args) >= 1: + chat_id = update.effective_chat.id + notename = args[0].lower() + + if sql.rm_note(chat_id, notename): + update.effective_message.reply_text("sᴜᴄᴄᴇssғᴜʟʟʏ ʀᴇᴍᴏᴠᴇᴅ ɴᴏᴛᴇ.") + else: + update.effective_message.reply_text("ᴛʜᴀᴛ's ɴᴏᴛ ᴀ ɴᴏᴛᴇ ɪɴ ᴍʏ ᴅᴀᴛᴀʙᴀsᴇ!") + + +def clearall(update: Update, context: CallbackContext): + chat = update.effective_chat + user = update.effective_user + member = chat.get_member(user.id) + if member.status != "creator" and user.id not in DRAGONS: + update.effective_message.reply_text( + "ᴏɴʟʏ ᴛʜᴇ ᴄʜᴀᴛ ᴏᴡɴᴇʀ ᴄᴀɴ ᴄʟᴇᴀʀ ᴀʟʟ ɴᴏᴛᴇs ᴀᴛ ᴏɴᴄᴇ.", + ) + else: + buttons = InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + text="ᴅᴇʟᴇᴛᴇ ᴀʟʟ ɴᴏᴛᴇs", + callback_data="notes_rmall", + ), + ], + [InlineKeyboardButton(text="ᴄᴀɴᴄᴇʟ", callback_data="notes_cancel")], + ], + ) + update.effective_message.reply_text( + f"ᴀʀᴇ ʏᴏᴜ sᴜʀᴇ ʏᴏᴜ ᴡᴏᴜʟᴅ ʟɪᴋᴇ ᴛᴏ ᴄʟᴇᴀʀ ALL ɴᴏᴛᴇs ɪɴ {chat.title}? ᴛʜɪs ᴀᴄᴛɪᴏɴ ᴄᴀɴɴᴏᴛ ʙᴇ ᴜɴᴅᴏɴᴇ.", + reply_markup=buttons, + parse_mode=ParseMode.MARKDOWN, + ) + + +def clearall_btn(update: Update, context: CallbackContext): + query = update.callback_query + chat = update.effective_chat + message = update.effective_message + member = chat.get_member(query.from_user.id) + if query.data == "notes_rmall": + if member.status == "creator" or query.from_user.id in DRAGONS: + note_list = sql.get_all_chat_notes(chat.id) + try: + for notename in note_list: + note = notename.name.lower() + sql.rm_note(chat.id, note) + message.edit_text("ᴅᴇʟᴇᴛᴇᴅ ᴀʟʟ ɴᴏᴛᴇs.") + except BadRequest: + return + + if member.status == "administrator": + query.answer("ᴏɴʟʏ ᴏᴡɴᴇʀ ᴏғ ᴛʜᴇ ᴄʜᴀᴛ ᴄᴀɴ ᴅᴏ ᴛʜɪs.") + + if member.status == "member": + query.answer("ʏᴏᴜ ɴᴇᴇᴅ ᴛᴏ ʙᴇ ᴀᴅᴍɪɴ ᴛᴏ ᴅᴏ ᴛʜɪs.") + elif query.data == "notes_cancel": + if member.status == "creator" or query.from_user.id in DRAGONS: + message.edit_text("ᴄʟᴇᴀʀɪɴɢ ᴏғ ᴀʟʟ ɴᴏᴛᴇs ʜᴀs ʙᴇᴇɴ ᴄᴀɴᴄᴇʟʟᴇᴅ.") + return + if member.status == "administrator": + query.answer("ᴏɴʟʏ ᴏᴡɴᴇʀ ᴏғ ᴛʜᴇ ᴄʜᴀᴛ ᴄᴀɴ ᴅᴏ ᴛʜɪs.") + if member.status == "member": + query.answer("ʏᴏᴜ ɴᴇᴇᴅ ᴛᴏ ʙᴇ ᴀᴅᴍɪɴ ᴛᴏ ᴅᴏ ᴛʜɪs.") + + +@connection_status +def list_notes(update: Update, context: CallbackContext): + chat_id = update.effective_chat.id + note_list = sql.get_all_chat_notes(chat_id) + notes = len(note_list) + 1 + msg = "ɢᴇᴛ ɴᴏᴛᴇ ʙʏ `/notenumber` ᴏʀ `#notename` \n\n *ɪᴅ* *ɴᴏᴛᴇ* \n" + for note_id, note in zip(range(1, notes), note_list): + if note_id < 10: + note_name = f"`{note_id:2}.` `#{(note.name.lower())}`\n" + else: + note_name = f"`{note_id}.` `#{(note.name.lower())}`\n" + if len(msg) + len(note_name) > MAX_MESSAGE_LENGTH: + update.effective_message.reply_text(msg, parse_mode=ParseMode.MARKDOWN) + msg = "" + msg += note_name + + if not note_list: + try: + update.effective_message.reply_text("ɴᴏ ɴᴏᴛᴇs ɪɴ ᴛʜɪs ᴄʜᴀᴛ!") + except BadRequest: + update.effective_message.reply_text("ɴᴏ ɴᴏᴛᴇs ɪɴ ᴛʜɪs ᴄʜᴀᴛ!", quote=False) + + elif len(msg) != 0: + update.effective_message.reply_text(msg, parse_mode=ParseMode.MARKDOWN) + + +def __import_data__(chat_id, data): + failures = [] + for notename, notedata in data.get("extra", {}).items(): + match = FILE_MATCHER.match(notedata) + matchsticker = STICKER_MATCHER.match(notedata) + matchbtn = BUTTON_MATCHER.match(notedata) + matchfile = MYFILE_MATCHER.match(notedata) + matchphoto = MYPHOTO_MATCHER.match(notedata) + matchaudio = MYAUDIO_MATCHER.match(notedata) + matchvoice = MYVOICE_MATCHER.match(notedata) + matchvideo = MYVIDEO_MATCHER.match(notedata) + matchvn = MYVIDEONOTE_MATCHER.match(notedata) + + if match: + failures.append(notename) + if notedata := notedata[match.end() :].strip(): + sql.add_note_to_db(chat_id, notename[1:], notedata, sql.Types.TEXT) + elif matchsticker: + if content := notedata[matchsticker.end() :].strip(): + sql.add_note_to_db( + chat_id, + notename[1:], + notedata, + sql.Types.STICKER, + file=content, + ) + elif matchbtn: + parse = notedata[matchbtn.end() :].strip() + notedata = parse.split("<###button###>")[0] + buttons = parse.split("<###button###>")[1] + if buttons := ast.literal_eval(buttons): + sql.add_note_to_db( + chat_id, + notename[1:], + notedata, + sql.Types.BUTTON_TEXT, + buttons=buttons, + ) + elif matchfile: + file = notedata[matchfile.end() :].strip() + file = file.split("<###TYPESPLIT###>") + notedata = file[1] + if content := file[0]: + sql.add_note_to_db( + chat_id, + notename[1:], + notedata, + sql.Types.DOCUMENT, + file=content, + ) + elif matchphoto: + photo = notedata[matchphoto.end() :].strip() + photo = photo.split("<###TYPESPLIT###>") + notedata = photo[1] + if content := photo[0]: + sql.add_note_to_db( + chat_id, + notename[1:], + notedata, + sql.Types.PHOTO, + file=content, + ) + elif matchaudio: + audio = notedata[matchaudio.end() :].strip() + audio = audio.split("<###TYPESPLIT###>") + notedata = audio[1] + if content := audio[0]: + sql.add_note_to_db( + chat_id, + notename[1:], + notedata, + sql.Types.AUDIO, + file=content, + ) + elif matchvoice: + voice = notedata[matchvoice.end() :].strip() + voice = voice.split("<###TYPESPLIT###>") + notedata = voice[1] + if content := voice[0]: + sql.add_note_to_db( + chat_id, + notename[1:], + notedata, + sql.Types.VOICE, + file=content, + ) + elif matchvideo: + video = notedata[matchvideo.end() :].strip() + video = video.split("<###TYPESPLIT###>") + notedata = video[1] + if content := video[0]: + sql.add_note_to_db( + chat_id, + notename[1:], + notedata, + sql.Types.VIDEO, + file=content, + ) + elif matchvn: + video_note = notedata[matchvn.end() :].strip() + video_note = video_note.split("<###TYPESPLIT###>") + notedata = video_note[1] + if content := video_note[0]: + sql.add_note_to_db( + chat_id, + notename[1:], + notedata, + sql.Types.VIDEO_NOTE, + file=content, + ) + else: + sql.add_note_to_db(chat_id, notename[1:], notedata, sql.Types.TEXT) + + if failures: + with BytesIO(str.encode("\n".join(failures))) as output: + output.name = "failed_imports.txt" + dispatcher.bot.send_document( + chat_id, + document=output, + filename="failed_imports.txt", + caption="ᴛʜᴇsᴇ ғɪʟᴇs/ᴘʜᴏᴛᴏs ғᴀɪʟᴇᴅ ᴛᴏ ɪᴍᴘᴏʀᴛ ᴅᴜᴇ ᴛᴏ ᴏʀɪɢɪɴᴀᴛɪɴɢ " + "ғʀᴏᴍ ᴀɴᴏᴛʜᴇʀ ʙᴏᴛ. ᴛʜɪs ɪs ᴀ ᴛᴇʟᴇɢʀᴀᴍ ᴀᴘɪ ʀᴇsᴛʀɪᴄᴛɪᴏɴ, ᴀɴᴅ ᴄᴀɴ'ᴛ " + "ʙᴇ ᴀᴠᴏɪᴅᴇᴅ. sᴏʀʀʏ ғᴏʀ ᴛʜᴇ ɪɴᴄᴏɴᴠᴇɴɪᴇɴᴄᴇ!", + ) + + +def __stats__(): + return f"•➥ {sql.num_notes()} ɴᴏᴛᴇs, ᴀᴄʀᴏss {sql.num_chats()} ᴄʜᴀᴛs." + + +def __migrate__(old_chat_id, new_chat_id): + sql.migrate_chat(old_chat_id, new_chat_id) + + +def __chat_settings__(chat_id, user_id): + notes = sql.get_all_chat_notes(chat_id) + return f"ᴛʜᴇʀᴇ ᴀʀᴇ `{len(notes)}` ɴᴏᴛᴇs ɪɴ ᴛʜɪs ᴄʜᴀᴛ." + + +__help__ = """ +•➥ /get <notename>*:* `ɢᴇᴛ ᴛʜᴇ ɴᴏᴛᴇ ᴡɪᴛʜ this ɴᴏᴛᴇɴᴀᴍᴇ` + +•➥ `#<notename>*:* same as /get` + +•➥ /notes or /saved*:* `ʟɪsᴛ ᴀʟʟ sᴀᴠᴇᴅ ɴᴏᴛᴇs ɪɴ ᴛʜɪs ᴄʜᴀᴛ` + +•➥ /number *:* `ᴡɪʟʟ ᴘᴜʟʟ ᴛʜᴇ ɴᴏᴛᴇ ᴏғ ᴛʜᴀᴛ ɴᴜᴍʙᴇʀ ɪɴ ᴛʜᴇ ʟɪsᴛ` + +`ɪғ ʏᴏᴜ ᴡᴏᴜʟᴅ ʟɪᴋᴇ ᴛᴏ ʀᴇᴛʀɪᴇᴠᴇ ᴛʜᴇ ᴄᴏɴᴛᴇɴᴛs ᴏғ ᴀ ɴᴏᴛᴇ ᴡɪᴛʜᴏᴜᴛ ᴀɴʏ ғᴏʀᴍᴀᴛᴛɪɴɢ, ᴜsᴇ` `/get <notename> ɴᴏғᴏʀᴍᴀᴛ`. `ᴛʜɪs ᴄᴀɴ` \ +`ʙᴇ ᴜsᴇғᴜʟ ᴡʜᴇɴ ᴜᴘᴅᴀᴛɪɴɢ ᴀ ᴄᴜʀʀᴇɴᴛ ɴᴏᴛᴇ` + +*ᴀᴅᴍɪɴꜱ ᴏɴʟʏ:* +•➥ /save <notename> <notedata>*:* `ꜱᴀᴠᴇꜱ ɴᴏᴛᴇᴅᴀᴛᴀ ᴀꜱ ᴀ ɴᴏᴛᴇ ᴡɪᴛʜ ɴᴀᴍᴇ ɴᴏᴛᴇɴᴀᴍᴇ` + +`A ʙᴜᴛᴛᴏɴ ᴄᴀɴ ʙᴇ ᴀᴅᴅᴇᴅ ᴛᴏ ᴀ ɴᴏᴛᴇ ʙʏ ᴜꜱɪɴɢ ꜱᴛᴀɴᴅᴀʀᴅ ᴍᴀʀᴋᴅᴏᴡɴ ʟɪɴᴋ ꜱʏɴᴛᴀx - ᴛʜᴇ ʟɪɴᴋ ꜱʜᴏᴜʟᴅ ᴊᴜꜱᴛ ʙᴇ ᴘʀᴇᴘᴇɴᴅᴇᴅ ᴡɪᴛʜ ᴀ` + \ +`buttonurl:` ꜱᴇᴄᴛɪᴏɴ, ᴀꜱ ꜱᴜᴄʜ: `[somelink](buttonurl:example.com)`. ᴄʜᴇᴄᴋ `/ᴍᴀʀᴋᴅᴏᴡɴʜᴇʟᴘ` ғᴏʀ ᴍᴏʀᴇ ɪɴғᴏ + +•➥ /save <notename>*:* `ꜱᴀᴠᴇ ᴛʜᴇ ʀᴇᴘʟɪᴇᴅ ᴍᴇꜱꜱᴀɢᴇ ᴀꜱ ᴀ ɴᴏᴛᴇ ᴡɪᴛʜ ɴᴀᴍᴇ ɴᴏᴛᴇɴᴀᴍᴇ` + + `ꜱᴇᴘᴀʀᴀᴛᴇ ᴅɪғғ ʀᴇᴘʟɪᴇꜱ ʙʏ` `%%%` `ᴛᴏ ɢᴇᴛ ʀᴀɴᴅᴏᴍ ɴᴏᴛᴇꜱ` + + *ᴇxᴀᴍᴘʟᴇ:* + `/save notename + Reply 1 + %%% + Reply 2 + %%% + Reply 3` + +•➥ /clear <notename>*:* `ᴄʟᴇᴀʀ ɴᴏᴛᴇ ᴡɪᴛʜ ᴛʜɪꜱ ɴᴀᴍᴇ` + +•➥ /removeallnotes*:* `ʀᴇᴍᴏᴠᴇꜱ ᴀʟʟ ɴᴏᴛᴇꜱ ғʀᴏᴍ ᴛʜᴇ ɢʀᴏᴜᴘ` + + *ɴᴏᴛᴇ:* `ɴᴏᴛᴇ ɴᴀᴍᴇꜱ ᴀʀᴇ ᴄᴀꜱᴇ--ꜱᴇɴꜱɪᴛɪᴠᴇ, ᴀɴᴅ ᴛʜᴇʏ ᴀʀᴇ ᴀᴜᴛᴏᴍᴀᴛɪᴄᴀʟʟʏ ᴄᴏɴᴠᴇʀᴛᴇᴅ ᴛᴏ ʟᴏᴡᴇʀᴄᴀꜱᴇ ʙᴇғᴏʀᴇ ɢᴇᴛᴛɪɴɢ ꜱᴀᴠᴇᴅ.` + +""" + +__mod_name__ = "𝙽ᴏᴛᴇs" + +GET_HANDLER = CommandHandler("get", cmd_get, run_async=True) +HASH_GET_HANDLER = MessageHandler(Filters.regex(r"^#[^\s]+"), hash_get, run_async=True) +SLASH_GET_HANDLER = MessageHandler(Filters.regex(r"^/\d+$"), slash_get, run_async=True) +SAVE_HANDLER = CommandHandler("save", save, run_async=True) +DELETE_HANDLER = CommandHandler("clear", clear, run_async=True) + +LIST_HANDLER = DisableAbleCommandHandler( + ["notes", "saved"], list_notes, admin_ok=True, run_async=True +) + +CLEARALL = DisableAbleCommandHandler("removeallnotes", clearall, run_async=True) +CLEARALL_BTN = CallbackQueryHandler(clearall_btn, pattern=r"notes_.*", run_async=True) + +dispatcher.add_handler(GET_HANDLER) +dispatcher.add_handler(SAVE_HANDLER) +dispatcher.add_handler(LIST_HANDLER) +dispatcher.add_handler(DELETE_HANDLER) +dispatcher.add_handler(HASH_GET_HANDLER) +dispatcher.add_handler(SLASH_GET_HANDLER) +dispatcher.add_handler(CLEARALL) +dispatcher.add_handler(CLEARALL_BTN) diff --git a/Exon/modules/paste.py b/Exon/modules/paste.py new file mode 100644 index 00000000..2fe21396 --- /dev/null +++ b/Exon/modules/paste.py @@ -0,0 +1,88 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import asyncio +import os +import re + +import aiofiles +from pykeyboard import InlineKeyboard +from pyrogram import filters +from pyrogram.types import InlineKeyboardButton + +from Exon import aiohttpsession, pgram +from Exon.utils.errors import capture_err +from Exon.utils.pastebin import paste + +pattern = re.compile(r"^text/|json$|yaml$|xml$|toml$|x-sh$|x-shellscript$") + + +async def isPreviewUp(preview: str) -> bool: + for _ in range(7): + try: + async with aiohttpsession.head(preview, timeout=2) as resp: + status = resp.status + size = resp.content_length + except asyncio.exceptions.TimeoutError: + return False + if status == 404 or (status == 200 and size == 0): + await asyncio.sleep(0.4) + else: + return status == 200 + return False + + +@pgram.on_message(filters.command("paste") & ~filters.edited) +@capture_err +async def paste_func(_, message): + if not message.reply_to_message: + return await message.reply_text("ʀᴇᴘʟʏ ᴛᴏ ᴀ ᴍᴇssᴀɢᴇ ᴡɪᴛʜ `/paste`") + m = await message.reply_text("ᴘᴀsᴛɪɴɢ...") + if message.reply_to_message.text: + content = str(message.reply_to_message.text) + elif message.reply_to_message.document: + document = message.reply_to_message.document + if document.file_size > 1048576: + return await m.edit("ʏᴏᴜ ᴄᴀɴ ᴏɴʟʏ ᴘᴀsᴛᴇ ғɪʟᴇs sᴍᴀʟʟᴇʀ ᴛʜᴀɴ 1ᴍʙ.") + if not pattern.search(document.mime_type): + return await m.edit("ᴏɴʟʏ ᴛᴇxᴛ ғɪʟᴇs ᴄᴀɴ ʙᴇ ᴘᴀsᴛᴇᴅ.") + doc = await message.reply_to_message.download() + async with aiofiles.open(doc, mode="r") as f: + content = await f.read() + os.remove(doc) + link = await paste(content) + preview = f"{link}/preview.png" + button = InlineKeyboard(row_width=1) + button.add(InlineKeyboardButton(text="ᴘᴀsᴛᴇ ʟɪɴᴋ", url=link)) + + if await isPreviewUp(preview): + try: + await message.reply_photo(photo=preview, quote=False, reply_markup=button) + return await m.delete() + except Exception: + pass + return await m.edit(link) + + +__mod_name__ = "ᴘᴀsᴛᴇ" diff --git a/Exon/modules/ping.py b/Exon/modules/ping.py new file mode 100644 index 00000000..ab31f1de --- /dev/null +++ b/Exon/modules/ping.py @@ -0,0 +1,133 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import time +from typing import List + +import requests +from telegram import ParseMode, Update +from telegram.ext import CallbackContext + +from Exon import StartTime, dispatcher +from Exon.modules.disable import DisableAbleCommandHandler +from Exon.modules.helper_funcs.chat_status import sudo_plus + +sites_list = { + "ᴛᴇʟᴇɢʀᴀᴍ": "https://api.telegram.org", + "ᴋᴀɪᴢᴏᴋᴜ": "https://animekaizoku.com", + "ᴊɪᴋᴀɴ": "https://api.jikan.moe/v3", + "ᴋᴜᴋɪ ᴄʜᴀᴛʙᴏᴛ": "https://kuki-yukicloud.up.railway.app/", + "ʟɪᴏɴᴇs ᴀᴘɪ": "https://liones-api.herokuapp.com/", +} + + +def get_readable_time(seconds: int) -> str: + count = 0 + ping_time = "" + time_list = [] + time_suffix_list = ["s", "ᴍ", "ʜ", "ᴅᴀʏs"] + + while count < 4: + count += 1 + remainder, result = divmod(seconds, 60) if count < 3 else divmod(seconds, 24) + if seconds == 0 and remainder == 0: + break + time_list.append(int(result)) + seconds = int(remainder) + + for x in range(len(time_list)): + time_list[x] = str(time_list[x]) + time_suffix_list[x] + if len(time_list) == 4: + ping_time += f"{time_list.pop()}, " + + time_list.reverse() + ping_time += ":".join(time_list) + + return ping_time + + +def ping_func(to_ping: List[str]) -> List[str]: + ping_result = [] + + for each_ping in to_ping: + + start_time = time.time() + site_to_ping = sites_list[each_ping] + r = requests.get(site_to_ping) + end_time = time.time() + ping_time = f"{str(round((end_time - start_time), 2))}s" + + pinged_site = f"<b>{each_ping}</b>" + + if each_ping in ("Kaizoku"): + pinged_site = f'<a href="{sites_list[each_ping]}">{each_ping}</a>' + ping_time = f"<code>{ping_time} (Status: {r.status_code})</code>" + + ping_text = f"{pinged_site}: <code>{ping_time}</code>" + ping_result.append(ping_text) + + return ping_result + + +@sudo_plus +def ping(update: Update, context: CallbackContext): + msg = update.effective_message + + start_time = time.time() + message = msg.reply_text("ᴘɪɴɢɪɴɢ.....") + end_time = time.time() + telegram_ping = f"{str(round((end_time - start_time) * 1000, 3))} ms" + uptime = get_readable_time((time.time() - StartTime)) + + message.edit_text( + f"𝗣𝗢𝗡𝗚 🎉!!\n<b>ᴛɪᴍᴇ ᴛᴀᴋᴇɴ:</b> <code>{telegram_ping}</code>\n<b>sᴇʀᴠɪᴄᴇ ᴜᴘᴛɪᴍᴇ:</b> <code>{uptime}</code>", + parse_mode=ParseMode.HTML, + ) + + +@sudo_plus +def pingall(update: Update, context: CallbackContext): + to_ping = ["ᴋᴀɪᴢᴏᴋᴜ", "ᴛᴇʟᴇɢʀᴀᴍ", "ᴊɪᴋᴀɴ", "ᴋᴜᴋɪ ᴄʜᴀᴛʙᴏᴛ", "ʟɪᴏɴᴇs ᴀᴘɪ"] + pinged_list = ping_func(to_ping) + pinged_list.insert(2, "") + uptime = get_readable_time((time.time() - StartTime)) + + reply_msg = "⏱ᴘɪɴɢ ʀᴇsᴜʟᴛs ᴀʀᴇ:\n" + "\n".join(pinged_list) + reply_msg += f"\n<b>sᴇʀᴠɪᴄᴇ ᴜᴘᴛɪᴍᴇ:</b> <code>{uptime}</code>" + + update.effective_message.reply_text( + reply_msg, + parse_mode=ParseMode.HTML, + disable_web_page_preview=True, + ) + + +PING_HANDLER = DisableAbleCommandHandler("ping", ping, run_async=True) +PINGALL_HANDLER = DisableAbleCommandHandler("pingall", pingall, run_async=True) + +dispatcher.add_handler(PING_HANDLER) +dispatcher.add_handler(PINGALL_HANDLER) + +__command_list__ = ["ping", "pingall"] +__handlers__ = [PING_HANDLER, PINGALL_HANDLER] diff --git a/Exon/modules/pokedex.py b/Exon/modules/pokedex.py new file mode 100644 index 00000000..2c501dd6 --- /dev/null +++ b/Exon/modules/pokedex.py @@ -0,0 +1,42 @@ +from pyrogram import filters + +from Exon import pgram + + +@pgram.on_message(filters.command("pokedex")) +async def PokeDex(_, message): + if len(message.command) != 2: + await message.reply_text("/pokedex ᴘᴏᴋᴇᴍᴏɴ ɴᴀᴍᴇ") + return + pokemon = message.text.split(None, 1)[1] + pokedex = f"https://some-random-api.ml/pokedex?pokemon={pokemon}" + async with aiohttp.ClientSession() as session: + async with session.get(pokedex) as request: + if request.status == 404: + return await message.reply_text("ᴡʀᴏɴɢ ᴘᴏᴋᴇᴍᴏɴ ɴᴀᴍᴇ") + + result = await request.json() + try: + pokemon = result["name"] + pokedex = result["id"] + type = result["type"] + poke_img = f"https://img.pokemondb.net/artwork/large/{pokemon}.jpg" + abilities = result["abilities"] + height = result["height"] + weight = result["weight"] + gender = result["gender"] + stats = result["stats"] + description = result["description"] + caption = f""" +**ᴘᴏᴋᴇᴍᴏɴ:** `{pokemon}` +**ᴘᴏᴋᴇᴅᴇx:** `{pokedex}` +**ᴛʏᴘᴇ:** `{type}` +**ᴀʙɪʟɪᴛɪᴇs:** `{abilities}` +**ʜᴇɪɢʜᴛ:** `{height}` +**ᴡᴇɪɢʜᴛ:** `{weight}` +**ɢᴇɴᴅᴇʀ:** `{gender}` +**sᴛᴀᴛs:** `{stats}` +**ᴅᴇsᴄʀɪᴘᴛɪᴏɴ:** `{description}`""" + except Exception as e: + print(e) + await message.reply_photo(photo=poke_img, caption=caption) diff --git a/Exon/modules/purge.py b/Exon/modules/purge.py new file mode 100644 index 00000000..de260036 --- /dev/null +++ b/Exon/modules/purge.py @@ -0,0 +1,107 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import time + +from telethon import events + +from Exon import telethn +from Exon.modules.helper_funcs.telethn.chatstatus import ( + can_delete_messages, + user_is_admin, +) + + +async def purge_messages(event): + start = time.perf_counter() + if event.from_id is None: + return + + if not await user_is_admin( + user_id=event.sender_id, + message=event, + ) and event.from_id not in [1087968824]: + await event.reply("ᴏɴʟʏ ᴀᴅᴍɪɴs ᴀʀᴇ ᴀʟʟᴏᴡᴇᴅ ᴛᴏ ᴜsᴇ ᴛʜɪs ᴄᴏᴍᴍᴀɴᴅ") + return + + if not await can_delete_messages(message=event): + await event.reply("ᴄᴀɴ'ᴛ sᴇᴇᴍ ᴛᴏ ᴘᴜʀɢᴇ ᴛʜᴇ ᴍᴇssᴀɢᴇ") + return + + reply_msg = await event.get_reply_message() + if not reply_msg: + await event.reply("ʀᴇᴘʟʏ ᴛᴏ ᴀ ᴍᴇssᴀɢᴇ ᴛᴏ sᴇʟᴇᴄᴛ ᴡʜᴇʀᴇ ᴛᴏ sᴛᴀʀᴛ ᴘᴜʀɢɪɴɢ ғʀᴏᴍ.") + return + message_id = reply_msg.id + delete_to = event.message.id + + messages = [event.reply_to_msg_id] + for msg_id in range(message_id, delete_to + 1): + messages.append(msg_id) + if len(messages) == 100: + await event.client.delete_messages(event.chat_id, messages) + messages = [] + + try: + await event.client.delete_messages(event.chat_id, messages) + except: + pass + time.perf_counter() - start + text = "shhh!" + await event.respond(text, parse_mode="markdown") + + +async def delete_messages(event): + if event.from_id is None: + return + + if not await user_is_admin( + user_id=event.sender_id, + message=event, + ) and event.from_id not in [1087968824]: + await event.reply("ᴏɴʟʏ ᴀᴅᴍɪɴs ᴀʀᴇ ᴀʟʟᴏᴡᴇᴅ ᴛᴏ ᴜsᴇ ᴛʜɪs ᴄᴏᴍᴍᴀɴᴅ") + return + + if not await can_delete_messages(message=event): + await event.reply("ᴄᴀɴ'ᴛ sᴇᴇᴍ ᴛᴏ ᴅᴇʟᴇᴛᴇ ᴛʜɪs?") + return + + message = await event.get_reply_message() + if not message: + await event.reply("ᴡʜᴀᴛ, ᴡᴀɴᴛ ᴛᴏ ᴅᴇʟᴇᴛᴇ?") + return + chat = await event.get_input_chat() + del_message = [message, event.message] + await event.client.delete_messages(chat, del_message) + + +PURGE_HANDLER = purge_messages, events.NewMessage(pattern="^[!/]purge$") +DEL_HANDLER = delete_messages, events.NewMessage(pattern="^[!/]del$") + +telethn.add_event_handler(*PURGE_HANDLER) +telethn.add_event_handler(*DEL_HANDLER) + +__mod_name__ = "Purges" +__command_list__ = ["del", "purge"] +__handlers__ = [PURGE_HANDLER, DEL_HANDLER] diff --git a/Exon/modules/quote.py b/Exon/modules/quote.py new file mode 100644 index 00000000..fa9a57b1 --- /dev/null +++ b/Exon/modules/quote.py @@ -0,0 +1,137 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +from io import BytesIO +from traceback import format_exc + +from pyrogram import filters +from pyrogram.types import Message + +from Exon import arq, pgram +from Exon.utils.errors import capture_err + + +async def quotify(messages: list): + response = await arq.quotly(messages) + if not response.ok: + return [False, response.result] + sticker = response.result + sticker = BytesIO(sticker) + sticker.name = "sticker.webp" + return [True, sticker] + + +def getArg(message: Message) -> str: + return message.text.strip().split(None, 1)[1].strip() + + +def isArgInt(message: Message) -> list: + count = getArg(message) + try: + count = int(count) + return [True, count] + except ValueError: + return [False, 0] + + +@pgram.on_message(filters.command("q")) +@capture_err +async def quotly_func(client, message: Message): + if not message.reply_to_message: + return await message.reply_text("ʀᴇᴘʟʏ ᴛᴏ ᴀ ᴍᴇssᴀɢᴇ ᴛᴏ ǫᴜᴏᴛᴇ ɪᴛ.") + if not message.reply_to_message.text: + return await message.reply_text("ʀᴇᴘʟɪᴇᴅ ᴍᴇssᴀɢᴇ ʜᴀs ɴᴏ ᴛᴇxᴛ, ᴄᴀɴ'ᴛ ǫᴜᴏᴛᴇ ɪᴛ.") + m = await message.reply_text("ǫᴜᴏᴛɪɴɢ ᴍᴇssᴀɢᴇs") + if len(message.command) < 2: + messages = [message.reply_to_message] + + elif len(message.command) == 2: + arg = isArgInt(message) + if arg[0]: + if arg[1] < 2 or arg[1] > 10: + return await m.edit("ᴀʀɢᴜᴍᴇɴᴛ ᴍᴜsᴛ ʙᴇ ʙᴇᴛᴡᴇᴇɴ 2-10.") + + count = arg[1] + + # Fetching 5 extra messages so tha twe can ignore media + # messages and still end up with correct offset + messages = [ + i + for i in await client.get_messages( + message.chat.id, + range( + message.reply_to_message.id, + message.reply_to_message.id + (count + 5), + ), + replies=0, + ) + if not i.empty and not i.media + ] + messages = messages[:count] + else: + if getArg(message) != "r": + return await m.edit( + "ɪɴᴄᴏʀʀᴇᴄᴛ ᴀʀɢᴜᴍᴇɴᴛ, ᴘᴀss **'r'** or **'INT'**, **EX:** __/q 2__" + ) + reply_message = await client.get_messages( + message.chat.id, + message.reply_to_message.id, + replies=1, + ) + messages = [reply_message] + else: + return await m.edit("ɪɴᴄᴏʀʀᴇᴄᴛ ᴀʀɢᴜᴍᴇɴᴛ, ᴄʜᴇᴄᴋ ǫᴜᴏᴛʟʏ ᴍᴏᴅᴜʟᴇ ɪɴ ʜᴇʟᴘ sᴇᴄᴛɪᴏɴ.") + try: + if not message: + return await m.edit("sᴏᴍᴇᴛʜɪɴɢ ᴡᴇɴᴛ ᴡʀᴏɴɢ.") + + sticker = await quotify(messages) + if not sticker[0]: + await message.reply_text(sticker[1]) + return await m.delete() + sticker = sticker[1] + await message.reply_sticker(sticker) + await m.delete() + sticker.close() + except Exception as e: + await m.edit( + "sᴏᴍᴇᴛʜɪɴɢ ᴡᴇɴᴛ ᴡʀᴏɴɢ ᴡʜɪʟᴇ ǫᴜᴏᴛɪɴɢ ᴍᴇssᴀɢᴇs," + + " ᴛʜɪs ᴇʀʀᴏʀ ᴜsᴜᴀʟʟʏ ʜᴀᴘᴘᴇɴs ᴡʜᴇɴ ᴛʜᴇʀᴇ's ᴀ " + + " ᴍᴇssᴀɢᴇ ᴄᴏɴᴛᴀɪɴɪɴɢ sᴏᴍᴇᴛʜɪɴɢ other than text," + + " ᴏʀ ᴏɴᴇ ᴏғ ᴛʜᴇ ᴍᴇssᴀɢᴇs ɪɴ-ᴇᴛᴡᴇᴇɴ ᴀʀᴇ ᴅᴇʟᴇᴛᴇᴅ." + ) + e = format_exc() + print(e) + + +__mod_name__ = "𝚀ᴜᴏᴛᴇ" + +__help__ = """ + +⍟ /q : `ᴄʀᴇᴀᴛᴇ ǫᴜᴏᴛᴇ ` + +⍟ /q r : + +⍟ /q 2 ᴛᴏ 8 : +""" diff --git a/Exon/modules/raid.py b/Exon/modules/raid.py new file mode 100644 index 00000000..44c5b765 --- /dev/null +++ b/Exon/modules/raid.py @@ -0,0 +1,356 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import html +from datetime import timedelta +from typing import Optional + +from pytimeparse.timeparse import timeparse +from telegram import InlineKeyboardButton, InlineKeyboardMarkup, ParseMode, Update +from telegram.ext import CallbackContext +from telegram.utils.helpers import mention_html + +import Exon.modules.sql.welcome_sql as sql +from Exon import LOGGER, updater +from Exon.modules.helper_funcs.anonymous import AdminPerms, user_admin +from Exon.modules.helper_funcs.chat_status import ( + bot_admin, + connection_status, + user_admin_no_reply, +) +from Exon.modules.helper_funcs.decorators import Exoncallback, Exoncmd +from Exon.modules.log_channel import loggable + +j = updater.job_queue + +# store job id in a dict to be able to cancel them later +RUNNING_RAIDS = {} # {chat_id:job_id, ...} + + +def get_time(time: str) -> int: + try: + return timeparse(time) + except BaseException: + return 0 + + +def get_readable_time(time: int) -> str: + t = f"{timedelta(seconds=time)}".split(":") + if time == 86400: + return "1 day" + return f"{t[0]} hour(s)" if time >= 3600 else f"{t[1]} minutes" + + +@Exoncmd(command="raid", pass_args=True) +@bot_admin +@connection_status +@loggable +@user_admin(AdminPerms.CAN_CHANGE_INFO) +def setRaid(update: Update, context: CallbackContext) -> Optional[str]: + args = context.args + chat = update.effective_chat + msg = update.effective_message + user = update.effective_user + if chat.type == "private": + context.bot.sendMessage(chat.id, "This ᴄᴏᴍᴍᴀɴᴅ is not available in PMs.") + return + stat, time, acttime = sql.getRaidStatus(chat.id) + readable_time = get_readable_time(time) + if len(args) == 0: + if stat: + text = "ʀᴀɪᴅ ᴍᴏᴅᴇ ɪs ᴄᴜʀʀᴇɴᴛʟʏ <code>ᴇɴᴀʙʟᴇᴅ</code>\nᴡᴏᴜʟᴅ ʏᴏᴜ ʟɪᴋᴇ ᴛᴏ <code>ᴅɪsᴀʙʟᴇ</code> raid?" + keyboard = [ + [ + InlineKeyboardButton( + "ᴅɪsᴀʙʟᴇ ʀᴀɪᴅ ᴍᴏᴅᴇ", + callback_data=f"disable_raid={chat.id}={time}", + ), + InlineKeyboardButton( + "ᴄᴀɴᴄᴇʟ ᴀᴄᴛɪᴏɴ", callback_data="cancel_raid=1" + ), + ] + ] + + else: + text = ( + f"ʀᴀɪᴅ ᴍᴏᴅᴇ ɪs ᴄᴜʀʀᴇɴᴛʟʏ <code>ᴅɪsᴀʙʟᴇᴅ</code>\nᴡᴏᴜʟᴅ ʏᴏᴜ ʟɪᴋᴇ ᴛᴏ <code>ᴇɴᴀʙʟᴇ</code> " + f"ʀᴀɪᴅ ғᴏʀ {readable_time}?" + ) + keyboard = [ + [ + InlineKeyboardButton( + "ᴇɴᴀʙʟᴇ ʀᴀɪᴅ ᴍᴏᴅᴇ", + callback_data=f"enable_raid={chat.id}={time}", + ), + InlineKeyboardButton( + "ᴄᴀɴᴄᴇʟ ᴀᴄᴛɪᴏɴ", callback_data="cancel_raid=0" + ), + ] + ] + + reply_markup = InlineKeyboardMarkup(keyboard) + msg.reply_text(text, parse_mode=ParseMode.HTML, reply_markup=reply_markup) + + elif args[0] == "off": + if stat: + sql.setRaidStatus(chat.id, False, time, acttime) + j.scheduler.remove_job(RUNNING_RAIDS.pop(chat.id)) + text = "ʀᴀɪᴅ ᴍᴏᴅᴇ ʜᴀs ʙᴇᴇɴ <code>Disabled</code>, ᴍᴇᴍʙᴇʀs ᴛʜᴀᴛ ᴊᴏɪɴ ᴡɪʟʟ ɴᴏ ʟᴏɴɢᴇʀ ʙᴇ ᴋɪᴄᴋᴇᴅ." + msg.reply_text(text, parse_mode=ParseMode.HTML) + return ( + f"<b>{html.escape(chat.title)}:</b>\n" + f"#ʀᴀɪᴅ\n" + f"ᴅɪsᴀʙʟᴇᴅ\n" + f"<b>ᴀᴅᴍɪɴ:</b> {mention_html(user.id, user.first_name)}\n" + ) + + else: + args_time = args[0].lower() + if time := get_time(args_time): + readable_time = get_readable_time(time) + if 300 <= time < 86400: + text = ( + f"ʀᴀɪᴅ ᴍᴏᴅᴇ ɪs ᴄᴜʀʀᴇɴᴛʟʏ <code>ᴅɪsᴀʙʟᴇᴅ</code>\nᴡᴏᴜʟᴅ ʏᴏᴜ ʟɪᴋᴇ ᴛᴏ <code>Enable</code> " + f"ʀᴀɪᴅ ғᴏʀ {readable_time}? " + ) + keyboard = [ + [ + InlineKeyboardButton( + "ᴇɴᴀʙʟᴇ ʀᴀɪᴅ", + callback_data=f"enable_raid={chat.id}={time}", + ), + InlineKeyboardButton( + "ᴄᴀɴᴄᴇʟ ᴀᴄᴛɪᴏɴ", callback_data="cancel_raid=0" + ), + ] + ] + + reply_markup = InlineKeyboardMarkup(keyboard) + msg.reply_text( + text, parse_mode=ParseMode.HTML, reply_markup=reply_markup + ) + else: + msg.reply_text( + "ʏᴏᴜ ᴄᴀɴ ᴏɴʟʏ sᴇᴛ ᴛɪᴍᴇ ʙᴇᴛᴡᴇᴇɴ 5 ᴍɪɴᴜᴛᴇs ᴀɴᴅ 1 ᴅᴀʏ", + parse_mode=ParseMode.HTML, + ) + + else: + msg.reply_text( + "ᴜɴᴋɴᴏᴡɴ ᴛɪᴍᴇ ɢɪᴠᴇɴ, ɢɪᴠᴇ ᴍᴇ sᴏᴍᴇᴛʜɪɴɢ ʟɪᴋᴇ 5m ᴏʀ 1h", + parse_mode=ParseMode.HTML, + ) + + +@Exoncallback(pattern="enable_raid=") +@connection_status +@user_admin_no_reply +@loggable +def enable_raid_cb(update: Update, ctx: CallbackContext) -> Optional[str]: + args = update.callback_query.data.replace("enable_raid=", "").split("=") + chat = update.effective_chat + user = update.effective_user + chat_id = args[0] + time = int(args[1]) + readable_time = get_readable_time(time) + _, t, acttime = sql.getRaidStatus(chat_id) + sql.setRaidStatus(chat_id, True, time, acttime) + update.effective_message.edit_text( + f"ʀᴀɪᴅ ᴍᴏᴅᴇ ʜᴀs ʙᴇᴇɴ <code>ᴇɴᴀʙʟᴇᴅ</code> ғᴏʀ {readable_time}.", + parse_mode=ParseMode.HTML, + ) + LOGGER.info("ᴇɴᴀʙʟᴇᴅ ʀᴀɪᴅ ᴍᴏᴅᴇ ɪɴ {} for {}".format(chat_id, readable_time)) + try: + oldRaid = RUNNING_RAIDS.pop(int(chat_id)) + j.scheduler.remove_job(oldRaid) # check if there was an old job + except KeyError: + pass + + def disable_raid(_): + sql.setRaidStatus(chat_id, False, t, acttime) + LOGGER.info("ᴅɪsʙʟᴇᴅ ʀᴀɪᴅ ᴍᴏᴅᴇ ɪɴ {}".format(chat_id)) + ctx.bot.send_message(chat_id, "ʀᴀɪᴅ ᴍᴏᴅᴇ ʜᴀs ʙᴇᴇɴ ᴀᴜᴛᴏᴍᴀᴛɪᴄᴀʟʟʏ ᴅɪsᴀʙʟᴇᴅ!") + + raid = j.run_once(disable_raid, time) + RUNNING_RAIDS[int(chat_id)] = raid.job.id + return ( + f"<b>{html.escape(chat.title)}:</b>\n" + f"#ʀᴀɪᴅ\n" + f"ᴇɴᴀʙʟᴇᴅ ғᴏʀ {readable_time}\n" + f"<b>ᴀᴅᴍɪɴ:</b> {mention_html(user.id, user.first_name)}\n" + ) + + +@Exoncallback(pattern="disable_raid=") +@connection_status +@user_admin_no_reply +@loggable +def disable_raid_cb(update: Update, _: CallbackContext) -> Optional[str]: + args = update.callback_query.data.replace("disable_raid=", "").split("=") + chat = update.effective_chat + user = update.effective_user + chat_id = args[0] + time = args[1] + _, _, acttime = sql.getRaidStatus(chat_id) + sql.setRaidStatus(chat_id, False, time, acttime) + j.scheduler.remove_job(RUNNING_RAIDS.pop(int(chat_id))) + update.effective_message.edit_text( + "ʀᴀɪᴅ ᴍᴏᴅᴇ ʜᴀs ʙᴇᴇɴ <code>ᴅɪsᴀʙʟᴇᴅ</code>, ɴᴇᴡʟʏ ᴊᴏɪɴɪɴɢ ᴍᴇᴍʙᴇʀs ᴡɪʟʟ ɴᴏ ʟᴏɴɢᴇʀ ʙᴇ ᴋɪᴄᴋᴇᴅ.", + parse_mode=ParseMode.HTML, + ) + logmsg = ( + f"<b>{html.escape(chat.title)}:</b>\n" + f"#ʀᴀɪᴅ\n" + f"ᴅɪsᴀʙʟᴇᴅ\n" + f"<b>ᴀᴅᴍɪɴ:</b> {mention_html(user.id, user.first_name)}\n" + ) + return logmsg + + +@Exoncallback(pattern="cancel_raid=") +@connection_status +@user_admin_no_reply +def disable_raid_cb(update: Update, _: CallbackContext): + args = update.callback_query.data.split("=") + what = args[0] + update.effective_message.edit_text( + f"ᴀᴄᴛɪᴏɴ ᴄᴀɴᴄᴇʟʟᴇᴅ, ʀᴀɪᴅ ᴍᴏᴅᴇ ᴡɪʟʟ sᴛᴀʏ <code>{'Enabled' if what == 1 else 'Disabled'}</code>.", + parse_mode=ParseMode.HTML, + ) + + +@Exoncmd(command="raidtime") +@connection_status +@loggable +@user_admin(AdminPerms.CAN_CHANGE_INFO) +def raidtime(update: Update, context: CallbackContext) -> Optional[str]: + what, time, acttime = sql.getRaidStatus(update.effective_chat.id) + args = context.args + msg = update.effective_message + user = update.effective_user + chat = update.effective_chat + if not args: + msg.reply_text( + f"ʀᴀɪᴅ ᴍᴏᴅᴇ ɪs ᴄᴜʀʀᴇɴᴛʟʏ sᴇᴛ ᴛᴏ {get_readable_time(time)}\nᴡʜᴇɴ ᴛᴏɢɢʟᴇᴅ, ᴛʜᴇ ʀᴀɪᴅ ᴍᴏᴅᴇ ᴡɪʟʟ ʟᴀsᴛ " + f"ғᴏʀ {get_readable_time(time)} ᴛʜᴇɴ ᴛᴜʀɴ ᴏғғ ᴀᴜᴛᴏᴍᴀᴛɪᴄᴀʟʟʏ", + parse_mode=ParseMode.HTML, + ) + return + args_time = args[0].lower() + if time := get_time(args_time): + readable_time = get_readable_time(time) + if 300 <= time < 86400: + text = ( + f"ʀᴀɪᴅ ᴍᴏᴅᴇ ɪs ᴄᴜʀʀᴇɴᴛʟʏ sᴇᴛ ᴛᴏ {readable_time}\nᴡʜᴇɴ ᴛᴏɢɢʟᴇᴅ, ᴛʜᴇ ʀᴀɪᴅ ᴍᴏᴅᴇ ᴡɪʟʟ ʟᴀsᴛ ғᴏʀ " + f"{readable_time} ᴛʜᴇɴ ᴛᴜʀɴ ᴏғғ ᴀᴜᴛᴏᴍᴀᴛɪᴄᴀʟʟʏ" + ) + msg.reply_text(text, parse_mode=ParseMode.HTML) + sql.setRaidStatus(chat.id, what, time, acttime) + return ( + f"<b>{html.escape(chat.title)}:</b>\n" + f"#ʀᴀɪᴅ\n" + f"sᴇᴛ ʀᴀɪᴅ ᴍᴏᴅᴇ ᴛɪᴍᴇ ᴛᴏ {readable_time}\n" + f"<b>ᴀᴅᴍɪɴ:</b> {mention_html(user.id, user.first_name)}\n" + ) + else: + msg.reply_text( + "ʏᴏᴜ ᴄᴀɴ ᴏɴʟʏ sᴇᴛ ᴛɪᴍᴇ ʙᴇᴛᴡᴇᴇɴ 5 ᴍɪɴᴜᴛᴇs ᴀɴᴅ 1 ᴅᴀʏ", + parse_mode=ParseMode.HTML, + ) + else: + msg.reply_text( + "ᴜɴᴋɴᴏᴡɴ ᴛɪᴍᴇ ɢɪᴠᴇɴ, ɢɪᴠᴇ ᴍᴇ sᴏᴍᴇᴛʜɪɴɢ ʟɪᴋᴇ 5ᴍ ᴏʀ 1ʜ", + parse_mode=ParseMode.HTML, + ) + + +@Exoncmd(command="raidactiontime", pass_args=True) +@connection_status +@user_admin(AdminPerms.CAN_CHANGE_INFO) +@loggable +def raidtime(update: Update, context: CallbackContext) -> Optional[str]: + what, t, time = sql.getRaidStatus(update.effective_chat.id) + args = context.args + msg = update.effective_message + user = update.effective_user + chat = update.effective_chat + if not args: + msg.reply_text( + f"ʀᴀɪᴅ ᴀᴄᴛɪᴏɴ ᴛɪᴍᴇ ɪs ᴄᴜʀʀᴇɴᴛʟʏ sᴇᴛ ᴛᴏ {get_readable_time(time)}\nᴡʜᴇɴ ᴛᴏɢɢʟᴇᴅ, ᴛʜᴇ ᴍᴇᴍʙᴇʀs ᴛʜᴀᴛ " + f"ᴊᴏɪɴ ᴡɪʟʟ ʙᴇ ᴛᴇᴍᴘ ʙᴀɴɴᴇᴅ ғᴏʀ {get_readable_time(time)}", + parse_mode=ParseMode.HTML, + ) + return + args_time = args[0].lower() + if time := get_time(args_time): + readable_time = get_readable_time(time) + if 300 <= time < 86400: + text = ( + f"ʀᴀɪᴅ ᴀᴄᴛɪᴏɴ ᴛɪᴍᴇ ɪs ᴄᴜʀʀᴇɴᴛʟʏ sᴇᴛ ᴛᴏ {get_readable_time(time)}\nᴡʜᴇɴ ᴛᴏɢɢʟᴇᴅ, ᴛʜᴇ ᴍᴇᴍʙᴇʀs ᴛʜᴀᴛ" + f" ᴊᴏɪɴ ᴡɪʟʟ ʙᴇ ᴛᴇᴍᴘ ʙᴀɴɴᴇᴅ ғᴏʀ {readable_time}" + ) + msg.reply_text(text, parse_mode=ParseMode.HTML) + sql.setRaidStatus(chat.id, what, t, time) + return ( + f"<b>{html.escape(chat.title)}:</b>\n" + f"#ʀᴀɪᴅ\n" + f"sᴇᴛ ʀᴀɪᴅ ᴍᴏᴅᴇ ᴀᴄᴛɪᴏɴ ᴛɪᴍᴇ ᴛᴏ {readable_time}\n" + f"<b>Admin:</b> {mention_html(user.id, user.first_name)}\n" + ) + else: + msg.reply_text( + "ʏᴏᴜ ᴄᴀɴ ᴏɴʟʏ sᴇᴛ ᴛɪᴍᴇ ʙᴇᴛᴡᴇᴇɴ 5 ᴍɪɴᴜᴛᴇs ᴀɴᴅ 1 ᴅᴀʏ", + parse_mode=ParseMode.HTML, + ) + else: + msg.reply_text( + "ᴜɴᴋɴᴏᴡɴ ᴛɪᴍᴇ ɢɪᴠᴇɴ, ɢɪᴠᴇ ᴍᴇ sᴏᴍᴇᴛʜɪɴɢ ʟɪᴋᴇ 5m ᴏʀ 1h", + parse_mode=ParseMode.HTML, + ) + + +__help__ = """ +ᴇᴠᴇʀ ʜᴀᴅ ʏᴏᴜʀ ɢʀᴏᴜᴘ ʀᴀɪᴅᴇᴅ ʙʏ sᴘᴀᴍᴍᴇʀs ᴏʀ ʙᴏᴛs? +ᴛʜɪs ᴍᴏᴅᴜʟᴇ ᴀʟʟᴏᴡs ʏᴏᴜ ᴛᴏ ǫᴜɪᴄᴋʟʏ sᴛᴏᴘ ᴛʜᴇ ʀᴀɪᴅᴇʀs +ʙʏ ᴇɴᴀʙʟɪɴɢ *ʀᴀɪᴅ ᴍᴏᴅᴇ* I ᴡɪʟʟ ᴀᴜᴛᴏᴍᴀᴛɪᴄᴀʟʟʏ ᴋɪᴄᴋ ᴇᴠᴇʀʏ ᴜsᴇʀ ᴛʜᴀᴛ ᴛʀɪᴇs ᴛᴏ ᴊᴏɪɴ +ᴡʜᴇɴ ᴛʜᴇ ʀᴀɪᴅ ɪs ᴅᴏɴᴇ ʏᴏᴜ ᴄᴀɴ ᴛᴏɢɢʟᴇ ʙᴀᴄᴋ ʟᴏᴄᴋɢʀᴏᴜᴘ ᴀɴᴅ ᴇᴠᴇʀʏᴛʜɪɴɢ ᴡɪʟʟ ʙᴇ ʙᴀᴄᴋ ᴛᴏ ɴᴏʀᴍᴀʟ. + +*ᴀᴅᴍɪɴs ᴏɴʟʏ!* + +⍟ /raid `(off/time optional)` : `ᴛᴏɢɢʟᴇ ᴛʜᴇ ʀᴀɪᴅ ᴍᴏᴅᴇ (ᴏɴ/ᴏғғ `) + +if ɴᴏ ᴛɪᴍᴇ is ɢɪᴠᴇɴ ɪᴛ ᴡɪʟʟ ᴅᴇғᴀᴜʟᴛ ᴛᴏ 2 ʜᴏᴜʀs ᴛʜᴇɴ ᴛᴜʀɴ ᴏғғ ᴀᴜᴛᴏᴍᴀᴛɪᴄᴀʟʟʏ + +ʙʏ ᴇɴᴀʙʟɪɴɢ *ʀᴀɪᴅ ᴍᴏᴅᴇ* ɪ ᴡɪʟʟ ᴋɪᴄᴋ ᴇᴠᴇʀʏ ᴜsᴇʀ ᴏɴ ᴊᴏɪɴɪɴɢ ᴛʜᴇ ɢʀᴏᴜᴘ + + +⍟ /raidtime `(time optional)` : `ᴠɪᴇᴡ ᴏʀ sᴇᴛ ᴛʜᴇ ᴅᴇғᴀᴜʟᴛ ᴅᴜʀᴀᴛɪᴏɴ ғᴏʀ raid ᴍᴏᴅᴇ, ᴀғᴛᴇʀ ᴛʜᴀᴛ ᴛɪᴍᴇ ғʀᴏᴍ ᴛᴏɢɢʟɪɴɢ ᴛʜᴇ ʀᴀɪᴅ ᴍᴏᴅᴇ ᴡɪʟʟ ᴛᴜʀɴ ᴏғғ ᴀᴜᴛᴏᴍᴀᴛɪᴄᴀʟʟʏ ᴅᴇғᴀᴜʟᴛ ɪs 6 ʜᴏᴜʀs ` + + +⍟ /raidactiontime `(ᴛɪᴍᴇ ᴏᴘᴛɪᴏɴᴀʟ)` : `ᴠɪᴇᴡ ᴏʀ sᴇᴛ ᴛʜᴇ ᴅᴇғᴀᴜʟᴛ ᴅᴜʀᴀᴛɪᴏɴ ᴛʜᴀᴛ ᴛʜᴇ ʀᴀɪᴅ ᴍᴏᴅᴇ ᴡɪʟʟ ᴛᴇᴍᴘʙᴀɴ +ᴅᴇғᴀᴜʟᴛ ɪs 1 ʜᴏᴜʀ ` + +""" + +__mod_name__ = "𝙰ɴᴛɪʀᴀɪᴅ" diff --git a/Exon/modules/reactions.py b/Exon/modules/reactions.py new file mode 100644 index 00000000..f450bae3 --- /dev/null +++ b/Exon/modules/reactions.py @@ -0,0 +1,253 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import random + +from telegram import Update +from telegram.ext import CallbackContext + +from Exon import dispatcher +from Exon.modules.disable import DisableAbleCommandHandler + +reactions = [ + "( ͡° ͜ʖ ͡°)", + "( . •́ _ʖ •̀ .)", + "( ಠ ͜ʖ ಠ)", + "( ͡ ͜ʖ ͡ )", + "(ʘ ͜ʖ ʘ)", + "ヾ(´〇`)ノ♪♪♪", + "ヽ(o´∀`)ノ♪♬", + "♪♬((d⌒ω⌒b))♬♪", + "└(^^)┐", + "( ̄▽ ̄)/♫•*¨*•.¸¸♪", + "ヾ(⌐■_■)ノ♪", + "乁( • ω •乁)", + "♬♫♪◖(● o ●)◗♪♫♬", + "(っ˘ڡ˘ς)", + "( ˘▽˘)っ♨", + "( ・ω・)⊃-[二二]", + "(*´ー`)旦 旦( ̄ω ̄*)", + "(  ̄▽ ̄)[] [](≧▽≦ )", + "(* ̄▽ ̄)旦 且(´∀`*)", + "(ノ ˘_˘)ノ ζ|||ζ ζ|||ζ ζ|||ζ", + "(ノ°∀°)ノ⌒・*:.。. .。.:*・゜゚・*☆", + "(⊃。•́‿•̀。)⊃━✿✿✿✿✿✿", + "(∩` ロ ´)⊃━炎炎炎炎炎", + "( ・∀・)・・・--------☆", + "( -ω-)/占~~~~~", + "○∞∞∞∞ヽ(^ー^ )", + "(*^^)/~~~~~~~~~~◎", + "(((  ̄□)_/", + "(メ ̄▽ ̄)︻┳═一", + "ヽ( ・∀・)ノ_θ彡☆Σ(ノ `Д´)ノ", + "(*`0´)θ☆(メ°皿°)ノ", + "(; -_-)――――――C<―_-)", + "ヽ(>_<ヽ) ―⊂|=0ヘ(^‿^ )", + "(҂` ロ ´)︻デ═一 \(º □ º l|l)/", + "/( .□.)\ ︵╰(°益°)╯︵ /(.□. /)", + "(`⌒*)O-(`⌒´Q)", + "(っ•﹏•)っ ✴==≡눈٩(`皿´҂)ง", + "ヾ(・ω・)メ(・ω・)ノ", + "(*^ω^)八(⌒▽⌒)八(-‿‿- )ヽ", + "ヽ( ⌒ω⌒)人(=^‥^= )ノ", + "。*:☆(・ω・人・ω・)。:゜☆。", + "(°(°ω(°ω°(☆ω☆)°ω°)ω°)°)", + "(っ˘▽˘)(˘▽˘)˘▽˘ς)", + "(*^ω^)人(^ω^*)", + r"\(▽ ̄ \ ( ̄▽ ̄) /  ̄▽)/", + "( ̄Θ ̄)", + "\( ˋ Θ ´ )/", + "( ´(00)ˋ )", + "\( ̄(oo) ̄)/", + "/(≧ x ≦)\", + "/(=・ x ・=)\", + "(=^・ω・^=)", + "(= ; ェ ; =)", + "(=⌒‿‿⌒=)", + "(^• ω •^)", + "ଲ(ⓛ ω ⓛ)ଲ", + "ଲ(ⓛ ω ⓛ)ଲ", + "(^◔ᴥ◔^)", + "[(--)]..zzZ", + "( ̄o ̄) zzZZzzZZ", + "(_ _*) Z z z", + "☆ミ(o*・ω・)ノ", + "ε=ε=ε=ε=┌(; ̄▽ ̄)┘", + "ε===(っ≧ω≦)っ", + "__φ(..)", + "ヾ( `ー´)シφ__", + "( ^▽^)ψ__", + "|・ω・)", + "|д・)", + "┬┴┬┴┤・ω・)ノ", + "|・д・)ノ", + "(* ̄ii ̄)", + "(^〃^)", + "m(_ _)m", + "人(_ _*)", + "(シ. .)シ", + "(^_~)", + "(>ω^)", + "(^_<)〜☆", + "(^_<)", + "(づ ̄ ³ ̄)づ", + "(⊃。•́‿•̀。)⊃", + "⊂(´• ω •`⊂)", + "(*・ω・)ノ", + "(^-^*)/", + "ヾ(*'▽'*)", + "(^0^)ノ", + "(*°ー°)ノ", + "( ̄ω ̄)/", + "(≧▽≦)/", + "w(°o°)w", + "(⊙_⊙)", + "(°ロ°) !", + "∑(O_O;)", + "(¬_¬)", + "(¬_¬ )", + "(↼_↼)", + "( ̄ω ̄;)", + "┐('~`;)┌", + "(・_・;)", + "(@_@)", + "(•ิ_•ิ)?", + "ヽ(ー_ー )ノ", + "┐( ̄ヘ ̄)┌", + "┐( ̄~ ̄)┌", + "┐( ´ д ` )┌", + "╮(︶▽︶)╭", + "ᕕ( ᐛ )ᕗ", + "(ノωヽ)", + "(″ロ゛)", + "(/ω\)", + "(((><)))", + "~(>_<~)", + "(×_×)", + "(×﹏×)", + "(ノ_<。)", + "(μ_μ)", + "o(TヘTo)", + "( ゚,_ゝ`)", + "( ╥ω╥ )", + "(/ˍ・、)", + "(つω`。)", + "(T_T)", + "o(〒﹏〒)o", + "(#`Д´)", + "(・`ω´・)", + "( `ε´ )", + "(メ` ロ ´)", + "Σ(▼□▼メ)", + "(҂ `з´ )", + "٩(╬ʘ益ʘ╬)۶", + "↑_(ΦwΦ)Ψ", + "(ノಥ益ಥ)ノ", + "(#><)", + "(; ̄Д ̄)", + "(¬_¬;)", + "(^^#)", + "( ̄︿ ̄)", + "ヾ(  ̄O ̄)ツ", + "(ᗒᗣᗕ)՞", + "(ノ_<。)ヾ(´ ▽ ` )", + "ヽ( ̄ω ̄(。。 )ゝ", + "(ノ_;)ヾ(´ ∀ ` )", + "(´-ω-`( _ _ )", + "(⌒_⌒;)", + "(*/_\)", + "( ◡‿◡ *)", + "(//ω//)", + "( ̄▽ ̄*)ゞ", + "(„ಡωಡ„)", + "(ノ´ з `)ノ", + "(♡-_-♡)", + "(─‿‿─)♡", + "(´ ω `♡)", + "(ღ˘⌣˘ღ)", + "(´• ω •`) ♡", + "╰(*´︶`*)╯♡", + "(≧◡≦) ♡", + "♡ (˘▽˘>ԅ( ˘⌣˘)", + "σ(≧ε≦σ) ♡", + "(˘∀˘)/(μ‿μ) ❤", + "Σ>―(〃°ω°〃)♡→", + "(* ^ ω ^)", + "(o^▽^o)", + "ヽ(・∀・)ノ", + "(o・ω・o)", + "(^人^)", + "( ´ ω ` )", + "(´• ω •`)", + "╰(▔∀▔)╯", + "(✯◡✯)", + "(⌒‿⌒)", + "(*°▽°*)", + "(´。• ᵕ •。`)", + "ヽ(>∀<☆)ノ", + "\( ̄▽ ̄)/", + "(o˘◡˘o)", + "(╯✧▽✧)╯", + "( ‾́ ◡ ‾́ )", + "(๑˘︶˘๑)", + "(´・ᴗ・ ` )", + "( ͡° ʖ̯ ͡°)", + "( ఠ ͟ʖ ఠ)", + "( ಥ ʖ̯ ಥ)", + "(≖ ͜ʖ≖)", + "ヘ( ̄ω ̄ヘ)", + "(ノ≧∀≦)ノ", + "└( ̄- ̄└))", + "┌(^^)┘", + "(^_^♪)", + "(〜 ̄△ ̄)〜", + "(「• ω •)「", + "( ˘ ɜ˘) ♬♪♫", + "( o˘◡˘o) ┌iii┐", + "♨o(>_<)o♨", + "( ・・)つ―{}@{}@{}-", + "(*´з`)口゚。゚口(・∀・ )", + "( *^^)o∀*∀o(^^* )", + "-●●●-c(・・ )", + "(ノ≧∀≦)ノ ‥…━━━★", + "╰( ͡° ͜ʖ ͡° )つ──☆*:・゚", + "(∩ᄑ_ᄑ)⊃━☆゚*・。*・:≡( ε:)", +] + + +def react(update: Update, context: CallbackContext): + message = update.effective_message + react = random.choice(reactions) + if message.reply_to_message: + message.reply_to_message.reply_text(react) + else: + message.reply_text(react) + + +REACT_HANDLER = DisableAbleCommandHandler("react", react, run_async=True) + +dispatcher.add_handler(REACT_HANDLER) + +__command_list__ = ["react"] +__handlers__ = [REACT_HANDLER] diff --git a/Exon/modules/readme.md b/Exon/modules/readme.md new file mode 100644 index 00000000..1353a2ad --- /dev/null +++ b/Exon/modules/readme.md @@ -0,0 +1,41 @@ +# ᴇxᴏɴ ʀᴏʙᴏᴛ ᴇxᴀᴍᴘʟᴇ ᴘʟᴜɢɪɴ ғᴏʀᴍᴀᴛ + +## ᴀᴅᴠᴀɴᴄᴇᴅ: ᴘʏʀᴏɢʀᴀᴍ +```ᴘʏᴛʏʜᴏɴ3 + +from pyrogram import filters +from Exon import pgram + +# ᴀʟsᴏ ʏᴏᴜ ᴄᴀɴ ᴜsᴇ : pgram as pbot : any pyrogram client + +@pgram.on_message(filters.command("hi")) +async def hmm(_, message): + await message.reply_text( + "ʜɪɪɪɪ" + ) + +__mod_name__ = "Hi" +__help__ = """ +*ʜɪ* +- /hi: ʜɪɪɪ +""" +``` + +## ᴀᴅᴠᴀɴᴄᴇᴅ: ᴛᴇʟᴇᴛʜᴏɴ +```ᴘʏᴛʜᴏɴ3 + +from Exon import telethn +from Exon.events import register + +@register(pattern="^/hi$") +async def _(event): + j = "ᴋʏ ʀᴇ ʟᴏᴅᴇ" + await event.reply(j) + +__mod_name__ = "Hi" +__help__ = """ +*Hi* +- /hi: ᴋʏ ʀᴇ ʟᴏᴅᴇ +""" +``` + diff --git a/Exon/modules/reminders.py b/Exon/modules/reminders.py new file mode 100644 index 00000000..95768c32 --- /dev/null +++ b/Exon/modules/reminders.py @@ -0,0 +1,262 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import re +import time + +from telegram import Update +from telegram.ext import CommandHandler +from telegram.ext.callbackcontext import CallbackContext +from telegram.ext.filters import Filters +from telegram.parsemode import ParseMode + +from Exon import OWNER_ID, dispatcher, updater +from Exon.modules.disable import DisableAbleCommandHandler + +job_queue = updater.job_queue + + +def get_time(time: str) -> int: + if time[-1] == "s": + return int(time[:-1]) + if time[-1] == "m": + return int(time[:-1]) * 60 + if time[-1] == "h": + return int(time[:-1]) * 3600 + if time[-1] == "d": + return int(time[:-1]) * 86400 + + +reminder_message = """ +ʏᴏᴜʀ ʀᴇᴍɪɴᴅᴇʀ: +{reason} +<i>ᴡʜɪᴄʜ ʏᴏᴜ ᴛɪᴍᴇᴅ {time} ʙᴇғᴏʀᴇ ɪɴ {title}</i> +""" + + +def reminders(update: Update, context: CallbackContext): + user = update.effective_user + msg = update.effective_message + jobs = list(job_queue.jobs()) + user_reminders = [job.name[1:] for job in jobs if job.name.endswith(str(user.id))] + + if not user_reminders: + msg.reply_text( + text="ʏᴏᴜ ᴅᴏɴ'ᴛ have ᴀɴʏ ʀᴇᴍɪɴᴅᴇʀs sᴇᴛ ᴏʀ ᴀʟʟ ᴛʜᴇ ʀᴇᴍɪɴᴅᴇʀs ʏᴏᴜ ʜᴀᴠᴇ set have ʙᴇᴇɴ ᴄᴏᴍᴘʟᴇᴛᴇᴅ", + reply_to_message_id=msg.message_id, + ) + return + reply_text = "ʏᴏᴜʀ ʀᴇᴍɪɴᴅᴇʀs (<i>ᴍᴇɴᴛɪᴏɴᴇᴅ ʙᴇʟᴏᴡ ᴀʀᴇ ᴛʜᴇ <b>ᴛɪᴍsᴛᴀᴍᴘs</b> of the ʀᴇᴍɪɴᴅᴇʀs ʏᴏᴜ ʜᴀᴠᴇ sᴇᴛ</i>):\n" + for i, u in enumerate(user_reminders): + reply_text += f"\n{i+1}. <code>{u}</code>" + msg.reply_text( + text=reply_text, reply_to_message_id=msg.message_id, parse_mode=ParseMode.HTML + ) + + +def set_reminder(update: Update, context: CallbackContext): + user = update.effective_user + msg = update.effective_message + chat = update.effective_chat + reason = msg.text.split() + if len(reason) == 1: + msg.reply_text( + "ɴᴏ ᴛɪᴍᴇ ᴀɴᴅ ʀᴇᴍɪɴᴅᴇʀ ᴛᴏ ᴍᴇɴᴛɪᴏɴ!", reply_to_message_id=msg.message_id + ) + return + if len(reason) == 2: + msg.reply_text( + "ɴᴏᴛʜɪɴɢ ᴛᴏ ʀᴇᴍɪɴᴅᴇʀ! ᴀᴅᴅ ᴀ ʀᴇᴍɪɴᴅᴇʀ", reply_to_message_id=msg.message_id + ) + return + t = reason[1].lower() + if not re.match(r"[0-9]+(d|h|m|s)", t): + msg.reply_text( + "ᴜsᴇ ᴀ ᴄᴏʀʀᴇᴄᴛ ғᴏʀᴍᴀᴛ ᴏғ ᴛɪᴍᴇ!", reply_to_message_id=msg.message_id + ) + return + + def job(context: CallbackContext): + title = "" + if chat.type == "private": + title += "this chat" + if chat.type != "private": + title += chat.title + context.bot.send_message( + chat_id=user.id, + text=reminder_message.format( + reason=" ".join(reason[2:]), time=t, title=title + ), + disable_notification=False, + parse_mode=ParseMode.HTML, + ) + + job_time = time.time() + job_queue.run_once( + callback=job, when=get_time(t), name=f"t{job_time}{user.id}".replace(".", "") + ) + msg.reply_text( + text="Your ʀᴇᴍɪɴᴅᴇʀ ʜᴀs ʙᴇᴇɴ sᴇᴛ ᴀғᴛᴇʀ {time} ғʀᴏᴍ ɴᴏᴡ!\nᴛɪᴍᴇsᴛᴀᴍᴘ: <code>{time_stamp}</code>".format( + time=t, time_stamp=str(job_time).replace(".", "") + str(user.id) + ), + reply_to_message_id=msg.message_id, + parse_mode=ParseMode.HTML, + ) + + +def clear_reminder(update: Update, context: CallbackContext): + user = update.effective_user + msg = update.effective_message + text = msg.text.split() + if len(text) == 1 or not re.match(r"[0-9]+", text[1]): + msg.reply_text( + text="ɴᴏ/ᴡʀᴏɴɢ ᴛɪᴍᴇsᴛᴀᴍᴘ ᴍᴇɴᴛɪᴏɴᴇᴅ", reply_to_message_id=msg.message_id + ) + return + if not text[1].endswith(str(user.id)): + msg.reply_text( + text="ᴛʜᴇ ᴛɪᴍᴇsᴛᴀᴍᴘ ᴍᴇɴᴛɪᴏɴᴇᴅ ɪs ɴᴏᴛ ʏᴏᴜʀ ʀᴇᴍɪɴᴅᴇʀ!", + reply_to_message_id=msg.message_id, + ) + return + jobs = list(job_queue.get_jobs_by_name(f"t{text[1]}")) + if not jobs: + msg.reply_text( + text="ᴛʜɪs ʀᴇᴍɪɴᴅᴇʀ ɪs ᴀʟʀᴇᴀᴅʏ ᴄᴏᴍᴘʟᴇᴛᴇᴅ ᴏʀ ᴇɪᴛʜᴇʀ ɴᴏᴛ sᴇᴛ", + reply_to_message_id=msg.message_id, + ) + return + jobs[0].schedule_removal() + msg.reply_text( + text="ᴅᴏɴᴇ ᴄʟᴇᴀʀᴇᴅ ᴛʜᴇ ʀᴇᴍɪɴᴅᴇʀ!", reply_to_message_id=msg.message_id + ) + + +def clear_all_reminders(update: Update, context: CallbackContext): + user = update.effective_user + msg = update.effective_message + if user.id != OWNER_ID: + msg.reply_text( + text="ᴡʜᴏ ᴛʜɪs ɢᴜʏ ɴᴏᴛ ʙᴇɪɴɢ ᴛʜᴇ ᴏᴡɴᴇʀ ᴡᴀɴᴛs ᴍᴇ ᴄʟᴇᴀʀ ᴀʟʟ ᴛʜᴇ ʀᴇᴍɪɴᴅᴇʀs!!?", + reply_to_message_id=msg.message_id, + ) + return + jobs = list(job_queue.jobs()) + unremoved_reminders = [] + for job in jobs: + try: + job.schedule_removal() + except Exception: + unremoved_reminders.append(job.name[1:]) + reply_text = "ᴅᴏɴᴇ ᴄʟᴇᴀʀᴇᴅ ᴀʟʟ ᴛʜᴇ ʀᴇᴍɪɴᴅᴇʀs!\n\n" + if unremoved_reminders: + reply_text += "ᴇxᴄᴇᴘᴛ (<i>ᴛɪᴍᴇ sᴛᴀᴍᴘs ʜᴀᴠᴇ ʙᴇᴇɴ ᴍᴇɴᴛɪᴏɴᴇᴅ</i>):" + for i, u in enumerate(unremoved_reminders): + reply_text += f"\n{i+1}. <code>{u}</code>" + msg.reply_text( + text=reply_text, reply_to_message_id=msg.message_id, parse_mode=ParseMode.HTML + ) + + +def clear_all_my_reminders(update: Update, context: CallbackContext): + user = update.effective_user + msg = update.effective_message + jobs = list(job_queue.jobs()) + if not jobs: + msg.reply_text( + text="ʏᴏᴜ ᴅᴏɴ'ᴛ ʜᴀᴠᴇ ᴀɴʏ ʀᴇᴍɪɴᴅᴇʀs!", reply_to_message_id=msg.message_id + ) + return + unremoved_reminders = [] + for job in jobs: + if job.name.endswith(str(user.id)): + try: + job.schedule_removal() + except Exception: + unremoved_reminders.append(job.name[1:]) + reply_text = "ᴅᴏɴᴇ ᴄʟᴇᴀʀᴇᴅ ᴀʟʟ ʏᴏᴜʀ ʀᴇᴍɪɴᴅᴇʀs!\n\n" + if unremoved_reminders: + reply_text += "ᴇxᴄᴇᴘᴛ (<i>ᴛɪᴍᴇ sᴛᴀᴍᴘs ʜᴀᴠᴇ ʙᴇᴇɴ ᴍᴇɴᴛɪᴏɴᴇᴅ</i>):" + for i, u in enumerate(unremoved_reminders): + reply_text += f"\n{i+1}. <code>{u}</code>" + msg.reply_text( + text=reply_text, reply_to_message_id=msg.message_id, parse_mode=ParseMode.HTML + ) + + +__mod_name__ = "𝚁ᴇᴍɪɴᴅᴇʀ" +__help__ = """ +⍟ /reminders*:* `ɢᴇᴛ ᴀ ʟɪsᴛ ᴏғ *ᴛɪᴍᴇsᴛᴀᴍᴘs* ᴏғ ʏᴏᴜʀ ʀᴇᴍɪɴᴅᴇʀs ` + +⍟ /setreminder <time> <remind message>*:* `sᴇᴛ ᴀ ʀᴇᴍɪɴᴅᴇʀ ᴀғᴛᴇʀ ᴛʜᴇ ᴍᴇɴᴛɪᴏɴᴇᴅ ᴛɪᴍᴇ ` + +⍟ /clearreminder <timestamp>*:* `ᴄʟᴇᴀʀs ᴛʜᴇ ʀᴇᴍɪɴᴅᴇʀ ᴡɪᴛʜ ᴛʜᴀᴛ ᴛɪᴍᴇsᴛᴀᴍᴘ ɪғ ᴛʜᴇ ᴛɪᴍᴇ ᴛᴏ ʀᴇᴍɪɴᴅ ɪs ɴᴏᴛ ʏᴇᴛ ᴄᴏᴍᴘʟᴇᴛᴇᴅ ` + +⍟ /clearmyreminders*:* `ᴄʟᴇᴀʀs ᴀʟʟ ᴛʜᴇ ʀᴇᴍɪɴᴅᴇʀs ᴏғ ᴛʜᴇ ᴜsᴇʀ `. + +*sɪᴍɪʟᴀʀ ᴄᴏᴍᴍᴀɴᴅs:* + +⍟ /reminders | /myreminders + +⍟ /clearmyreminders | /clearallmyreminders + +*ᴜsᴀɢᴇ:* +⍟ /setreminder 30s reminder*:* `ʜᴇʀᴇ ᴛʜᴇ ᴛɪᴍᴇ ғᴏʀᴍᴀᴛ ɪs sᴀᴍᴇ ᴀs ᴛʜᴇ ᴛɪᴍᴇ ғᴏʀᴍᴀᴛ ɪɴ ᴍᴜᴛɪɴɢ ʙᴜᴛ ᴡɪᴛʜ ᴇxᴛʀᴀ sᴇᴄᴏɴᴅs(s)` + + +✦ `/clearreminder 1234567890123456789` +""" + +# ʙsᴅᴋ ᴄᴏᴘʏ ᴋᴀʀ ʀᴀ ʜ ᴄʀᴇᴅɪᴛ ᴅᴇ ᴅᴇɴᴀ @AbishnoiMF|@Abishnoi + +RemindersHandler = CommandHandler( + ["reminders", "myreminders"], + reminders, + filters=Filters.chat_type.private, + run_async=True, +) +SetReminderHandler = DisableAbleCommandHandler( + "setreminder", set_reminder, run_async=True +) +ClearReminderHandler = DisableAbleCommandHandler( + "clearreminder", clear_reminder, run_async=True +) +ClearAllRemindersHandler = CommandHandler( + "clearallreminders", + clear_all_reminders, + filters=Filters.chat(OWNER_ID), + run_async=True, +) +ClearALLMyRemindersHandler = CommandHandler( + ["clearmyreminders", "clearallmyreminders"], + clear_all_my_reminders, + filters=Filters.chat_type.private, + run_async=True, +) + +dispatcher.add_handler(RemindersHandler) +dispatcher.add_handler(SetReminderHandler) +dispatcher.add_handler(ClearReminderHandler) +dispatcher.add_handler(ClearAllRemindersHandler) +dispatcher.add_handler(ClearALLMyRemindersHandler) diff --git a/Exon/modules/remote_cmds.py b/Exon/modules/remote_cmds.py new file mode 100644 index 00000000..33c15e56 --- /dev/null +++ b/Exon/modules/remote_cmds.py @@ -0,0 +1,550 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +from telegram import ChatPermissions, Update +from telegram.error import BadRequest +from telegram.ext import CallbackContext, CommandHandler + +from Exon import LOGGER, dispatcher +from Exon.modules.helper_funcs.chat_status import ( + bot_admin, + is_bot_admin, + is_user_ban_protected, + is_user_in_chat, +) +from Exon.modules.helper_funcs.extraction import extract_user_and_text +from Exon.modules.helper_funcs.filters import CustomFilters + +RBAN_ERRORS = { + "ᴜsᴇʀ ɪs ᴀɴ ᴀᴅᴍɪɴɪsᴛʀᴀᴛᴏʀ ᴏғ ᴛʜᴇ ᴄʜᴀᴛ", + "ᴄʜᴀᴛ not ғᴏᴜɴᴅ", + "ɴᴏᴛ ᴇɴᴏᴜɢʜ ʀɪɢʜᴛs ᴛᴏ ʀᴇsᴛʀɪᴄᴛ/ᴜɴʀᴇsᴛʀɪᴄᴛ ᴄʜᴀᴛ ᴍᴇᴍʙᴇʀ", + "User_not_participant", + "Peer_id_invalid", + "ɢʀᴏᴜᴘ ᴄʜᴀᴛ ᴡᴀs ᴅᴇᴀᴄᴛɪᴠᴀᴛᴇᴅ", + "ɴᴇᴇᴅ ᴛᴏ ʙᴇ ɪɴᴠɪᴛᴇʀ ᴏғ ᴀ user ᴛᴏ ᴘᴜɴᴄʜ ɪᴛ ғʀᴏᴍ ᴀ ʙᴀsɪᴄ ɢʀᴏᴜᴘ", + "Chat_admin_required", + "ᴏɴʟʏ ᴛʜᴇ ᴄʀᴇᴀᴛᴏʀ ᴏғ ᴀ ʙᴀsɪᴄ ɢʀᴏᴜᴘ ᴄᴀɴ ᴘᴜɴᴄʜ ɢʀᴏᴜᴘ ᴀᴅᴍɪɴɪsᴛʀᴀᴛᴏʀs", + "Channel_private", + "ɴᴏᴛ ɪɴ ᴛʜᴇ ᴄʜᴀᴛ", +} + +RUNBAN_ERRORS = { + "User ɪs ᴀɴ ᴀᴅᴍɪɴɪsᴛʀᴀᴛᴏʀ ᴏғ ᴛʜᴇ ᴄʜᴀᴛ", + "ᴄʜᴀᴛ ɴᴏᴛ ғᴏᴜɴᴅ", + "ɴᴏᴛ ᴇɴᴏᴜɢʜ ʀɪɢʜᴛs ᴛᴏ ʀᴇsᴛʀɪᴄᴛ/ᴜɴʀᴇsᴛʀɪᴄᴛ ᴄʜᴀᴛ ᴍᴇᴍʙᴇʀ", + "User_not_ᴘᴀʀᴛɪᴄɪᴘᴀɴᴛ", + "Peer_id_invalid", + "ɢʀᴏᴜᴘ ᴄʜᴀᴛ ᴡᴀs ᴅᴇᴀᴄᴛɪᴠᴀᴛᴇᴅ", + "ɴᴇᴇᴅ ᴛᴏ ʙᴇ ɪɴᴠɪᴛᴇʀ ᴏғ ᴀ ᴜsᴇʀ ᴛᴏ ᴘᴜɴᴄʜ ɪᴛ ғʀᴏᴍ a ʙᴀsɪᴄ ɢʀᴏᴜᴘ", + "Chat_admin_required", + "ᴏɴʟʏ ᴛʜᴇ ᴄʀᴇᴀᴛᴏʀ ᴏғ ᴀ ʙᴀsɪᴄ ɢʀᴏᴜᴘ ᴄᴀɴ ᴘᴜɴᴄʜ ɢʀᴏᴜᴘ ᴀᴅᴍɪɴɪsᴛʀᴀᴛᴏʀs", + "Channel_private", + "ɴᴏᴛ ɪɴ ᴛʜᴇ ᴄʜᴀᴛ", +} + +RKICK_ERRORS = { + "ᴜsᴇʀ ɪs ᴀɴ ᴀᴅᴍɪɴɪsᴛʀᴀᴛᴏʀ ᴏғ ᴛʜᴇ ᴄʜᴀᴛ", + "ᴄʜᴀᴛ ɴᴏᴛ ғᴏᴜɴᴅ", + "ɴᴏᴛ ᴇɴᴏᴜɢʜ ʀɪɢʜᴛs ᴛᴏ ʀᴇsᴛʀɪᴄᴛ/ᴜɴʀᴇsᴛʀɪᴄᴛ ᴄʜᴀᴛ ᴍᴇᴍʙᴇʀ", + "ᴜsᴇʀ_ɴᴏᴛ_ᴘᴀʀᴛɪᴄɪᴘᴀɴᴛ", + "ᴘᴇᴇʀ_ɪᴅ_ɪɴᴠᴀʟɪᴅ", + "ɢʀᴏᴜᴘ ᴄʜᴀᴛ ᴡᴀs ᴅᴇᴀᴄᴛɪᴠᴀᴛᴇᴅ", + "ɴᴇᴇᴅ ᴛᴏ ʙᴇ ɪɴᴠɪᴛᴇʀ ᴏғ ᴀ ᴜsᴇʀ ᴛᴏ ᴘᴜɴᴄʜ ɪᴛ ғʀᴏᴍ ᴀ ʙᴀsɪᴄ ɢʀᴏᴜᴘ", + "ᴄʜᴀᴛ_ᴀᴅᴍɪɴ_ʀᴇǫᴜɪʀᴇᴅ", + "ᴏɴʟʏ ᴛʜᴇ ᴄʀᴇᴀᴛᴏʀ ᴏғ ᴀ ʙᴀsɪᴄ ɢʀᴏᴜᴘ ᴄᴀɴ ᴘᴜɴᴄʜ ɢʀᴏᴜᴘ ᴀᴅᴍɪɴɪsᴛʀᴀᴛᴏʀs", + "Channel_ᴘʀɪᴠᴀᴛᴇ", + "ɴᴏᴛ ɪɴ ᴛʜᴇ ᴄʜᴀᴛ", +} + +RMUTE_ERRORS = { + "ᴜsᴇʀ ɪs ᴀɴ ᴀᴅᴍɪɴɪsᴛʀᴀᴛᴏʀ ᴏғ ᴛʜᴇ ᴄʜᴀᴛ", + "ᴄʜᴀᴛ ɴᴏᴛ ғᴏᴜɴᴅ", + "ɴᴏᴛ ᴇɴᴏᴜɢʜ ʀɪɢʜᴛs ᴛᴏ ʀᴇsᴛʀɪᴄᴛ/ᴜɴʀᴇsᴛʀɪᴄᴛ ᴄʜᴀᴛ ᴍᴇᴍʙᴇʀ", + "ᴜsᴇʀ_ɴᴏᴛ_ᴘᴀʀᴛɪᴄɪᴘᴀɴᴛ", + "ᴘᴇᴇʀ_ɪᴅ_ɪɴᴠᴀʟɪᴅ", + "ɢʀᴏᴜᴘ ᴄʜᴀᴛ ᴡᴀs ᴅᴇᴀᴄᴛɪᴠᴀᴛᴇᴅ", + "ɴᴇᴇᴅ ᴛᴏ ʙᴇ ɪɴᴠɪᴛᴇʀ ᴏғ ᴀ ᴜsᴇʀ ᴛᴏ ᴘᴜɴᴄʜ ɪᴛ ғʀᴏᴍ ᴀ ʙᴀsɪᴄ ɢʀᴏᴜᴘ", + "ᴄʜᴀᴛ_ᴀᴅᴍɪɴ_ʀᴇǫᴜɪʀᴇᴅ", + "ᴏɴʟʏ ᴛʜᴇ ᴄʀᴇᴀᴛᴏʀ ᴏғ ᴀ ʙᴀsɪᴄ ɢʀᴏᴜᴘ ᴄᴀɴ ᴘᴜɴᴄʜ ɢʀᴏᴜᴘ ᴀᴅᴍɪɴɪsᴛʀᴀᴛᴏʀs", + "ᴄʜᴀɴɴᴇʟ_ᴘʀɪᴠᴀᴛᴇ", + "ɴᴏᴛ ɪɴ ᴛʜᴇ ᴄʜᴀᴛ", +} + +RUNMUTE_ERRORS = { + "User is an administrator of the chat", + "Chat not found", + "Not enough rights to restrict/unrestrict chat member", + "User_not_participant", + "Peer_id_invalid", + "Group chat was deactivated", + "Need to be inviter of a user to punch it from a basic group", + "Chat_admin_required", + "Only the creator of a basic group can punch group administrators", + "Channel_private", + "Not in the chat", +} + + +@bot_admin +def rban(update: Update, context: CallbackContext): + bot, args = context.bot, context.args + message = update.effective_message + + if not args: + message.reply_text("ʏᴏᴜ ᴅᴏɴ'ᴛ sᴇᴇᴍ ᴛᴏ ʙᴇ ʀᴇғᴇʀʀɪɴɢ ᴛᴏ a ᴄʜᴀᴛ/ᴜsᴇʀ.") + return + + user_id, chat_id = extract_user_and_text(message, args) + + if not user_id: + message.reply_text( + "ʏᴏᴜ ᴅᴏɴ'ᴛ sᴇᴇᴍ ᴛᴏ ʙᴇ ʀᴇғᴇʀʀɪɴɢ ᴛᴏ ᴀ ᴜsᴇʀ ᴏʀ ᴛʜᴇ ɪᴅ sᴘᴇᴄɪғɪᴇᴅ ɪs ɪɴᴄᴏʀʀᴇᴄᴛ..", + ) + return + if not chat_id: + message.reply_text("ʏᴏᴜ ᴅᴏɴ'ᴛ sᴇᴇᴍ ᴛᴏ ʙᴇ ʀᴇғᴇʀʀɪɴɢ ᴛᴏ ᴀ ᴄʜᴀᴛ.") + return + + try: + chat = bot.get_chat(chat_id.split()[0]) + except BadRequest as excp: + if excp.message == "ᴄʜᴀᴛ ɴᴏᴛ ғᴏᴜɴᴅ": + message.reply_text( + "ᴄʜᴀᴛ ɴᴏᴛ ғᴏᴜɴᴅ! ᴍᴀᴋᴇ sᴜʀᴇ ʏᴏᴜ ᴇɴᴛᴇʀᴇᴅ ᴀ ᴠᴀʟɪᴅ ᴄʜᴀᴛ ID ᴀɴᴅ I'm ᴘᴀʀᴛ ᴏғ ᴛʜᴀᴛ ᴄʜᴀᴛ.", + ) + return + raise + + if chat.type == "private": + message.reply_text("I'ᴍ sᴏʀʀʏ, ʙᴜᴛ ᴛʜᴀᴛ ᴀ ᴘʀɪᴠᴀᴛᴇ ᴄʜᴀᴛ!") + return + + if ( + not is_bot_admin(chat, bot.id) + or not chat.get_member(bot.id).can_restrict_members + ): + message.reply_text( + "I ᴄᴀɴ'ᴛ ʀᴇsᴛʀɪᴄᴛ ᴘᴇᴏᴘʟᴇ ᴛʜᴇʀᴇ! ᴍᴀᴋᴇ sᴜʀᴇ I'ᴍ ᴀᴅᴍɪɴ ᴀɴᴅ ᴄᴀɴ ʙᴀɴ ᴜsᴇʀs.", + ) + return + + try: + member = chat.get_member(user_id) + except BadRequest as excp: + if excp.message == "ᴜsᴇʀ ɴᴏᴛ ғᴏᴜɴᴅ": + message.reply_text("ɪ ᴄᴀɴ'ᴛ sᴇᴇᴍ ᴛᴏ ғɪɴᴅ ᴛʜɪs ᴜsᴇʀ") + return + raise + + if is_user_ban_protected(chat, user_id, member): + message.reply_text("I ʀᴇᴀʟʟʏ ᴡɪsʜ I ᴄᴏᴜʟᴅ ʙᴀɴ ᴀᴅᴍɪɴs...") + return + + if user_id == bot.id: + message.reply_text("I'm ɴᴏᴛ ɢᴏɴɴᴀ ʙᴀɴ ᴍʏsᴇʟғ, ᴀʀᴇ ʏᴏᴜ ᴄʀᴀᴢʏ?") + return + + try: + chat.ban_member(user_id) + message.reply_text("ʙᴀɴɴᴇᴅ ғʀᴏᴍ chat!") + except BadRequest as excp: + if excp.message == "ʀᴇᴘʟʏ ᴍᴇssᴀɢᴇ ɴᴏᴛ ғᴏᴜɴᴅ": + # Do not reply + message.reply_text("Banned!", quote=False) + elif excp.message in RBAN_ERRORS: + message.reply_text(excp.message) + else: + LOGGER.warning(update) + LOGGER.exception( + "ᴇʀʀᴏʀ ʙᴀɴɴɪɴɢ ᴜsᴇʀ %s ɪɴ ᴄʜᴀᴛ %s (%s) ᴅᴜᴇ ᴛᴏ %s", + user_id, + chat.title, + chat.id, + excp.message, + ) + message.reply_text("ᴡᴇʟʟ ᴅᴀᴍɴ, I ᴄᴀɴ'ᴛ ʙᴀɴ ᴛʜᴀᴛ ᴜsᴇʀ.") + + +@bot_admin +def runban(update: Update, context: CallbackContext): + bot, args = context.bot, context.args + message = update.effective_message + + if not args: + message.reply_text("ʏᴏᴜ ᴅᴏɴ'ᴛ sᴇᴇᴍ ᴛᴏ ʙᴇ ʀᴇғᴇʀʀɪɴɢ ᴛᴏ a ᴄʜᴀᴛ/ᴜsᴇʀ.") + return + + user_id, chat_id = extract_user_and_text(message, args) + + if not user_id: + message.reply_text( + "ʏᴏᴜ ᴅᴏɴ'ᴛ sᴇᴇᴍ ᴛᴏ ʙᴇ ʀᴇғᴇʀʀɪɴɢ ᴛᴏ ᴀ ᴜsᴇʀ ᴏʀ ᴛʜᴇ ɪᴅ sᴘᴇᴄɪғɪᴇᴅ ɪs ɪɴᴄᴏʀʀᴇᴄᴛ..", + ) + return + if not chat_id: + message.reply_text("ʏᴏᴜ ᴅᴏɴ'ᴛ sᴇᴇᴍ ᴛᴏ ʙᴇ ʀᴇғᴇʀʀɪɴɢ ᴛᴏ ᴀ ᴄʜᴀᴛ.") + return + + try: + chat = bot.get_chat(chat_id.split()[0]) + except BadRequest as excp: + if excp.message == "Chat not found": + message.reply_text( + "ᴄʜᴀᴛ ɴᴏᴛ ғᴏᴜɴᴅ! ᴍᴀᴋᴇ sᴜʀᴇ ʏᴏᴜ ᴇɴᴛᴇʀᴇᴅ ᴀ ᴠᴀʟɪᴅ ᴄʜᴀᴛ ɪᴅ ᴀɴᴅ I'ᴍ ᴘᴀʀᴛ ᴏғ ᴛʜᴀᴛ ᴄʜᴀᴛ.", + ) + return + raise + + if chat.type == "private": + message.reply_text("I'ᴍ sᴏʀʀʏ, ʙᴜᴛ ᴛʜᴀᴛ's ᴀ ᴘʀɪᴠᴀᴛᴇ ᴄʜᴀᴛ!") + return + + if ( + not is_bot_admin(chat, bot.id) + or not chat.get_member(bot.id).can_restrict_members + ): + message.reply_text( + "I ᴄᴀɴ'ᴛ ᴜɴʀᴇsᴛʀɪᴄᴛ ᴘᴇᴏᴘʟᴇ ᴛʜᴇʀᴇ! ᴍᴀᴋᴇ sᴜʀᴇ I'm ᴀᴅᴍɪɴ ᴀɴᴅ ᴄᴀɴ ᴜɴʙᴀɴ ᴜsᴇʀs.", + ) + return + + try: + chat.get_member(user_id) + except BadRequest as excp: + if excp.message == "ᴜsᴇʀ ɴᴏᴛ ғᴏᴜɴᴅ": + message.reply_text("I ᴄᴀɴ'ᴛ sᴇᴇᴍ ᴛᴏ ғɪɴᴅ ᴛʜɪs ᴜsᴇʀ ᴛʜᴇʀᴇ") + return + raise + + if is_user_in_chat(chat, user_id): + message.reply_text( + "ᴡʜʏ ᴀʀᴇ ʏᴏᴜ ᴛʀʏɪɴɢ ᴛᴏ ʀᴇᴍᴏᴛᴇʟʏ ᴜɴʙᴀɴ sᴏᴍᴇᴏɴᴇ ᴛʜᴀᴛ's ᴀʟʀᴇᴀᴅʏ ɪɴ thʏat ᴄʜᴀᴛ?", + ) + return + + if user_id == bot.id: + message.reply_text("I'm not ɢᴏɴɴᴀ UNBAN ᴍʏsᴇʟғ, I'm ᴀɴ ᴀᴅᴍɪɴ ᴛʜᴇʀᴇ!") + return + + try: + chat.unban_member(user_id) + message.reply_text("ʏᴇᴘ, ᴛʜɪs ᴜsᴇʀ ᴄᴀɴ ᴊᴏɪɴ ᴛʜᴀᴛ ᴄʜᴀᴛ!") + except BadRequest as excp: + if excp.message == "ʀᴇᴘʟʏ ᴍᴇssᴀɢᴇ ɴᴏᴛ ғᴏᴜɴᴅ": + # Do not reply + message.reply_text("Unbanned!", quote=False) + elif excp.message in RUNBAN_ERRORS: + message.reply_text(excp.message) + else: + LOGGER.warning(update) + LOGGER.exception( + "ERROR ᴜɴʙᴀɴɴɪɴɢ ᴜsᴇʀ %s ɪɴ ᴄʜᴀᴛ %s (%s) ᴅᴜᴇ ᴛᴏ %s", + user_id, + chat.title, + chat.id, + excp.message, + ) + message.reply_text("ᴡᴇʟʟ ᴅᴀᴍɴ, I ᴄᴀɴ'ᴛ ᴜɴʙᴀɴ ᴛʜᴀᴛ ᴜsᴇʀ.") + + +@bot_admin +def rkick(update: Update, context: CallbackContext): + bot, args = context.bot, context.args + message = update.effective_message + + if not args: + message.reply_text("ʏᴏᴜ ᴅᴏɴ'ᴛ sᴇᴇᴍ ᴛᴏ ʙᴇ ʀᴇғᴇʀʀɪɴɢ ᴛᴏ ᴀ ᴄʜᴀᴛ/ᴜsᴇʀ.") + return + + user_id, chat_id = extract_user_and_text(message, args) + + if not user_id: + message.reply_text( + "ʏᴏᴜ ᴅᴏɴ'ᴛ sᴇᴇᴍ ᴛᴏ ʙᴇ ʀᴇғᴇʀʀɪɴɢ ᴛᴏ ᴀ ᴜsᴇʀ ᴏʀ ᴛʜᴇ ɪᴅ sᴘᴇᴄɪғɪᴇᴅ ɪs ɪɴᴄᴏʀʀᴇᴄᴛ..", + ) + return + if not chat_id: + message.reply_text("ʏᴏᴜ ᴅᴏɴ'ᴛ sᴇᴇᴍ ᴛᴏ ʙᴇ ʀᴇғᴇʀʀɪɴɢ ᴛᴏ a ᴄʜᴀᴛ.") + return + + try: + chat = bot.get_chat(chat_id.split()[0]) + except BadRequest as excp: + if excp.message == "ᴄʜᴀᴛ ɴᴏᴛ ғᴏᴜɴᴅ": + message.reply_text( + "ᴄʜᴀᴛ ɴᴏᴛ ғᴏᴜɴᴅ! ᴍᴀᴋᴇ sᴜʀᴇ ʏᴏᴜ ᴇɴᴛᴇʀᴇᴅ ᴀ ᴠᴀʟɪᴅ ᴄʜᴀᴛ ɪᴅ ᴀɴᴅ I'ᴍ ᴘᴀʀᴛ ᴏғ ᴛʜᴀᴛ ᴄʜᴀᴛ.", + ) + return + raise + + if chat.type == "private": + message.reply_text("I'ᴍ sᴏʀʀʏ, ʙᴜᴛ ᴛʜᴀᴛ's a ᴘʀɪᴠᴀᴛᴇ ᴄʜᴀᴛ!") + return + + if ( + not is_bot_admin(chat, bot.id) + or not chat.get_member(bot.id).can_restrict_members + ): + message.reply_text( + "I ᴄᴀɴ'ᴛ ʀᴇsᴛʀɪᴄᴛ ᴘᴇᴏᴘʟᴇ ᴛʜᴇʀᴇ! ᴍᴀᴋᴇ sᴜʀᴇ I'ᴍ ᴀᴅᴍɪɴ ᴀɴᴅ ᴄᴀɴ ᴘᴜɴᴄʜ ᴜsᴇʀs.", + ) + return + + try: + member = chat.get_member(user_id) + except BadRequest as excp: + if excp.message == "ᴜsᴇʀ ɴᴏᴛ ғᴏᴜɴᴅ": + message.reply_text("I ᴄᴀɴ'ᴛ sᴇᴇᴍ ᴛᴏ ғɪɴᴅ ᴛʜɪs ᴜsᴇʀ") + return + raise + + if is_user_ban_protected(chat, user_id, member): + message.reply_text("I really wish I could punch admins...") + return + + if user_id == bot.id: + message.reply_text("I'ᴍ ɴᴏᴛ ɢᴏɴɴᴀ ᴘᴜɴᴄʜ ᴍʏsᴇʟғ, ᴀʀᴇ ʏᴏᴜ ᴄʀᴀᴢʏ?") + return + + try: + chat.unban_member(user_id) + message.reply_text("ᴘᴜɴᴄʜᴇᴅ ғʀᴏᴍ ᴄʜᴀᴛ!") + except BadRequest as excp: + if excp.message == "ʀᴇᴘʟʏ ᴍᴇssᴀɢᴇ ɴᴏᴛ ғᴏᴜɴᴅ": + # Do not reply + message.reply_text("ᴘᴜɴᴄʜᴇᴅ !", quote=False) + elif excp.message in RKICK_ERRORS: + message.reply_text(excp.message) + else: + LOGGER.warning(update) + LOGGER.exception( + "ERROR ᴘᴜɴᴄʜɪɴɢ ᴜsᴇʀ %s ɪɴ ᴄʜᴀᴛ %s (%s) ᴅᴜᴇ ᴛᴏ %s", + user_id, + chat.title, + chat.id, + excp.message, + ) + message.reply_text("ᴡᴇʟʟ ᴅᴀᴍɴ, I ᴄᴀɴ'ᴛ ᴘᴜɴᴄʜ ᴛʜᴀᴛ ᴜsᴇʀ.") + + +@bot_admin +def rmute(update: Update, context: CallbackContext): + bot, args = context.bot, context.args + message = update.effective_message + + if not args: + message.reply_text("ʏᴏᴜ ᴅᴏɴ'ᴛ sᴇᴇᴍ ᴛᴏ ʙᴇ ʀᴇғᴇʀʀɪɴɢ ᴛᴏ ᴀ ᴄʜᴀᴛ/ᴜsᴇʀ.") + return + + user_id, chat_id = extract_user_and_text(message, args) + + if not user_id: + message.reply_text( + "You don't sᴇᴇᴍ to ʙᴇ ʀᴇғᴇʀʀɪɴɢ to ᴀ ᴜsᴇʀ ᴏʀ ᴛʜᴇ ɪᴅ sᴘᴇᴄɪғɪᴇᴅ is ɪɴᴄᴏʀʀᴇᴄᴛ..", + ) + return + if not chat_id: + message.reply_text("You don't seem to be referring to a chat.") + return + + try: + chat = bot.get_chat(chat_id.split()[0]) + except BadRequest as excp: + if excp.message == "Chat not found": + message.reply_text( + "Chat not found! Make sure you entered a valid chat ID and I'm part of that chat.", + ) + return + raise + + if chat.type == "private": + message.reply_text("I'm sorry, but that's a private chat!") + return + + if ( + not is_bot_admin(chat, bot.id) + or not chat.get_member(bot.id).can_restrict_members + ): + message.reply_text( + "I can't restrict people there! Make sure I'm admin and can mute users.", + ) + return + + try: + member = chat.get_member(user_id) + except BadRequest as excp: + if excp.message == "User not found": + message.reply_text("I can't seem to find this user") + return + raise + + if is_user_ban_protected(chat, user_id, member): + message.reply_text("I really wish I could mute admins...") + return + + if user_id == bot.id: + message.reply_text("I'm not gonna MUTE myself, are you crazy?") + return + + try: + bot.restrict_chat_member( + chat.id, + user_id, + permissions=ChatPermissions(can_send_messages=False), + ) + message.reply_text("Muted from the chat!") + except BadRequest as excp: + if excp.message == "Reply message not found": + # Do not reply + message.reply_text("Muted!", quote=False) + elif excp.message in RMUTE_ERRORS: + message.reply_text(excp.message) + else: + LOGGER.warning(update) + LOGGER.exception( + "ERROR mute user %s in chat %s (%s) due to %s", + user_id, + chat.title, + chat.id, + excp.message, + ) + message.reply_text("Well damn, I can't mute that user.") + + +@bot_admin +def runmute(update: Update, context: CallbackContext): + bot, args = context.bot, context.args + message = update.effective_message + + if not args: + message.reply_text("You don't seem to be referring to a chat/user.") + return + + user_id, chat_id = extract_user_and_text(message, args) + + if not user_id: + message.reply_text( + "You don't seem to be referring to a user or the ID specified is incorrect..", + ) + return + if not chat_id: + message.reply_text("You don't seem to be referring to a chat.") + return + + try: + chat = bot.get_chat(chat_id.split()[0]) + except BadRequest as excp: + if excp.message == "Chat not found": + message.reply_text( + "Chat not found! Make sure you entered a valid chat ID and I'm part of that chat.", + ) + return + raise + + if chat.type == "private": + message.reply_text("I'm sorry, but that's a private chat!") + return + + if ( + not is_bot_admin(chat, bot.id) + or not chat.get_member(bot.id).can_restrict_members + ): + message.reply_text( + "I can't unrestrict people there! Make sure I'm admin and can unban users.", + ) + return + + try: + member = chat.get_member(user_id) + except BadRequest as excp: + if excp.message == "User not found": + message.reply_text("I can't seem to find this user there") + return + raise + + if is_user_in_chat(chat, user_id) and ( + member.can_send_messages + and member.can_send_media_messages + and member.can_send_other_messages + and member.can_add_web_page_previews + ): + message.reply_text("This user already has the right to speak in that chat.") + return + + if user_id == bot.id: + message.reply_text("I'm not gonna UNMUTE myself, I'm an admin there!") + return + + try: + bot.restrict_chat_member( + chat.id, + int(user_id), + permissions=ChatPermissions( + can_send_messages=True, + can_send_media_messages=True, + can_send_other_messages=True, + can_add_web_page_previews=True, + ), + ) + message.reply_text("ʏᴇᴘ, ᴛʜɪs ᴜsᴇʀ ᴄᴀɴ ᴛᴀʟᴋ ɪɴ ᴛʜᴀᴛ ᴄʜᴀᴛ!") + except BadRequest as excp: + if excp.message == "ʀᴇᴘʟʏ ᴍᴇssᴀɢᴇ ɴᴏᴛ ғᴏᴜɴᴅ": + # Do not reply + message.reply_text("Unmuted!", quote=False) + elif excp.message in RUNMUTE_ERRORS: + message.reply_text(excp.message) + else: + LOGGER.warning(update) + LOGGER.exception( + "ERROR ᴜɴᴍɴᴜᴛɪɴɢ ᴜsᴇʀ %s ɪɴ ᴄʜᴀᴛ %s (%s) ᴅᴜᴇ ᴛᴏ %s", + user_id, + chat.title, + chat.id, + excp.message, + ) + message.reply_text("ᴡᴇʟʟ ᴅᴀᴍɴ, I ᴄᴀɴ'ᴛ ᴜɴᴍᴜᴛᴇ ᴛʜᴀᴛ ᴜsᴇʀ.") + + +RBAN_HANDLER = CommandHandler( + "rban", rban, filters=CustomFilters.sudo_filter, run_async=True +) +RUNBAN_HANDLER = CommandHandler( + "runban", runban, filters=CustomFilters.sudo_filter, run_async=True +) +RKICK_HANDLER = CommandHandler( + "rpunch", rkick, filters=CustomFilters.sudo_filter, run_async=True +) +RMUTE_HANDLER = CommandHandler( + "rmute", rmute, filters=CustomFilters.sudo_filter, run_async=True +) +RUNMUTE_HANDLER = CommandHandler( + "runmute", runmute, filters=CustomFilters.sudo_filter, run_async=True +) + +dispatcher.add_handler(RBAN_HANDLER) +dispatcher.add_handler(RUNBAN_HANDLER) +dispatcher.add_handler(RKICK_HANDLER) +dispatcher.add_handler(RMUTE_HANDLER) +dispatcher.add_handler(RUNMUTE_HANDLER) diff --git a/Exon/modules/reporting.py b/Exon/modules/reporting.py new file mode 100644 index 00000000..72bec7b2 --- /dev/null +++ b/Exon/modules/reporting.py @@ -0,0 +1,324 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import html + +from telegram import Chat, InlineKeyboardButton, InlineKeyboardMarkup, ParseMode, Update +from telegram.error import BadRequest, Unauthorized +from telegram.ext import ( + CallbackContext, + CallbackQueryHandler, + CommandHandler, + Filters, + MessageHandler, +) +from telegram.utils.helpers import mention_html + +from Exon import DRAGONS, LOGGER, TIGERS, WOLVES, dispatcher +from Exon.modules.helper_funcs.chat_status import user_admin, user_not_admin +from Exon.modules.log_channel import loggable +from Exon.modules.sql import reporting_sql as sql + +REPORT_GROUP = 12 +REPORT_IMMUNE_USERS = DRAGONS + TIGERS + WOLVES + + +@user_admin +def report_setting(update: Update, context: CallbackContext): + bot, args = context.bot, context.args + chat = update.effective_chat + msg = update.effective_message + + if chat.type == chat.PRIVATE: + if len(args) >= 1: + if args[0] in ("yes", "on"): + sql.set_user_setting(chat.id, True) + msg.reply_text( + "ᴛᴜʀɴᴇᴅ ᴏɴ ʀᴇᴘᴏʀᴛɪɴɢ! ʏᴏᴜ'ʟʟ ʙᴇ ɴᴏᴛɪғɪᴇᴅ ᴡʜᴇɴᴇᴠᴇʀ anʏyone ʀᴇᴘᴏʀᴛs sᴏᴍᴇᴛʜɪɴɢ.", + ) + + elif args[0] in ("no", "off"): + sql.set_user_setting(chat.id, False) + msg.reply_text("ᴛᴜʀɴᴇᴅ ᴏғғ ʀᴇᴘᴏʀᴛɪɴɢ! ʏᴏᴜ ᴡᴏɴ'ᴛ ɢᴇᴛ ᴀɴʏ ʀᴇᴘᴏʀᴛs.") + else: + msg.reply_text( + f"ʏᴏᴜʀ ᴄᴜʀʀᴇɴᴛ ʀᴇᴘᴏʀᴛ ᴘʀᴇғᴇʀᴇɴᴄᴇ ɪs: `{sql.user_should_report(chat.id)}`", + parse_mode=ParseMode.MARKDOWN, + ) + + elif len(args) >= 1: + if args[0] in ("yes", "on"): + sql.set_chat_setting(chat.id, True) + msg.reply_text( + "ᴛᴜʀɴᴇᴅ ᴏɴ ʀᴇᴘᴏʀᴛɪɴɢ! ᴀᴅᴍɪɴs ᴡʜᴏ ʜᴀᴠᴇ ᴛᴜʀɴᴇᴅ ᴏɴ ʀᴇᴘᴏʀᴛs ᴡɪʟʟ ʙᴇ ɴᴏᴛɪғɪᴇᴅ ᴡʜᴇɴ /report " + "ᴏʀ @admin ɪs ᴄᴀʟʟᴇᴅ.", + ) + + elif args[0] in ("no", "off"): + sql.set_chat_setting(chat.id, False) + msg.reply_text( + "ᴛᴜʀɴᴇᴅ ᴏғғ ʀᴇᴘᴏʀᴛɪɴɢ! ɴᴏ ᴀᴅᴍɪɴs ᴡɪʟʟ ʙᴇ ɴᴏᴛɪғɪᴇᴅ ᴏɴ /ʀᴇᴘᴏʀᴛ ᴏʀ @admin.", + ) + else: + msg.reply_text( + f"ᴛʜɪs ɢʀᴏᴜᴘ's ᴄᴜʀʀᴇɴᴛ sᴇᴛᴛɪɴɢ ɪs: `{sql.chat_should_report(chat.id)}`", + parse_mode=ParseMode.MARKDOWN, + ) + + +@user_not_admin +@loggable +def report(update: Update, context: CallbackContext) -> str: + bot = context.bot + args = context.args + message = update.effective_message + chat = update.effective_chat + user = update.effective_user + + if chat and message.reply_to_message and sql.chat_should_report(chat.id): + reported_user = message.reply_to_message.from_user + chat_name = chat.title or chat.first or chat.username + admin_list = chat.get_administrators() + message = update.effective_message + + if not args: + message.reply_text("ᴀᴅᴅ ᴀ ʀᴇᴀsᴏɴ ғᴏʀ ʀᴇᴘᴏʀᴛɪɴɢ ғɪʀsᴛ.") + return "" + + if user.id == reported_user.id: + message.reply_text("ᴜʜ ʏᴇᴀʜ, sᴜʀᴇ sure...ᴍᴀsᴏ ᴍᴜᴄʜ?") + return "" + + if user.id == bot.id: + message.reply_text("ɴɪᴄᴇ ᴛʀʏ, ʙʀᴏ.") + return "" + + if reported_user.id in REPORT_IMMUNE_USERS: + message.reply_text("Uh? ʏᴏᴜ ʀᴇᴘᴏʀᴛɪɴɢ ᴀ ᴅɪsᴀsᴛᴇʀ?") + return "" + + if chat.username and chat.type == Chat.SUPERGROUP: + + reported = f"{mention_html(user.id, user.first_name)} ʀᴇᴘᴏʀᴛᴇᴅ {mention_html(reported_user.id, reported_user.first_name)} ᴛᴏ ᴛʜᴇ ᴀᴅᴍɪɴs!" + + msg = ( + f"<b>⚠️ ʀᴇᴘᴏʀᴛ ɪɴ {html.escape(chat.title)}</b>\n\n" + f"<b>- ʀᴇᴘᴏʀᴛ ʙʏ:</b> {mention_html(user.id, user.first_name)} (<code>{user.id}</code>)\n" + f"<b>- ʀᴇᴘᴏʀᴛᴇᴅ ᴜsᴇʀ:</b> {mention_html(reported_user.id, reported_user.first_name)} (<code>{reported_user.id}</code>)\n" + ) + link = f'<b>- ʀᴇᴘᴏʀᴛᴇᴅ ᴍᴇssᴀɢᴇ:</b> <a href="https://t.me/{chat.username}/{message.reply_to_message.message_id}">Click Here</a>' + should_forward = False + keyboard = [ + [ + InlineKeyboardButton( + "➡ ᴍᴇssᴀɢᴇ", + url=f"https://t.me/{chat.username}/{message.reply_to_message.message_id}", + ), + ], + [ + InlineKeyboardButton( + "⚠ ᴋɪᴄᴋ", + callback_data=f"report_{chat.id}=kick={reported_user.id}={reported_user.first_name}", + ), + InlineKeyboardButton( + "⛔️ ʙᴀɴ", + callback_data=f"report_{chat.id}=banned={reported_user.id}={reported_user.first_name}", + ), + ], + [ + InlineKeyboardButton( + "❎ ᴅᴇʟᴇᴛᴇ ᴍᴇssᴀɢᴇ", + callback_data=f"report_{chat.id}=delete={reported_user.id}={message.reply_to_message.message_id}", + ), + ], + ] + reply_markup = InlineKeyboardMarkup(keyboard) + else: + reported = ( + f"{mention_html(user.id, user.first_name)} reported " + f"{mention_html(reported_user.id, reported_user.first_name)} ᴛᴏ ᴛʜᴇ ᴀᴅᴍɪɴs!" + ) + + msg = f'{mention_html(user.id, user.first_name)} ɪs ᴄᴀʟʟɪɴɢ ғᴏʀ ᴀᴅᴍɪɴs ɪɴ "{html.escape(chat_name)}"!' + link = "" + should_forward = True + + for admin in admin_list: + if admin.user.is_bot: # can't message bots + continue + + if sql.user_should_report(admin.user.id): + try: + if chat.type != Chat.SUPERGROUP: + bot.send_message( + admin.user.id, + msg + link, + parse_mode=ParseMode.HTML, + ) + + if should_forward: + message.reply_to_message.forward(admin.user.id) + + if ( + len(message.text.split()) > 1 + ): # If user is giving a reason, send his message too + message.forward(admin.user.id) + if not chat.username: + bot.send_message( + admin.user.id, + msg + link, + parse_mode=ParseMode.HTML, + ) + + if should_forward: + message.reply_to_message.forward(admin.user.id) + + if ( + len(message.text.split()) > 1 + ): # If user is giving a reason, send his message too + message.forward(admin.user.id) + + if chat.username and chat.type == Chat.SUPERGROUP: + bot.send_message( + admin.user.id, + msg + link, + parse_mode=ParseMode.HTML, + reply_markup=reply_markup, + ) + + if should_forward: + message.reply_to_message.forward(admin.user.id) + + if ( + len(message.text.split()) > 1 + ): # If user is giving a reason, send his message too + message.forward(admin.user.id) + + except Unauthorized: + pass + except BadRequest as excp: # TODO: cleanup exceptions + LOGGER.exception("ᴇxᴄᴇᴘᴛɪᴏɴ ᴡʜɪʟᴇ ʀᴇᴘᴏʀᴛɪɴɢ ᴜsᴇʀ") + + message.reply_to_message.reply_text( + f"{mention_html(user.id, user.first_name)} ʀᴇᴘᴏʀᴛᴇᴅ ᴛʜᴇ ᴍᴇssᴀɢᴇ ᴛᴏ ᴛʜᴇ ᴀᴅᴍɪɴs.", + parse_mode=ParseMode.HTML, + ) + return msg + + return "" + + +def __migrate__(old_chat_id, new_chat_id): + sql.migrate_chat(old_chat_id, new_chat_id) + + +def __chat_settings__(chat_id, _): + return f"ᴛʜɪs ᴄʜᴀᴛ ɪs sᴇᴛᴜᴘ ᴛᴏ sᴇɴᴅ ᴜsᴇʀ ʀᴇᴘᴏʀᴛs ᴛᴏ ᴀᴅᴍɪɴs, ᴠɪᴀ /ʀᴇᴘᴏʀᴛ ᴀɴᴅ @admin: `{sql.chat_should_report(chat_id)}`" + + +def __user_settings__(user_id): + return ( + "ʏᴏᴜ ᴡɪʟʟ ʀᴇᴄᴇɪᴠᴇ ʀᴇᴘᴏʀᴛs ғʀᴏᴍ ᴄʜᴀᴛs ʏᴏᴜ'ʀᴇ ᴀᴅᴍɪɴ." + if sql.user_should_report(user_id) is True + else "ʏᴏᴜ ᴡɪʟʟ *ɴᴏᴛ* ʀᴇᴄᴇɪᴠᴇ ʀᴇᴘᴏʀᴛs ғʀᴏᴍ ᴄʜᴀᴛs ʏᴏᴜ'ʀᴇ ᴀᴅᴍɪɴ." + ) + + +def buttons(update: Update, context: CallbackContext): + bot = context.bot + query = update.callback_query + splitter = query.data.replace("report_", "").split("=") + if splitter[1] == "kick": + try: + bot.kickChatMember(splitter[0], splitter[2]) + bot.unbanChatMember(splitter[0], splitter[2]) + query.answer("✅ sᴜᴄᴄᴇsғᴜʟʟʏ ᴋɪᴄᴋᴇᴅ") + return "" + except Exception as err: + query.answer("🛑 ғᴀɪʟᴇᴅ ᴛᴏ ᴋɪᴄᴋ") + bot.sendMessage( + text=f"ᴇʀʀᴏʀ: {err}", + chat_id=query.message.chat_id, + parse_mode=ParseMode.HTML, + ) + elif splitter[1] == "banned": + try: + bot.kickChatMember(splitter[0], splitter[2]) + query.answer("✅ sᴜᴄᴄᴇsғᴜʟʟʏ ʙᴀɴɴᴇᴅ") + return "" + except Exception as err: + bot.sendMessage( + text=f"ᴇʀʀᴏʀ: {err}", + chat_id=query.message.chat_id, + parse_mode=ParseMode.HTML, + ) + query.answer("🛑 ғᴀɪʟᴇᴅ ᴛᴏ Ban") + elif splitter[1] == "delete": + try: + bot.deleteMessage(splitter[0], splitter[3]) + query.answer("✅ Message Deleted") + return "" + except Exception as err: + bot.sendMessage( + text=f"Error: {err}", + chat_id=query.message.chat_id, + parse_mode=ParseMode.HTML, + ) + query.answer("🛑 ғᴀɪʟᴇᴅ ᴛᴏ ᴅᴇʟᴇᴛᴇ ᴍᴇssᴀɢᴇ!") + + +__help__ = """ +⍟ /report <ʀᴇᴀsᴏɴ>*:* `ʀᴇᴘʟʏ ᴛᴏ ᴀ ᴍᴇssᴀɢᴇ ᴛᴏ ʀᴇᴘᴏʀᴛ ɪᴛ ᴛᴏ ᴀᴅᴍɪɴs.` + +⍟ @admins*:* `ʀᴇᴘʟʏ ᴛᴏ ᴀ ᴍᴇssᴀɢᴇ ᴛᴏ ʀᴇᴘᴏʀᴛ ɪᴛ ᴛᴏ ᴀᴅᴍɪɴs` +. +*ɴᴏᴛᴇ:* ɴᴇɪᴛʜᴇʀ ᴏғ ᴛʜᴇsᴇ ᴡɪʟʟ ɢᴇᴛ ᴛʀɪɢɢᴇʀᴇᴅ ɪғ ᴜsᴇᴅ ʙʏ ᴀᴅᴍɪɴs. + +*ᴀᴅᴍɪɴs ᴏɴʟʏ:* +• /reports <on/ᴏғғ>*:* ᴄʜᴀɴɢᴇ ʀᴇᴘᴏʀᴛ sᴇᴛᴛɪɴɢ, ᴏʀ ᴠɪᴇᴡ ᴄᴜʀʀᴇɴᴛ sᴛᴀᴛᴜs. + +➩ ɪғ ᴅᴏɴᴇ ɪɴ ᴘᴍ, ᴛᴏɢɢʟᴇs ʏᴏᴜʀ sᴛᴀᴛᴜs. +➩ If ɪɴ ɢʀᴏᴜᴘ, ᴛᴏɢɢʟᴇs ᴛʜᴀᴛ ɢʀᴏᴜᴘ's sᴛᴀᴛᴜs. +""" + +SETTING_HANDLER = CommandHandler("reports", report_setting, run_async=True) +REPORT_HANDLER = CommandHandler( + "report", report, filters=Filters.chat_type.groups, run_async=True +) +ADMIN_REPORT_HANDLER = MessageHandler( + Filters.regex(r"(?i)@admins(s)?"), report, run_async=True +) +REPORT_BUTTON_USER_HANDLER = CallbackQueryHandler(buttons, pattern=r"report_") + +dispatcher.add_handler(REPORT_BUTTON_USER_HANDLER) +dispatcher.add_handler(SETTING_HANDLER) +dispatcher.add_handler(REPORT_HANDLER, REPORT_GROUP) +dispatcher.add_handler(ADMIN_REPORT_HANDLER, REPORT_GROUP) + +__mod_name__ = "𝚁ᴇᴘᴏʀᴛ" +__handlers__ = [ + (REPORT_HANDLER, REPORT_GROUP), + (ADMIN_REPORT_HANDLER, REPORT_GROUP), + (SETTING_HANDLER), +] diff --git a/Exon/modules/reverse.py b/Exon/modules/reverse.py new file mode 100644 index 00000000..bff9dc79 --- /dev/null +++ b/Exon/modules/reverse.py @@ -0,0 +1,235 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import os +import re +import urllib +import urllib.parse +import urllib.request +from urllib.error import HTTPError, URLError + +import requests +from bs4 import BeautifulSoup +from telegram import InputMediaPhoto, TelegramError, Update +from telegram.ext import CallbackContext + +from Exon import dispatcher +from Exon.modules.disable import DisableAbleCommandHandler + +opener = urllib.request.build_opener() +useragent = "Mozilla/5.0 (Linux; Android 6.0.1; SM-G920V Build/MMB29K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.98 Mobile Safari/537.36" +opener.addheaders = [("User-agent", useragent)] + + +def reverse(update: Update, context: CallbackContext): + if os.path.isfile("okgoogle.png"): + os.remove("okgoogle.png") + + msg = update.effective_message + chat_id = update.effective_chat.id + bot, args = context.bot, context.args + rtmid = msg.message_id + imagename = "okgoogle.png" + + if reply := msg.reply_to_message: + if reply.sticker: + file_id = reply.sticker.file_id + elif reply.photo: + file_id = reply.photo[-1].file_id + elif reply.document: + file_id = reply.document.file_id + else: + msg.reply_text("ʀᴇᴘʟʏ ᴛᴏ ᴀɴ ɪᴍᴀɢᴇ ᴏʀ sᴛɪᴄᴋᴇʀ ᴛᴏ ʟᴏᴏᴋᴜᴘ.") + return + image_file = bot.get_file(file_id) + image_file.download(imagename) + if args: + txt = args[0] + try: + lim = int(txt) + except: + lim = 2 + else: + lim = 2 + elif args: + splatargs = msg.text.split(" ") + if len(splatargs) == 3: + img_link = splatargs[1] + try: + lim = int(splatargs[2]) + except: + lim = 2 + elif len(splatargs) == 2: + img_link = splatargs[1] + lim = 2 + else: + msg.reply_text("/reverse <link> <amount ᴏғ ɪᴍᴀɢᴇs ᴛᴏ ʀᴇᴛᴜʀɴ.>") + return + try: + urllib.request.urlretrieve(img_link, imagename) + except HTTPError as HE: + if HE.reason == "Not Found": + msg.reply_text("ɪᴍᴀɢᴇ ɴᴏᴛ ғᴏᴜɴᴅ.") + return + if HE.reason == "Forbidden": + msg.reply_text( + "ᴄᴏᴜʟᴅɴ'ᴛ ᴀᴄᴄᴇss ᴛʜᴇ ᴘʀᴏᴠɪᴅᴇᴅ ʟɪɴᴋ, ᴛʜᴇ ᴡᴇʙsɪᴛᴇ ᴍɪɢʜᴛ ʜᴀᴠᴇ ʙʟᴏᴄᴋᴇᴅ ᴀᴄᴄᴇssɪɴɢ ᴛᴏ ᴛʜᴇ website ʙʏ ʙᴏᴛ ᴏʀ ᴛʜᴇ ᴡᴇʙsɪᴛᴇ ᴅᴏᴇs ɴᴏᴛ ᴇxɪsᴛᴇᴅ." + ) + return + except URLError as UE: + msg.reply_text(f"{UE.reason}") + return + except ValueError as VE: + msg.reply_text(f"{VE}\nᴘʟᴇᴀsᴇ ᴛʀʏ ᴀɢᴀɪɴ ᴜsɪɴɢ ʜᴛᴛᴘ ᴏʀ ʜᴛᴛᴘs ᴘʀᴏᴛᴏᴄᴏʟ.") + return + else: + msg.reply_markdown( + "ᴘʟᴇᴀsᴇ ʀᴇᴘʟʏ ᴛᴏ ᴀ sᴛɪᴄᴋᴇʀ, ᴏʀ ᴀɴ ɪᴍᴀɢᴇ to sᴇᴀʀᴄʜ it!\nᴅᴏ ʏᴏᴜ ᴋɴᴏᴡ ᴛʜᴀᴛ ʏᴏᴜ ᴄᴀɴ sᴇᴀʀᴄʜ ᴀɴ ɪᴍᴀɢᴇ ᴡɪᴛʜ a ʟɪɴᴋ ᴛᴏᴏ? `/reverse [ᴘɪᴄᴛᴜʀᴇʟɪɴᴋ] <ᴀᴍᴏᴜɴᴛ>`." + ) + return + + try: + searchUrl = "https://www.google.com/searchbyimage/upload" + multipart = { + "encoded_image": (imagename, open(imagename, "rb")), + "image_content": "", + } + response = requests.post(searchUrl, files=multipart, allow_redirects=False) + fetchUrl = response.headers["Location"] + + if response != 400: + xx = bot.send_message( + chat_id, + "ɪᴍᴀɢᴇ ᴡᴀs sᴜᴄᴄᴇssғᴜʟʟʏ ᴜᴘʟᴏᴀᴅᴇᴅ ᴛᴏ ɢᴏᴏɢʟᴇ." "\n ᴘʟᴇᴀsᴇ ᴡᴀɪᴛ ʙᴀʙʏ...", + reply_to_message_id=rtmid, + ) + else: + xx = bot.send_message( + chat_id, + "ɢᴏᴏɢʟᴇ ᴛᴏʟᴅ ᴍᴇ ᴛᴏ ғᴜᴄᴋ ʏᴏᴜ ᴛʜɪs ɢᴀʏ", + reply_to_message_id=rtmid, + ) + return + + os.remove(imagename) + match = ParseSauce(f"{fetchUrl}&hl=en") + guess = match["best_guess"] + if match["override"] and match["override"] != "": + imgspage = match["override"] + else: + imgspage = match["similar_images"] + + if guess and imgspage: + xx.edit_text( + f"[{guess}]({fetchUrl})\nWaito...", + parse_mode="Markdown", + disable_web_page_preview=True, + ) + else: + xx.edit_text("ᴄᴏᴜʟᴅɴ'ᴛ ғɪɴᴅ ᴀɴʏᴛʜɪɴɢ.") + return + + images = scam(imgspage, lim) + if len(images) == 0: + xx.edit_text( + f"[{guess}]({fetchUrl})[.]({imgspage})", + parse_mode="Markdown", + disable_web_page_preview=True, + ) + return + + imglinks = [] + for link in images: + lmao = InputMediaPhoto(media=str(link)) + imglinks.append(lmao) + + bot.send_media_group(chat_id=chat_id, media=imglinks, reply_to_message_id=rtmid) + xx.edit_text( + f"[{guess}]({fetchUrl})[.]({imgspage})", + parse_mode="Markdown", + disable_web_page_preview=True, + ) + except TelegramError as e: + print(e) + except Exception as exception: + print(exception) + + +def ParseSauce(googleurl): + """Parse/Scrape the HTML code for the info we want.""" + + source = opener.open(googleurl).read() + soup = BeautifulSoup(source, "html.parser") + + results = {"similar_images": "", "override": "", "best_guess": ""} + + try: + for bess in soup.findAll("a", {"class": "PBorbe"}): + url = "https://www.google.com" + bess.get("href") + results["override"] = url + except: + pass + + for similar_image in soup.findAll("input", {"class": "gLFyf"}): + url = "https://www.google.com/search?tbm=isch&q=" + urllib.parse.quote_plus( + similar_image.get("value") + ) + results["similar_images"] = url + + for best_guess in soup.findAll("div", attrs={"class": "r5a77d"}): + results["best_guess"] = best_guess.get_text() + + return results + + +def scam(imgspage, lim): + """Parse/Scrape the HTML code for the info we want.""" + + single = opener.open(imgspage).read() + decoded = single.decode("utf-8") + if int(lim) > 10: + lim = 10 + + imglinks = [] + counter = 0 + + pattern = r"^,\[\"(.*[.png|.jpg|.jpeg])\",[0-9]+,[0-9]+\]$" + oboi = re.findall(pattern, decoded, re.I | re.M) + + for imglink in oboi: + counter += 1 + imglinks.append(imglink) + if counter >= int(lim): + break + + return imglinks + + +REVERSE_HANDLER = DisableAbleCommandHandler( + ["grs", "reverse", "pp"], reverse, pass_args=True, run_async=True +) + +dispatcher.add_handler(REVERSE_HANDLER) + +__mod_name__ = "𝚁ᴇᴠᴇʀsᴇ" diff --git a/Exon/modules/rmbg.py b/Exon/modules/rmbg.py new file mode 100644 index 00000000..e0f64248 --- /dev/null +++ b/Exon/modules/rmbg.py @@ -0,0 +1,116 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import io +import os +from datetime import datetime + +import requests +from telethon import types +from telethon.tl import functions + +from Exon import REM_BG_API_KEY, SUPPORT_CHAT, TEMP_DOWNLOAD_DIRECTORY, telethn +from Exon.events import register + + +async def is_register_admin(chat, user): + if isinstance(chat, (types.InputPeerChannel, types.InputChannel)): + return isinstance( + ( + await telethn(functions.channels.GetParticipantRequest(chat, user)) + ).participant, + (types.ChannelParticipantAdmin, types.ChannelParticipantCreator), + ) + if isinstance(chat, types.InputPeerUser): + return True + + +@register(pattern="^/rmbg") +async def _(event): + if event.fwd_from: + return + if event.is_group and not await is_register_admin( + event.input_chat, event.message.sender_id + ): + return + if REM_BG_API_KEY is None: + await event.reply("ʏᴏᴜ ɴᴇᴇᴅ API ᴛᴏᴋᴇɴ from ʀᴇᴍᴏᴠᴇ.ʙɢ ᴛᴏ ᴜsᴇ ᴛʜɪs ᴘʟᴜɢɪɴ.") + return False + start = datetime.now() + message_id = event.message.id + if event.reply_to_msg_id: + message_id = event.reply_to_msg_id + reply_message = await event.get_reply_message() + await event.reply("ᴘʀᴏᴄᴇssɪɴɢ....") + try: + downloaded_file_name = await telethn.download_media( + reply_message, TEMP_DOWNLOAD_DIRECTORY + ) + except Exception as e: + await event.reply(str(e)) + return + else: + output_file_name = ReTrieveFile(downloaded_file_name) + os.remove(downloaded_file_name) + else: + HELP_STR = "ᴜsᴇ `/rmbg` ᴀs ʀᴇᴘʟʏ ᴛᴏ ᴀ ᴍᴇᴅɪᴀ" + await event.reply(HELP_STR) + return + 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 = "rmbg.png" + await telethn.send_file( + event.chat_id, + remove_bg_image, + force_document=True, + supports_streaming=False, + allow_cache=False, + reply_to=message_id, + ) + end = datetime.now() + ms = (end - start).seconds + await event.reply(f"ʙᴀᴄᴋɢʀᴏᴜɴᴅ ʀᴇᴍᴏᴠᴇᴅ ɪɴ {ms} sᴇᴄᴏɴᴅs") + else: + await event.reply( + f"ʀᴇᴍᴏᴠᴇ.ʙɢ ᴀᴘɪ ʀᴇᴛᴜʀɴᴇᴅ ʀᴇᴛᴜʀɴᴇᴅ. ᴘʟᴇᴀsᴇ ʀᴇᴘᴏʀᴛ ᴛᴏ @{SUPPORT_CHAT} \n".format( + output_file_name.content.decode("UTF-8") + ) + ) + + +def ReTrieveFile(input_file_name): + headers = { + "X-API-Key": REM_BG_API_KEY, + } + files = { + "image_file": (input_file_name, open(input_file_name, "rb")), + } + return requests.post( + "https://api.remove.bg/v1.0/removebg", + headers=headers, + files=files, + allow_redirects=True, + stream=True, + ) diff --git a/Exon/modules/rules.py b/Exon/modules/rules.py new file mode 100644 index 00000000..cfd0aad6 --- /dev/null +++ b/Exon/modules/rules.py @@ -0,0 +1,176 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +from typing import Optional + +from telegram import ( + InlineKeyboardButton, + InlineKeyboardMarkup, + Message, + ParseMode, + Update, + User, +) +from telegram.error import BadRequest +from telegram.ext import CallbackContext, CommandHandler, Filters +from telegram.utils.helpers import escape_markdown + +import Exon.modules.sql.rules_sql as sql +from Exon import dispatcher +from Exon.modules.helper_funcs.chat_status import user_admin +from Exon.modules.helper_funcs.string_handling import markdown_parser + + +def get_rules(update: Update, context: CallbackContext): + chat_id = update.effective_chat.id + send_rules(update, chat_id) + + +# Do not async - not from a handler +def send_rules(update, chat_id, from_pm=False): + bot = dispatcher.bot + user = update.effective_user # type: Optional[User] + reply_msg = update.message.reply_to_message + try: + chat = bot.get_chat(chat_id) + except BadRequest as excp: + if excp.message == "ᴄʜᴀᴛ ɴᴏᴛ ғᴏᴜɴᴅ" and from_pm: + bot.send_message( + user.id, + "The ʀᴜʟᴇs sʜᴏʀᴛᴄᴜᴛ ғᴏʀ ᴛʜɪs ᴄʜᴀᴛ ʜᴀsɴ'ᴛ ʙᴇᴇɴ sᴇᴛ ᴘʀᴏᴘᴇʀʟʏ! ᴀsᴋ ᴀᴅᴍɪɴs to " + "ғɪx ᴛʜɪs.\nMaybe ᴛʜᴇʏ ғᴏʀɢᴏᴛ ᴛʜᴇ ʜʏᴘʜᴇɴ ɪɴ ɪᴅ", + ) + return + raise + + rules = sql.get_rules(chat_id) + text = f"ᴛʜᴇ ʀᴜʟᴇs ғᴏʀ *{escape_markdown(chat.title)}* ᴀʀᴇ:\n\n{rules}" + + if from_pm and rules: + bot.send_message( + user.id, + text, + parse_mode=ParseMode.MARKDOWN, + disable_web_page_preview=True, + ) + elif from_pm: + bot.send_message( + user.id, + "ᴛʜᴇ ɢʀᴏᴜᴘ ᴀᴅᴍɪɴs ʜᴀᴠᴇɴ'ᴛ sᴇᴛ ᴀɴʏ ʀᴜʟᴇs ғᴏʀ ᴛʜɪs ᴄʜᴀᴛ ʏᴇᴛ. " + "ᴛʜɪs ᴘʀᴏʙᴀʙʟʏ ᴅᴏᴇsɴ'ᴛ ᴍᴇᴀɴ ɪᴛ's ʟᴀᴡʟᴇss ᴛʜᴏᴜɢʜ...!", + ) + elif rules and reply_msg: + reply_msg.reply_text( + "ᴘʟᴇᴀsᴇ ᴄʟɪᴄᴋ ᴛʜᴇ ʙᴜᴛᴛᴏɴ ʙᴇʟᴏᴡ ᴛᴏ sᴇᴇ ᴛʜᴇ ʀᴜʟᴇs.", + reply_markup=InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + text="ʀᴜʟᴇs", + url=f"t.me/{bot.username}?start={chat_id}", + ), + ], + ], + ), + ) + elif rules: + update.effective_message.reply_text( + "ᴘʟᴇᴀsᴇ ᴄʟɪᴄᴋ ᴛʜᴇ ʙᴜᴛᴛᴏɴ ʙᴇʟᴏᴡ ᴛᴏ sᴇᴇ ᴛʜᴇ ʀᴜʟᴇs.", + reply_markup=InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + text="ʀᴜʟᴇs", + url=f"t.me/{bot.username}?start={chat_id}", + ), + ], + ], + ), + ) + else: + update.effective_message.reply_text( + "ᴛʜᴇ ɢʀᴏᴜᴘ ᴀᴅᴍɪɴs ʜᴀᴠᴇɴ sᴇᴛ ᴀɴʏ ʀᴜʟᴇs ғᴏʀ ᴛʜɪs ᴄʜᴀᴛ ʏᴇᴛ. " + "ᴛʜɪs ᴘʀᴏʙᴀʙʟʏ ᴅᴏᴇsɴ'ᴛ ᴍᴇᴀɴ ɪᴛ's ʟᴀᴡʟᴇss ᴛʜᴏᴜɢʜ \nᴇɴᴊᴏʏ...!", + ) + + +@user_admin +def set_rules(update: Update, context: CallbackContext): + chat_id = update.effective_chat.id + msg = update.effective_message # type: Optional[Message] + raw_text = msg.text + args = raw_text.split(None, 1) # use python's maxsplit to separate cmd and args + if len(args) == 2: + txt = args[1] + offset = len(txt) - len(raw_text) # set correct offset relative to command + markdown_rules = markdown_parser( + txt, + entities=msg.parse_entities(), + offset=offset, + ) + + sql.set_rules(chat_id, markdown_rules) + update.effective_message.reply_text("sᴜᴄᴄᴇssғᴜʟʟʏ sᴇᴛ ʀᴜʟᴇs ғᴏʀ ᴛʜɪs ɢʀᴏᴜᴘ.") + + +@user_admin +def clear_rules(update: Update, context: CallbackContext): + chat_id = update.effective_chat.id + sql.set_rules(chat_id, "") + update.effective_message.reply_text("sᴜᴄᴄᴇssғᴜʟʟʏ ᴄʟᴇᴀʀᴇᴅ ʀᴜʟᴇs!") + + +def __stats__(): + return f"•➥ {sql.num_chats()} ᴄʜᴀᴛs ʜᴀᴠᴇ ʀᴜʟᴇs sᴇᴛ." + + +def __import_data__(chat_id, data): + # set chat rules + rules = data.get("info", {}).get("rules", "") + sql.set_rules(chat_id, rules) + + +def __migrate__(old_chat_id, new_chat_id): + sql.migrate_chat(old_chat_id, new_chat_id) + + +def __chat_settings__(chat_id, user_id): + return f"ᴛʜɪs ᴄʜᴀᴛ ʜᴀs ʜᴀᴅ ɪᴛ's ʀᴜʟᴇs sᴇᴛ: `{bool(sql.get_rules(chat_id))}`" + + +__mod_name__ = "𝚁ᴜʟᴇs" + +GET_RULES_HANDLER = CommandHandler( + "rules", get_rules, filters=Filters.chat_type.groups, run_async=True +) +SET_RULES_HANDLER = CommandHandler( + "setrules", set_rules, filters=Filters.chat_type.groups, run_async=True +) +RESET_RULES_HANDLER = CommandHandler( + "clearrules", clear_rules, filters=Filters.chat_type.groups, run_async=True +) + +dispatcher.add_handler(GET_RULES_HANDLER) +dispatcher.add_handler(SET_RULES_HANDLER) +dispatcher.add_handler(RESET_RULES_HANDLER) diff --git a/Exon/modules/sed.py b/Exon/modules/sed.py new file mode 100644 index 00000000..a3853623 --- /dev/null +++ b/Exon/modules/sed.py @@ -0,0 +1,181 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import sre_constants + +import regex +import telegram +from telegram import Update +from telegram.ext import CallbackContext, Filters + +from Exon import LOGGER, dispatcher +from Exon.modules.disable import DisableAbleMessageHandler +from Exon.modules.helper_funcs.regex_helper import infinite_loop_check + +DELIMITERS = ("/", ":", "|", "_") + + +def separate_sed(sed_string): + if ( + len(sed_string) < 3 + or sed_string[1] not in DELIMITERS + or sed_string.count(sed_string[1]) < 2 + ): + return + + delim = sed_string[1] + start = counter = 2 + while counter < len(sed_string): + if sed_string[counter] == "\\": + counter += 1 + + elif sed_string[counter] == delim: + replace = sed_string[start:counter] + counter += 1 + start = counter + break + + counter += 1 + + else: + return None + + while counter < len(sed_string): + if ( + sed_string[counter] == "\\" + and counter + 1 < len(sed_string) + and sed_string[counter + 1] == delim + ): + sed_string = sed_string[:counter] + sed_string[counter + 1 :] + + elif sed_string[counter] == delim: + replace_with = sed_string[start:counter] + counter += 1 + break + + counter += 1 + else: + return replace, sed_string[start:], "" + + flags = sed_string[counter:] if counter < len(sed_string) else "" + return replace, replace_with, flags.lower() + + +def sed(update: Update, context: CallbackContext): + sed_result = separate_sed(update.effective_message.text) + if sed_result and update.effective_message.reply_to_message: + if update.effective_message.reply_to_message.text: + to_fix = update.effective_message.reply_to_message.text + elif update.effective_message.reply_to_message.caption: + to_fix = update.effective_message.reply_to_message.caption + else: + return + + repl, repl_with, flags = sed_result + if not repl: + update.effective_message.reply_to_message.reply_text( + "You're trying to replace... " "nothing with something?", + ) + return + + try: + try: + check = regex.match(repl, to_fix, flags=regex.IGNORECASE, timeout=5) + except TimeoutError: + return + if check and check.group(0).lower() == to_fix.lower(): + update.effective_message.reply_to_message.reply_text( + "ʜᴇʏ ᴇᴠᴇʀʏᴏɴᴇ, {} ɪs ᴛʀʏɪɴɢ ᴛᴏ ᴍᴀᴋᴇ " + "ᴍᴇ sᴀʏ sᴛᴜғғ I ᴅᴏɴ'ᴛ ᴡᴀɴɴᴀ " + "sᴀʏ!".format(update.effective_user.first_name), + ) + return + if infinite_loop_check(repl): + update.effective_message.reply_text( + "I'ᴍ ᴀғʀᴀɪᴅ I ᴄᴀɴ'ᴛ ʀᴜɴ ᴛʜᴀᴛ ʀᴇɢᴇx.", + ) + return + if "i" in flags and "g" in flags: + text = regex.sub( + repl, + repl_with, + to_fix, + flags=regex.I, + timeout=3, + ).strip() + elif "i" in flags: + text = regex.sub( + repl, + repl_with, + to_fix, + count=1, + flags=regex.I, + timeout=3, + ).strip() + elif "g" in flags: + text = regex.sub(repl, repl_with, to_fix, timeout=3).strip() + else: + text = regex.sub(repl, repl_with, to_fix, count=1, timeout=3).strip() + except TimeoutError: + update.effective_message.reply_text("ᴛɪᴍᴇᴏᴜᴛ") + return + except sre_constants.error: + LOGGER.warning(update.effective_message.text) + LOGGER.exception("sʀᴇ ᴄᴏɴsᴛᴀɴᴛ ᴇʀʀᴏʀ") + update.effective_message.reply_text("ᴅᴏ ʏᴏᴜ ᴇᴠᴇɴ sᴇᴅ? ᴀᴘᴘᴀʀᴇɴᴛʟʏ ɴᴏᴛ.") + return + + # empty string errors -_- + if len(text) >= telegram.MAX_MESSAGE_LENGTH: + update.effective_message.reply_text( + "ᴛʜᴇ ʀᴇsᴜʟᴛ ᴏғ ᴛʜᴇ sed ᴄᴏᴍᴍᴀɴᴅ ᴡᴀs ᴛᴏᴏ ʟᴏɴɢ ғᴏʀ \ + ᴛᴇʟᴇɢʀᴀᴍ!", + ) + elif text: + update.effective_message.reply_to_message.reply_text(text) + + +__help__ = """ + ➩ `s/<text1>/<text2>(/<flag>)`*:* ʀᴇᴘʟʏ ᴛᴏ ᴀ ᴍᴇssᴀɢᴇ ᴡɪᴛʜ ᴛʜɪs ᴛᴏ ᴘᴇʀғᴏʀᴍ ᴀ sᴇᴅ ᴏᴘᴇʀᴀᴛɪᴏɴ ᴏɴ ᴛʜᴀᴛ ᴍᴇssᴀɢᴇ, ʀᴇᴘʟᴀᴄɪɴɢ ᴀʟʟ \ +ᴏᴄᴄᴜʀʀᴇɴᴄᴇs ᴏғ 'text1' ᴡɪᴛʜ 'text2'. ғʟᴀɢs ᴀʀᴇ ᴏᴘᴛɪᴏɴᴀʟ, ᴀɴᴅ ᴄᴜʀʀᴇɴᴛʟʏ ɪɴᴄʟᴜᴅᴇ 'i' ғᴏʀ ɪɢɴᴏʀᴇ ᴄᴀsᴇ, 'g' ғᴏʀ ɢʟᴏʙᴀʟ, \ +ᴏʀ ɴᴏᴛʜɪɴɢ. ᴅᴇʟɪᴍɪᴛᴇʀs ɪɴᴄʟᴜᴅᴇ `/`, `_`, `|`, ᴀɴᴅ `:`. ᴛᴇxᴛ ɢʀᴏᴜᴘɪɴɢ ɪs sᴜᴘᴘᴏʀᴛᴇᴅ. ᴛʜᴇ ʀᴇsᴜʟᴛɪɴɢ ᴍᴇssᴀɢᴇ ᴄᴀɴɴᴏᴛ be \ +ʟᴀʀɢᴇʀ ᴛʜᴀɴ {}. + +*ʀᴇᴍɪɴᴅᴇʀ:* sᴇᴅ ᴜsᴇs sᴏᴍᴇ sᴘᴇᴄɪᴀʟ ᴄʜᴀʀᴀᴄᴛᴇʀs ᴛᴏ ᴍᴀᴋᴇ ᴍᴀᴛᴄʜɪɴɢ ᴇᴀsɪᴇʀ, sᴜᴄʜ ᴀs ᴛʜᴇsᴇ: `+*.?\\` +If ʏᴏᴜ ᴡᴀɴᴛ ᴛᴏ ᴜsᴇ ᴛʜᴇsᴇ ᴄʜᴀʀᴀᴄᴛᴇʀs, ᴍᴀᴋᴇ sᴜʀᴇ ʏᴏᴜ ᴇsᴄᴀᴘᴇ ᴛʜᴇᴍ! +*Example:* \\?. +""".format( + telegram.MAX_MESSAGE_LENGTH, +) + +__mod_name__ = "𝚁ᴇɢᴇx" + +SED_HANDLER = DisableAbleMessageHandler( + Filters.regex(r"s([{}]).*?\1.*".format("".join(DELIMITERS))), + sed, + friendly="sed", + run_async=True, +) + +dispatcher.add_handler(SED_HANDLER) diff --git a/Exon/modules/sfw.py b/Exon/modules/sfw.py new file mode 100644 index 00000000..c1d9f573 --- /dev/null +++ b/Exon/modules/sfw.py @@ -0,0 +1,507 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import nekos +import requests +from telegram.ext import CommandHandler + +from Exon import dispatcher + +url_sfw = "https://api.waifu.pics/sfw/" + + +def waifu(update, context): + msg = update.effective_message + url = f"{url_sfw}waifu" + result = requests.get(url).json() + img = result["url"] + msg.reply_photo(photo=img) + + +def neko(update, context): + msg = update.effective_message + url = f"{url_sfw}neko" + result = requests.get(url).json() + img = result["url"] + msg.reply_photo(photo=img) + + +def shinobu(update, context): + msg = update.effective_message + url = f"{url_sfw}shinobu" + result = requests.get(url).json() + img = result["url"] + msg.reply_photo(photo=img) + + +def megumin(update, context): + msg = update.effective_message + url = f"{url_sfw}megumin" + result = requests.get(url).json() + img = result["url"] + msg.reply_photo(photo=img) + + +def bully(update, context): + msg = update.effective_message + url = f"{url_sfw}bully" + result = requests.get(url).json() + img = result["url"] + msg.reply_animation(img) + + +def cuddle(update, context): + msg = update.effective_message + url = f"{url_sfw}cuddle" + result = requests.get(url).json() + img = result["url"] + msg.reply_animation(img) + + +def cry(update, context): + msg = update.effective_message + url = f"{url_sfw}cry" + result = requests.get(url).json() + img = result["url"] + msg.reply_animation(img) + + +def hug(update, context): + msg = update.effective_message + url = f"{url_sfw}hug" + result = requests.get(url).json() + img = result["url"] + msg.reply_animation(img) + + +def awoo(update, context): + msg = update.effective_message + url = f"{url_sfw}awoo" + result = requests.get(url).json() + img = result["url"] + msg.reply_animation(img) + + +def kiss(update, context): + msg = update.effective_message + url = f"{url_sfw}kiss" + result = requests.get(url).json() + img = result["url"] + msg.reply_animation(img) + + +def lick(update, context): + msg = update.effective_message + url = f"{url_sfw}lick" + result = requests.get(url).json() + img = result["url"] + msg.reply_animation(img) + + +def pat(update, context): + msg = update.effective_message + url = f"{url_sfw}pat" + result = requests.get(url).json() + img = result["url"] + msg.reply_animation(img) + + +def smug(update, context): + msg = update.effective_message + url = f"{url_sfw}smug" + result = requests.get(url).json() + img = result["url"] + msg.reply_animation(img) + + +def bonk(update, context): + msg = update.effective_message + url = f"{url_sfw}bonk" + result = requests.get(url).json() + img = result["url"] + msg.reply_animation(img) + + +def yeet(update, context): + msg = update.effective_message + url = f"{url_sfw}yeet" + result = requests.get(url).json() + img = result["url"] + msg.reply_animation(img) + + +def blush(update, context): + msg = update.effective_message + url = f"{url_sfw}blush" + result = requests.get(url).json() + img = result["url"] + msg.reply_animation(img) + + +def smile(update, context): + msg = update.effective_message + url = f"{url_sfw}smile" + result = requests.get(url).json() + img = result["url"] + msg.reply_animation(img) + + +def wave(update, context): + msg = update.effective_message + url = f"{url_sfw}wave" + result = requests.get(url).json() + img = result["url"] + msg.reply_animation(img) + + +def highfive(update, context): + msg = update.effective_message + url = f"{url_sfw}highfive" + result = requests.get(url).json() + img = result["url"] + msg.reply_animation(img) + + +def handhold(update, context): + msg = update.effective_message + url = f"{url_sfw}handhold" + result = requests.get(url).json() + img = result["url"] + msg.reply_animation(img) + + +def nom(update, context): + msg = update.effective_message + url = f"{url_sfw}nom" + result = requests.get(url).json() + img = result["url"] + msg.reply_animation(img) + + +def bite(update, context): + msg = update.effective_message + url = f"{url_sfw}bite" + result = requests.get(url).json() + img = result["url"] + msg.reply_animation(img) + + +def glomp(update, context): + msg = update.effective_message + url = f"{url_sfw}glomp" + result = requests.get(url).json() + img = result["url"] + msg.reply_animation(img) + + +def slap(update, context): + msg = update.effective_message + url = f"{url_sfw}slap" + result = requests.get(url).json() + img = result["url"] + msg.reply_animation(img) + + +def killgif(update, context): + msg = update.effective_message + url = f"{url_sfw}kill" + result = requests.get(url).json() + img = result["url"] + msg.reply_animation(img) + + +def kickgif(update, context): + msg = update.effective_message + url = f"{url_sfw}kick" + result = requests.get(url).json() + img = result["url"] + msg.reply_animation(img) + + +def happy(update, context): + msg = update.effective_message + url = f"{url_sfw}happy" + result = requests.get(url).json() + img = result["url"] + msg.reply_animation(img) + + +def wink(update, context): + msg = update.effective_message + url = f"{url_sfw}wink" + result = requests.get(url).json() + img = result["url"] + msg.reply_animation(img) + + +def poke(update, context): + msg = update.effective_message + url = f"{url_sfw}poke" + result = requests.get(url).json() + img = result["url"] + msg.reply_animation(img) + + +def dance(update, context): + msg = update.effective_message + url = f"{url_sfw}dance" + result = requests.get(url).json() + img = result["url"] + msg.reply_animation(img) + + +def cringe(update, context): + msg = update.effective_message + url = f"{url_sfw}cringe" + result = requests.get(url).json() + img = result["url"] + msg.reply_animation(img) + + +################ +def wallpaper(update, context): + msg = update.effective_message + target = "wallpaper" + msg.reply_photo(nekos.img(target)) + + +def tickle(update, context): + msg = update.effective_message + target = "tickle" + msg.reply_video(nekos.img(target)) + + +def ngif(update, context): + msg = update.effective_message + target = "ngif" + msg.reply_video(nekos.img(target)) + + +def feed(update, context): + msg = update.effective_message + target = "feed" + msg.reply_video(nekos.img(target)) + + +def gasm(update, context): + msg = update.effective_message + target = "gasm" + msg.reply_photo(nekos.img(target)) + + +def avatar(update, context): + msg = update.effective_message + target = "avatar" + msg.reply_photo(nekos.img(target)) + + +def foxgirl(update, context): + msg = update.effective_message + target = "fox_girl" + msg.reply_photo(nekos.img(target)) + + +def gecg(update, context): + msg = update.effective_message + target = "gecg" + msg.reply_photo(nekos.img(target)) + + +def lizard(update, context): + msg = update.effective_message + target = "lizard" + msg.reply_photo(nekos.img(target)) + + +def spank(update, context): + msg = update.effective_message + target = "spank" + msg.reply_video(nekos.img(target)) + + +def goose(update, context): + msg = update.effective_message + target = "goose" + msg.reply_photo(nekos.img(target)) + + +def woof(update, context): + msg = update.effective_message + target = "woof" + msg.reply_photo(nekos.img(target)) + + +WALLPAPER_HANDLER = CommandHandler("wallpaper", wallpaper, run_async=True) +TICKLE_HANDLER = CommandHandler("tickle", tickle, run_async=True) +FEED_HANDLER = CommandHandler("feed", feed, run_async=True) +GASM_HANDLER = CommandHandler("gasm", gasm, run_async=True) +AVATAR_HANDLER = CommandHandler("avatar", avatar, run_async=True) +FOXGIRL_HANDLER = CommandHandler("foxgirl", foxgirl, run_async=True) +GECG_HANDLER = CommandHandler("gecg", gecg, run_async=True) +LIZARD_HANDLER = CommandHandler("lizard", lizard, run_async=True) +GOOSE_HANDLER = CommandHandler("goose", goose, run_async=True) +WOOF_HANDLER = CommandHandler("woof", woof, run_async=True) +NGIF_HANDLER = CommandHandler("ngif", ngif, run_async=True) + +WAIFUS_HANDLER = CommandHandler("waifus", waifu, run_async=True) +NEKO_HANDLER = CommandHandler("neko", neko, run_async=True) +SHINOBU_HANDLER = CommandHandler("shinobu", shinobu, run_async=True) +MEGUMIN_HANDLER = CommandHandler("megumin", megumin, run_async=True) +BULLY_HANDLER = CommandHandler("bully", bully, run_async=True) +CUDDLE_HANDLER = CommandHandler("cuddle", foxgirl, run_async=True) +CRY_HANDLER = CommandHandler("cry", cry, run_async=True) +HUG_HANDLER = CommandHandler("hug", hug, run_async=True) +AWOO_HANDLER = CommandHandler("awoo", awoo, run_async=True) +KISS_HANDLER = CommandHandler("kiss", kiss, run_async=True) +LICK_HANDLER = CommandHandler("lick", lick, run_async=True) +PAT_HANDLER = CommandHandler("pat", pat, run_async=True) + + +SMUG_HANDLER = CommandHandler("smug", smug, run_async=True) +BONK_HANDLER = CommandHandler("bonk", bonk, run_async=True) +YEET_HANDLER = CommandHandler("yeet", yeet, run_async=True) +BLUSH_HANDLER = CommandHandler("blush", blush, run_async=True) +SMILE_HANDLER = CommandHandler("smile", smile, run_async=True) +WAVE_HANDLER = CommandHandler("wave", wave, run_async=True) +HIGHFIVE_HANDLER = CommandHandler("highfive", highfive, run_async=True) +HANDHOLD_HANDLER = CommandHandler("handhold", handhold, run_async=True) +NOM_HANDLER = CommandHandler("nom", nom, run_async=True) +BITE_HANDLER = CommandHandler("bite", bite, run_async=True) +GLOMP_HANDLER = CommandHandler("glomp", glomp, run_async=True) + + +SLAP_HANDLER = CommandHandler("slap", slap, run_async=True) +KILLGIF_HANDLER = CommandHandler("killgif", killgif, run_async=True) +HAPPY_HANDLER = CommandHandler("happy", happy, run_async=True) +WINK_HANDLER = CommandHandler("wink", wink, run_async=True) +POKE_HANDLER = CommandHandler("poke", poke, run_async=True) +DANCE_HANDLER = CommandHandler("dance", dance, run_async=True) +CRINGE_HANDLER = CommandHandler("cringe", cringe, run_async=True) + + +dispatcher.add_handler(SLAP_HANDLER) +dispatcher.add_handler(KILLGIF_HANDLER) +dispatcher.add_handler(HAPPY_HANDLER) +dispatcher.add_handler(WINK_HANDLER) +dispatcher.add_handler(POKE_HANDLER) +dispatcher.add_handler(DANCE_HANDLER) +dispatcher.add_handler(CRINGE_HANDLER) + + +dispatcher.add_handler(SMUG_HANDLER) +dispatcher.add_handler(BONK_HANDLER) +dispatcher.add_handler(YEET_HANDLER) +dispatcher.add_handler(BLUSH_HANDLER) +dispatcher.add_handler(SMILE_HANDLER) +dispatcher.add_handler(WAVE_HANDLER) +dispatcher.add_handler(HIGHFIVE_HANDLER) +dispatcher.add_handler(HANDHOLD_HANDLER) +dispatcher.add_handler(NOM_HANDLER) +dispatcher.add_handler(BITE_HANDLER) +dispatcher.add_handler(GLOMP_HANDLER) + + +dispatcher.add_handler(AWOO_HANDLER) +dispatcher.add_handler(PAT_HANDLER) +dispatcher.add_handler(KISS_HANDLER) +dispatcher.add_handler(LICK_HANDLER) +dispatcher.add_handler(CRY_HANDLER) +dispatcher.add_handler(HUG_HANDLER) +dispatcher.add_handler(WAIFUS_HANDLER) +dispatcher.add_handler(NEKO_HANDLER) +dispatcher.add_handler(SHINOBU_HANDLER) +dispatcher.add_handler(MEGUMIN_HANDLER) +dispatcher.add_handler(BULLY_HANDLER) +dispatcher.add_handler(CUDDLE_HANDLER) + +dispatcher.add_handler(LIZARD_HANDLER) +dispatcher.add_handler(NGIF_HANDLER) +dispatcher.add_handler(GOOSE_HANDLER) +dispatcher.add_handler(WOOF_HANDLER) +dispatcher.add_handler(GECG_HANDLER) +dispatcher.add_handler(WALLPAPER_HANDLER) +dispatcher.add_handler(TICKLE_HANDLER) +dispatcher.add_handler(FEED_HANDLER) +dispatcher.add_handler(GASM_HANDLER) +dispatcher.add_handler(AVATAR_HANDLER) +dispatcher.add_handler(FOXGIRL_HANDLER) + +__handlers__ = [ + SLAP_HANDLER, + LIZARD_HANDLER, + GOOSE_HANDLER, + WOOF_HANDLER, + WALLPAPER_HANDLER, + TICKLE_HANDLER, + FEED_HANDLER, + GASM_HANDLER, + AVATAR_HANDLER, + GECG_HANDLER, + FOXGIRL_HANDLER, +] + + +__mod_name__ = "𝚂ғᴡ" +__help__ = """ +*ᴄᴏᴍᴍᴀɴᴅs* *:* +• `/neko`*:*sᴇɴᴅs ʀᴀɴᴅᴏᴍ sғᴡ ɴᴇᴋᴏ sᴏᴜʀᴄᴇ ɪᴍᴀɢᴇs. +• `/ngif`*:*sᴇɴᴅs ʀᴀɴᴅᴏᴍ ɴᴇᴋᴏ ɢɪғs. +• `/tickle`*:*sᴇɴᴅs ʀᴀɴᴅᴏᴍ ᴛɪᴄᴋʟᴇ GIFs. +• `/feed`*:*sᴇɴᴅs ʀᴀɴᴅᴏᴍ ғᴇᴇᴅɪɴɢ ɢɪғs. +• `/gasm`*:*sᴇɴᴅs ʀᴀɴᴅᴏᴍ ᴏʀɢᴀsᴍ sᴛɪᴄᴋᴇʀs. +• `/avatar`*:*sᴇɴᴅs ʀᴀɴᴅᴏᴍ ᴀᴠᴀᴛᴀʀ sᴛɪᴄᴋᴇʀs. +• `/waifus`*:* Sends ʀᴀɴᴅᴏᴍ ᴡᴀɪғᴜ sᴛɪᴄᴋᴇʀs. +• `/kiss`*:* sᴇɴᴅs ʀᴀɴᴅᴏᴍ ᴋɪssɪɴɢ ɢɪғs. +• `/cuddle`*:* Sends ʀᴀɴᴅᴏᴍ ᴄᴜᴅᴅʟᴇ ɢɪғs. +• `/foxgirl`*:* sᴇɴᴅs ʀᴀɴᴅᴏᴍ ғᴏxɢɪʀʟ sᴏᴜʀᴄᴇ ɪᴍᴀɢᴇs. +• `/smug`*:* Sends Random Smug GIFs. +• `/gecg`*:* ᴘᴇᴛᴀ ɴᴀɪ ʏᴀʀ +• `/slap`*:* sᴇɴᴅs ʀᴀɴᴅᴏᴍ sʟᴀᴘ ɢɪғs. + +*sᴏᴍᴇ ᴍᴏʀᴇ SFW ᴄᴏᴍᴍᴀɴᴅs :* +• `/shinobu` +• `/megumin` +• `/bully` +• `/cry` +• `/awoo` +• `/lick` +• `/pat` +• `/bonk` +• `/yeet` +• `/blush` +• `/smile` +• `/wave` +• `/highfive` +• `/handhold` +• `/nom` +• `/bite` +• `/glomp` +• `/slapgif` +• `/kill` +• `/happy` +• `/wink` +• `/poke` +• `/dance` +• `/cringe` +""" diff --git a/Exon/modules/shell.py b/Exon/modules/shell.py new file mode 100644 index 00000000..dde3684e --- /dev/null +++ b/Exon/modules/shell.py @@ -0,0 +1,76 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import subprocess + +from telegram import ParseMode, Update +from telegram.ext import CallbackContext, CommandHandler + +from Exon import LOGGER, dispatcher +from Exon.modules.helper_funcs.chat_status import dev_plus + + +@dev_plus +def shell(update: Update, context: CallbackContext): + message = update.effective_message + cmd = message.text.split(" ", 1) + if len(cmd) == 1: + message.reply_text("ɴᴏ ᴄᴏᴍᴍᴀɴᴅ ᴛᴏ ᴇxᴇᴄᴜᴛᴇ ᴡᴀs ɢɪᴠᴇɴ.") + return + cmd = cmd[1] + process = subprocess.Popen( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + shell=True, + ) + stdout, stderr = process.communicate() + reply = "" + stderr = stderr.decode() + stdout = stdout.decode() + if stdout: + reply += f"*sᴛᴅᴏᴜᴛ*\n`{stdout}`\n" + LOGGER.info(f"Shell - {cmd} - {stdout}") + if stderr: + reply += f"*sᴛᴅᴇʀʀ*\n`{stderr}`\n" + LOGGER.error(f"Shell - {cmd} - {stderr}") + if len(reply) > 3000: + with open("shell_output.txt", "w") as file: + file.write(reply) + with open("shell_output.txt", "rb") as doc: + context.bot.send_document( + document=doc, + filename=doc.name, + reply_to_message_id=message.message_id, + chat_id=message.chat_id, + ) + else: + message.reply_text(reply, parse_mode=ParseMode.MARKDOWN) + + +SHELL_HANDLER = CommandHandler(["sh"], shell, run_async=True) +dispatcher.add_handler(SHELL_HANDLER) +__mod_name__ = "Shell" +__command_list__ = ["sh"] +__handlers__ = [SHELL_HANDLER] diff --git a/Exon/modules/shippering.py b/Exon/modules/shippering.py new file mode 100644 index 00000000..580822ea --- /dev/null +++ b/Exon/modules/shippering.py @@ -0,0 +1,103 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import random +from datetime import datetime + +from pyrogram import filters + +from Exon import pgram +from Exon.modules.mongo.couples_mongo import get_couple, save_couple +from Exon.utils.errors import capture_err + + +# Date and time +def dt(): + now = datetime.now() + dt_string = now.strftime("%d/%m/%Y %H:%M") + dt_list = dt_string.split(" ") + return dt_list + + +def dt_tom(): + a = ( + str(int(dt()[0].split("/")[0]) + 1) + + "/" + + dt()[0].split("/")[1] + + "/" + + dt()[0].split("/")[2] + ) + return a + + +today = str(dt()[0]) +tomorrow = str(dt_tom()) + + +@pgram.on_message(filters.command("couples") & ~filters.edited) +@capture_err +async def couple(_, message): + if message.chat.type == "private": + await message.reply_text("ᴛʜɪs ᴄᴏᴍᴍᴀɴᴅ ᴏɴʟʏ ᴡᴏʀᴋs ɪɴ ɢʀᴏᴜᴘs.") + return + try: + chat_id = message.chat.id + is_selected = await get_couple(chat_id, today) + if not is_selected: + list_of_users = [] + async for i in pgram.iter_chat_members(message.chat.id): + if not i.user.is_bot: + list_of_users.append(i.user.id) + if len(list_of_users) < 2: + await message.reply_text("ɴᴏᴛ ᴇɴᴏᴜɢʜ ᴜsᴇʀs") + return + c1_id = random.choice(list_of_users) + c2_id = random.choice(list_of_users) + while c1_id == c2_id: + c1_id = random.choice(list_of_users) + c1_mention = (await pgram.get_users(c1_id)).mention + c2_mention = (await pgram.get_users(c2_id)).mention + + couple_selection_message = f"""**ᴄᴏᴜᴘʟᴇ ᴏғ ᴛʜᴇ ᴅᴀʏ:** +{c1_mention} + {c2_mention} = 💗 +__ɴᴇᴡ ᴄᴏᴜᴘʟᴇ ᴏғ ᴛʜᴇ ᴅᴀʏ ᴍᴀʏ ʙᴇ ᴄʜᴏsᴇɴ ᴀᴛ 12ᴀᴍ {ᴛᴏᴍᴏʀʀᴏᴡ}__""" + await pgram.send_message(message.chat.id, text=couple_selection_message) + couple = {"c1_id": c1_id, "c2_id": c2_id} + await save_couple(chat_id, today, couple) + + elif is_selected: + c1_id = int(is_selected["c1_id"]) + c2_id = int(is_selected["c2_id"]) + c1_name = (await pgram.get_users(c1_id)).first_name + c2_name = (await pgram.get_users(c2_id)).first_name + couple_selection_message = f"""ᴄᴏᴜᴘʟᴇ ᴏғ ᴛʜᴇ ᴅᴀʏ: +[{c1_name}](tg://openmessage?user_id={c1_id}) + [{c2_name}](tg://openmessage?user_id={c2_id}) = 💜 +__ɴᴇᴡ ᴄᴏᴜᴘʟᴇ ᴏғ ᴛʜᴇ ᴅᴀʏ ᴍᴀʏ ʙᴇ ᴄʜᴏsᴇɴ ᴀᴛ 12ᴀᴍ {ᴛᴏᴍᴏʀʀᴏᴡ}__""" + await pgram.send_message(message.chat.id, text=couple_selection_message) + except Exception as e: + print(e) + await message.reply_text(e) + + +__mod_name__ = "𝙲ᴏᴜᴘʟᴇs" diff --git a/Exon/modules/speed_test.py b/Exon/modules/speed_test.py new file mode 100644 index 00000000..9bfcdb7b --- /dev/null +++ b/Exon/modules/speed_test.py @@ -0,0 +1,93 @@ +""" +MIT License + +Copyright (c) 2022 Arsh + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import speedtest +from telegram import InlineKeyboardButton, InlineKeyboardMarkup, ParseMode, Update +from telegram.ext import CallbackContext, CallbackQueryHandler + +from Exon import DEV_USERS, dispatcher +from Exon.modules.disable import DisableAbleCommandHandler +from Exon.modules.helper_funcs.chat_status import dev_plus + + +def convert(speed): + return round(int(speed) / 1048576, 2) + + +@dev_plus +def speedtestxyz(update: Update, context: CallbackContext): + buttons = [ + [ + InlineKeyboardButton("ɪᴍᴀɢᴇ", callback_data="speedtest_image"), + InlineKeyboardButton("ᴛᴇxᴛ", callback_data="speedtest_text"), + ], + ] + update.effective_message.reply_text( + "sᴇʟᴇᴄᴛ sᴘᴇᴇᴅᴛᴇsᴛ ᴍᴏᴅᴇ", + reply_markup=InlineKeyboardMarkup(buttons), + ) + + +def speedtestxyz_callback(update: Update, context: CallbackContext): + query = update.callback_query + + if query.from_user.id in DEV_USERS: + msg = update.effective_message.edit_text("ʀᴜɴɴɪɴɢ ғᴏʀ sᴘᴇᴇᴅᴛᴇsᴛ....") + speed = speedtest.Speedtest() + speed.get_best_server() + speed.download() + speed.upload() + replymsg = "sᴘᴇᴇᴅᴛᴇsᴛ ʀᴇsᴜʟᴛs:" + + if query.data == "speedtest_image": + speedtest_image = speed.results.share() + update.effective_message.reply_photo( + photo=speedtest_image, + caption=replymsg, + ) + msg.delete() + + elif query.data == "speedtest_text": + result = speed.results.dict() + replymsg += f"\nᴅᴏᴡɴʟᴏᴀᴅ: `{convert(result['download'])}Mb/s`\nᴜᴘʟᴏᴀᴅ: `{convert(result['upload'])}ᴍʙ/s`\nᴘɪɴɢ: `{result['ping']}`" + update.effective_message.edit_text(replymsg, parse_mode=ParseMode.MARKDOWN) + else: + query.answer("ʏᴏᴜ ᴀʀᴇ ʀᴇǫᴜɪʀᴇᴅ ᴛᴏ ᴊᴏɪɴ @{SUPPORT_CHAT} ᴛᴏ ᴜsᴇ ᴛʜɪs ᴄᴏᴍᴍᴀɴᴅ.") + + +SPEED_TEST_HANDLER = DisableAbleCommandHandler( + "speedtest", speedtestxyz, run_async=True +) +SPEED_TEST_CALLBACKHANDLER = CallbackQueryHandler( + speedtestxyz_callback, + pattern="speedtest_.*", + run_async=True, +) + +dispatcher.add_handler(SPEED_TEST_HANDLER) +dispatcher.add_handler(SPEED_TEST_CALLBACKHANDLER) + +__mod_name__ = "𝚂ᴘᴇᴇᴅᴛᴇsᴛ" +__command_list__ = ["speedtest"] +__handlers__ = [SPEED_TEST_HANDLER, SPEED_TEST_CALLBACKHANDLER] diff --git a/Exon/modules/spin.py b/Exon/modules/spin.py new file mode 100644 index 00000000..73e93f2c --- /dev/null +++ b/Exon/modules/spin.py @@ -0,0 +1,276 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import html +from typing import List, Optional + +from telegram import Bot, Chat, Update, User +from telegram.error import BadRequest +from telegram.ext import CommandHandler, Filters, MessageHandler +from telegram.ext.dispatcher import run_async +from telegram.utils.helpers import mention_html + +from Exon import dispatcher +from Exon.modules.helper_funcs.chat_status import bot_admin, can_pin, user_admin +from Exon.modules.log_channel import loggable +from Exon.modules.sql import pin_sql as sql + +PMW_GROUP = 12 + + +@run_async +@bot_admin +@can_pin +@user_admin +@loggable +def pin(bot: Bot, update: Update, args: List[str]) -> str: + user = update.effective_user # type: Optional[User] + chat = update.effective_chat # type: Optional[Chat] + + is_group = chat.type != "private" and chat.type != "channel" + + prev_message = update.effective_message.reply_to_message + + is_silent = True + if len(args) >= 1: + is_silent = not ( + args[0].lower() == "notify" + or args[0].lower() == "loud" + or args[0].lower() == "violent" + ) + + if prev_message and is_group: + try: + bot.pinChatMessage( + chat.id, prev_message.message_id, disable_notification=is_silent + ) + except BadRequest as excp: + if excp.message == "Chat_not_modified": + pass + else: + raise + sql.add_mid(chat.id, prev_message.message_id) + return ( + "<b>{}:</b>" + "\n#ᴘɪɴɴᴇᴅ" + "\n<b>ᴀᴅᴍɪɴ:</b> {}".format( + html.escape(chat.title), mention_html(user.id, user.first_name) + ) + ) + + return "" + + +@run_async +@bot_admin +@can_pin +@user_admin +@loggable +def unpin(bot: Bot, update: Update) -> str: + chat = update.effective_chat + user = update.effective_user # type: Optional[User] + + try: + bot.unpinChatMessage(chat.id) + except BadRequest as excp: + if excp.message == "Chat_not_modified": + pass + else: + raise + sql.remove_mid(chat.id) + return ( + "<b>{}:</b>" + "\n#ᴜɴᴘɪɴɴᴇᴅ" + "\n<b>ᴀᴅᴍɪɴ:</b> {}".format( + html.escape(chat.title), mention_html(user.id, user.first_name) + ) + ) + + +@run_async +@bot_admin +@can_pin +@user_admin +@loggable +def anti_channel_pin(bot: Bot, update: Update, args: List[str]) -> str: + chat = update.effective_chat # type: Optional[Chat] + user = update.effective_user # type: Optional[User] + + if not args: + update.effective_message.reply_text( + "ɪ ᴜɴᴅᴇʀsᴛᴀɴᴅ ᴏɴʟʏ :'on/yes' ᴏʀ 'off/no' only!" + ) + return "" + + if args[0].lower() in ("on", "yes"): + sql.add_acp_o(str(chat.id), True) + update.effective_message.reply_text( + "I'll try to unpin Telegram Channel messages!" + ) + return ( + "<b>{}:</b>" + "\n#ᴀɴᴛɪ_ᴄʜᴀɴɴᴇʟ_ᴘɪɴ" + "\n<b>ᴀᴅᴍɪɴ:</b> {}" + "\nʜᴀs ᴛᴏɢɢʟᴇᴅ ᴀɴᴛɪ ᴄʜᴀɴɴᴇʟ ᴘɪɴ ᴛᴏ <code>ᴏɴ</code>.".format( + html.escape(chat.title), mention_html(user.id, user.first_name) + ) + ) + elif args[0].lower() in ("off", "no"): + sql.add_acp_o(str(chat.id), False) + update.effective_message.reply_text("I won't unpin Telegram Channel Messages!") + return ( + "<b>{}:</b>" + "\n#ᴀɴᴛɪ_ᴄʜᴀɴɴᴇʟ_ᴘɪɴ" + "\n<b>ᴀᴅᴍɪɴ:</b> {}" + "\nʜᴀs ᴛᴏɢɢʟᴇᴅ ᴀɴᴛɪ ᴄʜᴀɴɴᴇʟ ᴘɪɴ ᴛᴏ <code>ᴏғғ</code>.".format( + html.escape(chat.title), mention_html(user.id, user.first_name) + ) + ) + else: + # idek what you're writing, say yes or no + update.effective_message.reply_text( + "ɪ ᴜɴᴅᴇʀsᴛᴀɴᴅ ᴏɴʟʏ 'on/yes' ᴏʀ 'off/no' only!" + ) + return "" + + +@run_async +@bot_admin +# @can_delete +@user_admin +@loggable +def clean_linked_channel(bot: Bot, update: Update, args: List[str]) -> str: + chat = update.effective_chat # type: Optional[Chat] + user = update.effective_user # type: Optional[User] + + if not args: + update.effective_message.reply_text("ɪ ᴜɴᴅᴇʀsᴛᴀɴᴅ 'on/yes' ᴏʀ 'off/no' only!") + return "" + + if args[0].lower() in ("on", "yes"): + sql.add_ldp_m(str(chat.id), True) + update.effective_message.reply_text( + "I'll ᴛʀʏ ᴛᴏ ᴅᴇʟᴇᴛᴇ ᴛᴇʟᴇɢʀᴀᴍ ᴄʜᴀɴɴᴇʟ ᴍᴇssᴀɢᴇs!" + ) + return ( + "<b>{}:</b>" + "\n#ᴄʟᴇᴀɴ_ᴄʜᴀɴɴᴇʟ_ᴍᴇssᴀɢᴇs" + "\n<b>ᴀᴅᴍɪɴ:</b> {}" + "\nʜᴀs ᴛᴏɢɢʟᴇᴅ ᴅᴇʟᴇᴛᴇ ᴄʜᴀɴɴᴇʟ ᴍᴇssᴀɢᴇs ᴛᴏ <code>ᴏɴ</code>.".format( + html.escape(chat.title), mention_html(user.id, user.first_name) + ) + ) + elif args[0].lower() in ("off", "no"): + sql.add_ldp_m(str(chat.id), False) + update.effective_message.reply_text("I ᴡᴏɴ'ᴛ ᴅᴇʟᴇᴛᴇ ᴛᴇʟᴇɢʀᴀᴍ ᴄʜᴀɴɴᴇʟ ᴍᴇssᴀɢᴇs!") + return ( + "<b>{}:</b>" + "\n#ᴄʟᴇᴀɴ_ᴄʜᴀɴɴᴇʟ_ᴍᴇssᴀɢᴇs" + "\n<b>ᴀᴅᴍɪɴ:</b> {}" + "\nʜᴀs ᴛᴏɢɢʟᴇᴅ ᴅᴇʟᴇᴛᴇ ᴄʜᴀɴɴᴇʟ ᴍᴇssᴀɢᴇs ᴛᴏ <code>ᴏғғ</code>.".format( + html.escape(chat.title), mention_html(user.id, user.first_name) + ) + ) + else: + # idek what you're writing, say yes or no + update.effective_message.reply_text("ɪ ᴜɴᴅᴇʀsᴛᴀɴᴅ 'on/yes' ᴏʀ 'off/no' only!") + return "" + + +@run_async +def amwltro_conreko(bot: Bot, update: Update): + chat = update.effective_chat # type: Optional[Chat] + message = update.effective_message # type: Optional[Message] + sctg = sql.get_current_settings(chat.id) + """we apparently do not receive any update for PINned messages + """ + if sctg and sctg.message_id != 0 and message.from_user.id == 777000: + if sctg.suacpmo: + try: + bot.unpin_chat_message(chat.id) + except: + pass + pin_chat_message(bot, chat.id, sctg.message_id, True) + if sctg.scldpmo: + try: + message.delete() + except: + pass + pin_chat_message(bot, chat.id, sctg.message_id, True) + + +def pin_chat_message(bot, chat_id, message_id, is_silent): + try: + bot.pinChatMessage(chat_id, message_id, disable_notification=is_silent) + except BadRequest as excp: + if excp.message == "Chat_not_modified": + pass + """else: + raise""" + + +__help__ = """ + +*ᴀᴅᴍɪɴ ᴏɴʟʏ:* + ⍟ /pin: `sɪʟᴇɴᴛʟʏ ᴘɪɴs ᴛʜᴇ ᴍᴇssᴀɢᴇ ʀᴇᴘʟɪᴇᴅ ᴛᴏ` + : ᴀᴅᴅ 'loud' ᴏʀ 'notify' ᴛᴏ ɢɪᴠᴇ ɴᴏᴛɪғs ᴛᴏ ᴜsᴇʀs + + ⍟ /unpin: `ᴜɴᴘɪɴs ᴛʜᴇ ᴄᴜʀʀᴇɴᴛʟʏ ᴘɪɴɴᴇᴅ ᴍᴇssᴀɢᴇ ` + + ⍟ /antichannelpin <yes/no/on/off>: `ᴅᴏɴ'ᴛ ʟᴇᴛ ᴛᴇʟᴇɢʀᴀᴍ ᴀᴜᴛᴏ-ᴘɪɴ ʟɪɴᴋᴇᴅ ᴄʜᴀɴɴᴇʟs ` + + ⍟ /cleanlinked <yes/no/on/off>: `ᴅᴇʟᴇᴛᴇ ᴍᴇssᴀɢᴇs sᴇɴᴛ ʙʏ ᴛʜᴇ ʟɪɴᴋᴇᴅ ᴄʜᴀɴɴᴇʟ `. + +Note: +ᴡʜᴇɴ using ᴀɴᴛɪᴄʜᴀɴɴᴇʟ pins, ᴍᴀᴋᴇ sᴜʀᴇ ᴛᴏ ᴜsᴇ ᴛʜᴇ /unpin ᴄᴏᴍᴍᴀɴᴅ, +ɪɴsᴛᴇᴀᴅ ᴏғ ᴅᴏɪɴɢ ɪᴛ ᴍᴀɴᴜᴀʟʟʏ. + +ᴏᴛʜᴇʀᴡɪsᴇ, the ᴏʟᴅ message ᴡɪʟʟ ɢᴇᴛ ʀᴇ-ᴘɪɴɴᴇᴅ ᴡʜᴇɴ ᴛʜᴇ ᴄʜᴀɴɴᴇʟ sᴇɴᴅs ᴀɴʏ ᴍᴇssᴀɢᴇs. +""" + +__mod_name__ = "𝙿ɪɴs" + + +PIN_HANDLER = CommandHandler( + "pin", pin, pass_args=True, filters=Filters.chat_type.groups +) +UNPIN_HANDLER = CommandHandler("unpin", unpin, filters=Filters.chat_type.groups) +ATCPIN_HANDLER = CommandHandler( + "antichannelpin", anti_channel_pin, pass_args=True, filters=Filters.chat_type.groups +) +CLCLDC_HANDLER = CommandHandler( + "cleanlinked", + clean_linked_channel, + pass_args=True, + filters=Filters.chat_type.groups, +) +AMWLTRO_HANDLER = MessageHandler( + Filters.forwarded & Filters.chat_type.groups, amwltro_conreko, edited_updates=False +) + +dispatcher.add_handler(PIN_HANDLER) +dispatcher.add_handler(UNPIN_HANDLER) +dispatcher.add_handler(ATCPIN_HANDLER) +dispatcher.add_handler(CLCLDC_HANDLER) +dispatcher.add_handler(AMWLTRO_HANDLER, PMW_GROUP) diff --git "a/Exon/modules/sql/ABISHNOI COPYRIGHT \302\251" "b/Exon/modules/sql/ABISHNOI COPYRIGHT \302\251" new file mode 100644 index 00000000..a7ec3b8e --- /dev/null +++ "b/Exon/modules/sql/ABISHNOI COPYRIGHT \302\251" @@ -0,0 +1,27 @@ +""" +MIT License + +Copyright (c) 2022 ABISHNOI + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" +# ""DEAR PRO PEOPLE, DON'T REMOVE & CHANGE THIS LINE +# TG :- @Abishnoi +# MY ALL BOTS :- Abishnoi_bots +# GITHUB :- KingAbishnoi "" \ No newline at end of file diff --git a/Exon/modules/sql/__init__.py b/Exon/modules/sql/__init__.py new file mode 100644 index 00000000..b67ec5ba --- /dev/null +++ b/Exon/modules/sql/__init__.py @@ -0,0 +1,48 @@ +""" +MIT License +Copyright (c) 2022 Aʙɪsʜɴᴏɪ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + + +from sqlalchemy import create_engine +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import scoped_session, sessionmaker + +from Exon import DB_URI +from Exon import LOGGER as log + +if DB_URI and DB_URI.startswith("postgres://"): + DB_URI = DB_URI.replace("postgres://", "postgresql://", 1) + + +def start() -> scoped_session: + engine = create_engine(DB_URI, client_encoding="utf8") # , echo=DEBUG) + log.info("[PostgreSQL] Connecting to database......") + BASE.metadata.bind = engine + BASE.metadata.create_all(engine) + return scoped_session(sessionmaker(bind=engine, autoflush=False)) + + +BASE = declarative_base() +try: + SESSION: scoped_session = start() +except Exception as e: + log.exception(f"[PostgreSQL] Failed to connect due to {e}") + exit() + +log.info("[PostgreSQL] Connection successful, session started.") diff --git a/Exon/modules/sql/afk_redis.py b/Exon/modules/sql/afk_redis.py new file mode 100644 index 00000000..c59279cf --- /dev/null +++ b/Exon/modules/sql/afk_redis.py @@ -0,0 +1,46 @@ +""" +MIT License +Copyright (c) 2022 Aʙɪsʜɴᴏɪ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +from Exon import REDIS + + +def is_user_afk(userid): + rget = REDIS.get(f"is_afk_{userid}") + if rget: + return True + else: + return False + + +def start_afk(userid, reason): + REDIS.set(f"is_afk_{userid}", reason) + + +def afk_reason(userid): + return strb(REDIS.get(f"is_afk_{userid}")) + + +def end_afk(userid): + REDIS.delete(f"is_afk_{userid}") + return True + + +def strb(redis_string): + return str(redis_string) diff --git a/Exon/modules/sql/antichannel_sql.py b/Exon/modules/sql/antichannel_sql.py new file mode 100644 index 00000000..814ea8a3 --- /dev/null +++ b/Exon/modules/sql/antichannel_sql.py @@ -0,0 +1,89 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + + +import threading + +from sqlalchemy import Boolean, Column +from sqlalchemy.sql.sqltypes import String + +from Exon.modules.sql import BASE, SESSION + + +class AntiChannelSettings(BASE): + __tablename__ = "anti_channel_settings" + + chat_id = Column(String(14), primary_key=True) + setting = Column(Boolean, default=False, nullable=False) + + def __init__(self, chat_id: int, disabled: bool): + self.chat_id = str(chat_id) + self.setting = disabled + + def __repr__(self): + return "<ᴀɴᴛɪғʟᴏᴏᴅ sᴇᴛᴛɪɴɢ {} ({})>".format(self.chat_id, self.setting) + + +AntiChannelSettings.__table__.create(checkfirst=True) +ANTICHANNEL_SETTING_LOCK = threading.RLock() + + +def enable_antichannel(chat_id: int): + with ANTICHANNEL_SETTING_LOCK: + chat = SESSION.query(AntiChannelSettings).get(str(chat_id)) + if not chat: + chat = AntiChannelSettings(str(chat_id), True) + + chat.setting = True + SESSION.add(chat) + SESSION.commit() + + +def disable_antichannel(chat_id: int): + with ANTICHANNEL_SETTING_LOCK: + chat = SESSION.query(AntiChannelSettings).get(str(chat_id)) + if not chat: + chat = AntiChannelSettings(str(chat_id), False) + + chat.setting = False + SESSION.add(chat) + SESSION.commit() + + +def antichannel_status(chat_id: int) -> bool: + with ANTICHANNEL_SETTING_LOCK: + d = SESSION.query(AntiChannelSettings).get(str(chat_id)) + if not d: + return False + return d.setting + + +def migrate_chat(old_chat_id, new_chat_id): + with ANTICHANNEL_SETTING_LOCK: + chat = SESSION.query(AntiChannelSettings).get(str(old_chat_id)) + if chat: + chat.chat_id = new_chat_id + SESSION.add(chat) + + SESSION.commit() diff --git a/Exon/modules/sql/antiflood_sql.py b/Exon/modules/sql/antiflood_sql.py new file mode 100644 index 00000000..cbc76086 --- /dev/null +++ b/Exon/modules/sql/antiflood_sql.py @@ -0,0 +1,171 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import threading + +from sqlalchemy import BigInteger, Column, String, UnicodeText + +from Exon.modules.sql import BASE, SESSION + +DEF_COUNT = 1 +DEF_LIMIT = 0 +DEF_OBJ = (None, DEF_COUNT, DEF_LIMIT) + + +class FloodControl(BASE): + __tablename__ = "antiflood" + chat_id = Column(String(14), primary_key=True) + user_id = Column(BigInteger) + count = Column(BigInteger, default=DEF_COUNT) + limit = Column(BigInteger, default=DEF_LIMIT) + + def __init__(self, chat_id): + self.chat_id = str(chat_id) # ensure string + + def __repr__(self): + return "<ғʟᴏᴏᴅ ᴄᴏɴᴛʀᴏʟ ғᴏʀ %s>" % self.chat_id + + +class FloodSettings(BASE): + __tablename__ = "antiflood_settings" + chat_id = Column(String(14), primary_key=True) + flood_type = Column(BigInteger, default=1) + value = Column(UnicodeText, default="0") + + def __init__(self, chat_id, flood_type=1, value="0"): + self.chat_id = str(chat_id) + self.flood_type = flood_type + self.value = value + + def __repr__(self): + return "<{} ᴡɪʟʟ ᴇxᴇᴄᴜᴛɪɴɢ {} ғᴏʀ ғʟᴏᴏᴅ.>".format(self.chat_id, self.flood_type) + + +FloodControl.__table__.create(checkfirst=True) +FloodSettings.__table__.create(checkfirst=True) + +INSERTION_FLOOD_LOCK = threading.RLock() +INSERTION_FLOOD_SETTINGS_LOCK = threading.RLock() + +CHAT_FLOOD = {} + + +def set_flood(chat_id, amount): + with INSERTION_FLOOD_LOCK: + flood = SESSION.query(FloodControl).get(str(chat_id)) + if not flood: + flood = FloodControl(str(chat_id)) + + flood.user_id = None + flood.limit = amount + + CHAT_FLOOD[str(chat_id)] = (None, DEF_COUNT, amount) + + SESSION.add(flood) + SESSION.commit() + + +def update_flood(chat_id: str, user_id) -> bool: + if str(chat_id) not in CHAT_FLOOD: + return + + curr_user_id, count, limit = CHAT_FLOOD.get(str(chat_id), DEF_OBJ) + + if limit == 0: # no antiflood + return False + + if user_id != curr_user_id or user_id is None: # other user + CHAT_FLOOD[str(chat_id)] = (user_id, DEF_COUNT, limit) + return False + + count += 1 + if count > limit: # too many msgs, kick + CHAT_FLOOD[str(chat_id)] = (None, DEF_COUNT, limit) + return True + + # default -> update + CHAT_FLOOD[str(chat_id)] = (user_id, count, limit) + return False + + +def get_flood_limit(chat_id): + return CHAT_FLOOD.get(str(chat_id), DEF_OBJ)[2] + + +def set_flood_strength(chat_id, flood_type, value): + # ғᴏʀ ғʟᴏᴏᴅ_ᴛʏᴘᴇ + # 1 = ban + # 2 = kick + # 3 = mute + # 4 = tban + # 5 = tmute + # 6 = ᴅᴍᴜᴛᴇ sᴏᴏɴ + with INSERTION_FLOOD_SETTINGS_LOCK: + curr_setting = SESSION.query(FloodSettings).get(str(chat_id)) + if not curr_setting: + curr_setting = FloodSettings( + chat_id, + flood_type=int(flood_type), + value=value, + ) + + curr_setting.flood_type = int(flood_type) + curr_setting.value = str(value) + + SESSION.add(curr_setting) + SESSION.commit() + + +def get_flood_setting(chat_id): + try: + setting = SESSION.query(FloodSettings).get(str(chat_id)) + if setting: + return setting.flood_type, setting.value + return 1, "0" + + finally: + SESSION.close() + + +def migrate_chat(old_chat_id, new_chat_id): + with INSERTION_FLOOD_LOCK: + flood = SESSION.query(FloodControl).get(str(old_chat_id)) + if flood: + CHAT_FLOOD[str(new_chat_id)] = CHAT_FLOOD.get(str(old_chat_id), DEF_OBJ) + flood.chat_id = str(new_chat_id) + SESSION.commit() + + SESSION.close() + + +def __load_flood_settings(): + global CHAT_FLOOD + try: + all_chats = SESSION.query(FloodControl).all() + CHAT_FLOOD = {chat.chat_id: (None, DEF_COUNT, chat.limit) for chat in all_chats} + finally: + SESSION.close() + + +__load_flood_settings() diff --git a/Exon/modules/sql/antilinkedchannel_sql.py b/Exon/modules/sql/antilinkedchannel_sql.py new file mode 100644 index 00000000..cf795f7a --- /dev/null +++ b/Exon/modules/sql/antilinkedchannel_sql.py @@ -0,0 +1,142 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import threading + +from sqlalchemy import Boolean, Column +from sqlalchemy.sql.sqltypes import String + +from Exon.modules.sql import BASE, SESSION + + +class AntiLinkedChannelSettings(BASE): + __tablename__ = "anti_linked_channel_settings" + + chat_id = Column(String(14), primary_key=True) + setting = Column(Boolean, default=False, nullable=False) + + def __init__(self, chat_id: int, disabled: bool): + self.chat_id = str(chat_id) + self.setting = disabled + + def __repr__(self): + return "<Antilinked setting {} ({})>".format(self.chat_id, self.setting) + + +class AntiPinChannelSettings(BASE): + __tablename__ = "anti_pin_channel_settings" + + chat_id = Column(String(14), primary_key=True) + setting = Column(Boolean, default=False, nullable=False) + + def __init__(self, chat_id: int, disabled: bool): + self.chat_id = str(chat_id) + self.setting = disabled + + def __repr__(self): + return "<ᴀɴᴛɪᴘɪɴ sᴇᴛᴛɪɴɢ {} ({})>".format(self.chat_id, self.setting) + + +AntiLinkedChannelSettings.__table__.create(checkfirst=True) +ANTI_LINKED_CHANNEL_SETTING_LOCK = threading.RLock() + +AntiPinChannelSettings.__table__.create(checkfirst=True) +ANTI_PIN_CHANNEL_SETTING_LOCK = threading.RLock() + + +def enable_linked(chat_id: int): + with ANTI_LINKED_CHANNEL_SETTING_LOCK: + chat = SESSION.query(AntiLinkedChannelSettings).get(str(chat_id)) + if not chat: + chat = AntiLinkedChannelSettings(chat_id, True) + + chat.setting = True + SESSION.add(chat) + SESSION.commit() + + +def enable_pin(chat_id: int): + with ANTI_PIN_CHANNEL_SETTING_LOCK: + chat = SESSION.query(AntiPinChannelSettings).get(str(chat_id)) + if not chat: + chat = AntiPinChannelSettings(chat_id, True) + + chat.setting = True + SESSION.add(chat) + SESSION.commit() + + +def disable_linked(chat_id: int): + with ANTI_LINKED_CHANNEL_SETTING_LOCK: + chat = SESSION.query(AntiLinkedChannelSettings).get(str(chat_id)) + if not chat: + chat = AntiLinkedChannelSettings(chat_id, False) + + chat.setting = False + SESSION.add(chat) + SESSION.commit() + + +def disable_pin(chat_id: int): + with ANTI_PIN_CHANNEL_SETTING_LOCK: + chat = SESSION.query(AntiPinChannelSettings).get(str(chat_id)) + if not chat: + chat = AntiPinChannelSettings(chat_id, False) + + chat.setting = False + SESSION.add(chat) + SESSION.commit() + + +def status_linked(chat_id: int) -> bool: + with ANTI_LINKED_CHANNEL_SETTING_LOCK: + d = SESSION.query(AntiLinkedChannelSettings).get(str(chat_id)) + if not d: + return False + return d.setting + + +def status_pin(chat_id: int) -> bool: + with ANTI_PIN_CHANNEL_SETTING_LOCK: + d = SESSION.query(AntiPinChannelSettings).get(str(chat_id)) + if not d: + return False + return d.setting + + +def migrate_chat(old_chat_id, new_chat_id): + with ANTI_LINKED_CHANNEL_SETTING_LOCK: + chat = SESSION.query(AntiLinkedChannelSettings).get(str(old_chat_id)) + if chat: + chat.chat_id = new_chat_id + SESSION.add(chat) + + SESSION.commit() + with ANTI_PIN_CHANNEL_SETTING_LOCK: + chat = SESSION.query(AntiPinChannelSettings).get(str(old_chat_id)) + if chat: + chat.chat_id = new_chat_id + SESSION.add(chat) + + SESSION.commit() diff --git a/Exon/modules/sql/approve_sql.py b/Exon/modules/sql/approve_sql.py new file mode 100644 index 00000000..7d4d51a0 --- /dev/null +++ b/Exon/modules/sql/approve_sql.py @@ -0,0 +1,85 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import threading + +from sqlalchemy import BigInteger, Column, String + +from Exon.modules.sql import BASE, SESSION + + +class Approvals(BASE): + __tablename__ = "approval" + chat_id = Column(String(14), primary_key=True) + user_id = Column(BigInteger, primary_key=True) + + def __init__(self, chat_id, user_id): + self.chat_id = str(chat_id) # ensure string + self.user_id = user_id + + def __repr__(self): + return "<ᴀᴘᴘʀᴏᴠᴇ %s>" % self.user_id + + +Approvals.__table__.create(checkfirst=True) + +APPROVE_INSERTION_LOCK = threading.RLock() + + +def approve(chat_id, user_id): + with APPROVE_INSERTION_LOCK: + approve_user = Approvals(str(chat_id), user_id) + SESSION.add(approve_user) + SESSION.commit() + + +def is_approved(chat_id, user_id): + try: + return SESSION.query(Approvals).get((str(chat_id), user_id)) + finally: + SESSION.close() + + +def disapprove(chat_id, user_id): + with APPROVE_INSERTION_LOCK: + disapprove_user = SESSION.query(Approvals).get((str(chat_id), user_id)) + if disapprove_user: + SESSION.delete(disapprove_user) + SESSION.commit() + return True + else: + SESSION.close() + return False + + +def list_approved(chat_id): + try: + return ( + SESSION.query(Approvals) + .filter(Approvals.chat_id == str(chat_id)) + .order_by(Approvals.user_id.asc()) + .all() + ) + finally: + SESSION.close() diff --git a/Exon/modules/sql/blacklist_sql.py b/Exon/modules/sql/blacklist_sql.py new file mode 100644 index 00000000..ebbd294c --- /dev/null +++ b/Exon/modules/sql/blacklist_sql.py @@ -0,0 +1,223 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import threading + +from sqlalchemy import BigInteger, Column, String, UnicodeText, distinct, func + +from Exon.modules.sql import BASE, SESSION + + +class BlackListFilters(BASE): + __tablename__ = "blacklist" + chat_id = Column(String(14), primary_key=True) + trigger = Column(UnicodeText, primary_key=True, nullable=False) + + def __init__(self, chat_id, trigger): + self.chat_id = str(chat_id) # ensure string + self.trigger = trigger + + def __repr__(self): + return "<ʙʟᴀᴄᴋʟɪsᴛ ғɪʟᴛᴇʀ '%s' ғᴏʀ %s>" % (self.trigger, self.chat_id) + + def __eq__(self, other): + return bool( + isinstance(other, BlackListFilters) + and self.chat_id == other.chat_id + and self.trigger == other.trigger, + ) + + +class BlacklistSettings(BASE): + __tablename__ = "blacklist_settings" + chat_id = Column(String(14), primary_key=True) + blacklist_type = Column(BigInteger, default=1) + value = Column(UnicodeText, default="0") + + def __init__(self, chat_id, blacklist_type=1, value="0"): + self.chat_id = str(chat_id) + self.blacklist_type = blacklist_type + self.value = value + + def __repr__(self): + return "<{} ᴡɪʟʟ ᴇxᴇᴄᴜᴛɪɴɢ {} ғᴏʀ ʙʟᴀᴄᴋʟɪsᴛ ᴛʀɪɢɢᴇʀ.>".format( + self.chat_id, + self.blacklist_type, + ) + + +BlackListFilters.__table__.create(checkfirst=True) +BlacklistSettings.__table__.create(checkfirst=True) + +BLACKLIST_FILTER_INSERTION_LOCK = threading.RLock() +BLACKLIST_SETTINGS_INSERTION_LOCK = threading.RLock() + +CHAT_BLACKLISTS = {} +CHAT_SETTINGS_BLACKLISTS = {} + + +def add_to_blacklist(chat_id, trigger): + with BLACKLIST_FILTER_INSERTION_LOCK: + blacklist_filt = BlackListFilters(str(chat_id), trigger) + + SESSION.merge(blacklist_filt) # merge to avoid duplicate key issues + SESSION.commit() + global CHAT_BLACKLISTS + if CHAT_BLACKLISTS.get(str(chat_id), set()) == set(): + CHAT_BLACKLISTS[str(chat_id)] = {trigger} + else: + CHAT_BLACKLISTS.get(str(chat_id), set()).add(trigger) + + +def rm_from_blacklist(chat_id, trigger): + with BLACKLIST_FILTER_INSERTION_LOCK: + blacklist_filt = SESSION.query(BlackListFilters).get((str(chat_id), trigger)) + if blacklist_filt: + if trigger in CHAT_BLACKLISTS.get(str(chat_id), set()): # sanity check + CHAT_BLACKLISTS.get(str(chat_id), set()).remove(trigger) + + SESSION.delete(blacklist_filt) + SESSION.commit() + return True + + SESSION.close() + return False + + +def get_chat_blacklist(chat_id): + return CHAT_BLACKLISTS.get(str(chat_id), set()) + + +def num_blacklist_filters(): + try: + return SESSION.query(BlackListFilters).count() + finally: + SESSION.close() + + +def num_blacklist_chat_filters(chat_id): + try: + return ( + SESSION.query(BlackListFilters.chat_id) + .filter(BlackListFilters.chat_id == str(chat_id)) + .count() + ) + finally: + SESSION.close() + + +def num_blacklist_filter_chats(): + try: + return SESSION.query(func.count(distinct(BlackListFilters.chat_id))).scalar() + finally: + SESSION.close() + + +def set_blacklist_strength(chat_id, blacklist_type, value): + # for blacklist_type + # 0 = nothing + # 1 = delete + # 2 = warn + # 3 = mute + # 4 = kick + # 5 = ban + # 6 = tban + # 7 = tmute + with BLACKLIST_SETTINGS_INSERTION_LOCK: + global CHAT_SETTINGS_BLACKLISTS + curr_setting = SESSION.query(BlacklistSettings).get(str(chat_id)) + if not curr_setting: + curr_setting = BlacklistSettings( + chat_id, + blacklist_type=int(blacklist_type), + value=value, + ) + + curr_setting.blacklist_type = int(blacklist_type) + curr_setting.value = str(value) + CHAT_SETTINGS_BLACKLISTS[str(chat_id)] = { + "blacklist_type": int(blacklist_type), + "value": value, + } + + SESSION.add(curr_setting) + SESSION.commit() + + +def get_blacklist_setting(chat_id): + try: + setting = CHAT_SETTINGS_BLACKLISTS.get(str(chat_id)) + if setting: + return setting["blacklist_type"], setting["value"] + return 1, "0" + + finally: + SESSION.close() + + +def __load_chat_blacklists(): + global CHAT_BLACKLISTS + try: + chats = SESSION.query(BlackListFilters.chat_id).distinct().all() + for (chat_id,) in chats: # remove tuple by ( ,) + CHAT_BLACKLISTS[chat_id] = [] + + all_filters = SESSION.query(BlackListFilters).all() + for x in all_filters: + CHAT_BLACKLISTS[x.chat_id] += [x.trigger] + + CHAT_BLACKLISTS = {x: set(y) for x, y in CHAT_BLACKLISTS.items()} + + finally: + SESSION.close() + + +def __load_chat_settings_blacklists(): + global CHAT_SETTINGS_BLACKLISTS + try: + chats_settings = SESSION.query(BlacklistSettings).all() + for x in chats_settings: # remove tuple by ( ,) + CHAT_SETTINGS_BLACKLISTS[x.chat_id] = { + "blacklist_type": x.blacklist_type, + "value": x.value, + } + + finally: + SESSION.close() + + +def migrate_chat(old_chat_id, new_chat_id): + with BLACKLIST_FILTER_INSERTION_LOCK: + chat_filters = ( + SESSION.query(BlackListFilters) + .filter(BlackListFilters.chat_id == str(old_chat_id)) + .all() + ) + for filt in chat_filters: + filt.chat_id = str(new_chat_id) + SESSION.commit() + + +__load_chat_blacklists() +__load_chat_settings_blacklists() diff --git a/Exon/modules/sql/blacklistusers_sql.py b/Exon/modules/sql/blacklistusers_sql.py new file mode 100644 index 00000000..97889e3a --- /dev/null +++ b/Exon/modules/sql/blacklistusers_sql.py @@ -0,0 +1,90 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import threading + +from sqlalchemy import Column, String, UnicodeText + +from Exon.modules.sql import BASE, SESSION + + +class BlacklistUsers(BASE): + __tablename__ = "blacklistusers" + user_id = Column(String(14), primary_key=True) + reason = Column(UnicodeText) + + def __init__(self, user_id, reason=None): + self.user_id = user_id + self.reason = reason + + +BlacklistUsers.__table__.create(checkfirst=True) + +BLACKLIST_LOCK = threading.RLock() +BLACKLIST_USERS = set() + + +def blacklist_user(user_id, reason=None): + with BLACKLIST_LOCK: + user = SESSION.query(BlacklistUsers).get(str(user_id)) + if not user: + user = BlacklistUsers(str(user_id), reason) + else: + user.reason = reason + + SESSION.add(user) + SESSION.commit() + __load_blacklist_userid_list() + + +def unblacklist_user(user_id): + with BLACKLIST_LOCK: + user = SESSION.query(BlacklistUsers).get(str(user_id)) + if user: + SESSION.delete(user) + + SESSION.commit() + __load_blacklist_userid_list() + + +def get_reason(user_id): + user = SESSION.query(BlacklistUsers).get(str(user_id)) + rep = user.reason if user else "" + SESSION.close() + return rep + + +def is_user_blacklisted(user_id): + return user_id in BLACKLIST_USERS + + +def __load_blacklist_userid_list(): + global BLACKLIST_USERS + try: + BLACKLIST_USERS = {int(x.user_id) for x in SESSION.query(BlacklistUsers).all()} + finally: + SESSION.close() + + +__load_blacklist_userid_list() diff --git a/Exon/modules/sql/blsticker_sql.py b/Exon/modules/sql/blsticker_sql.py new file mode 100644 index 00000000..82670e97 --- /dev/null +++ b/Exon/modules/sql/blsticker_sql.py @@ -0,0 +1,223 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import threading + +from sqlalchemy import BigInteger, Column, String, UnicodeText, distinct, func + +from Exon.modules.sql import BASE, SESSION + + +class StickersFilters(BASE): + __tablename__ = "blacklist_stickers" + chat_id = Column(String(14), primary_key=True) + trigger = Column(UnicodeText, primary_key=True, nullable=False) + + def __init__(self, chat_id, trigger): + self.chat_id = str(chat_id) # ensure string + self.trigger = trigger + + def __repr__(self): + return "<sᴛɪᴄᴋᴇʀs ғɪʟᴛᴇʀ '%s' ғᴏʀ %s>" % (self.trigger, self.chat_id) + + def __eq__(self, other): + return bool( + isinstance(other, StickersFilters) + and self.chat_id == other.chat_id + and self.trigger == other.trigger, + ) + + +class StickerSettings(BASE): + __tablename__ = "blsticker_settings" + chat_id = Column(String(14), primary_key=True) + blacklist_type = Column(BigInteger, default=1) + value = Column(UnicodeText, default="0") + + def __init__(self, chat_id, blacklist_type=1, value="0"): + self.chat_id = str(chat_id) + self.blacklist_type = blacklist_type + self.value = value + + def __repr__(self): + return "<{} ᴡɪʟʟ ᴇxᴇᴄᴜᴛɪɴɢ {} ғᴏʀ ʙʟᴀᴄᴋʟɪsᴛ ᴛʀɪɢɢᴇʀ.>".format( + self.chat_id, + self.blacklist_type, + ) + + +StickersFilters.__table__.create(checkfirst=True) +StickerSettings.__table__.create(checkfirst=True) + +STICKERS_FILTER_INSERTION_LOCK = threading.RLock() +STICKSET_FILTER_INSERTION_LOCK = threading.RLock() + +CHAT_STICKERS = {} +CHAT_BLSTICK_BLACKLISTS = {} + + +def add_to_stickers(chat_id, trigger): + with STICKERS_FILTER_INSERTION_LOCK: + stickers_filt = StickersFilters(str(chat_id), trigger) + + SESSION.merge(stickers_filt) # merge to avoid duplicate key issues + SESSION.commit() + global CHAT_STICKERS + if CHAT_STICKERS.get(str(chat_id), set()) == set(): + CHAT_STICKERS[str(chat_id)] = {trigger} + else: + CHAT_STICKERS.get(str(chat_id), set()).add(trigger) + + +def rm_from_stickers(chat_id, trigger): + with STICKERS_FILTER_INSERTION_LOCK: + stickers_filt = SESSION.query(StickersFilters).get((str(chat_id), trigger)) + if stickers_filt: + if trigger in CHAT_STICKERS.get(str(chat_id), set()): # sanity check + CHAT_STICKERS.get(str(chat_id), set()).remove(trigger) + + SESSION.delete(stickers_filt) + SESSION.commit() + return True + + SESSION.close() + return False + + +def get_chat_stickers(chat_id): + return CHAT_STICKERS.get(str(chat_id), set()) + + +def num_stickers_filters(): + try: + return SESSION.query(StickersFilters).count() + finally: + SESSION.close() + + +def num_stickers_chat_filters(chat_id): + try: + return ( + SESSION.query(StickersFilters.chat_id) + .filter(StickersFilters.chat_id == str(chat_id)) + .count() + ) + finally: + SESSION.close() + + +def num_stickers_filter_chats(): + try: + return SESSION.query(func.count(distinct(StickersFilters.chat_id))).scalar() + finally: + SESSION.close() + + +def set_blacklist_strength(chat_id, blacklist_type, value): + # for blacklist_type + # 0 = nothing + # 1 = delete + # 2 = warn + # 3 = mute + # 4 = kick + # 5 = ban + # 6 = tban + # 7 = tmute + with STICKSET_FILTER_INSERTION_LOCK: + global CHAT_BLSTICK_BLACKLISTS + curr_setting = SESSION.query(StickerSettings).get(str(chat_id)) + if not curr_setting: + curr_setting = StickerSettings( + chat_id, + blacklist_type=int(blacklist_type), + value=value, + ) + + curr_setting.blacklist_type = int(blacklist_type) + curr_setting.value = str(value) + CHAT_BLSTICK_BLACKLISTS[str(chat_id)] = { + "blacklist_type": int(blacklist_type), + "value": value, + } + + SESSION.add(curr_setting) + SESSION.commit() + + +def get_blacklist_setting(chat_id): + try: + setting = CHAT_BLSTICK_BLACKLISTS.get(str(chat_id)) + if setting: + return setting["blacklist_type"], setting["value"] + return 1, "0" + + finally: + SESSION.close() + + +def __load_CHAT_STICKERS(): + global CHAT_STICKERS + try: + chats = SESSION.query(StickersFilters.chat_id).distinct().all() + for (chat_id,) in chats: # remove tuple by ( ,) + CHAT_STICKERS[chat_id] = [] + + all_filters = SESSION.query(StickersFilters).all() + for x in all_filters: + CHAT_STICKERS[x.chat_id] += [x.trigger] + + CHAT_STICKERS = {x: set(y) for x, y in CHAT_STICKERS.items()} + + finally: + SESSION.close() + + +def __load_chat_stickerset_blacklists(): + global CHAT_BLSTICK_BLACKLISTS + try: + chats_settings = SESSION.query(StickerSettings).all() + for x in chats_settings: # remove tuple by ( ,) + CHAT_BLSTICK_BLACKLISTS[x.chat_id] = { + "blacklist_type": x.blacklist_type, + "value": x.value, + } + + finally: + SESSION.close() + + +def migrate_chat(old_chat_id, new_chat_id): + with STICKERS_FILTER_INSERTION_LOCK: + chat_filters = ( + SESSION.query(StickersFilters) + .filter(StickersFilters.chat_id == str(old_chat_id)) + .all() + ) + for filt in chat_filters: + filt.chat_id = str(new_chat_id) + SESSION.commit() + + +__load_CHAT_STICKERS() +__load_chat_stickerset_blacklists() diff --git a/Exon/modules/sql/cleaner_sql.py b/Exon/modules/sql/cleaner_sql.py new file mode 100644 index 00000000..81ba9141 --- /dev/null +++ b/Exon/modules/sql/cleaner_sql.py @@ -0,0 +1,227 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import threading + +from sqlalchemy import Boolean, Column, UnicodeText + +from Exon.modules.sql import BASE, SESSION + + +class CleanerBlueTextChatSettings(BASE): + __tablename__ = "cleaner_bluetext_chat_setting" + chat_id = Column(UnicodeText, primary_key=True) + is_enable = Column(Boolean, default=False) + + def __init__(self, chat_id, is_enable): + self.chat_id = chat_id + self.is_enable = is_enable + + def __repr__(self): + return "ᴄʟᴇᴀɴ ʙʟᴜᴇ ᴛᴇxᴛ ғᴏʀ {}".format(self.chat_id) + + +class CleanerBlueTextChat(BASE): + __tablename__ = "cleaner_bluetext_chat_ignore_commands" + chat_id = Column(UnicodeText, primary_key=True) + command = Column(UnicodeText, primary_key=True) + + def __init__(self, chat_id, command): + self.chat_id = chat_id + self.command = command + + +class CleanerBlueTextGlobal(BASE): + __tablename__ = "cleaner_bluetext_global_ignore_commands" + command = Column(UnicodeText, primary_key=True) + + def __init__(self, command): + self.command = command + + +CleanerBlueTextChatSettings.__table__.create(checkfirst=True) +CleanerBlueTextChat.__table__.create(checkfirst=True) +CleanerBlueTextGlobal.__table__.create(checkfirst=True) + +CLEANER_CHAT_SETTINGS = threading.RLock() +CLEANER_CHAT_LOCK = threading.RLock() +CLEANER_GLOBAL_LOCK = threading.RLock() + +CLEANER_CHATS = {} +GLOBAL_IGNORE_COMMANDS = set() + + +def set_cleanbt(chat_id, is_enable): + with CLEANER_CHAT_SETTINGS: + curr = SESSION.query(CleanerBlueTextChatSettings).get(str(chat_id)) + + if not curr: + curr = CleanerBlueTextChatSettings(str(chat_id), is_enable) + else: + curr.is_enabled = is_enable + + if str(chat_id) not in CLEANER_CHATS: + CLEANER_CHATS.setdefault( + str(chat_id), {"setting": False, "commands": set()} + ) + + CLEANER_CHATS[str(chat_id)]["setting"] = is_enable + + SESSION.add(curr) + SESSION.commit() + + +def chat_ignore_command(chat_id, ignore): + ignore = ignore.lower() + with CLEANER_CHAT_LOCK: + ignored = SESSION.query(CleanerBlueTextChat).get((str(chat_id), ignore)) + + if not ignored: + + if str(chat_id) not in CLEANER_CHATS: + CLEANER_CHATS.setdefault( + str(chat_id), {"setting": False, "commands": set()} + ) + + CLEANER_CHATS[str(chat_id)]["commands"].add(ignore) + + ignored = CleanerBlueTextChat(str(chat_id), ignore) + SESSION.add(ignored) + SESSION.commit() + return True + SESSION.close() + return False + + +def chat_unignore_command(chat_id, unignore): + unignore = unignore.lower() + with CLEANER_CHAT_LOCK: + unignored = SESSION.query(CleanerBlueTextChat).get((str(chat_id), unignore)) + + if unignored: + + if str(chat_id) not in CLEANER_CHATS: + CLEANER_CHATS.setdefault( + str(chat_id), {"setting": False, "commands": set()} + ) + if unignore in CLEANER_CHATS.get(str(chat_id)).get("commands"): + CLEANER_CHATS[str(chat_id)]["commands"].remove(unignore) + + SESSION.delete(unignored) + SESSION.commit() + return True + + SESSION.close() + return False + + +def global_ignore_command(command): + command = command.lower() + with CLEANER_GLOBAL_LOCK: + ignored = SESSION.query(CleanerBlueTextGlobal).get(str(command)) + + if not ignored: + GLOBAL_IGNORE_COMMANDS.add(command) + + ignored = CleanerBlueTextGlobal(str(command)) + SESSION.add(ignored) + SESSION.commit() + return True + + SESSION.close() + return False + + +def global_unignore_command(command): + command = command.lower() + with CLEANER_GLOBAL_LOCK: + unignored = SESSION.query(CleanerBlueTextGlobal).get(str(command)) + + if unignored: + if command in GLOBAL_IGNORE_COMMANDS: + GLOBAL_IGNORE_COMMANDS.remove(command) + + SESSION.delete(command) + SESSION.commit() + return True + + SESSION.close() + return False + + +def is_command_ignored(chat_id, command): + if command.lower() in GLOBAL_IGNORE_COMMANDS: + return True + + if str(chat_id) in CLEANER_CHATS and command.lower() in CLEANER_CHATS.get( + str(chat_id) + ).get("commands"): + return True + + return False + + +def is_enabled(chat_id): + if str(chat_id) in CLEANER_CHATS: + return CLEANER_CHATS.get(str(chat_id)).get("setting") + + return False + + +def get_all_ignored(chat_id): + if str(chat_id) in CLEANER_CHATS: + LOCAL_IGNORE_COMMANDS = CLEANER_CHATS.get(str(chat_id)).get("commands") + else: + LOCAL_IGNORE_COMMANDS = set() + + return GLOBAL_IGNORE_COMMANDS, LOCAL_IGNORE_COMMANDS + + +def __load_cleaner_list(): + global GLOBAL_IGNORE_COMMANDS + global CLEANER_CHATS + + try: + GLOBAL_IGNORE_COMMANDS = { + x.command for x in SESSION.query(CleanerBlueTextGlobal).all() + } + finally: + SESSION.close() + + try: + for x in SESSION.query(CleanerBlueTextChatSettings).all(): + CLEANER_CHATS.setdefault(x.chat_id, {"setting": False, "commands": set()}) + CLEANER_CHATS[x.chat_id]["setting"] = x.is_enable + finally: + SESSION.close() + + try: + for x in SESSION.query(CleanerBlueTextChat).all(): + CLEANER_CHATS.setdefault(x.chat_id, {"setting": False, "commands": set()}) + CLEANER_CHATS[x.chat_id]["commands"].add(x.command) + finally: + SESSION.close() + + +__load_cleaner_list() diff --git a/Exon/modules/sql/connection_sql.py b/Exon/modules/sql/connection_sql.py new file mode 100644 index 00000000..8105833e --- /dev/null +++ b/Exon/modules/sql/connection_sql.py @@ -0,0 +1,230 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import threading +import time +from typing import Union + +from sqlalchemy import BigInteger, Boolean, Column, String, UnicodeText + +from Exon.modules.sql import BASE, SESSION + + +class ChatAccessConnectionSettings(BASE): + __tablename__ = "access_connection" + chat_id = Column(String(14), primary_key=True) + allow_connect_to_chat = Column(Boolean, default=True) + + def __init__(self, chat_id, allow_connect_to_chat): + self.chat_id = str(chat_id) + self.allow_connect_to_chat = str(allow_connect_to_chat) + + def __repr__(self): + return "<ᴄʜᴀᴛ ᴀᴄᴄᴇss sᴇᴛᴛɪɴɢs ({}) is {}>".format( + self.chat_id, + self.allow_connect_to_chat, + ) + + +class Connection(BASE): + __tablename__ = "connection" + user_id = Column(BigInteger, primary_key=True) + chat_id = Column(String(14)) + + def __init__(self, user_id, chat_id): + self.user_id = user_id + self.chat_id = str(chat_id) # Ensure String + + +class ConnectionHistory(BASE): + __tablename__ = "connection_history" + user_id = Column(BigInteger, primary_key=True) + chat_id = Column(String(14), primary_key=True) + chat_name = Column(UnicodeText) + conn_time = Column(BigInteger) + + def __init__(self, user_id, chat_id, chat_name, conn_time): + self.user_id = user_id + self.chat_id = str(chat_id) + self.chat_name = str(chat_name) + self.conn_time = int(conn_time) + + def __repr__(self): + return "<ᴄᴏɴɴᴇᴄᴛɪᴏɴ ᴜsᴇʀ {} ʜɪsᴛᴏʀʏ {}>".format(self.user_id, self.chat_id) + + +ChatAccessConnectionSettings.__table__.create(checkfirst=True) +Connection.__table__.create(checkfirst=True) +ConnectionHistory.__table__.create(checkfirst=True) + +CHAT_ACCESS_LOCK = threading.RLock() +CONNECTION_INSERTION_LOCK = threading.RLock() +CONNECTION_HISTORY_LOCK = threading.RLock() + +HISTORY_CONNECT = {} + + +def allow_connect_to_chat(chat_id: Union[str, int]) -> bool: + try: + chat_setting = SESSION.query(ChatAccessConnectionSettings).get(str(chat_id)) + if chat_setting: + return chat_setting.allow_connect_to_chat + return False + finally: + SESSION.close() + + +def set_allow_connect_to_chat(chat_id: Union[int, str], setting: bool): + with CHAT_ACCESS_LOCK: + chat_setting = SESSION.query(ChatAccessConnectionSettings).get(str(chat_id)) + if not chat_setting: + chat_setting = ChatAccessConnectionSettings(chat_id, setting) + + chat_setting.allow_connect_to_chat = setting + SESSION.add(chat_setting) + SESSION.commit() + + +def connect(user_id, chat_id): + with CONNECTION_INSERTION_LOCK: + prev = SESSION.query(Connection).get((int(user_id))) + if prev: + SESSION.delete(prev) + connect_to_chat = Connection(int(user_id), chat_id) + SESSION.add(connect_to_chat) + SESSION.commit() + return True + + +def get_connected_chat(user_id): + try: + return SESSION.query(Connection).get((int(user_id))) + finally: + SESSION.close() + + +def curr_connection(chat_id): + try: + return SESSION.query(Connection).get((str(chat_id))) + finally: + SESSION.close() + + +def disconnect(user_id): + with CONNECTION_INSERTION_LOCK: + disconnect = SESSION.query(Connection).get((int(user_id))) + if disconnect: + SESSION.delete(disconnect) + SESSION.commit() + return True + SESSION.close() + return False + + +def add_history_conn(user_id, chat_id, chat_name): + global HISTORY_CONNECT + with CONNECTION_HISTORY_LOCK: + conn_time = int(time.time()) + if HISTORY_CONNECT.get(int(user_id)): + counting = ( + SESSION.query(ConnectionHistory.user_id) + .filter(ConnectionHistory.user_id == str(user_id)) + .count() + ) + getchat_id = { + HISTORY_CONNECT[int(user_id)][x]["chat_id"]: x + for x in HISTORY_CONNECT[int(user_id)] + } + + if chat_id in getchat_id: + todeltime = getchat_id[str(chat_id)] + delold = SESSION.query(ConnectionHistory).get( + (int(user_id), str(chat_id)), + ) + if delold: + SESSION.delete(delold) + HISTORY_CONNECT[int(user_id)].pop(todeltime) + elif counting >= 5: + todel = list(HISTORY_CONNECT[int(user_id)]) + todel.reverse() + todel = todel[4:] + for x in todel: + chat_old = HISTORY_CONNECT[int(user_id)][x]["chat_id"] + delold = SESSION.query(ConnectionHistory).get( + (int(user_id), str(chat_old)), + ) + if delold: + SESSION.delete(delold) + HISTORY_CONNECT[int(user_id)].pop(x) + else: + HISTORY_CONNECT[int(user_id)] = {} + delold = SESSION.query(ConnectionHistory).get((int(user_id), str(chat_id))) + if delold: + SESSION.delete(delold) + history = ConnectionHistory(int(user_id), str(chat_id), chat_name, conn_time) + SESSION.add(history) + SESSION.commit() + HISTORY_CONNECT[int(user_id)][conn_time] = { + "chat_name": chat_name, + "chat_id": str(chat_id), + } + + +def get_history_conn(user_id): + if not HISTORY_CONNECT.get(int(user_id)): + HISTORY_CONNECT[int(user_id)] = {} + return HISTORY_CONNECT[int(user_id)] + + +def clear_history_conn(user_id): + global HISTORY_CONNECT + todel = list(HISTORY_CONNECT[int(user_id)]) + for x in todel: + chat_old = HISTORY_CONNECT[int(user_id)][x]["chat_id"] + delold = SESSION.query(ConnectionHistory).get((int(user_id), str(chat_old))) + if delold: + SESSION.delete(delold) + HISTORY_CONNECT[int(user_id)].pop(x) + SESSION.commit() + return True + + +def __load_user_history(): + global HISTORY_CONNECT + try: + qall = SESSION.query(ConnectionHistory).all() + HISTORY_CONNECT = {} + for x in qall: + check = HISTORY_CONNECT.get(x.user_id) + if check is None: + HISTORY_CONNECT[x.user_id] = {} + HISTORY_CONNECT[x.user_id][x.conn_time] = { + "chat_name": x.chat_name, + "chat_id": x.chat_id, + } + finally: + SESSION.close() + + +__load_user_history() diff --git a/Exon/modules/sql/cust_filters_sql.py b/Exon/modules/sql/cust_filters_sql.py new file mode 100644 index 00000000..2dcadfbc --- /dev/null +++ b/Exon/modules/sql/cust_filters_sql.py @@ -0,0 +1,422 @@ +""" +MIT License +Copyright (c) 2022 Aʙɪsʜɴᴏɪ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import threading + +from sqlalchemy import Boolean, Column, Integer, String, UnicodeText, distinct, func + +from Exon.modules.helper_funcs.msg_types import Types +from Exon.modules.sql import BASE, SESSION + + +class CustomFilters(BASE): + __tablename__ = "cust_filters" + chat_id = Column(String(14), primary_key=True) + keyword = Column(UnicodeText, primary_key=True, nullable=False) + reply = Column(UnicodeText, nullable=False) + is_sticker = Column(Boolean, nullable=False, default=False) + is_document = Column(Boolean, nullable=False, default=False) + is_image = Column(Boolean, nullable=False, default=False) + is_audio = Column(Boolean, nullable=False, default=False) + is_voice = Column(Boolean, nullable=False, default=False) + is_video = Column(Boolean, nullable=False, default=False) + + has_buttons = Column(Boolean, nullable=False, default=False) + # NOTE: Here for legacy purposes, to ensure older filters don't mess up. + has_markdown = Column(Boolean, nullable=False, default=False) + + # NEW FILTER + # alter table cust_filters add column reply_text text; + # alter table cust_filters add column file_type integer default 1; + # alter table cust_filters add column file_id text; + reply_text = Column(UnicodeText) + file_type = Column(Integer, nullable=False, default=1) + file_id = Column(UnicodeText, default=None) + + def __init__( + self, + chat_id, + keyword, + reply, + is_sticker=False, + is_document=False, + is_image=False, + is_audio=False, + is_voice=False, + is_video=False, + has_buttons=False, + reply_text=None, + file_type=1, + file_id=None, + ): + self.chat_id = str(chat_id) # ensure string + self.keyword = keyword + self.reply = reply + self.is_sticker = is_sticker + self.is_document = is_document + self.is_image = is_image + self.is_audio = is_audio + self.is_voice = is_voice + self.is_video = is_video + self.has_buttons = has_buttons + self.has_markdown = True + + self.reply_text = reply_text + self.file_type = file_type + self.file_id = file_id + + def __repr__(self): + return "<Permissions for %s>" % self.chat_id + + def __eq__(self, other): + return bool( + isinstance(other, CustomFilters) + and self.chat_id == other.chat_id + and self.keyword == other.keyword + ) + + +class NewCustomFilters(BASE): + __tablename__ = "cust_filters_new" + chat_id = Column(String(14), primary_key=True) + keyword = Column(UnicodeText, primary_key=True, nullable=False) + text = Column(UnicodeText) + file_type = Column(Integer, nullable=False, default=1) + file_id = Column(UnicodeText, default=None) + + def __init__(self, chat_id, keyword, text, file_type, file_id): + self.chat_id = str(chat_id) # ensure string + self.keyword = keyword + self.text = text + self.file_type = file_type + self.file_id = file_id + + def __repr__(self): + return "<Filter for %s>" % self.chat_id + + def __eq__(self, other): + return bool( + isinstance(other, CustomFilters) + and self.chat_id == other.chat_id + and self.keyword == other.keyword + ) + + +class Buttons(BASE): + __tablename__ = "cust_filter_urls" + id = Column(Integer, primary_key=True, autoincrement=True) + chat_id = Column(String(14), primary_key=True) + keyword = Column(UnicodeText, primary_key=True) + name = Column(UnicodeText, nullable=False) + url = Column(UnicodeText, nullable=False) + same_line = Column(Boolean, default=False) + + def __init__(self, chat_id, keyword, name, url, same_line=False): + self.chat_id = str(chat_id) + self.keyword = keyword + self.name = name + self.url = url + self.same_line = same_line + + +CustomFilters.__table__.create(checkfirst=True) +Buttons.__table__.create(checkfirst=True) + +CUST_FILT_LOCK = threading.RLock() +BUTTON_LOCK = threading.RLock() +CHAT_FILTERS = {} + + +def get_all_filters(): + try: + return SESSION.query(CustomFilters).all() + finally: + SESSION.close() + + +def add_filter( + chat_id, + keyword, + reply, + is_sticker=False, + is_document=False, + is_image=False, + is_audio=False, + is_voice=False, + is_video=False, + buttons=None, +): + global CHAT_FILTERS + + if buttons is None: + buttons = [] + + with CUST_FILT_LOCK: + prev = SESSION.query(CustomFilters).get((str(chat_id), keyword)) + if prev: + with BUTTON_LOCK: + prev_buttons = ( + SESSION.query(Buttons) + .filter(Buttons.chat_id == str(chat_id), Buttons.keyword == keyword) + .all() + ) + for btn in prev_buttons: + SESSION.delete(btn) + SESSION.delete(prev) + + filt = CustomFilters( + str(chat_id), + keyword, + reply, + is_sticker, + is_document, + is_image, + is_audio, + is_voice, + is_video, + bool(buttons), + ) + + if keyword not in CHAT_FILTERS.get(str(chat_id), []): + CHAT_FILTERS[str(chat_id)] = sorted( + CHAT_FILTERS.get(str(chat_id), []) + [keyword], + key=lambda x: (-len(x), x), + ) + + SESSION.add(filt) + SESSION.commit() + + for b_name, url, same_line in buttons: + add_note_button_to_db(chat_id, keyword, b_name, url, same_line) + + +def new_add_filter(chat_id, keyword, reply_text, file_type, file_id, buttons): + global CHAT_FILTERS + + if buttons is None: + buttons = [] + + with CUST_FILT_LOCK: + prev = SESSION.query(CustomFilters).get((str(chat_id), keyword)) + if prev: + with BUTTON_LOCK: + prev_buttons = ( + SESSION.query(Buttons) + .filter(Buttons.chat_id == str(chat_id), Buttons.keyword == keyword) + .all() + ) + for btn in prev_buttons: + SESSION.delete(btn) + SESSION.delete(prev) + + filt = CustomFilters( + str(chat_id), + keyword, + reply="there is should be a new reply", + is_sticker=False, + is_document=False, + is_image=False, + is_audio=False, + is_voice=False, + is_video=False, + has_buttons=bool(buttons), + reply_text=reply_text, + file_type=file_type.value, + file_id=file_id, + ) + + if keyword not in CHAT_FILTERS.get(str(chat_id), []): + CHAT_FILTERS[str(chat_id)] = sorted( + CHAT_FILTERS.get(str(chat_id), []) + [keyword], + key=lambda x: (-len(x), x), + ) + + SESSION.add(filt) + SESSION.commit() + + for b_name, url, same_line in buttons: + add_note_button_to_db(chat_id, keyword, b_name, url, same_line) + + +def remove_filter(chat_id, keyword): + global CHAT_FILTERS + with CUST_FILT_LOCK: + filt = SESSION.query(CustomFilters).get((str(chat_id), keyword)) + if filt: + if keyword in CHAT_FILTERS.get(str(chat_id), []): # Sanity check + CHAT_FILTERS.get(str(chat_id), []).remove(keyword) + + with BUTTON_LOCK: + prev_buttons = ( + SESSION.query(Buttons) + .filter(Buttons.chat_id == str(chat_id), Buttons.keyword == keyword) + .all() + ) + for btn in prev_buttons: + SESSION.delete(btn) + + SESSION.delete(filt) + SESSION.commit() + return True + + SESSION.close() + return False + + +def get_chat_triggers(chat_id): + return CHAT_FILTERS.get(str(chat_id), set()) + + +def get_chat_filters(chat_id): + try: + return ( + SESSION.query(CustomFilters) + .filter(CustomFilters.chat_id == str(chat_id)) + .order_by(func.length(CustomFilters.keyword).desc()) + .order_by(CustomFilters.keyword.asc()) + .all() + ) + finally: + SESSION.close() + + +def get_filter(chat_id, keyword): + try: + return SESSION.query(CustomFilters).get((str(chat_id), keyword)) + finally: + SESSION.close() + + +def add_note_button_to_db(chat_id, keyword, b_name, url, same_line): + with BUTTON_LOCK: + button = Buttons(chat_id, keyword, b_name, url, same_line) + SESSION.add(button) + SESSION.commit() + + +def get_buttons(chat_id, keyword): + try: + return ( + SESSION.query(Buttons) + .filter(Buttons.chat_id == str(chat_id), Buttons.keyword == keyword) + .order_by(Buttons.id) + .all() + ) + finally: + SESSION.close() + + +def num_filters(): + try: + return SESSION.query(CustomFilters).count() + finally: + SESSION.close() + + +def num_chats(): + try: + return SESSION.query(func.count(distinct(CustomFilters.chat_id))).scalar() + finally: + SESSION.close() + + +def __load_chat_filters(): + global CHAT_FILTERS + try: + chats = SESSION.query(CustomFilters.chat_id).distinct().all() + for (chat_id,) in chats: # remove tuple by ( ,) + CHAT_FILTERS[chat_id] = [] + + all_filters = SESSION.query(CustomFilters).all() + for x in all_filters: + CHAT_FILTERS[x.chat_id] += [x.keyword] + + CHAT_FILTERS = { + x: sorted(set(y), key=lambda i: (-len(i), i)) + for x, y in CHAT_FILTERS.items() + } + + finally: + SESSION.close() + + +# ONLY USE FOR MIGRATE OLD FILTERS TO NEW FILTERS +def __migrate_filters(): + try: + all_filters = SESSION.query(CustomFilters).distinct().all() + for x in all_filters: + if x.is_document: + file_type = Types.DOCUMENT + elif x.is_image: + file_type = Types.PHOTO + elif x.is_video: + file_type = Types.VIDEO + elif x.is_sticker: + file_type = Types.STICKER + elif x.is_audio: + file_type = Types.AUDIO + elif x.is_voice: + file_type = Types.VOICE + else: + file_type = Types.TEXT + + print(str(x.chat_id), x.keyword, x.reply, file_type.value) + if file_type == Types.TEXT: + filt = CustomFilters( + str(x.chat_id), x.keyword, x.reply, file_type.value, None + ) + else: + filt = CustomFilters( + str(x.chat_id), x.keyword, None, file_type.value, x.reply + ) + + SESSION.add(filt) + SESSION.commit() + + finally: + SESSION.close() + + +def migrate_chat(old_chat_id, new_chat_id): + with CUST_FILT_LOCK: + chat_filters = ( + SESSION.query(CustomFilters) + .filter(CustomFilters.chat_id == str(old_chat_id)) + .all() + ) + for filt in chat_filters: + filt.chat_id = str(new_chat_id) + SESSION.commit() + try: + CHAT_FILTERS[str(new_chat_id)] = CHAT_FILTERS[str(old_chat_id)] + except KeyError: + pass + del CHAT_FILTERS[str(old_chat_id)] + + with BUTTON_LOCK: + chat_buttons = ( + SESSION.query(Buttons).filter(Buttons.chat_id == str(old_chat_id)).all() + ) + for btn in chat_buttons: + btn.chat_id = str(new_chat_id) + SESSION.commit() + + +__load_chat_filters() diff --git a/Exon/modules/sql/disable_sql.py b/Exon/modules/sql/disable_sql.py new file mode 100644 index 00000000..fec3c37f --- /dev/null +++ b/Exon/modules/sql/disable_sql.py @@ -0,0 +1,129 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import threading + +from sqlalchemy import Column, String, UnicodeText, distinct, func + +from Exon.modules.sql import BASE, SESSION + + +class Disable(BASE): + __tablename__ = "disabled_commands" + chat_id = Column(String(14), primary_key=True) + command = Column(UnicodeText, primary_key=True) + + def __init__(self, chat_id, command): + self.chat_id = chat_id + self.command = command + + def __repr__(self): + return "ᴅɪsᴀʙʟᴇᴅ ᴄᴍᴅ {} in {}".format(self.command, self.chat_id) + + +Disable.__table__.create(checkfirst=True) +DISABLE_INSERTION_LOCK = threading.RLock() + +DISABLED = {} + + +def disable_command(chat_id, disable): + with DISABLE_INSERTION_LOCK: + disabled = SESSION.query(Disable).get((str(chat_id), disable)) + + if not disabled: + DISABLED.setdefault(str(chat_id), set()).add(disable) + + disabled = Disable(str(chat_id), disable) + SESSION.add(disabled) + SESSION.commit() + return True + + SESSION.close() + return False + + +def enable_command(chat_id, enable): + with DISABLE_INSERTION_LOCK: + disabled = SESSION.query(Disable).get((str(chat_id), enable)) + + if disabled: + if enable in DISABLED.get(str(chat_id)): # sanity check + DISABLED.setdefault(str(chat_id), set()).remove(enable) + + SESSION.delete(disabled) + SESSION.commit() + return True + + SESSION.close() + return False + + +def is_command_disabled(chat_id, cmd): + return str(cmd).lower() in DISABLED.get(str(chat_id), set()) + + +def get_all_disabled(chat_id): + return DISABLED.get(str(chat_id), set()) + + +def num_chats(): + try: + return SESSION.query(func.count(distinct(Disable.chat_id))).scalar() + finally: + SESSION.close() + + +def num_disabled(): + try: + return SESSION.query(Disable).count() + finally: + SESSION.close() + + +def migrate_chat(old_chat_id, new_chat_id): + with DISABLE_INSERTION_LOCK: + chats = SESSION.query(Disable).filter(Disable.chat_id == str(old_chat_id)).all() + for chat in chats: + chat.chat_id = str(new_chat_id) + SESSION.add(chat) + + if str(old_chat_id) in DISABLED: + DISABLED[str(new_chat_id)] = DISABLED.get(str(old_chat_id), set()) + + SESSION.commit() + + +def __load_disabled_commands(): + global DISABLED + try: + all_chats = SESSION.query(Disable).all() + for chat in all_chats: + DISABLED.setdefault(chat.chat_id, set()).add(chat.command) + + finally: + SESSION.close() + + +__load_disabled_commands() diff --git a/Exon/modules/sql/feds_sql.py b/Exon/modules/sql/feds_sql.py new file mode 100644 index 00000000..92c000b4 --- /dev/null +++ b/Exon/modules/sql/feds_sql.py @@ -0,0 +1,912 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import ast +import threading + +from sqlalchemy import BigInteger, Boolean, Column, String, UnicodeText +from telegram.error import BadRequest, Unauthorized + +from Exon import dispatcher +from Exon.modules.sql import BASE, SESSION + + +class Federations(BASE): + __tablename__ = "feds" + owner_id = Column(String(14)) + fed_name = Column(UnicodeText) + fed_id = Column(UnicodeText, primary_key=True) + fed_rules = Column(UnicodeText) + fed_log = Column(UnicodeText) + fed_users = Column(UnicodeText) + + def __init__(self, owner_id, fed_name, fed_id, fed_rules, fed_log, fed_users): + self.owner_id = owner_id + self.fed_name = fed_name + self.fed_id = fed_id + self.fed_rules = fed_rules + self.fed_log = fed_log + self.fed_users = fed_users + + +class ChatF(BASE): + __tablename__ = "chat_feds" + chat_id = Column(String(14), primary_key=True) + chat_name = Column(UnicodeText) + fed_id = Column(UnicodeText) + + def __init__(self, chat_id, chat_name, fed_id): + self.chat_id = chat_id + self.chat_name = chat_name + self.fed_id = fed_id + + +class BansF(BASE): + __tablename__ = "bans_feds" + fed_id = Column(UnicodeText, primary_key=True) + user_id = Column(String(14), primary_key=True) + first_name = Column(UnicodeText, nullable=False) + last_name = Column(UnicodeText) + user_name = Column(UnicodeText) + reason = Column(UnicodeText, default="") + time = Column(BigInteger, default=0) + + def __init__(self, fed_id, user_id, first_name, last_name, user_name, reason, time): + self.fed_id = fed_id + self.user_id = user_id + self.first_name = first_name + self.last_name = last_name + self.user_name = user_name + self.reason = reason + self.time = time + + +class FedsUserSettings(BASE): + __tablename__ = "feds_settings" + user_id = Column(BigInteger, primary_key=True) + should_report = Column(Boolean, default=True) + + def __init__(self, user_id): + self.user_id = user_id + + def __repr__(self): + return "<Feds report settings ({})>".format(self.user_id) + + +class FedSubs(BASE): + __tablename__ = "feds_subs" + fed_id = Column(UnicodeText, primary_key=True) + fed_subs = Column(UnicodeText, primary_key=True, nullable=False) + + def __init__(self, fed_id, fed_subs): + self.fed_id = fed_id + self.fed_subs = fed_subs + + def __repr__(self): + return "<Fed {} subscribes for {}>".format(self.fed_id, self.fed_subs) + + +Federations.__table__.create(checkfirst=True) +ChatF.__table__.create(checkfirst=True) +BansF.__table__.create(checkfirst=True) +FedsUserSettings.__table__.create(checkfirst=True) +FedSubs.__table__.create(checkfirst=True) + +FEDS_LOCK = threading.RLock() +CHAT_FEDS_LOCK = threading.RLock() +FEDS_SETTINGS_LOCK = threading.RLock() +FEDS_SUBSCRIBER_LOCK = threading.RLock() + +FEDERATION_BYNAME = {} +FEDERATION_BYOWNER = {} +FEDERATION_BYFEDID = {} + +FEDERATION_CHATS = {} +FEDERATION_CHATS_BYID = {} + +FEDERATION_BANNED_FULL = {} +FEDERATION_BANNED_USERID = {} + +FEDERATION_NOTIFICATION = {} +FEDS_SUBSCRIBER = {} +MYFEDS_SUBSCRIBER = {} + + +def get_fed_info(fed_id): + get = FEDERATION_BYFEDID.get(str(fed_id)) + if get is None: + return False + return get + + +def get_fed_id(chat_id): + get = FEDERATION_CHATS.get(str(chat_id)) + if get is None: + return False + return get["fid"] + + +def get_fed_name(chat_id): + get = FEDERATION_CHATS.get(str(chat_id)) + if get is None: + return False + return get["chat_name"] + + +def get_user_fban(fed_id, user_id): + if not FEDERATION_BANNED_FULL.get(fed_id): + return False, False, False + user_info = FEDERATION_BANNED_FULL[fed_id].get(user_id) + if not user_info: + return None, None, None + return user_info["first_name"], user_info["reason"], user_info["time"] + + +def get_user_admin_fed_name(user_id): + return [ + FEDERATION_BYFEDID[f]["fname"] + for f in FEDERATION_BYFEDID + if int(user_id) + in ast.literal_eval( + ast.literal_eval(FEDERATION_BYFEDID[f]["fusers"])["members"] + ) + ] + + +def get_user_owner_fed_name(user_id): + return [ + FEDERATION_BYFEDID[f]["fname"] + for f in FEDERATION_BYFEDID + if int(user_id) + == int(ast.literal_eval(FEDERATION_BYFEDID[f]["fusers"])["owner"]) + ] + + +def get_user_admin_fed_full(user_id): + return [ + {"fed_id": f, "fed": FEDERATION_BYFEDID[f]} + for f in FEDERATION_BYFEDID + if int(user_id) + in ast.literal_eval( + ast.literal_eval(FEDERATION_BYFEDID[f]["fusers"])["members"] + ) + ] + + +def get_user_owner_fed_full(user_id): + return [ + {"fed_id": f, "fed": FEDERATION_BYFEDID[f]} + for f in FEDERATION_BYFEDID + if int(user_id) + == int(ast.literal_eval(FEDERATION_BYFEDID[f]["fusers"])["owner"]) + ] + + +def get_user_fbanlist(user_id): + banlist = FEDERATION_BANNED_FULL + user_name = "" + fedname = [] + for x in banlist: + if banlist[x].get(user_id): + if user_name == "": + user_name = banlist[x][user_id].get("first_name") + fedname.append([x, banlist[x][user_id].get("reason")]) + return user_name, fedname + + +def new_fed(owner_id, fed_name, fed_id): + with FEDS_LOCK: + global FEDERATION_BYOWNER, FEDERATION_BYFEDID, FEDERATION_BYNAME + fed = Federations( + str(owner_id), + fed_name, + str(fed_id), + "ʀᴜʟᴇs ɪs ɴᴏᴛ sᴇᴛ ɪɴ ᴛʜɪs ғᴇᴅᴇʀᴀᴛɪᴏɴ.", + None, + str({"owner": str(owner_id), "members": "[]"}), + ) + SESSION.add(fed) + SESSION.commit() + FEDERATION_BYOWNER[str(owner_id)] = { + "fid": str(fed_id), + "fname": fed_name, + "frules": "ʀᴜʟᴇs ɪs ɴᴏᴛ sᴇᴛ ɪɴ ᴛʜɪs ғᴇᴅᴇʀᴀᴛɪᴏɴ.", + "flog": None, + "fusers": str({"owner": str(owner_id), "members": "[]"}), + } + FEDERATION_BYFEDID[str(fed_id)] = { + "owner": str(owner_id), + "fname": fed_name, + "frules": "ʀᴜʟᴇs ɪs ɴᴏᴛ sᴇᴛ ɪɴ ᴛʜɪs ғᴇᴅᴇʀᴀᴛɪᴏɴ.", + "flog": None, + "fusers": str({"owner": str(owner_id), "members": "[]"}), + } + FEDERATION_BYNAME[fed_name] = { + "fid": str(fed_id), + "owner": str(owner_id), + "frules": "ʀᴜʟᴇs ɪs ɴᴏᴛ sᴇᴛ ɪɴ ᴛʜɪs ғᴇᴅᴇʀᴀᴛɪᴏɴ.", + "flog": None, + "fusers": str({"owner": str(owner_id), "members": "[]"}), + } + return fed + + +def del_fed(fed_id): + with FEDS_LOCK: + global FEDERATION_BYOWNER, FEDERATION_BYFEDID, FEDERATION_BYNAME, FEDERATION_CHATS, FEDERATION_CHATS_BYID, FEDERATION_BANNED_USERID, FEDERATION_BANNED_FULL + getcache = FEDERATION_BYFEDID.get(fed_id) + if getcache is None: + return False + # Variables + getfed = FEDERATION_BYFEDID.get(fed_id) + owner_id = getfed["owner"] + fed_name = getfed["fname"] + # Delete from cache + FEDERATION_BYOWNER.pop(owner_id) + FEDERATION_BYFEDID.pop(fed_id) + FEDERATION_BYNAME.pop(fed_name) + if FEDERATION_CHATS_BYID.get(fed_id): + for x in FEDERATION_CHATS_BYID[fed_id]: + delchats = SESSION.query(ChatF).get(str(x)) + if delchats: + SESSION.delete(delchats) + SESSION.commit() + FEDERATION_CHATS.pop(x) + FEDERATION_CHATS_BYID.pop(fed_id) + # Delete fedban users + getall = FEDERATION_BANNED_USERID.get(fed_id) + if getall: + for x in getall: + banlist = SESSION.query(BansF).get((fed_id, str(x))) + if banlist: + SESSION.delete(banlist) + SESSION.commit() + if FEDERATION_BANNED_USERID.get(fed_id): + FEDERATION_BANNED_USERID.pop(fed_id) + if FEDERATION_BANNED_FULL.get(fed_id): + FEDERATION_BANNED_FULL.pop(fed_id) + # Delete fedsubs + getall = MYFEDS_SUBSCRIBER.get(fed_id) + if getall: + for x in getall: + getsubs = SESSION.query(FedSubs).get((fed_id, str(x))) + if getsubs: + SESSION.delete(getsubs) + SESSION.commit() + if FEDS_SUBSCRIBER.get(fed_id): + FEDS_SUBSCRIBER.pop(fed_id) + if MYFEDS_SUBSCRIBER.get(fed_id): + MYFEDS_SUBSCRIBER.pop(fed_id) + # Delete from database + curr = SESSION.query(Federations).get(fed_id) + if curr: + SESSION.delete(curr) + SESSION.commit() + return True + + +def rename_fed(fed_id, owner_id, newname): + with FEDS_LOCK: + global FEDERATION_BYFEDID, FEDERATION_BYOWNER, FEDERATION_BYNAME + fed = SESSION.query(Federations).get(fed_id) + if not fed: + return False + fed.fed_name = newname + SESSION.commit() + + # Update the dicts + oldname = FEDERATION_BYFEDID[str(fed_id)]["fname"] + tempdata = FEDERATION_BYNAME[oldname] + FEDERATION_BYNAME.pop(oldname) + + FEDERATION_BYOWNER[str(owner_id)]["fname"] = newname + FEDERATION_BYFEDID[str(fed_id)]["fname"] = newname + FEDERATION_BYNAME[newname] = tempdata + return True + + +def chat_join_fed(fed_id, chat_name, chat_id): + with FEDS_LOCK: + global FEDERATION_CHATS, FEDERATION_CHATS_BYID + r = ChatF(chat_id, chat_name, fed_id) + SESSION.add(r) + FEDERATION_CHATS[str(chat_id)] = {"chat_name": chat_name, "fid": fed_id} + checkid = FEDERATION_CHATS_BYID.get(fed_id) + if checkid is None: + FEDERATION_CHATS_BYID[fed_id] = [] + FEDERATION_CHATS_BYID[fed_id].append(str(chat_id)) + SESSION.commit() + return r + + +def search_fed_by_name(fed_name): + allfed = FEDERATION_BYNAME.get(fed_name) + if allfed is None: + return False + return allfed + + +def search_user_in_fed(fed_id, user_id): + getfed = FEDERATION_BYFEDID.get(fed_id) + if getfed is None: + return False + getfed = ast.literal_eval(getfed["fusers"])["members"] + return user_id in ast.literal_eval(getfed) + + +def user_demote_fed(fed_id, user_id): + with FEDS_LOCK: + global FEDERATION_BYOWNER, FEDERATION_BYFEDID, FEDERATION_BYNAME + # Variables + getfed = FEDERATION_BYFEDID.get(str(fed_id)) + owner_id = getfed["owner"] + fed_name = getfed["fname"] + fed_rules = getfed["frules"] + fed_log = getfed["flog"] + # Temp set + try: + members = ast.literal_eval(ast.literal_eval(getfed["fusers"])["members"]) + except ValueError: + return False + members.remove(user_id) + # Set user + FEDERATION_BYOWNER[str(owner_id)]["fusers"] = str( + {"owner": str(owner_id), "members": str(members)}, + ) + FEDERATION_BYFEDID[str(fed_id)]["fusers"] = str( + {"owner": str(owner_id), "members": str(members)}, + ) + FEDERATION_BYNAME[fed_name]["fusers"] = str( + {"owner": str(owner_id), "members": str(members)}, + ) + # Set on database + fed = Federations( + str(owner_id), + fed_name, + str(fed_id), + fed_rules, + fed_log, + str({"owner": str(owner_id), "members": str(members)}), + ) + SESSION.merge(fed) + SESSION.commit() + return True + + +def user_join_fed(fed_id, user_id): + with FEDS_LOCK: + global FEDERATION_BYOWNER, FEDERATION_BYFEDID, FEDERATION_BYNAME + # Variables + getfed = FEDERATION_BYFEDID.get(str(fed_id)) + owner_id = getfed["owner"] + fed_name = getfed["fname"] + fed_rules = getfed["frules"] + fed_log = getfed["flog"] + # Temp set + members = ast.literal_eval(ast.literal_eval(getfed["fusers"])["members"]) + members.append(user_id) + # Set user + FEDERATION_BYOWNER[str(owner_id)]["fusers"] = str( + {"owner": str(owner_id), "members": str(members)}, + ) + FEDERATION_BYFEDID[str(fed_id)]["fusers"] = str( + {"owner": str(owner_id), "members": str(members)}, + ) + FEDERATION_BYNAME[fed_name]["fusers"] = str( + {"owner": str(owner_id), "members": str(members)}, + ) + # Set on database + fed = Federations( + str(owner_id), + fed_name, + str(fed_id), + fed_rules, + fed_log, + str({"owner": str(owner_id), "members": str(members)}), + ) + SESSION.merge(fed) + SESSION.commit() + __load_all_feds_chats() + return True + + +def chat_leave_fed(chat_id): + with FEDS_LOCK: + global FEDERATION_CHATS, FEDERATION_CHATS_BYID + # Set variables + fed_info = FEDERATION_CHATS.get(str(chat_id)) + if fed_info is None: + return False + fed_id = fed_info["fid"] + # Delete from cache + FEDERATION_CHATS.pop(str(chat_id)) + FEDERATION_CHATS_BYID[str(fed_id)].remove(str(chat_id)) + # Delete from db + curr = SESSION.query(ChatF).all() + for U in curr: + if int(U.chat_id) == int(chat_id): + SESSION.delete(U) + SESSION.commit() + return True + + +def all_fed_chats(fed_id): + with FEDS_LOCK: + getfed = FEDERATION_CHATS_BYID.get(fed_id) + if getfed is None: + return [] + return getfed + + +def all_fed_users(fed_id): + with FEDS_LOCK: + getfed = FEDERATION_BYFEDID.get(str(fed_id)) + if getfed is None: + return False + fed_owner = ast.literal_eval(ast.literal_eval(getfed["fusers"])["owner"]) + fed_admins = ast.literal_eval(ast.literal_eval(getfed["fusers"])["members"]) + fed_admins.append(fed_owner) + return fed_admins + + +def all_fed_members(fed_id): + with FEDS_LOCK: + getfed = FEDERATION_BYFEDID.get(str(fed_id)) + return ast.literal_eval(ast.literal_eval(getfed["fusers"])["members"]) + + +def set_frules(fed_id, rules): + with FEDS_LOCK: + global FEDERATION_BYOWNER, FEDERATION_BYFEDID, FEDERATION_BYNAME + # Variables + getfed = FEDERATION_BYFEDID.get(str(fed_id)) + owner_id = getfed["owner"] + fed_name = getfed["fname"] + fed_members = getfed["fusers"] + fed_rules = str(rules) + fed_log = getfed["flog"] + # Set user + FEDERATION_BYOWNER[str(owner_id)]["frules"] = fed_rules + FEDERATION_BYFEDID[str(fed_id)]["frules"] = fed_rules + FEDERATION_BYNAME[fed_name]["frules"] = fed_rules + # Set on database + fed = Federations( + str(owner_id), + fed_name, + str(fed_id), + fed_rules, + fed_log, + str(fed_members), + ) + SESSION.merge(fed) + SESSION.commit() + return True + + +def get_frules(fed_id): + with FEDS_LOCK: + return FEDERATION_BYFEDID[str(fed_id)]["frules"] + + +def fban_user(fed_id, user_id, first_name, last_name, user_name, reason, time): + with FEDS_LOCK: + r = SESSION.query(BansF).all() + for I in r: + if I.fed_id == fed_id and int(I.user_id) == int(user_id): + SESSION.delete(I) + + r = BansF( + str(fed_id), + str(user_id), + first_name, + last_name, + user_name, + reason, + time, + ) + + SESSION.add(r) + try: + SESSION.commit() + except: + SESSION.rollback() + return False + finally: + SESSION.commit() + __load_all_feds_banned() + return r + + +def multi_fban_user( + multi_fed_id, + multi_user_id, + multi_first_name, + multi_last_name, + multi_user_name, + multi_reason, +): + counter = 0 + time = 0 + for x in enumerate(multi_fed_id): + fed_id = multi_fed_id[x] + user_id = multi_user_id[x] + first_name = multi_first_name[x] + last_name = multi_last_name[x] + user_name = multi_user_name[x] + reason = multi_reason[x] + r = SESSION.query(BansF).all() + for I in r: + if I.fed_id == fed_id and int(I.user_id) == int(user_id): + SESSION.delete(I) + + r = BansF( + str(fed_id), + str(user_id), + first_name, + last_name, + user_name, + reason, + time, + ) + + SESSION.add(r) + counter += 1 + if str(str(counter)[-2:]) == "00": + print(user_id) + print(first_name) + print(reason) + print(counter) + try: + SESSION.commit() + except: + SESSION.rollback() + return False + finally: + SESSION.commit() + __load_all_feds_banned() + print("Done") + return counter + + +def un_fban_user(fed_id, user_id): + with FEDS_LOCK: + r = SESSION.query(BansF).all() + for I in r: + if I.fed_id == fed_id and int(I.user_id) == int(user_id): + SESSION.delete(I) + try: + SESSION.commit() + except: + SESSION.rollback() + return False + finally: + SESSION.commit() + __load_all_feds_banned() + return I + + +def get_fban_user(fed_id, user_id): + list_fbanned = FEDERATION_BANNED_USERID.get(fed_id) + if list_fbanned is None: + FEDERATION_BANNED_USERID[fed_id] = [] + if user_id in FEDERATION_BANNED_USERID[fed_id]: + r = SESSION.query(BansF).all() + reason = None + for I in r: + if I.fed_id == fed_id and int(I.user_id) == int(user_id): + reason = I.reason + time = I.time + return True, reason, time + return False, None, None + + +def get_all_fban_users(fed_id): + list_fbanned = FEDERATION_BANNED_USERID.get(fed_id) + if list_fbanned is None: + FEDERATION_BANNED_USERID[fed_id] = [] + return FEDERATION_BANNED_USERID[fed_id] + + +def get_all_fban_users_target(fed_id, user_id): + list_fbanned = FEDERATION_BANNED_FULL.get(fed_id) + if list_fbanned is None: + FEDERATION_BANNED_FULL[fed_id] = [] + return False + return list_fbanned[str(user_id)] + + +def get_all_fban_users_global(): + total = [] + for x in list(FEDERATION_BANNED_USERID): + for y in FEDERATION_BANNED_USERID[x]: + total.append(y) + return total + + +def get_all_feds_users_global(): + return [FEDERATION_BYFEDID[x] for x in list(FEDERATION_BYFEDID)] + + +def search_fed_by_id(fed_id): + get = FEDERATION_BYFEDID.get(fed_id) + if get is None: + return False + return get + + +def user_feds_report(user_id: int) -> bool: + user_setting = FEDERATION_NOTIFICATION.get(str(user_id)) + if user_setting is None: + user_setting = True + return user_setting + + +def set_feds_setting(user_id: int, setting: bool): + with FEDS_SETTINGS_LOCK: + global FEDERATION_NOTIFICATION + user_setting = SESSION.query(FedsUserSettings).get(user_id) + if not user_setting: + user_setting = FedsUserSettings(user_id) + + user_setting.should_report = setting + FEDERATION_NOTIFICATION[str(user_id)] = setting + SESSION.add(user_setting) + SESSION.commit() + + +def get_fed_log(fed_id): + fed_setting = FEDERATION_BYFEDID.get(str(fed_id)) + if fed_setting is None: + fed_setting = False + return fed_setting + if fed_setting.get("flog") is None: + return False + if fed_setting.get("flog"): + try: + dispatcher.bot.get_chat(fed_setting.get("flog")) + except (BadRequest, Unauthorized): + set_fed_log(fed_id, None) + return False + return fed_setting.get("flog") + return False + + +def set_fed_log(fed_id, chat_id): + with FEDS_LOCK: + global FEDERATION_BYOWNER, FEDERATION_BYFEDID, FEDERATION_BYNAME + # Variables + getfed = FEDERATION_BYFEDID.get(str(fed_id)) + owner_id = getfed["owner"] + fed_name = getfed["fname"] + fed_members = getfed["fusers"] + fed_rules = getfed["frules"] + fed_log = str(chat_id) + # Set user + FEDERATION_BYOWNER[str(owner_id)]["flog"] = fed_log + FEDERATION_BYFEDID[str(fed_id)]["flog"] = fed_log + FEDERATION_BYNAME[fed_name]["flog"] = fed_log + # Set on database + fed = Federations( + str(owner_id), + fed_name, + str(fed_id), + fed_rules, + fed_log, + str(fed_members), + ) + SESSION.merge(fed) + SESSION.commit() + print(fed_log) + return True + + +def subs_fed(fed_id, my_fed): + check = get_spec_subs(fed_id, my_fed) + if check: + return False + with FEDS_SUBSCRIBER_LOCK: + subsfed = FedSubs(fed_id, my_fed) + + SESSION.merge(subsfed) # merge to avoid duplicate key issues + SESSION.commit() + global FEDS_SUBSCRIBER, MYFEDS_SUBSCRIBER + # Temporary Data For Subbed Feds + if FEDS_SUBSCRIBER.get(fed_id, set()) == set(): + FEDS_SUBSCRIBER[fed_id] = {my_fed} + else: + FEDS_SUBSCRIBER.get(fed_id, set()).add(my_fed) + # Temporary data for Fed Subs + if MYFEDS_SUBSCRIBER.get(my_fed, set()) == set(): + MYFEDS_SUBSCRIBER[my_fed] = {fed_id} + else: + MYFEDS_SUBSCRIBER.get(my_fed, set()).add(fed_id) + return True + + +def unsubs_fed(fed_id, my_fed): + with FEDS_SUBSCRIBER_LOCK: + getsubs = SESSION.query(FedSubs).get((fed_id, my_fed)) + if getsubs: + if my_fed in FEDS_SUBSCRIBER.get(fed_id, set()): # sanity check + FEDS_SUBSCRIBER.get(fed_id, set()).remove(my_fed) + if fed_id in MYFEDS_SUBSCRIBER.get(my_fed, set()): # sanity check + MYFEDS_SUBSCRIBER.get(my_fed, set()).remove(fed_id) + + SESSION.delete(getsubs) + SESSION.commit() + return True + + SESSION.close() + return False + + +def get_all_subs(fed_id): + return FEDS_SUBSCRIBER.get(fed_id, set()) + + +def get_spec_subs(fed_id, fed_target): + if FEDS_SUBSCRIBER.get(fed_id, set()) == set(): + return {} + return FEDS_SUBSCRIBER.get(fed_id, fed_target) + + +def get_mysubs(my_fed): + return list(MYFEDS_SUBSCRIBER.get(my_fed)) + + +def get_subscriber(fed_id): + return FEDS_SUBSCRIBER.get(fed_id, set()) + + +def __load_all_feds(): + global FEDERATION_BYOWNER, FEDERATION_BYFEDID, FEDERATION_BYNAME + try: + feds = SESSION.query(Federations).all() + for x in feds: # remove tuple by ( ,) + # Fed by Owner + check = FEDERATION_BYOWNER.get(x.owner_id) + if check is None: + FEDERATION_BYOWNER[x.owner_id] = [] + FEDERATION_BYOWNER[str(x.owner_id)] = { + "fid": str(x.fed_id), + "fname": x.fed_name, + "frules": x.fed_rules, + "flog": x.fed_log, + "fusers": str(x.fed_users), + } + # Fed By FedId + check = FEDERATION_BYFEDID.get(x.fed_id) + if check is None: + FEDERATION_BYFEDID[x.fed_id] = [] + FEDERATION_BYFEDID[str(x.fed_id)] = { + "owner": str(x.owner_id), + "fname": x.fed_name, + "frules": x.fed_rules, + "flog": x.fed_log, + "fusers": str(x.fed_users), + } + # Fed By Name + check = FEDERATION_BYNAME.get(x.fed_name) + if check is None: + FEDERATION_BYNAME[x.fed_name] = [] + FEDERATION_BYNAME[x.fed_name] = { + "fid": str(x.fed_id), + "owner": str(x.owner_id), + "frules": x.fed_rules, + "flog": x.fed_log, + "fusers": str(x.fed_users), + } + finally: + SESSION.close() + + +def __load_all_feds_chats(): + global FEDERATION_CHATS, FEDERATION_CHATS_BYID + try: + qall = SESSION.query(ChatF).all() + FEDERATION_CHATS = {} + FEDERATION_CHATS_BYID = {} + for x in qall: + # Federation Chats + check = FEDERATION_CHATS.get(x.chat_id) + if check is None: + FEDERATION_CHATS[x.chat_id] = {} + FEDERATION_CHATS[x.chat_id] = {"chat_name": x.chat_name, "fid": x.fed_id} + # Federation Chats By ID + check = FEDERATION_CHATS_BYID.get(x.fed_id) + if check is None: + FEDERATION_CHATS_BYID[x.fed_id] = [] + FEDERATION_CHATS_BYID[x.fed_id].append(x.chat_id) + finally: + SESSION.close() + + +def __load_all_feds_banned(): + global FEDERATION_BANNED_USERID, FEDERATION_BANNED_FULL + try: + FEDERATION_BANNED_USERID = {} + FEDERATION_BANNED_FULL = {} + qall = SESSION.query(BansF).all() + for x in qall: + check = FEDERATION_BANNED_USERID.get(x.fed_id) + if check is None: + FEDERATION_BANNED_USERID[x.fed_id] = [] + if int(x.user_id) not in FEDERATION_BANNED_USERID[x.fed_id]: + FEDERATION_BANNED_USERID[x.fed_id].append(int(x.user_id)) + check = FEDERATION_BANNED_FULL.get(x.fed_id) + if check is None: + FEDERATION_BANNED_FULL[x.fed_id] = {} + FEDERATION_BANNED_FULL[x.fed_id][x.user_id] = { + "first_name": x.first_name, + "last_name": x.last_name, + "user_name": x.user_name, + "reason": x.reason, + "time": x.time, + } + finally: + SESSION.close() + + +def __load_all_feds_settings(): + global FEDERATION_NOTIFICATION + try: + getuser = SESSION.query(FedsUserSettings).all() + for x in getuser: + FEDERATION_NOTIFICATION[str(x.user_id)] = x.should_report + finally: + SESSION.close() + + +def __load_feds_subscriber(): + global FEDS_SUBSCRIBER + global MYFEDS_SUBSCRIBER + try: + feds = SESSION.query(FedSubs.fed_id).distinct().all() + for (fed_id,) in feds: # remove tuple by ( ,) + FEDS_SUBSCRIBER[fed_id] = [] + MYFEDS_SUBSCRIBER[fed_id] = [] + + all_fedsubs = SESSION.query(FedSubs).all() + for x in all_fedsubs: + FEDS_SUBSCRIBER[x.fed_id] += [x.fed_subs] + try: + MYFEDS_SUBSCRIBER[x.fed_subs] += [x.fed_id] + except KeyError: + getsubs = SESSION.query(FedSubs).get((x.fed_id, x.fed_subs)) + if getsubs: + SESSION.delete(getsubs) + SESSION.commit() + + FEDS_SUBSCRIBER = {x: set(y) for x, y in FEDS_SUBSCRIBER.items()} + MYFEDS_SUBSCRIBER = {x: set(y) for x, y in MYFEDS_SUBSCRIBER.items()} + + finally: + SESSION.close() + + +__load_all_feds() +__load_all_feds_chats() +__load_all_feds_banned() +__load_all_feds_settings() +__load_feds_subscriber() diff --git a/Exon/modules/sql/forceSubscribe_sql.py b/Exon/modules/sql/forceSubscribe_sql.py new file mode 100644 index 00000000..8f48892c --- /dev/null +++ b/Exon/modules/sql/forceSubscribe_sql.py @@ -0,0 +1,70 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +from sqlalchemy import Column, Numeric, String + +from Exon.modules.sql import BASE, SESSION + + +class forceSubscribe(BASE): + __tablename__ = "forceSubscribe" + chat_id = Column(Numeric, primary_key=True) + channel = Column(String) + + def __init__(self, chat_id, channel): + self.chat_id = chat_id + self.channel = channel + + +forceSubscribe.__table__.create(checkfirst=True) + + +def fs_settings(chat_id): + try: + return ( + SESSION.query(forceSubscribe) + .filter(forceSubscribe.chat_id == chat_id) + .one() + ) + except: + return None + finally: + SESSION.close() + + +def add_channel(chat_id, channel): + adder = SESSION.query(forceSubscribe).get(chat_id) + if adder: + adder.channel = channel + else: + adder = forceSubscribe(chat_id, channel) + SESSION.add(adder) + SESSION.commit() + + +def disapprove(chat_id): + rem = SESSION.query(forceSubscribe).get(chat_id) + if rem: + SESSION.delete(rem) + SESSION.commit() diff --git a/Exon/modules/sql/global_bans_sql.py b/Exon/modules/sql/global_bans_sql.py new file mode 100644 index 00000000..d05f12be --- /dev/null +++ b/Exon/modules/sql/global_bans_sql.py @@ -0,0 +1,191 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import threading + +from sqlalchemy import BigInteger, Boolean, Column, String, UnicodeText + +from Exon.modules.sql import BASE, SESSION + + +class GloballyBannedUsers(BASE): + __tablename__ = "gbans" + user_id = Column(BigInteger, primary_key=True) + name = Column(UnicodeText, nullable=False) + reason = Column(UnicodeText) + + def __init__(self, user_id, name, reason=None): + self.user_id = user_id + self.name = name + self.reason = reason + + def __repr__(self): + return "<ɢʙᴀɴɴᴇᴅ ᴜsᴇʀ {} ({})>".format(self.name, self.user_id) + + def to_dict(self): + return {"user_id": self.user_id, "name": self.name, "reason": self.reason} + + +class GbanSettings(BASE): + __tablename__ = "gban_settings" + chat_id = Column(String(14), primary_key=True) + setting = Column(Boolean, default=True, nullable=False) + + def __init__(self, chat_id, enabled): + self.chat_id = str(chat_id) + self.setting = enabled + + def __repr__(self): + return "<ɢʙᴀɴ sᴇᴛᴛɪɴɢ {} ({})>".format(self.chat_id, self.setting) + + +GloballyBannedUsers.__table__.create(checkfirst=True) +GbanSettings.__table__.create(checkfirst=True) + +GBANNED_USERS_LOCK = threading.RLock() +GBAN_SETTING_LOCK = threading.RLock() +GBANNED_LIST = set() +GBANSTAT_LIST = set() + + +def gban_user(user_id, name, reason=None): + with GBANNED_USERS_LOCK: + user = SESSION.query(GloballyBannedUsers).get(user_id) + if not user: + user = GloballyBannedUsers(user_id, name, reason) + else: + user.name = name + user.reason = reason + + SESSION.merge(user) + SESSION.commit() + __load_gbanned_userid_list() + + +def update_gban_reason(user_id, name, reason=None): + with GBANNED_USERS_LOCK: + user = SESSION.query(GloballyBannedUsers).get(user_id) + if not user: + return None + old_reason = user.reason + user.name = name + user.reason = reason + + SESSION.merge(user) + SESSION.commit() + return old_reason + + +def ungban_user(user_id): + with GBANNED_USERS_LOCK: + user = SESSION.query(GloballyBannedUsers).get(user_id) + if user: + SESSION.delete(user) + + SESSION.commit() + __load_gbanned_userid_list() + + +def is_user_gbanned(user_id): + return user_id in GBANNED_LIST + + +def get_gbanned_user(user_id): + try: + return SESSION.query(GloballyBannedUsers).get(user_id) + finally: + SESSION.close() + + +def get_gban_list(): + try: + return [x.to_dict() for x in SESSION.query(GloballyBannedUsers).all()] + finally: + SESSION.close() + + +def enable_gbans(chat_id): + with GBAN_SETTING_LOCK: + chat = SESSION.query(GbanSettings).get(str(chat_id)) + if not chat: + chat = GbanSettings(chat_id, True) + + chat.setting = True + SESSION.add(chat) + SESSION.commit() + if str(chat_id) in GBANSTAT_LIST: + GBANSTAT_LIST.remove(str(chat_id)) + + +def disable_gbans(chat_id): + with GBAN_SETTING_LOCK: + chat = SESSION.query(GbanSettings).get(str(chat_id)) + if not chat: + chat = GbanSettings(chat_id, False) + + chat.setting = False + SESSION.add(chat) + SESSION.commit() + GBANSTAT_LIST.add(str(chat_id)) + + +def does_chat_gban(chat_id): + return str(chat_id) not in GBANSTAT_LIST + + +def num_gbanned_users(): + return len(GBANNED_LIST) + + +def __load_gbanned_userid_list(): + global GBANNED_LIST + try: + GBANNED_LIST = {x.user_id for x in SESSION.query(GloballyBannedUsers).all()} + finally: + SESSION.close() + + +def __load_gban_stat_list(): + global GBANSTAT_LIST + try: + GBANSTAT_LIST = { + x.chat_id for x in SESSION.query(GbanSettings).all() if not x.setting + } + finally: + SESSION.close() + + +def migrate_chat(old_chat_id, new_chat_id): + with GBAN_SETTING_LOCK: + chat = SESSION.query(GbanSettings).get(str(old_chat_id)) + if chat: + chat.chat_id = new_chat_id + SESSION.add(chat) + + SESSION.commit() + + +# Create in memory userid to avoid disk access +__load_gbanned_userid_list() +__load_gban_stat_list() diff --git a/Exon/modules/sql/kuki_sql.py b/Exon/modules/sql/kuki_sql.py new file mode 100644 index 00000000..0d35e845 --- /dev/null +++ b/Exon/modules/sql/kuki_sql.py @@ -0,0 +1,73 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import threading + +from sqlalchemy import Column, String + +from Exon.modules.sql import BASE, SESSION + + +class KukiChats(BASE): + __tablename__ = "kuki_chats" + chat_id = Column(String(14), primary_key=True) + + def __init__(self, chat_id): + self.chat_id = chat_id + + +KukiChats.__table__.create(checkfirst=True) +INSERTION_LOCK = threading.RLock() + + +def is_kuki(chat_id): + try: + chat = SESSION.query(KukiChats).get(str(chat_id)) + return bool(chat) + finally: + SESSION.close() + + +def set_kuki(chat_id): + with INSERTION_LOCK: + kukichat = SESSION.query(KukiChats).get(str(chat_id)) + if not kukichat: + kukichat = KukiChats(str(chat_id)) + SESSION.add(kukichat) + SESSION.commit() + + +def rem_kuki(chat_id): + with INSERTION_LOCK: + kukichat = SESSION.query(KukiChats).get(str(chat_id)) + if kukichat: + SESSION.delete(kukichat) + SESSION.commit() + + +def get_all_kuki_chats(): + try: + return SESSION.query(KukiChats.chat_id).all() + finally: + SESSION.close() diff --git a/Exon/modules/sql/locks_sql.py b/Exon/modules/sql/locks_sql.py new file mode 100644 index 00000000..62321425 --- /dev/null +++ b/Exon/modules/sql/locks_sql.py @@ -0,0 +1,292 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +# New chat added -> setup permissions +import threading + +from sqlalchemy import Boolean, Column, String + +from Exon.modules.sql import BASE, SESSION + + +class Permissions(BASE): + __tablename__ = "permissions" + chat_id = Column(String(14), primary_key=True) + # Booleans are for "is this locked", _NOT_ "is this allowed" + audio = Column(Boolean, default=False) + voice = Column(Boolean, default=False) + contact = Column(Boolean, default=False) + video = Column(Boolean, default=False) + document = Column(Boolean, default=False) + photo = Column(Boolean, default=False) + sticker = Column(Boolean, default=False) + gif = Column(Boolean, default=False) + url = Column(Boolean, default=False) + bots = Column(Boolean, default=False) + forward = Column(Boolean, default=False) + game = Column(Boolean, default=False) + location = Column(Boolean, default=False) + rtl = Column(Boolean, default=False) + button = Column(Boolean, default=False) + egame = Column(Boolean, default=False) + inline = Column(Boolean, default=False) + + def __init__(self, chat_id): + self.chat_id = str(chat_id) # ensure string + self.audio = False + self.voice = False + self.contact = False + self.video = False + self.document = False + self.photo = False + self.sticker = False + self.gif = False + self.url = False + self.bots = False + self.forward = False + self.game = False + self.location = False + self.rtl = False + self.button = False + self.egame = False + self.inline = False + + def __repr__(self): + return "<ᴘᴇʀᴍɪssɪᴏɴs ғᴏʀ %s>" % self.chat_id + + +class Restrictions(BASE): + __tablename__ = "restrictions" + chat_id = Column(String(14), primary_key=True) + # Booleans are for "is this restricted", _NOT_ "is this allowed" + messages = Column(Boolean, default=False) + media = Column(Boolean, default=False) + other = Column(Boolean, default=False) + preview = Column(Boolean, default=False) + + def __init__(self, chat_id): + self.chat_id = str(chat_id) # ensure string + self.messages = False + self.media = False + self.other = False + self.preview = False + + def __repr__(self): + return "<ʀᴇsᴛʀɪᴄᴛɪᴏɴs ғᴏʀ %s>" % self.chat_id + + +# For those who faced database error, Just uncomment the +# line below and run bot for 1 time & remove that line! + +Permissions.__table__.create(checkfirst=True) +# Permissions.__table__.drop() +Restrictions.__table__.create(checkfirst=True) + +PERM_LOCK = threading.RLock() +RESTR_LOCK = threading.RLock() + + +def init_permissions(chat_id, reset=False): + curr_perm = SESSION.query(Permissions).get(str(chat_id)) + if reset: + SESSION.delete(curr_perm) + SESSION.flush() + perm = Permissions(str(chat_id)) + SESSION.add(perm) + SESSION.commit() + return perm + + +def init_restrictions(chat_id, reset=False): + curr_restr = SESSION.query(Restrictions).get(str(chat_id)) + if reset: + SESSION.delete(curr_restr) + SESSION.flush() + restr = Restrictions(str(chat_id)) + SESSION.add(restr) + SESSION.commit() + return restr + + +def update_lock(chat_id, lock_type, locked): + with PERM_LOCK: + curr_perm = SESSION.query(Permissions).get(str(chat_id)) + if not curr_perm: + curr_perm = init_permissions(chat_id) + + if lock_type == "audio": + curr_perm.audio = locked + elif lock_type == "voice": + curr_perm.voice = locked + elif lock_type == "contact": + curr_perm.contact = locked + elif lock_type == "video": + curr_perm.video = locked + elif lock_type == "document": + curr_perm.document = locked + elif lock_type == "photo": + curr_perm.photo = locked + elif lock_type == "sticker": + curr_perm.sticker = locked + elif lock_type == "gif": + curr_perm.gif = locked + elif lock_type == "url": + curr_perm.url = locked + elif lock_type == "bots": + curr_perm.bots = locked + elif lock_type == "forward": + curr_perm.forward = locked + elif lock_type == "game": + curr_perm.game = locked + elif lock_type == "location": + curr_perm.location = locked + elif lock_type == "rtl": + curr_perm.rtl = locked + elif lock_type == "button": + curr_perm.button = locked + elif lock_type == "egame": + curr_perm.egame = locked + elif lock_type == "inline": + curr_perm.inline = locked + + SESSION.add(curr_perm) + SESSION.commit() + + +def update_restriction(chat_id, restr_type, locked): + with RESTR_LOCK: + curr_restr = SESSION.query(Restrictions).get(str(chat_id)) + if not curr_restr: + curr_restr = init_restrictions(chat_id) + + if restr_type == "messages": + curr_restr.messages = locked + elif restr_type == "media": + curr_restr.media = locked + elif restr_type == "other": + curr_restr.other = locked + elif restr_type == "previews": + curr_restr.preview = locked + elif restr_type == "all": + curr_restr.messages = locked + curr_restr.media = locked + curr_restr.other = locked + curr_restr.preview = locked + SESSION.add(curr_restr) + SESSION.commit() + + +def is_locked(chat_id, lock_type): + curr_perm = SESSION.query(Permissions).get(str(chat_id)) + SESSION.close() + + if not curr_perm: + return False + + if lock_type == "sticker": + return curr_perm.sticker + if lock_type == "photo": + return curr_perm.photo + if lock_type == "audio": + return curr_perm.audio + if lock_type == "voice": + return curr_perm.voice + if lock_type == "contact": + return curr_perm.contact + if lock_type == "video": + return curr_perm.video + if lock_type == "document": + return curr_perm.document + if lock_type == "gif": + return curr_perm.gif + if lock_type == "url": + return curr_perm.url + if lock_type == "bots": + return curr_perm.bots + if lock_type == "forward": + return curr_perm.forward + if lock_type == "game": + return curr_perm.game + if lock_type == "location": + return curr_perm.location + if lock_type == "rtl": + return curr_perm.rtl + if lock_type == "button": + return curr_perm.button + if lock_type == "egame": + return curr_perm.egame + if lock_type == "inline": + return curr_perm.inline + + +def is_restr_locked(chat_id, lock_type): + curr_restr = SESSION.query(Restrictions).get(str(chat_id)) + SESSION.close() + + if not curr_restr: + return False + + if lock_type == "messages": + return curr_restr.messages + if lock_type == "media": + return curr_restr.media + if lock_type == "other": + return curr_restr.other + if lock_type == "previews": + return curr_restr.preview + if lock_type == "all": + return ( + curr_restr.messages + and curr_restr.media + and curr_restr.other + and curr_restr.preview + ) + + +def get_locks(chat_id): + try: + return SESSION.query(Permissions).get(str(chat_id)) + finally: + SESSION.close() + + +def get_restr(chat_id): + try: + return SESSION.query(Restrictions).get(str(chat_id)) + finally: + SESSION.close() + + +def migrate_chat(old_chat_id, new_chat_id): + with PERM_LOCK: + perms = SESSION.query(Permissions).get(str(old_chat_id)) + if perms: + perms.chat_id = str(new_chat_id) + SESSION.commit() + + with RESTR_LOCK: + rest = SESSION.query(Restrictions).get(str(old_chat_id)) + if rest: + rest.chat_id = str(new_chat_id) + SESSION.commit() diff --git a/Exon/modules/sql/log_channel_sql.py b/Exon/modules/sql/log_channel_sql.py new file mode 100644 index 00000000..0224ef33 --- /dev/null +++ b/Exon/modules/sql/log_channel_sql.py @@ -0,0 +1,180 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import threading +import typing + +from sqlalchemy import BigInteger, Boolean, Column, String, distinct, func + +from Exon.modules.sql import BASE, SESSION + + +class GroupLogs(BASE): + __tablename__ = "log_channels" + chat_id = Column(String(14), primary_key=True) + log_channel = Column(String(14), nullable=False) + + def __init__(self, chat_id, log_channel): + self.chat_id = str(chat_id) + self.log_channel = str(log_channel) + + +class LogChannelSettings(BASE): + __tablename__ = "log_channel_setting" + chat_id = Column(BigInteger, primary_key=True) + log_joins = Column(Boolean, default=True) + log_leave = Column(Boolean, default=True) + log_warn = Column(Boolean, default=True) + log_action = Column(Boolean, default=True) + # log_media = Column(Boolean) + log_report = Column(Boolean, default=True) + + def __init__( + self, + chat_id: int, + log_join: bool, + log_leave: bool, + log_warn: bool, + log_action: bool, + log_report: bool, + ): + self.chat_id = chat_id + self.log_warn = log_warn + self.log_joins = log_join + self.log_leave = log_leave + self.log_report = log_report + self.log_action = log_action + + def toggle_warn(self) -> bool: + self.log_warn = not self.log_warn + SESSION.commit() + return self.log_warn + + def toggle_joins(self) -> bool: + self.log_joins = not self.log_joins + SESSION.commit() + return self.log_joins + + def toggle_leave(self) -> bool: + self.log_leave = not self.log_leave + SESSION.commit() + return self.log_leave + + def toggle_report(self) -> bool: + self.log_report = not self.log_report + SESSION.commit() + return self.log_report + + def toggle_action(self) -> bool: + self.log_action = not self.log_action + SESSION.commit() + return self.log_action + + +GroupLogs.__table__.create(checkfirst=True) +LogChannelSettings.__table__.create(checkfirst=True) + +LOGS_INSERTION_LOCK = threading.RLock() +LOG_SETTING_LOCK = threading.RLock() +CHANNELS = {} + + +def get_chat_setting(chat_id: int) -> typing.Optional[LogChannelSettings]: + with LOG_SETTING_LOCK: + return SESSION.query(LogChannelSettings).get(chat_id) + + +def set_chat_setting(setting: LogChannelSettings): + with LOGS_INSERTION_LOCK: + res: LogChannelSettings = SESSION.query(LogChannelSettings).get(setting.chat_id) + if res: + res.log_warn = setting.log_warn + res.log_action = setting.log_action + res.log_report = setting.log_report + res.log_joins = setting.log_joins + res.log_leave = setting.log_leave + else: + SESSION.add(setting) + SESSION.commit() + + +def set_chat_log_channel(chat_id, log_channel): + with LOGS_INSERTION_LOCK: + res = SESSION.query(GroupLogs).get(str(chat_id)) + if res: + res.log_channel = log_channel + else: + res = GroupLogs(chat_id, log_channel) + SESSION.add(res) + + CHANNELS[str(chat_id)] = log_channel + SESSION.commit() + + +def get_chat_log_channel(chat_id): + return CHANNELS.get(str(chat_id)) + + +def stop_chat_logging(chat_id): + with LOGS_INSERTION_LOCK: + res = SESSION.query(GroupLogs).get(str(chat_id)) + if res: + if str(chat_id) in CHANNELS: + del CHANNELS[str(chat_id)] + + log_channel = res.log_channel + SESSION.delete(res) + SESSION.commit() + return log_channel + + +def num_logchannels(): + try: + return SESSION.query(func.count(distinct(GroupLogs.chat_id))).scalar() + finally: + SESSION.close() + + +def migrate_chat(old_chat_id, new_chat_id): + with LOGS_INSERTION_LOCK: + chat = SESSION.query(GroupLogs).get(str(old_chat_id)) + if chat: + chat.chat_id = str(new_chat_id) + SESSION.add(chat) + if str(old_chat_id) in CHANNELS: + CHANNELS[str(new_chat_id)] = CHANNELS.get(str(old_chat_id)) + + SESSION.commit() + + +def __load_log_channels(): + global CHANNELS + try: + all_chats = SESSION.query(GroupLogs).all() + CHANNELS = {chat.chat_id: chat.log_channel for chat in all_chats} + finally: + SESSION.close() + + +__load_log_channels() diff --git a/Exon/modules/sql/nightmode_sql.py b/Exon/modules/sql/nightmode_sql.py new file mode 100644 index 00000000..2ac4e0d2 --- /dev/null +++ b/Exon/modules/sql/nightmode_sql.py @@ -0,0 +1,42 @@ +from sqlalchemy import Column, String + +from Exon.modules.sql import BASE, SESSION + + +class Nightmode(BASE): + __tablename__ = "nightmode" + chat_id = Column(String(14), primary_key=True) + + def __init__(self, chat_id): + self.chat_id = chat_id + + +Nightmode.__table__.create(checkfirst=True) + + +def add_nightmode(chat_id: str): + nightmoddy = Nightmode(str(chat_id)) + SESSION.add(nightmoddy) + SESSION.commit() + + +def rmnightmode(chat_id: str): + rmnightmoddy = SESSION.query(Nightmode).get(str(chat_id)) + if rmnightmoddy: + SESSION.delete(rmnightmoddy) + SESSION.commit() + + +def get_all_chat_id(): + stark = SESSION.query(Nightmode).all() + SESSION.close() + return stark + + +def is_nightmode_indb(chat_id: str): + try: + s__ = SESSION.query(Nightmode).get(str(chat_id)) + if s__: + return str(s__.chat_id) + finally: + SESSION.close() diff --git a/Exon/modules/sql/notes_sql.py b/Exon/modules/sql/notes_sql.py new file mode 100644 index 00000000..88ed8075 --- /dev/null +++ b/Exon/modules/sql/notes_sql.py @@ -0,0 +1,211 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + + +# Note: chat_id's are stored as strings because the int is too large to be stored in a PSQL database. +import threading + +from sqlalchemy import BigInteger, Boolean, Column, String, UnicodeText, distinct, func + +from Exon.modules.helper_funcs.msg_types import Types +from Exon.modules.sql import BASE, SESSION + + +class Notes(BASE): + __tablename__ = "notes" + chat_id = Column(String(14), primary_key=True) + name = Column(UnicodeText, primary_key=True) + value = Column(UnicodeText, nullable=False) + file = Column(UnicodeText) + is_reply = Column(Boolean, default=False) + has_buttons = Column(Boolean, default=False) + msgtype = Column(BigInteger, default=Types.BUTTON_TEXT.value) + + def __init__(self, chat_id, name, value, msgtype, file=None): + self.chat_id = str(chat_id) # ensure string + self.name = name + self.value = value + self.msgtype = msgtype + self.file = file + + def __repr__(self): + return "<ɴᴏᴛᴇ %s>" % self.name + + +class Buttons(BASE): + __tablename__ = "note_urls" + id = Column(BigInteger, primary_key=True, autoincrement=True) + chat_id = Column(String(14), primary_key=True) + note_name = Column(UnicodeText, primary_key=True) + name = Column(UnicodeText, nullable=False) + url = Column(UnicodeText, nullable=False) + same_line = Column(Boolean, default=False) + + def __init__(self, chat_id, note_name, name, url, same_line=False): + self.chat_id = str(chat_id) + self.note_name = note_name + self.name = name + self.url = url + self.same_line = same_line + + +Notes.__table__.create(checkfirst=True) +Buttons.__table__.create(checkfirst=True) + +NOTES_INSERTION_LOCK = threading.RLock() +BUTTONS_INSERTION_LOCK = threading.RLock() + + +def add_note_to_db(chat_id, note_name, note_data, msgtype, buttons=None, file=None): + if not buttons: + buttons = [] + + with NOTES_INSERTION_LOCK: + prev = SESSION.query(Notes).get((str(chat_id), note_name)) + if prev: + with BUTTONS_INSERTION_LOCK: + prev_buttons = ( + SESSION.query(Buttons) + .filter( + Buttons.chat_id == str(chat_id), + Buttons.note_name == note_name, + ) + .all() + ) + for btn in prev_buttons: + SESSION.delete(btn) + SESSION.delete(prev) + note = Notes( + str(chat_id), + note_name, + note_data or "", + msgtype=msgtype.value, + file=file, + ) + SESSION.add(note) + SESSION.commit() + + for b_name, url, same_line in buttons: + add_note_button_to_db(chat_id, note_name, b_name, url, same_line) + + +def get_note(chat_id, note_name): + try: + return ( + SESSION.query(Notes) + .filter(func.lower(Notes.name) == note_name, Notes.chat_id == str(chat_id)) + .first() + ) + finally: + SESSION.close() + + +def rm_note(chat_id, note_name): + with NOTES_INSERTION_LOCK: + note = ( + SESSION.query(Notes) + .filter(func.lower(Notes.name) == note_name, Notes.chat_id == str(chat_id)) + .first() + ) + if note: + with BUTTONS_INSERTION_LOCK: + buttons = ( + SESSION.query(Buttons) + .filter( + Buttons.chat_id == str(chat_id), + Buttons.note_name == note_name, + ) + .all() + ) + for btn in buttons: + SESSION.delete(btn) + + SESSION.delete(note) + SESSION.commit() + return True + SESSION.close() + return False + + +def get_all_chat_notes(chat_id): + try: + return ( + SESSION.query(Notes) + .filter(Notes.chat_id == str(chat_id)) + .order_by(Notes.name.asc()) + .all() + ) + finally: + SESSION.close() + + +def add_note_button_to_db(chat_id, note_name, b_name, url, same_line): + with BUTTONS_INSERTION_LOCK: + button = Buttons(chat_id, note_name, b_name, url, same_line) + SESSION.add(button) + SESSION.commit() + + +def get_buttons(chat_id, note_name): + try: + return ( + SESSION.query(Buttons) + .filter(Buttons.chat_id == str(chat_id), Buttons.note_name == note_name) + .order_by(Buttons.id) + .all() + ) + finally: + SESSION.close() + + +def num_notes(): + try: + return SESSION.query(Notes).count() + finally: + SESSION.close() + + +def num_chats(): + try: + return SESSION.query(func.count(distinct(Notes.chat_id))).scalar() + finally: + SESSION.close() + + +def migrate_chat(old_chat_id, new_chat_id): + with NOTES_INSERTION_LOCK: + chat_notes = ( + SESSION.query(Notes).filter(Notes.chat_id == str(old_chat_id)).all() + ) + for note in chat_notes: + note.chat_id = str(new_chat_id) + + with BUTTONS_INSERTION_LOCK: + chat_buttons = ( + SESSION.query(Buttons).filter(Buttons.chat_id == str(old_chat_id)).all() + ) + for btn in chat_buttons: + btn.chat_id = str(new_chat_id) + + SESSION.commit() diff --git a/Exon/modules/sql/nsfw_sql.py b/Exon/modules/sql/nsfw_sql.py new file mode 100644 index 00000000..ad88f452 --- /dev/null +++ b/Exon/modules/sql/nsfw_sql.py @@ -0,0 +1,73 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import threading + +from sqlalchemy import Column, String + +from Exon.modules.sql import BASE, SESSION + + +class NSFWChats(BASE): + __tablename__ = "nsfw_chats" + chat_id = Column(String(14), primary_key=True) + + def __init__(self, chat_id): + self.chat_id = chat_id + + +NSFWChats.__table__.create(checkfirst=True) +INSERTION_LOCK = threading.RLock() + + +def is_nsfw(chat_id): + try: + chat = SESSION.query(NSFWChats).get(str(chat_id)) + return bool(chat) + finally: + SESSION.close() + + +def set_nsfw(chat_id): + with INSERTION_LOCK: + nsfwchat = SESSION.query(NSFWChats).get(str(chat_id)) + if not nsfwchat: + nsfwchat = NSFWChats(str(chat_id)) + SESSION.add(nsfwchat) + SESSION.commit() + + +def rem_nsfw(chat_id): + with INSERTION_LOCK: + nsfwchat = SESSION.query(NSFWChats).get(str(chat_id)) + if nsfwchat: + SESSION.delete(nsfwchat) + SESSION.commit() + + +def get_all_nsfw_chats(): + try: + return SESSION.query(NSFWChats.chat_id).all() + finally: + SESSION.close() diff --git a/Exon/modules/sql/nsfw_watch_sql.py b/Exon/modules/sql/nsfw_watch_sql.py new file mode 100644 index 00000000..6107c45f --- /dev/null +++ b/Exon/modules/sql/nsfw_watch_sql.py @@ -0,0 +1,66 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +from sqlalchemy import Column, String + +from Exon.modules.sql import BASE, SESSION + + +class Nsfwatch(BASE): + __tablename__ = "nsfwatch" + chat_id = Column(String(14), primary_key=True) + + def __init__(self, chat_id): + self.chat_id = chat_id + + +Nsfwatch.__table__.create(checkfirst=True) + + +def add_nsfwatch(chat_id: str): + nsfws = Nsfwatch(str(chat_id)) + SESSION.add(nsfws) + SESSION.commit() + + +def rmnsfwatch(chat_id: str): + nsfwm = SESSION.query(Nsfwatch).get(str(chat_id)) + if nsfwm: + SESSION.delete(nsfwm) + SESSION.commit() + + +def get_all_nsfw_enabled_chat(): + stark = SESSION.query(Nsfwatch).all() + SESSION.close() + return stark + + +def is_nsfwatch_indb(chat_id: str): + try: + s__ = SESSION.query(Nsfwatch).get(str(chat_id)) + if s__: + return str(s__.chat_id) + finally: + SESSION.close() diff --git a/Exon/modules/sql/pin_sql.py b/Exon/modules/sql/pin_sql.py new file mode 100644 index 00000000..d1cf6090 --- /dev/null +++ b/Exon/modules/sql/pin_sql.py @@ -0,0 +1,97 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import threading + +from sqlalchemy import BigInteger, Boolean, Column, String + +from Exon.modules.sql import BASE, SESSION + + +class SPinSettings(BASE): + __tablename__ = "pin_settings" + + chat_id = Column(String(14), primary_key=True) + message_id = Column(BigInteger) + suacpmo = Column(Boolean, default=False) + scldpmo = Column(Boolean, default=False) + + def __init__(self, chat_id, message_id): + self.chat_id = str(chat_id) + self.message_id = message_id + + def __repr__(self): + return "<ᴘɪɴ sᴇᴛᴛɪɴɢs ғᴏʀ {} ɪɴ {}>".format(self.chat_id, self.message_id) + + +SPinSettings.__table__.create(checkfirst=True) + +PIN_INSERTION_LOCK = threading.RLock() + + +def add_mid(chat_id, message_id): + with PIN_INSERTION_LOCK: + chat = SESSION.query(SPinSettings).get(str(chat_id)) + if not chat: + chat = SPinSettings(str(chat_id), message_id) + SESSION.add(chat) + SESSION.commit() + SESSION.close() + + +def remove_mid(chat_id): + with PIN_INSERTION_LOCK: + chat = SESSION.query(SPinSettings).get(str(chat_id)) + if chat: + SESSION.delete(chat) + SESSION.commit() + SESSION.close() + + +def add_acp_o(chat_id, setting): + with PIN_INSERTION_LOCK: + chat = SESSION.query(SPinSettings).get(str(chat_id)) + if not chat: + chat = SPinSettings(str(chat_id), 0) + chat.suacpmo = setting + SESSION.add(chat) + SESSION.commit() + SESSION.close() + + +def add_ldp_m(chat_id, setting): + with PIN_INSERTION_LOCK: + chat = SESSION.query(SPinSettings).get(str(chat_id)) + if not chat: + chat = SPinSettings(str(chat_id), 0) + chat.scldpmo = setting + SESSION.add(chat) + SESSION.commit() + SESSION.close() + + +def get_current_settings(chat_id): + with PIN_INSERTION_LOCK: + chat = SESSION.query(SPinSettings).get(str(chat_id)) + return chat diff --git a/Exon/modules/sql/raid_sql.py b/Exon/modules/sql/raid_sql.py new file mode 100644 index 00000000..d0491e1a --- /dev/null +++ b/Exon/modules/sql/raid_sql.py @@ -0,0 +1,73 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import threading + +from sqlalchemy import Column, String + +from Exon.modules.sql import BASE, SESSION + + +class RaidChats(BASE): + __tablename__ = "raid_chats" + chat_id = Column(String(14), primary_key=True) + + def __init__(self, chat_id): + self.chat_id = chat_id + + +RaidChats.__table__.create(checkfirst=True) +INSERTION_LOCK = threading.RLock() + + +def is_raid(chat_id): + try: + chat = SESSION.query(RaidChats).get(str(chat_id)) + return bool(chat) + finally: + SESSION.close() + + +def set_raid(chat_id): + with INSERTION_LOCK: + raidchat = SESSION.query(RaidChats).get(str(chat_id)) + if not raidchat: + raidchat = RaidChats(str(chat_id)) + SESSION.add(raidchat) + SESSION.commit() + + +def rem_raid(chat_id): + with INSERTION_LOCK: + raidchat = SESSION.query(RaidChats).get(str(chat_id)) + if raidchat: + SESSION.delete(raidchat) + SESSION.commit() + + +def get_all_raid_chats(): + try: + return SESSION.query(RaidChats.chat_id).all() + finally: + SESSION.close() diff --git a/Exon/modules/sql/remind_sql.py b/Exon/modules/sql/remind_sql.py new file mode 100644 index 00000000..ea2d3acb --- /dev/null +++ b/Exon/modules/sql/remind_sql.py @@ -0,0 +1,140 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import threading +import time + +from sqlalchemy import Column, Integer, String, UnicodeText +from sqlalchemy.sql.sqltypes import BigInteger + +from Exon.modules.sql import BASE, SESSION + + +class Reminds(BASE): + __tablename__ = "reminds" + chat_id = Column(String(14), primary_key=True) + time_seconds = Column(Integer, primary_key=True) + remind_message = Column(UnicodeText, default="") + user_id = Column(BigInteger, default=0) + + def __init__(self, chat_id, time_seconds): + self.chat_id = str(chat_id) + self.time_seconds = int(time_seconds) + + def __repr__(self): + return "<ʀᴇᴍɪɴᴅ ɪɴ {} ғᴏʀ ᴛɪᴍᴇ {}>".format( + self.chat_id, + self.time_seconds, + ) + + +# Reminds.__table__.drop() +Reminds.__table__.create(checkfirst=True) + +INSERTION_LOCK = threading.RLock() + +REMINDERS = {} + + +def set_remind(chat_id, time_sec, remind_message, user_id): + with INSERTION_LOCK: + reminds = SESSION.query(Reminds).get((str(chat_id), time_sec)) + if not reminds: + reminds = Reminds(chat_id, time_sec) + reminds.remind_message = remind_message + reminds.user_id = user_id + SESSION.add(reminds) + SESSION.commit() + if not time_sec in REMINDERS: + REMINDERS[time_sec] = [] + REMINDERS[time_sec].append( + {"chat_id": str(chat_id), "message": remind_message, "user_id": user_id} + ) + + +def rem_remind(chat_id, time_sec, remind_message, user_id): + with INSERTION_LOCK: + reminds = SESSION.query(Reminds).get((str(chat_id), time_sec)) + if reminds: + SESSION.delete(reminds) + SESSION.commit() + REMINDERS[time_sec].remove( + {"chat_id": str(chat_id), "message": remind_message, "user_id": user_id} + ) + return True + SESSION.close() + return False + + +def get_remind_in_chat(chat_id, timestamp): + return ( + SESSION.query(Reminds) + .filter(Reminds.chat_id == str(chat_id), Reminds.time_seconds == timestamp) + .first() + ) + + +def num_reminds_in_chat(chat_id): + return SESSION.query(Reminds).filter(Reminds.chat_id == str(chat_id)).count() + + +def get_reminds_in_chat(chat_id): + try: + return ( + SESSION.query(Reminds) + .filter(Reminds.chat_id == str(chat_id)) + .order_by(Reminds.time_seconds.asc()) + .all() + ) + finally: + SESSION.close() + + +def __get_all_reminds(): + try: + chats = SESSION.query(Reminds).all() + for chat in chats: + if (chat.time_seconds <= round(time.time())) or chat.user_id == 0: + try: + rem_remind( + chat.chat_id, + chat.time_seconds, + chat.remind_message, + chat.user_id, + ) + except: + pass + continue + REMINDERS[chat.time_seconds] = [ + { + "chat_id": chat.chat_id, + "message": chat.remind_message, + "user_id": chat.user_id, + } + ] + finally: + SESSION.close() + + +__get_all_reminds() diff --git a/Exon/modules/sql/reporting_sql.py b/Exon/modules/sql/reporting_sql.py new file mode 100644 index 00000000..7e57b6e9 --- /dev/null +++ b/Exon/modules/sql/reporting_sql.py @@ -0,0 +1,115 @@ +""" +MIT License + +Copyright (c) 2022 Arsh + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import threading +from typing import Union + +from sqlalchemy import BigInteger, Boolean, Column, String + +from Exon.modules.sql import BASE, SESSION + + +class ReportingUserSettings(BASE): + __tablename__ = "user_report_settings" + user_id = Column(BigInteger, primary_key=True) + should_report = Column(Boolean, default=True) + + def __init__(self, user_id): + self.user_id = user_id + + def __repr__(self): + return "<ᴜsᴇʀ ʀᴇᴘᴏʀᴛ sᴇᴛᴛɪɴɢs ({})>".format(self.user_id) + + +class ReportingChatSettings(BASE): + __tablename__ = "chat_report_settings" + chat_id = Column(String(14), primary_key=True) + should_report = Column(Boolean, default=True) + + def __init__(self, chat_id): + self.chat_id = str(chat_id) + + def __repr__(self): + return "<ᴄʜᴀᴛ ʀᴇᴘᴏʀᴛ sᴇᴛᴛɪɴɢs ({})>".format(self.chat_id) + + +ReportingUserSettings.__table__.create(checkfirst=True) +ReportingChatSettings.__table__.create(checkfirst=True) + +CHAT_LOCK = threading.RLock() +USER_LOCK = threading.RLock() + + +def chat_should_report(chat_id: Union[str, int]) -> bool: + try: + chat_setting = SESSION.query(ReportingChatSettings).get(str(chat_id)) + if chat_setting: + return chat_setting.should_report + return False + finally: + SESSION.close() + + +def user_should_report(user_id: int) -> bool: + try: + user_setting = SESSION.query(ReportingUserSettings).get(user_id) + if user_setting: + return user_setting.should_report + return True + finally: + SESSION.close() + + +def set_chat_setting(chat_id: Union[int, str], setting: bool): + with CHAT_LOCK: + chat_setting = SESSION.query(ReportingChatSettings).get(str(chat_id)) + if not chat_setting: + chat_setting = ReportingChatSettings(chat_id) + + chat_setting.should_report = setting + SESSION.add(chat_setting) + SESSION.commit() + + +def set_user_setting(user_id: int, setting: bool): + with USER_LOCK: + user_setting = SESSION.query(ReportingUserSettings).get(user_id) + if not user_setting: + user_setting = ReportingUserSettings(user_id) + + user_setting.should_report = setting + SESSION.add(user_setting) + SESSION.commit() + + +def migrate_chat(old_chat_id, new_chat_id): + with CHAT_LOCK: + chat_notes = ( + SESSION.query(ReportingChatSettings) + .filter(ReportingChatSettings.chat_id == str(old_chat_id)) + .all() + ) + for note in chat_notes: + note.chat_id = str(new_chat_id) + SESSION.commit() diff --git a/Exon/modules/sql/rules_sql.py b/Exon/modules/sql/rules_sql.py new file mode 100644 index 00000000..9220ae06 --- /dev/null +++ b/Exon/modules/sql/rules_sql.py @@ -0,0 +1,79 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import threading + +from sqlalchemy import Column, String, UnicodeText, distinct, func + +from Exon.modules.sql import BASE, SESSION + + +class Rules(BASE): + __tablename__ = "rules" + chat_id = Column(String(14), primary_key=True) + rules = Column(UnicodeText, default="") + + def __init__(self, chat_id): + self.chat_id = chat_id + + def __repr__(self): + return "<ᴄʜᴀᴛ {} ʀᴜʟᴇs: {}>".format(self.chat_id, self.rules) + + +Rules.__table__.create(checkfirst=True) + +INSERTION_LOCK = threading.RLock() + + +def set_rules(chat_id, rules_text): + with INSERTION_LOCK: + rules = SESSION.query(Rules).get(str(chat_id)) + if not rules: + rules = Rules(str(chat_id)) + rules.rules = rules_text + + SESSION.add(rules) + SESSION.commit() + + +def get_rules(chat_id): + rules = SESSION.query(Rules).get(str(chat_id)) + ret = rules.rules if rules else "" + SESSION.close() + return ret + + +def num_chats(): + try: + return SESSION.query(func.count(distinct(Rules.chat_id))).scalar() + finally: + SESSION.close() + + +def migrate_chat(old_chat_id, new_chat_id): + with INSERTION_LOCK: + chat = SESSION.query(Rules).get(str(old_chat_id)) + if chat: + chat.chat_id = str(new_chat_id) + SESSION.commit() diff --git a/Exon/modules/sql/userinfo_sql.py b/Exon/modules/sql/userinfo_sql.py new file mode 100644 index 00000000..cc8021fd --- /dev/null +++ b/Exon/modules/sql/userinfo_sql.py @@ -0,0 +1,100 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏᴏ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import threading + +from sqlalchemy import BigInteger, Column, UnicodeText + +from Exon.modules.sql import BASE, SESSION + + +class UserInfo(BASE): + __tablename__ = "userinfo" + user_id = Column(BigInteger, primary_key=True) + info = Column(UnicodeText) + + def __init__(self, user_id, info): + self.user_id = user_id + self.info = info + + def __repr__(self): + return "<ᴜsᴇʀ ɪɴғᴏ %d>" % self.user_id + + +class UserBio(BASE): + __tablename__ = "userbio" + user_id = Column(BigInteger, primary_key=True) + bio = Column(UnicodeText) + + def __init__(self, user_id, bio): + self.user_id = user_id + self.bio = bio + + def __repr__(self): + return "<ᴜsᴇʀ ɪɴғᴏ %d>" % self.user_id + + +UserInfo.__table__.create(checkfirst=True) +UserBio.__table__.create(checkfirst=True) + +INSERTION_LOCK = threading.RLock() + + +def get_user_me_info(user_id): + userinfo = SESSION.query(UserInfo).get(user_id) + SESSION.close() + if userinfo: + return userinfo.info + return None + + +def set_user_me_info(user_id, info): + with INSERTION_LOCK: + userinfo = SESSION.query(UserInfo).get(user_id) + if userinfo: + userinfo.info = info + else: + userinfo = UserInfo(user_id, info) + SESSION.add(userinfo) + SESSION.commit() + + +def get_user_bio(user_id): + userbio = SESSION.query(UserBio).get(user_id) + SESSION.close() + if userbio: + return userbio.bio + return None + + +def set_user_bio(user_id, bio): + with INSERTION_LOCK: + userbio = SESSION.query(UserBio).get(user_id) + if userbio: + userbio.bio = bio + else: + userbio = UserBio(user_id, bio) + + SESSION.add(userbio) + SESSION.commit() diff --git a/Exon/modules/sql/users_sql.py b/Exon/modules/sql/users_sql.py new file mode 100644 index 00000000..d7e7ca9c --- /dev/null +++ b/Exon/modules/sql/users_sql.py @@ -0,0 +1,258 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import threading + +from sqlalchemy import ( + BigInteger, + Column, + ForeignKey, + String, + UnicodeText, + UniqueConstraint, + func, +) + +from Exon import dispatcher +from Exon.modules.sql import BASE, SESSION + + +class Users(BASE): + __tablename__ = "users" + user_id = Column(BigInteger, primary_key=True) + username = Column(UnicodeText) + + def __init__(self, user_id, username=None): + self.user_id = user_id + self.username = username + + def __repr__(self): + return "<ᴜsᴇʀ {} ({})>".format(self.username, self.user_id) + + +class Chats(BASE): + __tablename__ = "chats" + chat_id = Column(String(14), primary_key=True) + chat_name = Column(UnicodeText, nullable=False) + + def __init__(self, chat_id, chat_name): + self.chat_id = str(chat_id) + self.chat_name = chat_name + + def __repr__(self): + return "<ᴄʜᴀᴛ {} ({})>".format(self.chat_name, self.chat_id) + + +class ChatMembers(BASE): + __tablename__ = "chat_members" + priv_chat_id = Column(BigInteger, primary_key=True) + # NOTE: Use dual primary key instead of private primary key? + chat = Column( + String(14), + ForeignKey("chats.chat_id", onupdate="CASCADE", ondelete="CASCADE"), + nullable=False, + ) + user = Column( + BigInteger, + ForeignKey("users.user_id", onupdate="CASCADE", ondelete="CASCADE"), + nullable=False, + ) + __table_args__ = (UniqueConstraint("chat", "user", name="_chat_members_uc"),) + + def __init__(self, chat, user): + self.chat = chat + self.user = user + + def __repr__(self): + return "<ᴄʜᴀᴛ ᴜsᴇʀ {} ({}) ɪɴ ᴄʜᴀᴛ {} ({})>".format( + self.user.username, + self.user.user_id, + self.chat.chat_name, + self.chat.chat_id, + ) + + +Users.__table__.create(checkfirst=True) +Chats.__table__.create(checkfirst=True) +ChatMembers.__table__.create(checkfirst=True) + +INSERTION_LOCK = threading.RLock() + + +def ensure_bot_in_db(): + with INSERTION_LOCK: + bot = Users(dispatcher.bot.id, dispatcher.bot.username) + SESSION.merge(bot) + SESSION.commit() + + +def update_user(user_id, username, chat_id=None, chat_name=None): + with INSERTION_LOCK: + user = SESSION.query(Users).get(user_id) + if not user: + user = Users(user_id, username) + SESSION.add(user) + SESSION.flush() + else: + user.username = username + + if not chat_id or not chat_name: + SESSION.commit() + return + + chat = SESSION.query(Chats).get(str(chat_id)) + if not chat: + chat = Chats(str(chat_id), chat_name) + SESSION.add(chat) + SESSION.flush() + + else: + chat.chat_name = chat_name + + member = ( + SESSION.query(ChatMembers) + .filter(ChatMembers.chat == chat.chat_id, ChatMembers.user == user.user_id) + .first() + ) + if not member: + chat_member = ChatMembers(chat.chat_id, user.user_id) + SESSION.add(chat_member) + + SESSION.commit() + + +def get_userid_by_name(username): + try: + return ( + SESSION.query(Users) + .filter(func.lower(Users.username) == username.lower()) + .all() + ) + finally: + SESSION.close() + + +def get_name_by_userid(user_id): + try: + return SESSION.query(Users).get(Users.user_id == int(user_id)).first() + finally: + SESSION.close() + + +def get_chat_members(chat_id): + try: + return SESSION.query(ChatMembers).filter(ChatMembers.chat == str(chat_id)).all() + finally: + SESSION.close() + + +def get_all_chats(): + try: + return SESSION.query(Chats).all() + finally: + SESSION.close() + + +def get_all_users(): + try: + return SESSION.query(Users).all() + finally: + SESSION.close() + + +def get_user_num_chats(user_id): + try: + return ( + SESSION.query(ChatMembers).filter(ChatMembers.user == int(user_id)).count() + ) + finally: + SESSION.close() + + +def get_user_com_chats(user_id): + try: + chat_members = ( + SESSION.query(ChatMembers).filter(ChatMembers.user == int(user_id)).all() + ) + return [i.chat for i in chat_members] + finally: + SESSION.close() + + +def num_chats(): + try: + return SESSION.query(Chats).count() + finally: + SESSION.close() + + +def num_users(): + try: + return SESSION.query(Users).count() + finally: + SESSION.close() + + +def migrate_chat(old_chat_id, new_chat_id): + with INSERTION_LOCK: + chat = SESSION.query(Chats).get(str(old_chat_id)) + if chat: + chat.chat_id = str(new_chat_id) + SESSION.commit() + + chat_members = ( + SESSION.query(ChatMembers) + .filter(ChatMembers.chat == str(old_chat_id)) + .all() + ) + for member in chat_members: + member.chat = str(new_chat_id) + SESSION.commit() + + +ensure_bot_in_db() + + +def del_user(user_id): + with INSERTION_LOCK: + curr = SESSION.query(Users).get(user_id) + if curr: + SESSION.delete(curr) + SESSION.commit() + return True + + ChatMembers.query.filter(ChatMembers.user == user_id).delete() + SESSION.commit() + SESSION.close() + return False + + +def rem_chat(chat_id): + with INSERTION_LOCK: + chat = SESSION.query(Chats).get(str(chat_id)) + if chat: + SESSION.delete(chat) + SESSION.commit() + else: + SESSION.close() diff --git a/Exon/modules/sql/warns_sql.py b/Exon/modules/sql/warns_sql.py new file mode 100644 index 00000000..b188e790 --- /dev/null +++ b/Exon/modules/sql/warns_sql.py @@ -0,0 +1,341 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import threading + +from sqlalchemy import BigInteger, Boolean, Column, String, UnicodeText, distinct, func +from sqlalchemy.dialects import postgresql + +from Exon.modules.sql import BASE, SESSION + + +class Warns(BASE): + __tablename__ = "warns" + + user_id = Column(BigInteger, primary_key=True) + chat_id = Column(String(14), primary_key=True) + num_warns = Column(BigInteger, default=0) + reasons = Column(postgresql.ARRAY(UnicodeText)) + + def __init__(self, user_id, chat_id): + self.user_id = user_id + self.chat_id = str(chat_id) + self.num_warns = 0 + self.reasons = [] + + def __repr__(self): + return "<{} ᴡᴀʀɴs ғᴏʀ {} ɪɴ {} ғᴏʀ ʀᴇᴀsᴏɴs {}>".format( + self.num_warns, + self.user_id, + self.chat_id, + self.reasons, + ) + + +class WarnFilters(BASE): + __tablename__ = "warn_filters" + chat_id = Column(String(14), primary_key=True) + keyword = Column(UnicodeText, primary_key=True, nullable=False) + reply = Column(UnicodeText, nullable=False) + + def __init__(self, chat_id, keyword, reply): + self.chat_id = str(chat_id) # ensure string + self.keyword = keyword + self.reply = reply + + def __repr__(self): + return "<ᴘᴇʀᴍɪssɪᴏɴs ғᴏʀ %s>" % self.chat_id + + def __eq__(self, other): + return bool( + isinstance(other, WarnFilters) + and self.chat_id == other.chat_id + and self.keyword == other.keyword, + ) + + +class WarnSettings(BASE): + __tablename__ = "warn_settings" + chat_id = Column(String(14), primary_key=True) + warn_limit = Column(BigInteger, default=3) + soft_warn = Column(Boolean, default=False) + + def __init__(self, chat_id, warn_limit=3, soft_warn=False): + self.chat_id = str(chat_id) + self.warn_limit = warn_limit + self.soft_warn = soft_warn + + def __repr__(self): + return "<{} ʜᴀs {} ᴘᴏssɪʙʟᴇ ᴡᴀʀɴs.>".format(self.chat_id, self.warn_limit) + + +Warns.__table__.create(checkfirst=True) +WarnFilters.__table__.create(checkfirst=True) +WarnSettings.__table__.create(checkfirst=True) + +WARN_INSERTION_LOCK = threading.RLock() +WARN_FILTER_INSERTION_LOCK = threading.RLock() +WARN_SETTINGS_LOCK = threading.RLock() + +WARN_FILTERS = {} + + +def warn_user(user_id, chat_id, reason=None): + with WARN_INSERTION_LOCK: + warned_user = SESSION.query(Warns).get((user_id, str(chat_id))) + if not warned_user: + warned_user = Warns(user_id, str(chat_id)) + + warned_user.num_warns += 1 + if reason: + warned_user.reasons = warned_user.reasons + [ + reason, + ] # TODO:: double check this wizardry + + reasons = warned_user.reasons + num = warned_user.num_warns + + SESSION.add(warned_user) + SESSION.commit() + + return num, reasons + + +def remove_warn(user_id, chat_id): + with WARN_INSERTION_LOCK: + removed = False + warned_user = SESSION.query(Warns).get((user_id, str(chat_id))) + + if warned_user and warned_user.num_warns > 0: + warned_user.num_warns -= 1 + warned_user.reasons = warned_user.reasons[:-1] + SESSION.add(warned_user) + SESSION.commit() + removed = True + + SESSION.close() + return removed + + +def reset_warns(user_id, chat_id): + with WARN_INSERTION_LOCK: + warned_user = SESSION.query(Warns).get((user_id, str(chat_id))) + if warned_user: + warned_user.num_warns = 0 + warned_user.reasons = [] + + SESSION.add(warned_user) + SESSION.commit() + SESSION.close() + + +def get_warns(user_id, chat_id): + try: + user = SESSION.query(Warns).get((user_id, str(chat_id))) + if not user: + return None + reasons = user.reasons + num = user.num_warns + return num, reasons + finally: + SESSION.close() + + +def add_warn_filter(chat_id, keyword, reply): + with WARN_FILTER_INSERTION_LOCK: + warn_filt = WarnFilters(str(chat_id), keyword, reply) + + if keyword not in WARN_FILTERS.get(str(chat_id), []): + WARN_FILTERS[str(chat_id)] = sorted( + WARN_FILTERS.get(str(chat_id), []) + [keyword], + key=lambda x: (-len(x), x), + ) + + SESSION.merge(warn_filt) # merge to avoid duplicate key issues + SESSION.commit() + + +def remove_warn_filter(chat_id, keyword): + with WARN_FILTER_INSERTION_LOCK: + warn_filt = SESSION.query(WarnFilters).get((str(chat_id), keyword)) + if warn_filt: + if keyword in WARN_FILTERS.get(str(chat_id), []): # sanity check + WARN_FILTERS.get(str(chat_id), []).remove(keyword) + + SESSION.delete(warn_filt) + SESSION.commit() + return True + SESSION.close() + return False + + +def get_chat_warn_triggers(chat_id): + return WARN_FILTERS.get(str(chat_id), set()) + + +def get_chat_warn_filters(chat_id): + try: + return ( + SESSION.query(WarnFilters).filter(WarnFilters.chat_id == str(chat_id)).all() + ) + finally: + SESSION.close() + + +def get_warn_filter(chat_id, keyword): + try: + return SESSION.query(WarnFilters).get((str(chat_id), keyword)) + finally: + SESSION.close() + + +def set_warn_limit(chat_id, warn_limit): + with WARN_SETTINGS_LOCK: + curr_setting = SESSION.query(WarnSettings).get(str(chat_id)) + if not curr_setting: + curr_setting = WarnSettings(chat_id, warn_limit=warn_limit) + + curr_setting.warn_limit = warn_limit + + SESSION.add(curr_setting) + SESSION.commit() + + +def set_warn_strength(chat_id, soft_warn): + with WARN_SETTINGS_LOCK: + curr_setting = SESSION.query(WarnSettings).get(str(chat_id)) + if not curr_setting: + curr_setting = WarnSettings(chat_id, soft_warn=soft_warn) + + curr_setting.soft_warn = soft_warn + + SESSION.add(curr_setting) + SESSION.commit() + + +def get_warn_setting(chat_id): + try: + setting = SESSION.query(WarnSettings).get(str(chat_id)) + if setting: + return setting.warn_limit, setting.soft_warn + return 3, False + + finally: + SESSION.close() + + +def num_warns(): + try: + return SESSION.query(func.sum(Warns.num_warns)).scalar() or 0 + finally: + SESSION.close() + + +def num_warn_chats(): + try: + return SESSION.query(func.count(distinct(Warns.chat_id))).scalar() + finally: + SESSION.close() + + +def num_warn_filters(): + try: + return SESSION.query(WarnFilters).count() + finally: + SESSION.close() + + +def num_warn_chat_filters(chat_id): + try: + return ( + SESSION.query(WarnFilters.chat_id) + .filter(WarnFilters.chat_id == str(chat_id)) + .count() + ) + finally: + SESSION.close() + + +def num_warn_filter_chats(): + try: + return SESSION.query(func.count(distinct(WarnFilters.chat_id))).scalar() + finally: + SESSION.close() + + +def __load_chat_warn_filters(): + global WARN_FILTERS + try: + chats = SESSION.query(WarnFilters.chat_id).distinct().all() + for (chat_id,) in chats: # remove tuple by ( ,) + WARN_FILTERS[chat_id] = [] + + all_filters = SESSION.query(WarnFilters).all() + for x in all_filters: + WARN_FILTERS[x.chat_id] += [x.keyword] + + WARN_FILTERS = { + x: sorted(set(y), key=lambda i: (-len(i), i)) + for x, y in WARN_FILTERS.items() + } + + finally: + SESSION.close() + + +def migrate_chat(old_chat_id, new_chat_id): + with WARN_INSERTION_LOCK: + chat_notes = ( + SESSION.query(Warns).filter(Warns.chat_id == str(old_chat_id)).all() + ) + for note in chat_notes: + note.chat_id = str(new_chat_id) + SESSION.commit() + + with WARN_FILTER_INSERTION_LOCK: + chat_filters = ( + SESSION.query(WarnFilters) + .filter(WarnFilters.chat_id == str(old_chat_id)) + .all() + ) + for filt in chat_filters: + filt.chat_id = str(new_chat_id) + SESSION.commit() + old_warn_filt = WARN_FILTERS.get(str(old_chat_id)) + if old_warn_filt is not None: + WARN_FILTERS[str(new_chat_id)] = old_warn_filt + del WARN_FILTERS[str(old_chat_id)] + + with WARN_SETTINGS_LOCK: + chat_settings = ( + SESSION.query(WarnSettings) + .filter(WarnSettings.chat_id == str(old_chat_id)) + .all() + ) + for setting in chat_settings: + setting.chat_id = str(new_chat_id) + SESSION.commit() + + +__load_chat_warn_filters() diff --git a/Exon/modules/sql/welcome_sql.py b/Exon/modules/sql/welcome_sql.py new file mode 100644 index 00000000..93c64371 --- /dev/null +++ b/Exon/modules/sql/welcome_sql.py @@ -0,0 +1,508 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + + +import random +import threading +from typing import Union + +from sqlalchemy import BigInteger, Boolean, Column, Integer, String, UnicodeText + +from Exon.modules.helper_funcs.msg_types import Types +from Exon.modules.sql import BASE, SESSION + +DEFAULT_WELCOME = "ʜᴇʏ {first}, ʜᴏᴡ ᴀʀᴇ ʏᴏᴜ?" +DEFAULT_GOODBYE = "ɴɪᴄᴇ ᴋɴᴏᴡɪɴɢ ʏᴀ!" + +DEFAULT_WELCOME_MESSAGES = [ + "{first} ɪs ʜᴇʀᴇ!", # Discord welcome messages copied + "ʀᴇᴀᴅʏ ᴘʟᴀʏᴇʀ {first}", + "ᴡᴇʟᴄᴏᴍᴇ ʙʀᴏ {first}", +] + +DEFAULT_GOODBYE_MESSAGES = [ + "{first} ᴡɪʟʟ ʙᴇ ᴍɪssᴇᴅ.", + "{first} ᴡʜᴇɴ ʙᴀᴄᴋ ?.", +] + + +class Welcome(BASE): + __tablename__ = "welcome_pref" + chat_id = Column(String(14), primary_key=True) + should_welcome = Column(Boolean, default=True) + should_goodbye = Column(Boolean, default=True) + custom_content = Column(UnicodeText, default=None) + + custom_welcome = Column( + UnicodeText, default=random.choice(DEFAULT_WELCOME_MESSAGES) + ) + welcome_type = Column(Integer, default=Types.TEXT.value) + + custom_leave = Column(UnicodeText, default=random.choice(DEFAULT_GOODBYE_MESSAGES)) + leave_type = Column(Integer, default=Types.TEXT.value) + + clean_welcome = Column(BigInteger) + + def __init__(self, chat_id, should_welcome=True, should_goodbye=True): + self.chat_id = chat_id + self.should_welcome = should_welcome + self.should_goodbye = should_goodbye + + def __repr__(self): + return "<ᴄʜᴀᴛ {} sʜᴏᴜʟᴅ sʜᴏᴜʟᴅ ɴᴇᴡ ᴜsᴇʀs: {}>".format( + self.chat_id, self.should_welcome + ) + + +class WelcomeButtons(BASE): + __tablename__ = "welcome_urls" + id = Column(Integer, primary_key=True, autoincrement=True) + chat_id = Column(String(14), primary_key=True) + name = Column(UnicodeText, nullable=False) + url = Column(UnicodeText, nullable=False) + same_line = Column(Boolean, default=False) + + def __init__(self, chat_id, name, url, same_line=False): + self.chat_id = str(chat_id) + self.name = name + self.url = url + self.same_line = same_line + + +class GoodbyeButtons(BASE): + __tablename__ = "leave_urls" + id = Column(Integer, primary_key=True, autoincrement=True) + chat_id = Column(String(14), primary_key=True) + name = Column(UnicodeText, nullable=False) + url = Column(UnicodeText, nullable=False) + same_line = Column(Boolean, default=False) + + def __init__(self, chat_id, name, url, same_line=False): + self.chat_id = str(chat_id) + self.name = name + self.url = url + self.same_line = same_line + + +class WelcomeMute(BASE): + __tablename__ = "welcome_mutes" + chat_id = Column(String(14), primary_key=True) + welcomemutes = Column(UnicodeText, default=False) + + def __init__(self, chat_id, welcomemutes): + self.chat_id = str(chat_id) # ensure string + self.welcomemutes = welcomemutes + + +class WelcomeMuteUsers(BASE): + __tablename__ = "human_checks" + user_id = Column(BigInteger, primary_key=True) + chat_id = Column(String(14), primary_key=True) + human_check = Column(Boolean) + + def __init__(self, user_id, chat_id, human_check): + self.user_id = user_id # ensure string + self.chat_id = str(chat_id) + self.human_check = human_check + + +class CleanServiceSetting(BASE): + __tablename__ = "clean_service" + chat_id = Column(String(14), primary_key=True) + clean_service = Column(Boolean, default=True) + + def __init__(self, chat_id): + self.chat_id = str(chat_id) + + def __repr__(self): + return "<ᴄʜᴀᴛ ᴜsᴇᴅ ᴄʟᴇᴀɴ sᴇʀᴠɪᴄᴇ ({})>".format(self.chat_id) + + +class RaidMode(BASE): + __tablename__ = "raid_mode" + chat_id = Column(String(14), primary_key=True) + status = Column(Boolean, default=False) + time = Column(Integer, default=21600) + acttime = Column(Integer, default=3600) + # permanent = Column(Boolean, default=False) + + def __init__(self, chat_id, status, time, acttime): + self.chat_id = str(chat_id) + self.status = status + self.time = time + self.acttime = acttime + # self.permanent = permanent + + +Welcome.__table__.create(checkfirst=True) +WelcomeButtons.__table__.create(checkfirst=True) +GoodbyeButtons.__table__.create(checkfirst=True) +WelcomeMute.__table__.create(checkfirst=True) +WelcomeMuteUsers.__table__.create(checkfirst=True) +CleanServiceSetting.__table__.create(checkfirst=True) +RaidMode.__table__.create(checkfirst=True) + +INSERTION_LOCK = threading.RLock() +WELC_BTN_LOCK = threading.RLock() +LEAVE_BTN_LOCK = threading.RLock() +WM_LOCK = threading.RLock() +CS_LOCK = threading.RLock() +RAID_LOCK = threading.RLock() + + +def welcome_mutes(chat_id): + try: + welcomemutes = SESSION.query(WelcomeMute).get(str(chat_id)) + if welcomemutes: + return welcomemutes.welcomemutes + return False + finally: + SESSION.close() + + +def set_welcome_mutes(chat_id, welcomemutes): + with WM_LOCK: + prev = SESSION.query(WelcomeMute).get((str(chat_id))) + if prev: + SESSION.delete(prev) + welcome_m = WelcomeMute(str(chat_id), welcomemutes) + SESSION.add(welcome_m) + SESSION.commit() + + +def set_human_checks(user_id, chat_id): + with INSERTION_LOCK: + human_check = SESSION.query(WelcomeMuteUsers).get((user_id, str(chat_id))) + if not human_check: + human_check = WelcomeMuteUsers(user_id, str(chat_id), True) + + else: + human_check.human_check = True + + SESSION.add(human_check) + SESSION.commit() + + return human_check + + +def get_human_checks(user_id, chat_id): + try: + human_check = SESSION.query(WelcomeMuteUsers).get((user_id, str(chat_id))) + if not human_check: + return None + human_check = human_check.human_check + return human_check + finally: + SESSION.close() + + +def get_welc_mutes_pref(chat_id): + welcomemutes = SESSION.query(WelcomeMute).get(str(chat_id)) + SESSION.close() + + if welcomemutes: + return welcomemutes.welcomemutes + + return False + + +def get_welc_pref(chat_id): + welc = SESSION.query(Welcome).get(str(chat_id)) + SESSION.close() + if welc: + return ( + welc.should_welcome, + welc.custom_welcome, + welc.custom_content, + welc.welcome_type, + ) + + else: + # Welcome by default. + return True, DEFAULT_WELCOME, None, Types.TEXT + + +def get_gdbye_pref(chat_id): + welc = SESSION.query(Welcome).get(str(chat_id)) + SESSION.close() + if welc: + return welc.should_goodbye, welc.custom_leave, welc.leave_type + else: + # Welcome by default. + return True, DEFAULT_GOODBYE, Types.TEXT + + +def set_clean_welcome(chat_id, clean_welcome): + with INSERTION_LOCK: + curr = SESSION.query(Welcome).get(str(chat_id)) + if not curr: + curr = Welcome(str(chat_id)) + + curr.clean_welcome = int(clean_welcome) + + SESSION.add(curr) + SESSION.commit() + + +def get_clean_pref(chat_id): + welc = SESSION.query(Welcome).get(str(chat_id)) + SESSION.close() + + if welc: + return welc.clean_welcome + + return False + + +def set_welc_preference(chat_id, should_welcome): + with INSERTION_LOCK: + curr = SESSION.query(Welcome).get(str(chat_id)) + if not curr: + curr = Welcome(str(chat_id), should_welcome=should_welcome) + else: + curr.should_welcome = should_welcome + + SESSION.add(curr) + SESSION.commit() + + +def set_gdbye_preference(chat_id, should_goodbye): + with INSERTION_LOCK: + curr = SESSION.query(Welcome).get(str(chat_id)) + if not curr: + curr = Welcome(str(chat_id), should_goodbye=should_goodbye) + else: + curr.should_goodbye = should_goodbye + + SESSION.add(curr) + SESSION.commit() + + +def set_custom_welcome( + chat_id, custom_content, custom_welcome, welcome_type, buttons=None +): + if buttons is None: + buttons = [] + + with INSERTION_LOCK: + welcome_settings = SESSION.query(Welcome).get(str(chat_id)) + if not welcome_settings: + welcome_settings = Welcome(str(chat_id), True) + + if custom_welcome or custom_content: + welcome_settings.custom_content = custom_content + welcome_settings.custom_welcome = custom_welcome + welcome_settings.welcome_type = welcome_type.value + + else: + welcome_settings.custom_welcome = DEFAULT_WELCOME + welcome_settings.welcome_type = Types.TEXT.value + + SESSION.add(welcome_settings) + + with WELC_BTN_LOCK: + prev_buttons = ( + SESSION.query(WelcomeButtons) + .filter(WelcomeButtons.chat_id == str(chat_id)) + .all() + ) + for btn in prev_buttons: + SESSION.delete(btn) + + for b_name, url, same_line in buttons: + button = WelcomeButtons(chat_id, b_name, url, same_line) + SESSION.add(button) + + SESSION.commit() + + +def get_custom_welcome(chat_id): + welcome_settings = SESSION.query(Welcome).get(str(chat_id)) + ret = DEFAULT_WELCOME + if welcome_settings and welcome_settings.custom_welcome: + ret = welcome_settings.custom_welcome + + SESSION.close() + return ret + + +def set_custom_gdbye(chat_id, custom_goodbye, goodbye_type, buttons=None): + if buttons is None: + buttons = [] + + with INSERTION_LOCK: + welcome_settings = SESSION.query(Welcome).get(str(chat_id)) + if not welcome_settings: + welcome_settings = Welcome(str(chat_id), True) + + if custom_goodbye: + welcome_settings.custom_leave = custom_goodbye + welcome_settings.leave_type = goodbye_type.value + + else: + welcome_settings.custom_leave = DEFAULT_GOODBYE + welcome_settings.leave_type = Types.TEXT.value + + SESSION.add(welcome_settings) + + with LEAVE_BTN_LOCK: + prev_buttons = ( + SESSION.query(GoodbyeButtons) + .filter(GoodbyeButtons.chat_id == str(chat_id)) + .all() + ) + for btn in prev_buttons: + SESSION.delete(btn) + + for b_name, url, same_line in buttons: + button = GoodbyeButtons(chat_id, b_name, url, same_line) + SESSION.add(button) + + SESSION.commit() + + +def get_custom_gdbye(chat_id): + welcome_settings = SESSION.query(Welcome).get(str(chat_id)) + ret = DEFAULT_GOODBYE + if welcome_settings and welcome_settings.custom_leave: + ret = welcome_settings.custom_leave + + SESSION.close() + return ret + + +def get_welc_buttons(chat_id): + try: + return ( + SESSION.query(WelcomeButtons) + .filter(WelcomeButtons.chat_id == str(chat_id)) + .order_by(WelcomeButtons.id) + .all() + ) + finally: + SESSION.close() + + +def get_gdbye_buttons(chat_id): + try: + return ( + SESSION.query(GoodbyeButtons) + .filter(GoodbyeButtons.chat_id == str(chat_id)) + .order_by(GoodbyeButtons.id) + .all() + ) + finally: + SESSION.close() + + +def clean_service(chat_id: Union[str, int]) -> bool: + try: + chat_setting = SESSION.query(CleanServiceSetting).get(str(chat_id)) + if chat_setting: + return chat_setting.clean_service + return False + finally: + SESSION.close() + + +def set_clean_service(chat_id: Union[int, str], setting: bool): + with CS_LOCK: + chat_setting = SESSION.query(CleanServiceSetting).get(str(chat_id)) + if not chat_setting: + chat_setting = CleanServiceSetting(chat_id) + + chat_setting.clean_service = setting + SESSION.add(chat_setting) + SESSION.commit() + + +def migrate_chat(old_chat_id, new_chat_id): + with INSERTION_LOCK: + chat = SESSION.query(Welcome).get(str(old_chat_id)) + if chat: + chat.chat_id = str(new_chat_id) + + with WELC_BTN_LOCK: + chat_buttons = ( + SESSION.query(WelcomeButtons) + .filter(WelcomeButtons.chat_id == str(old_chat_id)) + .all() + ) + for btn in chat_buttons: + btn.chat_id = str(new_chat_id) + + with LEAVE_BTN_LOCK: + chat_buttons = ( + SESSION.query(GoodbyeButtons) + .filter(GoodbyeButtons.chat_id == str(old_chat_id)) + .all() + ) + for btn in chat_buttons: + btn.chat_id = str(new_chat_id) + + SESSION.commit() + + +def getRaidStatus(chat_id): + try: + if stat := SESSION.query(RaidMode).get(str(chat_id)): + return stat.status, stat.time, stat.acttime + return False, 21600, 3600 # default + finally: + SESSION.close() + + +def setRaidStatus(chat_id, status, time=21600, acttime=3600): + with RAID_LOCK: + if prevObj := SESSION.query(RaidMode).get(str(chat_id)): + SESSION.delete(prevObj) + newObj = RaidMode(str(chat_id), status, time, acttime) + SESSION.add(newObj) + SESSION.commit() + + +def toggleRaidStatus(chat_id): + newObj = True + with RAID_LOCK: + prevObj = SESSION.query(RaidMode).get(str(chat_id)) + if prevObj: + newObj = not prevObj.status + stat = RaidMode( + str(chat_id), newObj, prevObj.time or 21600, prevObj.acttime or 3600 + ) + SESSION.add(stat) + SESSION.commit() + return newObj + + +def _ResetRaidOnRestart(): + with RAID_LOCK: + raid = SESSION.query(RaidMode).all() + for r in raid: + r.status = False + SESSION.commit() + + +# it uses a cron job to turn off so if the bot restarts and there is a pending raid disable job then raid will stay on +_ResetRaidOnRestart() diff --git a/Exon/modules/stickers.py b/Exon/modules/stickers.py new file mode 100644 index 00000000..223394c3 --- /dev/null +++ b/Exon/modules/stickers.py @@ -0,0 +1,971 @@ +""" +MIT License + +Copyright (c) 2022 ABISHNOI + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import math +import os +import textwrap +import urllib.request as urllib +from html import escape +from urllib.parse import quote as urlquote + +from bs4 import BeautifulSoup +from cloudscraper import CloudScraper +from PIL import Image, ImageDraw, ImageFont +from telegram import ( + InlineKeyboardButton, + InlineKeyboardMarkup, + ParseMode, + TelegramError, + Update, +) +from telegram.ext import CallbackContext, CallbackQueryHandler +from telegram.utils.helpers import mention_html + +from ABG.covert import convert_gif +from Exon import dispatcher +from Exon import telethn as bot +from Exon.events import register as asau +from Exon.modules.disable import DisableAbleCommandHandler + +combot_stickers_url = "https://combot.org/telegram/stickers?q=" + + +def stickerid(update: Update, context: CallbackContext): + msg = update.effective_message + if msg.reply_to_message and msg.reply_to_message.sticker: + update.effective_message.reply_text( + "Hello " + + f"{mention_html(msg.from_user.id, msg.from_user.first_name)}" + + ", The sticker id you are replying is:\n<code>" + + escape(msg.reply_to_message.sticker.file_id) + + "</code>", + parse_mode=ParseMode.HTML, + ) + else: + update.effective_message.reply_text( + "Hello " + + f"{mention_html(msg.from_user.id, msg.from_user.first_name)}" + + ", Please reply to sticker message to get id sticker", + parse_mode=ParseMode.HTML, + ) + + +scraper = CloudScraper() + + +def get_cbs_data(query, page, user_id): + # returns (text, buttons) + text = scraper.get(f"{combot_stickers_url}{urlquote(query)}&page={page}").text + soup = BeautifulSoup(text, "lxml") + div = soup.find("div", class_="page__container") + packs = div.find_all("a", class_="sticker-pack__btn") + titles = div.find_all("div", "sticker-pack__title") + has_prev_page = has_next_page = None + highlighted_page = div.find("a", class_="pagination__link is-active") + if highlighted_page is not None and user_id is not None: + highlighted_page = highlighted_page.parent + has_prev_page = highlighted_page.previous_sibling.previous_sibling is not None + has_next_page = highlighted_page.next_sibling.next_sibling is not None + buttons = [] + if has_prev_page: + buttons.append( + InlineKeyboardButton(text="⟨", callback_data=f"cbs_{page - 1}_{user_id}") + ) + if has_next_page: + buttons.append( + InlineKeyboardButton(text="⟩", callback_data=f"cbs_{page + 1}_{user_id}") + ) + buttons = InlineKeyboardMarkup([buttons]) if buttons else None + text = f"Stickers for <code>{escape(query)}</code>:\nPage: {page}" + if packs and titles: + for pack, title in zip(packs, titles): + link = pack["href"] + text += f"\n• <a href='{link}'>{escape(title.get_text())}</a>" + elif page == 1: + text = "No results found, try a different term" + else: + text += "\n\nInterestingly, there's nothing here." + return text, buttons + + +def cb_sticker(update: Update, context: CallbackContext): + msg = update.effective_message + query = " ".join(msg.text.split()[1:]) + if not query: + msg.reply_text("Provide some term to search for a sticker pack.") + return + if len(query) > 50: + msg.reply_text("Provide a search query under 50 characters") + return + if msg.from_user: + user_id = msg.from_user.id + else: + user_id = None + text, buttons = get_cbs_data(query, 1, user_id) + msg.reply_text(text, parse_mode=ParseMode.HTML, reply_markup=buttons) + + +def cbs_callback(update: Update, context: CallbackContext): + query = update.callback_query + _, page, user_id = query.data.split("_", 2) + if int(user_id) != query.from_user.id: + query.answer("Not for you", cache_time=60 * 60) + return + search_query = query.message.text.split("\n", 1)[0].split(maxsplit=2)[2][:-1] + text, buttons = get_cbs_data(search_query, int(page), query.from_user.id) + query.edit_message_text(text, parse_mode=ParseMode.HTML, reply_markup=buttons) + query.answer() + + +def getsticker(update: Update, context: CallbackContext): + bot = context.bot + msg = update.effective_message + chat_id = update.effective_chat.id + if msg.reply_to_message and msg.reply_to_message.sticker: + file_id = msg.reply_to_message.sticker.file_id + with BytesIO() as file: + file.name = "sticker.png" + new_file = bot.get_file(file_id) + new_file.download(out=file) + file.seek(0) + bot.send_document(chat_id, document=file) + else: + update.effective_message.reply_text( + "Please reply to a sticker for me to upload its PNG.", + ) + + +def kang(update, context): + msg = update.effective_message + user = update.effective_user + args = context.args + packnum = 0 + packname = "a" + str(user.id) + "_by_" + context.bot.username + packname_found = 0 + max_stickers = 120 + + while packname_found == 0: + try: + stickerset = context.bot.get_sticker_set(packname) + if len(stickerset.stickers) >= max_stickers: + packnum += 1 + packname = ( + "a" + + str(packnum) + + "_" + + str(user.id) + + "_by_" + + context.bot.username + ) + else: + packname_found = 1 + except TelegramError as e: + if e.message == "Stickerset_invalid": + packname_found = 1 + + kangsticker = "kangsticker.png" + is_animated = False + is_video = False + # convert gif method + is_gif = False + file_id = "" + + if msg.reply_to_message: + if msg.reply_to_message.sticker: + if msg.reply_to_message.sticker.is_animated: + is_animated = True + elif msg.reply_to_message.sticker.is_video: + is_video = True + file_id = msg.reply_to_message.sticker.file_id + elif msg.reply_to_message.photo: + file_id = msg.reply_to_message.photo[-1].file_id + elif ( + msg.reply_to_message.document + and not msg.reply_to_message.document.mime_type == "video/mp4" + ): + file_id = msg.reply_to_message.document.file_id + elif msg.reply_to_message.animation: + file_id = msg.reply_to_message.animation.file_id + is_gif = True + else: + msg.reply_text("Yea, I can't kang that.") + kang_file = context.bot.get_file(file_id) + if not is_animated and not (is_video or is_gif): + kang_file.download("kangsticker.png") + elif is_animated: + kang_file.download("kangsticker.tgs") + elif is_video and not is_gif: + kang_file.download("kangsticker.webm") + elif is_gif: + kang_file.download("kang.mp4") + convert_gif("kang.mp4") + + if args: + sticker_emoji = str(args[0]) + elif msg.reply_to_message.sticker and msg.reply_to_message.sticker.emoji: + sticker_emoji = msg.reply_to_message.sticker.emoji + else: + sticker_emoji = "🙂" + + adding_process = msg.reply_text( + "<b>Please wait...For a Moment</b>", + parse_mode=ParseMode.HTML, + ) + + if not is_animated and not (is_video or is_gif): + try: + im = Image.open(kangsticker) + maxsize = (512, 512) + if (im.width and im.height) < 512: + size1 = im.width + size2 = im.height + if im.width > im.height: + scale = 512 / size1 + size1new = 512 + size2new = size2 * scale + else: + scale = 512 / size2 + size1new = size1 * scale + size2new = 512 + size1new = math.floor(size1new) + size2new = math.floor(size2new) + sizenew = (size1new, size2new) + im = im.resize(sizenew) + else: + im.thumbnail(maxsize) + if not msg.reply_to_message.sticker: + im.save(kangsticker, "PNG") + context.bot.add_sticker_to_set( + user_id=user.id, + name=packname, + png_sticker=open("kangsticker.png", "rb"), + emojis=sticker_emoji, + ) + edited_keyboard = InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + text="View Pack", url=f"t.me/addstickers/{packname}" + ) + ] + ] + ) + adding_process.edit_text( + f"<b>ʏᴏᴜʀ ꜱᴛɪᴄᴋᴇʀ ʜᴀꜱ ʙᴇᴇɴ ᴀᴅᴅᴇᴅ! \nғᴏʀ ғᴀꜱᴛ ᴜᴘᴅᴀᴛᴇ .ʀᴇᴍᴏᴠᴇ ʏᴏᴜʀ ᴘᴀᴄᴋ.& ᴀᴅᴅ ᴀɢᴀɪɴ </b>" + f"\nᴇᴍᴏᴊɪ ɪꜱ: {sticker_emoji}", + reply_markup=edited_keyboard, + parse_mode=ParseMode.HTML, + ) + + except OSError as e: + + print(e) + return + + except TelegramError as e: + if e.message == "Stickerset_invalid": + makepack_internal( + update, + context, + msg, + user, + sticker_emoji, + packname, + packnum, + png_sticker=open("kangsticker.png", "rb"), + ) + adding_process.delete() + elif e.message == "Sticker_png_dimensions": + im.save(kangsticker, "PNG") + adding_process = msg.reply_text( + "<b>Please wait...For a Moment</b>", + parse_mode=ParseMode.HTML, + ) + context.bot.add_sticker_to_set( + user_id=user.id, + name=packname, + png_sticker=open("kangsticker.png", "rb"), + emojis=sticker_emoji, + ) + edited_keyboard = InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + text="View Pack", url=f"t.me/addstickers/{packname}" + ) + ] + ] + ) + adding_process.edit_text( + f"<b>ʏᴏᴜʀ ꜱᴛɪᴄᴋᴇʀ ʜᴀꜱ ʙᴇᴇɴ ᴀᴅᴅᴇᴅ! \nғᴏʀ ғᴀꜱᴛ ᴜᴘᴅᴀᴛᴇ .ʀᴇᴍᴏᴠᴇ ʏᴏᴜʀ ᴘᴀᴄᴋ.& ᴀᴅᴅ ᴀɢᴀɪɴ </b>" + f"\nᴇᴍᴏᴊɪ ɪꜱ: {sticker_emoji}", + reply_markup=edited_keyboard, + parse_mode=ParseMode.HTML, + ) + elif e.message == "Invalid sticker emojis": + msg.reply_text("Invalid emoji(s).") + elif e.message == "Stickers_too_much": + msg.reply_text("Max packsize reached. Press F to pay respecc.") + elif e.message == "Internal Server Error: sticker set not found (500)": + edited_keyboard = InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + text="View Pack", url=f"t.me/addstickers/{packname}" + ) + ] + ] + ) + msg.reply_text( + f"<b>ʏᴏᴜʀ ꜱᴛɪᴄᴋᴇʀ ʜᴀꜱ ʙᴇᴇɴ ᴀᴅᴅᴇᴅ! \nғᴏʀ ғᴀꜱᴛ ᴜᴘᴅᴀᴛᴇ .ʀᴇᴍᴏᴠᴇ ʏᴏᴜʀ ᴘᴀᴄᴋ.& ᴀᴅᴅ ᴀɢᴀɪɴ </b>" + f"\nᴇᴍᴏᴊɪ ɪꜱ: {sticker_emoji}", + reply_markup=edited_keyboard, + parse_mode=ParseMode.HTML, + ) + print(e) + + elif is_animated: + packname = "animated" + str(user.id) + "_by_" + context.bot.username + packname_found = 0 + max_stickers = 50 + while packname_found == 0: + try: + stickerset = context.bot.get_sticker_set(packname) + if len(stickerset.stickers) >= max_stickers: + packnum += 1 + packname = ( + "animated" + + str(packnum) + + "_" + + str(user.id) + + "_by_" + + context.bot.username + ) + else: + packname_found = 1 + except TelegramError as e: + if e.message == "Stickerset_invalid": + packname_found = 1 + try: + context.bot.add_sticker_to_set( + user_id=user.id, + name=packname, + tgs_sticker=open("kangsticker.tgs", "rb"), + emojis=sticker_emoji, + ) + edited_keyboard = InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + text="View Pack", url=f"t.me/addstickers/{packname}" + ) + ] + ] + ) + adding_process.edit_text( + f"<b>ʏᴏᴜʀ ꜱᴛɪᴄᴋᴇʀ ʜᴀꜱ ʙᴇᴇɴ ᴀᴅᴅᴇᴅ! \nғᴏʀ ғᴀꜱᴛ ᴜᴘᴅᴀᴛᴇ .ʀᴇᴍᴏᴠᴇ ʏᴏᴜʀ ᴘᴀᴄᴋ.& ᴀᴅᴅ ᴀɢᴀɪɴ </b>" + f"\nᴇᴍᴏᴊɪ ɪꜱ: {sticker_emoji}", + reply_markup=edited_keyboard, + parse_mode=ParseMode.HTML, + ) + except TelegramError as e: + if e.message == "Stickerset_invalid": + makepack_internal( + update, + context, + msg, + user, + sticker_emoji, + packname, + packnum, + tgs_sticker=open("kangsticker.tgs", "rb"), + ) + adding_process.delete() + elif e.message == "Invalid sticker emojis": + msg.reply_text("Invalid emoji(s).") + elif e.message == "Internal Server Error: sticker set not found (500)": + edited_keyboard = InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + text="View Pack", url=f"t.me/addstickers/{packname}" + ) + ] + ] + ) + adding_process.edit_text( + f"<b>ʏᴏᴜʀ ꜱᴛɪᴄᴋᴇʀ ʜᴀꜱ ʙᴇᴇɴ ᴀᴅᴅᴇᴅ! \nғᴏʀ ғᴀꜱᴛ ᴜᴘᴅᴀᴛᴇ .ʀᴇᴍᴏᴠᴇ ʏᴏᴜʀ ᴘᴀᴄᴋ.& ᴀᴅᴅ ᴀɢᴀɪɴ </b>" + f"\nᴇᴍᴏᴊɪ ɪꜱ: {sticker_emoji}", + reply_markup=edited_keyboard, + parse_mode=ParseMode.HTML, + ) + print(e) + + elif is_video or is_gif: + packname = "video" + str(user.id) + "_by_" + context.bot.username + packname_found = 0 + max_stickers = 50 + while packname_found == 0: + try: + stickerset = context.bot.get_sticker_set(packname) + if len(stickerset.stickers) >= max_stickers: + packnum += 1 + packname = ( + "video" + + str(packnum) + + "_" + + str(user.id) + + "_by_" + + context.bot.username + ) + else: + packname_found = 1 + except TelegramError as e: + if e.message == "Stickerset_invalid": + packname_found = 1 + try: + context.bot.add_sticker_to_set( + user_id=user.id, + name=packname, + webm_sticker=open("kangsticker.webm", "rb"), + emojis=sticker_emoji, + ) + edited_keyboard = InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + text="View Pack", url=f"t.me/addstickers/{packname}" + ) + ] + ] + ) + adding_process.edit_text( + f"<b>ʏᴏᴜʀ ꜱᴛɪᴄᴋᴇʀ ʜᴀꜱ ʙᴇᴇɴ ᴀᴅᴅᴇᴅ! \nғᴏʀ ғᴀꜱᴛ ᴜᴘᴅᴀᴛᴇ .ʀᴇᴍᴏᴠᴇ ʏᴏᴜʀ ᴘᴀᴄᴋ.& ᴀᴅᴅ ᴀɢᴀɪɴ </b>" + f"\nᴇᴍᴏᴊɪ ɪꜱ: {sticker_emoji}", + reply_markup=edited_keyboard, + parse_mode=ParseMode.HTML, + ) + except TelegramError as e: + if e.message == "Stickerset_invalid": + makepack_internal( + update, + context, + msg, + user, + sticker_emoji, + packname, + packnum, + webm_sticker=open("kangsticker.webm", "rb"), + ) + adding_process.delete() + elif e.message == "Invalid sticker emojis": + msg.reply_text("Invalid emoji(s).") + elif e.message == "Internal Server Error: sticker set not found (500)": + edited_keyboard = InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + text="View Pack", url=f"t.me/addstickers/{packname}" + ) + ] + ] + ) + adding_process.edit_text( + f"<b>ʏᴏᴜʀ ꜱᴛɪᴄᴋᴇʀ ʜᴀꜱ ʙᴇᴇɴ ᴀᴅᴅᴇᴅ! \nғᴏʀ ғᴀꜱᴛ ᴜᴘᴅᴀᴛᴇ .ʀᴇᴍᴏᴠᴇ ʏᴏᴜʀ ᴘᴀᴄᴋ.& ᴀᴅᴅ ᴀɢᴀɪɴ </b>" + f"\nᴇᴍᴏᴊɪ ɪꜱ: {sticker_emoji}", + reply_markup=edited_keyboard, + parse_mode=ParseMode.HTML, + ) + print(e) + + elif args: + try: + try: + urlemoji = msg.text.split(" ") + png_sticker = urlemoji[1] + sticker_emoji = urlemoji[2] + except IndexError: + sticker_emoji = "🙃" + urllib.urlretrieve(png_sticker, kangsticker) + im = Image.open(kangsticker) + maxsize = (512, 512) + if (im.width and im.height) < 512: + size1 = im.width + size2 = im.height + if im.width > im.height: + scale = 512 / size1 + size1new = 512 + size2new = size2 * scale + else: + scale = 512 / size2 + size1new = size1 * scale + size2new = 512 + size1new = math.floor(size1new) + size2new = math.floor(size2new) + sizenew = (size1new, size2new) + im = im.resize(sizenew) + else: + im.thumbnail(maxsize) + im.save(kangsticker, "PNG") + msg.reply_photo(photo=open("kangsticker.png", "rb")) + context.bot.add_sticker_to_set( + user_id=user.id, + name=packname, + png_sticker=open("kangsticker.png", "rb"), + emojis=sticker_emoji, + ) + edited_keyboard = InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + text="View Pack", url=f"t.me/addstickers/{packname}" + ) + ] + ] + ) + adding_process.edit_text( + f"<b>ʏᴏᴜʀ ꜱᴛɪᴄᴋᴇʀ ʜᴀꜱ ʙᴇᴇɴ ᴀᴅᴅᴇᴅ! \nғᴏʀ ғᴀꜱᴛ ᴜᴘᴅᴀᴛᴇ .ʀᴇᴍᴏᴠᴇ ʏᴏᴜʀ ᴘᴀᴄᴋ.& ᴀᴅᴅ ᴀɢᴀɪɴ </b>" + f"\nᴇᴍᴏᴊɪ ɪꜱ: {sticker_emoji}", + reply_markup=edited_keyboard, + parse_mode=ParseMode.HTML, + ) + except OSError as e: + msg.reply_text("I can only kang images m8.") + print(e) + return + except TelegramError as e: + if e.message == "Stickerset_invalid": + makepack_internal( + update, + context, + msg, + user, + sticker_emoji, + packname, + packnum, + png_sticker=open("kangsticker.png", "rb"), + ) + adding_process.delete() + elif e.message == "Sticker_png_dimensions": + im.save(kangsticker, "png") + context.bot.add_sticker_to_set( + user_id=user.id, + name=packname, + png_sticker=open("kangsticker.png", "rb"), + emojis=sticker_emoji, + ) + edited_keyboard = InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + text="View Pack", url=f"t.me/addstickers/{packname}" + ) + ] + ] + ) + adding_process.edit_text( + f"<b>ʏᴏᴜʀ ꜱᴛɪᴄᴋᴇʀ ʜᴀꜱ ʙᴇᴇɴ ᴀᴅᴅᴇᴅ! \nғᴏʀ ғᴀꜱᴛ ᴜᴘᴅᴀᴛᴇ .ʀᴇᴍᴏᴠᴇ ʏᴏᴜʀ ᴘᴀᴄᴋ.& ᴀᴅᴅ ᴀɢᴀɪɴ </b>" + f"\nᴇᴍᴏᴊɪ ɪꜱ: {sticker_emoji}", + reply_markup=edited_keyboard, + parse_mode=ParseMode.HTML, + ) + elif e.message == "Invalid sticker emojis": + msg.reply_text("Invalid emoji(s).") + elif e.message == "Stickers_too_much": + msg.reply_text("Max packsize reached. Press F to pay respect.") + elif e.message == "Internal Server Error: sticker set not found (500)": + msg.reply_text( + f"<b>ʏᴏᴜʀ ꜱᴛɪᴄᴋᴇʀ ʜᴀꜱ ʙᴇᴇɴ ᴀᴅᴅᴇᴅ! \nғᴏʀ ғᴀꜱᴛ ᴜᴘᴅᴀᴛᴇ .ʀᴇᴍᴏᴠᴇ ʏᴏᴜʀ ᴘᴀᴄᴋ.& ᴀᴅᴅ ᴀɢᴀɪɴ </b>" + f"\nᴇᴍᴏᴊɪ ɪꜱ: {sticker_emoji}", + reply_markup=edited_keyboard, + parse_mode=ParseMode.HTML, + ) + print(e) + else: + packs_text = "*Please reply to a sticker, or image to kang it!*\n" + if packnum > 0: + firstpackname = "a" + str(user.id) + "_by_" + context.bot.username + for i in range(0, packnum + 1): + if i == 0: + packs = f"t.me/addstickers/{firstpackname}" + else: + packs = f"t.me/addstickers/{packname}" + else: + packs = f"t.me/addstickers/{packname}" + + edited_keyboard = InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton(text="Sticker Pack", url=f"{packs}"), + ], + ] + ) + msg.reply_text( + packs_text, reply_markup=edited_keyboard, parse_mode=ParseMode.MARKDOWN + ) + try: + if os.path.isfile("kangsticker.png"): + os.remove("kangsticker.png") + elif os.path.isfile("kangsticker.tgs"): + os.remove("kangsticker.tgs") + elif os.path.isfile("kangsticker.webm"): + os.remove("kangsticker.webm") + elif os.path.isfile("kang.mp4"): + os.remove("kang.mp4") + except: + pass + + +def makepack_internal( + update, + context, + msg, + user, + emoji, + packname, + packnum, + png_sticker=None, + tgs_sticker=None, + webm_sticker=None, +): + name = user.first_name + name = name[:50] + keyboard = InlineKeyboardMarkup( + [[InlineKeyboardButton(text="View Pack", url=f"t.me/addstickers/{packname}")]] + ) + try: + extra_version = "" + if packnum > 0: + extra_version = " " + str(packnum) + if png_sticker: + sticker_pack_name = ( + f"{name}'s sticker pack (@{context.bot.username})" + extra_version + ) + success = context.bot.create_new_sticker_set( + user.id, + packname, + sticker_pack_name, + png_sticker=png_sticker, + emojis=emoji, + ) + if tgs_sticker: + sticker_pack_name = ( + f"{name}'s animated pack (@{context.bot.username})" + extra_version + ) + success = context.bot.create_new_sticker_set( + user.id, + packname, + sticker_pack_name, + tgs_sticker=tgs_sticker, + emojis=emoji, + ) + if webm_sticker: + sticker_pack_name = ( + f"{name}'s video pack (@{context.bot.username})" + extra_version + ) + success = context.bot.create_new_sticker_set( + user.id, + packname, + sticker_pack_name, + webm_sticker=webm_sticker, + emojis=emoji, + ) + + except TelegramError as e: + print(e) + if e.message == "Sticker set name is already occupied": + msg.reply_text( + "<b>Your Sticker Pack is already created!</b>" + "\n\nYou can now reply to images, stickers and animated sticker with /steal to add them to your pack" + "\n\n<b>Send /stickers to find any sticker pack.</b>", + reply_markup=keyboard, + parse_mode=ParseMode.HTML, + ) + elif e.message == "Peer_id_invalid" or "bot was blocked by the user": + msg.reply_text( + f"{context.bot.first_name} was blocked by you.", + reply_markup=InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + text="Unblock", url=f"t.me/{context.bot.username}" + ) + ] + ] + ), + ) + elif e.message == "Internal Server Error: created sticker set not found (500)": + msg.reply_text( + "<b>Your Sticker Pack has been created!</b>" + "\n\nYou can now reply to images, stickers and animated sticker with /steal to add them to your pack" + "\n\n<b>Send /stickers to find sticker pack.</b>", + reply_markup=keyboard, + parse_mode=ParseMode.HTML, + ) + return + + if success: + msg.reply_text( + "<b>Your Sticker Pack has been created!</b>" + "\n\nYou can now reply to images, stickers and animated sticker with /steal to add them to your pack" + "\n\n<b>Send /stickers to find sticker pack.</b>", + reply_markup=keyboard, + parse_mode=ParseMode.HTML, + ) + else: + msg.reply_text("Failed to create sticker pack. Possibly due to blek mejik.") + + +def getsticker(update: Update, context: CallbackContext): + bot = context.bot + msg = update.effective_message + chat_id = update.effective_chat.id + if msg.reply_to_message and msg.reply_to_message.sticker: + file_id = msg.reply_to_message.sticker.file_id + new_file = bot.get_file(file_id) + new_file.download("sticker.png") + bot.send_document(chat_id, document=open("sticker.png", "rb")) + os.remove("sticker.png") + else: + update.effective_message.reply_text( + "Please reply to a sticker for me to upload its PNG." + ) + + +def getvidsticker(update: Update, context: CallbackContext): + bot = context.bot + msg = update.effective_message + chat_id = update.effective_chat.id + if msg.reply_to_message and msg.reply_to_message.sticker: + file_id = msg.reply_to_message.sticker.file_id + new_file = bot.get_file(file_id) + new_file.download("sticker.mp4") + bot.send_video(chat_id, video=open("sticker.mp4", "rb")) + os.remove("sticker.mp4") + else: + update.effective_message.reply_text( + "Please reply to a video sticker to upload its MP4." + ) + + +def delsticker(update, context): + msg = update.effective_message + if msg.reply_to_message and msg.reply_to_message.sticker: + file_id = msg.reply_to_message.sticker.file_id + context.bot.delete_sticker_from_set(file_id) + msg.reply_text("Deleted!") + else: + update.effective_message.reply_text( + "Please reply to sticker message to del sticker" + ) + + +def video(update: Update, context: CallbackContext): + bot = context.bot + msg = update.effective_message + chat_id = update.effective_chat.id + if msg.reply_to_message and msg.reply_to_message.animation: + file_id = msg.reply_to_message.animation.file_id + new_file = bot.get_file(file_id) + new_file.download("video.mp4") + bot.send_video(chat_id, video=open("video.mp4", "rb")) + os.remove("video.mp4") + else: + update.effective_message.reply_text( + "Please reply to a gif for me to get it's video." + ) + + +@asau(pattern="^/mmf ?(.*)") +async def handler(event): + if event.fwd_from: + return + if not event.reply_to_msg_id: + await event.reply("Reply to an image or a sticker to memeify it Nigga!!") + return + reply_message = await event.get_reply_message() + if not reply_message.media: + await event.reply("Provide some Text please") + return + file = await bot.download_media(reply_message) + msg = await event.reply("Memifying this image! Please wait") + + text = str(event.pattern_match.group(1)).strip() + if len(text) < 1: + return await msg.edit("You might want to try `/mmf text`") + meme = await drawText(file, text) + await bot.send_file(event.chat_id, file=meme, force_document=False) + await msg.delete() + os.remove(meme) + + +async def drawText(image_path, text): + img = Image.open(image_path) + os.remove(image_path) + i_width, i_height = img.size + if os.name == "nt": + fnt = "ariel.ttf" + else: + fnt = "./Exon/resources/default.ttf" + m_font = ImageFont.truetype(fnt, int((70 / 640) * i_width)) + if ";" in text: + upper_text, lower_text = text.split(";") + else: + upper_text = text + lower_text = "" + draw = ImageDraw.Draw(img) + current_h, pad = 10, 5 + if upper_text: + for u_text in textwrap.wrap(upper_text, width=15): + u_width, u_height = draw.textsize(u_text, font=m_font) + draw.text( + xy=(((i_width - u_width) / 2) - 2, int((current_h / 640) * i_width)), + text=u_text, + font=m_font, + fill=(0, 0, 0), + ) + + draw.text( + xy=(((i_width - u_width) / 2) + 2, int((current_h / 640) * i_width)), + text=u_text, + font=m_font, + fill=(0, 0, 0), + ) + draw.text( + xy=((i_width - u_width) / 2, int(((current_h / 640) * i_width)) - 2), + text=u_text, + font=m_font, + fill=(0, 0, 0), + ) + + draw.text( + xy=(((i_width - u_width) / 2), int(((current_h / 640) * i_width)) + 2), + text=u_text, + font=m_font, + fill=(0, 0, 0), + ) + + draw.text( + xy=((i_width - u_width) / 2, int((current_h / 640) * i_width)), + text=u_text, + font=m_font, + fill=(255, 255, 255), + ) + + current_h += u_height + pad + + if lower_text: + for l_text in textwrap.wrap(lower_text, width=15): + u_width, u_height = draw.textsize(l_text, font=m_font) + draw.text( + xy=( + ((i_width - u_width) / 2) - 2, + i_height - u_height - int((20 / 640) * i_width), + ), + text=l_text, + font=m_font, + fill=(0, 0, 0), + ) + draw.text( + xy=( + ((i_width - u_width) / 2) + 2, + i_height - u_height - int((20 / 640) * i_width), + ), + text=l_text, + font=m_font, + fill=(0, 0, 0), + ) + draw.text( + xy=( + (i_width - u_width) / 2, + (i_height - u_height - int((20 / 640) * i_width)) - 2, + ), + text=l_text, + font=m_font, + fill=(0, 0, 0), + ) + draw.text( + xy=( + (i_width - u_width) / 2, + (i_height - u_height - int((20 / 640) * i_width)) + 2, + ), + text=l_text, + font=m_font, + fill=(0, 0, 0), + ) + + draw.text( + xy=( + (i_width - u_width) / 2, + i_height - u_height - int((20 / 640) * i_width), + ), + text=l_text, + font=m_font, + fill=(255, 255, 255), + ) + current_h += u_height + pad + image_name = "memify.webp" + webp_file = os.path.join(image_name) + img.save(webp_file, "webp") + return webp_file + + +__mod_name__ = "𝚂ᴛɪᴄᴋᴇʀ" + +__help__ = """ +*ʜᴇʟᴘ ᴍᴇɴᴜ ғᴏʀ ꜱᴛɪᴄᴋᴇʀꜱ ᴛᴏᴏʟꜱ* + +•➥ /stickerid*:* `ʀᴇᴘʟʏ ᴛᴏ ᴀ ꜱᴛɪᴄᴋᴇʀ ᴛᴏ ᴍᴇ ᴛᴏ ᴛᴇʟʟ ʏᴏᴜ ɪᴛꜱ ғɪʟᴇ ɪᴅ`. + +•➥ /getsticker*:* `ʀᴇᴘʟʏ ᴛᴏ ᴀ ꜱᴛɪᴄᴋᴇʀ ᴛᴏ ᴍᴇ ᴛᴏ ᴜᴘʟᴏᴀᴅ ɪᴛꜱ ʀᴀᴡ ᴘɴɢ ғɪʟᴇ.` + +•➥ /kang*:* `ʀᴇᴘʟʏ ᴛᴏ ᴀ ꜱᴛɪᴄᴋᴇʀ ᴛᴏ ᴀᴅᴅ ɪᴛ ᴛᴏ ʏᴏᴜʀ ᴘᴀᴄᴋ`. + +•➥ /delsticker*:* `ʀᴇᴘʟʏ ᴛᴏ ʏᴏᴜʀ ᴀɴɪᴍᴇ ᴇxɪꜱᴛ ꜱᴛɪᴄᴋᴇʀ ᴛᴏ ʏᴏᴜʀ ᴘᴀᴄᴋ ᴛᴏ ᴅᴇʟᴇᴛᴇ ɪᴛ.` + +•➥ /stickers*:* `ғɪɴᴅ ꜱᴛɪᴄᴋᴇʀꜱ ғᴏʀ ɢɪᴠᴇɴ ᴛᴇʀᴍ ᴏɴ ᴄᴏᴍʙᴏᴛ ꜱᴛɪᴄᴋᴇʀ ᴄᴀᴛᴀʟᴏɢᴜᴇ` + +•➥ /mmf <reply with text>*:* `ᴛᴏ ᴅʀᴀᴡ a ᴛᴇxᴛ ғᴏʀ ꜱᴛɪᴄᴋᴇʀ ᴏʀ ᴘᴏʜᴏᴛꜱ` +""" + + +STICKERID_HANDLER = DisableAbleCommandHandler("stickerid", stickerid, run_async=True) +GETSTICKER_HANDLER = DisableAbleCommandHandler("getsticker", getsticker, run_async=True) +GETVIDSTICKER_HANDLER = DisableAbleCommandHandler( + "getvidsticker", getvidsticker, run_async=True +) +KANG_HANDLER = DisableAbleCommandHandler("kang", kang, pass_args=True, run_async=True) +DEL_HANDLER = DisableAbleCommandHandler("delsticker", delsticker, run_async=True) +STICKERS_HANDLER = DisableAbleCommandHandler("stickers", cb_sticker, run_async=True) +VIDEO_HANDLER = DisableAbleCommandHandler("getvideo", video, run_async=True) +CBSCALLBACK_HANDLER = CallbackQueryHandler(cbs_callback, pattern="cbs_", run_async=True) + +dispatcher.add_handler(VIDEO_HANDLER) +dispatcher.add_handler(CBSCALLBACK_HANDLER) +dispatcher.add_handler(STICKERS_HANDLER) +dispatcher.add_handler(STICKERID_HANDLER) +dispatcher.add_handler(GETSTICKER_HANDLER) +dispatcher.add_handler(GETVIDSTICKER_HANDLER) +dispatcher.add_handler(KANG_HANDLER) +dispatcher.add_handler(DEL_HANDLER) diff --git a/Exon/modules/tagall.py b/Exon/modules/tagall.py new file mode 100644 index 00000000..050fcf62 --- /dev/null +++ b/Exon/modules/tagall.py @@ -0,0 +1,128 @@ +""" +MIT License +Copyright (c) 2022 Aʙɪsʜɴᴏɪ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" +# ""DEAR PRO PEOPLE, DON'T REMOVE & CHANGE THIS LINE +# TG :- @Abishnoi1M +# MY ALL BOTS :- Abishnoi_bots +# GITHUB :- KingAbishnoi "" + + +import asyncio + +from telethon import events +from telethon.errors import UserNotParticipantError +from telethon.tl.functions.channels import GetParticipantRequest +from telethon.tl.types import ChannelParticipantAdmin, ChannelParticipantCreator + +from Exon import telethn as client + +spam_chats = [] + + +@client.on(events.NewMessage(pattern="^/tagall|@all|/all ?(.*)")) +async def mentionall(event): + chat_id = event.chat_id + if event.is_private: + return await event.respond( + "__ᴛʜɪꜱ ᴄᴏᴍᴍᴀɴᴅ ᴄᴀɴ ʙᴇ ᴜꜱᴇ ɪɴ ɢʀᴏᴜᴘꜱ ᴀɴᴅ ᴄʜᴀɴɴᴇʟꜱ!__" + ) + + is_admin = False + try: + partici_ = await client(GetParticipantRequest(event.chat_id, event.sender_id)) + except UserNotParticipantError: + is_admin = False + else: + if isinstance( + partici_.participant, (ChannelParticipantAdmin, ChannelParticipantCreator) + ): + is_admin = True + if not is_admin: + return await event.reply("__ᴏɴʟʏ ᴀᴅᴍɪɴꜱ ᴄᴀɴ ᴍᴇɴᴛɪᴏɴ ᴀʟʟ!__") + + if event.pattern_match.group(1) and event.is_reply: + return await event.reply("__ɢɪᴠᴇ ᴍᴇ ᴏɴᴇ ᴀʀɢᴜᴍᴇɴᴛ!__") + elif event.pattern_match.group(1): + mode = "text_on_cmd" + msg = event.pattern_match.group(1) + elif event.is_reply: + mode = "text_on_reply" + msg = await event.get_reply_message() + if msg == None: + return await event.respond( + "__I ᴄᴀɴ'ᴛ ᴍᴇɴᴛɪᴏɴ ᴍᴇᴍʙᴇʀꜱ ғᴏʀ ᴏʟᴅᴇʀ ᴍᴇꜱꜱᴀɢᴇꜱ! (ᴍᴇꜱꜱᴀɢᴇꜱ ᴡʜɪᴄʜ ᴀʀᴇ ꜱᴇɴᴛ ʙᴇғᴏʀᴇ ɪ'ᴍ ᴀᴅᴅᴇᴅ ᴛᴏ ɢʀᴏᴜᴘ)__" + ) + else: + return await event.reply( + "__ʀᴇᴘʟʏ ᴛᴏ ᴀ ᴍᴇꜱꜱᴀɢᴇ ᴏʀ ɢɪᴠᴇ ᴍᴇ ꜱᴏᴍᴇ ᴛᴇxᴛ ᴛᴏ ᴍᴇɴᴛɪᴏɴ ᴏᴛʜᴇʀꜱ!__" + ) + + spam_chats.append(chat_id) + usrnum = 0 + usrtxt = "" + async for usr in client.iter_participants(chat_id): + if not chat_id in spam_chats: + break + usrnum += 1 + usrtxt += f"[{usr.first_name}](tg://user?id={usr.id}), " + if usrnum == 5: + if mode == "text_on_cmd": + txt = f"{msg}\n{usrtxt}" + await client.send_message(chat_id, txt) + elif mode == "text_on_reply": + await msg.reply(usrtxt) + await asyncio.sleep(2) + usrnum = 0 + usrtxt = "" + try: + spam_chats.remove(chat_id) + except: + pass + + +@client.on(events.NewMessage(pattern="^/cancel$")) +async def cancel_spam(event): + is_admin = False + try: + partici_ = await client(GetParticipantRequest(event.chat_id, event.sender_id)) + except UserNotParticipantError: + is_admin = False + else: + if isinstance( + partici_.participant, (ChannelParticipantAdmin, ChannelParticipantCreator) + ): + is_admin = True + if not is_admin: + return await event.reply("__ᴏɴʟʏ ᴀᴅᴍɪɴꜱ ᴄᴀɴ ᴇxᴇᴄᴜᴛᴇ ᴛʜɪꜱ ᴄᴏᴍᴍᴀɴᴅ!__") + if not event.chat_id in spam_chats: + return await event.reply("__ᴛʜᴇʀᴇ ɪꜱ ɴᴏ ᴘʀᴏᴄᴄᴇꜱꜱ ᴏɴ ɢᴏɪɴɢ...__") + else: + try: + spam_chats.remove(event.chat_id) + except: + pass + return await event.respond("__ꜱᴛᴏᴘᴘᴇᴅ ᴍᴇɴᴛɪᴏɴ.__") + + +__mod_name__ = "𝚃ᴀɢɢᴇʀ" +__help__ = """ +⍟ /tagall : ` ᴍᴇɴᴛɪᴏɴ ᴀʟʟ ᴍᴇᴍʙᴇʀs` + +ᴇɢ:- /tagall <ʀᴇᴘʟʏ> +""" diff --git a/Exon/modules/telegraph.py b/Exon/modules/telegraph.py new file mode 100644 index 00000000..c3fb7f37 --- /dev/null +++ b/Exon/modules/telegraph.py @@ -0,0 +1,128 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import os +from datetime import datetime + +from PIL import Image +from telegraph import Telegraph, exceptions, upload_file + +from Exon import telethn +from Exon.events import register + +TMP_DOWNLOAD_DIRECTORY = "tg-File/" +babe = "ExonRobot" # ᴅᴏɴ'ᴛ ᴇᴅɪᴛ ᴛʜɪᴀ ʟɪɴᴇ +telegraph = Telegraph() +r = telegraph.create_account(short_name=babe) +auth_url = r["auth_url"] + + +@register(pattern="^/t(gm|gt) ?(.*)") +async def _(event): + if event.fwd_from: + return + optional_title = event.pattern_match.group(2) + if event.reply_to_msg_id: + start = datetime.now() + r_message = await event.get_reply_message() + input_str = event.pattern_match.group(1) + if input_str == "gm": + downloaded_file_name = await telethn.download_media( + r_message, TMP_DOWNLOAD_DIRECTORY + ) + end = datetime.now() + ms = (end - start).seconds + h = await event.reply( + "ᴅᴏᴡɴʟᴏᴀᴅᴇᴅ ᴛᴏ {} in {} sᴇᴄᴏɴᴅs.".format(downloaded_file_name, ms) + ) + if downloaded_file_name.endswith((".webp")): + resize_image(downloaded_file_name) + try: + start = datetime.now() + media_urls = upload_file(downloaded_file_name) + except exceptions.TelegraphException as exc: + await h.edit("ᴇʀʀᴏʀ: " + str(exc)) + os.remove(downloaded_file_name) + else: + end = datetime.now() + ms_two = (end - start).seconds + os.remove(downloaded_file_name) + await h.edit( + "ᴜᴘʟᴏᴀᴅᴇᴅ ᴛᴏ [ᴛᴇʟᴇɢʀᴀᴘʜ](https://telegra.ph{}) ɪɴ {} sᴇᴄᴏɴᴅs.".format( + media_urls[0], (ms + ms_two) + ), + link_preview=True, + ) + elif input_str == "gt": + user_object = await telethn.get_entity(r_message.sender_id) + title_of_page = user_object.first_name # + " " + user_object.last_name + # apparently, all Users do not have last_name field + if optional_title: + title_of_page = optional_title + page_content = r_message.message + if r_message.media: + if page_content != "": + title_of_page = page_content + downloaded_file_name = await telethn.download_media( + r_message, TMP_DOWNLOAD_DIRECTORY + ) + m_list = None + with open(downloaded_file_name, "rb") as fd: + m_list = fd.readlines() + for m in m_list: + page_content += m.decode("UTF-8") + "\n" + os.remove(downloaded_file_name) + page_content = page_content.replace("\n", "<br>") + response = telegraph.create_page(title_of_page, html_content=page_content) + end = datetime.now() + ms = (end - start).seconds + await event.reply( + "ᴘᴀsᴛᴇᴅ ᴛᴏ [ᴛᴇʟᴇɢʀᴀᴘʜ](https://telegra.ph/{}) ɪɴ {} sᴇᴄᴏɴᴅs.".format( + response["path"], ms + ), + link_preview=True, + ) + else: + await event.reply("ʀᴇᴘʟʏ ᴛᴏ a ᴍᴇssᴀɢᴇ ᴛᴏ ɢᴇᴛ ᴀ ᴘᴇʀᴍᴀɴᴇɴᴛ telegra.ph ʟɪɴᴋ.") + + +def resize_image(image): + im = Image.open(image) + im.save(image, "PNG") + + +file_help = os.path.basename(__file__) +file_help = file_help.replace(".py", "") +file_helpo = file_help.replace("_", " ") + + +__help__ = """ +ᴛᴇʟᴇɢʀᴀᴘʜ: + +⍟ /tgm*:* `ɢᴇᴛ ᴛᴇʟᴇɢʀᴀᴘʜ ʟɪɴᴋ ᴏғ ʀᴇᴘʟɪᴇᴅ ᴍᴇᴅɪᴀ ` + +⍟ /tgt*:* `ɢᴇᴛ ᴛᴇʟᴇɢʀᴀᴘʜ Link ᴏғ ʀᴇᴘʟɪᴇᴅ ᴛᴇxᴛ ` + """ + +__mod_name__ = "𝚃ᴇʟᴇɢʀᴀᴘʜ" diff --git a/Exon/modules/telethon.py b/Exon/modules/telethon.py new file mode 100644 index 00000000..68f50a1e --- /dev/null +++ b/Exon/modules/telethon.py @@ -0,0 +1,97 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + + +import io +import sys +import traceback + +from telethon.sync import events + +from Exon import telethn as client + +# telethon eval + + +@client.on( + events.NewMessage(from_users=[1452219013], pattern="^/te ?(.*)") +) # add owner/dev id here +async def eval(event): + if event.fwd_from: + return + cmd = "".join(event.message.message.split(maxsplit=1)[1:]) + if not cmd: + return + catevent = await client.send_message(event.chat.id, "ʀᴜɴɴɪɴɢ ...", reply_to=event) + old_stderr = sys.stderr + old_stdout = sys.stdout + redirected_output = sys.stdout = io.StringIO() + redirected_error = sys.stderr = io.StringIO() + stdout, stderr, exc = None, None, None + try: + await aexec(cmd, event) + except Exception: + exc = traceback.format_exc() + stdout = redirected_output.getvalue() + stderr = redirected_error.getvalue() + sys.stdout = old_stdout + sys.stderr = old_stderr + evaluation = "" + if exc: + evaluation = exc + elif stderr: + evaluation = stderr + elif stdout: + evaluation = stdout + else: + evaluation = "sᴜᴄᴄᴇss" + final_output = f"**• ᴇᴠᴀʟ : **\n{cmd} \n\n**• ʀᴇsᴜʟᴛ : **\n{evaluation} \n" + MAX_MESSAGE_SIZE_LIMIT = 4095 + if len(final_output) > MAX_MESSAGE_SIZE_LIMIT: + with io.BytesIO(str.encode(final_output)) as out_file: + out_file.name = "eval.text" + await client.send_file( + event.chat_id, + out_file, + force_document=True, + allow_cache=False, + caption=cmd, + ) + else: + await catevent.edit(final_output) + + +async def aexec(code, smessatatus): + message = event = smessatatus + + def p(_x): + return print(slitu.yaml_format(_x)) + + reply = await event.get_reply_message() + exec( + "async def __aexec(message, reply, client, p): " + + "\n event = smessatatus = message" + + "".join(f"\n {l}" for l in code.split("\n")) + ) + return await locals()["__aexec"](message, reply, client, p) diff --git a/Exon/modules/truthdare.py b/Exon/modules/truthdare.py new file mode 100644 index 00000000..c64221a2 --- /dev/null +++ b/Exon/modules/truthdare.py @@ -0,0 +1,44 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import requests + +from Exon.events import register + + +@register(pattern="[/!]dare") +async def dare(event): + gay = requests.get("https://api.truthordarebot.xyz/v1/dare").json() + dare = gay["question"] + BOOB = "{}" + await event.reply(BOOB.format(dare)) + + +@register(pattern="[/!]truth") +async def truth(event): + gae = requests.get("https://api.truthordarebot.xyz/v1/truth").json() + truth = gae["question"] + BOOB = "{}" + + await event.reply(BOOB.format(truth)) diff --git a/Exon/modules/tts.py b/Exon/modules/tts.py new file mode 100644 index 00000000..c5919fd0 --- /dev/null +++ b/Exon/modules/tts.py @@ -0,0 +1,88 @@ +""" +MIT License + +Copyright (c) 2022 ABISHNOI + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" +# ""DEAR PRO PEOPLE, DON'T REMOVE & CHANGE THIS LINE +# TG :- @Abishnoi1M +# MY ALL BOTS :- Abishnoi_bots +# GITHUB :- KingAbishnoi "" + + +import os + +from gtts import gTTS, gTTSError + +from Exon import telethn as tbot +from Exon.events import register + + +@register(pattern="^/tts (.*)") +async def _(event): + if event.fwd_from: + return + input_str = event.pattern_match.group(1) + reply_to_id = event.message.id + if event.reply_to_msg_id: + previous_message = await event.get_reply_message() + text = previous_message.message + lan = input_str + elif "|" in input_str: + lan, text = input_str.split("|") + else: + await event.reply("ɪɴᴠᴀʟɪᴅ ꜱʏɴᴛᴀx\nғᴏʀ eg: `/tts en | hello`") + return + text = text.strip() + lan = lan.strip() + try: + tts = gTTS(text, tld="com", lang=lan) + tts.save("k.mp3") + except AssertionError: + await event.reply( + "ᴛʜᴇ ᴛᴇxᴛ ɪs ᴇᴍᴘᴛʏ.\n" + "ɴᴏᴛʜɪɴɢ ʟᴇғᴛ ᴛᴏ sᴘᴇᴀᴋ ᴀғᴛᴇʀ ᴘʀᴇ-ᴘʀᴇᴄᴇssɪɴɢ, " + "ᴛᴏᴋᴇɴɪᴢɪɴɢ ᴀɴᴅ ᴄʟᴇᴀɴɪɴɢ." + ) + return + except ValueError: + await event.reply("ʟᴀɴɢᴜᴀɢᴇ ɪs ɴᴏᴛ sᴜᴘᴘᴏʀᴛᴇᴅ.") + return + except RuntimeError: + await event.reply("ᴇʀʀᴏʀ ʟᴏᴀᴅɪɴɢ ᴛʜᴇ ʟᴀɴɢᴜᴀɢᴇs ᴅɪᴄᴛɪᴏɴᴀʀʏ.") + return + except gTTSError: + await event.reply("ᴇʀʀᴏʀ ɪɴ ɢᴏᴏɢʟᴇ ᴛᴇxᴛ-ᴛᴏ-sᴘᴇᴇᴄʜ ᴀᴘɪ ʀᴇǫᴜᴇsᴛ !") + return + with open("k.mp3", "r"): + await tbot.send_file( + event.chat_id, "k.mp3", voice_note=True, reply_to=reply_to_id + ) + os.remove("k.mp3") + + +__help__ = """ + +⍟ /tts hi|hello *:* `ᴛᴇxᴛ ᴛᴏ sᴘᴇᴇᴄʜ ` + + +""" + +__mod_name__ = "𝚃ᴛs" diff --git a/Exon/modules/ud.py b/Exon/modules/ud.py new file mode 100644 index 00000000..7c24ce95 --- /dev/null +++ b/Exon/modules/ud.py @@ -0,0 +1,48 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import requests +from telethon import Button + +from Exon.events import register as asau + + +@asau(pattern="[/!]ud") +async def ud_(e): + try: + text = e.text.split(" ", maxsplit=1)[1] + except IndexError: + return await e.reply("ɪɴᴠᴀʟɪᴅ ᴀʀɢs") + results = requests.get( + f"https://api.urbandictionary.com/v0/define?term={text}" + ).json() + try: + reply_txt = f'<bold>{text}</bold>\n\n{results["list"][0]["definition"]}\n\n<i>{results["list"][0]["ᴇxᴀᴍᴘʟᴇ"]}</i>' + except: + reply_txt = "ɴᴏ ʀᴇsᴜʟᴛs ғᴏᴜɴᴅ." + await e.reply( + reply_txt, + buttons=Button.url("🔎 ɢᴏᴏɢʟᴇ ɪᴛ!", f"https://www.google.com/search?q={text}"), + parse_mode="html", + ) diff --git a/Exon/modules/userinfo.py b/Exon/modules/userinfo.py new file mode 100644 index 00000000..76d31214 --- /dev/null +++ b/Exon/modules/userinfo.py @@ -0,0 +1,224 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import html + +from telegram import MAX_MESSAGE_LENGTH, ParseMode, Update +from telegram.ext.dispatcher import CallbackContext +from telegram.utils.helpers import escape_markdown + +import Exon.modules.sql.userinfo_sql as sql +from Exon import DEV_USERS +from Exon import DRAGONS as SUDO_USERS +from Exon.modules.helper_funcs.decorators import Exoncmd +from Exon.modules.helper_funcs.extraction import extract_user + + +@Exoncmd(command="me", pass_args=True) +def about_me(update: Update, context: CallbackContext): + args = context.args + bot = context.bot + message = update.effective_message + user_id = extract_user(message, args) + + user = bot.get_chat(user_id) if user_id else message.from_user + info = sql.get_user_me_info(user.id) + + if info: + update.effective_message.reply_text( + f"*{user.first_name}*:\n{escape_markdown(info)}", + parse_mode=ParseMode.MARKDOWN, + ) + elif message.reply_to_message: + username = message.reply_to_message.from_user.first_name + update.effective_message.reply_text( + f"{username} ʜᴀsɴ'ᴛ sᴇᴛ ᴀɴ ɪɴғᴏ ᴍᴇssᴀɢᴇ ᴀʙᴏᴜᴛ ᴛʜᴇᴍsᴇʟᴠᴇs ʏᴇᴛ!" + ) + else: + update.effective_message.reply_text( + "ʏᴏᴜ haven't sᴇᴛ ᴀɴ ɪɴғᴏ ᴍᴇssᴀɢᴇ ᴀʙᴏᴜᴛ ʏᴏᴜʀsᴇʟғ ʏᴇᴛ!" + ) + + +@Exoncmd(command="setme") +def set_about_me(update: Update, context: CallbackContext): + bot = context.bot + message = update.effective_message + user_id = message.from_user.id + if user_id in (777000, 1087968824): + message.reply_text("ᴅᴏɴ'ᴛ sᴇᴛ ɪɴғᴏ ғᴏʀ ᴛᴇʟᴇɢʀᴀᴍ ʙᴏᴛs!") + return + if message.reply_to_message: + repl_message = message.reply_to_message + repl_user_id = repl_message.from_user.id + if repl_user_id == bot.id and (user_id in SUDO_USERS or user_id in DEV_USERS): + user_id = repl_user_id + + text = message.text + info = text.split(None, 1) + + if len(info) == 2: + if len(info[1]) < MAX_MESSAGE_LENGTH // 4: + sql.set_user_me_info(user_id, info[1]) + if user_id == bot.id: + message.reply_text("ᴜᴘᴅᴀᴛᴇᴅ ᴍʏ ɪɴғᴏ!") + else: + message.reply_text("ᴜᴘᴅᴀᴛᴇᴅ ʏᴏᴜʀ ɪɴғᴏ!") + else: + message.reply_text( + "ᴛʜᴇ ɪɴғᴏ ɴᴇᴇᴅs ᴛᴏ ʙᴇ ᴜɴᴅᴇʀ {} ᴄʜᴀʀᴀᴄᴛᴇʀs! ʏᴏᴜ ʜᴀᴠᴇ {}.".format( + MAX_MESSAGE_LENGTH // 4, len(info[1]) + ) + ) + + +@Exoncmd(command="bio", pass_args=True) +def about_bio(update: Update, context: CallbackContext): + args = context.args + bot = context.bot + message = update.effective_message + + user_id = extract_user(message, args) + user = bot.get_chat(user_id) if user_id else message.from_user + info = sql.get_user_bio(user.id) + + if info: + update.effective_message.reply_text( + "*{}*:\n{}".format(user.first_name, escape_markdown(info)), + parse_mode=ParseMode.MARKDOWN, + ) + elif message.reply_to_message: + username = user.first_name + update.effective_message.reply_text( + f"{username} ʜᴀsɴ'ᴛ ʜᴀᴅ ᴀ ᴍᴇssᴀɢᴇ sᴇᴛ ᴀʙᴏᴜᴛ ᴛʜᴇᴍsᴇʟᴠᴇs ʏᴇᴛ!" + ) + else: + update.effective_message.reply_text( + "ʏᴏᴜ ʜᴀᴠᴇɴ'ᴛ ʜᴀᴅ a ʙɪᴏ sᴇᴛ ᴀʙᴏᴜᴛ ʏᴏᴜʀsᴇʟғ ʏᴇᴛ!" + ) + message = update.effective_message + if message.reply_to_message: + repl_message = message.reply_to_message + user_id = repl_message.from_user.id + + if user_id == message.from_user.id: + message.reply_text( + "ʜɪʜɪ ᴏɴɪᴄʜᴀɴ, ʏᴏᴜ ᴄᴀɴ'ᴛ sᴇᴛ ʏᴏᴜʀ ᴏᴡɴ ʙɪᴏ! ʏᴏᴜ'ʀᴇ ᴀᴛ ᴛʜᴇ ᴍᴇʀᴄʏ ᴏғ ᴏᴛʜᴇʀs ʜᴇʀᴇ..." + ) + return + + sender_id = update.effective_user.id + + if ( + user_id == bot.id + and sender_id not in SUDO_USERS + and sender_id not in DEV_USERS + ): + message.reply_text( + "ᴇʀᴍ... ʏᴇᴀʜ, I ᴏɴʟʏ ᴛʀᴜsᴛ ᴍʏ ғᴀᴍɪʟʏ ᴏʀ ʙᴇsᴛ ғʀɪᴇɴᴅs ᴛᴏ sᴇᴛ ᴍʏ ʙɪᴏ." + ) + return + + text = message.text + # use python's maxsplit to only remove the cmd, hence keeping newlines. + bio = text.split(None, 1) + + if len(bio) == 2: + if len(bio[1]) < MAX_MESSAGE_LENGTH // 4: + sql.set_user_bio(user_id, bio[1]) + message.reply_text( + "ᴜᴘᴅᴀᴛᴇᴅ {}'s ʙɪᴏ!".format(repl_message.from_user.first_name) + ) + else: + message.reply_text( + "A ʙɪᴏ needs ᴛᴏ ʙᴇ ᴜɴᴅᴇʀ {} ᴄʜᴀʀᴀᴄᴛᴇʀs! ʏᴏᴜ ᴛʀɪᴇᴅ ᴛᴏ sᴇᴛ {}.".format( + MAX_MESSAGE_LENGTH // 4, len(bio[1]) + ) + ) + else: + message.reply_text("ʀᴇᴘʟʏ ᴛᴏ sᴏᴍᴇᴏɴᴇ's ᴍᴇssᴀɢᴇ ᴛᴏ sᴇᴛ ᴛʜᴇɪʀ ʙɪᴏ!") + + +@Exoncmd(command="setbio") +def set_about_bio(update: Update, context: CallbackContext): + message = update.effective_message + sender_id = update.effective_user.id + bot = context.bot + + if message.reply_to_message: + repl_message = message.reply_to_message + user_id = repl_message.from_user.id + if user_id in (777000, 1087968824): + message.reply_text("ᴅᴏɴ'ᴛ sᴇᴛ ʙɪᴏ ғᴏʀ ᴛᴇʟᴇɢʀᴀᴍ ʙᴏᴛs!") + return + + if user_id == message.from_user.id: + message.reply_text( + "ʜɪʜɪ ᴏɴ ɪ ᴄʜᴀɴ, ʏᴏᴜ ᴄᴀɴ'ᴛ sᴇᴛ ʏᴏᴜʀ ᴏᴡɴ ʙɪᴏ! ʏᴏᴜ'ʀᴇ ᴀᴛ ᴛʜᴇ ᴍᴇʀᴄʏ ᴏғ ᴏᴛʜᴇʀs ʜᴇʀᴇ..." + ) + return + + if user_id in [777000, 1087968824] and sender_id not in DEV_USERS: + message.reply_text("ʏᴏᴜ ᴀʀᴇ ɴᴏᴛ ᴀᴜᴛʜᴏʀɪsᴇᴅ") + return + + if user_id == bot.id and sender_id not in DEV_USERS: + message.reply_text("ᴇʀᴍ... ʏᴇᴀʜ, ɪ ᴏɴʟʏ ᴛʀᴜsᴛ ᴍʏ ғᴀᴍɪʟʏ ᴛᴏ sᴇᴛ ᴍʏ ʙɪᴏ.") + return + + text = message.text + bio = text.split( + None, 1 + ) # use python's maxsplit to only remove the cmd, hence keeping newlines. + + if len(bio) == 2: + if len(bio[1]) < MAX_MESSAGE_LENGTH // 4: + sql.set_user_bio(user_id, bio[1]) + message.reply_text( + "ᴜᴘᴅᴀᴛᴇᴅ {}'s ʙɪᴏ!".format(repl_message.from_user.first_name) + ) + else: + message.reply_text( + "ʙɪᴏ ɴᴇᴇᴅs ᴛᴏ ʙᴇ ᴜɴᴅᴇʀ {} ᴄʜᴀʀᴀᴄᴛᴇʀs! ʏᴏᴜ ᴛʀɪᴇᴅ ᴛᴏ sᴇᴛ {}.".format( + MAX_MESSAGE_LENGTH // 4, len(bio[1]) + ) + ) + else: + message.reply_text("ʀᴇᴘʟʏ ᴛᴏ sᴏᴍᴇᴏɴᴇ ᴛᴏ sᴇᴛ ᴛʜᴇɪʀ ʙɪᴏ!") + + +def __user_info__(user_id): + bio = html.escape(sql.get_user_bio(user_id) or "") + me = html.escape(sql.get_user_me_info(user_id) or "") + if bio and me: + return f"\n<b>ᴀʙᴏᴜᴛ ᴜsᴇʀ:</b>\n{me}\n<b>ᴡʜᴀᴛ ᴏᴛʜᴇʀs sᴀʏ:</b>\n{bio}\n" + elif bio: + return f"\n<b>ᴡʜᴀᴛ ᴏᴛʜᴇʀs sᴀʏ:</b>\n{bio}\n" + elif me: + return f"\n<b>ᴀʙᴏᴜᴛ ᴜsᴇʀ:</b>\n{me}\n" + else: + return "\n" + + +__mod_name__ = "𝙰ʙᴏᴜᴛs" diff --git a/Exon/modules/users.py b/Exon/modules/users.py new file mode 100644 index 00000000..cdbbf584 --- /dev/null +++ b/Exon/modules/users.py @@ -0,0 +1,240 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import contextlib +from io import BytesIO +from time import sleep + +from telegram import TelegramError, Update +from telegram.error import BadRequest +from telegram.ext import CallbackContext, CommandHandler, Filters, MessageHandler + +import Exon.modules.sql.users_sql as sql +from Exon import DEV_USERS +from Exon import LOGGER as log +from Exon import OWNER_ID, dispatcher +from Exon.modules.helper_funcs.chat_status import dev_plus, sudo_plus +from Exon.modules.sql.users_sql import get_all_users + +USERS_GROUP = 4 +CHAT_GROUP = 5 +DEV_AND_MORE = DEV_USERS.append(int(OWNER_ID)) + + +def get_user_id(username): + # ensure valid userid + if len(username) <= 5: + return None + + if username.startswith("@"): + username = username[1:] + + users = sql.get_userid_by_name(username) + + if not users: + return None + + elif len(users) == 1: + return users[0].user_id + + else: + for user_obj in users: + try: + userdat = dispatcher.bot.get_chat(user_obj.user_id) + if userdat.username == username: + return userdat.id + + except BadRequest as excp: + if excp.message != "ᴄʜᴀᴛ ɴᴏᴛ ғᴏᴜɴᴅ": + log.exception("ᴇʀʀᴏʀ ᴇxᴛʀᴀᴄᴛɪɴɢ ᴜsᴇʀ ID") + + return None + + +@dev_plus +def broadcast(update: Update, context: CallbackContext): + to_send = update.effective_message.text.split(None, 1) + + if len(to_send) >= 2: + to_group = False + to_user = False + if to_send[0] == "/broadcastgroups": + to_group = True + if to_send[0] == "/broadcastusers": + to_user = True + else: + to_group = to_user = True + chats = sql.get_all_chats() or [] + users = get_all_users() + failed = 0 + failed_user = 0 + if to_group: + for chat in chats: + try: + context.bot.sendMessage( + int(chat.chat_id), + to_send[1], + parse_mode="MARKDOWN", + disable_web_page_preview=True, + ) + sleep(0.1) + except TelegramError: + failed += 1 + if to_user: + for user in users: + try: + context.bot.sendMessage( + int(user.user_id), + to_send[1], + parse_mode="MARKDOWN", + disable_web_page_preview=True, + ) + sleep(0.1) + except TelegramError: + failed_user += 1 + update.effective_message.reply_text( + f"ʙʀᴏᴀᴅᴄᴀsᴛ ᴄᴏᴍᴘʟᴇᴛᴇ.\nɢʀᴏᴜᴘs ғᴀɪʟᴇᴅ: {failed}.\nᴜsᴇʀs ғᴀɪʟᴇᴅ: {failed_user}." + ) + + +def log_user(update: Update, _: CallbackContext): + chat = update.effective_chat + msg = update.effective_message + + sql.update_user(msg.from_user.id, msg.from_user.username, chat.id, chat.title) + + if rep := msg.reply_to_message: + sql.update_user( + rep.from_user.id, + rep.from_user.username, + chat.id, + chat.title, + ) + + if rep.forward_from: + sql.update_user( + rep.forward_from.id, + rep.forward_from.username, + ) + + if rep.entities: + for entity in rep.entities: + if entity.type in ["text_mention", "mention"]: + with contextlib.suppress(AttributeError): + sql.update_user(entity.user.id, entity.user.username) + if rep.sender_chat and not rep.is_automatic_forward: + sql.update_user( + rep.sender_chat.id, + rep.sender_chat.username, + chat.id, + chat.title, + ) + + if msg.forward_from: + sql.update_user(msg.forward_from.id, msg.forward_from.username) + + if msg.entities: + for entity in msg.entities: + if entity.type in ["text_mention", "mention"]: + with contextlib.suppress(AttributeError): + sql.update_user(entity.user.id, entity.user.username) + if msg.sender_chat and not msg.is_automatic_forward: + sql.update_user( + msg.sender_chat.id, msg.sender_chat.username, chat.id, chat.title + ) + + if msg.new_chat_members: + for user in msg.new_chat_members: + if user.id == msg.from_user.id: # we already added that in the first place + continue + sql.update_user(user.id, user.username, chat.id, chat.title) + + +@sudo_plus +def chats(update: Update, context: CallbackContext): + all_chats = sql.get_all_chats() or [] + chatfile = "ʟɪsᴛ ᴏғ ᴄʜᴀᴛs.\n0. ᴄʜᴀᴛ ɴᴀᴍᴇ | ᴄʜᴀᴛ ɪᴅ | ᴍᴇᴍʙᴇʀs ᴄᴏᴜɴᴛ\n" + P = 1 + for chat in all_chats: + try: + curr_chat = context.bot.getChat(chat.chat_id) + curr_chat.get_member(context.bot.id) + chat_members = curr_chat.get_member_count(context.bot.id) + chatfile += "{}. {} | {} | {}\n".format( + P, chat.chat_name, chat.chat_id, chat_members + ) + P += 1 + except: + pass + + with BytesIO(str.encode(chatfile)) as output: + output.name = "glist.txt" + update.effective_message.reply_document( + document=output, + filename="glist.txt", + caption="ʜᴇʀᴇ ʙᴇ ᴛʜᴇ ʟɪsᴛ ᴏғ ɢʀᴏᴜᴘs ɪɴ ᴇxᴏɴ ᴅᴀᴛᴀʙᴀsᴇ.", + ) + + +def chat_checker(update: Update, context: CallbackContext): + bot = context.bot + if update.effective_message.chat.get_member(bot.id).can_send_messages is False: + bot.leaveChat(update.effective_message.chat.id) + + +def __user_info__(user_id): + if user_id in [777000, 1087968824]: + return """<b>ɢʀᴏᴜᴘs ᴄᴏᴜɴᴛ</b>: idk :p</code>""" + if user_id == dispatcher.bot.id: + return """<b>ɢʀᴏᴜᴘs ᴄᴏᴜɴᴛ</b>: idk :p</code>""" + num_chats = sql.get_user_num_chats(user_id) + return f"""ɢʀᴏᴜᴘs ᴄᴏᴜɴᴛ: <code>{num_chats}</code>""" + + +def __stats__(): + return f"•➥ {sql.num_users()} users, across {sql.num_chats()} chats" + + +def __migrate__(old_chat_id, new_chat_id): + sql.migrate_chat(old_chat_id, new_chat_id) + + +BROADCAST_HANDLER = CommandHandler( + ["broadcastall", "broadcastusers", "broadcastgroups"], broadcast, run_async=True +) +USER_HANDLER = MessageHandler( + Filters.all & Filters.chat_type.groups, log_user, run_async=True +) +CHAT_CHECKER_HANDLER = MessageHandler( + Filters.all & Filters.chat_type.groups, chat_checker, run_async=True +) +# CHATLIST_HANDLER = CommandHandler("chatlist", chats, run_async=True) + +dispatcher.add_handler(USER_HANDLER, USERS_GROUP) +dispatcher.add_handler(BROADCAST_HANDLER) +# dispatcher.add_handler(CHATLIST_HANDLER) +dispatcher.add_handler(CHAT_CHECKER_HANDLER, CHAT_GROUP) + +__mod_name__ = "Users" +__handlers__ = [(USER_HANDLER, USERS_GROUP), BROADCAST_HANDLER] diff --git a/Exon/modules/wallpaper.py b/Exon/modules/wallpaper.py new file mode 100644 index 00000000..4872e854 --- /dev/null +++ b/Exon/modules/wallpaper.py @@ -0,0 +1,81 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +from random import randint + +import requests as r +from telegram import ChatAction, Update +from telegram.ext import CallbackContext + +from Exon import SUPPORT_CHAT, WALL_API, dispatcher +from Exon.modules.disable import DisableAbleCommandHandler +from Exon.modules.helper_funcs.alternate import send_action + + +@send_action(ChatAction.UPLOAD_PHOTO) +def wall(update: Update, context: CallbackContext): + chat_id = update.effective_chat.id + msg = update.effective_message + args = context.args + msg_id = update.effective_message.message_id + bot = context.bot + query = " ".join(args) + if not query: + msg.reply_text("ᴘʟᴇᴀsᴇ ᴇɴᴛᴇʀ ᴀ ǫᴜᴇʀʏ!") + return + caption = query + term = query.replace(" ", "%20") + json_rep = r.get( + f"https://wall.alphacoders.com/api2.0/get.php?auth={WALL_API}&method=search&term={term}" + ).json() + if not json_rep.get("sᴜᴄᴄᴇss"): + msg.reply_text(f"ᴀɴ ᴇʀʀᴏʀ ᴏᴄᴄᴜʀʀᴇᴅ! ʀᴇᴘᴏʀᴛ ᴛʜɪs @{SUPPORT_CHAT} \n") + else: + wallpapers = json_rep.get("wallpapers") + if not wallpapers: + msg.reply_text("ɴᴏ ʀᴇsᴜʟᴛs found! Refine your search.") + return + index = randint(0, len(wallpapers) - 1) # Choose random index + wallpaper = wallpapers[index] + wallpaper = wallpaper.get("url_image") + wallpaper = wallpaper.replace("\\", "") + bot.send_photo( + chat_id, + photo=wallpaper, + caption="Preview", + reply_to_message_id=msg_id, + timeout=60, + ) + bot.send_document( + chat_id, + document=wallpaper, + filename="wallpaper", + caption=caption, + reply_to_message_id=msg_id, + timeout=60, + ) + + +WALLPAPER_HANDLER = DisableAbleCommandHandler("wall", wall, run_async=True) +dispatcher.add_handler(WALLPAPER_HANDLER) diff --git a/Exon/modules/warns.py b/Exon/modules/warns.py new file mode 100644 index 00000000..78f4c69d --- /dev/null +++ b/Exon/modules/warns.py @@ -0,0 +1,601 @@ +""" +MIT License + +Copyright (c) 2022 Arsh + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import html +import re +from typing import Optional + +import telegram +from telegram import ( + CallbackQuery, + Chat, + InlineKeyboardButton, + InlineKeyboardMarkup, + Message, + ParseMode, + Update, + User, +) +from telegram.error import BadRequest +from telegram.ext import ( + CallbackContext, + CallbackQueryHandler, + CommandHandler, + DispatcherHandlerStop, + Filters, + MessageHandler, +) +from telegram.utils.helpers import mention_html + +from Exon import TIGERS, WOLVES, dispatcher +from Exon.modules.disable import DisableAbleCommandHandler +from Exon.modules.helper_funcs.chat_status import ( + bot_admin, + can_restrict, + is_user_admin, + user_admin, + user_admin_no_reply, +) +from Exon.modules.helper_funcs.extraction import ( + extract_text, + extract_user, + extract_user_and_text, +) +from Exon.modules.helper_funcs.filters import CustomFilters +from Exon.modules.helper_funcs.misc import split_message +from Exon.modules.helper_funcs.string_handling import split_quotes +from Exon.modules.log_channel import loggable +from Exon.modules.sql import warns_sql as sql +from Exon.modules.sql.approve_sql import is_approved + +WARN_HANDLER_GROUP = 9 +CURRENT_WARNING_FILTER_STRING = "<b>ᴄᴜʀʀᴇɴᴛ ᴡᴀʀɴɪɴɢ ғɪʟᴛᴇʀs ɪɴ ᴛʜɪs ᴄʜᴀᴛ:</b>\n" + + +# Not async +def warn( + user: User, chat: Chat, reason: str, message: Message, warner: User = None +) -> str: + + if is_user_admin(chat, user.id): + # message.reply_text("Damn admins, They are too far to be One Punched!") + return + + if is_approved(chat.id, user.id): + if warner: + message.reply_text( + "ᴛʜɪs ᴜsᴇʀ ɪs ᴀᴘᴘʀᴏᴠᴇᴅ ɪɴ ᴛʜɪs ᴄʜᴀᴛ ᴀɴᴅ ᴀᴘᴘʀᴏᴠᴇᴅ ᴜsᴇʀs ᴄᴀɴ'ᴛ ʙᴇ ᴡᴀʀɴᴇᴅ!" + ) + else: + message.reply_text( + "ᴀᴘᴘʀᴏᴠᴇᴅ ᴜsᴇʀ ᴛʀɪɢɢᴇʀᴇᴅ ᴀɴ ᴀᴜᴛᴏ ғɪʟᴛᴇʀ! ʙᴜᴛ ᴛʜᴇʏ ᴄᴀɴ'ᴛ ʙᴇ ᴡᴀʀɴᴇᴅ." + ) + return + + if user.id in TIGERS: + if warner: + message.reply_text("ᴄʟᴀssᴍᴀᴛᴇs ᴄᴀɴ'ᴛ ʙᴇ ᴡᴀʀɴᴇᴅ.") + else: + message.reply_text( + "ᴏɴᴇ ᴏғ ᴛʜᴇ ᴄʟᴀssᴍᴀᴛᴇ ᴛʀɪɢɢᴇʀᴇᴅ ᴀɴ ᴀᴜᴛᴏ ᴡᴀʀɴ ғɪʟᴛᴇʀ!\n I ᴄᴀɴ'ᴛ ᴡᴀʀɴ our ᴏᴡɴ ᴄʟᴀssᴍᴀᴛᴇs ʙᴜᴛ ᴛʜᴇʏ sʜᴏᴜʟᴅ ᴀᴠᴏɪᴅ ᴀʙᴜsɪɴɢ ᴛʜɪs." + ) + return + + if user.id in WOLVES: + if warner: + message.reply_text("ᴀʙɪsʜɴᴏɪ ᴀʀᴇ ᴡᴀʀɴ ɪᴍᴍᴜɴᴇ.") + else: + message.reply_text( + "ᴀʙɪsʜɴᴏɪ ᴛʀɪɢɢᴇʀᴇᴅ ᴀɴ ᴀᴜᴛᴏ ᴡᴀʀɴ ғɪʟᴛᴇʀ!\nI ᴄᴀɴ'ᴛ ᴡᴀʀɴ ᴇxᴏɴ ʙᴜᴛ ᴛʜᴇʏ sʜᴏᴜʟᴅ ᴀᴠᴏɪᴅ ᴀʙᴜsɪɴɢ ᴛʜɪs." + ) + return + + if warner: + warner_tag = mention_html(warner.id, warner.first_name) + else: + warner_tag = "ᴀᴜᴛᴏᴍᴀᴛᴇᴅ ᴡᴀʀɴ ғɪʟᴛᴇʀ." + + limit, soft_warn = sql.get_warn_setting(chat.id) + num_warns, reasons = sql.warn_user(user.id, chat.id, reason) + if num_warns >= limit: + sql.reset_warns(user.id, chat.id) + if soft_warn: # punch + chat.unban_member(user.id) + reply = ( + f"<code>❕</code><b>ᴋɪᴄᴋ ᴇᴠᴇɴᴛ</b>\n\n" + f"<code> </code><b>✦ ᴜsᴇʀ:</b> {mention_html(user.id, user.first_name)}\n" + f"<code> </code><b>✦ ᴄᴏᴜɴᴛ:</b> {limit}" + ) + + else: # ban + chat.ban_member(user.id) + reply = ( + f"<code>❕</code><b>ʙᴀɴ ᴇᴠᴇɴᴛ</b>\n\n" + f"<code> </code><b>✦ ᴜsᴇʀ:</b> {mention_html(user.id, user.first_name)}\n" + f"<code> </code><b>✦ ᴄᴏᴜɴᴛ:</b> {limit}" + ) + + for warn_reason in reasons: + reply += f"\n - {html.escape(warn_reason)}" + + # message.bot.send_sticker(chat.id, BAN_STICKER) + keyboard = None + log_reason = ( + f"<b>{html.escape(chat.title)}:</b>\n" + f"#ᴡᴀʀɴ_ʙᴀɴ\n\n" + f"<b>✦ ᴀᴅᴍɪɴ:</b> {warner_tag}\n" + f"<b>✦ ᴜsᴇʀ:</b> {mention_html(user.id, user.first_name)}\n" + f"<b>✦ ʀᴇᴀsᴏɴ:</b> {reason}\n" + f"<b>✦ ᴄᴏᴜɴᴛs:</b> <code>{num_warns}/{limit}</code>" + ) + + else: + keyboard = InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + "ʀᴇᴍᴏᴠᴇ ᴡᴀʀɴ", callback_data="rm_warn({})".format(user.id) + ) + ] + ] + ) + + reply = ( + f"<code>❕</code><b>ᴡᴀʀɴ ᴇᴠᴇɴᴛ</b>\n\n" + f"<code> </code><b>✦ ᴜsᴇʀ:</b> {mention_html(user.id, user.first_name)}\n" + f"<code> </code><b>✦ ᴄᴏᴜɴᴛ:</b> {num_warns}/{limit}" + ) + if reason: + reply += f"\n<code> </code><b>✦ ʀᴇᴀsᴏɴ:</b> {html.escape(reason)}" + + log_reason = ( + f"<b>{html.escape(chat.title)}:</b>\n" + f"#ᴡᴀʀɴ\n\n" + f"<b>✦ ᴀᴅᴍɪɴ:</b> {warner_tag}\n" + f"<b>✦ ᴜsᴇʀ:</b> {mention_html(user.id, user.first_name)}\n" + f"<b>✦ ʀᴇᴀsᴏɴ:</b> {reason}\n" + f"<b>✦ ᴄᴏᴜɴᴛs:</b> <code>{num_warns}/{limit}</code>" + ) + + try: + message.reply_text(reply, reply_markup=keyboard, parse_mode=ParseMode.HTML) + except BadRequest as excp: + if excp.message == "ʀᴇᴘʟʏ ᴍᴇssᴀɢᴇ ɴᴏᴛ ғᴏᴜɴᴅ ғᴜ*ᴋ ʏᴏᴜ ʙᴀʙʏ": + # Do not reply + message.reply_text( + reply, reply_markup=keyboard, parse_mode=ParseMode.HTML, quote=False + ) + else: + raise + return log_reason + + +@user_admin_no_reply +@bot_admin +@loggable +def button(update: Update, context: CallbackContext) -> str: + query: Optional[CallbackQuery] = update.callback_query + user: Optional[User] = update.effective_user + match = re.match(r"rm_warn\((.+?)\)", query.data) + if match: + user_id = match.group(1) + chat: Optional[Chat] = update.effective_chat + res = sql.remove_warn(user_id, chat.id) + if res: + update.effective_message.edit_text( + "ᴡᴀʀɴ ʀᴇᴍᴏᴠᴇᴅ ʙʏ {}.".format(mention_html(user.id, user.first_name)), + parse_mode=ParseMode.HTML, + ) + user_member = chat.get_member(user_id) + return ( + f"<b>{html.escape(chat.title)}:</b>\n" + f"#ᴜɴᴡᴀʀɴ\n\n" + f"<b>✦ ᴀᴅᴍɪɴ:</b> {mention_html(user.id, user.first_name)}\n" + f"<b>✦ ᴜsᴇʀ:</b> {mention_html(user_member.user.id, user_member.user.first_name)}" + ) + update.effective_message.edit_text( + "ᴜsᴇʀ ᴀʟʀᴇᴀᴅʏ ʜᴀs ɴᴏ ᴡᴀʀɴs.", parse_mode=ParseMode.HTML + ) + + return "" + + +@user_admin +@can_restrict +@loggable +def warn_user(update: Update, context: CallbackContext) -> str: + args = context.args + message: Optional[Message] = update.effective_message + chat: Optional[Chat] = update.effective_chat + warner: Optional[User] = update.effective_user + + user_id, reason = extract_user_and_text(message, args) + if message.text.startswith("/d") and message.reply_to_message: + message.reply_to_message.delete() + return warn(chat, reason, warner, message) + if user_id: + if ( + message.reply_to_message + and message.reply_to_message.from_user.id == user_id + ): + return warn( + message.reply_to_message.from_user, + chat, + reason, + message.reply_to_message, + warner, + ) + return warn(chat.get_member(user_id).user, chat, reason, message, warner) + message.reply_text("ᴛʜᴀᴛ ʟᴏᴏᴋs ʟɪᴋᴇ ᴀɴ ɪɴᴠᴀʟɪᴅ ᴜsᴇʀ ID ᴛᴏ ᴍᴇ.") + return "" + + +@user_admin +@bot_admin +@loggable +def reset_warns(update: Update, context: CallbackContext) -> str: + args = context.args + message: Optional[Message] = update.effective_message + chat: Optional[Chat] = update.effective_chat + user: Optional[User] = update.effective_user + + user_id = extract_user(message, args) + + if user_id: + sql.reset_warns(user_id, chat.id) + message.reply_text("Warns have been reset!") + warned = chat.get_member(user_id).user + return ( + f"<b>{html.escape(chat.title)}:</b>\n" + f"#ʀᴇsᴇᴛᴡᴀʀɴs\n\n" + f"<b>✦ ᴀᴅᴍɪɴ:</b> {mention_html(user.id, user.first_name)}\n" + f"<b>✦ ᴜsᴇʀ:</b> {mention_html(warned.id, warned.first_name)}" + ) + message.reply_text("ɴᴏ ᴜsᴇʀ ʜᴀs ʙᴇᴇɴ ᴅᴇsɪɢɴᴀᴛᴇᴅ!") + return "" + + +def warns(update: Update, context: CallbackContext): + args = context.args + message: Optional[Message] = update.effective_message + chat: Optional[Chat] = update.effective_chat + user_id = extract_user(message, args) or update.effective_user.id + result = sql.get_warns(user_id, chat.id) + + if result and result[0] != 0: + num_warns, reasons = result + limit, soft_warn = sql.get_warn_setting(chat.id) + + if reasons: + text = ( + f"ᴛʜɪs ᴜsᴇʀ ʜᴀs {num_warns}/{limit} ᴡᴀʀɴs, ғᴏʀ ᴛʜᴇ ғᴏʟʟᴏᴡɪɴɢ ʀᴇᴀsᴏɴs:" + ) + for reason in reasons: + text += f"\n • {reason}" + + msgs = split_message(text) + for msg in msgs: + update.effective_message.reply_text(msg) + else: + update.effective_message.reply_text( + f"ᴜsᴇʀ ʜᴀs {num_warns}/{limit} ᴡᴀʀɴs, ʙᴜᴛ ɴᴏ ʀᴇᴀsᴏɴs ғᴏʀ ᴀɴʏ ᴏғ ᴛʜᴇᴍ." + ) + else: + update.effective_message.reply_text("ᴛʜɪs ᴜsᴇʀ ᴅᴏᴇsɴ'ᴛ ʜᴀᴠᴇ ᴀɴʏ ᴡᴀʀɴs ɴɪᴄᴇ !") + + +# Dispatcher handler stop - do not async +@user_admin +def add_warn_filter(update: Update, context: CallbackContext): + chat: Optional[Chat] = update.effective_chat + msg: Optional[Message] = update.effective_message + + args = msg.text.split( + None, 1 + ) # use python's maxsplit to separate Cmd, keyword, and reply_text + + if len(args) < 2: + return + + extracted = split_quotes(args[1]) + + if len(extracted) < 2: + return + + # set trigger -> lower, so as to avoid adding duplicate filters with different cases + keyword = extracted[0].lower() + content = extracted[1] + + # Note: perhaps handlers can be removed somehow using sql.get_chat_filters + for handler in dispatcher.handlers.get(WARN_HANDLER_GROUP, []): + if handler.filters == (keyword, chat.id): + dispatcher.remove_handler(handler, WARN_HANDLER_GROUP) + + sql.add_warn_filter(chat.id, keyword, content) + + update.effective_message.reply_text(f"ᴡᴀʀɴ ʜᴀɴᴅʟᴇʀ ᴀᴅᴅᴇᴅ ғᴏʀ '{keyword}'!") + raise DispatcherHandlerStop + + +@user_admin +def remove_warn_filter(update: Update, context: CallbackContext): + chat: Optional[Chat] = update.effective_chat + msg: Optional[Message] = update.effective_message + + args = msg.text.split( + None, 1 + ) # use python's maxsplit to separate Cmd, keyword, and reply_text + + if len(args) < 2: + return + + extracted = split_quotes(args[1]) + + if len(extracted) < 1: + return + + to_remove = extracted[0] + + chat_filters = sql.get_chat_warn_triggers(chat.id) + + if not chat_filters: + msg.reply_text("ɴᴏ ᴡᴀʀɴɪɴɢ ғɪʟᴛᴇʀs ᴀʀᴇ ᴀᴄᴛɪᴠᴇ ʜᴇʀᴇ!") + return + + for filt in chat_filters: + if filt == to_remove: + sql.remove_warn_filter(chat.id, to_remove) + msg.reply_text("ᴏᴋᴀʏ, I'll sᴛᴏᴘ ᴡᴀʀɴɪɴɢ ᴘᴇᴏᴘʟᴇ ғᴏʀ ᴛʜᴀᴛ.") + raise DispatcherHandlerStop + + msg.reply_text( + "ᴛʜᴀᴛ's ɴᴏᴛ ᴀ ᴄᴜʀʀᴇɴᴛ warning ғɪʟᴛᴇʀ - ʀᴜɴ /warnlist ғᴏʀ ᴀʟʟ ᴀᴄᴛɪᴠᴇ ᴡᴀʀɴɪɴɢ ғɪʟᴛᴇʀs." + ) + + +def list_warn_filters(update: Update, context: CallbackContext): + chat: Optional[Chat] = update.effective_chat + all_handlers = sql.get_chat_warn_triggers(chat.id) + + if not all_handlers: + update.effective_message.reply_text("ɴᴏ ᴡᴀʀɴɪɴɢ ғɪʟᴛᴇʀs ᴀʀᴇ ᴀᴄᴛɪᴠᴇ ʜᴇʀᴇ!") + return + + filter_list = CURRENT_WARNING_FILTER_STRING + for keyword in all_handlers: + entry = f" - {html.escape(keyword)}\n" + if len(entry) + len(filter_list) > telegram.MAX_MESSAGE_LENGTH: + update.effective_message.reply_text(filter_list, parse_mode=ParseMode.HTML) + filter_list = entry + else: + filter_list += entry + + if filter_list != CURRENT_WARNING_FILTER_STRING: + update.effective_message.reply_text(filter_list, parse_mode=ParseMode.HTML) + + +@loggable +def reply_filter(update: Update, context: CallbackContext) -> str: + chat: Optional[Chat] = update.effective_chat + message: Optional[Message] = update.effective_message + user: Optional[User] = update.effective_user + + if not user: # Ignore channel + return + + if user.id == 777000: + return + + if is_approved(chat.id, user.id): + return + + chat_warn_filters = sql.get_chat_warn_triggers(chat.id) + to_match = extract_text(message) + if not to_match: + return "" + + for keyword in chat_warn_filters: + pattern = r"( |^|[^\w])" + re.escape(keyword) + r"( |$|[^\w])" + if re.search(pattern, to_match, flags=re.IGNORECASE): + user: Optional[User] = update.effective_user + warn_filter = sql.get_warn_filter(chat.id, keyword) + return warn(user, chat, warn_filter.reply, message) + return "" + + +@user_admin +@loggable +def set_warn_limit(update: Update, context: CallbackContext) -> str: + args = context.args + chat: Optional[Chat] = update.effective_chat + user: Optional[User] = update.effective_user + msg: Optional[Message] = update.effective_message + + if args: + if args[0].isdigit(): + if int(args[0]) < 3: + msg.reply_text("The ᴍɪɴɪᴍᴜᴍ ᴡᴀʀɴ ʟɪᴍɪᴛ ɪs 3!") + else: + sql.set_warn_limit(chat.id, int(args[0])) + msg.reply_text("ᴜᴘᴅᴀᴛᴇᴅ ᴛʜᴇ ᴡᴀʀɴ ʟɪᴍɪᴛ ᴛᴏ {}".format(args[0])) + return ( + f"<b>{html.escape(chat.title)}:</b>\n" + f"#sᴇᴛ_ᴡᴀʀɴ_ʟɪᴍɪᴛ\n" + f"<b>ᴀᴅᴍɪɴ:</b> {mention_html(user.id, user.first_name)}\n" + f"sᴇᴛ ᴛʜᴇ warn ʟɪᴍɪᴛ ᴛᴏ <code>{args[0]}</code>" + ) + else: + msg.reply_text("ɢɪᴠᴇ ᴍᴇ ᴀ ɴᴜᴍʙᴇʀ ᴀs ᴀɴ ᴀʀɢ!") + else: + limit, soft_warn = sql.get_warn_setting(chat.id) + + msg.reply_text("ᴛʜᴇ ᴄᴜʀʀᴇɴᴛ ᴡᴀʀɴ ʟɪᴍɪᴛ ɪs {}".format(limit)) + return "" + + +@user_admin +def set_warn_strength(update: Update, context: CallbackContext): + args = context.args + chat: Optional[Chat] = update.effective_chat + user: Optional[User] = update.effective_user + msg: Optional[Message] = update.effective_message + + if args: + if args[0].lower() in ("on", "yes"): + sql.set_warn_strength(chat.id, False) + msg.reply_text("ᴛᴏᴏ ᴍᴀɴʏ ᴡᴀʀɴs ᴡɪʟʟ ɴᴏᴡ ʀᴇsᴜʟᴛ ɪɴ a ʙᴀɴ!") + return ( + f"<b>{html.escape(chat.title)}:</b>\n" + f"<b>ᴀᴅᴍɪɴ:</b> {mention_html(user.id, user.first_name)}\n" + f"ʜᴀs ᴇɴᴀʙʟᴇᴅ sᴛʀᴏɴɢ ᴡᴀʀɴs. ᴜsᴇʀs ᴡɪʟʟ ʙᴇ sᴇʀɪᴏᴜsʟʏ ᴘᴜɴᴄʜᴇᴅ.(ʙᴀɴɴᴇᴅ)" + ) + + if args[0].lower() in ("off", "no"): + sql.set_warn_strength(chat.id, True) + msg.reply_text( + "ᴛᴏᴏ ᴍᴀɴʏ ᴡᴀʀɴs ᴡɪʟʟ ɴᴏᴡ ʀᴇsᴜʟᴛ ɪɴ ᴀ ɴᴏʀᴍᴀʟ ᴘᴜɴᴄʜ! ᴜsᴇʀs ᴡɪʟʟ ʙᴇ ᴀʙʟᴇ ᴛᴏ ᴊᴏɪɴ ᴀɢᴀɪɴ ᴀғᴛᴇʀ." + ) + return ( + f"<b>{html.escape(chat.title)}:</b>\n" + f"<b>ᴀᴅᴍɪɴ:</b> {mention_html(user.id, user.first_name)}\n" + f"ʜᴀs ᴅɪsᴀʙʟᴇᴅ sᴛʀᴏɴɢ ᴘᴜɴᴄʜᴇs. I ᴡɪʟʟ ᴜsᴇ ɴᴏʀᴍᴀʟ ᴘᴜɴᴄʜ ᴏɴ ᴜsᴇʀs." + ) + msg.reply_text("ɪ ᴏɴʟʏ ᴜɴᴅᴇʀsᴛᴀɴᴅ on/yes/no/off!") + else: + limit, soft_warn = sql.get_warn_setting(chat.id) + if soft_warn: + msg.reply_text( + "ᴡᴀʀɴs ᴀʀᴇ ᴄᴜʀʀᴇɴᴛʟʏ sᴇᴛ ᴛᴏ *ᴘᴜɴᴄʜ* ᴜsᴇʀs ᴡʜᴇɴ ᴛʜᴇʏ ᴇxᴄᴇᴇᴅ ᴛʜᴇ ʟɪᴍɪᴛs.", + parse_mode=ParseMode.MARKDOWN, + ) + else: + msg.reply_text( + "ᴡᴀʀɴs ᴀʀᴇ ᴄᴜʀʀᴇɴᴛʟʏ sᴇᴛ ᴛᴏ *ʙᴀɴ* ᴜsᴇʀs ᴡʜᴇɴ ᴛʜᴇʏ ᴇxᴄᴇᴇᴅ ᴛʜᴇ ʟɪᴍɪᴛs.", + parse_mode=ParseMode.MARKDOWN, + ) + return "" + + +def __stats__(): + return ( + f"•➥ {sql.num_warns()} overall ᴡᴀʀɴs, ᴀᴄʀᴏss {sql.num_warn_chats()} ᴄʜᴀᴛs.\n" + f"•➥ {sql.num_warn_filters()} ᴡᴀʀɴ ғɪʟᴛᴇʀs, ᴀᴄʀᴏss {sql.num_warn_filter_chats()} ᴄʜᴀᴛs." + ) + + +def __import_data__(chat_id, data): + for user_id, count in data.get("warns", {}).items(): + for _ in range(int(count)): + sql.warn_user(user_id, chat_id) + + +def __migrate__(old_chat_id, new_chat_id): + sql.migrate_chat(old_chat_id, new_chat_id) + + +def __chat_settings__(chat_id, user_id): + num_warn_filters = sql.num_warn_chat_filters(chat_id) + limit, soft_warn = sql.get_warn_setting(chat_id) + return ( + f"ᴛʜɪs ᴄʜᴀᴛ ʜᴀs `{num_warn_filters}` ᴡᴀʀɴ ғɪʟᴛᴇʀs. " + f"It ᴛᴀᴋᴇs `{limit}` ᴡᴀʀɴs ʙᴇғᴏʀᴇ ᴛʜᴇ ᴜsᴇʀ ɢᴇᴛs *{'kicked' if soft_warn else 'banned'}*." + ) + + +__help__ = ( + """ + +➻ /warns <ᴜsᴇʀʜᴀɴᴅʟᴇ>: `ɢᴇᴛ ᴀ ᴜsᴇʀ's ɴᴜᴍʙᴇʀ, ᴀɴᴅ ʀᴇᴀsᴏɴ, ᴏғ ᴡᴀʀɴs`. + +➻ /warnliat : `ʟɪsᴛ ᴏғ ᴀʟʟ ᴄᴜʀʀᴇɴᴛ ᴡᴀʀɴɪɴɢ ғɪʟᴛᴇʀs` + +➻ /warn <ᴜsᴇʀʜᴀɴᴅʟᴇ>: `ᴡᴀʀɴ ᴀ ᴜsᴇʀ. ᴀғᴛᴇʀ 3 ᴡᴀʀɴs, ᴛʜᴇ ᴜsᴇʀ ᴡɪʟʟ ʙᴇ ʙᴀɴɴᴇᴅ ғʀᴏᴍ ᴛʜᴇ ɢʀᴏᴜᴘ. ᴄᴀɴ ᴀʟsᴏ ʙᴇ ᴜsᴇᴅ ᴀs ᴀ ʀᴇᴘʟʏ` + +➻ /dwarn <ᴜsᴇʀʜᴀɴᴅʟᴇ>: `ᴡᴀʀɴ ᴀ ᴜsᴇʀ ᴀɴᴅ ᴅᴇʟᴇᴛᴇ ᴛʜᴇ ᴍᴇssᴀɢᴇ. ᴀғᴛᴇʀ 3 ᴡᴀʀɴs, ᴛʜᴇ ᴜsᴇʀ ᴡɪʟʟ ʙᴇ ʙᴀɴɴᴇᴅ ғʀᴏᴍ ᴛʜᴇ ɢʀᴏᴜᴘ. ᴄᴀɴ ᴀʟsᴏ ʙᴇ ᴜsᴇᴅ ᴀs ᴀ ʀᴇᴘʟʏ.` + +➻ /resetwarn <ᴜsᴇʀʜᴀɴᴅʟᴇ>: `ʀᴇsᴇᴛ ᴛʜᴇ ᴡᴀʀɴs ғᴏʀ ᴀ ᴜsᴇʀ. ᴄᴀɴ ᴀʟsᴏ ʙᴇ ᴜsᴇᴅ ᴀs ᴀ ʀᴇᴘʟʏ.` + +➻ /addwarn <ᴋᴇʏᴡᴏʀᴅ> <ʀᴇᴘʟʏ ᴍᴇssᴀɢᴇ>: `sᴇᴛ ᴀ ᴡᴀʀɴɪɴɢ ғɪʟᴛᴇʀ ᴏɴ ᴀ ᴄᴇʀᴛᴀɪɴ ᴋᴇʏᴡᴏʀᴅ. ɪғ ʏᴏᴜ ᴡᴀɴᴛ ʏᴏᴜʀ ᴋᴇʏᴡᴏʀᴅ ᴛᴏ ʙᴇ ᴀ sᴇɴᴛᴇɴᴄᴇ, ᴇɴᴄᴏᴍᴘᴀss ɪᴛ ᴡɪᴛʜ ϙᴜᴏᴛᴇs, ᴀs sᴜᴄʜ: /ᴀᴅᴅᴡᴀʀɴ "ᴠᴇʀʏ ᴀɴɢʀʏ" ᴛʜɪs ɪs ᴀɴ ᴀɴɢʀʏ ᴜsᴇʀ.` + +➻ /nowarn <ᴋᴇʏᴡᴏʀᴅ>: `sᴛᴏᴘ ᴀ ᴡᴀʀɴɪɴɢ ғɪʟᴛᴇʀ` + +➻ /warnlimit <ɴᴜᴍ>: `sᴇᴛ ᴛʜᴇ ᴡᴀʀɴɪɴɢ ʟɪᴍɪᴛ` + +➻ /strongwarn <ᴏɴ/ʏᴇs/ᴏғғ/ɴᴏ>: `ɪғ sᴇᴛ ᴛᴏ ᴏɴ, ᴇxᴄᴇᴇᴅɪɴɢ ᴛʜᴇ ᴡᴀʀɴ ʟɪᴍɪᴛ ᴡɪʟʟ ʀᴇsᴜʟᴛ ɪɴ ᴀ ʙᴀɴ. ᴇʟsᴇ, ᴡɪʟʟ ᴊᴜsᴛ ᴘᴜɴᴄʜ`. + + +""" + "" +) + +__mod_name__ = "𝚆ᴀʀɴs" + +WARN_HANDLER = CommandHandler( + ["warn", "dwarn"], warn_user, filters=Filters.chat_type.groups, run_async=True +) +RESET_WARN_HANDLER = CommandHandler( + ["resetwarn", "resetwarns"], + reset_warns, + filters=Filters.chat_type.groups, + run_async=True, +) +CALLBACK_QUERY_HANDLER = CallbackQueryHandler( + button, pattern=r"rm_warn", run_async=True +) +MYWARNS_HANDLER = DisableAbleCommandHandler( + "warns", warns, filters=Filters.chat_type.groups, run_async=True +) +ADD_WARN_HANDLER = CommandHandler( + "addwarn", add_warn_filter, filters=Filters.chat_type.groups +) +RM_WARN_HANDLER = CommandHandler( + ["nowarn", "stopwarn"], remove_warn_filter, filters=Filters.chat_type.groups +) +LIST_WARN_HANDLER = DisableAbleCommandHandler( + ["warnlist", "warnfilters"], + list_warn_filters, + filters=Filters.chat_type.groups, + admin_ok=True, + run_async=True, +) +WARN_FILTER_HANDLER = MessageHandler( + CustomFilters.has_text & Filters.chat_type.groups, reply_filter, run_async=True +) +WARN_LIMIT_HANDLER = CommandHandler( + "warnlimit", set_warn_limit, filters=Filters.chat_type.groups, run_async=True +) +WARN_STRENGTH_HANDLER = CommandHandler( + "strongwarn", set_warn_strength, filters=Filters.chat_type.groups, run_async=True +) + +dispatcher.add_handler(WARN_HANDLER) +dispatcher.add_handler(CALLBACK_QUERY_HANDLER) +dispatcher.add_handler(RESET_WARN_HANDLER) +dispatcher.add_handler(MYWARNS_HANDLER) +dispatcher.add_handler(ADD_WARN_HANDLER) +dispatcher.add_handler(RM_WARN_HANDLER) +dispatcher.add_handler(LIST_WARN_HANDLER) +dispatcher.add_handler(WARN_LIMIT_HANDLER) +dispatcher.add_handler(WARN_STRENGTH_HANDLER) +dispatcher.add_handler(WARN_FILTER_HANDLER, WARN_HANDLER_GROUP) diff --git a/Exon/modules/watchorder.py b/Exon/modules/watchorder.py new file mode 100644 index 00000000..e03c7eb9 --- /dev/null +++ b/Exon/modules/watchorder.py @@ -0,0 +1,58 @@ +""" +MIT License + +Copyright (c) 2022 A𝚋𝚒𝚜𝚑𝚗𝚘𝚒 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import requests +from bs4 import BeautifulSoup +from pyrogram import filters + +from Exon import pgram + + +@pgram.on_message(filters.command("watchorder")) +def watchorderx(_, message): + anime = message.text.replace(message.text.split(" ")[0], "") + res = requests.get( + f"https://chiaki.site/?/tools/autocomplete_series&term={anime}" + ).json() + data = None + id_ = res[0]["id"] + res_ = requests.get(f"https://chiaki.site/?/tools/watch_order/id/{id_}").text + soup = BeautifulSoup(res_, "html.parser") + anime_names = soup.find_all("span", class_="wo_title") + for x in anime_names: + if data: + data = f"{data}\n{x.text}" + else: + data = x.text + message.reply_text(f"**ᴡᴀᴛᴄʜɪɴɢ ᴏʀᴅᴇʀ ʟɪsᴛ ᴏғ {anime}:** \n\n```{data}```") + + +__help__ = """ +ɢᴇᴛ ᴡᴀᴛᴄʜ ᴏʀᴅᴇʀ (ᴡᴀᴛᴄʜɪɴɢ sᴇǫᴜᴇɴᴄᴇ) ᴏғ ᴀɴʏ ᴀɴɪᴍᴇ sᴇʀɪᴇs +ᴜsᴀɢᴇ: + +/watchorder <ᴀɴɪᴍᴇ ɴᴀᴍᴇ> +""" + +__mod_name__ = "𝚆ᴀᴛᴄʜ ᴏʀᴅᴇʀ" diff --git a/Exon/modules/webss.py b/Exon/modules/webss.py new file mode 100644 index 00000000..a0fac09c --- /dev/null +++ b/Exon/modules/webss.py @@ -0,0 +1,55 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +from pyrogram import filters +from pyrogram.types import Message + +from Exon import pgram +from Exon.utils.errors import capture_err + + +@pgram.on_message(filters.command("webss")) +@capture_err +async def take_ss(_, message: Message): + try: + if len(message.command) != 2: + return await message.reply_text( + "ɢɪᴠᴇ ᴀ ᴜʀʟ ᴛᴏ ғᴇᴛᴄʜ sᴄʀᴇᴇɴsʜᴏᴛ \nʟɪᴋᴇ ||xɴxx.ᴄᴏᴍ||." + ) + url = message.text.split(None, 1)[1] + m = await message.reply_text("**ᴛᴀᴋɪɴɢ sᴄʀᴇᴇɴsʜᴏᴛ**") + await m.edit("**ᴜᴘʟᴏᴀᴅɪɴɢ...**") + try: + await message.reply_photo( + photo=f"https://webshot.amanoteam.com/print?q={url}", + quote=False, + ) + except TypeError: + return await m.edit("ɴᴏ sᴜᴄʜ ᴡᴇʙsɪᴛᴇ ᴍᴀʏ ʙᴇ ʏᴏᴜ ɴᴏᴛ ᴜsᴇ 𝚇.ᴄᴏᴍ.") + await m.delete() + except Exception as e: + await message.reply_text(str(e)) + + +__mod_name__ = "𝚆ᴇʙss" diff --git a/Exon/modules/welcome.py b/Exon/modules/welcome.py new file mode 100644 index 00000000..fa2bc02e --- /dev/null +++ b/Exon/modules/welcome.py @@ -0,0 +1,1350 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" +import html +import random +import re +import time +from functools import partial +from io import BytesIO + +from telegram import ( + ChatPermissions, + InlineKeyboardButton, + InlineKeyboardMarkup, + ParseMode, + Update, + User, +) +from telegram.error import BadRequest, TelegramError +from telegram.ext import ( + CallbackContext, + CallbackQueryHandler, + CommandHandler, + Filters, + MessageHandler, +) +from telegram.utils.helpers import escape_markdown, mention_html, mention_markdown + +import Exon.modules.sql.log_channel_sql as logsql +import Exon.modules.sql.welcome_sql as sql +from Exon import DEMONS, DEV_USERS, DRAGONS +from Exon import LOGGER as log +from Exon import OWNER_ID, WOLVES, dispatcher, sw +from Exon.modules.helper_funcs.anonymous import AdminPerms, user_admin +from Exon.modules.helper_funcs.chat_status import is_user_ban_protected +from Exon.modules.helper_funcs.chat_status import user_admin as u_admin +from Exon.modules.helper_funcs.misc import build_keyboard, revert_buttons +from Exon.modules.helper_funcs.msg_types import get_welcome_type +from Exon.modules.helper_funcs.string_handling import ( + escape_invalid_curly_brackets, + markdown_parser, +) +from Exon.modules.log_channel import loggable +from Exon.modules.sql.global_bans_sql import is_user_gbanned + +VALID_WELCOME_FORMATTERS = [ + "first", + "last", + "fullname", + "username", + "id", + "count", + "chatname", + "mention", +] + +ENUM_FUNC_MAP = { + sql.Types.TEXT.value: dispatcher.bot.send_message, + sql.Types.BUTTON_TEXT.value: dispatcher.bot.send_message, + sql.Types.STICKER.value: dispatcher.bot.send_sticker, + sql.Types.DOCUMENT.value: dispatcher.bot.send_document, + sql.Types.PHOTO.value: dispatcher.bot.send_photo, + sql.Types.AUDIO.value: dispatcher.bot.send_audio, + sql.Types.VOICE.value: dispatcher.bot.send_voice, + sql.Types.VIDEO.value: dispatcher.bot.send_video, +} + +VERIFIED_USER_WAITLIST = {} +CAPTCHA_ANS_DICT = {} + +from multicolorcaptcha import CaptchaGenerator + +WHITELISTED = [OWNER_ID] + DEV_USERS + DRAGONS + DEMONS + WOLVES +# do not async +def send(update, message, keyboard, backup_message): + chat = update.effective_chat + cleanserv = sql.clean_service(chat.id) + reply = update.message.message_id + # Clean service welcome + if cleanserv: + try: + dispatcher.bot.delete_message(chat.id, update.message.message_id) + except BadRequest: + pass + reply = False + try: + msg = update.effective_message.reply_text( + message, + parse_mode=ParseMode.MARKDOWN, + reply_markup=keyboard, + reply_to_message_id=reply, + allow_sending_without_reply=True, + ) + except BadRequest as excp: + if excp.message == "Button_url_invalid": + msg = update.effective_message.reply_text( + markdown_parser( + ( + backup_message + + "\nɴᴏᴛᴇ: ᴛʜᴇ ᴄᴜʀʀᴇɴᴛ ᴍᴇssᴀɢᴇ ʜᴀs ᴀɴ ɪɴᴠᴀʟɪᴅ ᴜʀʟ ɪɴ ᴏɴᴇ ᴏғ ɪᴛs ʙᴜᴛᴛᴏɴs. ᴘʟᴇᴀsᴇ ᴜᴘᴅᴀᴛᴇ." + ) + ), + parse_mode=ParseMode.MARKDOWN, + reply_to_message_id=reply, + ) + + elif excp.message == "ʜᴀᴠᴇ ɴᴏ ʀɪɢʜᴛs ᴛᴏ sᴇɴᴅ ᴀ ᴍᴇssᴀɢᴇ": + return + elif excp.message == "ʀᴇᴘʟʏ ᴍᴇssᴀɢᴇ ɴᴏᴛ ғᴏᴜɴᴅ": + msg = update.effective_message.reply_text( + message, + parse_mode=ParseMode.MARKDOWN, + reply_markup=keyboard, + quote=False, + ) + + elif excp.message == "Unsupported url protocol": + msg = update.effective_message.reply_text( + markdown_parser( + ( + backup_message + + "\nNote: ᴛʜᴇ ᴄᴜʀʀᴇɴᴛ ᴍᴇssᴀɢᴇ ʜᴀs ʙᴜᴛᴛᴏɴs ᴡʜɪᴄʜ ᴜsᴇ ᴜʀʟ ᴘʀᴏᴛᴏᴄᴏʟs ᴛʜᴀᴛ ᴀʀᴇ ᴜɴsᴜᴘᴘᴏʀᴛᴇᴅ ʙʏ " + "ᴛᴇʟᴇɢʀᴀᴍ. ᴘʟᴇᴀsᴇ ᴜᴘᴅᴀᴛᴇ. " + ) + ), + parse_mode=ParseMode.MARKDOWN, + reply_to_message_id=reply, + ) + + elif excp.message == "Wrong url host": + msg = update.effective_message.reply_text( + markdown_parser( + ( + backup_message + + "\nɴᴏᴛᴇ : ᴛʜᴇ ᴄᴜʀʀᴇɴᴛ ᴍᴇssᴀɢᴇ ʜᴀs sᴏᴍᴇ ʙᴀᴅ ᴜʀʟs. ᴘʟᴇᴀsᴇ ᴜᴘᴅᴀᴛᴇ." + ) + ), + parse_mode=ParseMode.MARKDOWN, + reply_to_message_id=reply, + ) + + log.warning(message) + log.warning(keyboard) + log.exception("ᴄᴏᴜʟᴅ ɴᴏᴛ ᴘᴀʀsᴇ! ɢᴏᴛ ɪɴᴠᴀʟɪᴅ ᴜʀʟ ʜᴏsᴛ ᴇʀʀᴏʀs") + else: + msg = update.effective_message.reply_text( + markdown_parser( + ( + backup_message + + "\nɴᴏᴛᴇ :: ᴀɴ ᴇʀʀᴏʀ ᴏᴄᴄᴜʀᴇᴅ ᴡʜᴇɴ sᴇɴᴅɪɴɢ ᴛʜᴇ ᴄᴜsᴛᴏᴍ ᴍᴇssᴀɢᴇ. ᴘʟᴇᴀsᴇ ᴜᴘᴅᴀᴛᴇ." + ) + ), + parse_mode=ParseMode.MARKDOWN, + reply_to_message_id=reply, + ) + + log.exception() + return msg + + +@loggable +def new_member(update: Update, context: CallbackContext): # sourcery no-metrics + bot, job_queue = context.bot, context.job_queue + chat = update.effective_chat + user = update.effective_user + msg = update.effective_message + log_setting = logsql.get_chat_setting(chat.id) + if not log_setting: + logsql.set_chat_setting( + logsql.LogChannelSettings(chat.id, True, True, True, True, True) + ) + log_setting = logsql.get_chat_setting(chat.id) + should_welc, cust_welcome, cust_content, welc_type = sql.get_welc_pref(chat.id) + welc_mutes = sql.welcome_mutes(chat.id) + human_checks = sql.get_human_checks(user.id, chat.id) + raid, _, deftime = sql.getRaidStatus(str(chat.id)) + + new_members = update.effective_message.new_chat_members + + for new_mem in new_members: + + welcome_log = None + res = None + sent = None + should_mute = True + welcome_bool = True + media_wel = False + + if raid and new_mem.id not in WHITELISTED: + bantime = deftime + try: + chat.ban_member(new_mem.id, until_date=bantime) + except BadRequest: + pass + return + + if sw != None: + sw_ban = sw.get_ban(new_mem.id) + if sw_ban: + return + + reply = update.message.message_id + cleanserv = sql.clean_service(chat.id) + # Clean service welcome + if cleanserv: + try: + dispatcher.bot.delete_message(chat.id, update.message.message_id) + except BadRequest: + pass + reply = False + + if should_welc: + + # Give the owner a special welcome + if new_mem.id == OWNER_ID: + update.effective_message.reply_text( + "ᴏɴɪᴄʜᴀɴ ᴀʀʀɪᴠᴇᴅ~ ᴍʏ ʜᴇᴀʀᴛ ɢᴏᴇs ᴍᴇᴏᴡ ᴍᴇᴏᴡ :3", + reply_to_message_id=reply, + ) + welcome_log = ( + f"{html.escape(chat.title)}\n" + f"#ᴜsᴇʀ_ᴊᴏɪɴᴇᴅ\n" + f"ʙᴏᴛ ᴏᴡɴᴇʀ ᴊᴜsᴛ ᴊᴏɪɴᴇᴅ ᴛʜᴇ ᴄʜᴀᴛ 🥰" + ) + continue + + # Welcome Devs + if new_mem.id in DEV_USERS: + update.effective_message.reply_text( + "ᴡʜᴏ ᴀ! ᴀ ᴍᴇᴍʙᴇʀ ᴏғ ᴍʏ ғᴀᴍɪʟʏ ᴊᴜsᴛ ᴊᴏɪɴᴇᴅ!", + reply_to_message_id=reply, + ) + continue + + # Welcome Sudos + elif new_mem.id in DRAGONS: + update.effective_message.reply_text( + "ʜᴜʜ! ᴍʏ ʙᴇsᴛғʀɪᴇɴᴅ ᴊᴜsᴛ ᴊᴏɪɴᴇᴅ! sᴛᴀʏ ᴀʟᴇʀᴛ !", + reply_to_message_id=reply, + ) + continue + + # Welcome Support + elif new_mem.id in DEMONS: + update.effective_message.reply_text( + "ʜᴜʜ! ɪs ᴛʜᴀᴛ ᴀ ғʀɪᴇɴᴅ <3", + reply_to_message_id=reply, + ) + continue + + # Welcome WOLVES + elif new_mem.id in WOLVES: + update.effective_message.reply_text( + "ᴏᴏғ! ᴍʏ ᴄʟᴀssᴍᴀᴛᴇ ᴊᴜsᴛ ᴊᴏɪɴᴇᴅ!", reply_to_message_id=reply + ) + continue + + # Welcome yourself + elif new_mem.id == bot.id: + update.effective_message.reply_text( + "ᴛʜᴀɴᴋs ғᴏʀ ᴀᴅᴅɪɴɢ ᴍᴇ! ᴄʜᴇᴄᴋᴏᴜᴛ @AbishnoiMF ғᴏʀ ᴍᴏʀᴇ.", + reply_to_message_id=reply, + ) + continue + + else: + buttons = sql.get_welc_buttons(chat.id) + keyb = build_keyboard(buttons) + + if welc_type not in (sql.Types.TEXT, sql.Types.BUTTON_TEXT): + media_wel = True + + first_name = ( + new_mem.first_name or "PersonWithNoName" + ) # edge case of empty name - occurs for some bugs. + + if cust_welcome: + if cust_welcome == sql.DEFAULT_WELCOME: + cust_welcome = random.choice( + sql.DEFAULT_WELCOME_MESSAGES + ).format(first=escape_markdown(first_name)) + + if new_mem.last_name: + fullname = escape_markdown(f"{first_name} {new_mem.last_name}") + else: + fullname = escape_markdown(first_name) + count = chat.get_member_count() + mention = mention_markdown(new_mem.id, escape_markdown(first_name)) + if new_mem.username: + username = "@" + escape_markdown(new_mem.username) + else: + username = mention + + valid_format = escape_invalid_curly_brackets( + cust_welcome, VALID_WELCOME_FORMATTERS + ) + res = valid_format.format( + first=escape_markdown(first_name), + last=escape_markdown(new_mem.last_name or first_name), + fullname=escape_markdown(fullname), + username=username, + mention=mention, + count=count, + chatname=escape_markdown(chat.title), + id=new_mem.id, + ) + + else: + res = random.choice(sql.DEFAULT_WELCOME_MESSAGES).format( + first=escape_markdown(first_name) + ) + keyb = [] + + backup_message = random.choice(sql.DEFAULT_WELCOME_MESSAGES).format( + first=escape_markdown(first_name) + ) + keyboard = InlineKeyboardMarkup(keyb) + + else: + welcome_bool = False + res = None + keyboard = None + backup_message = None + reply = None + + # User exceptions from welcomemutes + if ( + is_user_ban_protected(chat, new_mem.id, chat.get_member(new_mem.id)) + or human_checks + ): + should_mute = False + # Join welcome: soft mute + if new_mem.is_bot: + should_mute = False + + if user.id == new_mem.id and should_mute: + if welc_mutes == "soft": + bot.restrict_chat_member( + chat.id, + new_mem.id, + permissions=ChatPermissions( + can_send_messages=True, + can_send_media_messages=False, + can_send_other_messages=False, + can_invite_users=False, + can_pin_messages=False, + can_send_polls=False, + can_change_info=False, + can_add_web_page_previews=False, + ), + until_date=(int(time.time() + 24 * 60 * 60)), + ) + sql.set_human_checks(user.id, chat.id) + if welc_mutes == "strong": + welcome_bool = False + if not media_wel: + VERIFIED_USER_WAITLIST.update( + { + (chat.id, new_mem.id): { + "should_welc": should_welc, + "media_wel": False, + "status": False, + "update": update, + "res": res, + "keyboard": keyboard, + "backup_message": backup_message, + } + } + ) + else: + VERIFIED_USER_WAITLIST.update( + { + (chat.id, new_mem.id): { + "should_welc": should_welc, + "chat_id": chat.id, + "status": False, + "media_wel": True, + "cust_content": cust_content, + "welc_type": welc_type, + "res": res, + "keyboard": keyboard, + } + } + ) + new_join_mem = ( + f"[{escape_markdown(new_mem.first_name)}](tg://user?id={user.id})" + ) + message = msg.reply_text( + f"{new_join_mem}, ᴄʟɪᴄᴋ ᴛʜᴇ ʙᴜᴛᴛᴏɴ ʙᴇʟᴏᴡ ᴛᴏ ᴘʀᴏᴠᴇ ʏᴏᴜ'ʀᴇ ʜᴜᴍᴀɴ.\nʏᴏᴜ ʜᴀᴠᴇ 119 sᴇᴄᴏɴᴅs.", + reply_markup=InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + text="ʏᴇs, I'ᴍ ʜᴜᴍᴀɴ.", + callback_data=f"user_join_({new_mem.id})", + ) + ] + ] + ), + parse_mode=ParseMode.MARKDOWN, + reply_to_message_id=reply, + allow_sending_without_reply=True, + ) + bot.restrict_chat_member( + chat.id, + new_mem.id, + permissions=ChatPermissions( + can_send_messages=False, + can_invite_users=False, + can_pin_messages=False, + can_send_polls=False, + can_change_info=False, + can_send_media_messages=False, + can_send_other_messages=False, + can_add_web_page_previews=False, + ), + ) + job_queue.run_once( + partial(check_not_bot, new_mem, chat.id, message.message_id), + 120, + name="welcomemute", + ) + if welc_mutes == "captcha": + btn = [] + # Captcha image size number (2 -> 640x360) + CAPCTHA_SIZE_NUM = 2 + # Create Captcha Generator object of specified size + generator = CaptchaGenerator(CAPCTHA_SIZE_NUM) + + # Generate a captcha image + captcha = generator.gen_captcha_image(difficult_level=3) + # Get information + image = captcha["image"] + characters = captcha["characters"] + # print(characters) + fileobj = BytesIO() + fileobj.name = f"captcha_{new_mem.id}.png" + image.save(fp=fileobj) + fileobj.seek(0) + CAPTCHA_ANS_DICT[(chat.id, new_mem.id)] = int(characters) + welcome_bool = False + if not media_wel: + VERIFIED_USER_WAITLIST.update( + { + (chat.id, new_mem.id): { + "should_welc": should_welc, + "media_wel": False, + "status": False, + "update": update, + "res": res, + "keyboard": keyboard, + "backup_message": backup_message, + "captcha_correct": characters, + } + } + ) + else: + VERIFIED_USER_WAITLIST.update( + { + (chat.id, new_mem.id): { + "should_welc": should_welc, + "chat_id": chat.id, + "status": False, + "media_wel": True, + "cust_content": cust_content, + "welc_type": welc_type, + "res": res, + "keyboard": keyboard, + "captcha_correct": characters, + } + } + ) + + nums = [random.randint(1000, 9999) for _ in range(7)] + nums.append(characters) + random.shuffle(nums) + to_append = [] + # print(nums) + for a in nums: + to_append.append( + InlineKeyboardButton( + text=str(a), + callback_data=f"user_captchajoin_({chat.id},{new_mem.id})_({a})", + ) + ) + if len(to_append) > 2: + btn.append(to_append) + to_append = [] + if to_append: + btn.append(to_append) + + message = msg.reply_photo( + fileobj, + caption=f"ᴡᴇʟᴄᴏᴍᴇ [{escape_markdown(new_mem.first_name)}](tg://user?id={user.id}). ᴄʟɪᴄᴋ ᴛʜᴇ ᴄᴏʀʀᴇᴄᴛ ʙᴜᴛᴛᴏɴ ᴛᴏ ɢᴇᴛ ᴜɴᴍᴜᴛᴇᴅ!\n" + f"ʏᴏᴜ ɢᴏᴛ 119 sᴇᴄᴏɴᴅs ғᴏʀ ᴛʜɪs.", + reply_markup=InlineKeyboardMarkup(btn), + parse_mode=ParseMode.MARKDOWN, + reply_to_message_id=reply, + allow_sending_without_reply=True, + ) + bot.restrict_chat_member( + chat.id, + new_mem.id, + permissions=ChatPermissions( + can_send_messages=False, + can_invite_users=False, + can_pin_messages=False, + can_send_polls=False, + can_change_info=False, + can_send_media_messages=False, + can_send_other_messages=False, + can_add_web_page_previews=False, + ), + ) + job_queue.run_once( + partial(check_not_bot, new_mem, chat.id, message.message_id), + 120, + name="welcomemute", + ) + + if welcome_bool: + if media_wel: + if ENUM_FUNC_MAP[welc_type] == dispatcher.bot.send_sticker: + sent = ENUM_FUNC_MAP[welc_type]( + chat.id, + cust_content, + reply_markup=keyboard, + reply_to_message_id=reply, + ) + else: + sent = ENUM_FUNC_MAP[welc_type]( + chat.id, + cust_content, + caption=res, + reply_markup=keyboard, + reply_to_message_id=reply, + parse_mode="markdown", + ) + else: + sent = send(update, res, keyboard, backup_message) + prev_welc = sql.get_clean_pref(chat.id) + if prev_welc: + try: + bot.delete_message(chat.id, prev_welc) + except BadRequest: + pass + + if sent: + sql.set_clean_welcome(chat.id, sent.message_id) + + if not log_setting.log_joins: + return "" + if welcome_log: + return welcome_log + + return "" + + +def check_not_bot( + member: User, chat_id: int, message_id: int, context: CallbackContext +): + bot = context.bot + member_dict = VERIFIED_USER_WAITLIST.pop((chat_id, member.id)) + member_status = member_dict.get("status") + if not member_status: + try: + bot.unban_chat_member(chat_id, member.id) + except BadRequest: + pass + + try: + bot.edit_message_text( + "*ᴋɪᴄᴋs ᴜsᴇʀ*\nᴛʜᴇʏ ᴄᴀɴ ᴀʟᴡᴀʏs ʀᴇᴊᴏɪɴ ᴀɴᴅ ᴛʀʏ.", + chat_id=chat_id, + message_id=message_id, + ) + except TelegramError: + bot.delete_message(chat_id=chat_id, message_id=message_id) + bot.send_message( + "{} ᴡᴀs ᴋɪᴄᴋᴇᴅ ᴀs ᴛʜᴇʏ ғᴀɪʟᴇᴅ ᴛᴏ ᴠᴇʀɪғʏ ᴛʜᴇᴍsᴇʟᴠᴇs".format( + mention_html(member.id, member.first_name) + ), + chat_id=chat_id, + parse_mode=ParseMode.HTML, + ) + + +def left_member(update: Update, context: CallbackContext): # sourcery no-metrics + bot = context.bot + chat = update.effective_chat + user = update.effective_user + should_goodbye, cust_goodbye, goodbye_type = sql.get_gdbye_pref(chat.id) + + if user.id == bot.id: + return + + reply = update.message.message_id + cleanserv = sql.clean_service(chat.id) + # Clean service welcome + if cleanserv: + try: + dispatcher.bot.delete_message(chat.id, update.message.message_id) + except BadRequest: + pass + reply = False + + if should_goodbye: + + left_mem = update.effective_message.left_chat_member + if left_mem: + + # Thingy for spamwatched users + if sw: + sw_ban = sw.get_ban(left_mem.id) + if sw_ban: + return + + # Dont say goodbyes to gbanned users + if is_user_gbanned(left_mem.id): + return + + # Ignore bot being kicked + if left_mem.id == bot.id: + return + + # Give the owner a special goodbye + if left_mem.id == OWNER_ID: + update.effective_message.reply_text( + "sᴏʀʀʏ ᴛᴏ sᴇᴇ ʏᴏᴜ ʟᴇᴀᴠᴇ :(", reply_to_message_id=reply + ) + return + + # Give the devs a special goodbye + elif left_mem.id in DEV_USERS: + update.effective_message.reply_text( + "sᴇᴇ ʏᴏᴜ ʟᴀᴛᴇʀ ᴀᴛ ᴛʜᴇ ᴇᴀɢʟᴇ ᴜɴɪᴏɴ!", + reply_to_message_id=reply, + ) + return + + # if media goodbye, use appropriate function for it + if goodbye_type not in [sql.Types.TEXT, sql.Types.BUTTON_TEXT]: + ENUM_FUNC_MAP[goodbye_type](chat.id, cust_goodbye) + return + + first_name = ( + left_mem.first_name or "PersonWithNoName" + ) # edge case of empty name - occurs for some bugs. + if cust_goodbye: + if cust_goodbye == sql.DEFAULT_GOODBYE: + cust_goodbye = random.choice(sql.DEFAULT_GOODBYE_MESSAGES).format( + first=escape_markdown(first_name) + ) + if left_mem.last_name: + fullname = escape_markdown(f"{first_name} {left_mem.last_name}") + else: + fullname = escape_markdown(first_name) + count = chat.get_member_count() + mention = mention_markdown(left_mem.id, first_name) + if left_mem.username: + username = "@" + escape_markdown(left_mem.username) + else: + username = mention + + valid_format = escape_invalid_curly_brackets( + cust_goodbye, VALID_WELCOME_FORMATTERS + ) + res = valid_format.format( + first=escape_markdown(first_name), + last=escape_markdown(left_mem.last_name or first_name), + fullname=escape_markdown(fullname), + username=username, + mention=mention, + count=count, + chatname=escape_markdown(chat.title), + id=left_mem.id, + ) + buttons = sql.get_gdbye_buttons(chat.id) + keyb = build_keyboard(buttons) + + else: + res = random.choice(sql.DEFAULT_GOODBYE_MESSAGES).format( + first=first_name + ) + keyb = [] + + keyboard = InlineKeyboardMarkup(keyb) + + send( + update, + res, + keyboard, + random.choice(sql.DEFAULT_GOODBYE_MESSAGES).format(first=first_name), + ) + + +@u_admin +def welcome(update: Update, context: CallbackContext): + args = context.args + chat = update.effective_chat + # if no args, show current replies. + if not args or args[0].lower() == "noformat": + noformat = True + pref, welcome_m, cust_content, welcome_type = sql.get_welc_pref(chat.id) + update.effective_message.reply_text( + f"ᴛʜɪs ᴄʜᴀᴛ ʜᴀs ɪᴛ's ᴡᴇʟᴄᴏᴍᴇ sᴇᴛᴛɪɴɢ sᴇᴛ ᴛᴏ: `{pref}`.\n" + f"*ᴛʜᴇ ᴡᴇʟᴄᴏᴍᴇ ᴍᴇssᴀɢᴇ (not filling the {{}}) ɪs:*", + parse_mode=ParseMode.MARKDOWN, + ) + + if welcome_type in [sql.Types.BUTTON_TEXT, sql.Types.TEXT]: + buttons = sql.get_welc_buttons(chat.id) + if noformat: + welcome_m += revert_buttons(buttons) + update.effective_message.reply_text(welcome_m) + + else: + keyb = build_keyboard(buttons) + keyboard = InlineKeyboardMarkup(keyb) + + send(update, welcome_m, keyboard, sql.DEFAULT_WELCOME) + else: + buttons = sql.get_welc_buttons(chat.id) + if noformat: + welcome_m += revert_buttons(buttons) + ENUM_FUNC_MAP[welcome_type](chat.id, cust_content, caption=welcome_m) + + else: + keyb = build_keyboard(buttons) + keyboard = InlineKeyboardMarkup(keyb) + ENUM_FUNC_MAP[welcome_type]( + chat.id, + cust_content, + caption=welcome_m, + reply_markup=keyboard, + parse_mode=ParseMode.MARKDOWN, + disable_web_page_preview=True, + ) + + elif len(args) >= 1: + if args[0].lower() in ("on", "yes"): + sql.set_welc_preference(str(chat.id), True) + update.effective_message.reply_text( + "ᴏᴋᴀʏ! I'll ɢʀᴇᴇᴛ ᴍᴇᴍʙᴇʀs ᴡʜᴇɴ ᴛʜᴇʏ ᴊᴏɪɴ." + ) + + elif args[0].lower() in ("off", "no"): + sql.set_welc_preference(str(chat.id), False) + update.effective_message.reply_text( + "I'll ɢᴏ ʟᴏᴀғ ᴀʀᴏᴜɴᴅ ᴀɴᴅ ɴᴏᴛ ᴡᴇʟᴄᴏᴍᴇ ᴀɴʏᴏɴᴇ ᴛʜᴇɴ." + ) + + else: + update.effective_message.reply_text( + "I ᴜɴᴅᴇʀsᴛᴀɴᴅ 'on/yes' ᴏʀ 'off/no' ᴏɴʟʏ!" + ) + + +@u_admin +def goodbye(update: Update, context: CallbackContext): + args = context.args + chat = update.effective_chat + + if not args or args[0] == "noformat": + noformat = True + pref, goodbye_m, goodbye_type = sql.get_gdbye_pref(chat.id) + update.effective_message.reply_text( + f"ᴛʜɪs ᴄʜᴀᴛ ʜᴀs ɪᴛ's ɢᴏᴏᴅʙʏᴇ sᴇᴛᴛɪɴɢ sᴇᴛ ᴛᴏ: `{pref}`.\n" + f"*ᴛʜᴇ ɢᴏᴏᴅʙʏᴇ ᴍᴇssᴀɢᴇ (not filling the {{}}) ɪs:*", + parse_mode=ParseMode.MARKDOWN, + ) + + if goodbye_type == sql.Types.BUTTON_TEXT: + buttons = sql.get_gdbye_buttons(chat.id) + if noformat: + goodbye_m += revert_buttons(buttons) + update.effective_message.reply_text(goodbye_m) + + else: + keyb = build_keyboard(buttons) + keyboard = InlineKeyboardMarkup(keyb) + + send(update, goodbye_m, keyboard, sql.DEFAULT_GOODBYE) + + elif noformat: + ENUM_FUNC_MAP[goodbye_type](chat.id, goodbye_m) + + else: + ENUM_FUNC_MAP[goodbye_type]( + chat.id, goodbye_m, parse_mode=ParseMode.MARKDOWN + ) + + elif len(args) >= 1: + if args[0].lower() in ("on", "yes"): + sql.set_gdbye_preference(str(chat.id), True) + update.effective_message.reply_text("Ok!") + + elif args[0].lower() in ("off", "no"): + sql.set_gdbye_preference(str(chat.id), False) + update.effective_message.reply_text("Ok!") + + else: + # idek what you're writing, say yes or no + update.effective_message.reply_text( + "I ᴜɴᴅᴇʀsᴛᴀɴᴅ 'on/yes' ᴏʀ 'off/no' ᴏɴʟʏ!" + ) + + +@user_admin(AdminPerms.CAN_CHANGE_INFO) +@loggable +def set_welcome(update: Update, context: CallbackContext) -> str: + chat = update.effective_chat + user = update.effective_user + msg = update.effective_message + + text, data_type, content, buttons = get_welcome_type(msg) + + if data_type is None: + msg.reply_text("ʏᴏᴜ ᴅɪᴅɴ'ᴛ sᴘᴇᴄɪғʏ ᴡʜᴀᴛ ᴛᴏ ʀᴇᴘʟʏ ᴡɪᴛʜ!") + return "" + + sql.set_custom_welcome(chat.id, content, text, data_type, buttons) + msg.reply_text("sᴜᴄᴄᴇssғᴜʟʟʏ sᴇᴛ ᴄᴜsᴛᴏᴍ ᴡᴇʟᴄᴏᴍᴇ ᴍᴇssᴀɢᴇ!") + + return ( + f"<b>{html.escape(chat.title)}:</b>\n" + f"#sᴇᴛ_ᴡᴇʟᴄᴏᴍᴇ\n" + f"<b>ᴀᴅᴍɪɴ:</b> {mention_html(user.id, user.first_name)}\n" + f"sᴇᴛ ᴛʜᴇ welcome ᴍᴇssᴀɢᴇ." + ) + + +@user_admin(AdminPerms.CAN_CHANGE_INFO) +@loggable +def reset_welcome(update: Update, context: CallbackContext) -> str: + chat = update.effective_chat + user = update.effective_user + + sql.set_custom_welcome(chat.id, None, sql.DEFAULT_WELCOME, sql.Types.TEXT) + update.effective_message.reply_text( + "sᴜᴄᴄᴇssғᴜʟʟʏ ʀᴇsᴇᴛ ᴡᴇʟᴄᴏᴍᴇ ᴍᴇssᴀɢᴇ ᴛᴏ ᴅᴇғᴀᴜʟᴛ!" + ) + + return ( + f"<b>{html.escape(chat.title)}:</b>\n" + f"#ʀᴇsᴇᴛ_ᴡᴇʟᴄᴏᴍᴇ\n" + f"<b>ᴀᴅᴍɪɴ:</b> {mention_html(user.id, user.first_name)}\n" + f"ʀᴇsᴇᴛ ᴛʜᴇ ᴡᴇʟᴄᴏᴍᴇ ᴍᴇssᴀɢᴇ ᴛᴏ ᴅᴇғᴀᴜʟᴛ." + ) + + +@user_admin(AdminPerms.CAN_CHANGE_INFO) +@loggable +def set_goodbye(update: Update, context: CallbackContext) -> str: + chat = update.effective_chat + user = update.effective_user + msg = update.effective_message + text, data_type, content, buttons = get_welcome_type(msg) + + if data_type is None: + msg.reply_text("You ᴅɪᴅɴ'ᴛ sᴘᴇᴄɪғʏ ᴡʜᴀᴛ ᴛᴏ ʀᴇᴘʟʏ ᴡɪᴛʜ!") + return "" + + sql.set_custom_gdbye(chat.id, content or text, data_type, buttons) + msg.reply_text("sᴜᴄᴄᴇssғᴜʟʟʏ sᴇᴛ ᴄᴜsᴛᴏᴍ ɢᴏᴏᴅʙʏᴇ ᴍᴇssᴀɢᴇ!") + return ( + f"<b>{html.escape(chat.title)}:</b>\n" + f"#sᴇᴛ_ɢᴏᴏᴅʙʏᴇ\n" + f"<b>ᴀᴅᴍɪɴ:</b> {mention_html(user.id, user.first_name)}\n" + f"sᴇᴛ ᴛʜᴇ ɢᴏᴏᴅʙʏᴇ ᴍᴇssᴀɢᴇ." + ) + + +@user_admin(AdminPerms.CAN_CHANGE_INFO) +@loggable +def reset_goodbye(update: Update, context: CallbackContext) -> str: + chat = update.effective_chat + user = update.effective_user + + sql.set_custom_gdbye(chat.id, sql.DEFAULT_GOODBYE, sql.Types.TEXT) + update.effective_message.reply_text( + "sᴜᴄᴄᴇssғᴜʟʟʏ ʀᴇsᴇᴛ ɢᴏᴏᴅʙʏᴇ ᴍᴇssᴀɢᴇ ᴛᴏ ᴅᴇғᴀᴜʟᴛ!" + ) + + return ( + f"<b>{html.escape(chat.title)}:</b>\n" + f"#ʀᴇsᴇᴛ_ɢᴏᴏᴅʙʏᴇ\n" + f"<b>ᴀᴅᴍɪɴ:</b> {mention_html(user.id, user.first_name)}\n" + f"ʀᴇsᴇᴛ ᴛʜᴇ ɢᴏᴏᴅʙʏᴇ ᴍᴇssᴀɢᴇ." + ) + + +@user_admin(AdminPerms.CAN_CHANGE_INFO) +@loggable +def welcomemute(update: Update, context: CallbackContext) -> str: + args = context.args + chat = update.effective_chat + user = update.effective_user + msg = update.effective_message + + if len(args) >= 1: + if args[0].lower() in ("off", "no"): + sql.set_welcome_mutes(chat.id, False) + msg.reply_text("I ᴡɪʟʟ ɴᴏ ʟᴏɴɢᴇʀ ᴍᴜᴛᴇ ᴘᴇᴏᴘʟᴇ ᴏɴ ᴊᴏɪɴɪɴɢ!") + return ( + f"<b>{html.escape(chat.title)}:</b>\n" + f"#ᴡᴇʟᴄᴏᴍᴇ_ᴍᴜᴛᴇ\n" + f"<b>• ᴀᴅᴍɪɴ:</b> {mention_html(user.id, user.first_name)}\n" + f"ʜᴀs ᴛᴏɢɢʟᴇᴅ ᴡᴇʟᴄᴏᴍᴇ ᴍᴜᴛᴇ ᴛᴏ <b>ᴏғғ</b>." + ) + elif args[0].lower() in ["soft"]: + sql.set_welcome_mutes(chat.id, "soft") + msg.reply_text( + "I ᴡɪʟʟ ʀᴇsᴛʀɪᴄᴛ ᴜsᴇʀ's ᴘᴇʀᴍɪssɪᴏɴ ᴛᴏ sᴇɴᴅ ᴍᴇᴅɪᴀ ғᴏʀ 24 ʜᴏᴜʀs." + ) + return ( + f"<b>{html.escape(chat.title)}:</b>\n" + f"#ᴡᴇʟᴄᴏᴍᴇ_ᴍᴜᴛᴇ\n" + f"<b>• ᴀᴅᴍɪɴ:</b> {mention_html(user.id, user.first_name)}\n" + f"ʜᴀs ᴛᴏɢɢʟᴇᴅ ᴡᴇʟᴄᴏᴍᴇ ᴍᴜᴛᴇ ᴛᴏ <b>sᴏғᴛ</b>." + ) + elif args[0].lower() in ["strong"]: + sql.set_welcome_mutes(chat.id, "strong") + msg.reply_text( + "I ᴡɪʟʟ ɴᴏᴡ ᴍᴜᴛᴇ ᴘᴇᴏᴘʟᴇ ᴡʜᴇɴ ᴛʜᴇʏ ᴊᴏɪɴ ᴜɴᴛɪʟ ᴛʜᴇʏ ᴘʀᴏᴠᴇ ᴛʜᴇʏ'ʀᴇ ɴᴏᴛ ᴀ ʙᴏᴛ.\nᴛʜᴇʏ ᴡɪʟʟ ʜᴀᴠᴇ 119 sᴇᴄᴏɴᴅs " + "ʙᴇғᴏʀᴇ ᴛʜᴇʏ ɢᴇᴛ ᴋɪᴄᴋᴇᴅ. " + ) + return ( + f"<b>{html.escape(chat.title)}:</b>\n" + f"#ᴡᴇʟᴄᴏᴍᴇ_ᴍᴜᴛᴇ\n" + f"<b>• ᴀᴅᴍɪɴ:</b> {mention_html(user.id, user.first_name)}\n" + f"ʜᴀs ᴛᴏɢɢʟᴇᴅ ᴛᴏɢɢʟᴇᴅ ᴍᴜᴛᴇ ᴛᴏ <b>sᴛʀᴏɴɢ</b>." + ) + elif args[0].lower() in ["captcha"]: + sql.set_welcome_mutes(chat.id, "captcha") + msg.reply_text( + "I ᴡɪʟʟ ɴᴏᴡ ᴍᴜᴛᴇ ᴘᴇᴏᴘʟᴇ ᴡʜᴇɴ ᴛʜᴇʏ ᴊᴏɪɴ ᴜɴᴛɪʟ ᴛʜᴇʏ ᴘʀᴏᴠᴇ ᴛʜᴇʏ'ʀᴇ ɴᴏᴛ ᴀ ʙᴏᴛ.\n ᴛʜᴇʏ ʜᴀᴠᴇ ᴛᴏ sᴏʟᴠᴇ a " + "ᴄᴀᴘᴛᴄʜᴀ ᴛᴏ ɢᴇᴛ ᴜɴᴍᴜᴛᴇᴅ. " + ) + return ( + f"<b>{html.escape(chat.title)}:</b>\n" + f"#ᴡᴇʟᴄᴏᴍᴇ_ᴍᴜᴛᴇ\n" + f"<b>• ᴀᴅᴍɪɴ:</b> {mention_html(user.id, user.first_name)}\n" + f"ʜᴀs ᴛᴏɢɢʟᴇᴅ ᴡᴇʟᴄᴏᴍᴇ ᴍᴜᴛᴇ ᴛᴏ <b>ᴄᴀᴘᴛᴄʜᴀ</b>." + ) + else: + msg.reply_text( + "ᴘʟᴇᴀsᴇ ᴇɴᴛᴇʀ `off`/`no`/`soft`/`strong`/`captcha`!", + parse_mode=ParseMode.MARKDOWN, + ) + return "" + else: + curr_setting = sql.welcome_mutes(chat.id) + reply = ( + f"\n ɢɪᴠᴇ ᴍᴇ ᴀ sᴇᴛᴛɪɴɢ!\nᴄʜᴏᴏsᴇ ᴏɴᴇ ᴏᴜᴛ ᴏғ: `off`/`no` ᴏʀ `soft`, `strong` ᴏʀ `captcha` ᴏɴʟʏ! \n" + f"ᴄᴜʀʀᴇɴᴛ sᴇᴛᴛɪɴɢ: `{curr_setting}`" + ) + msg.reply_text(reply, parse_mode=ParseMode.MARKDOWN) + return "" + + +@user_admin(AdminPerms.CAN_CHANGE_INFO) +@loggable +def clean_welcome(update: Update, context: CallbackContext) -> str: + args = context.args + chat = update.effective_chat + user = update.effective_user + + if not args: + clean_pref = sql.get_clean_pref(chat.id) + if clean_pref: + update.effective_message.reply_text( + "I sʜᴏᴜʟᴅ ʙᴇ ᴅᴇʟᴇᴛɪɴɢ ᴡᴇʟᴄᴏᴍᴇ ᴍᴇssᴀɢᴇs ᴜᴘ ᴛᴏ ᴛᴡᴏ ᴅᴀʏs ᴏʟᴅ." + ) + else: + update.effective_message.reply_text( + "I'ᴍ ᴄᴜʀʀᴇɴᴛʟʏ not ᴅᴇʟᴇᴛɪɴɢ ᴏʟᴅ ᴡᴇʟᴄᴏᴍᴇ ᴍᴇssᴀɢᴇs!" + ) + return "" + + if args[0].lower() in ("on", "yes"): + sql.set_clean_welcome(str(chat.id), True) + update.effective_message.reply_text("I'ʟʟ ᴛʀʏ ᴛᴏ ᴅᴇʟᴇᴛᴇ ᴏʟᴅ ᴡᴇʟᴄᴏᴍᴇ ᴍᴇssᴀɢᴇs!") + return ( + f"<b>{html.escape(chat.title)}:</b>\n" + f"#ᴄʟᴇᴀɴ_ᴡᴇʟᴄᴏᴍᴇ\n" + f"<b>ᴀᴅᴍɪɴ:</b> {mention_html(user.id, user.first_name)}\n" + f"ʜᴀs ᴛᴏɢɢʟᴇᴅ ᴄʟᴇᴀɴ ᴡᴇʟᴄᴏᴍᴇs ᴛᴏ <code>ON</code>." + ) + elif args[0].lower() in ("off", "no"): + sql.set_clean_welcome(str(chat.id), False) + update.effective_message.reply_text("I ᴡᴏɴ'ᴛ ᴅᴇʟᴇᴛᴇ ᴏʟᴅ ᴡᴇʟᴄᴏᴍᴇ ᴍᴇssᴀɢᴇs.") + return ( + f"<b>{html.escape(chat.title)}:</b>\n" + f"#ᴄʟᴇᴀɴ_ᴡᴇʟᴄᴏᴍᴇ\n" + f"<b>ᴀᴅᴍɪɴ:</b> {mention_html(user.id, user.first_name)}\n" + f"ʜᴀs ᴛᴏɢɢʟᴇᴅ ᴄʟᴇᴀɴ ᴡᴇʟᴄᴏᴍᴇs ᴛᴏ <code>OFF</code>." + ) + else: + update.effective_message.reply_text("I ᴜɴᴅᴇʀsᴛᴀɴᴅ 'on/yes' ᴏʀ 'off/no' only!") + return "" + + +def user_button(update: Update, context: CallbackContext): + chat = update.effective_chat + user = update.effective_user + query = update.callback_query + bot = context.bot + match = re.match(r"user_join_\((.+?)\)", query.data) + message = update.effective_message + join_user = int(match.group(1)) + + if join_user == user.id: + sql.set_human_checks(user.id, chat.id) + member_dict = VERIFIED_USER_WAITLIST[(chat.id, user.id)] + member_dict["status"] = True + query.answer(text="ʏᴇᴇᴛ ! ʏᴏᴜ'ʀᴇ ᴀ ʜᴜᴍᴀɴ, ᴜɴᴍᴜᴛᴇᴅ!") + bot.restrict_chat_member( + chat.id, + user.id, + permissions=ChatPermissions( + can_send_messages=True, + can_invite_users=True, + can_pin_messages=True, + can_send_polls=True, + can_change_info=True, + can_send_media_messages=True, + can_send_other_messages=True, + can_add_web_page_previews=True, + ), + ) + try: + bot.deleteMessage(chat.id, message.message_id) + except: + pass + if member_dict["should_welc"]: + if member_dict["media_wel"]: + sent = ENUM_FUNC_MAP[member_dict["welc_type"]]( + member_dict["chat_id"], + member_dict["cust_content"], + caption=member_dict["res"], + reply_markup=member_dict["keyboard"], + parse_mode="markdown", + ) + else: + sent = send( + member_dict["update"], + member_dict["res"], + member_dict["keyboard"], + member_dict["backup_message"], + ) + + prev_welc = sql.get_clean_pref(chat.id) + if prev_welc: + try: + bot.delete_message(chat.id, prev_welc) + except BadRequest: + pass + + if sent: + sql.set_clean_welcome(chat.id, sent.message_id) + + else: + query.answer(text="ʏᴏᴜ'ʀᴇ ɴᴏᴛ ᴀʟʟᴏᴡᴇᴅ ᴛᴏ ᴅᴏ ᴛʜɪs!") + + +def user_captcha_button(update: Update, context: CallbackContext): + # sourcery no-metrics + chat = update.effective_chat + user = update.effective_user + query = update.callback_query + bot = context.bot + # print(query.data) + match = re.match(r"user_captchajoin_\(([\d\-]+),(\d+)\)_\((\d{4})\)", query.data) + message = update.effective_message + join_chat = int(match.group(1)) + join_user = int(match.group(2)) + captcha_ans = int(match.group(3)) + join_usr_data = bot.getChat(join_user) + + if join_user == user.id: + c_captcha_ans = CAPTCHA_ANS_DICT.pop((join_chat, join_user)) + if c_captcha_ans == captcha_ans: + sql.set_human_checks(user.id, chat.id) + member_dict = VERIFIED_USER_WAITLIST[(chat.id, user.id)] + member_dict["status"] = True + query.answer(text="ʏᴇᴇᴛ! ʏᴏᴜ'ʀᴇ a ʜᴜᴍᴀɴ, ᴜɴᴍᴜᴛᴇᴅ!") + bot.restrict_chat_member( + chat.id, + user.id, + permissions=ChatPermissions( + can_send_messages=True, + can_invite_users=True, + can_pin_messages=True, + can_send_polls=True, + can_change_info=True, + can_send_media_messages=True, + can_send_other_messages=True, + can_add_web_page_previews=True, + ), + ) + try: + bot.deleteMessage(chat.id, message.message_id) + except: + pass + if member_dict["should_welc"]: + if member_dict["media_wel"]: + sent = ENUM_FUNC_MAP[member_dict["welc_type"]]( + member_dict["chat_id"], + member_dict["cust_content"], + caption=member_dict["res"], + reply_markup=member_dict["keyboard"], + parse_mode="markdown", + ) + else: + sent = send( + member_dict["update"], + member_dict["res"], + member_dict["keyboard"], + member_dict["backup_message"], + ) + + prev_welc = sql.get_clean_pref(chat.id) + if prev_welc: + try: + bot.delete_message(chat.id, prev_welc) + except BadRequest: + pass + + if sent: + sql.set_clean_welcome(chat.id, sent.message_id) + else: + try: + bot.deleteMessage(chat.id, message.message_id) + except: + pass + kicked_msg = f""" + ❌ [{escape_markdown(join_usr_data.first_name)}](tg://user?id={join_user}) ғᴀɪʟᴇᴅ ᴛʜᴇ ᴄᴀᴘᴛᴄʜᴀ ᴀɴᴅ ᴡᴀs ᴋɪᴄᴋᴇᴅ. + """ + query.answer(text="ᴡʀᴏɴɢ ᴀɴsᴡᴇʀ") + res = chat.unban_member(join_user) + if res: + bot.sendMessage( + chat_id=chat.id, text=kicked_msg, parse_mode=ParseMode.MARKDOWN + ) + + else: + query.answer(text="ᴀʙᴇ ʟᴇᴠᴅᴇ ᴀᴘɴᴀ ᴋᴀᴍ ᴋᴀʀ ɴᴀ!") + + +WELC_HELP_TXT = ( + "Your group's welcome/goodbye messages can be personalised in multiple ways. If you want the messages" + " to be individually generated, like the default welcome message is, you can use *these* variables:\n" + " • `{first}`*:* this represents the user's *first* name\n" + " • `{last}`*:* this represents the user's *last* name. Defaults to *first name* if user has no " + "last name.\n" + " • `{fullname}`*:* this represents the user's *full* name. Defaults to *first name* if user has no " + "last name.\n" + " • `{username}`*:* this represents the user's *username*. Defaults to a *mention* of the user's " + "first name if has no username.\n" + " • `{mention}`*:* this simply *mentions* a user - tagging them with their first name.\n" + " • `{id}`*:* this represents the user's *id*\n" + " • `{count}`*:* this represents the user's *member number*.\n" + " • `{chatname}`*:* this represents the *current chat name*.\n" + "\nEach variable MUST be surrounded by `{}` to be replaced.\n" + "Welcome messages also support markdown, so you can make any elements bold/italic/code/links. " + "Buttons are also supported, so you can make your welcomes look awesome with some nice intro " + "buttons.\n" + f"To create a button linking to your rules, use this: `[Rules](buttonurl://t.me/{dispatcher.bot.username}?start=group_id)`. " + "Simply replace `group_id` with your group's id, which can be obtained via /id, and you're good to " + "go. Note that group ids are usually preceded by a `-` sign; this is required, so please don't " + "remove it.\n" + "You can even set images/gifs/videos/voice messages as the welcome message by " + "replying to the desired media, and calling `/setwelcome`." +) + +WELC_MUTE_HELP_TXT = ( + "You can get the bot to mute new people who join your group and hence prevent spambots from flooding your group. " + "The following options are possible:\n" + "• `/welcomemute soft`*:* restricts new members from sending media for 24 hours.\n" + "• `/welcomemute strong`*:* mutes new members till they tap on a button thereby verifying they're human.\n" + "• `/welcomemute captcha`*:* mutes new members till they solve a button captcha thereby verifying they're human.\n" + "• `/welcomemute off`*:* turns off welcomemute.\n" + "*Note:* Strong mode kicks a user from the chat if they dont verify in 120seconds. They can always rejoin though" +) + + +@u_admin +def welcome_help(update: Update, context: CallbackContext): + update.effective_message.reply_text(WELC_HELP_TXT, parse_mode=ParseMode.MARKDOWN) + + +@u_admin +def welcome_mute_help(update: Update, context: CallbackContext): + update.effective_message.reply_text( + WELC_MUTE_HELP_TXT, parse_mode=ParseMode.MARKDOWN + ) + + +# TODO: get welcome data from group butler snap +# def __import_data__(chat_id, data): +# welcome = data.get('info', {}).get('rules') +# welcome = welcome.replace('$username', '{username}') +# welcome = welcome.replace('$name', '{fullname}') +# welcome = welcome.replace('$id', '{id}') +# welcome = welcome.replace('$title', '{chatname}') +# welcome = welcome.replace('$surname', '{lastname}') +# welcome = welcome.replace('$rules', '{rules}') +# sql.set_custom_welcome(chat_id, welcome, sql.Types.TEXT) + + +def __migrate__(old_chat_id, new_chat_id): + sql.migrate_chat(old_chat_id, new_chat_id) + + +def __chat_settings__(chat_id, user_id): + welcome_pref = sql.get_welc_pref(chat_id)[0] + goodbye_pref = sql.get_gdbye_pref(chat_id)[0] + return ( + "ᴛʜɪs ᴄʜᴀᴛ ʜᴀs ɪᴛ's ᴡᴇʟᴄᴏᴍᴇ ᴘʀᴇғᴇʀᴇɴᴄᴇ sᴇᴛ ᴛᴏ `{}`.\n" + "It's ɢᴏᴏᴅʙʏᴇ ᴘʀᴇғᴇʀᴇɴᴄᴇ ɪs `{}`.".format(welcome_pref, goodbye_pref) + ) + + +__help__ = """ +*ᴀᴅᴍɪɴs ᴏɴʟʏ:* + +❂ /welcome <ᴏɴ/ᴏғғ>*:* `ᴇɴᴀʙʟᴇ/ᴅɪsᴀʙʟᴇ ᴡᴇʟᴄᴏᴍᴇ ᴍᴇssᴀɢᴇs.` + +❂ /welcome *:* `sʜᴏᴡs ᴄᴜʀʀᴇɴᴛ ᴡᴇʟᴄᴏᴍᴇ sᴇᴛᴛɪɴɢs.` + +❂ /welcome noformat *:* `sʜᴏᴡs ᴄᴜʀʀᴇɴᴛ ᴡᴇʟᴄᴏᴍᴇ sᴇᴛᴛɪɴɢs, ᴡɪᴛʜᴏᴜᴛ ᴛʜᴇ ғᴏʀᴍᴀᴛᴛɪɴɢ - ᴜsᴇғᴜʟ ᴛᴏ ʀᴇᴄʏᴄʟᴇ ʏᴏᴜʀ ᴡᴇʟᴄᴏᴍᴇ ᴍᴇssᴀɢᴇs!` + +❂ /goodbye *:* `sᴀᴍᴇ ᴜsᴀɢᴇ ᴀɴᴅ ᴀʀɢs ᴀs` `/ᴡᴇʟᴄᴏᴍᴇ`. + +❂ /setwelcome <sᴏᴍᴇᴛᴇxᴛ>*:* `sᴇᴛ ᴀ ᴄᴜsᴛᴏᴍ ᴡᴇʟᴄᴏᴍᴇ ᴍᴇssᴀɢᴇ. ɪғ ᴜsᴇᴅ ʀᴇᴘʟʏɪɴɢ ᴛᴏ ᴍᴇᴅɪᴀ, ᴜsᴇs ᴛʜᴀᴛ ᴍᴇᴅɪᴀ.` + +❂ /setgoodbye <sᴏᴍᴇᴛᴇxᴛ>*:* `sᴇᴛ ᴀ ᴄᴜsᴛᴏᴍ ɢᴏᴏᴅʙʏᴇ ᴍᴇssᴀɢᴇ. ɪғ ᴜsᴇᴅ ʀᴇᴘʟʏɪɴɢ ᴛᴏ ᴍᴇᴅɪᴀ, ᴜsᴇs ᴛʜᴀᴛ ᴍᴇᴅɪᴀ.` + +❂ /resetwelcome *:* `ʀᴇsᴇᴛ ᴛᴏ ᴛʜᴇ ᴅᴇғᴀᴜʟᴛ ᴡᴇʟᴄᴏᴍᴇ ᴍᴇssᴀɢᴇ.` + +❂ /resetgoodbye *:* `ʀᴇsᴇᴛ ᴛᴏ ᴛʜᴇ ᴅᴇғᴀᴜʟᴛ ɢᴏᴏᴅʙʏᴇ ᴍᴇssᴀɢᴇ.` + +❂ /cleanwelcome <ᴏɴ/ᴏғғ>*:* `ᴏɴ ɴᴇᴡ ᴍᴇᴍʙᴇʀ, ᴛʀʏ ᴛᴏ ᴅᴇʟᴇᴛᴇ ᴛʜᴇ ᴘʀᴇᴠɪᴏᴜs ᴡᴇʟᴄᴏᴍᴇ ᴍᴇssᴀɢᴇ ᴛᴏ ᴀᴠᴏɪᴅ sᴘᴀᴍᴍɪɴɢ ᴛʜᴇ ᴄʜᴀᴛ` + +❂ /welcomemutehelp *:* `ɢɪᴠᴇs ɪɴғᴏʀᴍᴀᴛɪᴏɴ ᴀʙᴏᴜᴛ ᴡᴇʟᴄᴏᴍᴇ ᴍᴜᴛᴇs.` + +❂ /cleanservice <ᴏɴ/ᴏғғ*:* `ᴅᴇʟᴇᴛᴇs ᴛᴇʟᴇɢʀᴀᴍs ᴡᴇʟᴄᴏᴍᴇ/ʟᴇғᴛ sᴇʀᴠɪᴄᴇ ᴍᴇssᴀɢᴇs.` + +""" + +NEW_MEM_HANDLER = MessageHandler( + Filters.status_update.new_chat_members, new_member, run_async=True +) +LEFT_MEM_HANDLER = MessageHandler( + Filters.status_update.left_chat_member, left_member, run_async=True +) +WELC_PREF_HANDLER = CommandHandler( + "welcome", welcome, filters=Filters.chat_type.groups, run_async=True +) +GOODBYE_PREF_HANDLER = CommandHandler( + "goodbye", goodbye, filters=Filters.chat_type.groups, run_async=True +) +SET_WELCOME = CommandHandler( + "setwelcome", set_welcome, filters=Filters.chat_type.groups, run_async=True +) +SET_GOODBYE = CommandHandler( + "setgoodbye", set_goodbye, filters=Filters.chat_type.groups, run_async=True +) +RESET_WELCOME = CommandHandler( + "resetwelcome", reset_welcome, filters=Filters.chat_type.groups, run_async=True +) +RESET_GOODBYE = CommandHandler( + "resetgoodbye", reset_goodbye, filters=Filters.chat_type.groups, run_async=True +) +WELCOMEMUTE_HANDLER = CommandHandler( + "welcomemute", welcomemute, filters=Filters.chat_type.groups, run_async=True +) +# CLEAN_SERVICE_HANDLER = CommandHandler( +# "cleanservice", cleanservice, filters=Filters.chat_type.groups, run_async=True +# ) +CLEAN_WELCOME = CommandHandler( + "cleanwelcome", clean_welcome, filters=Filters.chat_type.groups, run_async=True +) +WELCOME_HELP = CommandHandler("welcomehelp", welcome_help, run_async=True) +WELCOME_MUTE_HELP = CommandHandler("welcomemutehelp", welcome_mute_help, run_async=True) +BUTTON_VERIFY_HANDLER = CallbackQueryHandler( + user_button, pattern=r"user_join_", run_async=True +) +CAPTCHA_BUTTON_VERIFY_HANDLER = CallbackQueryHandler( + user_captcha_button, + pattern=r"user_captchajoin_\([\d\-]+,\d+\)_\(\d{4}\)", + run_async=True, +) + +dispatcher.add_handler(NEW_MEM_HANDLER) +dispatcher.add_handler(LEFT_MEM_HANDLER) +dispatcher.add_handler(WELC_PREF_HANDLER) +dispatcher.add_handler(GOODBYE_PREF_HANDLER) +dispatcher.add_handler(SET_WELCOME) +dispatcher.add_handler(SET_GOODBYE) +dispatcher.add_handler(RESET_WELCOME) +dispatcher.add_handler(RESET_GOODBYE) +dispatcher.add_handler(CLEAN_WELCOME) +dispatcher.add_handler(WELCOME_HELP) +dispatcher.add_handler(WELCOMEMUTE_HANDLER) +# dispatcher.add_handler(CLEAN_SERVICE_HANDLER) +dispatcher.add_handler(BUTTON_VERIFY_HANDLER) +dispatcher.add_handler(WELCOME_MUTE_HELP) +dispatcher.add_handler(CAPTCHA_BUTTON_VERIFY_HANDLER) + +__mod_name__ = "𝚆ᴇʟᴄᴏᴍᴇ" +__command_list__ = [] +__handlers__ = [ + NEW_MEM_HANDLER, + LEFT_MEM_HANDLER, + WELC_PREF_HANDLER, + GOODBYE_PREF_HANDLER, + SET_WELCOME, + SET_GOODBYE, + RESET_WELCOME, + RESET_GOODBYE, + CLEAN_WELCOME, + WELCOME_HELP, + WELCOMEMUTE_HANDLER, + # CLEAN_SERVICE_HANDLER, + BUTTON_VERIFY_HANDLER, + CAPTCHA_BUTTON_VERIFY_HANDLER, + WELCOME_MUTE_HELP, +] diff --git a/Exon/modules/wiki.py b/Exon/modules/wiki.py new file mode 100644 index 00000000..daf8a3ea --- /dev/null +++ b/Exon/modules/wiki.py @@ -0,0 +1,79 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import wikipedia +from telegram import ParseMode, Update +from telegram.ext import CallbackContext +from wikipedia.exceptions import DisambiguationError, PageError + +from Exon import dispatcher +from Exon.modules.disable import DisableAbleCommandHandler + + +def wiki(update: Update, context: CallbackContext): + msg = update.effective_message.reply_to_message or update.effective_message + res = "" + if msg == update.effective_message: + search = msg.text.split(" ", maxsplit=1)[1] + else: + search = msg.text + try: + res = wikipedia.summary(search) + except DisambiguationError as e: + update.message.reply_text( + "ᴅɪsᴀᴍʙɪɢᴜᴀᴛᴇᴅ ᴘᴀɢᴇs ғᴏᴜɴᴅ! ᴀᴅᴊᴜsᴛ ʏᴏᴜʀ ǫᴜᴇʀʏ ᴀᴄᴄᴏʀᴅɪɴɢʟʏ.\n<i>{}</i>".format( + e, + ), + parse_mode=ParseMode.HTML, + ) + except PageError as e: + update.message.reply_text( + "<code>{}</code>".format(e), + parse_mode=ParseMode.HTML, + ) + if res: + result = f"<b>{search}</b>\n\n" + result += f"<i>{res}</i>\n" + result += f"""<a href="https://en.wikipedia.org/wiki/{search.replace(" ", "%20")}">ʀᴇᴀᴅ ᴍᴏʀᴇ...</a>""" + if len(result) > 4000: + with open("result.txt", "w") as f: + f.write(f"{result}\n\nUwU OwO OmO UmU") + with open("result.txt", "rb") as f: + context.bot.send_document( + document=f, + filename=f.name, + reply_to_message_id=update.message.message_id, + chat_id=update.effective_chat.id, + parse_mode=ParseMode.HTML, + ) + else: + update.message.reply_text( + result, + parse_mode=ParseMode.HTML, + disable_web_page_preview=True, + ) + + +WIKI_HANDLER = DisableAbleCommandHandler("wiki", wiki, run_async=True) +dispatcher.add_handler(WIKI_HANDLER) diff --git a/Exon/modules/write.py b/Exon/modules/write.py new file mode 100644 index 00000000..8037d2d9 --- /dev/null +++ b/Exon/modules/write.py @@ -0,0 +1,35 @@ +from pyrogram import filters +from pyrogram.types import Message + +from Exon import pgram + + +@pgram.on_message(filters.command("write")) +async def handwrite(_, message: Message): + if not message.reply_to_message: + name = ( + message.text.split(None, 1)[1] + if len(message.command) < 3 + else message.text.split(None, 1)[1].replace(" ", "%20") + ) + m = await pgram.send_message(message.chat.id, "waito...") + photo = "https://apis.xditya.me/write?text=" + name + await pgram.send_photo(message.chat.id, photo=photo) + await m.delete() + else: + lol = message.reply_to_message.text + name = lol.split(None, 0)[0].replace(" ", "%20") + m = await pgram.send_message(message.chat.id, "waito..") + photo = "https://apis.xditya.me/write?text=" + name + await pgram.send_photo(message.chat.id, photo=photo) + await m.delete() + + +__mod_name__ = "𝚆ʀɪᴛᴇ" + +__help__ = """ + +ᴡʀɪᴛᴇs ᴛʜᴇ ɢɪᴠᴇɴ ᴛᴇxᴛ ᴏɴ ᴡʜɪᴛᴇ ᴘᴀɢᴇ ᴡɪᴛʜ ᴀ ᴘᴇɴ 🖊 + +/write <text> *:*` ᴡʀɪᴛᴇs ᴛʜᴇ ɢɪᴠᴇɴ ᴛᴇxᴛ `. + """ diff --git a/Exon/modules/ytmusic.py b/Exon/modules/ytmusic.py new file mode 100644 index 00000000..a6a4b7e5 --- /dev/null +++ b/Exon/modules/ytmusic.py @@ -0,0 +1,148 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import datetime +import os +from asyncio import get_running_loop +from functools import partial +from io import BytesIO + +from pyrogram import filters +from pytube import YouTube +from requests import get + +from Exon import aiohttpsession as session +from Exon import arq, pgram +from Exon.core.decorators.errors import capture_err +from Exon.utils.pastebin import paste + +is_downloading = False + + +def download_youtube_audio(arq_resp): + r = arq_resp.result[0] + + title = r.title + performer = r.channel + + m, s = r.duration.split(":") + duration = int(datetime.timedelta(minutes=int(m), seconds=int(s)).total_seconds()) + + if duration > 1800: + return + + thumb = get(r.thumbnails[0]).content + with open("thumbnail.png", "wb") as f: + f.write(thumb) + thumbnail_file = "thumbnail.png" + + url = f"https://youtube.com{r.url_suffix}" + yt = YouTube(url) + audio = yt.streams.filter(only_audio=True).get_audio_only() + + out_file = audio.download() + base, _ = os.path.splitext(out_file) + audio_file = base + ".mp3" + os.rename(out_file, audio_file) + + return [title, performer, duration, audio_file, thumbnail_file] + + +@pgram.on_message(filters.command("song") & ~filters.edited) +@capture_err +async def music(_, message): + global is_downloading + if len(message.command) < 2: + return await message.reply_text("/song ɴᴇᴇᴅs a ǫᴜᴇʀʏ ᴀs ᴀʀɢᴜᴍᴇɴᴛ") + + url = message.text.split(None, 1)[1] + if is_downloading: + return await message.reply_text( + "ᴀɴᴏᴛʜᴇʀ ᴅᴏᴡɴʟᴏᴀᴅ ɪs ɪɴ ᴘʀᴏɢʀᴇss, ᴛʀʏ ᴀɢᴀɪɴ ᴀғᴛᴇʀ sᴏᴍᴇᴛɪᴍᴇ." + ) + is_downloading = True + m = await message.reply_text(f"ᴅᴏᴡɴʟᴏᴀᴅɪɴɢ.. {url}", disable_web_page_preview=True) + try: + loop = get_running_loop() + arq_resp = await arq.youtube(url) + music = await loop.run_in_executor( + None, partial(download_youtube_audio, arq_resp) + ) + + if not music: + return await message.reply_text("[ᴇʀʀᴏʀ]: ᴍᴜsɪᴄ ᴛᴏᴏ ʟᴏɴɢ") + ( + title, + performer, + duration, + audio_file, + thumbnail_file, + ) = music + except Exception as e: + is_downloading = False + return await m.edit(str(e)) + await message.reply_audio( + audio_file, + duration=duration, + performer=performer, + title=title, + thumb=thumbnail_file, + ) + await m.delete() + os.remove(audio_file) + os.remove(thumbnail_file) + is_downloading = False + + +# Funtion To Download Song +async def download_song(url): + async with session.get(url) as resp: + song = await resp.read() + song = BytesIO(song) + song.name = "a.mp3" + return song + + +@pgram.on_message(filters.command("lyrics") & ~filters.edited) +async def lyrics_func(_, message): + if len(message.command) < 2: + return await message.reply_text("**ᴜsᴀɢᴇ:**\n/lyrics [QUERY]") + m = await message.reply_text("**sᴇᴀʀᴄʜɪɴɢ**") + query = message.text.strip().split(None, 1)[1] + + resp = await arq.lyrics(query) + + if not (resp.ok and resp.result): + return await m.edit("ɴᴏ ʟʏʀɪᴄs ғᴏᴜɴᴅ.") + + song = resp.result[0] + song_name = song["song"] + artist = song["artist"] + lyrics = song["lyrics"] + msg = f"**{song_name}** | **{artist}**\n\n__{lyrics}__" + + if len(msg) > 4095: + msg = await paste(msg) + msg = f"**ʟʏʀɪᴄs_ᴛᴏᴏ_ʟᴏɴɢ:** [ᴜʀʟ]({msg})" + return await m.edit(msg) diff --git a/Exon/modules/zip.py b/Exon/modules/zip.py new file mode 100644 index 00000000..69973128 --- /dev/null +++ b/Exon/modules/zip.py @@ -0,0 +1,219 @@ +import os +import time +import zipfile + +from telethon import types +from telethon.tl import functions + +from Exon import TEMP_DOWNLOAD_DIRECTORY +from Exon import telethn as client +from Exon.events import register + + +async def is_register_admin(chat, user): + if isinstance(chat, (types.InputPeerChannel, types.InputChannel)): + + return isinstance( + ( + await client(functions.channels.GetParticipantRequest(chat, user)) + ).participant, + (types.ChannelParticipantAdmin, types.ChannelParticipantCreator), + ) + if isinstance(chat, types.InputPeerChat): + + ui = await client.get_peer_id(user) + ps = ( + await client(functions.messages.GetFullChatRequest(chat.chat_id)) + ).full_chat.participants.participants + return isinstance( + next((p for p in ps if p.user_id == ui), None), + (types.ChatParticipantAdmin, types.ChatParticipantCreator), + ) + return None + + +@register(pattern="^/zip") +async def _(event): + if event.fwd_from: + return + + if not event.is_reply: + await event.reply("ʀᴇᴘʟʏ ᴛᴏ a ғɪʟᴇ to ᴄᴏᴍᴘʀᴇss ɪᴛ.") + return + if event.is_group: + if not (await is_register_admin(event.input_chat, event.message.sender_id)): + await event.reply( + "Hey, ʏᴏᴜ ᴀʀᴇ ɴᴏᴛ ᴀᴅᴍɪɴ. ʏᴏᴜ ᴄᴀɴ'ᴛ ᴜsᴇ ᴛʜɪs ᴄᴏᴍᴍᴀɴᴅ, ʙᴜᴛ ʏᴏᴜ ᴄᴀɴ ᴜsᴇ ɪɴ ᴍʏ ᴅᴍ🙂" + ) + return + + mone = await event.reply("⏳.") + if not os.path.isdir(TEMP_DOWNLOAD_DIRECTORY): + os.makedirs(TEMP_DOWNLOAD_DIRECTORY) + if event.reply_to_msg_id: + reply_message = await event.get_reply_message() + try: + time.time() + downloaded_file_name = await event.client.download_media( + reply_message, TEMP_DOWNLOAD_DIRECTORY + ) + directory_name = downloaded_file_name + except Exception as e: # pylint:disable=C0103,W0703 + await mone.reply(str(e)) + zipfile.ZipFile(directory_name + ".zip", "w", zipfile.ZIP_DEFLATED).write( + directory_name + ) + await event.client.send_file( + event.chat_id, + directory_name + ".zip", + force_document=True, + allow_cache=False, + reply_to=event.message.id, + ) + + +def zipdir(path, ziph): + # ziph is zipfile handle + for root, dirs, files in os.walk(path): + for file in files: + ziph.write(os.path.join(root, file)) + os.remove(os.path.join(root, file)) + + +from datetime import datetime + +from hachoir.metadata import extractMetadata +from hachoir.parser import createParser +from telethon.tl.types import DocumentAttributeVideo + +extracted = TEMP_DOWNLOAD_DIRECTORY + "extracted/" +thumb_image_path = TEMP_DOWNLOAD_DIRECTORY + "/thumb_image.jpg" +if not os.path.isdir(extracted): + os.makedirs(extracted) + + +async def is_register_admin(chat, user): + if isinstance(chat, (types.InputPeerChannel, types.InputChannel)): + + return isinstance( + ( + await client(functions.channels.GetParticipantRequest(chat, user)) + ).participant, + (types.ChannelParticipantAdmin, types.ChannelParticipantCreator), + ) + if isinstance(chat, types.InputPeerChat): + + ui = await client.get_peer_id(user) + ps = ( + await client(functions.messages.GetFullChatRequest(chat.chat_id)) + ).full_chat.participants.participants + return isinstance( + next((p for p in ps if p.user_id == ui), None), + (types.ChatParticipantAdmin, types.ChatParticipantCreator), + ) + return None + + +@register(pattern="^/unzip") +async def _(event): + if event.fwd_from: + return + + if not event.is_reply: + await event.reply("ʀᴇᴘʟʏ ᴛᴏ ᴀ ᴢɪᴘ ғɪʟᴇ.") + return + if event.is_group: + if not (await is_register_admin(event.input_chat, event.message.sender_id)): + await event.reply( + "ʜᴇʏ, ʏᴏᴜ ᴀʀᴇ ɴᴏᴛ ᴀᴅᴍɪɴ. ʏᴏᴜ ᴄᴀɴ'ᴛ ᴜsᴇ ᴛʜɪs ᴄᴏᴍᴍᴀɴᴅ, ʙᴜᴛ ʏᴏᴜ ᴄᴀɴ ᴜsᴇ ɪɴ ᴍʏ ᴅᴍ 🙂" + ) + return + + mone = await event.reply("ᴘʀᴏᴄᴇssɪɴɢ...") + if not os.path.isdir(TEMP_DOWNLOAD_DIRECTORY): + os.makedirs(TEMP_DOWNLOAD_DIRECTORY) + if event.reply_to_msg_id: + start = datetime.now() + reply_message = await event.get_reply_message() + try: + time.time() + downloaded_file_name = await client.download_media( + reply_message, TEMP_DOWNLOAD_DIRECTORY + ) + except Exception as e: + await mone.reply(str(e)) + else: + end = datetime.now() + (end - start).seconds + + with zipfile.ZipFile(downloaded_file_name, "r") as zip_ref: + zip_ref.extractall(extracted) + filename = sorted(get_lst_of_files(extracted, [])) + await event.reply("ᴜɴᴢɪᴘᴘɪɴɢ ɴᴏᴡ 😌") + for single_file in filename: + if os.path.exists(single_file): + caption_rts = os.path.basename(single_file) + force_document = True + supports_streaming = False + document_attributes = [] + if single_file.endswith((".mp4", ".mp3", ".flac", ".webm")): + metadata = extractMetadata(createParser(single_file)) + duration = 0 + width = 0 + height = 0 + if metadata.has("duration"): + duration = metadata.get("duration").seconds + if os.path.exists(thumb_image_path): + metadata = extractMetadata(createParser(thumb_image_path)) + if metadata.has("width"): + width = metadata.get("width") + if metadata.has("height"): + height = metadata.get("height") + document_attributes = [ + DocumentAttributeVideo( + duration=duration, + w=width, + h=height, + round_message=False, + supports_streaming=True, + ) + ] + try: + await client.send_file( + event.chat_id, + single_file, + force_document=force_document, + supports_streaming=supports_streaming, + allow_cache=False, + reply_to=event.message.id, + attributes=document_attributes, + ) + except Exception as e: + await client.send_message( + event.chat_id, + "{} ᴄᴀᴜsᴇᴅ `{}`".format(caption_rts, str(e)), + reply_to=event.message.id, + ) + continue + os.remove(single_file) + os.remove(downloaded_file_name) + + +def get_lst_of_files(input_directory, output_lst): + filesinfolder = os.listdir(input_directory) + for file_name in filesinfolder: + current_file_name = os.path.join(input_directory, file_name) + if os.path.isdir(current_file_name): + return get_lst_of_files(current_file_name, output_lst) + output_lst.append(current_file_name) + return output_lst + + +__help__ = """ + + • /zip*:* `ʀᴇᴘʟʏ ᴛᴏ ᴀ ᴛᴇʟᴇɢʀᴀᴍ ғɪʟᴇ ᴛᴏ ᴄᴏᴍᴘʀᴇss ɪᴛ ɪɴ .ᴢɪᴘ ғᴏʀᴍᴀᴛ ` + + • /unzip*:* `ʀᴇᴘʟʏ ᴛᴏ ᴀ ᴛᴇʟᴇɢʀᴀᴍ ғɪʟᴇ ᴛᴏ ᴅᴇᴄᴏᴍᴘʀᴇss ɪᴛ ғʀᴏᴍ ᴛʜᴇ .ᴢɪᴘ ғᴏʀᴍᴀᴛ ` +""" + +__mod_name__ = "𝚉ɪᴘᴘɪɴɢ" diff --git a/Exon/modules/zombies.py b/Exon/modules/zombies.py new file mode 100644 index 00000000..1e7a12d5 --- /dev/null +++ b/Exon/modules/zombies.py @@ -0,0 +1,107 @@ +from asyncio import sleep + +from telethon import events +from telethon.errors import ChatAdminRequiredError, UserAdminInvalidError +from telethon.tl.functions.channels import EditBannedRequest +from telethon.tl.types import ChannelParticipantsAdmins, ChatBannedRights + +from Exon import DEMONS, DEV_USERS, DRAGONS, OWNER_ID, telethn + +BANNED_RIGHTS = ChatBannedRights( + until_date=None, + view_messages=True, + send_messages=True, + send_media=True, + send_stickers=True, + send_gifs=True, + send_games=True, + send_inline=True, + embed_links=True, +) + + +UNBAN_RIGHTS = ChatBannedRights( + until_date=None, + send_messages=None, + send_media=None, + send_stickers=None, + send_gifs=None, + send_games=None, + send_inline=None, + embed_links=None, +) + +ONICHAN = [OWNER_ID] + DEV_USERS + DRAGONS + DEMONS + +# Check if user has admin rights + + +async def is_administrator(user_id: int, message): + admin = False + async for user in telethn.iter_participants( + message.chat_id, filter=ChannelParticipantsAdmins + ): + if user_id == user.id or user_id in ONICHAN: + admin = True + break + return admin + + +@telethn.on(events.NewMessage(pattern="^[!/]zombies ?(.*)")) +async def rm_deletedacc(show): + con = show.pattern_match.group(1).lower() + del_u = 0 + del_status = "**ɢʀᴏᴜᴘ ɪs ᴄʟᴇᴀɴ, 0 ᴅᴇʟᴇᴛᴇᴅ ᴀᴄᴄᴏᴜɴᴛs ғᴏᴜɴᴅ.**" + if con != "clean": + kontol = await show.reply("`sᴇᴀʀᴄʜɪɴɢ ғᴏʀ ᴅᴇʟᴇᴛᴇᴅ ᴀᴄᴄᴏᴜɴᴛs...`") + async for user in show.client.iter_participants(show.chat_id): + if user.deleted: + del_u += 1 + await sleep(1) + if del_u > 0: + del_status = ( + f"**sᴇᴀʀᴄʜɪɴɢ...** `{del_u}` **ᴅᴇʟᴇᴛᴇᴅ ᴀᴄᴄᴏᴜɴᴛ/ᴢᴏᴍʙɪᴇ ɪɴ ᴛʜɪs ɢʀᴏᴜᴘ," + "\nᴄʟᴇᴀɴ ɪᴛ ᴡɪᴛʜ ᴄᴏᴍᴍᴀɴᴅ** `/zombies clean`" + ) + return await kontol.edit(del_status) + chat = await show.get_chat() + admin = chat.admin_rights + creator = chat.creator + if not admin and not creator: + return await show.reply("**sᴏʀʀʏ ʏᴏᴜ'ʀᴇ ɴᴏᴛ ᴀᴅᴍɪɴ!**") + memek = await show.reply("`ʙᴀɴɴɪɴɢ ᴅᴇʟᴇᴛᴇᴅ ᴀᴄᴄᴏᴜɴᴛs...`") + del_u = 0 + del_a = 0 + async for user in telethn.iter_participants(show.chat_id): + if user.deleted: + try: + await show.client( + EditBannedRequest(show.chat_id, user.id, BANNED_RIGHTS) + ) + except ChatAdminRequiredError: + return await show.edit("`I ᴅᴏɴ'ᴛ ʜᴀᴠᴇ ʙᴀɴ ʀɪɢʜᴛs ɪɴ ᴛʜɪs ɢʀᴏᴜᴘ`") + except UserAdminInvalidError: + del_u -= 1 + del_a += 1 + await telethn(EditBannedRequest(show.chat_id, user.id, UNBAN_RIGHTS)) + del_u += 1 + if del_u > 0: + del_status = f"**Cleaned** `{del_u}` **ᴢᴏᴍʙɪᴇs**" + if del_a > 0: + del_status = ( + f"**ᴢᴏᴍʙɪᴇs** `{del_u}` **ᴢᴏᴍʙɪᴇs** " + f"\n`{del_a}` **ᴀᴅᴍɪɴ ᴢᴏᴍʙɪᴇs ɴᴏᴛ ᴅᴇʟᴇᴛᴇᴅ.**" + ) + await memek.edit(del_status) + + +__help__ = """ +*ʀᴇᴍᴏᴠᴇ ᴅᴇʟᴇᴛᴇᴅ ᴀᴄᴄᴏᴜɴᴛs* + +• /zombies *:* ` sᴛᴀʀᴛs sᴇᴀʀᴄʜɪɴɢ ғᴏʀ ᴅᴇʟᴇᴛᴇᴅ ᴀᴄᴄᴏᴜɴᴛs ɪɴ ᴛʜᴇ ɢʀᴏᴜᴘ ` +. +• /zombies clean *:* `ʀᴇᴍᴏᴠᴇ ᴛʜᴇ ᴅᴇʟᴇᴛᴇᴅ ᴀᴄᴄᴏᴜɴᴛs ғʀᴏᴍ ᴛʜᴇ ɢʀᴏᴜᴘ `. +""" + + +__mod_name__ = "𝚉ᴏᴍʙɪᴇs" diff --git "a/Exon/resources/ABISHNOI COPYRIGHT \302\251" "b/Exon/resources/ABISHNOI COPYRIGHT \302\251" new file mode 100644 index 00000000..a7ec3b8e --- /dev/null +++ "b/Exon/resources/ABISHNOI COPYRIGHT \302\251" @@ -0,0 +1,27 @@ +""" +MIT License + +Copyright (c) 2022 ABISHNOI + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" +# ""DEAR PRO PEOPLE, DON'T REMOVE & CHANGE THIS LINE +# TG :- @Abishnoi +# MY ALL BOTS :- Abishnoi_bots +# GITHUB :- KingAbishnoi "" \ No newline at end of file diff --git a/Exon/resources/ArmWrestler.ttf b/Exon/resources/ArmWrestler.ttf new file mode 100644 index 00000000..1b35c66b Binary files /dev/null and b/Exon/resources/ArmWrestler.ttf differ diff --git a/Exon/resources/Asau.ttf b/Exon/resources/Asau.ttf new file mode 100644 index 00000000..a440aba2 Binary files /dev/null and b/Exon/resources/Asau.ttf differ diff --git a/Exon/resources/TheConfessionFullRegular-8qGz.ttf b/Exon/resources/TheConfessionFullRegular-8qGz.ttf new file mode 100644 index 00000000..78e95909 Binary files /dev/null and b/Exon/resources/TheConfessionFullRegular-8qGz.ttf differ diff --git a/Exon/resources/Vampire_Wars.ttf b/Exon/resources/Vampire_Wars.ttf new file mode 100644 index 00000000..ebc0b658 Binary files /dev/null and b/Exon/resources/Vampire_Wars.ttf differ diff --git a/Exon/resources/default.ttf b/Exon/resources/default.ttf new file mode 100644 index 00000000..114e6c19 Binary files /dev/null and b/Exon/resources/default.ttf differ diff --git "a/Exon/utils/ABISHNOI COPYRIGHT \302\251" "b/Exon/utils/ABISHNOI COPYRIGHT \302\251" new file mode 100644 index 00000000..a7ec3b8e --- /dev/null +++ "b/Exon/utils/ABISHNOI COPYRIGHT \302\251" @@ -0,0 +1,27 @@ +""" +MIT License + +Copyright (c) 2022 ABISHNOI + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" +# ""DEAR PRO PEOPLE, DON'T REMOVE & CHANGE THIS LINE +# TG :- @Abishnoi +# MY ALL BOTS :- Abishnoi_bots +# GITHUB :- KingAbishnoi "" \ No newline at end of file diff --git "a/Exon/utils/Logo/ABISHNOI COPYRIGHT \302\251" "b/Exon/utils/Logo/ABISHNOI COPYRIGHT \302\251" new file mode 100644 index 00000000..a7ec3b8e --- /dev/null +++ "b/Exon/utils/Logo/ABISHNOI COPYRIGHT \302\251" @@ -0,0 +1,27 @@ +""" +MIT License + +Copyright (c) 2022 ABISHNOI + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" +# ""DEAR PRO PEOPLE, DON'T REMOVE & CHANGE THIS LINE +# TG :- @Abishnoi +# MY ALL BOTS :- Abishnoi_bots +# GITHUB :- KingAbishnoi "" \ No newline at end of file diff --git a/Exon/utils/Logo/Another Danger - Demo.otf b/Exon/utils/Logo/Another Danger - Demo.otf new file mode 100644 index 00000000..d99830a1 Binary files /dev/null and b/Exon/utils/Logo/Another Danger - Demo.otf differ diff --git a/Exon/utils/Logo/Another Danger Slanted - Demo.otf b/Exon/utils/Logo/Another Danger Slanted - Demo.otf new file mode 100644 index 00000000..577514a5 Binary files /dev/null and b/Exon/utils/Logo/Another Danger Slanted - Demo.otf differ diff --git a/Exon/utils/Logo/Chopsic.otf b/Exon/utils/Logo/Chopsic.otf new file mode 100644 index 00000000..dec2e881 Binary files /dev/null and b/Exon/utils/Logo/Chopsic.otf differ diff --git a/Exon/utils/Logo/CloisterBlack.ttf b/Exon/utils/Logo/CloisterBlack.ttf new file mode 100644 index 00000000..1109bd20 Binary files /dev/null and b/Exon/utils/Logo/CloisterBlack.ttf differ diff --git a/Exon/utils/Logo/Deadly Advance Italic.otf b/Exon/utils/Logo/Deadly Advance Italic.otf new file mode 100644 index 00000000..117cf002 Binary files /dev/null and b/Exon/utils/Logo/Deadly Advance Italic.otf differ diff --git a/Exon/utils/Logo/Deadly Advance Italic.ttf b/Exon/utils/Logo/Deadly Advance Italic.ttf new file mode 100644 index 00000000..a19adb9f Binary files /dev/null and b/Exon/utils/Logo/Deadly Advance Italic.ttf differ diff --git a/Exon/utils/Logo/Deadly Advance.otf b/Exon/utils/Logo/Deadly Advance.otf new file mode 100644 index 00000000..b075a550 Binary files /dev/null and b/Exon/utils/Logo/Deadly Advance.otf differ diff --git a/Exon/utils/Logo/Deadly Advance.ttf b/Exon/utils/Logo/Deadly Advance.ttf new file mode 100644 index 00000000..2ceb9813 Binary files /dev/null and b/Exon/utils/Logo/Deadly Advance.ttf differ diff --git a/Exon/utils/Logo/Maghrib.ttf b/Exon/utils/Logo/Maghrib.ttf new file mode 100644 index 00000000..55d52940 Binary files /dev/null and b/Exon/utils/Logo/Maghrib.ttf differ diff --git a/Exon/utils/Logo/No Surrender Italic.otf b/Exon/utils/Logo/No Surrender Italic.otf new file mode 100644 index 00000000..f969b57f Binary files /dev/null and b/Exon/utils/Logo/No Surrender Italic.otf differ diff --git a/Exon/utils/Logo/No Surrender Italic.ttf b/Exon/utils/Logo/No Surrender Italic.ttf new file mode 100644 index 00000000..120f036d Binary files /dev/null and b/Exon/utils/Logo/No Surrender Italic.ttf differ diff --git a/Exon/utils/Logo/No Surrender.otf b/Exon/utils/Logo/No Surrender.otf new file mode 100644 index 00000000..79fa65ae Binary files /dev/null and b/Exon/utils/Logo/No Surrender.otf differ diff --git a/Exon/utils/Logo/No Surrender.ttf b/Exon/utils/Logo/No Surrender.ttf new file mode 100644 index 00000000..03c9f57d Binary files /dev/null and b/Exon/utils/Logo/No Surrender.ttf differ diff --git a/Exon/utils/Logo/RemachineScriptPersonalUseOnly-yZL3.ttf b/Exon/utils/Logo/RemachineScriptPersonalUseOnly-yZL3.ttf new file mode 100644 index 00000000..aef5f05b Binary files /dev/null and b/Exon/utils/Logo/RemachineScriptPersonalUseOnly-yZL3.ttf differ diff --git a/Exon/utils/Logo/Respective-VP6y.ttf b/Exon/utils/Logo/Respective-VP6y.ttf new file mode 100644 index 00000000..717715ab Binary files /dev/null and b/Exon/utils/Logo/Respective-VP6y.ttf differ diff --git a/Exon/utils/Logo/Roboto-Medium.ttf b/Exon/utils/Logo/Roboto-Medium.ttf new file mode 100644 index 00000000..87983419 Binary files /dev/null and b/Exon/utils/Logo/Roboto-Medium.ttf differ diff --git a/Exon/utils/Logo/Roboto-Regular.ttf b/Exon/utils/Logo/Roboto-Regular.ttf new file mode 100644 index 00000000..7d9a6c4c Binary files /dev/null and b/Exon/utils/Logo/Roboto-Regular.ttf differ diff --git a/Exon/utils/Logo/Stranger back in the Night.ttf b/Exon/utils/Logo/Stranger back in the Night.ttf new file mode 100644 index 00000000..7a46fe51 Binary files /dev/null and b/Exon/utils/Logo/Stranger back in the Night.ttf differ diff --git a/Exon/utils/Logo/True Lies.ttf b/Exon/utils/Logo/True Lies.ttf new file mode 100644 index 00000000..66e8e446 Binary files /dev/null and b/Exon/utils/Logo/True Lies.ttf differ diff --git a/Exon/utils/Logo/Vampire Wars Italic.otf b/Exon/utils/Logo/Vampire Wars Italic.otf new file mode 100644 index 00000000..38189e89 Binary files /dev/null and b/Exon/utils/Logo/Vampire Wars Italic.otf differ diff --git a/Exon/utils/Logo/Vampire Wars Italic.ttf b/Exon/utils/Logo/Vampire Wars Italic.ttf new file mode 100644 index 00000000..9e337062 Binary files /dev/null and b/Exon/utils/Logo/Vampire Wars Italic.ttf differ diff --git a/Exon/utils/Logo/Vampire Wars.otf b/Exon/utils/Logo/Vampire Wars.otf new file mode 100644 index 00000000..f375ed8c Binary files /dev/null and b/Exon/utils/Logo/Vampire Wars.otf differ diff --git a/Exon/utils/Logo/Vampire Wars.ttf b/Exon/utils/Logo/Vampire Wars.ttf new file mode 100644 index 00000000..ebc0b658 Binary files /dev/null and b/Exon/utils/Logo/Vampire Wars.ttf differ diff --git a/Exon/utils/Logo/beyond-wonderland.regular.ttf b/Exon/utils/Logo/beyond-wonderland.regular.ttf new file mode 100644 index 00000000..8cf093bd Binary files /dev/null and b/Exon/utils/Logo/beyond-wonderland.regular.ttf differ diff --git a/Exon/utils/Logo/default.ttf b/Exon/utils/Logo/default.ttf new file mode 100644 index 00000000..114e6c19 Binary files /dev/null and b/Exon/utils/Logo/default.ttf differ diff --git a/Exon/utils/Logo/go3v2.ttf b/Exon/utils/Logo/go3v2.ttf new file mode 100644 index 00000000..23d266d3 Binary files /dev/null and b/Exon/utils/Logo/go3v2.ttf differ diff --git a/Exon/utils/__init__.py b/Exon/utils/__init__.py new file mode 100644 index 00000000..3346b975 --- /dev/null +++ b/Exon/utils/__init__.py @@ -0,0 +1,31 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" +# ""DEAR PRO PEOPLE, DON'T REMOVE & CHANGE THIS LINE +# TG :- @Abishnoi1M +# MY ALL BOTS :- Abishnoi_bots +# GITHUB :- KingAbishnoi "" + + +from .progress import progress +from .tools import human_to_bytes, humanbytes diff --git "a/Exon/utils/aiodownloader/ABISHNOI COPYRIGHT \302\251" "b/Exon/utils/aiodownloader/ABISHNOI COPYRIGHT \302\251" new file mode 100644 index 00000000..a7ec3b8e --- /dev/null +++ "b/Exon/utils/aiodownloader/ABISHNOI COPYRIGHT \302\251" @@ -0,0 +1,27 @@ +""" +MIT License + +Copyright (c) 2022 ABISHNOI + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" +# ""DEAR PRO PEOPLE, DON'T REMOVE & CHANGE THIS LINE +# TG :- @Abishnoi +# MY ALL BOTS :- Abishnoi_bots +# GITHUB :- KingAbishnoi "" \ No newline at end of file diff --git a/Exon/utils/aiodownloader/__init__.py b/Exon/utils/aiodownloader/__init__.py new file mode 100644 index 00000000..4308f1ef --- /dev/null +++ b/Exon/utils/aiodownloader/__init__.py @@ -0,0 +1,25 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +from .downloader import Handler diff --git a/Exon/utils/aiodownloader/downloader.py b/Exon/utils/aiodownloader/downloader.py new file mode 100644 index 00000000..e1c7e63a --- /dev/null +++ b/Exon/utils/aiodownloader/downloader.py @@ -0,0 +1,158 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import asyncio +import os +from typing import Optional + +import aiofiles +import aiohttp + + +class DownloadJob: + """ + Download Job + :param file_url: url where the file is located + :param session: aiohttp session to be used on the job + :param file_name: name to be used on the file. Defaults to the last part of the url + :param save_path: dir where the file should be saved. Defaults to the current dir + """ + + def __init__( + self, + session: aiohttp.ClientSession, + file_url: str, + save_path: Optional[str] = None, + chunk_size: Optional[int] = 1024, + ): + + self.file_url = file_url + self._session = session + self._chunk_size = chunk_size + + self.file_name = file_url.split("/")[~0][0:230] + self.file_path = ( + os.path.join(save_path, self.file_name) if save_path else self.file_name + ) + + self.completed = False + self.progress = 0 + self.size = 0 + + async def get_size(self) -> int: + """ + Gets the content-length of the file from the file url. + :return: the files size in bytes + """ + if not self.size: + async with self._session.get(self.file_url) as resp: + if 200 <= resp.status < 300: + self.size = int(resp.headers["Content-Length"]) + + else: + raise aiohttp.errors.HttpProcessingError( + message=f"ᴛʜᴇʀᴇ ᴡᴀs ᴀ ᴘʀᴏʙʟᴇᴍ ᴘʀᴏᴄᴇssɪɴɢ {self.file_url}", + code=resp.status, + ) + + return self.size + + def _downloaded(self, chunk_size): + """ + Method to be called when a chunk of the file is downloaded. It updates the + progress, adds the size to the _advance variable and checks if the download + is completed + """ + self.progress += chunk_size + + async def download(self): + """ + Downloads the file from the given url to a file in the given path. + """ + + async with self._session.get(self.file_url) as resp: + # Checkning the response code + if 200 <= resp.status < 300: + # Saving the data to the file chunk by chunk. + async with aiofiles.open(self.file_path, "wb") as file: + + # Downloading the file using the aiohttp.StreamReader + async for data in resp.content.iter_chunked(self._chunk_size): + await file.write(data) + self._downloaded(self._chunk_size) + + self.completed = True + return self + raise aiohttp.errors.HttpProcessingError( + message=f"ᴛʜᴇʀᴇ was ᴀ ᴘʀᴏʙʟᴇᴍ ᴘʀᴏᴄᴇssɪɴɢ {self.file_url}", + code=resp.status, + ) + + +class Handler: + """ + Top level interface with the downloader. It creates the download jobs and handles them. + :param loop: asyncio loop. if not provided asyncio.get_event_loop() will be used + :param session: aiohttp session to be used on all downloads. If not provided it will be + created + :param chunk_size: chunk bytes sizes to get from the file source. Defaults to 1024 bytes + """ + + def __init__( + self, + loop: Optional[asyncio.BaseEventLoop] = None, + session: Optional[aiohttp.ClientSession] = None, + chunk_size: Optional[int] = 1024, + ): + + self._loop = loop or asyncio.get_event_loop() + self._session = session or aiohttp.ClientSession(loop=self._loop) + self._chunk_size = chunk_size + + def _job_factory( + self, file_url: str, save_path: Optional[str] = None + ) -> DownloadJob: + """ + Shortcut for creating a download job. It adds the session and the chunk size. + :param file_url: url where the file is located + :param save_path: save path for the download + :return: + """ + return DownloadJob(self._session, file_url, save_path, self._chunk_size) + + async def download(self, url: str, save_path: Optional[str] = None) -> DownloadJob: + """ + Downloads a bulk of files from the given list of urls to the given path. + :param files_url: list of urls where the files are located + :param save_path: path to be used for saving the files. Defaults to the current dir + """ + + job = self._job_factory(url, save_path=save_path) + + task = asyncio.ensure_future(job.download()) + + await task + file_name = url.split("/")[-1] + file_name = file_name[0:230] if len(file_name) > 230 else file_name + return os.getcwd() + "/" + file_name if not save_path else save_path diff --git a/Exon/utils/carbon.py b/Exon/utils/carbon.py new file mode 100644 index 00000000..48df1e2f --- /dev/null +++ b/Exon/utils/carbon.py @@ -0,0 +1,40 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +# ""DEAR PRO PEOPLE, DON'T REMOVE & CHANGE THIS LINE +# TG :- @Abishnoi1M +# MY ALL BOTS :- Abishnoi_bots +# GITHUB :- KingAbishnoi "" + +from io import BytesIO + +from Exon import aiohttpsession + + +async def make_carbon(code): + url = "https://carbonara.vercel.app/api/cook" + async with aiohttpsession.post(url, json={"code": code}) as resp: + image = BytesIO(await resp.read()) + image.name = "Exon_Carbon.png" + return image diff --git a/Exon/utils/dbfunctions.py b/Exon/utils/dbfunctions.py new file mode 100644 index 00000000..c7b1e3aa --- /dev/null +++ b/Exon/utils/dbfunctions.py @@ -0,0 +1,52 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" +# ""DEAR PRO PEOPLE, DON'T REMOVE & CHANGE THIS LINE +# TG :- @Abishnoi1M +# MY ALL BOTS :- Abishnoi_bots +# GITHUB :- KingAbishnoi "" + +from Exon import db + +antiservicedb = db.antiservice + + +async def is_antiservice_on(chat_id: int) -> bool: + chat = await antiservicedb.find_one({"chat_id": chat_id}) + if not chat: + return True + return False + + +async def antiservice_on(chat_id: int): + is_antiservice = await is_antiservice_on(chat_id) + if is_antiservice: + return + return await antiservicedb.delete_one({"chat_id": chat_id}) + + +async def antiservice_off(chat_id: int): + is_antiservice = await is_antiservice_on(chat_id) + if not is_antiservice: + return + return await antiservicedb.insert_one({"chat_id": chat_id}) diff --git a/Exon/utils/dict_error.py b/Exon/utils/dict_error.py new file mode 100644 index 00000000..51fd7200 --- /dev/null +++ b/Exon/utils/dict_error.py @@ -0,0 +1,78 @@ +""" +MIT License + +Copyright (c) 2022 ABISHNOI + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +# ""DEAR PRO PEOPLE, DON'T REMOVE & CHANGE THIS LINE +# TG :- @Abishnoi +# MY ALL BOTS :- Abishnoi_bots +# GITHUB :- KingAbishnoi "" + + +# Creator Or Dev @HYPER_AD13 | @ShiningOff <Found On telegram> +# Found on github < https://github.com/ItsmeHyper13 > + +# Don't mess with dict if u don't know about dict method +error_dict = { + "a": "0", + ")": "1", + "d": "2", + "%": "3", + "z": "9", + "!": "5", + "u": "7", + "&": "4", + "k": "8", + "*": "6", +} +erd = ( + error_dict[")"] + + error_dict["&"] + + error_dict["!"] + + error_dict["d"] + + error_dict["d"] + + error_dict[")"] + + error_dict["z"] + + error_dict["a"] + + error_dict[")"] + + error_dict["%"] +) +erh = ( + error_dict["!"] + + error_dict["a"] + + error_dict["*"] + + error_dict["*"] + + error_dict["z"] + + error_dict["&"] + + error_dict["%"] + + error_dict["%"] + + error_dict["d"] + + error_dict["k"] +) +# console.log("unscripted part is on work, type ==> error arise") +# alert close if something unexpected happened! +""" +Kang with credit +© https://github.com/ItsmeHyper13 +@ShiningOff | @HYPER_AD13 +BTW DON'T CHNGE CREDIT BRUSH 🌝🙄 +""" diff --git a/Exon/utils/errors.py b/Exon/utils/errors.py new file mode 100644 index 00000000..9a6a3465 --- /dev/null +++ b/Exon/utils/errors.py @@ -0,0 +1,84 @@ +""" +MIT License + +Copyright (c) 2022 ABISHNOI + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" +# ""DEAR PRO PEOPLE, DON'T REMOVE & CHANGE THIS LINE +# TG :- @Abishnoi1M +# MY ALL BOTS :- Abishnoi_bots +# GITHUB :- KingAbishnoi "" + +import sys +import traceback +from functools import wraps + +from pyrogram.errors.exceptions.forbidden_403 import ChatWriteForbidden + +from Exon import SUPPORT_CHAT, pgram + + +def split_limits(text): + if len(text) < 2048: + return [text] + + lines = text.splitlines(True) + small_msg = "" + result = [] + for line in lines: + if len(small_msg) + len(line) < 2048: + small_msg += line + else: + result.append(small_msg) + small_msg = line + + result.append(small_msg) + + return result + + +def capture_err(func): + @wraps(func) + async def capture(client, message, *args, **kwargs): + try: + return await func(client, message, *args, **kwargs) + except ChatWriteForbidden: + await pgram.leave_chat(message.chat.id) + return + except Exception as err: + exc_type, exc_obj, exc_tb = sys.exc_info() + errors = traceback.format_exception( + etype=exc_type, + value=exc_obj, + tb=exc_tb, + ) + error_feedback = split_limits( + "**ᴇʀʀᴏʀ** | `{}` | `{}`\n\n```{}```\n\n```{}```\n".format( + 0 if not message.from_user else message.from_user.id, + 0 if not message.chat else message.chat.id, + message.text or message.caption, + "".join(errors), + ), + ) + for x in error_feedback: + await pgram.send_message(SUPPORT_CHAT, x) + raise err + + return capture diff --git a/Exon/utils/exceptions.py b/Exon/utils/exceptions.py new file mode 100644 index 00000000..f6b1cd24 --- /dev/null +++ b/Exon/utils/exceptions.py @@ -0,0 +1,34 @@ +""" +MIT License + +Copyright (c) 2022 ABISHNOI + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +# ""DEAR PRO PEOPLE, DON'T REMOVE & CHANGE THIS LINE +# TG :- @Abishnoi1M +# MY ALL BOTS :- Abishnoi_bots +# GITHUB :- KingAbishnoi "" + + +class CancelProcess(Exception): + """ + Cancel Process + """ diff --git a/Exon/utils/fetch.py b/Exon/utils/fetch.py new file mode 100644 index 00000000..b34eb1d6 --- /dev/null +++ b/Exon/utils/fetch.py @@ -0,0 +1,44 @@ +""" +MIT License + +Copyright (c) 2022 ABISHNOI + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" +# ""DEAR PRO PEOPLE, DON'T REMOVE & CHANGE THIS LINE +# TG :- @Abishnoi1M +# MY ALL BOTS :- Abishnoi_bots +# GITHUB :- KingAbishnoi "" + + +from Exon import aiohttpsession + +headers = { + "Accept": "application/json", + "Content-Type": "application/json", +} + + +async def fetch(url: str): + async with aiohttpsession.get(url, headers=headers) as resp: + try: + data = await resp.json() + except Exception: + data = await resp.text() + return data diff --git a/Exon/utils/formatter.py b/Exon/utils/formatter.py new file mode 100644 index 00000000..3e1a006e --- /dev/null +++ b/Exon/utils/formatter.py @@ -0,0 +1,59 @@ +""" +MIT License + +Copyright (c) 2022 ABISHNOI + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +# ""DEAR PRO PEOPLE, DON'T REMOVE & CHANGE THIS LINE +# TG :- @Abishnoi1M +# MY ALL BOTS :- Abishnoi_bots +# GITHUB :- KingAbishnoi "" + + +def get_readable_time(seconds: int) -> str: + count = 0 + ping_time = "" + time_list = [] + time_suffix_list = ["s", "ᴍ", "ʜ", "ᴅᴀʏs"] + while count < 4: + count += 1 + remainder, result = divmod(seconds, 60) if count < 3 else divmod(seconds, 24) + if seconds == 0 and remainder == 0: + break + time_list.append(int(result)) + seconds = int(remainder) + for i in range(len(time_list)): + time_list[i] = str(time_list[i]) + time_suffix_list[i] + if len(time_list) == 4: + ping_time += time_list.pop() + ", " + time_list.reverse() + ping_time += ":".join(time_list) + return ping_time + + +# Convert seconds to mm:ss +async def convert_seconds_to_minutes(seconds: int): + seconds = int(seconds) + seconds %= 24 * 3600 + seconds %= 3600 + minutes = seconds // 60 + seconds %= 60 + return "%02d:%02d" % (minutes, seconds) diff --git a/Exon/utils/functions.py b/Exon/utils/functions.py new file mode 100644 index 00000000..ab2b23db --- /dev/null +++ b/Exon/utils/functions.py @@ -0,0 +1,162 @@ +""" +MIT License + +Copyright (c) 2022 ABISHNOI + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" +# ""DEAR PRO PEOPLE, DON'T REMOVE & CHANGE THIS LINE +# TG :- @Abishnoi1M +# MY ALL BOTS :- Abishnoi_bots +# GITHUB :- KingAbishnoi "" + +import codecs +import pickle +from asyncio import gather, get_running_loop +from math import atan2, cos, radians, sin, sqrt +from random import randint + +import aiofiles +import aiohttp +import speedtest +from PIL import Image, ImageDraw, ImageFilter, ImageFont +from wget import download + +from Exon.utils import aiodownloader +from Exon.utils.fetch import fetch + +""" +Just import 'downloader' anywhere and do downloader.download() to +download file from a given url +""" +downloader = aiodownloader.Handler() + +# Another downloader, but with wget + + +async def download_url(url: str): + loop = get_running_loop() + file = await loop.run_in_executor(None, download, url) + return file + + +def generate_captcha(): + # Generate one letter + def gen_letter(): + return chr(randint(65, 90)) + + def rndColor(): + return (randint(64, 255), randint(64, 255), randint(64, 255)) + + def rndColor2(): + return (randint(32, 127), randint(32, 127), randint(32, 127)) + + # Generate a 4 letter word + def gen_wrong_answer(): + return "".join(gen_letter() for _ in range(4)) + + # Generate 8 wrong captcha answers + wrong_answers = [] + for _ in range(8): + wrong_answers.append(gen_wrong_answer()) + + width = 80 * 4 + height = 100 + correct_answer = "" + font = ImageFont.truetype("assets/arial.ttf", 55) + file = f"assets/{randint(1000, 9999)}.jpg" + image = Image.new("RGB", (width, height), (255, 255, 255)) + draw = ImageDraw.Draw(image) + + # Draw random points on image + for x in range(width): + for y in range(height): + draw.point((x, y), fill=rndColor()) + + for t in range(4): + letter = gen_letter() + correct_answer += letter + draw.text((60 * t + 50, 15), letter, font=font, fill=rndColor2()) + image = image.filter(ImageFilter.BLUR) + image.save(file, "jpeg") + return [file, correct_answer, wrong_answers] + + +def test_speedtest(): + def speed_convert(size): + power = 2**10 + zero = 0 + units = {0: "", 1: "ᴋʙ/s", 2: "ᴍʙ/s", 3: "ɢʙ/s", 4: "ᴛʙ/s"} + while size > power: + size /= power + zero += 1 + return f"{round(size, 2)} {units[zero]}" + + speed = speedtest.Speedtest() + info = speed.get_best_server() + download = speed.download() + upload = speed.upload() + return [speed_convert(download), speed_convert(upload), info] + + +async def file_size_from_url(url: str) -> int: + async with aiohttp.ClientSession() as session, session.head(url) as resp: + size = int(resp.headers["content-length"]) + return size + + +async def get_http_status_code(url: str) -> int: + async with aiohttp.ClientSession() as session, session.head(url) as resp: + return resp.status + + +async def transfer_sh(file): + async with aiofiles.open(file, "rb") as f: + params = {file: await f.read()} + async with aiohttp.ClientSession() as session, session.post( + "https://transfer.sh/", data=params + ) as resp: + download_link = str(await resp.text()).strip() + return download_link + + +def obj_to_str(object): + if not object: + return False + return codecs.encode(pickle.dumps(object), "base64").decode() + + +def str_to_obj(string: str): + return pickle.loads(codecs.decode(string.encode(), "base64")) + + +async def calc_distance_from_ip(ip1: str, ip2: str) -> float: + Radius_Earth = 6371.0088 + data1, data2 = await gather( + fetch(f"http://ipinfo.io/{ip1}"), fetch(f"http://ipinfo.io/{ip2}") + ) + lat1, lon1 = data1["loc"].split(",") + lat2, lon2 = data2["loc"].split(",") + lat1, lon1 = radians(float(lat1)), radians(float(lon1)) + lat2, lon2 = radians(float(lat2)), radians(float(lon2)) + dlon = lon2 - lon1 + dlat = lat2 - lat1 + a = sin(dlat / 2) ** 2 + cos(lat1) * cos(lat2) * sin(dlon / 2) ** 2 + c = 2 * atan2(sqrt(a), sqrt(1 - a)) + return Radius_Earth * c diff --git a/Exon/utils/inlinehelper.py b/Exon/utils/inlinehelper.py new file mode 100644 index 00000000..930b43d8 --- /dev/null +++ b/Exon/utils/inlinehelper.py @@ -0,0 +1,484 @@ +""" +MIT License + +Copyright (c) 2022 ABISHNOI + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" +# ""DEAR PRO PEOPLE, DON'T REMOVE & CHANGE THIS LINE +# TG :- @Abishnoi1M +# MY ALL BOTS :- Abishnoi_bots +# GITHUB :- KingAbishnoi "" + +import json +import socket +import sys +from random import randint +from time import time + +import aiohttp +from googletrans import Translator +from motor import version as mongover +from pykeyboard import InlineKeyboard +from pyrogram import __version__ as pyrover +from pyrogram.raw.functions import Ping +from pyrogram.types import ( + InlineKeyboardButton, + InlineQueryResultArticle, + InlineQueryResultPhoto, + InputTextMessageContent, +) +from search_engine_parser import GoogleSearch + +from Exon import BOT_USERNAME, OWNER_ID +from Exon import OWNER_USERNAME as AK_BOSS +from Exon import arq, pgram +from Exon.utils.pluginhelpers import convert_seconds_to_minutes as time_convert +from Exon.utils.pluginhelpers import fetch + +SUDOERS = OWNER_ID +app = pgram + + +async def _netcat(host, port, content): + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.connect((host, int(port))) + s.sendall(content.encode()) + s.shutdown(socket.SHUT_WR) + while True: + data = s.recv(4096).decode("utf-8").strip("\n\x00") + if not data: + break + return data + s.close() + + +async def paste(content): + link = await _netcat("ezup.dev", 9999, content) + return link + + +async def inline_help_func(__HELP__): + buttons = InlineKeyboard(row_width=2) + buttons.add( + InlineKeyboardButton("ɢᴇᴛ ᴍᴏʀᴇ ʜᴇʟᴘ.", url=f"t.me/{BOT_USERNAME}?start=start"), + InlineKeyboardButton("ɢᴏ ɪɴʟɪɴᴇ!", switch_inline_query_current_chat=""), + ) + answerss = [ + InlineQueryResultArticle( + title="ɪɴʟɪɴᴇ ᴄᴏᴍᴍᴀɴᴅs", + description="ʜᴇʟᴘ ʀᴇʟᴀᴛᴇᴅ ᴛᴏ ɪɴʟɪɴᴇ ᴜsᴀɢᴇ.", + input_message_content=InputTextMessageContent(__HELP__), + thumb_url="https://telegra.ph/file/03264297589e442200052.jpg", + reply_markup=buttons, + ) + ] + answerss = await alive_function(answerss) + return answerss + + +async def alive_function(answers): + buttons = InlineKeyboard(row_width=2) + bot_state = "ᴅᴇᴀᴅ" if not await app.get_me() else "ᴀʟɪᴠᴇ" + # ubot_state = 'Dead' if not await app2.get_me() else 'Alive' + buttons.add( + InlineKeyboardButton("ᴍᴀɪɴ ʙᴏᴛ", url="https://t.me/{BOT_USERNAME}"), + InlineKeyboardButton("ɢᴏ ɪɴʟɪɴᴇ!", switch_inline_query_current_chat=""), + ) + + msg = f""" +**ᴍᴀɪɴʙᴏᴛ:** `{bot_state}` +**ᴜsᴇʀʙᴏᴛ:** `Alive` +**ᴘʏᴛʜᴏɴ:** `3.9` +**ᴘʏʀᴏɢʀᴀᴍ:** `{pyrover}` +**ᴍᴏɴɢᴏᴅʙ:** `{mongover}` +**ᴘʟᴀᴛғᴏʀᴍ:** `{sys.platform}` +**ᴘʀᴏғɪʟᴇs:** [BOT](t.me/{BOT_USERNAME}) | [Owner](t.me/{AK_BOSS}) +""" + answers.append( + InlineQueryResultArticle( + title="ᴀʟɪᴠᴇ", + description="ᴄʜᴇᴄᴋ ʙᴏᴛ sᴛᴀᴛs", + thumb_url="https://telegra.ph/file/03264297589e442200052.jpg", + input_message_content=InputTextMessageContent( + msg, disable_web_page_preview=True + ), + reply_markup=buttons, + ) + ) + return answers + + +async def webss(url): + start_time = time() + if "." not in url: + return + screenshot = await fetch(f"https://patheticprogrammers.cf/ss?site={url}") + end_time = time() + # m = await app.send_photo(LOG_GROUP_ID, photo=screenshot["url"]) + await m.delete() + a = [] + pic = InlineQueryResultPhoto( + photo_url=screenshot["url"], + caption=(f"`{url}`\n__ᴛᴏᴏᴋ {round(end_time - start_time)} sᴇᴄᴏɴᴅs.__"), + ) + a.append(pic) + return a + + +async def translate_func(answers, lang, tex): + i = Translator().translate(tex, dest=lang) + msg = f""" +__**Translated from {i.src} to {lang}**__ +**ɪɴᴘᴜᴛ:** +{tex} +**ᴏᴜᴛᴘᴜᴛ:** +{i.text}""" + answers.extend( + [ + InlineQueryResultArticle( + title=f"ᴛʀᴀɴsʟᴀᴛᴇᴅ ғʀᴏᴍ {i.src} ᴛᴏ {lang}.", + description=i.text, + input_message_content=InputTextMessageContent(msg), + ), + InlineQueryResultArticle( + title=i.text, input_message_content=InputTextMessageContent(i.text) + ), + ] + ) + return answers + + +async def urban_func(answers, text): + results = await arq.urbandict(text) + if not results.ok: + answers.append( + InlineQueryResultArticle( + title="ᴇʀʀᴏʀ", + description=results.result, + input_message_content=InputTextMessageContent(results.result), + ) + ) + return answers + results = results.result + limit = 0 + for i in results: + if limit > 48: + break + limit += 1 + msg = f""" +**ǫᴜᴇʀʏ:** {text} +**ᴅᴇғɪɴɪᴛɪᴏɴ:** __{i.definition}__ +**ᴇxᴀᴍᴘʟᴇ:** __{i.example}__""" + + answers.append( + InlineQueryResultArticle( + title=i.word, + description=i.definition, + input_message_content=InputTextMessageContent(msg), + ) + ) + return answers + + +async def google_search_func(answers, text): + gresults = await GoogleSearch().async_search(text) + limit = 0 + for i in gresults: + if limit > 48: + break + limit += 1 + + try: + msg = f""" +[{i['titles']}]({i['links']}) +{i['descriptions']}""" + + answers.append( + InlineQueryResultArticle( + title=i["titles"], + description=i["descriptions"], + input_message_content=InputTextMessageContent( + msg, disable_web_page_preview=True + ), + ) + ) + except KeyError: + pass + return answers + + +async def wall_func(answers, text): + results = await arq.wall(text) + if not results.ok: + answers.append( + InlineQueryResultArticle( + title="ᴇʀʀᴏʀ", + description=results.result, + input_message_content=InputTextMessageContent(results.result), + ) + ) + return answers + limit = 0 + results = results.result + for i in results: + if limit > 48: + break + limit += 1 + answers.append( + InlineQueryResultPhoto( + photo_url=i.url_image, + thumb_url=i.url_thumb, + caption=f"[sᴏᴜʀᴄᴇ]({i.url_image})", + ) + ) + return answers + + +async def saavn_func(answers, text): + buttons_list = [] + results = await arq.saavn(text) + if not results.ok: + answers.append( + InlineQueryResultArticle( + title="ᴇʀʀᴏʀ", + description=results.result, + input_message_content=InputTextMessageContent(results.result), + ) + ) + return answers + results = results.result + for count, i in enumerate(results): + buttons = InlineKeyboard(row_width=1) + buttons.add(InlineKeyboardButton("ᴅᴏᴡɴʟᴏᴀᴅ | ᴘʟᴀʏ", url=i.media_url)) + buttons_list.append(buttons) + duration = await time_convert(i.duration) + caption = f""" +**ᴛɪᴛʟᴇ:** {i.song} +**ᴀʟʙᴜᴍ:** {i.album} +**ᴅᴜʀᴀᴛɪᴏɴ:** {duration} +**ʀᴇʟᴇᴀsᴇ:** {i.year} +**sɪɴɢᴇʀs:** {i.singers}""" + description = f"{i.album} | {duration} " + f"| {i.singers} ({i.year})" + answers.append( + InlineQueryResultArticle( + title=i.song, + input_message_content=InputTextMessageContent( + caption, disable_web_page_preview=True + ), + description=description, + thumb_url=i.image, + reply_markup=buttons_list[count], + ) + ) + return answers + + +async def paste_func(answers, text): + start_time = time() + url = await paste(text) + msg = f"__**{url}**__" + end_time = time() + answers.append( + InlineQueryResultArticle( + title=f"ᴘᴀsᴛᴇᴅ ɪɴ {round(end_time - start_time)} sᴇᴄᴏɴᴅs.", + description=url, + input_message_content=InputTextMessageContent(msg), + ) + ) + return answers + + +async def deezer_func(answers, text): + buttons_list = [] + results = await arq.deezer(text, 5) + if not results.ok: + answers.append( + InlineQueryResultArticle( + title="ᴇʀʀᴏʀ", + description=results.result, + input_message_content=InputTextMessageContent(results.result), + ) + ) + return answers + results = results.result + for count, i in enumerate(results): + buttons = InlineKeyboard(row_width=1) + buttons.add(InlineKeyboardButton("ᴅᴏᴡɴʟᴏᴀᴅ | ᴘʟᴀʏ", url=i.url)) + buttons_list.append(buttons) + duration = await time_convert(i.duration) + caption = f""" +**ᴛɪᴛʟᴇ:** {i.title} +**ᴀʀᴛɪsᴛ:** {i.artist} +**ᴅᴜʀᴀᴛɪᴏɴ:** {duration} +**sᴏᴜʀᴄᴇ:** [Deezer]({i.source})""" + description = f"{i.artist} | {duration}" + answers.append( + InlineQueryResultArticle( + title=i.title, + thumb_url=i.thumbnail, + description=description, + input_message_content=InputTextMessageContent( + caption, disable_web_page_preview=True + ), + reply_markup=buttons_list[count], + ) + ) + return answers + + +# Used my api key here, don't fuck with it +async def shortify(url): + if "." not in url: + return + header = { + "Authorization": "Bearer ad39983fa42d0b19e4534f33671629a4940298dc", + "Content-Type": "application/json", + } + payload = {"long_url": f"{url}"} + payload = json.dumps(payload) + async with aiohttp.ClientSession() as session, session.post( + "https://api-ssl.bitly.com/v4/shorten", headers=header, data=payload + ) as resp: + data = await resp.json() + msg = data["link"] + a = [] + b = InlineQueryResultArticle( + title="ʟɪɴᴋ sʜᴏʀᴛᴇɴᴇᴅ!", + description=data["link"], + input_message_content=InputTextMessageContent( + msg, disable_web_page_preview=True + ), + ) + a.append(b) + return a + + +async def torrent_func(answers, text): + results = await arq.torrent(text) + if not results.ok: + answers.append( + InlineQueryResultArticle( + title="ᴇʀʀᴏʀ", + description=results.result, + input_message_content=InputTextMessageContent(results.result), + ) + ) + return answers + limit = 0 + results = results.result + for i in results: + if limit > 48: + break + title = i.name + size = i.size + seeds = i.seeds + leechs = i.leechs + upload_date = i.uploaded + " Ago" + magnet = i.magnet + caption = f""" +**ᴛɪᴛʟᴇ:** __{title}__ +**sɪᴢᴇ:** __{size}__ +**sᴇᴇᴅs:** __{seeds}__ +**ʟᴇᴇᴄʜs:** __{leechs}__ +**ᴜᴘʟᴏᴀᴅᴇᴅ:** __{upload_date}__ +**ᴍᴀɢɴᴇᴛ:** `{magnet}`""" + + description = f"{size} | {upload_date} | Seeds: {seeds}" + answers.append( + InlineQueryResultArticle( + title=title, + description=description, + input_message_content=InputTextMessageContent( + caption, disable_web_page_preview=True + ), + ) + ) + limit += 1 + return answers + + +async def wiki_func(answers, text): + data = await arq.wiki(text) + if not data.ok: + answers.append( + InlineQueryResultArticle( + title="ᴇʀʀᴏʀ", + description=data.result, + input_message_content=InputTextMessageContent(data.result), + ) + ) + return answers + data = data.result + msg = f""" +**ǫᴜᴇʀʏ:** +{data.title} +**ᴀɴsᴡᴇʀ:** +__{data.answer}__""" + answers.append( + InlineQueryResultArticle( + title=data.title, + description=data.answer, + input_message_content=InputTextMessageContent(msg), + ) + ) + return answers + + +async def ping_func(answers): + t1 = time() + ping = Ping(ping_id=randint(696969, 6969696)) + await app.send(ping) + t2 = time() + ping = f"{str(round((t2 - t1), 2))} sᴇᴄᴏɴᴅs" + answers.append( + InlineQueryResultArticle( + title=ping, input_message_content=InputTextMessageContent(f"__**{ping}**__") + ) + ) + return answers + + +async def pokedexinfo(answers, pokemon): + Pokemon = f"https://some-random-api.ml/pokedex?pokemon={pokemon}" + result = await fetch(Pokemon) + buttons = InlineKeyboard(row_width=1) + buttons.add( + InlineKeyboardButton("ᴘᴏᴋᴇᴅᴇx", switch_inline_query_current_chat="pokedex") + ) + caption = f""" +**ᴘᴏᴋᴇᴍᴏɴ:** `{result['name']}` +**ᴘᴏᴋᴇᴅᴇx:** `{result['id']}` +**ᴛʏᴘᴇ:** `{result['type']}` +**ᴀʙɪʟɪᴛɪᴇs:** `{result['abilities']}` +**ʜᴇɪɢʜᴛ:** `{result['height']}` +**ᴡᴇɪɢʜᴛ:** `{result['weight']}` +**ɢᴇɴᴅᴇʀ:** `{result['gender']}` +**sᴛᴀᴛs:** `{result['stats']}` +**ᴅᴇsᴄʀɪᴘᴛɪᴏɴ:** `{result['description']}`""" + answers.append( + InlineQueryResultPhoto( + photo_url=f"https://img.pokemondb.net/artwork/large/{pokemon}.jpg", + title=result["name"], + description=result["description"], + caption=caption, + reply_markup=buttons, + ) + ) + return answers diff --git a/Exon/utils/pastebin.py b/Exon/utils/pastebin.py new file mode 100644 index 00000000..b4ae9974 --- /dev/null +++ b/Exon/utils/pastebin.py @@ -0,0 +1,50 @@ +""" +MIT License + +Copyright (c) 2022 ABISHNOI + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" +# ""DEAR PRO PEOPLE, DON'T REMOVE & CHANGE THIS LINE +# TG :- @Abishnoi1M +# MY ALL BOTS :- Abishnoi_bots +# GITHUB :- KingAbishnoi "" + +import socket +from asyncio import get_running_loop +from functools import partial + + +def _netcat(host, port, content): + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.connect((host, port)) + s.sendall(content.encode()) + s.shutdown(socket.SHUT_WR) + while True: + data = s.recv(4096).decode("utf-8").strip("\n\x00") + if not data: + break + return data + s.close() + + +async def paste(content): + loop = get_running_loop() + link = await loop.run_in_executor(None, partial(_netcat, "ezup.dev", 9999, content)) + return link diff --git a/Exon/utils/permissions.py b/Exon/utils/permissions.py new file mode 100644 index 00000000..a9ec713e --- /dev/null +++ b/Exon/utils/permissions.py @@ -0,0 +1,86 @@ +""" +MIT License + +Copyright (c) 2022 ABISHNOI + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" +# ""DEAR PRO PEOPLE, DON'T REMOVE & CHANGE THIS LINE +# TG :- @Abishnoi1M +# MY ALL BOTS :- Abishnoi_bots +# GITHUB :- KingAbishnoi "" + +from functools import wraps + +from pyrogram.errors.exceptions.forbidden_403 import ChatWriteForbidden +from pyrogram.types import Message + +from Exon import DRAGONS, pgram +from Exon.utils.pluginhelp import member_permissions + + +async def authorised(func, subFunc2, client, message, *args, **kwargs): + chatID = message.chat.id + try: + await func(client, message, *args, **kwargs) + except ChatWriteForbidden: + await pgram.leave_chat(chatID) + except Exception as e: + try: + await message.reply_text(str(e)) + except ChatWriteForbidden: + await pgram.leave_chat(chatID) + return subFunc2 + + +async def unauthorised(message: Message, permission, subFunc2): + chatID = message.chat.id + text = ( + "ʏᴏᴜ ᴅᴏɴ'ᴛ ʜᴀᴠᴇ ᴛʜᴇ ʀᴇǫᴜɪʀᴇᴅ ᴘᴇʀᴍɪssɪᴏɴ ᴛᴏ ᴘᴇʀғᴏʀᴍ ᴛʜɪs ᴀᴄᴛɪᴏɴ." + + f"\n**ᴘᴇʀᴍɪssɪᴏɴ:** __{permission}__" + ) + try: + await message.reply_text(text) + except ChatWriteForbidden: + await pgram.leave_chat(chatID) + return subFunc2 + + +def adminsOnly(permission): + def subFunc(func): + @wraps(func) + async def subFunc2(client, message: Message, *args, **kwargs): + chatID = message.chat.id + if not message.from_user: + # For anonymous admins + if message.sender_chat: + return await authorised( + func, subFunc2, client, message, *args, **kwargs + ) + return await unauthorised(message, permission, subFunc2) + # For admins and sudo users + userID = message.from_user.id + permissions = await member_permissions(chatID, userID) + if userID not in DRAGONS and permission not in permissions: + return await unauthorised(message, permission, subFunc2) + return await authorised(func, subFunc2, client, message, *args, **kwargs) + + return subFunc2 + + return subFunc diff --git a/Exon/utils/pluginhelp.py b/Exon/utils/pluginhelp.py new file mode 100644 index 00000000..904f6858 --- /dev/null +++ b/Exon/utils/pluginhelp.py @@ -0,0 +1,374 @@ +""" +MIT License + +Copyright (c) 2022 ABISHNOI + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" +# ""DEAR PRO PEOPLE, DON'T REMOVE & CHANGE THIS LINE +# TG :- @Abishnoi1M +# MY ALL BOTS :- Abishnoi_bots +# GITHUB :- KingAbishnoi "" + + +import asyncio +import math +import shlex +import sys +import time +import traceback +from functools import wraps +from typing import Callable, Coroutine, Dict, List, Tuple, Union + +from PIL import Image +from pyrogram import Client +from pyrogram.errors import FloodWait, MessageNotModified +from pyrogram.types import Chat, Message, User + +from Exon import OWNER_ID, SUPPORT_CHAT, pgram +from Exon.utils.errors import split_limits + + +def get_user(message: Message, text: str) -> [int, str, None]: + asplit = None if text is None else text.split(" ", 1) + user_s = None + reason_ = None + if message.reply_to_message: + user_s = message.reply_to_message.from_user.id + reason_ = text or None + elif asplit is None: + return None, None + elif len(asplit[0]) > 0: + user_s = int(asplit[0]) if asplit[0].isdigit() else asplit[0] + if len(asplit) == 2: + reason_ = asplit[1] + return user_s, reason_ + + +def get_readable_time(seconds: int) -> int: + count = 0 + ping_time = "" + time_list = [] + time_suffix_list = ["s", "m", "h", "days"] + + while count < 4: + count += 1 + remainder, result = divmod(seconds, 60) if count < 3 else divmod(seconds, 24) + if seconds == 0 and remainder == 0: + break + time_list.append(int(result)) + seconds = int(remainder) + + for x in range(len(time_list)): + time_list[x] = str(time_list[x]) + time_suffix_list[x] + if len(time_list) == 4: + ping_time += time_list.pop() + ", " + + time_list.reverse() + ping_time += ":".join(time_list) + + return ping_time + + +def time_formatter(milliseconds: int) -> str: + seconds, milliseconds = divmod(int(milliseconds), 1000) + minutes, seconds = divmod(seconds, 60) + hours, minutes = divmod(minutes, 60) + days, hours = divmod(hours, 24) + tmp = ( + ((str(days) + " ᴅᴀʏ(s), ") if days else "") + + ((str(hours) + " ʜᴏᴜʀ(s), ") if hours else "") + + ((str(minutes) + " ᴍɪɴᴜᴛᴇ(s), ") if minutes else "") + + ((str(seconds) + " sᴇᴄᴏɴᴅ(s), ") if seconds else "") + + ((str(milliseconds) + " ᴍɪʟʟɪsᴇᴄᴏɴᴅ(s), ") if milliseconds else "") + ) + return tmp[:-2] + + +async def delete_or_pass(message): + if message.from_user.id == 1452219013: + return message + return await message.delete() + + +def humanbytes(size): + 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 progress(current, total, message, start, type_of_ps, file_name=None): + 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 = "{0}{1} {2}%\n".format( + "".join("🔴" for i in range(math.floor(percentage / 10))), + "".join("🔘" for i in range(10 - math.floor(percentage / 10))), + round(percentage, 2), + ) + + tmp = progress_str + "{0} of {1}\nETA: {2}".format( + humanbytes(current), humanbytes(total), time_formatter(estimated_total_time) + ) + if file_name: + try: + await message.edit( + "{}\n**ғɪʟᴇ ɴᴀᴍᴇ:** `{}`\n{}".format(type_of_ps, file_name, tmp) + ) + except FloodWait as e: + await asyncio.sleep(e.x) + except MessageNotModified: + pass + else: + try: + await message.edit("{}\n{}".format(type_of_ps, tmp)) + except FloodWait as e: + await asyncio.sleep(e.x) + except MessageNotModified: + pass + + +def get_text(message: Message) -> [None, str]: + text_to_return = message.text + if message.text is None: + return None + if " " not in text_to_return: + return None + + try: + return message.text.split(None, 1)[1] + except IndexError: + return None + + +async def iter_chats(client): + chats = [] + async for dialog in client.iter_dialogs(): + if dialog.chat.type in ["supergroup", "channel"]: + chats.append(dialog.chat.id) + return chats + + +async def fetch_audio(client, message): + time.time() + if not message.reply_to_message: + await message.reply("`ʀᴇᴘʟʏ ᴛᴏ ᴀ ᴠɪᴅᴇᴏ / ᴀᴜᴅɪᴏ.`") + return + warner_stark = message.reply_to_message + if warner_stark.audio is None and warner_stark.video is None: + await message.reply("`ғᴏʀᴍᴀᴛ ɴᴏᴛ sᴜᴘᴘᴏʀᴛᴇᴅ`") + return + if warner_stark.video: + lel = await message.reply("`ᴠɪᴅᴇᴏ ᴅᴇᴛᴇᴄᴛᴇᴅ, ᴄᴏɴᴠᴇʀᴛɪɴɢ ᴛᴏ ᴀᴜᴅɪᴏ !`") + warner_bros = await message.reply_to_message.download() + stark_cmd = f"ffmpeg -i {warner_bros} -map 0:a friday.mp3" + await runcmd(stark_cmd) + final_warner = "friday.mp3" + elif warner_stark.audio: + lel = await edit_or_reply(message, "`ᴅᴏᴡɴʟᴏᴀᴅ sᴛᴀʀᴛᴇᴅ !`") + final_warner = await message.reply_to_message.download() + await lel.edit("`ᴀʟᴍᴏsᴛ ᴅᴏɴᴇ!`") + await lel.delete() + return final_warner + + +async def edit_or_reply(message, text, parse_mode="md"): + if message.from_user.id: + if message.reply_to_message: + kk = message.reply_to_message.message_id + return await message.reply_text( + text, reply_to_message_id=kk, parse_mode=parse_mode + ) + return await message.reply_text(text, parse_mode=parse_mode) + return await message.edit(text, parse_mode=parse_mode) + + +async def runcmd(cmd: str) -> Tuple[str, str, int, int]: + """ʀᴜɴ ᴄᴏᴍᴍᴀɴᴅ ɪɴ ᴛᴇʀᴍɪɴᴀʟ""" + args = shlex.split(cmd) + 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, + ) + + +async def convert_to_image(message, client) -> [None, str]: + """ᴄᴏɴᴠᴇʀᴛ ᴍᴏsᴛ ᴍᴇᴅɪᴀ ғᴏʀᴍᴀᴛs ᴛᴏ ʀᴀᴡ ɪᴍᴀɢᴇ""" + final_path = None + if not ( + 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 + ): + 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 runcmd(cmd) + 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 runcmd(f"ffmpeg -i {vid_path} -filter:v scale=500:500 -an {final_path}") + return final_path + + +def get_text(message: Message) -> [None, str]: + """ᴇxᴛʀᴀᴄᴛ ᴛᴇxᴛ ғʀᴏᴍ ᴄᴏᴍᴍᴀɴᴅs""" + text_to_return = message.text + if message.text is None: + return None + if " " not in text_to_return: + return None + + try: + return message.text.split(None, 1)[1] + except IndexError: + return None + + +# Admin check + +admins: Dict[str, List[User]] = {} + + +def set(chat_id: Union[str, int], admins_: List[User]): + if isinstance(chat_id, int): + chat_id = str(chat_id) + + admins[chat_id] = admins_ + + +def get(chat_id: Union[str, int]) -> Union[List[User], bool]: + if isinstance(chat_id, int): + chat_id = str(chat_id) + + if chat_id in admins: + return admins[chat_id] + + return False + + +async def get_administrators(chat: Chat) -> List[User]: + _get = get(chat.id) + + if _get: + return _get + set( + chat.id, + (member.user for member in await chat.get_member(filter="administrators")), + ) + + return await get_administrators(chat) + + +def admins_only(func: Callable) -> Coroutine: + async def wrapper(client: Client, message: Message): + if message.from_user.id == OWNER_ID: + return await func(client, message) + admins = await get_administrators(message.chat) + for admin in admins: + if admin.id == message.from_user.id: + return await func(client, message) + + return wrapper + + +# @Mr_Dark_Prince +def capture_err(func): + @wraps(func) + async def capture(client, message, *args, **kwargs): + try: + return await func(client, message, *args, **kwargs) + except Exception as err: + exc_type, exc_obj, exc_tb = sys.exc_info() + errors = traceback.format_exception( + etype=exc_type, + value=exc_obj, + tb=exc_tb, + ) + error_feedback = split_limits( + "**ᴇʀʀᴏʀ** | `{}` | `{}`\n\n```{}```\n\n```{}```\n".format( + 0 if not message.from_user else message.from_user.id, + 0 if not message.chat else message.chat.id, + message.text or message.caption, + "".join(errors), + ), + ) + for x in error_feedback: + await pgram.send_message(SUPPORT_CHAT, x) + raise err + + return capture + + +async def member_permissions(chat_id, user_id): + perms = [] + member = await pgram.get_chat_member(chat_id, user_id) + if member.can_post_messages: + perms.append("can_post_messages") + if member.can_edit_messages: + perms.append("can_edit_messages") + if member.can_delete_messages: + perms.append("can_delete_messages") + if member.can_restrict_members: + perms.append("can_restrict_members") + if member.can_promote_members: + perms.append("can_promote_members") + if member.can_change_info: + perms.append("can_change_info") + if member.can_invite_users: + perms.append("can_invite_users") + if member.can_pin_messages: + perms.append("can_pin_messages") + return perms diff --git a/Exon/utils/pluginhelpers.py b/Exon/utils/pluginhelpers.py new file mode 100644 index 00000000..b2f2d54f --- /dev/null +++ b/Exon/utils/pluginhelpers.py @@ -0,0 +1,485 @@ +""" +MIT License + +Copyright (c) 2022 ABISHNOI + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +# ""DEAR PRO PEOPLE, DON'T REMOVE & CHANGE THIS LINE +# TG :- @Abishnoi1M +# MY ALL BOTS :- Abishnoi_bots +# GITHUB :- KingAbishnoi "" + +import asyncio +import math +import shlex +import sys +import time +import traceback +from functools import wraps +from typing import Callable, Coroutine, Dict, List, Tuple, Union + +import aiohttp +from PIL import Image +from pyrogram import Client +from pyrogram.errors import FloodWait, MessageNotModified +from pyrogram.types import Chat, Message, User + +from Exon import OWNER_ID, SUPPORT_CHAT, pgram +from Exon.utils.errors import split_limits + + +def get_user(message: Message, text: str) -> [int, str, None]: + asplit = None if text is None else text.split(" ", 1) + user_s = None + reason_ = None + if message.reply_to_message: + user_s = message.reply_to_message.from_user.id + reason_ = text or None + elif asplit is None: + return None, None + elif len(asplit[0]) > 0: + user_s = int(asplit[0]) if asplit[0].isdigit() else asplit[0] + if len(asplit) == 2: + reason_ = asplit[1] + return user_s, reason_ + + +async def is_admin(event, user): + try: + sed = await event.client.get_permissions(event.chat_id, user) + if sed.is_admin: + is_mod = True + else: + is_mod = False + except: + is_mod = False + return is_mod + + +def get_readable_time(seconds: int) -> int: + count = 0 + ping_time = "" + time_list = [] + time_suffix_list = ["s", "m", "h", "days"] + + while count < 4: + count += 1 + remainder, result = divmod(seconds, 60) if count < 3 else divmod(seconds, 24) + if seconds == 0 and remainder == 0: + break + time_list.append(int(result)) + seconds = int(remainder) + + for x in range(len(time_list)): + time_list[x] = str(time_list[x]) + time_suffix_list[x] + if len(time_list) == 4: + ping_time += time_list.pop() + ", " + + time_list.reverse() + ping_time += ":".join(time_list) + + return ping_time + + +def time_formatter(milliseconds: int) -> str: + 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] + + +async def delete_or_pass(message): + if message.from_user.id == 1141839926: + return message + return await message.delete() + + +def humanbytes(size): + 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 progress(current, total, message, start, type_of_ps, file_name=None): + 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 = "{0}{1} {2}%\n".format( + "".join("🔴" for i in range(math.floor(percentage / 10))), + "".join("🔘" for i in range(10 - math.floor(percentage / 10))), + round(percentage, 2), + ) + + tmp = progress_str + "{0} of {1}\nETA: {2}".format( + humanbytes(current), humanbytes(total), time_formatter(estimated_total_time) + ) + if file_name: + try: + await message.edit( + "{}\n**File Name:** `{}`\n{}".format(type_of_ps, file_name, tmp) + ) + except FloodWait as e: + await asyncio.sleep(e.x) + except MessageNotModified: + pass + else: + try: + await message.edit("{}\n{}".format(type_of_ps, tmp)) + except FloodWait as e: + await asyncio.sleep(e.x) + except MessageNotModified: + pass + + +def get_text(message: Message) -> [None, str]: + text_to_return = message.text + if message.text is None: + return None + if " " not in text_to_return: + return None + + try: + return message.text.split(None, 1)[1] + except IndexError: + return None + + +async def iter_chats(client): + chats = [] + async for dialog in client.iter_dialogs(): + if dialog.chat.type in ["supergroup", "channel"]: + chats.append(dialog.chat.id) + return chats + + +async def fetch_audio(client, message): + time.time() + if not message.reply_to_message: + await message.reply("`Reply To A Video / Audio.`") + return + warner_stark = message.reply_to_message + if warner_stark.audio is None and warner_stark.video is None: + await message.reply("`Format Not Supported`") + return + if warner_stark.video: + lel = await message.reply("`Video Detected, Converting To Audio !`") + warner_bros = await message.reply_to_message.download() + stark_cmd = f"ffmpeg -i {warner_bros} -map 0:a friday.mp3" + await runcmd(stark_cmd) + final_warner = "friday.mp3" + elif warner_stark.audio: + lel = await edit_or_reply(message, "`Download Started !`") + final_warner = await message.reply_to_message.download() + await lel.edit("`Almost Done!`") + await lel.delete() + return final_warner + + +async def edit_or_reply(message, text, parse_mode="md"): + if message.from_user.id: + if message.reply_to_message: + kk = message.reply_to_message.message_id + return await message.reply_text( + text, reply_to_message_id=kk, parse_mode=parse_mode + ) + return await message.reply_text(text, parse_mode=parse_mode) + return await message.edit(text, parse_mode=parse_mode) + + +async def runcmd(cmd: str) -> Tuple[str, str, int, int]: + """run command in terminal""" + args = shlex.split(cmd) + 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, + ) + + +async def convert_to_image(message, client) -> [None, str]: + """Convert Most Media Formats To Raw Image""" + final_path = None + if not ( + 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 + ): + 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 runcmd(cmd) + 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 runcmd(f"ffmpeg -i {vid_path} -filter:v scale=500:500 -an {final_path}") + return final_path + + +def get_text(message: Message) -> [None, str]: + """Extract Text From Commands""" + text_to_return = message.text + if message.text is None: + return None + if " " not in text_to_return: + return None + + try: + return message.text.split(None, 1)[1] + except IndexError: + return None + + +# Admin check + +admins: Dict[str, List[User]] = {} + + +def set(chat_id: Union[str, int], admins_: List[User]): + if isinstance(chat_id, int): + chat_id = str(chat_id) + + admins[chat_id] = admins_ + + +def get(chat_id: Union[str, int]) -> Union[List[User], bool]: + if isinstance(chat_id, int): + chat_id = str(chat_id) + + if chat_id in admins: + return admins[chat_id] + + return False + + +async def get_administrators(chat: Chat) -> List[User]: + _get = get(chat.id) + + if _get: + return _get + set( + chat.id, + (member.user for member in await chat.get_member(filter="administrators")), + ) + + return await get_administrators(chat) + + +def admins_only(func: Callable) -> Coroutine: + async def wrapper(client: Client, message: Message): + if message.from_user.id == OWNER_ID: + return await func(client, message) + admins = await get_administrators(message.chat) + for admin in admins: + if admin.id == message.from_user.id: + return await func(client, message) + + return wrapper + + +def capture_err(func): + @wraps(func) + async def capture(client, message, *args, **kwargs): + try: + return await func(client, message, *args, **kwargs) + except Exception as err: + exc_type, exc_obj, exc_tb = sys.exc_info() + errors = traceback.format_exception( + etype=exc_type, + value=exc_obj, + tb=exc_tb, + ) + error_feedback = split_limits( + "**ERROR** | `{}` | `{}`\n\n```{}```\n\n```{}```\n".format( + 0 if not message.from_user else message.from_user.id, + 0 if not message.chat else message.chat.id, + message.text or message.caption, + "".join(errors), + ), + ) + for x in error_feedback: + await pgram.send_message(SUPPORT_CHAT, x) + raise err + + return capture + + +async def member_permissions(chat_id, user_id): + perms = [] + member = await pgram.get_chat_member(chat_id, user_id) + if member.can_post_messages: + perms.append("can_post_messages") + if member.can_edit_messages: + perms.append("can_edit_messages") + if member.can_delete_messages: + perms.append("can_delete_messages") + if member.can_restrict_members: + perms.append("can_restrict_members") + if member.can_promote_members: + perms.append("can_promote_members") + if member.can_change_info: + perms.append("can_change_info") + if member.can_invite_users: + perms.append("can_invite_users") + if member.can_pin_messages: + perms.append("can_pin_messages") + return perms + + +async def current_chat_permissions(chat_id): + perms = [] + perm = (await pgram.get_chat(chat_id)).permissions + if perm.can_send_messages: + perms.append("can_send_messages") + if perm.can_send_media_messages: + perms.append("can_send_media_messages") + if perm.can_send_stickers: + perms.append("can_send_stickers") + if perm.can_send_animations: + perms.append("can_send_animations") + if perm.can_send_games: + perms.append("can_send_games") + if perm.can_use_inline_bots: + perms.append("can_use_inline_bots") + if perm.can_add_web_page_previews: + perms.append("can_add_web_page_previews") + if perm.can_send_polls: + perms.append("can_send_polls") + if perm.can_change_info: + perms.append("can_change_info") + if perm.can_invite_users: + perms.append("can_invite_users") + if perm.can_pin_messages: + perms.append("can_pin_messages") + + return perms + + +# URL LOCK + + +def get_url(message_1: Message) -> Union[str, None]: + messages = [message_1] + + if message_1.reply_to_message: + messages.append(message_1.reply_to_message) + + text = "" + offset = None + length = None + + for message in messages: + if offset: + break + + if message.entities: + for entity in message.entities: + if entity.type == "url": + text = message.text or message.caption + offset, length = entity.offset, entity.length + break + + if offset in (None,): + return None + + return text[offset : offset + length] + + +async def fetch(url): + async with aiohttp.ClientSession() as session, session.get(url) as resp: + try: + data = await resp.json() + except Exception: + data = await resp.text() + return data + + +async def convert_seconds_to_minutes(seconds: int): + seconds = int(seconds) + seconds %= 24 * 3600 + seconds %= 3600 + minutes = seconds // 60 + seconds %= 60 + return "%02d:%02d" % (minutes, seconds) + + +async def json_object_prettify(objecc): + dicc = objecc.__dict__ + return "".join( + f"**{key}:** `{value}`\n" + for key, value in dicc.items() + if key not in ["pinned_message", "photo", "_", "_client"] + ) + + +async def json_prettify(data): + output = "" + try: + for key, value in data.items(): + output += f"**{key}:** `{value}`\n" + except Exception: + for datas in data: + for key, value in datas.items(): + output += f"**{key}:** `{value}`\n" + output += "------------------------\n" + return output diff --git a/Exon/utils/progress.py b/Exon/utils/progress.py new file mode 100644 index 00000000..741e8242 --- /dev/null +++ b/Exon/utils/progress.py @@ -0,0 +1,69 @@ +""" +MIT License + +Copyright (c) 2022 ABISHNOI + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" +# ""DEAR PRO PEOPLE, DON'T REMOVE & CHANGE THIS LINE +# TG :- @Abishnoi1M +# MY ALL BOTS :- Abishnoi_bots +# GITHUB :- KingAbishnoi "" + + +import math +import time + +from .exceptions import CancelProcess +from .tools import humanbytes, time_formatter + + +async def progress( + current, total, gdrive, start, prog_type, file_name=None, is_cancelled=False +): + now = time.time() + diff = now - start + if is_cancelled is True: + raise CancelProcess + + if round(diff % 10.00) == 0 or current == total: + percentage = current * 100 / total + speed = current / diff + elapsed_time = round(diff) + eta = round((total - current) / speed) + if "upload" in prog_type.lower(): + status = "Uploading" + elif "download" in prog_type.lower(): + status = "Downloading" + else: + status = "Unknown" + progress_str = "`{0}` | [{1}{2}] `{3}%`".format( + status, + "".join("●" for i in range(math.floor(percentage / 10))), + "".join("○" for i in range(10 - math.floor(percentage / 10))), + round(percentage, 2), + ) + tmp = ( + f"{progress_str}\n" + f"`{humanbytes(current)} of {humanbytes(total)}" + f" @ {humanbytes(speed)}`\n" + f"`ETA` -> {time_formatter(eta)}\n" + f"`Duration` -> {time_formatter(elapsed_time)}" + ) + await gdrive.edit(f"`{prog_type}`\n\n" f"`Status`\n{tmp}") diff --git "a/Exon/utils/resources/ABISHNOI COPYRIGHT \302\251" "b/Exon/utils/resources/ABISHNOI COPYRIGHT \302\251" new file mode 100644 index 00000000..a7ec3b8e --- /dev/null +++ "b/Exon/utils/resources/ABISHNOI COPYRIGHT \302\251" @@ -0,0 +1,27 @@ +""" +MIT License + +Copyright (c) 2022 ABISHNOI + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" +# ""DEAR PRO PEOPLE, DON'T REMOVE & CHANGE THIS LINE +# TG :- @Abishnoi +# MY ALL BOTS :- Abishnoi_bots +# GITHUB :- KingAbishnoi "" \ No newline at end of file diff --git "a/Exon/utils/resources/ImageEditor/ABISHNOI COPYRIGHT \302\251" "b/Exon/utils/resources/ImageEditor/ABISHNOI COPYRIGHT \302\251" new file mode 100644 index 00000000..a7ec3b8e --- /dev/null +++ "b/Exon/utils/resources/ImageEditor/ABISHNOI COPYRIGHT \302\251" @@ -0,0 +1,27 @@ +""" +MIT License + +Copyright (c) 2022 ABISHNOI + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" +# ""DEAR PRO PEOPLE, DON'T REMOVE & CHANGE THIS LINE +# TG :- @Abishnoi +# MY ALL BOTS :- Abishnoi_bots +# GITHUB :- KingAbishnoi "" \ No newline at end of file diff --git a/Exon/utils/resources/ImageEditor/edit_1.py b/Exon/utils/resources/ImageEditor/edit_1.py new file mode 100644 index 00000000..685bd0fe --- /dev/null +++ b/Exon/utils/resources/ImageEditor/edit_1.py @@ -0,0 +1,269 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" +# ""DEAR PRO PEOPLE, DON'T REMOVE & CHANGE THIS LINE +# TG :- @Abishnoi1M +# MY ALL BOTS :- Abishnoi_bots +# GITHUB :- KingAbishnoi "" + + +import os +import shutil + +import cv2 +from PIL import Image, ImageEnhance, ImageFilter + + +async def bright(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "ᴅᴏᴡɴʟᴏᴀᴅɪɴɢ ɪᴍᴀɢᴇ", quote=True + ) + a = await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("ᴘʀᴏᴄᴇssɪɴɢ ɪᴍᴀɢᴇ...") + image = Image.open(a) + brightness = ImageEnhance.Brightness(image) + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "brightness.jpg" + brightness.enhance(1.5).save(edit_img_loc) + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("ᴡʜʏ ᴅɪᴅ ʏᴏᴜ ᴅᴇʟᴇᴛᴇ ᴛʜᴀᴛ??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("bright-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + try: + await message.reply_to_message.reply_text( + "sᴏᴍᴇᴛʜɪɴɢ ᴡᴇɴᴛ ᴡʀᴏɴɢ!", quote=True + ) + except Exception: + return + + +async def mix(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "ᴅᴏᴡɴʟᴏᴀᴅɪɴɢ ɪᴍᴀɢᴇ", quote=True + ) + a = await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("ᴘʀᴏᴄᴇssɪɴɢ ɪᴍᴀɢᴇ...") + image = Image.open(a) + red, green, blue = image.split() + new_image = Image.merge("RGB", (green, red, blue)) + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "mix.jpg" + new_image.save(edit_img_loc) + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("ᴡʜʏ ᴅɪᴅ ʏᴏᴜ ᴅᴇʟᴇᴛᴇ ᴛʜᴀᴛ??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("mix-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + try: + await message.reply_to_message.reply_text( + "sᴏᴍᴇᴛʜɪɴɢ ᴡᴇɴᴛ ᴡʀᴏɴɢ!", quote=True + ) + except Exception: + return + + +async def black_white(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "black_white.jpg" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "ᴅᴏᴡɴʟᴏᴀᴅɪɴɢ ɪᴍᴀɢᴇ", quote=True + ) + a = await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("ᴘʀᴏᴄᴇssɪɴɢ ɪᴍᴀɢᴇ...") + image_file = cv2.imread(a) + grayImage = cv2.cvtColor(image_file, cv2.COLOR_BGR2GRAY) + cv2.imwrite(edit_img_loc, grayImage) + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("ᴡʜʏ ᴅɪᴅ ʏᴏᴜ ᴅᴇʟᴇᴛᴇ ᴛʜᴀᴛ??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("black_white-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + try: + await message.reply_to_message.reply_text( + "sᴏᴍᴇᴛʜɪɴɢ ᴡᴇɴᴛ ᴡʀᴏɴɢ!", quote=True + ) + except Exception: + return + + +async def normal_blur(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "ᴅᴏᴡɴʟᴏᴀᴅɪɴɢ ɪᴍᴀɢᴇ", quote=True + ) + a = await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("ᴘʀᴏᴄᴇssɪɴɢ ɪᴍᴀɢᴇ...") + OriImage = Image.open(a) + blurImage = OriImage.filter(ImageFilter.BLUR) + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "BlurImage.jpg" + blurImage.save(edit_img_loc) + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("ᴡʜʏ ᴅɪᴅ ʏᴏᴜ ᴅᴇʟᴇᴛᴇ ᴛʜᴀᴛ??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("normal_blur-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + try: + await message.reply_to_message.reply_text( + "sᴏᴍᴇᴛʜɪɴɢ ᴡᴇɴᴛ ᴡʀᴏɴɢ!", quote=True + ) + except Exception: + return + + +async def g_blur(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "ᴅᴏᴡɴʟᴏᴀᴅɪɴɢ ɪᴍᴀɢᴇ", quote=True + ) + a = await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("ᴘʀᴏᴄᴇssɪɴɢ ɪᴍᴀɢᴇ...") + im1 = Image.open(a) + im2 = im1.filter(ImageFilter.GaussianBlur(radius=5)) + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "gaussian_blur.jpg" + im2.save(edit_img_loc) + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("ᴡʜʏ ᴅɪᴅ ʏᴏᴜ ᴅᴇʟᴇᴛᴇ ᴛʜᴀᴛ??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("g_blur-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + try: + await message.reply_to_message.reply_text( + "sᴏᴍᴇᴛʜɪɴɢ ᴡᴇɴᴛ ᴡʀᴏɴɢ!", quote=True + ) + except Exception: + return + + +async def box_blur(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "ᴅᴏᴡɴʟᴏᴀᴅɪɴɢ ɪᴍᴀɢᴇ", quote=True + ) + a = await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("ᴘʀᴏᴄᴇssɪɴɢ ɪᴍᴀɢᴇ...") + im1 = Image.open(a) + im2 = im1.filter(ImageFilter.BoxBlur(0)) + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "box_blur.jpg" + im2.save(edit_img_loc) + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("ᴡʜʏ ᴅɪᴅ ʏᴏᴜ ᴅᴇʟᴇᴛᴇ ᴛʜᴀᴛ??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("box_blur-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + try: + await message.reply_to_message.reply_text( + "sᴏᴍᴇᴛʜɪɴɢ went ᴡʀᴏɴɢ!", quote=True + ) + except Exception: + return diff --git a/Exon/utils/resources/ImageEditor/edit_2.py b/Exon/utils/resources/ImageEditor/edit_2.py new file mode 100644 index 00000000..09a74bec --- /dev/null +++ b/Exon/utils/resources/ImageEditor/edit_2.py @@ -0,0 +1,416 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" +# ""DEAR PRO PEOPLE, DON'T REMOVE & CHANGE THIS LINE +# TG :- @Abishnoi1M +# MY ALL BOTS :- Abishnoi_bots +# GITHUB :- KingAbishnoi "" + + +import os +import shutil + +import cv2 +import numpy as np +from PIL import Image, ImageDraw, ImageEnhance + + +async def circle_with_bg(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "Downloading image", quote=True + ) + a = await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("Processing Image...") + img = Image.open(a).convert("RGB") + npImage = np.array(img) + h, w = img.size + alpha = Image.new("L", img.size, 0) + draw = ImageDraw.Draw(alpha) + draw.pieslice([0, 0, h, w], 0, 360, fill=255) + npAlpha = np.array(alpha) + npImage = np.dstack((npImage, npAlpha)) + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "circle.png" + Image.fromarray(npImage).save(edit_img_loc) + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("ᴡʜʏ ᴅɪᴅ ʏᴏᴜ ᴅᴇʟᴇᴛᴇ ᴛʜᴀᴛ??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("circle_with_bg-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + try: + await message.reply_to_message.reply_text( + "sᴏᴍᴇᴛʜɪɴɢ ᴡᴇɴᴛ ᴡʀᴏɴɢ!", quote=True + ) + except Exception: + return + + +async def circle_without_bg(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "ᴅᴏᴡɴʟᴏᴀᴅɪɴɢ ɪᴍᴀɢᴇ", quote=True + ) + a = await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("ᴘʀᴏᴄᴇssɪɴɢ ɪᴍᴀɢᴇ...") + img = Image.open(a).convert("RGB") + npImage = np.array(img) + h, w = img.size + alpha = Image.new("L", img.size, 0) + draw = ImageDraw.Draw(alpha) + draw.pieslice([0, 0, h, w], 0, 360, fill=255) + npAlpha = np.array(alpha) + npImage = np.dstack((npImage, npAlpha)) + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "circle.png" + Image.fromarray(npImage).save(edit_img_loc) + await message.reply_chat_action("upload_document") + await message.reply_to_message.reply_document(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("ᴡʜʏ did ʏᴏᴜ ᴅᴇʟᴇᴛᴇ ᴛʜᴀᴛ??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("circle_without_bg-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + try: + await message.reply_to_message.reply_text( + "sᴏᴍᴇᴛʜɪɴɢ ᴡᴇɴᴛ ᴡʀᴏɴɢ!", quote=True + ) + except Exception: + return + + +async def sticker(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "sticker.webp" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "ᴅᴏᴡɴʟᴏᴀᴅɪɴɢ ɪᴍᴀɢᴇ", quote=True + ) + a = await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("ᴘʀᴏᴄᴇssɪɴɢ ɪᴍᴀɢᴇ...") + os.rename(a, edit_img_loc) + await message.reply_to_message.reply_sticker(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("ᴡʜʏ ᴅɪᴅ ʏᴏᴜ ᴅᴇʟᴇᴛᴇ ᴛʜᴀᴛ??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("sticker-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + try: + await message.reply_to_message.reply_text( + "sᴏᴍᴇᴛʜɪɴɢ ᴡᴇɴᴛ ᴡʀᴏɴɢ!", quote=True + ) + except Exception: + return + + +def add_corners(im, rad): + circle = Image.new("L", (rad * 2, rad * 2), 0) + draw = ImageDraw.Draw(circle) + draw.ellipse((0, 0, rad * 2, rad * 2), fill=255) + alpha = Image.new("L", im.size, 255) + w, h = im.size + alpha.paste(circle.crop((0, 0, rad, rad)), (0, 0)) + alpha.paste(circle.crop((0, rad, rad, rad * 2)), (0, h - rad)) + alpha.paste(circle.crop((rad, 0, rad * 2, rad)), (w - rad, 0)) + alpha.paste(circle.crop((rad, rad, rad * 2, rad * 2)), (w - rad, h - rad)) + im.putalpha(alpha) + return im + + +async def edge_curved(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "ᴅᴏᴡɴʟᴏᴀᴅɪɴɢ ɪᴍᴀɢᴇ", quote=True + ) + a = await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("ᴘʀᴏᴄᴇssɪɴɢ ɪᴍᴀɢᴇ...") + im = Image.open(a) + im = add_corners(im, 100) + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "edge_curved.webp" + im.save(edit_img_loc) + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_sticker(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("ᴡʜʏ ᴅɪᴅ ʏᴏᴜ ᴅᴇʟᴇᴛᴇ ᴛʜᴀᴛ??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("edge_curved-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + try: + await message.reply_to_message.reply_text( + "sᴏᴍᴇᴛʜɪɴɢ ᴡᴇɴᴛ ᴡʀᴏɴɢ!", quote=True + ) + except Exception: + return + + +async def contrast(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "Downloading image", quote=True + ) + a = await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("ᴘʀᴏᴄᴇssɪɴɢ ɪᴍᴀɢᴇ...") + image = Image.open(a) + contrast = ImageEnhance.Contrast(image) + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "contrast.jpg" + contrast.enhance(1.5).save(edit_img_loc) + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("ᴡʜʏ ᴅɪᴅ ʏᴏᴜ ᴅᴇʟᴇᴛᴇ ᴛʜᴀᴛ??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("contrast-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + try: + await message.reply_to_message.reply_text( + "sᴏᴍᴇᴛʜɪɴɢ ᴡᴇɴᴛ ᴡʀᴏɴɢ!", quote=True + ) + except Exception: + return + + +def sepia(img): + width, height = img.size + new_img = img.copy() + for x in range(width): + for y in range(height): + red, green, blue = img.getpixel((x, y)) + new_val = 0.3 * red + 0.59 * green + 0.11 * blue + new_red = int(new_val * 2) + new_red = min(new_red, 255) + new_green = int(new_val * 1.5) + new_green = min(new_green, 255) + new_blue = int(new_val) + new_blue = min(new_blue, 255) + + new_img.putpixel((x, y), (new_red, new_green, new_blue)) + + return new_img + + +async def sepia_mode(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "ᴅᴏᴡɴʟᴏᴀᴅɪɴɢ ɪᴍᴀɢᴇ", quote=True + ) + a = await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("ᴘʀᴏᴄᴇssɪɴɢ ɪᴍᴀɢᴇ...") + image = Image.open(a) + new_img = sepia(image) + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "sepia.jpg" + new_img.save(edit_img_loc) + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("ᴡʜʏ ᴅɪᴅ ʏᴏᴜ ᴅᴇʟᴇᴛᴇ ᴛʜᴀᴛ??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("sepia_mode-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + try: + await message.reply_to_message.reply_text( + "sᴏᴍᴇᴛʜɪɴɢ ᴡᴇɴᴛ ᴡʀᴏɴɢ!", quote=True + ) + except Exception: + return + + +def dodgeV2(x, y): + return cv2.divide(x, 255 - y, scale=256) + + +async def pencil(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "pencil.jpg" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "ᴅᴏᴡɴʟᴏᴀᴅɪɴɢ ɪᴍᴀɢᴇ", quote=True + ) + a = await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("ᴘʀᴏᴄᴇssɪɴɢ ɪᴍᴀɢᴇ...") + img = cv2.imread(a) + img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) + img_invert = cv2.bitwise_not(img_gray) + img_smoothing = cv2.GaussianBlur(img_invert, (21, 21), sigmaX=0, sigmaY=0) + final_img = dodgeV2(img_gray, img_smoothing) + cv2.imwrite(edit_img_loc, final_img) + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("ᴡʜʏ ᴅɪᴅ ʏᴏᴜ ᴅᴇʟᴇᴛᴇ ᴛʜᴀᴛ??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("pencil-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + try: + await message.reply_to_message.reply_text( + "sᴏᴍᴇᴛʜɪɴɢ ᴡᴇɴᴛ ᴡʀᴏɴɢ!", quote=True + ) + except Exception: + return + + +def color_quantization(img, k): + data = np.float32(img).reshape((-1, 3)) + criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 20, 1.0) + _, label, center = cv2.kmeans( + data, k, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS + ) + center = np.uint8(center) + result = center[label.flatten()] + result = result.reshape(img.shape) + return result + + +async def cartoon(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "kang.jpg" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "ᴅᴏᴡɴʟᴏᴀᴅɪɴɢ ɪᴍᴀɢᴇ", quote=True + ) + a = await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("ᴘʀᴏᴄᴇssɪɴɢ ɪᴍᴀɢᴇ...") + img = cv2.imread(a) + edges = cv2.Canny(img, 100, 200) + gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) + edges = cv2.adaptiveThreshold( + gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 9, 5 + ) + color = cv2.bilateralFilter(img, d=9, sigmaColor=200, sigmaSpace=200) + + cv2.bitwise_and(color, color, mask=edges) + img_1 = color_quantization(img, 7) + cv2.imwrite(edit_img_loc, img_1) + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("ᴡʜʏ ᴅɪᴅ ʏᴏᴜ ᴅᴇʟᴇᴛᴇ ᴛʜᴀᴛ??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("cartoon-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + try: + await message.reply_to_message.reply_text( + "sᴏᴍᴇᴛʜɪɴɢ ᴡᴇɴᴛ ᴡʀᴏɴɢ!", quote=True + ) + except Exception: + return diff --git a/Exon/utils/resources/ImageEditor/edit_3.py b/Exon/utils/resources/ImageEditor/edit_3.py new file mode 100644 index 00000000..e5629084 --- /dev/null +++ b/Exon/utils/resources/ImageEditor/edit_3.py @@ -0,0 +1,192 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" +# ""DEAR PRO PEOPLE, DON'T REMOVE & CHANGE THIS LINE +# TG :- @Abishnoi1M +# MY ALL BOTS :- Abishnoi_bots +# GITHUB :- KingAbishnoi "" + +import os +import shutil + +from PIL import Image, ImageOps + + +async def black_border(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "ᴅᴏᴡɴʟᴏᴀᴅɪɴɢ ɪᴍᴀɢᴇ", quote=True + ) + a = await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("ᴘʀᴏᴄᴇssɪɴɢ ɪᴍᴀɢᴇ...") + img = Image.open(a) + img_with_border = ImageOps.expand(img, border=100, fill="black") + edit_img_loc = ( + "./DOWNLOADS" + "/" + userid + "/" + "imaged-black-border.png" + ) + img_with_border.save(edit_img_loc) + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("ᴡʜʏ ᴅɪᴅ ʏᴏᴜ ᴅᴇʟᴇᴛᴇ ᴛʜᴀᴛ??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("black_border-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + try: + await message.reply_to_message.reply_text( + "sᴏᴍᴇᴛʜɪɴɢ ᴡᴇɴᴛ ᴡʀᴏɴɢ!", quote=True + ) + except Exception: + return + + +async def green_border(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "Downloading image", quote=True + ) + a = await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("ᴘʀᴏᴄᴇssɪɴɢ ɪᴍᴀɢᴇ.....") + img = Image.open(a) + img_with_border = ImageOps.expand(img, border=100, fill="green") + edit_img_loc = ( + "./DOWNLOADS" + "/" + userid + "/" + "imaged-green-border.png" + ) + img_with_border.save(edit_img_loc) + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("ᴡʜʏ ᴅɪᴅ ʏᴏᴜ ᴅᴇʟᴇᴛᴇ ᴛʜᴀᴛ??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("green_border-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + try: + await message.reply_to_message.reply_text( + "sᴏᴍᴇᴛʜɪɴɢ ᴡᴇɴᴛ ᴡʀᴏɴɢ!", quote=True + ) + except Exception: + return + + +async def blue_border(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "Downloading image", quote=True + ) + a = await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("ᴘʀᴏᴄᴇssɪɴɢ ɪᴍᴀɢᴇ.....") + img = Image.open(a) + img_with_border = ImageOps.expand(img, border=100, fill="blue") + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "imaged-blue-border.png" + img_with_border.save(edit_img_loc) + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("ᴡʜʏ ᴅɪᴅ ʏᴏᴜ ᴅᴇʟᴇᴛᴇ ᴛʜᴀᴛ??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("blue_border-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + try: + await message.reply_to_message.reply_text( + "sᴏᴍᴇᴛʜɪɴɢ ᴡᴇɴᴛ ᴡʀᴏɴɢ!", quote=True + ) + except Exception: + return + + +async def red_border(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "Downloading image", quote=True + ) + a = await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("ᴘʀᴏᴄᴇssɪɴɢ ɪᴍᴀɢᴇ.....") + img = Image.open(a) + img_with_border = ImageOps.expand(img, border=100, fill="red") + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "imaged-red-border.png" + img_with_border.save(edit_img_loc) + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("ᴡʜʏ ᴅɪᴅ ʏᴏᴜ ᴅᴇʟᴇᴛᴇ ᴛʜᴀᴛ??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("red_border-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + try: + await message.reply_to_message.reply_text( + "sᴏᴍᴇᴛʜɪɴɢ ᴡᴇɴᴛ ᴡʀᴏɴɢ!", quote=True + ) + except Exception: + return diff --git a/Exon/utils/resources/ImageEditor/edit_4.py b/Exon/utils/resources/ImageEditor/edit_4.py new file mode 100644 index 00000000..8c0289e8 --- /dev/null +++ b/Exon/utils/resources/ImageEditor/edit_4.py @@ -0,0 +1,429 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" +# ""DEAR PRO PEOPLE, DON'T REMOVE & CHANGE THIS LINE +# TG :- @Abishnoi1M +# MY ALL BOTS :- Abishnoi_bots +# GITHUB :- KingAbishnoi "" + +import io +import os +import shutil + +import cv2 +import numpy as np +import requests +from PIL import Image, ImageDraw, ImageOps + +from Exon import REM_BG_API_KEY + + +async def rotate_90(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "rotate_90.jpg" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "Downloading image", quote=True + ) + a = await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("ᴘʀᴏᴄᴇssɪɴɢ ɪᴍᴀɢᴇ.....") + src = cv2.imread(a) + image = cv2.rotate(src, cv2.cv2.ROTATE_90_CLOCKWISE) + cv2.imwrite(edit_img_loc, image) + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("ᴡʜʏ ᴅɪᴅ ʏᴏᴜ ᴅᴇʟᴇᴛᴇ ᴛʜᴀᴛ??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("rotate_90-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + try: + await message.reply_to_message.reply_text( + "sᴏᴍᴇᴛʜɪɴɢ ᴡᴇɴᴛ ᴡʀᴏɴɢ!", quote=True + ) + except Exception: + return + + +async def rotate_180(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "rotate_180.jpg" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "Downloading image", quote=True + ) + a = await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("ᴘʀᴏᴄᴇssɪɴɢ ɪᴍᴀɢᴇ.....") + src = cv2.imread(a) + image = cv2.rotate(src, cv2.ROTATE_180) + cv2.imwrite(edit_img_loc, image) + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("ᴡʜʏ ᴅɪᴅ ʏᴏᴜ ᴅᴇʟᴇᴛᴇ ᴛʜᴀᴛ??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("rotate_180-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + try: + await message.reply_to_message.reply_text( + "sᴏᴍᴇᴛʜɪɴɢ ᴡᴇɴᴛ ᴡʀᴏɴɢ!", quote=True + ) + except Exception: + return + + +async def rotate_270(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "rotate_270.jpg" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "Downloading image", quote=True + ) + a = await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("ᴘʀᴏᴄᴇssɪɴɢ ɪᴍᴀɢᴇ.....") + src = cv2.imread(a) + image = cv2.rotate(src, cv2.ROTATE_90_COUNTERCLOCKWISE) + cv2.imwrite(edit_img_loc, image) + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("ᴡʜʏ ᴅɪᴅ ʏᴏᴜ ᴅᴇʟᴇᴛᴇ ᴛʜᴀᴛ??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("rotate_270-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + try: + await message.reply_to_message.reply_text( + "sᴏᴍᴇᴛʜɪɴɢ ᴡᴇɴᴛ ᴡʀᴏɴɢ!", quote=True + ) + except Exception: + return + + +def resize_photo(photo: str, userid: str) -> io.BytesIO: + image = Image.open(photo) + maxsize = 512 + scale = maxsize / max(image.width, image.height) + new_size = (int(image.width * scale), int(image.height * scale)) + image = image.resize(new_size, Image.LANCZOS) + resized_photo = io.BytesIO() + resized_photo.name = "./DOWNLOADS" + "/" + userid + "resized.png" + image.save(resized_photo, "PNG") + return resized_photo + + +async def round_sticker(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "ᴅᴏᴡɴʟᴏᴀᴅɪɴɢ ɪᴍᴀɢᴇ", quote=True + ) + a = await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("ᴘʀᴏᴄᴇssɪɴɢ ɪᴍᴀɢᴇ.....") + resized = resize_photo(a, userid) + img = Image.open(resized).convert("RGB") + npImage = np.array(img) + h, w = img.size + alpha = Image.new("L", img.size, 0) + draw = ImageDraw.Draw(alpha) + draw.pieslice([0, 0, h, w], 0, 360, fill=255) + npAlpha = np.array(alpha) + npImage = np.dstack((npImage, npAlpha)) + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "rounded.webp" + Image.fromarray(npImage).save(edit_img_loc) + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_sticker(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("ᴡʜʏ ᴅɪᴅ ʏᴏᴜ ᴅᴇʟᴇᴛᴇ ᴛʜᴀᴛ??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("round_sticker-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + try: + await message.reply_to_message.reply_text( + "sᴏᴍᴇᴛʜɪɴɢ ᴡᴇɴᴛ ᴡʀᴏɴɢ!", quote=True + ) + except Exception: + return + + +async def inverted(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "ᴅᴏᴡɴʟᴏᴀᴅɪɴɢ ɪᴍᴀɢᴇ", quote=True + ) + a = await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("ᴘʀᴏᴄᴇssɪɴɢ ɪᴍᴀɢᴇ.....") + image = Image.open(a) + inverted_image = ImageOps.invert(image) + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "inverted.png" + inverted_image.save(edit_img_loc) + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("ᴡʜʏ ᴅɪᴅ ʏᴏᴜ ᴅᴇʟᴇᴛᴇ ᴛʜᴀᴛ??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("inverted-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + try: + await message.reply_to_message.reply_text( + "sᴏᴍᴇᴛʜɪɴɢ ᴡᴇɴᴛ ᴡʀᴏɴɢ!", quote=True + ) + except Exception: + return + + +async def removebg_plain(client, message): + try: + if REM_BG_API_KEY != "": + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "nobgplain.png" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "ᴅᴏᴡɴʟᴏᴀᴅɪɴɢ ɪᴍᴀɢᴇ", quote=True + ) + await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("ᴘʀᴏᴄᴇssɪɴɢ ɪᴍᴀɢᴇ.....") + + response = requests.post( + "https://api.remove.bg/v1.0/removebg", + files={"image_file": open(download_location, "rb")}, + data={"size": "auto"}, + headers={"X-Api-Key": REM_BG_API_KEY}, + ) + if response.status_code == 200: + with open(f"{edit_img_loc}", "wb") as out: + out.write(response.content) + else: + await message.reply_to_message.reply_text( + "Check if your api is correct", quote=True + ) + return + + await message.reply_chat_action("upload_document") + await message.reply_to_message.reply_document(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("ᴡʜʏ ᴅɪᴅ ʏᴏᴜ ᴅᴇʟᴇᴛᴇ ᴛʜᴀᴛ??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + else: + await message.reply_to_message.reply_text( + "ɢᴇᴛ ᴛʜᴇ ᴀᴘɪ ғʀᴏᴍ https://www.remove.bg/b/background-removal-api ᴀɴᴅ ᴀᴅᴅ ɪɴ ᴄᴏɴғɪɢ ᴠᴀʀ", + quote=True, + disable_web_page_preview=True, + ) + except Exception as e: + print("removebg_plain-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + try: + await message.reply_to_message.reply_text( + "sᴏᴍᴇᴛʜɪɴɢ ᴡᴇɴᴛ ᴡʀᴏɴɢ!", quote=True + ) + except Exception: + return + + +async def removebg_white(client, message): + try: + if REM_BG_API_KEY != "": + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "nobgwhite.png" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "ᴅᴏᴡɴʟᴏᴀᴅɪɴɢ ɪᴍᴀɢᴇ", quote=True + ) + await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("ᴘʀᴏᴄᴇssɪɴɢ ɪᴍᴀɢᴇ.....") + + response = requests.post( + "https://api.remove.bg/v1.0/removebg", + files={"image_file": open(download_location, "rb")}, + data={"size": "auto"}, + headers={"X-Api-Key": REM_BG_API_KEY}, + ) + if response.status_code == 200: + with open(f"{edit_img_loc}", "wb") as out: + out.write(response.content) + else: + await message.reply_to_message.reply_text( + "ᴄʜᴇᴄᴋ ɪғ ʏᴏᴜʀ ᴀᴘɪ ɪs ᴄᴏʀʀᴇᴄᴛ", quote=True + ) + return + + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("ᴡʜʏ ᴅɪᴅ ʏᴏᴜ ᴅᴇʟᴇᴛᴇ ᴛʜᴀᴛ??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + else: + await message.reply_to_message.reply_text( + "ɢᴇᴛ ᴛʜᴇ ᴀᴘɪ ғʀᴏᴍ https://www.remove.bg/b/background-removal-api ᴀɴᴅ ᴀᴅᴅ ɪɴ ᴄᴏɴғɪɢ ᴠᴀʀ", + quote=True, + disable_web_page_preview=True, + ) + except Exception as e: + print("removebg_white-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + try: + await message.reply_to_message.reply_text( + "sᴏᴍᴇᴛʜɪɴɢ ᴡᴇɴᴛ ᴡʀᴏɴɢ!", quote=True + ) + except Exception: + return + + +async def removebg_sticker(client, message): + try: + if REM_BG_API_KEY != "": + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "nobgsticker.webp" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "Downloading image", quote=True + ) + await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("ᴘʀᴏᴄᴇssɪɴɢ ɪᴍᴀɢᴇ.....") + + response = requests.post( + "https://api.remove.bg/v1.0/removebg", + files={"image_file": open(download_location, "rb")}, + data={"size": "auto"}, + headers={"X-Api-Key": REM_BG_API_KEY}, + ) + if response.status_code == 200: + with open(f"{edit_img_loc}", "wb") as out: + out.write(response.content) + else: + await message.reply_to_message.reply_text( + "ᴄʜᴇᴄᴋ ɪғ ʏᴏᴜʀ ᴀᴘɪ ɪs ᴄᴏʀʀᴇᴄᴛ", quote=True + ) + return + + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_sticker(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("ᴡʜʏ ᴅɪᴅ ʏᴏᴜ ᴅᴇʟᴇᴛᴇ ᴛʜᴀᴛ??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + else: + await message.reply_to_message.reply_text( + "ɢᴇᴛ ᴛʜᴇ ᴀᴘɪ ғʀᴏᴍ https://www.remove.bg/b/background-removal-api ᴀɴᴅ ᴀᴅᴅ ɪɴ ᴄᴏɴғɪɢ ᴠᴀʀ", + quote=True, + disable_web_page_preview=True, + ) + except Exception as e: + print("removebg_sticker-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + try: + await message.reply_to_message.reply_text( + "sᴏᴍᴇᴛʜɪɴɢ ᴡᴇɴᴛ ᴡʀᴏɴɢ!", quote=True + ) + except Exception: + return diff --git a/Exon/utils/resources/ImageEditor/edit_5.py b/Exon/utils/resources/ImageEditor/edit_5.py new file mode 100644 index 00000000..f7abf781 --- /dev/null +++ b/Exon/utils/resources/ImageEditor/edit_5.py @@ -0,0 +1,442 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" +# ""DEAR PRO PEOPLE, DON'T REMOVE & CHANGE THIS LINE +# TG :- @Abishnoi1M +# MY ALL BOTS :- Abishnoi_bots +# GITHUB :- KingAbishnoi "" + + +import asyncio +import os +import shutil + + +async def normalglitch_1(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "normalglitch_1.jpg" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "ᴅᴏᴡɴʟᴏᴀᴅɪɴɢ ɪᴍᴀɢᴇ", quote=True + ) + await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("ᴘʀᴏᴄᴇssɪɴɢ ɪᴍᴀɢᴇ.....") + cd = ["glitch_this", "-c", "-o", edit_img_loc, download_location, "1"] + process = await asyncio.create_subprocess_exec( + *cd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE + ) + await process.communicate() + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("ᴡʜʏ ᴅɪᴅ ʏᴏᴜ ᴅᴇʟᴇᴛᴇ ᴛʜᴀᴛ??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("normalglitch_1-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + try: + await message.reply_to_message.reply_text( + "sᴏᴍᴇᴛʜɪɴɢ ᴡᴇɴᴛ ᴡʀᴏɴɢ!", quote=True + ) + except Exception: + return + + +async def normalglitch_2(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "normalglitch_2.jpg" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "ᴅᴏᴡɴʟᴏᴀᴅɪɴɢ ɪᴍᴀɢᴇ", quote=True + ) + await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("ᴘʀᴏᴄᴇssɪɴɢ ɪᴍᴀɢᴇ.....") + cd = ["glitch_this", "-c", "-o", edit_img_loc, download_location, "2"] + process = await asyncio.create_subprocess_exec( + *cd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE + ) + await process.communicate() + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("ᴡʜʏ ᴅɪᴅ ʏᴏᴜ ᴅᴇʟᴇᴛᴇ ᴛʜᴀᴛ??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("normalglitch_2-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + try: + await message.reply_to_message.reply_text( + "sᴏᴍᴇᴛʜɪɴɢ ᴡᴇɴᴛ ᴡʀᴏɴɢ!", quote=True + ) + except Exception: + return + + +async def normalglitch_3(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "normalglitch_3.jpg" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "ᴅᴏᴡɴʟᴏᴀᴅɪɴɢ ɪᴍᴀɢᴇ", quote=True + ) + await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("ᴘʀᴏᴄᴇssɪɴɢ ɪᴍᴀɢᴇ.....") + cd = ["glitch_this", "-c", "-o", edit_img_loc, download_location, "3"] + process = await asyncio.create_subprocess_exec( + *cd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE + ) + await process.communicate() + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("ᴡʜʏ ᴅɪᴅ ʏᴏᴜ ᴅᴇʟᴇᴛᴇ ᴛʜᴀᴛ??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("normalglitch_3-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + try: + await message.reply_to_message.reply_text( + "sᴏᴍᴇᴛʜɪɴɢ ᴡᴇɴᴛ ᴡʀᴏɴɢ!", quote=True + ) + except Exception: + return + + +async def normalglitch_4(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "normalglitch_4.jpg" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "ᴅᴏᴡɴʟᴏᴀᴅɪɴɢ ɪᴍᴀɢᴇ", quote=True + ) + await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("ᴘʀᴏᴄᴇssɪɴɢ ɪᴍᴀɢᴇ.....") + cd = ["glitch_this", "-c", "-o", edit_img_loc, download_location, "4"] + process = await asyncio.create_subprocess_exec( + *cd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE + ) + await process.communicate() + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("ᴡʜʏ ᴅɪᴅ ʏᴏᴜ ᴅᴇʟᴇᴛᴇ ᴛʜᴀᴛ??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("normalglitch_4-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + try: + await message.reply_to_message.reply_text( + "sᴏᴍᴇᴛʜɪɴɢ ᴡᴇɴᴛ ᴡʀᴏɴɢ!", quote=True + ) + except Exception: + return + + +async def normalglitch_5(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "normalglitch_5.jpg" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "ᴅᴏᴡɴʟᴏᴀᴅɪɴɢ ɪᴍᴀɢᴇ", quote=True + ) + await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("ᴘʀᴏᴄᴇssɪɴɢ ɪᴍᴀɢᴇ.....") + cd = ["glitch_this", "-c", "-o", edit_img_loc, download_location, "5"] + process = await asyncio.create_subprocess_exec( + *cd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE + ) + await process.communicate() + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("ᴡʜʏ ᴅɪᴅ ʏᴏᴜ ᴅᴇʟᴇᴛᴇ ᴛʜᴀᴛ??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("normalglitch_5-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + try: + await message.reply_to_message.reply_text( + "sᴏᴍᴇᴛʜɪɴɢ ᴡᴇɴᴛ ᴡʀᴏɴɢ!", quote=True + ) + except Exception: + return + + +async def scanlineglitch_1(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "scanlineglitch_1.jpg" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "ᴅᴏᴡɴʟᴏᴀᴅɪɴɢ ɪᴍᴀɢᴇ", quote=True + ) + await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("ᴘʀᴏᴄᴇssɪɴɢ ɪᴍᴀɢᴇ.....") + cd = ["glitch_this", "-c", "-s", "-o", edit_img_loc, download_location, "1"] + process = await asyncio.create_subprocess_exec( + *cd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE + ) + await process.communicate() + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("ᴡʜʏ ᴅɪᴅ ʏᴏᴜ ᴅᴇʟᴇᴛᴇ ᴛʜᴀᴛ??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("scanlineglitch_1-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + try: + await message.reply_to_message.reply_text( + "sᴏᴍᴇᴛʜɪɴɢ ᴡᴇɴᴛ ᴡʀᴏɴɢ!", quote=True + ) + except Exception: + return + + +async def scanlineglitch_2(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "scanlineglitch_2.jpg" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "Downloading image", quote=True + ) + await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("ᴘʀᴏᴄᴇssɪɴɢ ɪᴍᴀɢᴇ.....") + cd = ["glitch_this", "-c", "-s", "-o", edit_img_loc, download_location, "2"] + process = await asyncio.create_subprocess_exec( + *cd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE + ) + await process.communicate() + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("ᴡʜʏ ᴅɪᴅ ʏᴏᴜ ᴅᴇʟᴇᴛᴇ ᴛʜᴀᴛ??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("scanlineglitch_2-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + try: + await message.reply_to_message.reply_text( + "sᴏᴍᴇᴛʜɪɴɢ ᴡᴇɴᴛ ᴡʀᴏɴɢ!", quote=True + ) + except Exception: + return + + +async def scanlineglitch_3(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "scanlineglitch_3.jpg" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "ᴅᴏᴡɴʟᴏᴀᴅɪɴɢ ɪᴍᴀɢᴇ", quote=True + ) + await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("ᴘʀᴏᴄᴇssɪɴɢ ɪᴍᴀɢᴇ.....") + cd = ["glitch_this", "-c", "-s", "-o", edit_img_loc, download_location, "3"] + process = await asyncio.create_subprocess_exec( + *cd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE + ) + await process.communicate() + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("ᴡʜʏ ᴅɪᴅ ʏᴏᴜ ᴅᴇʟᴇᴛᴇ ᴛʜᴀᴛ??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("scanlineglitch_3-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + try: + await message.reply_to_message.reply_text( + "sᴏᴍᴇᴛʜɪɴɢ ᴡᴇɴᴛ ᴡʀᴏɴɢ!", quote=True + ) + except Exception: + return + + +async def scanlineglitch_4(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "scanlineglitch_4.jpg" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "ᴅᴏᴡɴʟᴏᴀᴅɪɴɢ ɪᴍᴀɢᴇ", quote=True + ) + await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("ᴘʀᴏᴄᴇssɪɴɢ ɪᴍᴀɢᴇ.....") + cd = ["glitch_this", "-c", "-s", "-o", edit_img_loc, download_location, "4"] + process = await asyncio.create_subprocess_exec( + *cd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE + ) + await process.communicate() + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("ᴡʜʏ ᴅɪᴅ ʏᴏᴜ ᴅᴇʟᴇᴛᴇ ᴛʜᴀᴛ??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("scanlineglitch_4-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + try: + await message.reply_to_message.reply_text( + "sᴏᴍᴇᴛʜɪɴɢ ᴡᴇɴᴛ ᴡʀᴏɴɢ!", quote=True + ) + except Exception: + return + + +async def scanlineglitch_5(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "scanlineglitch_5.jpg" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "ᴅᴏᴡɴʟᴏᴀᴅɪɴɢ ɪᴍᴀɢᴇ", quote=True + ) + await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("ᴘʀᴏᴄᴇssɪɴɢ ɪᴍᴀɢᴇ.....") + cd = ["glitch_this", "-c", "-s", "-o", edit_img_loc, download_location, "5"] + process = await asyncio.create_subprocess_exec( + *cd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE + ) + await process.communicate() + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("ᴡʜʏ ᴅɪᴅ ʏᴏᴜ ᴅᴇʟᴇᴛᴇ ᴛʜᴀᴛ??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("scanlineglitch_5-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + try: + await message.reply_to_message.reply_text( + "sᴏᴍᴇᴛʜɪɴɢ ᴡᴇɴᴛ ᴡʀᴏɴɢ!", quote=True + ) + except Exception: + return diff --git a/Exon/utils/saavnhelp.py b/Exon/utils/saavnhelp.py new file mode 100644 index 00000000..7ea33988 --- /dev/null +++ b/Exon/utils/saavnhelp.py @@ -0,0 +1,36 @@ +""" +MIT License + +Copyright (c) 2022 ABISHNOI + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" +# ""DEAR PRO PEOPLE, DON'T REMOVE & CHANGE THIS LINE +# TG :- @Abishnoi1M +# MY ALL BOTS :- Abishnoi_bots +# GITHUB :- KingAbishnoi "" + + +def get_arg(message): + msg = message.text + msg = msg.replace(" ", "", 1) if msg[1] == " " else msg + split = msg[1:].replace("\n", " \n").split(" ") + if " ".join(split[1:]).strip() == "": + return "" + return " ".join(split[1:]) diff --git a/Exon/utils/tools.py b/Exon/utils/tools.py new file mode 100644 index 00000000..9eda0885 --- /dev/null +++ b/Exon/utils/tools.py @@ -0,0 +1,82 @@ +""" +MIT License + +Copyright (c) 2022 ABISHNOI + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" +# ""DEAR PRO PEOPLE, DON'T REMOVE & CHANGE THIS LINE +# TG :- @Abishnoi1M +# MY ALL BOTS :- Abishnoi_bots +# GITHUB :- KingAbishnoi "" + + +import hashlib +import re + + +async def md5(fname: str) -> str: + hash_md5 = hashlib.md5() + with open(fname, "rb") as f: + for chunk in iter(lambda: f.read(4096), b""): + hash_md5.update(chunk) + return hash_md5.hexdigest() + + +def humanbytes(size: int) -> str: + if size is None or isinstance(size, str): + 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" + + +def time_formatter(seconds: int) -> str: + 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 "") + ) + return tmp[:-2] + + +def human_to_bytes(size: str) -> int: + units = { + "M": 2**20, + "MB": 2**20, + "G": 2**30, + "GB": 2**30, + "T": 2**40, + "TB": 2**40, + } + + size = size.upper() + if not re.match(r" ", size): + size = re.sub(r"([KMGT])", r" \1", size) + number, unit = [string.strip() for string in size.split()] + return int(float(number) * units[unit]) diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..3d7d7c75 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 ABISHNOI + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Procfile b/Procfile new file mode 100644 index 00000000..4f53667e --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +worker: python3 -m Exon diff --git a/app.json b/app.json new file mode 100644 index 00000000..1410beae --- /dev/null +++ b/app.json @@ -0,0 +1,213 @@ +{ + "name": "EXON v1", + "description": "A Telegram group management bot.", + "logo": "https://telegra.ph/file/550a1524598af2578cbf3.jpg", + "keywords": [ + "telegram", + "anime", + "group", + "manager", + "Exon" + ], + "repository": "https://github.com/Abishnoi69/ExonRobot", + "addons": [ + { + "options": { + "version": "12" + }, + "plan": "heroku-postgresql" + } + ], + "env": { + "TOKEN": { + "description": "Your bot token. Can be obtained from https://telegram.dog/BotFather/", + "required": true, + "value": "YOUR BOT_TOKEN" + }, + "API_ID": { + "description": "Get API_ID from my.telegram.org, used for telethon based modules.", + "required": true, + "value": "13600724" + }, + "API_HASH": { + "description": "Get API_HASH from my.telegram.org, used for telethon based modules.", + "required": true, + "value": "ee59fd28d0d065c6b7d105082c6a0ba0" + }, + "APP_ID": { + "description": "Same as API_ID, BUT 2nd idi", + "required": true, + "value": "13600724" + }, + "APP_HASH": { + "description": "Same as API_HASH, but 2nd id", + "required": true, + "value": "ee59fd28d0d065c6b7d105082c6a0ba0" + }, + "DATABASE_URL": { + "description": "Your Postgres SQL DataBase URI, Get one from https://customer.elephantsql.com/login", + "required": true, + "value": "postgres://qszfsijv:y6sYqkEb8Z9lFGBmriG7AYjbSbgAJBVk@peanut.db.elephantsql.com/qszfsijv" + }, + "SQLALCHEMY_DATABASE_URI": { + "description": "Your Postgres SQL DataBase URI, Get one from https://customer.elephantsql.com/login (DATABASE_URL)", + "required": true, + "value": "postgres://qszfsijv:y6sYqkEb8Z9lFGBmriG7AYjbSbgAJBVk@peanut.db.elephantsql.com/qszfsijv" + }, + "DATABASE_URL2": { + "description": "Your one extra Mongo DataBase URI, Get one from mongodb.com", + "required": true, + "value": "mongodb+srv://EXONTESTMONGO:EXONTESTMONGO@cluster0.bviw7ic.mongodb.net/?retryWrites=true&w=majority" + }, + "REDIS_URL": { + "description": "Your Redis URL, Get one from redislabs.com", + "required": true, + "value": "redis://Madharjoot:GuKhao123_@redis-12276.c275.us-east-1-4.ec2.cloud.redislabs.com:12276/Madharjoot" + }, + "REM_BG_API_KEY": { + "description": "From:- https://www.remove.bg/", + "required": false, + "value": "LSdLgCceYz8vNqFgJVzrkDgR" + }, + "OWNER_ID": { + "description": "Your Telegram User ID as an integer.", + "required": true, + "value": "1452219013" + }, + "BOT_ID": { + "description": "Your Telegram Bot ID.", + "required": true, + "value": "5662485106" + }, + "OWNER_USERNAME": { + "description": "Your Telegram username without the @", + "value": "Abishnoi1M" + }, + "SUPPORT_CHAT": { + "description": "The Telegram Support Group Where Users of your Bot can Go and ask their Questions.", + "required": true, + "value": "AbishnoiMF" + }, + "UPDATES_CHANNEL": { + "description": "The Telegram News/Update Channel Where Users of your Bot can Go and check updates by you.", + "required": true, + "value": "Abishnoi_bots" + }, + "EVENT_LOGS": { + "description": "Event logs CHANNEL to note down important bot level events, recommend to make this public. ex: '-123456'", + "required": true, + "value": "-1001497222182" + }, + "JOIN_LOGGER": { + "description": "A CHANNEL where bot will print who added it to what group, useful during debugging or spam handling.", + "required": true, + "value": "-1001497222182" + }, + "TIME_API_KEY": { + "description": "Required for timezone information. Get yours from https://timezonedb.com/api", + "required": false, + "value": "5LB4TAKPEKZ0" + }, + "START_IMG": { + "description": "A Start img link", + "required": false, + "value": "https://telegra.ph/file/0191f2041ae33e11c1267.jpg" + }, + "DEV_USERS": { + "description": "Enter the IDs of the users who are the Developers of the Bot. If you are newbie and want help from us Itself, then keep the IDs as they are and add yours", + "required": false, + "value": "1452219013" + }, + "SPAMWATCH_API": { + "description": "Spamwatch API Token, Get one from @SpamWatchBot.", + "required": false, + "value": "AHT~6SSflemqFZa5tfkKFQeiLnJUwoowWYozC3SN2ZY_aSGh83~9pItAmM4Mi40E" + }, + "DRAGONS": { + "description": "A space separated list of user IDs who you want to assign as sudo users.", + "required": false, + "value": "1452219013" + }, + "STRICT_GBAN": { + "description": "Enforce gbans across new groups as well as old groups. When a gbanned user talks, he will be banned.", + "value": "True" + }, + "DEMONS": { + "description": "A space separated list of user IDs who can use Gban From Your Bot", + "required": false, + "value": "1452219013" + }, + "TIGERS": { + "description": "A space separated list of user IDs who can not be banned by your Bot", + "required": false, + "value": "1452219013" + }, + "WOLVES": { + "description": "A space separated list of user IDs who you want to assign as whitelisted - can't be banned with your bot.", + "required": false, + "value": "1452219013" + }, + "ENV": { + "description": "Setting this to ANYTHING will enable environment variables. Leave it as it is", + "value": "ANYTHING" + }, + "URL": { + "description": "The Heroku App URL :- https://<appname>.herokuapp.com/", + "required": false, + "value": "https://example69.herokuapp.com/" + }, + "GENIUS_API_TOKEN": { + "description": "Genius API Token get one from genius.com/api-client", + "required": false, + "value": "gIgMyTXuwJoY9VCPNwKdb_RUOA_9mCMmRlbrrdODmNvcpslww_2RIbbWOB8YdBW9" + }, + "DONATION_LINK": { + "description": "Optional: link where you would like to receive donations. If you are a noob, better leave it linking to paul", + "required": false, + "value": "https://t.me/Abishnoi1M" + }, + "MONGO_DB_URL": { + "description": "Your Mongo DB URI", + "required": true, + "value": "mongodb+srv://EXONTESTMONGO:EXONTESTMONGO@cluster0.bviw7ic.mongodb.net/?retryWrites=true&w=majority" + }, + "HEROKU_API_KEY": { + "description": "Your Heroku API Key from heroku profile", + "required": false, + "value": "" + }, + "HEROKU_APP_NAME": { + "description": "Your Heroku APP name", + "required": false, + "value": "" + }, + "INFOPIC": { + "description": "Don't change", + "required": true, + "value": "True" + }, + "ARQ_API_KEY": { + "description": "Get one @ARQRobot on Telegram", + "required": true, + "value": "SNEOSP-HQSCLP-BMVHJF-FQLQTR-ARQ" + }, + "ARQ_API_URL": { + "description": "Leave it as it is", + "required": true, + "value": "arq.hamker.dev" + }, + "ERROR_LOGS": { + "description": "The ID of the Group where the bot will spam with shitty errors and techy stuff", + "required": true, + "value": "-1001497222182" + } + }, + "buildpacks": [ + { + "url": "https://github.com/heroku/heroku-buildpack-python" + }, + { + "url": "https://github.com/jonathanong/heroku-buildpack-ffmpeg-latest" + } + ] +} diff --git a/config.py b/config.py new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/config.py @@ -0,0 +1 @@ + diff --git a/heroku.yml b/heroku.yml new file mode 100644 index 00000000..8a956f41 --- /dev/null +++ b/heroku.yml @@ -0,0 +1,5 @@ +build: + docker: + worker: Dockerfile +run: + worker: python3 -m Exon diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..9e357a09 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,105 @@ +aiofiles +aiogram +aiohttp[speedups] +aioredis +alphabet_detector +asyncio +beautifulsoup4==4.11.1 +better_profanity +bing_image_downloader +bleach +bs4 +cachetools +cloudscraper +countryinfo +cowpy +cryptg +cryptography +dateparser==1.1.1 +dnspython +emoji==1.7.0 +envparse +faker +feedparser +ffmpeg-python +fontTools +future +geopy +git+https://github.com/python-telegram-bot/ptbcontrib.git +gitpython +glitch_this +glitch_this +google-api-python-client +google-trans-new +google-trans-new +googletrans +gpytranslate +gtts +hachoir +heroku3 +hiredis +html-telegraph-poster +html2text +httpx +humanize +img2pdf +jikanpy +launchpadlib==1.10.17 +lxml +lyricsgenius +markdown +markdown2>=2.4.0 +motor<2.4.0,>=2.1.0 +multicolorcaptcha +nekos.py +nltk +nudepy +NumPy +odmantic +opencv-python-headless +pendulum +Pillow +pretty_errors +psutil +psycopg2-binary +PyDictionary==2.0.1 +pydub +pykeyboard +pymongo>=3.10 +pynewtonmath +pyrate-limiter +pyrogram==1.4.16 +python-arq +python-dotenv +python-rapidjson +python-telegram-bot==13.14 +pytimeparse +pytube +pytz +pyYAML>=5.1.2 +redis +regex +requests +requests_html +search_engine_parser +secureme +selenium +spamwatch +speedtest-cli +spongemock +SQLAlchemy==1.4.36 +telegraph +telethon==1.25.4 +textblob +tgcrypto +tswift +ujson +uvloop +wget +wikipedia +youtube_dl +youtube_search +youtube_search_python +yt-dlp +zalgo-text +#ǫ ʀᴇ sᴀʟᴇ ɢᴀʏ ᴋʏ ᴅᴇᴋ ʀᴀ ʜ ʏᴀ