├── .env.sample ├── .gitignore ├── Procfile ├── bot.py └── requirements.txt /.env.sample: -------------------------------------------------------------------------------- 1 | BOT_TOKEN= 2 | REDIS_URI= 3 | REDIS_PASSWORD= 4 | OWNERS= -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .env 3 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | worker: python bot.py -------------------------------------------------------------------------------- /bot.py: -------------------------------------------------------------------------------- 1 | # (c) @xditya 2 | 3 | import contextlib 4 | import logging 5 | from random import choice 6 | import re 7 | 8 | from decouple import config 9 | from aioredis import Redis 10 | from requests import get 11 | from html_telegraph_poster import TelegraphPoster 12 | 13 | from telethon import Button, TelegramClient, events, functions, errors 14 | 15 | # initializing logger 16 | logging.basicConfig( 17 | level=logging.INFO, format="[%(levelname)s] %(asctime)s - %(message)s" 18 | ) 19 | log = logging.getLogger("XDITYA") 20 | 21 | # fetching variales from env 22 | try: 23 | BOT_TOKEN = config("BOT_TOKEN") 24 | OWNERS = config("OWNERS") 25 | REDIS_URI = config("REDIS_URI") 26 | REDIS_PASSWORD = config("REDIS_PASSWORD") 27 | except Exception as ex: 28 | log.info(ex) 29 | 30 | OWNERS = [int(i) for i in OWNERS.split(" ")] 31 | OWNERS.append(719195224) if 719195224 not in OWNERS else None 32 | 33 | log.info("Connecting bot.") 34 | try: 35 | bot = TelegramClient(None, 6, "eb06d4abfb49dc3eeb1aeb98ae0f581e").start( 36 | bot_token=BOT_TOKEN 37 | ) 38 | except Exception as e: 39 | log.warning(e) 40 | exit(1) 41 | 42 | t = TelegraphPoster(use_api=True) 43 | t.create_api_token("@TheEmailBot", "MailBot", "https://www.xditya.me/") 44 | 45 | REDIS_URI = REDIS_URI.split(":") 46 | db = Redis( 47 | host=REDIS_URI[0], 48 | port=REDIS_URI[1], 49 | password=REDIS_PASSWORD, 50 | decode_responses=True, 51 | ) 52 | 53 | # users to db 54 | def str_to_list(text): # Returns List 55 | return text.split(" ") 56 | 57 | 58 | def list_to_str(list): # Returns String # sourcery skip: avoid-builtin-shadow 59 | str = " ".join(f"{x}" for x in list) 60 | return str.strip() 61 | 62 | 63 | async def is_added(var, id): # Take int or str with numbers only , Returns Boolean 64 | if not str(id).isdigit(): 65 | return False 66 | users = await get_all(var) 67 | return str(id) in users 68 | 69 | 70 | async def add_to_db(var, id): # Take int or str with numbers only , Returns Boolean 71 | # sourcery skip: avoid-builtin-shadow 72 | id = str(id) 73 | if not id.isdigit(): 74 | return False 75 | try: 76 | users = await get_all(var) 77 | users.append(id) 78 | await db.set(var, list_to_str(users)) 79 | return True 80 | except Exception as e: 81 | return False 82 | 83 | 84 | async def get_all(var): # Returns List 85 | users = await db.get(var) 86 | return [""] if users is None or users == "" else str_to_list(users) 87 | 88 | 89 | # join checks 90 | async def check_user(user): 91 | ok = True 92 | try: 93 | await bot( 94 | functions.channels.GetParticipantRequest( 95 | channel="BotzHub", participant=user 96 | ) 97 | ) 98 | ok = True 99 | except errors.rpcerrorlist.UserNotParticipantError: 100 | ok = False 101 | return ok 102 | 103 | 104 | # functions 105 | @bot.on(events.NewMessage(incoming=True, pattern="^/start$")) 106 | async def start_msg(event): 107 | user = await event.get_sender() 108 | msg = f"Hi {user.first_name}, welcome to the bot!\n\nI'm a MailBox Bot - I can generate a random e-mail address for you and send you the e-mails that come to that e-mail address!\n\nHit /generate to set-up your inbox!" 109 | btns = [ 110 | Button.inline("Disclaimer", data="disclaimer"), 111 | Button.url("Updates", url="https://t.me/BotzHub"), 112 | ] 113 | if not await check_user(user.id): 114 | msg += "\n\nI'm limited to the users in @BotzHub. Kinly join @BotzHub and then /start the bot!" 115 | btns = Button.url("Join Channel", url="https://t.me/BotzHub") 116 | await event.reply(msg, buttons=btns) 117 | if not await is_added("MAILBOT", user.id): 118 | await add_to_db("MAILBOT", user.id) 119 | 120 | 121 | @bot.on(events.CallbackQuery(data="back")) 122 | async def back(event): 123 | user = await event.get_sender() 124 | msg = f"Hi {user.first_name}, welcome to the bot!\n\nI'm a MailBox Bot - I can generate a random e-mail address for you and send you the e-mails that come to that e-mail address!\n\nHit /generate to set-up your inbox!" 125 | btns = [ 126 | Button.inline("Disclaimer", data="disclaimer"), 127 | Button.url("Updates", url="https://t.me/BotzHub"), 128 | ] 129 | if not await check_user(user.id): 130 | msg += "\n\nI'm limited to the users in @BotzHub. Kinly join @BotzHub and then /start the bot!" 131 | btns = Button.url("Join Channel", url="https://t.me/BotzHub") 132 | await event.edit(msg, buttons=btns) 133 | 134 | 135 | @bot.on(events.CallbackQuery(data="disclaimer")) 136 | async def domain_list(event): 137 | await event.edit( 138 | "**__Disclaimer__**\nDo not send sensitive information to the emails generated by the bot.", 139 | buttons=Button.inline("« Back", data="back"), 140 | ) 141 | 142 | 143 | @bot.on(events.NewMessage(pattern="^/generate")) 144 | async def gen_id(event): 145 | if not await check_user(event.sender_id): 146 | await event.reply("Kindly join @BotzHub to be able to use this bot!") 147 | return 148 | e = await event.reply("Please wait...") 149 | resp = get("https://www.1secmail.com/api/v1/?action=getDomainList") 150 | if resp.status_code != 200: 151 | await e.edit("Server down!") 152 | return 153 | try: 154 | domains = eval(resp.text) 155 | except Exception as ex: 156 | await e.edit( 157 | "Unknown error while fetching domain list, report to @BotzHubChat." 158 | ) 159 | log.exception("Error while parsing domains: %s", ex) 160 | return 161 | butt = [[Button.inline(domain, data=f"dmn_{domain}")] for domain in domains] 162 | await e.edit("Please select a domain from the below list.", buttons=butt) 163 | 164 | 165 | async def get_random_domain(event, num=None): 166 | resp = get(f"https://www.1secmail.com/api/v1/?action=genRandomMailbox&count={num}") 167 | if resp.status_code != 200: 168 | await event.edit("Server down!") 169 | return 170 | try: 171 | domains = eval(resp.text) 172 | except Exception as ex: 173 | await e.edit( 174 | "Unknown error while fetching domain list, report to @BotzHubChat." 175 | ) 176 | log.exception("Error while fetching domains: %s", ex) 177 | return 178 | return choice(domains) 179 | 180 | 181 | @bot.on(events.CallbackQuery(data=re.compile("dmn_(.*)"))) 182 | async def on_selection(event): 183 | domain_name = event.pattern_match.group(1).decode("utf-8") 184 | user = await event.get_sender() 185 | if user.username: 186 | domain = f"{user.username}@{domain_name}" 187 | else: 188 | domain = await get_random_domain(event, 5) 189 | await event.edit( 190 | f"Generated email address: `{domain}`", 191 | buttons=[ 192 | [Button.inline("Proceed", data=f"mbx_{domain}")], 193 | [Button.inline("Generate random email", data="gen_random")], 194 | [Button.inline("Generate custom email", data=f"gen_custom_{domain_name}")], 195 | ], 196 | ) 197 | 198 | 199 | @bot.on(events.CallbackQuery(data=re.compile("gen_(.*)"))) 200 | async def gen_xx(event): 201 | ch = event.pattern_match.group(1).decode("utf-8") 202 | ev = await event.edit("Please wait...") 203 | with contextlib.suppress(errors.rpcerrorlist.MessageNotModifiedError): 204 | if ch == "random": 205 | domain = await get_random_domain(event, 5) 206 | await ev.edit( 207 | f"Generated email address: `{domain}`", 208 | buttons=[ 209 | [Button.inline("Proceed", data=f"mbx_{domain}")], 210 | [Button.inline("Generate random email", data="gen_random")], 211 | [Button.inline("Generate custom email", data="gen_custom")], 212 | ], 213 | ) 214 | elif ch.startswith("custom"): 215 | try: 216 | domain_name = ch.split("_", 1)[1] 217 | except IndexError: 218 | domain_name = await get_random_domain(event, 5) 219 | await ev.delete() 220 | async with bot.conversation(event.sender_id) as conv: 221 | await conv.send_message( 222 | "Enter the custom username (no spaces allowed) (send within one minute):" 223 | ) 224 | msg = await conv.get_response() 225 | if not msg.text: 226 | await msg.reply( 227 | "Received an unexpected input. Use /generate again!" 228 | ) 229 | return 230 | if "@" in msg.text: 231 | await msg.reply( 232 | 'Custom usernames cannot contain "@"\nUse /generate again!' 233 | ) 234 | return 235 | username = msg.text.split()[0] 236 | domain = f"{username}@{domain_name}" 237 | await msg.reply( 238 | f"Generated email address: `{domain}`", 239 | buttons=[ 240 | [Button.inline("Proceed", data=f"mbx_{domain}")], 241 | ], 242 | ) 243 | 244 | 245 | @bot.on(events.CallbackQuery(data=re.compile("mbx_(.*)"))) 246 | async def mailbox(event): 247 | email = event.pattern_match.group(1).decode("utf-8") 248 | await event.edit( 249 | f"Current email address: `{email}`\nReceived emails: 0", 250 | buttons=Button.inline("Refresh MailBox", data=f"ref_{email}"), 251 | ) 252 | 253 | 254 | async def get_mails(ev, email): 255 | username, domain = email.split("@") 256 | api_uri = f"https://www.1secmail.com/api/v1/?action=getMessages&login={username}&domain={domain}" 257 | resp = get(api_uri) 258 | if resp.status_code != 200: 259 | await ev.edit("Server down! Report to @BotzHubChat.") 260 | return 261 | try: 262 | mails = eval(resp.text) 263 | except Exception as exc: 264 | await ev.edit("Error while parsing mailbox. Report to @BotzHubChat") 265 | log.exception("Error parsing mailbox: %s", exc) 266 | return 267 | return mails 268 | 269 | 270 | @bot.on(events.CallbackQuery(data=re.compile("ref_(.*)"))) 271 | async def refresh_mb(event): 272 | email = event.pattern_match.group(1).decode("utf-8") 273 | await event.answer("Refreshing...") 274 | with contextlib.suppress(errors.MessageNotModifiedError): 275 | mails = await get_mails(event, email) 276 | if not mails: 277 | return 278 | buttons = [] 279 | for mail in mails[:50]: 280 | if subj := mail.get("subject"): 281 | subj = f"{subj[:50]}..." 282 | buttons.append( 283 | [Button.inline(subj, data=f"ex_{email}||{mail.get('id')}")] 284 | ) 285 | await event.edit( 286 | f"Current email address: `{email}`\nReceived emails: {len(mails)}\nClick on the buttons below to read the corresponding e-mail.", 287 | buttons=buttons, 288 | ) 289 | await event.answer("Refreshed") 290 | 291 | 292 | @bot.on(events.CallbackQuery(data=re.compile("ex_(.*)"))) 293 | async def read_mail(event): 294 | ev = await event.edit("Please wait...") 295 | args = event.pattern_match.group(1).decode("utf-8") 296 | email, mail_id = args.split("||") 297 | username, domain = email.split("@") 298 | mails = await get_mails(ev, email) 299 | user = await event.get_sender() 300 | if not mails: 301 | return 302 | c = 0 303 | for mail in mails: 304 | if mail.get("id") == int(mail_id): 305 | api = f"https://www.1secmail.com/api/v1/?action=readMessage&login={username}&domain={domain}&id={mail_id}" 306 | resp = get(api) 307 | if resp.status_code != 200: 308 | await ev.edit("Server down! Report to @BotzHubChat.") 309 | return 310 | try: 311 | content = resp.json() 312 | except Exception as exc: 313 | await ev.edit("Error while email content. Report to @BotzHubChat") 314 | log.exception("Error parsing email content: %s", exc) 315 | return 316 | msg = f"**__New Email__**\n\n**From:** `{content.get('from')}`\n**Subject:** `{content.get('subject')}`\n**Message:**" 317 | response = t.post( 318 | title=f"Email for {user.first_name}", 319 | author="@TheEmailBot", 320 | text=content.get("body"), 321 | ) 322 | msg += f" [read message]({response.get('url')})\n" 323 | if attachments := content.get("attachments"): 324 | msg += "**Attachments found in mail. Click the below buttons to download.**" 325 | buttons = [ 326 | [ 327 | Button.url( 328 | attachment.get("filename"), 329 | url=f"https://www.1secmail.com/api/v1/?action=download&login={username}&domain={domain}&id={mail_id}&file={attachment.get('filename')}", 330 | ) 331 | ] 332 | for attachment in attachments 333 | ] 334 | buttons.append([Button.url("Read email", url=response.get("url"))]) 335 | buttons.append([Button.inline("« Back", data=f"ref_{email}")]) 336 | await event.edit(msg, buttons=buttons, link_preview=False) 337 | else: 338 | await ev.edit( 339 | msg, 340 | link_preview=False, 341 | buttons=[ 342 | [Button.url("Read email", url=response.get("url"))], 343 | [Button.inline("« Back", data=f"ref_{email}")], 344 | ], 345 | ) 346 | c += 1 347 | break 348 | if c == 0: 349 | await event.edit( 350 | "Expired.", buttons=Button.inline("« Back", data=f"ref_{email}") 351 | ) 352 | 353 | 354 | @bot.on(events.NewMessage(from_users=OWNERS, pattern="^/stats$")) 355 | async def stats(event): 356 | xx = await event.reply("Calculating stats...") 357 | users = await get_all("MAILBOT") 358 | await xx.edit(f"**MailBot stats:**\n\nTotal Users: {len(users)}") 359 | 360 | 361 | @bot.on(events.NewMessage(incoming=True, from_users=OWNERS, pattern="^/broadcast$")) 362 | async def broad(e): 363 | if not e.reply_to_msg_id: 364 | return await e.reply( 365 | "Please use `/broadcast` as reply to the message you want to broadcast." 366 | ) 367 | msg = await e.get_reply_message() 368 | xx = await e.reply("In progress...") 369 | users = await get_all("MAILBOT") 370 | done = error = 0 371 | for i in users: 372 | try: 373 | await bot.send_message( 374 | int(i), 375 | msg.text.format(user=(await bot.get_entity(int(i))).first_name), 376 | file=msg.media, 377 | buttons=msg.buttons, 378 | link_preview=False, 379 | ) 380 | done += 1 381 | except Exception as brd_er: 382 | log.error("Broadcast error:\nChat: %d\nError: %s", int(i), brd_er) 383 | error += 1 384 | await xx.edit("Broadcast completed.\nSuccess: {}\nFailed: {}".format(done, error)) 385 | 386 | 387 | log.info("\nBot has started.\n(c) @xditya\n") 388 | bot.run_until_disconnected() 389 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | python-decouple 2 | telethon==1.24.0 3 | aioredis 4 | hiredis 5 | requests 6 | git+https://github.com/xditya/html-telegraph-poster --------------------------------------------------------------------------------