Commit Graph
23 Commits
Author SHA1 Message Date
agw ff76660910 fix(yt): én hentning ad gangen, nye forsøg ved 403, genoptag afbrudte
CD / deploy (push) Successful in 20s
CI / check (push) Successful in 30s
Tre fejl, der forstærkede hinanden:
- Hver tilføjelse startede sin egen kø-tømning, så flere hentninger kørte
  samtidig (og natlig timer kunne køre oveni). Mange samtidige hentninger fra
  samme IP er netop det YouTube svarer 403 på. Køen har nu en fil-lås
  (yt_koe.lock), så kun én kørsel henter, også på tværs af processer.
- yt-dlp gentager ikke selv en 403. Midlertidige fejl (403/429/timeout) prøves
  nu igen op til to gange med pause og en anden YouTube-klient
  (tv,web_safari og mweb). Permanente fejl prøves ikke igen.
- Et job der stod som "running", da portalen genstartede, blev hængende for
  altid. Med låsen ved vi, at et sådant job er afbrudt, og det lægges i kø
  igen ved næste kørsel.

Fejlede job har en "Prøv igen"-knap (og "Prøv alle fejlede igen"), og den
natlige kørsel opdaterer yt-dlp, før den henter.

Claude-Session: https://claude.ai/code/session_012LCTmU2ewo8tnM1dByYAoG
2026-10-06 13:54:07 +02:00
agw fbdb257079 feat(yt): slet-knap på hentede videoer
CD / deploy (push) Successful in 14s
CI / check (push) Successful in 23s
Hver række under "Hentede videoer" på /admin/yt har nu en Slet-knap (med
bekræftelse; nævner det, hvis videoen er favorit). Sletningen bruger samme
sikkerhedsnet som oprydningen: kun filer portalen selv har hentet, og
undertekster/miniature slettes med. Er det jobbets sidste fil, markeres jobbet
som slettet, og Emby-biblioteket opdateres. Video-id'et bliver i
abonnementets "seen", så en overvåget kanal ikke henter den igen.

Claude-Session: https://claude.ai/code/session_012LCTmU2ewo8tnM1dByYAoG
2026-10-06 13:38:56 +02:00
agw 265b48905a fix(yt): manglende import asyncio gav 500 på overvågning og tilføjelse
CI / check (push) Successful in 33s
CD / deploy (push) Successful in 49s
main.py brugte asyncio.create_task i YouTube-ruterne (tilføj video, tilføj
og kør overvågede kanaler), men importen på modulniveau forsvandt 2026-09-20
(9675a5b); kun to funktioner havde en lokal import. Siden da gav "tilføj
overvåget kanal" og "tilføj video" 500 (NameError).

CI kører nu pyflakes og fejler på udefinerede navne, så det fanges før drift.

Claude-Session: https://claude.ai/code/session_012LCTmU2ewo8tnM1dByYAoG
2026-10-06 13:34:32 +02:00
agw 902ab6244e fix(podcast): Messenger på iPhone og vandret scroll
CD / deploy (push) Successful in 16s
CI / check (push) Successful in 23s
Messenger på iPhone blokerer også x-safari-https://, så "Åbn i Safari" gjorde
ingenting. I stedet vises to veje, der virker: menuen ⋯ → "Åbn i ekstern
browser", eller "Kopiér adressen" og tilføj feedet i Apple Podcasts
(Bibliotek → ⋯ → Følg en serie via URL). Kopieringen falder tilbage til
execCommand/prompt, hvis clipboard-API'et er spærret. Android beholder
intent://-knappen.

Siden kunne skubbes til siden på iPhone. Afspiller og felter må nu aldrig
blive bredere end kortet, felter har 16 px skrift (ellers zoomer iOS ind),
lange ord brydes, og html/body skjuler vandret overløb.

Claude-Session: https://claude.ai/code/session_012LCTmU2ewo8tnM1dByYAoG
2026-10-06 08:03:19 +02:00
agw 6d32fd2b71 fix(podcast): lille omslag i link-forhåndsvisningen
CD / deploy (push) Successful in 15s
CI / check (push) Successful in 23s
Med det store omslag (3000 px) viste Messenger et kvadratisk billede i hele
beskedens bredde. Forhåndsvisningen bruger nu cover-lille.jpg (300 px), når
den findes, så den vises som en miniature ved teksten.

