diff --git a/lib/common/constants.dart b/lib/common/constants.dart index 067e29254..7a0b31d51 100644 --- a/lib/common/constants.dart +++ b/lib/common/constants.dart @@ -17,4 +17,223 @@ class Constants { static const String thirdSign = '04224646d1fea004e79606d3b038c84a'; static const String thirdApi = 'https://www.mcbbs.net/template/mcbbs/image/special_photo_bg.png'; + + //内容来自 https://passport.bilibili.com/web/generic/country/list + static const List> internationalDialingPrefix = [ + {"id": 1, "cname": "中国大陆", "country_id": "86"}, + {"id": 5, "cname": "中国香港特别行政区", "country_id": "852"}, + {"id": 2, "cname": "中国澳门特别行政区", "country_id": "853"}, + {"id": 3, "cname": "中国台湾", "country_id": "886"}, + {"id": 4, "cname": "美国", "country_id": "1"}, + {"id": 6, "cname": "比利时", "country_id": "32"}, + {"id": 7, "cname": "澳大利亚", "country_id": "61"}, + {"id": 8, "cname": "法国", "country_id": "33"}, + {"id": 9, "cname": "加拿大", "country_id": "1"}, + {"id": 10, "cname": "日本", "country_id": "81"}, + {"id": 11, "cname": "新加坡", "country_id": "65"}, + {"id": 12, "cname": "韩国", "country_id": "82"}, + {"id": 13, "cname": "马来西亚", "country_id": "60"}, + {"id": 14, "cname": "英国", "country_id": "44"}, + {"id": 15, "cname": "意大利", "country_id": "39"}, + {"id": 16, "cname": "德国", "country_id": "49"}, + {"id": 18, "cname": "俄罗斯", "country_id": "7"}, + {"id": 19, "cname": "新西兰", "country_id": "64"}, //common:1-19 + {"id": 153, "cname": "瓦利斯群岛和富图纳群岛", "country_id": "1681"}, + {"id": 152, "cname": "葡萄牙", "country_id": "351"}, + {"id": 151, "cname": "帕劳", "country_id": "680"}, + {"id": 150, "cname": "诺福克岛", "country_id": "672"}, + {"id": 149, "cname": "挪威", "country_id": "47"}, + {"id": 148, "cname": "纽埃岛", "country_id": "683"}, + {"id": 147, "cname": "尼日利亚", "country_id": "234"}, + {"id": 146, "cname": "尼日尔", "country_id": "227"}, + {"id": 145, "cname": "尼加拉瓜", "country_id": "505"}, + {"id": 144, "cname": "尼泊尔", "country_id": "977"}, + {"id": 143, "cname": "瑙鲁", "country_id": "674"}, + {"id": 154, "cname": "格鲁吉亚", "country_id": "995"}, + {"id": 155, "cname": "瑞典", "country_id": "46"}, + {"id": 165, "cname": "沙特阿拉伯", "country_id": "966"}, + {"id": 164, "cname": "桑给巴尔岛", "country_id": "259"}, + {"id": 163, "cname": "塞舌尔共和国", "country_id": "248"}, + {"id": 162, "cname": "塞浦路斯", "country_id": "357"}, + {"id": 161, "cname": "塞内加尔", "country_id": "221"}, + {"id": 160, "cname": "塞拉利昂", "country_id": "232"}, + {"id": 159, "cname": "萨摩亚,东部", "country_id": "684"}, + {"id": 158, "cname": "萨摩亚,西部", "country_id": "685"}, + {"id": 157, "cname": "萨尔瓦多", "country_id": "503"}, + {"id": 156, "cname": "瑞士", "country_id": "41"}, + {"id": 166, "cname": "圣多美和普林西比", "country_id": "239"}, + {"id": 142, "cname": "塞尔维亚", "country_id": "381"}, + {"id": 141, "cname": "南非", "country_id": "27"}, + {"id": 128, "cname": "毛里塔尼亚", "country_id": "222"}, + {"id": 127, "cname": "毛里求斯", "country_id": "230"}, + {"id": 126, "cname": "马歇尔岛", "country_id": "692"}, + {"id": 125, "cname": "马提尼克岛", "country_id": "596"}, + {"id": 124, "cname": "马其顿", "country_id": "389"}, + {"id": 123, "cname": "马里亚纳岛", "country_id": "1670"}, + {"id": 122, "cname": "马里", "country_id": "223"}, + {"id": 121, "cname": "马拉维", "country_id": "265"}, + {"id": 120, "cname": "马耳他", "country_id": "356"}, + {"id": 119, "cname": "马尔代夫", "country_id": "960"}, + {"id": 129, "cname": "蒙古", "country_id": "976"}, + {"id": 130, "cname": "蒙特塞拉特岛", "country_id": "1664"}, + {"id": 140, "cname": "纳米比亚", "country_id": "264"}, + {"id": 139, "cname": "墨西哥", "country_id": "52"}, + {"id": 138, "cname": "莫桑比克", "country_id": "258"}, + {"id": 137, "cname": "摩纳哥", "country_id": "377"}, + {"id": 136, "cname": "摩洛哥", "country_id": "212"}, + {"id": 135, "cname": "摩尔多瓦", "country_id": "373"}, + {"id": 134, "cname": "缅甸", "country_id": "95"}, + {"id": 133, "cname": "密克罗尼西亚", "country_id": "691"}, + {"id": 132, "cname": "秘鲁", "country_id": "51"}, + {"id": 131, "cname": "孟加拉国", "country_id": "880"}, + {"id": 118, "cname": "马达加斯加", "country_id": "261"}, + {"id": 167, "cname": "圣卢西亚", "country_id": "1784"}, + {"id": 216, "cname": "智利", "country_id": "56"}, + {"id": 203, "cname": "牙买加", "country_id": "1876"}, + {"id": 202, "cname": "叙利亚", "country_id": "963"}, + {"id": 201, "cname": "匈牙利", "country_id": "36"}, + {"id": 200, "cname": "科特迪瓦", "country_id": "225"}, + {"id": 199, "cname": "希腊", "country_id": "30"}, + {"id": 198, "cname": "西班牙", "country_id": "34"}, + {"id": 197, "cname": "乌兹别克斯坦", "country_id": "998"}, + {"id": 196, "cname": "乌拉圭", "country_id": "598"}, + {"id": 195, "cname": "乌克兰", "country_id": "380"}, + {"id": 194, "cname": "乌干达", "country_id": "256"}, + {"id": 204, "cname": "亚美尼亚", "country_id": "374"}, + {"id": 205, "cname": "也门", "country_id": "967"}, + {"id": 215, "cname": "直布罗陀", "country_id": "350"}, + {"id": 214, "cname": "乍得", "country_id": "235"}, + {"id": 213, "cname": "赞比亚", "country_id": "260"}, + {"id": 212, "cname": "越南", "country_id": "84"}, + {"id": 211, "cname": "约旦", "country_id": "962"}, + {"id": 210, "cname": "印尼", "country_id": "62"}, + {"id": 209, "cname": "印度", "country_id": "91"}, + {"id": 208, "cname": "以色列", "country_id": "972"}, + {"id": 207, "cname": "伊朗", "country_id": "98"}, + {"id": 206, "cname": "伊拉克", "country_id": "964"}, + {"id": 193, "cname": "文莱", "country_id": "673"}, + {"id": 192, "cname": "委内瑞拉", "country_id": "58"}, + {"id": 191, "cname": "维珍群岛(英属)", "country_id": "1284"}, + {"id": 178, "cname": "泰国", "country_id": "66"}, + {"id": 177, "cname": "索马里", "country_id": "252"}, + {"id": 176, "cname": "所罗门群岛", "country_id": "677"}, + {"id": 175, "cname": "苏里南", "country_id": "597"}, + {"id": 174, "cname": "苏丹", "country_id": "249"}, + {"id": 173, "cname": "斯威士兰", "country_id": "268"}, + {"id": 172, "cname": "斯洛文尼亚", "country_id": "386"}, + {"id": 171, "cname": "斯洛伐克", "country_id": "421"}, + {"id": 170, "cname": "斯里兰卡", "country_id": "94"}, + {"id": 169, "cname": "圣皮埃尔和密克隆群岛", "country_id": "508"}, + {"id": 179, "cname": "坦桑尼亚", "country_id": "255"}, + {"id": 180, "cname": "汤加", "country_id": "676"}, + {"id": 190, "cname": "维珍群岛(美属)", "country_id": "1340"}, + {"id": 189, "cname": "瓦努阿图", "country_id": "678"}, + {"id": 188, "cname": "托克劳岛", "country_id": "690"}, + {"id": 187, "cname": "土库曼斯坦", "country_id": "993"}, + {"id": 186, "cname": "土耳其", "country_id": "90"}, + {"id": 185, "cname": "图瓦卢", "country_id": "688"}, + {"id": 184, "cname": "突尼斯", "country_id": "216"}, + {"id": 183, "cname": "阿森松岛", "country_id": "247"}, + {"id": 182, "cname": "特立尼达和多巴哥", "country_id": "1868"}, + {"id": 181, "cname": "特克斯和凯科斯", "country_id": "1649"}, + {"id": 168, "cname": "圣马力诺", "country_id": "378"}, + {"id": 67, "cname": "法属圭亚那", "country_id": "594"}, + {"id": 54, "cname": "不丹", "country_id": "975"}, + {"id": 53, "cname": "博茨瓦纳", "country_id": "267"}, + {"id": 52, "cname": "伯利兹", "country_id": "501"}, + {"id": 51, "cname": "玻利维亚", "country_id": "591"}, + {"id": 50, "cname": "波兰", "country_id": "48"}, + {"id": 49, "cname": "波黑", "country_id": "387"}, + {"id": 48, "cname": "波多黎各", "country_id": "1787"}, + {"id": 47, "cname": "冰岛", "country_id": "354"}, + {"id": 46, "cname": "贝宁", "country_id": "229"}, + {"id": 45, "cname": "保加利亚", "country_id": "359"}, + {"id": 55, "cname": "布基纳法索", "country_id": "226"}, + {"id": 56, "cname": "布隆迪", "country_id": "257"}, + {"id": 66, "cname": "法属波利尼西亚", "country_id": "689"}, + {"id": 65, "cname": "法罗岛", "country_id": "298"}, + {"id": 64, "cname": "厄立特里亚", "country_id": "291"}, + {"id": 63, "cname": "厄瓜多尔", "country_id": "593"}, + {"id": 62, "cname": "多米尼加代表", "country_id": "1809"}, + {"id": 61, "cname": "多米尼加", "country_id": "1767"}, + {"id": 60, "cname": "多哥", "country_id": "228"}, + {"id": 59, "cname": "迪戈加西亚岛", "country_id": "246"}, + {"id": 58, "cname": "丹麦", "country_id": "45"}, + {"id": 57, "cname": "赤道几内亚", "country_id": "240"}, + {"id": 44, "cname": "百慕大群岛", "country_id": "1441"}, + {"id": 43, "cname": "白俄罗斯", "country_id": "375"}, + {"id": 42, "cname": "巴西", "country_id": "55"}, + {"id": 29, "cname": "爱尔兰", "country_id": "353"}, + {"id": 28, "cname": "埃塞俄比亚", "country_id": "251"}, + {"id": 27, "cname": "埃及", "country_id": "20"}, + {"id": 26, "cname": "阿塞拜疆", "country_id": "994"}, + {"id": 25, "cname": "阿曼", "country_id": "968"}, + {"id": 24, "cname": "阿联酋", "country_id": "971"}, + {"id": 23, "cname": "阿根廷", "country_id": "54"}, + {"id": 22, "cname": "阿富汗", "country_id": "93"}, + {"id": 21, "cname": "阿尔及利亚", "country_id": "213"}, + {"id": 20, "cname": "阿尔巴尼亚", "country_id": "355"}, + {"id": 30, "cname": "爱沙尼亚", "country_id": "372"}, + {"id": 31, "cname": "安道尔", "country_id": "376"}, + {"id": 41, "cname": "巴拿马", "country_id": "507"}, + {"id": 40, "cname": "巴林", "country_id": "973"}, + {"id": 39, "cname": "巴拉圭", "country_id": "595"}, + {"id": 38, "cname": "巴基斯坦", "country_id": "92"}, + {"id": 37, "cname": "巴哈马群岛", "country_id": "1242"}, + {"id": 36, "cname": "巴布亚新几内亚", "country_id": "675"}, + {"id": 35, "cname": "巴巴多斯", "country_id": "1246"}, + {"id": 34, "cname": "奥地利", "country_id": "43"}, + {"id": 33, "cname": "安提瓜岛和巴布达", "country_id": "1268"}, + {"id": 32, "cname": "安哥拉", "country_id": "244"}, + {"id": 68, "cname": "非洲中部", "country_id": "236"}, + {"id": 117, "cname": "罗马尼亚", "country_id": "40"}, + {"id": 104, "cname": "科威特", "country_id": "965"}, + {"id": 103, "cname": "科摩罗", "country_id": "269"}, + {"id": 102, "cname": "开曼群岛", "country_id": "1345"}, + {"id": 101, "cname": "卡塔尔", "country_id": "974"}, + {"id": 100, "cname": "喀麦隆", "country_id": "237"}, + {"id": 99, "cname": "聚会岛", "country_id": "262"}, + {"id": 98, "cname": "津巴布韦", "country_id": "263"}, + {"id": 97, "cname": "捷克", "country_id": "420"}, + {"id": 96, "cname": "柬埔寨", "country_id": "855"}, + {"id": 95, "cname": "加蓬", "country_id": "241"}, + {"id": 105, "cname": "克罗地亚", "country_id": "385"}, + {"id": 106, "cname": "肯尼亚", "country_id": "254"}, + {"id": 116, "cname": "卢旺达", "country_id": "250"}, + {"id": 115, "cname": "卢森堡", "country_id": "352"}, + {"id": 114, "cname": "利比亚", "country_id": "218"}, + {"id": 113, "cname": "利比里亚", "country_id": "231"}, + {"id": 112, "cname": "立陶宛", "country_id": "370"}, + {"id": 111, "cname": "黎巴嫩", "country_id": "961"}, + {"id": 110, "cname": "老挝", "country_id": "856"}, + {"id": 109, "cname": "莱索托", "country_id": "266"}, + {"id": 108, "cname": "拉脱维亚", "country_id": "371"}, + {"id": 107, "cname": "库克岛", "country_id": "682"}, + {"id": 94, "cname": "加纳", "country_id": "233"}, + {"id": 93, "cname": "几内亚比绍", "country_id": "245"}, + {"id": 92, "cname": "几内亚", "country_id": "224"}, + {"id": 79, "cname": "格林纳达", "country_id": "1473"}, + {"id": 78, "cname": "哥斯达黎加", "country_id": "506"}, + {"id": 77, "cname": "哥伦比亚", "country_id": "57"}, + {"id": 76, "cname": "刚果(金)", "country_id": "243"}, + {"id": 75, "cname": "刚果", "country_id": "242"}, + {"id": 74, "cname": "冈比亚", "country_id": "220"}, + {"id": 73, "cname": "福克兰岛", "country_id": "500"}, + {"id": 72, "cname": "佛得角", "country_id": "238"}, + {"id": 71, "cname": "芬兰", "country_id": "358"}, + {"id": 70, "cname": "斐济", "country_id": "679"}, + {"id": 80, "cname": "格陵兰岛", "country_id": "299"}, + {"id": 81, "cname": "古巴", "country_id": "53"}, + {"id": 91, "cname": "吉尔吉斯斯坦", "country_id": "996"}, + {"id": 90, "cname": "吉布提", "country_id": "253"}, + {"id": 89, "cname": "基里巴斯", "country_id": "686"}, + {"id": 88, "cname": "维克岛", "country_id": "1808"}, + {"id": 87, "cname": "洪都拉斯", "country_id": "504"}, + {"id": 86, "cname": "荷兰", "country_id": "31"}, + {"id": 85, "cname": "朝鲜", "country_id": "850"}, + {"id": 84, "cname": "海地", "country_id": "509"}, + {"id": 83, "cname": "关岛", "country_id": "1671"}, + {"id": 82, "cname": "瓜德罗普岛", "country_id": "590"}, + {"id": 69, "cname": "菲律宾", "country_id": "63"} + ]; } diff --git a/lib/common/widgets/video_popup_menu.dart b/lib/common/widgets/video_popup_menu.dart index 036e3ea3c..08b8d4180 100644 --- a/lib/common/widgets/video_popup_menu.dart +++ b/lib/common/widgets/video_popup_menu.dart @@ -77,7 +77,7 @@ class VideoPopupMenu extends StatelessWidget { String? accessKey = GStorage.localCache .get(LocalCacheKey.accessKey, defaultValue: {})['value']; if (accessKey == null || accessKey == "") { - SmartDialog.showToast("本操作使用app端接口,请前往【隐私设置】刷新access_key"); + SmartDialog.showToast("请退出账号后重新登录"); return; } if (videoItem is RecVideoItemAppModel) { diff --git a/lib/http/api.dart b/lib/http/api.dart index 4fede3aa0..6e10da62b 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -462,12 +462,19 @@ class Api { // web端验证码登录 // web端密码登录 + static const String logInByWebPwd = + '${HttpString.passBaseUrl}/x/passport-login/web/login'; + + // 获取guestID + // static const String getGuestId = '/x/passport-user/guest/reg'; // app端短信验证码 static const String appSmsCode = '${HttpString.passBaseUrl}/x/passport-login/sms/send'; // app端验证码登录 + static const String logInByAppSms = + '${HttpString.passBaseUrl}/x/passport-login/login/sms'; // 获取短信验证码 // static const String appSafeSmsCode = @@ -477,8 +484,8 @@ class Api { /// username /// password /// key - /// rhash - static const String loginInByPwdApi = + /// salt + static const String loginByPwdApi = '${HttpString.passBaseUrl}/x/passport-login/oauth2/login'; /// 密码加密密钥 diff --git a/lib/http/interceptor.dart b/lib/http/interceptor.dart index 6071ea0f1..6481b1e10 100644 --- a/lib/http/interceptor.dart +++ b/lib/http/interceptor.dart @@ -3,43 +3,42 @@ import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:dio/dio.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; -import 'package:hive/hive.dart'; -import '../utils/storage.dart'; class ApiInterceptor extends Interceptor { - @override - void onRequest(RequestOptions options, RequestInterceptorHandler handler) { - // print("请求之前"); - // 在请求之前添加头部或认证信息 - // options.headers['Authorization'] = 'Bearer token'; - // options.headers['Content-Type'] = 'application/json'; - handler.next(options); - } + // @override + // void onRequest(RequestOptions options, RequestInterceptorHandler handler) { + // print("请求之前"); + // // 在请求之前添加头部或认证信息 + // options.headers['Authorization'] = 'Bearer token'; + // options.headers['Content-Type'] = 'application/json'; + // handler.next(options); + // } - @override - void onResponse(Response response, ResponseInterceptorHandler handler) { - try { - if (response.statusCode == 302) { - final List locations = response.headers['location']!; - if (locations.isNotEmpty) { - if (locations.first.startsWith('https://www.mcbbs.net')) { - final Uri uri = Uri.parse(locations.first); - final String? accessKey = uri.queryParameters['access_key']; - final String? mid = uri.queryParameters['mid']; - try { - Box localCache = GStorage.localCache; - localCache.put(LocalCacheKey.accessKey, - {'mid': mid, 'value': accessKey}); - } catch (_) {} - } - } - } - } catch (err) { - print('ApiInterceptor: $err'); - } + // @override + // void onResponse(Response response, ResponseInterceptorHandler handler) { + // try { + // if (response.statusCode == 302) { + // final List locations = response.headers['location']!; + // if (locations.isNotEmpty) { + // if (locations.first.startsWith('https://www.mcbbs.net')) { + // print('ApiInterceptor@@@@@: ${locations.first}'); + // final Uri uri = Uri.parse(locations.first); + // final String? accessKey = uri.queryParameters['access_key']; + // final String? mid = uri.queryParameters['mid']; + // try { + // Box localCache = GStorage.localCache; + // localCache.put(LocalCacheKey.accessKey, + // {'mid': mid, 'value': accessKey}); + // } catch (_) {} + // } + // } + // } + // } catch (err) { + // print('ApiInterceptor: $err'); + // } - handler.next(response); - } + // handler.next(response); + // } @override void onError(DioException err, ErrorInterceptorHandler handler) async { diff --git a/lib/http/login.dart b/lib/http/login.dart index ff3fee23e..72b4fa695 100644 --- a/lib/http/login.dart +++ b/lib/http/login.dart @@ -3,12 +3,68 @@ import 'dart:math'; import 'package:crypto/crypto.dart'; import 'package:dio/dio.dart'; import 'package:encrypt/encrypt.dart'; -import 'package:uuid/uuid.dart'; +import '../common/constants.dart'; import '../models/login/index.dart'; -import '../utils/login.dart'; +import '../utils/utils.dart'; import 'index.dart'; class LoginHttp { + static String deviceId = genDeviceId(); + static String buvid = genBuvid(); + static String host = 'passport.bilibili.com'; + static String traceId = + '11111111111111111111111111111111:1111111111111111:0:0'; + static String statistics = Uri.encodeComponent( + '{"appId": 5,"platform": 3,"version": "1.46.2","abtest": ""}'); + static String userAgent = + 'Mozilla/5.0 BiliDroid/1.46.2 (bbcallen@gmail.com) os/android model/vivo mobi_app/android_hd build/1462100 channel/yingyongbao innerVer/1462100 osVer/14 network/2'; + static Future> getHDcode() async { + var params = { + 'appkey': Constants.appKey, + // 'local_id': 'Y952A395BB157D305D8A8340FC2AAECECE17', + 'local_id': '0', + //精确到秒的时间戳 + 'ts': (DateTime.now().millisecondsSinceEpoch ~/ 1000).toString(), + 'platform': 'android', + 'mobi_app': 'android_hd', + }; + String sign = Utils.appSign( + params, + Constants.appKey, + Constants.appSec, + ); + var res = await Request() + .post(Api.getTVCode, queryParameters: {...params, 'sign': sign}); + print(res); + if (res.data['code'] == 0) { + return {'status': true, 'data': res.data['data']}; + } else { + return {'status': false, 'msg': res.data['message']}; + } + } + + static Future codePoll(String authCode) async { + var params = { + 'appkey': Constants.appKey, + 'auth_code': authCode, + 'local_id': '0', + 'ts': (DateTime.now().millisecondsSinceEpoch ~/ 1000).toString(), + }; + String sign = Utils.appSign( + params, + Constants.appKey, + Constants.appSec, + ); + var res = await Request() + .post(Api.qrcodePoll, queryParameters: {...params, 'sign': sign}); + return { + 'status': res.data['code'] == 0, + 'code': res.data['code'], + 'data': res.data['data'], + 'msg': res.data['message'] + }; + } + static Future queryCaptcha() async { var res = await Request().get(Api.getCaptcha); if (res.data['code'] == 0) { @@ -21,107 +77,141 @@ class LoginHttp { } } + // 获取salt与PubKey + static Future getWebKey() async { + var res = await Request().get(Api.getWebKey); + //data: {'disable_rcmd': 0, 'local_id': LoginUtils.generateBuvid()}); + if (res.data['code'] == 0) { + return {'status': true, 'data': res.data['data']}; + } else { + return {'status': false, 'data': {}, 'msg': res.data['message']}; + } + } + static Future sendSmsCode({ - int? cid, - required int tel, - required String token, - required String challenge, - required String validate, - required String seccode, + required String cid, + required String tel, + // String? deviceTouristId, + String? gee_challenge, + String? gee_seccode, + String? gee_validate, + String? recaptcha_token, }) async { + int timestamp = DateTime.now().millisecondsSinceEpoch; + var data = { + 'appkey': Constants.appKey, + 'build': '1462100', + 'buvid': buvid, + 'c_locale': 'zh_CN', + 'cid': cid, + // if (deviceTouristId != null) 'device_tourist_id': deviceTouristId, + 'disable_rcmd': '0', + if (gee_challenge != null) 'gee_challenge': gee_challenge, + if (gee_seccode != null) 'gee_seccode': gee_seccode, + if (gee_validate != null) 'gee_validate': gee_validate, + 'local_id': buvid, + // https://chinggg.github.io/post/appre/ + 'login_session_id': + md5.convert(utf8.encode(buvid + timestamp.toString())).toString(), + 'mobi_app': 'android_hd', + 'platform': 'android', + if (recaptcha_token != null) 'recaptcha_token': recaptcha_token, + 's_locale': 'zh_CN', + 'statistics': statistics, + 'tel': tel, + 'ts': (timestamp ~/ 1000).toString(), + }; + String sign = Utils.appSign( + data, + Constants.appKey, + Constants.appSec, + ); + var headers = { + 'Host': host, + 'buvid': buvid, + 'env': 'prod', + 'app-key': 'android_hd', + 'user-agent': userAgent, + 'x-bili-trace-id': traceId, + 'x-bili-aurora-eid': '', + 'x-bili-aurora-zone': '', + 'bili-http-engine': 'cronet', + 'content-type': 'application/x-www-form-urlencoded; charset=utf-8', + }; + var res = await Request().post( Api.appSmsCode, - data: { - 'cid': cid, - 'tel': tel, - "source": "main_web", - 'token': token, - 'challenge': challenge, - 'validate': validate, - 'seccode': seccode, - }, + data: {...data, 'sign': sign}, options: Options( contentType: Headers.formUrlEncodedContentType, - // headers: {'user-agent': ApiConstants.userAgent} + headers: headers, ), ); print(res); + if (res.data['code'] == 0 && res.data['data']['recaptcha_url'] == "") { + return {'status': true, 'data': res.data['data']}; + } else { + return { + 'status': false, + 'code': res.data['code'], + 'msg': res.data['message'], + 'data': res.data['data'] + }; + } } - // web端验证码 - static Future sendWebSmsCode({ - int? cid, - required int tel, - required String token, - required String challenge, - required String validate, - required String seccode, - }) async { - Map data = { - 'cid': cid, - 'tel': tel, - 'token': token, - 'challenge': challenge, - 'validate': validate, - 'seccode': seccode, - }; - FormData formData = FormData.fromMap({...data}); - var res = await Request().post( - Api.smsCode, - data: formData, - options: Options( - contentType: Headers.formUrlEncodedContentType, - ), - ); - print(res); - } + // static Future getGuestId(String key) async { + // dynamic publicKey = RSAKeyParser().parse(key); + // var params = { + // 'appkey': Constants.appKey, + // 'build': '1462100', + // 'buvid': buvid, + // 'c_locale': 'zh_CN', + // 'channel': 'yingyongbao', + // 'deviceInfo': 'xxxxxx', + // 'disable_rcmd': '0', + // 'dt': Uri.encodeComponent(Encrypter(RSA(publicKey: publicKey)) + // .encrypt(generateRandomString(16)) + // .base64), + // 'local_id': buvid, + // 'mobi_app': 'android_hd', + // 'platform': 'android', + // 's_locale': 'zh_CN', + // 'statistics': statistics, + // 'ts': (DateTime.now().millisecondsSinceEpoch ~/ 1000).toString(), + // }; + // String sign = Utils.appSign( + // params, + // Constants.appKey, + // Constants.appSec, + // ); + // var headers = { + // 'Host': host, + // 'buvid': buvid, + // 'env': 'prod', + // 'app-key': 'android_hd', + // 'user-agent': userAgent, + // 'x-bili-trace-id': traceId, + // 'x-bili-aurora-eid': '', + // 'x-bili-aurora-zone': '', + // 'bili-http-engine': 'cronet', + // 'content-type': 'application/x-www-form-urlencoded; charset=utf-8', + // }; + // var res = await Request().post(Api.getGuestId, + // queryParameters: {...params, 'sign': sign}, + // options: Options( + // contentType: Headers.formUrlEncodedContentType, + // headers: headers, + // )); + // print("getGuestId: $res"); + // if (res.data['code'] == 0) { + // return {'status': true, 'data': res.data['data']}; + // } else { + // return {'status': false, 'msg': res.data['message']}; + // } + // } - // web端验证码登录 - static Future loginInByWebSmsCode() async {} - - // web端密码登录 - static Future liginInByWebPwd() async {} - - // app端验证码 - static Future sendAppSmsCode({ - int? cid, - required int tel, - required String token, - required String challenge, - required String validate, - required String seccode, - }) async { - Map data = { - 'cid': cid, - 'tel': tel, - 'login_session_id': const Uuid().v4().replaceAll('-', ''), - 'recaptcha_token': token, - 'gee_challenge': challenge, - 'gee_validate': validate, - 'gee_seccode': seccode, - 'channel': 'bili', - 'buvid': buvid(), - 'local_id': buvid(), - // 'ts': DateTime.now().millisecondsSinceEpoch ~/ 1000, - 'statistics': { - "appId": 1, - "platform": 3, - "version": "7.52.0", - "abtest": "" - }, - }; - // FormData formData = FormData.fromMap({...data}); - var res = await Request().post( - Api.appSmsCode, - data: data, - options: Options( - contentType: Headers.formUrlEncodedContentType, - ), - ); - print(res); - } - - static String buvid() { + static String genBuvid() { var mac = []; var random = Random(); @@ -137,40 +227,215 @@ class LoginHttp { return 'XY${md5Arr[2]}${md5Arr[12]}${md5Arr[22]}$md5Str'; } - // 获取盐hash跟PubKey - static Future getWebKey() async { - var res = await Request().get(Api.getWebKey, - data: {'disable_rcmd': 0, 'local_id': LoginUtils.generateBuvid()}); - if (res.data['code'] == 0) { - return {'status': true, 'data': res.data['data']}; - } else { - return {'status': false, 'data': {}, 'msg': res.data['message']}; - } + static String genDeviceId() { + // https://github.com/bilive/bilive_client/blob/2873de0532c54832f5464a4c57325ad9af8b8698/bilive/lib/app_client.ts#L62 + final String yyyyMMddHHmmss = DateTime.now() + .toIso8601String() + .replaceAll(RegExp(r'[-:TZ]'), '') + .substring(0, 14); + + final Random random = Random(); // Random.secure(); + final String randomHex32 = + List.generate(32, (index) => random.nextInt(16).toRadixString(16)) + .join(); + final String randomHex16 = + List.generate(16, (index) => random.nextInt(16).toRadixString(16)) + .join(); + + final String deviceID = randomHex32 + yyyyMMddHHmmss + randomHex16; + + final List bytes = RegExp(r'\w{2}') + .allMatches(deviceID) + .map((match) => int.parse(match.group(0)!, radix: 16)) + .toList(); + final int checksumValue = bytes.reduce((a, b) => a + b); + final String check = checksumValue + .toRadixString(16) + .substring(checksumValue.toRadixString(16).length - 2); + + return deviceID + check; + } + + static String generateRandomString(int length) { + const chars = + '123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; + final Random random = Random(); // Random.secure(); + return List.generate(length, (index) => chars[random.nextInt(chars.length)]) + .join(); } // app端密码登录 - static Future loginInByMobPwd({ - required String tel, + static Future loginByPwd({ + required String username, required String password, required String key, - required String rhash, + required String salt, + String? gee_challenge, + String? gee_seccode, + String? gee_validate, + String? recaptcha_token, }) async { dynamic publicKey = RSAKeyParser().parse(key); - String passwordEncryptyed = - Encrypter(RSA(publicKey: publicKey)).encrypt(rhash + password).base64; + print(publicKey); + String passwordEncrypted = + Encrypter(RSA(publicKey: publicKey)).encrypt(salt + password).base64; + Map data = { - 'username': tel, - 'password': passwordEncryptyed, - 'local_id': LoginUtils.generateBuvid(), - 'disable_rcmd': "0", + 'appkey': Constants.appKey, + 'bili_local_id': deviceId, + 'build': '1462100', + 'buvid': buvid, + 'c_locale': 'zh_CN', + 'channel': 'yingyongbao', + 'device': 'phone', + 'device_id': deviceId, + //'device_meta': '', + 'device_name': 'vivo', + 'device_platform': 'Android14vivo', + 'disable_rcmd': '0', + 'dt': Uri.encodeComponent(Encrypter(RSA(publicKey: publicKey)) + .encrypt(generateRandomString(16)) + .base64), + 'from_pv': 'main.homepage.avatar-nologin.all.click', + 'from_url': Uri.encodeComponent('bilibili://pegasus/promo'), + if (gee_challenge != null) 'gee_challenge': gee_challenge, + if (gee_seccode != null) 'gee_seccode': gee_seccode, + if (gee_validate != null) 'gee_validate': gee_validate, + 'local_id': buvid, //LoginUtils.generateBuvid(), + 'mobi_app': 'android_hd', + 'password': passwordEncrypted, + 'permission': 'ALL', + 'platform': 'android', + if (recaptcha_token != null) 'recaptcha_token': recaptcha_token, + 's_locale': 'zh_CN', + 'statistics': statistics, + 'ts': (DateTime.now().millisecondsSinceEpoch ~/ 1000).toString(), + 'username': username, + }; + String sign = Utils.appSign( + data, + Constants.appKey, + Constants.appSec, + ); + data['sign'] = sign; + data.map((key, value) { + print('$key: $value'); + return MapEntry(key, value); + }); + final Map headers = { + 'Host': host, + 'buvid': buvid, + 'env': 'prod', + 'app-key': 'android_hd', + 'user-agent': userAgent, + 'x-bili-trace-id': traceId, + 'x-bili-aurora-eid': '', + 'x-bili-aurora-zone': '', + 'bili-http-engine': 'cronet', + 'content-type': 'application/x-www-form-urlencoded; charset=utf-8', }; var res = await Request().post( - Api.loginInByPwdApi, + Api.loginByPwdApi, data: data, options: Options( contentType: Headers.formUrlEncodedContentType, + headers: headers, + //responseType: ResponseType.plain ), ); print(res); + 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'] + }; + } + } + + // app端短信验证码登录 + static Future loginBySms({ + required String captchaKey, + required String tel, + required String code, + required String cid, + required String key, + }) async { + dynamic publicKey = RSAKeyParser().parse(key); + Map data = { + 'appkey': Constants.appKey, + 'bili_local_id': deviceId, + 'build': '1462100', + 'buvid': buvid, + 'c_locale': 'zh_CN', + 'captcha_key': captchaKey, + 'channel': 'yingyongbao', + 'cid': cid, + 'code': code, + 'device': 'phone', + 'device_id': deviceId, + //'device_meta': '', + 'device_name': 'vivo', + 'device_platform': 'Android14vivo', + // 'device_tourist_id': '', + 'disable_rcmd': '0', + 'dt': Uri.encodeComponent(Encrypter(RSA(publicKey: publicKey)) + .encrypt(generateRandomString(16)) + .base64), + 'from_pv': 'main.my-information.my-login.0.click', + 'from_url': Uri.encodeComponent('bilibili://user_center/mine'), + 'local_id': buvid, + 'mobi_app': 'android_hd', + 'platform': 'android', + 's_locale': 'zh_CN', + 'statistics': statistics, + 'tel': tel, + 'ts': (DateTime.now().millisecondsSinceEpoch ~/ 1000).toString(), + }; + String sign = Utils.appSign( + data, + Constants.appKey, + Constants.appSec, + ); + data['sign'] = sign; + data.map((key, value) { + print('$key: $value'); + return MapEntry(key, value); + }); + final Map headers = { + 'Host': host, + 'buvid': buvid, + 'env': 'prod', + 'app-key': 'android_hd', + 'user-agent': userAgent, + 'x-bili-trace-id': traceId, + 'x-bili-aurora-eid': '', + 'x-bili-aurora-zone': '', + 'bili-http-engine': 'cronet', + 'content-type': 'application/x-www-form-urlencoded; charset=utf-8', + }; + var res = await Request().post( + Api.logInByAppSms, + data: data, + options: Options( + contentType: Headers.formUrlEncodedContentType, + headers: headers, + //responseType: ResponseType.plain + ), + ); + print(res); + 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'] + }; + } } } diff --git a/lib/http/member.dart b/lib/http/member.dart index 3b3781615..0c04126fb 100644 --- a/lib/http/member.dart +++ b/lib/http/member.dart @@ -1,6 +1,3 @@ -import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; -import 'package:hive/hive.dart'; -import '../common/constants.dart'; import '../models/dynamics/result.dart'; import '../models/follow/result.dart'; import '../models/member/archive.dart'; @@ -8,7 +5,6 @@ import '../models/member/coin.dart'; import '../models/member/info.dart'; import '../models/member/seasons.dart'; import '../models/member/tags.dart'; -import '../utils/storage.dart'; import '../utils/utils.dart'; import '../utils/wbi_sign.dart'; import 'index.dart'; @@ -375,125 +371,6 @@ class MemberHttp { } } - // 获取TV authCode - static Future getTVCode() async { - SmartDialog.showLoading(msg: "正在申请HD版二维码..."); - var params = { - 'appkey': Constants.appKey, - // 'local_id': 'Y952A395BB157D305D8A8340FC2AAECECE17', - 'local_id': '0', - 'ts': DateTime.now().millisecondsSinceEpoch.toString(), - 'platform': 'android', - 'mobi_app': 'android_hd', - }; - String sign = Utils.appSign( - params, - Constants.appKey, - Constants.appSec, - ); - var res = await Request() - .post(Api.getTVCode, queryParameters: {...params, 'sign': sign}); - SmartDialog.dismiss(); - print(res.data); - if (res.data['code'] == 0) { - print("getTVCode"); - return { - 'status': true, - 'data': res.data['data']['auth_code'], - 'msg': '操作成功' - }; - } else { - return { - 'status': false, - 'data': [], - 'msg': res.data, - }; - } - } - - // 获取access_key - static Future cookieToKey() async { - var authCodeRes = await getTVCode(); - if (authCodeRes['status']) { - SmartDialog.showLoading(msg: "正在确认登录..."); - var confirmRes = - await Request().post(Api.qrcodeConfirm, queryParameters: { - 'auth_code': authCodeRes['data'], - 'local_id': '0', - 'build': 1442100, - 'scanning_type': 1, - 'csrf': await Request.getCsrf(), - }); - print("confirmRes"); - print(confirmRes); - SmartDialog.dismiss(); - if (confirmRes.data['code'] != 0) { - return { - 'status': false, - 'data': [], - 'msg': - "确认登录失败:${confirmRes.data['message']}\n\n请在设置中退出账号,重启app,重新登录再试", - }; - } - SmartDialog.showLoading(msg: "等待500毫秒..."); - await Future.delayed(const Duration(milliseconds: 500)); - SmartDialog.dismiss(); - SmartDialog.showLoading(msg: "正在获取登录结果(含access_key)..."); - var res = await qrcodePoll(authCodeRes['data']); - SmartDialog.dismiss(); - if (res['status']) { - return {'status': true, 'data': [], 'msg': res['msg']}; - } else { - return { - 'status': false, - 'data': [], - 'msg': "登录结果获取失败:${res.data['msg']}", - }; - } - } else { - return { - 'status': false, - 'data': [], - 'msg': "TV版二维码申请失败:${authCodeRes['msg']}", - }; - } - } - - static Future qrcodePoll(authCode) async { - var params = { - 'appkey': Constants.appKey, - 'auth_code': authCode.toString(), - 'local_id': '0', - 'ts': (DateTime.now().millisecondsSinceEpoch ~/ 1000).toString(), - }; - String sign = Utils.appSign( - params, - Constants.appKey, - Constants.appSec, - ); - var res = await Request() - .post(Api.qrcodePoll, queryParameters: {...params, 'sign': sign}); - if (res.data['code'] == 0) { - String accessKey = res.data['data']['access_token']; - Box localCache = GStorage.localCache; - Box userInfoCache = GStorage.userInfo; - var userInfo = userInfoCache.get('userInfoCache'); - localCache.put( - LocalCacheKey.accessKey, {'mid': userInfo.mid, 'value': accessKey}); - return { - 'status': true, - 'data': [], - 'msg': '操作成功,当前获取的access_key为:$accessKey' - }; - } else { - return { - 'status': false, - 'data': [], - 'msg': res.data['message'], - }; - } - } - // 获取up播放数、点赞数 static Future memberView({required int mid}) async { var res = await Request().get(Api.getMemberViewApi, data: {'mid': mid}); diff --git a/lib/http/video.dart b/lib/http/video.dart index 8b60638ce..89a61b0c8 100644 --- a/lib/http/video.dart +++ b/lib/http/video.dart @@ -325,7 +325,7 @@ class VideoHttp { String? accessKey = GStorage.localCache .get(LocalCacheKey.accessKey, defaultValue: {})['value']; if (accessKey == null || accessKey == "") { - return {'status': false, 'msg': "本操作使用app端接口,请前往【隐私设置】刷新access_key"}; + return {'status': false, 'msg': "请退出账号后重新登录"}; } var res = await Request().post( Api.dislikeVideo, @@ -355,7 +355,7 @@ class VideoHttp { String? accessKey = GStorage.localCache .get(LocalCacheKey.accessKey, defaultValue: {})['value']; if (accessKey == null || accessKey == "") { - return {'status': false, 'msg': "本操作使用app端接口,请前往【隐私设置】刷新access_key"}; + return {'status': false, 'msg': "请退出账号后重新登录"}; } assert((reasonId != null) ^ (feedbackId != null)); var res = await Request().get(Api.feedDislike, data: { @@ -386,7 +386,7 @@ class VideoHttp { String? accessKey = GStorage.localCache .get(LocalCacheKey.accessKey, defaultValue: {})['value']; if (accessKey == null || accessKey == "") { - return {'status': false, 'msg': "本操作使用app端接口,请前往【隐私设置】刷新access_key"}; + return {'status': false, 'msg': "请退出账号后重新登录"}; } // assert ((reasonId != null) ^ (feedbackId != null)); var res = await Request().get(Api.feedDislikeCancel, data: { diff --git a/lib/pages/login/controller.dart b/lib/pages/login/controller.dart index b1fce8075..2b247622d 100644 --- a/lib/pages/login/controller.dart +++ b/lib/pages/login/controller.dart @@ -1,204 +1,439 @@ +import 'dart:async'; import 'dart:io'; +import 'package:PiliPalaX/common/constants.dart'; import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:PiliPalaX/http/login.dart'; import 'package:gt3_flutter_plugin/gt3_flutter_plugin.dart'; 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'; -class LoginPageController extends GetxController { - final GlobalKey mobFormKey = GlobalKey(); - final GlobalKey passwordFormKey = GlobalKey(); - final GlobalKey msgCodeFormKey = GlobalKey(); +import '../../http/constants.dart'; +import '../../http/init.dart'; +import '../../http/user.dart'; +import '../../utils/storage.dart'; +import '../home/controller.dart'; +import '../media/controller.dart'; - final TextEditingController mobTextController = TextEditingController(); +class LoginPageController extends GetxController + with GetSingleTickerProviderStateMixin { + final TextEditingController telTextController = TextEditingController(); + final TextEditingController usernameTextController = TextEditingController(); final TextEditingController passwordTextController = TextEditingController(); - final TextEditingController msgCodeTextController = TextEditingController(); + final TextEditingController smsCodeTextController = TextEditingController(); - final FocusNode mobTextFieldNode = FocusNode(); - final FocusNode passwordTextFieldNode = FocusNode(); - final FocusNode msgCodeTextFieldNode = FocusNode(); + Rx> codeInfo = Rx>({}); - final PageController pageViewController = PageController(); - - RxInt currentIndex = 0.obs; + late TabController tabController; final Gt3FlutterPlugin captcha = Gt3FlutterPlugin(); - // 默认密码登录 - RxInt loginType = 0.obs; + CaptchaDataModel captchaData = CaptchaDataModel(); + RxInt qrCodeLeftTime = 180.obs; + Rx statusQRCode = ''.obs; - // 监听pageView切换 - void onPageChange(int index) { - currentIndex.value = index; + Map selectedCountryCodeId = + Constants.internationalDialingPrefix.first; + String captchaKey = ''; + RxInt smsSendCooldown = 0.obs; + int smsSendTimestamp = 0; + + // 定时器 + Timer? qrCodeTimer; + Timer? smsSendCooldownTimer; + + + @override + void onInit() { + super.onInit(); + tabController = TabController(length: 3, vsync: this) + ..addListener(_handleTabChange); } - // 输入手机号 下一页 - void nextStep() async { - if ((mobFormKey.currentState as FormState).validate()) { - await pageViewController.animateToPage( - 1, - duration: const Duration(microseconds: 3000), - curve: Curves.easeInOut, - ); - passwordTextFieldNode.requestFocus(); - } + @override + void onClose() { + tabController.removeListener(_handleTabChange); + tabController.dispose(); + qrCodeTimer?.cancel(); + smsSendCooldownTimer?.cancel(); + super.onClose(); } - // 上一页 - void previousPage() async { - passwordTextFieldNode.unfocus(); - await Future.delayed(const Duration(milliseconds: 200)); - pageViewController.animateToPage( - 0, - duration: const Duration(microseconds: 300), - curve: Curves.easeInOut, - ); - } + void refreshQRCode() { + LoginHttp.getHDcode().then((res) { + if (res['status']) { + qrCodeTimer?.cancel(); + codeInfo.value = res; + codeInfo.refresh(); + print("codeInfo"); + print(codeInfo); + qrCodeTimer = Timer.periodic(const Duration(milliseconds: 1000), (t) { + qrCodeLeftTime.value = 180 - t.tick; + if (qrCodeLeftTime <= 0) { + t.cancel(); + statusQRCode.value = '二维码已过期,请刷新'; + qrCodeLeftTime = 0.obs; + return; + } - // 切换登录方式 - void changeLoginType() { - loginType.value = loginType.value == 0 ? 1 : 0; - if (loginType.value == 0) { - passwordTextFieldNode.requestFocus(); - } else { - msgCodeTextFieldNode.requestFocus(); - } - } - - // app端密码登录 - void loginInByAppPassword() async { - if ((passwordFormKey.currentState as FormState).validate()) { - var webKeyRes = await LoginHttp.getWebKey(); - if (webKeyRes['status']) { - String rhash = webKeyRes['data']['hash']; - String key = webKeyRes['data']['key']; - LoginHttp.loginInByMobPwd( - tel: mobTextController.text, - password: passwordTextController.text, - key: key, - rhash: rhash, - ); + LoginHttp.codePoll(codeInfo.value['data']['auth_code']) + .then((value) async { + if (value['status']) { + t.cancel(); + statusQRCode.value = '扫码成功'; + print(value['data']); + await afterLoginByApp( + value['data'], value['data']['cookie_info']); + Get.back(); + } else if (value['code'] == 86038) { + t.cancel(); + qrCodeLeftTime = 0.obs; + } else { + statusQRCode.value = value['msg']; + } + }); + }); } else { - SmartDialog.showToast(webKeyRes['msg']); + SmartDialog.showToast(res['msg']); + } + }); + } + + void _handleTabChange() { + print('tabController.index ${tabController.index}'); + if (tabController.index == 2) { + if (qrCodeTimer == null || qrCodeTimer!.isActive == false) { + refreshQRCode(); } } } - // 验证码登录 - void loginInByCode() { - if ((msgCodeFormKey.currentState as FormState).validate()) {} - } - - // app端验证码 - void getMsgCode() async { - getCaptcha((data) async { - CaptchaDataModel captchaData = data; - var res = await LoginHttp.sendAppSmsCode( - cid: 86, - tel: 13734077064, - token: captchaData.token!, - challenge: captchaData.geetest!.challenge!, - validate: captchaData.validate!, - seccode: captchaData.seccode!, - ); - print(res); + Future afterLoginByApp(Map token_info, cookie_info) async { + Box localCache = GStorage.localCache; + localCache.put(LocalCacheKey.accessKey, { + 'mid': token_info['mid'], + 'value': token_info['access_token'], + 'refresh': token_info['refresh_token'] }); + List cookieInfo = cookie_info['cookies']; + print("cookieInfo"); + print(cookieInfo); + List cookies = []; + String cookieStrings = cookieInfo.map((cookie) { + String cstr = + '${cookie['name']}=${cookie['value']};Domain=.bilibili.com;Path=/;'; + cookies.add(Cookie.fromSetCookieValue(cstr)); + return cstr; + }).join(''); + List Urls = [ + HttpString.baseUrl, + HttpString.apiBaseUrl, + HttpString.tUrl + ]; + for (var url in Urls) { + await Request.cookieManager.cookieJar + .saveFromResponse(Uri.parse(url), cookies); + } + print(cookieStrings); + print(Request.cookieManager.cookieJar + .loadForRequest(Uri.parse(HttpString.apiBaseUrl))); + Request.dio.options.headers['cookie'] = cookieStrings; + print(Request.dio.options); + try { + await WebviewCookieManager().setCookies(cookies); + } catch (e) { + SmartDialog.showToast('webview设置cookie失败,$e'); + } + final result = await UserHttp.userInfo(); + if (result['status'] && result['data'].isLogin) { + SmartDialog.showToast('登录成功,当前采用「' + '${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.find(); + mediaCtr.mid = result['data'].mid; + await LoginUtils.refreshLoginStatus(true); + } else { + // 获取用户信息失败 + SmartDialog.showNotify( + msg: '登录失败,请检查cookie是否正确,${result['message']}', + notifyType: NotifyType.warning); + } } // 申请极验验证码 - Future getCaptcha(oncall) async { - SmartDialog.showLoading(msg: '请求中...'); - var result = await LoginHttp.queryCaptcha(); - SmartDialog.dismiss(); - if (result['status']) { - CaptchaDataModel captchaData = result['data']; - var registerData = Gt3RegisterData( - challenge: captchaData.geetest!.challenge, - gt: captchaData.geetest!.gt!, - success: true, - ); - captcha.addEventHandler(onShow: (Map message) async { - }, onClose: (Map message) async { - SmartDialog.showToast('关闭验证'); - }, onResult: (Map message) async { - debugPrint("Captcha result: $message"); - String code = message["code"]; - if (code == "1") { - // 发送 message["result"] 中的数据向 B 端的业务服务接口进行查询 - SmartDialog.showToast('验证成功'); - captchaData.validate = message['result']['geetest_validate']; - captchaData.seccode = message['result']['geetest_seccode']; - captchaData.geetest!.challenge = - message['result']['geetest_challenge']; - oncall(captchaData); - } else { - // 终端用户完成验证失败,自动重试 If the verification fails, it will be automatically retried. - debugPrint("Captcha result code : $code"); - } - }, onError: (Map message) async { - String code = message["code"]; + Future getCaptcha(geeGt, geeChallenge, onSuccess) async { + var registerData = Gt3RegisterData( + challenge: geeChallenge, + gt: geeGt, + success: true, + ); - // 处理验证中返回的错误 Handling errors returned in verification - if (Platform.isAndroid) { - // Android 平台 - if (code == "-2") { - // Dart 调用异常 Call exception - } else if (code == "-1") { - // Gt3RegisterData 参数不合法 Parameter is invalid - } else if (code == "201") { - // 网络无法访问 Network inaccessible - } else if (code == "202") { - // Json 解析错误 Analysis error - } else if (code == "204") { - // WebView 加载超时,请检查是否混淆极验 SDK Load timed out - } else if (code == "204_1") { - // WebView 加载前端页面错误,请查看日志 Error loading front-end page, please check the log - } else if (code == "204_2") { - // WebView 加载 SSLError - } else if (code == "206") { - // gettype 接口错误或返回为 null API error or return null - } else if (code == "207") { - // getphp 接口错误或返回为 null API error or return null - } else if (code == "208") { - // ajax 接口错误或返回为 null API error or return null + captcha.addEventHandler( + onShow: (Map message) async {}, + onClose: (Map message) async { + SmartDialog.showToast('关闭验证'); + }, + onResult: (Map message) async { + debugPrint("Captcha result: $message"); + String code = message["code"]; + if (code == "1") { + // 发送 message["result"] 中的数据向 B 端的业务服务接口进行查询 + SmartDialog.showToast('验证成功'); + captchaData.validate = message['result']['geetest_validate']; + captchaData.seccode = message['result']['geetest_seccode']; + captchaData.geetest = GeetestData( + challenge: message['result']['geetest_challenge'], + gt: geeGt, + ); + onSuccess(); } else { - // 更多错误码参考开发文档 More error codes refer to the development document - // https://docs.geetest.com/sensebot/apirefer/errorcode/android + // 终端用户完成验证失败,自动重试 If the verification fails, it will be automatically retried. + debugPrint("Captcha result code : $code"); + } + }, + onError: (Map message) async { + SmartDialog.showToast("Captcha onError: $message"); + String code = message["code"]; + // 处理验证中返回的错误 Handling errors returned in verification + if (Platform.isAndroid) { + // Android 平台 + if (code == "-2") { + // Dart 调用异常 Call exception + } else if (code == "-1") { + // Gt3RegisterData 参数不合法 Parameter is invalid + } else if (code == "201") { + // 网络无法访问 Network inaccessible + } else if (code == "202") { + // Json 解析错误 Analysis error + } else if (code == "204") { + // WebView 加载超时,请检查是否混淆极验 SDK Load timed out + } else if (code == "204_1") { + // WebView 加载前端页面错误,请查看日志 Error loading front-end page, please check the log + } else if (code == "204_2") { + // WebView 加载 SSLError + } else if (code == "206") { + // gettype 接口错误或返回为 null API error or return null + } else if (code == "207") { + // getphp 接口错误或返回为 null API error or return null + } else if (code == "208") { + // ajax 接口错误或返回为 null API error or return null + } else { + // 更多错误码参考开发文档 More error codes refer to the development document + // https://docs.geetest.com/sensebot/apirefer/errorcode/android + } } - } - if (Platform.isIOS) { - // iOS 平台 - if (code == "-1009") { - // 网络无法访问 Network inaccessible - } else if (code == "-1004") { - // 无法查找到 HOST Unable to find HOST - } else if (code == "-1002") { - // 非法的 URL Illegal URL - } else if (code == "-1001") { - // 网络超时 Network timeout - } else if (code == "-999") { - // 请求被意外中断, 一般由用户进行取消操作导致 The interrupted request was usually caused by the user cancelling the operation - } else if (code == "-21") { - // 使用了重复的 challenge Duplicate challenges are used - // 检查获取 challenge 是否进行了缓存 Check if the fetch challenge is cached - } else if (code == "-20") { - // 尝试过多, 重新引导用户触发验证即可 Try too many times, lead the user to request verification again - } else if (code == "-10") { - // 预判断时被封禁, 不会再进行图形验证 Banned during pre-judgment, and no more image captcha verification - } else if (code == "-2") { - // Dart 调用异常 Call exception - } else if (code == "-1") { - // Gt3RegisterData 参数不合法 Parameter is invalid - } else { - // 更多错误码参考开发文档 More error codes refer to the development document - // https://docs.geetest.com/sensebot/apirefer/errorcode/ios + if (Platform.isIOS) { + // iOS 平台 + if (code == "-1009") { + // 网络无法访问 Network inaccessible + } else if (code == "-1004") { + // 无法查找到 HOST Unable to find HOST + } else if (code == "-1002") { + // 非法的 URL Illegal URL + } else if (code == "-1001") { + // 网络超时 Network timeout + } else if (code == "-999") { + // 请求被意外中断, 一般由用户进行取消操作导致 The interrupted request was usually caused by the user cancelling the operation + } else if (code == "-21") { + // 使用了重复的 challenge Duplicate challenges are used + // 检查获取 challenge 是否进行了缓存 Check if the fetch challenge is cached + } else if (code == "-20") { + // 尝试过多, 重新引导用户触发验证即可 Try too many times, lead the user to request verification again + } else if (code == "-10") { + // 预判断时被封禁, 不会再进行图形验证 Banned during pre-judgment, and no more image captcha verification + } else if (code == "-2") { + // Dart 调用异常 Call exception + } else if (code == "-1") { + // Gt3RegisterData 参数不合法 Parameter is invalid + } else { + // 更多错误码参考开发文档 More error codes refer to the development document + // https://docs.geetest.com/sensebot/apirefer/errorcode/ios + } } + }); + captcha.startCaptcha(registerData); + } + + // app端密码登录 + void loginByPassword() async { + String username = usernameTextController.text; + String password = passwordTextController.text; + if (username.isEmpty || password.isEmpty) { + SmartDialog.showToast('用户名或密码不能为空'); + return; + } + // if ((passwordFormKey.currentState as FormState).validate()) { + var webKeyRes = await LoginHttp.getWebKey(); + print(webKeyRes); + if (!webKeyRes['status']) { + SmartDialog.showToast(webKeyRes['msg']); + return; + } + String salt = webKeyRes['data']['hash']; + String key = webKeyRes['data']['key']; + print(key); + var res = await LoginHttp.loginByPwd( + username: username, + password: password, + key: key, + salt: salt, + gee_validate: captchaData.validate, + gee_seccode: captchaData.seccode, + gee_challenge: captchaData.geetest?.challenge, + recaptcha_token: captchaData.token, + ); + print(res); + if (res['status']) { + SmartDialog.showToast('登录成功'); + var data = res['data']; + for (var key in data.keys) { + print('$key: ${data[key]}'); + } + await afterLoginByApp(data['token_info'], data['cookie_info']); + Get.back(); + } else { + // handle login result + switch (res['code']) { + case 0: + // login success + break; + case -105: + String captureUrl = res['data']['url']; + 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, () { + loginByPassword(); + }); + break; + default: + SmartDialog.showToast(res['msg']); + // login failed + break; + } + } + // } + } + + // 短信验证码登录 + void loginBySmsCode() async { + if (telTextController.text.isEmpty) { + SmartDialog.showToast('手机号不能为空'); + return; + } + if (captchaKey.isEmpty) { + SmartDialog.showToast('请先点击获取验证码'); + return; + } + if (smsCodeTextController.text.isEmpty) { + SmartDialog.showToast('验证码不能为空'); + return; + } + if (DateTime.now().millisecondsSinceEpoch - smsSendTimestamp > + 1000 * 60 * 5) { + SmartDialog.showToast('验证码已过期,请重新获取'); + return; + } + var webKeyRes = await LoginHttp.getWebKey(); + if (!webKeyRes['status']) { + SmartDialog.showToast(webKeyRes['msg']); + return; + } + String key = webKeyRes['data']['key']; + var res = await LoginHttp.loginBySms( + tel: telTextController.text, + code: smsCodeTextController.text, + captchaKey: captchaKey, + cid: selectedCountryCodeId['country_id'], + key: key, + ); + print(res); + if (res['status']) { + SmartDialog.showToast('登录成功'); + var data = res['data']; + for (var key in data.keys) { + print('$key: ${data[key]}'); + } + await afterLoginByApp(data['token_info'], data['cookie_info']); + Get.back(); + } else { + SmartDialog.showToast(res['msg']); + } + } + + // app端验证码 + void sendSmsCode() async { + if (telTextController.text.isEmpty) { + SmartDialog.showToast('手机号不能为空'); + return; + } + // String? guestId; + // var webKeyRes = await LoginHttp.getWebKey(); + // if (!webKeyRes['status']) { + // SmartDialog.showToast(webKeyRes['msg']); + // } else { + // String key = webKeyRes['data']['key']; + // var guestIdRes = await LoginHttp.getGuestId(key); + // if (!guestIdRes['status']) { + // SmartDialog.showToast(guestIdRes['msg']); + // } else { + // guestId = guestIdRes['data']['guest_id']; + // } + // } + + var res = await LoginHttp.sendSmsCode( + tel: telTextController.text, + cid: selectedCountryCodeId['country_id'], + // deviceTouristId: guestId, + gee_validate: captchaData.validate, + gee_seccode: captchaData.seccode, + gee_challenge: captchaData.geetest?.challenge, + recaptcha_token: captchaData.token, + ); + print(res); + if (res['status']) { + SmartDialog.showToast('发送成功'); + smsSendTimestamp = DateTime.now().millisecondsSinceEpoch; + smsSendCooldown.value = 60; + captchaKey = res['data']['captcha_key']; + smsSendCooldownTimer = Timer.periodic(Duration(seconds: 1), (timer) { + smsSendCooldown.value = 60 - timer.tick; + if (smsSendCooldown <= 0) { + smsSendCooldownTimer?.cancel(); + smsSendCooldown.value = 0; } }); - captcha.startCaptcha(registerData); - } else {} + } else { + // handle login result + switch (res['code']) { + case 0: + case -105: + String captureUrl = res['data']['recaptcha_url']; + 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(); + }); + break; + default: + SmartDialog.showToast(res['msg']); + // login failed + break; + } + } } } diff --git a/lib/pages/login/view.dart b/lib/pages/login/view.dart index bd696fecb..73e42fbd2 100644 --- a/lib/pages/login/view.dart +++ b/lib/pages/login/view.dart @@ -1,5 +1,14 @@ +import 'dart:ui'; + +import 'package:PiliPalaX/common/constants.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; +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'; @@ -12,355 +21,463 @@ class LoginPage extends StatefulWidget { class _LoginPageState extends State { final LoginPageController _loginPageCtr = Get.put(LoginPageController()); - + // late Future> codeFuture; + // 二维码生成时间 + bool showPassword = false; + GlobalKey globalKey = GlobalKey(); @override void initState() { super.initState(); } @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - leading: Obx( - () => _loginPageCtr.currentIndex.value == 0 - ? IconButton( - tooltip: '关闭', - onPressed: () async { - _loginPageCtr.mobTextFieldNode.unfocus(); - await Future.delayed(const Duration(milliseconds: 200)); - Get.back(); - }, - icon: const Icon(Icons.close_outlined), - ) - : IconButton( - tooltip: '返回', - onPressed: () => _loginPageCtr.previousPage(), - icon: const Icon(Icons.arrow_back), - ), - ), - ), - body: PageView( - physics: const NeverScrollableScrollPhysics(), - controller: _loginPageCtr.pageViewController, - onPageChanged: (int index) => _loginPageCtr.onPageChange(index), - children: [ - Padding( - padding: EdgeInsets.only( - left: 20, - right: 20, - top: 10, - bottom: MediaQuery.of(context).padding.bottom + 10, + void dispose() { + _loginPageCtr.dispose(); + super.dispose(); + } + + Widget loginByQRCode() { + return Column( + children: [ + const SizedBox(height: 20), + const Text('使用 bilibili 官方 App 扫码登录'), + const SizedBox(height: 20), + Obx(() => Text('剩余有效时间: ${_loginPageCtr.qrCodeLeftTime} 秒', + style: + const TextStyle(fontFeatures: [FontFeature.tabularFigures()]))), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // const SizedBox(width: 20), + TextButton.icon( + onPressed: _loginPageCtr.refreshQRCode, + icon: const Icon(Icons.refresh), + label: const Text('刷新二维码'), ), - child: Form( - key: _loginPageCtr.mobFormKey, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.max, - children: [ - Text( - '登录', - style: Theme.of(context).textTheme.titleLarge!.copyWith( - letterSpacing: 1, - height: 2.1, - fontSize: 34, - fontWeight: FontWeight.w500), + TextButton.icon( + onPressed: () async { + SmartDialog.showLoading(msg: '正在生成截图'); + RenderRepaintBoundary boundary = globalKey.currentContext! + .findRenderObject()! as RenderRepaintBoundary; + var image = await boundary.toImage(); + ByteData? byteData = + await image.toByteData(format: ImageByteFormat.png); + Uint8List pngBytes = byteData!.buffer.asUint8List(); + SmartDialog.dismiss(); + SmartDialog.showLoading(msg: '正在保存至图库'); + String picName = + "PiliPalaX_loginQRCode_${DateTime.now().toString().replaceAll(' ', '_').replaceAll(':', '-').split('.').first}"; + final SaveResult result = await SaverGallery.saveImage( + Uint8List.fromList(pngBytes), + name: picName, + fileExtension: 'png', + // 保存到 PiliPalaX文件夹 + androidRelativePath: "Pictures/PiliPalaX", + androidExistNotSave: false, + ); + SmartDialog.dismiss(); + if (result.isSuccess) { + await SmartDialog.showToast('「$picName」已保存 '); + } else { + await SmartDialog.showToast('保存失败,${result.errorMessage}'); + } + }, + icon: const Icon(Icons.save), + label: const Text('保存至相册'), + ), + ], + ), + RepaintBoundary( + key: globalKey, + child: Obx(() => QrImageView( + backgroundColor: Theme.of(context).colorScheme.background, + eyeStyle: QrEyeStyle( + eyeShape: QrEyeShape.square, + color: Theme.of(context).colorScheme.primary, ), - Row( - children: [ - Text( - '请使用您的 BiliBili 账号登录。', - style: Theme.of(context).textTheme.titleSmall!, - ), - GestureDetector( - onTap: () {}, - child: const Icon(Icons.info_outline, size: 16), - ) - ], + dataModuleStyle: QrDataModuleStyle( + dataModuleShape: QrDataModuleShape.square, + color: Theme.of(context).colorScheme.secondary, ), - Container( - margin: const EdgeInsets.only(top: 38, bottom: 15), - child: TextFormField( - controller: _loginPageCtr.mobTextController, - focusNode: _loginPageCtr.mobTextFieldNode, - keyboardType: TextInputType.number, - decoration: InputDecoration( - isDense: true, - labelText: '输入手机号码', - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(6.0), - ), - ), - // 校验用户名 - validator: (v) { - return v!.trim().isNotEmpty ? null : "手机号码不能为空"; - }, - onSaved: (val) { - print(val); - }, - onEditingComplete: () { - _loginPageCtr.nextStep(); - }, - ), - ), - GestureDetector( - onTap: () { - Get.offNamed( - '/webview', - parameters: { - 'url': - 'https://passport.bilibili.com/h5-app/passport/login', - 'type': 'login', - 'pageTitle': '登录bilibili', - }, - ); - }, - child: Padding( - padding: const EdgeInsets.only(left: 2), - child: Text( - '使用网页端登录', - style: TextStyle( - color: Theme.of(context).colorScheme.primary), - ), - ), - ), - const Spacer(), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - TextButton(onPressed: () {}, child: const Text('中国大陆')), - TextButton( - style: TextButton.styleFrom( - padding: const EdgeInsets.fromLTRB(20, 0, 20, 0), - foregroundColor: - Theme.of(context).colorScheme.onPrimary, - backgroundColor: - Theme.of(context).colorScheme.primary, // 设置按钮背景色 - ), - onPressed: () => _loginPageCtr.nextStep(), - child: const Text('下一步'), - ) - ], - ), - ], + data: _loginPageCtr.codeInfo.value['data']?['url'] ?? "", + size: 200, + semanticsLabel: '二维码', + ))), + const SizedBox(height: 10), + Obx(() => Text(_loginPageCtr.statusQRCode.value)), + Obx(() => GestureDetector( + onTap: () { + //以外部方式打开此链接 + launchUrlString( + _loginPageCtr.codeInfo.value['data']?['url'] ?? "", + mode: LaunchMode.externalApplication); + }, + child: Padding( + padding: + const EdgeInsets.symmetric(horizontal: 20, vertical: 20), + child: Text(_loginPageCtr.codeInfo.value['data']?['url'] ?? "", + style: Theme.of(context).textTheme.labelSmall!.copyWith( + color: Theme.of(context) + .colorScheme + .onSurface + .withOpacity(0.4))), + ), + )), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Text('请务必在 PiliPalaX 开源仓库等可信渠道下载安装。', + style: Theme.of(context).textTheme.labelSmall!.copyWith( + color: Theme.of(context) + .colorScheme + .onSurface + .withOpacity(0.4)))), + ], + ); + } + + Widget loginByPassword() { + return Column( + children: [ + const SizedBox(height: 20), + const Text('使用账号密码登录'), + const SizedBox(height: 10), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + child: TextField( + controller: _loginPageCtr.usernameTextController, + inputFormatters: [FilteringTextInputFormatter.deny(RegExp(r"\s"))], + decoration: InputDecoration( + prefixIcon: const Icon(Icons.account_box), + border: const UnderlineInputBorder(), + labelText: '账号', + hintText: '邮箱/手机号', + suffixIcon: IconButton( + onPressed: _loginPageCtr.usernameTextController.clear, + icon: const Icon(Icons.clear), ), ), ), - Padding( - padding: EdgeInsets.only( - left: 20, - right: 20, - top: 10, - bottom: MediaQuery.of(context).padding.bottom + 10, - ), - child: Obx( - () => _loginPageCtr.loginType.value == 0 - ? Form( - key: _loginPageCtr.passwordFormKey, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.max, - children: [ - Row( - children: [ - Text( - '密码登录', - style: Theme.of(context) - .textTheme - .titleLarge! - .copyWith( - letterSpacing: 1, - height: 2.1, - fontSize: 34, - fontWeight: FontWeight.w500), - ), - const SizedBox(width: 4), - IconButton( - tooltip: '切换至验证码登录', - style: ButtonStyle( - backgroundColor: - MaterialStateProperty.resolveWith( - (states) { - return Theme.of(context) - .colorScheme - .primary - .withOpacity(0.1); - }), - ), - onPressed: () => - _loginPageCtr.changeLoginType(), - icon: const Icon(Icons.swap_vert_outlined), - ) - ], - ), - Text( - '请输入您的 BiliBili 密码。', - style: Theme.of(context).textTheme.titleSmall!, - ), - Container( - margin: const EdgeInsets.only(top: 38, bottom: 15), - child: TextFormField( - controller: _loginPageCtr.passwordTextController, - focusNode: _loginPageCtr.passwordTextFieldNode, - keyboardType: TextInputType.visiblePassword, - decoration: InputDecoration( - isDense: true, - labelText: '输入密码', - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(6.0), - ), - ), - // 校验用户名 - validator: (v) { - return v!.trim().isNotEmpty ? null : "密码不能为空"; - }, - onSaved: (val) { - print(val); - }, - ), - ), - const Spacer(), - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - TextButton( - onPressed: () => _loginPageCtr.previousPage(), - child: const Text('上一步'), - ), - const SizedBox(width: 15), - TextButton( - style: TextButton.styleFrom( - padding: - const EdgeInsets.fromLTRB(20, 0, 20, 0), - foregroundColor: - Theme.of(context).colorScheme.onPrimary, - backgroundColor: Theme.of(context) - .colorScheme - .primary, // 设置按钮背景色 - ), - onPressed: () => - _loginPageCtr.loginInByAppPassword(), - child: const Text('确认登录'), - ) - ], - ), - ], - ), - ) - : Form( - key: _loginPageCtr.msgCodeFormKey, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.max, - children: [ - Row( - children: [ - Text( - '验证码登录', - style: Theme.of(context) - .textTheme - .titleLarge! - .copyWith( - letterSpacing: 1, - height: 2.1, - fontSize: 34, - fontWeight: FontWeight.w500), - ), - const SizedBox(width: 4), - IconButton( - tooltip: '切换至密码登录', - style: ButtonStyle( - backgroundColor: - MaterialStateProperty.resolveWith( - (states) { - return Theme.of(context) - .colorScheme - .primary - .withOpacity(0.1); - }), - ), - onPressed: () => - _loginPageCtr.changeLoginType(), - icon: const Icon(Icons.swap_vert_outlined), - ) - ], - ), - Text( - '请输入收到到验证码。', - style: Theme.of(context).textTheme.titleSmall!, - ), - Container( - margin: const EdgeInsets.only(top: 38, bottom: 15), - child: Stack( - children: [ - TextFormField( - controller: - _loginPageCtr.msgCodeTextController, - focusNode: _loginPageCtr.msgCodeTextFieldNode, - maxLength: 6, - keyboardType: TextInputType.number, - decoration: InputDecoration( - isDense: true, - labelText: '输入验证码', - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(6.0), - ), - ), - // 校验用户名 - validator: (v) { - return v!.trim().isNotEmpty - ? null - : "验证码不能为空"; - }, - onSaved: (val) { - print(val); - }, - ), - Positioned( - right: 8, - top: 4, - child: Center( - child: TextButton( - onPressed: () => - _loginPageCtr.getMsgCode(), - child: const Text('获取验证码'), - ), - ), - ), - ], - ), - ), - const Spacer(), - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - TextButton( - onPressed: () => _loginPageCtr.previousPage(), - child: const Text('上一步'), - ), - const SizedBox(width: 15), - TextButton( - style: TextButton.styleFrom( - padding: - const EdgeInsets.fromLTRB(20, 0, 20, 0), - foregroundColor: - Theme.of(context).colorScheme.onPrimary, - backgroundColor: Theme.of(context) - .colorScheme - .primary, // 设置按钮背景色 - ), - onPressed: () => _loginPageCtr.loginInByCode(), - child: const Text('确认登录'), - ) - ], - ), - ], - ), - ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + child: TextField( + obscureText: !showPassword, + keyboardType: TextInputType.visiblePassword, + inputFormatters: [FilteringTextInputFormatter.deny(RegExp(r"\s"))], + controller: _loginPageCtr.passwordTextController, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.lock), + border: const UnderlineInputBorder(), + labelText: '密码', + suffixIcon: IconButton( + onPressed: _loginPageCtr.passwordTextController.clear, + icon: const Icon(Icons.clear), + ), ), ), - ], - ), + ), + Row( + children: [ + const SizedBox(width: 10), + Checkbox( + value: showPassword, + onChanged: (value) { + setState(() { + showPassword = value!; + }); + }, + ), + const Text('显示密码'), + const Spacer(), + TextButton( + onPressed: () { + //https://passport.bilibili.com/h5-app/passport/login/findPassword + //https://passport.bilibili.com/passport/findPassword + showDialog( + context: context, + builder: (context) { + return SimpleDialog( + title: const Text('忘记密码?'), + contentPadding: + const EdgeInsets.fromLTRB(0.0, 2.0, 0.0, 16.0), + children: [ + const Padding( + padding: EdgeInsets.fromLTRB(25, 0, 25, 10), + child: Text("试试扫码、手机号登录,或选择")), + ListTile( + title: const Text( + '找回密码(手机版)', + ), + leading: const Icon(Icons.smartphone_outlined), + subtitle: const Text( + 'https://passport.bilibili.com/h5-app/passport/login/findPassword', + ), + dense: false, + onTap: () async { + Get.back(); + Get.toNamed('/webview', parameters: { + 'url': + 'https://passport.bilibili.com/h5-app/passport/login/findPassword', + 'type': 'url', + 'pageTitle': '忘记密码', + }); + }), + ListTile( + title: const Text( + '找回密码(电脑版)', + ), + leading: const Icon(Icons.desktop_windows_outlined), + subtitle: const Text( + 'https://passport.bilibili.com/pc/passport/findPassword', + ), + dense: false, + onTap: () async { + Get.back(); + Get.toNamed('/webview', parameters: { + 'url': + 'https://passport.bilibili.com/pc/passport/findPassword', + 'type': 'url', + 'pageTitle': '忘记密码', + 'uaType': 'pc' + }); + }), + ], + ); + }, + ); + }, + child: const Text('忘记密码'), + ), + const SizedBox(width: 20), + ], + ), + OutlinedButton.icon( + onPressed: _loginPageCtr.loginByPassword, + icon: const Icon(Icons.login_outlined), + label: const Text('登录'), + ), + const SizedBox(height: 20), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Text( + '根据 bilibili 官方登录接口规范,密码将在本地加盐、加密后传输。\n' + '盐与公钥均由官方提供;以 RSA/ECB/PKCS1Padding 方式加密。\n' + '账号密码仅用于该登录接口,不予保存;本地仅存储登录凭证。\n' + '请务必在 PiliPalaX 开源仓库等可信渠道下载安装。', + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.labelSmall!.copyWith( + color: Theme.of(context) + .colorScheme + .onSurface + .withOpacity(0.4)))), + ], ); } + + Widget loginBySmS() { + return Column( + children: [ + const SizedBox(height: 20), + const Text('使用手机短信验证码登录'), + const SizedBox(height: 10), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + child: Container( + decoration: UnderlineTabIndicator( + borderSide: BorderSide( + color: + Theme.of(context).colorScheme.outline.withOpacity(0.4)), + ), + child: Row( + children: [ + const SizedBox(width: 12), + const Icon(Icons.phone), + const SizedBox(width: 12), + PopupMenuButton>( + padding: EdgeInsets.zero, + tooltip: '选择国际冠码,' + '当前为${_loginPageCtr.selectedCountryCodeId['cname']},' + '+${_loginPageCtr.selectedCountryCodeId['country_id']}', + //position: PopupMenuPosition.under, + onSelected: (Map type) {}, + itemBuilder: (BuildContext context) => Constants + .internationalDialingPrefix + .map((Map item) { + return PopupMenuItem>( + onTap: () { + setState(() { + _loginPageCtr.selectedCountryCodeId = item; + }); + }, + value: item, + // height: menuItemHeight, + child: Row(children: [ + Text(item['cname']), + const Spacer(), + Text("+${item['country_id']}") + ]), + ); + }).toList(), + child: Text( + "+${_loginPageCtr.selectedCountryCodeId['country_id']}"), + ), + const SizedBox(width: 6), + SizedBox( + height: 24, // 这里设置固定高度 + child: VerticalDivider( + color: Theme.of(context) + .colorScheme + .outline + .withOpacity(0.5), + ), + ), + const SizedBox(width: 6), + Expanded( + child: TextField( + controller: _loginPageCtr.telTextController, + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, + ], + decoration: InputDecoration( + border: InputBorder.none, + labelText: '手机号', + suffixIcon: IconButton( + onPressed: _loginPageCtr.telTextController.clear, + icon: const Icon(Icons.clear), + ), + ), + )), + ], + ), + )), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + child: Container( + decoration: UnderlineTabIndicator( + borderSide: BorderSide( + color: + Theme.of(context).colorScheme.outline.withOpacity(0.4)), + ), + child: Row( + children: [ + Expanded( + child: TextField( + controller: _loginPageCtr.smsCodeTextController, + decoration: const InputDecoration( + prefixIcon: Icon(Icons.key), + border: InputBorder.none, + labelText: '验证码', + ), + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, + ], + ), + ), + Obx(() => TextButton.icon( + onPressed: _loginPageCtr.smsSendCooldown > 0 + ? null + : _loginPageCtr.sendSmsCode, + icon: const Icon(Icons.send), + label: Text(_loginPageCtr.smsSendCooldown > 0 + ? '等待${_loginPageCtr.smsSendCooldown}秒' + : '获取验证码'), + )), + ], + ), + )), + const SizedBox(height: 20), + OutlinedButton.icon( + onPressed: _loginPageCtr.loginBySmsCode, + icon: const Icon(Icons.login_outlined), + label: const Text('登录'), + ), + const SizedBox(height: 20), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Text( + '手机号仅用于 bilibili 官方发送验证码与登录接口,不予保存;\n' + '本地仅存储登录凭证。\n' + '请务必在 PiliPalaX 开源仓库等可信渠道下载安装。', + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.labelSmall!.copyWith( + color: Theme.of(context) + .colorScheme + .onSurface + .withOpacity(0.4)))), + ], + ); + } + + @override + Widget build(BuildContext context) { + return OrientationBuilder(builder: (context, orientation) { + return Scaffold( + resizeToAvoidBottomInset: false, + appBar: AppBar( + leading: IconButton( + tooltip: '关闭', + icon: const Icon(Icons.close_outlined), + onPressed: Get.back), + title: Row(children: [ + const Text('登录'), + if (orientation == Orientation.landscape) ...[ + const Spacer(), + Flexible( + child: TabBar( + dividerHeight: 0, + tabs: const [ + Tab( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [Icon(Icons.lock), Text(' 密码')])), + Tab( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [Icon(Icons.key), Text(' 短信')])), + Tab( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [Icon(Icons.qr_code), Text(' 扫码')])), + ], + controller: _loginPageCtr.tabController, + )) + ] + ]), + bottom: orientation == Orientation.portrait + ? TabBar( + tabs: const [ + Tab(icon: Icon(Icons.lock), text: '密码'), + Tab(icon: Icon(Icons.key), text: '短信'), + Tab(icon: Icon(Icons.qr_code), text: '扫码'), + ], + controller: _loginPageCtr.tabController, + ) + : null, + ), + body: TabBarView( + physics: const AlwaysScrollableScrollPhysics(), + controller: _loginPageCtr.tabController, + children: [ + tabViewOuter(loginByPassword()), + tabViewOuter(loginBySmS()), + tabViewOuter(loginByQRCode()), + ], + ), + ); + }); + } + + Widget tabViewOuter(child) { + return SingleChildScrollView( + child: Align( + alignment: Alignment.topCenter, + child: SizedBox( + height: 500, + width: 600, + child: child, + ))); + } + } diff --git a/lib/pages/mine/controller.dart b/lib/pages/mine/controller.dart index be0c77e24..a34c14909 100644 --- a/lib/pages/mine/controller.dart +++ b/lib/pages/mine/controller.dart @@ -36,22 +36,20 @@ class MineController extends GetxController { onLogin() async { if (!userLogin.value) { - Get.toNamed( - '/webview', - parameters: { - 'url': 'https://passport.bilibili.com/h5-app/passport/login', - 'type': 'login', - 'pageTitle': '登录bilibili', - }, - ); - // Get.toNamed('/loginPage'); + // Get.toNamed( + // '/webview', + // parameters: { + // 'url': 'https://passport.bilibili.com/h5-app/passport/login', + // 'type': 'login', + // 'pageTitle': '登录bilibili', + // }, + // ); + Get.toNamed('/loginPage', preventDuplicates: false); } else { int mid = userInfo.value.mid!; String face = userInfo.value.face!; - Get.toNamed( - '/member?mid=$mid', - arguments: {'face': face}, - ); + Get.toNamed('/member?mid=$mid', + arguments: {'face': face}, preventDuplicates: false); } } diff --git a/lib/pages/setting/controller.dart b/lib/pages/setting/controller.dart index 3594ce4ed..4c1015ea6 100644 --- a/lib/pages/setting/controller.dart +++ b/lib/pages/setting/controller.dart @@ -7,6 +7,7 @@ import 'package:PiliPalaX/models/common/theme_type.dart'; import 'package:PiliPalaX/utils/feed_back.dart'; import 'package:PiliPalaX/utils/login.dart'; import 'package:PiliPalaX/utils/storage.dart'; +import 'package:webview_flutter/webview_flutter.dart'; import '../../models/common/dynamic_badge_mode.dart'; import '../../models/common/nav_bar_config.dart'; import '../main/index.dart'; @@ -32,7 +33,8 @@ class SettingController extends GetxController { super.onInit(); userInfo = userInfoCache.get('userInfoCache'); userLogin.value = userInfo != null; - hiddenSettingUnlocked.value = setting.get(SettingBoxKey.hiddenSettingUnlocked, defaultValue: false); + hiddenSettingUnlocked.value = + setting.get(SettingBoxKey.hiddenSettingUnlocked, defaultValue: false); feedBackEnable.value = setting.get(SettingBoxKey.feedBackEnable, defaultValue: false); toastOpacity.value = @@ -65,12 +67,23 @@ class SettingController extends GetxController { // 清空cookie await Request.cookieManager.cookieJar.deleteAll(); Request.dio.options.headers['cookie'] = ''; - // 清空本地存储的用户标识 userInfoCache.put('userInfoCache', null); - localCache - .put(LocalCacheKey.accessKey, {'mid': -1, 'value': ''}); - + localCache.put(LocalCacheKey.accessKey, + {'mid': -1, 'value': '', 'refresh': ''}); + try { + final WebViewController controller = WebViewController(); + controller.clearCache(); + controller.clearLocalStorage(); + WebViewCookieManager().clearCookies(); + } catch (e) { + print(e); + } + userLogin.value = false; + if (Get.isRegistered()) { + MainController mainController = Get.find(); + mainController.userLogin.value = false; + } await LoginUtils.refreshLoginStatus(false); Get.back(); }, @@ -107,8 +120,7 @@ class SettingController extends GetxController { dynamicBadgeType.value = result; setting.put(SettingBoxKey.dynamicBadgeMode, result.code); MainController mainController = Get.put(MainController()); - mainController.dynamicBadgeType = - DynamicBadgeMode.values[result.code]; + mainController.dynamicBadgeType = DynamicBadgeMode.values[result.code]; if (mainController.dynamicBadgeType != DynamicBadgeMode.hidden) { mainController.getUnreadDynamic(); } diff --git a/lib/pages/setting/privacy_setting.dart b/lib/pages/setting/privacy_setting.dart index 66c3dc12d..212d8cc7e 100644 --- a/lib/pages/setting/privacy_setting.dart +++ b/lib/pages/setting/privacy_setting.dart @@ -1,4 +1,3 @@ -import 'package:PiliPalaX/utils/cookie.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; @@ -68,42 +67,26 @@ class _PrivacySettingState extends State { subtitle: Text('已拉黑用户', style: subTitleStyle), leading: const Icon(Icons.block), ), - ListTile( - onTap: () async { - if (!userLogin) { - SmartDialog.showToast('请先登录'); - return; - } - var res = await MemberHttp.cookieToKey(); - if (res['status']) { - SmartDialog.showToast(res['msg']); - } else { - SmartDialog.showToast("刷新失败:${res['msg']}"); - } - }, - dense: false, - title: Text('刷新access_key', style: titleStyle), - leading: const Icon(Icons.perm_device_info_outlined), - subtitle: Text( - '用于app端推荐接口的用户凭证。刷新有小概率导致其他设备下线。若app端未推荐个性化内容,可尝试刷新或清除本app数据后重新登录', - style: subTitleStyle), - ), - if (hiddenSettingUnlocked) - ListTile( - title: Text( - '导入/导出cookie', - style: titleStyle, - ), - subtitle: Text( - 'cookie代表您的登录状态,仅供高级用户使用', - style: subTitleStyle, - ), - leading: const Icon(Icons.cookie_outlined), - dense: false, - onTap: () { - import_export_cookies(titleStyle, subTitleStyle); - }, - ), + // ListTile( + // onTap: () async { + // if (!userLogin) { + // SmartDialog.showToast('请先登录'); + // return; + // } + // var res = await MemberHttp.cookieToKey(); + // if (res['status']) { + // SmartDialog.showToast(res['msg']); + // } else { + // SmartDialog.showToast("刷新失败:${res['msg']}"); + // } + // }, + // dense: false, + // title: Text('刷新access_key', style: titleStyle), + // leading: const Icon(Icons.perm_device_info_outlined), + // subtitle: Text( + // '用于app端推荐接口的用户凭证。若app端未推荐个性化内容,可尝试刷新或清除本app数据后重新登录', + // style: subTitleStyle), + // ), ListTile( onTap: () { MineController.onChangeAnonymity(context); @@ -150,147 +133,4 @@ class _PrivacySettingState extends State { ); } - void import_export_cookies(TextStyle titleStyle, TextStyle subTitleStyle) { - showDialog( - context: context, - builder: (context) { - return SimpleDialog( - title: const Text('导入/导出cookie', style: TextStyle(color: Colors.red)), - children: [ - ListTile( - title: Text( - '导出cookie至剪贴板', - style: titleStyle.copyWith(color: Colors.red), - ), - leading: const Icon( - Icons.warning_amber, - color: Colors.red, - ), - subtitle: Text( - '泄露账号cookie等同于绕过账号密码与验证码直接登录,可导致隐私泄露、风控、毁号、盗号等各类问题。\n' - '你应妥善保管该cookie且仅供自己使用。你承诺,不会利用本服务进行任何违法或不当的活动。你承诺,对所进行的一切活动' - '(包括但不限于网上点击同意或提交各类规则协议或购买服务、分享资讯或图片等)负全部责任。\n' - '你承诺、理解、同意并确认,在你的账户遭到未获授权的使用,或者发生其他任何安全问题时,' - '作者不对上述情形产生的任何直接或间接的遗失或损害承担责任。', - style: subTitleStyle.copyWith(color: Colors.redAccent), - ), - dense: false, - onTap: () async { - Navigator.of(context).pop(); - if (!userLogin) { - SmartDialog.showToast('请先登录'); - return; - } - final String cookie = await CookieTool.exportCookie(); - await showDialog( - context: context, - builder: (context) { - return AlertDialog( - title: const Text('导出cookie(危险)', - style: TextStyle(color: Colors.red)), - content: Text(cookie), - actions: [ - TextButton( - onPressed: () async { - Navigator.of(context).pop(); - await Clipboard.setData( - ClipboardData(text: cookie)); - }, - child: const Text('复制(危险)', - style: TextStyle(color: Colors.red)), - ), - TextButton( - onPressed: () async { - Navigator.of(context).pop(); - }, - child: const Text('取消'), - ), - ], - ); - }, - ); - }), - ListTile( - title: Text( - '从剪贴板导入cookie', - style: titleStyle, - ), - leading: const Icon( - Icons.warning_amber, - color: Colors.red, - ), - subtitle: Text( - '导入将覆盖当前登录状态,你应自行对利用服务从事的所有行为及结果承担责任,请慎用', - style: subTitleStyle, - ), - dense: false, - onTap: () async { - ClipboardData? data = await Clipboard.getData('text/plain'); - if (data == null || data.text == null || data.text == '') { - SmartDialog.showToast('未检测到剪贴板内容'); - return; - } - if (!context.mounted) return; - await showDialog( - context: context, - builder: (context) { - return AlertDialog( - title: const Text('导入剪贴板中的cookie'), - content: Text(data.text!), - actions: [ - TextButton( - onPressed: () async { - Get.back(); - }, - child: const Text('取消'), - ), - TextButton( - onPressed: () async { - Get.back(); - final String cookie = data.text!; - try { - await CookieTool.importCookie(cookie); - await SmartDialog.showToast('已导入'); - await CookieTool.onSet(); - final result = await UserHttp.userInfo(); - if (result['status'] && - result['data'].isLogin) { - SmartDialog.showToast('登录成功,当前采用「' - '${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.find(); - mediaCtr.mid = result['data'].mid; - await LoginUtils.refreshLoginStatus(true); - Get.back(); - } else { - // 获取用户信息失败 - SmartDialog.showNotify( - msg: - '登录失败,请检查cookie是否正确,${result['message']}', - notifyType: NotifyType.warning); - } - } catch (e) { - SmartDialog.showToast('导入失败:$e'); - } - }, - child: const Text('确认'), - ), - ], - ); - }, - ); - }), - ], - ); - }, - ); - } } diff --git a/lib/pages/webview/controller.dart b/lib/pages/webview/controller.dart index ae5b54292..936ced848 100644 --- a/lib/pages/webview/controller.dart +++ b/lib/pages/webview/controller.dart @@ -1,25 +1,16 @@ // ignore_for_file: avoid_print -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; -import 'package:hive/hive.dart'; import 'package:PiliPalaX/http/init.dart'; -import 'package:PiliPalaX/http/user.dart'; -import 'package:PiliPalaX/pages/home/index.dart'; -import 'package:PiliPalaX/pages/media/index.dart'; -import 'package:PiliPalaX/utils/cookie.dart'; import 'package:PiliPalaX/utils/event_bus.dart'; import 'package:PiliPalaX/utils/id_utils.dart'; -import 'package:PiliPalaX/utils/login.dart'; -import 'package:PiliPalaX/utils/storage.dart'; import 'package:webview_flutter/webview_flutter.dart'; class WebviewController extends GetxController { String url = ''; RxString type = ''.obs; String pageTitle = ''; + String uaType = ''; final WebViewController controller = WebViewController(); RxInt loadProgress = 0.obs; RxBool loadShow = true.obs; @@ -31,13 +22,9 @@ class WebviewController extends GetxController { url = Get.parameters['url']!; type.value = Get.parameters['type']!; pageTitle = Get.parameters['pageTitle']!; + uaType = Get.parameters['uaType'] ?? 'mob'; - if (type.value == 'login') { - controller.clearCache(); - controller.clearLocalStorage(); - WebViewCookieManager().clearCookies(); - } - webviewInit(); + webviewInit(uaType: uaType); } webviewInit({String uaType = 'mob'}) { @@ -85,13 +72,7 @@ class WebviewController extends GetxController { // 加载完成 onUrlChange: (UrlChange urlChange) async { loadShow.value = false; - String url = urlChange.url ?? ''; - if (type.value == 'login' && - (url.startsWith( - 'https://passport.bilibili.com/web/sso/exchange_cookie') || - url.startsWith('https://m.bilibili.com/'))) { - confirmLogin(url); - } + // String url = urlChange.url ?? ''; }, onWebResourceError: (WebResourceError error) {}, onNavigationRequest: (NavigationRequest request) { @@ -112,52 +93,4 @@ class WebviewController extends GetxController { ..loadRequest(Uri.parse(url)); } - confirmLogin(url) async { - var content = ''; - if (url != null) { - content = '${content + url}; \n'; - } - try { - await CookieTool.onSet(); - final result = await UserHttp.userInfo(); - if (result['status'] && result['data'].isLogin) { - SmartDialog.showToast('登录成功,当前采用「' - '${GStorage.setting.get(SettingBoxKey.defaultRcmdType, defaultValue: 'web')}' - '端」推荐'); - try { - 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.find(); - mediaCtr.mid = result['data'].mid; - await LoginUtils.refreshLoginStatus(true); - } catch (err) { - SmartDialog.show(builder: (BuildContext context) { - return AlertDialog( - title: const Text('登录遇到问题'), - content: Text(err.toString()), - actions: [ - TextButton( - onPressed: () => controller.reload(), - child: const Text('确认'), - ) - ], - ); - }); - } - Get.back(); - } else { - // 获取用户信息失败 - SmartDialog.showToast(result['msg']); - Clipboard.setData(ClipboardData(text: result['msg'])); - } - } catch (e) { - SmartDialog.showNotify(msg: e.toString(), notifyType: NotifyType.warning); - content = content + e.toString(); - Clipboard.setData(ClipboardData(text: content)); - } - } } diff --git a/lib/pages/webview/view.dart b/lib/pages/webview/view.dart index c30c714ed..585cf8d59 100644 --- a/lib/pages/webview/view.dart +++ b/lib/pages/webview/view.dart @@ -36,25 +36,14 @@ class _WebviewPageState extends State { icon: Icon(Icons.refresh_outlined, color: Theme.of(context).colorScheme.primary), ), - if (_webviewController.type.value != 'login') - IconButton( - tooltip: '用外部浏览器打开', - onPressed: () { - launchUrl(Uri.parse(_webviewController.url)); - }, - icon: Icon(Icons.open_in_browser_outlined, - color: Theme.of(context).colorScheme.primary), - ), - if (_webviewController.type.value == 'login') ...[ - TextButton( - onPressed: () => _webviewController.confirmLogin(null), - child: const Text('刷新登录态'), - ), - TextButton( - child: const Text('电脑版'), - onPressed: () => _webviewController.webviewInit(uaType: 'pc'), - ) - ], + IconButton( + tooltip: '用外部浏览器打开', + onPressed: () { + launchUrl(Uri.parse(_webviewController.url)); + }, + icon: Icon(Icons.open_in_browser_outlined, + color: Theme.of(context).colorScheme.primary), + ), const SizedBox(width: 12) ], ), diff --git a/lib/utils/cookie.dart b/lib/utils/cookie.dart deleted file mode 100644 index b61072735..000000000 --- a/lib/utils/cookie.dart +++ /dev/null @@ -1,49 +0,0 @@ -import 'dart:convert'; -import 'dart:io'; - -import 'package:PiliPalaX/http/constants.dart'; -import 'package:PiliPalaX/http/init.dart'; -import 'package:webview_cookie_manager/webview_cookie_manager.dart'; - -class CookieTool { - static exportCookie() async { - Map allCookies = {}; - List Urls = [HttpString.baseUrl, HttpString.apiBaseUrl, HttpString.tUrl]; - for (var url in Urls) { - allCookies[url] = await WebviewCookieManager().getCookies(url) - .then((cookies) => cookies.map((cookie) => '${cookie.name}=${cookie.value}').join('; ')); - } - return jsonEncode(allCookies); - } - static importCookie(String cookie) async { - var allCookies = jsonDecode(cookie); - for (var url in allCookies.keys) { - List cookiesStringList = allCookies[url]!.split('; '); - List cookies = []; - for (var c in cookiesStringList) { - List kv = c.split('='); - cookies.add(Cookie(kv[0], kv[1])); - } - await Request.cookieManager.cookieJar.saveFromResponse(Uri.parse(url), cookies); - if (url == HttpString.baseUrl) { - Request.dio.options.headers['cookie'] = allCookies[url]; - } - } - } - static onSet() async { - var cookies = await WebviewCookieManager().getCookies(HttpString.baseUrl); - await Request.cookieManager.cookieJar - .saveFromResponse(Uri.parse(HttpString.baseUrl), cookies); - var cookieString = - cookies.map((cookie) => '${cookie.name}=${cookie.value}').join('; '); - Request.dio.options.headers['cookie'] = cookieString; - - cookies = await WebviewCookieManager().getCookies(HttpString.apiBaseUrl); - await Request.cookieManager.cookieJar - .saveFromResponse(Uri.parse(HttpString.apiBaseUrl), cookies); - - cookies = await WebviewCookieManager().getCookies(HttpString.tUrl); - await Request.cookieManager.cookieJar - .saveFromResponse(Uri.parse(HttpString.tUrl), cookies); - } -} diff --git a/pubspec.lock b/pubspec.lock index a8b44c4da..6a8a33c35 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -657,10 +657,10 @@ packages: dependency: "direct main" description: name: gt3_flutter_plugin - sha256: "786b3b53e117678845488f8212d1b959cb969a94e7f5e68d7698d98cf6929eef" + sha256: "08f35692e937770ad6b3e2017eb8ef81839a82b8a63f5acf3abab14b688fc36c" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted - version: "0.0.9" + version: "0.1.0" hive: dependency: "direct main" description: @@ -1183,6 +1183,22 @@ packages: url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted version: "3.1.0" + qr: + dependency: transitive + description: + name: qr + sha256: "64957a3930367bf97cc211a5af99551d630f2f4625e38af10edd6b19131b64b3" + url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" + source: hosted + version: "3.0.1" + qr_flutter: + dependency: "direct main" + description: + name: qr_flutter + sha256: "5095f0fc6e3f71d08adef8feccc8cea4f12eec18a2e31c2e8d82cb6019f4b097" + url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" + source: hosted + version: "4.1.0" rxdart: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 07cef833f..66767a8be 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 1.0.21+114514 +version: 1.0.22+114514 environment: sdk: ">=2.19.6 <3.0.0" @@ -52,6 +52,9 @@ dependencies: extended_image: ^8.2.0 saver_gallery: ^3.0.1 + # QRCode + qr_flutter: ^4.1.0 + # 存储 path_provider: ^2.1.1 hive: ^2.2.3 @@ -128,7 +131,7 @@ dependencies: # html渲染 flutter_html: ^3.0.0-beta.2 # 极验 - gt3_flutter_plugin: ^0.0.8 + gt3_flutter_plugin: ^0.1.0 uuid: ^3.0.7 scrollable_positioned_list: ^0.3.8 nil: ^1.1.1