Release v0.2.34

This commit is contained in:
ponzischeme89
2026-08-09 08:25:50 +12:00
parent b1128bcce2
commit fdd9e6cab2
116 changed files with 11418 additions and 879 deletions
+30 -3
View File
@@ -29,6 +29,7 @@ import (
"github.com/ponzischeme89/memby/server/internal/foryou"
serverlogging "github.com/ponzischeme89/memby/server/internal/logging"
"github.com/ponzischeme89/memby/server/internal/mdblist"
"github.com/ponzischeme89/memby/server/internal/opensubtitles"
"github.com/ponzischeme89/memby/server/internal/radarr"
"github.com/ponzischeme89/memby/server/internal/recommend"
"github.com/ponzischeme89/memby/server/internal/sonarr"
@@ -50,9 +51,19 @@ type Server struct {
log *slog.Logger
events *serverlogging.Buffer
sonarrMu sync.Mutex
radarrMu sync.Mutex
bazarrMu sync.Mutex
mdblistMu sync.Mutex
// sonarrSeriesMu guards the catalogue cache separately from the calendar's, so an add
// to My Shows never waits behind a launcher rebuilding the schedule row.
sonarrSeriesMu sync.Mutex
radarrMu sync.Mutex
bazarrMu sync.Mutex
// openSubtitles is built from the operator's saved credentials rather than from
// configuration, so it is cached against a fingerprint of them and rebuilt when they
// change. It is cached at all because the client holds a login token, and logging in
// per download would spend a different allowance than the one being conserved.
openSubtitlesMu sync.Mutex
openSubtitles *opensubtitles.Client
openSubtitlesKey string
mdblistMu sync.Mutex
// mdblistSettingsCache spares every row and keystroke a settings read.
mdblistSettingsCache mdblistSettingsCache
// ratingsWarm fills and renews the durable rating cache behind the viewer, so a row
@@ -125,6 +136,9 @@ func (s *Server) Routes() http.Handler {
v1.Handle("GET /v1/home", s.authed(s.handleHome))
v1.Handle("GET /v1/screensaver", s.authed(s.handleScreensaver))
v1.Handle("GET /v1/search", s.authed(s.handleSearch))
// A genre is browsed, not searched: the chip is a filter and this is the route that
// treats it as one. Paged, because a household's Drama shelf is not a screenful.
v1.Handle("GET /v1/genres/{genre}/items", s.authed(s.handleGenreItems))
v1.Handle("GET /v1/search/history", s.authed(s.handleRecentSearches))
v1.Handle("POST /v1/search/history", s.authed(s.handleSearchHistory))
v1.Handle("GET /v1/requests/lookup", s.authed(s.handleRequestLookup))
@@ -145,6 +159,10 @@ func (s *Server) Routes() http.Handler {
v1.Handle("GET /v1/features", s.authed(s.handleFeatures))
// A viewer's settings follow the person, not the television. Both verbs land on one
// handler because a write answers with the stored document, not the submitted one.
// The palette, fetched only when the revision on the status poll moves. A GET with no
// write beside it: what a viewer may change is themeId, and that is an ordinary
// setting on the route above — this route only answers with what came of it.
v1.Handle("GET /v1/theme", s.authed(s.handleTheme))
v1.Handle("GET /v1/preferences", s.authed(s.handlePreferences))
v1.Handle("PUT /v1/preferences", s.authed(s.handlePreferences))
@@ -155,10 +173,19 @@ func (s *Server) Routes() http.Handler {
v1.Handle("GET /v1/items/{id}/related", s.authed(s.handleRelated))
v1.Handle("POST /v1/items/{id}/favorite", s.authed(s.handleFavorite))
v1.Handle("POST /v1/items/{id}/played", s.authed(s.handlePlayed))
v1.Handle("POST /v1/items/{id}/hide-from-resume", s.authed(s.handleHideFromResume))
v1.Handle("GET /v1/items/{id}/playback", s.authed(s.handlePlayback))
v1.Handle("GET /v1/items/{id}/next", s.authed(s.handleNextEpisode))
v1.Handle("GET /v1/items/{id}/subtitles/search", s.authed(s.handleSubtitleSearch))
v1.Handle("POST /v1/items/{id}/subtitles/download", s.authed(s.handleSubtitleDownload))
// Repairing the timing of a subtitle the title already has, which is a different
// question from fetching another copy of it — see subtitle_fix.go.
v1.Handle("POST /v1/items/{id}/subtitles/fix", s.authed(s.handleSubtitleFix))
// The one route that serves a subtitle rather than pointing at Emby's. It exists for
// the provider that hands back bytes instead of writing beside the media file; the
// token arrives in the query string, the way artwork's does, because a media player
// fetching a sidecar sends none of Memby's headers.
v1.Handle("GET /v1/subtitles/{file}", s.authed(s.handleStoredSubtitle))
v1.Handle("GET /v1/items/{id}/trailer", s.authed(s.handleTrailer))
v1.Handle("GET /v1/items/{id}/intro", s.authed(s.handleIntro))
v1.Handle("GET /v1/items/{id}/trickplay", s.authed(s.handleTrickplay))