diff --git a/api/blueprints/mail/oauth_request.py b/api/blueprints/mail/oauth_request.py index acd54cf..f5eb902 100644 --- a/api/blueprints/mail/oauth_request.py +++ b/api/blueprints/mail/oauth_request.py @@ -180,7 +180,6 @@ async def request_oauth_authorization_url( mongo_conn = current_app.data_mongo, session_token = inbound_headers["X-Session-Token"], user_info = kwargs["session_info"], - service_type = "email", service_client = inbound_data.mailClient, auth_type = "oauth" ) diff --git a/models/behaviour/mail/oauth.py b/models/behaviour/mail/oauth.py index 1556318..5d3a938 100644 --- a/models/behaviour/mail/oauth.py +++ b/models/behaviour/mail/oauth.py @@ -100,7 +100,6 @@ class MailOAuthModel(BaseModel): db_conn: AsyncMySQL, mongo_conn: AsyncMongo, user_info: dict, - service_type: Literal["email", "chat"], service_client: Literal["gmail"], auth_type: Literal["oauth"], session_token: str = None @@ -112,7 +111,6 @@ class MailOAuthModel(BaseModel): :param db_conn: The database connection (MariaDB) to use to perform the action. :param mongo_conn: The database connection (MongoDB) to use to perform the action. :param user_info: The dictionary that has the user's session information. - :param service_type: The type of service being provided. :param service_client: The name of the company or brand that is providing this service that is being integrated. :param auth_type: To identify the type of authentication being done here. This could indicate simple password authentication, more advance OAuth2.0 authentication, etc. @@ -127,7 +125,7 @@ class MailOAuthModel(BaseModel): mongo_json = await mongo_conn.find_one_and_update( collection = MailOAuthModel.AUTH_COLLECTION, filter = { - "serviceType": service_type, + "serviceType": "email", "client": service_client, "authType": auth_type, "user": user_info, @@ -138,7 +136,7 @@ class MailOAuthModel(BaseModel): }, "$setOnInsert": { "version": "1.0.0", - "serviceType": service_type, + "serviceType": "email", "client": service_client, "authType": auth_type, "user": user_info, @@ -189,10 +187,10 @@ class MailOAuthModel(BaseModel): """ This method is to be called when the end user authorizes your service to connect to his third-party account. For - example, when the end user allows you to access his GMail account. + example, when the end user allows you to access his GMail account. USE THIS FOR UPDATING (REFRESHING) TOKENS + ALSO. :param db_conn: The database connection (MariaDB) to use to perform the action. :param mongo_conn: The database connection (MongoDB) to use to perform the action. - :param user_info: The dictionary that has the user's session information. :param user_identifier: The identifier granted by the 'get_user_identifier' method. :param token: The token granted by the third-party service. :param session_token: The session token of the user who requested this service. @@ -243,6 +241,44 @@ class MailOAuthModel(BaseModel): # Done here: return token_saved + async def get_token( + self, + mongo_conn: AsyncMongo, + user_identifier: ObjectId | str = None, + **kwargs + ) -> dict | None: + + """ + To retrieve stored tokens from the database. + :param mongo_conn: The database connection (MongoDB) to use to perform the action. + :param user_identifier: The identifier granted by the 'get_user_identifier' method. + :param kwargs: Any set of key-value pairs to build custom search criteria. This could be things like the user + info, the client, the type of authentication used, or even the kind of service. + :return: The retrieved record that has the token, and information about the service and client if found, else + None when there is no matching record. + """ + + # Build the filter: + filter_json = {k: v for k, v in kwargs.items()} + if user_identifier: filter_json["_id"] = ObjectId(user_identifier) + + # If there is no search criteria, we exit with failure: + if not filter_json: return None + + # If there is some filtering possible, + # we fetch and return the token: + return await mongo_conn.find_one( + collection = self.AUTH_COLLECTION, + filter = filter_json, + projection = { + "_id": True, + "serviceType": True, + "authType": True, + "client": True, + "token": True + } + ) + # ***************************************************************************************************************** # ***** **** diff --git a/models/behaviour/mail/sync.py b/models/behaviour/mail/sync.py index aaa84de..da95512 100644 --- a/models/behaviour/mail/sync.py +++ b/models/behaviour/mail/sync.py @@ -32,6 +32,9 @@ # To make sibling directories accessible for imports: import sys + +from langchain.chains.summarize.stuff_prompt import prompt_template + sys.path.append(".") sys.path.append("..") @@ -41,12 +44,20 @@ from utils_v2.date_time import date_time from utils_v2.database.async_mysql_v2 import AsyncMySQL from utils_v2.database.async_mongo_v2 import AsyncMongo +# Mail Clients: +from utils_v2.goog.gmail.gmail_client import AsyncGMailClient +from utils_v2.goog.models.data.auth_tokens import GoogleAuthTokens + # Base model: from models.behaviour.base import BaseModel # To work with MongoDB: from bson import ObjectId +# To work with LLMs: +from langchain_openai import ChatOpenAI +from langchain_core.prompts import ChatPromptTemplate + # To work with datatypes: from typing import Literal @@ -91,22 +102,98 @@ import copy # ***************************************************************************************************************** -class MailReadModel(BaseModel): +class MailSyncModel(BaseModel): + # For MongoDB: AUTH_COLLECTION = "_authTokens" + MAIL_COLLECTION = "_messages" - async def read_one( + # For AI Magic through LLMs: + prompt_template = ChatPromptTemplate.from_messages([ + ( + "system", + "You're a mail summary expert that summarizes mails in 150 chars or less. HIDE SENSITIVE INFO (LIKE OTPS) FROM THE SUMMARY." + ), + ( + "user", + "Please summarize this mail: \"\"\"{mail}\"\"\"" + ) + ]) + + async def sync_one( self, - db_conn: AsyncMySQL, mongo_conn: AsyncMongo, - user_info: dict, - service_type: Literal["email", "chat"], - service_client: Literal["gmail"], - auth_type: Literal["oauth"], - session_token: str = None + user_identifier: str | ObjectId, + mail_client: AsyncGMailClient, + tokens: GoogleAuthTokens, + message_id: str, + llm: ChatOpenAI = None, + session_token: str = None, + force_sync: bool = False ) -> ObjectId: - pass + # ┏┓┓ ┓ ┏┓ • • ┳┓ ┓ + # ┃ ┣┓┏┓┏┃┏ ┣ ┓┏┓┏╋┓┏┓┏┓ ┣┫┏┓┏┏┓┏┓┏┫┏ + # ┗┛┛┗┗ ┗┛┗ ┗┛┛┗┗┛┗┗┛┗┗┫ ┛┗┗ ┗┗┛┛ ┗┻┛ + # ┛ + + # Check if you already have that mail in your database: + existing_record = await mongo_conn.find_one( + collection = self.MAIL_COLLECTION, + filter = { + "messageType": "email", + "$or": [ + {"payload.messageId": message_id} + ] + }, + projection = {"_id": True} + ) + + # If there already exists such a record, and we haven't been forced to re-sync it: + if existing_record and not force_sync: return existing_record["_id"] + + # ┏┓ ┓┏ • ┓ ┓ + # ┃┃┏┓┏┓┏┓┏┓┏┓┏┓ ┃┃┏┓┏┓┓┏┓┣┓┃┏┓┏ + # ┣┛┛ ┗ ┣┛┗┻┛ ┗ ┗┛┗┻┛ ┗┗┻┗┛┗┗ ┛ + # ┛ + + mail_payload = None + mail_id = existing_record["_id"] if existing_record else None + + # ┏┓┳┳┓ •┓ + # ┃┓┃┃┃┏┓┓┃ + # ┗┛┛ ┗┗┻┗┗ + + if isinstance(mail_client, AsyncGMailClient): + + # Refresh the tokens: + tokens_refreshed + + # Fetch the mail formatted message: + client_response = await mail_client.get_message( + tokens = tokens, + message_id = message_id, + return_raw = False + ) + + # If the fetch was successful: + if client_response.success: + + # Summarize the content: + prompt = self.prompt_template.invoke({"mail": client_response.data.pop["unformattedText"]}) + llm_response = await llm.ainvoke(prompt) + client_response.data["aiSnippet"] = llm_response.content + + # Note down the response: + mail_payload = client_response.data + + # ┏┓ ┏┳┓┓ ┳┳┓ •┓ + # ┗┓┓┏┏┓┏ ┃ ┣┓┏┓ ┃┃┃┏┓┓┃ + # ┗┛┗┫┛┗┗ ┻ ┛┗┗ ┛ ┗┗┻┗┗ + # ┛ + + if mail_payload: + pass # ***************************************************************************************************************** diff --git a/playground/langchain_test.py b/playground/langchain_test.py index e69de29..4254415 100644 --- a/playground/langchain_test.py +++ b/playground/langchain_test.py @@ -0,0 +1,32 @@ +from langchain_core.messages import HumanMessage, SystemMessage +from langchain_core.prompts import ChatPromptTemplate +from langchain_openai import ChatOpenAI +import asyncio + +async def main(): + prompt_template = ChatPromptTemplate.from_messages( + [ + ( + "system", + "You're a mail summary expert that summarizes mails in 150 chars or less. HIDE SENSITIVE INFO (LIKE OTPS) FROM THE SUMMARY." + ), + ( + "user", + "Please summarize this mail: \"\"\"{mail}\"\"\"" + ) + ] + ) + + model = ChatOpenAI( + model = "gpt-4o-mini", + openai_api_key = r"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" + ) + + prompt = prompt_template.invoke({"mail": r"Find your next favourite\r\n\r\n===================\r\nAmazon Prime Video\r\nhttps://www.primevideo.com?ref_=LHD\r\n===================\r\n\r\n\r\n\r\n\r\n\r\n\r\n===================\r\n\r\n\r\nHere's your guide to blockbuster movies, TV shows & Amazon Originals available on Prime Video. There's always something new for everyone, so get started now!\r\n#FDFDFD\r\nblack\r\n\r\nFind your next favourite\r\nhttps://app.primevideo.com/landing?pageId=completedseriesormovie&pageType=AVCustomerEngagement&ref_=LTC_P2_WB\r\n\r\n\r\n\r\n\r\n===================\r\n\r\nThe Rana Daggubati Show\r\nhttps://www.primevideo.com/dp/detail/amzn1.dv.gti.de3c27de-4339-4faf-873a-1067fe6d0a72?ref_=LHC_P3_IM_P1D_LMnUKb\r\nFeaturing genuine conversations, fun moments and a healthy dose of quirk- this is The Rana Daggubati Show. It\u2019s not just another show, it\u2019s an experience\u2014one that dares to break norms and redefine entertainment by featuring fun and exciting conversations with stars from telugu industry and beyond. Is it a Talk Show? is it a reality show? Find out for yourself.\r\nTalk Show and Variety \u2022 Unscripted 13+\r\n2024, \r\n\r\n\r\n===================\r\n\r\nTV shows we think you'll like\r\n--------------\r\n\r\nCitadel Honey Bunny - Season 1\r\nhttps://www.primevideo.com/dp/detail/amzn1.dv.gti.6223e70b-6b53-4dec-ab7d-3f7b5c3c9ef5?ref_=LGC_P4_I1_IM\r\n\r\n\r\n--------------\r\n\r\nCross - Season 1\r\nhttps://www.primevideo.com/dp/detail/amzn1.dv.gti.2d4ade33-2c00-49c6-aba8-ab96fc6bff2f?ref_=LGC_P4_I2_IM\r\n\r\n\r\n--------------\r\n\r\nREACHER (TV) - SEASON 01\r\nhttps://www.primevideo.com/dp/detail/amzn1.dv.gti.d62095f9-f33c-429b-a8a6-fd74c0461704?ref_=LGC_P4_I3_IM\r\n\r\n\r\n--------------\r\n\r\nFarzi - Season 1\r\nhttps://www.primevideo.com/dp/detail/amzn1.dv.gti.257b2a28-5e4f-44cf-b94f-74ec7795ca3e?ref_=LGC_P4_I4_IM\r\n\r\n\r\n--------------\r\n\r\n\r\n===================\r\n\r\nVettaiyan\r\nhttps://www.primevideo.com/dp/detail/amzn1.dv.gti.17ca7f09-4f84-4d9c-aa90-3c552ef1cc7d?ref_=LHC_P5_IM_P2D_67t3Ma\r\nA sought-after supercop gets caught in a series of unexpected events when he guns down a criminal in a murder case. Amidst his struggle to justify the act, he must confront his moral compass while fighting corrupt, larger-than-life forces. Vettaiyan explores the underbelly of crime investigations with one man's fight against a world, where justice is a rare commodity.\r\nDrama \u2022 Action 16+\r\n2024, 2 h 41 min\r\n\r\n\r\n===================\r\n\r\nRecommended movies\r\n--------------\r\n\r\nThe Ministry of Ungentlemanly Warfare\r\nhttps://www.primevideo.com/dp/detail/amzn1.dv.gti.50a6ea07-f4a9-4191-a343-5f6887a93572?ref_=LGC_P6_I1_IM\r\n\r\n\r\n--------------\r\n\r\nStree 2: Sarkate Ka Aatank\r\nhttps://www.primevideo.com/dp/detail/amzn1.dv.gti.648f1205-89c6-4039-835d-6fdf85dca7ab?ref_=LGC_P6_I2_IM\r\n\r\n\r\n--------------\r\n\r\nGladiator\r\nhttps://www.primevideo.com/dp/detail/amzn1.dv.gti.f2a9f680-2275-2949-77fd-e67f07b680f0?ref_=LGC_P6_I3_IM\r\n\r\n\r\n--------------\r\n\r\nA Quiet Place: Day One\r\nhttps://www.primevideo.com/dp/detail/amzn1.dv.gti.9e833c01-8299-4ebf-823e-b5b24a67bd9d?ref_=LGC_P6_I4_IM\r\n\r\n\r\n--------------\r\n\r\n\r\n===================\r\n\r\n\r\n\r\nFind us on:\r\n* YouTube\r\n https://youtube.com/PrimeVideoIN\r\n* Facebook\r\n https://facebook.com/PrimeVideoIN\r\n* Twitter\r\n https://twitter.com/PrimeVideoIN\r\n* Instagram\r\n https://instagram.com/primevideoin\r\n\r\n\r\nPrime Video provides occasional email updates about new and noteworthy movies and TV shows. We are sensitive to your time and remain committed to limiting our emails to important news, as well as critical information about your account. If you do not want to receive future e-mails of this sort from Prime Video, please modify your account settings here.\r\nhttps://www.primevideo.com/optout/1OWT7V8FxqNel.OoNdzOJmq5-yEpFvPkCDwn3ExI4Dho?ref_=LFT\r\n\r\nThis message is sent to you by the Amazon entity noted here.\r\nhttps://www.primevideo.com/help?ref_=LFT_dv_web_footer_terms&nodeId=202064890\r\n\r\n\u00a92024 Amazon.com, Inc. or its affiliates. Amazon, Prime, Prime Video, Twitch, Fire, and all related logos are trademarks of Amazon.com, Inc. or its affiliates.\r\n\r\nPlease note that this email was sent to pskhushal@gmail.com from a notification-only address that can't accept incoming emails. Please do not reply to this message. If you have any questions and wish to contact us, click here.\r\nhttps://www.primevideo.com/help/contact-us?ref_=LFT\r\n\r\nReference: 1218235951\r\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nFind your next favourite\n\n\r\n \u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\r\n \u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\r\n \u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\u034f\u200c\r\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nHere's your guide to blockbuster movies, TV shows & Amazon Originals available on Prime Video. There's always something new for everyone, so get started now!\n\n\n\n\n\n\nFind your next favourite\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nThe Rana Daggubati Show\n\n\n\n\nFeaturing genuine conversations, fun moments and a healthy dose of quirk- this is The Rana Daggubati Show. It\u2019s not just another show, it\u2019s an experience\u2014one that dares to break norms and redefine entertainment by featuring fun and exciting conversations with stars from telugu industry and beyond. Is it a Talk Show? is it a reality show? Find out for yourself.\n\n\nTalk Show and Variety \u2022 Unscripted\n13+\n\n\n\n2024\n\n\n\n\n\n\nTV shows we think you'll like\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nVettaiyan\n\n\n\n\nA sought-after supercop gets caught in a series of unexpected events when he guns down a criminal in a murder case. Amidst his struggle to justify the act, he must confront his moral compass while fighting corrupt, larger-than-life forces. Vettaiyan explores the underbelly of crime investigations with one man's fight against a world, where justice is a rare commodity.\n\n\nDrama \u2022 Action\n16+\n\n\n\n2024\n2 h 41 min\n\n\n\n\n\n\nRecommended movies\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nPrime Video provides occasional email updates about new and noteworthy movies and TV shows. We are sensitive to your time and remain committed to limiting our emails to important news, as well as critical information about your account. If you do not want to receive future e-mails of this sort from Prime Video, please modify your account settings here.\nThis message is sent to you by the Amazon entity noted here.\n\u00a92024 Amazon.com, Inc. or its affiliates. Amazon, Prime, Prime Video, Twitch, Fire, and all related logos are trademarks of Amazon.com, Inc. or its affiliates.\nPlease note that this email was sent to pskhushal@gmail.com from a notification-only address that can't accept incoming emails. Please do not reply to this message. If you have any questions and wish to contact us, click here.\nReference: 1218235951\n\n\n\n\n\n\n\n\n\r\n \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0\r\n \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0\r\n\n\n\n"}) + # print(prompt) + print("Going to OpenAI...") + response = await model.ainvoke(prompt) + print(response.content) + print(response) + +asyncio.run(main())