from __future__ import annotations import base64 from email.mime.text import MIMEText from google.auth.transport.requests import Request from google.oauth2.credentials import Credentials from googleapiclient.discovery import build from scripts.edoxid_calendar_config import GoogleConfig class GmailService: """Kirim email reminder H-1/1-jam sebagai tu@informatika.untan.ac.id langsung (scope gmail.send, akun OAuth yang sama dengan Calendar).""" def __init__(self, config: GoogleConfig): # Sengaja TIDAK set `scopes=` di sini: refresh_token sudah membawa scope # gabungan (calendar + gmail.send) dari OAuth consent aslinya. Meminta # scope yang lebih sempit secara eksplisit saat refresh terbukti bisa # ditolak Google dengan `invalid_scope` tergantung versi google-auth # yang jalan (dites 2026-07-27: lolos di lokal, gagal di container # Airflow karena google-auth versi lain via apache-airflow-providers-google). creds = Credentials( token=None, refresh_token=config.refresh_token, token_uri="https://oauth2.googleapis.com/token", client_id=config.client_id, client_secret=config.client_secret, ) creds.refresh(Request()) self._service = build("gmail", "v1", credentials=creds, cache_discovery=False) def send(self, to: list[str], subject: str, body_text: str) -> str: message = MIMEText(body_text, "plain", "utf-8") message["to"] = ", ".join(to) message["subject"] = subject raw = base64.urlsafe_b64encode(message.as_bytes()).decode("utf-8") result = self._service.users().messages().send(userId="me", body={"raw": raw}).execute() return result["id"]