mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-07-16 13:08:02 +00:00
Implement email auto translate cache task
This commit is contained in:
+35
-2
@@ -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
@@ -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}")
|
||||
|
||||
Reference in New Issue
Block a user