diff --git a/README.md b/README.md index 50e809c..7cb77f2 100644 --- a/README.md +++ b/README.md @@ -494,5 +494,3 @@ MailThunder/ ## License This project is licensed under the [MIT License](LICENSE). - -Copyright (c) 2021 JE-Chen diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..95aa3a1 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,86 @@ +# MailThunder Documentation + +[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](../LICENSE) +[![Python](https://img.shields.io/badge/python-3.9%2B-blue.svg)](https://www.python.org/) +[![PyPI](https://img.shields.io/pypi/v/je_mail_thunder)](https://pypi.org/project/je-mail-thunder/) +[![Read the Docs](https://readthedocs.org/projects/mailthunder/badge/?version=latest)](https://mailthunder.readthedocs.io/) +[![Main README](https://img.shields.io/badge/Main-README-green.svg)](../README.md) +[![GitHub](https://img.shields.io/badge/GitHub-Repository-black.svg)](https://github.com/Integration-Automation/MailThunder) + +This directory contains the [Read the Docs](https://readthedocs.io/) Sphinx documentation source for **MailThunder**. + +For the full project overview, see the [Main README](../README.md). + +--- + +## Documentation Structure + +``` +docs/ + README.md # This file + Makefile # Unix build script + make.bat # Windows build script + requirements.txt # Sphinx dependencies + source/ + conf.py # Sphinx configuration + index.rst # Root page (toctree entry point) + docs/ + Eng/ # English documentation + eng_index.rst # Overview & architecture + installation.rst # Installation guide + authentication.rst # Authentication setup + send_google_mail.rst # Sending emails (SMTP) + read_google_mail.rst # Reading emails (IMAP) + scripting_engine.rst # JSON scripting engine + project_templates.rst # Project template scaffolding + cli.rst # Command-line interface + socket_server.rst # TCP socket server + package_manager.rst # Dynamic package loader + logging.rst # Logging system + exceptions.rst # Custom exceptions + Zh/ # 繁體中文文件 + zh_index.rst # 總覽與架構 + installation.rst # 安裝指南 + authentication.rst # 認證設定 + send_google_mail.rst # 寄送郵件 (SMTP) + read_google_mail.rst # 讀取郵件 (IMAP) + scripting_engine.rst # JSON 腳本引擎 + project_templates.rst # 專案模板 + cli.rst # 命令列介面 + socket_server.rst # Socket 伺服器 + package_manager.rst # 套件管理器 + logging.rst # 日誌記錄 + exceptions.rst # 例外處理 + API/ # API reference + api_index.rst # Public exports & module map + smtp_api.rst # SMTPWrapper API + imap_api.rst # IMAPWrapper API + executor_api.rst # Executor API + utils_api.rst # Utility functions API +``` + +--- + +## Building Locally + +```bash +pip install -r docs/requirements.txt +cd docs +make html # Linux / macOS +make.bat html # Windows +``` + +Open `docs/build/html/index.html` in your browser. + +--- + +## Read the Docs Configuration + +Configured via `.readthedocs.yaml` in the project root: + +| Setting | Value | +|---------|-------| +| Build OS | `ubuntu-22.04` | +| Python | `3.11` | +| Sphinx config | `docs/source/conf.py` | +| Theme | `sphinx-rtd-theme` | diff --git a/docs/source/conf.py b/docs/source/conf.py index 6bb341d..487ce71 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -1,15 +1,10 @@ # Configuration file for the Sphinx documentation builder. # -# This file only contains a selection of the most common options. For a full -# list see the documentation: +# For the full list of configuration options see: # https://www.sphinx-doc.org/en/master/usage/configuration.html # -- Path setup -------------------------------------------------------------- -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# import os import sys from pathlib import Path @@ -26,27 +21,22 @@ # -- General configuration --------------------------------------------------- -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named "sphinx.ext.*") or your custom -# ones. extensions = [] -# Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This pattern also affects html_static_path and html_extra_path. exclude_patterns = [] # -- Options for HTML output ------------------------------------------------- -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# html_theme = "sphinx_rtd_theme" -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ["_static"] + +# -- Options for sphinx_rtd_theme -------------------------------------------- + +html_theme_options = { + "navigation_depth": 4, + "collapse_navigation": False, + "titles_only": False, +} diff --git a/docs/source/docs/API/api_index.rst b/docs/source/docs/API/api_index.rst index 3c30c88..7bfa4d3 100644 --- a/docs/source/docs/API/api_index.rst +++ b/docs/source/docs/API/api_index.rst @@ -1,9 +1,86 @@ -MailThunder API +API Reference +============= + +This section provides the complete API reference for all public classes, methods, +and functions in MailThunder. + +Public Exports +-------------- + +All public APIs are accessible from the top-level ``je_mail_thunder`` package: + +.. code-block:: python + + from je_mail_thunder import ( + # SMTP + SMTPWrapper, # SMTP wrapper class + smtp_instance, # Pre-created SMTP instance (or None) + + # IMAP + IMAPWrapper, # IMAP wrapper class + imap_instance, # Pre-created IMAP instance (or None) + + # Authentication + set_mail_thunder_os_environ, # Set auth env vars + get_mail_thunder_os_environ, # Get auth env vars + mail_thunder_content_data_dict, # Global credential dict + is_need_to_save_content, # Check if credentials need saving + read_output_content, # Read mail_thunder_content.json + write_output_content, # Write mail_thunder_content.json + + # Executor + execute_action, # Execute action list + execute_files, # Execute multiple action files + add_command_to_executor, # Register custom commands + + # JSON + read_action_json, # Read JSON action file + + # File Utilities + get_dir_files_as_list, # List directory files + + # Project + create_project_dir, # Scaffold project with templates + ) + ---- -.. toctree:: - :maxdepth: 4 +Module Map +---------- - imap_api.rst - smtp_api.rst +.. list-table:: + :header-rows: 1 + :widths: 40 60 + * - Import Path + - Description + * - ``je_mail_thunder.smtp.smtp_wrapper`` + - ``SMTPWrapper`` class, ``smtp_instance`` + * - ``je_mail_thunder.imap.imap_wrapper`` + - ``IMAPWrapper`` class, ``imap_instance`` + * - ``je_mail_thunder.utils.executor.action_executor`` + - ``Executor`` class, ``execute_action()``, ``execute_files()``, ``add_command_to_executor()`` + * - ``je_mail_thunder.utils.save_mail_user_content.save_on_env`` + - ``set_mail_thunder_os_environ()``, ``get_mail_thunder_os_environ()`` + * - ``je_mail_thunder.utils.save_mail_user_content.mail_thunder_content_save`` + - ``read_output_content()``, ``write_output_content()`` + * - ``je_mail_thunder.utils.save_mail_user_content.mail_thunder_content_data`` + - ``mail_thunder_content_data_dict``, ``is_need_to_save_content()`` + * - ``je_mail_thunder.utils.json.json_file`` + - ``read_action_json()``, ``write_action_json()`` + * - ``je_mail_thunder.utils.json_format.json_process`` + - ``reformat_json()`` + * - ``je_mail_thunder.utils.file_process.get_dir_file_list`` + - ``get_dir_files_as_list()`` + * - ``je_mail_thunder.utils.project.create_project_structure`` + - ``create_project_dir()`` + * - ``je_mail_thunder.utils.package_manager.package_manager_class`` + - ``PackageManager`` class, ``package_manager`` + * - ``je_mail_thunder.utils.socket_server.mail_thunder_socket_server`` + - ``TCPServer``, ``TCPServerHandler``, ``start_autocontrol_socket_server()`` + * - ``je_mail_thunder.utils.logging.loggin_instance`` + - ``mail_thunder_logger`` + * - ``je_mail_thunder.utils.exception.exceptions`` + - All custom exception classes + * - ``je_mail_thunder.utils.exception.exception_tags`` + - Error tag strings diff --git a/docs/source/docs/API/executor_api.rst b/docs/source/docs/API/executor_api.rst new file mode 100644 index 0000000..73501fb --- /dev/null +++ b/docs/source/docs/API/executor_api.rst @@ -0,0 +1,223 @@ +Executor API +============ + +**Module:** ``je_mail_thunder.utils.executor.action_executor`` + +The Executor is the core engine that powers MailThunder's JSON scripting system. +It maps command names to Python callables and executes action lists sequentially. + +---- + +Executor Class +-------------- + +.. code-block:: python + + class Executor: + """ + Action executor that maps command names to callables. + + The event_dict contains all registered commands, including: + - Built-in MailThunder commands (MT_smtp_*, MT_imap_*, etc.) + - Python built-in functions (print, len, range, etc.) + - Dynamically loaded package members + - User-registered custom commands + """ + + def __init__(self): + self.event_dict: dict = {...} + +**Pre-registered commands in ``event_dict``:** + +.. list-table:: + :header-rows: 1 + :widths: 40 60 + + * - Command + - Bound To + * - ``MT_smtp_later_init`` + - ``smtp_instance.later_init`` + * - ``MT_smtp_create_message_with_attach_and_send`` + - ``smtp_instance.create_message_with_attach_and_send`` + * - ``MT_smtp_create_message_and_send`` + - ``smtp_instance.create_message_and_send`` + * - ``smtp_quit`` + - ``smtp_instance.quit`` + * - ``MT_imap_later_init`` + - ``imap_instance.later_init`` + * - ``MT_imap_select_mailbox`` + - ``imap_instance.select_mailbox`` + * - ``MT_imap_search_mailbox`` + - ``imap_instance.search_mailbox`` + * - ``MT_imap_mail_content_list`` + - ``imap_instance.mail_content_list`` + * - ``MT_imap_output_all_mail_as_file`` + - ``imap_instance.output_all_mail_as_file`` + * - ``MT_imap_quit`` + - ``imap_instance.quit`` + * - ``MT_set_mail_thunder_os_environ`` + - ``set_mail_thunder_os_environ`` + * - ``MT_get_mail_thunder_os_environ`` + - ``get_mail_thunder_os_environ`` + * - ``MT_add_package_to_executor`` + - ``package_manager.add_package_to_executor`` + * - *(all Python builtins)* + - ``print``, ``len``, ``range``, ``type``, ``str``, ``int``, etc. + +---- + +Module-Level Functions +---------------------- + +execute_action() +~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + def execute_action(action_list: [list, dict]) -> dict + +Execute a list of action commands. + +**Parameters:** + +- ``action_list`` — Either: + + - A ``list`` of actions: ``[["cmd1"], ["cmd2", args], ...]`` + - A ``dict`` with an ``"auto_control"`` key: ``{"auto_control": [["cmd1"], ...]}`` + +**Returns:** A ``dict`` mapping ``"execute: [action]"`` to the return value of each +command. If a command fails, the value is the exception's ``repr()``. + +**Action dispatch logic:** + +.. code-block:: text + + action = ["command_name"] + → event_dict["command_name"]() + + action = ["command_name", {"k1": "v1", "k2": "v2"}] + → event_dict["command_name"](**{"k1": "v1", "k2": "v2"}) + + action = ["command_name", ["arg1", "arg2"]] + → event_dict["command_name"](*["arg1", "arg2"]) + + action = ["command_name", "something", "extra"] (len > 2) + → raises ExecuteActionException + +**Error handling:** The executor does **not** stop on errors. It catches exceptions +per-action, logs them, and continues executing remaining actions. + +**Output:** Each action and its result are printed to stdout. + +**Example:** + +.. code-block:: python + + from je_mail_thunder import execute_action + + result = execute_action([ + ["print", ["Hello!"]], + ["MT_smtp_later_init"], + ["smtp_quit"] + ]) + # result = { + # "execute: ['print', ['Hello!']]": None, + # "execute: ['MT_smtp_later_init']": None, + # "execute: ['smtp_quit']": None, + # } + +---- + +execute_files() +~~~~~~~~~~~~~~~~ + +.. code-block:: python + + def execute_files(execute_files_list: list) -> list + +Execute multiple JSON action files sequentially. + +**Parameters:** + +- ``execute_files_list`` — A list of file paths to JSON action files + +**Returns:** A list of result dicts (one per file, same format as ``execute_action()``). + +**Example:** + +.. code-block:: python + + from je_mail_thunder import execute_files, get_dir_files_as_list + + files = get_dir_files_as_list("/path/to/actions/") + results = execute_files(files) + +---- + +add_command_to_executor() +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + def add_command_to_executor(command_dict: dict) -> None + +Register custom functions in the executor's ``event_dict``. + +**Parameters:** + +- ``command_dict`` — A dict mapping command name strings to callable functions. + Values **must** be ``types.MethodType`` or ``types.FunctionType``. + +**Raises:** ``AddCommandException`` if any value is not a valid function type. + +**Example:** + +.. code-block:: python + + from je_mail_thunder import add_command_to_executor + + def my_handler(msg): + print(f"Custom: {msg}") + + add_command_to_executor({"my_cmd": my_handler}) + +---- + +Singleton Instance +------------------ + +.. code-block:: python + + executor = Executor() + package_manager.executor = executor + +The ``Executor`` is instantiated as a module-level singleton. The +``PackageManager``'s ``executor`` reference is set to this instance, allowing +dynamically loaded packages to register their members. + +---- + +Exceptions +---------- + +.. list-table:: + :header-rows: 1 + :widths: 30 70 + + * - Exception + - Condition + * - ``ExecuteActionException`` + - Action list is empty, wrong type, ``auto_control`` key missing, + or action has more than 2 elements + * - ``AddCommandException`` + - ``add_command_to_executor()`` receives a non-callable value + +---- + +Thread Safety +------------- + +- ``read_action_json()`` and ``write_action_json()`` use ``threading.Lock`` for + thread-safe file I/O +- The ``event_dict`` is **not** thread-safe — avoid concurrent modifications +- The executor singleton is shared across the process diff --git a/docs/source/docs/API/imap_api.rst b/docs/source/docs/API/imap_api.rst index 92c371c..bc7210a 100644 --- a/docs/source/docs/API/imap_api.rst +++ b/docs/source/docs/API/imap_api.rst @@ -1,69 +1,317 @@ -MailThunder IMAP API +IMAPWrapper API +=============== + +**Module:** ``je_mail_thunder.imap.imap_wrapper`` + +``IMAPWrapper`` extends ``imaplib.IMAP4_SSL`` to provide a high-level interface for +reading, searching, and exporting emails via the IMAP4 protocol. + +---- + +Class Definition +---------------- + +.. code-block:: python + + class IMAPWrapper(imaplib.IMAP4_SSL): + """ + IMAP wrapper with auto-login, search, and export utilities. + + Inherits all methods from imaplib.IMAP4_SSL. + Supports context manager (with statement). + """ + + def __init__(self, host: str = "imap.gmail.com"): + ... + +**Parameters:** + +.. list-table:: + :header-rows: 1 + :widths: 20 20 60 + + * - Parameter + - Default + - Description + * - ``host`` + - ``"imap.gmail.com"`` + - IMAP server hostname (SSL) + ---- +Methods +------- + +later_init() +~~~~~~~~~~~~ + .. code-block:: python - def later_init(self): - """ - Try to log in - :return: None - """ + def later_init(self) -> None + +Attempt to log in to the IMAP server. Calls ``try_to_login_with_env_or_content()`` +internally. Catches and logs all exceptions without raising. + +---- + +try_to_login_with_env_or_content() +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: python - def try_to_login_with_env_or_content(self): - """ - Try to find user and password on cwd /mail_thunder_content.json or env var - :return: None - """ + def try_to_login_with_env_or_content(self) -> None + +Attempt to log in using credentials from the config file or environment variables. + +**Authentication flow:** + +1. Read ``mail_thunder_content.json`` from ``Path.cwd()`` +2. If valid ``user`` + ``password`` keys found → ``self.login(user, password)`` +3. Else read ``mail_thunder_user`` + ``mail_thunder_user_password`` env vars +4. If env vars set → ``self.login(user, password)`` +5. On failure → log error with ``mail_thunder_content_login_failed`` tag + +---- + +select_mailbox() +~~~~~~~~~~~~~~~~~ .. code-block:: python - def select_mailbox(self, mailbox: str = "INBOX", readonly: bool = False): - """ - :param mailbox: Mailbox we want to select like INBOX - :param readonly: Readonly or not - :return: None - """ + def select_mailbox( + self, + mailbox: str = "INBOX", + readonly: bool = False + ) -> bool + +Select a mailbox for subsequent operations. + +**Parameters:** + +.. list-table:: + :header-rows: 1 + :widths: 20 20 60 + + * - Parameter + - Default + - Description + * - ``mailbox`` + - ``"INBOX"`` + - Mailbox name. Common: ``"INBOX"``, ``"Sent"``, ``"Drafts"``, ``"Trash"``. + Gmail: ``"[Gmail]/All Mail"``, ``"[Gmail]/Starred"``, ``"[Gmail]/Spam"``. + * - ``readonly`` + - ``False`` + - If ``True``, opens in read-only mode (messages not marked as read) + +**Returns:** ``True`` if IMAP status is ``"OK"``, ``False`` otherwise. + +---- + +search_mailbox() +~~~~~~~~~~~~~~~~~ .. code-block:: python - def search_mailbox(self, search_str: [str, list] = "ALL", charset: str = None) -> list: - """ - Get all mail detail as list - :param search_str: Search pattern - :param charset: Charset pattern - :return: All mail detail as list [mail_response, mail_decode, mail_content] - """ + def search_mailbox( + self, + search_str: [str, list] = "ALL", + charset: str = None + ) -> list + +Search the selected mailbox and return raw mail details. + +**Parameters:** + +.. list-table:: + :header-rows: 1 + :widths: 20 20 60 + + * - Parameter + - Default + - Description + * - ``search_str`` + - ``"ALL"`` + - IMAP search criteria (RFC 3501 Section 6.4.4) + * - ``charset`` + - ``None`` + - Character set for the search + +**Returns:** A list of ``[response, decode_info, message]`` tuples: + +- ``response`` — IMAP response status string (e.g., ``"OK"``) +- ``decode_info`` — Raw RFC822 decode header bytes (e.g., ``b'1 (RFC822 {12345}'``) +- ``message`` — ``email.message.Message`` object (parsed with ``email.policy.default``) + +**Internally:** For each matching email, calls ``self.fetch(num, "(RFC822)")`` and +parses the result with ``email.message_from_bytes()``. + +---- + +mail_content_list() +~~~~~~~~~~~~~~~~~~~~ .. code-block:: python - def mail_content_list( - self, search_str: [str, list] = "ALL", charset: str = None) -> List[Dict[str, Union[str, bytes]]]: - mail_thunder_logger.info(f"imap_mail_content_list, search_str: {search_str}, charset: {charset}") - """ - Get all mail content as list - :param search_str: Search pattern - :param charset: Charset pattern - :return: All mail content as list [{"SUBJECT": "mail_subject", "FROM": "mail_from", "TO": "mail_to"}] - """ + def mail_content_list( + self, + search_str: [str, list] = "ALL", + charset: str = None + ) -> List[Dict[str, Union[str, bytes]]] + +Get all mail content as a list of parsed dictionaries. + +**Parameters:** Same as ``search_mailbox()``. + +**Returns:** A list of dicts, one per email: .. code-block:: python - def output_all_mail_as_file( - self, search_str: [str, list] = "ALL", charset: str = None) -> List[Dict[str, Union[str, bytes]]]: - mail_thunder_logger.info(f"imap_mail_content_list, search_str: {search_str}, charset: {charset}") - """ - Get all mail content data and output as file - :param search_str: Search pattern - :param charset: Charset pattern - :return: All mail content as list [{"SUBJECT": "mail_subject", "FROM": "mail_from", "TO": "mail_to"}] - """ + { + "SUBJECT": str, # Decoded subject line + "FROM": str, # Sender address + "TO": str, # Recipient address + "BODY": str # Body text (first part for multipart emails) + } + +**Body extraction logic:** + +- If the email ``is_multipart()``: extracts the payload of the first part +- Otherwise: extracts the full payload +- Decodes using ``email.header.decode_header()`` + +---- + +output_all_mail_as_file() +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + def output_all_mail_as_file( + self, + search_str: [str, list] = "ALL", + charset: str = None + ) -> List[Dict[str, Union[str, bytes]]] + +Export all matching emails to local files. + +**Parameters:** Same as ``search_mailbox()``. + +**Returns:** The same list of mail content dicts as ``mail_content_list()``. + +**File naming logic:** + +- Filename = ``{subject}{counter}`` (e.g., ``My Subject0``, ``My Subject1``) +- Counter starts at 0 and increments for duplicate subjects +- Files are created in the current working directory +- Body is written as UTF-8 text (decoded from bytes if necessary) + +---- + +quit() +~~~~~~ + +.. code-block:: python + + def quit(self) -> None + +Close the selected mailbox and log out from the IMAP server. +Calls ``self.close()`` followed by ``self.logout()``. +Catches and logs exceptions. + +---- + +Context Manager +--------------- + +``IMAPWrapper`` supports the ``with`` statement. ``close()`` and ``logout()`` +are called on exit: + +.. code-block:: python + + with IMAPWrapper() as imap: + imap.later_init() + imap.select_mailbox("INBOX") + emails = imap.mail_content_list() + # imap.close() and imap.logout() called automatically + +.. note:: + + The context manager calls ``close()`` + ``logout()`` directly (without exception + handling), while ``quit()`` wraps them in a try/except. For robustness, prefer + using ``quit()`` explicitly or the context manager. + +---- + +Global Instance +--------------- + +.. code-block:: python + + imap_instance: IMAPWrapper | None + +A pre-created ``IMAPWrapper`` instance that attempts to connect to ``imap.gmail.com`` +at import time. Set to ``None`` if the connection fails. +Used internally by the JSON scripting executor. + +---- + +Inherited from IMAP4_SSL +------------------------- + +All standard ``imaplib.IMAP4_SSL`` methods are available: + +- ``login(user, password)`` — Manual authentication +- ``select(mailbox, readonly)`` — Low-level mailbox selection +- ``search(charset, *criteria)`` — Low-level IMAP SEARCH +- ``fetch(message_set, message_parts)`` — Fetch message data +- ``store(message_set, command, flags)`` — Alter message flags +- ``copy(message_set, new_mailbox)`` — Copy messages +- ``expunge()`` — Permanently remove deleted messages +- ``list(directory, pattern)`` — List available mailboxes +- ``close()`` — Close selected mailbox +- ``logout()`` — Log out from server + +See `imaplib documentation `_ +for the full reference. + +---- + +IMAP Search Criteria Quick Reference +------------------------------------- + +.. list-table:: + :header-rows: 1 + :widths: 30 70 + + * - Criteria + - Description + * - ``ALL`` + - All messages + * - ``UNSEEN`` + - Unread messages + * - ``SEEN`` + - Read messages + * - ``FLAGGED`` + - Flagged/starred messages + * - ``FROM "addr"`` + - Messages from sender + * - ``TO "addr"`` + - Messages to recipient + * - ``SUBJECT "text"`` + - Subject contains text + * - ``BODY "text"`` + - Body contains text + * - ``SINCE "DD-Mon-YYYY"`` + - On or after date + * - ``BEFORE "DD-Mon-YYYY"`` + - Before date + * - ``LARGER n`` + - Larger than n bytes + * - ``SMALLER n`` + - Smaller than n bytes + +Multiple criteria are combined with implicit AND: .. code-block:: python - def quit(self): - """ - Quit service and close connect - :return: None - """ + imap.mail_content_list(search_str='FROM "user@example.com" UNSEEN') diff --git a/docs/source/docs/API/smtp_api.rst b/docs/source/docs/API/smtp_api.rst index 033dc18..cd9aea0 100644 --- a/docs/source/docs/API/smtp_api.rst +++ b/docs/source/docs/API/smtp_api.rst @@ -1,75 +1,327 @@ -MailThunder SMTP API +SMTPWrapper API +=============== + +**Module:** ``je_mail_thunder.smtp.smtp_wrapper`` + +``SMTPWrapper`` extends ``smtplib.SMTP_SSL`` to provide a high-level interface for +sending emails with support for plain text, HTML, and attachments. + ---- +Class Definition +---------------- + .. code-block:: python - def later_init(self): - """ - Try to log in - :return: None - """ + class SMTPWrapper(smtplib.SMTP_SSL): + """ + SMTP wrapper with auto-login and message creation utilities. + + Inherits all methods from smtplib.SMTP_SSL. + Supports context manager (with statement). + """ + + def __init__(self, host: str = "smtp.gmail.com", port: int = 465): + ... + +**Parameters:** + +.. list-table:: + :header-rows: 1 + :widths: 20 20 60 + + * - Parameter + - Default + - Description + * - ``host`` + - ``"smtp.gmail.com"`` + - SMTP server hostname + * - ``port`` + - ``465`` + - SMTP server port (SSL) + +---- + +Properties +---------- + +.. list-table:: + :header-rows: 1 + :widths: 20 15 65 + + * - Property + - Type + - Description + * - ``login_state`` + - ``bool`` + - ``True`` if the SMTP session is authenticated, ``False`` otherwise. + Set by ``try_to_login_with_env_or_content()`` and reset by ``quit()``. + +---- + +Methods +------- + +later_init() +~~~~~~~~~~~~ .. code-block:: python - @staticmethod - def create_message(message_content: str, message_setting_dict: dict, **kwargs): - """ - Create new EmailMessage instance - :param message_content: Mail content - :param message_setting_dict: Dict include SUBJECT FROM TO and another EmailMessage Key and Value - :param kwargs: EmailMessage setting - :return: None - """ + def later_init(self) -> None + +Attempt to log in to the SMTP server. Calls ``try_to_login_with_env_or_content()`` +internally. Catches and logs all exceptions without raising. + +**Example:** .. code-block:: python - @staticmethod - def create_message_with_attach(message_content: str, message_setting_dict: dict, - attach_file: str, use_html: bool = False): - """ - Create new EmailMessage with attach file instance - :param message_content: Mail content - :param message_setting_dict: Dict include SUBJECT FROM TO and another EmailMessage Key and Value - :param attach_file: File path as str - :param use_html: Enable HTML format (If attach file is html) - :return: None - """ + smtp = SMTPWrapper() + smtp.later_init() + +---- + +create_message() +~~~~~~~~~~~~~~~~~ .. code-block:: python - def try_to_login_with_env_or_content(self): - """ - Try to find user and password on cwd /mail_thunder_content.json or env var - :return: None - """ + @staticmethod + def create_message( + message_content: str, + message_setting_dict: dict, + **kwargs + ) -> EmailMessage + +Create a new ``EmailMessage`` instance. + +**Parameters:** + +.. list-table:: + :header-rows: 1 + :widths: 25 15 60 + + * - Parameter + - Type + - Description + * - ``message_content`` + - ``str`` + - The email body content (plain text) + * - ``message_setting_dict`` + - ``dict`` + - Email headers. Required: ``"Subject"``, ``"From"``, ``"To"``. + Optional: ``"Cc"``, ``"Bcc"``, ``"Reply-To"``, any RFC 2822 header. + * - ``**kwargs`` + - ``dict`` + - Additional keyword arguments passed to the ``EmailMessage`` constructor + +**Returns:** ``EmailMessage`` instance. + +**Example:** .. code-block:: python - def quit(self): - """ - Quit service and close connect - :return: None - """ + message = SMTPWrapper.create_message( + message_content="Hello!", + message_setting_dict={ + "Subject": "Test", + "From": "sender@gmail.com", + "To": "receiver@gmail.com" + } + ) + +---- + +create_message_with_attach() +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: python - def create_message_with_attach_and_send(self, message_content: str, message_setting_dict: dict, - attach_file: str, use_html: bool = False): - """ - Create new EmailMessage with attach file instance then send EmailMessage instance - :param message_content: Mail content - :param message_setting_dict: Dict include SUBJECT FROM TO and another EmailMessage Key and Value - :param attach_file: File path as str - :param use_html: Enable HTML format (If attach file is html) - :return: None - """ + @staticmethod + def create_message_with_attach( + message_content: str, + message_setting_dict: dict, + attach_file: str, + use_html: bool = False + ) -> MIMEMultipart + +Create a new ``MIMEMultipart`` message with an attachment. MIME type is +auto-detected using ``mimetypes.guess_type()``. + +**Parameters:** + +.. list-table:: + :header-rows: 1 + :widths: 25 15 60 + + * - Parameter + - Type + - Description + * - ``message_content`` + - ``str`` + - Email body (plain text or HTML) + * - ``message_setting_dict`` + - ``dict`` + - Email headers (Subject, From, To, etc.) + * - ``attach_file`` + - ``str`` + - Path to the file to attach + * - ``use_html`` + - ``bool`` + - If ``True``, body is ``MIMEText(content, "html")``. Default: ``False``. + +**Returns:** ``MIMEMultipart`` instance. + +**MIME type handling:** + +.. list-table:: + :header-rows: 1 + :widths: 20 40 40 + + * - Main Type + - Handler + - Class + * - ``text`` + - ``open(file, "r+")`` + - ``MIMEText`` + * - ``image`` + - ``open(file, "rb")`` + - ``MIMEImage`` + * - ``audio`` + - ``open(file, "rb")`` + - ``MIMEAudio`` + * - other + - ``open(file, "rb")`` + - ``MIMEBase`` with ``set_payload()`` + +The attachment gets ``Content-Disposition: attachment`` and ``Content-ID`` headers +set to the filename. + +---- + +create_message_and_send() +~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: python - def create_message_and_send(self, message_content: str, message_setting_dict: dict, **kwargs): - """ - Create new EmailMessage instance then send EmailMessage instance - :param message_content: Mail content - :param message_setting_dict: Dict include SUBJECT FROM TO and another EmailMessage Key and Value - :return: None - """ + def create_message_and_send( + self, + message_content: str, + message_setting_dict: dict, + **kwargs + ) -> None + +Create a new ``EmailMessage`` and immediately send it via ``send_message()``. + +**Parameters:** Same as ``create_message()``. + +**Example:** + +.. code-block:: python + + with SMTPWrapper() as smtp: + smtp.later_init() + smtp.create_message_and_send( + message_content="Hello!", + message_setting_dict={ + "Subject": "Quick Email", + "From": "sender@gmail.com", + "To": "receiver@gmail.com" + } + ) + +---- + +create_message_with_attach_and_send() +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + def create_message_with_attach_and_send( + self, + message_content: str, + message_setting_dict: dict, + attach_file: str, + use_html: bool = False + ) -> None + +Create a ``MIMEMultipart`` message with attachment and immediately send it. + +**Parameters:** Same as ``create_message_with_attach()``. + +---- + +try_to_login_with_env_or_content() +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + def try_to_login_with_env_or_content(self) -> bool + +Attempt to log in using credentials from the config file or environment variables. + +**Authentication flow:** + +1. Read ``mail_thunder_content.json`` from ``Path.cwd()`` +2. If valid ``user`` + ``password`` keys found → ``self.login(user, password)`` +3. Else read ``mail_thunder_user`` + ``mail_thunder_user_password`` env vars +4. If env vars set → ``self.login(user, password)`` +5. On ``SMTPAuthenticationError`` → log error, return ``False`` + +**Returns:** ``True`` if login succeeded, ``False`` otherwise. +Sets ``self.login_state`` accordingly. + +---- + +quit() +~~~~~~ + +.. code-block:: python + + def quit(self) -> None + +Disconnect from the SMTP server. Resets ``login_state`` to ``False``. +Catches and logs exceptions (e.g., if already disconnected). + +---- + +Context Manager +--------------- + +``SMTPWrapper`` supports the ``with`` statement. ``quit()`` is called on exit: + +.. code-block:: python + + with SMTPWrapper() as smtp: + smtp.later_init() + smtp.create_message_and_send(...) + # smtp.quit() called automatically + +---- + +Global Instance +--------------- + +.. code-block:: python + + smtp_instance: SMTPWrapper | None + +A pre-created ``SMTPWrapper`` instance that attempts to connect to +``smtp.gmail.com:465`` at import time. Set to ``None`` if the connection fails. +Used internally by the JSON scripting executor. + +---- + +Inherited from SMTP_SSL +------------------------ + +All standard ``smtplib.SMTP_SSL`` methods are available: + +- ``login(user, password)`` — Manual authentication +- ``send_message(msg)`` — Send an ``EmailMessage`` or ``MIMEMultipart`` +- ``sendmail(from_addr, to_addrs, msg)`` — Low-level send +- ``ehlo()`` / ``helo()`` — SMTP handshake +- ``noop()`` — No-operation (keepalive) + +See `smtplib documentation `_ +for the full reference. diff --git a/docs/source/docs/API/utils_api.rst b/docs/source/docs/API/utils_api.rst new file mode 100644 index 0000000..9d3ccf7 --- /dev/null +++ b/docs/source/docs/API/utils_api.rst @@ -0,0 +1,404 @@ +Utility Functions API +===================== + +This page documents all utility functions and modules in MailThunder. + +---- + +Authentication Utilities +------------------------ + +**Module:** ``je_mail_thunder.utils.save_mail_user_content.save_on_env`` + +set_mail_thunder_os_environ() +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + def set_mail_thunder_os_environ( + mail_thunder_user: str, + mail_thunder_user_password: str + ) -> None + +Set authentication environment variables using ``os.environ.update()``. + +**Parameters:** + +- ``mail_thunder_user`` — Email address string +- ``mail_thunder_user_password`` — Password or app password string + +**Sets:** + +- ``os.environ["mail_thunder_user"]`` +- ``os.environ["mail_thunder_user_password"]`` + +---- + +get_mail_thunder_os_environ() +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + def get_mail_thunder_os_environ() -> dict + +Get current authentication environment variables. + +**Returns:** + +.. code-block:: python + + { + "mail_thunder_user": str | None, + "mail_thunder_user_password": str | None + } + +Returns ``None`` for unset variables. + +---- + +Content File Utilities +---------------------- + +**Module:** ``je_mail_thunder.utils.save_mail_user_content.mail_thunder_content_save`` + +read_output_content() +~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + def read_output_content() -> dict | None + +Read ``mail_thunder_content.json`` from the current working directory. + +**Returns:** A dict with ``"user"`` and ``"password"`` keys, or ``None`` if +the file does not exist. + +**Side effect:** Updates the global ``mail_thunder_content_data_dict`` with +the file contents. + +**Thread safety:** Uses ``threading.Lock``. + +---- + +write_output_content() +~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + def write_output_content() -> None + +Write the current ``mail_thunder_content_data_dict`` to +``mail_thunder_content.json`` in the current working directory. +The output is reformatted JSON (indented, sorted keys). + +**Thread safety:** Uses ``threading.Lock``. + +---- + +Content Data +~~~~~~~~~~~~ + +**Module:** ``je_mail_thunder.utils.save_mail_user_content.mail_thunder_content_data`` + +.. code-block:: python + + mail_thunder_content_data_dict = { + "user": None, + "password": None + } + +Global mutable dict holding the current credentials. +Updated by ``read_output_content()`` and can be manually modified. + +.. code-block:: python + + def is_need_to_save_content() -> bool + +Returns ``True`` if any value in ``mail_thunder_content_data_dict`` is not ``None``. + +---- + +JSON File Utilities +------------------- + +**Module:** ``je_mail_thunder.utils.json.json_file`` + +read_action_json() +~~~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + def read_action_json(json_file_path: str) -> list + +Read and parse a JSON action file. + +**Parameters:** + +- ``json_file_path`` — Path to the JSON file + +**Returns:** Parsed JSON data (typically a list or dict). + +**Raises:** ``JsonActionException`` if the file is not found or cannot be parsed. + +**Thread safety:** Uses ``threading.Lock``. + +---- + +write_action_json() +~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + def write_action_json(json_save_path: str, action_json: list) -> None + +Write action data to a JSON file (indented, 4 spaces). + +**Parameters:** + +- ``json_save_path`` — File path to write to +- ``action_json`` — Data to serialize as JSON + +**Raises:** ``JsonActionException`` if the file cannot be saved. + +**Thread safety:** Uses ``threading.Lock``. + +---- + +JSON Formatting +~~~~~~~~~~~~~~~ + +**Module:** ``je_mail_thunder.utils.json_format.json_process`` + +.. code-block:: python + + def reformat_json(json_string: str, **kwargs) -> str + +Reformat a JSON string with indentation (4 spaces) and sorted keys. + +**Parameters:** + +- ``json_string`` — JSON string to reformat +- ``**kwargs`` — Additional arguments passed to ``json.dumps()`` + +**Returns:** Reformatted JSON string. + +**Raises:** ``MailThunderJsonException`` on parse or type errors. + +---- + +File Process Utilities +---------------------- + +**Module:** ``je_mail_thunder.utils.file_process.get_dir_file_list`` + +get_dir_files_as_list() +~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + def get_dir_files_as_list( + dir_path: str = os.getcwd(), + default_search_file_extension: str = ".json" + ) -> List[str] + +Walk a directory tree and collect all files matching the given extension. + +**Parameters:** + +.. list-table:: + :header-rows: 1 + :widths: 35 20 45 + + * - Parameter + - Default + - Description + * - ``dir_path`` + - ``os.getcwd()`` + - Directory to walk + * - ``default_search_file_extension`` + - ``".json"`` + - File extension filter (case-insensitive) + +**Returns:** A list of absolute file path strings. Empty list if nothing found. + +**Example:** + +.. code-block:: python + + from je_mail_thunder import get_dir_files_as_list + + # Get all .json files in a directory + files = get_dir_files_as_list("/path/to/actions/") + # ["/path/to/actions/action1.json", "/path/to/actions/action2.json"] + + # Get all .py files + files = get_dir_files_as_list("/path/to/code/", ".py") + +---- + +Project Scaffolding +------------------- + +**Module:** ``je_mail_thunder.utils.project.create_project_structure`` + +create_project_dir() +~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + def create_project_dir( + project_path: str = None, + parent_name: str = "MailThunder" + ) -> None + +Create a project directory with template files. + +**Parameters:** + +.. list-table:: + :header-rows: 1 + :widths: 25 25 50 + + * - Parameter + - Default + - Description + * - ``project_path`` + - ``os.getcwd()`` + - Base directory for the project + * - ``parent_name`` + - ``"MailThunder"`` + - Name of the project root folder + +**Creates:** + +.. code-block:: text + + {project_path}/{parent_name}/ + keyword/ + keyword1.json # SMTP send template + keyword2.json # IMAP export template + bad_keyword_1.json # Security warning example + executor/ + executor_one_file.py # Single file executor + executor_folder.py # Directory executor + executor_bad_file.py # Bad practice example + +Template ``{temp}`` placeholders are replaced with absolute paths during creation. + +---- + +Socket Server +------------- + +**Module:** ``je_mail_thunder.utils.socket_server.mail_thunder_socket_server`` + +start_autocontrol_socket_server() +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + def start_autocontrol_socket_server( + host: str = "localhost", + port: int = 9944 + ) -> TCPServer + +Start a TCP socket server that accepts JSON action commands. + +**Parameters:** + +- ``host`` — Bind address (default: ``"localhost"``) +- ``port`` — TCP port (default: ``9944``) + +**Returns:** ``TCPServer`` instance with ``close_flag`` attribute. + +The server runs in a daemon background thread via ``threading.Thread(daemon=True)``. +Can also read ``host``/``port`` from ``sys.argv[1]``/``sys.argv[2]``. + +---- + +TCPServer +~~~~~~~~~ + +.. code-block:: python + + class TCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer): + close_flag: bool = False + +Custom TCP server with threading support. ``close_flag`` is set to ``True`` +when the ``"quit_server"`` command is received. + +---- + +TCPServerHandler +~~~~~~~~~~~~~~~~ + +.. code-block:: python + + class TCPServerHandler(socketserver.BaseRequestHandler): + def handle(self): ... + +Handles incoming client connections: + +- Receives up to 8192 bytes (UTF-8) +- If command is ``"quit_server"``: shuts down the server +- Otherwise: parses as JSON, calls ``execute_action()``, sends results back +- Response terminated with ``"Return_Data_Over_JE\n"`` + +---- + +Package Manager +--------------- + +**Module:** ``je_mail_thunder.utils.package_manager.package_manager_class`` + +.. code-block:: python + + class PackageManager: + installed_package_dict: dict # Cache of loaded packages + executor: Executor | None # Reference to the executor + +**Methods:** + +.. list-table:: + :header-rows: 1 + :widths: 35 65 + + * - Method + - Description + * - ``check_package(package: str)`` + - Check if a package exists and import it. Returns the module or ``None``. + * - ``add_package_to_executor(package: str)`` + - Load all functions, builtins, and classes from a package into the executor. + Members are prefixed with ``{package}_``. + * - ``add_package_to_callback_executor(package: str)`` + - Same as above but for the callback executor reference. + * - ``get_member(package, predicate, target)`` + - Extract members matching a predicate and register in target's ``event_dict``. + * - ``add_package_to_target(package, target)`` + - Run ``get_member()`` for isfunction, isbuiltin, and isclass predicates. + +**Singleton:** + +.. code-block:: python + + package_manager = PackageManager() + +Module-level singleton. Its ``executor`` attribute is set to the main ``Executor`` +instance after initialization. + +---- + +Logging +------- + +**Module:** ``je_mail_thunder.utils.logging.loggin_instance`` + +.. code-block:: python + + mail_thunder_logger = logging.getLogger("Mail Thunder") + +Pre-configured logger with: + +- **File handler:** ``Mail_Thunder.log`` (INFO level, ``w+`` mode) +- **Stream handler:** ``stderr`` (WARNING level) +- **Format:** ``%(asctime)s | %(name)s | %(levelname)s | %(message)s`` diff --git a/docs/source/docs/Eng/authentication.rst b/docs/source/docs/Eng/authentication.rst new file mode 100644 index 0000000..bb827a8 --- /dev/null +++ b/docs/source/docs/Eng/authentication.rst @@ -0,0 +1,191 @@ +Authentication +============== + +MailThunder supports two authentication methods. When ``later_init()`` or +``try_to_login_with_env_or_content()`` is called, it tries the JSON config file +first, then falls back to environment variables. + +Authentication Flow +------------------- + +.. code-block:: text + + later_init() called + │ + ▼ + Read mail_thunder_content.json from cwd + │ + ├── File found and has "user" + "password" + │ │ + │ ▼ + │ Login with file credentials ─── Success ──▶ Done (login_state = True) + │ │ + │ └── Failure ──▶ Log error + │ + └── File not found or invalid + │ + ▼ + Read env vars: mail_thunder_user + mail_thunder_user_password + │ + ├── Env vars set + │ │ + │ ▼ + │ Login with env credentials ─── Success ──▶ Done (login_state = True) + │ │ + │ └── Failure ──▶ Log error + │ + └── Env vars not set ──▶ Log error + +Method 1: JSON Config File +--------------------------- + +Create a file named ``mail_thunder_content.json`` in your **current working directory**: + +.. code-block:: json + + { + "user": "your_email@gmail.com", + "password": "your_app_password" + } + +.. warning:: + + This file contains your email credentials in plain text. Do not commit it to + version control. Add ``mail_thunder_content.json`` to your ``.gitignore``. + +**How it works internally:** + +1. ``read_output_content()`` checks if ``mail_thunder_content.json`` exists in ``Path.cwd()`` +2. If found, it reads the JSON and updates ``mail_thunder_content_data_dict`` +3. The ``user`` and ``password`` values are passed to ``smtplib.SMTP_SSL.login()`` + or ``imaplib.IMAP4_SSL.login()`` + +Method 2: Environment Variables +------------------------------- + +**Option A — Set via Python at runtime:** + +.. code-block:: python + + from je_mail_thunder import set_mail_thunder_os_environ + + set_mail_thunder_os_environ( + mail_thunder_user="your_email@gmail.com", + mail_thunder_user_password="your_app_password" + ) + +This calls ``os.environ.update()`` to set two environment variables: + +- ``mail_thunder_user`` +- ``mail_thunder_user_password`` + +**Option B — Set in your shell before running the script:** + +.. code-block:: bash + + # Linux / macOS + export mail_thunder_user="your_email@gmail.com" + export mail_thunder_user_password="your_app_password" + +.. code-block:: batch + + :: Windows CMD + set mail_thunder_user=your_email@gmail.com + set mail_thunder_user_password=your_app_password + +.. code-block:: powershell + + # Windows PowerShell + $env:mail_thunder_user = "your_email@gmail.com" + $env:mail_thunder_user_password = "your_app_password" + +**Retrieve current env var values:** + +.. code-block:: python + + from je_mail_thunder import get_mail_thunder_os_environ + + creds = get_mail_thunder_os_environ() + # Returns: {"mail_thunder_user": "...", "mail_thunder_user_password": "..."} + +Method 3: Manual Credential Injection +-------------------------------------- + +You can directly update the global credential dict before calling login: + +.. code-block:: python + + from je_mail_thunder import mail_thunder_content_data_dict + + mail_thunder_content_data_dict.update({ + "user": "your_email@gmail.com", + "password": "your_app_password", + }) + +This is useful for programmatic scenarios where credentials come from a vault, +database, or other external source. + +Gmail-Specific Setup +-------------------- + +If you are using Gmail, there are two extra requirements: + +1. **Use an App Password** — Gmail does not allow login with your regular Google + account password. You must generate an App Password: + + - Go to `Google App Passwords `_ + - Select "Mail" and your device + - Copy the generated 16-character password + - Use this as the ``password`` value + +2. **Enable IMAP** (for reading emails) — By default, IMAP access is disabled in Gmail: + + - Go to Gmail Settings > See all settings > Forwarding and POP/IMAP + - Under "IMAP access", select "Enable IMAP" + - Save changes + +.. note:: + + App Passwords require 2-Step Verification to be enabled on your Google account. + +Checking Login State (SMTP) +---------------------------- + +``SMTPWrapper`` tracks authentication state via the ``login_state`` property: + +.. code-block:: python + + from je_mail_thunder import SMTPWrapper + + smtp = SMTPWrapper() + print(smtp.login_state) # False + + success = smtp.try_to_login_with_env_or_content() + print(smtp.login_state) # True if login succeeded + print(success) # True or False + +Via JSON Scripting Engine +------------------------- + +Set authentication credentials in a JSON action file: + +.. code-block:: json + + { + "auto_control": [ + ["MT_set_mail_thunder_os_environ", { + "mail_thunder_user": "your_email@gmail.com", + "mail_thunder_user_password": "your_app_password" + }], + ["MT_smtp_later_init"], + ["MT_smtp_create_message_and_send", { + "message_content": "Hello!", + "message_setting_dict": { + "Subject": "Test", + "From": "your_email@gmail.com", + "To": "receiver@gmail.com" + } + }], + ["smtp_quit"] + ] + } diff --git a/docs/source/docs/Eng/cli.rst b/docs/source/docs/Eng/cli.rst new file mode 100644 index 0000000..407cc41 --- /dev/null +++ b/docs/source/docs/Eng/cli.rst @@ -0,0 +1,175 @@ +Command-Line Interface +====================== + +MailThunder provides a CLI via ``python -m je_mail_thunder`` for executing JSON +action files, running JSON strings, and scaffolding projects directly from the +terminal. + +---- + +Usage +----- + +.. code-block:: bash + + python -m je_mail_thunder [OPTIONS] + +---- + +Options +------- + +.. list-table:: + :header-rows: 1 + :widths: 10 25 65 + + * - Flag + - Long Flag + - Description + * - ``-e`` + - ``--execute_file`` + - Execute a single JSON action file + * - ``-d`` + - ``--execute_dir`` + - Execute all JSON action files in a directory + * - + - ``--execute_str`` + - Execute a JSON string directly + * - ``-c`` + - ``--create_project`` + - Create a project directory with templates + +If no flags are provided, the CLI raises ``MailThunderArgparseException``. + +---- + +Execute a Single Action File +----------------------------- + +.. code-block:: bash + + python -m je_mail_thunder -e /path/to/action.json + +This reads the JSON file, parses the action list, and passes it to ``execute_action()``. + +**Example:** + +.. code-block:: bash + + python -m je_mail_thunder -e my_project/keyword/keyword1.json + +---- + +Execute All Files in a Directory +--------------------------------- + +.. code-block:: bash + + python -m je_mail_thunder -d /path/to/actions/ + +This scans the directory for all ``.json`` files (using ``get_dir_files_as_list()``) +and executes each one sequentially via ``execute_files()``. + +**Example:** + +.. code-block:: bash + + python -m je_mail_thunder -d my_project/keyword/ + +---- + +Execute a JSON String +--------------------- + +.. code-block:: bash + + python -m je_mail_thunder --execute_str '[["print", ["Hello from CLI!"]]]' + +This parses the JSON string and executes it directly. + +.. note:: + + **Windows note:** On Windows platforms (``win32``, ``cygwin``, ``msys``), the + JSON string is double-parsed (``json.loads`` is called twice) to handle shell + escaping differences. You may need to wrap the string in extra quotes: + + .. code-block:: batch + + python -m je_mail_thunder --execute_str "\"[[\\\"print\\\", [\\\"Hello!\\\"]]]]\"" + + On Linux/macOS, single quoting works directly: + + .. code-block:: bash + + python -m je_mail_thunder --execute_str '[["print", ["Hello!"]]]' + +---- + +Create a Project +---------------- + +.. code-block:: bash + + python -m je_mail_thunder -c /path/to/project + +This creates a project directory with template files at the specified path. +See :doc:`project_templates` for details on the generated structure. + +---- + +Exit Codes +---------- + +- **0** — All actions executed successfully +- **1** — An error occurred (exception printed to ``stderr``) + +---- + +Examples +-------- + +**Send an email from the command line:** + +.. code-block:: bash + + # Create a JSON action file + cat > send.json << 'EOF' + { + "auto_control": [ + ["MT_smtp_later_init"], + ["MT_smtp_create_message_and_send", { + "message_content": "Hello from CLI!", + "message_setting_dict": { + "Subject": "CLI Test", + "To": "receiver@gmail.com", + "From": "sender@gmail.com" + } + }], + ["smtp_quit"] + ] + } + EOF + + # Execute it + python -m je_mail_thunder -e send.json + +**Run multiple action files:** + +.. code-block:: bash + + # Create action directory + mkdir -p actions/ + + # Copy/create action files + cp send.json actions/ + cp read.json actions/ + + # Execute all + python -m je_mail_thunder -d actions/ + +**Scaffold a new project:** + +.. code-block:: bash + + python -m je_mail_thunder -c /home/user/projects + # Creates /home/user/projects/MailThunder/ with templates diff --git a/docs/source/docs/Eng/eng_index.rst b/docs/source/docs/Eng/eng_index.rst index 481c0e4..8a38a44 100644 --- a/docs/source/docs/Eng/eng_index.rst +++ b/docs/source/docs/Eng/eng_index.rst @@ -1,8 +1,83 @@ -MailThunder English Documentation ----- +Overview +======== -.. toctree:: - :maxdepth: 4 +**MailThunder** is a lightweight and flexible email automation tool for Python, +built on top of the standard library's ``smtplib`` and ``imaplib`` modules. - read_google_mail.rst - send_google_mail.rst \ No newline at end of file +What is MailThunder? +-------------------- + +MailThunder wraps Python's ``smtplib.SMTP_SSL`` and ``imaplib.IMAP4_SSL`` to provide +a higher-level interface for common email tasks: + +- **Sending** plain-text and HTML emails with file attachments via SMTP +- **Reading**, searching, and exporting emails via IMAP4 +- **Automating** email workflows using a JSON-based scripting engine +- **Scaffolding** projects with pre-built templates +- **Controlling** email automation remotely via a TCP socket server + +Architecture +------------ + +MailThunder is organized into the following core modules: + +.. code-block:: text + + je_mail_thunder/ + __init__.py # Public API exports + __main__.py # CLI entry point (argparse) + smtp/ + smtp_wrapper.py # SMTPWrapper — extends smtplib.SMTP_SSL + imap/ + imap_wrapper.py # IMAPWrapper — extends imaplib.IMAP4_SSL + utils/ + executor/ # JSON scripting engine (Executor class) + file_process/ # File utility (directory listing) + json/ # JSON file read/write with thread safety + json_format/ # JSON reformatting + logging/ # Logger instance (file + stream handlers) + package_manager/ # Dynamic package loader (PackageManager) + project/ # Project template scaffolding + template/ # Template definitions (keyword JSON + executor Python) + save_mail_user_content/# Auth config file + env var handling + socket_server/ # TCP socket server (ThreadingMixIn) + exception/ # Custom exception classes and error tags + +How It Works +------------ + +1. **Authentication**: MailThunder reads credentials from ``mail_thunder_content.json`` + in the current working directory, or falls back to ``mail_thunder_user`` / + ``mail_thunder_user_password`` environment variables. + +2. **SMTP (Sending)**: ``SMTPWrapper`` connects to an SMTP server over SSL (default: + ``smtp.gmail.com:465``). It provides methods to create ``EmailMessage`` or + ``MIMEMultipart`` objects and send them in a single call. + +3. **IMAP (Reading)**: ``IMAPWrapper`` connects to an IMAP server over SSL (default: + ``imap.gmail.com``). It provides methods to select mailboxes, search emails using + IMAP SEARCH syntax, parse results into Python dicts, and export them to files. + +4. **Scripting Engine**: The ``Executor`` class maps command names to Python callables. + JSON action files contain lists of ``["command_name", arguments]`` tuples that are + executed sequentially. Custom functions and entire Python packages can be loaded + at runtime. + +5. **Logging**: All operations are logged to ``Mail_Thunder.log`` (file handler at + INFO level) and ``stderr`` (stream handler at WARNING level). + +Supported Platforms +------------------- + +- **Python**: 3.9 or later +- **OS**: Windows, macOS, Linux +- **Dependencies**: None beyond the Python standard library + +Next Steps +---------- + +- :doc:`installation` — Install MailThunder +- :doc:`authentication` — Configure email credentials +- :doc:`send_google_mail` — Send your first email +- :doc:`read_google_mail` — Read emails from your inbox +- :doc:`scripting_engine` — Automate workflows with JSON scripts diff --git a/docs/source/docs/Eng/exceptions.rst b/docs/source/docs/Eng/exceptions.rst new file mode 100644 index 0000000..2df7955 --- /dev/null +++ b/docs/source/docs/Eng/exceptions.rst @@ -0,0 +1,119 @@ +Exceptions +========== + +MailThunder defines a hierarchy of custom exceptions for different error scenarios. +All exceptions inherit from the base ``MailThunderException`` class. + +---- + +Exception Hierarchy +------------------- + +.. code-block:: text + + Exception (Python built-in) + └── MailThunderException + ├── MailThunderJsonException + ├── MailThunderContentException + ├── MailThunderArgparseException + ├── ExecuteActionException + ├── AddCommandException + └── JsonActionException + +---- + +Exception Reference +------------------- + +.. list-table:: + :header-rows: 1 + :widths: 35 65 + + * - Exception + - When Raised + * - ``MailThunderException`` + - Base exception for all MailThunder errors. Not raised directly. + * - ``MailThunderJsonException`` + - JSON reformatting failures (invalid JSON data or type errors) + * - ``MailThunderContentException`` + - Errors reading or writing ``mail_thunder_content.json`` + * - ``MailThunderArgparseException`` + - CLI receives no valid arguments or unknown function + * - ``ExecuteActionException`` + - Executor receives invalid action format, null action list, or wrong data type + * - ``AddCommandException`` + - ``add_command_to_executor()`` receives a non-callable value (not a function/method) + * - ``JsonActionException`` + - JSON action file not found or cannot be saved + +---- + +Error Tags +---------- + +Each exception is associated with a human-readable error tag string defined in +``je_mail_thunder.utils.exception.exception_tags``: + +.. list-table:: + :header-rows: 1 + :widths: 45 55 + + * - Error Tag + - Message + * - ``mail_thunder_cant_reformat_json_error`` + - ``"Can't reformat json is type right?"`` + * - ``mail_thunder_wrong_json_data_error`` + - ``"Can't parser json"`` + * - ``mail_thunder_content_set_compiler_error`` + - ``"When set compiler using content make an error"`` + * - ``mail_thunder_content_file_error`` + - ``"MailThunder content file error"`` + * - ``mail_thunder_content_login_failed`` + - ``"can't login with mail thunder content"`` + * - ``mail_thunder_service_file_error`` + - ``"service param -s got the wrong data"`` + * - ``mail_thunder_login_error`` + - ``"need set --user and --password to login"`` + * - ``mail_thunder_argparse_get_wrong_function`` + - ``"get unknown function"`` + * - ``add_command_exception`` + - ``"command value type should be as method or function"`` + * - ``executor_list_error`` + - ``"executor receive wrong data list is none or wrong type"`` + * - ``cant_execute_action_error`` + - ``"cant execute action"`` + * - ``cant_find_json_error`` + - ``"cant find json file"`` + * - ``cant_save_json_error`` + - ``"cant save json file"`` + * - ``action_is_null_error`` + - ``"json action is null"`` + +---- + +Catching Exceptions +------------------- + +You can catch MailThunder exceptions in your code: + +.. code-block:: python + + from je_mail_thunder import execute_action + from je_mail_thunder.utils.exception.exceptions import ( + MailThunderException, + ExecuteActionException, + JsonActionException, + ) + + try: + execute_action([["nonexistent_command"]]) + except ExecuteActionException as e: + print(f"Executor error: {e}") + except MailThunderException as e: + print(f"MailThunder error: {e}") + +.. note:: + + Most MailThunder methods (especially in ``SMTPWrapper`` and ``IMAPWrapper``) catch + exceptions internally and log them rather than propagating. Exceptions are more + commonly raised by the executor, JSON file utilities, and CLI components. diff --git a/docs/source/docs/Eng/installation.rst b/docs/source/docs/Eng/installation.rst new file mode 100644 index 0000000..44d14db --- /dev/null +++ b/docs/source/docs/Eng/installation.rst @@ -0,0 +1,82 @@ +Installation +============ + +This page covers all the ways to install MailThunder. + +Requirements +------------ + +- **Python 3.9** or later +- No additional dependencies beyond the Python standard library +- ``pip`` for package installation + +Install from PyPI +----------------- + +**Stable release:** + +.. code-block:: bash + + pip install je_mail_thunder + +**Development release** (latest features, may be unstable): + +.. code-block:: bash + + pip install je_mail_thunder_dev + +Install from Source +------------------- + +Clone the repository and install in editable (development) mode: + +.. code-block:: bash + + git clone https://github.com/Integration-Automation/MailThunder.git + cd MailThunder + pip install -e . + +Install Development Dependencies +--------------------------------- + +If you plan to contribute or run the test suite: + +.. code-block:: bash + + pip install -r dev_requirements.txt + +This installs: + +- ``pytest`` — for running unit tests +- ``coverage`` — for code coverage reports + +Verify Installation +------------------- + +After installing, verify that MailThunder is available: + +.. code-block:: bash + + python -c "import je_mail_thunder; print('MailThunder installed successfully')" + +You can also check the version via PyPI metadata: + +.. code-block:: bash + + pip show je_mail_thunder + +Upgrade +------- + +To upgrade to the latest version: + +.. code-block:: bash + + pip install --upgrade je_mail_thunder + +Uninstall +--------- + +.. code-block:: bash + + pip uninstall je_mail_thunder diff --git a/docs/source/docs/Eng/logging.rst b/docs/source/docs/Eng/logging.rst new file mode 100644 index 0000000..b20ac26 --- /dev/null +++ b/docs/source/docs/Eng/logging.rst @@ -0,0 +1,136 @@ +Logging +======= + +MailThunder uses Python's standard ``logging`` module to log all operations. +Logs are useful for debugging authentication issues, tracking sent/received emails, +and monitoring the scripting executor. + +---- + +Logger Configuration +-------------------- + +The logger is configured in ``je_mail_thunder.utils.logging.loggin_instance``: + +.. code-block:: python + + import logging + import sys + + mail_thunder_logger = logging.getLogger("Mail Thunder") + mail_thunder_logger.setLevel(logging.INFO) + +**Logger name:** ``"Mail Thunder"`` + +---- + +Log Handlers +------------ + +MailThunder registers two log handlers: + +.. list-table:: + :header-rows: 1 + :widths: 20 20 20 40 + + * - Handler + - Output + - Level + - Description + * - File handler + - ``Mail_Thunder.log`` + - ``INFO`` + - Logs all operations (INFO and above) to a file in the current working directory + * - Stream handler + - ``stderr`` + - ``WARNING`` + - Only prints warnings and errors to the console + +**Log format:** + +.. code-block:: text + + %(asctime)s | %(name)s | %(levelname)s | %(message)s + +**Example log entries:** + +.. code-block:: text + + 2025-01-15 10:30:00,123 | Mail Thunder | INFO | MT_smtp_later_init + 2025-01-15 10:30:01,456 | Mail Thunder | INFO | smtp_create_message_and_send, message_content: Hello!, message_setting_dict: {...} + 2025-01-15 10:30:01,789 | Mail Thunder | INFO | SMTP quit + 2025-01-15 10:30:02,012 | Mail Thunder | ERROR | smtp_try_to_login_with_env_or_content, failed: SMTPAuthenticationError(...) + +---- + +What Gets Logged +---------------- + +**SMTP operations (INFO level):** + +- ``MT_smtp_later_init`` — SMTP login attempt +- ``smtp_create_message`` — Message creation with content and settings +- ``smtp_create_message_with_attach`` — Attachment message creation +- ``smtp_create_message_and_send`` — Send with content details +- ``smtp_create_message_with_attach_and_send`` — Send with attachment details +- ``smtp_try_to_login_with_env_or_content`` — Login attempt +- ``SMTP quit`` — Disconnection + +**IMAP operations (INFO level):** + +- ``MT_imap_later_init`` — IMAP login attempt +- ``imap_try_to_login_with_env_or_content`` — Login attempt +- ``imap_select_mailbox`` — Mailbox selection with name and readonly flag +- ``imap_search_mailbox`` — Search with criteria +- ``imap_mail_content_list`` — Content list retrieval +- ``MT_imap_quit`` — Disconnection + +**Executor operations (INFO level):** + +- ``Execute {action}`` — Each action as it is executed +- ``Add command to executor {command_dict}`` — Custom command registration + +**Package manager operations (INFO level):** + +- ``add_package_to_executor, package: {package}`` — Package loading + +**Error logging (ERROR level):** + +All exceptions in SMTP, IMAP, executor, and package manager operations are caught +and logged at ERROR level with the full exception representation. + +---- + +Log File Location +----------------- + +The log file ``Mail_Thunder.log`` is created in the **current working directory** +when the module is first imported. The file is opened in ``w+`` mode, meaning +it is overwritten on each program run. + +.. note:: + + Since the file handler uses ``mode="w+"``, logs from previous runs are not + preserved. If you need persistent logging, consider configuring a custom + handler or copying the log file after each run. + +---- + +Accessing the Logger +-------------------- + +You can access MailThunder's logger to add custom handlers or change the level: + +.. code-block:: python + + from je_mail_thunder.utils.logging.loggin_instance import mail_thunder_logger + + # Change log level + mail_thunder_logger.setLevel(logging.DEBUG) + + # Add a custom handler + custom_handler = logging.StreamHandler() + custom_handler.setLevel(logging.DEBUG) + mail_thunder_logger.addHandler(custom_handler) + + # Now all debug messages will be printed diff --git a/docs/source/docs/Eng/package_manager.rst b/docs/source/docs/Eng/package_manager.rst new file mode 100644 index 0000000..ffe33c7 --- /dev/null +++ b/docs/source/docs/Eng/package_manager.rst @@ -0,0 +1,177 @@ +Package Manager +=============== + +MailThunder's ``PackageManager`` allows you to dynamically load any installed Python +package into the scripting executor at runtime, making all of its functions, built-in +functions, and classes available as action commands. + +---- + +How It Works +------------ + +When ``MT_add_package_to_executor`` is called with a package name: + +1. ``importlib.find_spec()`` checks if the package is installed +2. ``importlib.import_module()`` imports the package +3. ``inspect.getmembers()`` extracts all functions, builtins, and classes +4. Each member is registered in the executor's ``event_dict`` with the naming + convention ``{package_name}_{member_name}`` + +.. code-block:: text + + MT_add_package_to_executor("os") + │ + ▼ + find_spec("os") → found + │ + ▼ + import_module("os") + │ + ▼ + getmembers(os, isfunction) → os_getcwd, os_listdir, os_makedirs, ... + getmembers(os, isbuiltin) → os_system, os_open, os_close, ... + getmembers(os, isclass) → os_error, ... + │ + ▼ + All registered in executor.event_dict + +---- + +Usage in JSON Scripts +--------------------- + +.. code-block:: json + + { + "auto_control": [ + ["MT_add_package_to_executor", ["os"]], + ["os_system", ["echo Hello from os.system"]], + ["os_getcwd"] + ] + } + +.. code-block:: json + + { + "auto_control": [ + ["MT_add_package_to_executor", ["json"]], + ["json_dumps", [{"key": "value"}]] + ] + } + +---- + +Usage in Python +--------------- + +.. code-block:: python + + from je_mail_thunder import execute_action + + execute_action([ + ["MT_add_package_to_executor", ["math"]], + ["math_sqrt", [144]], + ["math_factorial", [10]] + ]) + +---- + +Naming Convention +----------------- + +All members are prefixed with the package name and an underscore: + +.. code-block:: text + + Package "os" → os_system, os_getcwd, os_path, os_listdir, ... + Package "json" → json_dumps, json_loads, json_dump, json_load, ... + Package "math" → math_sqrt, math_ceil, math_floor, math_factorial, ... + +This prevents name collisions between packages and with built-in commands. + +---- + +Loaded Package Cache +-------------------- + +The ``PackageManager`` caches loaded packages in ``installed_package_dict``. +If a package has already been loaded, subsequent calls to +``MT_add_package_to_executor`` with the same package name will reuse the +cached import rather than re-importing. + +---- + +What Gets Loaded +---------------- + +Three categories of members are extracted from each package: + +.. list-table:: + :header-rows: 1 + :widths: 25 35 40 + + * - Category + - Detection + - Example + * - Functions + - ``inspect.isfunction`` + - ``os.getcwd``, ``json.dumps`` + * - Built-in functions + - ``inspect.isbuiltin`` + - ``os.system``, ``os.open`` + * - Classes + - ``inspect.isclass`` + - ``os.error``, ``json.JSONDecodeError`` + +.. note:: + + Sub-modules are **not** automatically loaded. For example, loading ``os`` does + not automatically make ``os.path`` functions available. You would need to + separately load ``os.path``: + + .. code-block:: json + + { + "auto_control": [ + ["MT_add_package_to_executor", ["os.path"]], + ["os.path_join", ["/home", "user", "file.txt"]] + ] + } + +---- + +Error Handling +-------------- + +- If the package is not installed, a ``ModuleNotFoundError`` message is printed + to ``stderr`` (not raised as an exception) +- If the executor is not available (``None``), an error message is printed +- Import errors during module loading are caught and printed to ``stderr`` + +---- + +Security Warning +---------------- + +.. warning:: + + **Loading packages like ``os``, ``subprocess``, ``shutil``, or ``sys`` into + the executor grants the scripting engine access to system-level operations.** + + This is a significant security risk when: + + - Processing untrusted JSON action files + - Exposing the socket server to external clients + - Running in multi-tenant environments + + **Best practices:** + + - Only load packages you explicitly need + - Never load system packages (``os``, ``subprocess``, ``sys``) in production + - Validate and sanitize all inputs + - Do not expose the socket server to untrusted networks + - Review JSON action files before executing them + + The ``bad_keyword_1.json`` template is included as an educational example of + what to avoid. diff --git a/docs/source/docs/Eng/project_templates.rst b/docs/source/docs/Eng/project_templates.rst new file mode 100644 index 0000000..4616664 --- /dev/null +++ b/docs/source/docs/Eng/project_templates.rst @@ -0,0 +1,198 @@ +Project Templates +================= + +MailThunder can scaffold a new project directory with pre-built template files, +giving you a ready-to-use starting point for email automation. + +---- + +Creating a Project +------------------ + +**From Python:** + +.. code-block:: python + + from je_mail_thunder import create_project_dir + + # Create in the current working directory with default name "MailThunder" + create_project_dir() + + # Create at a specific path with a custom name + create_project_dir(project_path="/path/to/projects", parent_name="MyMailProject") + +**From CLI:** + +.. code-block:: bash + + python -m je_mail_thunder -c /path/to/project + +**Parameters:** + +.. list-table:: + :header-rows: 1 + :widths: 25 20 55 + + * - Parameter + - Default + - Description + * - ``project_path`` + - ``os.getcwd()`` + - Directory where the project folder will be created + * - ``parent_name`` + - ``"MailThunder"`` + - Name of the project root directory + +---- + +Generated Structure +------------------- + +.. code-block:: text + + MyMailProject/ + keyword/ + keyword1.json # SMTP send email template + keyword2.json # IMAP read and export template + bad_keyword_1.json # Package loading example (security warning) + executor/ + executor_one_file.py # Execute a single action file + executor_folder.py # Execute all action files in a directory + executor_bad_file.py # Bad practice example (security warning) + +---- + +Template Files: keyword/ +------------------------- + +**keyword1.json** — SMTP send email template: + +.. code-block:: json + + [ + ["MT_smtp_later_init"], + ["MT_smtp_create_message_and_send", { + "message_content": "test", + "message_setting_dict": { + "Subject": "test_subject", + "To": "example@gmail.com", + "From": "example@gmail.com" + } + }], + ["smtp_quit"] + ] + +**keyword2.json** — IMAP read and export template: + +.. code-block:: json + + [ + ["MT_imap_later_init"], + ["MT_imap_select_mailbox"], + ["MT_imap_output_all_mail_as_file"] + ] + +**bad_keyword_1.json** — Package loading example: + +.. code-block:: json + + [ + ["MT_add_package_to_executor", ["os"]], + ["os_system", ["python --version"]], + ["os_system", ["python -m pip --version"]] + ] + +.. warning:: + + ``bad_keyword_1.json`` demonstrates loading the ``os`` package into the executor, + which enables running arbitrary system commands. This is included as an educational + example of what **not** to do in production. Never load ``os``, ``subprocess``, or + similar packages when processing untrusted input. + +---- + +Template Files: executor/ +-------------------------- + +**executor_one_file.py** — Execute a single action file: + +.. code-block:: python + + from je_mail_thunder import execute_action, read_action_json + + execute_action( + read_action_json( + r"/path/to/MyMailProject/keyword/keyword1.json" + ) + ) + +**executor_folder.py** — Execute all JSON action files in a directory: + +.. code-block:: python + + from je_mail_thunder import execute_files, get_dir_files_as_list + + execute_files( + get_dir_files_as_list( + r"/path/to/MyMailProject/keyword" + ) + ) + +**executor_bad_file.py** — Bad practice example: + +.. code-block:: python + + # This example is primarily intended to remind users of the importance of verifying input. + from je_mail_thunder import execute_action, read_action_json + + execute_action( + read_action_json( + r"/path/to/MyMailProject/keyword/bad_keyword_1.json" + ) + ) + +.. note:: + + The ``{temp}`` placeholder in template source code is replaced with the actual + absolute path during project creation. + +---- + +Customizing Templates +--------------------- + +After scaffolding, you can freely modify the generated files: + +1. Edit ``keyword/*.json`` files to match your email workflow +2. Update the email addresses, subjects, and content +3. Add new ``.json`` action files to the ``keyword/`` directory +4. Modify ``executor/*.py`` files to add error handling or custom logic + +**Example: Adding a new workflow:** + +Create ``keyword/weekly_report.json``: + +.. code-block:: json + + { + "auto_control": [ + ["MT_smtp_later_init"], + ["MT_smtp_create_message_with_attach_and_send", { + "message_content": "Please find the weekly report attached.", + "message_setting_dict": { + "Subject": "Weekly Report", + "To": "team@company.com", + "From": "reporter@company.com" + }, + "attach_file": "/data/reports/weekly.pdf", + "use_html": false + }], + ["smtp_quit"] + ] + } + +Then execute it: + +.. code-block:: bash + + python -m je_mail_thunder -e MyMailProject/keyword/weekly_report.json diff --git a/docs/source/docs/Eng/read_google_mail.rst b/docs/source/docs/Eng/read_google_mail.rst index ca6d9af..1bb2392 100644 --- a/docs/source/docs/Eng/read_google_mail.rst +++ b/docs/source/docs/Eng/read_google_mail.rst @@ -1,30 +1,327 @@ -Read Google Mail via IMAP +Reading Emails (IMAP) +===================== + +MailThunder's ``IMAPWrapper`` extends ``imaplib.IMAP4_SSL`` to provide methods for +reading, searching, and exporting emails via the IMAP4 protocol. + +Default connection: ``imap.gmail.com`` (SSL). + +.. note:: + + **Gmail users:** Make sure you have + `enabled IMAP `_ in your Gmail settings. + +---- + +Reading All Emails from INBOX +----------------------------- + +.. code-block:: python + + from je_mail_thunder import IMAPWrapper + + with IMAPWrapper() as imap: + imap.later_init() + imap.select_mailbox("INBOX") + emails = imap.mail_content_list() + for mail in emails: + print(f"Subject: {mail['SUBJECT']}") + print(f"From: {mail['FROM']}") + print(f"To: {mail['TO']}") + print(f"Body: {mail['BODY'][:200]}...") + print("---") + +**Returned dictionary format:** + +Each email is a dictionary with four keys: + +.. code-block:: python + + { + "SUBJECT": "Email subject line", # str — decoded subject + "FROM": "sender@example.com", # str — sender address + "TO": "receiver@example.com", # str — recipient address + "BODY": "Email body content..." # str — body text (decoded) + } + +For multipart emails, the body of the first part is extracted. + +---- + +Searching Emails +---------------- + +The ``search_str`` parameter follows the +`IMAP SEARCH command syntax (RFC 3501 Section 6.4.4) `_. + +**Search by sender:** + +.. code-block:: python + + emails = imap.mail_content_list(search_str='FROM "someone@example.com"') + +**Search by subject:** + +.. code-block:: python + + emails = imap.mail_content_list(search_str='SUBJECT "Important"') + +**Search unread emails:** + +.. code-block:: python + + emails = imap.mail_content_list(search_str="UNSEEN") + +**Search by date:** + +.. code-block:: python + + emails = imap.mail_content_list(search_str='SINCE "01-Jan-2025"') + emails = imap.mail_content_list(search_str='BEFORE "31-Dec-2025"') + emails = imap.mail_content_list(search_str='ON "15-Jun-2025"') + +**Combine multiple criteria** (implicit AND): + +.. code-block:: python + + emails = imap.mail_content_list(search_str='FROM "boss@company.com" UNSEEN') + emails = imap.mail_content_list(search_str='SINCE "01-Jan-2025" SUBJECT "Report"') + +**Full search criteria reference:** + +.. list-table:: + :header-rows: 1 + :widths: 30 70 + + * - Criteria + - Description + * - ``ALL`` + - All messages in the mailbox (default) + * - ``UNSEEN`` + - Messages not marked as read + * - ``SEEN`` + - Messages marked as read + * - ``FLAGGED`` + - Messages with the \\Flagged flag (starred in Gmail) + * - ``UNFLAGGED`` + - Messages without the \\Flagged flag + * - ``ANSWERED`` + - Messages that have been replied to + * - ``UNANSWERED`` + - Messages that have not been replied to + * - ``DELETED`` + - Messages marked for deletion + * - ``FROM "string"`` + - Messages from the specified sender + * - ``TO "string"`` + - Messages sent to the specified recipient + * - ``CC "string"`` + - Messages with the specified CC recipient + * - ``SUBJECT "string"`` + - Messages with text in the subject + * - ``BODY "string"`` + - Messages containing text in the body + * - ``TEXT "string"`` + - Messages containing text in headers or body + * - ``SINCE "DD-Mon-YYYY"`` + - Messages on or after the date (e.g., ``"01-Jan-2025"``) + * - ``BEFORE "DD-Mon-YYYY"`` + - Messages before the date + * - ``ON "DD-Mon-YYYY"`` + - Messages on the exact date + * - ``LARGER n`` + - Messages larger than n bytes + * - ``SMALLER n`` + - Messages smaller than n bytes + * - ``NEW`` + - Messages that are recent and unseen + * - ``OLD`` + - Messages that are not recent + +---- + +Exporting All Emails to Files +------------------------------ + +Export every email in the selected mailbox to local files: + +.. code-block:: python + + from je_mail_thunder import IMAPWrapper + + with IMAPWrapper() as imap: + imap.later_init() + imap.select_mailbox("INBOX") + exported = imap.output_all_mail_as_file() + print(f"Exported {len(exported)} emails") + +**File naming:** + +- Each email is saved in the current working directory +- Filename = email subject + numeric suffix +- First occurrence: ``My Subject0`` +- Duplicate subjects: ``My Subject1``, ``My Subject2``, ... +- File content = decoded email body text + +---- + +Getting Raw Mail Details +------------------------ + +For advanced use cases, ``search_mailbox()`` returns the raw parsed data: + +.. code-block:: python + + from je_mail_thunder import IMAPWrapper + + with IMAPWrapper() as imap: + imap.later_init() + imap.select_mailbox("INBOX") + raw_list = imap.search_mailbox() + for response, decode_info, message in raw_list: + # response: IMAP response status string (e.g., "OK") + # decode_info: raw RFC822 decode header bytes (e.g., b'1 (RFC822 {12345}') + # message: email.message.Message object — full Python email object + print(f"Subject: {message.get('Subject')}") + print(f"Date: {message.get('Date')}") + print(f"Content-Type: {message.get_content_type()}") + print(f"Message-ID: {message.get('Message-ID')}") + + # Access all headers + for key in message.keys(): + print(f" {key}: {message[key]}") + + # Access parts of multipart messages + if message.is_multipart(): + for i, part in enumerate(message.get_payload()): + print(f" Part {i}: {part.get_content_type()}") + +The ``message`` object is a standard Python ``email.message.Message`` (parsed with +``email.policy.default``), giving you full access to all headers, parts, and +decoding utilities. + +---- + +Read-Only Mode +-------------- + +Open the mailbox in read-only mode to prevent any side effects (e.g., marking +emails as read): + +.. code-block:: python + + from je_mail_thunder import IMAPWrapper + + with IMAPWrapper() as imap: + imap.later_init() + success = imap.select_mailbox("INBOX", readonly=True) + if success: + emails = imap.mail_content_list() + +---- + +Selecting Different Mailboxes +----------------------------- + +Besides ``INBOX``, you can select other standard or provider-specific mailboxes: + +.. code-block:: python + + # Standard mailboxes + imap.select_mailbox("Sent") + imap.select_mailbox("Drafts") + imap.select_mailbox("Trash") + + # Gmail-specific labels + imap.select_mailbox("[Gmail]/All Mail") + imap.select_mailbox("[Gmail]/Starred") + imap.select_mailbox("[Gmail]/Spam") + imap.select_mailbox("[Gmail]/Important") + +The ``select_mailbox()`` method returns ``True`` if the selection succeeded +(IMAP status ``"OK"``), ``False`` otherwise. + +---- + +Using a Different IMAP Provider +--------------------------------- + +Pass a custom ``host`` to the constructor: + +.. code-block:: python + + from je_mail_thunder import IMAPWrapper + + # Outlook / Office 365 + imap = IMAPWrapper(host="outlook.office365.com") + + # Yahoo Mail + imap = IMAPWrapper(host="imap.mail.yahoo.com") + + # Custom server + imap = IMAPWrapper(host="imap.example.com") + ---- +Manual Login Without Context Manager +-------------------------------------- + .. code-block:: python - from je_mail_thunder import IMAPWrapper, set_mail_thunder_os_environ + from je_mail_thunder import IMAPWrapper, set_mail_thunder_os_environ + + imap = IMAPWrapper(host="imap.gmail.com") + + set_mail_thunder_os_environ( + "your_email@gmail.com", + "your_app_password" + ) + + imap.later_init() + imap.select_mailbox("INBOX") + + for mail in imap.mail_content_list(): + print(mail.get("SUBJECT")) + print(mail.get("FROM")) + print(mail.get("TO")) + print(mail.get("BODY")) + + # Always quit when done + imap.quit() + +---- + +Reading via JSON Scripting Engine +--------------------------------- + +**Read and display emails:** + +.. code-block:: json + + { + "auto_control": [ + ["MT_imap_later_init"], + ["MT_imap_select_mailbox", {"mailbox": "INBOX", "readonly": true}], + ["MT_imap_mail_content_list", {"search_str": "UNSEEN"}], + ["MT_imap_quit"] + ] + } + +**Export all emails to files:** + +.. code-block:: json + + { + "auto_control": [ + ["MT_imap_later_init"], + ["MT_imap_select_mailbox"], + ["MT_imap_output_all_mail_as_file"], + ["MT_imap_quit"] + ] + } - # Set imap host - imap_host = 'imap.gmail.com' - # Init IMAPWrapper - imap_wrapper = IMAPWrapper(host=imap_host) +Execute via CLI: - set_mail_thunder_os_environ( - "test_user", # your user - "test_password" # your password - ) +.. code-block:: bash - imap_wrapper.later_init() - # Select INBOX - imap_wrapper.select() - # Get mail list - mail_list = imap_wrapper.mail_content_list() - # Print SUBJECT FROM TO BODY - for mail in mail_list: - print(mail.get("SUBJECT")) - print(mail.get("FROM")) - print(mail.get("TO")) - print(mail.get("BODY")) - # Quit - imap_wrapper.quit() + python -m je_mail_thunder -e /path/to/read_action.json diff --git a/docs/source/docs/Eng/scripting_engine.rst b/docs/source/docs/Eng/scripting_engine.rst new file mode 100644 index 0000000..6c1e5ae --- /dev/null +++ b/docs/source/docs/Eng/scripting_engine.rst @@ -0,0 +1,376 @@ +JSON Scripting Engine +===================== + +MailThunder includes a powerful JSON-based scripting engine that lets you automate +email workflows without writing Python code. The engine is built around the +``Executor`` class, which maps command names to Python callables. + +---- + +How It Works +------------ + +1. You write a JSON file (or dict) containing an ``auto_control`` key +2. The value is a list of action commands +3. Each command is executed sequentially by the ``Executor`` +4. Results are collected and returned as a dict + +.. code-block:: text + + JSON Action File + │ + ▼ + execute_action(action_list) + │ + ├── If dict: extract action_list["auto_control"] + ├── If list: use directly + │ + ▼ + For each action in action_list: + │ + ├── action = ["command_name"] ──▶ executor.event_dict["command_name"]() + ├── action = ["command_name", {k: v}] ──▶ executor.event_dict["command_name"](**{k: v}) + └── action = ["command_name", [a, b]] ──▶ executor.event_dict["command_name"](*[a, b]) + +---- + +Action File Format +------------------ + +Action files use the ``auto_control`` key containing a list of commands: + +.. code-block:: json + + { + "auto_control": [ + ["command_name"], + ["command_name", {"key": "value"}], + ["command_name", ["arg1", "arg2"]] + ] + } + +**Argument conventions:** + +.. list-table:: + :header-rows: 1 + :widths: 40 20 40 + + * - Format + - Type + - Python Equivalent + * - ``["command"]`` + - No arguments + - ``command()`` + * - ``["command", {"k": "v"}]`` + - Keyword arguments + - ``command(**{"k": "v"})`` + * - ``["command", ["a", "b"]]`` + - Positional arguments + - ``command(*["a", "b"])`` + +You can also pass a plain list (without the ``auto_control`` wrapper) directly to +``execute_action()``: + +.. code-block:: python + + from je_mail_thunder import execute_action + + execute_action([ + ["MT_smtp_later_init"], + ["smtp_quit"] + ]) + +---- + +Built-in Commands +----------------- + +**SMTP commands:** + +.. list-table:: + :header-rows: 1 + :widths: 40 60 + + * - Command + - Description + * - ``MT_smtp_later_init`` + - Initialize and log in to SMTP server + * - ``MT_smtp_create_message_and_send`` + - Create and send a plain text email + * - ``MT_smtp_create_message_with_attach_and_send`` + - Create and send an email with attachment + * - ``smtp_quit`` + - Disconnect from SMTP server + +**IMAP commands:** + +.. list-table:: + :header-rows: 1 + :widths: 40 60 + + * - Command + - Description + * - ``MT_imap_later_init`` + - Initialize and log in to IMAP server + * - ``MT_imap_select_mailbox`` + - Select a mailbox (default: INBOX) + * - ``MT_imap_search_mailbox`` + - Search and get raw mail details + * - ``MT_imap_mail_content_list`` + - Get parsed mail content as list of dicts + * - ``MT_imap_output_all_mail_as_file`` + - Export all emails to local files + * - ``MT_imap_quit`` + - Disconnect from IMAP server + +**Authentication commands:** + +.. list-table:: + :header-rows: 1 + :widths: 40 60 + + * - Command + - Description + * - ``MT_set_mail_thunder_os_environ`` + - Set auth environment variables + * - ``MT_get_mail_thunder_os_environ`` + - Get current auth environment variables + +**Package management:** + +.. list-table:: + :header-rows: 1 + :widths: 40 60 + + * - Command + - Description + * - ``MT_add_package_to_executor`` + - Load a Python package into the executor + +**Python builtins:** + +All Python built-in functions (``print``, ``len``, ``range``, ``type``, ``str``, +``int``, ``list``, ``dict``, etc.) are automatically registered and available as +commands. + +---- + +Examples +-------- + +**Send a plain text email:** + +.. code-block:: json + + { + "auto_control": [ + ["MT_smtp_later_init"], + ["MT_smtp_create_message_and_send", { + "message_content": "Hello from the scripting engine!", + "message_setting_dict": { + "Subject": "Automated Email", + "To": "receiver@gmail.com", + "From": "sender@gmail.com" + } + }], + ["smtp_quit"] + ] + } + +**Send an email with attachment:** + +.. code-block:: json + + { + "auto_control": [ + ["MT_smtp_later_init"], + ["MT_smtp_create_message_with_attach_and_send", { + "message_content": "Please review the attached report.", + "message_setting_dict": { + "Subject": "Monthly Report", + "To": "receiver@gmail.com", + "From": "sender@gmail.com" + }, + "attach_file": "/path/to/report.pdf", + "use_html": false + }], + ["smtp_quit"] + ] + } + +**Read and export all emails:** + +.. code-block:: json + + { + "auto_control": [ + ["MT_imap_later_init"], + ["MT_imap_select_mailbox"], + ["MT_imap_output_all_mail_as_file"], + ["MT_imap_quit"] + ] + } + +**Search unread emails in read-only mode:** + +.. code-block:: json + + { + "auto_control": [ + ["MT_imap_later_init"], + ["MT_imap_select_mailbox", {"mailbox": "INBOX", "readonly": true}], + ["MT_imap_mail_content_list", {"search_str": "UNSEEN"}], + ["MT_imap_quit"] + ] + } + +**Set credentials then send:** + +.. code-block:: json + + { + "auto_control": [ + ["MT_set_mail_thunder_os_environ", { + "mail_thunder_user": "sender@gmail.com", + "mail_thunder_user_password": "your_app_password" + }], + ["MT_smtp_later_init"], + ["MT_smtp_create_message_and_send", { + "message_content": "Credentials set via script!", + "message_setting_dict": { + "Subject": "Auth Test", + "To": "receiver@gmail.com", + "From": "sender@gmail.com" + } + }], + ["smtp_quit"] + ] + } + +**Using Python builtins:** + +.. code-block:: json + + { + "auto_control": [ + ["print", ["Hello from the executor!"]], + ["print", ["The answer is: 42"]] + ] + } + +---- + +Executing Action Files +---------------------- + +**From Python:** + +.. code-block:: python + + from je_mail_thunder import execute_action, read_action_json + + # Execute a single file + result = execute_action(read_action_json("/path/to/action.json")) + + # The result is a dict mapping "execute: [action]" to return values + for action, return_value in result.items(): + print(action, return_value) + +**Execute multiple files in a directory:** + +.. code-block:: python + + from je_mail_thunder import execute_files, get_dir_files_as_list + + # Get all .json files in the directory + files = get_dir_files_as_list("/path/to/actions/") + results = execute_files(files) + + # results is a list of dicts, one per file + for i, result in enumerate(results): + print(f"File {i}: {result}") + +**From CLI:** + +.. code-block:: bash + + # Execute a single file + python -m je_mail_thunder -e /path/to/action.json + + # Execute all JSON files in a directory + python -m je_mail_thunder -d /path/to/actions/ + + # Execute a JSON string + python -m je_mail_thunder --execute_str '[["print", ["Hello!"]]]' + +---- + +Extending with Custom Commands +------------------------------ + +Add your own functions to the executor: + +.. code-block:: python + + from je_mail_thunder import add_command_to_executor, execute_action + + def send_notification(channel, message): + print(f"[{channel}] {message}") + return {"status": "sent", "channel": channel} + + def process_data(filename): + print(f"Processing {filename}...") + return {"processed": filename} + + # Register custom commands + add_command_to_executor({ + "notify": send_notification, + "process": process_data + }) + + # Use them in action lists + execute_action([ + ["process", ["data.csv"]], + ["notify", {"channel": "#alerts", "message": "Processing complete"}] + ]) + +.. warning:: + + Only ``types.MethodType`` and ``types.FunctionType`` instances can be added. + Passing a non-callable (e.g., a string or int) will raise ``AddCommandException``. + +---- + +Execution Output +---------------- + +When actions are executed, each command and its return value are printed to stdout: + +.. code-block:: text + + execute: ['MT_smtp_later_init'] + None + execute: ['MT_smtp_create_message_and_send', {...}] + None + execute: ['smtp_quit'] + None + +If an action fails, the exception is caught, logged, and stored in the result dict: + +.. code-block:: text + + execute: ['invalid_command'] + TypeError("'NoneType' object is not callable") + +The executor does **not** stop on errors — it continues executing subsequent actions +and collects all results. + +---- + +Thread Safety +------------- + +The ``Executor`` class is instantiated as a module-level singleton (``executor``). +JSON file reads (``read_action_json``) use a ``threading.Lock`` for thread-safe +file access. However, the executor's ``event_dict`` itself is not locked, so +concurrent modifications to the command registry should be avoided. diff --git a/docs/source/docs/Eng/send_google_mail.rst b/docs/source/docs/Eng/send_google_mail.rst index 95f59a0..a62fb14 100644 --- a/docs/source/docs/Eng/send_google_mail.rst +++ b/docs/source/docs/Eng/send_google_mail.rst @@ -1,24 +1,331 @@ -Send Google Mail via SMTP +Sending Emails (SMTP) +===================== + +MailThunder's ``SMTPWrapper`` extends ``smtplib.SMTP_SSL`` to provide convenient methods +for creating and sending emails. It supports plain text, HTML, and file attachments. + +Default connection: ``smtp.gmail.com:465`` (SSL). + +---- + +Sending a Plain Text Email +--------------------------- + +The simplest way to send an email using the context manager: + +.. code-block:: python + + from je_mail_thunder import SMTPWrapper + + with SMTPWrapper() as smtp: + smtp.later_init() + smtp.create_message_and_send( + message_content="Hello from MailThunder!", + message_setting_dict={ + "Subject": "Test Email", + "From": "sender@gmail.com", + "To": "receiver@gmail.com" + } + ) + +The ``message_setting_dict`` accepts any valid ``EmailMessage`` header key: + +- ``Subject`` — Email subject line +- ``From`` — Sender address +- ``To`` — Recipient address +- ``Cc`` — Carbon copy recipients +- ``Bcc`` — Blind carbon copy recipients +- ``Reply-To`` — Reply address +- Any other RFC 2822 header + ---- +Sending an Email with Attachment +-------------------------------- + +MailThunder auto-detects the MIME type of the attachment file using +``mimetypes.guess_type()``: + .. code-block:: python - from je_mail_thunder import SMTPWrapper - from je_mail_thunder import mail_thunder_content_data_dict + from je_mail_thunder import SMTPWrapper + + with SMTPWrapper() as smtp: + smtp.later_init() + smtp.create_message_with_attach_and_send( + message_content="Please see the attached file.", + message_setting_dict={ + "Subject": "Email with Attachment", + "From": "sender@gmail.com", + "To": "receiver@gmail.com" + }, + attach_file="/path/to/file.pdf", + use_html=False + ) + +**MIME type detection:** + +.. list-table:: + :header-rows: 1 + :widths: 20 30 50 + + * - Category + - MIME Type + - File Extensions + * - Text + - ``text/*`` + - ``.txt``, ``.csv``, ``.html``, ``.xml``, ``.json`` + * - Image + - ``image/*`` + - ``.png``, ``.jpg``, ``.jpeg``, ``.gif``, ``.svg``, ``.bmp`` + * - Audio + - ``audio/*`` + - ``.mp3``, ``.wav``, ``.ogg``, ``.flac`` + * - Fallback + - ``application/octet-stream`` + - ``.pdf``, ``.zip``, ``.exe``, ``.docx``, ``.xlsx``, etc. + +The attachment is added with ``Content-Disposition: attachment`` and a +``Content-ID`` header using the filename. + +---- + +Sending an HTML Email +--------------------- + +Set ``use_html=True`` to send HTML-formatted email content: - smtp_wrapper = SMTPWrapper() - # need have mail_thunder_content.json in current folder - # and need to init SMTPWrapper first +.. code-block:: python + + from je_mail_thunder import SMTPWrapper + + html_content = """ + + +

