optimuslib 0.0.47__py3-none-any.whl → 0.0.48__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
optimuslib/optimuslib.py
CHANGED
|
@@ -109,7 +109,7 @@ log.setLevel(logging.DEBUG)
|
|
|
109
109
|
# # Handler - 1
|
|
110
110
|
file = logging.FileHandler('optimuslibLogs.log', 'a', 'utf-8')
|
|
111
111
|
# fileformat = logging.Formatter(fmt='%(asctime)s - %(levelname)s - %(filename)s - %(module)s:%(funcName)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
|
|
112
|
-
fileformat = logging.Formatter(fmt='%(asctime)s
|
|
112
|
+
fileformat = logging.Formatter(fmt='%(asctime)s | %(levelname)s | %(filename)s | %(funcName)s | %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
|
|
113
113
|
file.setLevel(logging.DEBUG)
|
|
114
114
|
# file.setLevel(logging.INFO)
|
|
115
115
|
file.setFormatter(fileformat)
|
|
@@ -1552,21 +1552,43 @@ from datetime import datetime, timezone
|
|
|
1552
1552
|
|
|
1553
1553
|
|
|
1554
1554
|
def extract_clean_text(msg):
|
|
1555
|
-
|
|
1556
|
-
|
|
1555
|
+
text_parts = []
|
|
1556
|
+
|
|
1557
1557
|
for part in msg.walk():
|
|
1558
|
-
|
|
1558
|
+
content_type = part.get_content_type()
|
|
1559
|
+
charset = part.get_content_charset()
|
|
1560
|
+
|
|
1561
|
+
log.debug(f"Found MIME part: {content_type}, charset={charset}")
|
|
1562
|
+
|
|
1563
|
+
if content_type in ("text/plain", "text/html"):
|
|
1559
1564
|
raw = part.get_payload(decode=True)
|
|
1560
|
-
decoded = quopri.decodestring(raw).decode(errors="ignore")
|
|
1561
|
-
soup = BeautifulSoup(decoded, "html.parser")
|
|
1562
|
-
text += soup.get_text(" ")
|
|
1563
|
-
return text
|
|
1564
1565
|
|
|
1566
|
+
if not raw:
|
|
1567
|
+
log.debug("Empty payload")
|
|
1568
|
+
continue
|
|
1569
|
+
|
|
1570
|
+
try:
|
|
1571
|
+
decoded = raw.decode(charset or "utf-8", errors="ignore")
|
|
1572
|
+
except Exception:
|
|
1573
|
+
decoded = quopri.decodestring(raw).decode(errors="ignore")
|
|
1574
|
+
|
|
1575
|
+
if content_type == "text/html":
|
|
1576
|
+
soup = BeautifulSoup(decoded, "html.parser")
|
|
1577
|
+
decoded = soup.get_text(" ")
|
|
1565
1578
|
|
|
1566
|
-
|
|
1567
|
-
|
|
1579
|
+
text_parts.append(decoded)
|
|
1580
|
+
|
|
1581
|
+
full_text = " ".join(text_parts)
|
|
1582
|
+
log.debug(f"Extracted email text (first 300 chars): {full_text[:300]!r}")
|
|
1583
|
+
|
|
1584
|
+
return full_text
|
|
1585
|
+
|
|
1586
|
+
|
|
1587
|
+
|
|
1588
|
+
def is_recent(msg, max_age_seconds):
|
|
1568
1589
|
date_hdr = msg.get("Date")
|
|
1569
1590
|
if not date_hdr:
|
|
1591
|
+
log.debug("Email has no Date header")
|
|
1570
1592
|
return False
|
|
1571
1593
|
|
|
1572
1594
|
try:
|
|
@@ -1576,10 +1598,13 @@ def is_recent(msg):
|
|
|
1576
1598
|
|
|
1577
1599
|
now = datetime.now(timezone.utc)
|
|
1578
1600
|
age = (now - msg_time).total_seconds()
|
|
1579
|
-
return 0 <= age <= MAX_AGE_SECONDS
|
|
1580
|
-
except Exception:
|
|
1581
|
-
return False
|
|
1582
1601
|
|
|
1602
|
+
log.debug(f"Email age (seconds): {age}")
|
|
1603
|
+
return 0 <= age <= max_age_seconds
|
|
1604
|
+
|
|
1605
|
+
except Exception as e:
|
|
1606
|
+
log.exception("Failed to parse email date")
|
|
1607
|
+
return False
|
|
1583
1608
|
|
|
1584
1609
|
def input_with_timeout(prompt, timeout):
|
|
1585
1610
|
"""Get user input with timeout"""
|
|
@@ -1612,28 +1637,46 @@ def poll_for_otp(EMAIL, APP_PASSWORD, SENDER, SUBJECT, POLL_INTERVAL, POLL_TIMEO
|
|
|
1612
1637
|
# 🔑 Gmail IMAP requires re-select to refresh UNSEEN
|
|
1613
1638
|
mail.select("inbox", readonly=False)
|
|
1614
1639
|
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
)
|
|
1640
|
+
filter = f'(UNSEEN FROM "{SENDER}" SUBJECT "{SUBJECT}")'
|
|
1641
|
+
log.debug(f"IMAP search filter: {filter}")
|
|
1642
|
+
status, messages = mail.search(None,filter)
|
|
1619
1643
|
|
|
1620
1644
|
ids = messages[0].split()
|
|
1621
1645
|
|
|
1646
|
+
log.debug(f"IMAP search status: {status}")
|
|
1647
|
+
log.debug(f"Message IDs returned: {ids}")
|
|
1648
|
+
|
|
1622
1649
|
for eid in ids:
|
|
1623
1650
|
_, data = mail.fetch(eid, "(RFC822)")
|
|
1624
1651
|
msg = email.message_from_bytes(data[0][1])
|
|
1625
1652
|
|
|
1626
|
-
|
|
1653
|
+
log.debug(f"From: {msg.get('From')}")
|
|
1654
|
+
log.debug(f"Subject: {msg.get('Subject')}")
|
|
1655
|
+
log.debug(f"Date: {msg.get('Date')}")
|
|
1656
|
+
|
|
1657
|
+
if not is_recent(msg, MAX_AGE_SECONDS):
|
|
1627
1658
|
continue
|
|
1628
1659
|
|
|
1629
1660
|
body = extract_clean_text(msg)
|
|
1630
|
-
match = re.search(r"OTP[^0-9]*(\d{6})", body, re.IGNORECASE)
|
|
1631
1661
|
|
|
1632
|
-
if
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1662
|
+
if not body.strip():
|
|
1663
|
+
log.warning("Email body extracted but empty")
|
|
1664
|
+
continue
|
|
1665
|
+
|
|
1666
|
+
log.debug("Running OTP regex scan")
|
|
1667
|
+
|
|
1668
|
+
match = re.search(r"\b(\d{4,8})\b", body)
|
|
1669
|
+
|
|
1670
|
+
if not match:
|
|
1671
|
+
log.warning("No OTP match found in email body")
|
|
1672
|
+
continue
|
|
1673
|
+
|
|
1674
|
+
otp = match.group(1)
|
|
1675
|
+
log.info(f"OTP extracted successfully: {otp}")
|
|
1676
|
+
|
|
1677
|
+
mail.store(eid, "+FLAGS", "\\Seen")
|
|
1678
|
+
mail.logout()
|
|
1679
|
+
return otp
|
|
1637
1680
|
|
|
1638
1681
|
log.info("Waiting for OTP...")
|
|
1639
1682
|
time.sleep(POLL_INTERVAL)
|
|
@@ -1643,10 +1686,11 @@ def poll_for_otp(EMAIL, APP_PASSWORD, SENDER, SUBJECT, POLL_INTERVAL, POLL_TIMEO
|
|
|
1643
1686
|
|
|
1644
1687
|
|
|
1645
1688
|
def get_gmail_otp(EMAIL, APP_PASSWORD, SENDER, SUBJECT,POLL_INTERVAL,POLL_TIMEOUT,MAX_AGE_SECONDS,MANUAL_INPUT_TIMEOUT):
|
|
1689
|
+
|
|
1646
1690
|
"""Get OTP automatically or fall back to manual input"""
|
|
1647
1691
|
try:
|
|
1648
1692
|
otp = poll_for_otp(EMAIL, APP_PASSWORD, SENDER, SUBJECT, POLL_INTERVAL, POLL_TIMEOUT, MAX_AGE_SECONDS)
|
|
1649
|
-
log.info("OTP received automatically:
|
|
1693
|
+
log.info(f"OTP received automatically: {otp}")
|
|
1650
1694
|
return otp
|
|
1651
1695
|
|
|
1652
1696
|
except TimeoutError:
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
optimuslib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
optimuslib/optimuslib.py,sha256=xsoQQgTyA5UhIfLS3mEyLKR4jzbhLriT-E-3yk7FcXg,66286
|
|
3
|
+
optimuslib-0.0.48.dist-info/METADATA,sha256=1VrHrlb6_vwzIFBuNiEXSIu1_isvo6Or6tzhtrtt_5A,723
|
|
4
|
+
optimuslib-0.0.48.dist-info/WHEEL,sha256=3ny-bZhpXrU6vSQ1UPG34FoxZBp3lVcvK0LkgUz6VLk,88
|
|
5
|
+
optimuslib-0.0.48.dist-info/RECORD,,
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
optimuslib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
optimuslib/optimuslib.py,sha256=G0k0nO98nOmoG2kVsroA_wDVf7eC-3bTIe8m9LT_Spc,64967
|
|
3
|
-
optimuslib-0.0.47.dist-info/METADATA,sha256=ruM-YP1zzXsL3awy5p4au78JdF0QF0CjM7_uoCyZZDo,723
|
|
4
|
-
optimuslib-0.0.47.dist-info/WHEEL,sha256=3ny-bZhpXrU6vSQ1UPG34FoxZBp3lVcvK0LkgUz6VLk,88
|
|
5
|
-
optimuslib-0.0.47.dist-info/RECORD,,
|
|
File without changes
|