7a6dc7d9ae
Requesting a narrower scope explicitly (scopes=[...]) during refresh was rejected by Google as invalid_scope in the Airflow container's google-auth version, even though it worked locally with a newer one. Drop scopes= entirely — the refresh_token already carries the full granted scope (calendar + gmail.send), no need to request a subset.
43 lines
1.7 KiB
Python
43 lines
1.7 KiB
Python
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"]
|