diff --git a/backend/api/cookies_controller.py b/backend/api/cookies_controller.py new file mode 100644 index 0000000..3d410a5 --- /dev/null +++ b/backend/api/cookies_controller.py @@ -0,0 +1,16 @@ +from pathlib import Path + +from fastapi import APIRouter, HTTPException, Request, status + +router = APIRouter() +COOKIES_PATH = Path("/app/cookies.txt") + + +@router.post("/cookies", status_code=status.HTTP_204_NO_CONTENT) +async def uploadCookies(request: Request): + body = (await request.body()).decode("utf-8", errors="replace") + if not body.startswith("# Netscape"): + raise HTTPException(status.HTTP_400_BAD_REQUEST, "Kein Netscape-Cookie-File") + tmp = COOKIES_PATH.with_suffix(".tmp") + tmp.write_text(body, encoding="utf-8") + tmp.replace(COOKIES_PATH) diff --git a/backend/base/app.py b/backend/base/app.py index e0f3c01..2717721 100644 --- a/backend/base/app.py +++ b/backend/base/app.py @@ -1,6 +1,7 @@ from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware +from api.cookies_controller import router as cookiesRouter from api.profile_controller import router as profilesRouter from api.video_controller import router as videosRouter from database.database import SessionLocal, createTables @@ -18,6 +19,7 @@ app.add_middleware( app.include_router(videosRouter) app.include_router(profilesRouter) +app.include_router(cookiesRouter) registerWebsocket(app) diff --git a/browser_extension/.amo-upload-uuid b/browser_extension/.amo-upload-uuid index cad6680..29ff78d 100644 --- a/browser_extension/.amo-upload-uuid +++ b/browser_extension/.amo-upload-uuid @@ -1 +1 @@ -{"uploadUuid":"50f3b9723bb7448c905059a445f98e2e","channel":"unlisted","xpiCrcHash":"7d68b51762b603ece4d52513d186f2d3e263430054421f4d0041b58f19ecabd0"} \ No newline at end of file +{"uploadUuid":"d90f6b10216044b49f278d2ac7e340cd","channel":"unlisted","xpiCrcHash":"7b6c7cd66d1833f7ddc9b05bf378315c44aedd55645515c77d4c4b3e41d450a1"} \ No newline at end of file diff --git a/browser_extension/api/background.js b/browser_extension/api/background.js index 33ace32..51baf91 100644 --- a/browser_extension/api/background.js +++ b/browser_extension/api/background.js @@ -1,9 +1,20 @@ const SERVER_BASE = "https://youtube.marha.de"; -browser.runtime.onMessage.addListener(({ profileId, video }) => { - fetch(`${SERVER_BASE}/profiles/${profileId}/videos`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(video), - }).catch(() => {}); +browser.runtime.onMessage.addListener((msg) => { + if (msg?.type === "sync-cookies") { + return syncCookies(); + } + if (msg?.profileId && msg?.video) { + fetch(`${SERVER_BASE}/profiles/${msg.profileId}/videos`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(msg.video), + }).catch(() => {}); + } +}); + +syncCookies(); +browser.alarms.create("cookieSync", { periodInMinutes: 1440 }); +browser.alarms.onAlarm.addListener((a) => { + if (a.name === "cookieSync") syncCookies(); }); diff --git a/browser_extension/api/syncCookies.js b/browser_extension/api/syncCookies.js new file mode 100644 index 0000000..b696c40 --- /dev/null +++ b/browser_extension/api/syncCookies.js @@ -0,0 +1,37 @@ +const COOKIE_SYNC_URL = "https://youtube.marha.de/cookies"; + +function toNetscape(cookies) { + const lines = ["# Netscape HTTP Cookie File"]; + for (const c of cookies) { + const domain = c.httpOnly ? `#HttpOnly_${c.domain}` : c.domain; + const includeSubdomain = c.domain.startsWith(".") ? "TRUE" : "FALSE"; + const secure = c.secure ? "TRUE" : "FALSE"; + const expiration = Math.floor(c.expirationDate || 0); + lines.push([domain, includeSubdomain, c.path, secure, expiration, c.name, c.value].join("\t")); + } + return lines.join("\n") + "\n"; +} + +async function syncCookies() { + const when = new Date().toISOString(); + try { + const cookies = await browser.cookies.getAll({ domain: ".youtube.com" }); + if (cookies.length === 0) { + await browser.storage.local.set({ lastCookieSync: { when, ok: false, error: "keine YouTube-Cookies gefunden" } }); + return; + } + const body = toNetscape(cookies); + const res = await fetch(COOKIE_SYNC_URL, { + method: "POST", + headers: { "Content-Type": "text/plain" }, + body, + }); + if (!res.ok) { + await browser.storage.local.set({ lastCookieSync: { when, ok: false, error: `HTTP ${res.status}` } }); + return; + } + await browser.storage.local.set({ lastCookieSync: { when, ok: true, count: cookies.length } }); + } catch (e) { + await browser.storage.local.set({ lastCookieSync: { when, ok: false, error: String(e) } }); + } +} diff --git a/browser_extension/config/popup.html b/browser_extension/config/popup.html index d737e4e..9bf2855 100644 --- a/browser_extension/config/popup.html +++ b/browser_extension/config/popup.html @@ -2,15 +2,27 @@

Profil

+ +
+

Cookie-Sync

+
noch nicht synchronisiert
+ +
+ diff --git a/browser_extension/config/popup.js b/browser_extension/config/popup.js index 08aefd4..ab3744b 100644 --- a/browser_extension/config/popup.js +++ b/browser_extension/config/popup.js @@ -1,7 +1,9 @@ const SERVER_URL = "https://youtube.marha.de/profiles"; const container = document.getElementById("profiles"); +const statusEl = document.getElementById("cookieStatus"); +const syncBtn = document.getElementById("syncBtn"); -async function load() { +async function loadProfiles() { try { const res = await fetch(SERVER_URL); const profiles = await res.json(); @@ -27,4 +29,41 @@ async function load() { } } -load(); +function formatAgo(iso) { + const diff = Date.now() - new Date(iso).getTime(); + const mins = Math.floor(diff / 60000); + if (mins < 1) return "gerade eben"; + if (mins < 60) return `vor ${mins} min`; + const hrs = Math.floor(mins / 60); + if (hrs < 24) return `vor ${hrs} h`; + const days = Math.floor(hrs / 24); + return `vor ${days} Tagen`; +} + +async function refreshStatus() { + const { lastCookieSync } = await browser.storage.local.get("lastCookieSync"); + if (!lastCookieSync) { + statusEl.className = "status"; + statusEl.textContent = "noch nicht synchronisiert"; + return; + } + if (lastCookieSync.ok) { + statusEl.className = "status ok"; + statusEl.textContent = `OK (${lastCookieSync.count} Cookies, ${formatAgo(lastCookieSync.when)})`; + } else { + statusEl.className = "status fail"; + statusEl.textContent = `Fehler: ${lastCookieSync.error}`; + } +} + +syncBtn.addEventListener("click", async () => { + syncBtn.disabled = true; + syncBtn.textContent = "Syncing..."; + await browser.runtime.sendMessage({ type: "sync-cookies" }); + await refreshStatus(); + syncBtn.disabled = false; + syncBtn.textContent = "Jetzt synchronisieren"; +}); + +loadProfiles(); +refreshStatus(); diff --git a/browser_extension/manifest.json b/browser_extension/manifest.json index a131a7a..9859fb9 100644 --- a/browser_extension/manifest.json +++ b/browser_extension/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 2, "name": "YouTube Video Erfasser", - "version": "1.0.0", + "version": "1.1.1", "description": "Erfasst YouTube-Videos und sendet sie an den Server", "browser_specific_settings": { "gecko": { @@ -12,9 +12,11 @@ } }, "permissions": [ - "*://www.youtube.com/*", + "*://*.youtube.com/*", "https://youtube.marha.de/*", - "storage" + "storage", + "cookies", + "alarms" ], "content_scripts": [ { @@ -23,7 +25,7 @@ } ], "background": { - "scripts": ["api/background.js"] + "scripts": ["api/syncCookies.js", "api/background.js"] }, "browser_action": { "default_popup": "config/popup.html",