optimuslib 0.0.47__py3-none-any.whl → 0.0.49__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
|
@@ -99,33 +99,27 @@
|
|
|
99
99
|
import logging
|
|
100
100
|
|
|
101
101
|
# Creating logger
|
|
102
|
-
# logging.basicConfig(level=logging.DEBUG)
|
|
103
|
-
# logging.basicConfig(level=logging.DEBUG, filename='logs.txt', format='%(asctime)s - %(levelname)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
|
|
104
|
-
# logging.basicConfig(level=logging.DEBUG, filename='logs.txt', format='%(asctime)s — %(name)s — %(levelname)s — %(funcName)s:%(lineno)d — %(message)s', datefmt='%d-%b-%y %H:%M:%S')
|
|
105
102
|
log = logging.getLogger(__name__)
|
|
106
103
|
log.setLevel(logging.DEBUG)
|
|
107
|
-
# log.setLevel(logging.INFO)
|
|
108
104
|
|
|
109
|
-
#
|
|
105
|
+
# Handler - 1: File Handler
|
|
110
106
|
file = logging.FileHandler('optimuslibLogs.log', 'a', 'utf-8')
|
|
111
|
-
|
|
112
|
-
fileformat = logging.Formatter(fmt='%(asctime)s - %(levelname)s - %(filename)s - %(funcName)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
|
|
107
|
+
fileformat = logging.Formatter(fmt='%(asctime)s | %(levelname)s | %(filename)s | %(funcName)s | %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
|
|
113
108
|
file.setLevel(logging.DEBUG)
|
|
114
|
-
# file.setLevel(logging.INFO)
|
|
115
109
|
file.setFormatter(fileformat)
|
|
116
110
|
|
|
117
|
-
#
|
|
111
|
+
# Handler - 2: Stream Handler
|
|
118
112
|
stream = logging.StreamHandler()
|
|
119
|
-
# streamformat = logging.Formatter(fmt='%(asctime)s - %(levelname)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
|
|
120
|
-
streamformat = fileformat
|
|
121
113
|
stream.setLevel(logging.DEBUG)
|
|
122
|
-
|
|
123
|
-
stream.setFormatter(streamformat)
|
|
114
|
+
stream.setFormatter(fileformat)
|
|
124
115
|
|
|
125
|
-
#
|
|
116
|
+
# Adding handlers to the logger (not to root logger to avoid duplicates)
|
|
126
117
|
log.addHandler(file)
|
|
127
118
|
log.addHandler(stream)
|
|
128
119
|
|
|
120
|
+
# Prevent propagation to root logger to avoid duplicate logs
|
|
121
|
+
log.propagate = False
|
|
122
|
+
|
|
129
123
|
def loglib(data):
|
|
130
124
|
log.info(data)
|
|
131
125
|
log.info('%d %s','1', 'a')
|
|
@@ -1552,21 +1546,43 @@ from datetime import datetime, timezone
|
|
|
1552
1546
|
|
|
1553
1547
|
|
|
1554
1548
|
def extract_clean_text(msg):
|
|
1555
|
-
|
|
1556
|
-
|
|
1549
|
+
text_parts = []
|
|
1550
|
+
|
|
1557
1551
|
for part in msg.walk():
|
|
1558
|
-
|
|
1552
|
+
content_type = part.get_content_type()
|
|
1553
|
+
charset = part.get_content_charset()
|
|
1554
|
+
|
|
1555
|
+
log.debug(f"Found MIME part: {content_type}, charset={charset}")
|
|
1556
|
+
|
|
1557
|
+
if content_type in ("text/plain", "text/html"):
|
|
1559
1558
|
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
1559
|
|
|
1560
|
+
if not raw:
|
|
1561
|
+
log.debug("Empty payload")
|
|
1562
|
+
continue
|
|
1563
|
+
|
|
1564
|
+
try:
|
|
1565
|
+
decoded = raw.decode(charset or "utf-8", errors="ignore")
|
|
1566
|
+
except Exception:
|
|
1567
|
+
decoded = quopri.decodestring(raw).decode(errors="ignore")
|
|
1568
|
+
|
|
1569
|
+
if content_type == "text/html":
|
|
1570
|
+
soup = BeautifulSoup(decoded, "html.parser")
|
|
1571
|
+
decoded = soup.get_text(" ")
|
|
1565
1572
|
|
|
1566
|
-
|
|
1567
|
-
|
|
1573
|
+
text_parts.append(decoded)
|
|
1574
|
+
|
|
1575
|
+
full_text = " ".join(text_parts)
|
|
1576
|
+
log.debug(f"Extracted email text (first 300 chars): {full_text[:300]!r}")
|
|
1577
|
+
|
|
1578
|
+
return full_text
|
|
1579
|
+
|
|
1580
|
+
|
|
1581
|
+
|
|
1582
|
+
def is_recent(msg, max_age_seconds):
|
|
1568
1583
|
date_hdr = msg.get("Date")
|
|
1569
1584
|
if not date_hdr:
|
|
1585
|
+
log.debug("Email has no Date header")
|
|
1570
1586
|
return False
|
|
1571
1587
|
|
|
1572
1588
|
try:
|
|
@@ -1576,10 +1592,13 @@ def is_recent(msg):
|
|
|
1576
1592
|
|
|
1577
1593
|
now = datetime.now(timezone.utc)
|
|
1578
1594
|
age = (now - msg_time).total_seconds()
|
|
1579
|
-
return 0 <= age <= MAX_AGE_SECONDS
|
|
1580
|
-
except Exception:
|
|
1581
|
-
return False
|
|
1582
1595
|
|
|
1596
|
+
log.debug(f"Email age (seconds): {age}")
|
|
1597
|
+
return 0 <= age <= max_age_seconds
|
|
1598
|
+
|
|
1599
|
+
except Exception as e:
|
|
1600
|
+
log.exception("Failed to parse email date")
|
|
1601
|
+
return False
|
|
1583
1602
|
|
|
1584
1603
|
def input_with_timeout(prompt, timeout):
|
|
1585
1604
|
"""Get user input with timeout"""
|
|
@@ -1612,28 +1631,46 @@ def poll_for_otp(EMAIL, APP_PASSWORD, SENDER, SUBJECT, POLL_INTERVAL, POLL_TIMEO
|
|
|
1612
1631
|
# 🔑 Gmail IMAP requires re-select to refresh UNSEEN
|
|
1613
1632
|
mail.select("inbox", readonly=False)
|
|
1614
1633
|
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
)
|
|
1634
|
+
filter = f'(UNSEEN FROM "{SENDER}" SUBJECT "{SUBJECT}")'
|
|
1635
|
+
log.debug(f"IMAP search filter: {filter}")
|
|
1636
|
+
status, messages = mail.search(None,filter)
|
|
1619
1637
|
|
|
1620
1638
|
ids = messages[0].split()
|
|
1621
1639
|
|
|
1640
|
+
log.debug(f"IMAP search status: {status}")
|
|
1641
|
+
log.debug(f"Message IDs returned: {ids}")
|
|
1642
|
+
|
|
1622
1643
|
for eid in ids:
|
|
1623
1644
|
_, data = mail.fetch(eid, "(RFC822)")
|
|
1624
1645
|
msg = email.message_from_bytes(data[0][1])
|
|
1625
1646
|
|
|
1626
|
-
|
|
1647
|
+
log.debug(f"From: {msg.get('From')}")
|
|
1648
|
+
log.debug(f"Subject: {msg.get('Subject')}")
|
|
1649
|
+
log.debug(f"Date: {msg.get('Date')}")
|
|
1650
|
+
|
|
1651
|
+
if not is_recent(msg, MAX_AGE_SECONDS):
|
|
1627
1652
|
continue
|
|
1628
1653
|
|
|
1629
1654
|
body = extract_clean_text(msg)
|
|
1630
|
-
match = re.search(r"OTP[^0-9]*(\d{6})", body, re.IGNORECASE)
|
|
1631
1655
|
|
|
1632
|
-
if
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1656
|
+
if not body.strip():
|
|
1657
|
+
log.warning("Email body extracted but empty")
|
|
1658
|
+
continue
|
|
1659
|
+
|
|
1660
|
+
log.debug("Running OTP regex scan")
|
|
1661
|
+
|
|
1662
|
+
match = re.search(r"\b(\d{4,8})\b", body)
|
|
1663
|
+
|
|
1664
|
+
if not match:
|
|
1665
|
+
log.warning("No OTP match found in email body")
|
|
1666
|
+
continue
|
|
1667
|
+
|
|
1668
|
+
otp = match.group(1)
|
|
1669
|
+
log.info(f"OTP extracted successfully: {otp}")
|
|
1670
|
+
|
|
1671
|
+
mail.store(eid, "+FLAGS", "\\Seen")
|
|
1672
|
+
mail.logout()
|
|
1673
|
+
return otp
|
|
1637
1674
|
|
|
1638
1675
|
log.info("Waiting for OTP...")
|
|
1639
1676
|
time.sleep(POLL_INTERVAL)
|
|
@@ -1643,10 +1680,11 @@ def poll_for_otp(EMAIL, APP_PASSWORD, SENDER, SUBJECT, POLL_INTERVAL, POLL_TIMEO
|
|
|
1643
1680
|
|
|
1644
1681
|
|
|
1645
1682
|
def get_gmail_otp(EMAIL, APP_PASSWORD, SENDER, SUBJECT,POLL_INTERVAL,POLL_TIMEOUT,MAX_AGE_SECONDS,MANUAL_INPUT_TIMEOUT):
|
|
1683
|
+
|
|
1646
1684
|
"""Get OTP automatically or fall back to manual input"""
|
|
1647
1685
|
try:
|
|
1648
1686
|
otp = poll_for_otp(EMAIL, APP_PASSWORD, SENDER, SUBJECT, POLL_INTERVAL, POLL_TIMEOUT, MAX_AGE_SECONDS)
|
|
1649
|
-
log.info("OTP received automatically:
|
|
1687
|
+
log.info(f"OTP received automatically: {otp}")
|
|
1650
1688
|
return otp
|
|
1651
1689
|
|
|
1652
1690
|
except TimeoutError:
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
optimuslib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
optimuslib/optimuslib.py,sha256=nZPdZXFPB0uOWqp3xMx5e9d5Yrd3qdRkFWOm7WqCufo,65665
|
|
3
|
+
optimuslib-0.0.49.dist-info/METADATA,sha256=1T3JxGmn2y7HlZVlvhP4IWzOb9g9hZJEbRRWW0iSMd4,723
|
|
4
|
+
optimuslib-0.0.49.dist-info/WHEEL,sha256=3ny-bZhpXrU6vSQ1UPG34FoxZBp3lVcvK0LkgUz6VLk,88
|
|
5
|
+
optimuslib-0.0.49.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
|