Implement email auto translate cache task

This commit is contained in:
pewdiepie-archdaemon
2026-06-29 14:59:25 +00:00
parent 19e2326a6f
commit 46b127b1f3
3 changed files with 335 additions and 13 deletions
+35 -2
View File
@@ -424,12 +424,19 @@ SCHEDULED_DB = Path(SCHEDULED_EMAILS_DB)
OWNER_SCOPED_EMAIL_CACHE_TABLES = {
"email_summaries",
"email_ai_replies",
"email_translations",
"email_calendar_extractions",
"email_urgency_alerts",
"sender_signatures",
}
def email_translation_body_hash(body: str) -> str:
import hashlib as _hashlib
normalized = (body or "").strip()
return _hashlib.sha256(normalized.encode("utf-8", errors="ignore")).hexdigest()
def _email_cache_owner_clause(owner: str = "") -> tuple[str, tuple[str, ...]]:
owner = (owner or "").strip()
if owner:
@@ -437,8 +444,15 @@ def _email_cache_owner_clause(owner: str = "") -> tuple[str, tuple[str, ...]]:
return "(owner = '' OR owner IS NULL)", ()
def _ensure_owner_scoped_email_cache_table(conn, table: str, create_sql: str, columns: list[str]):
def _ensure_owner_scoped_email_cache_table(
conn,
table: str,
create_sql: str,
columns: list[str],
pk_columns: list[str] | None = None,
):
"""Rebuild legacy Message-ID-only cache tables with owner in the PK."""
desired_pk_cols = pk_columns or ["message_id", "owner"]
conn.execute(create_sql)
try:
info = conn.execute(f"PRAGMA table_info({table})").fetchall()
@@ -457,7 +471,7 @@ def _ensure_owner_scoped_email_cache_table(conn, table: str, create_sql: str, co
else:
conn.execute(f"ALTER TABLE {table} ADD COLUMN {col} TEXT")
cols.append(col)
if "owner" in cols and pk_cols == ["message_id", "owner"]:
if "owner" in cols and pk_cols == desired_pk_cols:
return
conn.execute(f"ALTER TABLE {table} RENAME TO {table}__old")
@@ -590,6 +604,25 @@ def _init_scheduled_db():
PRIMARY KEY (message_id, owner)
)
""", ["message_id", "owner", "uid", "folder", "reply", "model_used", "created_at"])
_ensure_owner_scoped_email_cache_table(conn, "email_translations", """
CREATE TABLE IF NOT EXISTS email_translations (
body_hash TEXT,
owner TEXT DEFAULT '',
target_language TEXT DEFAULT 'English',
uid TEXT,
folder TEXT,
subject TEXT,
sender TEXT,
translation TEXT,
same_language INTEGER DEFAULT 0,
model_used TEXT,
created_at TEXT NOT NULL,
PRIMARY KEY (body_hash, owner, target_language)
)
""", [
"body_hash", "owner", "target_language", "uid", "folder", "subject", "sender",
"translation", "same_language", "model_used", "created_at",
], ["body_hash", "owner", "target_language"])
# Email tags / spam classification cache. SECURITY: keyed by
# (message_id, owner) because Message-IDs are GLOBAL (a newsletter goes
# to many users with the same Message-ID). Without owner-scoping, a
+61 -1
View File
@@ -55,7 +55,7 @@ from routes.email_helpers import (
_friendly_email_auth_error,
SendEmailRequest, ExtractStyleRequest,
ATTACHMENTS_DIR, COMPOSE_UPLOADS_DIR, SCHEDULED_DB,
attachment_extract_dir, _email_cache_owner_clause,
attachment_extract_dir, _email_cache_owner_clause, email_translation_body_hash,
)
from routes.email_pollers import _start_poller
@@ -3971,6 +3971,36 @@ def setup_email_routes():
if not body:
return {"success": False, "error": "No body provided"}
body_hash = email_translation_body_hash(body)
try:
_c = _sql3.connect(SCHEDULED_DB)
owner_clause, owner_params = _email_cache_owner_clause(owner)
row = _c.execute(
f"SELECT translation, same_language, model_used FROM email_translations "
f"WHERE body_hash = ? AND target_language = ? AND {owner_clause}",
(body_hash, target_language, *owner_params),
).fetchone()
_c.close()
if row:
if int(row[1] or 0):
return {
"success": True,
"same_language": True,
"language": target_language,
"model_used": row[2] or "cached",
"cached": True,
}
if row[0]:
return {
"success": True,
"translation": row[0],
"language": target_language,
"model_used": row[2] or "cached",
"cached": True,
}
except Exception as e:
logger.warning(f"Failed to read email translation cache: {e}")
candidates = []
seen = set()
@@ -4028,6 +4058,21 @@ def setup_email_routes():
content = (content or "").strip()
content = _extract_reply(content)
if "<<<SAME_LANGUAGE>>>" in content:
try:
_c = _sql3.connect(SCHEDULED_DB)
_c.execute("""
INSERT OR REPLACE INTO email_translations
(body_hash, owner, target_language, uid, folder, subject, sender,
translation, same_language, model_used, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""", (
body_hash, owner, target_language, data.get("uid", ""), data.get("folder", ""),
subject, sender, "", 1, model, datetime.utcnow().isoformat(),
))
_c.commit()
_c.close()
except Exception as e:
logger.warning(f"Failed to cache same-language email translation: {e}")
return {"success": True, "same_language": True, "language": target_language, "model_used": model}
marker = re.search(r"<<<TRANSLATION>>>\s*(.*?)\s*<<<END>>>", content, re.S | re.I)
if marker:
@@ -4037,6 +4082,21 @@ def setup_email_routes():
content = re.sub(r"\s*<<<END>>>\s*$", "", content, flags=re.I).strip()
if not content:
return {"success": False, "error": "Empty response from model"}
try:
_c = _sql3.connect(SCHEDULED_DB)
_c.execute("""
INSERT OR REPLACE INTO email_translations
(body_hash, owner, target_language, uid, folder, subject, sender,
translation, same_language, model_used, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""", (
body_hash, owner, target_language, data.get("uid", ""), data.get("folder", ""),
subject, sender, content, 0, model, datetime.utcnow().isoformat(),
))
_c.commit()
_c.close()
except Exception as e:
logger.warning(f"Failed to cache email translation: {e}")
return {"success": True, "translation": content, "language": target_language, "model_used": model}
except Exception as e:
logger.error(f"Failed to translate email: {e}")