watchlist

package
v0.3.8 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 11, 2026 License: GPL-3.0 Imports: 27 Imported by: 0

Documentation

Overview

Package watchlist ingests authorized viewers' watchlists and lazily checks their release availability.

Index

Constants

View Source
const Redacted = "••••"

Variables

This section is empty.

Functions

func ExportKeys added in v0.2.4

func ExportKeys(ctx context.Context, db *sql.DB, userID int64) ([]core.RatingKey, error)

ExportKeys reads the live watchlist, including manual and synced sources. Multiple sources for one title produce one exported entry.

func ImportTitleTx added in v0.2.4

func ImportTitleTx(ctx context.Context, tx *sql.Tx, userID int64, title core.Title, added int64) error

ImportTitleTx adds a reviewed title to the live viewer watchlist in the caller's transaction, so import bookkeeping and all list writes are atomic.

Types

type Item

type Item struct {
	Title       core.Title `json:"title"`
	Sources     []string   `json:"sources"`
	AddedAt     time.Time  `json:"addedAt"`
	CheckedAt   time.Time  `json:"checkedAt,omitempty"`
	AvailableAt time.Time  `json:"availableAt,omitempty"`
}

type Notifier

type Notifier interface {
	TitleAvailable(context.Context, string) error
}

type OnceNotifier added in v0.2.48

type OnceNotifier interface {
	TitleAvailableOnce(ctx context.Context, key, title string) error
}

OnceNotifier queues an alert at most once per key, however often it is called (notify.Service implements it). With it, an episode alert is queued exactly once even when a crash or error interrupts marking it sent. A plain Notifier is still supported but then at least once: such a retry repeats the alert, at most alertAttempts more times.

type Options

type Options struct {
	Logger           *slog.Logger
	Sealer           config.FieldSealer
	Catalog          core.Catalog
	Resolver         core.Resolver
	Engine           core.Engine
	Trakt            Source
	Tokens           func(context.Context, int64) (string, error)
	Users            func(context.Context) ([]core.User, error)
	Allowed          func(*core.User, *core.Title) bool
	Notifier         Notifier
	Client           *http.Client
	PlexBaseURL      string
	ClientIdentifier string
	Now              func() time.Time
	PrewarmBytes     int64
	// Cache records why an arrival was warmed when it supports provenance.
	Cache core.CacheProvenanceRecorder
	// SyncInterval is how often Run passes over every viewer (30m).
	SyncInterval time.Duration
	// ResumeSchedule makes Run wait until SyncInterval has passed since the
	// last pass recorded in the database (by any node) before its first
	// pass, instead of passing at once. A cluster sets it because Run
	// restarts on every leader handoff.
	ResumeSchedule bool
	// OnAvailable runs when a watchlist title first becomes playable.
	// The key is the movie, show, or newly released episode.
	OnAvailable func(context.Context, int64, core.RatingKey)
	// OnFriendsRSSChanged runs when the household "Friends' watchlists" set changes.
	OnFriendsRSSChanged func(context.Context)
}

type RSSFeedStatus

type RSSFeedStatus struct {
	Configured      bool   `json:"configured"`
	LastRead        int64  `json:"lastRead"`
	Imported        int    `json:"imported"`
	UnmatchedTitles int    `json:"unmatchedTitles"`
	Problem         string `json:"problem,omitempty"`
}

RSSFeedStatus reports the latest poll state of a configured Plex watchlist RSS feed.

type Service

type Service struct {
	// contains filtered or unexported fields
}

func New

func New(db *sql.DB, o Options) *Service

func (*Service) Check

func (s *Service) Check(ctx context.Context, u *core.User) error

Check resolves only explicitly watched-for, released titles, never the whole catalogue. A healthy resolver result is required before marking available.

A show (or season) becomes available through its first released episode. After that, each newer regular episode is resolved once it airs and, when a healthy release exists, announced and prewarmed once (see newEpisode).

func (*Service) CheckHousehold

func (s *Service) CheckHousehold(ctx context.Context) error

CheckHousehold checks availability and prefetches releases for the shared "Friends' watchlists" set within the same per-pass 20-title budget.

func (*Service) Contains added in v0.3.0

func (s *Service) Contains(ctx context.Context, userID int64, key core.RatingKey) (bool, error)

Contains reports membership from any watchlist source for one viewer.

func (*Service) ContainsMany added in v0.3.0

func (s *Service) ContainsMany(ctx context.Context, userID int64, keys []core.RatingKey) (map[core.RatingKey]bool, error)

ContainsMany reads membership for a page in one query per chunk.