Claude-Session: https://claude.ai/code/session_012LCTmU2ewo8tnM1dByYAoG
2026-10-06 07:54:54 +02:00
agw 176859b15a fix(podcast): fælles link virker i Messenger
CD / deploy (push) Successful in 16s
CI / check (push) Successful in 23s
Messengers indbyggede browser kan ikke åbne podcast-apps (podcast://,
overcast:// osv.), så abonnér-knapperne gjorde ingenting. Siden genkender nu
Facebook-, Messenger- og Instagram-browseren og viser en knap, der åbner
siden i Safari (x-safari-https) eller på Android i den almindelige browser
(intent://), plus en vejledning til menuen. Afspilleren virker stadig direkte.

Link-forhåndsvisningen (facebookexternalhit m.fl.) får en minimal side med
titel, en neutral tekst og omslaget som og:-tags, aldrig episodelisten, og
den tælles ikke som en hentning. Den fulde side har de samme og:-tags.

Claude-Session: https://claude.ai/code/session_012LCTmU2ewo8tnM1dByYAoG
2026-10-06 07:52:11 +02:00
agw 70bc700a11 feat(podcast): også gruppen Gramps må lytte
CD / deploy (push) Successful in 16s
CI / check (push) Successful in 22s
Slægten uden for den nære familie inviteres i gruppen Gramps, der kun giver
Gramps, podcasten og portalen. PODCAST_GROUP er nu kommasepareret
(standard "Familie,Gramps"). Podcast-kortet på forsiden vises også, selvom
brugeren ingen andre apps har.

Invitationssiden forklarer de kendte grupper med en linje, så det er tydeligt
hvad Familie, Gramps og Crawlkit-team giver.

Claude-Session: https://claude.ai/code/session_012LCTmU2ewo8tnM1dByYAoG
2026-10-04 22:29:19 +02:00
agw 94e5a0ca01 feat(podcast): fælles familielink uden login
CD / deploy (push) Successful in 15s
CI / check (push) Successful in 23s
Et valgfrit fælles link, /podcast/f/<nøgle>/, til fx familiens Messenger-
gruppe: samme afspillerside som den personlige, med abonnér-knapper og feed
under samme sti, men uden login og uden admin-ting. Kladder vises aldrig der.
I /admin/podcast laves linket, nøglen kan skiftes (det gamle dør med det
samme), det kan slås fra, og hentningerne tælles.

Siden er noindex (header og meta) og sender ingen referrer. Forkerte nøgler
giver 404 ligesom ukendte stier, og en klient der rammer 20 forkerte nøgler
på ti minutter, får 404 på alt under /podcast/<nøgle> i et stykke tid.
Det gælder også de personlige nøgler.

Claude-Session: https://claude.ai/code/session_012LCTmU2ewo8tnM1dByYAoG
2026-10-04 20:11:28 +02:00
agw 4274047158 feat(podcast): familiens podcast med personlige feeds
CD / deploy (push) Successful in 16s
CI / check (push) Successful in 22s
/podcast viser familiens podcasts (Slægtens Stemmer) med afspiller og en
abonnér-knap til Apple Podcasts, Overcast, Pocket Casts og AntennaPod. Podcast-
apps kan ikke logge ind med Authentik, så hver lytter får sin egen hemmelige
nøgle i adressen (/podcast/<nøgle>/<show>.xml); mp3-filer og omslag ligger
under samme nøgle. En nøgle kan trækkes tilbage i /admin/podcast uden at de
andre mærker det, og admin ser hvem der henter hvad.

Episoder kan ligge som kladde: så ses de kun af administratorer, også i deres
eget feed, indtil de udgives fra admin-siden. Adgang: gruppen PODCAST_GROUP
(standard Familie) og admins. Forsiden får et kort, når der er episoder.

Data ligger i DATA/podcast/ og kommer aldrig i git. mutagen læser varigheden.

Claude-Session: https://claude.ai/code/session_012LCTmU2ewo8tnM1dByYAoG
2026-10-04 19:31:11 +02:00
agw 11399bb860 fix(auth): admin-sider sender til login i stedet for 403
CD / deploy (push) Successful in 19s
CI / check (push) Successful in 27s
Et link til fx /admin/claude-login (fra en ntfy) i en browser uden
portal-session gav "403 Kun for administratorer". Tre af dem på /admin fik
CrowdSec til at bandlyse IP'en som admin-probing, midt i et login.

Sidevisninger uden session sendes nu til /auth/login?next=<sti> og efter
login tilbage dertil; next accepteres kun som en sti på denne side.
Fetch- og POST-kald uden session får 401. Logget ind uden admin er
stadig 403. claude-login skriver nu brugernavnet i request-filen.

Claude-Session: https://claude.ai/code/session_012LCTmU2ewo8tnM1dByYAoG
2026-10-03 08:56:24 +02:00
agw 6f21c07d91 feat(admin): log Claude ind igen fra telefonen
Claude Codes login på værten holder 28 dage, og et langtidstoken kan ikke
bruges til Remote Control, så der skal logges ind igen hver 4. uge. Hidtil
krævede det en terminal for at kopiere en adresse ud og en kode ind igen.

/admin/claude-login viser hvornår login udløber og har en "Start login"-knap.
Værten (claude-login-agent, som spørger portalen, ikke omvendt) starter
`claude auth login`, og siden viser login-adressen; koden fra Claudes side
indsættes på samme side og hentes af værten et par sekunder senere. Portalen
gemmer kun den korte kode, indtil den er hentet; den virker kun sammen med
login-processen på værten (PKCE).

Claude-Session: https://claude.ai/code/session_012LCTmU2ewo8tnM1dByYAoG
2026-10-03 08:32:29 +02:00
agw fd50bed5fd feat(admin): hemmeligheds-drop til Claude
/admin/secrets tager imod et token eller kodeord og krypterer det med det
samme med PVE-værtens offentlige X25519-nøgle (sealed box: HKDF-SHA256 +
ChaCha20-Poly1305, navnet som associated data). Kun ciphertext gemmes;
portalen kan ikke læse det igen. På værten henter secret-drop get <navn>
det én gang og sletter kopien her.

Claude-Session: https://claude.ai/code/session_012LCTmU2ewo8tnM1dByYAoG
2026-09-30 21:59:59 +02:00
agw e05c376452 Revert "feat(mcp): hovedhusets egen gateway paa /hovedhus/mcp"
Hovedhuset administreres i stedet som en almindelig backend i /mcp
(registry + oauth_access pr. bruger i /admin/mcp-keys).

Claude-Session: https://claude.ai/code/session_012LCTmU2ewo8tnM1dByYAoG
2026-09-29 17:32:32 +02:00
agw 0635f4a76c feat(mcp): hovedhusets egen gateway paa /hovedhus/mcp
Selvstaendig under-app med egen Authentik-provider (hovedhus-mcp, gruppe
Hovedhus), egen OIDCProxy/noegle/lager og backends fra
data/hovedhus_gateway.json, aldrig fra mcp_registry.json. Ingen statiske
noegler; UserGate kraever navngiven bruger og fejler lukket.

Claude-Session: https://claude.ai/code/session_012LCTmU2ewo8tnM1dByYAoG
2026-09-29 17:27:26 +02:00
agw 52b1a2bb6c feat(admin): invitationslinks med gruppe og udløbstid
Udrullet direkte til prod 2026-09-29; importeret her så repo == prod.
Bruger Authentik-flowet weircon-invitation (grupper og engangs via
fixed_data og politikker i flowet).

Claude-Session: https://claude.ai/code/session_012LCTmU2ewo8tnM1dByYAoG
2026-09-29 17:13:43 +02:00
agw 9675a5b76b feat(mcp): eksponér YouTube-offline i portalens MCP + importér MCP-laget i repoet
Fem nye værktøjer i portal-navnerummet: yt_status, yt_download, yt_watch,
yt_unwatch, yt_check_subs. Gatewayen monterer mcp_server under namespace
"portal", så de arver portal_-præfikset og de scopes nøglerne allerede har —
ingen ekstra registrering.

Download og abonnement-tjek returnerer med det samme og arbejder videre i
baggrunden, så et MCP-kald ikke blokerer i minutter på en lang video.

Importerer samtidig mcp_server.py, mcp_gateway.py, mcpkeys.py og
templates/admin_mcp_keys.html, som kun har eksisteret i prod (CT151) og aldrig
i repoet.

ÅRSAG TIL IMPORTEN: den manglende viden i repoet kostede nedetid. Da jeg
udrullede repoets main.py og config.py til prod i går, overskrev jeg
MCP-monteringen (mcp_app.lifespan + route/middleware-overførsel), hele
/admin/mcp-keys-modulet og konstanterne MCP_REGISTRY/MCP_SELF_URL.
weircon.dk/mcp svarede 404 i ~13 timer. Rettet ved at genopbygge begge filer
fra prod-backuppen plus YouTube-ændringerne, ikke omvendt.

Repo og prod er nu identiske bortset fra static/portal.css og
templates/admin.html, hvor repoet er FORAN (wt-*-temaklasser, aldrig udrullet).

Claude-Session: https://claude.ai/code/session_011WxG8pbLj61jYU1MMaD7VC
2026-09-20 09:20:35 +02:00
agw 2c146b0711 feat(yt): overvåg playlister og kanaler, med cycle X på kanaler
Playliste-abonnement henter nye videoer efterhånden som de lægges i playlisten.
Kanal-abonnement holder en rullende buffer: cycle X = de X nyeste videoer ligger
klar, og kommer der en ny, ryger den ældste ud.

X tæller kun kanal-abonnementets egne hentninger. Manuelle hentninger,
playliste-abonnementer og favoritter tæller ikke med — og favoritter slettes
aldrig, så en favoritering frigør en plads i cyklussen til næste video.

Tre ting der måtte designes udenom:

1. `seen` husker hvert video-id abonnementet nogensinde har hentet, også efter
   videoen er slettet igen. Uden det ville set-reglen slette en video efter 7
   dage, hvorefter kanalen stadig har den blandt sine nyeste og henter den igen
   — i en uendelig løkke.
2. Playliste og kanal primer forskelligt ved oprettelse. En playliste kan have
   hundredvis af videoer, og ønsket var NYE videoer, så første tjek markerer det
   eksisterende som kendt uden at hente. En kanal henter derimod de X nyeste med
   det samme; det er hele pointen med cycle X. Resten af bagkataloget markeres
   som kendt, så den aldrig begynder at hente gammelt indhold.
3. enforce_cycles rører intet hvis Emby ikke svarer. Uden favorit-data kunne den
   komme til at slette noget fredet — samme konservative holdning som mediaclean.

Downloads gemmer nu video-id og upload-dato, så cyklussen kan sortere efter hvor
ny videoen ER, ikke hvornår vi tilfældigvis hentede den.

Timeren kører i rækkefølgen: tjek abonnementer -> hent -> håndhæv cyklus ->
slet sete. Cyklussen skal håndhæves efter hentningen, ellers ser den et halvt
bibliotek.

Claude-Session: https://claude.ai/code/session_011WxG8pbLj61jYU1MMaD7VC
2026-09-19 22:32:40 +02:00
agw fdf015946e feat(yt): YouTube offline — hent til Emby, slet igen når videoen er set
Nyt admin-modul /admin/yt: indsæt en YouTube-URL, og videoen hentes med yt-dlp
til Emby-biblioteket "YouTube" (/media/yt-offline) — klar til offline-brug.

Oprydningen er bevidst modsat mediaclean.py: dér tæller selv delvis afspilning
som "set" for at frede alt der er rørt, her slettes kun videoer der er eksplicit
markeret som set (Emby UserData.Played). Halvt sete videoer bliver liggende.
Favoritter slettes aldrig, og en favoritering i karensperioden freder igen.

Sikkerhedsnet: oprydningen rører kun filer portalen selv har hentet (registreret
med fuld sti i yt_state.json). Det eksisterende YT-bibliotek er urørligt, og
download-træet er lagt uden for det, så Emby ikke indekserer dobbelt.

Standardvalg:
- H.264/AAC foretrækkes frem for AV1. Pascal-GPU'en i CT100 kan ikke afkode AV1,
  og mange afspillere kan det slet ikke offline. Koster ~1,8x diskplads.
- SponsorBlock klipper sponsorater ud og beholder resten som kapitler.
- Undertekster hentes hvis de findes, men er aldrig et krav: YouTube rate-limiter
  undertekst-endpointet uafhængigt af videoen, så en 429 dér ville ellers vælte
  hele hentningen. Fald-tilbage henter videoen uden undertekster.
- Metadata, miniature og kapitler indlejres, så Emby har noget at vise.

Alt kan redigeres i UI'et. Natlig timer (yt-offline.timer) tømmer køen og rydder op.

Claude-Session: https://claude.ai/code/session_011WxG8pbLj61jYU1MMaD7VC
2026-09-19 19:47:48 +02:00
agw d9d5a12df1 reconcile: import current prod state from CT151 (media-cleanup, access admin, ops versioning, newer dashboard) 2026-06-15 12:27:24 +02:00
agwandClaude Opus 4.8 742d08c9fb refactor: split main.py i moduler (config/cache/store/services/authentik) + async dashboard (TTFB ~6ms, indhold via /dashboard-content) + TTL-cache på Authentik-læsninger
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 21:50:00 +02:00
agwandClaude Opus 4.8 7cf79fa901 admin: bruger- og gruppe-administration (opret bruger m. gruppe-adgang, medlemsstyring)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 21:20:00 +02:00
agwandClaude Opus 4.8 dfe500c178 portal: favoritter+visning, app-/cookie-/ops-admin, ⌘K-palette, ikoner, grupper
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 21:00:00 +02:00
agwandClaude Opus 4.8 74c8d9f500 weircon-front: SSO-portal til weircon.dk (Authentik-login, app-dashboard, admin, favoritter, tema, palette)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 20:00:00 +02:00