import json import os import pyperclip import random import requests import string import webbrowser from random_username.generate import generate_username from dataclasses import dataclass from pathlib import Path from tempfile import NamedTemporaryFile from time import sleep from typing import Dict class Account: """Representing a temprary mailbox.""" def __init__(self, id, address, password): self.id_ = id self.address = address self.password = password # Set the JWT jwt = MailTm._make_account_request("token", self.address, self.password) self.auth_headers = { "accept": "application/ld+json", "Content-Type": "application/json", "Authorization": "Bearer {}".format(jwt["token"]) } self.api_address = MailTm.api_address def get_messages(self, page=1): """Download a list of messages currently in the account.""" r = requests.get("{}/messages?page={}".format(self.api_address, page), headers=self.auth_headers, verify=False) messages = [] for message_data in r.json()["hydra:member"]: # recover full message r = requests.get( f"{self.api_address}/messages/{message_data['id']}", headers=self.auth_headers, verify=False) text = r.json()["text"] html = r.json()["html"] # prepare the mssage object messages.append(Message( message_data["id"], message_data["from"], message_data["to"], message_data["subject"], message_data["intro"], text, html, message_data)) return messages def delete_account(self): """Try to delete the account. Returns True if it succeeds.""" r = requests.delete("{}/accounts/{}".format(self.api_address, self.id_), headers=self.auth_headers, verify=False) return r.status_code == 204 def monitor_account(self): """Keep waiting for new messages and open them in the browser.""" while True: print("\nWaiting for new messages...") start = len(self.get_messages()) while len(self.get_messages()) == start: sleep(1) print("New message arrived!") self.get_messages()[0].open_web() @dataclass class Message: """Simple data class that holds a message information.""" id_: str from_: Dict to: Dict subject: str intro: str text: str html: str data: Dict def open_web(self): """Open a temporary html file with the mail inside in the browser.""" with NamedTemporaryFile(mode="w", delete=False, suffix=".html") as f: html = self.html[0].replace("\n", "
").replace("\r", "") message = """ from: {}
to: {}
subject: {}

{} """.format(self.from_, self.to, self.subject, html) f.write(message) f.flush() file_name = f.name open_webbrowser("file://{}".format(file_name)) # Wait a second before deleting the tempfile, so that the # browser can load it safely sleep(1) # os.remove(file_name) def open_webbrowser(link: str) -> None: """Open a url in the browser ignoring error messages.""" saverr = os.dup(2) os.close(2) os.open(os.devnull, os.O_RDWR) try: webbrowser.open(link) finally: os.dup2(saverr, 2) class CouldNotGetAccountException(Exception): """Raised if a POST on /accounts or /authorization_token return a failed status code.""" class InvalidDbAccountException(Exception): """Raised if an account could not be recovered from the db file.""" class MailTm: """A python wrapper for mail.tm web api, which is documented here: https://api.mail.tm/""" api_address = "https://api.mail.tm" db_file = os.path.join(Path.home(), ".pymailtm") def _get_domains_list(self): r = requests.get("{}/domains".format(self.api_address), verify=False) response = r.json() domains = list(map(lambda x: x["domain"], response["hydra:member"])) return domains def get_account(self, password=None): """Create and return a new account.""" username = (generate_username(1)[0]).lower() domain = random.choice(self._get_domains_list()) address = "{}@{}".format(username, domain) if not password: password = self._generate_password(6) response = self._make_account_request("accounts", address, password) account = Account(response["id"], response["address"], password) self._save_account(account) return account def _generate_password(self, length): letters = string.ascii_letters + string.digits return ''.join(random.choice(letters) for i in range(length)) @staticmethod def _make_account_request(endpoint, address, password): account = {"address": address, "password": password} headers = { "accept": "application/ld+json", "Content-Type": "application/json" } r = requests.post("{}/{}".format(MailTm.api_address, endpoint), data=json.dumps(account), headers=headers, verify=False) if r.status_code not in [200, 201]: raise CouldNotGetAccountException() return r.json() def monitor_new_account(self, force_new=False): """Create a new account and monitor it for new messages.""" account = self._open_account(new=force_new) account.monitor_account() def _save_account(self, account: Account): """Save the account data for later use.""" data = { "id": account.id_, "address": account.address, "password": account.password } with open(self.db_file, "w+") as db: json.dump(data, db) def _load_account(self): """Return the last used account.""" with open(self.db_file, "r") as db: data = json.load(db) # send a /me request to ensure the account is there if "address" not in data or "password" not in data or "id" not in data: # No valid db file was found, raise raise InvalidDbAccountException() else: return Account(data["id"], data["address"], data["password"]) def _open_account(self, new=False): """Recover a saved account data, check if it's still there and return that one; otherwise create a new one and return it. :param new: bool - force the creation of a new account""" def _new(): account = self.get_account() print("New account created and copied to clipboard: {}".format(account.address), flush=True) return account if new: account = _new() else: try: account = self._load_account() print("Account recovered and copied to clipboard: {}".format(account.address), flush=True) except Exception: account = _new() pyperclip.copy(account.address) print("") return account def browser_login(self, new=False): """Print login credentials and open the login page in the browser.""" account = self._open_account(new=new) print("\nAccount credentials:") print("\nEmail: {}".format(account.address)) print("Password: {}\n".format(account.password)) open_webbrowser("https://mail.tm/") sleep(1) # Allow for the output of webbrowser to arrive