Function for interactive login in the browser [BETA]

This commit is contained in:
Santiago Ramirez 2024-06-23 11:29:19 -04:00
parent a35eece00c
commit 86b3f20c34
7 changed files with 228 additions and 48 deletions

2
.gitignore vendored
View File

@ -78,3 +78,5 @@ android/key.properties
**/pb_data
tm.json
.local-chrome
.vs

View File

@ -325,5 +325,6 @@
"connect_client_alert": "You're being controlled by {client}",
"this_device": "This Device",
"remote": "Remote",
"stats": "Stats"
"stats": "Stats",
"continue_in_browser": "Continue in browser"
}

View File

@ -325,5 +325,6 @@
"add_library_location": "Añadir a la biblioteca",
"remove_library_location": "Eliminar de la biblioteca",
"local_tab": "Local",
"stats": "Estadísticas"
"stats": "Estadísticas",
"continue_in_browser": "Continuar en el navegador"
}

View File

@ -1,6 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:puppeteer/protocol/network.dart';
import 'package:puppeteer/puppeteer.dart';
import 'package:spotube/extensions/context.dart';
import 'package:spotube/provider/authentication_provider.dart';
@ -16,8 +18,12 @@ class TokenLoginForm extends HookConsumerWidget {
Widget build(BuildContext context, ref) {
final authenticationNotifier = ref.watch(authenticationProvider.notifier);
final directCodeController = useTextEditingController();
final platform = Theme.of(context).platform;
final isDesktop =
platform == TargetPlatform.linux || platform == TargetPlatform.windows;
final isLoading = useState(false);
final showManualConf = useState(false);
return ConstrainedBox(
constraints: const BoxConstraints(
@ -25,46 +31,115 @@ class TokenLoginForm extends HookConsumerWidget {
),
child: Column(
children: [
TextField(
controller: directCodeController,
decoration: InputDecoration(
hintText: context.l10n.spotify_cookie("\"sp_dc\""),
labelText: context.l10n.cookie_name_cookie("sp_dc"),
),
keyboardType: TextInputType.visiblePassword,
),
const SizedBox(height: 10),
FilledButton(
onPressed: isLoading.value
? null
: () async {
try {
isLoading.value = true;
if (directCodeController.text.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(context.l10n.fill_in_all_fields),
behavior: SnackBarBehavior.floating,
),
);
return;
}
final cookieHeader =
"sp_dc=${directCodeController.text.trim()}";
Visibility(
visible: showManualConf.value,
child: Column(
children: [
TextField(
controller: directCodeController,
decoration: InputDecoration(
hintText: context.l10n.spotify_cookie("\"sp_dc\""),
labelText: context.l10n.cookie_name_cookie("sp_dc"),
),
keyboardType: TextInputType.visiblePassword,
),
const SizedBox(height: 10),
FilledButton(
onPressed: isLoading.value
? null
: () async {
try {
isLoading.value = true;
if (directCodeController.text.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content:
Text(context.l10n.fill_in_all_fields),
behavior: SnackBarBehavior.floating,
),
);
return;
}
final cookieHeader =
"sp_dc=${directCodeController.text.trim()}";
authenticationNotifier.setCredentials(
await AuthenticationCredentials.fromCookie(
cookieHeader),
);
if (context.mounted) {
onDone?.call();
}
} finally {
isLoading.value = false;
}
},
child: Text(context.l10n.submit),
)
authenticationNotifier.setCredentials(
await AuthenticationCredentials.fromCookie(
cookieHeader),
);
if (context.mounted) {
onDone?.call();
}
} finally {
isLoading.value = false;
}
},
child: Text(context.l10n.submit),
)
],
)),
Visibility(
visible: isDesktop && !showManualConf.value,
child: Column(
children: [
SizedBox(
width: double.infinity,
height: 48.0,
child: ElevatedButton(
onPressed: isLoading.value
? null
: () async {
final browser =
await puppeteer.launch(headless: false);
try {
List<Cookie> cookies = [];
final page = await browser.newPage();
await page.goto(
'https://accounts.spotify.com/en/login',
wait: Until.domContentLoaded);
while (browser.isConnected) {
cookies = await page.cookies();
for (final cookie in cookies) {
if (cookie.name == "sp_dc") {
await browser.close();
final cookieHeader =
"sp_dc=${cookie.value.trim()}";
authenticationNotifier.setCredentials(
await AuthenticationCredentials
.fromCookie(cookieHeader),
);
if (context.mounted) {
onDone?.call();
}
return;
}
}
}
} catch (_) {
showManualConf.value = true;
} finally {
isLoading.value = false;
await browser.close();
}
},
style: ButtonStyle(
shape: WidgetStateProperty.all<RoundedRectangleBorder>(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15.0),
),
),
),
child: Text(context.l10n.continue_in_browser),
),
),
],
))
],
),
);

View File

@ -1258,18 +1258,18 @@ packages:
dependency: "direct main"
description:
name: json_annotation
sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
url: "https://pub.dev"
source: hosted
version: "4.8.1"
version: "4.9.0"
json_serializable:
dependency: "direct dev"
description:
name: json_serializable
sha256: aa1f5a8912615733e0fdc7a02af03308933c93235bdc8d50d0b0c8a8ccb0b969
sha256: ea1432d167339ea9b5bb153f0571d0039607a873d6e04e0117af043f14a1fd4b
url: "https://pub.dev"
source: hosted
version: "6.7.1"
version: "6.8.0"
leak_tracker:
dependency: transitive
description:
@ -1711,13 +1711,13 @@ packages:
source: hosted
version: "1.2.3"
puppeteer:
dependency: transitive
dependency: "direct main"
description:
name: puppeteer
sha256: "6833edca01b1e9dcdd9a6e41bad84b706dfba4366d095c4edff64b00c02ac472"
sha256: c45c51b4ad8d70acdffeb1cfb9d16b60a7eaab7bfef314dd5b02c3607269b556
url: "https://pub.dev"
source: hosted
version: "3.8.0"
version: "3.11.0"
quiver:
dependency: transitive
description:

View File

@ -121,6 +121,7 @@ dependencies:
tray_manager: ^0.2.2
http: ^1.2.1
riverpod: ^2.5.1
puppeteer: ^3.11.0
dev_dependencies:
build_runner: ^2.4.11

View File

@ -1 +1,101 @@
{}
{
"ar": [
"continue_in_browser"
],
"bn": [
"continue_in_browser"
],
"ca": [
"continue_in_browser"
],
"cs": [
"continue_in_browser"
],
"de": [
"continue_in_browser"
],
"eu": [
"continue_in_browser"
],
"fa": [
"continue_in_browser"
],
"fi": [
"continue_in_browser"
],
"fr": [
"continue_in_browser"
],
"hi": [
"continue_in_browser"
],
"id": [
"continue_in_browser"
],
"it": [
"continue_in_browser"
],
"ja": [
"continue_in_browser"
],
"ka": [
"continue_in_browser"
],
"ko": [
"continue_in_browser"
],
"ne": [
"continue_in_browser"
],
"nl": [
"continue_in_browser"
],
"pl": [
"continue_in_browser"
],
"pt": [
"continue_in_browser"
],
"ru": [
"continue_in_browser"
],
"th": [
"continue_in_browser"
],
"tr": [
"continue_in_browser"
],
"uk": [
"continue_in_browser"
],
"vi": [
"continue_in_browser"
],
"zh": [
"continue_in_browser"
]
}