Hello!

+

This is an HTML email sent via MailThunder.

+
    +
  • Feature 1
  • +
  • Feature 2
  • +
+ + + """ + + with SMTPWrapper() as smtp: + smtp.later_init() + smtp.create_message_with_attach_and_send( + message_content=html_content, + message_setting_dict={ + "Subject": "HTML Email", + "From": "sender@gmail.com", + "To": "receiver@gmail.com" + }, + attach_file="/path/to/style.css", + use_html=True + ) + +When ``use_html=True``, the body is wrapped in ``MIMEText(content, "html")`` +instead of ``MIMEText(content)`` (which defaults to ``"plain"``). + +---- + +Two-Step: Create Then Send +-------------------------- + +Separate message creation from sending for more control: + +.. code-block:: python + + from je_mail_thunder import SMTPWrapper + + with SMTPWrapper() as smtp: + smtp.later_init() + + # Step 1: Create the message object (returns EmailMessage) + message = smtp.create_message( + message_content="Hello!", + message_setting_dict={ + "Subject": "Two-Step Email", + "From": "sender@gmail.com", + "To": "receiver@gmail.com" + } + ) + + # Inspect or modify the message before sending + print(message["Subject"]) # "Two-Step Email" + + # Step 2: Send it (inherited from SMTP_SSL) + smtp.send_message(message) + +For attachments: + +.. code-block:: python + + # Step 1: Create with attachment (returns MIMEMultipart) + message = smtp.create_message_with_attach( + message_content="See attached.", + message_setting_dict={ + "Subject": "Report", + "From": "sender@gmail.com", + "To": "receiver@gmail.com" + }, + attach_file="/path/to/report.pdf", + use_html=False + ) + + # Step 2: Send + smtp.send_message(message) + +---- + +Using a Different SMTP Provider +-------------------------------- + +Pass custom ``host`` and ``port`` to the constructor: + +.. code-block:: python + + from je_mail_thunder import SMTPWrapper + + # Outlook / Office 365 + smtp = SMTPWrapper(host="smtp-mail.outlook.com", port=465) + + # Yahoo Mail + smtp = SMTPWrapper(host="smtp.mail.yahoo.com", port=465) + + # Custom SMTP server + smtp = SMTPWrapper(host="mail.example.com", port=465) + +.. note:: + + MailThunder uses ``SMTP_SSL`` (implicit SSL on connect). If your provider + requires STARTTLS on port 587, you may need to use the underlying + ``smtplib`` directly or subclass ``SMTPWrapper``. + +---- + +Manual Login Without Context Manager +-------------------------------------- + +If you prefer explicit lifecycle management: + +.. code-block:: python + + from je_mail_thunder import SMTPWrapper + from je_mail_thunder import mail_thunder_content_data_dict + + smtp = SMTPWrapper() + + # Inject credentials manually + mail_thunder_content_data_dict.update({ + "user": "your_email@gmail.com", + "password": "your_app_password", + }) + + # Login + smtp.try_to_login_with_env_or_content() + print(smtp.login_state) # True + + # Create and send + user = mail_thunder_content_data_dict.get("user") + message = smtp.create_message( + "Hello World!", + {"Subject": "Manual Login", "To": user, "From": user} + ) + smtp.send_message(message) + + # Always quit when done + smtp.quit() + +---- + +Sending via JSON Scripting Engine +--------------------------------- + +Send emails without writing Python code by using a JSON action file: + +.. code-block:: json + + { + "auto_control": [ + ["MT_smtp_later_init"], + ["MT_smtp_create_message_and_send", { + "message_content": "Hello from scripting engine!", + "message_setting_dict": { + "Subject": "Automated Email", + "To": "receiver@gmail.com", + "From": "sender@gmail.com" + } + }], + ["smtp_quit"] + ] + } + +With attachment: + +.. code-block:: json + + { + "auto_control": [ + ["MT_smtp_later_init"], + ["MT_smtp_create_message_with_attach_and_send", { + "message_content": "Report attached.", + "message_setting_dict": { + "Subject": "Monthly Report", + "To": "receiver@gmail.com", + "From": "sender@gmail.com" + }, + "attach_file": "/path/to/report.pdf", + "use_html": false + }], + ["smtp_quit"] + ] + } + +Execute via CLI: + +.. code-block:: bash + + python -m je_mail_thunder -e /path/to/send_action.json + +---- + +Sending via Command Line +------------------------- + +Execute a JSON string directly from the terminal: + +.. code-block:: bash + + python -m je_mail_thunder --execute_str '[["MT_smtp_later_init"], ["MT_smtp_create_message_and_send", {"message_content": "Hello!", "message_setting_dict": {"Subject": "CLI Email", "To": "receiver@gmail.com", "From": "sender@gmail.com"}}], ["smtp_quit"]]' + +---- + +Error Handling +-------------- + +All ``SMTPWrapper`` methods catch exceptions internally and log them. If you need +to handle errors yourself, use ``try_to_login_with_env_or_content()`` which returns +a ``bool``: + +.. code-block:: python - mail_thunder_content_data_dict.update({ - "user": "test_user", # your user - "password": "test_password", # your password - }) + from je_mail_thunder import SMTPWrapper - user = mail_thunder_content_data_dict.get("user") + smtp = SMTPWrapper() + success = smtp.try_to_login_with_env_or_content() - smtp_wrapper.try_to_login_with_env_or_content() + if success: + smtp.create_message_and_send( + message_content="Authenticated!", + message_setting_dict={ + "Subject": "Success", + "From": "sender@gmail.com", + "To": "receiver@gmail.com" + } + ) + else: + print("Login failed — check credentials") - message = smtp_wrapper.create_message("test", {"Subject": "test_subject", "To": user, "From": user}) - smtp_wrapper.send_message(message) - smtp_wrapper.quit() \ No newline at end of file + smtp.quit() diff --git a/docs/source/docs/Eng/socket_server.rst b/docs/source/docs/Eng/socket_server.rst new file mode 100644 index 0000000..81f8073 --- /dev/null +++ b/docs/source/docs/Eng/socket_server.rst @@ -0,0 +1,225 @@ +Socket Server +============= + +MailThunder includes a TCP socket server that accepts JSON action commands remotely, +enabling remote control of email automation workflows over the network. + +The server is built on Python's ``socketserver.ThreadingMixIn`` and +``socketserver.TCPServer``, handling each client connection in a separate thread. + +---- + +Starting the Server +------------------- + +.. code-block:: python + + from je_mail_thunder.utils.socket_server.mail_thunder_socket_server import ( + start_autocontrol_socket_server + ) + + server = start_autocontrol_socket_server(host="localhost", port=9944) + # Server is now running in a daemon background thread + +**Parameters:** + +.. list-table:: + :header-rows: 1 + :widths: 20 20 60 + + * - Parameter + - Default + - Description + * - ``host`` + - ``"localhost"`` + - Host address to bind to + * - ``port`` + - ``9944`` + - TCP port to listen on + +The server can also accept ``host`` and ``port`` from ``sys.argv``: + +- ``sys.argv[1]`` → ``host`` +- ``sys.argv[2]`` → ``port`` + +The server thread is a daemon thread — it will be automatically terminated when +the main program exits. + +---- + +Sending Commands to the Server +------------------------------ + +Commands are sent as JSON-encoded action lists (the same format as the +``auto_control`` list without the wrapper key): + +.. code-block:: python + + import socket + import json + + # Connect to the server + client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + client.connect(("localhost", 9944)) + + # Send an action command list + command = json.dumps([ + ["MT_smtp_later_init"], + ["MT_smtp_create_message_and_send", { + "message_content": "Sent via socket server!", + "message_setting_dict": { + "Subject": "Remote Email", + "To": "receiver@gmail.com", + "From": "sender@gmail.com" + } + }], + ["smtp_quit"] + ]) + client.send(command.encode("utf-8")) + + # Receive response + response = client.recv(8192).decode("utf-8") + print(response) + + client.close() + +---- + +Server Protocol +--------------- + +**Request format:** + +.. code-block:: text + + Client ──▶ Server: JSON-encoded action list (UTF-8 bytes, max 8192 bytes) + +**Response format:** + +.. code-block:: text + + Server ──▶ Client: For each command result: + \n + Followed by terminator: + Return_Data_Over_JE\n + +**Shutdown:** + +.. code-block:: text + + Client ──▶ Server: "quit_server" (literal string, not JSON) + Server: Shuts down gracefully + +---- + +Example: Full Client-Server Interaction +--------------------------------------- + +**Server (server.py):** + +.. code-block:: python + + from je_mail_thunder.utils.socket_server.mail_thunder_socket_server import ( + start_autocontrol_socket_server + ) + import time + + server = start_autocontrol_socket_server("localhost", 9944) + print("Server started on localhost:9944") + + # Keep the main thread alive + try: + while not server.close_flag: + time.sleep(1) + except KeyboardInterrupt: + server.shutdown() + print("Server stopped") + +**Client (client.py):** + +.. code-block:: python + + import socket + import json + + def send_command(host, port, actions): + client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + client.connect((host, port)) + client.send(json.dumps(actions).encode("utf-8")) + response = client.recv(8192).decode("utf-8") + client.close() + return response + + # Send an email remotely + result = send_command("localhost", 9944, [ + ["MT_smtp_later_init"], + ["MT_smtp_create_message_and_send", { + "message_content": "Remote email!", + "message_setting_dict": { + "Subject": "Socket Server Test", + "To": "receiver@gmail.com", + "From": "sender@gmail.com" + } + }], + ["smtp_quit"] + ]) + print("Result:", result) + + # Shut down the server + result = send_command("localhost", 9944, "quit_server") + +.. note:: + + The ``quit_server`` command is a literal string, **not** a JSON-encoded action list. + +---- + +Server Architecture +------------------- + +.. code-block:: text + + TCPServer (socketserver.ThreadingMixIn + socketserver.TCPServer) + │ + ├── server.serve_forever() ← runs in daemon thread + │ + └── For each client connection: + │ + ▼ + TCPServerHandler.handle() + │ + ├── recv(8192) ← read command bytes + │ + ├── command == "quit_server" + │ └── server.shutdown() + │ + └── command is JSON action list + │ + ▼ + execute_action(json.loads(command)) + │ + ▼ + sendto(result) for each action result + sendto("Return_Data_Over_JE") + +**Key properties:** + +- ``server.close_flag`` — ``bool``, set to ``True`` when ``quit_server`` is received +- Each connection is handled in a new thread (``ThreadingMixIn``) +- Max receive buffer: 8192 bytes per command +- Encoding: UTF-8 + +---- + +Security Considerations +----------------------- + +.. warning:: + + The socket server does **not** provide any authentication or encryption. + Any client that can connect to the host:port can execute commands. + + **Do not** expose the socket server to public networks without additional + security measures (firewall rules, SSH tunneling, TLS wrapper, etc.). + + By default, binding to ``localhost`` restricts access to the local machine only. diff --git a/docs/source/docs/Zh/authentication.rst b/docs/source/docs/Zh/authentication.rst new file mode 100644 index 0000000..8dcb52d --- /dev/null +++ b/docs/source/docs/Zh/authentication.rst @@ -0,0 +1,190 @@ +認證設定 +======== + +MailThunder 支援兩種認證方式。當呼叫 ``later_init()`` 或 +``try_to_login_with_env_or_content()`` 時,系統會先嘗試 JSON 設定檔, +若找不到則使用環境變數。 + +認證流程 +-------- + +.. code-block:: text + + later_init() 被呼叫 + │ + ▼ + 讀取目前工作目錄的 mail_thunder_content.json + │ + ├── 找到檔案且包含 "user" + "password" + │ │ + │ ▼ + │ 使用檔案認證資訊登入 ─── 成功 ──▶ 完成 (login_state = True) + │ │ + │ └── 失敗 ──▶ 記錄錯誤 + │ + └── 找不到檔案或無效 + │ + ▼ + 讀取環境變數:mail_thunder_user + mail_thunder_user_password + │ + ├── 環境變數已設定 + │ │ + │ ▼ + │ 使用環境變數登入 ─── 成功 ──▶ 完成 (login_state = True) + │ │ + │ └── 失敗 ──▶ 記錄錯誤 + │ + └── 環境變數未設定 ──▶ 記錄錯誤 + +方式一:JSON 設定檔 +-------------------- + +在 **目前工作目錄** 建立名為 ``mail_thunder_content.json`` 的檔案: + +.. code-block:: json + + { + "user": "your_email@gmail.com", + "password": "your_app_password" + } + +.. warning:: + + 此檔案包含您的郵件認證資訊(明文)。請勿將其提交到版本控制系統。 + 請將 ``mail_thunder_content.json`` 加入 ``.gitignore``。 + +**內部運作方式:** + +1. ``read_output_content()`` 檢查 ``Path.cwd()`` 中是否存在 ``mail_thunder_content.json`` +2. 若找到,讀取 JSON 並更新 ``mail_thunder_content_data_dict`` +3. ``user`` 和 ``password`` 值傳遞給 ``smtplib.SMTP_SSL.login()`` + 或 ``imaplib.IMAP4_SSL.login()`` + +方式二:環境變數 +----------------- + +**選項 A — 在 Python 中於執行期設定:** + +.. code-block:: python + + from je_mail_thunder import set_mail_thunder_os_environ + + set_mail_thunder_os_environ( + mail_thunder_user="your_email@gmail.com", + mail_thunder_user_password="your_app_password" + ) + +此呼叫 ``os.environ.update()`` 設定兩個環境變數: + +- ``mail_thunder_user`` +- ``mail_thunder_user_password`` + +**選項 B — 在終端機中設定:** + +.. code-block:: bash + + # Linux / macOS + export mail_thunder_user="your_email@gmail.com" + export mail_thunder_user_password="your_app_password" + +.. code-block:: batch + + :: Windows CMD + set mail_thunder_user=your_email@gmail.com + set mail_thunder_user_password=your_app_password + +.. code-block:: powershell + + # Windows PowerShell + $env:mail_thunder_user = "your_email@gmail.com" + $env:mail_thunder_user_password = "your_app_password" + +**取得目前環境變數值:** + +.. code-block:: python + + from je_mail_thunder import get_mail_thunder_os_environ + + creds = get_mail_thunder_os_environ() + # 回傳: {"mail_thunder_user": "...", "mail_thunder_user_password": "..."} + +方式三:手動注入認證資訊 +------------------------- + +您可以在呼叫登入前直接更新全域認證字典: + +.. code-block:: python + + from je_mail_thunder import mail_thunder_content_data_dict + + mail_thunder_content_data_dict.update({ + "user": "your_email@gmail.com", + "password": "your_app_password", + }) + +這在認證資訊來自金鑰庫、資料庫或其他外部來源的程式化情境中很有用。 + +Gmail 特殊設定 +-------------- + +如果您使用 Gmail,有兩個額外需求: + +1. **使用應用程式密碼** — Gmail 不允許使用一般 Google 帳戶密碼登入。 + 您必須產生應用程式密碼: + + - 前往 `Google 應用程式密碼 `_ + - 選擇「郵件」和您的裝置 + - 複製產生的 16 字元密碼 + - 使用此密碼作為 ``password`` 值 + +2. **啟用 IMAP** (讀取郵件時需要) — Gmail 預設停用 IMAP 存取: + + - 前往 Gmail 設定 > 查看所有設定 > 轉寄和 POP/IMAP + - 在「IMAP 存取」下,選擇「啟用 IMAP」 + - 儲存變更 + +.. note:: + + 應用程式密碼需要您的 Google 帳戶啟用兩步驟驗證。 + +檢查登入狀態 (SMTP) +--------------------- + +``SMTPWrapper`` 透過 ``login_state`` 屬性追蹤認證狀態: + +.. code-block:: python + + from je_mail_thunder import SMTPWrapper + + smtp = SMTPWrapper() + print(smtp.login_state) # False + + success = smtp.try_to_login_with_env_or_content() + print(smtp.login_state) # 登入成功則為 True + print(success) # True 或 False + +透過 JSON 腳本引擎設定 +----------------------- + +在 JSON 動作檔中設定認證資訊: + +.. code-block:: json + + { + "auto_control": [ + ["MT_set_mail_thunder_os_environ", { + "mail_thunder_user": "your_email@gmail.com", + "mail_thunder_user_password": "your_app_password" + }], + ["MT_smtp_later_init"], + ["MT_smtp_create_message_and_send", { + "message_content": "Hello!", + "message_setting_dict": { + "Subject": "測試", + "From": "your_email@gmail.com", + "To": "receiver@gmail.com" + } + }], + ["smtp_quit"] + ] + } diff --git a/docs/source/docs/Zh/cli.rst b/docs/source/docs/Zh/cli.rst new file mode 100644 index 0000000..7275c19 --- /dev/null +++ b/docs/source/docs/Zh/cli.rst @@ -0,0 +1,152 @@ +命令列介面 +========== + +MailThunder 透過 ``python -m je_mail_thunder`` 提供命令列介面 (CLI), +可直接從終端機執行 JSON 動作檔、JSON 字串和建立專案。 + +---- + +使用方式 +-------- + +.. code-block:: bash + + python -m je_mail_thunder [選項] + +---- + +選項 +---- + +.. list-table:: + :header-rows: 1 + :widths: 10 25 65 + + * - 旗標 + - 完整旗標 + - 說明 + * - ``-e`` + - ``--execute_file`` + - 執行單一 JSON 動作檔 + * - ``-d`` + - ``--execute_dir`` + - 執行目錄中所有 JSON 動作檔 + * - + - ``--execute_str`` + - 直接執行 JSON 字串 + * - ``-c`` + - ``--create_project`` + - 建立包含模板的專案目錄 + +若未提供任何旗標,CLI 會引發 ``MailThunderArgparseException``。 + +---- + +執行單一動作檔 +-------------- + +.. code-block:: bash + + python -m je_mail_thunder -e /path/to/action.json + +讀取 JSON 檔案、解析動作列表,並傳遞給 ``execute_action()``。 + +---- + +執行目錄中所有檔案 +------------------ + +.. code-block:: bash + + python -m je_mail_thunder -d /path/to/actions/ + +掃描目錄中所有 ``.json`` 檔案(使用 ``get_dir_files_as_list()``), +並透過 ``execute_files()`` 依序執行。 + +---- + +執行 JSON 字串 +--------------- + +.. code-block:: bash + + python -m je_mail_thunder --execute_str '[["print", ["Hello from CLI!"]]]' + +解析 JSON 字串並直接執行。 + +.. note:: + + **Windows 注意事項:** 在 Windows 平台(``win32``、``cygwin``、``msys``), + JSON 字串會被雙重解析(``json.loads`` 呼叫兩次)以處理 shell 跳脫字元差異。 + + 在 Linux/macOS 上,單引號直接可用: + + .. code-block:: bash + + python -m je_mail_thunder --execute_str '[["print", ["Hello!"]]]' + +---- + +建立專案 +-------- + +.. code-block:: bash + + python -m je_mail_thunder -c /path/to/project + +在指定路徑建立包含模板檔案的專案目錄。 +詳見 :doc:`project_templates`。 + +---- + +結束代碼 +-------- + +- **0** — 所有動作執行成功 +- **1** — 發生錯誤(例外訊息印出到 ``stderr``) + +---- + +範例 +---- + +**從命令列寄送郵件:** + +.. code-block:: bash + + # 建立 JSON 動作檔 + cat > send.json << 'EOF' + { + "auto_control": [ + ["MT_smtp_later_init"], + ["MT_smtp_create_message_and_send", { + "message_content": "Hello from CLI!", + "message_setting_dict": { + "Subject": "CLI 測試", + "To": "receiver@gmail.com", + "From": "sender@gmail.com" + } + }], + ["smtp_quit"] + ] + } + EOF + + # 執行 + python -m je_mail_thunder -e send.json + +**執行多個動作檔:** + +.. code-block:: bash + + mkdir -p actions/ + cp send.json actions/ + cp read.json actions/ + python -m je_mail_thunder -d actions/ + +**建立新專案:** + +.. code-block:: bash + + python -m je_mail_thunder -c /home/user/projects + # 建立 /home/user/projects/MailThunder/ 包含模板 diff --git a/docs/source/docs/Zh/exceptions.rst b/docs/source/docs/Zh/exceptions.rst new file mode 100644 index 0000000..5394141 --- /dev/null +++ b/docs/source/docs/Zh/exceptions.rst @@ -0,0 +1,115 @@ +例外處理 +======== + +MailThunder 為不同的錯誤情境定義了一組自訂例外類別。 +所有例外都繼承自基底 ``MailThunderException`` 類別。 + +---- + +例外階層 +-------- + +.. code-block:: text + + Exception (Python 內建) + └── MailThunderException + ├── MailThunderJsonException + ├── MailThunderContentException + ├── MailThunderArgparseException + ├── ExecuteActionException + ├── AddCommandException + └── JsonActionException + +---- + +例外參考 +-------- + +.. list-table:: + :header-rows: 1 + :widths: 35 65 + + * - 例外 + - 引發時機 + * - ``MailThunderException`` + - 所有 MailThunder 錯誤的基底例外。不會直接引發。 + * - ``MailThunderJsonException`` + - JSON 格式化失敗(無效的 JSON ��料或型別錯誤) + * - ``MailThunderContentException`` + - 讀取或寫入 ``mail_thunder_content.json`` 時發生錯誤 + * - ``MailThunderArgparseException`` + - CLI 未收到有效參數或收到未知函式 + * - ``ExecuteActionException`` + - 執行器收到無效的動作格式、空動作列表或錯誤的資料型別 + * - ``AddCommandException`` + - ``add_command_to_executor()`` 收到非可呼叫值(非函式/方法) + * - ``JsonActionException`` + - 找不到 JSON 動作檔或無法儲存 + +---- + +錯誤標籤 +-------- + +每個例外都關聯一個人類可讀的錯誤標籤字串,定義在 +``je_mail_thunder.utils.exception.exception_tags``: + +.. list-table:: + :header-rows: 1 + :widths: 45 55 + + * - 錯誤標籤 + - 訊息 + * - ``mail_thunder_cant_reformat_json_error`` + - ``"Can't reformat json is type right?"`` + * - ``mail_thunder_wrong_json_data_error`` + - ``"Can't parser json"`` + * - ``mail_thunder_content_set_compiler_error`` + - ``"When set compiler using content make an error"`` + * - ``mail_thunder_content_file_error`` + - ``"MailThunder content file error"`` + * - ``mail_thunder_content_login_failed`` + - ``"can't login with mail thunder content"`` + * - ``mail_thunder_argparse_get_wrong_function`` + - ``"get unknown function"`` + * - ``add_command_exception`` + - ``"command value type should be as method or function"`` + * - ``executor_list_error`` + - ``"executor receive wrong data list is none or wrong type"`` + * - ``cant_execute_action_error`` + - ``"cant execute action"`` + * - ``cant_find_json_error`` + - ``"cant find json file"`` + * - ``cant_save_json_error`` + - ``"cant save json file"`` + * - ``action_is_null_error`` + - ``"json action is null"`` + +---- + +捕獲例外 +-------- + +您可以在程式碼中捕獲 MailThunder 例外: + +.. code-block:: python + + from je_mail_thunder import execute_action + from je_mail_thunder.utils.exception.exceptions import ( + MailThunderException, + ExecuteActionException, + JsonActionException, + ) + + try: + execute_action([["nonexistent_command"]]) + except ExecuteActionException as e: + print(f"執行器錯誤: {e}") + except MailThunderException as e: + print(f"MailThunder 錯誤: {e}") + +.. note:: + + 大多數 MailThunder 方法(特別是 ``SMTPWrapper`` 和 ``IMAPWrapper``) + 會在內部捕獲例外並記錄,而非向上傳播。例外更常由執行器、JSON 檔案工具 + 和 CLI 元件引發。 diff --git a/docs/source/docs/Zh/installation.rst b/docs/source/docs/Zh/installation.rst new file mode 100644 index 0000000..ded209b --- /dev/null +++ b/docs/source/docs/Zh/installation.rst @@ -0,0 +1,82 @@ +安裝指南 +======== + +本頁說明所有安裝 MailThunder 的方式。 + +系統需求 +-------- + +- **Python 3.9** 以上 +- 無需額外相依套件 (僅使用 Python 標準函式庫) +- ``pip`` 套件管理工具 + +從 PyPI 安裝 +------------- + +**穩定版本:** + +.. code-block:: bash + + pip install je_mail_thunder + +**開發版本** (最新功能,可能不穩定): + +.. code-block:: bash + + pip install je_mail_thunder_dev + +從原始碼安裝 +------------ + +克隆儲存庫並以可編輯(開發)模式安裝: + +.. code-block:: bash + + git clone https://github.com/Integration-Automation/MailThunder.git + cd MailThunder + pip install -e . + +安裝開發相依套件 +----------------- + +若您計劃貢獻程式碼或執行測試套件: + +.. code-block:: bash + + pip install -r dev_requirements.txt + +包含: + +- ``pytest`` — 用於執行單元測試 +- ``coverage`` — 用於程式碼覆蓋率報告 + +驗證安裝 +-------- + +安裝完成後,驗證 MailThunder 是否可用: + +.. code-block:: bash + + python -c "import je_mail_thunder; print('MailThunder 安裝成功')" + +查看版本資訊: + +.. code-block:: bash + + pip show je_mail_thunder + +升級 +---- + +升級至最新版本: + +.. code-block:: bash + + pip install --upgrade je_mail_thunder + +解除安裝 +-------- + +.. code-block:: bash + + pip uninstall je_mail_thunder diff --git a/docs/source/docs/Zh/logging.rst b/docs/source/docs/Zh/logging.rst new file mode 100644 index 0000000..b5e9fa5 --- /dev/null +++ b/docs/source/docs/Zh/logging.rst @@ -0,0 +1,134 @@ +日誌記錄 +======== + +MailThunder 使用 Python 標準 ``logging`` 模組記錄所有操作。 +日誌對於除錯認證問題、追蹤寄送/接收的郵件以及監控腳本執行器非常有用。 + +---- + +日誌記錄器設定 +-------------- + +日誌記錄器在 ``je_mail_thunder.utils.logging.loggin_instance`` 中設定: + +.. code-block:: python + + import logging + import sys + + mail_thunder_logger = logging.getLogger("Mail Thunder") + mail_thunder_logger.setLevel(logging.INFO) + +**記錄器名稱:** ``"Mail Thunder"`` + +---- + +日誌處理器 +---------- + +MailThunder 註冊兩個日誌處理器: + +.. list-table:: + :header-rows: 1 + :widths: 20 20 20 40 + + * - 處理器 + - 輸出 + - 級別 + - 說明 + * - 檔案處理器 + - ``Mail_Thunder.log`` + - ``INFO`` + - 將所有操作(INFO 以上)記錄到目前工作目錄的檔案 + * - 串流處理器 + - ``stderr`` + - ``WARNING`` + - 僅將警告和錯誤印出到主控台 + +**日誌格式:** + +.. code-block:: text + + %(asctime)s | %(name)s | %(levelname)s | %(message)s + +**日誌範例:** + +.. code-block:: text + + 2025-01-15 10:30:00,123 | Mail Thunder | INFO | MT_smtp_later_init + 2025-01-15 10:30:01,456 | Mail Thunder | INFO | smtp_create_message_and_send, message_content: Hello!, ... + 2025-01-15 10:30:01,789 | Mail Thunder | INFO | SMTP quit + 2025-01-15 10:30:02,012 | Mail Thunder | ERROR | smtp_try_to_login_with_env_or_content, failed: ... + +---- + +記錄的內容 +---------- + +**SMTP 操作 (INFO 級別):** + +- ``MT_smtp_later_init`` — SMTP 登入嘗試 +- ``smtp_create_message`` — 訊息建立,包含內容和設定 +- ``smtp_create_message_with_attach`` — 附件訊息建立 +- ``smtp_create_message_and_send`` — 寄送,包含內容詳情 +- ``smtp_create_message_with_attach_and_send`` — 附件寄送詳情 +- ``smtp_try_to_login_with_env_or_content`` — 登入嘗試 +- ``SMTP quit`` — 斷開連線 + +**IMAP 操作 (INFO 級別):** + +- ``MT_imap_later_init`` — IMAP 登入嘗試 +- ``imap_try_to_login_with_env_or_content`` — 登入嘗試 +- ``imap_select_mailbox`` — 信箱選擇,包含名稱和唯讀旗標 +- ``imap_search_mailbox`` — 搜尋條件 +- ``imap_mail_content_list`` — 內容列表擷取 +- ``MT_imap_quit`` — 斷開連線 + +**執行器操作 (INFO 級別):** + +- ``Execute {action}`` — 每個動作執行時 +- ``Add command to executor {command_dict}`` — 自訂命令註冊 + +**套件管理器操作 (INFO 級別):** + +- ``add_package_to_executor, package: {package}`` — 套件載入 + +**錯誤記錄 (ERROR 級別):** + +SMTP、IMAP、執行器和套件管理器中的所有例外都被捕獲並以 ERROR 級別記錄, +包含完整的例外表示。 + +---- + +日誌檔位置 +---------- + +日誌檔 ``Mail_Thunder.log`` 在模組首次匯入時於 **目前工作目錄** 建立。 +檔案以 ``w+`` 模式開啟,��示每次程式執行時會被覆寫。 + +.. note:: + + 由於檔案處理器使用 ``mode="w+"``,先前執行的日誌不會被保留。 + 若需要持久化日誌,請考慮設定自訂處理器或在每次執行後複製日誌檔。 + +---- + +存取日誌記錄器 +-------------- + +您可以存取 MailThunder 的日誌記錄器來新增自訂處理器或變更級別: + +.. code-block:: python + + from je_mail_thunder.utils.logging.loggin_instance import mail_thunder_logger + import logging + + # 變更日誌級別 + mail_thunder_logger.setLevel(logging.DEBUG) + + # 新增自訂處理器 + custom_handler = logging.StreamHandler() + custom_handler.setLevel(logging.DEBUG) + mail_thunder_logger.addHandler(custom_handler) + + # 現在所有除錯訊息都會被印出 diff --git a/docs/source/docs/Zh/package_manager.rst b/docs/source/docs/Zh/package_manager.rst new file mode 100644 index 0000000..9e203a5 --- /dev/null +++ b/docs/source/docs/Zh/package_manager.rst @@ -0,0 +1,160 @@ +套件管理器 +========== + +MailThunder 的 ``PackageManager`` 允許您在執行期動態載入任何已安裝的 Python 套件 +至腳本執行器,使其所有函式、內建函式和類別可作為動作命令使用。 + +---- + +運作原理 +-------- + +當呼叫 ``MT_add_package_to_executor`` 並傳入套件名稱時: + +1. ``importlib.find_spec()`` 檢查套件是否已安裝 +2. ``importlib.import_module()`` 匯入該套件 +3. ``inspect.getmembers()`` 提取所有函式、內建函式和類別 +4. 每個成員以 ``{套件名稱}_{成員名稱}`` 命名慣例註冊至執行器的 ``event_dict`` + +.. code-block:: text + + MT_add_package_to_executor("os") + │ + ▼ + find_spec("os") → 找到 + │ + ▼ + import_module("os") + │ + ▼ + getmembers(os, isfunction) → os_getcwd, os_listdir, os_makedirs, ... + getmembers(os, isbuiltin) → os_system, os_open, os_close, ... + getmembers(os, isclass) → os_error, ... + │ + ▼ + 全部註冊至 executor.event_dict + +---- + +在 JSON 腳本中使用 +------------------- + +.. code-block:: json + + { + "auto_control": [ + ["MT_add_package_to_executor", ["os"]], + ["os_system", ["echo Hello from os.system"]], + ["os_getcwd"] + ] + } + +.. code-block:: json + + { + "auto_control": [ + ["MT_add_package_to_executor", ["json"]], + ["json_dumps", [{"key": "value"}]] + ] + } + +---- + +在 Python 中使用 +----------------- + +.. code-block:: python + + from je_mail_thunder import execute_action + + execute_action([ + ["MT_add_package_to_executor", ["math"]], + ["math_sqrt", [144]], + ["math_factorial", [10]] + ]) + +---- + +命名慣例 +-------- + +所有成員以套件名稱和底線為前綴: + +.. code-block:: text + + 套件 "os" → os_system, os_getcwd, os_path, os_listdir, ... + 套件 "json" → json_dumps, json_loads, json_dump, json_load, ... + 套件 "math" → math_sqrt, math_ceil, math_floor, math_factorial, ... + +此做法防止套件之間以及與內建命令的名稱衝突。 + +---- + +已載入套件快取 +-------------- + +``PackageManager`` 在 ``installed_package_dict`` 中快取已載入的套件。 +若套件已被載入,後續對同一套件名稱呼叫 ``MT_add_package_to_executor`` +會重用快取的匯入,而非重新匯入。 + +---- + +載入的內容 +---------- + +從每個套件提取三種類別的成員: + +.. list-table:: + :header-rows: 1 + :widths: 25 35 40 + + * - 類別 + - 偵測方式 + - 範例 + * - 函式 + - ``inspect.isfunction`` + - ``os.getcwd``、``json.dumps`` + * - 內建函式 + - ``inspect.isbuiltin`` + - ``os.system``、``os.open`` + * - 類別 + - ``inspect.isclass`` + - ``os.error``、``json.JSONDecodeError`` + +.. note:: + + 子模組 **不會** 自動載入。例如,載入 ``os`` 不會自動使 ``os.path`` + 的函式可用。您需要另外載入 ``os.path``: + + .. code-block:: json + + { + "auto_control": [ + ["MT_add_package_to_executor", ["os.path"]], + ["os.path_join", ["/home", "user", "file.txt"]] + ] + } + +---- + +安全性警告 +---------- + +.. warning:: + + **將 ``os``、``subprocess``、``shutil`` 或 ``sys`` 等套件載入執行器, + 會授予腳本引擎存取系統層級操作的權限。** + + 在以下情境中這是重大安全風險: + + - 處理不受信任的 JSON 動作檔 + - 將 Socket 伺服器暴露給外部客戶端 + - 在多租戶環境中運行 + + **最佳做法:** + + - 僅載入您明確需要的套件 + - 在正式環境中切勿載入系統套件(``os``、``subprocess``、``sys``) + - 驗證和過濾所有輸入 + - 不要將 Socket 伺服器暴露給不受信任的網路 + - 執行前審查 JSON 動作檔 diff --git a/docs/source/docs/Zh/project_templates.rst b/docs/source/docs/Zh/project_templates.rst new file mode 100644 index 0000000..cdc8516 --- /dev/null +++ b/docs/source/docs/Zh/project_templates.rst @@ -0,0 +1,196 @@ +專案模板 +======== + +MailThunder 可以建立一個新的專案目錄,包含預建模板檔案, +讓您擁有可立即使用的郵件自動化起點。 + +---- + +建立專案 +-------- + +**從 Python:** + +.. code-block:: python + + from je_mail_thunder import create_project_dir + + # 在目前工作目錄建立,使用預設名稱 "MailThunder" + create_project_dir() + + # 在指定路徑建立,使用自訂名稱 + create_project_dir(project_path="/path/to/projects", parent_name="MyMailProject") + +**從命令列:** + +.. code-block:: bash + + python -m je_mail_thunder -c /path/to/project + +**參數:** + +.. list-table:: + :header-rows: 1 + :widths: 25 20 55 + + * - 參數 + - 預設值 + - 說明 + * - ``project_path`` + - ``os.getcwd()`` + - 建立專案資料夾的目錄 + * - ``parent_name`` + - ``"MailThunder"`` + - 專案根目錄名稱 + +---- + +產生的結構 +---------- + +.. code-block:: text + + MyMailProject/ + keyword/ + keyword1.json # SMTP 寄送郵件模板 + keyword2.json # IMAP 讀取並匯出模板 + bad_keyword_1.json # 套件載入範例(安全性警告) + executor/ + executor_one_file.py # 執行單一動作檔 + executor_folder.py # 執行目錄中所有動作檔 + executor_bad_file.py # 不良做法範例(安全性警告) + +---- + +模板檔案:keyword/ +-------------------- + +**keyword1.json** — SMTP 寄送郵件模板: + +.. code-block:: json + + [ + ["MT_smtp_later_init"], + ["MT_smtp_create_message_and_send", { + "message_content": "test", + "message_setting_dict": { + "Subject": "test_subject", + "To": "example@gmail.com", + "From": "example@gmail.com" + } + }], + ["smtp_quit"] + ] + +**keyword2.json** — IMAP 讀取並匯出模板: + +.. code-block:: json + + [ + ["MT_imap_later_init"], + ["MT_imap_select_mailbox"], + ["MT_imap_output_all_mail_as_file"] + ] + +**bad_keyword_1.json** — 套件載入範例: + +.. code-block:: json + + [ + ["MT_add_package_to_executor", ["os"]], + ["os_system", ["python --version"]], + ["os_system", ["python -m pip --version"]] + ] + +.. warning:: + + ``bad_keyword_1.json`` 展示如何將 ``os`` 套件載入執行器,這使得腳本引擎 + 可以執行任意系統命令。此範例作為教育用途,展示在正式環境中 **不應該** 做的事。 + 在處理不受信任的輸入時,切勿載入 ``os``、``subprocess`` 或類似套件。 + +---- + +模板檔案:executor/ +--------------------- + +**executor_one_file.py** — 執行單一動作檔: + +.. code-block:: python + + from je_mail_thunder import execute_action, read_action_json + + execute_action( + read_action_json( + r"/path/to/MyMailProject/keyword/keyword1.json" + ) + ) + +**executor_folder.py** — 執行目錄中所有 JSON 動作檔: + +.. code-block:: python + + from je_mail_thunder import execute_files, get_dir_files_as_list + + execute_files( + get_dir_files_as_list( + r"/path/to/MyMailProject/keyword" + ) + ) + +**executor_bad_file.py** — 不良做法範例: + +.. code-block:: python + + # 此範例主要用於提醒使用者驗證輸入的重要性。 + from je_mail_thunder import execute_action, read_action_json + + execute_action( + read_action_json( + r"/path/to/MyMailProject/keyword/bad_keyword_1.json" + ) + ) + +.. note:: + + 模板原始碼中的 ``{temp}`` 佔位符在專案建立時會被替換為實際的絕對路徑。 + +---- + +自訂模板 +-------- + +建立完成後,您可以自由修改產生的檔案: + +1. 編輯 ``keyword/*.json`` 檔案以符合您的郵件工作流程 +2. 更新郵件地址、主旨和內容 +3. 在 ``keyword/`` 目錄新增 ``.json`` 動作檔 +4. 修改 ``executor/*.py`` 檔案以加入錯誤處理或自訂邏輯 + +**範例:新增工作流程** + +建立 ``keyword/weekly_report.json``: + +.. code-block:: json + + { + "auto_control": [ + ["MT_smtp_later_init"], + ["MT_smtp_create_message_with_attach_and_send", { + "message_content": "請查看附件的週報。", + "message_setting_dict": { + "Subject": "週報", + "To": "team@company.com", + "From": "reporter@company.com" + }, + "attach_file": "/data/reports/weekly.pdf", + "use_html": false + }], + ["smtp_quit"] + ] + } + +執行: + +.. code-block:: bash + + python -m je_mail_thunder -e MyMailProject/keyword/weekly_report.json diff --git a/docs/source/docs/Zh/read_google_mail.rst b/docs/source/docs/Zh/read_google_mail.rst index 51460f1..f5e5feb 100644 --- a/docs/source/docs/Zh/read_google_mail.rst +++ b/docs/source/docs/Zh/read_google_mail.rst @@ -1,30 +1,323 @@ -使用 IMAP 讀取 Google 郵件 +讀取郵件 (IMAP) +================ + +MailThunder 的 ``IMAPWrapper`` 繼承自 ``imaplib.IMAP4_SSL``,提供讀取、搜尋和匯出郵件的方法。 + +預設連接:``imap.gmail.com`` (SSL)。 + +.. note:: + + **Gmail 使用者注意:** 請確認已在 Gmail 設定中 + `啟用 IMAP `_。 + +---- + +讀取收件匣中的所有郵件 +---------------------- + +.. code-block:: python + + from je_mail_thunder import IMAPWrapper + + with IMAPWrapper() as imap: + imap.later_init() + imap.select_mailbox("INBOX") + emails = imap.mail_content_list() + for mail in emails: + print(f"主旨: {mail['SUBJECT']}") + print(f"寄件者: {mail['FROM']}") + print(f"收件者: {mail['TO']}") + print(f"內文: {mail['BODY'][:200]}...") + print("---") + +**回傳的字典格式:** + +每封郵件為一個包含四個鍵的字典: + +.. code-block:: python + + { + "SUBJECT": "郵件主旨", # str — 解碼後的主旨 + "FROM": "sender@example.com", # str — 寄件者地址 + "TO": "receiver@example.com", # str — 收件者地址 + "BODY": "郵件內文..." # str — 內文(已解碼) + } + +對於多部分(multipart)郵件,擷取第一個部分的內文。 + +---- + +搜尋郵件 +-------- + +``search_str`` 參數遵循 +`IMAP SEARCH 指令語法 (RFC 3501 第 6.4.4 節) `_。 + +**依寄件者搜尋:** + +.. code-block:: python + + emails = imap.mail_content_list(search_str='FROM "someone@example.com"') + +**依主旨搜尋:** + +.. code-block:: python + + emails = imap.mail_content_list(search_str='SUBJECT "重要"') + +**搜尋未讀郵件:** + +.. code-block:: python + + emails = imap.mail_content_list(search_str="UNSEEN") + +**依日期搜尋:** + +.. code-block:: python + + emails = imap.mail_content_list(search_str='SINCE "01-Jan-2025"') + emails = imap.mail_content_list(search_str='BEFORE "31-Dec-2025"') + emails = imap.mail_content_list(search_str='ON "15-Jun-2025"') + +**組合多個條件** (隱含 AND): + +.. code-block:: python + + emails = imap.mail_content_list(search_str='FROM "boss@company.com" UNSEEN') + emails = imap.mail_content_list(search_str='SINCE "01-Jan-2025" SUBJECT "報告"') + +**完整搜尋條件參考:** + +.. list-table:: + :header-rows: 1 + :widths: 30 70 + + * - 條件 + - 說明 + * - ``ALL`` + - 信箱中所有郵件(預設) + * - ``UNSEEN`` + - 未讀郵件 + * - ``SEEN`` + - 已讀郵件 + * - ``FLAGGED`` + - 已標記的郵件(Gmail 中的星號郵件) + * - ``UNFLAGGED`` + - 未標記的郵件 + * - ``ANSWERED`` + - 已回覆的郵件 + * - ``UNANSWERED`` + - 未回覆的郵件 + * - ``DELETED`` + - 已標記刪除的郵件 + * - ``FROM "字串"`` + - 來自指定寄件者的郵件 + * - ``TO "字串"`` + - 寄給指定收件者的郵件 + * - ``CC "字串"`` + - 指定副本收件者的郵件 + * - ``SUBJECT "字串"`` + - 主旨包含指定文字的郵件 + * - ``BODY "字串"`` + - 內文包含指定文字的郵件 + * - ``TEXT "字串"`` + - 標頭或內文包含指定文字的郵件 + * - ``SINCE "DD-Mon-YYYY"`` + - 指定日期當天或之後的郵件(例如 ``"01-Jan-2025"``) + * - ``BEFORE "DD-Mon-YYYY"`` + - 指定日期之前的郵件 + * - ``ON "DD-Mon-YYYY"`` + - 指定日期當天的郵件 + * - ``LARGER n`` + - 大於 n 位元組的郵件 + * - ``SMALLER n`` + - 小於 n 位元組的郵件 + * - ``NEW`` + - 最近且未讀的郵件 + * - ``OLD`` + - 非最近的郵件 + +---- + +匯出所有郵件為檔案 +------------------ + +將選定信箱中的每封郵件匯出為本地檔案: + +.. code-block:: python + + from je_mail_thunder import IMAPWrapper + + with IMAPWrapper() as imap: + imap.later_init() + imap.select_mailbox("INBOX") + exported = imap.output_all_mail_as_file() + print(f"已匯出 {len(exported)} 封郵件") + +**檔案命名規則:** + +- 每封郵件儲存在目前工作目錄 +- 檔名 = 郵件主旨 + 數字後綴 +- 第一封:``My Subject0`` +- 重複主旨:``My Subject1``、``My Subject2``、... +- 檔案內容 = 解碼後的郵件內文 + +---- + +取得原始郵件資料 +---------------- + +進階用途可使用 ``search_mailbox()`` 取得原始資料: + +.. code-block:: python + + from je_mail_thunder import IMAPWrapper + + with IMAPWrapper() as imap: + imap.later_init() + imap.select_mailbox("INBOX") + raw_list = imap.search_mailbox() + for response, decode_info, message in raw_list: + # response: IMAP 回應狀態字串(例如 "OK") + # decode_info: 原始 RFC822 解碼標頭位元組 + # message: email.message.Message 物件 — 完整的 Python 郵件物件 + print(f"主旨: {message.get('Subject')}") + print(f"日期: {message.get('Date')}") + print(f"內容類型: {message.get_content_type()}") + + # 存取所有標頭 + for key in message.keys(): + print(f" {key}: {message[key]}") + + # 存取多部分郵件的各部分 + if message.is_multipart(): + for i, part in enumerate(message.get_payload()): + print(f" 部分 {i}: {part.get_content_type()}") + +``message`` 物件是標準的 Python ``email.message.Message`` +(使用 ``email.policy.default`` 解析),提供完整的標頭、部分和解碼工具存取。 + +---- + +唯讀模式 +-------- + +以唯讀模式開啟信箱,防止副作用(如將郵件標記為已讀): + +.. code-block:: python + + from je_mail_thunder import IMAPWrapper + + with IMAPWrapper() as imap: + imap.later_init() + success = imap.select_mailbox("INBOX", readonly=True) + if success: + emails = imap.mail_content_list() + +---- + +選擇不同的信箱 +-------------- + +除了 ``INBOX``,您可以選擇其他標準或供應商特定的信箱: + +.. code-block:: python + + # 標準信箱 + imap.select_mailbox("Sent") + imap.select_mailbox("Drafts") + imap.select_mailbox("Trash") + + # Gmail 特定標籤 + imap.select_mailbox("[Gmail]/All Mail") + imap.select_mailbox("[Gmail]/Starred") + imap.select_mailbox("[Gmail]/Spam") + imap.select_mailbox("[Gmail]/Important") + +``select_mailbox()`` 方法在選擇成功時回傳 ``True``(IMAP 狀態 ``"OK"``), +否則回傳 ``False``。 + +---- + +使用其他 IMAP 服務供應商 +------------------------- + +透過建構子傳入自訂 ``host``: + +.. code-block:: python + + from je_mail_thunder import IMAPWrapper + + # Outlook / Office 365 + imap = IMAPWrapper(host="outlook.office365.com") + + # Yahoo Mail + imap = IMAPWrapper(host="imap.mail.yahoo.com") + + # 自訂伺服器 + imap = IMAPWrapper(host="imap.example.com") + ---- +手動登入(不使用 Context Manager) +----------------------------------- + .. code-block:: python - from je_mail_thunder import IMAPWrapper, set_mail_thunder_os_environ + from je_mail_thunder import IMAPWrapper, set_mail_thunder_os_environ + + imap = IMAPWrapper(host="imap.gmail.com") + + set_mail_thunder_os_environ( + "your_email@gmail.com", + "your_app_password" + ) + + imap.later_init() + imap.select_mailbox("INBOX") + + for mail in imap.mail_content_list(): + print(mail.get("SUBJECT")) + print(mail.get("FROM")) + print(mail.get("TO")) + print(mail.get("BODY")) + + # 完成後務必關閉連線 + imap.quit() + +---- + +透過 JSON 腳本引擎讀取 +----------------------- + +**讀取並顯示郵件:** + +.. code-block:: json + + { + "auto_control": [ + ["MT_imap_later_init"], + ["MT_imap_select_mailbox", {"mailbox": "INBOX", "readonly": true}], + ["MT_imap_mail_content_list", {"search_str": "UNSEEN"}], + ["MT_imap_quit"] + ] + } + +**匯出所有郵件為檔案:** + +.. code-block:: json + + { + "auto_control": [ + ["MT_imap_later_init"], + ["MT_imap_select_mailbox"], + ["MT_imap_output_all_mail_as_file"], + ["MT_imap_quit"] + ] + } - # 設置 IMAP 主機 - imap_host = 'imap.gmail.com' - # 初始 IMAP 包裝類別 - imap_wrapper = IMAPWrapper(host=imap_host) +透過命令列執行: - set_mail_thunder_os_environ( - "test_user", # your user - "test_password" # your password - ) +.. code-block:: bash - imap_wrapper.later_init() - # 選擇搜尋的信箱 (沒有帶參數是全部) - imap_wrapper.select() - # 取得郵件列表 - mail_list = imap_wrapper.mail_content_list() - # 輸出基本資訊 - for mail in mail_list: - print(mail.get("SUBJECT")) - print(mail.get("FROM")) - print(mail.get("TO")) - print(mail.get("BODY")) - # 離開 - imap_wrapper.quit() + python -m je_mail_thunder -e /path/to/read_action.json diff --git a/docs/source/docs/Zh/scripting_engine.rst b/docs/source/docs/Zh/scripting_engine.rst new file mode 100644 index 0000000..5fe8d5a --- /dev/null +++ b/docs/source/docs/Zh/scripting_engine.rst @@ -0,0 +1,323 @@ +JSON 腳本引擎 +============== + +MailThunder 內建強大的 JSON 腳本引擎,讓您無需撰寫 Python 程式碼即可自動化郵件工作流程。 +引擎建構於 ``Executor`` 類別之上,將命令名稱對映到 Python 可呼叫物件。 + +---- + +運作原理 +-------- + +1. 您撰寫一個包含 ``auto_control`` 鍵的 JSON 檔案(或字典) +2. 值是一個動作命令列表 +3. 每個命令由 ``Executor`` 依序執行 +4. 結果被收集並以字典形式回傳 + +.. code-block:: text + + JSON 動作檔 + │ + ▼ + execute_action(action_list) + │ + ├── 若為 dict: 提取 action_list["auto_control"] + ├── 若為 list: 直接使用 + │ + ▼ + 對每個動作 in action_list: + │ + ├── action = ["command_name"] ──▶ executor.event_dict["command_name"]() + ├── action = ["command_name", {k: v}] ──▶ executor.event_dict["command_name"](**{k: v}) + └── action = ["command_name", [a, b]] ──▶ executor.event_dict["command_name"](*[a, b]) + +---- + +動作檔格式 +---------- + +動作檔使用 ``auto_control`` 鍵包含命令列表: + +.. code-block:: json + + { + "auto_control": [ + ["command_name"], + ["command_name", {"key": "value"}], + ["command_name", ["arg1", "arg2"]] + ] + } + +**參數慣例:** + +.. list-table:: + :header-rows: 1 + :widths: 40 20 40 + + * - 格式 + - 類型 + - Python 對應 + * - ``["command"]`` + - 無參數 + - ``command()`` + * - ``["command", {"k": "v"}]`` + - 關鍵字參數 + - ``command(**{"k": "v"})`` + * - ``["command", ["a", "b"]]`` + - 位置參數 + - ``command(*["a", "b"])`` + +您也可以直接傳入純列表(不含 ``auto_control`` 包裝)給 ``execute_action()``: + +.. code-block:: python + + from je_mail_thunder import execute_action + + execute_action([ + ["MT_smtp_later_init"], + ["smtp_quit"] + ]) + +---- + +內建命令 +-------- + +**SMTP 命令:** + +.. list-table:: + :header-rows: 1 + :widths: 45 55 + + * - 命令 + - 說明 + * - ``MT_smtp_later_init`` + - 初始化並登入 SMTP 伺服器 + * - ``MT_smtp_create_message_and_send`` + - 建立並寄送純文字郵件 + * - ``MT_smtp_create_message_with_attach_and_send`` + - 建立並寄送附件郵件 + * - ``smtp_quit`` + - 斷開 SMTP 連線 + +**IMAP 命令:** + +.. list-table:: + :header-rows: 1 + :widths: 45 55 + + * - 命令 + - 說明 + * - ``MT_imap_later_init`` + - 初始化並登入 IMAP 伺服器 + * - ``MT_imap_select_mailbox`` + - 選擇信箱(預設:INBOX) + * - ``MT_imap_search_mailbox`` + - 搜尋並取得原始郵件資料 + * - ``MT_imap_mail_content_list`` + - 取得解析後的郵件內容列表 + * - ``MT_imap_output_all_mail_as_file`` + - 匯出所有郵件為本地檔案 + * - ``MT_imap_quit`` + - 斷開 IMAP 連線 + +**認證命令:** + +.. list-table:: + :header-rows: 1 + :widths: 45 55 + + * - 命令 + - 說明 + * - ``MT_set_mail_thunder_os_environ`` + - 設定認證環境變數 + * - ``MT_get_mail_thunder_os_environ`` + - 取得目前認證環境變數 + +**套件管理:** + +.. list-table:: + :header-rows: 1 + :widths: 45 55 + + * - 命令 + - 說明 + * - ``MT_add_package_to_executor`` + - 載入 Python 套件至執行器 + +**Python 內建函式:** + +所有 Python 內建函式(``print``、``len``、``range``、``type``、``str``、 +``int``、``list``、``dict`` 等)自動註冊,可作為命令使用。 + +---- + +範例 +---- + +**寄送純文字郵件:** + +.. code-block:: json + + { + "auto_control": [ + ["MT_smtp_later_init"], + ["MT_smtp_create_message_and_send", { + "message_content": "Hello from the scripting engine!", + "message_setting_dict": { + "Subject": "自動化郵件", + "To": "receiver@gmail.com", + "From": "sender@gmail.com" + } + }], + ["smtp_quit"] + ] + } + +**讀取並匯出所有郵件:** + +.. code-block:: json + + { + "auto_control": [ + ["MT_imap_later_init"], + ["MT_imap_select_mailbox"], + ["MT_imap_output_all_mail_as_file"], + ["MT_imap_quit"] + ] + } + +**搜尋未讀郵件(唯讀模式):** + +.. code-block:: json + + { + "auto_control": [ + ["MT_imap_later_init"], + ["MT_imap_select_mailbox", {"mailbox": "INBOX", "readonly": true}], + ["MT_imap_mail_content_list", {"search_str": "UNSEEN"}], + ["MT_imap_quit"] + ] + } + +**設定認證後寄送:** + +.. code-block:: json + + { + "auto_control": [ + ["MT_set_mail_thunder_os_environ", { + "mail_thunder_user": "sender@gmail.com", + "mail_thunder_user_password": "your_app_password" + }], + ["MT_smtp_later_init"], + ["MT_smtp_create_message_and_send", { + "message_content": "透過腳本設定認證!", + "message_setting_dict": { + "Subject": "認證測試", + "To": "receiver@gmail.com", + "From": "sender@gmail.com" + } + }], + ["smtp_quit"] + ] + } + +---- + +執行動作檔 +---------- + +**從 Python:** + +.. code-block:: python + + from je_mail_thunder import execute_action, read_action_json + + # 執行單一檔案 + result = execute_action(read_action_json("/path/to/action.json")) + + # 結果是一個 dict,將 "execute: [action]" 對映到回傳值 + for action, return_value in result.items(): + print(action, return_value) + +**執行目錄中的多個檔案:** + +.. code-block:: python + + from je_mail_thunder import execute_files, get_dir_files_as_list + + # 取得目錄中所有 .json 檔案 + files = get_dir_files_as_list("/path/to/actions/") + results = execute_files(files) + +**從命令列:** + +.. code-block:: bash + + # 執行單一檔案 + python -m je_mail_thunder -e /path/to/action.json + + # 執行目錄中所有 JSON 檔案 + python -m je_mail_thunder -d /path/to/actions/ + + # 執行 JSON 字串 + python -m je_mail_thunder --execute_str '[["print", ["Hello!"]]]' + +---- + +擴充自訂命令 +------------ + +將您自己的函式加入執行器: + +.. code-block:: python + + from je_mail_thunder import add_command_to_executor, execute_action + + def send_notification(channel, message): + print(f"[{channel}] {message}") + return {"status": "sent", "channel": channel} + + # 註冊自訂命令 + add_command_to_executor({ + "notify": send_notification, + }) + + # 在動作列表中使用 + execute_action([ + ["notify", {"channel": "#alerts", "message": "處理完成"}] + ]) + +.. warning:: + + 僅可加入 ``types.MethodType`` 和 ``types.FunctionType`` 實體。 + 傳入非可呼叫物件(如字串或整數)將引發 ``AddCommandException``。 + +---- + +執行輸出 +-------- + +執行動作時,每個命令及其回傳值會印出到 stdout: + +.. code-block:: text + + execute: ['MT_smtp_later_init'] + None + execute: ['MT_smtp_create_message_and_send', {...}] + None + execute: ['smtp_quit'] + None + +若動作失敗,例外會被捕獲、記錄並存入結果字典。 +執行器 **不會** 在錯誤時停止 — 會繼續執行後續動作並收集所有結果。 + +---- + +執行緒安全 +---------- + +``Executor`` 類別以模組級別單例(``executor``)實體化。 +JSON 檔案讀取(``read_action_json``)使用 ``threading.Lock`` 確保執行緒安全的檔案存取。 +但執行器的 ``event_dict`` 本身未加鎖,因此應避免並發修改命令註冊表。 diff --git a/docs/source/docs/Zh/send_google_mail.rst b/docs/source/docs/Zh/send_google_mail.rst index 19c120e..7d49412 100644 --- a/docs/source/docs/Zh/send_google_mail.rst +++ b/docs/source/docs/Zh/send_google_mail.rst @@ -1,23 +1,315 @@ -使用 Google 信箱寄送郵件 +寄送郵件 (SMTP) +================ + +MailThunder 的 ``SMTPWrapper`` 繼承自 ``smtplib.SMTP_SSL``,提供便捷的方法來建立和寄送郵件。 +支援純文字、HTML 和檔案附件。 + +預設連接:``smtp.gmail.com:465`` (SSL)。 + +---- + +寄送純文字郵件 +-------------- + +使用 context manager 的最簡方式: + +.. code-block:: python + + from je_mail_thunder import SMTPWrapper + + with SMTPWrapper() as smtp: + smtp.later_init() + smtp.create_message_and_send( + message_content="Hello from MailThunder!", + message_setting_dict={ + "Subject": "測試郵件", + "From": "sender@gmail.com", + "To": "receiver@gmail.com" + } + ) + +``message_setting_dict`` 接受任何有效的 ``EmailMessage`` 標頭鍵: + +- ``Subject`` — 郵件主旨 +- ``From`` — 寄件者地址 +- ``To`` — 收件者地址 +- ``Cc`` — 副本收件者 +- ``Bcc`` — 密件副本收件者 +- ``Reply-To`` — 回覆地址 +- 其他 RFC 2822 標頭 + ---- +寄送附件郵件 +------------ + +MailThunder 使用 ``mimetypes.guess_type()`` 自動偵測附件的 MIME 類型: + .. code-block:: python - from je_mail_thunder import SMTPWrapper - from je_mail_thunder import mail_thunder_content_data_dict + from je_mail_thunder import SMTPWrapper + + with SMTPWrapper() as smtp: + smtp.later_init() + smtp.create_message_with_attach_and_send( + message_content="請查看附件。", + message_setting_dict={ + "Subject": "附件郵件", + "From": "sender@gmail.com", + "To": "receiver@gmail.com" + }, + attach_file="/path/to/file.pdf", + use_html=False + ) + +**MIME 類型偵測:** + +.. list-table:: + :header-rows: 1 + :widths: 20 30 50 + + * - 類別 + - MIME 類型 + - 副檔名 + * - 文字 + - ``text/*`` + - ``.txt``、``.csv``、``.html``、``.xml``、``.json`` + * - 圖片 + - ``image/*`` + - ``.png``、``.jpg``、``.jpeg``、``.gif``、``.svg``、``.bmp`` + * - 音訊 + - ``audio/*`` + - ``.mp3``、``.wav``、``.ogg``、``.flac`` + * - 其他 + - ``application/octet-stream`` + - ``.pdf``、``.zip``、``.exe``、``.docx``、``.xlsx`` 等 + +附件以 ``Content-Disposition: attachment`` 加入,並使用檔名設定 ``Content-ID`` 標頭。 + +---- - smtp_wrapper = SMTPWrapper() +寄送 HTML 格式郵件 +------------------- + +將 ``use_html`` 設為 ``True`` 即可寄送 HTML 格式郵件: + +.. code-block:: python + + from je_mail_thunder import SMTPWrapper + + html_content = """ + + +

