├── .gitignore ├── Dockerfile ├── LICENSE.txt ├── README.md ├── app ├── config.py ├── forward.py ├── logger.py └── main.py ├── optional-requirements.txt └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | eggs/ 15 | .eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | wheels/ 22 | share/python-wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .nox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *.cover 48 | *.py,cover 49 | .hypothesis/ 50 | .pytest_cache/ 51 | cover/ 52 | 53 | # Translations 54 | *.mo 55 | *.pot 56 | 57 | # Django stuff: 58 | *.log 59 | local_settings.py 60 | db.sqlite3 61 | db.sqlite3-journal 62 | 63 | # Flask stuff: 64 | instance/ 65 | .webassets-cache 66 | 67 | # Scrapy stuff: 68 | .scrapy 69 | 70 | # Sphinx documentation 71 | docs/_build/ 72 | 73 | # PyBuilder 74 | .pybuilder/ 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | # For a library or package, you might want to ignore these files since the code is 86 | # intended to run in multiple environments; otherwise, check them in: 87 | # .python-version 88 | 89 | # pipenv 90 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 91 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 92 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 93 | # install all needed dependencies. 94 | #Pipfile.lock 95 | 96 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 97 | __pypackages__/ 98 | 99 | # Celery stuff 100 | celerybeat-schedule 101 | celerybeat.pid 102 | 103 | # SageMath parsed files 104 | *.sage.py 105 | 106 | # Environments 107 | .env 108 | .venv 109 | env/ 110 | venv/ 111 | ENV/ 112 | env.bak/ 113 | venv.bak/ 114 | 115 | # Spyder project settings 116 | .spyderproject 117 | .spyproject 118 | 119 | # Rope project settings 120 | .ropeproject 121 | 122 | # mkdocs documentation 123 | /site 124 | 125 | # mypy 126 | .mypy_cache/ 127 | .dmypy.json 128 | dmypy.json 129 | 130 | # Pyre type checker 131 | .pyre/ 132 | 133 | # pytype static type analyzer 134 | .pytype/ 135 | 136 | # Cython debug symbols 137 | cython_debug/ 138 | 139 | # VSCode folder 140 | .vscode 141 | 142 | # Pyrogram session files 143 | *session* 144 | 145 | # JSON files 146 | *.json 147 | 148 | # Blocked images folder 149 | blocked_img/ 150 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Use an official Python runtime as a parent image 2 | FROM python:3.11-slim 3 | 4 | # Install ffmpeg and other dependencies 5 | RUN apt-get update && apt-get install ffmpeg libsm6 libxext6 -y 6 | 7 | # Set the working directory in the container 8 | WORKDIR /usr/src/app 9 | 10 | # Copy the requirements file into the container at /usr/src/app 11 | COPY requirements.txt optional-requirements.txt ./ 12 | 13 | # Run pip to install the dependencies 14 | RUN pip install --no-cache-dir -r requirements.txt 15 | 16 | # Install the optional dependencies 17 | RUN pip install --no-cache-dir -r optional-requirements.txt 18 | 19 | # Copy the source code into the container 20 | COPY . . 21 | 22 | # Command to run the application 23 | CMD ["python", "app/main.py"] 24 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2022 Scott Chacon and others 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PyWardBot 2 | > Telegram forwarder written in Python 3 | 4 | PyWardBot is an open source Telegram message forwarder powered by 5 | [Pyrogram](https://github.com/pyrogram/pyrogram) 6 | 7 | ![img_1](https://i.imgur.com/F12yXjv.gif) 8 | 9 | ## Features 10 | - Set by Telegram ID or by username 11 | - Telegram protected content bypass 12 | - Message reply detection 13 | - Message edit detection 14 | - Messaged delete detection 15 | - Enable and disable options 16 | - Replace words 17 | - Block words 18 | - Add origin chats 19 | - Get chat info 20 | - Two sending mode: **copy message or forward message** 21 | 22 | ## Use cases 23 | - Forward messages from one chat to another 24 | - Download protected media files 25 | - Backup messages 26 | - Send messages to multiple chats 27 | 28 | ## Installation and configuration 29 | 30 | ### Installation 31 | First, you will need to have 32 | [Python](https://realpython.com/installing-python/#how-to-install-python-on-windows) 33 | and 34 | [^1] [Git](https://github.com/git-guides/install-git) installed on your system. 35 | Also you will need to create a new 36 | [Telegram bot](https://www.siteguarding.com/en/how-to-get-telegram-bot-api-token) 37 | and get the bot token. 38 | 39 | After installing Python and Git, and creating a bot, open a terminal and do the following: 40 | ```bash 41 | # Clone repository 42 | git clone https://github.com/nunnito/PyWardBot.git 43 | 44 | # Change directory to the cloned repository 45 | cd PyWardBot 46 | 47 | # Install dependencies 48 | pip install -r requirements.txt 49 | 50 | # Run the bot 51 | python3 app/main.py 52 | ``` 53 | 54 | ### Configuration 55 | After running the bot for the first time, you will get the following output: 56 | ``` 57 | 25-05-2022 18:00:00 - config:23 - ERROR: bot.json not found 58 | 25-05-2022 18:00:00 - config:24 - WARNING: bot.json has been created with default values. Please edit it with your own api_id and api_hash values. You can find them on https://my.telegram.org/apps 59 | ``` 60 | You will need to edit the `bot.json` file with your own `api_id` and `api_hash` values in order to run the bot. 61 | 62 | Get those values from 63 | [this tutorial](https://arshmaan.com/how-to-get-telegram-api-id-and-hash-id/) 64 | or just go to [My Telegram Org](https://my.telegram.org/apps) and create a new app. 65 | 66 | Once you have these values go to the `PyWardBot/app/config/` folder and open the `bot.json` file. 67 | 68 | You will see a content like this: 69 | ```json 70 | { 71 | "api_id": 1234567, 72 | "api_hash": "0123456789abcdef0123456789abcdef", 73 | "admins": [] 74 | } 75 | ``` 76 | Replace `1234567` and `0123456789abcdef0123456789abcdef` with your own `api_id` and `api_hash` values. 77 | 78 | Then run the bot again: 79 | ```bash 80 | python3 app/main.py 81 | ``` 82 | 83 | You will be prompted to enter your phone number: 84 | ```bash 85 | 25-05-2022 18:00:00 - main:839 - INFO: Log-in with your phone number 86 | Welcome to Pyrogram (version 1.4.8) 87 | Pyrogram is free software and comes with ABSOLUTELY NO WARRANTY. Licensed 88 | under the terms of the GNU Lesser General Public License v3 or later (LGPLv3+). 89 | 90 | Enter phone number or bot token: 91 | ``` 92 | 93 | Remember to enter your phone number in the international format (e.g. +11234567890). 94 | 95 | After entering your phone number, a code will be sent to your Telegram account. Enter the code and press enter. 96 | 97 | Then you will be prompted to enter your bot token: 98 | ```bash 99 | 25-05-2022 18:00:0 - main:843 - INFO: Log-in with you bot token 100 | Welcome to Pyrogram (version 1.4.8) 101 | Pyrogram is free software and comes with ABSOLUTELY NO WARRANTY. Licensed 102 | under the terms of the GNU Lesser General Public License v3 or later (LGPLv3+). 103 | 104 | Enter phone number or bot token: 105 | ``` 106 | 107 | After entering your bot token, and if everything went well, you will see the following output: 108 | ```bash 109 | 25-05-2022 20:15:08 - main:845 - INFO: Bot started 110 | 25-05-2022 20:20:08 - main:846 - INFO: Bot username: @YOURBOTUSERNAME 111 | ``` 112 | 113 | That's it! Now you can start the bot and set it up! 114 | 115 | 116 | ## FAQ 117 | ### Is necessary to install Git? 118 | No, you can download the source code as a zip file from [here](https://github.com/nunnito/PyWardBot/archive/refs/heads/master.zip). Then unzip it and follow the installation [instructions](#installation) 119 | 120 | ### How to add a new admin? 121 | By default, the bot is configured to run only for yourself. If you want to add a new admin, you can do it by adding the new Telegram ID to the `admins` array in the `bot.json` file. 122 | 123 | ### What are the different modes of sending messages? 124 | There are two modes of sending messages: 125 | 126 | - **copy message**: the bot will copy the message to the destination chat. 127 | - **forward message**: the bot will forward the message to the destination chat. 128 | 129 | 130 | ### How to send only outgoing or only incoming messages? 131 | For this you need to open the `forwarding.json` file and modify the `outgoing` and `incoming` properties. By default, the bot will forward all messages. 132 | -------------------------------------------------------------------------------- /app/config.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | from pathlib import Path 4 | 5 | from logger import logger 6 | 7 | # Create a folder that will hold the configuration files 8 | app_dir = Path(__file__).parent 9 | config_dir = app_dir / "config" 10 | config_dir.mkdir(exist_ok=True) 11 | 12 | 13 | class Bot: 14 | bot = { 15 | "api_id": 1234567, 16 | "api_hash": "0123456789abcdef0123456789abcdef", 17 | "admins": [] 18 | } 19 | 20 | def get_config(self) -> dict: 21 | """Load the bot configuration from the bot.json file.""" 22 | if not os.path.exists(config_dir/"bot.json"): 23 | logger.error("bot.json not found") 24 | logger.warning("bot.json has been created with default values. " + 25 | "Please edit it with your own api_id and api_hash" + 26 | " values. You can find them on " + 27 | "https://my.telegram.org/apps.\n" + 28 | "you can also use the API_ID and API_HASH " + 29 | "envinroment variables.") 30 | 31 | with open(config_dir/"bot.json", "w") as f: 32 | json.dump(self.bot, f, indent=4, ensure_ascii=False) 33 | 34 | if not os.getenv("API_ID") or not os.getenv("API_HASH"): 35 | logger.error("API_ID and API_HASH environment variables not " + 36 | " found please add them to your environment " + 37 | "variables.") 38 | logger.error("Exiting...") 39 | exit(1) 40 | 41 | with open(config_dir/"bot.json", "r") as f: 42 | return json.load(f) 43 | 44 | def add_admin(self, admin: int) -> None: 45 | """Add an admin to the bot configuration.""" 46 | if admin not in self.get_config()["admins"]: 47 | config = self.get_config() 48 | config["admins"].append(admin) 49 | 50 | with open(config_dir/"bot.json", "w") as f: 51 | json.dump(config, f, indent=4, ensure_ascii=False) 52 | 53 | 54 | class Forwarding: 55 | forwarding = {"forwarders": [], "blocked_images": []} 56 | 57 | async def get_config(self) -> dict: 58 | """Load the forwarding configuration from the forwarding.json file.""" 59 | if not os.path.exists(config_dir/"forwarding.json"): 60 | logger.warning("forwarding.json not found") 61 | 62 | with open(config_dir/"forwarding.json", "w") as f: 63 | json.dump(self.forwarding, f, indent=4, ensure_ascii=False) 64 | 65 | with open(config_dir/"forwarding.json", "r") as f: 66 | return json.load(f) 67 | 68 | async def get_forwarding_ids(self) -> list: 69 | """Get the list of forwarding IDs.""" 70 | forwarders = (await self.get_config())["forwarders"] 71 | forwarding_ids = [] 72 | 73 | for forwarder in forwarders: 74 | for id in forwarder["source"].keys(): 75 | forwarding_ids.append(int(id)) 76 | 77 | # Remove duplicates and return the list 78 | return list(dict.fromkeys(forwarding_ids)) 79 | 80 | async def get_forwardings(self) -> list: 81 | """Get the list of forwarding targets""" 82 | forwarders = (await self.get_config())["forwarders"] 83 | forwarding_targets = [] 84 | 85 | for forwarder in forwarders: 86 | forwarding_targets.append(forwarder["target"]) 87 | 88 | # Remove duplicates and return the list 89 | return list(dict.fromkeys(forwarding_targets)) 90 | 91 | async def get_forwarder(self, forwarder_id: str) -> dict: 92 | """Get the forwarder with the given hash ID.""" 93 | forwarders = (await self.get_config())["forwarders"] 94 | 95 | for forwarder in forwarders: 96 | if str(forwarder["target"]) == forwarder_id: 97 | return forwarder 98 | 99 | async def update_forwarder(self, forwarder_dict: dict): 100 | """Update the forwarding configuration.""" 101 | config = await self.get_config() 102 | target = forwarder_dict["target"] 103 | 104 | for forwarder in config["forwarders"]: 105 | if forwarder["target"] == target: 106 | for key, value in forwarder_dict.items(): 107 | forwarder[key] = value 108 | 109 | with open(config_dir/"forwarding.json", "w") as f: 110 | json.dump(config, f, indent=4, ensure_ascii=False) 111 | 112 | async def add_forwarder(self, name: str, target: str, source: dict): 113 | """Add a new forwarding rule.""" 114 | config = await self.get_config() 115 | config["forwarders"].append( 116 | { 117 | "name": name, 118 | "target": int(target), 119 | "enabled": True, 120 | "forwarding_mode": "copy", 121 | "incoming": True, 122 | "outgoing": True, 123 | "reply": True, 124 | "duplicated_text": False, 125 | "send_text_only": False, 126 | "translate": False, 127 | "translate_to": "en", 128 | "translate_from": "auto", 129 | "translate_show_original": False, 130 | "translate_original_prefix": "Original:", 131 | "translate_translation_prefix": "Translated:", 132 | "replace_words_mode": "word_boundary_match", 133 | "replace_words": {}, 134 | "blocked_words": [], 135 | "source": source, 136 | "patterns": [] 137 | } 138 | ) 139 | 140 | with open(config_dir/"forwarding.json", "w") as f: 141 | json.dump(config, f, indent=4, ensure_ascii=False) 142 | 143 | async def remove_forwarder(self, forwarder_id: str): 144 | """Remove a forwarding rule.""" 145 | config = await self.get_config() 146 | 147 | for forwarder in config["forwarders"]: 148 | if forwarder["target"] == int(forwarder_id): 149 | config["forwarders"].remove(forwarder) 150 | 151 | with open(config_dir/"forwarding.json", "w") as f: 152 | json.dump(config, f, indent=4, ensure_ascii=False) 153 | 154 | async def get_blocked_images(self) -> list: 155 | """Get the list of blocked images.""" 156 | config = await self.get_config() 157 | blocked_images = config["blocked_images"] 158 | 159 | return blocked_images 160 | 161 | async def add_blocked_image(self, image: str): 162 | """Add a new blocked image.""" 163 | config = await self.get_config() 164 | config["blocked_images"].append(image) 165 | 166 | with open(config_dir/"forwarding.json", "w") as f: 167 | json.dump(config, f, indent=4, ensure_ascii=False) 168 | 169 | 170 | class MessagesIDs: 171 | async def get_message_ids(self) -> list: 172 | """Get the list of message IDs.""" 173 | if not os.path.exists(config_dir/"messages.json"): 174 | logger.warning("messages.json not found") 175 | 176 | with open(config_dir/"messages.json", "w") as f: 177 | json.dump({}, f, indent=4, ensure_ascii=False) 178 | 179 | with open(config_dir/"messages.json", "r") as f: 180 | return json.load(f) 181 | 182 | async def add_message_id(self, target: str, source: str, real_id: int, 183 | copy_id: int): 184 | """Add a message ID to the list of IDs.""" 185 | logger.debug(f"Adding message ID {copy_id} to {source} in {target}") 186 | messages_ids = await self.get_message_ids() 187 | 188 | if target not in messages_ids: 189 | messages_ids[target] = {} 190 | if source not in messages_ids[target]: 191 | messages_ids[target][source] = {} 192 | messages_ids[target][source][str(real_id)] = copy_id 193 | 194 | with open(config_dir/"messages.json", "w") as f: 195 | json.dump(messages_ids, f, indent=4, ensure_ascii=False) 196 | -------------------------------------------------------------------------------- /app/forward.py: -------------------------------------------------------------------------------- 1 | # TODO: whitelist words or whitelist patterns to only capture messages that 2 | # match with a pattern or contains a word. 3 | # TODO: make a filter config to only capture messages that contains selected 4 | # media. Ex: Select to only forward messages with photos, videos, audios, etc. 5 | # TODO: Blacklist regex pattern? 6 | # TODO: Make a filter to only capture types of messages. 7 | # Ex: Only forwarded messages, edited messages, deleted messages, etc. 8 | # TODO: Make a filter to only send media messages, skipping text and viceversa 9 | # Ex: If a photo with a caption is sent, only send the photo, not the caption 10 | # TODO: Toggle message removal from the chat. 11 | # TODO: Edited, deleted, pinned, replied messages toggles. 12 | 13 | import os 14 | import re 15 | import datetime 16 | from pathlib import Path 17 | from os import getenv 18 | 19 | from deep_translator import GoogleTranslator 20 | from sewar.full_ref import uqi 21 | from cv2 import imread 22 | 23 | from pyrogram import Client, filters 24 | from pyrogram.raw.functions.messages import SendVote 25 | from pyrogram.enums import MessageMediaType, PollType 26 | from pyrogram.types import (Message, InputMediaPhoto, InputMediaVideo, 27 | InputMediaAudio, InputMediaDocument, 28 | InputMediaAnimation) 29 | from pyrogram.errors.exceptions.bad_request_400 import (MediaInvalid, 30 | MessageIdInvalid, 31 | MessageNotModified) 32 | 33 | from config import Bot, Forwarding, MessagesIDs 34 | from logger import logger 35 | 36 | # Config path 37 | app_dir = Path(__file__).parent 38 | config_dir = app_dir / "config" 39 | 40 | # Load the bot configuration 41 | bot_config = Bot().get_config() 42 | 43 | 44 | # Set up the Telegram client 45 | API_ID = getenv("API_ID") if getenv("API_ID") else bot_config["api_id"] 46 | API_HASH = getenv("API_HASH") if getenv("API_HASH") else bot_config["api_hash"] 47 | 48 | user = Client(str(Path(config_dir/"user")), API_ID, API_HASH) 49 | current_media_group = None # Current media group ID 50 | 51 | Messages = MessagesIDs() 52 | Forwardings = Forwarding() 53 | 54 | 55 | async def is_identical_to_last(message: Message, target: int) -> bool: 56 | """Check if the message is identical to the last message""" 57 | generator = message._client.get_chat_history(target, 1) 58 | last_message = [m async for m in generator] 59 | return message.text == last_message[0].text 60 | 61 | 62 | async def translate(text: str, to: str, from_: str, show_original: bool, 63 | original_prefix: str, translation_prefix: str) -> str: 64 | """Translate the text""" 65 | strip_text = False 66 | found_hashtag = re.search(r"#\w+\b", text) 67 | 68 | # This is to avoid the translation of the hashtag 69 | if found_hashtag: 70 | stripped_text = found_hashtag.group() 71 | text = text.replace(stripped_text, "3141592") 72 | strip_text = True 73 | 74 | # Translate the text 75 | translated_text = GoogleTranslator(from_, to).translate(text) 76 | if strip_text: 77 | translated_text = translated_text.replace("3141592", stripped_text) 78 | text = text.replace("3141592", stripped_text) 79 | 80 | # Show the original text and translated text together 81 | if show_original: 82 | return (f"{original_prefix}\n{text}\n\n" 83 | f"{translation_prefix}\n{translated_text}") 84 | 85 | return translated_text 86 | 87 | 88 | async def get_media_type(message: Message) -> str: 89 | match message.media: 90 | case MessageMediaType.PHOTO: 91 | return message.photo.file_id 92 | case MessageMediaType.VIDEO: 93 | return message.video.file_id 94 | case MessageMediaType.AUDIO: 95 | return message.audio.file_id 96 | case MessageMediaType.VOICE: 97 | return message.voice.file_id 98 | case MessageMediaType.DOCUMENT: 99 | return message.document.file_id 100 | case MessageMediaType.ANIMATION: 101 | return message.animation.file_id 102 | case MessageMediaType.VIDEO_NOTE: 103 | return message.video_note.file_id 104 | case MessageMediaType.STICKER: 105 | return message.sticker.file_id 106 | 107 | 108 | async def is_image_blocked(message: Message) -> bool: 109 | """Check if the image is blocked""" 110 | imgs = await Forwardings.get_blocked_images() 111 | 112 | # Set name of the image to current timestamp 113 | name = datetime.datetime.now().timestamp() 114 | 115 | # Download the photo and read it with cv2 116 | test_path = await message.download(config_dir/"blocked_img"/f"{name}.jpg") 117 | test_img = imread(test_path) 118 | 119 | # Remove the downloaded photo (this is safe because is already in memory) 120 | if os.path.isfile(test_path): 121 | os.remove(test_path) 122 | 123 | for img in imgs: 124 | # Open original image 125 | og_img = imread(img) 126 | 127 | # Check if the image is the same size 128 | if og_img.shape[:2] == (message.photo.height, message.photo.width): 129 | # Check similarity with the original image 130 | if uqi(test_img, og_img) >= 0.9: 131 | logger.debug(f"Image '{img}' is blocked") 132 | return True 133 | 134 | return False 135 | 136 | 137 | async def is_forwarder(filter, client: Client, message: Message) -> bool: 138 | """Check if the chat id is in the forwarding list""" 139 | id = message.chat.id 140 | forwarding_ids = await Forwardings.get_forwarding_ids() 141 | return id in forwarding_ids 142 | 143 | 144 | async def replace_words(target: dict, text: str, 145 | is_caption: bool = False) -> str: 146 | """Replace words and select text with regex""" 147 | words = target["replace_words"] 148 | if text is None: 149 | logger.debug("Text is None, returning an empty string") 150 | return "" 151 | 152 | logger.debug(f"Replace mode is: {target['replace_words_mode']}") 153 | for word in words: 154 | # Replace only boundary words matches 155 | if target["replace_words_mode"] == "word_boundary_match": 156 | sub_word = word 157 | # If the word starts with 158 | if word[0] in ["@", "#", "$"]: 159 | sub_word = word[1:] 160 | symbol = word[0] 161 | pattern = r"[%s]\b%s\b" % (symbol, sub_word) 162 | # If the word ends with 163 | elif word[-1] in ["@", "#", "$"]: 164 | sub_word = word[:-1] 165 | symbol = word[-1] 166 | pattern = r"\b%s[%s]" % (sub_word, symbol) 167 | else: 168 | pattern = r"\b%s\b" % word 169 | text = re.sub(pattern, words[word], text, flags=re.I) 170 | # Replace any match 171 | else: 172 | text = re.sub(word, words[word], text, flags=re.I) 173 | 174 | # Select a match with regex 175 | for pattern in target["patterns"]: 176 | if re.search(pattern["pattern"], text, re.DOTALL): 177 | logger.debug(f"Pattern '{pattern['name']}' found in text") 178 | text = re.search(pattern["pattern"], text, re.DOTALL) 179 | text = text.group(pattern["group"]) 180 | break 181 | 182 | if target["translate"]: 183 | translated_text = await translate( 184 | text, 185 | target["translate_to"], 186 | target["translate_from"], 187 | target["translate_show_original"], 188 | target["translate_original_prefix"], 189 | target["translate_translation_prefix"]) 190 | if is_caption and len(translated_text) <= 1024: 191 | text = translated_text 192 | elif not is_caption and len(translated_text) <= 4096: 193 | text = translated_text 194 | 195 | logger.debug("Returning text") 196 | return text 197 | 198 | 199 | async def forward_message(message: Message, target: dict, edited=False, 200 | media_group=False): 201 | """Forward a message to a target""" 202 | msg_ids = await Messages.get_message_ids() 203 | target = str(target["target"]) 204 | source = str(message.chat.id) 205 | ids = message.id 206 | 207 | if media_group: 208 | messages = await user.get_media_group(source, message.id) 209 | ids = [msg.id for msg in messages] 210 | 211 | if edited: 212 | origin_edit_id = str(message.id) 213 | edit_id = -1 214 | can_edit = True 215 | # If the message id is in the list of messages ids 216 | if target in msg_ids and source in msg_ids[target] and origin_edit_id\ 217 | in msg_ids[target][source]: 218 | edit_id = msg_ids[target][source][origin_edit_id] 219 | else: 220 | can_edit = False 221 | logger.error("The message cannot be deleted, because it does " + 222 | "not exist in the target chat") 223 | if media_group and can_edit: 224 | edit_id = [msg_ids[target][source][str(id)] for id in ids] 225 | if edit_id != -1: 226 | deleted_id = await user.get_messages(target, edit_id) 227 | # Remove message, to resend it already edited 228 | if media_group: 229 | await on_deleted_message(user, deleted_id) 230 | else: 231 | await on_deleted_message(user, [deleted_id]) 232 | await user.delete_messages(target, edit_id) 233 | 234 | if not message.chat.has_protected_content: 235 | msg = await user.forward_messages(target, source, ids) 236 | from_user = message.chat.title if message.chat.title else\ 237 | message.chat.first_name 238 | 239 | if media_group: 240 | mgm = msg[0] 241 | to_user = mgm.chat.title if mgm.chat.title else mgm.chat.first_name 242 | else: 243 | to_user = msg.chat.title if msg.chat.title else msg.chat.first_name 244 | logger.info(f"Forwarding message from {from_user} to {to_user}") 245 | else: 246 | logger.error("The chat is restricted from forwarding messages") 247 | return 248 | 249 | if media_group: 250 | for old_msg, new_msg in zip(messages, msg): 251 | await Messages.add_message_id(target, source, old_msg.id, 252 | new_msg.id) 253 | else: 254 | await Messages.add_message_id(target, source, message.id, msg.id) 255 | 256 | if media_group and edited: 257 | await on_media_group_edited(user, msg[0]) 258 | elif media_group: 259 | await on_media_group(user, msg[0]) 260 | else: 261 | await on_new_message(user, msg) 262 | 263 | 264 | async def copy_message(message: Message, target: dict, edited=False, 265 | pinned=False, reply=False, media_group=False): 266 | """Copy a message to a target""" 267 | msg_ids = await Messages.get_message_ids() 268 | forwarder = target 269 | target = str(target["target"]) 270 | source = str(message.chat.id) 271 | from_user = message.chat.title if message.chat.title else\ 272 | message.chat.first_name 273 | 274 | if pinned: 275 | origin_pinned_id = str(message.pinned_message.id) 276 | pinned_id = -1 277 | to_user = await user.get_chat(target) 278 | to_user = to_user.title if to_user.title else to_user.first_name 279 | 280 | # If the message id is in the list of messages ids 281 | if target in msg_ids and source in msg_ids[target] and\ 282 | origin_pinned_id in msg_ids[target][source]: 283 | pinned_id = msg_ids[target][source][origin_pinned_id] 284 | try: 285 | await user.pin_chat_message(target, pinned_id, both_sides=True) 286 | logger.info(f"Pinning message from {from_user} to {to_user}") 287 | except MessageIdInvalid: 288 | logger.error("The message cannot be pinned, because it does not " + 289 | "exist in the target chat") 290 | return 291 | 292 | elif media_group: 293 | messages = await user.get_media_group(source, message.id) 294 | media_input = [] 295 | downloaded_media = [] 296 | 297 | for msg_media in messages: 298 | text = await replace_words(forwarder, msg_media.caption, True) 299 | entities = msg_media.caption_entities 300 | # If the chat has protected content, download the media to send it 301 | if msg_media.chat.has_protected_content: 302 | path = await msg_media.download() 303 | # If the chat has not protected content, get the file_id to send it 304 | else: 305 | path = await get_media_type(msg_media) 306 | 307 | downloaded_media.append(path) 308 | if msg_media.media is MessageMediaType.PHOTO: 309 | media_input.append(InputMediaPhoto(path, text)) 310 | elif msg_media.media is MessageMediaType.VIDEO: 311 | media_input.append(InputMediaVideo(path, caption=text)) 312 | elif msg_media.media is MessageMediaType.AUDIO: 313 | media_input.append(InputMediaAudio(path, caption=text)) 314 | elif msg_media.media is MessageMediaType.DOCUMENT: 315 | media_input.append(InputMediaDocument(path, caption=text)) 316 | elif msg_media.media is MessageMediaType.ANIMATION: 317 | media_input.append(InputMediaAnimation(path, caption=text)) 318 | 319 | reply_id = None 320 | if reply: 321 | src_reply_id = str(message.reply_to_message.id) 322 | # If the message id is in the list of messages ids 323 | if target in msg_ids and source in msg_ids[target] and\ 324 | src_reply_id in msg_ids[target][source]: 325 | reply_id = msg_ids[target][source][src_reply_id] 326 | logger.debug(f"Replying to message: {reply_id}") 327 | else: 328 | logger.error("The reply message cannot be forwarded, " + 329 | "because it does not exist in the target chat") 330 | return 331 | 332 | if forwarder["send_text_only"]: 333 | msg = await user.send_message(target, text, entities=entities, 334 | reply_to_message_id=reply_id) 335 | await Messages.add_message_id(target, source, messages[0].id, 336 | msg.id) 337 | else: 338 | msg = await user.send_media_group(target, media_input, 339 | reply_to_message_id=reply_id) 340 | for old_msg, new_msg in zip(messages, msg): 341 | await Messages.add_message_id(target, source, old_msg.id, 342 | new_msg.id) 343 | 344 | to_user = msg[0].chat.title if msg[0].chat.title else\ 345 | msg[0].chat.first_name 346 | logger.info(f"Copying media group from {from_user} to {to_user}") 347 | 348 | for path in downloaded_media: 349 | if os.path.isfile(path): 350 | logger.debug(f"Removing file {path}") 351 | os.remove(path) 352 | 353 | elif message.media is not None: 354 | downloadable_media = [MessageMediaType.PHOTO, MessageMediaType.VIDEO, 355 | MessageMediaType.AUDIO, MessageMediaType.VOICE, 356 | MessageMediaType.DOCUMENT, 357 | MessageMediaType.ANIMATION, 358 | MessageMediaType.VIDEO_NOTE, 359 | MessageMediaType.STICKER] 360 | text = await replace_words(forwarder, message.caption, True) 361 | entities = message.caption_entities 362 | reply_id = None 363 | 364 | if message.media in downloadable_media: 365 | if message.media is MessageMediaType.PHOTO: 366 | if await is_image_blocked(message): 367 | logger.error("The image is blocked") 368 | return 369 | 370 | # If the chat has protected content, download the media to send it 371 | if message.chat.has_protected_content: 372 | logger.debug(f"{from_user} has protected content, " 373 | "downloading media") 374 | path = await message.download() 375 | else: 376 | logger.debug(f"{from_user} has no protected content, " 377 | "using file_id") 378 | path = await get_media_type(message) 379 | 380 | if reply: 381 | src_reply_id = str(message.reply_to_message_id) 382 | # If the message id is in the list of messages ids 383 | if target in msg_ids and source in msg_ids[target] and\ 384 | src_reply_id in msg_ids[target][source]: 385 | reply_id = msg_ids[target][source][src_reply_id] 386 | logger.debug(f"Replying to message: {reply_id}") 387 | # If the chat has not protected content, get the file_id to send it 388 | else: 389 | logger.error("The media cannot be replied, because it " + 390 | "does not exist in the target chat") 391 | return 392 | 393 | if edited: 394 | origin_edit_id = str(message.id) 395 | edit_id = -1 396 | # If the message id is in the list of messages ids 397 | if target in msg_ids and source in msg_ids[target] and\ 398 | origin_edit_id in msg_ids[target][source]: 399 | edit_id = msg_ids[target][source][origin_edit_id] 400 | if message.media is MessageMediaType.PHOTO: 401 | media = InputMediaPhoto(path, text) 402 | elif message.media is MessageMediaType.VIDEO: 403 | media = InputMediaVideo(path, caption=text) 404 | elif message.media is MessageMediaType.AUDIO: 405 | media = InputMediaAudio(path, caption=text) 406 | elif message.media is MessageMediaType.DOCUMENT: 407 | media = InputMediaDocument(path, caption=text) 408 | elif message.media is MessageMediaType.ANIMATION: 409 | media = InputMediaAnimation(path, caption=text) 410 | 411 | try: 412 | if message.media is MessageMediaType.WEB_PAGE: 413 | text = await replace_words(forwarder, message.text) 414 | msg = await user.edit_message_text(target, edit_id, text, 415 | entities=entities) 416 | to_user = msg.chat.title if msg.chat.title else\ 417 | msg.chat.first_name 418 | logger.info(f"Editing media from {from_user} to {to_user}") 419 | else: 420 | msg = await user.edit_message_media(target, edit_id, media) 421 | to_user = msg.chat.title if msg.chat.title else\ 422 | msg.chat.first_name 423 | logger.info(f"Editing media from {from_user} to {to_user}") 424 | if entities is not None: 425 | await user.edit_message_caption( 426 | target, edit_id, text, caption_entities=entities) 427 | except MessageIdInvalid: 428 | logger.error("The media cannot be edited, because it does " + 429 | "not exist in the target chat") 430 | return 431 | except MessageNotModified: 432 | logger.error("The media could not be edited, because an atte" + 433 | "mpt was made to edit using the same content") 434 | return 435 | 436 | else: 437 | if forwarder["send_text_only"] and text != "": 438 | msg = await user.send_message(target, text, entities=entities, 439 | reply_to_message_id=reply_id) 440 | elif message.media is MessageMediaType.PHOTO: 441 | msg = await user.send_photo(target, path, text, 442 | caption_entities=entities, 443 | reply_to_message_id=reply_id) 444 | elif message.media is MessageMediaType.AUDIO: 445 | msg = await user.send_audio(target, path, text, 446 | caption_entities=entities, 447 | reply_to_message_id=reply_id) 448 | elif message.media is MessageMediaType.DOCUMENT: 449 | msg = await user.send_document(target, path, caption=text, 450 | caption_entities=entities, 451 | reply_to_message_id=reply_id) 452 | elif message.media is MessageMediaType.STICKER: 453 | msg = await user.send_sticker(target, path, 454 | reply_to_message_id=reply_id) 455 | elif message.media is MessageMediaType.VIDEO: 456 | msg = await user.send_video(target, path, text, 457 | caption_entities=entities, 458 | reply_to_message_id=reply_id) 459 | elif message.media is MessageMediaType.ANIMATION: 460 | msg = await user.send_animation(target, path, text, 461 | caption_entities=entities, 462 | reply_to_message_id=reply_id) 463 | elif message.media is MessageMediaType.VOICE: 464 | msg = await user.send_voice(target, path, text, 465 | caption_entities=entities, 466 | reply_to_message_id=reply_id) 467 | elif message.media is MessageMediaType.VIDEO_NOTE: 468 | msg = await user.send_video_note(target, path, 469 | reply_to_message_id=reply_id) 470 | elif message.media is MessageMediaType.LOCATION: 471 | latitude = message.location.latitude 472 | longitude = message.location.longitude 473 | msg = await user.send_location(target, latitude, longitude, 474 | reply_to_message_id=reply_id) 475 | elif message.media is MessageMediaType.VENUE: 476 | latitude = message.venue.location.latitude 477 | longitude = message.venue.location.longitude 478 | title = message.venue.title 479 | address = message.venue.address 480 | msg = await user.send_venue(target, latitude, longitude, title, 481 | address, 482 | reply_to_message_id=reply_id) 483 | elif message.media is MessageMediaType.CONTACT: 484 | phone_number = message.contact.phone_number 485 | first_name = message.contact.first_name 486 | last_name = message.contact.last_name 487 | msg = await user.send_contact(target, phone_number, 488 | first_name, last_name, 489 | reply_to_message_id=reply_id) 490 | elif message.media is MessageMediaType.DICE: 491 | emoji = message.dice.emoji 492 | msg = await user.send_dice(target, emoji, 493 | reply_to_message_id=reply_id) 494 | elif message.media is MessageMediaType.WEB_PAGE: 495 | entities = message.entities 496 | text = await replace_words(forwarder, message.text) 497 | msg = await user.send_message(target, text, entities=entities, 498 | reply_to_message_id=reply_id) 499 | # Only will be sent if the poll type is regular 500 | elif message.media is MessageMediaType.POLL and\ 501 | message.poll.type is PollType.REGULAR: 502 | question = message.poll.question 503 | options = [option.text for option in message.poll.options] 504 | is_anonymous = message.poll.is_anonymous 505 | type = message.poll.type 506 | allows_multiple_answers = message.poll.allows_multiple_answers 507 | try: 508 | msg = await user.send_poll(target, question, options, 509 | is_anonymous, type, 510 | allows_multiple_answers, 511 | reply_to_message_id=reply_id) 512 | except MediaInvalid: 513 | logger.error("The poll could not be sent. Maybe the " + 514 | "target is a private chat?") 515 | return 516 | elif message.media is MessageMediaType.POLL and\ 517 | message.poll.type is PollType.QUIZ: 518 | # Answer the quiz to get the correct answer and explanation 519 | peer = await user.resolve_peer(message.chat.id) 520 | msg_id = message.id 521 | options = [b'0'] 522 | r = await user.invoke(SendVote(peer=peer, msg_id=msg_id, 523 | options=options)) 524 | 525 | # Get the correct answer 526 | for question in r.updates[0].results.results: 527 | if question.correct: 528 | correct_option = int(question.option) 529 | break 530 | # Get the explanation 531 | explanation = r.updates[0].results.solution 532 | 533 | # Get base quiz 534 | question = message.poll.question 535 | options = [option.text for option in message.poll.options] 536 | is_anonymous = message.poll.is_anonymous 537 | type = message.poll.type 538 | allows_multiple_answers = message.poll.allows_multiple_answers 539 | 540 | # Send the quiz 541 | try: 542 | msg = await user.send_poll(target, question, options, 543 | is_anonymous, type, 544 | allows_multiple_answers, 545 | correct_option, explanation, 546 | reply_to_message_id=reply_id) 547 | except MediaInvalid: 548 | logger.error("The poll could not be sent. Maybe the " + 549 | "target is a private chat?") 550 | return 551 | 552 | to_user = msg.chat.title if msg.chat.title else msg.chat.first_name 553 | logger.info(f"Sending media from {from_user} to {to_user}") 554 | await Messages.add_message_id(target, source, message.id, msg.id) 555 | 556 | if message.media in downloadable_media: 557 | if os.path.isfile(path): 558 | logger.debug(f"Removing file {path}") 559 | os.remove(path) 560 | 561 | # If the message is just a text message 562 | else: 563 | text = await replace_words(forwarder, message.text) 564 | entities = message.entities 565 | reply_id = None 566 | 567 | if not forwarder["duplicated_text"]: 568 | if await is_identical_to_last(message, target): 569 | logger.debug("The message is identical to the last message, " + 570 | "skipping") 571 | return 572 | 573 | if reply: 574 | src_reply_id = str(message.reply_to_message_id) 575 | # If the message id is in the list of messages ids 576 | if target in msg_ids and source in msg_ids[target] and\ 577 | src_reply_id in msg_ids[target][source]: 578 | reply_id = msg_ids[target][source][src_reply_id] 579 | logger.debug(f"Replying to message: {reply_id}") 580 | else: 581 | logger.error("The message cannot be replied, because it " + 582 | "does not exist in the target chat") 583 | return 584 | 585 | if edited: 586 | origin_edit_id = str(message.id) 587 | edit_id = -1 588 | if target in msg_ids and origin_edit_id in msg_ids[target][source]: 589 | edit_id = msg_ids[target][source][origin_edit_id] 590 | try: 591 | msg = await user.edit_message_text(target, edit_id, text, 592 | entities=entities) 593 | to_user = msg.chat.title if msg.chat.title else\ 594 | msg.chat.first_name 595 | logger.info(f"Editing message from {from_user} to {to_user}") 596 | except MessageIdInvalid: 597 | logger.error("The message cannot be edited, because it does " + 598 | "not exist in the target chat") 599 | return 600 | except MessageNotModified: 601 | logger.error("The message could not be edited, because an at" + 602 | "tempt was made to edit using the same content") 603 | return 604 | 605 | else: 606 | msg = await user.send_message(target, text, entities=entities, 607 | reply_to_message_id=reply_id) 608 | to_user = msg.chat.title if msg.chat.title else msg.chat.first_name 609 | logger.info(f"Sending message from {from_user} to {to_user}") 610 | await Messages.add_message_id(target, source, message.id, msg.id) 611 | 612 | # Call messages function to handle the new message 613 | if media_group and reply: 614 | await on_media_group_reply(user, msg[0]) 615 | elif media_group: 616 | await on_media_group(user, msg[0]) 617 | elif reply: 618 | await on_message_reply(user, msg) 619 | elif edited: 620 | await on_message_edited(user, msg) 621 | else: 622 | await on_new_message(user, msg) 623 | 624 | 625 | async def get_targets(event: Message, media_group=False): 626 | """Get the target chats for the message""" 627 | targets = [] 628 | source = event.chat.id 629 | forwarding_ids = await Forwardings.get_forwarding_ids() 630 | config = await Forwardings.get_config() 631 | outgoing = event.outgoing 632 | 633 | if source in forwarding_ids: 634 | for forwarder in config["forwarders"]: 635 | next_forwarder = False 636 | 637 | for word in forwarder["blocked_words"]: 638 | if media_group: 639 | for message in event: 640 | if message.caption is not None: 641 | if word.lower() in message.caption.lower(): 642 | next_forwarder = True 643 | break 644 | if next_forwarder: 645 | break 646 | else: 647 | if event.caption is not None: 648 | if word.lower() in event.caption.lower(): 649 | next_forwarder = True 650 | break 651 | if event.text is not None: 652 | if word.lower() in event.text.lower(): 653 | next_forwarder = True 654 | break 655 | 656 | if next_forwarder: 657 | continue 658 | if forwarder["enabled"]: 659 | # If forward outgoing messages is disabled, add the message ID 660 | # to messages.json 661 | if outgoing and not forwarder["outgoing"]: 662 | await Messages.add_message_id(str(forwarder["target"]), 663 | str(source), event.id, 664 | event.id) 665 | continue 666 | # If forward incoming messages is disabled, add the message ID 667 | # to messages.json 668 | if not outgoing and not forwarder["incoming"]: 669 | await Messages.add_message_id(str(forwarder["target"]), 670 | str(source), event.id, 671 | event.id) 672 | continue 673 | if source in [int(src) for src in forwarder["source"].keys()]: 674 | targets.append(forwarder) 675 | 676 | return targets 677 | 678 | 679 | @user.on_edited_message(filters.create(is_forwarder) & ~filters.media_group) 680 | async def on_message_edited(client: Client, message: Message): 681 | """Handle edited messages""" 682 | for target in await get_targets(message): 683 | if target["forwarding_mode"] == "copy": 684 | await copy_message(message, target, edited=True) 685 | else: 686 | await forward_message(message, target, edited=True) 687 | 688 | 689 | @user.on_message(filters.create(is_forwarder) & filters.pinned_message & 690 | ~filters.media_group) 691 | async def on_message_pinned(client: Client, message: Message): 692 | """Handle pinned messages""" 693 | for target in await get_targets(message): 694 | await copy_message(message, target, pinned=True) 695 | 696 | 697 | @user.on_message(filters.create(is_forwarder) & filters.reply & 698 | ~filters.media_group) 699 | async def on_message_reply(client: Client, message: Message): 700 | """Handle the reply to a message""" 701 | for target in await get_targets(message): 702 | if not target["reply"]: # If the reply is disabled, continue 703 | continue 704 | if target["forwarding_mode"] == "copy": 705 | await copy_message(message, target, reply=True) 706 | else: 707 | await forward_message(message, target) 708 | 709 | 710 | @user.on_message(filters.create(is_forwarder) & ~filters.media_group) 711 | async def on_new_message(client: Client, message: Message): 712 | """Handle new messages""" 713 | for target in await get_targets(message): 714 | if target["forwarding_mode"] == "copy": 715 | await copy_message(message, target) 716 | else: 717 | await forward_message(message, target) 718 | 719 | 720 | @user.on_message(filters.create(is_forwarder) & filters.media_group & 721 | filters.reply) 722 | async def on_media_group_reply(client: Client, message: Message): 723 | """Handle reply media groups messages""" 724 | global current_media_group 725 | if current_media_group == message.media_group_id: 726 | return 727 | 728 | current_media_group = message.media_group_id 729 | for target in await get_targets(message): 730 | if not target["reply"]: # If the reply is disabled, continue 731 | continue 732 | if target["forwarding_mode"] == "copy": 733 | await copy_message(message, target, media_group=True, reply=True) 734 | else: 735 | await forward_message(message, target) 736 | 737 | 738 | @user.on_edited_message(filters.create(is_forwarder) & filters.media_group) 739 | async def on_media_group_edited(client: Client, message: Message): 740 | """Handle edited media group messages""" 741 | for target in await get_targets(message): 742 | if target["forwarding_mode"] == "copy": 743 | await copy_message(message, target, edited=True) 744 | else: 745 | await forward_message(message, target, media_group=True, 746 | edited=True) 747 | 748 | 749 | @user.on_message(filters.create(is_forwarder) & filters.media_group) 750 | async def on_media_group(client: Client, message: Message): 751 | """Handle media group messages""" 752 | global current_media_group 753 | if current_media_group == message.media_group_id: 754 | return 755 | 756 | current_media_group = message.media_group_id 757 | for target in await get_targets(message): 758 | if target["forwarding_mode"] == "copy": 759 | await copy_message(message, target, media_group=True) 760 | else: 761 | await forward_message(message, target, media_group=True) 762 | 763 | 764 | @user.on_deleted_messages() 765 | async def on_deleted_message(client: Client, messages: list[Message]): 766 | msg_ids = await Messages.get_message_ids() 767 | deleted_msg = [] 768 | deleted_ids = [] 769 | # If the message comes from a private chat 770 | if messages[0].chat is None: 771 | for message in messages: 772 | msg_id = str(message.id) 773 | for target in msg_ids: 774 | for source in msg_ids[target]: 775 | if msg_id in msg_ids[target][source]: 776 | del_id = msg_ids[target][source][msg_id] 777 | msg_deleted = await client.get_messages(target, del_id) 778 | deleted_msg.append(msg_deleted) 779 | deleted_ids.append(del_id) 780 | # If the message comes from a group or channel 781 | else: 782 | for message in messages: 783 | msg_id = str(message.id) 784 | chat = str(message.chat.id) 785 | for target in msg_ids: 786 | if chat in msg_ids[target] and msg_id in msg_ids[target][chat]: 787 | del_id = msg_ids[target][chat][msg_id] 788 | msg_deleted = await client.get_messages(target, del_id) 789 | deleted_msg.append(msg_deleted) 790 | deleted_ids.append(del_id) 791 | 792 | # If there are messages to delete 793 | if len(deleted_msg) > 0 and msg_deleted.chat is not None: 794 | target = msg_deleted.chat.id 795 | from_user = msg_deleted.chat.title if msg_deleted.chat.title else\ 796 | msg_deleted.chat.first_name 797 | logger.info(f"Removing messages from {from_user}") 798 | await user.delete_messages(target, deleted_ids) 799 | await on_deleted_message(user, deleted_msg) 800 | -------------------------------------------------------------------------------- /app/logger.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import sys 3 | 4 | 5 | # Idea taken from https://stackoverflow.com/a/56944256 6 | class ColorFormatter(logging.Formatter): 7 | """ Logging Formatter to add colors """ 8 | 9 | # Custom colors (Only works on Linux and macOS) 10 | if sys.platform.startswith("linux") or sys.platform.startswith("darwin"): 11 | red = "\x1b[31;21m" 12 | green = "\x1b[32;21m" 13 | blue = "\x1b[34;21m" 14 | yellow = "\x1b[33;21m" 15 | grey = "\x1b[38;21m" 16 | bold_red = "\x1b[31;1m" 17 | reset = "\x1b[0m" 18 | else: 19 | red = "" 20 | green = "" 21 | blue = "" 22 | yellow = "" 23 | grey = "" 24 | bold_red = "" 25 | reset = "" 26 | 27 | format = "%(asctime)s - %(module)s:%(lineno)d - %(levelname)s: %(message)s" 28 | date_format = "%d-%m-%Y %H:%M:%S" 29 | 30 | FORMATS = { 31 | logging.DEBUG: blue + format + reset, 32 | logging.INFO: green + format + reset, 33 | logging.WARNING: yellow + format + reset, 34 | logging.ERROR: red + format + reset, 35 | logging.CRITICAL: bold_red + format + reset 36 | } 37 | 38 | def format(self, record): 39 | log_fmt = self.FORMATS.get(record.levelno) 40 | formatter = logging.Formatter(log_fmt, datefmt=self.date_format) 41 | return formatter.format(record) 42 | 43 | 44 | # TODO: Use the config file log level 45 | 46 | # Create console handler 47 | console_handler = logging.StreamHandler() 48 | console_handler.setLevel(logging.DEBUG) 49 | console_handler.setFormatter(ColorFormatter()) 50 | 51 | # Create logger 52 | logger = logging.getLogger("main") 53 | logger.setLevel(logging.DEBUG) 54 | logger.addHandler(console_handler) 55 | -------------------------------------------------------------------------------- /app/main.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | import re 3 | import datetime 4 | from os import getenv 5 | from pathlib import Path 6 | 7 | from pyrogram import Client, filters, idle 8 | from pyrogram.enums import ChatType, MessageMediaType 9 | from pyrogram.types import (Message, CallbackQuery, InlineKeyboardButton, 10 | InlineKeyboardMarkup) 11 | from pyrogram.errors.exceptions.bad_request_400 import (ChannelInvalid, 12 | PeerIdInvalid, 13 | UsernameNotOccupied, 14 | UsernameInvalid) 15 | from pyrogram.errors.exceptions.not_acceptable_406 import ChannelPrivate 16 | 17 | from forward import user 18 | from config import Forwarding, Bot 19 | from logger import logger 20 | 21 | # Config path 22 | app_dir = Path(__file__).parent 23 | config_dir = app_dir / "config" 24 | 25 | # Translation languages 26 | LANGS = { 27 | "Auto": "auto", 28 | "Spanish": "es", 29 | "English": "en", 30 | "Italian": "it", 31 | "French": "fr", 32 | "Portuguese": "pt", 33 | "German": "de" 34 | } 35 | # Reversed languages 36 | REV_LANGS = {v: k for k, v in LANGS.items()} 37 | 38 | # Load the bot configuration 39 | bot_config = Bot().get_config() 40 | forwardings = Forwarding() 41 | 42 | # Set up the Telegram client 43 | API_ID = getenv("API_ID") if getenv("API_ID") else bot_config["api_id"] 44 | API_HASH = getenv("API_HASH") if getenv("API_HASH") else bot_config["api_hash"] 45 | 46 | bot = Client(str(Path(config_dir/"bot")), API_ID, API_HASH) 47 | commands = ["start", "menu", "blockimage", 48 | "blockall", "rmblockall", "replaceall", "rmreplaceall"] 49 | answer_users = {} 50 | 51 | 52 | # Lambda function to create a hash from a string 53 | async def md5(word): return hashlib.md5(word.encode("utf-8")).hexdigest() 54 | 55 | 56 | async def is_admin(filter, client: Client, event: Message | CallbackQuery): 57 | """ Check if the user is an admin""" 58 | id = event.chat.id if type(event) is Message else event.message.chat.id 59 | admins = Bot().get_config()["admins"] 60 | return id in admins 61 | 62 | 63 | @bot.on_message(filters.create(is_admin) & filters.command(commands)) 64 | async def on_command(client: Client, message: Message): 65 | current_command = message.command[0] 66 | 67 | if "start" == current_command or "menu" == current_command: 68 | await menu(message, False) 69 | if "blockimage" == current_command: 70 | if message.media is MessageMediaType.PHOTO: 71 | await block_image(message) 72 | else: 73 | await message.reply("This command can only be used in photos") 74 | 75 | if "blockall" == message.command[0]: 76 | await block_all(message) 77 | if "rmblockall" == message.command[0]: 78 | await rm_block_all(message) 79 | if "replaceall" == message.command[0]: 80 | await replace_all(message) 81 | if "rmreplaceall" == message.command[0]: 82 | await rm_replace_all(message) 83 | 84 | 85 | @bot.on_message(filters.create(is_admin)) 86 | async def on_message(client: Client, message: Message): 87 | global answer_users 88 | user_id = message.chat.id 89 | 90 | if str(user_id) not in answer_users: 91 | answer_users[str(user_id)] = [False, None, None] 92 | if answer_users[str(user_id)][0]: 93 | answer_type = answer_users[str(user_id)][1] 94 | forwarder_id = answer_users[str(user_id)][2] 95 | 96 | if answer_type == "change_name": 97 | await change_name(message, forwarder_id, True) 98 | elif answer_type == "add_replace_word": 99 | await add_replace_word(message, forwarder_id, True) 100 | elif answer_type == "add_blocked_word": 101 | await add_blocked_word(message, forwarder_id, True) 102 | elif answer_type == "source_add": 103 | await source_add(message, forwarder_id, True) 104 | elif answer_type == "new_forwarder_target": 105 | await new_forwarder_get_target(message, True) 106 | elif answer_type == "new_forwarder_source": 107 | chat_id = list(forwarder_id.keys())[0] 108 | chat_name = forwarder_id[chat_id] 109 | await new_forwarder_get_source(message, chat_id, chat_name, True) 110 | 111 | 112 | @bot.on_callback_query(filters.create(is_admin)) 113 | async def on_callback_query(client: Client, callback_query: CallbackQuery): 114 | data = callback_query.data 115 | message = callback_query.message 116 | user_id = message.chat.id 117 | answer_users[str(user_id)] = [False, None, None] 118 | 119 | if data == "menu": 120 | await menu(message) 121 | if data == "forwarders": 122 | await forwarders(message) 123 | if data.startswith("forwarder_"): 124 | id_hash = data.split("_")[-1] 125 | await forwarder(message, id_hash) 126 | if data.startswith("name_"): 127 | id_hash = data.split("_")[-1] 128 | await change_name(message, id_hash) 129 | if data.startswith("enabled_"): 130 | id_hash = data.split("_")[-1] 131 | await enable_forwarder(message, id_hash) 132 | if data.startswith("reply_"): 133 | id_hash = data.split("_")[-1] 134 | await toggle_reply(message, id_hash) 135 | if data.startswith("duplicated_text_"): 136 | id_hash = data.split("_")[-1] 137 | await toggle_duplicated_text(message, id_hash) 138 | if data.startswith("forwarding_mode_"): 139 | id_hash = data.split("_")[-1] 140 | await change_forwarding_mode(message, id_hash) 141 | if data.startswith("replace_words_"): 142 | id_hash = data.split("_")[-1] 143 | await change_replace_words(message, id_hash) 144 | if data.startswith("replace_delete_"): 145 | id_hash = data.split("_")[-1] 146 | word = data.split("_")[-2] 147 | await delete_replace_word(message, id_hash, word) 148 | if data.startswith("replace_add_"): 149 | id_hash = data.split("_")[-1] 150 | await add_replace_word(message, id_hash) 151 | if data.startswith("blocked_words_"): 152 | id_hash = data.split("_")[-1] 153 | await change_blocked_words(message, id_hash) 154 | if data.startswith("blocked_delete_"): 155 | id_hash = data.split("_")[-1] 156 | word = data.split("_")[-2] 157 | await delete_blocked_word(message, id_hash, word) 158 | if data.startswith("blocked_add_"): 159 | id_hash = data.split("_")[-1] 160 | await add_blocked_word(message, id_hash) 161 | if data.startswith("source_chats_"): 162 | id_hash = data.split("_")[-1] 163 | await source_chats(message, id_hash) 164 | if data.startswith("source_chat_"): 165 | id_hash = data.split("_")[-1] 166 | chat_id = data.split("_")[-2] 167 | await source_chat(message, id_hash, chat_id) 168 | if data.startswith("source_delete_"): 169 | id_hash = data.split("_")[-1] 170 | chat_id = data.split("_")[-2] 171 | await source_delete(message, id_hash, chat_id) 172 | if data.startswith("source_add_"): 173 | id_hash = data.split("_")[-1] 174 | await source_add(message, id_hash) 175 | if data.startswith("info_"): 176 | id_hash = data.split("_")[-1] 177 | await forwarder_info(message, id_hash) 178 | 179 | if data == "new": 180 | await new_forwarder_get_target(message) 181 | if data.startswith("delete_forwarder_"): 182 | id_hash = data.split("_")[-1] 183 | await delete_forwarder(message, id_hash, 1) 184 | if data.startswith("confirm_delete_forwarder_"): 185 | id_hash = data.split("_")[-1] 186 | await delete_forwarder(message, id_hash, 2) 187 | 188 | if data.startswith("send_text_only_"): 189 | id_hash = data.split("_")[-1] 190 | await toggle_send_text_only(message, id_hash) 191 | 192 | if data.startswith("translation_"): 193 | id_hash = data.split("_")[-1] 194 | await translation(message, id_hash) 195 | if data.startswith("toggle_translation_"): 196 | id_hash = data.split("_")[-1] 197 | await toggle_translation(message, id_hash) 198 | if data.startswith("toggle_show_original_"): 199 | id_hash = data.split("_")[-1] 200 | await toggle_show_original(message, id_hash) 201 | if data.startswith("translate_to_select_"): 202 | id_hash = data.split("_")[-1] 203 | await translate_to_select(message, id_hash) 204 | if data.startswith("translate_to_set_"): 205 | id_hash = data.split("_")[-1] 206 | lang_code = data.split("_")[-2] 207 | await translate_to_set(message, id_hash, lang_code) 208 | if data.startswith("translate_from_select_"): 209 | id_hash = data.split("_")[-1] 210 | await translate_from_select(message, id_hash) 211 | if data.startswith("translate_from_set_"): 212 | id_hash = data.split("_")[-1] 213 | lang_code = data.split("_")[-2] 214 | await translate_from_set(message, id_hash, lang_code) 215 | 216 | await callback_query.answer() 217 | 218 | 219 | async def menu(message: Message, edit: bool = True) -> None: 220 | """ Create the menu. """ 221 | # Create the keyboard 222 | keyboard = [ 223 | [{"🔥 See forwarding": "forwarders"}], 224 | [{"➕ Create forwarding": "new"}], 225 | ] 226 | keyboard = await create_keyboard(keyboard) 227 | # Create the text 228 | text = "Welcome, what do you want to do?" 229 | 230 | # Send the message 231 | if edit: 232 | await message.edit(text, reply_markup=keyboard) 233 | else: 234 | await message.reply(text, reply_markup=keyboard) 235 | 236 | 237 | async def forwarders(message: Message) -> None: 238 | """ Create the forwarders menu. """ 239 | forwarders = (await forwardings.get_config())["forwarders"] 240 | forwarders = sorted(forwarders, key=lambda x: x["name"]) 241 | 242 | # Create the keyboard 243 | keyboard = [] 244 | for forwarder in forwarders: 245 | name = ("🟢 " if forwarder["enabled"] else "🔴 ") + forwarder["name"] 246 | forwarder_id = f"forwarder_{forwarder['target']}" 247 | keyboard.append([{name: forwarder_id}]) 248 | keyboard.append([{"◀️ Back": "menu"}]) 249 | keyboard = await create_keyboard(keyboard) 250 | # Create the text 251 | text = "Select a forwarding" 252 | 253 | # Send the message 254 | await message.edit(text, reply_markup=keyboard) 255 | 256 | 257 | async def forwarder(message: Message, forwarder_id: str) -> None: 258 | """ Forward the message to the forwarder. """ 259 | # Get the forwarder 260 | forwarder = await forwardings.get_forwarder(forwarder_id) 261 | 262 | name = f"✏️ Name: {forwarder['name']}" 263 | enabled = "🟢 Enabled" if forwarder["enabled"] else "🔴 Disabled" 264 | reply = "🔁 Reply: on" if forwarder["reply"] else "🔁 Reply: off" 265 | duplicated_text = ("🔄 Duplicated text: on" if forwarder["duplicated_text"] 266 | else "🔄 Duplicated text: off") 267 | forwarding_mode = "↪️ Forwarding mode: " 268 | forwarding_mode += ("copy" if forwarder["forwarding_mode"] == "copy" else 269 | "forward") 270 | replace_words = "🔖 Words to replace" 271 | blocked_words = "🚫 Blocked words" 272 | source_chats = "👁 Source chats" 273 | 274 | send_text_only = ("🔄 Send text only: on" if forwarder["send_text_only"] 275 | else "🔄 Send text only: off") 276 | translation = "🗣️ Translation" 277 | 278 | # Create the keyboard 279 | keyboard = [ 280 | [{name: f"name_{forwarder_id}"}], 281 | [{enabled: f"enabled_{forwarder_id}"}], 282 | [{reply: f"reply_{forwarder_id}"}], 283 | [{duplicated_text: f"duplicated_text_{forwarder_id}"}], 284 | [{forwarding_mode: f"forwarding_mode_{forwarder_id}"}], 285 | [{replace_words: f"replace_words_{forwarder_id}"}], 286 | [{blocked_words: f"blocked_words_{forwarder_id}"}], 287 | [{source_chats: f"source_chats_{forwarder_id}"}], 288 | [{send_text_only: f"send_text_only_{forwarder_id}"}], 289 | [{translation: f"translation_{forwarder_id}"}], 290 | [{"ℹ️ Information": f"info_{forwarder_id}"}], 291 | [{"🗑️ Delete": f"delete_forwarder_{forwarder_id}"}], 292 | [{"◀️ Back": "forwarders"}] 293 | ] 294 | if forwarder["forwarding_mode"] != "copy": 295 | keyboard.pop(5) 296 | 297 | text = "Forwarding settings" 298 | 299 | # Send the message 300 | keyboard = await create_keyboard(keyboard) 301 | await message.edit(text, reply_markup=keyboard) 302 | 303 | 304 | async def change_name(message: Message, forwarder_id: str, change=False): 305 | """ Change the name of the forwarder. """ 306 | user_id = message.chat.id 307 | 308 | # Get the forwarder 309 | forwarder_dict = await forwardings.get_forwarder(forwarder_id) 310 | 311 | if not change: 312 | # Create the keyboard 313 | keyboard = [ 314 | [{"◀️ Back": f"forwarder_{forwarder_id}"}] 315 | ] 316 | keyboard = await create_keyboard(keyboard) 317 | # Create the text 318 | text = "Enter the new name" 319 | 320 | # Send the message 321 | await message.edit(text, reply_markup=keyboard) 322 | 323 | answer_users[str(user_id)] = [True, "change_name", forwarder_id, 324 | message] 325 | else: 326 | message_edit = answer_users[str(user_id)][3] 327 | answer = message.text 328 | forwarder_dict["name"] = answer 329 | await forwardings.update_forwarder(forwarder_dict) 330 | await forwarder(message_edit, forwarder_id) 331 | answer_users[str(user_id)] = [False, None, None, None] 332 | 333 | 334 | async def enable_forwarder(message: Message, forwarder_id: str): 335 | """ Enable the forwarder. """ 336 | # Get the forwarder 337 | forwarder_dict = await forwardings.get_forwarder(forwarder_id) 338 | 339 | forwarder_dict["enabled"] = not forwarder_dict["enabled"] 340 | await forwardings.update_forwarder(forwarder_dict) 341 | await forwarder(message, forwarder_id) 342 | 343 | 344 | async def toggle_reply(message: Message, forwarder_id: str): 345 | """ Toggle the reply of the forwarder. """ 346 | # Get the forwarder 347 | forwarder_dict = await forwardings.get_forwarder(forwarder_id) 348 | 349 | forwarder_dict["reply"] = not forwarder_dict["reply"] 350 | await forwardings.update_forwarder(forwarder_dict) 351 | await forwarder(message, forwarder_id) 352 | 353 | 354 | async def toggle_duplicated_text(message: Message, forwarder_id: str): 355 | """ Toggle the duplicated text of the forwarder. """ 356 | # Get the forwarder 357 | forwarder_dict = await forwardings.get_forwarder(forwarder_id) 358 | 359 | forwarder_dict["duplicated_text"] = not forwarder_dict["duplicated_text"] 360 | await forwardings.update_forwarder(forwarder_dict) 361 | await forwarder(message, forwarder_id) 362 | 363 | 364 | async def change_forwarding_mode(message: Message, forwarder_id: str): 365 | """ Change the forwarding mode of the forwarder. """ 366 | # Get the forwarder 367 | forwarder_dict = await forwardings.get_forwarder(forwarder_id) 368 | 369 | if forwarder_dict["forwarding_mode"] == "copy": 370 | forwarder_dict["forwarding_mode"] = "forward" 371 | else: 372 | forwarder_dict["forwarding_mode"] = "copy" 373 | await forwardings.update_forwarder(forwarder_dict) 374 | await forwarder(message, forwarder_id) 375 | 376 | 377 | async def change_replace_words(message: Message, forwarder_id: str): 378 | """ Change the replace words of the forwarder. """ 379 | # Get the forwarder 380 | forwarder_dict = await forwardings.get_forwarder(forwarder_id) 381 | 382 | # Create the keyboard 383 | keyboard = [] 384 | for word, value in forwarder_dict["replace_words"].items(): 385 | word_md5 = await md5(word) 386 | keyboard.append([ 387 | {f"{word} -> {value}": 388 | f"replace_delete_{word_md5}_{forwarder_id}"}]) 389 | keyboard.append([{"➕ Add": f"replace_add_{forwarder_id}"}]) 390 | keyboard.append([{"◀️ Back": f"forwarder_{forwarder_id}"}]) 391 | keyboard = await create_keyboard(keyboard) 392 | 393 | # Create the text 394 | text = "Add a word to replace.\n" 395 | text += "To delete a word, click on its name." 396 | 397 | # Send the message 398 | await message.edit(text, reply_markup=keyboard) 399 | 400 | 401 | async def delete_replace_word(message: Message, forwarder_id: str, 402 | word_md5: str): 403 | """ Delete a replace word from the forwarder. """ 404 | forwarder = await forwardings.get_forwarder(forwarder_id) 405 | 406 | # Create the keyboard 407 | keyboard = [] 408 | for source_id, source_name in forwarder["source"].items(): 409 | name = f"{source_name} ({source_id})" 410 | keyboard.append([{name: f"source_chat_{source_id}"}]) 411 | keyboard.append([{"◀️ Back": f"forwarder_{forwarder_id}"}]) 412 | keyboard = await create_keyboard(keyboard) 413 | # Create the text 414 | text = "Select a source chat." 415 | 416 | # Send the message 417 | await message.edit(text, reply_markup=keyboard) 418 | # Get the forwarder 419 | forwarder_dict = await forwardings.get_forwarder(forwarder_id) 420 | 421 | # Find the word by its md5 422 | for word in forwarder_dict["replace_words"].keys(): 423 | if await md5(word) == word_md5: 424 | break 425 | 426 | # Delete the word 427 | del forwarder_dict["replace_words"][word] 428 | await forwardings.update_forwarder(forwarder_dict) 429 | await change_replace_words(message, forwarder_id) 430 | 431 | 432 | async def add_replace_word(message: Message, forwarder_id: str, change=False): 433 | """ Add a replace word to the forwarder. """ 434 | user_id = message.chat.id 435 | # Create the keyboard 436 | keyboard = [ 437 | [{"◀️ Back": f"replace_words_{forwarder_id}"}] 438 | ] 439 | keyboard = await create_keyboard(keyboard) 440 | 441 | if not change: 442 | # Create the text 443 | text = "Enter the word to be replaced in this format:\n" 444 | text += "`word>replaced_word`\n\n" 445 | text += "You can add several replacements at the same time, just\n" 446 | text += "split them with a line break.\n\n" 447 | text += "Example:\n" 448 | text += "`word1>replaced_word1\n" 449 | text += "word2>replaced_word2\n" 450 | text += "word3>replaced_word3`" 451 | 452 | # Send the message 453 | await message.edit(text, reply_markup=keyboard) 454 | 455 | answer_users[str(user_id)] = [True, "add_replace_word", forwarder_id, 456 | message] 457 | else: 458 | message_edit = answer_users[str(user_id)][3] 459 | answer = message.text 460 | 461 | # Check if has the right format 462 | if ">" not in answer: 463 | text = "**Error:** The format is not correct.\n\n" 464 | text += f"**Text entered:** ```{answer}```" 465 | await message_edit.edit(text, reply_markup=keyboard) 466 | return 467 | 468 | # Get the words and the values to replace 469 | replaces = answer.split("\n") 470 | replaces = [replace.split(">") for replace in replaces] 471 | 472 | # Get the forwarder 473 | forwarder_dict = await forwardings.get_forwarder(forwarder_id) 474 | 475 | # Add the words and the values to replace 476 | for replace in replaces: 477 | word, value = replace 478 | forwarder_dict["replace_words"][word] = value 479 | 480 | # Update the forwarder 481 | await forwardings.update_forwarder(forwarder_dict) 482 | await change_replace_words(message_edit, forwarder_id) 483 | answer_users[str(user_id)] = [False, None, None, None] 484 | 485 | 486 | async def change_blocked_words(message: Message, forwarder_id: str): 487 | """ Change the blocked words of the forwarder. """ 488 | # Get the forwarder 489 | forwarder_dict = await forwardings.get_forwarder(forwarder_id) 490 | 491 | # Create the keyboard 492 | keyboard = [] 493 | for word in forwarder_dict["blocked_words"]: 494 | word_md5 = await md5(word) 495 | keyboard.append([ 496 | {f"{word}": f"blocked_delete_{word_md5}_{forwarder_id}"}]) 497 | keyboard.append([{"➕ Add": f"blocked_add_{forwarder_id}"}]) 498 | keyboard.append([{"◀️ Back": f"forwarder_{forwarder_id}"}]) 499 | keyboard = await create_keyboard(keyboard) 500 | 501 | # Create the text 502 | text = "Add a word to replace.\n" 503 | text += "To delete a word, click on its name." 504 | 505 | # Send the message 506 | await message.edit(text, reply_markup=keyboard) 507 | 508 | 509 | async def delete_blocked_word(message: Message, forwarder_id: str, 510 | word_md5: str): 511 | """ Delete a blocked word from the forwarder. """ 512 | # Get the forwarder 513 | forwarder_dict = await forwardings.get_forwarder(forwarder_id) 514 | 515 | # Find the word by its md5 516 | for word in forwarder_dict["blocked_words"]: 517 | if await md5(word) == word_md5: 518 | break 519 | 520 | # Delete the word 521 | forwarder_dict["blocked_words"].remove(word) 522 | await forwardings.update_forwarder(forwarder_dict) 523 | await change_blocked_words(message, forwarder_id) 524 | 525 | 526 | async def add_blocked_word(message: Message, forwarder_id: str, change=False): 527 | """ Add a blocked word to the forwarder. """ 528 | user_id = message.chat.id 529 | # Create the keyboard 530 | keyboard = [ 531 | [{"◀️ Back": f"blocked_words_{forwarder_id}"}] 532 | ] 533 | keyboard = await create_keyboard(keyboard) 534 | 535 | if not change: 536 | # Create the text 537 | text = "Enter the word you want to block.\n\n" 538 | text += "You can add several words to block at the same time, \n" 539 | text += "just split them with a line break.\n\n" 540 | text += "Example:\n" 541 | text += "`word1\nword2\nword3`" 542 | 543 | # Send the message 544 | await message.edit(text, reply_markup=keyboard) 545 | 546 | answer_users[str(user_id)] = [True, "add_blocked_word", forwarder_id, 547 | message] 548 | else: 549 | message_edit = answer_users[str(user_id)][3] 550 | answer = message.text 551 | 552 | # Get the words and the values to blocked 553 | blockeds = answer.split("\n") 554 | 555 | # Get the forwarder 556 | forwarder_dict = await forwardings.get_forwarder(forwarder_id) 557 | 558 | # Add the words and the values to blocked 559 | for blocked in blockeds: 560 | forwarder_dict["blocked_words"].append(blocked) 561 | 562 | # Update the forwarder 563 | await forwardings.update_forwarder(forwarder_dict) 564 | await change_blocked_words(message_edit, forwarder_id) 565 | answer_users[str(user_id)] = [False, None, None, None] 566 | 567 | 568 | async def source_chats(message: Message, forwarder_id) -> None: 569 | """ Create the forwarders menu. """ 570 | forwarder = await forwardings.get_forwarder(forwarder_id) 571 | 572 | # Create the keyboard 573 | keyboard = [] 574 | for source_id, source_name in forwarder["source"].items(): 575 | name = f"{source_name}" 576 | keyboard.append([{name: f"source_chat_{source_id}_{forwarder_id}"}]) 577 | keyboard.append([{"➕ Add": f"source_add_{forwarder_id}"}]) 578 | keyboard.append([{"◀️ Back": f"forwarder_{forwarder_id}"}]) 579 | keyboard = await create_keyboard(keyboard) 580 | # Create the text 581 | text = "Select a source chat." 582 | 583 | # Send the message 584 | await message.edit(text, reply_markup=keyboard) 585 | 586 | 587 | async def source_chat(message: Message, forwarder_id: str, source_id: str): 588 | """ Forward the message to the forwarder. """ 589 | # Create the keyboard 590 | keyboard = [ 591 | [{"🗑️ Delete": f"source_delete_{source_id}_{forwarder_id}"}], 592 | [{"◀️ Back": f"source_chats_{forwarder_id}"}] 593 | ] 594 | 595 | text = "Source chat settings." 596 | text += await get_chat_info(source_id) 597 | 598 | # Update the forwarders name 599 | if re.search(r"\*\*Name:\*\* (.+)", text): 600 | name = re.search(r"\*\*Name:\*\* (.+)", text).group(1) 601 | forwarder_dict = await forwardings.get_forwarder(forwarder_id) 602 | forwarder_dict["source"][source_id] = name 603 | await forwardings.update_forwarder(forwarder_dict) 604 | 605 | # Send the message 606 | keyboard = await create_keyboard(keyboard) 607 | await message.edit(text, reply_markup=keyboard) 608 | 609 | 610 | async def source_delete(message: Message, forwarder_id: str, source_id: str): 611 | """ Delete the source chat. """ 612 | # Get the forwarder 613 | forwarder_dict = await forwardings.get_forwarder(forwarder_id) 614 | 615 | # Delete the source chat 616 | del forwarder_dict["source"][source_id] 617 | await forwardings.update_forwarder(forwarder_dict) 618 | await source_chats(message, forwarder_id) 619 | 620 | 621 | async def source_add(message: Message, forwarder_id: str, change=False): 622 | """ Add the source chat. """ 623 | user_id = message.chat.id 624 | # Create the keyboard 625 | keyboard = [ 626 | [{"◀️ Back": f"source_chats_{forwarder_id}"}] 627 | ] 628 | keyboard = await create_keyboard(keyboard) 629 | 630 | if not change: 631 | # Create the text 632 | text = "Enter the source chat.\n\n" 633 | text += "You can add chats in several ways:\n\n" 634 | text += "**-Entering the chat ID**\n" 635 | text += "`628910404\n628910400`\n\n" 636 | text += "**-Entering the chat username**\n" 637 | text += "`@Nunnito\n@Python`\n\n" 638 | text += "**-Entering a link from a chat message**\n" 639 | text += "`https://t.me/c/1165316653/1815937\nhttps://t.me/python/1234`" 640 | text += "\n\n**-Forwarding a chat message**\n" 641 | 642 | # Send the message 643 | await message.edit(text, reply_markup=keyboard) 644 | 645 | answer_users[str(user_id)] = [True, "source_add", forwarder_id, 646 | message] 647 | else: 648 | # Message to be edited 649 | message_edit = answer_users[str(user_id)][3] 650 | # Get the forwarder 651 | forwarder_dict = await forwardings.get_forwarder(forwarder_id) 652 | 653 | # Get all the chats ids 654 | chats_ids, invalid_ids = await get_chats_id(message) 655 | 656 | # Add the chats and the values to source chat 657 | for chat_id, name in chats_ids.items(): 658 | forwarder_dict["source"][chat_id] = name 659 | 660 | # Create the text 661 | text = "" 662 | if chats_ids: 663 | text = "The following source chats have been added:\n" 664 | for chat_id, name in chats_ids.items(): 665 | text += f"**{name}**\n" 666 | if invalid_ids: 667 | text += "\n\nThe following chats have not been added:\n" 668 | for chat_id in invalid_ids: 669 | text += f"**{chat_id}**\n" 670 | 671 | # Update the forwarder 672 | await forwardings.update_forwarder(forwarder_dict) 673 | # Send the message 674 | await message_edit.edit(text, reply_markup=keyboard) 675 | answer_users[str(user_id)] = [False, None, None, None] 676 | 677 | 678 | async def forwarder_info(message: Message, forwarder_id: str): 679 | """ Create the forwarders menu. """ 680 | # Create the keyboard 681 | keyboard = [ 682 | [{"◀️ Back": f"forwarder_{forwarder_id}"}] 683 | ] 684 | keyboard = await create_keyboard(keyboard) 685 | 686 | # Create the text 687 | text = await get_chat_info(forwarder_id) 688 | 689 | # Send the message 690 | await message.edit(text, reply_markup=keyboard) 691 | 692 | 693 | async def new_forwarder_get_target(message: Message, change=False): 694 | """ Create a new forwarder. """ 695 | user_id = message.chat.id 696 | # Create the keyboard 697 | keyboard = [ 698 | [{"◀️ Back": "menu"}] 699 | ] 700 | keyboard = await create_keyboard(keyboard) 701 | 702 | if not change: 703 | # Create the text 704 | text = "Enter the target chat.\n\n" 705 | text += "You can add chats in several ways:\n\n" 706 | text += "**-Entering the chat ID**\n" 707 | text += "`628910404`\n\n" 708 | text += "**-Entering the chat username**\n" 709 | text += "`@Nunnito`\n\n" 710 | text += "**-Entering a link from a chat message**\n" 711 | text += "`https://t.me/c/1165316653/1815937`" 712 | text += "\n\n**-Forwarding a chat message**\n" 713 | 714 | # Send the message 715 | await message.edit(text, reply_markup=keyboard) 716 | 717 | answer_users[str(user_id)] = [True, "new_forwarder_target", None, 718 | message] 719 | else: 720 | # Message to be edited 721 | message_edit = answer_users[str(user_id)][3] 722 | 723 | # Get all the chats ids 724 | chats_ids, invalid_ids = await get_chats_id(message) 725 | if not chats_ids: 726 | text = "The target chat is invalid." 727 | await message_edit.edit(text, reply_markup=keyboard) 728 | answer_users[str(user_id)] = [False, None, None, None] 729 | return 730 | elif await forwardings.get_forwarder(list(chats_ids.keys())[0]): 731 | text = "The target chat is already in use." 732 | await message_edit.edit(text, reply_markup=keyboard) 733 | answer_users[str(user_id)] = [False, None, None, None] 734 | return 735 | else: 736 | # Get the chat id 737 | chat_id = list(chats_ids.keys())[0] 738 | # Get the chat name 739 | chat_name = chats_ids[chat_id] 740 | 741 | await new_forwarder_get_source(message_edit, chat_id, chat_name) 742 | 743 | 744 | async def new_forwarder_get_source(message: Message, forwarder_id: str, 745 | forwarder_name: str, change=False): 746 | """ Create a new forwarder. """ 747 | user_id = message.chat.id 748 | # Create the keyboard 749 | keyboard = [ 750 | [{"◀️ Back": "forwarders"}] 751 | ] 752 | keyboard = await create_keyboard(keyboard) 753 | 754 | if not change: 755 | # Create the text 756 | text = "Excellent, now enter the source chat.\n\n" 757 | text += "It is added in a very similar way to the target chat, " 758 | text += "the only difference is that more that one cam be added.\n\n" 759 | text += "You can add chats in several ways:\n\n" 760 | text += "**-Entering the chat ID**\n" 761 | text += "`628910404\n628910400`\n\n" 762 | text += "**-Entering the chat username**\n" 763 | text += "`@Nunnito\n@Python`\n\n" 764 | text += "**-Entering a link from a chat message**\n" 765 | text += "`https://t.me/c/1165316653/1815937\nhttps://t.me/python/1234`" 766 | text += "\n\n**-Forwarding a chat message**\n" 767 | 768 | # Send the message 769 | await message.edit(text, reply_markup=keyboard) 770 | 771 | answer_users[str(user_id)] = [True, "new_forwarder_source", 772 | {forwarder_id: forwarder_name}, message] 773 | else: 774 | # Message to be edited 775 | message_edit = answer_users[str(user_id)][3] 776 | 777 | # Get all the chats ids 778 | sources, invalid_ids = await get_chats_id(message) 779 | if not sources: 780 | text = "The source chat is invalid." 781 | await message_edit.edit(text, reply_markup=keyboard) 782 | answer_users[str(user_id)] = [False, None, None, None] 783 | return 784 | 785 | # Create the text 786 | text = "" 787 | if sources: 788 | text = "The following source chats have been added:\n" 789 | for chat_id, name in sources.items(): 790 | text += f"**{name}**\n" 791 | if invalid_ids: 792 | text += "\n\nThe following chats have not been added:\n" 793 | for chat_id in invalid_ids: 794 | text += f"**{chat_id}**\n" 795 | 796 | await forwardings.add_forwarder(forwarder_name, forwarder_id, sources) 797 | # Send the message 798 | await message_edit.edit(text, reply_markup=keyboard) 799 | answer_users[str(user_id)] = [False, None, None, None] 800 | 801 | 802 | async def delete_forwarder(message: Message, forwarder_id: str, step=1): 803 | """ Delete a forwarder. """ 804 | if step == 1: 805 | # Create the keyboard 806 | keyboard = [ 807 | [{"Yes, sure": f"confirm_delete_forwarder_{forwarder_id}"}], 808 | [{"No, cancel": f"forwarder_{forwarder_id}"}] 809 | ] 810 | keyboard = await create_keyboard(keyboard) 811 | 812 | # Create the text 813 | text = "Are you sure you want to delete it?" 814 | text += await get_chat_info(forwarder_id) 815 | 816 | # Send the message 817 | await message.edit(text, reply_markup=keyboard) 818 | elif step == 2: 819 | # Delete the forwarder 820 | await forwardings.remove_forwarder(forwarder_id) 821 | 822 | # Return to the forwarders menu 823 | await forwarders(message) 824 | 825 | 826 | async def toggle_send_text_only(message: Message, forwarder_id: str): 827 | """ Toggle send text only """ 828 | # Get the forwarder 829 | forwarder_dict = await forwardings.get_forwarder(forwarder_id) 830 | 831 | forwarder_dict["send_text_only"] = not forwarder_dict["send_text_only"] 832 | await forwardings.update_forwarder(forwarder_dict) 833 | await forwarder(message, forwarder_id) 834 | 835 | 836 | async def translation(message: Message, forwarder_id: str): 837 | """ Translation system """ 838 | # Get to and from languages 839 | forwarder_dict = await forwardings.get_forwarder(forwarder_id) 840 | enable = forwarder_dict["translate"] 841 | to_lang = REV_LANGS[forwarder_dict["translate_to"]] 842 | from_lang = REV_LANGS[forwarder_dict["translate_from"]] 843 | show_original = forwarder_dict["translate_show_original"] 844 | 845 | # Create the keyboard 846 | keyboard = [ 847 | [{"🟢 Enabled" if enable else "🔴 Disabled": 848 | f"toggle_translation_{forwarder_id}"}], 849 | [{f"🔄 Show original: {'Yes' if show_original else 'No'}": 850 | f"toggle_show_original_{forwarder_id}"}], 851 | [{f"↪️ To: {to_lang}": f"translate_to_select_{forwarder_id}"}], 852 | [{f"↩️ From: {from_lang}": f"translate_from_select_{forwarder_id}"}], 853 | [{"◀️ Back": f"forwarder_{forwarder_id}"}] 854 | ] 855 | 856 | # Create the keyboard 857 | keyboard = await create_keyboard(keyboard) 858 | # Create the text 859 | text = "Configure the translation" 860 | 861 | # Send the message 862 | await message.edit(text, reply_markup=keyboard) 863 | 864 | 865 | async def toggle_translation(message: Message, forwarder_id: str): 866 | """ Toggle the translation of the forwarder. """ 867 | # Get the forwarder 868 | forwarder_dict = await forwardings.get_forwarder(forwarder_id) 869 | 870 | forwarder_dict["translate"] = not forwarder_dict["translate"] 871 | await forwardings.update_forwarder(forwarder_dict) 872 | await translation(message, forwarder_id) 873 | 874 | 875 | async def toggle_show_original(message: Message, forwarder_id: str): 876 | """ Toggle the show original of the forwarder. """ 877 | # Get the forwarder 878 | forwarder_dict = await forwardings.get_forwarder(forwarder_id) 879 | 880 | forwarder_dict["translate_show_original"] = not forwarder_dict[ 881 | "translate_show_original"] 882 | await forwardings.update_forwarder(forwarder_dict) 883 | await translation(message, forwarder_id) 884 | 885 | 886 | async def translate_to_select(message: Message, forwarder_id: str): 887 | """ Select the to language. """ 888 | # Get the forwarder 889 | forwarder_dict = await forwardings.get_forwarder(forwarder_id) 890 | 891 | # Create the keyboard 892 | keyboard = [] 893 | for lang, lang_code in LANGS.items(): 894 | if lang_code == "auto": 895 | continue 896 | elif lang_code == forwarder_dict["translate_to"]: 897 | btn_text = f"🟢 {lang}" 898 | else: 899 | btn_text = f"🔴 {lang}" 900 | btn_data = {btn_text: f"translate_to_set_{lang_code}_{forwarder_id}"} 901 | keyboard.append([btn_data]) 902 | keyboard.append([{"◀️ Back": f"translation_{forwarder_id}"}]) 903 | 904 | # Create the keyboard 905 | keyboard = await create_keyboard(keyboard) 906 | # Create the text 907 | text = "Select the language to translate to" 908 | 909 | # Send the message 910 | await message.edit(text, reply_markup=keyboard) 911 | 912 | 913 | async def translate_to_set(message: Message, forwarder_id: str, lang: str): 914 | """ Set the to language. """ 915 | # Get the forwarder 916 | forwarder_dict = await forwardings.get_forwarder(forwarder_id) 917 | forwarder_dict["translate_to"] = lang 918 | 919 | await forwardings.update_forwarder(forwarder_dict) 920 | await translate_to_select(message, forwarder_id) 921 | 922 | 923 | async def translate_from_select(message: Message, forwarder_id: str): 924 | """ Select the from language. """ 925 | # Get the forwarder 926 | forwarder_dict = await forwardings.get_forwarder(forwarder_id) 927 | 928 | # Create the keyboard 929 | keyboard = [] 930 | for lang, lang_code in LANGS.items(): 931 | if lang_code == forwarder_dict["translate_from"]: 932 | btn_text = f"🔴 {lang}" 933 | else: 934 | btn_text = f"🟢 {lang}" 935 | btn_data = {btn_text: f"translate_from_set_{lang_code}_{forwarder_id}"} 936 | keyboard.append([btn_data]) 937 | keyboard.append([{"◀️ Back": f"translation_{forwarder_id}"}]) 938 | 939 | # Create the keyboard 940 | keyboard = await create_keyboard(keyboard) 941 | # Create the text 942 | text = "Select the language to translate from" 943 | 944 | # Send the message 945 | await message.edit(text, reply_markup=keyboard) 946 | 947 | 948 | async def translate_from_set(message: Message, forwarder_id: str, lang: str): 949 | """ Set the from language. """ 950 | # Get the forwarder 951 | forwarder_dict = await forwardings.get_forwarder(forwarder_id) 952 | forwarder_dict["translate_from"] = lang 953 | 954 | await forwardings.update_forwarder(forwarder_dict) 955 | await translate_from_select(message, forwarder_id) 956 | 957 | 958 | async def create_keyboard(buttons: list) -> InlineKeyboardMarkup: 959 | """ Create a keyboard with the given buttons. 960 | 961 | Parameters 962 | ---------- 963 | buttons : list 964 | A list with the buttons to create. 965 | The keys are the button text and the values are the callback data. 966 | 967 | Returns 968 | ------- 969 | InlineKeyboardMarkup 970 | The keyboard with the given buttons. 971 | """ 972 | keyboard = [] 973 | # Create the buttons 974 | for i, button_index in enumerate(buttons): 975 | keyboard.append([]) 976 | for button in button_index: 977 | for key, value in button.items(): 978 | keyboard[i].append(InlineKeyboardButton(key, value)) 979 | 980 | return InlineKeyboardMarkup(keyboard) 981 | 982 | 983 | async def get_chat_info(chat_id: str) -> str: 984 | # Create the text 985 | try: 986 | chat_info = await user.get_chat(chat_id) 987 | if chat_info.type is ChatType.CHANNEL or\ 988 | chat_info.type is ChatType.SUPERGROUP: 989 | chat_type = ("Channel" if chat_info.type is ChatType.CHANNEL 990 | else "Group") 991 | text = f"\n\n**Name:** {chat_info.title}" 992 | text += f"\n**ID:** `{chat_info.id}`" 993 | text += f"\n**Type:** {chat_type}" 994 | if chat_info.username: 995 | text += f"\n**Username:** @{chat_info.username}" 996 | text += f"\n**Members count:** {chat_info.members_count}" 997 | text += "\n**Protected content:** " 998 | text += "Yes" if chat_info.has_protected_content else "No" 999 | elif chat_info.type is ChatType.PRIVATE: 1000 | text = f"\n\n**Name:** {chat_info.first_name}" 1001 | if chat_info.last_name: 1002 | text += f" {chat_info.last_name}" 1003 | text += f"\n**ID:** `{chat_info.id}`" 1004 | text += "\n**Type:** Private" 1005 | if chat_info.username: 1006 | text += f"\n**Username:** @{chat_info.username}" 1007 | elif chat_info.type is ChatType.BOT: 1008 | text = f"\n\n**Name:** {chat_info.first_name}" 1009 | text += f"\n**ID:** `{chat_info.id}`" 1010 | text += "\n**Type:** Bot" 1011 | text += f"\n**Username:** @{chat_info.username}" 1012 | except (ChannelInvalid, ChannelPrivate, PeerIdInvalid, 1013 | UsernameNotOccupied): 1014 | text = "\n\n**Error:** Chat information could not be obtained.\n" 1015 | text += "**Reason:** The chat does not exist or you are not in it.\n" 1016 | text += f"**ID:** `{chat_id}`" 1017 | 1018 | return text 1019 | 1020 | 1021 | async def get_chats_id(message: Message) -> list | bool: 1022 | """ Get the chat IDs from the given chats. """ 1023 | if message.forward_from_chat: 1024 | name = message.forward_from_chat.title 1025 | chats_ids = {message.forward_from_chat.id: name} 1026 | return chats_ids, None 1027 | elif message.forward_from: 1028 | name = message.forward_from.first_name 1029 | if message.forward_from.last_name: 1030 | name += f" {message.forward_from.last_name}" 1031 | chats_ids = {message.forward_from.id: name} 1032 | return chats_ids, None 1033 | else: 1034 | chats_ids = {} 1035 | invalid_ids = [] 1036 | chats = message.text.split("\n") 1037 | for chat in chats: 1038 | chat_id = None 1039 | chat_id_url = re.search(r"https://t.me/c/(\d+)/\d+", chat) 1040 | chat_username_url = re.search(r"https://t.me/(\w+)/\d+", chat) 1041 | is_username = re.search(r"^@?\w+", chat) 1042 | if chat_id_url: 1043 | chat_id = int(f"-100{chat_id_url.group(1)}") 1044 | elif chat_username_url: 1045 | chat_id = chat_username_url.group(1) 1046 | elif chat.isnumeric() or chat.startswith("-"): 1047 | chat_id = int(chat) 1048 | elif is_username: 1049 | chat_id = is_username.group(0) 1050 | else: 1051 | invalid_ids.append(chat) 1052 | 1053 | if chat_id: 1054 | try: 1055 | chat_info = await user.get_chat(chat_id) 1056 | chat_id = str(chat_info.id) 1057 | if chat_info.title: 1058 | name = chat_info.title 1059 | else: 1060 | name = chat_info.first_name 1061 | name += f" {chat_info.last_name}" if\ 1062 | chat_info.last_name else "" 1063 | chats_ids[chat_id] = name 1064 | except (ChannelInvalid, ChannelPrivate, PeerIdInvalid, 1065 | UsernameNotOccupied, UsernameInvalid): 1066 | invalid_ids.append(chat_id) 1067 | 1068 | return chats_ids, invalid_ids 1069 | 1070 | 1071 | async def block_image(message: Message) -> None: 1072 | """ Add blocked images to all the forwarders. """ 1073 | # Set name to current timestamp 1074 | name = datetime.datetime.now().timestamp() 1075 | # Download the image 1076 | image_path = await message.download(config_dir/"blocked_img"/f"{name}.jpg") 1077 | 1078 | # Add the image to blocked images 1079 | await forwardings.add_blocked_image(image_path) 1080 | 1081 | # Reply 1082 | await message.reply_text("Image blocked!") 1083 | 1084 | 1085 | async def block_all(message: Message) -> None: 1086 | """ Add blocked words to all the forwarders. """ 1087 | # Get the forwarders 1088 | forwarders_ids = await forwardings.get_forwardings() 1089 | 1090 | # If the blocked words are empty 1091 | if message.text.replace("/blockall", "").strip() == "": 1092 | await message.reply_text("Blocked words can't be empty!") 1093 | return 1094 | 1095 | # Get the blocked words 1096 | blocked_words = message.text.replace("/blockall", "").strip().split("\n") 1097 | 1098 | # Add the blocked words to all the forwarders 1099 | for forwarder_id in forwarders_ids: 1100 | forwarder_dict = await forwardings.get_forwarder(str(forwarder_id)) 1101 | 1102 | # Add the blocked words to the forwarder 1103 | for blocked_word in blocked_words: 1104 | if blocked_word not in forwarder_dict["blocked_words"]: 1105 | forwarder_dict["blocked_words"].append(blocked_word) 1106 | 1107 | await forwardings.update_forwarder(forwarder_dict) 1108 | 1109 | await message.reply_text("Word blocking added to all the forwarders.") 1110 | 1111 | 1112 | async def rm_block_all(message: Message) -> None: 1113 | """ Remove blocked words from all the forwarders. """ 1114 | # Get the forwarders 1115 | forwarders_ids = await forwardings.get_forwardings() 1116 | 1117 | # If the blocked words are empty 1118 | if message.text.replace("/rmblockall", "").strip() == "": 1119 | await message.reply_text("Blocked words can't be empty!") 1120 | return 1121 | 1122 | # Get the blocked words 1123 | blocked_words = message.text.replace("/rmblockall", "").strip().split("\n") 1124 | 1125 | # Remove the blocked words from all the forwarders 1126 | for forwarder_id in forwarders_ids: 1127 | forwarder_dict = await forwardings.get_forwarder(str(forwarder_id)) 1128 | 1129 | for blocked_word in blocked_words: 1130 | if blocked_word in forwarder_dict["blocked_words"]: 1131 | forwarder_dict["blocked_words"].remove(blocked_word) 1132 | 1133 | await forwardings.update_forwarder(forwarder_dict) 1134 | 1135 | await message.reply_text("Word blocking removed from all the forwarders.") 1136 | 1137 | 1138 | async def replace_all(message: Message) -> None: 1139 | """ Add replaced words to all the forwarders. """ 1140 | # Get the forwarders 1141 | forwarders_ids = await forwardings.get_forwardings() 1142 | 1143 | # If the replaced words are empty 1144 | if message.text.replace("/replaceall", "").strip() == "": 1145 | await message.reply_text("Replaced words can't be empty!") 1146 | return 1147 | 1148 | # Get the replaced words 1149 | replaced_words = message.text.replace("/replaceall", "").strip() 1150 | 1151 | # Check if has the right format 1152 | if ">" not in replaced_words: 1153 | text = "**Error:** The format of the replaced words is incorrect.\n\n" 1154 | text += f"**Introduced text:** `{replaced_words}`" 1155 | await message.reply_text(text) 1156 | return 1157 | 1158 | # Get the words and the values to replace 1159 | replaces = replaced_words.split("\n") 1160 | replaces = [replace.split(">") for replace in replaces] 1161 | 1162 | # Add the replaced words to all the forwarders 1163 | for forwarder_id in forwarders_ids: 1164 | forwarder_dict = await forwardings.get_forwarder(str(forwarder_id)) 1165 | 1166 | for replace in replaces: 1167 | word, value = replace 1168 | forwarder_dict["replace_words"][word] = value 1169 | 1170 | await forwardings.update_forwarder(forwarder_dict) 1171 | 1172 | await message.reply_text("Word replacement added to all the forwarders.") 1173 | 1174 | 1175 | async def rm_replace_all(message: Message) -> None: 1176 | """ Remove replaced words from all the forwarders. """ 1177 | # Get the forwarders 1178 | forwarders_ids = await forwardings.get_forwardings() 1179 | 1180 | # If the replaced words are empty 1181 | if message.text.replace("/rmreplaceall", "").strip() == "": 1182 | await message.reply_text("Replaced words can't be empty!") 1183 | return 1184 | 1185 | # Get the replaced words 1186 | replaced_words = message.text.replace("/rmreplaceall", "")\ 1187 | .strip().split("\n") 1188 | 1189 | # Remove the replaced words from all the forwarders 1190 | for forwarder_id in forwarders_ids: 1191 | forwarder_dict = await forwardings.get_forwarder(str(forwarder_id)) 1192 | 1193 | for replace in replaced_words: 1194 | if replace in forwarder_dict["replace_words"]: 1195 | forwarder_dict["replace_words"].pop(replace) 1196 | 1197 | await forwardings.update_forwarder(forwarder_dict) 1198 | 1199 | await message.reply_text( 1200 | "Word replacement removed from all the forwarders.") 1201 | 1202 | 1203 | if not Path(config_dir/"user.session").exists(): 1204 | logger.info("Log-in with your phone number") 1205 | user.start() 1206 | 1207 | # Iter over all chats 1208 | for dialog in user.get_dialogs(): 1209 | pass 1210 | 1211 | Bot().add_admin(user.get_me().id) # Add user id to admin database 1212 | if not Path(config_dir/"bot.session").exists(): 1213 | logger.info("Log-in with you bot token") 1214 | bot.start() 1215 | logger.info("Bot started") 1216 | logger.info(f"Bot username: @{bot.get_me().username}") 1217 | idle() 1218 | bot.stop() 1219 | -------------------------------------------------------------------------------- /optional-requirements.txt: -------------------------------------------------------------------------------- 1 | TgCrypto 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | https://github.com/KurimuzonAkuma/pyrogram/archive/v2.1.21.zip 2 | sewar 3 | opencv-python 4 | deep_translator 5 | --------------------------------------------------------------------------------