Skip to content

feat(yamtrack): deliver through the api when configured - #369

Draft
sean-wils wants to merge 6 commits into
lostb1t:mainfrom
sean-wils:feat/yamtrack-api
Draft

feat(yamtrack): deliver through the api when configured#369
sean-wils wants to merge 6 commits into
lostb1t:mainfrom
sean-wils:feat/yamtrack-api

Conversation

@sean-wils

@sean-wils sean-wils commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Stacked on #304, first commit is that branch.

POC; kinda going against what I said before with regards to a standard webhook. With the way Yamtrack handles IDs through the webhook I'm finding it INCREDIBLY difficult to achieve a 100% success rate for scrobbling episodes. Movies are fine.

Yamtrack is adding an API (FuzzyGrim/Yamtrack#1378 + FuzzyGrim/Yamtrack#1608) that addresses an episode as show + season/episode in TMDB terms, which remux stores for every episode. The webhook can only match an episode on its own tvdb/imdb id, which most episodes ingested from TMDB season listings don't have, and it returns 200 no matter what, so failures are invisible.

This adds a use_api option to the Yamtrack addon (default off, released Yamtrack has no API yet). With it on, delivery goes through the API and falls back to the webhook wherever the API can't act: no series tmdb id, season/episode numbers TMDB doesn't list (TVDB year-seasons etc), movies without a tmdb id. Failures land on the delivery row instead of being swallowed.

Tested end to end against a Yamtrack built from those two PRs: episode with no external ids tracked by coordinates, a TVDB year-season episode fell back to the webhook and matched, movie create/update, mark unplayed deletes, and an unidentifiable episode fails visibly instead of pretending to deliver.

Several trackers are fed through Jellyfin's webhook plugin and differ
only in the body they want and how they read a response.
`webhook_media_tracker` is the half they share: building the
notification, posting it, and classifying the reply into retryable,
reauth or permanent. A provider supplies a `WebhookFormat` and nothing
else.

Yamtrack is the first one on it. It keys an episode on the episode's
own ids, which is why `WebhookItem` carries both those and the series'.

A tracker may hold its credential in the url, as a path segment or a
query parameter, and a transport error prints the url it failed on.
That string becomes `last_error` and `GET /users/{id}/mediatrackers`
hands it back, so the url is dropped before the message is built.
TMDB's season listing carries no `external_ids`, so an episode reaches a
tracker with nothing of its own. Yamtrack matches an episode on its own
tvdb or imdb id, never the series', so it depends on
`complete_episode_ids` for every episode it can place.

Nothing in this file reaches that code, and a break there costs no test
and no error: the webhook still takes the delivery and Yamtrack drops
the episode on the floor.
Yamtrack looks an episode up by its own tvdb or imdb id and reads
nothing else. `is_matchable` passes an episode whose only ids are the
series', which this provider never consults, so one that resolved to
nothing was posted anyway, answered 200, and recorded as delivered.

Found on a season-0 special, where TMDB carries no external ids for the
episode and completion leaves it with none: the webhook went out with an
empty `ProviderIds` and the watch was lost with nothing to show for it.
30% of the episodes in the library it was found on carry no tmdb id of
their own, so they all rest on that lookup succeeding.
The guard refused an episode whose ids were empty, but Yamtrack reads an
episode's own tvdb or imdb id and never looks at tmdb on its TV path. An
episode carrying only a tmdb id has something for `provider_ids` to send
and still identifies nothing, so it passed the check, took a 200, and
was recorded as delivered.

That is the shape most of a real library is in: TMDB gives an episode
its own tmdb id and no `external_ids` at all, so the case the guard was
written for was the case it let through.
The shared half sat at `addons/webhook_media_tracker.rs`, next to the
addons rather than inside the capability it serves, which read as a claim
on webhooks generally. It is neither: no operator-configured URL, no body
a user can edit, just the payload dialect these trackers' receivers parse.
Moving it to `media_tracker/jellyfin_webhook_body.rs` puts it where its
only caller lives and names it after what it builds.

Yamtrack's API doc line goes with it: their write API is merged to a
feature branch, not to a release, so "no released write API" is the
accurate claim.
Yamtrack's unreleased api (feat/add-api plus its PR 1608) addresses an
episode as show plus season and episode number, which remux stores for
every episode, where the webhook needs the episode's own tvdb or imdb
id. A use_api addon option switches delivery over; the webhook stays
the default and the whole of what released Yamtrack accepts.

The api route hands back to the webhook wherever it cannot act: an
episode with no series tmdb id, coordinates TMDB no longer lists (a
fifth of a sampled library is numbered in another provider's scheme,
TVDB year seasons mostly), and a movie without a tmdb id. Between the
two routes, everything carrying any resolvable identity lands, and a
failure is recorded on the delivery rather than swallowed by the
webhook's unconditional 200.
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