你好!

+

這是透過 MailThunder 寄送的 HTML 郵件

+
    +
  • 功能一
  • +
  • 功能二
  • +
+ + + """ + + with SMTPWrapper() as smtp: + smtp.later_init() + smtp.create_message_with_attach_and_send( + message_content=html_content, + message_setting_dict={ + "Subject": "HTML 郵件", + "From": "sender@gmail.com", + "To": "receiver@gmail.com" + }, + attach_file="/path/to/style.css", + use_html=True + ) + +當 ``use_html=True`` 時,內文以 ``MIMEText(content, "html")`` 包裝, +而非預設的 ``MIMEText(content)`` (預設為 ``"plain"``)。 + +---- + +分步操作:先建立訊息再寄送 +--------------------------- + +分開建立訊息與寄送,獲得更多控制: + +.. code-block:: python + + from je_mail_thunder import SMTPWrapper + + with SMTPWrapper() as smtp: + smtp.later_init() + + # 步驟一:建立訊息物件(回傳 EmailMessage) + message = smtp.create_message( + message_content="你好!", + message_setting_dict={ + "Subject": "分步郵件", + "From": "sender@gmail.com", + "To": "receiver@gmail.com" + } + ) + + # 可在寄送前檢查或修改訊息 + print(message["Subject"]) # "分步郵件" + + # 步驟二:寄送(繼承自 SMTP_SSL) + smtp.send_message(message) + +建立附件訊息: + +.. code-block:: python - mail_thunder_content_data_dict.update({ - "user": "test_user", # 你的使用者 - "password": "test_password", # 你的密碼 (google 需使用應用程式密碼) - }) + # 步驟一:建立附件訊息(回傳 MIMEMultipart) + message = smtp.create_message_with_attach( + message_content="請查看附件。", + message_setting_dict={ + "Subject": "報告", + "From": "sender@gmail.com", + "To": "receiver@gmail.com" + }, + attach_file="/path/to/report.pdf", + use_html=False + ) + + # 步驟二:寄送 + smtp.send_message(message) + +---- + +使用其他 SMTP 服務供應商 +------------------------- + +透過建構子傳入自訂 ``host`` 和 ``port``: + +.. code-block:: python + + from je_mail_thunder import SMTPWrapper + + # Outlook / Office 365 + smtp = SMTPWrapper(host="smtp-mail.outlook.com", port=465) + + # Yahoo Mail + smtp = SMTPWrapper(host="smtp.mail.yahoo.com", port=465) + + # 自訂 SMTP 伺服器 + smtp = SMTPWrapper(host="mail.example.com", port=465) + +.. note:: + + MailThunder 使用 ``SMTP_SSL``(連接時即啟用 SSL)。若您的供應商要求 + 在 587 埠使用 STARTTLS,您可能需要直接使用底層 ``smtplib`` + 或繼承 ``SMTPWrapper``。 + +---- + +手動登入(不使用 Context Manager) +----------------------------------- + +.. code-block:: python + + from je_mail_thunder import SMTPWrapper + from je_mail_thunder import mail_thunder_content_data_dict + + smtp = SMTPWrapper() + + # 手動注入認證資訊 + mail_thunder_content_data_dict.update({ + "user": "your_email@gmail.com", + "password": "your_app_password", + }) + + # 登入 + smtp.try_to_login_with_env_or_content() + print(smtp.login_state) # True + + # 建立並寄送 + user = mail_thunder_content_data_dict.get("user") + message = smtp.create_message( + "Hello World!", + {"Subject": "手動登入", "To": user, "From": user} + ) + smtp.send_message(message) + + # 完成後務必關閉連線 + smtp.quit() + +---- + +透過 JSON 腳本引擎寄送 +----------------------- + +使用 JSON 動作檔寄送郵件,無需撰寫 Python 程式碼: + +.. code-block:: json + + { + "auto_control": [ + ["MT_smtp_later_init"], + ["MT_smtp_create_message_and_send", { + "message_content": "Hello from scripting engine!", + "message_setting_dict": { + "Subject": "自動化郵件", + "To": "receiver@gmail.com", + "From": "sender@gmail.com" + } + }], + ["smtp_quit"] + ] + } + +附件版本: + +.. code-block:: json + + { + "auto_control": [ + ["MT_smtp_later_init"], + ["MT_smtp_create_message_with_attach_and_send", { + "message_content": "請查看附件報告。", + "message_setting_dict": { + "Subject": "月報", + "To": "receiver@gmail.com", + "From": "sender@gmail.com" + }, + "attach_file": "/path/to/report.pdf", + "use_html": false + }], + ["smtp_quit"] + ] + } + +透過命令列執行: + +.. code-block:: bash + + python -m je_mail_thunder -e /path/to/send_action.json + +---- + +錯誤處理 +-------- + +所有 ``SMTPWrapper`` 方法內部捕獲例外並記錄。若需自行處理錯誤, +使用 ``try_to_login_with_env_or_content()``(回傳 ``bool``): + +.. code-block:: python - user = mail_thunder_content_data_dict.get("user") + from je_mail_thunder import SMTPWrapper - smtp_wrapper.try_to_login_with_env_or_content() + smtp = SMTPWrapper() + success = smtp.try_to_login_with_env_or_content() - message = smtp_wrapper.create_message("test", {"Subject": "test_subject", "To": user, "From": user}) - smtp_wrapper.send_message(message) - smtp_wrapper.quit() + if success: + smtp.create_message_and_send( + message_content="已認證!", + message_setting_dict={ + "Subject": "成功", + "From": "sender@gmail.com", + "To": "receiver@gmail.com" + } + ) + else: + print("登入失敗 — 請檢查認證資訊") + smtp.quit() diff --git a/docs/source/docs/Zh/socket_server.rst b/docs/source/docs/Zh/socket_server.rst new file mode 100644 index 0000000..8a1a9ce --- /dev/null +++ b/docs/source/docs/Zh/socket_server.rst @@ -0,0 +1,218 @@ +Socket 伺服器 +============== + +MailThunder 內建 TCP Socket 伺服器,接收遠端 JSON 動作命令, +實現透過網路遠端控制郵件自動化工作流程。 + +伺服器建構於 Python 的 ``socketserver.ThreadingMixIn`` 和 ``socketserver.TCPServer``, +以獨立執行緒處理每個客戶端連線。 + +---- + +啟動伺服器 +---------- + +.. code-block:: python + + from je_mail_thunder.utils.socket_server.mail_thunder_socket_server import ( + start_autocontrol_socket_server + ) + + server = start_autocontrol_socket_server(host="localhost", port=9944) + # 伺服器正在背景 daemon 執行緒中運行 + +**參數:** + +.. list-table:: + :header-rows: 1 + :widths: 20 20 60 + + * - 參數 + - 預設值 + - 說明 + * - ``host`` + - ``"localhost"`` + - 綁定的主機地址 + * - ``port`` + - ``9944`` + - 監聽的 TCP 埠號 + +伺服器執行緒為 daemon 執行緒 — 當主程式結束時會自動終止。 + +---- + +傳送命令至伺服器 +---------------- + +命令以 JSON 編碼的動作列表傳送(與 ``auto_control`` 列表格式相同, +不含包裝鍵): + +.. code-block:: python + + import socket + import json + + # 連接伺服器 + client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + client.connect(("localhost", 9944)) + + # 傳送動作命令列表 + command = json.dumps([ + ["MT_smtp_later_init"], + ["MT_smtp_create_message_and_send", { + "message_content": "透過 socket 伺服器寄送!", + "message_setting_dict": { + "Subject": "遠端郵件", + "To": "receiver@gmail.com", + "From": "sender@gmail.com" + } + }], + ["smtp_quit"] + ]) + client.send(command.encode("utf-8")) + + # 接收回應 + response = client.recv(8192).decode("utf-8") + print(response) + + client.close() + +---- + +伺服器協定 +---------- + +**請求格式:** + +.. code-block:: text + + 客戶端 ──▶ 伺服器: JSON 編碼的動作列表(UTF-8 位元組,最大 8192 位元組) + +**回應格式:** + +.. code-block:: text + + 伺服器 ──▶ 客戶端: 對每個命令結果: + <回傳值>\n + 接著終止符: + Return_Data_Over_JE\n + +**關閉:** + +.. code-block:: text + + 客戶端 ──▶ 伺服器: "quit_server"(純文字字串,非 JSON) + 伺服器: 優雅關閉 + +---- + +完整範例:客戶端-伺服器互動 +---------------------------- + +**伺服器端 (server.py):** + +.. code-block:: python + + from je_mail_thunder.utils.socket_server.mail_thunder_socket_server import ( + start_autocontrol_socket_server + ) + import time + + server = start_autocontrol_socket_server("localhost", 9944) + print("伺服器已在 localhost:9944 啟動") + + try: + while not server.close_flag: + time.sleep(1) + except KeyboardInterrupt: + server.shutdown() + print("伺服器已停止") + +**客戶端 (client.py):** + +.. code-block:: python + + import socket + import json + + def send_command(host, port, actions): + client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + client.connect((host, port)) + client.send(json.dumps(actions).encode("utf-8")) + response = client.recv(8192).decode("utf-8") + client.close() + return response + + # 遠端寄送郵件 + result = send_command("localhost", 9944, [ + ["MT_smtp_later_init"], + ["MT_smtp_create_message_and_send", { + "message_content": "遠端郵件!", + "message_setting_dict": { + "Subject": "Socket 伺服器測試", + "To": "receiver@gmail.com", + "From": "sender@gmail.com" + } + }], + ["smtp_quit"] + ]) + print("結果:", result) + + # 關閉伺服器 + result = send_command("localhost", 9944, "quit_server") + +.. note:: + + ``quit_server`` 命令是純文字字串,**不是** JSON 編碼的動作列表。 + +---- + +伺服器架構 +---------- + +.. code-block:: text + + TCPServer (socketserver.ThreadingMixIn + socketserver.TCPServer) + │ + ├── server.serve_forever() ← 在 daemon 執行緒中運行 + │ + └── 對每個客戶端連線: + │ + ▼ + TCPServerHandler.handle() + │ + ├── recv(8192) ← 讀取命令位元組 + │ + ├── command == "quit_server" + │ └── server.shutdown() + │ + └── command 是 JSON 動作列表 + │ + ▼ + execute_action(json.loads(command)) + │ + ▼ + sendto(result) 對每個動作結果 + sendto("Return_Data_Over_JE") + +**重要屬性:** + +- ``server.close_flag`` — ``bool``,收到 ``quit_server`` 時設為 ``True`` +- 每個連線在新執行緒中處理(``ThreadingMixIn``) +- 最大接收緩衝:每個命令 8192 位元組 +- 編碼:UTF-8 + +---- + +安全性考量 +---------- + +.. warning:: + + Socket 伺服器 **不提供** 任何認證或加密。 + 任何可連接到 host:port 的客戶端都可以執行命令。 + + **請勿** 在沒有額外安全措施的情況下將 Socket 伺服器暴露到公共網路 + (防火牆規則、SSH 隧道、TLS 包裝等)。 + + 預設綁定到 ``localhost`` 僅限本機存取。 diff --git a/docs/source/docs/Zh/zh_index.rst b/docs/source/docs/Zh/zh_index.rst index 321b56a..d0daa28 100644 --- a/docs/source/docs/Zh/zh_index.rst +++ b/docs/source/docs/Zh/zh_index.rst @@ -1,8 +1,81 @@ -MailThunder 繁體中文 文件 +總覽 +==== + +**MailThunder** 是一個輕量且靈活的 Python 電子郵件自動化工具, +建構於 Python 標準函式庫的 ``smtplib`` 和 ``imaplib`` 模組之上。 + +什麼是 MailThunder? +-------------------- + +MailThunder 封裝了 Python 的 ``smtplib.SMTP_SSL`` 和 ``imaplib.IMAP4_SSL``, +提供更高階的介面來處理常見的電子郵件任務: + +- **寄送** 純文字和 HTML 郵件,支援檔案附件,透過 SMTP +- **讀取**、搜尋和匯出郵件,透過 IMAP4 +- **自動化** 郵件工作流程,使用 JSON 腳本引擎 +- **建立專案** 使用預建模板快速開始 +- **遠端控制** 透過 TCP Socket 伺服器控制郵件自動化 + +架構 ---- -.. toctree:: - :maxdepth: 4 +MailThunder 核心模組架構: + +.. code-block:: text + + je_mail_thunder/ + __init__.py # 公開 API 匯出 + __main__.py # CLI 進入點 (argparse) + smtp/ + smtp_wrapper.py # SMTPWrapper — 繼承 smtplib.SMTP_SSL + imap/ + imap_wrapper.py # IMAPWrapper — 繼承 imaplib.IMAP4_SSL + utils/ + executor/ # JSON 腳本引擎 (Executor 類別) + file_process/ # 檔案工具 (目錄列表) + json/ # JSON 檔案讀寫 (執行緒安全) + json_format/ # JSON 格式化 + logging/ # 日誌記錄實體 (檔案 + 串流處理器) + package_manager/ # 動態套件載入器 (PackageManager) + project/ # 專案模板建立 + template/ # 模板定義 (keyword JSON + executor Python) + save_mail_user_content/# 認證設定檔 + 環境變數處理 + socket_server/ # TCP Socket 伺服器 (ThreadingMixIn) + exception/ # 自訂例外類別和錯誤標籤 + +運作原理 +-------- + +1. **認證**: MailThunder 從目前工作目錄的 ``mail_thunder_content.json`` 讀取認證資訊, + 若找不到則使用 ``mail_thunder_user`` / ``mail_thunder_user_password`` 環境變數。 + +2. **SMTP (寄送)**: ``SMTPWrapper`` 透過 SSL 連接 SMTP 伺服器 (預設: + ``smtp.gmail.com:465``)。提供方法來建立 ``EmailMessage`` 或 ``MIMEMultipart`` + 物件並一次呼叫完成寄送。 + +3. **IMAP (讀取)**: ``IMAPWrapper`` 透過 SSL 連接 IMAP 伺服器 (預設: + ``imap.gmail.com``)。提供方法來選擇信箱、使用 IMAP SEARCH 語法搜尋郵件、 + 將結果解析為 Python 字典,以及匯出為檔案。 + +4. **腳本引擎**: ``Executor`` 類別將命令名稱對映到 Python 可呼叫物件。 + JSON 動作檔包含 ``["command_name", arguments]`` 元組列表,依序執行。 + 可在執行期載入自訂函式和整個 Python 套件。 + +5. **日誌記錄**: 所有操作記錄到 ``Mail_Thunder.log`` (檔案處理器,INFO 級別) + 和 ``stderr`` (串流處理器,WARNING 級別)。 + +支援平台 +-------- + +- **Python**: 3.9 以上 +- **作業系統**: Windows、macOS、Linux +- **相依套件**: 無 (僅使用 Python 標準函式庫) + +下一步 +------ - read_google_mail.rst - send_google_mail.rst \ No newline at end of file +- :doc:`installation` — 安裝 MailThunder +- :doc:`authentication` — 設定郵件認證 +- :doc:`send_google_mail` — 寄送第一封郵件 +- :doc:`read_google_mail` — 讀取收件匣郵件 +- :doc:`scripting_engine` — 使用 JSON 腳本自動化工作流程 diff --git a/docs/source/index.rst b/docs/source/index.rst index b2077f8..9293660 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -1,20 +1,128 @@ +.. MailThunder documentation master file + MailThunder +=========== + +.. image:: https://img.shields.io/pypi/v/je_mail_thunder + :target: https://pypi.org/project/je-mail-thunder/ + :alt: PyPI + +.. image:: https://img.shields.io/badge/python-3.9%2B-blue.svg + :target: https://www.python.org/ + :alt: Python 3.9+ + +.. image:: https://img.shields.io/badge/license-MIT-blue.svg + :target: https://github.com/Integration-Automation/MailThunder/blob/main/LICENSE + :alt: MIT License + +**MailThunder** is a lightweight and flexible email automation tool for Python. +It wraps Python's ``smtplib.SMTP_SSL`` and ``imaplib.IMAP4_SSL`` protocols, provides a +JSON-based scripting engine and project templates, and makes sending, receiving, and +managing email content effortless. + ---- -.. toctree:: - :maxdepth: 4 +Key Features +------------ - docs/Eng/eng_index.rst - docs/Zh/zh_index.rst - docs/API/api_index.rst +- **SMTP support** — Send emails via SSL with Gmail (default) or any SMTP provider +- **IMAP4 support** — Read, search, and export emails via IMAP4 SSL +- **Attachment handling** — Automatically detect MIME types for text, image, audio, and binary files +- **HTML email** — Send HTML-formatted emails with attachments +- **JSON scripting engine** — Automate email workflows using JSON action files +- **Project templates** — Scaffold projects with pre-built keyword and executor templates +- **Socket server** — Control MailThunder remotely via TCP socket commands +- **Package manager** — Dynamically load Python packages into the scripting executor +- **Environment variable auth** — Authenticate via config file or OS environment variables +- **Context manager support** — Use ``with`` statement for both SMTP and IMAP connections +- **Command-line interface** — Execute action files, directories, or JSON strings from the terminal +- **Built-in logging** — All operations are logged to ``Mail_Thunder.log`` ---- -RoadMap +Quick Example +------------- + +**Send an email:** + +.. code-block:: python + + from je_mail_thunder import SMTPWrapper + + with SMTPWrapper() as smtp: + smtp.later_init() + smtp.create_message_and_send( + message_content="Hello from MailThunder!", + message_setting_dict={ + "Subject": "Test Email", + "From": "sender@gmail.com", + "To": "receiver@gmail.com" + } + ) + +**Read emails:** + +.. code-block:: python + + from je_mail_thunder import IMAPWrapper + + with IMAPWrapper() as imap: + imap.later_init() + imap.select_mailbox("INBOX") + for mail in imap.mail_content_list(): + print(mail["SUBJECT"], mail["FROM"]) ---- -* Project Kanban -* https://github.com/orgs/Integration-Automation/projects/2/views/1 +.. toctree:: + :maxdepth: 4 + :caption: English Documentation + + docs/Eng/eng_index + docs/Eng/installation + docs/Eng/authentication + docs/Eng/send_google_mail + docs/Eng/read_google_mail + docs/Eng/scripting_engine + docs/Eng/project_templates + docs/Eng/cli + docs/Eng/socket_server + docs/Eng/package_manager + docs/Eng/logging + docs/Eng/exceptions + +.. toctree:: + :maxdepth: 4 + :caption: 繁體中文文件 + + docs/Zh/zh_index + docs/Zh/installation + docs/Zh/authentication + docs/Zh/send_google_mail + docs/Zh/read_google_mail + docs/Zh/scripting_engine + docs/Zh/project_templates + docs/Zh/cli + docs/Zh/socket_server + docs/Zh/package_manager + docs/Zh/logging + docs/Zh/exceptions + +.. toctree:: + :maxdepth: 4 + :caption: API Reference + + docs/API/api_index + docs/API/smtp_api + docs/API/imap_api + docs/API/executor_api + docs/API/utils_api ---- + +Links +----- + +* `GitHub Repository `_ +* `PyPI Package `_ +* `Project Kanban `_