|
|
@@ -65,6 +65,7 @@ IMAP_HOST = "outlook.office365.com"
|
|
|
IMAP_PORT = 993
|
|
|
REQUEST_TIMEOUT_SECONDS = 45
|
|
|
FETCH_LIMIT_DEFAULT = 5
|
|
|
+FETCH_LIMIT_MAX = 120
|
|
|
BASE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
|
|
ACCOUNT_LOG_PATH = os.path.join(BASE_DIR, "data", "account-run-history.txt")
|
|
|
ACCOUNT_RECORDS_SNAPSHOT_PATH = os.path.join(BASE_DIR, "data", "account-run-history.json")
|
|
|
@@ -507,18 +508,61 @@ def normalize_message(message_id, raw_bytes, mailbox):
|
|
|
}
|
|
|
|
|
|
|
|
|
+def normalize_fetch_limit(top):
|
|
|
+ try:
|
|
|
+ numeric = int(top or FETCH_LIMIT_DEFAULT)
|
|
|
+ except Exception:
|
|
|
+ numeric = FETCH_LIMIT_DEFAULT
|
|
|
+ return max(1, min(numeric, FETCH_LIMIT_MAX))
|
|
|
+
|
|
|
+
|
|
|
+def search_imap_message_ids(client, target_email=""):
|
|
|
+ normalized_target = str(target_email or "").strip().lower()
|
|
|
+ if not normalized_target:
|
|
|
+ return []
|
|
|
+
|
|
|
+ matched_ids = []
|
|
|
+ seen_ids = set()
|
|
|
+ for header_name in ["To", "Delivered-To", "Envelope-To", "X-Original-To", "Cc"]:
|
|
|
+ try:
|
|
|
+ status, data = client.search(None, "HEADER", header_name, f'"{normalized_target}"')
|
|
|
+ except Exception:
|
|
|
+ continue
|
|
|
+ if status != "OK" or not data or not data[0]:
|
|
|
+ continue
|
|
|
+ for message_id in data[0].split():
|
|
|
+ if not message_id or message_id in seen_ids:
|
|
|
+ continue
|
|
|
+ seen_ids.add(message_id)
|
|
|
+ matched_ids.append(message_id)
|
|
|
+
|
|
|
+ matched_ids.sort(key=lambda item: int(item) if item.isdigit() else 0)
|
|
|
+ return matched_ids
|
|
|
+
|
|
|
+
|
|
|
+def load_selected_message_ids(client, top, target_email=""):
|
|
|
+ limit = normalize_fetch_limit(top)
|
|
|
+ target_ids = search_imap_message_ids(client, target_email)
|
|
|
+ if target_ids:
|
|
|
+ return list(reversed(target_ids[-limit:]))
|
|
|
+
|
|
|
+ status, data = client.search(None, "ALL")
|
|
|
+ if status != "OK" or not data or not data[0]:
|
|
|
+ return []
|
|
|
+ message_ids = data[0].split()
|
|
|
+ return list(reversed(message_ids[-limit:]))
|
|
|
+
|
|
|
+
|
|
|
def fetch_messages(email_addr, access_token, mailbox="INBOX", top=FETCH_LIMIT_DEFAULT):
|
|
|
client = None
|
|
|
logical_mailbox = normalize_mailbox_label(mailbox)
|
|
|
try:
|
|
|
client = open_mailbox(email_addr, access_token)
|
|
|
select_mailbox(client, mailbox)
|
|
|
- status, data = client.search(None, "ALL")
|
|
|
- if status != "OK" or not data or not data[0]:
|
|
|
+ selected_ids = load_selected_message_ids(client, top)
|
|
|
+ if not selected_ids:
|
|
|
return {"mailbox": logical_mailbox, "messages": [], "count": 0}
|
|
|
|
|
|
- message_ids = data[0].split()
|
|
|
- selected_ids = list(reversed(message_ids[-max(1, min(int(top or FETCH_LIMIT_DEFAULT), 30)):]))
|
|
|
messages = []
|
|
|
for message_id in selected_ids:
|
|
|
fetch_status, fetch_data = client.fetch(message_id, "(RFC822)")
|
|
|
@@ -552,18 +596,16 @@ def fetch_messages_for_mailboxes(email_addr, access_token, mailboxes, top):
|
|
|
return {"mailboxResults": mailbox_results, "messages": all_messages}
|
|
|
|
|
|
|
|
|
-def fetch_basic_imap_messages(host, port, username, password, mailbox="INBOX", top=FETCH_LIMIT_DEFAULT):
|
|
|
+def fetch_basic_imap_messages(host, port, username, password, mailbox="INBOX", top=FETCH_LIMIT_DEFAULT, target_email=""):
|
|
|
client = None
|
|
|
logical_mailbox = normalize_mailbox_label(mailbox)
|
|
|
try:
|
|
|
client = open_basic_imap_mailbox(host, port, username, password)
|
|
|
select_mailbox(client, mailbox)
|
|
|
- status, data = client.search(None, "ALL")
|
|
|
- if status != "OK" or not data or not data[0]:
|
|
|
+ selected_ids = load_selected_message_ids(client, top, target_email)
|
|
|
+ if not selected_ids:
|
|
|
return {"mailbox": logical_mailbox, "messages": [], "count": 0}
|
|
|
|
|
|
- message_ids = data[0].split()
|
|
|
- selected_ids = list(reversed(message_ids[-max(1, min(int(top or FETCH_LIMIT_DEFAULT), 30)):]))
|
|
|
messages = []
|
|
|
for message_id in selected_ids:
|
|
|
fetch_status, fetch_data = client.fetch(message_id, "(RFC822)")
|
|
|
@@ -586,11 +628,19 @@ def fetch_basic_imap_messages(host, port, username, password, mailbox="INBOX", t
|
|
|
pass
|
|
|
|
|
|
|
|
|
-def fetch_basic_imap_messages_for_mailboxes(host, port, username, password, mailboxes, top):
|
|
|
+def fetch_basic_imap_messages_for_mailboxes(host, port, username, password, mailboxes, top, target_email=""):
|
|
|
mailbox_results = []
|
|
|
all_messages = []
|
|
|
for mailbox in mailboxes or ["INBOX"]:
|
|
|
- result = fetch_basic_imap_messages(host, port, username, password, mailbox=mailbox, top=top)
|
|
|
+ result = fetch_basic_imap_messages(
|
|
|
+ host,
|
|
|
+ port,
|
|
|
+ username,
|
|
|
+ password,
|
|
|
+ mailbox=mailbox,
|
|
|
+ top=top,
|
|
|
+ target_email=target_email,
|
|
|
+ )
|
|
|
mailbox_results.append(result)
|
|
|
all_messages.extend(result["messages"])
|
|
|
all_messages.sort(key=lambda item: int(item.get("receivedTimestamp") or 0), reverse=True)
|
|
|
@@ -599,6 +649,7 @@ def fetch_basic_imap_messages_for_mailboxes(host, port, username, password, mail
|
|
|
|
|
|
def collect_basic_imap_messages(payload, mailboxes, top):
|
|
|
settings = resolve_basic_imap_settings(payload)
|
|
|
+ target_email = str(payload.get("targetEmail") or payload.get("email") or "").strip().lower()
|
|
|
result = fetch_basic_imap_messages_for_mailboxes(
|
|
|
settings["host"],
|
|
|
settings["port"],
|
|
|
@@ -606,6 +657,7 @@ def collect_basic_imap_messages(payload, mailboxes, top):
|
|
|
settings["password"],
|
|
|
mailboxes,
|
|
|
top,
|
|
|
+ target_email=target_email,
|
|
|
)
|
|
|
result["transport"] = "imap-basic"
|
|
|
result["settings"] = {
|