├── .gitignore ├── Aptfile ├── LICENSE ├── Procfile ├── README.md ├── database.py ├── main.py ├── plugins ├── admin.py ├── commands.py └── qrcode.py └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | **/__pycache__ 2 | **/venv 3 | *.session 4 | *.session-journal 5 | *.pyc 6 | *.env 7 | 8 | **/downloads 9 | -------------------------------------------------------------------------------- /Aptfile: -------------------------------------------------------------------------------- 1 | libzbar0 2 | libzbar-dev 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Fayas 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 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | worker: python main.py 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QR Code Bot 2 | A telegram QR Code encode and decode bot 3 | 4 | --- 5 | 6 | ## Features 7 | 8 | 1. QR Code Encoding 9 | 2. QR Code Decoding 10 | 3. Database (MongoDB) 11 | 4. Broadcast 12 | 5. Status Command 13 | 6. Settings Command 14 | 7. Document or Photo Selection 15 | 16 | --- 17 | 18 | ## Variables 19 | 20 | - `API_HASH` Your API Hash from my.telegram.org 21 | - `API_ID` Your API ID from my.telegram.org 22 | - `BOT_TOKEN` Your bot token from @BotFather 23 | - `AUTH_USERS` Authorized users IDs separated by whitespace 24 | - `DATABASE_URL` MongoDB URL 25 | - `DATABASE_NAME` Your database name (not required) 26 | 27 | --- 28 | 29 | ## Other Credits 30 | 31 | - [Abhijith NT](https://github.com/AbhijithNT) for his [QR Code Bot](https://github.com/AbhijithNT/QRCode-Telegram-bot) 32 | 33 | --- 34 | -------------------------------------------------------------------------------- /database.py: -------------------------------------------------------------------------------- 1 | import os 2 | import motor.motor_asyncio 3 | 4 | 5 | DATABASE_URL = os.environ.get("DATABASE_URL") 6 | DATABASE_NAME = os.environ.get("DATABASE_NAME", "QR-Code-Bot") 7 | 8 | 9 | class Database: 10 | def __init__(self, url=DATABASE_URL, database_name=DATABASE_NAME): 11 | self._client = motor.motor_asyncio.AsyncIOMotorClient(url) 12 | self.db = self._client[database_name] 13 | self.col = self.db.users 14 | self.cache = {} 15 | 16 | def new_user(self, id): 17 | return dict( 18 | id = id, 19 | as_file = False 20 | ) 21 | 22 | async def add_user(self, id): 23 | user = self.new_user(id) 24 | await self.col.insert_one(user) 25 | 26 | async def get_user(self, id): 27 | user = self.cache.get(id) 28 | if user is not None: 29 | return user 30 | 31 | user = await self.col.find_one({"id": int(id)}) 32 | self.cache[id] = user 33 | return user 34 | 35 | async def is_user_exist(self, id): 36 | user = await self.col.find_one({'id':int(id)}) 37 | return True if user else False 38 | 39 | async def total_users_count(self): 40 | count = await self.col.count_documents({}) 41 | return count 42 | 43 | async def get_all_users(self): 44 | all_users = self.col.find({}) 45 | return all_users 46 | 47 | async def delete_user(self, user_id): 48 | await self.col.delete_many({'id': int(user_id)}) 49 | 50 | async def is_as_file(self, id): 51 | user = await self.get_user(id) 52 | return user.get("as_file", False) 53 | 54 | async def update_as_file(self, id, as_file): 55 | self.cache[id]["as_file"] = as_file 56 | await self.col.update_one({"id": id}, {"$set": {"as_file": as_file}}) 57 | 58 | 59 | db = Database() 60 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import os 2 | from dotenv import load_dotenv 3 | from pyrogram import Client 4 | 5 | 6 | load_dotenv() 7 | 8 | Bot = Client( 9 | "QR Code Bot", 10 | bot_token=os.environ.get("BOT_TOKEN"), 11 | api_id=int(os.environ.get("API_ID")), 12 | api_hash=os.environ.get("API_HASH"), 13 | plugins=dict(root="plugins") 14 | ) 15 | 16 | Bot.run() 17 | -------------------------------------------------------------------------------- /plugins/admin.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | import math 4 | import json 5 | import string 6 | import random 7 | import traceback 8 | import asyncio 9 | import datetime 10 | import aiofiles 11 | import pyqrcode 12 | import pyrogram 13 | from random import choice 14 | from pyrogram import Client, filters 15 | from pyrogram.errors import FloodWait, InputUserDeactivated, UserIsBlocked, PeerIdInvalid, UserNotParticipant, UserBannedInChannel 16 | from pyrogram.errors.exceptions.bad_request_400 import PeerIdInvalid 17 | from database import db 18 | 19 | 20 | AUTH_USERS = set(int(x) for x in os.environ.get("AUTH_USERS", "").split()) 21 | 22 | 23 | async def send_msg(user_id, message): 24 | try: 25 | await message.copy(chat_id=user_id) 26 | return 200, None 27 | except FloodWait as e: 28 | await asyncio.sleep(e.x) 29 | return send_msg(user_id, message) 30 | except InputUserDeactivated: 31 | return 400, f"{user_id} : deactivated\n" 32 | except UserIsBlocked: 33 | return 400, f"{user_id} : blocked the bot\n" 34 | except PeerIdInvalid: 35 | return 400, f"{user_id} : user id invalid\n" 36 | except Exception as e: 37 | return 500, f"{user_id} : {traceback.format_exc()}\n" 38 | 39 | 40 | @Client.on_message(filters.private & filters.command("broadcast") & filters.reply) 41 | async def broadcast(bot, update): 42 | 43 | if (update.from_user.id not in AUTH_USERS): 44 | return 45 | 46 | broadcast_ids = {} 47 | all_users = await db.get_all_users() 48 | broadcast_msg = update.reply_to_message 49 | while True: 50 | broadcast_id = ''.join([random.choice(string.ascii_letters) for i in range(3)]) 51 | if not broadcast_ids.get(broadcast_id): 52 | break 53 | out = await update.reply_text( 54 | text=f"Broadcast Started! You will be notified with log file when all the users are notified." 55 | ) 56 | start_time = time.time() 57 | total_users = await db.total_users_count() 58 | done = 0 59 | failed = 0 60 | success = 0 61 | broadcast_ids[broadcast_id] = dict(total=total_users, current=done, failed=failed, success=success) 62 | async with aiofiles.open('broadcast.txt', 'w') as broadcast_log_file: 63 | async for user in all_users: 64 | sts, msg = await send_msg(user_id = int(user['id']), message = broadcast_msg) 65 | if msg is not None: 66 | await broadcast_log_file.write(msg) 67 | if sts == 200: 68 | success += 1 69 | else: 70 | failed += 1 71 | if sts == 400: 72 | await db.delete_user(user['id']) 73 | done += 1 74 | if broadcast_ids.get(broadcast_id) is None: 75 | break 76 | else: 77 | broadcast_ids[broadcast_id].update(dict(current = done, failed = failed, success = success)) 78 | if broadcast_ids.get(broadcast_id): 79 | broadcast_ids.pop(broadcast_id) 80 | completed_in = datetime.timedelta(seconds=int(time.time()-start_time)) 81 | await asyncio.sleep(3) 82 | await out.delete() 83 | if failed == 0: 84 | await update.reply_text(text=f"broadcast completed in `{completed_in}`\n\nTotal users {total_users}.\nTotal done {done}, {success} success and {failed} failed.", quote=True) 85 | else: 86 | await update.reply_document(document='broadcast.txt', caption=f"broadcast completed in `{completed_in}`\n\nTotal users {total_users}.\nTotal done {done}, {success} success and {failed} failed.") 87 | os.remove('broadcast.txt') 88 | -------------------------------------------------------------------------------- /plugins/commands.py: -------------------------------------------------------------------------------- 1 | from pyrogram import Client, filters 2 | from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton 3 | from database import db 4 | 5 | 6 | START_TEXT = """**Hello {} 😌 7 | I am a QR Code Bot** 8 | 9 | >> `I can generate text to QR Code with QR Code decode to text support.`""" 10 | 11 | HELP_TEXT = """**Hey, Follow these steps:** 12 | 13 | ➠ Send me a link/text I will generate the QR code of that text 14 | ➠ Send me a QR code image I will decode that image and convert to text 15 | 16 | **Available Commands** 17 | 18 | /start - Checking Bot Online 19 | /help - For more help 20 | /about - For more about me 21 | /settings - For bot settings 22 | /reset - For reset settings 23 | /status - For bot status""" 24 | 25 | ABOUT_TEXT = """--**About Me 😎**-- 26 | 27 | 🤖 **Name :** [QR Code Bot](https://telegram.me/{}) 28 | 29 | 👨‍💻 **Developer :** [GitHub](https://github.com/FayasNoushad) | [Telegram](https://telegram.me/FayasNoushad) 30 | 31 | 🌐 **Source :** [👉 Click here](https://github.com/FayasNoushad/QR-Code-bot) 32 | 33 | 📝 **Language :** [Python3](https://python.org) 34 | 35 | 🧰 **Framework :** [Pyrogram](https://pyrogram.org)""" 36 | 37 | SETTINGS_TEXT = "**Settings**" 38 | 39 | START_BUTTONS = InlineKeyboardMarkup( 40 | [ 41 | [ 42 | InlineKeyboardButton('⚙ Help', callback_data='help'), 43 | InlineKeyboardButton('About 🔰', callback_data='about'), 44 | InlineKeyboardButton('Close ✖️', callback_data='close') 45 | ] 46 | ] 47 | ) 48 | 49 | HELP_BUTTONS = InlineKeyboardMarkup( 50 | [ 51 | [ 52 | InlineKeyboardButton('🏘 Home', callback_data='home'), 53 | InlineKeyboardButton('About 🔰', callback_data='about') 54 | ],[ 55 | InlineKeyboardButton('⚒ Settings', callback_data='settings'), 56 | InlineKeyboardButton('Close ✖️', callback_data='close') 57 | ] 58 | ] 59 | ) 60 | 61 | ABOUT_BUTTONS = InlineKeyboardMarkup( 62 | [ 63 | [ 64 | InlineKeyboardButton('🏘 Home', callback_data='home'), 65 | InlineKeyboardButton('Help ⚙', callback_data='help'), 66 | InlineKeyboardButton('Close ✖️', callback_data='close') 67 | ] 68 | ] 69 | ) 70 | 71 | 72 | @Client.on_callback_query() 73 | async def cb_handler(bot, update): 74 | if update.data == "lol": 75 | await update.answer( 76 | text="Select a button below", 77 | show_alert=True 78 | ) 79 | elif update.data == "home": 80 | await update.message.edit_text( 81 | text=START_TEXT.format(update.from_user.mention), 82 | reply_markup=START_BUTTONS, 83 | disable_web_page_preview=True 84 | ) 85 | elif update.data == "help": 86 | await update.message.edit_text( 87 | text=HELP_TEXT, 88 | reply_markup=HELP_BUTTONS, 89 | disable_web_page_preview=True 90 | ) 91 | elif update.data == "about": 92 | await update.message.edit_text( 93 | text=ABOUT_TEXT, 94 | reply_markup=ABOUT_BUTTONS, 95 | disable_web_page_preview=True 96 | ) 97 | elif update.data == "settings": 98 | await display_settings(bot, update, db, cb=True, cb_text=True) 99 | elif update.data == "close": 100 | await update.message.delete() 101 | elif update.data == "set_af": 102 | as_file = await db.is_as_file(update.from_user.id) 103 | await db.update_as_file(update.from_user.id, not as_file) 104 | if as_file: 105 | alert_text = "Upload mode changed to file successfully" 106 | else: 107 | alert_text = "Upload mode changed to photo successfully" 108 | await update.answer(text=alert_text, show_alert=True) 109 | await display_settings(bot, update, db, cb=True) 110 | 111 | 112 | @Client.on_message(filters.private & filters.command(["start"])) 113 | async def start(bot, update): 114 | if not await db.is_user_exist(update.from_user.id): 115 | await db.add_user(update.from_user.id) 116 | await update.reply_text( 117 | text=START_TEXT.format(update.from_user.mention), 118 | disable_web_page_preview=True, 119 | reply_markup=START_BUTTONS, 120 | quote=True 121 | ) 122 | 123 | 124 | @Client.on_message(filters.private & filters.command(["help"])) 125 | async def help(bot, update): 126 | if not await db.is_user_exist(update.from_user.id): 127 | await db.add_user(update.from_user.id) 128 | await update.reply_text( 129 | text=HELP_TEXT, 130 | disable_web_page_preview=True, 131 | reply_markup=HELP_BUTTONS, 132 | quote=True 133 | ) 134 | 135 | 136 | @Client.on_message(filters.private & filters.command(["about"])) 137 | async def about(bot, update): 138 | if not await db.is_user_exist(update.from_user.id): 139 | await db.add_user(update.from_user.id) 140 | await update.reply_text( 141 | text=ABOUT_TEXT, 142 | disable_web_page_preview=True, 143 | reply_markup=ABOUT_BUTTONS, 144 | quote=True 145 | ) 146 | 147 | 148 | @Client.on_message(filters.private & filters.command(["reset"])) 149 | async def reset(bot, update): 150 | await db.delete_user(update.from_user.id) 151 | await db.add_user(update.from_user.id) 152 | await update.reply_text("Settings reset successfully") 153 | 154 | 155 | @Client.on_message(filters.private & filters.command(["settings"])) 156 | async def settings(bot, update): 157 | if not await db.is_user_exist(update.from_user.id): 158 | await db.add_user(update.from_user.id) 159 | await display_settings(bot, update, db) 160 | 161 | 162 | async def display_settings(bot, update, db, cb=False, cb_text=False): 163 | chat_id = update.from_user.id 164 | as_file = await db.is_as_file(chat_id) 165 | as_file_btn = [ 166 | InlineKeyboardButton("Upload Mode", callback_data="lol") 167 | ] 168 | if as_file: 169 | as_file_btn.append( 170 | InlineKeyboardButton('Upload as File', callback_data='set_af') 171 | ) 172 | else: 173 | as_file_btn.append( 174 | InlineKeyboardButton('Upload as Photo', callback_data='set_af') 175 | ) 176 | close_btn = [ 177 | InlineKeyboardButton('Close ✖️', callback_data='close') 178 | ] 179 | settings_buttons = [as_file_btn, close_btn] 180 | try: 181 | if cb: 182 | if cb and cb_text: 183 | await update.message.edit_text( 184 | text=SETTINGS_TEXT, 185 | disable_web_page_preview=True, 186 | reply_markup=InlineKeyboardMarkup(settings_buttons) 187 | ) 188 | else: 189 | await update.edit_message_reply_markup( 190 | InlineKeyboardMarkup(settings_buttons) 191 | ) 192 | else: 193 | await update.reply_text( 194 | text=SETTINGS_TEXT, 195 | quote=True, 196 | disable_web_page_preview=True, 197 | reply_markup=InlineKeyboardMarkup(settings_buttons) 198 | ) 199 | except Exception as error: 200 | print(error) 201 | 202 | 203 | @Client.on_message(filters.private & filters.command("status")) 204 | async def status(bot, update): 205 | total_users = await db.total_users_count() 206 | text = "**Bot Status**\n" 207 | text += f"\n**Total Users:** `{total_users}`" 208 | await update.reply_text( 209 | text=text, 210 | quote=True, 211 | disable_web_page_preview=True 212 | ) 213 | -------------------------------------------------------------------------------- /plugins/qrcode.py: -------------------------------------------------------------------------------- 1 | import os 2 | from pyrogram import Client, filters 3 | from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton 4 | from PIL import Image 5 | from pyzbar.pyzbar import decode 6 | import pyqrcode 7 | from database import db 8 | 9 | 10 | QR_BUTTONS = InlineKeyboardMarkup( 11 | [ 12 | [ 13 | InlineKeyboardButton( 14 | text="⚙ Feedback ⚙", url=f"https://telegram.me/FayasNoushad" 15 | ) 16 | ] 17 | ] 18 | ) 19 | 20 | 21 | @Client.on_message(filters.private & (filters.photo | filters.document)) 22 | async def qr_decode(bot, update): 23 | if not await db.is_user_exist(update.from_user.id): 24 | await db.add_user(update.from_user.id) 25 | if (update.document) and ("image" not in update.document.mime_type): 26 | await update.reply_text(text="Send a QR code image to decode.", quote=True) 27 | return 28 | decode_text = await update.reply_text(text="Processing your request...") 29 | if update.photo: 30 | name = update.photo.file_id 31 | else: 32 | name = update.document.file_id 33 | dl_location = f"./downloads/{str(update.from_user.id)}/{name}" 34 | im_dowload = "" 35 | qr_text = "" 36 | try: 37 | await decode_text.edit("Trying to download....") 38 | im_dowload = await update.download(file_name=dl_location + ".png") 39 | except Exception as error: 40 | await decode_text.edit(text=error) 41 | return 42 | try: 43 | await decode_text.edit(text="Decoding.....") 44 | qr_text_data = decode(Image.open(im_dowload)) 45 | qr_text_list = list(qr_text_data[0]) # Listing 46 | qr_text_ext = str(qr_text_list[0]).split("'")[1] # Text Extract 47 | qr_text = "".join(qr_text_ext) # Text_join 48 | except Exception as error: 49 | await decode_text.edit(text=error) 50 | return 51 | await decode_text.edit_text( 52 | text=f"Decoded text/link :-\n\n{qr_text}", 53 | reply_markup=InlineKeyboardMarkup( 54 | [ 55 | [ 56 | InlineKeyboardButton( 57 | text="⚙ Feedback ⚙", url=f"https://telegram.me/FayasNoushad" 58 | ) 59 | ] 60 | ] 61 | ), 62 | disable_web_page_preview=True, 63 | ) 64 | try: 65 | os.remove(im_dowload) 66 | except Exception as error: 67 | print(error) 68 | 69 | 70 | @Client.on_message(filters.text & filters.private) 71 | async def qr_encode(bot, update): 72 | if not await db.is_user_exist(update.from_user.id): 73 | await db.add_user(update.from_user.id) 74 | qr = await update.reply_text(text="Making your QR Code...", quote=True) 75 | s = str(update.text) 76 | directory = f"./downloads/" 77 | if not os.path.isdir(directory): 78 | os.makedirs(directory) 79 | qrname = f"./downloads/{str(update.from_user.id)}_qr_code.png" 80 | try: 81 | qrcode = pyqrcode.create(s.encode("utf-8")) # Encode the text using UTF-8 82 | qrcode.png(qrname + ".png", scale=6) 83 | except UnicodeDecodeError: 84 | qr.edit_text("Unsupported characters found in the text.") 85 | await qr.delete() 86 | return 87 | except Exception as error: 88 | await qr.edit_text(error) 89 | return 90 | img = qrname + ".png" 91 | as_file = await db.is_as_file(update.from_user.id) 92 | try: 93 | await qr.edit_text("Trying to Uploading....") 94 | if as_file: 95 | await update.reply_document(document=img, reply_markup=QR_BUTTONS) 96 | else: 97 | await update.reply_photo(photo=img, reply_markup=QR_BUTTONS, quote=True) 98 | await qr.delete() 99 | except Exception as error: 100 | print(error) 101 | try: 102 | os.remove(img) 103 | except Exception as error: 104 | print(error) 105 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pyrogram 2 | tgcrypto 3 | pyqrcode 4 | pypng 5 | pyzbar 6 | pyzbar[scripts] 7 | Pillow 8 | motor 9 | aiofiles 10 | dnspython 11 | python-dotenv 12 | --------------------------------------------------------------------------------