feat(mdblist): sync watched history, scrobbles and ratings with MDBList
Connects MDBList through its OAuth device-code grant, registered as a Device Code app so no client secret or redirect URI ships in the binary and TV, mobile and desktop all use the same flow. MDBList omits `verification_uri_complete`, but its device page seeds the code field from a `user_code` query parameter and the sign-in redirect preserves the query string, so the activation link is built locally and the dialog's open button lands on a filled-in form instead of an empty one. A server-supplied complete URL still wins if one ever appears. Poll state is read from the response body rather than the status code: `authorization_pending` and `slow_down` both arrive as HTTP 400, and a missing grant answers 404 `device_not_found`. Writes go out as real-time `/scrobble/*` reports plus `/sync/watched` for the marks that never pass through the player, with ratings on `/sync/ratings`. Matching uses IMDb and TMDb only — MDBList's id block has no `tvdb` field, so a TVDB-only item is skipped rather than written under an empty id block.
This commit is contained in:
@@ -6,6 +6,7 @@ import '../../models/catalog/catalog_item.dart';
|
||||
import '../../providers/trackers_provider.dart';
|
||||
import '../../services/trackers/anilist/anilist_tracker.dart';
|
||||
import '../../services/trackers/mal/mal_tracker.dart';
|
||||
import '../../services/trackers/mdblist/mdblist_tracker.dart';
|
||||
import '../../services/trackers/simkl/simkl_tracker.dart';
|
||||
import '../../services/trackers/tracker.dart';
|
||||
import '../../services/trackers/tracker_constants.dart';
|
||||
@@ -88,5 +89,11 @@ class TrackerServiceInfo {
|
||||
ratingSource: SimklTracker.instance,
|
||||
startConnection: startSimklConnection,
|
||||
),
|
||||
TrackerServiceInfo.shared(
|
||||
TrackerConfig.mdblist(),
|
||||
logoSource: CatalogSourceId.mdblist,
|
||||
ratingSource: MdblistTracker.instance,
|
||||
startConnection: startMdblistConnection,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import '../../models/trackers/device_code.dart';
|
||||
import '../../providers/trackers_provider.dart';
|
||||
import '../../services/trackers/anilist/anilist_tracker.dart';
|
||||
import '../../services/trackers/mal/mal_tracker.dart';
|
||||
import '../../services/trackers/mdblist/mdblist_tracker.dart';
|
||||
import '../../services/trackers/oauth_proxy_client.dart';
|
||||
import '../../services/trackers/simkl/simkl_tracker.dart';
|
||||
import '../../services/trackers/tracker_constants.dart';
|
||||
@@ -60,6 +61,20 @@ Future<void> startSimklConnection(BuildContext context) {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> startMdblistConnection(BuildContext context) {
|
||||
final account = context.read<TrackersProvider>();
|
||||
final name = t.services.names.mdblist;
|
||||
return launchTrackerConnect<DeviceCode>(
|
||||
context,
|
||||
isBusyOrConnected: account.isConnecting(TrackerService.mdblist) || account.isMdblistConnected,
|
||||
serviceName: name,
|
||||
connect: (cb) => account.connectMdblist(onCodeReady: cb),
|
||||
onCancel: account.cancelConnect,
|
||||
buildDialog: (p, cancel) => DeviceCodeDialog(code: p, serviceName: name, onCancel: cancel),
|
||||
urlFor: (p) => p.verificationUrlComplete ?? p.verificationUrl,
|
||||
);
|
||||
}
|
||||
|
||||
/// Per-service wiring for [TrackerSettingsScreen]. Keeps tracker-specific
|
||||
/// method names out of the shared screen body.
|
||||
class TrackerConfig {
|
||||
@@ -107,11 +122,20 @@ class TrackerConfig {
|
||||
onScrobbleChanged: SimklTracker.instance.setEnabled,
|
||||
disconnect: (a) => a.disconnectSimkl(),
|
||||
);
|
||||
|
||||
static TrackerConfig mdblist() => TrackerConfig(
|
||||
service: TrackerService.mdblist,
|
||||
displayName: t.services.names.mdblist,
|
||||
isConnected: (a) => a.isMdblistConnected,
|
||||
username: (a) => a.mdblistUsername,
|
||||
onScrobbleChanged: MdblistTracker.instance.setEnabled,
|
||||
disconnect: (a) => a.disconnectMdblist(),
|
||||
);
|
||||
}
|
||||
|
||||
/// Shared settings screen for MAL, AniList, and Simkl. Only reachable while
|
||||
/// connected — if the session drops (refresh failure, back-nav race) we pop
|
||||
/// back to the hub.
|
||||
/// Shared settings screen for MAL, AniList, Simkl and MDBList. Only reachable
|
||||
/// while connected — if the session drops (refresh failure, back-nav race) we
|
||||
/// pop back to the hub.
|
||||
class TrackerSettingsScreen extends StatelessWidget {
|
||||
final TrackerConfig config;
|
||||
const TrackerSettingsScreen({super.key, required this.config});
|
||||
|
||||
Reference in New Issue
Block a user