Files

333 lines
14 KiB
Python

"""
AUTHOR:
Khushal P Soonderji
DATE:
Thursday, 16th Jan., 2025.
OBJECTIVE:
To handle all mail-related behaviour for Gmail from one place.
REFERENCES:
N/A
DOWNLOADS:
N/A
"""
# *****************************************************************************************************************
# ***** ****
# *** IMPORT ***
# ***** ****
# *****************************************************************************************************************
# To make sibling directories accessible for imports:
import sys
sys.path.append(".")
sys.path.append("..")
# My async utils:
from utils_v2.database.async_mysql_v2 import AsyncMySQL
from utils_v2.database.async_mongo_v2 import AsyncMongo
from utils_v2.cache.async_redis_cache_v2 import AsyncRedisCache
# Controllers:
from controllers_v2.message.mail.base import MailController
from controllers.core.ai.llm import CoreLLMController
# Models:
from models.core.user import CoreUserInfoModel
from models.core.auth_token import CoreAuthTokenModel
from models.api.message.mail.oauth import (
OAuthMailAuthorizationRequestHeaders,
OAuthMailAuthorizationRequestData
)
from models.message.mail.oauth import OAuthMailGetAuthorizationURLResponse, OAuthMailHandleCallbackResponse
from models.core.message import CoreMessageModel
from models.core.ai.llm import LLMInput, LLMOutput, LLMInputMessage
from models.message.mail.sync import MailSyncOneResult, MailSyncManyResults
from models.message.mail.send import MailSendOneResult
# Mail Client(s):
from utils_v2.goog.controllers.gmail.gmail_client import AsyncGmailClient
from utils_v2.goog.controllers.gmail.gmail_message import GmailMessage
# To work with datatypes:
from typing import List, Any
# To work with date and time:
import datetime
# To make HTTP requests:
import httpx
# *****************************************************************************************************************
# ***** ****
# *** MACROS / ONE-TIME INIT ***
# ***** ****
# *****************************************************************************************************************
# --- Nothing Yet
# *****************************************************************************************************************
# ***** ****
# *** VARIABLES ***
# ***** ****
# *****************************************************************************************************************
# --- Nothing Yet
# *****************************************************************************************************************
# ***** ****
# *** FUNCTIONS ***
# ***** ****
# *****************************************************************************************************************
# --- Nothing Yet
# *****************************************************************************************************************
# ***** ****
# *** CLASSES ***
# ***** ****
# *****************************************************************************************************************
class AllMailController(MailController):
# ┏┓
# ┃ ┏┓┏┓┏╋┏┓┓┏┏╋┏┓┏┓
# ┗┛┗┛┛┗┛┗┛ ┗┻┗┗┗┛┛
def __init__(
self,
cache: AsyncRedisCache = None,
http_client: httpx.AsyncClient = None,
alert_url: str = None,
base_filter: dict = None,
debug: bool = True,
debug_prefix: str = "All Mail (C) | ",
debug_only_errors: bool = True
):
"""
This is the foundational controller for all mail services. This is built on top of the core message controller,
and, in turn, all individual mail client controllers must be built on top of this.
:param cache: The object to use for caching results from database calls.
:param http_client: The HTTP client
:param base_filter: The basic filter that will be applied to all fetching/updating queries. WARNING: THE BASE
FILTER WILL ALWAYS BE APPLIED AUTOMATICALLY. SET THIS UP WISELY.
:param debug: Whether, or not, you would like to print debugging messages:
:param debug_prefix: The prefix to print with the debugging messages.
:param debug_only_errors: Whether you would like to print only error messages or all messages.
:return: None.
"""
# Prepare the combined base filter:
mail_filter = {}
for k, v in (base_filter or {}).items(): mail_filter[k] = v
mail_filter["serviceType"] = "email"
# Invoke the parent's constructor:
MailController.__init__(
self,
cache = cache,
alert_url = alert_url,
http_client = http_client,
base_filter = mail_filter,
debug = debug,
debug_prefix = debug_prefix,
debug_only_errors = debug_only_errors
)
# ┓┏ ┓
# ┣┫┏┓┃┏┓┏┓┏┓┏
# ┛┗┗ ┗┣┛┗ ┛ ┛
# ┛
pass
# ┏┓┏┓ ┓ ┏┓ ┏┓
# ┃┃┣┫┓┏╋┣┓┏┛ ┃┫
# ┗┛┛┗┗┻┗┛┗┗━•┗┛
async def get_authorization_url(
self,
sql_conn: AsyncMySQL,
mongo_data_conn: AsyncMongo,
mail_client: AsyncGmailClient,
user_info: CoreUserInfoModel,
inbound_data: OAuthMailAuthorizationRequestData,
session_token: str
) -> OAuthMailGetAuthorizationURLResponse:
"""
To accept an incoming request for mail integration and provide a URL that the user can use to authorize your
service to access his mail inbox.
:param sql_conn: The database connection to use to perform this task.
:param mongo_data_conn: The database connection to use to perform this task.
:param mail_client: The instance of the third-party mail client that will be used to get the URL.
:param user_info: The information about your user who is trying to use this system.
:param inbound_data: The data that came in with the request (API call).
:param session_token: The session token of the user.
:return: A structure response with details about the URL generation process.
"""
raise NotImplementedError
async def handle_authorization_callback(
self,
sql_conn: AsyncMySQL,
mongo_data_conn: AsyncMongo,
mail_client: AsyncGmailClient,
request_url: str,
inbound_data: dict,
session_token: str = None
) -> OAuthMailHandleCallbackResponse:
"""
To handle the authorization callback for the mail client. The user may grant or deny authorization.
:param sql_conn: The database connection to use to perform this task.
:param mongo_data_conn: The database connection to use to perform this task.
:param mail_client: The instance of the third-party mail client that will be used to get the URL.
:param request_url: The full callback URL invoked by the third-party client.
:param inbound_data: The data that came in with the request (API call).
:param session_token: The session token of the user. It is expected that this will be null in all cases.
:return: A structured response of the process of handling the mail callback.
"""
raise NotImplementedError
async def refresh_authorization(
self,
sql_conn: AsyncMySQL,
mongo_data_conn: AsyncMongo,
mail_client: AsyncGmailClient,
http_client: httpx.AsyncClient,
auth_token: CoreAuthTokenModel,
force_refresh: bool = False,
session_token: str = None
) -> CoreAuthTokenModel:
"""
To refresh the third-party client's access/authorization token(s) before use.
:param sql_conn: The database connection to use when storing the refreshed tokens.
:param mongo_data_conn: The database connection to use when storing the refreshed tokens.
:param mail_client: The connection of the third-party mail client.
:param http_client: The HTTP client to use to make the token refresh request.
:param auth_token: The auth-token model of the existing integration. This may get updated if a refresh is needed
(or forced).
:param force_refresh: Whether, or not, you would like to force a refresh even if the token hasn't expired yet.
:param session_token: The session token of the user. This will be null if this method is invoked by a cron
script in the background. Needed only to identify the user in case of a failure to send a timely alert.
:return: The same auth-token model instance, but maybe with updated tokens.
"""
raise NotImplementedError
# ┳┳┓ •┓ ┏┓ • •
# ┃┃┃┏┓┓┃ ┗┓┓┏┏┳┓┏┳┓┏┓┏┓┓┓┏┓╋┓┏┓┏┓
# ┛ ┗┗┻┗┗ ┗┛┗┻┛┗┗┛┗┗┗┻┛ ┗┗┗┻┗┗┗┛┛┗
pass
# ┳┳┓ •┓ ┏┓ ╹•
# ┃┃┃┏┓┓┃ ┗┓┓┏┏┓┏ ┓┏┓┏┓
# ┛ ┗┗┻┗┗ ┗┛┗┫┛┗┗ ┗┛┗┗┫
# ┛ ┛
# To synchronize the mails on the third-party client's server and your server. You are effectively making a copy of
# the mail on your database.
async def sync_mails(
self,
sql_conn: AsyncMySQL,
mongo_data_conn: AsyncMongo,
mail_client: AsyncGmailClient,
auth_token: CoreAuthTokenModel,
user_info: CoreUserInfoModel | None,
llm: CoreLLMController = None,
force_sync: bool = False,
start_date: datetime.datetime = None,
end_date: datetime.datetime = None,
max_count: int = 100,
session_token: str = None
) -> MailSyncManyResults:
"""
To fetch mails from the third-party client and store them to your database.
:param sql_conn: The database connection to use to perform this task.
:param mongo_data_conn: The database connection to use to perform this task.
:param mail_client: The instance of the third-party mail client that will be used to get the URL.
:param auth_token: The credentials to the account with the third-party client.
:param user_info: The information about your user who is trying to use this system. Needed to note LLM token
usage in the process of mail summarization.
:param llm: The instance of the LLm that can be used to summarize the contents of the mail.
:param force_sync: To forcefully sync a mail even if it already exists in the database.
:param start_date: The starting date (inclusive) from which mails must be sync'd.
:param end_date: The ending date (inclusive) till which mails must be sync'd.
:param max_count: The max. no. of mails to sync.
:param session_token: TO identify a user session. This will be null if a cron script invokes this method, else
it will be received from the inputs of the API call.
:return:
"""
raise NotImplementedError
# ┳┳┓ •┓ ┓ • •
# ┃┃┃┏┓┓┃ ┃ ┓┏╋┓┏┓┏┓
# ┛ ┗┗┻┗┗ ┗┛┗┛┗┗┛┗┗┫
# ┛
# Use these to show your users their mails once the mails are on your server. This would include activities like
# listing mails, showing full mails, showing mail trails, etc.
pass
# ┳┳┓ •┓ ┏┓ ┓•
# ┃┃┃┏┓┓┃ ┗┓┏┓┏┓┏┫┓┏┓┏┓
# ┛ ┗┗┻┗┗ ┗┛┗ ┛┗┗┻┗┛┗┗┫
# ┛
async def send_mail(
self,
sql_conn: AsyncMySQL,
mongo_data_conn: AsyncMongo,
mail_client: AsyncGmailClient,
mail_message: GmailMessage,
auth_token: CoreAuthTokenModel,
client_thread_id: str | None,
user_info: CoreUserInfoModel | None,
llm: CoreLLMController = None,
session_token: str = None
) -> MailSendOneResult:
raise NotImplementedError
# *****************************************************************************************************************
# ***** ****
# *** MAIN PROGRAM ***
# ***** ****
# *****************************************************************************************************************
if __name__ == "__main__":
pass