Files
plezy/lib/services/trackers/mal/mal_tracker.dart
T

99 lines
3.1 KiB
Dart

import 'package:http/http.dart' as http;
import '../../../models/trackers/tracker_context.dart';
import '../../../utils/app_logger.dart';
import '../../settings_service.dart';
import '../tracker.dart';
import '../tracker_constants.dart';
import 'mal_client.dart';
import 'mal_session.dart';
/// MyAnimeList scrobble tracker. Marks `num_watched_episodes` on the user's
/// list entry once playback crosses the watched threshold.
///
/// MAL is anime-only: no-op when [TrackerContext.anime] is null.
///
/// For anime episodes, MAL receives watched progress in the mapped anime entry
/// when Fribb can define that scope, otherwise local episode progress.
class MalTracker extends TrackerBase {
static MalTracker? _instance;
static MalTracker get instance => _instance ??= MalTracker._();
MalTracker._();
@override
String get name => 'mal';
@override
TrackerService get service => TrackerService.mal;
@override
bool get needsFribb => true;
MalClient? _client;
final Map<int, Future<int?>> _episodeCountLoads = {};
@override
bool get hasActiveClient => _client != null;
@override
bool readEnabledSetting(SettingsService settings) => settings.read(SettingsService.enableMalScrobble);
void rebindSession(
MalSession? session, {
required void Function() onSessionInvalidated,
void Function(MalSession)? onSessionUpdated,
http.Client? httpClient,
}) {
_client?.dispose();
_episodeCountLoads.clear();
_client = session == null
? null
: MalClient(
session,
onSessionInvalidated: onSessionInvalidated,
onSessionUpdated: onSessionUpdated,
httpClient: httpClient,
);
}
@override
Future<void> markWatched(TrackerContext ctx) async {
final client = _client;
final malId = ctx.anime?.mal;
if (client == null || malId == null) return;
final Map<String, String> fields;
if (ctx.isMovie) {
fields = {'status': 'completed', 'num_watched_episodes': '1'};
} else {
final progress = ctx.animeProgress ?? ctx.episodeNumber;
if (progress == null || progress <= 0) return;
final total = ctx.animeProgress == null ? null : await _episodeCount(client, malId);
final watched = total != null && progress > total ? total : progress;
fields = {
'status': total != null && progress >= total ? 'completed' : 'watching',
'num_watched_episodes': '$watched',
};
}
await client.updateMyListStatus(malId, fields);
appLogger.d('MAL: updated list status (mal=$malId, fields=$fields)');
}
Future<int?> _episodeCount(MalClient client, int malId) {
final existing = _episodeCountLoads[malId];
if (existing != null) return existing;
late final Future<int?> loading;
loading = client.getAnimeEpisodeCount(malId).catchError((Object e) {
if (identical(_episodeCountLoads[malId], loading)) {
final _ = _episodeCountLoads.remove(malId);
}
appLogger.d('MAL: failed to fetch anime episode count (mal=$malId)', error: e);
return null;
});
_episodeCountLoads[malId] = loading;
return loading;
}
}