(20241126) GMail v2 started with better data handling and feedback.
This commit is contained in:
+130
-60
@@ -45,6 +45,7 @@ from utils_v2.string import json
|
||||
from utils_v2.string import regex
|
||||
from utils_v2.date_time import date_time
|
||||
from utils_v2.oauth.services.goog import GoogleOAuth
|
||||
from utils_v2.mail import mail_parser
|
||||
|
||||
# Related to Google:
|
||||
from google.auth.transport.requests import Request
|
||||
@@ -69,6 +70,9 @@ from icecream import IceCreamDebugger
|
||||
# For computational help:
|
||||
import math
|
||||
|
||||
# For base64 encoding:
|
||||
import base64
|
||||
|
||||
|
||||
# *****************************************************************************************************************
|
||||
# ***** ****
|
||||
@@ -679,63 +683,26 @@ class AsyncGMailClient:
|
||||
# Done here:
|
||||
return messages
|
||||
|
||||
@staticmethod
|
||||
def format_message(
|
||||
raw_message: Dict[str, Any],
|
||||
consider_timezone = date_time.TIMEZONE_UTC,
|
||||
) -> Dict[str, Any] | None:
|
||||
|
||||
message_ts = date_time.parse_date_time(raw_message["internalDate"], timezone = consider_timezone)
|
||||
message_ts = date_time.to_timezone(message_ts, date_time.TIMEZONE_UTC)
|
||||
message_headers = {h["name"]: h["value"] for h in raw_message["payload"]["headers"]}
|
||||
message = {
|
||||
"ts": message_ts,
|
||||
"messageId": raw_message["id"],
|
||||
"threadId": raw_message["threadId"],
|
||||
"labels": raw_message["labelIds"],
|
||||
"summary": raw_message["snippet"],
|
||||
"headers": message_headers,
|
||||
"from": regex.find_first(text = message_headers["From"], pattern = regex.REGEX_EMAIL_ID),
|
||||
"to": regex.find_first(text = message_headers["To"], pattern = regex.REGEX_EMAIL_ID),
|
||||
"cc": [
|
||||
regex.find_first(
|
||||
text = cc_mail.strip(),
|
||||
pattern = regex.REGEX_EMAIL_ID
|
||||
) for cc_mail in cc_mails.split(",")
|
||||
] if (cc_mails := message_headers.get("Cc")) else None,
|
||||
"bcc": [
|
||||
regex.find_first(
|
||||
text = bcc_mail.strip(),
|
||||
pattern = regex.REGEX_EMAIL_ID
|
||||
) for bcc_mail in bcc_mails.split(",")
|
||||
] if (bcc_mails := message_headers.get("Bcc")) else None,
|
||||
"subject": message_headers["Subject"]
|
||||
}
|
||||
|
||||
# Done here:
|
||||
return message
|
||||
|
||||
async def get_message(
|
||||
self,
|
||||
message_id: str,
|
||||
return_raw = False,
|
||||
consider_timezone = date_time.TIMEZONE_UTC,
|
||||
raise_exception: bool = False
|
||||
) -> Dict[str, Any] | None:
|
||||
) -> Dict[str, Any] | str | None:
|
||||
|
||||
"""
|
||||
To get one message of this user. The message will be identified by its id.
|
||||
To get one message of this user. The message will be identified by its id. Note that, if you choose to return
|
||||
the raw message, the message body will be compliant with RFC 5322 and RFC 2045 (among others).
|
||||
DOCUMENTATION:
|
||||
1. https://developers.google.com/gmail/api/reference/rest/v1/users.messages/list
|
||||
1. https://developers.google.com/gmail/api/reference/rest/v1/users.messages/get
|
||||
2. https://developers.google.com/gmail/api/reference/rest/v1/users.messages#Message
|
||||
2. https://developers.google.com/gmail/api/reference/rest/v1/users.messages#Message.MessagePart
|
||||
2. https://developers.google.com/gmail/api/reference/rest/v1/Format
|
||||
:param message_id: The id that Google assigned to the message.
|
||||
:param return_raw: Whether you want the raw message or the formatted message.
|
||||
:param consider_timezone: The timestamp given by Google doesn't have timezone information. Use this parameter to
|
||||
control what timezone the timestamp is interpreted as.
|
||||
:param raise_exception: If set to True, any exceptions that occur will be propagated, else they wil be
|
||||
suppressed.
|
||||
:return: The list of labels.
|
||||
:return: The message either parsed as a JSON, or as a raw text body. If the API call fails, the response will be
|
||||
a null value.
|
||||
"""
|
||||
|
||||
# Start by assuming failure:
|
||||
@@ -750,21 +717,25 @@ class AsyncGMailClient:
|
||||
if not self._debug_only_errors: self._printer("Getting One Message.", self.__user_email)
|
||||
api_response = await self.__http_client.get(
|
||||
url = f"https://gmail.googleapis.com/gmail/v1/users/{self.__user_email}/messages/{message_id}",
|
||||
headers = {"Authorization": f"Bearer {self.__credentials.token}"}
|
||||
headers = {"Authorization": f"Bearer {self.__credentials.token}"},
|
||||
params = {"format": "raw"}
|
||||
)
|
||||
|
||||
# If the API call failed:
|
||||
if api_response.status_code not in [200]: return message
|
||||
|
||||
# Return the raw message if asked:
|
||||
# Extract the raw message and respond based on the request:
|
||||
api_json = api_response.json()
|
||||
if return_raw: return api_json
|
||||
|
||||
# Else we extract and format the response:
|
||||
message = self.format_message(
|
||||
raw_message = api_json,
|
||||
consider_timezone = consider_timezone
|
||||
)
|
||||
message = base64.urlsafe_b64decode(api_json["raw"])
|
||||
if return_raw: message = message.decode()
|
||||
else:
|
||||
message = mail_parser.parse(message)
|
||||
message["labels"] = api_json["labelIds"]
|
||||
message["messageId"] = api_json["id"]
|
||||
message["threadId"] = api_json["threadId"]
|
||||
message["historyId"] = api_json["historyId"]
|
||||
message["snippet"] = api_json["snippet"]
|
||||
message["sizeEstimate"] = api_json["sizeEstimate"]
|
||||
|
||||
# In case something goes wrong along the way:
|
||||
except Exception as exception:
|
||||
@@ -775,6 +746,97 @@ class AsyncGMailClient:
|
||||
# Done here:
|
||||
return message
|
||||
|
||||
async def modify_messages(
|
||||
self,
|
||||
message_ids: List[str] | str,
|
||||
add_label_ids: List[str] | str = None,
|
||||
remove_label_ids: List[str] | str = None,
|
||||
raise_exception: bool = False
|
||||
) -> bool:
|
||||
|
||||
"""
|
||||
To add or remove labels from messages.
|
||||
DOCUMENTATION:
|
||||
1. https://developers.google.com/gmail/api/reference/rest/v1/users.messages/batchModify
|
||||
:param message_ids: One or more message ids (assigned by Google).
|
||||
:param add_label_ids: One or more label ids (not the display name of the label).
|
||||
:param remove_label_ids: One or more label ids (not the display name of the label).
|
||||
:param raise_exception: If set to True, any exceptions that occur will be propagated, else they wil be
|
||||
suppressed.
|
||||
:return: True if the operation succeeded, else False.
|
||||
"""
|
||||
|
||||
# Start by assuming failure:
|
||||
success = False
|
||||
|
||||
try:
|
||||
|
||||
# Standard token-refresh check:
|
||||
await self.__ensure_token()
|
||||
|
||||
# Build the needed JSON:
|
||||
body_json = {"ids": message_ids if isinstance(message_ids, list) else [message_ids]}
|
||||
if add_label_ids: body_json["addLabelIds"] = add_label_ids if isinstance(add_label_ids, list) else [add_label_ids]
|
||||
if remove_label_ids: body_json["removeLabelIds"] = remove_label_ids if isinstance(remove_label_ids, list) else [remove_label_ids]
|
||||
|
||||
# Make the API call:
|
||||
if not self._debug_only_errors: self._printer("Modifying Message(s).", self.__user_email)
|
||||
api_response = await self.__http_client.post(
|
||||
url = f"https://gmail.googleapis.com/gmail/v1/users/{self.__user_email}/messages/batchModify",
|
||||
headers = {"Authorization": f"Bearer {self.__credentials.token}"},
|
||||
json = body_json
|
||||
)
|
||||
|
||||
# Check if our request was successful:
|
||||
if api_response.status_code in [200, 204]: success = True
|
||||
|
||||
# In case something goes wrong along the way:
|
||||
except Exception as exception:
|
||||
if raise_exception: raise
|
||||
self._printer(exception)
|
||||
success = False
|
||||
|
||||
# Done here:
|
||||
return success
|
||||
|
||||
async def delete_messages(
|
||||
self,
|
||||
message_ids: List[str] | str,
|
||||
raise_exception: bool = False
|
||||
) -> bool:
|
||||
|
||||
# Start by assuming failure:
|
||||
success = False
|
||||
|
||||
try:
|
||||
|
||||
# Standard token-refresh check:
|
||||
await self.__ensure_token()
|
||||
|
||||
# Make the API call:
|
||||
if not self._debug_only_errors: self._printer("Deleting Message(s).", self.__user_email)
|
||||
api_response = await self.__http_client.post(
|
||||
url = f"https://gmail.googleapis.com/gmail/v1/users/{self.__user_email}/messages/batchDelete",
|
||||
headers = {"Authorization": f"Bearer {self.__credentials.token}"},
|
||||
json = {"ids": message_ids if isinstance(message_ids, list) else [message_ids]}
|
||||
)
|
||||
|
||||
print("HTTP CODE:", api_response.status_code)
|
||||
try: print("HTTP JSON:", json.to_string(api_response.json()))
|
||||
except Exception as e: print("HTTP JSON:", e)
|
||||
|
||||
# Check if our request was successful:
|
||||
if api_response.status_code in [200, 204]: success = True
|
||||
|
||||
# In case something goes wrong along the way:
|
||||
except Exception as exception:
|
||||
if raise_exception: raise
|
||||
self._printer(exception)
|
||||
success = False
|
||||
|
||||
# Done here:
|
||||
return success
|
||||
|
||||
|
||||
# *****************************************************************************************************************
|
||||
# ***** ****
|
||||
@@ -854,13 +916,21 @@ if __name__ == "__main__":
|
||||
# print(json.to_string(await my_gmail.delete_label(label_id = "Label_6")))
|
||||
|
||||
# print(json.to_string(await my_gmail.list_messages(count = 10)))
|
||||
print(json.to_string(
|
||||
await my_gmail.get_message(
|
||||
message_id = "1935e92672e67f22",
|
||||
return_raw = True
|
||||
),
|
||||
default = str)
|
||||
)
|
||||
# print(json.to_string(
|
||||
# await my_gmail.get_message(
|
||||
# message_id = "19367930033154ca",
|
||||
# return_raw = False
|
||||
# ),
|
||||
# default = str)
|
||||
# )
|
||||
|
||||
# print(json.to_string(await my_gmail.modify_messages(
|
||||
# message_ids = ["19367930033154ca"],
|
||||
# add_label_ids = ["Label_5"],
|
||||
# remove_label_ids = ["Label_3"]
|
||||
# )))
|
||||
|
||||
print(json.to_string(await my_gmail.delete_messages(message_ids = "19367930033154ca")))
|
||||
|
||||
|
||||
asyncio.run(main())
|
||||
|
||||
Reference in New Issue
Block a user