mirror of
https://github.com/HChaZZY/PiliPlus.git
synced 2025-12-06 09:13:48 +08:00
feat: 新版登录页:以APP接口和新界面全面重构网页版登录;更新二维码与极验插件;更新版本号
This commit is contained in:
@@ -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<Map<String, dynamic>> 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"}
|
||||
];
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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';
|
||||
|
||||
/// 密码加密密钥
|
||||
|
||||
@@ -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<String> 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,
|
||||
<String, String?>{'mid': mid, 'value': accessKey});
|
||||
} catch (_) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
print('ApiInterceptor: $err');
|
||||
}
|
||||
// @override
|
||||
// void onResponse(Response response, ResponseInterceptorHandler handler) {
|
||||
// try {
|
||||
// if (response.statusCode == 302) {
|
||||
// final List<String> 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,
|
||||
// <String, String?>{'mid': mid, 'value': accessKey});
|
||||
// } catch (_) {}
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// } catch (err) {
|
||||
// print('ApiInterceptor: $err');
|
||||
// }
|
||||
|
||||
handler.next(response);
|
||||
}
|
||||
// handler.next(response);
|
||||
// }
|
||||
|
||||
@override
|
||||
void onError(DioException err, ErrorInterceptorHandler handler) async {
|
||||
|
||||
@@ -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<Map<String, dynamic>> 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<String, dynamic> 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 = <String>[];
|
||||
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<int> 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<String, dynamic> 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<String, dynamic>(key, value);
|
||||
});
|
||||
final Map<String, String> 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<String, dynamic> 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<String, dynamic>(key, value);
|
||||
});
|
||||
final Map<String, String> 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']
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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});
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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<FormState>();
|
||||
final GlobalKey passwordFormKey = GlobalKey<FormState>();
|
||||
final GlobalKey msgCodeFormKey = GlobalKey<FormState>();
|
||||
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<Map<String, dynamic>> codeInfo = Rx<Map<String, dynamic>>({});
|
||||
|
||||
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<String> statusQRCode = ''.obs;
|
||||
|
||||
// 监听pageView切换
|
||||
void onPageChange(int index) {
|
||||
currentIndex.value = index;
|
||||
Map<String, dynamic> 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<String, dynamic> 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<dynamic> cookieInfo = cookie_info['cookies'];
|
||||
print("cookieInfo");
|
||||
print(cookieInfo);
|
||||
List<Cookie> 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<String> 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<HomeController>();
|
||||
homeCtr.updateLoginStatus(true);
|
||||
homeCtr.userFace.value = result['data'].face;
|
||||
final MediaController mediaCtr = Get.find<MediaController>();
|
||||
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<String, dynamic> message) async {
|
||||
}, onClose: (Map<String, dynamic> message) async {
|
||||
SmartDialog.showToast('关闭验证');
|
||||
}, onResult: (Map<String, dynamic> 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<String, dynamic> 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<String, dynamic> message) async {},
|
||||
onClose: (Map<String, dynamic> message) async {
|
||||
SmartDialog.showToast('关闭验证');
|
||||
},
|
||||
onResult: (Map<String, dynamic> 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<String, dynamic> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<LoginPage> {
|
||||
final LoginPageController _loginPageCtr = Get.put(LoginPageController());
|
||||
|
||||
// late Future<Map<String, dynamic>> 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<Map<String, dynamic>>(
|
||||
padding: EdgeInsets.zero,
|
||||
tooltip: '选择国际冠码,'
|
||||
'当前为${_loginPageCtr.selectedCountryCodeId['cname']},'
|
||||
'+${_loginPageCtr.selectedCountryCodeId['country_id']}',
|
||||
//position: PopupMenuPosition.under,
|
||||
onSelected: (Map<String, dynamic> type) {},
|
||||
itemBuilder: (BuildContext context) => Constants
|
||||
.internationalDialingPrefix
|
||||
.map((Map<String, dynamic> item) {
|
||||
return PopupMenuItem<Map<String, dynamic>>(
|
||||
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: <TextInputFormatter>[
|
||||
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: <TextInputFormatter>[
|
||||
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,
|
||||
)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 mainController = Get.find<MainController>();
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -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<PrivacySetting> {
|
||||
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<PrivacySetting> {
|
||||
);
|
||||
}
|
||||
|
||||
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<HomeController>();
|
||||
homeCtr.updateLoginStatus(true);
|
||||
homeCtr.userFace.value = result['data'].face;
|
||||
final MediaController mediaCtr =
|
||||
Get.find<MediaController>();
|
||||
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('确认'),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<HomeController>();
|
||||
homeCtr.updateLoginStatus(true);
|
||||
homeCtr.userFace.value = result['data'].face;
|
||||
final MediaController mediaCtr = Get.find<MediaController>();
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,25 +36,14 @@ class _WebviewPageState extends State<WebviewPage> {
|
||||
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') ...<Widget>[
|
||||
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)
|
||||
],
|
||||
),
|
||||
|
||||
@@ -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<String, String> allCookies = {};
|
||||
List<String> 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<String> cookiesStringList = allCookies[url]!.split('; ');
|
||||
List<Cookie> cookies = [];
|
||||
for (var c in cookiesStringList) {
|
||||
List<String> 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user