# backend/app/connectors/mail/real_imap.py — IMAP(HEY/일반) sync 커넥터 (phase-13) # 로컬 우선 대안: OAuth 불필요. UID SEARCH SINCE 증분 → 동일 normalize_email 로 귀결. import email as email_lib import imaplib from datetime import UTC, datetime from sqlmodel import Session from ...crypto import decrypt_token # token_enc 에 {host,user,password} 저장 from ..base import BaseConnector, NormalizedRecord, RawRecord from .normalize import normalize_email, upsert_email class ImapConnector(BaseConnector): domain = "mail" entity_type = "email" def fetch(self, session: Session, *, full: bool = False): creds = decrypt_token(self.account.token_enc) if not creds: raise PermissionError("imap credentials missing") m = imaplib.IMAP4_SSL(creds["host"]) m.login(creds["user"], creds["password"]) m.select("INBOX") crit = "ALL" if full else "UNSEEN" _typ, data = m.search(None, crit) for uid in data[0].split(): _t, msgdata = m.fetch(uid, "(RFC822)") raw_bytes = msgdata[0][1] msg = email_lib.message_from_bytes(raw_bytes) payload = { "from": msg.get("From", ""), "subject": msg.get("Subject", ""), "to": msg.get("To", "나"), "preview": ( (msg.get_payload(decode=False) or "")[:120] if not msg.is_multipart() else "" ), "date": msg.get("Date", "방금"), } yield RawRecord( external_id=msg.get("Message-ID", uid.decode()), payload=payload, etag=msg.get("Date", ""), external_updated_at=datetime.now(UTC), ) m.logout() def normalize(self, raw: RawRecord) -> NormalizedRecord: return normalize_email(self.account, raw, provider="imap") def write(self, session: Session, norm: NormalizedRecord): return upsert_email(session, norm) def event_for(self, norm, entity_id): return "mail.received"