Skip to content

Fix provider search matching/ranking; add optional curd-web integration hooks#107

Open
ac1606 wants to merge 1 commit into
Wraient:mainfrom
ac1606:curd-web-provider-fixes
Open

Fix provider search matching/ranking; add optional curd-web integration hooks#107
ac1606 wants to merge 1 commit into
Wraient:mainfrom
ac1606:curd-web-provider-fixes

Conversation

@ac1606

@ac1606 ac1606 commented Jul 3, 2026

Copy link
Copy Markdown

Summary

A handful of real provider-matching/search bugs found while building a browser
front-end (curd-web) that drives curd through a fake-mpv IPC bridge instead of
real mpv — plus two small, additive, opt-in hooks that frontend uses. None of
this changes default/standalone behavior.

Bug fixes

  • Season/sequel scoring (internal/provider.go): substring containment
    between a season and its sequel ("Sword Art Online" vs
    "Sword Art Online II") was scored as a fuzzy-match bonus instead of
    recognized as a different season. isLikelyDifferentSeason() penalizes
    titles differing only by a trailing season/sequel token.
  • Search ranking on reordered queries (internal/anilist.go): re-ranking
    used raw Levenshtein distance against the full, unordered query, so a
    legitimately reordered query ("bunny girl senpai rascal does not dream")
    scored as a near-total mismatch. wordOverlapScore() (fraction of query
    words present, order-independent) is now the primary sort key, Levenshtein
    only a tiebreaker.
  • TTY race on rapid re-selection (internal/selection_menu.go):
    bubbletea's tea.NewProgram(...).Run() can transiently report "could not open a new TTY" immediately after a prior Program on the same pty just
    exited (real timing race, not frontend-specific). Retried up to 4x/75ms
    apart, scoped to that exact error string.
  • Stale saved provider ids trusted blindly (internal/provider.go,
    internal/provider_mapping.go): a previously-saved provider id was used
    without re-checking it still matches the current AniList media. Now
    re-verified via a fresh provider search (MAL id match, title/season checks,
    episode-count signal) before playback.
  • Provider stack default order (internal/provider_disabled.go):
    reordered to anineko -> anipub -> senshi. Senshi can return
    metadata-correct (right title/MAL id/episode) but wrong video content —
    verified live by sampling its HLS output with ffmpeg against the other
    providers for the same episode/anime.

Regression tests added for each; go test ./... and go vet ./... pass.

curd-web integration hooks (additive, opt-in, no effect on standalone use)

  • CurdWebModeEnabled() (internal/player.go): gated behind a CURD_WEB=1
    env var, exposes the active provider name over the existing mpv IPC channel
    (--curd-web-provider on spawn, user-data/curd-web-provider set_property
    on reuse) so a browser frontend can show which provider is serving the
    current stream. Real mpv silently accepts unknown user-data/* properties,
    and the CLI flag is never added unless CURD_WEB=1 is set, so this has no
    effect when curd drives real mpv directly.
  • SwitchToSingleProviderStream() + -remap-provider flag (cmd/curd/main.go,
    internal/provider_mapping.go): non-interactively resolves and returns
    (as JSON on stdout) a directly playable stream on one specific provider for
    one anime/episode, without walking the full provider stack. This exists
    because providerNamesForAnime deliberately always prefers config stack
    order over any saved provider (see
    TestProviderNamesForAnimePrefersStackBeforeSavedProvider) — so a frontend
    offering a manual "try a different source" action needs a way to get a
    specific provider's stream directly, since a persisted remap alone would
    otherwise get silently walked past by the stack on the very next episode
    load.

Test plan

  • go build ./...
  • go vet ./...
  • go test ./... (full suite, including new regression tests)
  • Live-verified against real AniList/provider network calls (SAO S1 vs
    SAO II matching, reordered-query search, provider stack order, and the
    new -remap-provider flag resolving a real stream)

Several real bugs found while building a browser front-end (curd-web)
that drives curd through a fake-mpv IPC bridge instead of real mpv:

- internal/provider.go: substring containment between a season and its
  sequel ("Sword Art Online" vs "Sword Art Online II") was scored as a
  fuzzy-match bonus instead of recognized as a different season.
  isLikelyDifferentSeason() penalizes titles differing only by a
  trailing season/sequel token.
- internal/anilist.go: search re-ranking used raw Levenshtein distance
  against the full, unordered query, penalizing legitimate word
  reordering ("bunny girl senpai rascal does not dream") as a near-total
  mismatch. wordOverlapScore() (fraction of query words present,
  order-independent) is now the primary sort key.
- internal/selection_menu.go: bubbletea's tea.NewProgram(...).Run() can
  transiently report "could not open a new TTY" right after a prior
  Program on the same pty just exited — a real timing race under
  non-standard terminal backends, not specific to any one frontend.
  Retried up to 4x/75ms apart, scoped to that exact error string.
- internal/provider.go, provider_mapping.go: a previously-saved
  provider id was trusted without re-verifying it still matches the
  current AniList media; now re-verified via a fresh provider search
  (MAL id match, title/season checks, episode-count signal) before
  playback.
- internal/provider_disabled.go: default provider stack reordered to
  anineko -> anipub -> senshi. Senshi can return metadata-correct
  (right title/MAL id/episode) but wrong video content — verified live
  by sampling its HLS output with ffmpeg against the other providers
  for the same episode.

Also adds two small, additive hooks used by curd-web (no effect on
normal/standalone use):
- CurdWebModeEnabled() (player.go): gated behind a CURD_WEB=1 env var,
  exposes the active provider name over the existing mpv IPC channel
  (--curd-web-provider on spawn, user-data/curd-web-provider
  set_property on reuse) so a browser frontend can show which provider
  is serving the current stream.
- SwitchToSingleProviderStream() + -remap-provider flag (main.go,
  provider_mapping.go): non-interactively resolves and returns a
  directly playable stream on one specific provider for one
  anime/episode, without walking the full provider stack. Exists
  because providerNamesForAnime deliberately always prefers config
  stack order over any saved provider (see
  TestProviderNamesForAnimePrefersStackBeforeSavedProvider) — so a
  frontend offering a manual "try a different source" action needs a
  way to get a specific provider's stream directly rather than relying
  on a persisted mapping that the stack order would otherwise walk
  straight past.

Full regression suite (go test ./...) and go vet ./... pass.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant