detain / phlix-plugin-trakt
Trakt scrobbler plugin for Phlix — scrobbles playback to Trakt.tv and syncs watch history over the Trakt OAuth API.
Package info
github.com/detain/phlix-plugin-trakt
Type:phlix-plugin
pkg:composer/detain/phlix-plugin-trakt
Requires
- php: >=8.3
- detain/phlix-shared: ^0.52.0
- psr/container: ^1.1 || ^2.0
- psr/log: ^1.0 || ^2.0 || ^3.0
Requires (Dev)
- phpstan/phpstan: ^2.2
- phpunit/phpunit: ^10.0
- squizlabs/php_codesniffer: ^4.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-11-13 18:56:07 UTC
README
Trakt scrobbler plugin for Phlix — scrobbles playback to Trakt.tv and syncs your watch history over the Trakt OAuth API.
Overview
Connects a Phlix server to your Trakt.tv account:
- Scrobbles playback (start / pause / stop) so Trakt shows what you're watching in real time and marks items watched at the end.
- Two-way history sync — PULLS your Trakt watch history into Phlix on the
configurable
sync_interval_minutesinterval; PUSHES Phlix completions back to Trakt event-driven (on playback stop at ≥ 90%), never on the interval. The push fires from inside the playback-stop scrobble path, so it needs both toggles: withscrobble_enabledoff nothing is pushed even while pull sync stays on, and withsync_enabledoff neither pulls nor pushes happen while scrobbles alone still flow. The full gate matrix is in Settings. - Authenticates via OAuth, refreshing tokens automatically.
It subscribes to phlix.playback.started, phlix.playback.paused,
phlix.playback.resumed, and phlix.playback.stopped.
Install
From the Phlix admin Plugins section, paste this repo's URL:
https://github.com/detain/phlix-plugin-trakt
…or from the CLI:
php bin/phlix plugin:install https://github.com/detain/phlix-plugin-trakt
Set
redirect_uribefore you authorize. The redirect URI you register on your Trakt API app must match the one Phlix sends, byte for byte. If noredirect_uriis configured, the plugin falls back to the historic built-in defaulthttps://localhost/api/v1/oauth/trakt/callback(kept unchanged so existing registrations keep working) and logs a warning. That localhost value only suits a local install: for any other host, setTRAKT_REDIRECT_URI(or thetrakt.redirect_uriadmin setting) to your server's real callback URL and register the same URL with Trakt. See Settings below.
Settings
| Setting | Type | Required | Secret | Tier | Default | Description |
|---|---|---|---|---|---|---|
enabled |
boolean | No | No | standard |
false |
Turns Trakt scrobbling and sync on or off. Optional; default off. |
username |
string | No | No | advanced |
"" |
The Trakt account Phlix is linked to; set automatically during authorization. The plugin stays idle until it is present. |
access_token |
string | No | Yes | advanced |
null |
Obtained automatically when you authorize Phlix with Trakt — you do not enter this by hand. Create an API app only if you self-host credentials. |
refresh_token |
string | No | Yes | advanced |
null |
Set automatically during authorization; used to renew the access token. Not entered by hand. |
expires_at |
integer | No | Yes | advanced |
null |
Unix timestamp when the access token expires. Managed automatically. |
sync_enabled |
boolean | No | No | advanced |
true |
Pulls Trakt watch history into Phlix and lets finished Phlix items be pushed to Trakt. Pulled completions are sized from the Trakt entry runtime when present, else from the local media item duration. Pushing completions additionally requires scrobbling to be enabled. Optional; default on. |
sync_interval_minutes |
integer | No | No | advanced |
30 |
How often the scheduled Trakt→Phlix history pull runs. Optional; default 30. |
scrobble_enabled |
boolean | No | No | advanced |
true |
Report play/pause/stop to Trakt as you watch. Optional; default on. |
Which toggles gate which behavior (all three rows also require the master
enabled switch and completed authorization — tokens plus username, which
together are the plugin's configured state):
| Behavior | Needs scrobble_enabled |
Needs sync_enabled |
|---|---|---|
| Scrobble start / pause / resume / stop | Yes | — |
| Scheduled and on-demand history pull (Trakt→Phlix) | — | Yes |
| Completion push on stop at ≥ 90% (Phlix→Trakt) | Yes | Yes |
access_token/refresh_token/expires_atare set automatically via the Trakt authorization (OAuth) flow — you never enter them by hand. Create an API application at https://app.trakt.tv/settings/apps/api/new only if you self-host your own Trakt credentials.
Self-host credentials are configured host-side, not in this table. Your app's
client_id/client_secret/redirect_urilive on the Phlix server, which supplies them to the plugin at runtime: set theTRAKT_CLIENT_ID,TRAKT_CLIENT_SECRETandTRAKT_REDIRECT_URIenvironment variables, or enter them in the admin Settings page — thetrakt.client_id/trakt.client_secret/trakt.redirect_urikeys there take precedence. The server'sconfig/scrobblers/trakt.phpdocuments that layer, and itstoken_encryption_keyis what the plugin uses to encrypt OAuth tokens at rest.
Rate Limits and Scheduling
Trakt throttles by request rate and answers HTTP 429 with a
Retry-After hint. The plugin parses it and surfaces it as
TraktRateLimitException::$retryAfter, but deliberately does not
auto-retry: a scrobble
that hits 429 is logged and dropped, and the next history pull simply
waits for its regular tick — a blind deferred retry against a sustained
rate limiter would queue load instead of relieving it. For the same
reason the X-Ratelimit-* headers on successful (2xx) responses are not
carried: nothing in the plugin budgets against them today, so surfacing
them would be theater. retryAfter stays on the typed exception as an
informational value for hosts that choose to consume it.
History pulling also has two independent schedulers: the plugin's own
periodic timer (armed from sync_interval_minutes on enable) and the
host server's scheduled watch-history sweep. Nothing inside this plugin
coordinates the two, so a linked account can see pulls on both cadences;
deduplication is a host-layer responsibility, not something this
repository can promise.
Development
composer install vendor/bin/phpunit # default: deterministic Unit suite vendor/bin/phpunit --testsuite Network # live httpbin round trips, on demand vendor/bin/phpstan analyse src --level=9 # CI static-analysis gate vendor/bin/phpcs src --standard=PSR12 # CI coding-standards gate php scripts/security-audit-check.php # audit composer.lock (runtime + dev)
These deterministic gates run in CI on PHP 8.3, 8.4 and 8.5. The phpstan config
(phpstan.neon) carries ten broad ignoreErrors families — every
"unknown class"/"invalid type" shape, silenced because the host classes they
refer to are not available in plugin scope. The plugin's own typed code is
analysed at level 9 with no per-line suppressions.
The entry class is Phlix\Plugins\Scrobbler\Trakt\TraktPlugin (implements
Phlix\Shared\Plugin\LifecycleInterface and
Phlix\Shared\Plugin\ConfigurableInterface). It runs inside a Phlix server
host, which provides the playback/library services at runtime. Host-supplied
classes (Phlix\Auth\WatchHistory, Phlix\Common\Uuid,
Phlix\Media\Library\MediaItem, Phlix\Admin\SettingsRepository,
Workerman\MySQL\Connection, Workerman\Worker, Workerman\Http\Client,
Workerman\Http\Response, Workerman\Timer) are stubbed from
tests/bootstrap.php so the unit suite runs standalone. phpunit.xml sets the
default suite to Unit and excludes tests/Network/HttpClientNetworkTest.php,
which talks to a third-party service.
License
MIT — see LICENSE.