addproject
This commit is contained in:
226
base_framework/public_tools/pymailtm/pymailtm.py
Normal file
226
base_framework/public_tools/pymailtm/pymailtm.py
Normal file
@@ -0,0 +1,226 @@
|
||||
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", "<br>").replace("\r", "")
|
||||
message = """<html>
|
||||
<head></head>
|
||||
<body>
|
||||
<b>from:</b> {}<br>
|
||||
<b>to:</b> {}<br>
|
||||
<b>subject:</b> {}<br><br>
|
||||
{}</body>
|
||||
</html>""".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
|
||||
Reference in New Issue
Block a user