diff --git a/lib/providers/auth.dart b/lib/providers/auth.dart index 7090895..3e30891 100644 --- a/lib/providers/auth.dart +++ b/lib/providers/auth.dart @@ -9,15 +9,23 @@ import 'package:rhythm_box/providers/database.dart'; import 'package:rhythm_box/services/database/database.dart'; extension ExpirationAuthenticationTableData on AuthenticationTableData { - bool get isExpired => DateTime.now().isAfter(expiration); + bool get isExpired => DateTime.now().isAfter(spotifyExpiration); - String? getCookie(String key) => cookie.value + String? getCookie(String key) => spotifyCookie.value .split('; ') .firstWhereOrNull((c) => c.trim().startsWith('$key=')) ?.trim() .split('=') .last .replaceAll(';', ''); + + String? getNeteaseCookie(String key) => neteaseCookie?.value + .split(';') + .firstWhereOrNull((c) => c.trim().startsWith('$key=')) + ?.trim() + .split('=') + .last + .replaceAll(';', ''); } class AuthenticationProvider extends GetxController { @@ -55,18 +63,18 @@ class AuthenticationProvider extends GetxController { void _setRefreshTimer() { refreshTimer?.cancel(); if (auth.value != null && auth.value!.isExpired) { - refreshCredentials(); + refreshSpotifyCredentials(); } refreshTimer = Timer( - auth.value!.expiration.difference(DateTime.now()), - () => refreshCredentials(), + auth.value!.spotifyExpiration.difference(DateTime.now()), + () => refreshSpotifyCredentials(), ); } - Future refreshCredentials() async { + Future refreshSpotifyCredentials() async { final database = Get.find().database; final refreshedCredentials = - await credentialsFromCookie(auth.value!.cookie.value); + await credentialsFromCookie(auth.value!.spotifyCookie.value); await database .update(database.authenticationTable) @@ -112,9 +120,10 @@ class AuthenticationProvider extends GetxController { return AuthenticationTableCompanion.insert( id: const Value(0), - cookie: DecryptedText("${res.headers["set-cookie"]?.join(";")}; $spDc"), - accessToken: DecryptedText(body['accessToken']), - expiration: DateTime.fromMillisecondsSinceEpoch( + spotifyCookie: + DecryptedText("${res.headers["set-cookie"]?.join(";")}; $spDc"), + spotifyAccessToken: DecryptedText(body['accessToken']), + spotifyExpiration: DateTime.fromMillisecondsSinceEpoch( body['accessTokenExpirationTimestampMs']), ); } catch (e) { @@ -123,6 +132,21 @@ class AuthenticationProvider extends GetxController { } } + Future setNeteaseCredentials(String cookie) async { + final database = Get.find().database; + await database.update(database.authenticationTable).replace( + AuthenticationTableCompanion.insert( + id: const Value(0), + spotifyCookie: auth.value!.spotifyCookie, + spotifyAccessToken: auth.value!.spotifyAccessToken, + spotifyExpiration: auth.value!.spotifyExpiration, + neteaseCookie: Value(DecryptedText(cookie)), + neteaseExpiration: const Value(null), + ), + ); + await loadAuthenticationData(); + } + Future logout() async { auth.value = null; final database = Get.find().database; @@ -132,6 +156,21 @@ class AuthenticationProvider extends GetxController { // Additional cleanup if necessary } + Future logoutNetease() async { + final database = Get.find().database; + await database.update(database.authenticationTable).replace( + AuthenticationTableCompanion.insert( + id: const Value(0), + spotifyCookie: auth.value!.spotifyCookie, + spotifyAccessToken: auth.value!.spotifyAccessToken, + spotifyExpiration: auth.value!.spotifyExpiration, + neteaseCookie: const Value(null), + neteaseExpiration: const Value(null), + ), + ); + await loadAuthenticationData(); + } + @override void onClose() { refreshTimer?.cancel(); diff --git a/lib/providers/spotify.dart b/lib/providers/spotify.dart index e4a5482..0f7e7c4 100644 --- a/lib/providers/spotify.dart +++ b/lib/providers/spotify.dart @@ -44,7 +44,7 @@ class SpotifyProvider extends GetxController { log('[SpotifyApi] Using user credentials...'); final AuthenticationProvider authenticate = Get.find(); return SpotifyApi.withAccessToken( - authenticate.auth.value!.accessToken.value); + authenticate.auth.value!.spotifyAccessToken.value); } @override diff --git a/lib/router.dart b/lib/router.dart index 30d7e2f..72bf94b 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:rhythm_box/screens/about.dart'; import 'package:rhythm_box/screens/album/view.dart'; +import 'package:rhythm_box/screens/auth/login_netease.dart'; import 'package:rhythm_box/screens/auth/mobile_login.dart'; import 'package:rhythm_box/screens/explore.dart'; import 'package:rhythm_box/screens/library/view.dart'; @@ -104,6 +105,11 @@ final router = GoRouter(routes: [ name: 'authMobileLogin', builder: (context, state) => const MobileLogin(), ), + GoRoute( + path: '/auth/netease/login', + name: 'authMobileLoginNetease', + builder: (context, state) => const LoginNeteaseScreen(), + ), ], ), ]); diff --git a/lib/screens/auth/login_netease.dart b/lib/screens/auth/login_netease.dart new file mode 100644 index 0000000..e45d543 --- /dev/null +++ b/lib/screens/auth/login_netease.dart @@ -0,0 +1,156 @@ +import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; +import 'package:get/get.dart'; +import 'package:go_router/go_router.dart'; +import 'package:rhythm_box/providers/auth.dart'; +import 'package:rhythm_box/providers/error_notifier.dart'; +import 'package:rhythm_box/services/sourced_track/sources/netease.dart'; +import 'package:rhythm_box/widgets/sized_container.dart'; + +class LoginNeteaseScreen extends StatefulWidget { + const LoginNeteaseScreen({super.key}); + + @override + State createState() => _LoginNeteaseScreenState(); +} + +class _LoginNeteaseScreenState extends State { + final TextEditingController _phoneController = TextEditingController(); + final TextEditingController _phoneRegionController = TextEditingController(); + final TextEditingController _passwordController = TextEditingController(); + + late final AuthenticationProvider _auth = Get.find(); + + bool _isLogging = false; + + Future _sentCaptcha() async { + setState(() => _isLogging = true); + + final phone = _phoneController.text; + var region = _phoneRegionController.text; + if (region.isEmpty) region = '86'; + final client = NeteaseSourcedTrack.getClient(); + final resp = await client.get( + '/captcha/sent?phone=$phone&ctcode=$region×tamp=${DateTime.now().millisecondsSinceEpoch}', + ); + + if (resp.statusCode != 200 || resp.body?['code'] != 200) { + Get.find().showError( + resp.bodyString ?? resp.status.toString(), + ); + } + + setState(() => _isLogging = false); + } + + Future _performLogin() async { + setState(() => _isLogging = true); + + final phone = _phoneController.text; + final password = _passwordController.text; + var region = _phoneRegionController.text; + if (region.isEmpty) region = '86'; + final client = NeteaseSourcedTrack.getClient(); + final resp = await client.get( + '/login/cellphone?phone=$phone&captcha=$password&countrycode=$region×tamp=${DateTime.now().millisecondsSinceEpoch}', + ); + + if (resp.statusCode != 200 || resp.body?['code'] != 200) { + Get.find().showError( + resp.bodyString ?? resp.status.toString(), + ); + setState(() => _isLogging = false); + return; + } + + await _auth.setNeteaseCredentials(resp.body['cookie']); + + setState(() => _isLogging = false); + + GoRouter.of(context).goNamed('settings'); + } + + @override + void dispose() { + _phoneController.dispose(); + _phoneRegionController.dispose(); + _passwordController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Connect with Netease Cloud Music'), + ), + body: CenteredContainer( + maxWidth: 320, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + children: [ + SizedBox( + width: 64, + child: TextField( + controller: _phoneRegionController, + decoration: const InputDecoration( + border: OutlineInputBorder(), + hintText: '86', + isDense: true, + ), + onTapOutside: (_) => + FocusManager.instance.primaryFocus?.unfocus(), + ), + ), + const Gap(8), + Expanded( + child: TextField( + controller: _phoneController, + decoration: const InputDecoration( + border: OutlineInputBorder(), + label: Text('Phone Number'), + isDense: true, + ), + onTapOutside: (_) => + FocusManager.instance.primaryFocus?.unfocus(), + ), + ), + ], + ), + const Gap(8), + Row( + children: [ + Expanded( + child: TextField( + controller: _passwordController, + obscureText: true, + decoration: const InputDecoration( + border: OutlineInputBorder(), + label: Text('Captcha Code'), + isDense: true, + ), + onTapOutside: (_) => + FocusManager.instance.primaryFocus?.unfocus(), + ), + ), + const Gap(8), + IconButton( + onPressed: _isLogging ? null : () => _sentCaptcha(), + icon: const Icon(Icons.sms), + tooltip: 'Get Captcha', + ), + ], + ), + const Gap(8), + TextButton( + onPressed: _isLogging ? null : () => _performLogin(), + child: const Text('Login'), + ) + ], + ), + ), + ); + } +} diff --git a/lib/screens/explore.dart b/lib/screens/explore.dart index 36b596f..3b4ddb3 100644 --- a/lib/screens/explore.dart +++ b/lib/screens/explore.dart @@ -72,8 +72,8 @@ class _ExploreScreenState extends State { } if (_auth.auth.value != null) { - final customEndpoint = - CustomSpotifyEndpoints(_auth.auth.value?.accessToken.value ?? ''); + final customEndpoint = CustomSpotifyEndpoints( + _auth.auth.value?.spotifyAccessToken.value ?? ''); final forYouView = await customEndpoint.getView( 'made-for-x-hub', market: market, diff --git a/lib/screens/settings.dart b/lib/screens/settings.dart index 909a282..e9155cb 100644 --- a/lib/screens/settings.dart +++ b/lib/screens/settings.dart @@ -7,6 +7,7 @@ import 'package:rhythm_box/providers/spotify.dart'; import 'package:rhythm_box/providers/user_preferences.dart'; import 'package:rhythm_box/screens/auth/login.dart'; import 'package:rhythm_box/services/database/database.dart'; +import 'package:rhythm_box/services/sourced_track/sources/netease.dart'; import 'package:rhythm_box/widgets/auto_cache_image.dart'; import 'package:rhythm_box/widgets/sized_container.dart'; @@ -86,6 +87,75 @@ class _SettingsScreenState extends State { }, ); }), + Obx(() { + if (_authenticate.auth.value == null) { + return const SizedBox(); + } + if (_authenticate.auth.value?.neteaseCookie == null) { + return ListTile( + contentPadding: const EdgeInsets.symmetric(horizontal: 24), + leading: const Icon(Icons.cloud_outlined), + title: const Text('Connect with Netease Cloud Music'), + subtitle: const Text( + 'Make us able to play more music from Netease Cloud Music'), + trailing: const Icon(Icons.chevron_right), + enabled: !_isLoggingIn, + onTap: () async { + setState(() => _isLoggingIn = true); + await GoRouter.of(context) + .pushNamed('authMobileLoginNetease'); + setState(() => _isLoggingIn = false); + }, + ); + } + + return FutureBuilder( + future: NeteaseSourcedTrack.getClient().get('/user/account'), + builder: (context, snapshot) { + if (!snapshot.hasData) { + return ListTile( + contentPadding: + const EdgeInsets.symmetric(horizontal: 24), + leading: const SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + strokeWidth: 3, + ), + ), + title: const Text('Loading...'), + trailing: IconButton( + onPressed: () { + _authenticate.logoutNetease(); + }, + icon: const Icon(Icons.link_off), + ), + ); + } + + return ListTile( + leading: + snapshot.data!.body['profile']['avatarUrl'] != null + ? CircleAvatar( + backgroundImage: AutoCacheImage.provider( + snapshot.data!.body['profile']['avatarUrl'], + ), + ) + : const Icon(Icons.account_circle), + title: Text(snapshot.data!.body['profile']['nickname']), + subtitle: const Text( + 'Connected with your Netease Cloud Music account', + ), + trailing: IconButton( + onPressed: () { + _authenticate.logoutNetease(); + }, + icon: const Icon(Icons.link_off), + ), + ); + }, + ); + }), Obx(() { if (_authenticate.auth.value == null) { return const SizedBox(); @@ -95,7 +165,7 @@ class _SettingsScreenState extends State { contentPadding: const EdgeInsets.symmetric(horizontal: 24), leading: const Icon(Icons.logout), title: const Text('Log out'), - subtitle: const Text('Disconnect with this Spotify account'), + subtitle: const Text('Disconnect with every account'), trailing: const Icon(Icons.chevron_right), onTap: () async { _authenticate.logout(); diff --git a/lib/services/database/database.dart b/lib/services/database/database.dart index d997835..a0b0832 100755 --- a/lib/services/database/database.dart +++ b/lib/services/database/database.dart @@ -55,7 +55,7 @@ class AppDatabase extends _$AppDatabase { AppDatabase() : super(_openConnection()); @override - int get schemaVersion => 3; + int get schemaVersion => 1; @override MigrationStrategy get migration { @@ -63,18 +63,7 @@ class AppDatabase extends _$AppDatabase { onCreate: (Migrator m) async { await m.createAll(); }, - onUpgrade: (Migrator m, int from, int to) async { - if (from < 2) { - await m.addColumn(preferencesTable, preferencesTable.playerWakelock); - } - if (from > 3) { - await m.addColumn( - preferencesTable, preferencesTable.neteaseApiInstance); - await m.dropColumn( - preferencesTable, preferencesTable.audioSource.name); - await m.addColumn(preferencesTable, preferencesTable.audioSource); - } - }, + onUpgrade: (Migrator m, int from, int to) async {}, ); } } diff --git a/lib/services/database/database.g.dart b/lib/services/database/database.g.dart index 4146ad3..f31d6d9 100644 --- a/lib/services/database/database.g.dart +++ b/lib/services/database/database.g.dart @@ -18,29 +18,54 @@ class $AuthenticationTableTable extends AuthenticationTable requiredDuringInsert: false, defaultConstraints: GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); - static const VerificationMeta _cookieMeta = const VerificationMeta('cookie'); - @override - late final GeneratedColumnWithTypeConverter cookie = - GeneratedColumn('cookie', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true) - .withConverter( - $AuthenticationTableTable.$convertercookie); - static const VerificationMeta _accessTokenMeta = - const VerificationMeta('accessToken'); + static const VerificationMeta _spotifyCookieMeta = + const VerificationMeta('spotifyCookie'); @override late final GeneratedColumnWithTypeConverter - accessToken = GeneratedColumn('access_token', aliasedName, false, + spotifyCookie = GeneratedColumn( + 'spotify_cookie', aliasedName, false, type: DriftSqlType.string, requiredDuringInsert: true) .withConverter( - $AuthenticationTableTable.$converteraccessToken); - static const VerificationMeta _expirationMeta = - const VerificationMeta('expiration'); + $AuthenticationTableTable.$converterspotifyCookie); + static const VerificationMeta _spotifyAccessTokenMeta = + const VerificationMeta('spotifyAccessToken'); @override - late final GeneratedColumn expiration = GeneratedColumn( - 'expiration', aliasedName, false, - type: DriftSqlType.dateTime, requiredDuringInsert: true); + late final GeneratedColumnWithTypeConverter + spotifyAccessToken = GeneratedColumn( + 'spotify_access_token', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true) + .withConverter( + $AuthenticationTableTable.$converterspotifyAccessToken); + static const VerificationMeta _spotifyExpirationMeta = + const VerificationMeta('spotifyExpiration'); @override - List get $columns => [id, cookie, accessToken, expiration]; + late final GeneratedColumn spotifyExpiration = + GeneratedColumn('spotify_expiration', aliasedName, false, + type: DriftSqlType.dateTime, requiredDuringInsert: true); + static const VerificationMeta _neteaseCookieMeta = + const VerificationMeta('neteaseCookie'); + @override + late final GeneratedColumnWithTypeConverter + neteaseCookie = GeneratedColumn( + 'netease_cookie', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false) + .withConverter( + $AuthenticationTableTable.$converterneteaseCookien); + static const VerificationMeta _neteaseExpirationMeta = + const VerificationMeta('neteaseExpiration'); + @override + late final GeneratedColumn neteaseExpiration = + GeneratedColumn('netease_expiration', aliasedName, true, + type: DriftSqlType.dateTime, requiredDuringInsert: false); + @override + List get $columns => [ + id, + spotifyCookie, + spotifyAccessToken, + spotifyExpiration, + neteaseCookie, + neteaseExpiration + ]; @override String get aliasedName => _alias ?? actualTableName; @override @@ -55,15 +80,22 @@ class $AuthenticationTableTable extends AuthenticationTable if (data.containsKey('id')) { context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); } - context.handle(_cookieMeta, const VerificationResult.success()); - context.handle(_accessTokenMeta, const VerificationResult.success()); - if (data.containsKey('expiration')) { + context.handle(_spotifyCookieMeta, const VerificationResult.success()); + context.handle(_spotifyAccessTokenMeta, const VerificationResult.success()); + if (data.containsKey('spotify_expiration')) { context.handle( - _expirationMeta, - expiration.isAcceptableOrUnknown( - data['expiration']!, _expirationMeta)); + _spotifyExpirationMeta, + spotifyExpiration.isAcceptableOrUnknown( + data['spotify_expiration']!, _spotifyExpirationMeta)); } else if (isInserting) { - context.missing(_expirationMeta); + context.missing(_spotifyExpirationMeta); + } + context.handle(_neteaseCookieMeta, const VerificationResult.success()); + if (data.containsKey('netease_expiration')) { + context.handle( + _neteaseExpirationMeta, + neteaseExpiration.isAcceptableOrUnknown( + data['netease_expiration']!, _neteaseExpirationMeta)); } return context; } @@ -77,14 +109,19 @@ class $AuthenticationTableTable extends AuthenticationTable return AuthenticationTableData( id: attachedDatabase.typeMapping .read(DriftSqlType.int, data['${effectivePrefix}id'])!, - cookie: $AuthenticationTableTable.$convertercookie.fromSql( - attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}cookie'])!), - accessToken: $AuthenticationTableTable.$converteraccessToken.fromSql( + spotifyCookie: $AuthenticationTableTable.$converterspotifyCookie.fromSql( attachedDatabase.typeMapping.read( - DriftSqlType.string, data['${effectivePrefix}access_token'])!), - expiration: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}expiration'])!, + DriftSqlType.string, data['${effectivePrefix}spotify_cookie'])!), + spotifyAccessToken: $AuthenticationTableTable.$converterspotifyAccessToken + .fromSql(attachedDatabase.typeMapping.read(DriftSqlType.string, + data['${effectivePrefix}spotify_access_token'])!), + spotifyExpiration: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, data['${effectivePrefix}spotify_expiration'])!, + neteaseCookie: $AuthenticationTableTable.$converterneteaseCookien.fromSql( + attachedDatabase.typeMapping.read( + DriftSqlType.string, data['${effectivePrefix}netease_cookie'])), + neteaseExpiration: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, data['${effectivePrefix}netease_expiration']), ); } @@ -93,45 +130,69 @@ class $AuthenticationTableTable extends AuthenticationTable return $AuthenticationTableTable(attachedDatabase, alias); } - static TypeConverter $convertercookie = + static TypeConverter $converterspotifyCookie = EncryptedTextConverter(); - static TypeConverter $converteraccessToken = + static TypeConverter $converterspotifyAccessToken = EncryptedTextConverter(); + static TypeConverter $converterneteaseCookie = + EncryptedTextConverter(); + static TypeConverter $converterneteaseCookien = + NullAwareTypeConverter.wrap($converterneteaseCookie); } class AuthenticationTableData extends DataClass implements Insertable { final int id; - final DecryptedText cookie; - final DecryptedText accessToken; - final DateTime expiration; + final DecryptedText spotifyCookie; + final DecryptedText spotifyAccessToken; + final DateTime spotifyExpiration; + final DecryptedText? neteaseCookie; + final DateTime? neteaseExpiration; const AuthenticationTableData( {required this.id, - required this.cookie, - required this.accessToken, - required this.expiration}); + required this.spotifyCookie, + required this.spotifyAccessToken, + required this.spotifyExpiration, + this.neteaseCookie, + this.neteaseExpiration}); @override Map toColumns(bool nullToAbsent) { final map = {}; map['id'] = Variable(id); { - map['cookie'] = Variable( - $AuthenticationTableTable.$convertercookie.toSql(cookie)); + map['spotify_cookie'] = Variable($AuthenticationTableTable + .$converterspotifyCookie + .toSql(spotifyCookie)); } { - map['access_token'] = Variable( - $AuthenticationTableTable.$converteraccessToken.toSql(accessToken)); + map['spotify_access_token'] = Variable($AuthenticationTableTable + .$converterspotifyAccessToken + .toSql(spotifyAccessToken)); + } + map['spotify_expiration'] = Variable(spotifyExpiration); + if (!nullToAbsent || neteaseCookie != null) { + map['netease_cookie'] = Variable($AuthenticationTableTable + .$converterneteaseCookien + .toSql(neteaseCookie)); + } + if (!nullToAbsent || neteaseExpiration != null) { + map['netease_expiration'] = Variable(neteaseExpiration); } - map['expiration'] = Variable(expiration); return map; } AuthenticationTableCompanion toCompanion(bool nullToAbsent) { return AuthenticationTableCompanion( id: Value(id), - cookie: Value(cookie), - accessToken: Value(accessToken), - expiration: Value(expiration), + spotifyCookie: Value(spotifyCookie), + spotifyAccessToken: Value(spotifyAccessToken), + spotifyExpiration: Value(spotifyExpiration), + neteaseCookie: neteaseCookie == null && nullToAbsent + ? const Value.absent() + : Value(neteaseCookie), + neteaseExpiration: neteaseExpiration == null && nullToAbsent + ? const Value.absent() + : Value(neteaseExpiration), ); } @@ -140,9 +201,14 @@ class AuthenticationTableData extends DataClass serializer ??= driftRuntimeOptions.defaultSerializer; return AuthenticationTableData( id: serializer.fromJson(json['id']), - cookie: serializer.fromJson(json['cookie']), - accessToken: serializer.fromJson(json['accessToken']), - expiration: serializer.fromJson(json['expiration']), + spotifyCookie: serializer.fromJson(json['spotifyCookie']), + spotifyAccessToken: + serializer.fromJson(json['spotifyAccessToken']), + spotifyExpiration: + serializer.fromJson(json['spotifyExpiration']), + neteaseCookie: serializer.fromJson(json['neteaseCookie']), + neteaseExpiration: + serializer.fromJson(json['neteaseExpiration']), ); } @override @@ -150,31 +216,51 @@ class AuthenticationTableData extends DataClass serializer ??= driftRuntimeOptions.defaultSerializer; return { 'id': serializer.toJson(id), - 'cookie': serializer.toJson(cookie), - 'accessToken': serializer.toJson(accessToken), - 'expiration': serializer.toJson(expiration), + 'spotifyCookie': serializer.toJson(spotifyCookie), + 'spotifyAccessToken': + serializer.toJson(spotifyAccessToken), + 'spotifyExpiration': serializer.toJson(spotifyExpiration), + 'neteaseCookie': serializer.toJson(neteaseCookie), + 'neteaseExpiration': serializer.toJson(neteaseExpiration), }; } AuthenticationTableData copyWith( {int? id, - DecryptedText? cookie, - DecryptedText? accessToken, - DateTime? expiration}) => + DecryptedText? spotifyCookie, + DecryptedText? spotifyAccessToken, + DateTime? spotifyExpiration, + Value neteaseCookie = const Value.absent(), + Value neteaseExpiration = const Value.absent()}) => AuthenticationTableData( id: id ?? this.id, - cookie: cookie ?? this.cookie, - accessToken: accessToken ?? this.accessToken, - expiration: expiration ?? this.expiration, + spotifyCookie: spotifyCookie ?? this.spotifyCookie, + spotifyAccessToken: spotifyAccessToken ?? this.spotifyAccessToken, + spotifyExpiration: spotifyExpiration ?? this.spotifyExpiration, + neteaseCookie: + neteaseCookie.present ? neteaseCookie.value : this.neteaseCookie, + neteaseExpiration: neteaseExpiration.present + ? neteaseExpiration.value + : this.neteaseExpiration, ); AuthenticationTableData copyWithCompanion(AuthenticationTableCompanion data) { return AuthenticationTableData( id: data.id.present ? data.id.value : this.id, - cookie: data.cookie.present ? data.cookie.value : this.cookie, - accessToken: - data.accessToken.present ? data.accessToken.value : this.accessToken, - expiration: - data.expiration.present ? data.expiration.value : this.expiration, + spotifyCookie: data.spotifyCookie.present + ? data.spotifyCookie.value + : this.spotifyCookie, + spotifyAccessToken: data.spotifyAccessToken.present + ? data.spotifyAccessToken.value + : this.spotifyAccessToken, + spotifyExpiration: data.spotifyExpiration.present + ? data.spotifyExpiration.value + : this.spotifyExpiration, + neteaseCookie: data.neteaseCookie.present + ? data.neteaseCookie.value + : this.neteaseCookie, + neteaseExpiration: data.neteaseExpiration.present + ? data.neteaseExpiration.value + : this.neteaseExpiration, ); } @@ -182,69 +268,89 @@ class AuthenticationTableData extends DataClass String toString() { return (StringBuffer('AuthenticationTableData(') ..write('id: $id, ') - ..write('cookie: $cookie, ') - ..write('accessToken: $accessToken, ') - ..write('expiration: $expiration') + ..write('spotifyCookie: $spotifyCookie, ') + ..write('spotifyAccessToken: $spotifyAccessToken, ') + ..write('spotifyExpiration: $spotifyExpiration, ') + ..write('neteaseCookie: $neteaseCookie, ') + ..write('neteaseExpiration: $neteaseExpiration') ..write(')')) .toString(); } @override - int get hashCode => Object.hash(id, cookie, accessToken, expiration); + int get hashCode => Object.hash(id, spotifyCookie, spotifyAccessToken, + spotifyExpiration, neteaseCookie, neteaseExpiration); @override bool operator ==(Object other) => identical(this, other) || (other is AuthenticationTableData && other.id == this.id && - other.cookie == this.cookie && - other.accessToken == this.accessToken && - other.expiration == this.expiration); + other.spotifyCookie == this.spotifyCookie && + other.spotifyAccessToken == this.spotifyAccessToken && + other.spotifyExpiration == this.spotifyExpiration && + other.neteaseCookie == this.neteaseCookie && + other.neteaseExpiration == this.neteaseExpiration); } class AuthenticationTableCompanion extends UpdateCompanion { final Value id; - final Value cookie; - final Value accessToken; - final Value expiration; + final Value spotifyCookie; + final Value spotifyAccessToken; + final Value spotifyExpiration; + final Value neteaseCookie; + final Value neteaseExpiration; const AuthenticationTableCompanion({ this.id = const Value.absent(), - this.cookie = const Value.absent(), - this.accessToken = const Value.absent(), - this.expiration = const Value.absent(), + this.spotifyCookie = const Value.absent(), + this.spotifyAccessToken = const Value.absent(), + this.spotifyExpiration = const Value.absent(), + this.neteaseCookie = const Value.absent(), + this.neteaseExpiration = const Value.absent(), }); AuthenticationTableCompanion.insert({ this.id = const Value.absent(), - required DecryptedText cookie, - required DecryptedText accessToken, - required DateTime expiration, - }) : cookie = Value(cookie), - accessToken = Value(accessToken), - expiration = Value(expiration); + required DecryptedText spotifyCookie, + required DecryptedText spotifyAccessToken, + required DateTime spotifyExpiration, + this.neteaseCookie = const Value.absent(), + this.neteaseExpiration = const Value.absent(), + }) : spotifyCookie = Value(spotifyCookie), + spotifyAccessToken = Value(spotifyAccessToken), + spotifyExpiration = Value(spotifyExpiration); static Insertable custom({ Expression? id, - Expression? cookie, - Expression? accessToken, - Expression? expiration, + Expression? spotifyCookie, + Expression? spotifyAccessToken, + Expression? spotifyExpiration, + Expression? neteaseCookie, + Expression? neteaseExpiration, }) { return RawValuesInsertable({ if (id != null) 'id': id, - if (cookie != null) 'cookie': cookie, - if (accessToken != null) 'access_token': accessToken, - if (expiration != null) 'expiration': expiration, + if (spotifyCookie != null) 'spotify_cookie': spotifyCookie, + if (spotifyAccessToken != null) + 'spotify_access_token': spotifyAccessToken, + if (spotifyExpiration != null) 'spotify_expiration': spotifyExpiration, + if (neteaseCookie != null) 'netease_cookie': neteaseCookie, + if (neteaseExpiration != null) 'netease_expiration': neteaseExpiration, }); } AuthenticationTableCompanion copyWith( {Value? id, - Value? cookie, - Value? accessToken, - Value? expiration}) { + Value? spotifyCookie, + Value? spotifyAccessToken, + Value? spotifyExpiration, + Value? neteaseCookie, + Value? neteaseExpiration}) { return AuthenticationTableCompanion( id: id ?? this.id, - cookie: cookie ?? this.cookie, - accessToken: accessToken ?? this.accessToken, - expiration: expiration ?? this.expiration, + spotifyCookie: spotifyCookie ?? this.spotifyCookie, + spotifyAccessToken: spotifyAccessToken ?? this.spotifyAccessToken, + spotifyExpiration: spotifyExpiration ?? this.spotifyExpiration, + neteaseCookie: neteaseCookie ?? this.neteaseCookie, + neteaseExpiration: neteaseExpiration ?? this.neteaseExpiration, ); } @@ -254,17 +360,26 @@ class AuthenticationTableCompanion if (id.present) { map['id'] = Variable(id.value); } - if (cookie.present) { - map['cookie'] = Variable( - $AuthenticationTableTable.$convertercookie.toSql(cookie.value)); + if (spotifyCookie.present) { + map['spotify_cookie'] = Variable($AuthenticationTableTable + .$converterspotifyCookie + .toSql(spotifyCookie.value)); } - if (accessToken.present) { - map['access_token'] = Variable($AuthenticationTableTable - .$converteraccessToken - .toSql(accessToken.value)); + if (spotifyAccessToken.present) { + map['spotify_access_token'] = Variable($AuthenticationTableTable + .$converterspotifyAccessToken + .toSql(spotifyAccessToken.value)); } - if (expiration.present) { - map['expiration'] = Variable(expiration.value); + if (spotifyExpiration.present) { + map['spotify_expiration'] = Variable(spotifyExpiration.value); + } + if (neteaseCookie.present) { + map['netease_cookie'] = Variable($AuthenticationTableTable + .$converterneteaseCookien + .toSql(neteaseCookie.value)); + } + if (neteaseExpiration.present) { + map['netease_expiration'] = Variable(neteaseExpiration.value); } return map; } @@ -273,9 +388,11 @@ class AuthenticationTableCompanion String toString() { return (StringBuffer('AuthenticationTableCompanion(') ..write('id: $id, ') - ..write('cookie: $cookie, ') - ..write('accessToken: $accessToken, ') - ..write('expiration: $expiration') + ..write('spotifyCookie: $spotifyCookie, ') + ..write('spotifyAccessToken: $spotifyAccessToken, ') + ..write('spotifyExpiration: $spotifyExpiration, ') + ..write('neteaseCookie: $neteaseCookie, ') + ..write('neteaseExpiration: $neteaseExpiration') ..write(')')) .toString(); } @@ -3824,16 +3941,20 @@ abstract class _$AppDatabase extends GeneratedDatabase { typedef $$AuthenticationTableTableCreateCompanionBuilder = AuthenticationTableCompanion Function({ Value id, - required DecryptedText cookie, - required DecryptedText accessToken, - required DateTime expiration, + required DecryptedText spotifyCookie, + required DecryptedText spotifyAccessToken, + required DateTime spotifyExpiration, + Value neteaseCookie, + Value neteaseExpiration, }); typedef $$AuthenticationTableTableUpdateCompanionBuilder = AuthenticationTableCompanion Function({ Value id, - Value cookie, - Value accessToken, - Value expiration, + Value spotifyCookie, + Value spotifyAccessToken, + Value spotifyExpiration, + Value neteaseCookie, + Value neteaseExpiration, }); class $$AuthenticationTableTableTableManager extends RootTableManager< @@ -3855,27 +3976,35 @@ class $$AuthenticationTableTableTableManager extends RootTableManager< ComposerState(db, table)), updateCompanionCallback: ({ Value id = const Value.absent(), - Value cookie = const Value.absent(), - Value accessToken = const Value.absent(), - Value expiration = const Value.absent(), + Value spotifyCookie = const Value.absent(), + Value spotifyAccessToken = const Value.absent(), + Value spotifyExpiration = const Value.absent(), + Value neteaseCookie = const Value.absent(), + Value neteaseExpiration = const Value.absent(), }) => AuthenticationTableCompanion( id: id, - cookie: cookie, - accessToken: accessToken, - expiration: expiration, + spotifyCookie: spotifyCookie, + spotifyAccessToken: spotifyAccessToken, + spotifyExpiration: spotifyExpiration, + neteaseCookie: neteaseCookie, + neteaseExpiration: neteaseExpiration, ), createCompanionCallback: ({ Value id = const Value.absent(), - required DecryptedText cookie, - required DecryptedText accessToken, - required DateTime expiration, + required DecryptedText spotifyCookie, + required DecryptedText spotifyAccessToken, + required DateTime spotifyExpiration, + Value neteaseCookie = const Value.absent(), + Value neteaseExpiration = const Value.absent(), }) => AuthenticationTableCompanion.insert( id: id, - cookie: cookie, - accessToken: accessToken, - expiration: expiration, + spotifyCookie: spotifyCookie, + spotifyAccessToken: spotifyAccessToken, + spotifyExpiration: spotifyExpiration, + neteaseCookie: neteaseCookie, + neteaseExpiration: neteaseExpiration, ), )); } @@ -3889,21 +4018,33 @@ class $$AuthenticationTableTableFilterComposer ColumnFilters(column, joinBuilders: joinBuilders)); ColumnWithTypeConverterFilters - get cookie => $state.composableBuilder( - column: $state.table.cookie, + get spotifyCookie => $state.composableBuilder( + column: $state.table.spotifyCookie, builder: (column, joinBuilders) => ColumnWithTypeConverterFilters( column, joinBuilders: joinBuilders)); ColumnWithTypeConverterFilters - get accessToken => $state.composableBuilder( - column: $state.table.accessToken, + get spotifyAccessToken => $state.composableBuilder( + column: $state.table.spotifyAccessToken, builder: (column, joinBuilders) => ColumnWithTypeConverterFilters( column, joinBuilders: joinBuilders)); - ColumnFilters get expiration => $state.composableBuilder( - column: $state.table.expiration, + ColumnFilters get spotifyExpiration => $state.composableBuilder( + column: $state.table.spotifyExpiration, + builder: (column, joinBuilders) => + ColumnFilters(column, joinBuilders: joinBuilders)); + + ColumnWithTypeConverterFilters + get neteaseCookie => $state.composableBuilder( + column: $state.table.neteaseCookie, + builder: (column, joinBuilders) => ColumnWithTypeConverterFilters( + column, + joinBuilders: joinBuilders)); + + ColumnFilters get neteaseExpiration => $state.composableBuilder( + column: $state.table.neteaseExpiration, builder: (column, joinBuilders) => ColumnFilters(column, joinBuilders: joinBuilders)); } @@ -3916,18 +4057,28 @@ class $$AuthenticationTableTableOrderingComposer builder: (column, joinBuilders) => ColumnOrderings(column, joinBuilders: joinBuilders)); - ColumnOrderings get cookie => $state.composableBuilder( - column: $state.table.cookie, + ColumnOrderings get spotifyCookie => $state.composableBuilder( + column: $state.table.spotifyCookie, builder: (column, joinBuilders) => ColumnOrderings(column, joinBuilders: joinBuilders)); - ColumnOrderings get accessToken => $state.composableBuilder( - column: $state.table.accessToken, + ColumnOrderings get spotifyAccessToken => $state.composableBuilder( + column: $state.table.spotifyAccessToken, builder: (column, joinBuilders) => ColumnOrderings(column, joinBuilders: joinBuilders)); - ColumnOrderings get expiration => $state.composableBuilder( - column: $state.table.expiration, + ColumnOrderings get spotifyExpiration => $state.composableBuilder( + column: $state.table.spotifyExpiration, + builder: (column, joinBuilders) => + ColumnOrderings(column, joinBuilders: joinBuilders)); + + ColumnOrderings get neteaseCookie => $state.composableBuilder( + column: $state.table.neteaseCookie, + builder: (column, joinBuilders) => + ColumnOrderings(column, joinBuilders: joinBuilders)); + + ColumnOrderings get neteaseExpiration => $state.composableBuilder( + column: $state.table.neteaseExpiration, builder: (column, joinBuilders) => ColumnOrderings(column, joinBuilders: joinBuilders)); } diff --git a/lib/services/database/tables/authentication.dart b/lib/services/database/tables/authentication.dart index 9604195..0a9038c 100755 --- a/lib/services/database/tables/authentication.dart +++ b/lib/services/database/tables/authentication.dart @@ -2,7 +2,10 @@ part of '../database.dart'; class AuthenticationTable extends Table { IntColumn get id => integer().autoIncrement()(); - TextColumn get cookie => text().map(EncryptedTextConverter())(); - TextColumn get accessToken => text().map(EncryptedTextConverter())(); - DateTimeColumn get expiration => dateTime()(); + TextColumn get spotifyCookie => text().map(EncryptedTextConverter())(); + TextColumn get spotifyAccessToken => text().map(EncryptedTextConverter())(); + DateTimeColumn get spotifyExpiration => dateTime()(); + TextColumn get neteaseCookie => + text().map(EncryptedTextConverter()).nullable()(); + DateTimeColumn get neteaseExpiration => dateTime().nullable()(); } diff --git a/lib/services/sourced_track/sources/netease.dart b/lib/services/sourced_track/sources/netease.dart index cce5294..af6ef59 100755 --- a/lib/services/sourced_track/sources/netease.dart +++ b/lib/services/sourced_track/sources/netease.dart @@ -1,6 +1,8 @@ import 'package:collection/collection.dart'; import 'package:drift/drift.dart'; import 'package:get/get.dart' hide Value; +import 'package:get/get_connect/http/src/request/request.dart'; +import 'package:rhythm_box/providers/auth.dart'; import 'package:rhythm_box/providers/database.dart'; import 'package:rhythm_box/providers/user_preferences.dart'; import 'package:rhythm_box/services/database/database.dart'; @@ -38,9 +40,25 @@ class NeteaseSourcedTrack extends SourcedTrack { } static GetConnect getClient() { - final client = GetConnect(); + final client = GetConnect( + withCredentials: true, + timeout: const Duration(seconds: 30), + ); client.baseUrl = getBaseUrl(); - client.timeout = const Duration(seconds: 30); + client.httpClient.addRequestModifier((Request request) async { + final AuthenticationProvider auth = Get.find(); + if (auth.auth.value!.neteaseCookie != null) { + final cookie = + 'MUSIC_U=${auth.auth.value!.getNeteaseCookie('MUSIC_U')}'; + if (request.headers['Cookie'] == null) { + request.headers['Cookie'] = cookie; + } else { + request.headers['Cookie'] = request.headers['Cookie']! + cookie; + } + } + + return request; + }); return client; } @@ -80,7 +98,7 @@ class NeteaseSourcedTrack extends SourcedTrack { await client.get('/check/music?id=${siblings.first.info.id}'); if (checkResp.body['success'] != true) throw TrackNotFoundError(track); - await await db.database.into(db.database.sourceMatchTable).insert( + await db.database.into(db.database.sourceMatchTable).insert( SourceMatchTableCompanion.insert( trackId: track.id!, sourceId: siblings.first.info.id,