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 : `sᴇᴛs ᴄʜᴀᴛ ᴛɪᴛʟᴇ`
+
+ • /setdesc : `sᴇᴛs ᴄʜᴀᴛ ᴅᴇsᴄʀɪᴘᴛɪᴏɴ`
+
+ • /setsticker : ` sᴇᴛs sᴛɪᴄᴋᴇʀ ᴘᴀᴄᴋ ɪɴ ᴀ sᴜᴘᴇʀɢʀᴏᴜᴘ`
+
+ • /setgpic : `sᴇᴛs ɢʀᴏᴜᴘ's ᴘʀᴏғɪʟᴇ ᴘʜᴏᴛᴏ `
+
+ • /delgpic: `ʀᴇᴍᴏᴠᴇs ɢʀᴏᴜᴘ's ᴘʀᴏғɪʟᴇ ᴘʜᴏᴛᴏ `
+
+ • /admins: `sʜᴏᴡs ᴀᴅᴍɪɴ ʟɪsᴛ ɪɴ ᴛʜᴇ ᴄʜᴀᴛ`
+
+ • /invitelink: `ɢᴇᴛs ɪɴᴠɪᴛᴇ ʟɪɴᴋ ᴏғ ᴛʜᴀᴛ ᴄʜᴀᴛ `
+
+*ᴍᴏᴅᴇʀᴀᴛɪᴏɴ*:
+
+❉ ʙᴀɴɴɪɴɢ ᴀɴᴅ ᴋɪᴄᴋs: ❉
+
+ • /ban : `ʙᴀɴs a ᴜsᴇʀ (ᴠɪᴀ ʜᴀɴᴅʟᴇ, ᴏʀ ʀᴇᴘʟʏ`)
+
+ • /sban : `sɪʟᴇɴᴛʟʏ ʙᴀɴ ᴀ ᴜsᴇʀ ᴛʜᴇɴ ᴅᴇʟᴇᴛᴇs ᴄᴏᴍᴍᴀɴᴅ + ʀᴇᴘʟɪᴇᴅ ᴛᴏ ᴍᴇssᴀɢᴇ ᴀɴᴅ ᴅᴏᴇsɴ'ᴛ ʀᴇᴘʟʏ (ᴠɪᴀ ʜᴀɴᴅʟᴇ, ᴏʀ ʀᴇᴘʟʏ)`
+
+ • /dban : `sɪʟᴇɴᴛʟʏ ʙᴀɴs ᴛʜᴇ ᴜsᴇʀ ᴀɴᴅ ᴅᴇʟᴇᴛᴇs ᴛʜᴇ ᴛᴀʀɢᴇᴛ ʀᴇᴘʟɪᴇᴅ ᴛᴏ ᴍᴇssᴀɢᴇ
+
+ • /tban x(m/h/d): `ʙᴀɴs ᴀ ᴜsᴇʀ ғᴏʀ x ᴛɪᴍᴇ, (ᴠɪᴀ ʜᴀɴᴅʟᴇ, ᴏʀ ʀᴇᴘʟʏ) ᴍ = ᴍɪɴᴜᴛᴇs, ʜ = ʜᴏᴜʀs, ᴅ = ᴅᴀʏs
+
+ • /unban : `ᴜɴʙᴀɴs ᴀ ᴜsᴇʀ (ᴠɪᴀ ʜᴀɴᴅʟᴇ, ᴏʀ ʀᴇᴘʟʏ)`
+
+ • /punch or kick : `ᴘᴜɴᴄʜᴇs ᴀ ᴜsᴇʀ ᴏᴜᴛ ᴏғ ᴛʜᴇ group (ᴠɪᴀ ʜᴀɴᴅʟᴇ, ᴏʀ ʀᴇᴘʟʏ)`
+
+❉ ᴍᴜᴛɪɴɢ: ❉
+
+ • /mute : `sɪʟᴇɴᴄᴇs ᴀ ᴜsᴇʀ, ᴄᴀɴ ᴀʟsᴏ ʙᴇ ᴜsᴇᴅ ᴀs ᴀ ʀᴇᴘʟʏ, ᴍᴜᴛɪɴɢ ᴛʜᴇ ʀᴇᴘʟɪᴇᴅ ᴛᴏ ᴜsᴇʀ`
+
+ • /tmute x(m/h/d): ᴍᴜᴛᴇs ᴀ ᴜsᴇʀ ғᴏʀ x ᴛɪᴍᴇ (ᴠɪᴀ ʜᴀɴᴅʟᴇ, ᴏʀ ʀᴇᴘʟʏ). ᴍ = minutes, ʜ = ʜᴏᴜʀs, ᴅ = ᴅᴀʏs
+
+ • /unmute : `ᴜɴᴍᴜᴛᴇ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 = "ᴛʜɪs ᴜsᴇʀ ɪs ᴄᴜʀʀᴇɴᴛʟʏ ᴀғᴋ (ᴀᴡᴀʏ ғʀᴏᴍ ᴋᴇʏʙᴏᴀʀᴅ)."
+ text += f"\nsɪɴᴄᴇ: {since_afk}"
+
+ else:
+ text = "ᴛʜɪs ᴜsᴇʀ ɪs ᴄᴜʀʀᴇɴᴛʟʏ ɪsɴ'ᴛ ᴀғᴋ (ᴀᴡᴀʏ ғʀᴏᴍ ᴋᴇʏʙᴏᴀʀᴅ)."
+ 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 )",
+ )
+ 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("", "")
+ .replace("", "")
+ .replace("
", "")
+ .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("", ""),
+ parse_mode=ParseMode.MARKDOWN,
+ )
+ else:
+ update.effective_message.reply_text(
+ msg.replace("", ""),
+ 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"sᴇᴀʀᴄʜ ʀᴇsᴜʟᴛs ғᴏʀ {html.escape(search_query)}
on @KaizokuAnime: \n\n"
+ for entry in search_result:
+ post_link = "https://animekaizoku.com/" + entry.a["href"]
+ post_name = html.escape(entry.text)
+ result += f"• {post_name}\n"
+ else:
+ more_results = False
+ result = f"ɴᴏ ʀᴇsᴜʟᴛ ғᴏᴜɴᴅ ғᴏʀ {html.escape(search_query)}
on @KaizokuAnime"
+
+ post_link = entry.a["href"]
+ post_name = html.escape(entry.text.strip())
+ result += f"• {post_name}\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 *:* `ʀᴇᴛᴜʀɴs ɪɴғᴏʀᴍᴀᴛɪᴏɴ ᴀʙᴏᴜᴛ ᴛʜᴇ ᴀɴɪᴍᴇ ғʀᴏᴍ ᴀɴɪʟɪsᴛ `
+
+• /character *:* `ʀᴇᴛᴜʀɴs ɪɴғᴏʀᴍᴀᴛɪᴏɴ ᴀʙᴏᴜᴛ ᴛʜᴇ ᴄʜᴀʀᴀᴄᴛᴇʀ ғʀᴏᴍ ᴀɴɪʟɪsᴛ `
+
+• /manga *:* `ʀᴇᴛᴜʀɴs ɪɴғᴏʀᴍᴀᴛɪᴏɴ ᴀʙᴏᴜᴛ ᴛʜᴇ ᴍᴀɴɢᴀ ғʀᴏᴍ ᴀɴɪʟɪsᴛ `
+
+• /upcoming*:* `ʀᴇᴛᴜʀɴs ᴀ ʟɪsᴛ ᴏғ ɴᴇᴡ ᴀɴɪᴍᴇ ɪɴ ᴛʜᴇ upcoming sᴇᴀsᴏɴs ғʀᴏᴍ ᴀɴɪʟɪsᴛ `
+
+• /airing *:* `ʀᴇᴛᴜʀɴ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"❝ {quote}❞\n\n{character} from {anime}"
+ 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"❝ {quote}❞\n\n{character} from {anime}"
+ 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"{html.escape(chat.title)}:\n#{tag}\nUser: {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"{chat.title}:\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"{html.escape(chat_name)}:\n#sᴇᴛғʟᴏᴏᴅ\nAdmin: {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"{html.escape(chat_name)}:\n#SETFLOOD\nAdmin: {mention_html(user.id, user.first_name)}\nSet antiflood to {amount}
."
+
+ 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 `.
+ ᴇ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 `.
+ ᴇ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"{settypeflood}:\nAdmin: {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 *:* `ᴇɴᴀʙʟᴇᴅ ᴀɴᴛɪɴ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"{html.escape(chat.title)}:\n"
+ f"#ᴀᴘᴘʀᴏᴠᴇᴅ\n"
+ f"ᴀᴅᴍɪɴ: {mention_html(user.id, user.first_name)}\n"
+ f"ᴜsᴇʀ: {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"{html.escape(chat.title)}:\n"
+ f"#ᴜɴᴀᴘᴘʀᴏᴠᴇᴅ\n"
+ f"ᴀᴅᴍɪɴ: {mention_html(user.id, user.first_name)}\n"
+ f"ᴜsᴇʀ: {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"{html.escape(chat.title)}:\n"
+ f"#{'S' if silent else ''}ʙᴀɴɴᴇᴅ\n"
+ f"ᴀᴅᴍɪɴ: {mention_html(user.id, html.escape(user.first_name))}\n"
+ f"ᴜsᴇʀ: {mention_html(member.user.id, html.escape(member.user.first_name))}"
+ )
+ if reason:
+ log += f"ʀᴇᴀsᴏɴ: {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"❕
Ban Event\n\n"
+ f"• ᴜsᴇʀ: {mention_html(member.user.id, html.escape(member.user.first_name))}\n"
+ f"• ᴜsᴇʀ 𝙸𝙳: {member.user.id}
\n"
+ f"• ʙᴀɴɴᴇᴅ ʙʏ: {mention_html(user.id, html.escape(user.first_name))}"
+ )
+ if reason:
+ reply += f"\n• ʀᴇᴀsᴏɴ: {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"{html.escape(chat.title)}:\n"
+ "#ᴛᴇᴍᴘ ʙᴀɴɴᴇᴅ\n"
+ f"ᴀᴅᴍɪɴ: {mention_html(user.id, html.escape(user.first_name))}\n"
+ f"ᴜsᴇʀ: {mention_html(member.user.id, html.escape(member.user.first_name))}\n"
+ f"ᴛɪᴍᴇ: {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"❕
Temporarily Banned\n\n"
+ f"• ᴜsᴇʀ: {mention_html(member.user.id, html.escape(member.user.first_name))}\n"
+ f"• ᴜsᴇʀ ɪᴅ: {member.user.id}
\n"
+ f"• ʙᴀɴɴᴇᴅ ғᴏʀ: {time_val}\n"
+ f"• ʙᴀɴɴᴇᴅ ʙʏ: {mention_html(user.id, html.escape(user.first_name))}"
+ )
+ if reason:
+ reply_msg += f"\n• ʀᴇᴀsᴏɴ: {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))} [{member.user.id}
] ʙᴀɴɴᴇᴅ ғᴏʀ {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))} [{member.user.id}
] ᴜɴʙᴀɴɴᴇᴅ ʙʏ {mention_html(user.id, html.escape(user.first_name))}",
+ parse_mode=ParseMode.HTML,
+ )
+ bot.answer_callback_query(query.id, text="Unbanned!")
+ return (
+ f"{html.escape(chat.title)}:\n"
+ f"#ᴜɴʙᴀɴɴᴇᴅ\n"
+ f"ᴀᴅᴍɪɴ: {mention_html(user.id, user.first_name)}\n"
+ f"ᴜsᴇʀ: {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))} [{member.user.id}
] Kicked by {mention_html(user.id, html.escape(user.first_name))}",
+ parse_mode=ParseMode.HTML,
+ )
+ log = (
+ f"{html.escape(chat.title)}:\n"
+ f"#ᴋɪᴄᴋᴇᴅ\n"
+ f"ᴀᴅᴍɪɴ: {mention_html(user.id, html.escape(user.first_name))}\n"
+ f"ᴜsᴇʀ: {mention_html(member.user.id, html.escape(member.user.first_name))}"
+ )
+ if reason:
+ log += f"\nʀᴇᴀsᴏɴ: {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))} [{member.user.id}
] ᴡᴀs ᴜɴʙᴀɴɴᴇᴅ ʙʏ {mention_html(user.id, user.first_name)}",
+ parse_mode=ParseMode.HTML,
+ )
+ log = (
+ f"{html.escape(chat.title)}:\n"
+ f"#ᴜɴʙᴀɴɴᴇᴅ\n"
+ f"ᴀᴅᴍɪɴ: {mention_html(user.id, html.escape(user.first_name))}\n"
+ f"ᴜsᴇʀ: {mention_html(member.user.id, html.escape(member.user.first_name))}"
+ )
+ if reason:
+ log += f"\nʀᴇᴀsᴏɴ: {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"{html.escape(chat.title)}:\n"
+ f"#ᴜɴʙᴀɴɴᴇᴅ\n"
+ f"ᴜsᴇʀ: {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"{html.escape(chat.title)}:\n#ʙᴀɴᴍᴇ\nᴜsᴇʀ: {mention_html(user.id, user.first_name)}\nɪᴅ: {user_id}
"
+
+ 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 *:*` ʙᴀɴs ᴀ ᴜsᴇʀ. (ᴠɪᴀ ʜᴀɴᴅʟᴇ, ᴏʀ ʀᴇᴘʟʏ `
+)
+• /sban <ᴜsᴇʀʜᴀɴᴅʟᴇ>*:* `sɪʟᴇɴᴛʟʏ ʙᴀɴ ᴀ ᴜsᴇʀ. ᴅᴇʟᴇᴛᴇs ᴄᴏᴍᴍᴀɴᴅ, ʀᴇᴘʟɪᴇᴅ ᴍᴇssᴀɢᴇ ᴀɴᴅ ᴅᴏᴇsɴ'ᴛ ʀᴇᴘʟʏ. (ᴠɪᴀ ʜᴀɴᴅʟᴇ, ᴏʀ ʀᴇᴘʟʏ)`
+
+• /tban <ᴜsᴇʀʜᴀɴᴅʟᴇ> x(m/h/d)*:* `ʙᴀɴs ᴀ ᴜsᴇʀ ғᴏʀ x ᴛɪᴍᴇ. (ᴠɪᴀ ʜᴀɴᴅʟᴇ, ᴏʀ ʀᴇᴘʟʏ). ᴍ = ᴍɪɴᴜᴛᴇs, h = ʜᴏᴜʀs, d = ᴅᴀʏs.`
+
+• /unban *:* `ᴜɴʙᴀɴs ᴀ ᴜsᴇʀ. (ᴠɪᴀ ʜᴀɴᴅʟᴇ, ᴏʀ ʀᴇᴘʟʏ )`
+
+• /kick *:* `ᴋɪᴄᴋs ᴀ ᴜsᴇʀ ᴏᴜᴛ ᴏғ ᴛʜᴇ ɢʀᴏᴜᴘ, (via ʜᴀɴᴅʟᴇ, ᴏʀ ʀᴇᴘʟʏ)`
+
+• /mute *:* `sɪʟᴇɴᴄᴇs ᴀ ᴜsᴇʀ. ᴄᴀɴ ᴀʟsᴏ ʙᴇ ᴜsᴇᴅ ᴀs ᴀ ʀᴇᴘʟʏ, ᴍᴜᴛɪɴɢ ᴛʜᴇ ʀᴇᴘʟɪᴇᴅ ᴛᴏ ᴜsᴇʀ.`
+
+• /tmute x(m/h/d)*:* `ᴍᴜᴛᴇs a ᴜsᴇʀᴛ for x ᴛɪᴍᴇ. (ᴠɪᴀ ʜᴀɴᴅʟᴇ, ᴏʀ ʀᴇᴘʟʏ). ᴍ = ᴍɪɴᴜᴛᴇs, h = ʜᴏᴜʀs, d = ᴅᴀʏs `
+.
+• /unmute *:* `ᴜɴᴍᴜᴛᴇs ᴀ ~ user. ᴄᴀɴ ᴀʟsᴏ ʙᴇ ᴜsᴇᴅ ᴀs a ʀᴇᴘʟʏ, ᴍᴜᴛɪɴɢ ᴛʜᴇ ʀᴇᴘʟɪᴇᴅ ᴛᴏ ᴜsᴇʀ `
+.
+• /zombies*:* `sᴇᴀʀᴄʜᴇs ᴅᴇʟᴇᴛᴇᴅ ᴀᴄᴄᴏᴜɴᴛ `
+
+• /zombies clean*:* `ʀᴇᴍᴏᴠᴇs ᴅᴇʟᴇᴛᴇᴅ ᴀᴄᴄᴏᴜɴᴛs ғʀᴏᴍ ᴛʜᴇ ɢʀᴏᴜᴘ `
+.
+• /abishnoi <ᴍ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 ɪɴ {chat_name}:\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"{html.escape(trigger)}
\n"
+ else:
+ for trigger in all_blacklisted:
+ filter_list += f" - {html.escape(trigger)}
\n"
+
+ # for trigger in all_blacklisted:
+ # filter_list += " - {}
\n".format(html.escape(trigger))
+
+ split_text = split_message(filter_list)
+ for text in split_text:
+ if (
+ filter_list
+ == f"ᴄᴜʀʀᴇɴᴛ ʙʟᴀᴄᴋʟɪsᴛᴇᴅ ᴡᴏʀᴅs ɪɴ {html.escape(chat_name)}:\n"
+ ):
+ send_message(
+ update.effective_message,
+ f"ɴᴏ ʙʟᴀᴄᴋʟɪsᴛᴇᴅ ᴡᴏʀᴅs ɪɴ {html.escape(chat_name)}!",
+ 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ᴛ {html.escape(to_blacklist[0])}
ɪɴ ᴄʜᴀᴛ: {html.escape(chat_name)}!",
+ parse_mode=ParseMode.HTML,
+ )
+
+ else:
+ send_message(
+ update.effective_message,
+ f"ᴀᴅᴅᴇᴅ ʙʟᴀᴄᴋʟɪsᴛ ᴛʀɪɢɢᴇʀ: {len(to_blacklist)}
ɪɴ {html.escape(chat_name)}!",
+ 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"ʀᴇᴍᴏᴠᴇᴅ {html.escape(to_unblacklist[0])}
ғʀᴏᴍ ʙʟᴀᴄᴋʟɪsᴛ ɪɴ {html.escape(chat_name)}!",
+ parse_mode=ParseMode.HTML,
+ )
+
+ else:
+ send_message(
+ update.effective_message, "ᴛʜɪs ɪs ɴᴏᴛ ᴀ ʙʟᴀᴄᴋʟɪsᴛ ᴛʀɪɢɢᴇʀ!"
+ )
+
+ elif successful == len(to_unblacklist):
+ send_message(
+ update.effective_message,
+ f"ʀᴇᴍᴏᴠᴇᴅ {successful}
ғʀᴏᴍ ʙʟᴀᴄᴋʟɪsᴛ ɪɴ {html.escape(chat_name)}!",
+ 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"ʀᴇᴍᴏᴠᴇᴅ {successful}
ғʀᴏᴍ ʙʟᴀᴄᴋʟɪ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 `.
+
+ ᴇ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 `.
+ ᴇ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"{html.escape(chat.title)}:\nᴀᴅᴍɪɴ: {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 *:* `ᴀᴄᴛɪᴏɴ ᴛᴏ ᴘᴇʀғᴏʀᴍ ᴡʜᴇɴ sᴏᴍᴇᴏɴᴇ sᴇɴᴅs ʙʟᴀᴄᴋʟɪsᴛᴇᴅ ᴡᴏʀᴅs.`
+
+`ʀʙʟᴀᴄᴋʟɪsᴛ sᴛɪᴄᴋᴇʀ ɪs ᴜsᴇᴅ ᴛᴏ sᴛᴏᴘ ᴄᴇʀᴛᴀɪɴ sᴛɪᴄᴋᴇʀs. ᴡʜᴇɴᴇᴠᴇʀ a sᴛɪᴄᴋᴇʀ ɪs sᴇɴᴛ, ᴛʜᴇ ᴍᴇssᴀɢᴇ ᴡɪʟʟ ʙᴇ ᴅᴇʟᴇᴛᴇᴅ ɪᴍᴍᴇᴅɪᴀᴛᴇʟʏ.`
+
+*ɴᴏᴛᴇ:* `ʙʟᴀᴄᴋʟɪsᴛ sᴛɪᴄᴋᴇʀs ᴅᴏ ɴᴏᴛ ᴀғғᴇᴄᴛ ᴛʜᴇ ɢʀᴏᴜᴘ ᴀᴅᴍɪɴ`
+
+✪ /blsticker*:* `ꜱᴇᴇ ᴄᴜʀʀᴇɴᴛ ʙʟᴀᴄᴋʟɪꜱᴛᴇᴅ ꜱᴛɪᴄᴋᴇʀ`
+
+
+✪ /addblsticker *:* `ᴀᴅᴅ ᴛʜᴇ ꜱᴛɪᴄᴋᴇʀ ᴛʀɪɢɢᴇʀ ᴛᴏ ᴛʜᴇ ʙʟᴀᴄᴋ ʟɪꜱᴛ. ᴄᴀɴ ʙᴇ ᴀᴅᴅᴇᴅ ᴠɪᴀ ʀᴇᴘʟʏ ꜱᴛɪᴄᴋᴇʀ`
+
+✪ /unblsticker *:* `ʀᴇᴍᴏᴠᴇ ᴛʀɪɢɢᴇʀꜱ ғʀᴏᴍ ʙʟᴀᴄᴋʟɪꜱᴛ. ᴛʜᴇ ꜱᴀᴍᴇ ɴᴇᴡʟɪɴᴇ ʟᴏɢɪᴄ ᴀᴘᴘʟɪᴇꜱ ʜᴇʀᴇ, ꜱᴏ ʏᴏᴜ ᴄᴀɴ ᴅᴇʟᴇᴛᴇ ᴍᴜʟᴛɪᴘʟᴇ ᴛʀɪɢɢᴇʀꜱ ᴀᴛ ᴏɴᴄᴇ`
+
+✪ /rmblsticker *:* `ꜱᴀᴍᴇ ᴀꜱ ᴀʙᴏᴠᴇ`
+
+✪ /blstickermode *:* `ꜱᴇᴛꜱ ᴜᴘ ᴀ ᴅᴇғᴀᴜʟᴛ ᴀᴄᴛɪᴏɴ ᴏɴ ᴡʜᴀᴛ ᴛᴏ ᴅᴏ ɪғ ᴜꜱᴇʀꜱ ᴜꜱᴇ ʙʟᴀᴄᴋʟɪꜱᴛᴇᴅ ꜱᴛɪᴄᴋᴇʀꜱ`
+
+ɴᴏᴛᴇ:
+✪ `ᴄᴀɴ ʙᴇ` `https://t.me/addstickers/` `ᴏʀ ᴊᴜꜱᴛ` `` `ᴏʀ ʀᴇᴘʟʏ ᴛᴏ ᴛʜᴇ ꜱᴛɪᴄᴋᴇʀ ᴍᴇꜱꜱᴀɢᴇ`
+
+"""
+
+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"ʟɪsᴛ ʙʟᴀᴄᴋʟɪsᴛᴇᴅ sᴛɪᴄᴋᴇʀs ᴄᴜʀʀᴇɴᴛʟʏ ɪɴ {chat_name}:\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"{html.escape(trigger)}
\n"
+ elif len(args) == 0:
+ for trigger in all_stickerlist:
+ sticker_list += f" - {html.escape(trigger)}
\n"
+
+ split_text = split_message(sticker_list)
+ for text in split_text:
+ if (
+ sticker_list
+ == f"ʟɪsᴛ ʙʟᴀᴄᴋʟɪsᴛᴇᴅ sᴛɪᴄᴋᴇʀs ᴄᴜʀʀᴇɴᴛʟʏ ɪɴ {chat_name}:\n".format(
+ html.escape(chat_name)
+ )
+ ):
+ send_message(
+ update.effective_message,
+ f"ᴛʜᴇʀᴇ ᴀʀᴇ ɴᴏ ʙʟᴀᴄᴋʟɪsᴛ sᴛɪᴄᴋᴇʀs ɪɴ {html.escape(chat_name)}!",
+ 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ᴛɪᴄᴋᴇʀ {html.escape(to_blacklist[0])}
ᴀᴅᴅᴇᴅ ᴛᴏ ʙʟᴀᴄᴋʟɪsᴛ sᴛɪʏᴄᴋᴇʀs ɪɴ {html.escape(chat_name)}!",
+ parse_mode=ParseMode.HTML,
+ )
+
+ else:
+ send_message(
+ update.effective_message,
+ f"{added}
sᴛɪᴄᴋᴇʀs ᴀᴅᴅᴇᴅ ᴛᴏ ʙʟᴀᴄᴋʟɪsᴛ sᴛɪᴄᴋᴇʀ ɪɴ {html.escape(chat_name)}!",
+ 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 {trigger}
ᴀᴅᴅᴇᴅ ᴛᴏ ʙʟᴀᴄᴋʟɪsᴛ sᴛɪᴄᴋᴇʀs ɪɴ {html.escape(chat_name)}!",
+ 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ᴛɪᴄᴋᴇʀ {html.escape(to_unblacklist[0])}
ᴅᴇʟᴇᴛᴇᴅ ғʀᴏᴍ ʙʟᴀᴄᴋʟɪsᴛ ɪɴ {html.escape(chat_name)}!",
+ 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ᴛɪᴄᴋᴇʀ {successful}
ᴅᴇʟᴇᴛᴇᴅ ғʀᴏᴍ ʙʟᴀᴄᴋʟɪsᴛ ɪɴ {html.escape(chat_name)}!",
+ 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ᴛɪᴄᴋᴇʀ {successful}
ᴅᴇʟᴇᴛᴇᴅ ғʀᴏᴍ ʙʟᴀᴄᴋʟɪ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ᴛɪᴄᴋᴇʀ {trigger}
ᴅᴇʟᴇᴛᴇᴅ ғʀᴏᴍ ʙʟᴀᴄᴋʟɪsᴛ ɪɴ {chat_name}!",
+ 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 `.
+ ᴇ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 `.
+ ᴇ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"{html.escape(chat.title)}:\nᴀᴅᴍɪɴ: {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"ᴀᴅᴍɪɴ: {mention_html(user.id, html.escape(user.first_name))}\n"
+ f"ᴜsᴇʀ: {mention_html(target_user.id, html.escape(target_user.first_name))}"
+ )
+ if reason:
+ log_message += f"\nʀᴇᴀsᴏɴ: {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"ᴀᴅᴍɪɴ: {mention_html(user.id, html.escape(user.first_name))}\n"
+ f"ᴜsᴇʀ: {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 = "ʙʟᴀᴄᴋʟɪsᴛᴇᴅ ᴜsᴇʀs\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ᴛᴇᴅ: {}"
+ 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ᴏɴ: {reason}
"
+ 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"{html.escape(chat.title)}:\n"
+ f"AI_ᴅɪsᴀʙʟᴇᴅ\n"
+ f"ᴀᴅᴍɪɴ: {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"{html.escape(chat.title)}:\n"
+ f"ᴀɪ_ᴇɴᴀʙʟᴇ\n"
+ f"ᴀᴅᴍɪɴ: {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 = "ᴋᴜᴋɪ-ᴇɴᴀʙʟᴇᴅ ᴄʜᴀᴛs\n"
+ for chat in chats:
+ try:
+ x = context.bot.get_chat(int(*chat))
+ name = x.title or x.first_name
+ text += f"• {name}
\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ᴀʙʟᴇᴅ ғᴏʀ {html.escape(chat.title)}"
+
+ message.reply_text(reply, parse_mode=ParseMode.HTML)
+
+ elif val in ("yes", "on"):
+ sql.set_cleanbt(chat.id, True)
+ reply = f"ʙʟᴜᴇᴛᴇxᴛ ᴄʟᴇᴀɴɪɴɢ ʜᴀs ʙᴇᴇɴ ᴇɴᴀʙʟᴇᴅ ғᴏʀ {html.escape(chat.title)}"
+
+ 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ᴛ ᴄʟᴇᴀɴɪɴɢ ғᴏʀ {html.escape(chat.title)} : {clean_status}"
+
+ 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"{args[0]} ʜᴀ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"{args[0]} ʜᴀ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"{args[0]} ʜᴀ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"{args[0]} ʜᴀ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" - {x}
\n"
+
+ if local_ignore_list:
+ text += "\nᴛʜᴇ ғᴏʟʟᴏᴡɪɴɢ ᴄᴏᴍᴍᴀɴᴅs ᴀʀᴇ ᴄᴜʀʀᴇɴᴛʟʏ ɪɢɴᴏʀᴇᴅ ʟᴏᴄᴀʟʟʏ ғʀᴏᴍ ʙʟᴜᴇᴛᴇxᴛ ᴄʟᴇᴀɴɪɴɢ :\n"
+
+ for x in local_ignore_list:
+ text += f" - {x}
\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 *:* `ᴄʟᴇᴀɴ ᴄᴏᴍᴍᴀɴᴅs ᴀғᴛᴇʀ sᴇɴᴅɪɴɢ`
+
+• /ignoreblue *:* `ᴘʀᴇᴠᴇɴᴛ ᴀᴜᴛᴏ ᴄʟᴇᴀɴɪɴɢ ᴏғ ᴛʜᴇ ᴄᴏᴍᴍᴀɴᴅ `
+
+• /unignoreblue *:* `ʀᴇᴍᴏᴠᴇ ᴘʀᴇᴠᴇɴᴛ ᴀᴜᴛᴏ ᴄʟᴇᴀɴɪɴɢ ᴏғ ᴛʜᴇ ᴄᴏᴍᴍᴀɴᴅ `
+
+• /listblue*:* `ʟɪsᴛ ᴄᴜʀʀᴇɴᴛʟʏ ᴡʜɪᴛᴇʟɪsᴛᴇᴅ ᴄᴏᴍᴍᴀɴᴅs `
+
+*ᴏɴʟʏ ᴄᴏᴍᴍᴀɴᴅs, ᴀᴅᴍɪɴs ᴄᴀɴɴᴏᴛ ᴜsᴇ ᴛʜᴇsᴇ:*
+
+• /gignoreblue *:* `ɢʟᴏʙᴀʟʟʏ ɪɢɴᴏʀᴇᴀ ʙʟᴜᴇᴛᴇxᴛ ᴄʟᴇᴀɴɪɴɢ ᴏғ sᴀᴠᴇᴅ ᴡᴏʀᴅ ᴀᴄʀᴏss` {BOT_NAME}.
+
+• /ungignoreblue *:* `ʀᴇᴍᴏᴠᴇ 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 ɪɴ ᴘᴍ)
+
+❂ /connection: `ʟɪꜱᴛ ᴄᴏɴɴᴇᴄᴛᴇᴅ ᴄʜᴀᴛꜱ`
+
+❂ /disconnect: `ᴅɪꜱᴄᴏɴɴᴇᴄᴛ ғʀᴏᴍ ᴀ ᴄʜᴀᴛ`
+
+❂ /helpconnect: `ʟɪꜱᴛ ᴀᴠᴀɪʟᴀʙʟᴇ ᴄᴏᴍᴍᴀɴᴅꜱ ᴛʜᴀᴛ ᴄᴀɴ ʙᴇ ᴜꜱᴇᴅ ʀᴇᴍᴏᴛᴇʟʏ`
+
+*ᴀᴅᴍɪɴ ᴏɴʟʏ:*
+
+❂ /allowconnect : `ᴀʟʟᴏᴡ ᴀ ᴜꜱᴇʀ ᴛᴏ ᴄᴏɴɴᴇᴄᴛ ᴛᴏ ᴀ ᴄʜᴀᴛ`
+
+"""
+
+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 *:* ᴀᴅᴅ a ғɪʟᴛᴇʀ ᴛᴏ ᴛʜɪꜱ chat. ᴛʜᴇ ʙᴏᴛ ᴡɪʟʟ ɴᴏᴡ ʀᴇᴘʟʏ ᴛʜᴀᴛ ᴍᴇꜱꜱᴀɢᴇ ᴡʜᴇɴᴇᴠᴇʀ 'ᴋᴇʏᴡᴏʀᴅ
+ɪꜱ ᴍᴇɴᴛɪᴏɴᴇᴅ. ɪғ ʏᴏᴜ ʀᴇᴘʟʏ ᴛᴏ ᴀ ꜱᴛɪᴄᴋᴇʀ ᴡɪᴛʜ ᴀ ᴋᴇʏᴡᴏʀᴅ, ᴛʜᴇ ʙᴏᴛ ᴡɪʟʟ ʀᴇᴘʟʏ ᴡɪᴛʜ ᴛʜᴀᴛ ꜱᴛɪᴄᴋᴇʀ. ɴᴏᴛᴇ: ᴀʟʟ ғɪʟᴛᴇʀ
+ᴋᴇʏᴡᴏʀᴅꜱ ᴀʀᴇ ɪɴ ʟᴏᴡᴇʀᴄᴀꜱᴇ. ɪғ ʏᴏᴜ ᴡᴀɴᴛ ʏᴏᴜʀ ᴋᴇʏᴡᴏʀᴅ ᴛᴏ ʙᴇ ᴀ ꜱᴇɴᴛᴇɴᴄᴇꜱ, ᴜꜱᴇ ϙᴜᴏᴛᴇꜱ. ᴇɢ: /filter "hey there" ʜᴇʏ ʜᴇʟʟᴏ
+ ꜱᴇᴘᴀʀᴀᴛᴇ ᴅɪғғ ʀᴇᴘʟɪᴇꜱ ʙʏ `%%%` ᴛᴏ ɢᴇᴛ ʀᴀɴᴅᴏᴍ ʀᴇᴘʟɪᴇꜱ
+
+ *ᴇxᴀᴍᴘʟᴇ:*
+ `/filter "filtername"
+ Reply 1
+ %%%
+ Reply 2
+ %%%
+ Reply 3`
+❂ /stop *:* `ꜱᴛᴏᴘ ᴛʜᴀᴛ ғɪʟᴛᴇʀ`
+*ᴄʜᴀᴛ 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""
+
+ 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 *:* `ᴇɴᴀʙʟᴇ ᴛʜᴀᴛ ᴄᴏᴍᴍᴀɴᴅ `
+
+• /disable *:* `ᴅɪsᴀʙʟᴇ ᴛʜᴀᴛ ᴄᴏᴍᴍᴀɴᴅ `
+
+• /enablemodule *:* `ᴇɴᴀʙʟᴇ ᴀʟʟ ᴄᴏᴍᴍᴀɴᴅs ɪɴ ᴛʜᴀᴛ ᴍᴏᴅᴜʟᴇ `
+
+• /disablemodule *:* `ᴅɪ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"ᴀᴅᴍɪɴ: {mention_html(user.id, html.escape(user.first_name))}\n"
+ f"ᴜsᴇʀ: {mention_html(user_member.id, html.escape(user_member.first_name))}"
+ )
+
+ if chat.type != "private":
+ log_message = f"{html.escape(chat.title)}:\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"ᴀᴅᴍɪɴ: {mention_html(user.id, html.escape(user.first_name))}\n"
+ f"ᴜsᴇʀ: {mention_html(user_member.id, html.escape(user_member.first_name))}"
+ )
+
+ if chat.type != "private":
+ log_message = f"{html.escape(chat.title)}:\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"ᴀᴅᴍɪɴ: {mention_html(user.id, html.escape(user.first_name))} \n"
+ f"ᴜsᴇʀ: {mention_html(user_member.id, html.escape(user_member.first_name))}"
+ )
+
+ if chat.type != "private":
+ log_message = f"{html.escape(chat.title)}:\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"ᴀᴅᴍɪɴ: {mention_html(user.id, html.escape(user.first_name))} \n"
+ f"ᴜsᴇʀ: {mention_html(user_member.id, html.escape(user_member.first_name))}"
+ )
+
+ if chat.type != "private":
+ log_message = f"{html.escape(chat.title)}:\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"ᴀᴅᴍɪɴ: {mention_html(user.id, html.escape(user.first_name))}\n"
+ f"ᴜsᴇʀ: {mention_html(user_member.id, html.escape(user_member.first_name))}"
+ )
+
+ if chat.type != "private":
+ log_message = f"{html.escape(chat.title)}:\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"ᴀᴅᴍɪɴ: {mention_html(user.id, html.escape(user.first_name))}\n"
+ f"ᴜsᴇʀ: {mention_html(user_member.id, html.escape(user_member.first_name))}"
+ )
+
+ if chat.type != "private":
+ log_message = f"{html.escape(chat.title)}:\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"ᴀᴅᴍɪɴ: {mention_html(user.id, html.escape(user.first_name))}\n"
+ f"ᴜsᴇʀ: {mention_html(user_member.id, html.escape(user_member.first_name))}"
+ )
+
+ if chat.type != "private":
+ log_message = f"{html.escape(chat.title)}:\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"ᴀᴅᴍɪɴ: {mention_html(user.id, html.escape(user.first_name))}\n"
+ f"ᴜsᴇʀ: {mention_html(user_member.id, html.escape(user_member.first_name))}"
+ )
+
+ if chat.type != "private":
+ log_message = f"{html.escape(chat.title)}:\n{log_message}"
+
+ return log_message
+ message.reply_text("ᴛʜɪs ᴜsᴇʀ ɪs ɴᴏᴛ ᴏᴜʀ ᴄʟᴀssᴍᴀᴛᴇ!")
+ return ""
+
+
+@whitelist_plus
+def whitelistlist(update: Update, context: CallbackContext):
+ reply = "ᴇxᴏɴ:\n\n"
+ m = update.effective_message.reply_text(
+ "ɢᴀᴛʜᴇʀɪɴɢ ɪɴᴛᴇʟ ғʀᴏᴍ ᴇxᴏɴ..
",
+ 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 = "Classmates:\n\n"
+ m = update.effective_message.reply_text(
+ "ɢᴀᴛʜᴇʀɪɴɢ ɪɴᴛᴇʟ ғʀᴏᴍ ᴇxᴏɴ ɪǫ.
",
+ 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(
+ "ɢᴀᴛʜᴇʀɪɴɢ ɪɴᴛᴇʟ ғʀᴏᴍ .
",
+ parse_mode=ParseMode.HTML,
+ )
+ reply = "ғʀɪᴇɴᴅs:\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(
+ "ɢᴀᴛʜᴇʀɪɴɢ ɪɴᴛᴇʟ ғʀᴏᴍ ᴇxᴏɴ ʜǫ.
",
+ parse_mode=ParseMode.HTML,
+ )
+ true_sudo = list(set(DRAGONS) - set(DEV_USERS))
+ reply = "ʙᴇsᴛᴏ ғʀɪᴇɴᴅᴏs:\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(
+ "ɢᴀᴛʜᴇʀɪɴɢ ɪɴᴛᴇʟ ғʀᴏᴍ ᴀʙɪsʜɴᴏɪ HQ..
",
+ parse_mode=ParseMode.HTML,
+ )
+ true_dev = list(set(DEV_USERS) - {OWNER_ID})
+ reply = "ғᴀᴍɪʟʏ ᴍᴇᴍʙᴇʀs:\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}\nAn unknown error occured:\n{e}
",
+ 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}\nAn unknown error occured:\n{e}
",
+ 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 = "ᴇʀʀᴏʀs ʟɪsᴛ:\n"
+ for x, value in e.items():
+ msg += f"• {x}:
{value} #{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}\nOnichan some errors are happening:"
+ f"\n{e}
",
+ parse_mode="html",
+ )
+ return
+
+ url = f"https://spaceb.in/{response['payload']['id']}"
+ context.bot.send_message(
+ JOIN_LOGGER,
+ text=f"#{context.error.identifier}\nOnichan some errors are happening!:"
+ f"\n{e}
",
+ 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 = "Errors List:\n"
+ for x, value in e.items():
+ msg += f"• {x}:
{value} #{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 ")
+ """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"ɴᴇᴡ ғᴇᴅᴇʀᴀᴛɪᴏɴ: {fed_name}\nID: {fed_id}
",
+ 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 = 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: {fed_id}
)"
+ )
+
+ 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 = "ғᴇᴅᴇʀᴀᴛɪᴏɴ ɪɴғᴏʀᴍᴀᴛɪᴏɴ:" + f"\nFedID: {fed_id}
"
+ text += f"\nɴᴀᴍᴇ: {info['fname']}"
+ text += f"\nᴄʀᴇᴀᴛᴏʀ: {mention_html(owner.id, owner_name)}"
+ text += f"\nᴀʟʟ ᴀᴅᴍɪɴs: {TotalAdminFed}
"
+ getfban = sql.get_all_fban_users(fed_id)
+ text += f"\nᴛᴏᴛᴀʟ ʙᴀɴɴᴇᴅ ᴜsᴇʀs: {len(getfban)}
"
+ getfchat = sql.all_fed_chats(fed_id)
+ text += f"\nɴᴜᴍʙᴇʀ ᴏғ ɢʀᴏᴜᴘs ɪɴ ᴛʜɪs ғᴇᴅᴇʀᴀᴛɪᴏɴ: {len(getfchat)}
"
+
+ 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"ғᴇᴅᴇʀᴀᴛɪᴏɴ ᴀᴅᴍɪɴ {info['fname']}:\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,
+ "ғᴇᴅʙᴀɴ ʀᴇᴀsᴏɴ ᴜᴘᴅᴀᴛᴇᴅ"
+ "\nғᴇᴅᴇʀᴀᴛɪᴏɴ: {}"
+ "\nғᴇᴅᴇʀᴀᴛɪᴏɴ ᴀᴅᴍɪɴ: {}"
+ "\nᴜsᴇʀ: {}"
+ "\nᴜsᴇʀ ID: {}
"
+ "\nʀᴇᴀsᴏɴ: {}".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"],
+ "FedBan reason updated"
+ "\nFederation: {}"
+ "\nFederation Admin: {}"
+ "\nUser: {}"
+ "\nᴜsᴇʀ ID: {}
"
+ "\nReason: {}".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,
+ "ғᴇᴅʙᴀɴ ʀᴇᴀsᴏɴ ᴜᴘᴅᴀᴛᴇᴅ"
+ "\nғᴇᴅᴇʀᴀᴛɪᴏɴ: {}"
+ "\nғᴇᴅᴇʀᴀᴛɪᴏɴ ᴀᴅᴍɪɴ: {}"
+ "\nᴜsᴇʀ: {}"
+ "\nᴜsᴇʀ ɪᴅ: {}
"
+ "\nʀᴇᴀsᴏɴ: {}".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, "FedBan reason updated" \
+ "\nFederation: {}" \
+ "\nFederation Admin: {}" \
+ "\nUser: {}" \
+ "\nUser ID: {}
" \
+ "\nReason: {}".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,
+ "FedBan reason updated" \
+ "\nFederation: {}" \
+ "\nFederation Admin: {}" \
+ "\nUser: {}" \
+ "\nUser ID: {}
" \
+ "\nReason: {}".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 {}.".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,
+ "FedBan reason updated"
+ "\nFederation: {}"
+ "\nFederation Admin: {}"
+ "\nUser: {}"
+ "\nUser ID: {}
"
+ "\nReason: {}".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"],
+ "FedBan reason updated"
+ "\nFederation: {}"
+ "\nFederation Admin: {}"
+ "\nUser: {}"
+ "\nUser ID: {}
"
+ "\nReason: {}".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,
+ "FedBan reason updated"
+ "\nFederation: {}"
+ "\nFederation Admin: {}"
+ "\nUser: {}"
+ "\nUser ID: {}
"
+ "\nReason: {}".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, "FedBan reason updated" \
+ "\nFederation: {}" \
+ "\nFederation Admin: {}" \
+ "\nUser: {}" \
+ "\nUser ID: {}
" \
+ "\nReason: {}".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,
+ "FedBan reason updated" \
+ "\nFederation: {}" \
+ "\nFederation Admin: {}" \
+ "\nUser: {}" \
+ "\nUser ID: {}
" \
+ "\nReason: {}".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,
+ "Un-FedBan"
+ "\nFederation: {}"
+ "\nFederation Admin: {}"
+ "\nUser: {}"
+ "\nUser ID: {}
".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"],
+ "Un-FedBan"
+ "\nFederation: {}"
+ "\nFederation Admin: {}"
+ "\nUser: {}"
+ "\nUser ID: {}
".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,
+ "Un-FedBan"
+ "\nFederation: {}"
+ "\nFederation Admin: {}"
+ "\nUser: {}"
+ "\nUser ID: {}
".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, "Un-FedBan" \
+ "\nFederation: {}" \
+ "\nFederation Admin: {}" \
+ "\nUser: {}" \
+ "\nUser ID: {}
".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,
+ "Un-FedBan" \
+ "\nFederation: {}" \
+ "\nFederation Admin: {}" \
+ "\nUser: {}" \
+ "\nUser ID: {}
".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 = "{} users have been banned from the federation {}:\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 += " • {} ({}
)\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 = "New chat joined the federation {}:\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 += " ? {} ({}
)\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 "
+ 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 "
+ 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 `. To unsubscribe `/unsubfed `."
+ )
+ 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: {}.".format(infoname)
+ elif is_user_fed_admin(fed_id, user_id):
+ text = "Federation admin of: {}.".format(infoname)
+
+ elif fban:
+ text = "Federation banned: Yes"
+ text += "\nReason: {}".format(fbanreason)
+ else:
+ text = "Federation banned: No"
+ 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 `*:* Creates a Federation, One allowed per user
+ • `/renamefed `*:* Renames the fed id to a new name
+ • `/delfed `*:* Delete a Federation, and any information related to it. Will not cancel blocked users
+ • `/fpromote `*:* Assigns the user as a federation admin. Enables all commands for the user under `Fed Admins`
+ • `/fdemote `*:* Drops the User from the admin Federation to a normal User
+ • `/subfed `*:* Subscribes to a given fed ID, bans from that subscribed fed will also happen in your fed
+ • `/unsubfed `*:* Unsubscribes to a given fed ID
+ • `/setfedlog `*:* Sets the group as a fed log report base for the federation
+ • `/unsetfedlog `*:* Removed the group as a fed log report base for the federation
+ • `/fbroadcast `*:* 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 `*:* Fed bans a user
+ • `/unfban `*:* Removes a user from a fed ban
+ • `/fedinfo `*:* Information about the specified Federation
+ • `/joinfed `*:* Join the current chat to the Federation. Only chat owners can do this. Every chat can only be in one Federation
+ • `/leavefed `*:* Leave the Federation given. Only chat owners can do this
+ • `/setfrules `*:* 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 `*:* 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"ᴄᴏᴍᴍᴏɴ ᴄʜᴀᴛs ᴡɪᴛʜ {name}\n\n"
+ for chat in common_list:
+ try:
+ chat_name = bot.get_chat(chat).title
+ sleep(0.3)
+ text += f"• {chat_name}
\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 : `ɢᴇᴛ ʏᴏᴜʀ ᴅᴇsɪʀᴇᴅ ɢɪғ`
+- /gif ; (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"
+ "{}
\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 = "{} ({})\n".format(html.escape(chat.title), chat.id)
+ else:
+ chat_origin = "{}\n".format(chat.id)
+
+ log_message = (
+ f"#ɢʙᴀɴɴᴇᴅ\n"
+ f"ᴏʀɪɢɪɴᴀᴛᴇᴅ ғʀᴏᴍ: {chat_origin}
\n"
+ f"ᴀᴅᴍɪɴ: {mention_html(user.id, user.first_name)}\n"
+ f"ʙᴀɴɴᴇᴅ ᴜsᴇʀ: {mention_html(user_chat.id, user_chat.first_name)}\n"
+ f"ʙᴀɴɴᴇᴅ ᴜsᴇʀ ɪᴅ: {user_chat.id}
\n"
+ f"ᴇᴠᴇɴᴛ sᴛᴀᴍᴘ: {current_time}
"
+ )
+
+ if reason:
+ if chat.type == chat.SUPERGROUP and chat.username:
+ log_message += f'\nʀᴇᴀsᴏɴ: {reason}'
+ else:
+ log_message += f"\nʀᴇᴀsᴏɴ: {reason}
"
+
+ 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ᴄʜᴀᴛs ᴀғғᴇᴄᴛᴇᴅ: {gbanned_chats}
",
+ parse_mode=ParseMode.HTML,
+ )
+ else:
+ send_to_list(
+ bot,
+ DRAGONS + DEMONS,
+ f"ɢʙᴀɴ ᴄᴏᴍᴘʟᴇᴛᴇ! (ᴜsᴇʀ ʙᴀɴɴᴇᴅ ɪɴ {gbanned_chats}
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ʀᴇᴀsᴏɴ: {html.escape(user.reason)}
"
+ f"ᴀᴘᴘᴇᴀʟ ᴄʜᴀᴛ: @{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"{html.escape(chat.title)} ({chat.id})\n"
+ else:
+ chat_origin = f"{chat.id}\n"
+
+ log_message = (
+ f"#ᴜɴɢʙᴀɴɴᴇᴅ\n"
+ f"ᴏʀɪɢɪɴᴀᴛᴇᴅ ғʀᴏᴍ: {chat_origin}
\n"
+ f"ᴀᴅᴍɪɴ: {mention_html(user.id, user.first_name)}\n"
+ f"ᴜɴʙᴀɴɴᴇᴅ ᴜsᴇʀ: {mention_html(user_chat.id, user_chat.first_name)}\n"
+ f"ᴜɴʙᴀɴɴᴇᴅ ᴜsᴇʀ ID: {user_chat.id}
\n"
+ f"ᴇᴠᴇɴᴛ sᴛᴀᴍᴘ: {current_time}
"
+ )
+
+ 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ᴄʜᴀᴛs ᴀғғᴇᴄᴛᴇᴅ: {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"ᴀʟᴇʀᴛ: ᴛʜɪs ᴜsᴇʀ ʜᴀs ʙᴇᴇɴ ɢʟᴏʙᴀʟʟʏ ʙᴀɴɴᴇᴅ ʙʏ @SpamWatch\n"
+ f"*ʙᴀɴs ᴛʜᴇᴍ ғʀᴏᴍ ʜᴇʀᴇ*
.\n"
+ f"ᴀᴘᴘᴇᴀʟ ғᴏʀ ᴜɴʙᴀɴ: {SPAMWATCH_SUPPORT_CHAT}\n"
+ f"ᴜsᴇʀ ɪᴅ: {sw_ban.id}
\n"
+ f"ʙᴀɴ ʀᴇᴀsᴏɴ: {html.escape(sw_ban.reason)}
\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"ᴀʟᴇʀᴛ: ᴛʜɪs ᴜsᴇʀ ʜᴀs ʙᴇᴇɴ ɢʟᴏʙᴀʟʟʏ ʙᴀɴɴᴇᴅ ʙʏ ᴛʜᴇ ʙᴏᴛ ᴏᴡɴᴇʀ\n"
+ f"*ʙᴀɴs ᴛʜᴇᴍ ғʀᴏᴍ ʜᴇʀᴇ*
.\n"
+ f"ᴀᴘᴘᴇᴀʟ ғᴏʀ ᴜɴʙᴀɴ: @{SUPPORT_CHAT}\n"
+ f"ᴜsᴇʀ ɪᴅ: {user_id}
"
+ )
+ user = sql.get_gbanned_user(user_id)
+ if user.reason:
+ text += f"\nʙᴀɴ ʀᴇᴀsᴏɴ: {html.escape(user.reason)}
"
+ 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 = "ɢʙᴀɴɴᴇᴅ: {}"
+ 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ʀᴇᴀsᴏɴ: {html.escape(user.reason)}
"
+ text += f"\nᴀᴘᴘᴇᴀʟ ᴄʜᴀᴛ: @{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 *:*` ᴡɪʟʟ ᴛᴏɢɢʟᴇ ᴏᴜʀ ᴀɴᴛɪ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 :- `sᴇᴀʀᴄʜ ɢᴏᴏɢʟᴇ ғᴏʀ ɪᴍᴀɢᴇs ᴀɴᴅ ʀᴇᴛᴜʀɴs ᴛʜᴇᴍ ғᴏʀ ɢʀᴇᴀᴛᴇʀ ɴᴏ. ᴏғ ʀᴇsᴜʟᴛs sᴘᴇᴄɪғʏ ʟɪᴍ, ғᴏʀ`
+
+ᴇɢ: `/img hello lim=10`
+
+⍟ /reverse :- `ʀᴇᴘʟʏ ᴛᴏ ᴀ sᴛɪᴄᴋᴇʀ, or ᴏʀ ɪᴍᴀɢᴇ ᴛᴏ sᴇᴀʀᴄʜ ɪᴛ `
+
+ ᴅᴏ ʏᴏᴜ ᴋɴᴏᴡ that ʏᴏᴜ ᴄᴀɴ search ᴀɴ ɪᴍᴀɢᴇ ᴡɪᴛʜ ᴀ ʟɪɴᴋ ᴛᴏᴏ?
+ /reverse picturelink .
+
+⍟ /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"ᴛʀᴀɴsʟᴀᴛᴇᴅ ғʀᴏᴍ {source} ᴛᴏ {dest}:\n"
+ f"{translation.text}
"
+ )
+
+ 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 : `ᴛʀᴀɴsʟᴀᴛᴇs ᴛᴏ `
+
+ᴇɢ: `/tl en`: `ᴛʀᴀɴsʟᴀᴛᴇs ᴛᴏ ᴇɴɢʟɪsʜ `
+
+⍟ /tl //: ᴛʀᴀɴsʟᴀᴛᴇs ғʀᴏᴍ ᴛᴏ .
+
+ᴇɢ: `/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'ᴍ ɴᴏᴛ ᴀᴅᴍɪɴ ɪɴ {update_chat_title}! "
+
+ 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 ɪɴ {update_chat_title}!\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 {update_chat_title}!\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 ᴄᴀɴ'ᴛ ᴘʀᴏᴍᴏᴛᴇ/ᴅᴇᴍᴏᴛᴇ ᴘᴇᴏᴘʟᴇ ɪɴ {update_chat_title}!\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ᴛʀɪᴄᴛ ᴘᴇᴏᴘʟᴇ ɪɴ {update_chat_title}!\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"(?[*_`\[])",
+)
+
+# regex to find []() links -> hyperlinks/buttons
+LINK_REGEX = re.compile(r"(? 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 (
+ "{}:"
+ "\n#ʟᴏᴄᴋ"
+ "\nᴀᴅᴍɪɴ: {}"
+ "\nʟᴏᴄᴋᴇᴅ {}
.".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 (
+ "{}:"
+ "\n#ᴘᴇʀᴍɪssɪᴏɴ_ʟᴏᴄᴋ"
+ "\nᴀᴅᴍɪɴ: {}"
+ "\nʟᴏᴄᴋᴇᴅ {}
.".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 (
+ "{}:"
+ "\n#ᴜɴʟᴏᴄᴋ"
+ "\nᴀᴅᴍɪɴ: {}"
+ "\nᴜɴʟᴏᴄᴋᴇᴅ {}
.".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 (
+ "{}:"
+ "\n#ᴜɴʟᴏᴄᴋ"
+ "\nᴀᴅᴍɪɴ: {}"
+ "\nᴜɴʟᴏᴄᴋᴇᴅ {}
.".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 *:* `ʟᴏᴄᴋ ɪᴛᴇᴍꜱ ᴏғ ᴀ ᴄᴇʀᴛᴀɪɴ ɴype (ɴᴏᴛ ᴀᴠᴀɪʟᴀʙʟᴇ ɪɴ ᴘʀɪᴠᴀᴛᴇ)`
+
+•➥ /unlock *:* `ᴜɴʟᴏᴄᴋ ɪᴛᴇᴍꜱ ᴏғ ᴀ ᴄᴇʀᴛᴀɪɴ ᴛʏᴘᴇ (ɴᴏᴛ ᴀᴠᴀɪʟᴀʙʟᴇ ɪɴ ᴘʀɪᴠᴀᴛᴇ)`
+
+•➥ /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ᴇᴠᴇɴᴛ sᴛᴀᴍᴘ: {datetime.utcnow().strftime(datetime_fmt)}
"
+
+ if message.chat.type == chat.SUPERGROUP and message.chat.username:
+ result += f'\nʟɪɴᴋ: ᴄʟɪᴄᴋ ʜᴇʀᴇ'
+ 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ᴇᴠᴇɴᴛ sᴛᴀᴍᴘ: {}
".format(
+ datetime.utcnow().strftime(datetime_fmt),
+ )
+
+ if message.chat.type == chat.SUPERGROUP and message.chat.username:
+ result += f'\nLink: click here'
+ 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.
+
+❂ _italic_
: wrapping text with '_' will produce italic text
+❂ *bold*
: wrapping text with '*' will produce bold text
+❂ `code`
: wrapping text with '`' will produce monospaced text, also known as 'code'
+❂ [sometext](someURL)
: this will create a link - the message will just show sometext
, \
+ᴀɴᴅ ᴛᴀᴘᴘɪɴɢ ᴏɴ ɪᴛ ᴡɪʟʟ ᴏᴘᴇɴ ᴛʜᴇ ᴘᴀɢᴇ ᴀᴛ someURL
.
+ᴇxᴀᴍᴘʟᴇ:[test](example.com)
+
+❂ [buttontext](buttonurl:someURL)
: this is a special enhancement to allow users to have telegram \
+buttons in their markdown. buttontext
will be what is displayed on the button, and someurl
\
+
+ᴡɪʟʟ be ᴛʜᴇ ᴜʀʟ ᴡʜɪᴄʜ ɪs ᴏᴘᴇɴᴇᴅ.
+
+ᴇxᴀᴍᴘʟᴇ: [ᴛʜɪs ɪs ᴀ ʙᴜᴛᴛᴏɴ](buttonurl:example.com)
+
+If you want multiple buttons on the same line, use :same, as such:
+[one](buttonurl://example.com)
+[two](buttonurl://google.com:same)
+
+ᴛʜɪs ᴡɪʟʟ ᴄʀᴇᴀᴛᴇ ᴛᴡᴏ ʙᴜᴛᴛᴏɴs ᴏɴ ᴀ sɪɴɢʟᴇ ʟɪɴᴇ, ɪɴsᴛᴇᴀᴅ ᴏғ ᴏɴᴇ ʙᴜᴛᴛᴏɴ ᴘᴇʀ ʟɪɴᴇ.
+
+Keep in mind that your message MUST 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{msg.reply_to_message.animation.file_id}
",
+ 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"╒═══「 ᴀɴᴀʟʏᴢᴇᴅ ʀᴇsᴜʟᴛs: 」\n"
+ f"✦ ᴜsᴇʀ ID: {user.id}
\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 += "\n\nsᴘᴀᴍᴡᴀᴛᴄʜ:\n"
+ text += "ᴛʜɪs ᴘᴇʀsᴏɴ is ʙᴀɴɴᴇᴅ ɪɴ sᴘᴀᴍᴡᴀᴛᴄʜ!"
+ text += f"\nʀᴇᴀsᴏɴ: {spamwtc.reason}
"
+ text += "\nAppeal ᴀᴛ @SpamWatchSupport"
+ else:
+ text += "\n\nSpamWatch: Not banned"
+ disaster_level_present = False
+ num_chats = sql.get_user_num_chats(user.id)
+ text += f"\n\nᴄʜᴀᴛ ᴄᴏᴜɴᴛ: {num_chats}
"
+ 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 ᴛʜᴇ ᴛɪᴛʟᴇ {result.custom_title} ʜᴇʀᴇ."
+ )
+ if user.id == OWNER_ID:
+ text += "\n\nᴏᴜʀ ᴄᴜᴛᴇ ᴏᴡɴᴇʀ
:3"
+ disaster_level_present = True
+ elif user.id in DEV_USERS:
+ text += "\n\nᴛʜɪs ᴜsᴇʀ ɪs a ᴘᴀʀᴛ ᴏғ ᴏᴜʀ ғᴀᴍɪʟʏ
"
+ disaster_level_present = True
+ elif user.id in SUDO_USERS:
+ text += "\n\nᴏɴᴇ ᴏғ ᴏᴜʀ ʙᴇsᴛᴏ ғʀɪᴇɴᴅᴏs, ᴛᴏᴜᴄʜ ʜɪᴍ ᴀɴᴅ ʏᴏᴜ ᴀʀᴇ ᴅᴇᴀᴅ ᴍᴇᴀᴛ
"
+ disaster_level_present = True
+ elif user.id in SUPPORT_USERS:
+ text += "\n\nᴛʜɪs user is ᴏᴜʀ ғʀɪᴇɴᴅ
✨"
+ disaster_level_present = True
+ elif user.id in TIGERS:
+ text += "\n\nᴏɴᴇ ᴏғ ᴍʏ ᴄʟᴀssᴍᴀᴛᴇs
:p"
+ disaster_level_present = True
+ elif user.id in WHITELIST_USERS:
+ text += "\n\nᴍᴇᴍʙᴇʀ ᴏғ Exon ᴛᴇᴄʜ, ᴛᴏᴛᴀʟʟʏ ᴄᴏᴏʟ ʀɪɢʜᴛ ?
"
+ disaster_level_present = True
+ if disaster_level_present:
+ text += ' [?]'
+ 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"ᴄʜᴀᴛ ɪɴғᴏʀᴍᴀᴛɪᴏɴ:\n" f"ᴄʜᴀᴛ ᴛɪᴛʟᴇ: {user.title}"
+ if user.username:
+ text += f"\nᴜsᴇʀɴᴀᴍᴇ: @{html.escape(user.username)}"
+ text += f"\nᴄʜᴀᴛ ɪᴅ: {user.id}
"
+ text += f"\nᴄʜᴀᴛ ᴛʏᴘᴇ: {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 *:* `ᴛʏᴘᴇ ᴛʜᴇ ᴡᴏʀᴅ ᴏʀ ᴇxᴘʀᴇssɪᴏɴ ʏᴏᴜ ᴡᴀɴᴛ ᴛᴏ sᴇᴀʀᴄʜ ᴜsᴇ `
+
+ᴡɪᴋɪᴘᴇᴅɪᴀ:
+⍟ ❂ /wiki *:* `ᴡɪᴋɪᴘᴇᴅɪᴀ ʏᴏᴜʀ ǫᴜᴇʀʏ `
+
+ᴡᴀʟʟᴘᴀᴘᴇʀs:
+⍟ /wall *:* `get ᴀ ᴡᴀʟʟᴘᴀᴘᴇʀ ғʀᴏᴍ ᴀʟᴘʜᴀᴄᴏᴅᴇʀs `
+
+ʙᴏᴏᴋs:
+⍟ /book *:* `ɢᴇᴛ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"ᴀᴛᴛᴇᴍᴘᴛɪɴɢ ᴛᴏ ʟᴏᴀᴅ ᴍᴏᴅᴜʟᴇ : {text}",
+ 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ғᴜʟʟʏ ʟᴏᴀᴅᴇᴅ ᴍᴏᴅᴜʟᴇ : {text}",
+ 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"ᴀᴛᴛᴇᴍᴘᴛɪɴɢ ᴛᴏ ᴜɴʟᴏᴀᴅ ᴍᴏᴅᴜʟᴇ : {text}",
+ 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ғᴜʟʟʏ ᴜɴʟᴏᴀᴅᴇᴅ ᴍᴏᴅᴜʟᴇ : {text}",
+ 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"- {mod_name} ({file_name})
\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ᴏɴɢ ᴏɴ ᴠᴄ.
+
+/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"{html.escape(chat.title)}:\n"
+ f"#ᴍᴜᴛᴇ\n"
+ f"ᴀᴅᴍɪɴ: {mention_html(user.id, user.first_name)}\n"
+ f"ᴜsᴇʀ: {mention_html(member.user.id, member.user.first_name)}"
+ )
+
+ if reason:
+ log += f"\nʀᴇᴀsᴏɴ: {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"🗣️
ᴍᴜᴛᴇ Event\n"
+ f"
• ᴍᴜᴛᴇᴅ ᴜsᴇʀ: {mention_html(member.user.id, member.user.first_name)}"
+ )
+ if reason:
+ msg += f"\n
• ʀᴇᴀsᴏɴ: \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 {message.chat.title}\nʀᴇᴀsᴏɴ: {reason}
",
+ parse_mode=ParseMode.HTML,
+ )
+
+ return (
+ f"{html.escape(chat.title)}:\n"
+ f"#ᴜɴᴍᴜᴛᴇ\n"
+ f"ᴀᴅᴍɪɴ: {mention_html(user.id, user.first_name)}\n"
+ f"ᴜsᴇʀ: {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"{html.escape(chat.title)}:\n"
+ f"#ᴛᴇᴍᴘ ᴍᴜᴛᴇᴅ\n"
+ f"ᴀᴅᴍɪɴ: {mention_html(user.id, user.first_name)}\n"
+ f"ᴜsᴇʀ: {mention_html(member.user.id, member.user.first_name)}\n"
+ f"ᴛɪᴍᴇ: {time_val}"
+ )
+ if reason:
+ log += f"\nʀᴇᴀsᴏɴ: {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"🗣️
ᴛɪᴍᴇ ᴍᴜᴛᴇ ᴇᴠᴇɴᴛ\n"
+ f"
• ᴍᴜᴛᴇᴅ ᴜsᴇʀ: {mention_html(member.user.id, member.user.first_name)}\n"
+ f"
• ᴜsᴇʀ ᴡɪʟʟ ʙᴇ ᴍᴜᴛᴇᴅ ғᴏʀ: {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"{html.escape(chat.title)}:\n"
+ f"#ᴜɴᴍᴜᴛᴇ\n"
+ f"ᴀᴅᴍɪɴ: {mention_html(user.id, user.first_name)}\n"
+ f"ᴜsᴇʀ: {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"{title}
\n\nTags:\n{tags}\nArtists:\n{artist}\nPages:\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"
" 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 *:* `ɢᴇᴛ ᴛʜᴇ ɴᴏᴛᴇ ᴡɪᴛʜ this ɴᴏᴛᴇɴᴀᴍᴇ`
+
+•➥ `#*:* same as /get`
+
+•➥ /notes or /saved*:* `ʟɪsᴛ ᴀʟʟ sᴀᴠᴇᴅ ɴᴏᴛᴇs ɪɴ ᴛʜɪs ᴄʜᴀᴛ`
+
+•➥ /number *:* `ᴡɪʟʟ ᴘᴜʟʟ ᴛʜᴇ ɴᴏᴛᴇ ᴏғ ᴛʜᴀᴛ ɴᴜᴍʙᴇʀ ɪɴ ᴛʜᴇ ʟɪsᴛ`
+
+`ɪғ ʏᴏᴜ ᴡᴏᴜʟᴅ ʟɪᴋᴇ ᴛᴏ ʀᴇᴛʀɪᴇᴠᴇ ᴛʜᴇ ᴄᴏɴᴛᴇɴᴛs ᴏғ ᴀ ɴᴏᴛᴇ ᴡɪᴛʜᴏᴜᴛ ᴀɴʏ ғᴏʀᴍᴀᴛᴛɪɴɢ, ᴜsᴇ` `/get ɴᴏғᴏʀᴍᴀᴛ`. `ᴛʜɪs ᴄᴀɴ` \
+`ʙᴇ ᴜsᴇғᴜʟ ᴡʜᴇɴ ᴜᴘᴅᴀᴛɪɴɢ ᴀ ᴄᴜʀʀᴇɴᴛ ɴᴏᴛᴇ`
+
+*ᴀᴅᴍɪɴꜱ ᴏɴʟʏ:*
+•➥ /save *:* `ꜱᴀᴠᴇꜱ ɴᴏᴛᴇᴅᴀᴛᴀ ᴀꜱ ᴀ ɴᴏᴛᴇ ᴡɪᴛʜ ɴᴀᴍᴇ ɴᴏᴛᴇɴᴀᴍᴇ`
+
+`A ʙᴜᴛᴛᴏɴ ᴄᴀɴ ʙᴇ ᴀᴅᴅᴇᴅ ᴛᴏ ᴀ ɴᴏᴛᴇ ʙʏ ᴜꜱɪɴɢ ꜱᴛᴀɴᴅᴀʀᴅ ᴍᴀʀᴋᴅᴏᴡɴ ʟɪɴᴋ ꜱʏɴᴛᴀx - ᴛʜᴇ ʟɪɴᴋ ꜱʜᴏᴜʟᴅ ᴊᴜꜱᴛ ʙᴇ ᴘʀᴇᴘᴇɴᴅᴇᴅ ᴡɪᴛʜ ᴀ`
+ \
+`buttonurl:` ꜱᴇᴄᴛɪᴏɴ, ᴀꜱ ꜱᴜᴄʜ: `[somelink](buttonurl:example.com)`. ᴄʜᴇᴄᴋ `/ᴍᴀʀᴋᴅᴏᴡɴʜᴇʟᴘ` ғᴏʀ ᴍᴏʀᴇ ɪɴғᴏ
+
+•➥ /save *:* `ꜱᴀᴠᴇ ᴛʜᴇ ʀᴇᴘʟɪᴇᴅ ᴍᴇꜱꜱᴀɢᴇ ᴀꜱ ᴀ ɴᴏᴛᴇ ᴡɪᴛʜ ɴᴀᴍᴇ ɴᴏᴛᴇɴᴀᴍᴇ`
+
+ `ꜱᴇᴘᴀʀᴀᴛᴇ ᴅɪғғ ʀᴇᴘʟɪᴇꜱ ʙʏ` `%%%` `ᴛᴏ ɢᴇᴛ ʀᴀɴᴅᴏᴍ ɴᴏᴛᴇꜱ`
+
+ *ᴇxᴀᴍᴘʟᴇ:*
+ `/save notename
+ Reply 1
+ %%%
+ Reply 2
+ %%%
+ Reply 3`
+
+•➥ /clear *:* `ᴄʟᴇᴀʀ ɴᴏᴛᴇ ᴡɪᴛʜ ᴛʜɪꜱ ɴᴀᴍᴇ`
+
+•➥ /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"{each_ping}"
+
+ if each_ping in ("Kaizoku"):
+ pinged_site = f'{each_ping}'
+ ping_time = f"{ping_time} (Status: {r.status_code})
"
+
+ ping_text = f"{pinged_site}: {ping_time}
"
+ 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ᴛɪᴍᴇ ᴛᴀᴋᴇɴ: {telegram_ping}
\nsᴇʀᴠɪᴄᴇ ᴜᴘᴛɪᴍᴇ: {uptime}
",
+ 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"\nsᴇʀᴠɪᴄᴇ ᴜᴘᴛɪᴍᴇ: {uptime}
"
+
+ 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 ᴄᴜʀʀᴇɴᴛʟʏ ᴇɴᴀʙʟᴇᴅ
\nᴡᴏᴜʟᴅ ʏᴏᴜ ʟɪᴋᴇ ᴛᴏ ᴅɪsᴀʙʟᴇ
raid?"
+ keyboard = [
+ [
+ InlineKeyboardButton(
+ "ᴅɪsᴀʙʟᴇ ʀᴀɪᴅ ᴍᴏᴅᴇ",
+ callback_data=f"disable_raid={chat.id}={time}",
+ ),
+ InlineKeyboardButton(
+ "ᴄᴀɴᴄᴇʟ ᴀᴄᴛɪᴏɴ", callback_data="cancel_raid=1"
+ ),
+ ]
+ ]
+
+ else:
+ text = (
+ f"ʀᴀɪᴅ ᴍᴏᴅᴇ ɪs ᴄᴜʀʀᴇɴᴛʟʏ ᴅɪsᴀʙʟᴇᴅ
\nᴡᴏᴜʟᴅ ʏᴏᴜ ʟɪᴋᴇ ᴛᴏ ᴇɴᴀʙʟᴇ
"
+ 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 ʙᴇᴇɴ Disabled
, ᴍᴇᴍʙᴇʀs ᴛʜᴀᴛ ᴊᴏɪɴ ᴡɪʟʟ ɴᴏ ʟᴏɴɢᴇʀ ʙᴇ ᴋɪᴄᴋᴇᴅ."
+ msg.reply_text(text, parse_mode=ParseMode.HTML)
+ return (
+ f"{html.escape(chat.title)}:\n"
+ f"#ʀᴀɪᴅ\n"
+ f"ᴅɪsᴀʙʟᴇᴅ\n"
+ f"ᴀᴅᴍɪɴ: {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 ᴄᴜʀʀᴇɴᴛʟʏ ᴅɪsᴀʙʟᴇᴅ
\nᴡᴏᴜʟᴅ ʏᴏᴜ ʟɪᴋᴇ ᴛᴏ Enable
"
+ 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 ʙᴇᴇɴ ᴇɴᴀʙʟᴇᴅ
ғᴏʀ {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"{html.escape(chat.title)}:\n"
+ f"#ʀᴀɪᴅ\n"
+ f"ᴇɴᴀʙʟᴇᴅ ғᴏʀ {readable_time}\n"
+ f"ᴀᴅᴍɪɴ: {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 ʙᴇᴇɴ ᴅɪsᴀʙʟᴇᴅ
, ɴᴇᴡʟʏ ᴊᴏɪɴɪɴɢ ᴍᴇᴍʙᴇʀs ᴡɪʟʟ ɴᴏ ʟᴏɴɢᴇʀ ʙᴇ ᴋɪᴄᴋᴇᴅ.",
+ parse_mode=ParseMode.HTML,
+ )
+ logmsg = (
+ f"{html.escape(chat.title)}:\n"
+ f"#ʀᴀɪᴅ\n"
+ f"ᴅɪsᴀʙʟᴇᴅ\n"
+ f"ᴀᴅᴍɪɴ: {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ᴛᴀʏ {'Enabled' if what == 1 else 'Disabled'}
.",
+ 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"{html.escape(chat.title)}:\n"
+ f"#ʀᴀɪᴅ\n"
+ f"sᴇᴛ ʀᴀɪᴅ ᴍᴏᴅᴇ ᴛɪᴍᴇ ᴛᴏ {readable_time}\n"
+ f"ᴀᴅᴍɪɴ: {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"{html.escape(chat.title)}:\n"
+ f"#ʀᴀɪᴅ\n"
+ f"sᴇᴛ ʀᴀɪᴅ ᴍᴏᴅᴇ ᴀᴄᴛɪᴏɴ ᴛɪᴍᴇ ᴛᴏ {readable_time}\n"
+ f"Admin: {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}
+ᴡʜɪᴄʜ ʏᴏᴜ ᴛɪᴍᴇᴅ {time} ʙᴇғᴏʀᴇ ɪɴ {title}
+"""
+
+
+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 (ᴍᴇɴᴛɪᴏɴᴇᴅ ʙᴇʟᴏᴡ ᴀʀᴇ ᴛʜᴇ ᴛɪᴍsᴛᴀᴍᴘs of the ʀᴇᴍɪɴᴅᴇʀs ʏᴏᴜ ʜᴀᴠᴇ sᴇᴛ):\n"
+ for i, u in enumerate(user_reminders):
+ reply_text += f"\n{i+1}. {u}
"
+ 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ᴛᴀᴍᴘ: {time_stamp}
".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ᴄᴇᴘᴛ (ᴛɪᴍᴇ sᴛᴀᴍᴘs ʜᴀᴠᴇ ʙᴇᴇɴ ᴍᴇɴᴛɪᴏɴᴇᴅ):"
+ for i, u in enumerate(unremoved_reminders):
+ reply_text += f"\n{i+1}. {u}
"
+ 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ᴄᴇᴘᴛ (ᴛɪᴍᴇ sᴛᴀᴍᴘs ʜᴀᴠᴇ ʙᴇᴇɴ ᴍᴇɴᴛɪᴏɴᴇᴅ):"
+ for i, u in enumerate(unremoved_reminders):
+ reply_text += f"\n{i+1}. {u}
"
+ 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