README / Telegram_Job_Bot /imap_handler.py
nmnm969989's picture
Upload 550 files
ece3e8e verified
Raw History Blame Contribute Delete
8.79 kB
import imaplib
import email
from email.header import decode_header
import re
import time
def get_latest_otp(bot_email, bot_password, search_term, timeout=60):
"""
Connects to Gmail, waits for a new email containing the search_term (e.g. 'workday' or 'netflix'),
extracts a 6-digit OTP code or a verification link. It will leave the email in your inbox.
"""
print(f"[IMAP] Waiting up to {timeout} seconds for an email related to '{search_term}'...")
start_time = time.time()
while time.time() - start_time < timeout:
try:
mail = imaplib.IMAP4_SSL("imap.gmail.com")
mail.login(bot_email, bot_password)
mail.select("inbox")
# Search for ONLY UNSEEN emails since we are keeping them in the inbox now
status, messages = mail.search(None, "UNSEEN")
email_ids = messages[0].split()
if email_ids:
# Check from newest to oldest
for e_id in reversed(email_ids):
res, msg_data = mail.fetch(e_id, "(RFC822)")
for response_part in msg_data:
if isinstance(response_part, tuple):
msg = email.message_from_bytes(response_part[1])
# Decode subject
subject, encoding = decode_header(msg["Subject"])[0]
if isinstance(subject, bytes):
subject = subject.decode(encoding if encoding else "utf-8")
sender = msg.get("From")
# If this email is from the company we are waiting for
if search_term.lower() in subject.lower() or search_term.lower() in sender.lower():
print(f"[IMAP] Found email: {subject} from {sender}")
# Extract Body
body = ""
if msg.is_multipart():
for part in msg.walk():
content_type = part.get_content_type()
if content_type == "text/plain" or content_type == "text/html":
try:
body = part.get_payload(decode=True).decode()
except:
pass
else:
body = msg.get_payload(decode=True).decode()
# Clean HTML tags if present to make regex more accurate
# We can do a simple replace or just rely on regex, but removing tags helps.
clean_body = re.sub(r'<[^>]+>', ' ', body)
# 1. Look for a 4 to 8 digit OTP code (some companies use 4, 6, or 8 digits)
otp_match = re.search(r'\b\d{4,8}\b', clean_body)
if otp_match:
code = otp_match.group(0)
print(f"[IMAP] Extracted OTP Code: {code}")
# Mark as read instead of deleting (optional, but safe)
mail.store(e_id, '+FLAGS', '\\Seen')
mail.logout()
return {"type": "code", "value": code}
# 2. Look for a verification link
link_match = re.search(r'href=[\'"]?([^\'" >]+)', body)
if link_match:
link = link_match.group(1)
if "verify" in link.lower() or "confirm" in link.lower():
print(f"[IMAP] Extracted Verification Link: {link}")
mail.store(e_id, '+FLAGS', '\\Seen')
mail.logout()
return {"type": "link", "value": link}
print("[IMAP] Found email but no OTP or Link inside.")
mail.logout()
except Exception as e:
print(f"[IMAP] Error reading email: {e}")
# Wait 5 seconds before checking again
time.sleep(5)
print("[IMAP] Timeout reached. No email arrived.")
return None
def scan_for_interview_invites():
import os
from dotenv import load_dotenv
load_dotenv(override=True)
bot_email = os.getenv("BOT_EMAIL")
bot_password = os.getenv("BOT_PASSWORD")
if not bot_email or not bot_password:
raise Exception("BOT_EMAIL or BOT_PASSWORD not set in .env")
mail = imaplib.IMAP4_SSL("imap.gmail.com")
mail.login(bot_email, bot_password)
mail.select("inbox")
# Search for UNSEEN emails
status, messages = mail.search(None, "UNSEEN")
email_ids = messages[0].split()
results = []
if not email_ids:
mail.logout()
return results
for e_id in reversed(email_ids): # Process newest first
res, msg_data = mail.fetch(e_id, "(RFC822)")
for response_part in msg_data:
if isinstance(response_part, tuple):
msg = email.message_from_bytes(response_part[1])
subject, encoding = decode_header(msg["Subject"])[0]
if isinstance(subject, bytes):
subject = subject.decode(encoding if encoding else "utf-8")
sender = msg.get("From", "")
subj_lower = subject.lower()
# We are looking for interviews OR rejections
is_interview = any(k in subj_lower for k in ["interview", "next steps", "assessment", "calendly", "invitation"])
is_rejection = any(k in subj_lower for k in ["update on your application", "unfortunately", "not moving forward", "status of your application"])
if is_interview or is_rejection:
body = ""
if msg.is_multipart():
for part in msg.walk():
if part.get_content_type() == "text/plain":
try: body = part.get_payload(decode=True).decode()
except: pass
break
else:
try: body = msg.get_payload(decode=True).decode()
except: pass
if not body: continue
# Determine status
if is_interview: new_status = "Interview"
elif "unfortunately" in body.lower() or "not moving forward" in body.lower(): new_status = "Rejected"
else: continue # False positive
# Extract company name using Gemini
from main import get_gemini_client
client = get_gemini_client()
company_name = sender.split("@")[-1].split(".")[0].title() # fallback
if client:
try:
prompt = f"Extract the company name from this email. Reply ONLY with the company name, nothing else. Email From: {sender}\nSubject: {subject}\nBody: {body[:1000]}"
resp = client.models.generate_content(model="gemini-2.5-flash", contents=prompt)
company_name = resp.text.strip()
except: pass
results.append({"company": company_name, "status": new_status, "subject": subject})
# Update Notion CRM
from bot_features import sync_to_notion
sync_to_notion("", f"Update from email: {subject}", new_status, client, override_company=company_name)
mail.store(e_id, '+FLAGS', '\\Seen')
# Limit to 20 emails per scan
if len(results) >= 20: break
mail.logout()
return results