From 3615ceb2cb3b8043934a113c2e9a3606c16d770f Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 5 Aug 2025 21:55:30 +0400 Subject: [PATCH 01/10] upd: .gitignore --- .gitignore | 204 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 203 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index ed8ebf5..38667da 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,203 @@ -__pycache__ \ No newline at end of file +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[codz] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py.cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# UV +# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +uv.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock +#poetry.toml + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python. +# https://pdm-project.org/en/latest/usage/project/#working-with-version-control +#pdm.lock +#pdm.toml +.pdm-python +.pdm-build/ + +# pixi +# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control. +#pixi.lock +# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one +# in the .venv directory. It is recommended not to include this directory in version control. +.pixi + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.envrc +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +# Abstra +# Abstra is an AI-powered process automation framework. +# Ignore directories containing user credentials, local state, and settings. +# Learn more at https://abstra.io/docs +.abstra/ + +# Visual Studio Code +# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore +# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore +# and can be added to the global gitignore or merged into this file. However, if you prefer, +# you could uncomment the following to ignore the entire vscode folder +# .vscode/ + +# Ruff stuff: +.ruff_cache/ + +# PyPI configuration file +.pypirc + +# Marimo +marimo/_static/ +marimo/_lsp/ +__marimo__/ + +# Streamlit +.streamlit/secrets.toml From 28b6679b71097b242ab6f88a166dd3253349482d Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 5 Aug 2025 21:57:57 +0400 Subject: [PATCH 02/10] upd: license --- LICENSE | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/LICENSE b/LICENSE index 63b4b68..2063c1e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) [year] [fullname] +Copyright (c) 2025 MainSilent, iamlostshe Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. From 157eb02e353415e63ab0f822af84a31d355d00db Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 5 Aug 2025 22:07:43 +0400 Subject: [PATCH 03/10] setup.py -> pyproject.toml --- README.md | 10 +--------- pyproject.toml | 24 ++++++++++++++++++++++++ setup.py | 27 --------------------------- 3 files changed, 25 insertions(+), 36 deletions(-) create mode 100644 pyproject.toml delete mode 100644 setup.py diff --git a/README.md b/README.md index a27a4bb..30586d3 100644 --- a/README.md +++ b/README.md @@ -9,16 +9,8 @@ MailTm is a free temporary mail service, This library is useful for automation t ## Installation -Windows: - -``` -pip install MailTm -``` - -Linux/Mac OS: - ``` -pip3 install MailTm +pip install git+https://github.com/iamlostshe/mail-tm ``` ## Example diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..b5c8f4b --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,24 @@ +[project] +name = "mail-tm" +version="0.0.90" +description="Temporary Email" +readme = "README.md" +requires-python = ">=3.13" +authors = [ + {name="MainSilent"}, + {name="iamlostshe", email="vanamelcikov7275@gmail.com"} +] +dependencies = [ + "requests>=2.32.4", +] +keywords=['mail', 'email', 'temporary mail', 'temporary email', 'mailtm'] +classifiers=[ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", +] + +[project.urls] +Homepage = "https://github.com/iamlostshe/mail-tm" +Repository = "https://github.com/iamlostshe/mail-tm.git" +"Bug Tracker" = "https://github.com/iamlostshe/mail-tm/issues" diff --git a/setup.py b/setup.py deleted file mode 100644 index 332a2fb..0000000 --- a/setup.py +++ /dev/null @@ -1,27 +0,0 @@ -import setuptools - -with open("README.md", "r") as fh: - long_description = fh.read() - -setuptools.setup( - name="MailTm", - version="0.0.81", - author="MainSilent", - description="Temporary Email", - long_description=long_description, - long_description_content_type="text/markdown", - keywords=['mail', 'email', 'temporary mail', 'temporary email', 'mailtm'], - url="https://github.com/MainSilent/MailTm", - project_urls={ - "Bug Tracker": "https://github.com/MainSilent/MailTm/issues", - }, - classifiers=[ - "Programming Language :: Python :: 3", - "License :: OSI Approved :: MIT License", - "Operating System :: OS Independent", - ], - package_dir={"": "src"}, - packages=setuptools.find_packages(where="src"), - python_requires=">=3.6", - install_requires=['requests'] -) From 33d1bdb5b6b225aba6310dc94560a32ca90ff515 Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 5 Aug 2025 22:11:09 +0400 Subject: [PATCH 04/10] add: ruff linter --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index b5c8f4b..35822e7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,3 +22,6 @@ classifiers=[ Homepage = "https://github.com/iamlostshe/mail-tm" Repository = "https://github.com/iamlostshe/mail-tm.git" "Bug Tracker" = "https://github.com/iamlostshe/mail-tm/issues" + +[tool.ruff.lint] +select = ["ALL"] From a6b908a15218792da718a0da9c00b202916c6b69 Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 5 Aug 2025 22:11:37 +0400 Subject: [PATCH 05/10] lint: autolint --- src/mailtm/__init__.py | 2 +- src/mailtm/email.py | 34 ++++++++++++++++++---------------- src/mailtm/message.py | 20 ++++++++++---------- 3 files changed, 29 insertions(+), 27 deletions(-) diff --git a/src/mailtm/__init__.py b/src/mailtm/__init__.py index 845c041..2ebe67e 100644 --- a/src/mailtm/__init__.py +++ b/src/mailtm/__init__.py @@ -1 +1 @@ -from .email import Email \ No newline at end of file +from .email import Email diff --git a/src/mailtm/email.py b/src/mailtm/email.py index 94bf592..c4e198d 100644 --- a/src/mailtm/email.py +++ b/src/mailtm/email.py @@ -1,14 +1,16 @@ -import json -import string import random +import string + import requests + from .message import Listen + def username_gen(length=24, chars= string.ascii_letters + string.digits): - return ''.join(random.choice(chars) for _ in range(length)) + return "".join(random.choice(chars) for _ in range(length)) def password_gen(length=8, chars= string.ascii_letters + string.digits + string.punctuation): - return ''.join(random.choice(chars) for _ in range(length)) + return "".join(random.choice(chars) for _ in range(length)) class Email(Listen): token = "" @@ -27,9 +29,9 @@ def domains(self): try: data = response.json() - for domain in data['hydra:member']: - if domain['isActive']: - self.domain = domain['domain'] + for domain in data["hydra:member"]: + if domain["isActive"]: + self.domain = domain["domain"] return True raise Exception("No Domain") @@ -44,15 +46,15 @@ def register(self, username=None, password=None, domain=None): url = "https://api.mail.tm/accounts" payload = { "address": f"{username}@{self.domain}", - "password": password + "password": password, } - headers = { 'Content-Type': 'application/json' } + headers = { "Content-Type": "application/json" } response = self.session.post(url, headers=headers, json=payload) response.raise_for_status() data = response.json() try: - self.address = data['address'] + self.address = data["address"] except: self.address = f"{username}@{self.domain}" @@ -65,21 +67,21 @@ def get_token(self, password): url = "https://api.mail.tm/token" payload = { "address": self.address, - "password": password + "password": password, } - headers = {'Content-Type': 'application/json'} + headers = {"Content-Type": "application/json"} response = self.session.post(url, headers=headers, json=payload) response.raise_for_status() try: - self.token = response.json()['token'] + self.token = response.json()["token"] except: raise Exception("Failed to get token") - + if __name__ == "__main__": def listener(message): - print("\nSubject: " + message['subject']) - print("Content: " + message['text'] if message['text'] else message['html']) + print("\nSubject: " + message["subject"]) + print("Content: " + message["text"] if message["text"] else message["html"]) # Get Domains test = Email() diff --git a/src/mailtm/message.py b/src/mailtm/message.py index f22499a..042c810 100644 --- a/src/mailtm/message.py +++ b/src/mailtm/message.py @@ -1,26 +1,26 @@ -import json import time from threading import Thread + class Listen: listen = False message_ids = [] def message_list(self): url = "https://api.mail.tm/messages" - headers = { 'Authorization': 'Bearer ' + self.token } + headers = { "Authorization": "Bearer " + self.token } response = self.session.get(url, headers=headers) response.raise_for_status() - + data = response.json() return [ - msg for i, msg in enumerate(data['hydra:member']) - if data['hydra:member'][i]['id'] not in self.message_ids + msg for i, msg in enumerate(data["hydra:member"]) + if data["hydra:member"][i]["id"] not in self.message_ids ] def message(self, idx): url = "https://api.mail.tm/messages/" + idx - headers = { 'Authorization': 'Bearer ' + self.token } + headers = { "Authorization": "Bearer " + self.token } response = self.session.get(url, headers=headers) response.raise_for_status() return response.json() @@ -28,8 +28,8 @@ def message(self, idx): def run(self): while self.listen: for message in self.message_list(): - self.message_ids.append(message['id']) - message = self.message(message['id']) + self.message_ids.append(message["id"]) + message = self.message(message["id"]) self.listener(message) time.sleep(self.interval) @@ -45,7 +45,7 @@ def start(self, listener, interval=3): # Start listening thread self.thread = Thread(target=self.run) self.thread.start() - + def stop(self): self.listen = False - self.thread.join() \ No newline at end of file + self.thread.join() From 63c7cece12005b043e38d515cd62d6da506eb13f Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 5 Aug 2025 22:43:24 +0400 Subject: [PATCH 06/10] lint: handlint --- src/mailtm/__init__.py | 4 ++ src/mailtm/consts.py | 3 ++ src/mailtm/email.py | 113 ++++++++++++++++------------------------- src/mailtm/message.py | 39 +++++++------- 4 files changed, 72 insertions(+), 87 deletions(-) create mode 100644 src/mailtm/consts.py diff --git a/src/mailtm/__init__.py b/src/mailtm/__init__.py index 2ebe67e..d0b8ce8 100644 --- a/src/mailtm/__init__.py +++ b/src/mailtm/__init__.py @@ -1 +1,5 @@ +"""Initializing the library.""" + from .email import Email + +__all__ = ("Email",) diff --git a/src/mailtm/consts.py b/src/mailtm/consts.py new file mode 100644 index 0000000..06aabb7 --- /dev/null +++ b/src/mailtm/consts.py @@ -0,0 +1,3 @@ +"""Some constant values.""" + +BASE_URL = "https://api.mail.tm/" diff --git a/src/mailtm/email.py b/src/mailtm/email.py index c4e198d..500e1a0 100644 --- a/src/mailtm/email.py +++ b/src/mailtm/email.py @@ -1,99 +1,74 @@ +"""The main module for interacting with EMail.""" + import random import string import requests +from .consts import BASE_URL from .message import Listen -def username_gen(length=24, chars= string.ascii_letters + string.digits): - return "".join(random.choice(chars) for _ in range(length)) +def username_gen( + length: int = 24, + chars: str = string.ascii_letters + string.digits, +) -> str: + """Username generation.""" + return "".join(random.choice(chars) for _ in range(length)) # noqa: S311 + + +def password_gen( + length: int = 8, + chars: str = string.ascii_letters + string.digits + string.punctuation, +) -> str: + """Password generation.""" + return "".join(random.choice(chars) for _ in range(length)) # noqa: S311 -def password_gen(length=8, chars= string.ascii_letters + string.digits + string.punctuation): - return "".join(random.choice(chars) for _ in range(length)) class Email(Listen): - token = "" - domain = "" - address = "" - session = requests.Session() + """The class module for interacting with EMail.""" - def __init__(self): - if not self.domains(): - print("Failed to get domains") + def __init__(self) -> None: + self.domains() + self.session = requests.Session(headers={"Content-Type": "application/json"}) - def domains(self): - url = "https://api.mail.tm/domains" + def domains(self) -> None: + url = f"{BASE_URL}domains" response = self.session.get(url) response.raise_for_status() - try: - data = response.json() - for domain in data["hydra:member"]: - if domain["isActive"]: - self.domain = domain["domain"] - return True - - raise Exception("No Domain") - except: - return False - - def register(self, username=None, password=None, domain=None): - self.domain = domain if domain else self.domain - username = username if username else username_gen() - password = password if password else password_gen() - - url = "https://api.mail.tm/accounts" + data = response.json() + for domain in data["hydra:member"]: + if domain["isActive"]: + self.domain = domain["domain"] + + def register( + self, + username: str | None = username_gen(), + password: str | None = password_gen(), + domain: str | None = None, + ) -> None: + if domain: + self.domain = domain + + url = f"{BASE_URL}accounts" + self.address = f"{username}@{self.domain}" payload = { - "address": f"{username}@{self.domain}", + "address": self.address, "password": password, } - headers = { "Content-Type": "application/json" } - response = self.session.post(url, headers=headers, json=payload) + response = self.session.post(url, json=payload) response.raise_for_status() - data = response.json() - try: - self.address = data["address"] - except: - self.address = f"{username}@{self.domain}" - self.get_token(password) - if not self.address: - raise Exception("Failed to make an address") - def get_token(self, password): - url = "https://api.mail.tm/token" + url = f"{BASE_URL}token" payload = { "address": self.address, "password": password, } - headers = {"Content-Type": "application/json"} - response = self.session.post(url, headers=headers, json=payload) + response = self.session.post(url, json=payload) response.raise_for_status() - try: - self.token = response.json()["token"] - except: - raise Exception("Failed to get token") - - -if __name__ == "__main__": - def listener(message): - print("\nSubject: " + message["subject"]) - print("Content: " + message["text"] if message["text"] else message["html"]) - - # Get Domains - test = Email() - print("\nDomain: " + test.domain) - - # Make new email address - test.register() - print("\nEmail Adress: " + str(test.address)) - - # Start listening - test.start(listener) - print("\nWaiting for new emails...") - # Stop listening - # test.stop() + self.token = response.json()["token"] diff --git a/src/mailtm/message.py b/src/mailtm/message.py index 042c810..207b2e1 100644 --- a/src/mailtm/message.py +++ b/src/mailtm/message.py @@ -1,40 +1,43 @@ import time from threading import Thread +from .consts import BASE_URL + class Listen: - listen = False - message_ids = [] + def __init__(self) -> None: + self.listen = False + self.message_ids = [] - def message_list(self): - url = "https://api.mail.tm/messages" - headers = { "Authorization": "Bearer " + self.token } + def message_list(self) -> list: + url = f"{BASE_URL}messages" + headers = {"Authorization": "Bearer " + self.token} response = self.session.get(url, headers=headers) response.raise_for_status() data = response.json() - return [ - msg for i, msg in enumerate(data["hydra:member"]) - if data["hydra:member"][i]["id"] not in self.message_ids - ] - - def message(self, idx): - url = "https://api.mail.tm/messages/" + idx - headers = { "Authorization": "Bearer " + self.token } + return [ + msg + for i, msg in enumerate(data["hydra:member"]) + if data["hydra:member"][i]["id"] not in self.message_ids + ] + + def message(self, idx: str) -> dict | list: + url = f"{BASE_URL}messages/{idx}" + headers = {"Authorization": "Bearer " + self.token} response = self.session.get(url, headers=headers) response.raise_for_status() return response.json() - def run(self): + def run(self) -> None: while self.listen: for message in self.message_list(): self.message_ids.append(message["id"]) - message = self.message(message["id"]) - self.listener(message) + self.listener(self.message(message["id"])) time.sleep(self.interval) - def start(self, listener, interval=3): + def start(self, listener, interval: int = 3): if self.listen: self.stop() @@ -46,6 +49,6 @@ def start(self, listener, interval=3): self.thread = Thread(target=self.run) self.thread.start() - def stop(self): + def stop(self) -> None: self.listen = False self.thread.join() From 112a6b47ecebffc279d01d6f2647b9c7a5ffea29 Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 6 Aug 2025 00:52:16 +0400 Subject: [PATCH 07/10] requests -> aiohttp --- README.md | 56 ++++++++++++++++++++--------------- pyproject.toml | 2 +- src/mailtm/consts.py | 6 ++++ src/mailtm/email.py | 46 ++++++++++++++++------------- src/mailtm/message.py | 69 ++++++++++++++++++++----------------------- 5 files changed, 97 insertions(+), 82 deletions(-) diff --git a/README.md b/README.md index 30586d3..b6f0d5d 100644 --- a/README.md +++ b/README.md @@ -1,46 +1,56 @@ -# MailTM API Wrapper +# mail-tm API wrapper -[![Downloads](https://pepy.tech/badge/mailtm)](https://pepy.tech/project/mailtm) +Mail-tm is a free temporary mail service, This library is useful for automation tasks such as making accounts that needs email verification. -[![Downloads](https://pepy.tech/badge/mailtm/month)](https://pepy.tech/project/mailtm) -[![Downloads](https://pepy.tech/badge/mailtm/week)](https://pepy.tech/project/mailtm) - -MailTm is a free temporary mail service, This library is useful for automation tasks such as making accounts that needs email verification. +> There are plans to write a custom server for deploying personal temporary email on your server. ## Installation -``` +```bash pip install git+https://github.com/iamlostshe/mail-tm ``` ## Example ```python +import asyncio + from mailtm import Email -def listener(message): - print("\nSubject: " + message['subject']) - print("Content: " + message['text'] if message['text'] else message['html']) -# Get Domains -test = Email() -print("\nDomain: " + test.domain) +async def main() -> None: + """Start msg cycle.""" + def listener(message: dict) -> None: + print("\nSubject: " + message["subject"]) + print("Content: " + message["text"] if message["text"] else message["html"]) + + # Get Domains + test = Email() + await test.init() + print("\nDomain: " + test.domain) -# Make new email address -test.register() -print("\nEmail Adress: " + str(test.address)) + # Make new email address + await test.register() + print("\nEmail Adress: " + str(test.address)) + + # Start listening + await test.start(listener) + print("\nWaiting for new emails...") + + +if __name__ == "__main__": + asyncio.run(main()) -# Start listening -test.start(listener) -print("\nWaiting for new emails...") ``` # Documentation -API: https://mail.tm +API: [api.mail.tm](https://api.mail.tm/) + +- `register(username: str | None = username_gen(), password: str | None = password_gen(), domain: str | None = None)`: -`register(username=None, password=None, domain=None)` | Make an email account with random credentials, You can also pass a username, password and domain to use the same account. +Make an email account with random credentials, You can also pass a username, password and domain to use the same account. -`start(listener, interval=3)` | Start listening for new emails, Interval means how many seconds takes to sync, And you also need to pass a function for `listener`, This function gets called when new email arrive. +- `start(listener: any, interval: int = 3) -> None`: -`stop()` | Stop listening for new emails. +Start listening for new emails, Interval means how many seconds takes to sync, And you also need to pass a function for `listener`, This function gets called when new email arrive. diff --git a/pyproject.toml b/pyproject.toml index 35822e7..1e2ac85 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ authors = [ {name="iamlostshe", email="vanamelcikov7275@gmail.com"} ] dependencies = [ - "requests>=2.32.4", + "aiohttp>=3.12.14", ] keywords=['mail', 'email', 'temporary mail', 'temporary email', 'mailtm'] classifiers=[ diff --git a/src/mailtm/consts.py b/src/mailtm/consts.py index 06aabb7..f2bc753 100644 --- a/src/mailtm/consts.py +++ b/src/mailtm/consts.py @@ -1,3 +1,9 @@ """Some constant values.""" BASE_URL = "https://api.mail.tm/" + +USERNAME_ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789" +PASSWORD_ALPHABET = ( + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" # noqa: S105 + "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~" +) diff --git a/src/mailtm/email.py b/src/mailtm/email.py index 500e1a0..756e32d 100644 --- a/src/mailtm/email.py +++ b/src/mailtm/email.py @@ -1,17 +1,16 @@ """The main module for interacting with EMail.""" import random -import string -import requests +from aiohttp import ClientSession -from .consts import BASE_URL +from .consts import BASE_URL, PASSWORD_ALPHABET, USERNAME_ALPHABET from .message import Listen def username_gen( length: int = 24, - chars: str = string.ascii_letters + string.digits, + chars: str = USERNAME_ALPHABET, ) -> str: """Username generation.""" return "".join(random.choice(chars) for _ in range(length)) # noqa: S311 @@ -19,7 +18,7 @@ def username_gen( def password_gen( length: int = 8, - chars: str = string.ascii_letters + string.digits + string.punctuation, + chars: str = PASSWORD_ALPHABET, ) -> str: """Password generation.""" return "".join(random.choice(chars) for _ in range(length)) # noqa: S311 @@ -28,47 +27,52 @@ def password_gen( class Email(Listen): """The class module for interacting with EMail.""" - def __init__(self) -> None: - self.domains() - self.session = requests.Session(headers={"Content-Type": "application/json"}) + async def init(self) -> None: + """Init temporary Email class.""" + self.session = ClientSession(headers={"Content-Type": "application/json"}) + await self.domains() - def domains(self) -> None: + async def domains(self) -> None: + """Init Email domains.""" url = f"{BASE_URL}domains" - response = self.session.get(url) - response.raise_for_status() - data = response.json() + async with self.session.get(url) as response: + data = await response.json() + for domain in data["hydra:member"]: if domain["isActive"]: self.domain = domain["domain"] - def register( + async def register( self, username: str | None = username_gen(), password: str | None = password_gen(), domain: str | None = None, ) -> None: + """Mail registration.""" if domain: self.domain = domain - url = f"{BASE_URL}accounts" self.address = f"{username}@{self.domain}" + + url = f"{BASE_URL}accounts" payload = { "address": self.address, "password": password, } - response = self.session.post(url, json=payload) - response.raise_for_status() - self.get_token(password) + await self.session.post(url, json=payload) + await self.get_token(password) - def get_token(self, password): + async def get_token(self, password: str) -> None: + """Get token.""" url = f"{BASE_URL}token" payload = { "address": self.address, "password": password, } - response = self.session.post(url, json=payload) - response.raise_for_status() - self.token = response.json()["token"] + async with self.session.post(url, json=payload) as response: + token = (await response.json())["token"] + + self.session.headers.update({"Authorization": f"Bearer {token}"}) diff --git a/src/mailtm/message.py b/src/mailtm/message.py index 207b2e1..9131caf 100644 --- a/src/mailtm/message.py +++ b/src/mailtm/message.py @@ -1,54 +1,49 @@ -import time -from threading import Thread +"""Manage messages module.""" + +import asyncio from .consts import BASE_URL class Listen: + """Listener class.""" + def __init__(self) -> None: - self.listen = False + """Init class.""" self.message_ids = [] - def message_list(self) -> list: + async def message_list(self) -> list: + """Get a list of messages.""" url = f"{BASE_URL}messages" - headers = {"Authorization": "Bearer " + self.token} - response = self.session.get(url, headers=headers) - response.raise_for_status() - data = response.json() + async with self.session.get(url) as response: + data = await response.json() + return [ msg for i, msg in enumerate(data["hydra:member"]) if data["hydra:member"][i]["id"] not in self.message_ids ] - def message(self, idx: str) -> dict | list: + async def message(self, idx: str) -> dict: + """Get the text of the message.""" url = f"{BASE_URL}messages/{idx}" - headers = {"Authorization": "Bearer " + self.token} - response = self.session.get(url, headers=headers) - response.raise_for_status() - return response.json() - - def run(self) -> None: - while self.listen: - for message in self.message_list(): - self.message_ids.append(message["id"]) - self.listener(self.message(message["id"])) - - time.sleep(self.interval) - - def start(self, listener, interval: int = 3): - if self.listen: - self.stop() - - self.listener = listener - self.interval = interval - self.listen = True - - # Start listening thread - self.thread = Thread(target=self.run) - self.thread.start() - - def stop(self) -> None: - self.listen = False - self.thread.join() + async with self.session.get(url) as response: + return await response.json() + + async def run(self, listener: any) -> None: + """Run a message check.""" + for message in await self.message_list(): + self.message_ids.append(message["id"]) + listener(await self.message(message["id"])) + + async def start(self, listener: any, interval: int = 3) -> None: + """Run a message check cycle.""" + while True: + try: + await self.run(listener) + await asyncio.sleep(interval) + except KeyboardInterrupt: + print("Stop working") # noqa: T201 + await self.session.close() + break From e99b6c4d545ae2500055fac4096f5ff6ff30aee9 Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 6 Aug 2025 01:01:54 +0400 Subject: [PATCH 08/10] add: dependabot --- .github/dependabot.yml | 6 ++++++ README.md | 4 +++- 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..b38df29 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "pip" + directory: "/" + schedule: + interval: "daily" diff --git a/README.md b/README.md index b6f0d5d..9a9e980 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,11 @@ # mail-tm API wrapper -Mail-tm is a free temporary mail service, This library is useful for automation tasks such as making accounts that needs email verification. +> Fork of [MainSilent/MailTm](https://github.com/MainSilent/MailTm). > There are plans to write a custom server for deploying personal temporary email on your server. +Mail-tm is a free temporary mail service, This library is useful for automation tasks such as making accounts that needs email verification. + ## Installation ```bash From bda736895102a53c68278bac8892ba83872b83f8 Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 6 Aug 2025 01:02:54 +0400 Subject: [PATCH 09/10] add: vscode settings --- .vscode/settings.json | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..f5e4817 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,23 @@ +{ + // Linter settings + "[python]": { + "editor.formatOnPaste": true, + "editor.formatOnSave": true, + "editor.formatOnType": true, + "editor.defaultFormatter": "charliermarsh.ruff" + }, + + // Files view + "files.autoSave": "afterDelay", + "files.exclude": { + "**/__pycache__": true, + "**/.venv": true, + "**/.ruff_cache": true, + "**/.mypy_cache": true, + ".python-version": true, + "uv.lock": true, + }, + + // Editor + "editor.rulers": [88], +} From a3a44ff1a9b9a387322367eee2aca3af6debe3b5 Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 6 Aug 2025 02:53:45 +0400 Subject: [PATCH 10/10] upd: base_url | domain usage --- README.md | 9 +++++---- src/mailtm/consts.py | 2 +- src/mailtm/email.py | 22 ++++++++++++---------- src/mailtm/message.py | 6 ++---- 4 files changed, 20 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 9a9e980..a986873 100644 --- a/README.md +++ b/README.md @@ -22,18 +22,20 @@ from mailtm import Email async def main() -> None: """Start msg cycle.""" + def listener(message: dict) -> None: print("\nSubject: " + message["subject"]) print("Content: " + message["text"] if message["text"] else message["html"]) - # Get Domains + # Get base url and domains test = Email() await test.init() - print("\nDomain: " + test.domain) + print("\nBase url:", test.base_url) + print("Domain:", test.domain) # Make new email address await test.register() - print("\nEmail Adress: " + str(test.address)) + print("\nEmail Adress:", test.address) # Start listening await test.start(listener) @@ -42,7 +44,6 @@ async def main() -> None: if __name__ == "__main__": asyncio.run(main()) - ``` # Documentation diff --git a/src/mailtm/consts.py b/src/mailtm/consts.py index f2bc753..de8c005 100644 --- a/src/mailtm/consts.py +++ b/src/mailtm/consts.py @@ -1,6 +1,6 @@ """Some constant values.""" -BASE_URL = "https://api.mail.tm/" +BASE_URLS = ["https://api.mail.tm/", "https://api.mail.gw/"] USERNAME_ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789" PASSWORD_ALPHABET = ( diff --git a/src/mailtm/email.py b/src/mailtm/email.py index 756e32d..24741d8 100644 --- a/src/mailtm/email.py +++ b/src/mailtm/email.py @@ -4,7 +4,7 @@ from aiohttp import ClientSession -from .consts import BASE_URL, PASSWORD_ALPHABET, USERNAME_ALPHABET +from .consts import BASE_URLS, PASSWORD_ALPHABET, USERNAME_ALPHABET from .message import Listen @@ -27,21 +27,23 @@ def password_gen( class Email(Listen): """The class module for interacting with EMail.""" - async def init(self) -> None: + async def init(self, base_url: str | None = random.choice(BASE_URLS)) -> None: # noqa: S311 """Init temporary Email class.""" - self.session = ClientSession(headers={"Content-Type": "application/json"}) + self.base_url = base_url + self.session = ClientSession( + base_url=self.base_url, + headers={"Content-Type": "application/json"}, + ) await self.domains() async def domains(self) -> None: """Init Email domains.""" - url = f"{BASE_URL}domains" + url = "domains" async with self.session.get(url) as response: - data = await response.json() + data = (await response.json())["hydra:member"] - for domain in data["hydra:member"]: - if domain["isActive"]: - self.domain = domain["domain"] + self.domain = random.choice([d for d in data if d["isActive"]])["domain"] # noqa: S311 async def register( self, @@ -55,7 +57,7 @@ async def register( self.address = f"{username}@{self.domain}" - url = f"{BASE_URL}accounts" + url = "accounts" payload = { "address": self.address, "password": password, @@ -66,7 +68,7 @@ async def register( async def get_token(self, password: str) -> None: """Get token.""" - url = f"{BASE_URL}token" + url = "token" payload = { "address": self.address, "password": password, diff --git a/src/mailtm/message.py b/src/mailtm/message.py index 9131caf..8b0199b 100644 --- a/src/mailtm/message.py +++ b/src/mailtm/message.py @@ -2,8 +2,6 @@ import asyncio -from .consts import BASE_URL - class Listen: """Listener class.""" @@ -14,7 +12,7 @@ def __init__(self) -> None: async def message_list(self) -> list: """Get a list of messages.""" - url = f"{BASE_URL}messages" + url = "messages" async with self.session.get(url) as response: data = await response.json() @@ -27,7 +25,7 @@ async def message_list(self) -> list: async def message(self, idx: str) -> dict: """Get the text of the message.""" - url = f"{BASE_URL}messages/{idx}" + url = f"messages/{idx}" async with self.session.get(url) as response: return await response.json()