j' dZddlmZddlZddlZddlZddlZddlmZmZddl m Z ddl Z dZ dZ dZd Zd Zdd Zdd Zdd ZddZ ddZ ddZddZdddZedk(rej4eyy)u gmail_check.py — checker MECCANICO "c'e' posta nuova rispetto all'ultimo controllo?" NESSUN LLM, nessun giudizio di importanza, nessuna classificazione. Puro confronto di timestamp via API Gmail REST. Pensato per essere invocato da un `servizio` (systemd su Nave, stesso pattern di servizio-inbox-watch / servizio-gate-watch) ogni ~20 minuti: SOLO se questo script stampa qualcosa il servizio fara' partire lo step successivo (giudizio LLM su sessione fresca) — che non e' parte di questo file. Standard di progetto (Dropbox/adrian/CLAUDE.md): Python 3.11, httpx come client HTTP, REST diretto senza librerie Google pesanti, PEP8 / max 100 char. ------------------------------------------------------------------------------------ USO python gmail_check.py --since 2026-09-09T08:00:00Z python gmail_check.py --since 2026-09-09T08:00:00Z --env /percorso/.env INPUT --since timestamp ISO 8601 (con 'Z' o offset). Solo i messaggi con data di ricezione STRETTAMENTE successiva a questo istante contano come "nuovi". --env percorso del file .env con le credenziali OAuth (default: .env accanto a questo script). Variabili richieste: GMAIL_CHECK_CLIENT_ID GMAIL_CHECK_CLIENT_SECRET GMAIL_CHECK_REFRESH_TOKEN Scope OAuth necessario: gmail.readonly (sola lettura). --query override della query server-side (default: "-category:promotions -category:social -category:forums newer_than:1d") --debug diagnostica su stderr (conteggi, tempi) — mai contenuti delle email. OUTPUT (stdout) - Se NON c'e' nulla di nuovo: nessun output, exit 0 (silenzioso, come slave_mailbox_read.sh — un poll che non deve fare rumore a vuoto). - Se c'e' qualcosa: un JSON array, un oggetto per messaggio (non per thread): [{"thread_id": "...", "message_id": "...", "from": "...", "subject": "...", "date_iso": "2026-09-09T08:12:34+00:00"}, ...] Solo header, nessuno snippet / corpo: la lettura piu' profonda, se servira', la fara' lo step LLM successivo, non questo script. EXIT CODE 0 esecuzione ok (sia "niente di nuovo" sia "trovati N messaggi") 2 errore d'uso (argomenti / .env mancante o incompleto) 3 errore API / rete / OAuth (il servizio chiamante deve distinguere "errore" da "niente di nuovo": qui l'output e' vuoto ma l'exit code NON e' 0) GOTCHA riusato dalla sentinella MCP precedente Un thread puo' contenere messaggi di date diverse. NON si deduplica per threadId e non ci si fida della data del thread: si filtra messaggio per messaggio confrontando `internalDate` (epoch ms, preciso) contro --since. ) annotationsN)datetimetimezone)Pathz#https://oauth2.googleapis.com/tokenz.https://gmail.googleapis.com/gmail/v1/users/mezD-category:promotions -category:social -category:forums newer_than:1dg>@)GMAIL_CHECK_CLIENT_IDGMAIL_CHECK_CLIENT_SECRETGMAIL_CHECK_REFRESH_TOKENcF|rtd|tjyy)Nz[gmail_check] file)printsysstderr)debugmsgs gmail_check.py_logrKs  se$3::6 c|jstd|i}|jdjD]}|j }|r|j dsd|vr+|j d\}}}|j }|j j dj d}|s|||<|S)zParser .env minimale (KEY=VALUE, righe # ignorate, quote opzionali). Nessuna dipendenza esterna: il formato qui e' banale e controllato da noi.z.env non trovato: zutf-8)encoding#="')is_fileFileNotFoundError read_text splitlinesstrip startswith partition)pathoutrawlinekey_vals rload_envr)Ps <<>"4TF ;<<C~~w~/::<yy{ts+s$ nnS) Qiikiik$**3/ CH Jrc|j}|jdr|dddz}tj|}|j |j t j}t|jdzS)z epoch millisecondi UTC. Accetta il suffisso 'Z'.ZNz+00:00)tzinfo) rendswithr fromisoformatr-replacerutcint timestamp)valuetextdts r parse_sincer8bsm ;;=D }}SCRy8#    %B yy ZZx||Z , r||~$ %%rc.|jt|d|d|ddd}|jdk7r(td|jd |jd d|j j d }|s td t|d |S)zNrefresh_token -> access_token (flusso REST standard Google, nessuna libreria).rrr refresh_token) client_id client_secretr: grant_type)datazrefresh token fallito: HTTP  N access_tokenz$risposta OAuth priva di access_tokenzaccess_token ottenuto)post TOKEN_URL status_code RuntimeErrorr6jsongetr)clientenvrresptokens rget_access_tokenrLms ;;45 !<= !<=)    D 3*4+;+;*s rlist_message_idsr_s'% 12G "C!J C0 ",F; zzZL 267zS   s ".t/?/?.@$))DS/ARS yy{ 488J+1r2XXo.    3s8*CDE Jrc dd|i}gd}|jtd|||}|jdk7r+td|d|jd |jd d|j S) zJMetadati leggeri di un messaggio: internalDate + header From/Subject/Date.rNrO))formatmetadata)metadataHeadersFrom)rcSubject)rcDatez /messages/rTr?z get message z fallito: HTTP r@N)rGrYrDrEr6rF)rHrK message_idrVrUrJs rfetch_metadatarhs'% 12GF :: ,j -fg  D 3:,od6F6F5GqSWTWHY Z   99;rc|jdijdgD]G}|jddj|jk(s5|jddcSy)NpayloadrVnamer5)rGlower)rjrkhs r header_valuerosa [[B ' + +Ir :& 55  " " $ 455"% %& rc tjd}|jddd|jdtt t j jdz d |jd td |jd dd|j|} t|j} t!t |j"}t&Dcgc]}|j)|r|}}|r.tddj+|tjyt-j.} t1j2t45} t7| ||j8} t;| | |j<|j8} g} | D]} | j)d}|st?| | |} tA|j)dd}||krFtEjF|dz tHjJjM}| jO|j)d| j)dd|tQ|dtQ|d |d! dddtW|j8tY d$tY d%t-j.|z d&d'| sy| j[d()tt]j^| d*+y#t$r(}td|tjYd}~yd}~wwxYw#t$$r.}tt|tjYd}~yd}~wwxYwcc}w#tBtf$rd}YwxYw#1swYxYw#t0jRtTf$r(}td"|tjYd}~y#d}~wwxYw),Nz>Checker meccanico: c'e' posta nuova dopo --since? (nessun LLM)) descriptionz--sinceTz-timestamp ISO 8601 (es. 2026-09-09T08:00:00Z))requiredhelpz--envz.envz+percorso file .env con le credenziali OAuth)defaultrsz--queryz override query Gmail server-sidez--debug store_truez%diagnostica su stderr (mai contenuti))actionrsz--since non valido: r z.env incompleto, mancano: z, )timeoutid internalDate0rr.)tzthreadIdrlrdre) thread_idrgfromsubjectdate_isozerrore API/rete/OAuth: z nuovi dopo --since su z candidati in z.1fsc |dS)Nr)ms rzmain..s Qz]r)r&F) ensure_ascii)0argparseArgumentParser add_argumentstrr__file__resolveparent DEFAULT_QUERY parse_argsr8since ValueErrorr rrr)rIr REQUIRED_ENVrGjointime monotonichttpxClient HTTP_TIMEOUTrLrr_r\rhr3 TypeErrorr fromtimestamprr2 isoformatappendro HTTPErrorrErr[sortrFdumps)argvparserargssince_msexcrIkmissingstartedrHrK candidatesfreshitemmsg_idmeta internal_msrs rmainrsU  $ $TF  D7fg DN**,33f<= :   =?ab  ,=de   T "Dtzz* tDHH~&'9Qcggajq9G9 *499W+=*>?cjjQnnG  \\, / 6$VS$**=E)&%TZZPJ*,E" $%feV<$"%dhh~s&C"DK(*#11$&8<<)+ %)XXj$((:r:R%S&, ,T6 :#/i#@$,    B  u:,-c*o->?nn( -Q 0  JJ*J+ $**U /0 {  $SE*<  c#hSZZ(:&":.$"#K$  : OO\ * 'u-CJJ?s.K4L(*M"M" N &A"M? M'$B M?.N 4 L%=L  L%( M1$MM'M<8M?;M<<M??N N O %OO __main__)rboolrrreturnNone)r"rrdict[str, str])r5rrr3)rH httpx.ClientrIrrrrr) rHrrKrr\rrrrzlist[dict[str, str]])rHrrKrrgrrdict)rjrrkrrr)N)rzlist[str] | Nonerr3)__doc__ __future__rrrFrrrrpathlibrrrCrYrrrrr)r8rLr_rhror__name__exitrrrrs0d# ' 1 = V   7 $&, !$-09=2 !$25 *M ` z CHHTVr