phase 2 code complete
Some checks failed
Build and test GAM / build (false, build, 1, Build Intel Ubuntu Jammy, ubuntu-22.04) (push) Has been cancelled
Build and test GAM / build (false, build, 10, Build x86_64 macOS 15, macos-15-intel) (push) Has been cancelled
Build and test GAM / build (false, build, 11, Build x86_64 macOS 26, macos-26-intel) (push) Has been cancelled
Build and test GAM / build (false, build, 12, Build Arm MacOS 26, macos-26) (push) Has been cancelled
Build and test GAM / build (false, build, 13, Build Intel Windows, windows-2025-vs2026) (push) Has been cancelled
Build and test GAM / build (false, build, 14, Build Arm Windows, windows-11-arm) (push) Has been cancelled
Build and test GAM / build (false, build, 2, Build Intel Ubuntu Noble, ubuntu-24.04) (push) Has been cancelled
Build and test GAM / build (false, build, 3, Build Arm Ubuntu Noble, ubuntu-24.04-arm) (push) Has been cancelled
Build and test GAM / build (false, build, 4, Build Arm Ubuntu Jammy, ubuntu-22.04-arm) (push) Has been cancelled
Build and test GAM / build (false, build, 5, Build Intel StaticX Legacy, ubuntu-22.04, yes) (push) Has been cancelled
Build and test GAM / build (false, build, 6, Build Arm StaticX Legacy, ubuntu-22.04-arm, yes) (push) Has been cancelled
Build and test GAM / build (false, build, 8, Build Arm MacOS 14, macos-14) (push) Has been cancelled
Build and test GAM / build (false, build, 9, Build Arm MacOS 15, macos-15) (push) Has been cancelled
Build and test GAM / build (false, test, 16, Test Python 3.11, ubuntu-24.04, 3.11) (push) Has been cancelled
Build and test GAM / build (false, test, 17, Test Python 3.12, ubuntu-24.04, 3.12) (push) Has been cancelled
Build and test GAM / build (false, test, 18, Test Python 3.13, ubuntu-24.04, 3.13) (push) Has been cancelled
Build and test GAM / build (false, test, 19, Test Python 3.15-dev, ubuntu-24.04, 3.15-dev) (push) Has been cancelled
Build and test GAM / build (true, test, 20, Test Python 3.14 freethread, ubuntu-24.04, 3.14) (push) Has been cancelled
Build and test GAM / publish (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled

This commit is contained in:
Jay Lee
2026-07-03 19:52:57 -04:00
parent 7c639d3487
commit 5b27b7b875
10 changed files with 1210 additions and 1206 deletions

View File

@@ -22,67 +22,63 @@ from gamlib import glcfg as GC
from gamlib import glgapi as GAPI
def _getMain():
return sys.modules['gam']
_gam = lambda: sys.modules['gam']
# Add attachements to an email message
def _addAttachmentsToMessage(message, attachments):
gam = _getMain()
for attachment in attachments:
try:
attachFilename = gam.setFilePath(attachment[0], GC.INPUT_DIR)
attachFilename = _gam().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(_gam().readFile(attachFilename, 'r', attachment[1]), _subtype=sub_type, _charset=_gam().UTF8)
elif main_type == 'image':
msg = MIMEImage(gam.readFile(attachFilename, 'rb'), _subtype=sub_type)
msg = MIMEImage(_gam().readFile(attachFilename, 'rb'), _subtype=sub_type)
elif main_type == 'audio':
msg = MIMEAudio(gam.readFile(attachFilename, 'rb'), _subtype=sub_type)
msg = MIMEAudio(_gam().readFile(attachFilename, 'rb'), _subtype=sub_type)
elif main_type == 'application':
msg = MIMEApplication(gam.readFile(attachFilename, 'rb'), _subtype=sub_type)
msg = MIMEApplication(_gam().readFile(attachFilename, 'rb'), _subtype=sub_type)
else:
msg = MIMEBase(main_type, sub_type)
msg.set_payload(gam.readFile(attachFilename, 'rb'))
msg.set_payload(_gam().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)}')
_gam().usageErrorExit(f'{attachFilename}: {str(e)}')
# Add embedded images to an email message
def _addEmbeddedImagesToMessage(message, embeddedImages):
gam = _getMain()
for embeddedImage in embeddedImages:
try:
imageFilename = gam.setFilePath(embeddedImage[0], GC.INPUT_DIR)
imageFilename = _gam().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(_gam().readFile(imageFilename, 'rb'), _subtype=sub_type)
else:
msg = MIMEBase(main_type, sub_type)
msg.set_payload(gam.readFile(imageFilename, 'rb'))
msg.set_payload(_gam().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)}')
_gam().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):
gam = _getMain()
Act = gam.Act
Ent = gam.Ent
Act = _gam().Act
Ent = _gam().Ent
if charset is None:
charset = gam.UTF8
charset = _gam().UTF8
if action is None:
action = Act.SENDEMAIL
@@ -96,26 +92,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)
_gam().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)
_gam().entityActionFailedWarning([entityType, addr, Ent.MESSAGE, msgSubject], errMsg, i, count)
def cleanAddr(emailAddr):
match = gam.NAME_EMAIL_ADDRESS_PATTERN.match(emailAddr)
match = _gam().NAME_EMAIL_ADDRESS_PATTERN.match(emailAddr)
if match:
emailName = match.group(1)
emailAddr = gam.normalizeEmailAddressOrUID(match.group(2), noUid=True, noLower=True)
emailAddr = _gam().normalizeEmailAddressOrUID(match.group(2), noUid=True, noLower=True)
return (f'{emailName} <{emailAddr}>', emailAddr)
emailAddr = gam.normalizeEmailAddressOrUID(emailAddr, noUid=True, noLower=True)
emailAddr = _gam().normalizeEmailAddressOrUID(emailAddr, noUid=True, noLower=True)
return (emailAddr, emailAddr)
if msgFrom is None:
msgFrom = gam._getAdminEmail()
msgFrom = _gam()._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(_gam().UTF8)
if not attachments and not embeddedImages:
message = MIMEText(msgBody, ['plain', 'html'][html], charset)
else:
@@ -145,26 +141,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 = _gam().buildGAPIServiceObject(API.GMAIL, mailBoxAddr)
if not gmail:
Act.Set(parentAction)
return
else:
userId = mailBoxAddr
gmail = gam.buildGAPIObject(API.GMAIL)
gmail = _gam().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 = _gam().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)
_gam().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)
_gam().entityActionFailedWarning([Ent.RECIPIENT, msgTo, Ent.MESSAGE, msgSubject], str(e), i, count)
else:
message['To'] = msgTo if msgTo else mailBoxAddr
server = None
@@ -175,7 +171,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(_gam().UTF8))
else:
server.login(GC.Values[GC.SMTP_USERNAME], GC.Values[GC.SMTP_PASSWORD])
result = server.send_message(message)
@@ -183,7 +179,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)
_gam().entityActionFailedWarning([Ent.RECIPIENT, msgTo, Ent.MESSAGE, msgSubject], str(e), i, count)
if server:
try:
server.quit()