spotube/lib/provider/scrobbler/scrobbler.dart
2025-04-20 12:41:02 +06:00

133 lines
3.6 KiB
Dart

import 'dart:async';
import 'package:drift/drift.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:scrobblenaut/scrobblenaut.dart';
import 'package:spotify/spotify.dart';
import 'package:spotube/collections/env.dart';
import 'package:spotube/collections/vars.dart';
import 'package:spotube/extensions/artist_simple.dart';
import 'package:spotube/models/database/database.dart';
import 'package:spotube/services/logger/logger.dart';
class ScrobblerNotifier extends AsyncNotifier<Scrobblenaut?> {
final StreamController<Track> _scrobbleController =
StreamController<Track>.broadcast();
AppDatabase get db => getIt.get<AppDatabase>();
@override
build() async {
final loginInfo = await (db.select(db.scrobblerTable)
..where((t) => t.id.equals(0)))
.getSingleOrNull();
final subscription =
db.select(db.scrobblerTable).watch().listen((event) async {
try {
if (event.isNotEmpty) {
state = await AsyncValue.guard(
() async => Scrobblenaut(
lastFM: await LastFM.authenticateWithPasswordHash(
apiKey: Env.lastFmApiKey,
apiSecret: Env.lastFmApiSecret,
username: event.first.username,
passwordHash: event.first.passwordHash.value,
),
),
);
} else {
state = const AsyncValue.data(null);
}
} catch (e, stack) {
AppLogger.reportError(e, stack);
}
});
final scrobblerSubscription =
_scrobbleController.stream.listen((track) async {
try {
await state.asData?.value?.track.scrobble(
artist: track.artists!.first.name!,
track: track.name!,
album: track.album!.name!,
chosenByUser: true,
duration: track.duration,
timestamp: DateTime.now().toUtc(),
trackNumber: track.trackNumber,
);
} catch (e, stackTrace) {
AppLogger.reportError(e, stackTrace);
}
});
ref.onDispose(() {
subscription.cancel();
scrobblerSubscription.cancel();
});
if (loginInfo == null) {
return null;
}
return Scrobblenaut(
lastFM: await LastFM.authenticateWithPasswordHash(
apiKey: Env.lastFmApiKey,
apiSecret: Env.lastFmApiSecret,
username: loginInfo.username,
passwordHash: loginInfo.passwordHash.value,
),
);
}
Future<void> login(
String username,
String password,
) async {
final lastFm = await LastFM.authenticate(
apiKey: Env.lastFmApiKey,
apiSecret: Env.lastFmApiSecret,
username: username,
password: password,
);
if (!lastFm.isAuth) throw Exception("Invalid credentials");
await db.into(db.scrobblerTable).insert(
ScrobblerTableCompanion.insert(
id: const Value(0),
username: username,
passwordHash: DecryptedText(lastFm.passwordHash!),
),
);
}
Future<void> logout() async {
state = const AsyncValue.data(null);
await db.delete(db.scrobblerTable).go();
}
void scrobble(Track track) {
_scrobbleController.add(track);
}
Future<void> love(Track track) async {
await state.asData?.value?.track.love(
artist: track.artists!.asString(),
track: track.name!,
);
}
Future<void> unlove(Track track) async {
await state.asData?.value?.track.unLove(
artist: track.artists!.asString(),
track: track.name!,
);
}
}
final scrobblerProvider =
AsyncNotifierProvider<ScrobblerNotifier, Scrobblenaut?>(
() => ScrobblerNotifier(),
);