""" Platform Gateway — Kindle integration (send books via SMTP2GO). """ import json import base64 import urllib.request from pathlib import Path from config import ( BOOKLORE_URL, BOOKLORE_BOOKS_DIR, BOOKDROP_DIR, SMTP2GO_API_KEY, SMTP2GO_FROM_EMAIL, SMTP2GO_FROM_NAME, KINDLE_EMAIL_1, KINDLE_EMAIL_2, ) from proxy import proxy_request from integrations.booklore import booklore_auth def _find_book_file(book_id: str) -> tuple: """Find the actual ebook file for a Booklore book ID. Returns (file_path, book_metadata) or (None, None).""" token = booklore_auth() if not token: return None, None # Get book metadata from Booklore s, _, r = proxy_request( f"{BOOKLORE_URL}/api/v1/books/{book_id}", "GET", {"Authorization": f"Bearer {token}"}, timeout=10 ) if s != 200: return None, None book = json.loads(r) meta = book.get("metadata", {}) title = meta.get("title", "") if not title or not BOOKLORE_BOOKS_DIR.exists(): return None, meta # Search for the file in the library directory title_lower = title.lower() title_words = set(title_lower.split()[:4]) # First 4 words for matching best_match = None best_score = 0 for ext in ["epub", "pdf", "mobi", "azw3"]: for filepath in BOOKLORE_BOOKS_DIR.rglob(f"*.{ext}"): fname = filepath.stem.lower() # Check if title words appear in filename matches = sum(1 for w in title_words if w in fname) score = matches / len(title_words) if title_words else 0 # Prefer epub > pdf > mobi > azw3 ext_bonus = {"epub": 0.1, "pdf": 0.05, "mobi": 0.03, "azw3": 0.02}.get(ext, 0) score += ext_bonus if score > best_score: best_score = score best_match = filepath if best_match and best_score >= 0.5: return best_match, meta return None, meta def handle_send_to_kindle(handler, book_id: str, body: bytes): """Send a book file to a Kindle email via SMTP2GO API.""" if not SMTP2GO_API_KEY or not SMTP2GO_FROM_EMAIL: handler._send_json({"error": "Email not configured"}, 502) return try: data = json.loads(body) except Exception as e: handler._send_json({"error": "Invalid JSON"}, 400) return target = data.get("target", "1") kindle_email = KINDLE_EMAIL_1 if target == "1" else KINDLE_EMAIL_2 if not kindle_email: handler._send_json({"error": f"Kindle target {target} not configured"}, 400) return # Find the book file file_path, meta = _find_book_file(book_id) if not file_path or not file_path.exists(): handler._send_json({"error": "Book file not found on disk"}, 404) return title = meta.get("title", "Book") if meta else "Book" author = ", ".join(meta.get("authors", [])) if meta else "" # Read file and encode as base64 file_data = file_path.read_bytes() file_b64 = base64.b64encode(file_data).decode("ascii") filename = file_path.name # Determine MIME type ext = file_path.suffix.lower() mime_map = {".epub": "application/epub+zip", ".pdf": "application/pdf", ".mobi": "application/x-mobipocket-ebook", ".azw3": "application/x-mobi8-ebook"} mime_type = mime_map.get(ext, "application/octet-stream") # Send via SMTP2GO API email_payload = { "api_key": SMTP2GO_API_KEY, "sender": f"{SMTP2GO_FROM_NAME} <{SMTP2GO_FROM_EMAIL}>", "to": [kindle_email], "subject": f"{title}" + (f" - {author}" if author else ""), "text_body": f"Sent from Platform: {title}" + (f" by {author}" if author else ""), "attachments": [{ "filename": filename, "fileblob": file_b64, "mimetype": mime_type, }] } try: req_body = json.dumps(email_payload).encode("utf-8") req = urllib.request.Request( "https://api.smtp2go.com/v3/email/send", data=req_body, headers={"Content-Type": "application/json"}, ) with urllib.request.urlopen(req, timeout=30) as resp: result = json.loads(resp.read()) if result.get("data", {}).get("succeeded", 0) > 0: handler._send_json({ "success": True, "title": title, "sentTo": kindle_email, "format": ext.lstrip(".").upper(), "size": len(file_data), }) else: handler._send_json({"error": "Email send failed", "detail": result}, 500) except Exception as e: handler._send_json({"error": f"SMTP2GO error: {str(e)}"}, 500) def handle_send_file_to_kindle(handler, body: bytes): """Send a downloaded file to Kindle by filename from bookdrop directory.""" if not SMTP2GO_API_KEY or not SMTP2GO_FROM_EMAIL: handler._send_json({"error": "Email not configured"}, 502) return try: data = json.loads(body) except Exception as e: handler._send_json({"error": "Invalid JSON"}, 400) return filename = data.get("filename", "") target = data.get("target", "1") title = data.get("title", filename) kindle_email = KINDLE_EMAIL_1 if target == "1" else KINDLE_EMAIL_2 if not kindle_email: handler._send_json({"error": f"Kindle target {target} not configured"}, 400) return # Find file in bookdrop or booklore-books file_path = None for search_dir in [BOOKDROP_DIR, BOOKLORE_BOOKS_DIR]: if not search_dir.exists(): continue for fp in search_dir.rglob("*"): if fp.is_file() and fp.name == filename: file_path = fp break if file_path: break if not file_path or not file_path.exists(): handler._send_json({"error": f"File not found: {filename}"}, 404) return file_data = file_path.read_bytes() file_b64 = base64.b64encode(file_data).decode("ascii") ext = file_path.suffix.lower() mime_map = {".epub": "application/epub+zip", ".pdf": "application/pdf", ".mobi": "application/x-mobipocket-ebook", ".azw3": "application/x-mobi8-ebook"} mime_type = mime_map.get(ext, "application/octet-stream") email_payload = { "api_key": SMTP2GO_API_KEY, "sender": f"{SMTP2GO_FROM_NAME} <{SMTP2GO_FROM_EMAIL}>", "to": [kindle_email], "subject": title, "text_body": f"Sent from Platform: {title}", "attachments": [{"filename": filename, "fileblob": file_b64, "mimetype": mime_type}] } try: req_body = json.dumps(email_payload).encode("utf-8") req = urllib.request.Request("https://api.smtp2go.com/v3/email/send", data=req_body, headers={"Content-Type": "application/json"}) with urllib.request.urlopen(req, timeout=30) as resp: result = json.loads(resp.read()) if result.get("data", {}).get("succeeded", 0) > 0: handler._send_json({"success": True, "title": title, "sentTo": kindle_email, "format": ext.lstrip(".").upper()}) else: handler._send_json({"error": "Email send failed", "detail": result}, 500) except Exception as e: handler._send_json({"error": f"SMTP2GO error: {str(e)}"}, 500)