Phase 3 complete

This commit is contained in:
Jay Lee
2026-07-04 05:37:34 -04:00
parent 5b27b7b875
commit c41149bedb
58 changed files with 1716 additions and 1983 deletions

View File

@@ -20,65 +20,67 @@ from email.mime.text import MIMEText
from gamlib import glapi as API
from gamlib import glcfg as GC
from gamlib import glgapi as GAPI
from util.api import _getAdminEmail, buildGAPIObject, buildGAPIServiceObject, callGAPI
from util.args import NAME_EMAIL_ADDRESS_PATTERN, UTF8, normalizeEmailAddressOrUID
from util.display import entityActionFailedWarning, entityActionPerformed, entityActionPerformedMessage
from util.errors import usageErrorExit
from util.fileio import readFile, setFilePath
_gam = lambda: sys.modules['gam']
# Add attachements to an email message
def _addAttachmentsToMessage(message, attachments):
for attachment in attachments:
try:
attachFilename = _gam().setFilePath(attachment[0], GC.INPUT_DIR)
attachFilename = setFilePath(attachment[0], GC.INPUT_DIR)
attachContentType, attachEncoding = mimetypes.guess_type(attachFilename)
if attachContentType is None or attachEncoding is not None:
attachContentType = 'application/octet-stream'
main_type, sub_type = attachContentType.split('/', 1)
if main_type == 'text':
msg = MIMEText(_gam().readFile(attachFilename, 'r', attachment[1]), _subtype=sub_type, _charset=_gam().UTF8)
msg = MIMEText(readFile(attachFilename, 'r', attachment[1]), _subtype=sub_type, _charset=UTF8)
elif main_type == 'image':
msg = MIMEImage(_gam().readFile(attachFilename, 'rb'), _subtype=sub_type)
msg = MIMEImage(readFile(attachFilename, 'rb'), _subtype=sub_type)
elif main_type == 'audio':
msg = MIMEAudio(_gam().readFile(attachFilename, 'rb'), _subtype=sub_type)
msg = MIMEAudio(readFile(attachFilename, 'rb'), _subtype=sub_type)
elif main_type == 'application':
msg = MIMEApplication(_gam().readFile(attachFilename, 'rb'), _subtype=sub_type)
msg = MIMEApplication(readFile(attachFilename, 'rb'), _subtype=sub_type)
else:
msg = MIMEBase(main_type, sub_type)
msg.set_payload(_gam().readFile(attachFilename, 'rb'))
msg.set_payload(readFile(attachFilename, 'rb'))
msg.add_header('Content-Disposition', 'attachment', filename=os.path.basename(attachFilename))
message.attach(msg)
except (IOError, UnicodeDecodeError) as e:
_gam().usageErrorExit(f'{attachFilename}: {str(e)}')
usageErrorExit(f'{attachFilename}: {str(e)}')
# Add embedded images to an email message
def _addEmbeddedImagesToMessage(message, embeddedImages):
for embeddedImage in embeddedImages:
try:
imageFilename = _gam().setFilePath(embeddedImage[0], GC.INPUT_DIR)
imageFilename = setFilePath(embeddedImage[0], GC.INPUT_DIR)
imageContentType, imageEncoding = mimetypes.guess_type(imageFilename)
if imageContentType is None or imageEncoding is not None:
imageContentType = 'application/octet-stream'
main_type, sub_type = imageContentType.split('/', 1)
if main_type == 'image':
msg = MIMEImage(_gam().readFile(imageFilename, 'rb'), _subtype=sub_type)
msg = MIMEImage(readFile(imageFilename, 'rb'), _subtype=sub_type)
else:
msg = MIMEBase(main_type, sub_type)
msg.set_payload(_gam().readFile(imageFilename, 'rb'))
msg.set_payload(readFile(imageFilename, 'rb'))
msg.add_header('Content-Disposition', 'attachment', filename=os.path.basename(imageFilename))
msg.add_header('Content-ID', f'<{embeddedImage[1]}>')
message.attach(msg)
except (IOError, UnicodeDecodeError) as e:
_gam().usageErrorExit(f'{imageFilename}: {str(e)}')
usageErrorExit(f'{imageFilename}: {str(e)}')
# Send an email
def send_email(msgSubject, msgBody, msgTo, i=0, count=0, clientAccess=False, msgFrom=None, msgReplyTo=None,
html=False, charset=None, attachments=None, embeddedImages=None,
msgHeaders=None, ccRecipients=None, bccRecipients=None, mailBox=None, threadId=None,
action=None):
Act = _gam().Act
Ent = _gam().Ent
if charset is None:
charset = _gam().UTF8
charset = UTF8
if action is None:
action = Act.SENDEMAIL
@@ -92,26 +94,26 @@ def send_email(msgSubject, msgBody, msgTo, i=0, count=0, clientAccess=False, msg
toSent.remove(addr)
toFailed[addr] = f'{err[0]}: {err[1]}'
if toSent:
_gam().entityActionPerformed([entityType, ','.join(toSent), Ent.MESSAGE, msgSubject], i, count)
entityActionPerformed([entityType, ','.join(toSent), Ent.MESSAGE, msgSubject], i, count)
for addr, errMsg in toFailed.items():
_gam().entityActionFailedWarning([entityType, addr, Ent.MESSAGE, msgSubject], errMsg, i, count)
entityActionFailedWarning([entityType, addr, Ent.MESSAGE, msgSubject], errMsg, i, count)
def cleanAddr(emailAddr):
match = _gam().NAME_EMAIL_ADDRESS_PATTERN.match(emailAddr)
match = NAME_EMAIL_ADDRESS_PATTERN.match(emailAddr)
if match:
emailName = match.group(1)
emailAddr = _gam().normalizeEmailAddressOrUID(match.group(2), noUid=True, noLower=True)
emailAddr = normalizeEmailAddressOrUID(match.group(2), noUid=True, noLower=True)
return (f'{emailName} <{emailAddr}>', emailAddr)
emailAddr = _gam().normalizeEmailAddressOrUID(emailAddr, noUid=True, noLower=True)
emailAddr = normalizeEmailAddressOrUID(emailAddr, noUid=True, noLower=True)
return (emailAddr, emailAddr)
if msgFrom is None:
msgFrom = _gam()._getAdminEmail()
msgFrom = _getAdminEmail()
# Force ASCII for RFC compliance
# xmlcharref seems to work to display at least
# some unicode in HTML body and is ignored in
# plain text body.
# msgBody = msgBody.encode('ascii', 'xmlcharrefreplace').decode(_gam().UTF8)
# msgBody = msgBody.encode('ascii', 'xmlcharrefreplace').decode(UTF8)
if not attachments and not embeddedImages:
message = MIMEText(msgBody, ['plain', 'html'][html], charset)
else:
@@ -141,26 +143,26 @@ def send_email(msgSubject, msgBody, msgTo, i=0, count=0, clientAccess=False, msg
Act.Set(action)
if not GC.Values[GC.SMTP_HOST]:
if not clientAccess:
userId, gmail = _gam().buildGAPIServiceObject(API.GMAIL, mailBoxAddr)
userId, gmail = buildGAPIServiceObject(API.GMAIL, mailBoxAddr)
if not gmail:
Act.Set(parentAction)
return
else:
userId = mailBoxAddr
gmail = _gam().buildGAPIObject(API.GMAIL)
gmail = buildGAPIObject(API.GMAIL)
message['To'] = msgTo if msgTo else userId
body = {'raw': base64.urlsafe_b64encode(message.as_bytes()).decode()}
if threadId is not None:
body['threadId'] = threadId
try:
result = _gam().callGAPI(gmail.users().messages(), 'send',
result = callGAPI(gmail.users().messages(), 'send',
throwReasons=[GAPI.SERVICE_NOT_AVAILABLE, GAPI.AUTH_ERROR, GAPI.DOMAIN_POLICY,
GAPI.INVALID, GAPI.INVALID_ARGUMENT, GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED],
userId=userId, body=body, fields='id')
_gam().entityActionPerformedMessage([Ent.RECIPIENT, msgTo, Ent.MESSAGE, msgSubject], f"{result['id']}", i, count)
entityActionPerformedMessage([Ent.RECIPIENT, msgTo, Ent.MESSAGE, msgSubject], f"{result['id']}", i, count)
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy,
GAPI.invalid, GAPI.invalidArgument, GAPI.forbidden, GAPI.permissionDenied) as e:
_gam().entityActionFailedWarning([Ent.RECIPIENT, msgTo, Ent.MESSAGE, msgSubject], str(e), i, count)
entityActionFailedWarning([Ent.RECIPIENT, msgTo, Ent.MESSAGE, msgSubject], str(e), i, count)
else:
message['To'] = msgTo if msgTo else mailBoxAddr
server = None
@@ -171,7 +173,7 @@ def send_email(msgSubject, msgBody, msgTo, i=0, count=0, clientAccess=False, msg
server.starttls(context=ssl.create_default_context(cafile=GC.Values[GC.CACERTS_PEM]))
if GC.Values[GC.SMTP_USERNAME] and GC.Values[GC.SMTP_PASSWORD]:
if isinstance(GC.Values[GC.SMTP_PASSWORD], bytes):
server.login(GC.Values[GC.SMTP_USERNAME], base64.b64decode(GC.Values[GC.SMTP_PASSWORD]).decode(_gam().UTF8))
server.login(GC.Values[GC.SMTP_USERNAME], base64.b64decode(GC.Values[GC.SMTP_PASSWORD]).decode(UTF8))
else:
server.login(GC.Values[GC.SMTP_USERNAME], GC.Values[GC.SMTP_PASSWORD])
result = server.send_message(message)
@@ -179,7 +181,7 @@ def send_email(msgSubject, msgBody, msgTo, i=0, count=0, clientAccess=False, msg
checkResult(Ent.RECIPIENT_CC, ccRecipients)
checkResult(Ent.RECIPIENT_BCC, bccRecipients)
except smtplib.SMTPException as e:
_gam().entityActionFailedWarning([Ent.RECIPIENT, msgTo, Ent.MESSAGE, msgSubject], str(e), i, count)
entityActionFailedWarning([Ent.RECIPIENT, msgTo, Ent.MESSAGE, msgSubject], str(e), i, count)
if server:
try:
server.quit()