diff --git a/docker-compose.yml b/docker-compose.yml index 097d1b7..70585c8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,7 @@ services: mail_czsk: + stdin_open: true + tty: true image: t0is/vat-mail-loader:latest container_name: mail_czsk environment: @@ -15,6 +17,8 @@ services: - ./data/czsk:/data mail_rcw: + stdin_open: true + tty: true image: t0is/vat-mail-loader:latest container_name: mail_rcw environment: @@ -27,6 +31,8 @@ services: - ./data/rcw:/data mail_rcw_offers: + stdin_open: true + tty: true image: t0is/vat-mail-loader:latest container_name: mail_rcw_offers environment: diff --git a/main.py b/main.py index 1c0737d..90754bf 100644 --- a/main.py +++ b/main.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """ -download_emails.py +download_emails - Uses Gmail API to fetch all messages delivered to a single support address. - Pulls configured Gmail signatures (in any language) via Settings API. @@ -19,8 +19,10 @@ import re from datetime import datetime from collections import defaultdict import logging +import sys from google.oauth2.credentials import Credentials +from google.auth.transport.requests import Request from google_auth_oauthlib.flow import InstalledAppFlow from googleapiclient.discovery import build @@ -58,16 +60,32 @@ NAME_PATTERNS = { def get_gmail_service(token_path: str): """ - Load OAuth client credentials and per-account token. + Load OAuth credentials and handle refreshing. + Performs interactive auth only when no valid token/refresh available. """ creds = None + # Load existing tokens if os.path.exists(token_path): creds = Credentials.from_authorized_user_file(token_path, SCOPES) + # Refresh if expired + if creds and creds.expired and creds.refresh_token: + logger.info("Refreshing access token using refresh token...") + creds.refresh(Request()) + # If no valid credentials, do full auth flow if not creds or not creds.valid: flow = InstalledAppFlow.from_client_secrets_file('credentials.json', SCOPES) - creds = flow.run_local_server(port=0) - with open(token_path, 'w') as f: - f.write(creds.to_json()) + auth_url, _ = flow.authorization_url(access_type='offline', prompt='consent') + logger.warning("Please open this URL in your browser:\n%s", auth_url) + sys.stdout.write("Enter the authorization code here: ") + sys.stdout.flush() + code = sys.stdin.readline().strip() + flow.fetch_token(code=code) + creds = flow.credentials + # Save for next time + with open(token_path, 'w') as token_file: + token_file.write(creds.to_json()) + logger.info("Saved new token to %s", token_path) + # Build service return build('gmail', 'v1', credentials=creds) @@ -116,13 +134,11 @@ def extract_author(body: str, signatures: list) -> str: sig = s.get('signature') if sig and sig in body: return s['name'] - # 2) Manual name patterns for name, patterns in NAME_PATTERNS.items(): for pat in patterns: if pat in body: return name - # 3) Regex fallback match = re.search( r'(?im)(?:Podpis|S pozdravem|Díky|Thanks|Regards|Best regards|Sincerely)[\s,]*\r?\n([^\r\n]{2,})',