func (*Service) Generation added in v0.3.0

func (s *Service) Generation() uint64

Generation changes when watchlist membership changes.

func (*Service) HouseholdItems

func (s *Service) HouseholdItems(ctx context.Context) ([]Item, error)

HouseholdItems returns released titles in the shared "Friends' watchlists" set (user_id=0) for availability monitoring and inspection.

func (*Service) Hubs

func (s *Service) Hubs(ctx context.Context, u *core.User, limit int) ([]core.Hub, error)

func (*Service) Items

func (s *Service) Items(ctx context.Context, u *core.User) ([]Item, error)

func (*Service) Membership added in v0.3.0

func (s *Service) Membership(ctx context.Context, userID int64, key core.RatingKey) (bool, *time.Time, error)

Membership reads one viewer/title pair across current watchlist sources. The latest stored source timestamp matches Items' existing ordering. It is an observation or import time, not the title's date added and not necessarily the upstream addition time. A zero timestamp is unknown rather than the Unix epoch.

func (*Service) MigrateSecrets

func (s *Service) MigrateSecrets(ctx context.Context) (int, error)

MigrateSecrets seals RSS URLs stored in plaintext; it returns 1 when it rewrote the settings row. Unreadable rows are left untouched.

func (*Service) Prune

func (s *Service) Prune(ctx context.Context) (int64, error)

Prune removes watchlist state that can never be used again and reports the rows removed:

  • handled episodes of shows no longer on the viewer's watchlist (left by a check that raced a removal, or recorded before removals cleared them);
  • episode alerts that were sent or gave up, once no viewer watchlists the episode's show or season. Only a watchlisted, already available show alerts, and only for its newest released episode not yet handled; a show added again records its newest episode without an alert, so the episode cannot be announced again. The alert's notify_once key goes with it.

Alerts with keys that do not encode a show are kept.

func (*Service) Remind

func (s *Service) Remind(ctx context.Context, u *core.User, key core.RatingKey) error

func (*Service) Remove

func (s *Service) Remove(ctx context.Context, userID int64, key core.RatingKey) error

func (*Service) Router

func (s *Service) Router() http.Handler

Router must be mounted behind Ether's owner authentication middleware.

func (*Service) Run

func (s *Service) Run(ctx context.Context)

Run is cancelled by the app; individual viewers are isolated and bounded. Run passes over every viewer (watchlist sync and availability check) plus the shared household Friends' watchlists set at once and then every SyncInterval; with ResumeSchedule the first pass waits out the interval since the last recorded pass.

func (*Service) SaveSettings

func (s *Service) SaveSettings(ctx context.Context, v Settings) error

func (*Service) Settings

func (s *Service) Settings(ctx context.Context) (Settings, error)

func (*Service) Sync

func (s *Service) Sync(ctx context.Context, u *core.User) error

Sync preserves the last successful snapshot when an upstream fails.

func (*Service) SyncRSS

func (s *Service) SyncRSS(ctx context.Context) error

SyncRSS polls configured Plex watchlist RSS feeds: the household "Friends' watchlists" set (unattributed) and the owner's "Your Watchlist" feed.

func (*Service) Watchlisted added in v0.3.0

func (s *Service) Watchlisted(ctx context.Context) (map[int64][]core.RatingKey, error)

Watchlisted returns every viewer's watchlisted titles by viewer id, released or not, from every source. A season counts as its show.

type Settings

type Settings struct {
	Enabled              bool           `json:"enabled"`
	Plex                 bool           `json:"plex"`
	PlexFriendsRSSURL    string         `json:"plexFriendsRssUrl"`
	PlexOwnerRSSURL      string         `json:"plexOwnerRssUrl"`
	PlexFriendsRSSStatus *RSSFeedStatus `json:"plexFriendsRssStatus,omitempty"`
	PlexOwnerRSSStatus   *RSSFeedStatus `json:"plexOwnerRssStatus,omitempty"`
	Trakt                bool           `json:"trakt"`
	Notify               bool           `json:"notify"`
	Prewarm              bool           `json:"prewarm"`
	CheckHours           int            `json:"checkHours"`
	NaturalSearch        bool           `json:"naturalSearch"`
	Recaps               bool           `json:"recaps"`
	SubtitleTranslate    bool           `json:"subtitleTranslate"`
	SpeechToText         bool           `json:"speechToText"`
	OverviewTranslate    bool           `json:"overviewTranslate"`
}

type Source

type Source interface {
	Watchlist(context.Context, int64, int) ([]core.Title, error)
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL