diff --git a/lib/common/constants.dart b/lib/common/constants.dart index 5cc3b3b3..55261da1 100644 --- a/lib/common/constants.dart +++ b/lib/common/constants.dart @@ -21,13 +21,10 @@ class Constants { static const String traceId = '11111111111111111111111111111111:1111111111111111:0:0'; static const String userAgent = - 'Mozilla/5.0 BiliDroid/1.46.2 (bbcallen@gmail.com) os/android model/vivo mobi_app/android build/1462100 channel/bili innerVer/1462100 osVer/14 network/2'; + 'Mozilla/5.0 BiliDroid/1.46.2 (bbcallen@gmail.com) os/android model/vivo mobi_app/android_hd build/2001100 channel/yingyongbao innerVer/2001100 osVer/14 network/2'; static const String statistics = '%7B%22appId%22%3A5%2C%22platform%22%3A3%2C%22version%22%3A%221.46.2%22%2C%22abtest%22%3A%22%22%7D'; - // jsonEncode( - // {"appId": 5, "platform": 3, "version": "1.46.2", "abtest": ""}); - // Uri.encodeComponent( - // '{"appId": 5,"platform": 3,"version": "1.46.2","abtest": ""}'); + //Uri.encodeComponent('{"appId": 5,"platform": 3,"version": "1.46.2","abtest": ""}'); //内容来自 https://passport.bilibili.com/web/generic/country/list static const List> internationalDialingPrefix = [ diff --git a/lib/http/api.dart b/lib/http/api.dart index 31a8f4ed..e8566f02 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -587,6 +587,9 @@ class Api { static const String safeCenterSmsVerify = '${HttpString.passBaseUrl}/x/safecenter/login/tel/verify'; + static const String oauth2AccessToken = + '${HttpString.passBaseUrl}/x/passport-login/oauth2/access_token'; + /// 密码加密密钥 /// disable_rcmd /// local_id diff --git a/lib/http/login.dart b/lib/http/login.dart index 91191ca6..fbef3f44 100644 --- a/lib/http/login.dart +++ b/lib/http/login.dart @@ -2,7 +2,6 @@ import 'dart:convert'; import 'package:crypto/crypto.dart'; import 'package:dio/dio.dart'; import 'package:encrypt/encrypt.dart'; -import 'package:flutter/material.dart'; import '../common/constants.dart'; import '../models/login/index.dart'; import '../utils/login.dart'; @@ -43,6 +42,7 @@ class LoginHttp { ); var res = await Request() .post(Api.getTVCode, queryParameters: {...params, 'sign': sign}); + if (res.data['code'] == 0) { return {'status': true, 'data': res.data['data']}; } else { @@ -107,9 +107,10 @@ class LoginHttp { int timestamp = DateTime.now().millisecondsSinceEpoch; var data = { 'appkey': Constants.appKey, - 'build': '1462100', + 'build': '2001100', 'buvid': buvid, 'c_locale': 'zh_CN', + 'channel': 'yingyongbao', 'cid': cid, // if (deviceTouristId != null) 'device_tourist_id': deviceTouristId, 'disable_rcmd': '0', @@ -142,6 +143,7 @@ class LoginHttp { headers: headers, ), ); + if (res.data['code'] == 0 && res.data['data']['recaptcha_url'] == "") { return {'status': true, 'data': res.data['data']}; } else { @@ -158,7 +160,7 @@ class LoginHttp { // dynamic publicKey = RSAKeyParser().parse(key); // var params = { // 'appkey': Constants.appKey, - // 'build': '1462100', + // 'build': '2001100', // 'buvid': buvid, // 'c_locale': 'zh_CN', // 'channel': 'yingyongbao', @@ -185,7 +187,7 @@ class LoginHttp { // contentType: Headers.formUrlEncodedContentType, // headers: headers, // )); - // debugPrint("getGuestId: $res"); + // print("getGuestId: $res"); // if (res.data['code'] == 0) { // return {'status': true, 'data': res.data['data']}; // } else { @@ -211,7 +213,7 @@ class LoginHttp { Map data = { 'appkey': Constants.appKey, 'bili_local_id': deviceId, - 'build': '1462100', + 'build': '2001100', 'buvid': buvid, 'c_locale': 'zh_CN', 'channel': 'yingyongbao', @@ -247,7 +249,6 @@ class LoginHttp { ); data['sign'] = sign; data.map((key, value) { - debugPrint('$key: $value'); return MapEntry(key, value); }); var res = await Request().post( @@ -259,6 +260,7 @@ class LoginHttp { //responseType: ResponseType.plain ), ); + if (res.data['code'] == 0) { return { 'status': true, @@ -287,7 +289,7 @@ class LoginHttp { Map data = { 'appkey': Constants.appKey, 'bili_local_id': deviceId, - 'build': '1462100', + 'build': '2001100', 'buvid': buvid, 'c_locale': 'zh_CN', 'captcha_key': captchaKey, @@ -321,7 +323,6 @@ class LoginHttp { ); data['sign'] = sign; data.map((key, value) { - debugPrint('$key: $value'); return MapEntry(key, value); }); var res = await Request().post( @@ -333,6 +334,7 @@ class LoginHttp { //responseType: ResponseType.plain ), ); + if (res.data['code'] == 0) { return {'status': true, 'data': res.data['data']}; } else { @@ -349,9 +351,12 @@ class LoginHttp { static Future safeCenterGetInfo({ required String tmpCode, }) async { - var res = await Request().get(Api.safeCenterGetInfo, queryParameters: { - 'tmp_code': tmpCode, - }); + var res = await Request().get( + Api.safeCenterGetInfo, + queryParameters: { + 'tmp_code': tmpCode, + }, + ); if (res.data['code'] == 0) { return {'status': true, 'data': res.data['data']}; } else { @@ -364,9 +369,10 @@ class LoginHttp { } } - // 风控验证手机前的验证码 + // 风控验证手机前的极验验证码 static Future preCapture() async { var res = await Request().post(Api.preCapture); + if (res.data['code'] == 0) { return {'status': true, 'data': res.data['data']}; } else { @@ -379,23 +385,40 @@ class LoginHttp { } } - // 风控验证手机 + // 风控验证手机:发送短信验证码 static Future safeCenterSmsCode({ String? smsType, required String tmpCode, - required String geeChallenge, - required String geeSeccode, - required String geeValidate, - required String recaptchaToken, + String? geeChallenge, + String? geeSeccode, + String? geeValidate, + String? recaptchaToken, + required String refererUrl, }) async { - var res = await Request().post(Api.safeCenterSmsCode, data: { + Map data = { + 'disable_rcmd': '0', 'sms_type': smsType ?? 'loginTelCheck', 'tmp_code': tmpCode, - 'gee_challenge': geeChallenge, - 'gee_seccode': geeSeccode, - 'gee_validate': geeValidate, - 'recaptcha_token': recaptchaToken, - }); + if (geeChallenge != null) 'gee_challenge': geeChallenge, + if (geeSeccode != null) 'gee_seccode': geeSeccode, + if (geeValidate != null) 'gee_validate': geeValidate, + if (recaptchaToken != null) 'recaptcha_token': recaptchaToken, + }; + String sign = Utils.appSign( + data, + Constants.appKey, + Constants.appSec, + ); + data['sign'] = sign; + var res = await Request().post( + Api.safeCenterSmsCode, + data: data, + options: + Options(contentType: Headers.formUrlEncodedContentType, headers: { + "Referer": refererUrl, + }), + ); + if (res.data['code'] == 0) { return {'status': true, 'data': res.data['data']}; } else { @@ -408,21 +431,93 @@ class LoginHttp { } } - static Future safeCenterSmsVerify( - {String? type, - required String code, - required String tmpCode, - required String requestId, - required String source, - required String captchaKey}) async { - var res = await Request().post(Api.safeCenterSmsVerify, data: { + // 风控验证手机:提交短信验证码 + static Future safeCenterSmsVerify({ + String? type, + required String code, + required String tmpCode, + required String requestId, + required String source, + required String captchaKey, + required String refererUrl, + }) async { + Map data = { 'type': type ?? 'loginTelCheck', 'code': code, 'tmp_code': tmpCode, 'request_id': requestId, 'source': source, 'captcha_key': captchaKey, + }; + String sign = Utils.appSign( + data, + Constants.appKey, + Constants.appSec, + ); + data['sign'] = sign; + var res = await Request().post( + Api.safeCenterSmsVerify, + data: data, + options: + Options(contentType: Headers.formUrlEncodedContentType, headers: { + "Referer": refererUrl, + }), + ); + + if (res.data['code'] == 0) { + return {'status': true, 'data': res.data['data']}; + } else { + return { + 'status': false, + 'code': res.data['code'], + 'msg': res.data['message'], + 'data': res.data['data'] + }; + } + } + + // 风控验证手机:用oauthCode换回accessToken + static Future oauth2AccessToken({ + required String code, + }) async { + Map data = { + 'appkey': Constants.appKey, + 'build': '2001100', + 'buvid': buvid, + // 'c_locale': 'zh_CN', + // 'channel': 'yingyongbao', + 'code': code, + // 'device': 'phone', + // 'device_id': deviceId, + // 'device_name': 'vivo', + // 'device_platform': 'Android14vivo', + 'disable_rcmd': '0', + 'grant_type': 'authorization_code', + 'local_id': buvid, + 'mobi_app': 'android_hd', + 'platform': 'android', + // 's_locale': 'zh_CN', + // 'statistics': Constants.statistics, + 'ts': (DateTime.now().millisecondsSinceEpoch ~/ 1000).toString(), + }; + String sign = Utils.appSign( + data, + Constants.appKey, + Constants.appSec, + ); + data['sign'] = sign; + data.map((key, value) { + return MapEntry(key, value); }); + var res = await Request().post( + Api.oauth2AccessToken, + data: data, + options: Options( + contentType: Headers.formUrlEncodedContentType, + headers: headers, + ), + ); + if (res.data['code'] == 0) { return {'status': true, 'data': res.data['data']}; } else { diff --git a/lib/pages/login/controller.dart b/lib/pages/login/controller.dart index 9dec7ac6..54e546f6 100644 --- a/lib/pages/login/controller.dart +++ b/lib/pages/login/controller.dart @@ -11,7 +11,6 @@ import 'package:PiliPalaX/models/login/index.dart'; import '../../utils/login.dart'; import 'package:hive/hive.dart'; import 'package:webview_cookie_manager/webview_cookie_manager.dart'; -import 'package:flutter_inappwebview/flutter_inappwebview.dart' as web; import '../../http/constants.dart'; import '../../http/init.dart'; @@ -60,6 +59,10 @@ class LoginPageController extends GetxController tabController.dispose(); qrCodeTimer?.cancel(); smsSendCooldownTimer?.cancel(); + telTextController.dispose(); + usernameTextController.dispose(); + passwordTextController.dispose(); + smsCodeTextController.dispose(); super.onClose(); } @@ -69,8 +72,6 @@ class LoginPageController extends GetxController qrCodeTimer?.cancel(); codeInfo.value = res; codeInfo.refresh(); - debugPrint("codeInfo"); - debugPrint(codeInfo.toString()); qrCodeTimer = Timer.periodic(const Duration(milliseconds: 1000), (t) { qrCodeLeftTime.value = 180 - t.tick; if (qrCodeLeftTime <= 0) { @@ -103,7 +104,6 @@ class LoginPageController extends GetxController } void _handleTabChange() { - debugPrint('tabController.index ${tabController.index}'); if (tabController.index == 2) { if (qrCodeTimer == null || qrCodeTimer!.isActive == false) { refreshQRCode(); @@ -120,8 +120,6 @@ class LoginPageController extends GetxController 'refresh': token_info['refresh_token'] }); List cookieInfo = cookie_info['cookies']; - debugPrint("cookieInfo"); - debugPrint(cookieInfo.toString()); List cookies = []; String cookieStrings = cookieInfo.map((cookie) { String cstr = @@ -140,34 +138,22 @@ class LoginPageController extends GetxController } Request.dio.options.headers['cookie'] = cookieStrings; await WebviewCookieManager().setCookies(cookies); - for (Cookie item in cookies) { - await web.CookieManager().setCookie( - url: web.WebUri(item.domain ?? ''), - name: item.name, - value: item.value, - path: item.path ?? '', - domain: item.domain, - isSecure: item.secure, - isHttpOnly: item.httpOnly, - ); - } } catch (e) { SmartDialog.showToast('设置登录态失败,$e'); } final result = await UserHttp.userInfo(); if (result['status'] && result['data'].isLogin) { SmartDialog.showToast('登录成功,当前采用「' - '${GStorage.setting.get(SettingBoxKey.defaultRcmdType, defaultValue: 'app')}' + '${GStorage.setting.get(SettingBoxKey.defaultRcmdType, defaultValue: 'web')}' '端」推荐'); - Box userInfoCache = GStorage.userInfo; - await userInfoCache.put('userInfoCache', result['data']); - final HomeController homeCtr = Get.find(); - homeCtr.updateLoginStatus(true); - homeCtr.userFace.value = result['data'].face; - final MediaController? mediaCtr = Get.isRegistered() - ? Get.find() - : null; - mediaCtr?.mid = result['data'].mid; + await GStorage.userInfo.put('userInfoCache', result['data']); + try { + final HomeController homeCtr = Get.find(); + homeCtr.updateLoginStatus(true); + homeCtr.userFace.value = result['data'].face; + final MediaController mediaCtr = Get.find(); + mediaCtr.mid = result['data'].mid; + } catch (_) {} await LoginUtils.refreshLoginStatus(true); } else { // 获取用户信息失败 @@ -300,112 +286,145 @@ class LoginPageController extends GetxController ); if (res['status']) { var data = res['data']; - for (var key in data.keys) { - debugPrint('$key: ${data[key]}'); - } if (data == null) { SmartDialog.showToast('登录异常,接口未返回数据:${res["msg"]}'); return; } if (data['status'] == 2) { SmartDialog.showToast(data['message']); - return; - //{"code":0,"message":"0","ttl":1,"data":{"status":2,"message":"本次登录环境存在风险, 需使用手机号进行验证或绑定","url":"https://passport.bilibili.com/h5-app/passport/risk/verify?tmp_token=9e785433940891dfa78f033fb7928181&request_id=e5a6d6480df04097870be56c6e60f7ef&source=risk","token_info":null,"cookie_info":null,"sso":null,"is_new":false,"is_tourist":false}} - //todo: 后续登录流程:https://ivan.hanloth.cn/archives/530/ - // String Url = data['url']!; - // Uri currentUri = Uri.parse(Url); - // var safeCenterRes = await LoginHttp.safeCenterGetInfo( - // tmpCode: currentUri.queryParameters['tmp_token']!); - // //{"code":0,"message":"0","ttl":1,"data":{"account_info":{"hide_tel":"111*****111","hide_mail":"aaa*****aaaa.aaa","bind_mail":true,"bind_tel":true,"tel_verify":true,"mail_verify":true,"unneeded_check":false,"bind_safe_question":false,"mid":1111111},"member_info":{"nickname":"xxxxxxx","face":"https://i0.hdslb.com/bfs/face/xxxxxxx.jpg","realname_status":false},"sns_info":{"bind_google":false,"bind_fb":false,"bind_apple":false,"bind_qq":true,"bind_weibo":true,"bind_wechat":false},"account_safe":{"score":80}}} - // if (!safeCenterRes['status']) { - // SmartDialog.showToast("获取安全验证信息失败,请尝试其它登录方式\n" - // "(${safeCenterRes['code']}) ${safeCenterRes['msg']}"); - // return; - // } - // Map accountInfo = { - // "hindTel": safeCenterRes['data']['account_info']!["hide_tel"], - // "hindMail": safeCenterRes['data']['account_info']!["hide_mail"], - // }; - // if (!safeCenterRes['data']['account_info']!['tel_verify']) { - // SmartDialog.showToast("当前账号未支持手机号验证,请尝试其它登录方式"); - // return; - // } - // TextEditingController _textFieldController = TextEditingController(); - // String captchaKey = ''; - // Get.dialog(AlertDialog( - // title: const Text("本次登录需要验证您的手机号"), - // content: Column(children: [ - // Text(accountInfo['hindTel'] ?? '未能获取手机号'), - // TextField( - // controller: _textFieldController, - // decoration: const InputDecoration(hintText: "请输入短信验证码"), - // ), - // ]), - // actions: [ - // TextButton( - // child: const Text("发送验证码 "), - // onPressed: () async { - // var preCaptureRes = await LoginHttp.preCapture(); - // if (!preCaptureRes['status']) { - // SmartDialog.showToast("获取验证码失败,请尝试其它登录方式\n" - // "(${preCaptureRes['code']}) ${preCaptureRes['msg']}"); - // return; - // } - // String geeGt = preCaptureRes['data']['gee_gt']!; - // String geeChallenge = preCaptureRes['data']['gee_challenge']; - // captchaData.token = preCaptureRes['data']['recaptcha_token']!; - - // getCaptcha(geeGt, geeChallenge, () async { - // var safeCenterSendSmsCodeRes = - // await LoginHttp.safeCenterSmsCode( - // tmpCode: currentUri.queryParameters['tmp_token']!, - // geeChallenge: geeChallenge, - // geeSeccode: captchaData.seccode!, - // geeValidate: captchaData.validate!, - // recaptchaToken: captchaData.token!); - // if (!safeCenterSendSmsCodeRes['status']) { - // SmartDialog.showToast("发送短信验证码失败,请尝试其它登录方式\n" - // "(${safeCenterSendSmsCodeRes['code']}) ${safeCenterSendSmsCodeRes['msg']}"); - // return; - // } - // SmartDialog.showToast("短信验证码已发送,请查收"); - // captchaKey = safeCenterSendSmsCodeRes['data']['captcha_key']; - // }); - // }, - // ), - // TextButton( - // onPressed: Get.back, - // child: const Text(" 取消"), - // ), - // TextButton( - // onPressed: () async { - // String? code = _textFieldController.text; - // if (code.isEmpty) { - // SmartDialog.showToast("请输入短信验证码"); - // return; - // } - // var safeCenterSmsVerifyRes = - // await LoginHttp.safeCenterSmsVerify( - // code: code, - // tmpCode: currentUri.queryParameters['tmp_token']!, - // requestId: currentUri.queryParameters['request_id']!, - // source: currentUri.queryParameters['source']!, - // captchaKey: captchaKey, - // ); - // if (!safeCenterSmsVerifyRes['status']) { - // SmartDialog.showToast("验证短信验证码失败,请尝试其它登录方式\n" - // "(${safeCenterSmsVerifyRes['code']}) ${safeCenterSmsVerifyRes['msg']}"); - // return; - // } - // SmartDialog.showToast("验证成功,正在登录"); - // // loginByPassword(); - // }, - // child: const Text("确认"), - // ), - // ], - // )); - // return; + //{"code":0,"message":"0","ttl":1,"data":{"status":2,"message":"本次登录环境存在风险, 需使用手机号进行验证或绑定","url":"https://passport.bilibili.com/h5-app/passport/risk/verify?tmp_token=9e785433940891dfa78f033fb7928181&request_id=e5a6d6480df04097870be56c6e60f7ef&source=risk","token_info":null,"cookie_info":null,"sso":null,"is_new":false,"is_tourist":false}} + String url = data['url']!; + Uri currentUri = Uri.parse(url); + var safeCenterRes = await LoginHttp.safeCenterGetInfo( + tmpCode: currentUri.queryParameters['tmp_token']!); + //{"code":0,"message":"0","ttl":1,"data":{"account_info":{"hide_tel":"111*****111","hide_mail":"aaa*****aaaa.aaa","bind_mail":true,"bind_tel":true,"tel_verify":true,"mail_verify":true,"unneeded_check":false,"bind_safe_question":false,"mid":1111111},"member_info":{"nickname":"xxxxxxx","face":"https://i0.hdslb.com/bfs/face/xxxxxxx.jpg","realname_status":false},"sns_info":{"bind_google":false,"bind_fb":false,"bind_apple":false,"bind_qq":true,"bind_weibo":true,"bind_wechat":false},"account_safe":{"score":80}}} + if (!safeCenterRes['status']) { + SmartDialog.showToast("获取安全验证信息失败,请尝试其它登录方式\n" + "(${safeCenterRes['code']}) ${safeCenterRes['msg']}"); + return; + } + Map accountInfo = { + "hindTel": safeCenterRes['data']['account_info']!["hide_tel"], + "hindMail": safeCenterRes['data']['account_info']!["hide_mail"], + }; + if (!safeCenterRes['data']['account_info']!['tel_verify']) { + SmartDialog.showToast("当前账号未支持手机号验证,请尝试其它登录方式"); + return; + } + TextEditingController textFieldController = TextEditingController(); + String captchaKey = ''; + Get.dialog(AlertDialog( + title: const Text("本次登录需要验证您的手机号"), + content: Column(mainAxisSize: MainAxisSize.min, children: [ + Text( + accountInfo['hindTel'] ?? '未能获取手机号', + style: const TextStyle(fontSize: 20), + ), + const SizedBox(height: 10), + // 带有清空按钮的输入框 + + TextField( + controller: textFieldController, + textAlign: TextAlign.center, + decoration: InputDecoration( + hintText: "请输入短信验证码", + suffixIcon: IconButton( + icon: const Icon(Icons.clear), + onPressed: textFieldController.clear, + ), + ), + ), + ]), + actions: [ + TextButton( + child: const Text("发送验证码 "), + onPressed: () async { + var preCaptureRes = await LoginHttp.preCapture(); + if (!preCaptureRes['status'] || preCaptureRes['data'] == null) { + SmartDialog.showToast("获取验证码失败,请尝试其它登录方式\n" + "(${preCaptureRes['code']}) ${preCaptureRes['msg']} ${preCaptureRes['data']}"); + } + String? geeGt = preCaptureRes['data']['gee_gt']; + String? geeChallenge = preCaptureRes['data']['gee_challenge']; + captchaData.token = preCaptureRes['data']['recaptcha_token']; + if (!isGeeArgumentValid(geeGt, geeChallenge)) { + SmartDialog.showToast("获取极验参数为空,请尝试其它登录方式\n" + "(${preCaptureRes['code']}) ${preCaptureRes['msg']} ${preCaptureRes['data']}"); + return; + } + + getCaptcha(geeGt, geeChallenge, () async { + var safeCenterSendSmsCodeRes = + await LoginHttp.safeCenterSmsCode( + tmpCode: currentUri.queryParameters['tmp_token']!, + geeChallenge: geeChallenge, + geeSeccode: captchaData.seccode, + geeValidate: captchaData.validate, + recaptchaToken: captchaData.token, + refererUrl: url, + ); + if (!safeCenterSendSmsCodeRes['status']) { + SmartDialog.showToast("发送短信验证码失败,请尝试其它登录方式\n" + "(${safeCenterSendSmsCodeRes['code']}) ${safeCenterSendSmsCodeRes['msg']}"); + return; + } + SmartDialog.showToast("短信验证码已发送,请查收"); + captchaKey = safeCenterSendSmsCodeRes['data']['captcha_key']; + }); + }, + ), + TextButton( + onPressed: Get.back, + child: const Text(" 取消"), + ), + TextButton( + onPressed: () async { + String? code = textFieldController.text; + if (code.isEmpty) { + SmartDialog.showToast("请输入短信验证码"); + return; + } + var safeCenterSmsVerifyRes = + await LoginHttp.safeCenterSmsVerify( + code: code, + tmpCode: currentUri.queryParameters['tmp_token']!, + requestId: currentUri.queryParameters['request_id']!, + source: currentUri.queryParameters['source']!, + captchaKey: captchaKey, + refererUrl: url, + ); + if (!safeCenterSmsVerifyRes['status']) { + SmartDialog.showToast("验证短信验证码失败,请尝试其它登录方式\n" + "(${safeCenterSmsVerifyRes['code']}) ${safeCenterSmsVerifyRes['msg']}"); + return; + } + SmartDialog.showToast("验证成功,正在登录"); + var oauth2AccessTokenRes = await LoginHttp.oauth2AccessToken( + code: safeCenterSmsVerifyRes['data']['code'], + ); + if (!oauth2AccessTokenRes['status']) { + SmartDialog.showToast("登录失败,请尝试其它登录方式\n" + "(${oauth2AccessTokenRes['code']}) ${oauth2AccessTokenRes['msg']}"); + return; + } + var data = oauth2AccessTokenRes['data']; + if (data['token_info'] == null || data['cookie_info'] == null) { + SmartDialog.showToast( + '登录异常,接口未返回身份信息,可能是因为账号风控,请尝试其它登录方式。\n${oauth2AccessTokenRes["msg"]},\n $data'); + return; + } + SmartDialog.showToast('正在保存身份信息'); + await afterLoginByApp(data['token_info'], data['cookie_info']); + Get.back(); + Get.back(); + }, + child: const Text("确认"), + ), + ], + )); + + return; } if (data['token_info'] == null || data['cookie_info'] == null) { SmartDialog.showToast( @@ -476,9 +495,6 @@ class LoginPageController extends GetxController if (res['status']) { SmartDialog.showToast('登录成功'); var data = res['data']; - for (var key in data.keys) { - debugPrint('$key: ${data[key]}'); - } await afterLoginByApp(data['token_info'], data['cookie_info']); Get.back(); } else { @@ -505,6 +521,34 @@ class LoginPageController extends GetxController // guestId = guestIdRes['data']['guest_id']; // } // } + // var preCaptureRes = await LoginHttp.preCapture(); + // if (!preCaptureRes['status']) { + // SmartDialog.showToast("获取验证码失败,请尝试其它登录方式\n" + // "(${preCaptureRes['code']}) ${preCaptureRes['msg']}"); + // return; + // } + // String geeGt = preCaptureRes['data']['gee_gt']!; + // String geeChallenge = preCaptureRes['data']['gee_challenge']; + // captchaData.token = preCaptureRes['data']['recaptcha_token']!; + + // getCaptcha(geeGt, geeChallenge, () async { + + // var safeCenterSendSmsCodeRes = + // await LoginHttp.safeCenterSmsCode( + // tmpCode: currentUri.queryParameters['tmp_token']!, + // geeChallenge: geeChallenge, + // geeSeccode: captchaData.seccode!, + // geeValidate: captchaData.validate!, + // recaptchaToken: captchaData.token!, + // refererUrl: url, + // ); + // if (!safeCenterSendSmsCodeRes['status']) { + // SmartDialog.showToast("发送短信验证码失败,请尝试其它登录方式\n" + // "(${safeCenterSendSmsCodeRes['code']}) ${safeCenterSendSmsCodeRes['msg']}"); + // return; + // } + // SmartDialog.showToast("短信验证码已发送,请查收"); + // captchaKey = safeCenterSendSmsCodeRes['data']['captcha_key']; var res = await LoginHttp.sendSmsCode( tel: telTextController.text, @@ -533,15 +577,34 @@ class LoginPageController extends GetxController switch (res['code']) { case 0: case -105: - String captureUrl = res['data']?['recaptcha_url'] ?? ""; - if (captureUrl == "") { - SmartDialog.showToast('验证信息错误:${res["msg"]}\n返回内容:${res["data"]}'); + String? captureUrl = res['data']?['recaptcha_url']; + String? geeGt; + String? geeChallenge; + if (captureUrl != null && captureUrl.isNotEmpty) { + Uri captureUri = Uri.parse(captureUrl); + captchaData.token = captureUri.queryParameters['recaptcha_token']; + geeGt = captureUri.queryParameters['gee_gt']; + geeChallenge = captureUri.queryParameters['gee_challenge']; + } + + if (!isGeeArgumentValid(geeGt, geeChallenge)) { + debugPrint('验证信息错误:${res["msg"]}\n返回内容:${res["data"]},尝试另一个验证码接口'); + var preCaptureRes = await LoginHttp.preCapture(); + if (!preCaptureRes['status'] || preCaptureRes['data'] == null) { + SmartDialog.showToast("获取验证码失败,请尝试其它登录方式\n" + "(${preCaptureRes['code']}) ${preCaptureRes['msg']} ${preCaptureRes['data']}"); + return; + } + geeGt = preCaptureRes['data']['gee_gt']; + geeChallenge = preCaptureRes['data']['gee_challenge']; + captchaData.token = preCaptureRes['data']['recaptcha_token']; + } + + if (!isGeeArgumentValid(geeGt, geeChallenge)) { + SmartDialog.showToast("获取验证码失败,请尝试其它登录方式\n"); return; } - Uri captureUri = Uri.parse(captureUrl); - captchaData.token = captureUri.queryParameters['recaptcha_token']!; - String geeGt = captureUri.queryParameters['gee_gt']!; - String geeChallenge = captureUri.queryParameters['gee_challenge']!; + getCaptcha(geeGt, geeChallenge, () { sendSmsCode(); }); @@ -552,5 +615,12 @@ class LoginPageController extends GetxController break; } } + // }); + } + + bool isGeeArgumentValid(String? geeGt, String? geeChallenge) { + return geeGt?.isNotEmpty == true && + geeChallenge?.isNotEmpty == true && + captchaData.token?.isNotEmpty == true; } } diff --git a/lib/pages/login/view.dart b/lib/pages/login/view.dart index 2badccf4..ed26def8 100644 --- a/lib/pages/login/view.dart +++ b/lib/pages/login/view.dart @@ -8,7 +8,6 @@ import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:qr_flutter/qr_flutter.dart'; import 'package:saver_gallery/saver_gallery.dart'; -import 'package:url_launcher/url_launcher_string.dart'; import 'controller.dart'; @@ -26,21 +25,6 @@ class _LoginPageState extends State { bool showPassword = false; GlobalKey globalKey = GlobalKey(); - @override - void initState() { - super.initState(); - } - - @override - void dispose() { - _loginPageCtr.dispose(); - _loginPageCtr.telTextController.dispose(); - _loginPageCtr.usernameTextController.dispose(); - _loginPageCtr.passwordTextController.dispose(); - _loginPageCtr.smsCodeTextController.dispose(); - super.dispose(); - } - Widget loginByQRCode() { return Column( children: [ @@ -48,8 +32,10 @@ class _LoginPageState extends State { const Text('使用 bilibili 官方 App 扫码登录'), const SizedBox(height: 20), Obx(() => Text('剩余有效时间: ${_loginPageCtr.qrCodeLeftTime} 秒', - style: - const TextStyle(fontFeatures: [FontFeature.tabularFigures()]))), + style: TextStyle( + fontFeatures: const [FontFeature.tabularFigures()], + color: Theme.of(context).colorScheme.primaryFixedDim))), + const SizedBox(height: 5), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ @@ -94,8 +80,18 @@ class _LoginPageState extends State { ), RepaintBoundary( key: globalKey, - child: Obx( - () => QrImageView( + child: Obx(() { + if (_loginPageCtr.codeInfo.value['data']?['url'] == null) { + return Container( + height: 200, + width: 200, + alignment: Alignment.center, + child: CircularProgressIndicator( + semanticsLabel: '二维码加载中', + ), + ); + } + return QrImageView( backgroundColor: Colors.white, eyeStyle: QrEyeStyle( eyeShape: QrEyeShape.square, @@ -105,33 +101,39 @@ class _LoginPageState extends State { dataModuleShape: QrDataModuleShape.square, color: Colors.black87, ), - data: _loginPageCtr.codeInfo.value['data']?['url'] ?? "", + data: _loginPageCtr.codeInfo.value['data']!['url']!, size: 200, semanticsLabel: '二维码', - ), - ), + ); + }), ), const SizedBox(height: 10), - Obx(() => Text(_loginPageCtr.statusQRCode.value)), + Obx(() => Text( + _loginPageCtr.statusQRCode.value, + style: TextStyle( + color: Theme.of(context).colorScheme.secondaryFixedDim), + )), Obx(() => GestureDetector( onTap: () { //以外部方式打开此链接 - launchUrlString( - _loginPageCtr.codeInfo.value['data']?['url'] ?? "", - mode: LaunchMode.externalApplication); + // launchUrlString( + // _loginPageCtr.codeInfo.value['data']?['url'] ?? "", + // mode: LaunchMode.externalApplication); + // 复制到剪贴板 + Clipboard.setData(ClipboardData( + text: _loginPageCtr.codeInfo.value['data']?['url'] ?? "")); + SmartDialog.showToast('已复制到剪贴板,可粘贴至已登录的app私信处发送,然后点击已发送的链接打开', + displayTime: const Duration(seconds: 5)); }, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 20), - child: Text( - _loginPageCtr.codeInfo.value['data']?['url'] ?? "", - style: Theme.of(context).textTheme.labelSmall!.copyWith( + child: Text(_loginPageCtr.codeInfo.value['data']?['url'] ?? "", + style: Theme.of(context).textTheme.labelSmall!.copyWith( color: Theme.of(context) .colorScheme .onSurface - .withOpacity(0.4), - ), - ), + .withOpacity(0.4))), ), )), Padding( @@ -177,7 +179,7 @@ class _LoginPageState extends State { inputFormatters: [FilteringTextInputFormatter.deny(RegExp(r"\s"))], controller: _loginPageCtr.passwordTextController, decoration: InputDecoration( - prefixIcon: const Icon(Icons.lock), + prefixIcon: const Icon(Icons.password), border: const UnderlineInputBorder(), labelText: '密码', suffixIcon: IconButton( @@ -265,7 +267,7 @@ class _LoginPageState extends State { ), OutlinedButton.icon( onPressed: _loginPageCtr.loginByPassword, - icon: const Icon(Icons.login_outlined), + icon: const Icon(Icons.login), label: const Text('登录'), ), const SizedBox(height: 20), @@ -377,7 +379,7 @@ class _LoginPageState extends State { child: TextField( controller: _loginPageCtr.smsCodeTextController, decoration: const InputDecoration( - prefixIcon: Icon(Icons.key), + prefixIcon: Icon(Icons.sms), border: InputBorder.none, labelText: '验证码', ), @@ -402,7 +404,7 @@ class _LoginPageState extends State { const SizedBox(height: 20), OutlinedButton.icon( onPressed: _loginPageCtr.loginBySmsCode, - icon: const Icon(Icons.login_outlined), + icon: const Icon(Icons.login), label: const Text('登录'), ), const SizedBox(height: 20), @@ -440,17 +442,23 @@ class _LoginPageState extends State { dividerHeight: 0, tabs: const [ Tab( - child: Row( - mainAxisSize: MainAxisSize.min, - children: [Icon(Icons.lock), Text(' 密码')])), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [Icon(Icons.password), Text(' 密码')], + ), + ), Tab( - child: Row( - mainAxisSize: MainAxisSize.min, - children: [Icon(Icons.key), Text(' 短信')])), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [Icon(Icons.sms), Text(' 短信')], + ), + ), Tab( - child: Row( - mainAxisSize: MainAxisSize.min, - children: [Icon(Icons.qr_code), Text(' 扫码')])), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [Icon(Icons.qr_code), Text(' 扫码')], + ), + ), ], controller: _loginPageCtr.tabController, )) @@ -459,8 +467,8 @@ class _LoginPageState extends State { bottom: orientation == Orientation.portrait ? TabBar( tabs: const [ - Tab(icon: Icon(Icons.lock), text: '密码'), - Tab(icon: Icon(Icons.key), text: '短信'), + Tab(icon: Icon(Icons.password), text: '密码'), + Tab(icon: Icon(Icons.sms), text: '短信'), Tab(icon: Icon(Icons.qr_code), text: '扫码'), ], controller: _loginPageCtr.tabController,