├── runtime.txt ├── .gitignore ├── Procfile ├── .gitattributes ├── requirements.txt ├── example.env ├── helpers ├── functions.py └── database.py ├── config.py ├── README.md ├── main.py ├── LICENSE ├── plugins ├── set_username.py ├── change_mode.py ├── disable.py ├── add_to_db.py ├── enable.py ├── auto_username.py └── start.py └── app.json /runtime.txt: -------------------------------------------------------------------------------- 1 | python-3.9.7 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | __pycache__ -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | worker: python3 main.py -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pyrogram 2 | TgCrypto 3 | motor 4 | pymongo 5 | pymongo[srv] 6 | python-dotenv 7 | -------------------------------------------------------------------------------- /example.env: -------------------------------------------------------------------------------- 1 | API_ID= 2 | API_HASH= 3 | BOT_TOKEN= 4 | BOT_USERNAME= 5 | DATABASE_URL= 6 | LOG_CHANNEL= 7 | -------------------------------------------------------------------------------- /helpers/functions.py: -------------------------------------------------------------------------------- 1 | from pyrogram.types import Message 2 | 3 | 4 | async def get_caption(message: Message) -> str: 5 | caption = "" 6 | if message.caption: 7 | caption = message.caption 8 | elif message.text: 9 | caption = message.text 10 | 11 | return caption 12 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | from os import getenv 2 | 3 | from dotenv import load_dotenv 4 | 5 | load_dotenv() 6 | 7 | API_ID = int(getenv("API_ID")) 8 | API_HASH = getenv("API_HASH") 9 | BOT_TOKEN = getenv("BOT_TOKEN") 10 | BOT_USERNAME = getenv("BOT_USERNAME") 11 | DATABASE_URL = getenv("DATABASE_URL") 12 | LOG_CHANNEL = int(getenv("LOG_CHANNEL")) 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Telegram Username Attacher 2 | 3 | An Open Source Username Attacher Telegram Bot 4 | 5 | **Feature**: 6 | 7 | - Add Channels Username To Posts 8 | - Set Custom Username 9 | - Set Text Mode 10 | 11 | ## Installation 12 | 13 | ### The Easy Way 14 | 15 | ##### Tap the Deploy To Heroku button below to deploy straight to Heroku! 16 | 17 | [![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://www.heroku.com/deploy?template=https://github.com/M4hbod/UsernameAttacher/tree/master) 18 | 19 | --- 20 | 21 | ### The Hard Way 22 | 23 | ```sh 24 | pip install -r requirements.txt 25 | cp .env.example .env 26 | --- EDIT .env values appropriately --- 27 | python main.py 28 | ``` 29 | 30 | - For Feedback and Suggestions, Feel free to DM me on [Telegram](https://t.me/M4hbod) 31 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from pyrogram import Client, idle 2 | from pyrogram.errors import AccessTokenInvalid, ApiIdInvalid, ApiIdPublishedFlood 3 | 4 | from config import API_HASH, API_ID, BOT_TOKEN 5 | 6 | app = Client( 7 | ":memory:", 8 | api_id=API_ID, 9 | api_hash=API_HASH, 10 | bot_token=BOT_TOKEN, 11 | plugins=dict(root="plugins"), 12 | ) 13 | 14 | # Run Bot 15 | if __name__ == "__main__": 16 | try: 17 | app.start() 18 | except (ApiIdInvalid, ApiIdPublishedFlood): 19 | raise Exception("Your API_ID/API_HASH is not valid.") 20 | except AccessTokenInvalid: 21 | raise Exception("Your BOT_TOKEN is not valid.") 22 | 23 | uname = app.get_me().username 24 | print(f"@{uname} Started Successfully!") 25 | 26 | idle() 27 | app.stop() 28 | 29 | print("Bot stopped!") 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Mahbod Nasiri 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /plugins/set_username.py: -------------------------------------------------------------------------------- 1 | import contextlib 2 | from asyncio import sleep 3 | 4 | from helpers.database import db 5 | from pyrogram import Client, filters 6 | from pyrogram.types import Message 7 | 8 | 9 | @Client.on_message( 10 | filters.channel 11 | & filters.text 12 | & filters.regex("^(setusername)") 13 | & ~filters.forwarded, 14 | group=-3, 15 | ) 16 | async def set_usernme(client: Client, message: Message): 17 | me = await client.get_chat_member(message.chat.id, "me") 18 | if me.privileges.can_edit_messages: 19 | channel_username = message.text.split() 20 | if len(channel_username) != 2: 21 | return await message.edit("Incorrect Usage!\nUsage: setusername ") 22 | channel_username = channel_username[1].strip() 23 | if channel_username.startswith("@"): 24 | channel_username = channel_username.replace("@", "") 25 | await db.set_username(message.chat.id, channel_username) 26 | with contextlib.suppress(Exception): 27 | await message.edit(f"Default Username Changed To -> {channel_username}") 28 | await sleep(5) 29 | with contextlib.suppress(Exception): 30 | await message.delete() 31 | -------------------------------------------------------------------------------- /plugins/change_mode.py: -------------------------------------------------------------------------------- 1 | import contextlib 2 | from asyncio import sleep 3 | 4 | from helpers.database import db 5 | from pyrogram import Client, filters 6 | from pyrogram.types import Message 7 | 8 | 9 | @Client.on_message( 10 | filters.channel & filters.regex("^(setmode)") & ~filters.forwarded, group=-4 11 | ) 12 | async def set_mode(client: Client, message: Message): 13 | me = await client.get_chat_member(message.chat.id, "me") 14 | channel = await db.get_channel_info(message.chat.id) 15 | 16 | if me.privileges.can_edit_messages: 17 | mode_text = message.text.split() 18 | if len(mode_text) != 2: 19 | return await message.edit("Incorrect mode!") 20 | mode_text = mode_text[1].strip().lower() 21 | 22 | if mode_text in ["normal", "bold", "italic", "underline", "strike"]: 23 | mode = mode_text 24 | else: 25 | await message.edit("Incorrect mode!") 26 | return 27 | 28 | await db.set_mode(message.chat.id, mode) 29 | with contextlib.suppress(Exception): 30 | await message.edit(f"Mode Changed To -> {mode}") 31 | await sleep(5) 32 | 33 | with contextlib.suppress(Exception): 34 | await message.delete() 35 | -------------------------------------------------------------------------------- /plugins/disable.py: -------------------------------------------------------------------------------- 1 | import contextlib 2 | from asyncio import sleep 3 | 4 | from helpers.database import db 5 | from pyrogram import Client, filters 6 | from pyrogram.types import Message 7 | 8 | 9 | @Client.on_message( 10 | filters.channel & filters.text & filters.regex("^(disable)$") & ~filters.forwarded, 11 | group=-2, 12 | ) 13 | async def disabler(client: Client, message: Message): 14 | me = await client.get_chat_member(message.chat.id, "me") 15 | channel = await db.get_channel_info(message.chat.id) 16 | 17 | if me.privileges.can_edit_messages: 18 | if channel["enabled"] == False: 19 | await message.edit("It's already turned OFF❌") 20 | else: 21 | await db.disable(message.chat.id) 22 | await message.edit("Disabled❌") 23 | 24 | await sleep(5) 25 | with contextlib.suppress(Exception): 26 | await message.delete() 27 | 28 | elif me.privileges.can_post_messages: 29 | with contextlib.suppress(Exception): 30 | await message.reply("Give me Edit Message permission then try again") 31 | 32 | else: 33 | with contextlib.suppress(Exception): 34 | await client.send_message( 35 | message.from_user.id, "Give me Edit Message permission then try again" 36 | ) 37 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Username Attacher Bot", 3 | "description": "Telegram bot to add username to channel posts", 4 | "logo": "https://user-images.githubusercontent.com/74229780/168831902-162eebe1-14b2-451b-afb6-dcb697570af3.jpg", 5 | "keywords": [ 6 | "pyrogram" 7 | ], 8 | "buildpacks": [{ 9 | "url": "heroku/python" 10 | }], 11 | "formation": { 12 | "worker": { 13 | "quantity": 1, 14 | "size": "free" 15 | } 16 | }, 17 | "repository": "https://github.com/M4hbod/UsernameAttacher", 18 | "env": { 19 | "API_ID": { 20 | "description": "Get this value from my.telegram.org.", 21 | "required": true, 22 | "value": "" 23 | }, 24 | "API_HASH": { 25 | "description": "Get this value from my.telegram.org.", 26 | "required": true, 27 | "value": "" 28 | }, 29 | "BOT_TOKEN": { 30 | "description": "Obtain a Telegram bot token by contacting @BotFather", 31 | "required": true, 32 | "value": "" 33 | }, 34 | "BOT_USERNAME": { 35 | "description": "Add your bots username", 36 | "required": false, 37 | "value": "" 38 | }, 39 | "DATABASE_URL": { 40 | "description": "Add your MongoDB URL", 41 | "required": false, 42 | "value": "" 43 | }, 44 | "LOG_CHANNEL": { 45 | "description": "Add your log channel ID", 46 | "required": false, 47 | "value": "" 48 | } 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /plugins/add_to_db.py: -------------------------------------------------------------------------------- 1 | from pyrogram.enums import ChatType 2 | 3 | from config import LOG_CHANNEL 4 | from helpers.database import db 5 | from pyrogram import Client, filters 6 | from pyrogram.types import Message 7 | 8 | 9 | @Client.on_message((filters.private | filters.channel), group=-1) 10 | async def _(bot: Client, message: Message): 11 | if message.chat.type == ChatType.PRIVATE: 12 | if not await db.is_user_exist(message.chat.id): 13 | await db.add_user(message.chat.id) 14 | await bot.send_message( 15 | chat_id=LOG_CHANNEL, 16 | text=f"**📣 Bot Notification.**\n\n📌 New #USER\n🧝🏻‍♂️ Name: {message.from_user.first_name}\n📮 Chat ID: `{message.chat.id}`\n🧝🏻‍♂️ Profile: {message.from_user.mention}", 17 | ) 18 | elif message.chat.type == ChatType.CHANNEL: 19 | if not await db.is_channel_exist(message.chat.id): 20 | await db.add_channel(message.chat.id, "normal") 21 | await bot.send_message( 22 | chat_id=LOG_CHANNEL, 23 | text=f"**📣 Bot Notification.**\n\n📌 New #CHANNEL\n🧝🏻‍♂️ Name: {message.chat.title}\n📮 Chat ID: `{message.chat.id}`", 24 | ) 25 | await message.continue_propagation() 26 | 27 | 28 | @Client.on_message(filters.group, group=9) 29 | async def leave_groups(client: Client, message: Message): 30 | await client.send_message( 31 | message.chat.id, "I'm not designed to work in groups.\nHave a great time :)" 32 | ) 33 | await client.leave_chat(message.chat.id) 34 | -------------------------------------------------------------------------------- /plugins/enable.py: -------------------------------------------------------------------------------- 1 | import contextlib 2 | from asyncio import sleep 3 | 4 | from helpers.database import db 5 | from pyrogram import Client, filters 6 | from pyrogram.types import Message 7 | 8 | 9 | @Client.on_message( 10 | filters.channel & filters.text & filters.regex("^(enable)$") & ~filters.forwarded, 11 | group=-3, 12 | ) 13 | async def enabler(client: Client, message: Message): 14 | me = await client.get_chat_member(message.chat.id, "me") 15 | channel = await db.get_channel_info(message.chat.id) 16 | 17 | if not message.chat.username and channel["username"] == "default": 18 | if me.privileges.can_edit_messages: 19 | await message.edit( 20 | "Your channel is private and you haven't set any username, so you can't use this bot!" 21 | ) 22 | return 23 | 24 | if me.privileges.can_edit_messages: 25 | if channel["enabled"]: 26 | await message.edit("It's already turned ON✅") 27 | else: 28 | await db.enable(message.chat.id) 29 | await message.edit("Enabled✅") 30 | await sleep(5) 31 | with contextlib.suppress(Exception): 32 | await message.delete() 33 | elif me.privileges.can_post_messages: 34 | with contextlib.suppress(Exception): 35 | await message.reply("Give me Edit Message permission then try again") 36 | else: 37 | with contextlib.suppress(Exception): 38 | await client.send_message( 39 | message.from_user.id, "Give me Edit Message permission then try again" 40 | ) 41 | -------------------------------------------------------------------------------- /plugins/auto_username.py: -------------------------------------------------------------------------------- 1 | from pyrogram.enums import ParseMode 2 | 3 | from helpers.database import db 4 | from helpers.functions import get_caption 5 | from pyrogram import Client, filters 6 | from pyrogram.types import Message 7 | 8 | GROUP_MEDIA = set() 9 | 10 | 11 | @Client.on_message(filters.channel & ~filters.forwarded, group=1) 12 | async def auto_username(client: Client, message: Message): 13 | channel = await db.get_channel_info(message.chat.id) 14 | if not message.chat.username and channel["username"] == "default": 15 | return 16 | me = await client.get_chat_member(message.chat.id, "me") 17 | if me.privileges.can_edit_messages and channel["enabled"]: 18 | if channel["username"] == "default": 19 | username = message.chat.username 20 | else: 21 | username = channel["username"] 22 | mode = channel["mode"] 23 | sign = "" 24 | if mode == "bold": 25 | sign = f"\n@{username}" 26 | elif mode == "italic": 27 | sign = f"\n@{username}" 28 | elif mode == "normal": 29 | sign = f"\n@{username}" 30 | elif mode == "strike": 31 | sign = f"\n@{username}" 32 | elif mode == "underline": 33 | sign = f"\n@{username}" 34 | caption = await get_caption(message) 35 | caption += sign 36 | if message.media_group_id: 37 | if message.media_group_id in GROUP_MEDIA: 38 | return 39 | else: 40 | GROUP_MEDIA.add(message.media_group_id) 41 | await message.edit( 42 | text=caption, disable_web_page_preview=True, parse_mode=ParseMode.HTML 43 | ) 44 | -------------------------------------------------------------------------------- /plugins/start.py: -------------------------------------------------------------------------------- 1 | from pyrogram.enums import ParseMode 2 | 3 | from config import BOT_USERNAME 4 | from pyrogram import Client, filters 5 | from pyrogram.types import Message 6 | 7 | 8 | @Client.on_message( 9 | filters.private 10 | & ~filters.command( 11 | [ 12 | "start", 13 | f"start@{BOT_USERNAME}", 14 | "help", 15 | f"help@{BOT_USERNAME}", 16 | "modes", 17 | f"modes@{BOT_USERNAME}", 18 | ] 19 | ), 20 | group=11, 21 | ) 22 | async def unknown_message_private(client: Client, message: Message): 23 | await message.reply("Use /start or /help please :)") 24 | 25 | 26 | @Client.on_message( 27 | filters.private 28 | & filters.command( 29 | ["start", f"start@{BOT_USERNAME}", "help", f"help@{BOT_USERNAME}"] 30 | ), 31 | group=5, 32 | ) 33 | async def start_private(client: Client, message: Message): 34 | await message.reply( 35 | f"""Hi {message.from_user.mention}, 36 | Add me to your channel and give me Edit Message permission then send **enable** command in your channel:) 37 | If you wanna turn it of type `disable` in your channel ;) 38 | There is a few modes that you can use, send /modes to see them. 39 | If your channel is private then you should first user `setusername (custom username)` and if you want to change your channel to a public one, you can use `setusername default` to get username automatically.""" 40 | ) 41 | 42 | 43 | @Client.on_message( 44 | filters.group 45 | & filters.command( 46 | ["start", f"start@{BOT_USERNAME}", "help", f"help@{BOT_USERNAME}"] 47 | ), 48 | group=10, 49 | ) 50 | async def start_groups(client: Client, message: Message): 51 | await message.reply("Message me in private bro :)") 52 | 53 | 54 | @Client.on_message( 55 | filters.private & filters.command(["modes", f"modes@{BOT_USERNAME}"]), group=8 56 | ) 57 | async def start_groups(client: Client, message: Message): 58 | await message.reply( 59 | """So There is 6 modes available, you can send setmode (mode name) in your channel to choose a specific mode. 60 | 61 | E.x: set bold 62 | These are the modes: 63 | normal - @Telegram 64 | bold - @Telegram 65 | italic - @Telegram 66 | underline - @Telegram 67 | strike - @Telegram 68 | ⚠️NOTE: DON'T FORGET TO ENABLE THE BOT IN YOUR CHANNEL 69 | 70 | Made with ❤️ by Mahbod""", 71 | parse_mode=ParseMode.HTML, 72 | disable_web_page_preview=True, 73 | ) 74 | -------------------------------------------------------------------------------- /helpers/database.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | import motor.motor_asyncio 4 | from config import BOT_USERNAME, DATABASE_URL 5 | 6 | 7 | class Database: 8 | def __init__(self, uri, database_name): 9 | self._client = motor.motor_asyncio.AsyncIOMotorClient(uri) 10 | self.db = self._client[database_name] 11 | self.col = self.db 12 | 13 | def get_user_dict(self, chat_id: int): 14 | return dict(id=chat_id, join_date=datetime.date.today().isoformat()) 15 | 16 | def get_channel_dict(self, chat_id: int, defult_type: str): 17 | return dict( 18 | id=chat_id, 19 | join_date=datetime.date.today().isoformat(), 20 | settings=dict(enabled=False, mode=defult_type, username="default"), 21 | ) 22 | 23 | async def add_user(self, chat_id: int): 24 | user = self.get_user_dict(chat_id) 25 | await self.col.users.insert_one(user) 26 | 27 | async def add_channel(self, chat_id: int, defult_type: str): 28 | channel = self.get_channel_dict(chat_id, defult_type) 29 | await self.col.channels.insert_one(channel) 30 | 31 | async def is_user_exist(self, chat_id: int): 32 | user = await self.col.users.find_one({"id": chat_id}) 33 | return bool(user) 34 | 35 | async def is_channel_exist(self, chat_id: int): 36 | channel = await self.col.channels.find_one({"id": chat_id}) 37 | return bool(channel) 38 | 39 | async def get_channel_info(self, chat_id: int): 40 | default = dict(enabled=False, mode="normal", username="default") 41 | chat = await self.col.channels.find_one({"id": chat_id}) 42 | try: 43 | return chat.get("settings", default) 44 | except: 45 | return default 46 | 47 | async def enable(self, chat_id: int): 48 | channel = await self.get_channel_info(chat_id) 49 | mode = channel["mode"] 50 | username = channel["username"] 51 | channel = dict(enabled=True, mode=mode, username=username) 52 | await self.col.channels.update_one( 53 | {"id": chat_id}, {"$set": {"settings": channel}} 54 | ) 55 | 56 | async def disable(self, chat_id: int): 57 | channel = await self.get_channel_info(chat_id) 58 | mode = channel["mode"] 59 | username = channel["username"] 60 | channel = dict(enabled=False, mode=mode, username=username) 61 | await self.col.channels.update_one( 62 | {"id": chat_id}, {"$set": {"settings": channel}} 63 | ) 64 | 65 | async def set_mode(self, chat_id: int, mode: str): 66 | channel = await self.get_channel_info(chat_id) 67 | status = channel["enabled"] 68 | username = channel["username"] 69 | channel = dict(enabled=status, mode=mode, username=username) 70 | await self.col.channels.update_one( 71 | {"id": chat_id}, {"$set": {"settings": channel}} 72 | ) 73 | 74 | async def set_username(self, chat_id: int, username: str): 75 | channel = await self.get_channel_info(chat_id) 76 | status = channel["enabled"] 77 | mode = channel["mode"] 78 | channel = dict(enabled=status, mode=mode, username=username) 79 | await self.col.channels.update_one( 80 | {"id": chat_id}, {"$set": {"settings": channel}} 81 | ) 82 | 83 | 84 | # Database 85 | db = Database(DATABASE_URL, BOT_USERNAME) 86 | --------------------------------------------------------------------------------