mirror of
https://github.com/HChaZZY/PiliPlus.git
synced 2025-12-06 09:13:48 +08:00
feat: account manager (#468)
* feat: account manager * remove dep * some fixes * migrate accounts * reimplement clearCookie
This commit is contained in:
committed by
GitHub
parent
94fa0652ac
commit
b15fdfa2ff
@@ -131,7 +131,7 @@ class _ListSheetContentState extends CommonSlidePageState<ListSheetContent>
|
||||
reverse = _isList
|
||||
? List.generate(widget.season.sections.length, (_) => false)
|
||||
: [false];
|
||||
if (GStorage.isLogin && widget.bvid != null && widget.season != null) {
|
||||
if (Accounts.main.isLogin && widget.bvid != null && widget.season != null) {
|
||||
_favStream ??= StreamController<int>();
|
||||
() async {
|
||||
dynamic result = await VideoHttp.videoRelation(bvid: widget.bvid);
|
||||
|
||||
@@ -68,8 +68,7 @@ class VideoCustomActions {
|
||||
VideoCustomAction(
|
||||
'不感兴趣', 'dislike', Icon(MdiIcons.thumbDownOutline, size: 16),
|
||||
() async {
|
||||
String? accessKey = GStorage.localCache
|
||||
.get(LocalCacheKey.accessKey, defaultValue: {})['value'];
|
||||
String? accessKey = Accounts.get(AccountType.recommend).accessKey;
|
||||
if (accessKey == null || accessKey == "") {
|
||||
SmartDialog.showToast("请退出账号后重新登录");
|
||||
return;
|
||||
|
||||
@@ -43,16 +43,12 @@ class GrpcRepo {
|
||||
static const gzipEncoder = GZipEncoder();
|
||||
static const gzipDecoder = GZipDecoder();
|
||||
|
||||
static final bool _isLogin = GStorage.userInfo.get('userInfoCache') != null;
|
||||
static final int? _mid = GStorage.userInfo.get('userInfoCache')?.mid;
|
||||
static final String? _accessKey = GStorage.localCache
|
||||
.get(LocalCacheKey.accessKey, defaultValue: {})['value'];
|
||||
static final String? _accessKey = Accounts.main.accessKey;
|
||||
static const _build = 1462100;
|
||||
static const _biliChannel = 'bili';
|
||||
static const _mobiApp = 'android_hd';
|
||||
static const _phone = 'phone';
|
||||
|
||||
static final _eId = _isLogin ? Utils.genAuroraEid(_mid!) : '';
|
||||
static final _buvid = LoginUtils.buvid;
|
||||
static final _traceId = Utils.genTraceId();
|
||||
static final _sessionId = Utils.generateRandomString(8);
|
||||
@@ -63,11 +59,9 @@ class GrpcRepo {
|
||||
'gzip-accept-encoding': 'gzip,identity',
|
||||
'user-agent': '${Constants.userAgent} grpc-java-cronet/1.36.1',
|
||||
'x-bili-gaia-vtoken': '',
|
||||
'x-bili-aurora-eid': _isLogin ? _eId : '',
|
||||
'x-bili-mid': _isLogin ? _mid.toString() : '0',
|
||||
'x-bili-aurora-zone': '',
|
||||
'x-bili-trace-id': _traceId,
|
||||
if (_isLogin) 'authorization': 'identify_v1 $_accessKey',
|
||||
if (_accessKey != null) 'authorization': 'identify_v1 $_accessKey',
|
||||
'buvid': _buvid,
|
||||
'bili-http-engine': 'cronet',
|
||||
'te': 'trailers',
|
||||
|
||||
@@ -622,6 +622,8 @@ class Api {
|
||||
static const qrcodePoll =
|
||||
'${HttpString.passBaseUrl}/x/passport-tv-login/qrcode/poll';
|
||||
|
||||
static const logout = '${HttpString.passBaseUrl}/login/exit/v2';
|
||||
|
||||
/// 置顶视频
|
||||
static const getTopVideoApi = '/x/space/top/arc';
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/utils/accounts/account.dart';
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
@@ -92,7 +93,7 @@ class DynamicsHttp {
|
||||
dynamic id,
|
||||
dynamic rid,
|
||||
dynamic type,
|
||||
bool? clearCookie,
|
||||
bool clearCookie = false,
|
||||
}) async {
|
||||
var res = await Request().get(
|
||||
Api.dynamicDetail,
|
||||
@@ -104,7 +105,7 @@ class DynamicsHttp {
|
||||
'features': 'itemOpusStyle',
|
||||
},
|
||||
options:
|
||||
clearCookie == true ? Options(extra: {'clearCookie': true}) : null,
|
||||
clearCookie ? Options(extra: {'account': AnonymousAccount()}) : null,
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
try {
|
||||
|
||||
@@ -4,20 +4,17 @@ import 'dart:developer';
|
||||
import 'dart:io';
|
||||
import 'dart:math' show Random;
|
||||
import 'package:PiliPlus/build_config.dart';
|
||||
import 'package:PiliPlus/utils/accounts/account.dart';
|
||||
import 'package:PiliPlus/utils/accounts/account_manager/account_mgr.dart';
|
||||
import 'package:archive/archive.dart';
|
||||
import 'package:brotli/brotli.dart';
|
||||
import 'package:cookie_jar/cookie_jar.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:dio/io.dart';
|
||||
import 'package:dio_cookie_manager/dio_cookie_manager.dart';
|
||||
import 'package:dio_http2_adapter/dio_http2_adapter.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:PiliPlus/utils/id_utils.dart';
|
||||
import '../utils/storage.dart';
|
||||
import '../utils/utils.dart';
|
||||
import 'api.dart';
|
||||
import 'constants.dart';
|
||||
import 'interceptor.dart';
|
||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart' as web;
|
||||
|
||||
class Request {
|
||||
@@ -25,101 +22,69 @@ class Request {
|
||||
static const brotilDecoder = BrotliDecoder();
|
||||
|
||||
static final Request _instance = Request._internal();
|
||||
static late CookieManager cookieManager;
|
||||
static late AccountManager accountManager;
|
||||
static late final Dio dio;
|
||||
factory Request() => _instance;
|
||||
late bool enableSystemProxy;
|
||||
late String systemProxyHost;
|
||||
late String systemProxyPort;
|
||||
static final _rand = Random();
|
||||
static final RegExp spmPrefixExp =
|
||||
RegExp(r'<meta name="spm_prefix" content="([^"]+?)">');
|
||||
|
||||
/// 设置cookie
|
||||
static setCookie() async {
|
||||
final String cookiePath = await Utils.getCookiePath();
|
||||
final PersistCookieJar cookieJar = PersistCookieJar(
|
||||
ignoreExpires: true,
|
||||
storage: FileStorage(cookiePath),
|
||||
);
|
||||
cookieManager = CookieManager(cookieJar);
|
||||
dio.interceptors.add(cookieManager);
|
||||
dio.interceptors.add(ApiInterceptor());
|
||||
final List<Cookie> cookies = await cookieManager.cookieJar
|
||||
.loadForRequest(Uri.parse(HttpString.baseUrl));
|
||||
for (Cookie item in cookies) {
|
||||
await web.CookieManager().setCookie(
|
||||
url: web.WebUri(item.domain ?? ''),
|
||||
name: item.name,
|
||||
value: item.value,
|
||||
path: item.path ?? '',
|
||||
domain: item.domain,
|
||||
isSecure: item.secure,
|
||||
isHttpOnly: item.httpOnly,
|
||||
);
|
||||
}
|
||||
final userInfo = GStorage.userInfo.get('userInfoCache');
|
||||
if (userInfo?.mid != null) {
|
||||
final List<Cookie> tUrlCookies = await cookieManager.cookieJar
|
||||
.loadForRequest(Uri.parse(HttpString.tUrl));
|
||||
if (tUrlCookies.isEmpty) {
|
||||
try {
|
||||
await dio.head(HttpString.tUrl);
|
||||
} catch (e) {
|
||||
log("setCookie, ${e.toString()}");
|
||||
}
|
||||
}
|
||||
setOptionsHeaders(userInfo);
|
||||
}
|
||||
|
||||
try {
|
||||
await buvidActivate();
|
||||
} catch (e) {
|
||||
log("setCookie, ${e.toString()}");
|
||||
}
|
||||
|
||||
// final String cookieString = cookies
|
||||
// .map((Cookie cookie) => '${cookie.name}=${cookie.value}')
|
||||
// .join('; ');
|
||||
// dio.options.headers['cookie'] = cookieString;
|
||||
accountManager = AccountManager();
|
||||
dio.interceptors.add(accountManager);
|
||||
await Accounts.refresh();
|
||||
final List<Cookie> cookies = Accounts.main.cookieJar.toList();
|
||||
final webManager = web.CookieManager();
|
||||
await Future.wait(cookies.map((item) => webManager.setCookie(
|
||||
url: web.WebUri(item.domain ?? ''),
|
||||
name: item.name,
|
||||
value: item.value,
|
||||
path: item.path ?? '',
|
||||
domain: item.domain,
|
||||
isSecure: item.secure,
|
||||
isHttpOnly: item.httpOnly,
|
||||
)));
|
||||
}
|
||||
|
||||
// 从cookie中获取 csrf token
|
||||
static Future<String> getCsrf() async {
|
||||
List<Cookie> cookies = await cookieManager.cookieJar
|
||||
.loadForRequest(Uri.parse(HttpString.apiBaseUrl));
|
||||
return cookies
|
||||
.firstWhere((e) => e.name == 'bili_jct', orElse: () => Cookie('', ''))
|
||||
.value;
|
||||
return Accounts.main.csrf;
|
||||
}
|
||||
|
||||
static setOptionsHeaders(userInfo) {
|
||||
dio.options.headers['x-bili-mid'] = userInfo.mid.toString();
|
||||
dio.options.headers['x-bili-aurora-eid'] =
|
||||
IdUtils.genAuroraEid(userInfo.mid);
|
||||
}
|
||||
static Future<void> buvidActive(Account account) async {
|
||||
// 这样线程不安全, 但仍按预期进行
|
||||
if (account.activited) return;
|
||||
account.activited = true;
|
||||
try {
|
||||
final html = await Request().get(Api.dynamicSpmPrefix,
|
||||
options: Options(extra: {'account': account}));
|
||||
final String spmPrefix = spmPrefixExp.firstMatch(html.data)!.group(1)!;
|
||||
final String randPngEnd = base64.encode(
|
||||
List<int>.generate(32, (_) => _rand.nextInt(256)) +
|
||||
List<int>.filled(4, 0) +
|
||||
[73, 69, 78, 68] +
|
||||
List<int>.generate(4, (_) => _rand.nextInt(256)));
|
||||
|
||||
static Future buvidActivate() async {
|
||||
var html = await Request().get(Api.dynamicSpmPrefix);
|
||||
String spmPrefix = spmPrefixExp.firstMatch(html.data)!.group(1)!;
|
||||
Random rand = Random();
|
||||
String randPngEnd = base64.encode(
|
||||
List<int>.generate(32, (_) => rand.nextInt(256)) +
|
||||
List<int>.filled(4, 0) +
|
||||
[73, 69, 78, 68] +
|
||||
List<int>.generate(4, (_) => rand.nextInt(256)));
|
||||
String jsonData = json.encode({
|
||||
'3064': 1,
|
||||
'39c8': '$spmPrefix.fp.risk',
|
||||
'3c43': {
|
||||
'adca': 'Linux',
|
||||
'bfe9': randPngEnd.substring(randPngEnd.length - 50),
|
||||
},
|
||||
});
|
||||
|
||||
String jsonData = json.encode({
|
||||
'3064': 1,
|
||||
'39c8': '$spmPrefix.fp.risk',
|
||||
'3c43': {
|
||||
'adca': 'Linux',
|
||||
'bfe9': randPngEnd.substring(randPngEnd.length - 50),
|
||||
},
|
||||
});
|
||||
|
||||
await Request().post(Api.activateBuvidApi,
|
||||
data: {'payload': jsonData},
|
||||
options: Options(contentType: Headers.jsonContentType));
|
||||
await Request().post(Api.activateBuvidApi,
|
||||
data: {'payload': jsonData},
|
||||
options: Options(contentType: Headers.jsonContentType));
|
||||
;
|
||||
} catch (e) {
|
||||
log("setCookie, $e");
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -225,7 +190,7 @@ class Request {
|
||||
} on DioException catch (e) {
|
||||
Response errResponse = Response(
|
||||
data: {
|
||||
'message': await ApiInterceptor.dioError(e)
|
||||
'message': await AccountManager.dioError(e)
|
||||
}, // 将自定义 Map 数据赋值给 Response 的 data 属性
|
||||
statusCode: -1,
|
||||
requestOptions: RequestOptions(),
|
||||
@@ -254,7 +219,7 @@ class Request {
|
||||
} on DioException catch (e) {
|
||||
Response errResponse = Response(
|
||||
data: {
|
||||
'message': await ApiInterceptor.dioError(e)
|
||||
'message': await AccountManager.dioError(e)
|
||||
}, // 将自定义 Map 数据赋值给 Response 的 data 属性
|
||||
statusCode: -1,
|
||||
requestOptions: RequestOptions(),
|
||||
@@ -279,7 +244,7 @@ class Request {
|
||||
return response.data;
|
||||
} on DioException catch (e) {
|
||||
debugPrint('downloadFile error: $e');
|
||||
return Future.error(ApiInterceptor.dioError(e));
|
||||
return Future.error(AccountManager.dioError(e));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
import 'package:PiliPlus/http/api.dart';
|
||||
import 'package:PiliPlus/pages/mine/controller.dart';
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
|
||||
class ApiInterceptor extends Interceptor {
|
||||
static const List<String> anonymityList = [
|
||||
Api.videoUrl,
|
||||
Api.videoIntro,
|
||||
Api.relatedList,
|
||||
Api.replyList,
|
||||
Api.replyReplyList,
|
||||
Api.searchSuggest,
|
||||
Api.searchByType,
|
||||
Api.heartBeat,
|
||||
Api.ab2c,
|
||||
Api.bangumiInfo,
|
||||
Api.liveRoomInfo,
|
||||
Api.onlineTotal,
|
||||
Api.dynamicDetail,
|
||||
Api.aiConclusion,
|
||||
Api.getSeasonDetailApi,
|
||||
Api.liveRoomDmToken,
|
||||
Api.liveRoomDmPrefetch,
|
||||
];
|
||||
|
||||
@override
|
||||
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
|
||||
void onRemoveCookie() {
|
||||
options.headers.remove('x-bili-mid');
|
||||
options.headers.remove('x-bili-aurora-eid');
|
||||
options.headers.remove('x-bili-aurora-zone');
|
||||
options.headers['cookie'] = '';
|
||||
options.queryParameters.remove('access_key');
|
||||
options.queryParameters.remove('csrf');
|
||||
options.queryParameters.remove('csrf_token');
|
||||
if (options.data is Map) {
|
||||
options.data.remove('access_key');
|
||||
options.data.remove('csrf');
|
||||
options.data.remove('csrf_token');
|
||||
}
|
||||
}
|
||||
|
||||
// app端不需要cookie
|
||||
// if (options.uri.host == 'app.bilibili.com') {
|
||||
// options.headers.remove('cookie');
|
||||
// }
|
||||
|
||||
if (options.extra['clearCookie'] == true) {
|
||||
onRemoveCookie();
|
||||
} else if (MineController.anonymity.value) {
|
||||
String uri = options.uri.toString();
|
||||
for (var i in anonymityList) {
|
||||
// 如果请求的url包含无痕列表中的url,则清空cookie
|
||||
// 但需要保证匹配到url的后半部分不再出现/符号,否则会误伤
|
||||
int index = uri.indexOf(i);
|
||||
if (index == -1) continue;
|
||||
if (uri.lastIndexOf('/') >= index + i.length) continue;
|
||||
//SmartDialog.showToast('触发无痕模式\n\n$i\n\n${options.uri}');
|
||||
onRemoveCookie();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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')) {
|
||||
// debugPrint('ApiInterceptor@@@@@: ${locations.first}');
|
||||
// final Uri uri = Uri.parse(locations.first);
|
||||
// final String? accessKey = uri.queryParameters['access_key'];
|
||||
// final String? mid = uri.queryParameters['mid'];
|
||||
// try {
|
||||
// GStorage.localCache.put(LocalCacheKey.accessKey,
|
||||
// <String, String?>{'mid': mid, 'value': accessKey});
|
||||
// } catch (_) {}
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// } catch (err) {
|
||||
// debugPrint('ApiInterceptor: $err');
|
||||
// }
|
||||
|
||||
// handler.next(response);
|
||||
// }
|
||||
|
||||
@override
|
||||
void onError(DioException err, ErrorInterceptorHandler handler) async {
|
||||
// 处理网络请求错误
|
||||
// handler.next(err);
|
||||
String url = err.requestOptions.uri.toString();
|
||||
debugPrint('🌹🌹ApiInterceptor: $url');
|
||||
if (url.contains('heartbeat') ||
|
||||
url.contains('seg.so') ||
|
||||
url.contains('online/total') ||
|
||||
url.contains('github') ||
|
||||
(url.contains('skipSegments') && err.requestOptions.method == 'GET')) {
|
||||
// skip
|
||||
} else {
|
||||
SmartDialog.showToast(
|
||||
await dioError(err) + url,
|
||||
// displayType: SmartToastType.onlyRefresh,
|
||||
// displayTime: const Duration(milliseconds: 1200),
|
||||
);
|
||||
}
|
||||
super.onError(err, handler);
|
||||
}
|
||||
|
||||
static Future<String> dioError(DioException error) async {
|
||||
switch (error.type) {
|
||||
case DioExceptionType.badCertificate:
|
||||
return '证书有误!';
|
||||
case DioExceptionType.badResponse:
|
||||
return '服务器异常,请稍后重试!';
|
||||
case DioExceptionType.cancel:
|
||||
return '请求已被取消,请重新请求';
|
||||
case DioExceptionType.connectionError:
|
||||
return '连接错误,请检查网络设置';
|
||||
case DioExceptionType.connectionTimeout:
|
||||
return '网络连接超时,请检查网络设置';
|
||||
case DioExceptionType.receiveTimeout:
|
||||
return '响应超时,请稍后重试!';
|
||||
case DioExceptionType.sendTimeout:
|
||||
return '发送请求超时,请检查网络设置';
|
||||
case DioExceptionType.unknown:
|
||||
final String res =
|
||||
(await Connectivity().checkConnectivity()).first.title;
|
||||
return '$res网络异常 ${error.error}';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension _ConnectivityResultExt on ConnectivityResult {
|
||||
String get title => const ['蓝牙', 'Wi-Fi', '局域', '流量', '无', '代理', '其他'][index];
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'dart:convert';
|
||||
import 'package:PiliPlus/utils/accounts/account.dart';
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:encrypt/encrypt.dart';
|
||||
@@ -11,9 +12,7 @@ import 'index.dart';
|
||||
class LoginHttp {
|
||||
static final String deviceId = LoginUtils.genDeviceId();
|
||||
static final String buvid = LoginUtils.buvid;
|
||||
static const String host = 'passport.bilibili.com';
|
||||
static final Map<String, String> headers = {
|
||||
'Host': host,
|
||||
'buvid': buvid,
|
||||
'env': 'prod',
|
||||
'app-key': 'android_hd',
|
||||
@@ -27,21 +26,14 @@ class LoginHttp {
|
||||
|
||||
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});
|
||||
Utils.appSign(params);
|
||||
var res = await Request().post(Api.getTVCode, queryParameters: params);
|
||||
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
@@ -52,18 +44,12 @@ class LoginHttp {
|
||||
|
||||
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});
|
||||
Utils.appSign(params);
|
||||
var res = await Request().post(Api.qrcodePoll, queryParameters: params);
|
||||
return {
|
||||
'status': res.data['code'] == 0,
|
||||
'code': res.data['code'],
|
||||
@@ -106,7 +92,6 @@ class LoginHttp {
|
||||
}) async {
|
||||
int timestamp = DateTime.now().millisecondsSinceEpoch;
|
||||
var data = {
|
||||
'appkey': Constants.appKey,
|
||||
'build': '2001100',
|
||||
'buvid': buvid,
|
||||
'c_locale': 'zh_CN',
|
||||
@@ -129,15 +114,11 @@ class LoginHttp {
|
||||
'tel': tel,
|
||||
'ts': (timestamp ~/ 1000).toString(),
|
||||
};
|
||||
String sign = Utils.appSign(
|
||||
data,
|
||||
Constants.appKey,
|
||||
Constants.appSec,
|
||||
);
|
||||
Utils.appSign(data);
|
||||
|
||||
var res = await Request().post(
|
||||
Api.appSmsCode,
|
||||
data: {...data, 'sign': sign},
|
||||
data: data,
|
||||
options: Options(
|
||||
contentType: Headers.formUrlEncodedContentType,
|
||||
headers: headers,
|
||||
@@ -211,7 +192,6 @@ class LoginHttp {
|
||||
Encrypter(RSA(publicKey: publicKey)).encrypt(salt + password).base64;
|
||||
|
||||
Map<String, String> data = {
|
||||
'appkey': Constants.appKey,
|
||||
'bili_local_id': deviceId,
|
||||
'build': '2001100',
|
||||
'buvid': buvid,
|
||||
@@ -242,15 +222,7 @@ class LoginHttp {
|
||||
'ts': (DateTime.now().millisecondsSinceEpoch ~/ 1000).toString(),
|
||||
'username': username,
|
||||
};
|
||||
String sign = Utils.appSign(
|
||||
data,
|
||||
Constants.appKey,
|
||||
Constants.appSec,
|
||||
);
|
||||
data['sign'] = sign;
|
||||
data.map((key, value) {
|
||||
return MapEntry<String, dynamic>(key, value);
|
||||
});
|
||||
Utils.appSign(data);
|
||||
var res = await Request().post(
|
||||
Api.loginByPwdApi,
|
||||
data: data,
|
||||
@@ -287,7 +259,6 @@ class LoginHttp {
|
||||
}) async {
|
||||
dynamic publicKey = RSAKeyParser().parse(key);
|
||||
Map<String, String> data = {
|
||||
'appkey': Constants.appKey,
|
||||
'bili_local_id': deviceId,
|
||||
'build': '2001100',
|
||||
'buvid': buvid,
|
||||
@@ -316,15 +287,7 @@ class LoginHttp {
|
||||
'tel': tel,
|
||||
'ts': (DateTime.now().millisecondsSinceEpoch ~/ 1000).toString(),
|
||||
};
|
||||
String sign = Utils.appSign(
|
||||
data,
|
||||
Constants.appKey,
|
||||
Constants.appSec,
|
||||
);
|
||||
data['sign'] = sign;
|
||||
data.map((key, value) {
|
||||
return MapEntry<String, dynamic>(key, value);
|
||||
});
|
||||
Utils.appSign(data);
|
||||
var res = await Request().post(
|
||||
Api.logInByAppSms,
|
||||
data: data,
|
||||
@@ -404,12 +367,7 @@ class LoginHttp {
|
||||
if (geeValidate != null) 'gee_validate': geeValidate,
|
||||
if (recaptchaToken != null) 'recaptcha_token': recaptchaToken,
|
||||
};
|
||||
String sign = Utils.appSign(
|
||||
data,
|
||||
Constants.appKey,
|
||||
Constants.appSec,
|
||||
);
|
||||
data['sign'] = sign;
|
||||
Utils.appSign(data);
|
||||
var res = await Request().post(
|
||||
Api.safeCenterSmsCode,
|
||||
data: data,
|
||||
@@ -449,12 +407,7 @@ class LoginHttp {
|
||||
'source': source,
|
||||
'captcha_key': captchaKey,
|
||||
};
|
||||
String sign = Utils.appSign(
|
||||
data,
|
||||
Constants.appKey,
|
||||
Constants.appSec,
|
||||
);
|
||||
data['sign'] = sign;
|
||||
Utils.appSign(data);
|
||||
var res = await Request().post(
|
||||
Api.safeCenterSmsVerify,
|
||||
data: data,
|
||||
@@ -500,15 +453,7 @@ class LoginHttp {
|
||||
// 'statistics': Constants.statistics,
|
||||
'ts': (DateTime.now().millisecondsSinceEpoch ~/ 1000).toString(),
|
||||
};
|
||||
String sign = Utils.appSign(
|
||||
data,
|
||||
Constants.appKey,
|
||||
Constants.appSec,
|
||||
);
|
||||
data['sign'] = sign;
|
||||
data.map((key, value) {
|
||||
return MapEntry<String, dynamic>(key, value);
|
||||
});
|
||||
Utils.appSign(data);
|
||||
var res = await Request().post(
|
||||
Api.oauth2AccessToken,
|
||||
data: data,
|
||||
@@ -529,4 +474,15 @@ class LoginHttp {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
static Future<Map> logout(Account account) async {
|
||||
dynamic res = await Request().post(
|
||||
Api.logout,
|
||||
data: {'biliCSRF': account.csrf},
|
||||
options: Options(
|
||||
contentType: Headers.formUrlEncodedContentType,
|
||||
extra: {'account': account}),
|
||||
);
|
||||
return {'status': res.data['code'] == 0, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,11 +65,7 @@ class MemberHttp {
|
||||
required int mid,
|
||||
required int page,
|
||||
}) async {
|
||||
String? accessKey = GStorage.localCache
|
||||
.get(LocalCacheKey.accessKey, defaultValue: {})['value'];
|
||||
Map<String, String> data = {
|
||||
if (accessKey?.isNotEmpty == true) 'access_key': accessKey!,
|
||||
'appkey': Constants.appKey,
|
||||
'build': '1462100',
|
||||
'c_locale': 'zh_CN',
|
||||
'channel': 'yingyongbao',
|
||||
@@ -79,15 +75,8 @@ class MemberHttp {
|
||||
'ps': '10',
|
||||
's_locale': 'zh_CN',
|
||||
'statistics': Constants.statistics,
|
||||
'ts': (DateTime.now().millisecondsSinceEpoch ~/ 1000).toString(),
|
||||
'vmid': mid.toString(),
|
||||
};
|
||||
String sign = Utils.appSign(
|
||||
data,
|
||||
Constants.appKey,
|
||||
Constants.appSec,
|
||||
);
|
||||
data['sign'] = sign;
|
||||
dynamic res = await Request().get(
|
||||
Api.spaceArticle,
|
||||
queryParameters: data,
|
||||
@@ -108,11 +97,7 @@ class MemberHttp {
|
||||
static Future<LoadingState> spaceFav({
|
||||
required int mid,
|
||||
}) async {
|
||||
String? accessKey = GStorage.localCache
|
||||
.get(LocalCacheKey.accessKey, defaultValue: {})['value'];
|
||||
Map<String, String> data = {
|
||||
if (accessKey?.isNotEmpty == true) 'access_key': accessKey!,
|
||||
'appkey': Constants.appKey,
|
||||
'build': '1462100',
|
||||
'c_locale': 'zh_CN',
|
||||
'channel': 'yingyongbao',
|
||||
@@ -120,15 +105,8 @@ class MemberHttp {
|
||||
'platform': 'android',
|
||||
's_locale': 'zh_CN',
|
||||
'statistics': Constants.statistics,
|
||||
'ts': (DateTime.now().millisecondsSinceEpoch ~/ 1000).toString(),
|
||||
'up_mid': mid.toString(),
|
||||
};
|
||||
String sign = Utils.appSign(
|
||||
data,
|
||||
Constants.appKey,
|
||||
Constants.appSec,
|
||||
);
|
||||
data['sign'] = sign;
|
||||
dynamic res = await Request().get(
|
||||
Api.spaceFav,
|
||||
queryParameters: data,
|
||||
@@ -176,12 +154,8 @@ class MemberHttp {
|
||||
int? seasonId,
|
||||
int? seriesId,
|
||||
}) async {
|
||||
String? accessKey = GStorage.localCache
|
||||
.get(LocalCacheKey.accessKey, defaultValue: {})['value'];
|
||||
Map<String, String> data = {
|
||||
if (accessKey?.isNotEmpty == true) 'access_key': accessKey!,
|
||||
if (aid != null) 'aid': aid.toString(),
|
||||
'appkey': Constants.appKey,
|
||||
'build': '1462100',
|
||||
'c_locale': 'zh_CN',
|
||||
'channel': 'yingyongbao',
|
||||
@@ -197,25 +171,16 @@ class MemberHttp {
|
||||
if (order != null) 'order': order,
|
||||
if (sort != null) 'sort': sort,
|
||||
'statistics': Constants.statistics,
|
||||
'ts': (DateTime.now().millisecondsSinceEpoch ~/ 1000).toString(),
|
||||
'vmid': mid.toString(),
|
||||
};
|
||||
String sign = Utils.appSign(
|
||||
data,
|
||||
Constants.appKey,
|
||||
Constants.appSec,
|
||||
);
|
||||
data['sign'] = sign;
|
||||
dynamic res = await Request().get(
|
||||
type == ContributeType.video
|
||||
? Api.spaceArchive
|
||||
: type == ContributeType.charging
|
||||
? Api.spaceChargingArchive
|
||||
: type == ContributeType.season
|
||||
? Api.spaceSeason
|
||||
: type == ContributeType.series
|
||||
? Api.spaceSeries
|
||||
: Api.spaceBangumi,
|
||||
switch (type) {
|
||||
ContributeType.video => Api.spaceArchive,
|
||||
ContributeType.charging => Api.spaceChargingArchive,
|
||||
ContributeType.season => Api.spaceSeason,
|
||||
ContributeType.series => Api.spaceSeries,
|
||||
ContributeType.bangumi => Api.spaceBangumi,
|
||||
},
|
||||
queryParameters: data,
|
||||
options: Options(
|
||||
headers: {
|
||||
@@ -234,11 +199,7 @@ class MemberHttp {
|
||||
static Future<LoadingState> space({
|
||||
int? mid,
|
||||
}) async {
|
||||
String? accessKey = GStorage.localCache
|
||||
.get(LocalCacheKey.accessKey, defaultValue: {})['value'];
|
||||
Map<String, String> data = {
|
||||
if (accessKey?.isNotEmpty == true) 'access_key': accessKey!,
|
||||
'appkey': Constants.appKey,
|
||||
'build': '1462100',
|
||||
'c_locale': 'zh_CN',
|
||||
'channel': 'yingyongbao',
|
||||
@@ -246,15 +207,8 @@ class MemberHttp {
|
||||
'platform': 'android',
|
||||
's_locale': 'zh_CN',
|
||||
'statistics': Constants.statistics,
|
||||
'ts': (DateTime.now().millisecondsSinceEpoch ~/ 1000).toString(),
|
||||
'vmid': mid.toString(),
|
||||
};
|
||||
String sign = Utils.appSign(
|
||||
data,
|
||||
Constants.appKey,
|
||||
Constants.appSec,
|
||||
);
|
||||
data['sign'] = sign;
|
||||
dynamic res = await Request().get(
|
||||
Api.space,
|
||||
queryParameters: data,
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:PiliPlus/grpc/app/main/community/reply/v1/reply.pb.dart';
|
||||
import 'package:PiliPlus/grpc/grpc_repo.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/models/video/reply/item.dart';
|
||||
import 'package:PiliPlus/utils/accounts/account.dart';
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
@@ -13,7 +14,8 @@ import 'api.dart';
|
||||
import 'init.dart';
|
||||
|
||||
class ReplyHttp {
|
||||
static Options get _options => Options(extra: {'clearCookie': true});
|
||||
static Options get _options =>
|
||||
Options(extra: {'account': AnonymousAccount()});
|
||||
|
||||
static RegExp replyRegExp =
|
||||
RegExp(GStorage.banWordForReply, caseSensitive: false);
|
||||
|
||||
@@ -18,9 +18,7 @@ import '../models/video_detail_res.dart';
|
||||
import '../utils/id_utils.dart';
|
||||
import '../utils/recommend_filter.dart';
|
||||
import '../utils/storage.dart';
|
||||
import '../utils/utils.dart';
|
||||
import '../utils/wbi_sign.dart';
|
||||
import '../pages/mine/controller.dart';
|
||||
import 'api.dart';
|
||||
import 'init.dart';
|
||||
import 'login.dart';
|
||||
@@ -71,15 +69,8 @@ class VideoHttp {
|
||||
}
|
||||
|
||||
// 添加额外的loginState变量模拟未登录状态
|
||||
static Future<LoadingState> rcmdVideoListApp(
|
||||
{bool loginStatus = true, required int freshIdx}) async {
|
||||
static Future<LoadingState> rcmdVideoListApp({required int freshIdx}) async {
|
||||
Map<String, String> data = {
|
||||
'access_key': loginStatus
|
||||
? (GStorage.localCache
|
||||
.get(LocalCacheKey.accessKey, defaultValue: {})['value'] ??
|
||||
'')
|
||||
: '',
|
||||
'appkey': Constants.appKey,
|
||||
'build': '1462100',
|
||||
'c_locale': 'zh_CN',
|
||||
'channel': 'yingyongbao',
|
||||
@@ -106,16 +97,8 @@ class VideoHttp {
|
||||
's_locale': 'zh_CN',
|
||||
'splash_id': '',
|
||||
'statistics': Constants.statistics,
|
||||
'ts': (DateTime.now().millisecondsSinceEpoch ~/ 1000).toString(),
|
||||
'voice_balance': '0'
|
||||
};
|
||||
String sign = Utils.appSign(
|
||||
data,
|
||||
Constants.appKey,
|
||||
Constants.appSec,
|
||||
);
|
||||
data['sign'] = sign;
|
||||
|
||||
var res = await Request().get(
|
||||
Api.recommendListApp,
|
||||
queryParameters: data,
|
||||
@@ -227,24 +210,22 @@ class VideoHttp {
|
||||
'qn': qn ?? 80,
|
||||
// 获取所有格式的视频
|
||||
'fnval': 4048,
|
||||
};
|
||||
|
||||
// 免登录查看1080p
|
||||
if ((GStorage.userInfo.get('userInfoCache') == null ||
|
||||
MineController.anonymity.value) &&
|
||||
GStorage.setting.get(SettingBoxKey.p1080, defaultValue: true)) {
|
||||
data['try_look'] = 1;
|
||||
}
|
||||
|
||||
Map params = await WbiSign.makSign({
|
||||
...data,
|
||||
'fourk': 1,
|
||||
'voice_balance': 1,
|
||||
'gaia_source': 'pre-load',
|
||||
'web_location': 1550101,
|
||||
});
|
||||
};
|
||||
|
||||
late final usePgcApi = forcePgcApi == true || GStorage.isLogin;
|
||||
// 免登录查看1080p
|
||||
if ((Accounts.get(AccountType.video).isLogin) &&
|
||||
GStorage.setting.get(SettingBoxKey.p1080, defaultValue: true)) {
|
||||
data['try_look'] = 1;
|
||||
}
|
||||
|
||||
Map params = await WbiSign.makSign(data);
|
||||
|
||||
late final usePgcApi =
|
||||
forcePgcApi == true || Accounts.get(AccountType.video).isLogin;
|
||||
|
||||
try {
|
||||
var res = await Request().get(
|
||||
@@ -326,7 +307,7 @@ class VideoHttp {
|
||||
data: {
|
||||
'platform': 'web',
|
||||
'season_id': seasonId,
|
||||
'csrf': await Request.getCsrf(),
|
||||
'csrf': Accounts.main.csrf,
|
||||
},
|
||||
options: Options(
|
||||
contentType: Headers.formUrlEncodedContentType,
|
||||
@@ -427,14 +408,12 @@ class VideoHttp {
|
||||
}) async {
|
||||
var res = await Request().post(
|
||||
Api.coinVideo,
|
||||
queryParameters: {
|
||||
'aid': IdUtils.bv2av(bvid),
|
||||
data: {
|
||||
'aid': IdUtils.bv2av(bvid).toString(),
|
||||
// 'bvid': bvid,
|
||||
'multiply': multiply,
|
||||
'select_like': selectLike,
|
||||
'access_key': GStorage.localCache
|
||||
.get(LocalCacheKey.accessKey, defaultValue: {})['value'],
|
||||
// 'csrf': await Request.getCsrf(),
|
||||
'multiply': multiply.toString(),
|
||||
'select_like': selectLike.toString(),
|
||||
// 'csrf': Accounts.main.csrf,
|
||||
},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
@@ -461,7 +440,7 @@ class VideoHttp {
|
||||
Api.triple,
|
||||
data: {
|
||||
'ep_id': epId,
|
||||
'csrf': await Request.getCsrf(),
|
||||
'csrf': Accounts.main.csrf,
|
||||
},
|
||||
options: Options(
|
||||
contentType: Headers.formUrlEncodedContentType,
|
||||
@@ -489,7 +468,7 @@ class VideoHttp {
|
||||
'ramval': 0,
|
||||
'source': 'web_normal',
|
||||
'ga': 1,
|
||||
'csrf': await Request.getCsrf(),
|
||||
'csrf': Accounts.main.csrf,
|
||||
},
|
||||
options: Options(
|
||||
contentType: Headers.formUrlEncodedContentType,
|
||||
@@ -509,18 +488,19 @@ class VideoHttp {
|
||||
|
||||
// (取消)点赞
|
||||
static Future likeVideo({required String bvid, required bool type}) async {
|
||||
var res = await Request().post(Api.likeVideo, queryParameters: {
|
||||
'aid': IdUtils.bv2av(bvid),
|
||||
'like': type ? 0 : 1,
|
||||
'access_key': GStorage.localCache
|
||||
.get(LocalCacheKey.accessKey, defaultValue: {})['value'],
|
||||
}
|
||||
// queryParameters: {
|
||||
// 'bvid': bvid,
|
||||
// 'like': type ? 1 : 2,
|
||||
// 'csrf': await Request.getCsrf(),
|
||||
// },
|
||||
);
|
||||
var res = await Request().post(
|
||||
Api.likeVideo,
|
||||
data: {
|
||||
'aid': IdUtils.bv2av(bvid).toString(),
|
||||
'like': type ? '0' : '1',
|
||||
},
|
||||
options: Options(contentType: Headers.formUrlEncodedContentType),
|
||||
// queryParameters: {
|
||||
// 'bvid': bvid,
|
||||
// 'like': type ? 1 : 2,
|
||||
// 'csrf': Accounts.main.csrf,
|
||||
// },
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
@@ -530,17 +510,14 @@ class VideoHttp {
|
||||
|
||||
// (取消)点踩
|
||||
static Future dislikeVideo({required String bvid, required bool type}) async {
|
||||
String? accessKey = GStorage.localCache
|
||||
.get(LocalCacheKey.accessKey, defaultValue: {})['value'];
|
||||
if (accessKey == null || accessKey == "") {
|
||||
if (Accounts.main.accessKey.isNullOrEmpty) {
|
||||
return {'status': false, 'msg': "请退出账号后重新登录"};
|
||||
}
|
||||
var res = await Request().post(
|
||||
Api.dislikeVideo,
|
||||
queryParameters: {
|
||||
'aid': IdUtils.bv2av(bvid),
|
||||
'dislike': type ? 0 : 1,
|
||||
'access_key': accessKey,
|
||||
data: {
|
||||
'aid': IdUtils.bv2av(bvid).toString(),
|
||||
'dislike': type ? '0' : '1',
|
||||
},
|
||||
);
|
||||
if (res.data is! String && res.data['code'] == 0) {
|
||||
@@ -559,9 +536,7 @@ class VideoHttp {
|
||||
required int id,
|
||||
int? reasonId,
|
||||
int? feedbackId}) async {
|
||||
String? accessKey = GStorage.localCache
|
||||
.get(LocalCacheKey.accessKey, defaultValue: {})['value'];
|
||||
if (accessKey == null || accessKey == "") {
|
||||
if (Accounts.get(AccountType.recommend).accessKey.isNullOrEmpty) {
|
||||
return {'status': false, 'msg': "请退出账号后重新登录"};
|
||||
}
|
||||
assert((reasonId != null) ^ (feedbackId != null));
|
||||
@@ -571,10 +546,8 @@ class VideoHttp {
|
||||
// 'mid': mid,
|
||||
if (reasonId != null) 'reason_id': reasonId,
|
||||
if (feedbackId != null) 'feedback_id': feedbackId,
|
||||
'build': 1,
|
||||
'build': '1',
|
||||
'mobi_app': 'android',
|
||||
'access_key': accessKey,
|
||||
'appkey': Constants.appKey,
|
||||
});
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true};
|
||||
@@ -589,9 +562,7 @@ class VideoHttp {
|
||||
required int id,
|
||||
int? reasonId,
|
||||
int? feedbackId}) async {
|
||||
String? accessKey = GStorage.localCache
|
||||
.get(LocalCacheKey.accessKey, defaultValue: {})['value'];
|
||||
if (accessKey == null || accessKey == "") {
|
||||
if (Accounts.get(AccountType.recommend).accessKey.isNullOrEmpty) {
|
||||
return {'status': false, 'msg': "请退出账号后重新登录"};
|
||||
}
|
||||
// assert ((reasonId != null) ^ (feedbackId != null));
|
||||
@@ -601,10 +572,8 @@ class VideoHttp {
|
||||
// 'mid': mid,
|
||||
if (reasonId != null) 'reason_id': reasonId,
|
||||
if (feedbackId != null) 'feedback_id': feedbackId,
|
||||
'build': 1,
|
||||
'build': '1',
|
||||
'mobi_app': 'android',
|
||||
'access_key': accessKey,
|
||||
'appkey': Constants.appKey,
|
||||
});
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true};
|
||||
@@ -624,7 +593,7 @@ class VideoHttp {
|
||||
'resources': ids?.join(','),
|
||||
'media_id': delIds,
|
||||
'platform': 'web',
|
||||
'csrf': await Request.getCsrf(),
|
||||
'csrf': Accounts.main.csrf,
|
||||
},
|
||||
options: Options(contentType: Headers.formUrlEncodedContentType),
|
||||
);
|
||||
@@ -649,7 +618,7 @@ class VideoHttp {
|
||||
'type': type ?? 2,
|
||||
'add_media_ids': addIds ?? '',
|
||||
'del_media_ids': delIds ?? '',
|
||||
'csrf': await Request.getCsrf(),
|
||||
'csrf': Accounts.main.csrf,
|
||||
},
|
||||
options: Options(contentType: Headers.formUrlEncodedContentType),
|
||||
);
|
||||
@@ -672,7 +641,7 @@ class VideoHttp {
|
||||
// 'resources': '$epId:24',
|
||||
// 'add_media_ids': addIds ?? '',
|
||||
// 'del_media_ids': delIds ?? '',
|
||||
// 'csrf': await Request.getCsrf(),
|
||||
// 'csrf': Accounts.main.csrf,
|
||||
// },
|
||||
// options: Options(
|
||||
// headers: {
|
||||
@@ -709,7 +678,7 @@ class VideoHttp {
|
||||
if (mid != null) 'mid': mid,
|
||||
'resources': resources.join(','),
|
||||
'platform': 'web',
|
||||
'csrf': await Request.getCsrf(),
|
||||
'csrf': Accounts.main.csrf,
|
||||
},
|
||||
options: Options(contentType: Headers.formUrlEncodedContentType),
|
||||
);
|
||||
@@ -783,7 +752,7 @@ class VideoHttp {
|
||||
'message': message,
|
||||
if (pictures != null) 'pictures': jsonEncode(pictures),
|
||||
if (syncToDynamic == true) 'sync_to_dynamic': 1,
|
||||
'csrf': await Request.getCsrf(),
|
||||
'csrf': Accounts.main.csrf,
|
||||
};
|
||||
var res = await Request().post(
|
||||
Api.replyAdd,
|
||||
@@ -806,7 +775,7 @@ class VideoHttp {
|
||||
'type': type, //type.index
|
||||
'oid': oid,
|
||||
'rpid': rpid,
|
||||
'csrf': await Request.getCsrf(),
|
||||
'csrf': Accounts.main.csrf,
|
||||
});
|
||||
log(res.toString());
|
||||
if (res.data['code'] == 0) {
|
||||
@@ -842,7 +811,7 @@ class VideoHttp {
|
||||
"entity_id": mid,
|
||||
'fp': Request.headerUa(type: 'pc'),
|
||||
},
|
||||
'csrf': await Request.getCsrf(),
|
||||
'csrf': Accounts.main.csrf,
|
||||
},
|
||||
options: Options(
|
||||
contentType: Headers.formUrlEncodedContentType,
|
||||
@@ -878,7 +847,7 @@ class VideoHttp {
|
||||
if (epid != null) 'type': 4,
|
||||
if (subType != null) 'sub_type': subType,
|
||||
'played_time': progress,
|
||||
'csrf': await Request.getCsrf(),
|
||||
'csrf': Accounts.main.csrf,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -891,7 +860,7 @@ class VideoHttp {
|
||||
'desc': desc,
|
||||
'oid': oid,
|
||||
'upper_mid': upperMid,
|
||||
'csrf': await Request.getCsrf(),
|
||||
'csrf': Accounts.main.csrf,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -899,7 +868,7 @@ class VideoHttp {
|
||||
static Future bangumiAdd({int? seasonId}) async {
|
||||
var res = await Request().post(Api.bangumiAdd, queryParameters: {
|
||||
'season_id': seasonId,
|
||||
'csrf': await Request.getCsrf(),
|
||||
'csrf': Accounts.main.csrf,
|
||||
});
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
@@ -920,7 +889,7 @@ class VideoHttp {
|
||||
static Future bangumiDel({int? seasonId}) async {
|
||||
var res = await Request().post(Api.bangumiDel, queryParameters: {
|
||||
'season_id': seasonId,
|
||||
'csrf': await Request.getCsrf(),
|
||||
'csrf': Accounts.main.csrf,
|
||||
});
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
@@ -946,7 +915,7 @@ class VideoHttp {
|
||||
data: {
|
||||
'season_id': seasonId,
|
||||
'status': status,
|
||||
'csrf': await Request.getCsrf(),
|
||||
'csrf': Accounts.main.csrf,
|
||||
},
|
||||
options: Options(
|
||||
contentType: Headers.formUrlEncodedContentType,
|
||||
@@ -1143,7 +1112,7 @@ class VideoHttp {
|
||||
var res = await Request().get(
|
||||
Api.noteList,
|
||||
queryParameters: {
|
||||
'csrf': await Request.getCsrf(),
|
||||
'csrf': Accounts.main.csrf,
|
||||
'oid': oid,
|
||||
'oid_type': 0,
|
||||
'pn': page,
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
// 首页推荐类型
|
||||
enum RcmdType { web, app, notLogin }
|
||||
|
||||
extension RcmdTypeExtension on RcmdType {
|
||||
String get values => ['web', 'app', 'notLogin'][index];
|
||||
String get labels => ['web端', 'app端', '游客模式'][index];
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:PiliPlus/build_config.dart';
|
||||
import 'package:PiliPlus/http/constants.dart';
|
||||
import 'package:PiliPlus/services/loggeer.dart';
|
||||
import 'package:PiliPlus/utils/accounts/account.dart';
|
||||
import 'package:PiliPlus/utils/login.dart';
|
||||
import 'package:cookie_jar/cookie_jar.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
@@ -13,7 +12,6 @@ import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:PiliPlus/models/github/latest.dart';
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
import '../../http/init.dart';
|
||||
import '../../utils/cache_manage.dart';
|
||||
|
||||
class AboutPage extends StatefulWidget {
|
||||
@@ -250,22 +248,8 @@ Commit Hash: ${BuildConfig.commitHash}''',
|
||||
title: const Text('导出'),
|
||||
onTap: () async {
|
||||
Get.back();
|
||||
dynamic accessKey = GStorage.localCache
|
||||
.get(LocalCacheKey.accessKey, defaultValue: {});
|
||||
dynamic cookies = (await Request.cookieManager.cookieJar
|
||||
.loadForRequest(Uri.parse(HttpString.baseUrl)))
|
||||
.map(
|
||||
(Cookie cookie) => {
|
||||
'name': cookie.name,
|
||||
'value': cookie.value,
|
||||
},
|
||||
)
|
||||
.toList();
|
||||
dynamic res = jsonEncode({
|
||||
'accessKey': accessKey,
|
||||
'cookies': cookies,
|
||||
});
|
||||
Utils.copyText('$res');
|
||||
String res = jsonEncode(Accounts.account.toMap());
|
||||
Utils.copyText(res);
|
||||
// if (context.mounted) {
|
||||
// showDialog(
|
||||
// context: context,
|
||||
@@ -282,9 +266,7 @@ Commit Hash: ${BuildConfig.commitHash}''',
|
||||
Get.back();
|
||||
ClipboardData? data =
|
||||
await Clipboard.getData('text/plain');
|
||||
if (data == null ||
|
||||
data.text == null ||
|
||||
data.text!.isEmpty) {
|
||||
if (data?.text?.isNotEmpty != true) {
|
||||
SmartDialog.showToast('剪贴板无数据');
|
||||
return;
|
||||
}
|
||||
@@ -295,7 +277,7 @@ Commit Hash: ${BuildConfig.commitHash}''',
|
||||
return AlertDialog(
|
||||
title: const Text('是否导入以下登录信息?'),
|
||||
content: SingleChildScrollView(
|
||||
child: Text(data.text!),
|
||||
child: Text(data!.text!),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
@@ -309,14 +291,21 @@ Commit Hash: ${BuildConfig.commitHash}''',
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
try {
|
||||
dynamic res = jsonDecode(data.text!);
|
||||
LoginUtils.onLogin(
|
||||
res['accessKey'],
|
||||
{'cookies': res['cookies']},
|
||||
);
|
||||
final res = (jsonDecode(data.text!)
|
||||
as Map)
|
||||
.map((key, value) => MapEntry(key,
|
||||
LoginAccount.fromJson(value)));
|
||||
Accounts.account
|
||||
.putAll(res)
|
||||
.then((_) => Accounts.refresh())
|
||||
.then((_) {
|
||||
if (Accounts.main.isLogin) {
|
||||
return LoginUtils.onLoginMain();
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
SmartDialog.showToast('导入失败:$e');
|
||||
}
|
||||
@@ -448,6 +437,7 @@ Commit Hash: ${BuildConfig.commitHash}''',
|
||||
GStorage.localCache.clear(),
|
||||
GStorage.video.clear(),
|
||||
GStorage.historyWord.clear(),
|
||||
Accounts.clear(),
|
||||
]);
|
||||
SmartDialog.showToast('重置成功');
|
||||
},
|
||||
|
||||
@@ -2,14 +2,13 @@ import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:PiliPlus/grpc/app/main/community/reply/v1/reply.pb.dart';
|
||||
import 'package:PiliPlus/http/constants.dart';
|
||||
import 'package:PiliPlus/http/init.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/http/reply.dart';
|
||||
import 'package:PiliPlus/models/common/reply_type.dart';
|
||||
import 'package:PiliPlus/models/video/reply/data.dart';
|
||||
import 'package:PiliPlus/pages/common/common_controller.dart';
|
||||
import 'package:PiliPlus/pages/video/detail/reply_new/reply_page.dart';
|
||||
import 'package:PiliPlus/utils/accounts/account.dart';
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:PiliPlus/utils/global_data.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
@@ -31,7 +30,7 @@ abstract class ReplyController extends CommonController {
|
||||
|
||||
late final savedReplies = {};
|
||||
|
||||
late final bool isLogin = GStorage.userInfo.get('userInfoCache') != null;
|
||||
late final bool isLogin = Accounts.main.isLogin;
|
||||
|
||||
CursorReply? cursor;
|
||||
late Rx<Mode> mode = Mode.MAIN_LIST_HOT.obs;
|
||||
@@ -374,10 +373,10 @@ abstract class ReplyController extends CommonController {
|
||||
// biliSendCommAntifraud
|
||||
if (Platform.isAndroid && _biliSendCommAntifraud) {
|
||||
try {
|
||||
List<Cookie> cookies = await Request.cookieManager.cookieJar
|
||||
.loadForRequest(Uri.parse(HttpString.apiBaseUrl));
|
||||
final String cookieString = cookies
|
||||
.map((Cookie cookie) => '${cookie.name}=${cookie.value}')
|
||||
final String cookieString = Accounts.main.cookieJar
|
||||
.toJson()
|
||||
.entries
|
||||
.map((i) => '${i.key}=${i.value}')
|
||||
.join(';');
|
||||
Utils.channel.invokeMethod(
|
||||
'biliSendCommAntifraud',
|
||||
|
||||
@@ -312,7 +312,7 @@ class AuthorPanel extends StatelessWidget {
|
||||
},
|
||||
minLeadingWidth: 0,
|
||||
),
|
||||
if (GStorage.isLogin)
|
||||
if (Accounts.main.isLogin)
|
||||
ListTile(
|
||||
title: Text(
|
||||
'举报',
|
||||
|
||||
@@ -25,7 +25,7 @@ class LiveController extends CommonController {
|
||||
return super.onRefresh();
|
||||
}
|
||||
|
||||
late RxBool isLogin = GStorage.isLogin.obs;
|
||||
late RxBool isLogin = Accounts.main.isLogin.obs;
|
||||
late Rx<LoadingState> followListState = LoadingState.loading().obs;
|
||||
late int followPage = 1;
|
||||
late bool followEnd = false;
|
||||
|
||||
@@ -3,9 +3,9 @@ import 'dart:io';
|
||||
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/common/widgets/icon_button.dart';
|
||||
import 'package:PiliPlus/common/widgets/radio_widget.dart';
|
||||
import 'package:PiliPlus/http/init.dart';
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:PiliPlus/utils/login.dart';
|
||||
import 'package:PiliPlus/utils/accounts/account.dart';
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -84,8 +84,8 @@ class LoginPageController extends GetxController
|
||||
if (value['status']) {
|
||||
t.cancel();
|
||||
statusQRCode.value = '扫码成功';
|
||||
await LoginUtils.onLogin(
|
||||
value['data'], value['data']['cookie_info']);
|
||||
await setAccount(
|
||||
value['data'], value['data']['cookie_info']['cookies']);
|
||||
Get.back();
|
||||
} else if (value['code'] == 86038) {
|
||||
t.cancel();
|
||||
@@ -212,31 +212,27 @@ class LoginPageController extends GetxController
|
||||
}
|
||||
try {
|
||||
dynamic result = await Request().get(
|
||||
"https://api.bilibili.com/x/member/web/account",
|
||||
options: Options(
|
||||
headers: {
|
||||
"Cookie": cookieTextController.text,
|
||||
},
|
||||
),
|
||||
"/x/member/web/account",
|
||||
options: Options(headers: {
|
||||
"cookie": cookieTextController.text,
|
||||
}, extra: {
|
||||
'account': AnonymousAccount()
|
||||
}),
|
||||
);
|
||||
if (result.data['code'] == 0) {
|
||||
try {
|
||||
await LoginUtils.onLogin(
|
||||
{'mid': '${result.data['data']['mid']}'},
|
||||
{
|
||||
'cookies':
|
||||
cookieTextController.text.split(';').toList().map((item) {
|
||||
List list = item.split('=').toList();
|
||||
return {
|
||||
'name': list.firstOrNull,
|
||||
'value': list.getOrNull(1),
|
||||
};
|
||||
}).toList()
|
||||
},
|
||||
);
|
||||
if (GStorage.isLogin) {
|
||||
Get.back();
|
||||
}
|
||||
await LoginAccount(
|
||||
BiliCookieJar.fromJson(Map.fromEntries(
|
||||
cookieTextController.text.split(';').map((item) {
|
||||
final list = item.split('=');
|
||||
return MapEntry(list.first, list.skip(1).join());
|
||||
}))),
|
||||
null,
|
||||
null)
|
||||
.onChange();
|
||||
if (!Accounts.main.isLogin) await switchAccountDialog(Get.context!);
|
||||
SmartDialog.showToast('登录成功');
|
||||
Get.back();
|
||||
} catch (e) {
|
||||
SmartDialog.showToast("登录失败: $e");
|
||||
}
|
||||
@@ -434,8 +430,8 @@ class LoginPageController extends GetxController
|
||||
return;
|
||||
}
|
||||
SmartDialog.showToast('正在保存身份信息');
|
||||
await LoginUtils.onLogin(
|
||||
data['token_info'], data['cookie_info']);
|
||||
await setAccount(
|
||||
data['token_info'], data['cookie_info']['cookies']);
|
||||
Get.back();
|
||||
Get.back();
|
||||
},
|
||||
@@ -453,7 +449,7 @@ class LoginPageController extends GetxController
|
||||
return;
|
||||
}
|
||||
SmartDialog.showToast('正在保存身份信息');
|
||||
await LoginUtils.onLogin(data['token_info'], data['cookie_info']);
|
||||
await setAccount(data['token_info'], data['cookie_info']['cookies']);
|
||||
Get.back();
|
||||
} else {
|
||||
// handle login result
|
||||
@@ -516,7 +512,7 @@ class LoginPageController extends GetxController
|
||||
if (res['status']) {
|
||||
SmartDialog.showToast('登录成功');
|
||||
var data = res['data'];
|
||||
await LoginUtils.onLogin(data['token_info'], data['cookie_info']);
|
||||
await setAccount(data['token_info'], data['cookie_info']['cookies']);
|
||||
Get.back();
|
||||
} else {
|
||||
SmartDialog.showToast(res['msg']);
|
||||
@@ -644,4 +640,82 @@ class LoginPageController extends GetxController
|
||||
geeChallenge?.isNotEmpty == true &&
|
||||
captchaData.token?.isNotEmpty == true;
|
||||
}
|
||||
|
||||
Future<void> setAccount(Map tokenInfo, List cookieInfo) async {
|
||||
await Future.wait([
|
||||
LoginAccount(BiliCookieJar.fromList(cookieInfo),
|
||||
tokenInfo['access_token'], tokenInfo['refresh_token'])
|
||||
.onChange(),
|
||||
AnonymousAccount().logout().then((i) => Request.buvidActive(i))
|
||||
]);
|
||||
if (Accounts.main.isLogin) {
|
||||
SmartDialog.showToast('登录成功');
|
||||
} else {
|
||||
SmartDialog.showToast('登录成功, 请先设置账号模式');
|
||||
await switchAccountDialog(Get.context!);
|
||||
}
|
||||
}
|
||||
|
||||
static Future switchAccountDialog(BuildContext context) {
|
||||
if (Accounts.account.isEmpty) {
|
||||
return SmartDialog.showToast('请先登录');
|
||||
}
|
||||
final selectAccount = Accounts.accountMode
|
||||
.map((key, value) => MapEntry(key, value.mid.toString()));
|
||||
final options = {'0': '0', for (String i in Accounts.account.keys) i: i};
|
||||
return showDialog(
|
||||
context: context,
|
||||
builder: (context) => StatefulBuilder(builder: (context, setState) {
|
||||
return AlertDialog(
|
||||
title: const Text('选择账号mid, 为0时使用匿名'),
|
||||
titlePadding: const EdgeInsets.only(left: 22, top: 16, right: 22),
|
||||
contentPadding: const EdgeInsets.symmetric(vertical: 5),
|
||||
actionsPadding: const EdgeInsets.only(
|
||||
left: 16,
|
||||
right: 16,
|
||||
bottom: 10,
|
||||
),
|
||||
content: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: AccountType.values
|
||||
.map(
|
||||
(e) => WrapRadioOptionsGroup<String>(
|
||||
groupTitle: e.title,
|
||||
options: options,
|
||||
selectedValue: selectAccount[e],
|
||||
onChanged: (v) => setState(() => selectAccount[e] = v!),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: Get.back,
|
||||
child: Text(
|
||||
'取消',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
for (var i in selectAccount.entries) {
|
||||
var account =
|
||||
Accounts.account.get(i.value) ?? AnonymousAccount();
|
||||
if (account != Accounts.get(i.key)) {
|
||||
Accounts.set(i.key, account);
|
||||
}
|
||||
}
|
||||
Get.back();
|
||||
},
|
||||
child: const Text('确定'),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ class MainController extends GetxController {
|
||||
}
|
||||
hideTabBar =
|
||||
GStorage.setting.get(SettingBoxKey.hideTabBar, defaultValue: true);
|
||||
isLogin.value = GStorage.isLogin;
|
||||
isLogin.value = Accounts.main.isLogin;
|
||||
dynamicBadgeMode = DynamicBadgeMode.values[GStorage.setting.get(
|
||||
SettingBoxKey.dynamicBadgeMode,
|
||||
defaultValue: DynamicBadgeMode.number.index)];
|
||||
|
||||
@@ -4,7 +4,6 @@ import 'package:PiliPlus/http/constants.dart';
|
||||
import 'package:PiliPlus/http/index.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
@@ -54,10 +53,6 @@ class _EditProfilePageState extends State<EditProfilePage> {
|
||||
|
||||
_getInfo() async {
|
||||
Map<String, String> data = {
|
||||
'access_key': GStorage.localCache
|
||||
.get(LocalCacheKey.accessKey, defaultValue: {})['value'] ??
|
||||
'',
|
||||
'appkey': Constants.appKey,
|
||||
'build': '1462100',
|
||||
'c_locale': 'zh_CN',
|
||||
'channel': 'yingyongbao',
|
||||
@@ -65,19 +60,10 @@ class _EditProfilePageState extends State<EditProfilePage> {
|
||||
'platform': 'android',
|
||||
's_locale': 'zh_CN',
|
||||
'statistics': Constants.statistics,
|
||||
'ts': (DateTime.now().millisecondsSinceEpoch ~/ 1000).toString(),
|
||||
};
|
||||
String sign = Utils.appSign(
|
||||
data,
|
||||
Constants.appKey,
|
||||
Constants.appSec,
|
||||
);
|
||||
data['sign'] = sign;
|
||||
Request()
|
||||
.get(
|
||||
'${HttpString.appBaseUrl}/x/v2/account/myinfo',
|
||||
queryParameters: data,
|
||||
)
|
||||
.get('${HttpString.appBaseUrl}/x/v2/account/myinfo',
|
||||
queryParameters: data)
|
||||
.then((data) {
|
||||
setState(() {
|
||||
if (data.data['code'] == 0) {
|
||||
@@ -329,10 +315,6 @@ class _EditProfilePageState extends State<EditProfilePage> {
|
||||
dynamic datum,
|
||||
}) async {
|
||||
Map<String, String> data = {
|
||||
'access_key': GStorage.localCache
|
||||
.get(LocalCacheKey.accessKey, defaultValue: {})['value'] ??
|
||||
'',
|
||||
'appkey': Constants.appKey,
|
||||
'build': '1462100',
|
||||
'c_locale': 'zh_CN',
|
||||
'channel': 'yingyongbao',
|
||||
@@ -350,12 +332,6 @@ class _EditProfilePageState extends State<EditProfilePage> {
|
||||
else if (type == ProfileType.sex)
|
||||
'sex': datum.toString(),
|
||||
};
|
||||
String sign = Utils.appSign(
|
||||
data,
|
||||
Constants.appKey,
|
||||
Constants.appSec,
|
||||
);
|
||||
data['sign'] = sign;
|
||||
Request()
|
||||
.post(
|
||||
'/x/member/app/${type.name}/update',
|
||||
|
||||
@@ -24,7 +24,7 @@ class MemberCoinPage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _MemberCoinPageState extends State<MemberCoinPage> {
|
||||
late final _ownerMid = GStorage.ownerMid;
|
||||
late final _ownerMid = Accounts.main.mid;
|
||||
|
||||
late final _ctr = Get.put(
|
||||
MemberCoinController(mid: widget.mid),
|
||||
|
||||
@@ -24,7 +24,7 @@ class MemberLikePage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _MemberLikePageState extends State<MemberLikePage> {
|
||||
late final _ownerMid = GStorage.ownerMid;
|
||||
late final _ownerMid = Accounts.main.mid;
|
||||
|
||||
late final _ctr = Get.put(
|
||||
MemberLikeController(mid: widget.mid),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:PiliPlus/utils/accounts/account.dart';
|
||||
import 'package:PiliPlus/utils/login.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
@@ -20,8 +21,9 @@ class MineController extends GetxController {
|
||||
|
||||
Rx<ThemeType> themeType = ThemeType.system.obs;
|
||||
static Box get setting => GStorage.setting;
|
||||
static RxBool anonymity =
|
||||
(setting.get(SettingBoxKey.anonymity, defaultValue: false) as bool).obs;
|
||||
static RxBool anonymity = (Accounts.account.isNotEmpty &&
|
||||
!Accounts.get(AccountType.heartbeat).isLogin)
|
||||
.obs;
|
||||
ThemeType get nextThemeType =>
|
||||
ThemeType.values[(themeType.value.index + 1) % ThemeType.values.length];
|
||||
|
||||
@@ -36,8 +38,8 @@ class MineController extends GetxController {
|
||||
}
|
||||
}
|
||||
|
||||
onLogin() async {
|
||||
if (!isLogin.value) {
|
||||
onLogin([bool longPress = false]) async {
|
||||
if (!isLogin.value || longPress) {
|
||||
Get.toNamed('/loginPage', preventDuplicates: false);
|
||||
} else {
|
||||
int mid = userInfo.value.mid!;
|
||||
@@ -58,13 +60,13 @@ class MineController extends GetxController {
|
||||
GStorage.userInfo.put('userInfoCache', res['data']);
|
||||
isLogin.value = true;
|
||||
} else {
|
||||
LoginUtils.onLogout();
|
||||
LoginUtils.onLogoutMain();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
SmartDialog.showToast(res['msg']);
|
||||
if (res['msg'] == '账号未登录') {
|
||||
LoginUtils.onLogout();
|
||||
LoginUtils.onLogoutMain();
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -79,8 +81,13 @@ class MineController extends GetxController {
|
||||
}
|
||||
|
||||
static onChangeAnonymity(BuildContext context) {
|
||||
if (Accounts.account.isEmpty) {
|
||||
SmartDialog.showToast('请先登录');
|
||||
return;
|
||||
}
|
||||
anonymity.value = !anonymity.value;
|
||||
if (anonymity.value) {
|
||||
Accounts.accountMode[AccountType.heartbeat] = AnonymousAccount();
|
||||
SmartDialog.show(
|
||||
clickMaskDismiss: false,
|
||||
usePenetrate: true,
|
||||
@@ -122,8 +129,8 @@ class MineController extends GetxController {
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
SmartDialog.dismiss();
|
||||
setting.put(SettingBoxKey.anonymity, true);
|
||||
anonymity.value = true;
|
||||
Accounts.set(
|
||||
AccountType.heartbeat, AnonymousAccount());
|
||||
SmartDialog.showToast('已设为永久无痕模式');
|
||||
},
|
||||
child: Text(
|
||||
@@ -136,8 +143,6 @@ class MineController extends GetxController {
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
SmartDialog.dismiss();
|
||||
setting.put(SettingBoxKey.anonymity, false);
|
||||
anonymity.value = true;
|
||||
SmartDialog.showToast('已设为临时无痕模式');
|
||||
},
|
||||
child: Text(
|
||||
@@ -158,7 +163,7 @@ class MineController extends GetxController {
|
||||
},
|
||||
);
|
||||
} else {
|
||||
setting.put(SettingBoxKey.anonymity, false);
|
||||
Accounts.set(AccountType.heartbeat, Accounts.main);
|
||||
SmartDialog.show(
|
||||
clickMaskDismiss: false,
|
||||
usePenetrate: true,
|
||||
|
||||
@@ -129,6 +129,7 @@ class _MinePageState extends State<MinePage> {
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: _mineController.onLogin,
|
||||
onLongPress: () => _mineController.onLogin(true),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
|
||||
@@ -6,7 +6,7 @@ import 'package:PiliPlus/utils/storage.dart';
|
||||
class RcmdController extends CommonController {
|
||||
late bool enableSaveLastData = GStorage.setting
|
||||
.get(SettingBoxKey.enableSaveLastData, defaultValue: false);
|
||||
late String defaultRcmdType = 'app';
|
||||
late bool appRcmd = true;
|
||||
|
||||
int? lastRefreshAt;
|
||||
late bool savedRcmdTip = GStorage.savedRcmdTip;
|
||||
@@ -14,8 +14,7 @@ class RcmdController extends CommonController {
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
defaultRcmdType = GStorage.setting
|
||||
.get(SettingBoxKey.defaultRcmdType, defaultValue: 'app');
|
||||
appRcmd = GStorage.appRcmd;
|
||||
|
||||
currentPage = 0;
|
||||
queryData();
|
||||
@@ -23,15 +22,9 @@ class RcmdController extends CommonController {
|
||||
|
||||
@override
|
||||
Future<LoadingState> customGetData() {
|
||||
return defaultRcmdType == 'app' || defaultRcmdType == 'notLogin'
|
||||
? VideoHttp.rcmdVideoListApp(
|
||||
loginStatus: defaultRcmdType != 'notLogin',
|
||||
freshIdx: currentPage,
|
||||
)
|
||||
: VideoHttp.rcmdVideoList(
|
||||
freshIdx: currentPage,
|
||||
ps: 20,
|
||||
);
|
||||
return appRcmd
|
||||
? VideoHttp.rcmdVideoListApp(freshIdx: currentPage)
|
||||
: VideoHttp.rcmdVideoList(freshIdx: currentPage, ps: 20);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -16,9 +16,7 @@ class RecommendSetting extends StatelessWidget {
|
||||
ListTile(
|
||||
dense: true,
|
||||
subtitle: Text(
|
||||
'¹ 若默认web端推荐不太符合预期,可尝试切换至app端。\n'
|
||||
'¹ 选择“游客模式(notLogin)”,将以空的key请求app推荐接口,但播放页仍会携带用户信息,保证账号能正常记录进度、点赞投币等。\n\n'
|
||||
'² 由于接口未提供关注信息,无法豁免相关视频中的已关注Up。\n\n'
|
||||
'¹ 由于接口未提供关注信息,无法豁免相关视频中的已关注Up。\n\n'
|
||||
'* 其它(如热门视频、手动搜索、链接跳转等)均不受过滤器影响。\n'
|
||||
'* 设定较严苛的条件可导致推荐项数锐减或多次请求,请酌情选择。\n'
|
||||
'* 后续可能会增加更多过滤条件,敬请期待。',
|
||||
|
||||
@@ -8,7 +8,6 @@ import 'package:PiliPlus/models/common/sponsor_block/skip_type.dart';
|
||||
import 'package:PiliPlus/pages/setting/slide_color_picker.dart';
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:get/get.dart';
|
||||
@@ -66,7 +65,6 @@ class _SponsorBlockPageState extends State<SponsorBlockPage> {
|
||||
Request()
|
||||
.get(
|
||||
'$_blockServer/api/status/uptime',
|
||||
options: Options(extra: {'clearCookie': true}),
|
||||
)
|
||||
.then((res) {
|
||||
setState(() {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:PiliPlus/build_config.dart';
|
||||
import 'package:PiliPlus/http/login.dart';
|
||||
import 'package:PiliPlus/pages/about/index.dart';
|
||||
import 'package:PiliPlus/pages/login/controller.dart';
|
||||
import 'package:PiliPlus/pages/setting/extra_setting.dart';
|
||||
import 'package:PiliPlus/pages/setting/play_setting.dart';
|
||||
import 'package:PiliPlus/pages/setting/privacy_setting.dart';
|
||||
@@ -9,13 +10,10 @@ import 'package:PiliPlus/pages/setting/video_setting.dart';
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:PiliPlus/utils/login.dart';
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../http/init.dart';
|
||||
|
||||
class _SettingsModel {
|
||||
final String name;
|
||||
final String title;
|
||||
@@ -39,7 +37,7 @@ class SettingPage extends StatefulWidget {
|
||||
|
||||
class _SettingPageState extends State<SettingPage> {
|
||||
late String _type = 'privacySetting';
|
||||
final RxBool _isLogin = GStorage.isLogin.obs;
|
||||
final RxBool _isLogin = Accounts.main.isLogin.obs;
|
||||
TextStyle get _titleStyle => Theme.of(context).textTheme.titleMedium!;
|
||||
TextStyle get _subTitleStyle => Theme.of(context)
|
||||
.textTheme
|
||||
@@ -170,6 +168,12 @@ class _SettingPageState extends State<SettingPage> {
|
||||
: Text(item.subtitle!, style: _subTitleStyle),
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
onTap: () => LoginPageController.switchAccountDialog(context),
|
||||
leading: const Icon(Icons.switch_account_outlined),
|
||||
title: const Text('设置账号模式'),
|
||||
),
|
||||
// TODO: 多账号登出
|
||||
_buildLoginItem,
|
||||
ListTile(
|
||||
tileColor: _getTileColor(_items.last.name),
|
||||
@@ -204,41 +208,36 @@ class _SettingPageState extends State<SettingPage> {
|
||||
),
|
||||
),
|
||||
),
|
||||
if (BuildConfig.isDebug)
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
_isLogin.value = false;
|
||||
LoginUtils.onLogout();
|
||||
},
|
||||
child: Text(
|
||||
'仅登出',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
_isLogin.value = false;
|
||||
LoginUtils.onLogoutMain();
|
||||
final account = Accounts.main;
|
||||
Accounts.accountMode
|
||||
.removeWhere((_, a) => a == account);
|
||||
account.logout().then((_) => Accounts.refresh());
|
||||
},
|
||||
child: Text(
|
||||
'仅登出',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
SmartDialog.showLoading();
|
||||
dynamic res = await Request().post(
|
||||
'https://passport.bilibili.com/login/exit/v2',
|
||||
data: {
|
||||
'biliCSRF': await Request.getCsrf(),
|
||||
},
|
||||
options: Options(
|
||||
contentType:
|
||||
Headers.formUrlEncodedContentType,
|
||||
),
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
await LoginUtils.onLogout();
|
||||
final res = await LoginHttp.logout(Accounts.main);
|
||||
if (res['status']) {
|
||||
await Accounts.main.logout();
|
||||
await LoginUtils.onLogoutMain();
|
||||
_isLogin.value = false;
|
||||
SmartDialog.dismiss();
|
||||
Get.back();
|
||||
} else {
|
||||
SmartDialog.dismiss();
|
||||
SmartDialog.showToast('${res.data['message']}');
|
||||
SmartDialog.showToast(res['msg'].toString());
|
||||
}
|
||||
},
|
||||
child: const Text('确认'),
|
||||
|
||||
@@ -3,7 +3,6 @@ import 'dart:math';
|
||||
|
||||
import 'package:PiliPlus/common/widgets/refresh_indicator.dart'
|
||||
show kDragContainerExtentPercentage, displacement;
|
||||
import 'package:PiliPlus/http/interceptor.dart';
|
||||
import 'package:PiliPlus/http/reply.dart';
|
||||
import 'package:PiliPlus/http/video.dart';
|
||||
import 'package:PiliPlus/main.dart';
|
||||
@@ -11,7 +10,6 @@ import 'package:PiliPlus/models/common/audio_normalization.dart';
|
||||
import 'package:PiliPlus/models/common/dynamic_badge_mode.dart';
|
||||
import 'package:PiliPlus/models/common/dynamics_type.dart';
|
||||
import 'package:PiliPlus/models/common/nav_bar_config.dart';
|
||||
import 'package:PiliPlus/models/common/rcmd_type.dart';
|
||||
import 'package:PiliPlus/models/common/reply_sort_type.dart';
|
||||
import 'package:PiliPlus/models/common/super_resolution_type.dart';
|
||||
import 'package:PiliPlus/models/common/theme_type.dart';
|
||||
@@ -35,6 +33,7 @@ import 'package:PiliPlus/pages/setting/widgets/switch_item.dart';
|
||||
import 'package:PiliPlus/plugin/pl_player/models/bottom_progress_behavior.dart';
|
||||
import 'package:PiliPlus/plugin/pl_player/models/fullscreen_mode.dart';
|
||||
import 'package:PiliPlus/plugin/pl_player/utils/fullscreen.dart';
|
||||
import 'package:PiliPlus/utils/accounts/account_manager/account_mgr.dart';
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:PiliPlus/utils/feed_back.dart';
|
||||
import 'package:PiliPlus/utils/global_data.dart';
|
||||
@@ -1229,35 +1228,19 @@ List<SettingsModel> get videoSettings => [
|
||||
|
||||
List<SettingsModel> get recommendSettings => [
|
||||
SettingsModel(
|
||||
settingsType: SettingsType.normal,
|
||||
title: '首页推荐类型',
|
||||
leading: const Icon(Icons.model_training_outlined),
|
||||
getSubtitle: () => '当前使用「${GStorage.defaultRcmdType}端」推荐¹',
|
||||
onTap: (setState) async {
|
||||
String? result = await showDialog(
|
||||
context: Get.context!,
|
||||
builder: (context) {
|
||||
return SelectDialog<String>(
|
||||
title: '推荐类型',
|
||||
value: GStorage.defaultRcmdType,
|
||||
values: RcmdType.values.map((e) {
|
||||
return {'title': e.labels, 'value': e.values};
|
||||
}).toList(),
|
||||
);
|
||||
},
|
||||
);
|
||||
if (result != null) {
|
||||
if (result == 'app') {
|
||||
if (GStorage.isLogin.not) {
|
||||
SmartDialog.showToast('尚未登录,无法收到个性化推荐');
|
||||
}
|
||||
settingsType: SettingsType.sw1tch,
|
||||
title: '首页使用app端推荐',
|
||||
subtitle: '若web端推荐不太符合预期,可尝试切换至app端推荐',
|
||||
leading: const Icon(Icons.model_training_outlined),
|
||||
setKey: SettingBoxKey.appRcmd,
|
||||
defaultVal: true,
|
||||
onChanged: (value) {
|
||||
try {
|
||||
Get.find<RcmdController>().appRcmd = value;
|
||||
} catch (e) {
|
||||
debugPrint('$e');
|
||||
}
|
||||
await GStorage.setting.put(SettingBoxKey.defaultRcmdType, result);
|
||||
SmartDialog.showToast('下次启动时生效');
|
||||
setState();
|
||||
}
|
||||
},
|
||||
),
|
||||
}),
|
||||
SettingsModel(
|
||||
settingsType: SettingsType.sw1tch,
|
||||
title: '推荐动态',
|
||||
@@ -1430,7 +1413,7 @@ List<SettingsModel> get recommendSettings => [
|
||||
SettingsModel(
|
||||
settingsType: SettingsType.sw1tch,
|
||||
title: '过滤器也应用于相关视频',
|
||||
subtitle: '视频详情页的相关视频也进行过滤²',
|
||||
subtitle: '视频详情页的相关视频也进行过滤¹',
|
||||
leading: const Icon(Icons.explore_outlined),
|
||||
setKey: SettingBoxKey.applyFilterToRelatedVideos,
|
||||
defaultVal: true,
|
||||
@@ -1442,7 +1425,7 @@ List<SettingsModel> get privacySettings => [
|
||||
SettingsModel(
|
||||
settingsType: SettingsType.normal,
|
||||
onTap: (setState) {
|
||||
if (GStorage.isLogin.not) {
|
||||
if (Accounts.main.isLogin.not) {
|
||||
SmartDialog.showToast('登录后查看');
|
||||
return;
|
||||
}
|
||||
@@ -1472,7 +1455,8 @@ List<SettingsModel> get privacySettings => [
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text('查看详情'),
|
||||
content: Text(ApiInterceptor.anonymityList.join('\n')),
|
||||
content: Text(AccountManager.apiTypeSet[AccountType.heartbeat]!
|
||||
.join('\n')),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
@@ -1501,12 +1485,9 @@ List<SettingsModel> get extraSettings => [
|
||||
onTap: () => Get.toNamed('/sponsorBlock'),
|
||||
leading: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
const Icon(Icons.shield_outlined),
|
||||
Icon(
|
||||
Icons.play_arrow_rounded,
|
||||
size: 15,
|
||||
),
|
||||
children: const [
|
||||
Icon(Icons.shield_outlined),
|
||||
Icon(Icons.play_arrow_rounded, size: 15),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -2056,9 +2037,9 @@ List<SettingsModel> get extraSettings => [
|
||||
subtitle: '发送评论后检查评论是否可见',
|
||||
leading: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
const Icon(Icons.shield_outlined),
|
||||
const Icon(Icons.reply, size: 14),
|
||||
children: const [
|
||||
Icon(Icons.shield_outlined),
|
||||
Icon(Icons.reply, size: 14),
|
||||
],
|
||||
),
|
||||
setKey: SettingBoxKey.enableCommAntifraud,
|
||||
@@ -2081,12 +2062,9 @@ List<SettingsModel> get extraSettings => [
|
||||
subtitle: '发布/转发动态后检查动态是否可见',
|
||||
leading: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
const Icon(Icons.shield_outlined),
|
||||
Icon(
|
||||
Icons.motion_photos_on,
|
||||
size: 12,
|
||||
),
|
||||
children: const [
|
||||
Icon(Icons.shield_outlined),
|
||||
Icon(Icons.motion_photos_on, size: 12),
|
||||
],
|
||||
),
|
||||
setKey: SettingBoxKey.enableCreateDynAntifraud,
|
||||
@@ -2097,7 +2075,7 @@ List<SettingsModel> get extraSettings => [
|
||||
title: '屏蔽带货动态',
|
||||
leading: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
children: const [
|
||||
Icon(Icons.shopping_bag_outlined, size: 14),
|
||||
Icon(Icons.not_interested),
|
||||
],
|
||||
@@ -2113,7 +2091,7 @@ List<SettingsModel> get extraSettings => [
|
||||
title: '屏蔽带货评论',
|
||||
leading: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
children: const [
|
||||
Icon(Icons.shopping_bag_outlined, size: 14),
|
||||
Icon(Icons.not_interested),
|
||||
],
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import 'package:PiliPlus/http/init.dart';
|
||||
import 'package:PiliPlus/http/constants.dart';
|
||||
import 'package:PiliPlus/http/video.dart';
|
||||
import 'package:PiliPlus/models/video/play/CDN.dart';
|
||||
import 'package:PiliPlus/models/video/play/url.dart';
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:PiliPlus/utils/id_utils.dart';
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
import 'package:PiliPlus/utils/video_utils.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
@@ -46,23 +45,12 @@ class _SelectDialogState<T> extends State<SelectDialog<T>> {
|
||||
if (result['status']) {
|
||||
VideoItem videoItem = result['data'].dash.video.first;
|
||||
|
||||
late final isLogin = GStorage.isLogin;
|
||||
late final dynamic mid =
|
||||
GStorage.userInfo.get('userInfoCache')?.mid;
|
||||
|
||||
for (CDNService item in CDNService.values) {
|
||||
if (mounted.not) {
|
||||
break;
|
||||
}
|
||||
String videoUrl = VideoUtils.getCdnUrl(videoItem, item.code);
|
||||
Dio dio = Dio()
|
||||
..options.headers['referer'] = 'https://www.bilibili.com/';
|
||||
if (isLogin) {
|
||||
dio.interceptors.add(Request.cookieManager);
|
||||
dio.options.headers['x-bili-mid'] = mid;
|
||||
dio.options.headers['x-bili-aurora-eid'] =
|
||||
IdUtils.genAuroraEid(mid);
|
||||
}
|
||||
Dio dio = Dio()..options.headers['referer'] = HttpString.baseUrl;
|
||||
int maxSize = 8 * 1024 * 1024;
|
||||
int downloaded = 0;
|
||||
int start = DateTime.now().millisecondsSinceEpoch;
|
||||
|
||||
@@ -26,7 +26,6 @@ import 'package:PiliPlus/pages/video/detail/widgets/send_danmaku_panel.dart';
|
||||
import 'package:PiliPlus/pages/video/detail/widgets/media_list_panel.dart';
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:easy_debounce/easy_throttle.dart';
|
||||
import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart';
|
||||
import 'package:floating/floating.dart';
|
||||
@@ -493,17 +492,14 @@ class VideoDetailController extends GetxController
|
||||
late final List listData = [];
|
||||
|
||||
Future _vote(String uuid, int type) async {
|
||||
Request()
|
||||
.post(
|
||||
Request().post(
|
||||
'${GStorage.blockServer}/api/voteOnSponsorTime',
|
||||
queryParameters: {
|
||||
'UUID': uuid,
|
||||
'userID': GStorage.blockUserID,
|
||||
'type': type,
|
||||
},
|
||||
options: options,
|
||||
)
|
||||
.then((res) {
|
||||
).then((res) {
|
||||
SmartDialog.showToast(res.statusCode == 200 ? '投票成功' : '投票失败');
|
||||
});
|
||||
}
|
||||
@@ -522,17 +518,14 @@ class VideoDetailController extends GetxController
|
||||
dense: true,
|
||||
onTap: () {
|
||||
Get.back();
|
||||
Request()
|
||||
.post(
|
||||
Request().post(
|
||||
'${GStorage.blockServer}/api/voteOnSponsorTime',
|
||||
queryParameters: {
|
||||
'UUID': segment.UUID,
|
||||
'userID': GStorage.blockUserID,
|
||||
'category': item.name,
|
||||
},
|
||||
options: options,
|
||||
)
|
||||
.then((res) {
|
||||
).then((res) {
|
||||
SmartDialog.showToast(
|
||||
'类别更改${res.statusCode == 200 ? '成功' : '失败'}');
|
||||
});
|
||||
@@ -725,8 +718,6 @@ class VideoDetailController extends GetxController
|
||||
);
|
||||
}
|
||||
|
||||
Options get options => Options(extra: {'clearCookie': true});
|
||||
|
||||
Future _querySponsorBlock() async {
|
||||
positionSubscription?.cancel();
|
||||
videoLabel.value = '';
|
||||
@@ -738,7 +729,6 @@ class VideoDetailController extends GetxController
|
||||
'videoID': bvid,
|
||||
'cid': cid.value,
|
||||
},
|
||||
options: options,
|
||||
);
|
||||
handleSBData(result);
|
||||
}
|
||||
@@ -940,7 +930,6 @@ class VideoDetailController extends GetxController
|
||||
Request().post(
|
||||
'${GStorage.blockServer}/api/viewedVideoSponsorTime',
|
||||
queryParameters: {'UUID': item.UUID},
|
||||
options: options,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
@@ -330,8 +330,7 @@ class _PostPanelState extends CommonCollapseSlidePageState<PostPanel> {
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
Request()
|
||||
.post(
|
||||
Request().post(
|
||||
'${GStorage.blockServer}/api/skipSegments',
|
||||
queryParameters: {
|
||||
'videoID': videoDetailController.bvid,
|
||||
@@ -355,9 +354,7 @@ class _PostPanelState extends CommonCollapseSlidePageState<PostPanel> {
|
||||
)
|
||||
.toList(),
|
||||
},
|
||||
options: videoDetailController.options,
|
||||
)
|
||||
.then(
|
||||
).then(
|
||||
(res) {
|
||||
if (res.statusCode == 200) {
|
||||
Get.back();
|
||||
|
||||
@@ -1048,7 +1048,7 @@ class ReplyItem extends StatelessWidget {
|
||||
'/x/v2/reply/report',
|
||||
data: {
|
||||
'add_blacklist': banUid,
|
||||
'csrf': await Request.getCsrf(),
|
||||
'csrf': Accounts.main.csrf,
|
||||
'gaia_source': 'main_h5',
|
||||
'oid': item.oid,
|
||||
'platform': 'android',
|
||||
@@ -1135,7 +1135,7 @@ class ReplyItem extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
dynamic ownerMid = GStorage.ownerMid;
|
||||
int ownerMid = Accounts.main.mid;
|
||||
Color errorColor = Theme.of(context).colorScheme.error;
|
||||
|
||||
return Padding(
|
||||
@@ -1168,7 +1168,7 @@ class ReplyItem extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
if (ownerMid != null) ...[
|
||||
if (ownerMid != 0) ...[
|
||||
ListTile(
|
||||
onTap: () => menuActionHandler('delete'),
|
||||
minLeadingWidth: 0,
|
||||
|
||||
@@ -1177,7 +1177,7 @@ class ReplyItemGrpc extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
dynamic ownerMid = GStorage.ownerMid;
|
||||
int ownerMid = Accounts.main.mid;
|
||||
Color errorColor = Theme.of(context).colorScheme.error;
|
||||
|
||||
return Padding(
|
||||
@@ -1210,7 +1210,7 @@ class ReplyItemGrpc extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
if (ownerMid != null) ...[
|
||||
if (ownerMid != 0) ...[
|
||||
ListTile(
|
||||
onTap: () => menuActionHandler('delete'),
|
||||
minLeadingWidth: 0,
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:PiliPlus/http/constants.dart';
|
||||
import 'package:PiliPlus/http/init.dart';
|
||||
import 'package:PiliPlus/utils/accounts/account.dart';
|
||||
import 'package:PiliPlus/utils/app_scheme.dart';
|
||||
import 'package:PiliPlus/utils/cache_manage.dart';
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:cookie_jar/cookie_jar.dart' as cookie_jar;
|
||||
|
||||
enum _WebviewMenuItem {
|
||||
refresh,
|
||||
@@ -127,10 +127,8 @@ class _WebviewPageNewState extends State<WebviewPageNew> {
|
||||
}
|
||||
break;
|
||||
case _WebviewMenuItem.resetCookie:
|
||||
final List<cookie_jar.Cookie> cookies = await Request
|
||||
.cookieManager.cookieJar
|
||||
.loadForRequest(Uri.parse(HttpString.baseUrl));
|
||||
for (cookie_jar.Cookie item in cookies) {
|
||||
final cookies = Accounts.main.cookieJar.toList();
|
||||
for (var item in cookies) {
|
||||
await CookieManager().setCookie(
|
||||
url: WebUri(item.domain ?? ''),
|
||||
name: item.name,
|
||||
|
||||
193
lib/utils/accounts/account.dart
Normal file
193
lib/utils/accounts/account.dart
Normal file
@@ -0,0 +1,193 @@
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:cookie_jar/cookie_jar.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
|
||||
abstract class Account {
|
||||
final bool isLogin = false;
|
||||
late final DefaultCookieJar cookieJar;
|
||||
String? accessKey;
|
||||
String? refresh;
|
||||
late final Set<AccountType> type;
|
||||
|
||||
final int mid = 0;
|
||||
late String csrf;
|
||||
final Map<String, String> headers = const {};
|
||||
|
||||
bool activited = false;
|
||||
|
||||
Future<AnonymousAccount> logout();
|
||||
Future<void> onChange();
|
||||
|
||||
Map<String, dynamic>? toJson();
|
||||
}
|
||||
|
||||
@HiveType(typeId: 9)
|
||||
class LoginAccount implements Account {
|
||||
@override
|
||||
final bool isLogin = true;
|
||||
@override
|
||||
@HiveField(0)
|
||||
late final DefaultCookieJar cookieJar;
|
||||
@override
|
||||
@HiveField(1)
|
||||
String? accessKey;
|
||||
@override
|
||||
@HiveField(2)
|
||||
String? refresh;
|
||||
@override
|
||||
@HiveField(3)
|
||||
late final Set<AccountType> type;
|
||||
|
||||
@override
|
||||
late final int mid = int.parse(_midStr);
|
||||
|
||||
@override
|
||||
late final Map<String, String> headers = {
|
||||
'x-bili-mid': _midStr,
|
||||
'x-bili-aurora-eid': Utils.genAuroraEid(mid),
|
||||
};
|
||||
@override
|
||||
late String csrf =
|
||||
cookieJar.domainCookies['bilibili.com']!['/']!['bili_jct']!.cookie.value;
|
||||
|
||||
@override
|
||||
bool activited = false;
|
||||
|
||||
@override
|
||||
Future<AnonymousAccount> logout() async {
|
||||
await Future.wait([cookieJar.deleteAll(), _box.delete(_midStr)]);
|
||||
return AnonymousAccount();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onChange() => _box.put(_midStr, this);
|
||||
|
||||
@override
|
||||
Map<String, dynamic>? toJson() => {
|
||||
'cookies': cookieJar.toJson(),
|
||||
'accessKey': accessKey,
|
||||
'refresh': refresh,
|
||||
'type': type.map((i) => i.index).toList()
|
||||
};
|
||||
|
||||
late final String _midStr = cookieJar
|
||||
.domainCookies['bilibili.com']!['/']!['DedeUserID']!.cookie.value;
|
||||
|
||||
late final Box<LoginAccount> _box = Accounts.account;
|
||||
|
||||
LoginAccount(this.cookieJar, this.accessKey, this.refresh,
|
||||
[Set<AccountType>? type]) {
|
||||
this.type = type ?? {};
|
||||
}
|
||||
|
||||
LoginAccount.fromJson(Map json) {
|
||||
cookieJar = BiliCookieJar.fromJson(json['cookies']);
|
||||
accessKey = json['accessKey'];
|
||||
refresh = json['refresh'];
|
||||
type = (json['type'] as Iterable?)
|
||||
?.map((i) => AccountType.values[i])
|
||||
.toSet() ??
|
||||
{};
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => mid.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) || (other is Account && mid == other.mid);
|
||||
}
|
||||
|
||||
class AnonymousAccount implements Account {
|
||||
@override
|
||||
final bool isLogin = false;
|
||||
@override
|
||||
late final DefaultCookieJar cookieJar;
|
||||
@override
|
||||
String? accessKey;
|
||||
@override
|
||||
String? refresh;
|
||||
@override
|
||||
Set<AccountType> type = {};
|
||||
@override
|
||||
final int mid = 0;
|
||||
@override
|
||||
String csrf = '';
|
||||
@override
|
||||
final Map<String, String> headers = const {};
|
||||
|
||||
@override
|
||||
bool activited = false;
|
||||
|
||||
@override
|
||||
Future<AnonymousAccount> logout() async {
|
||||
await cookieJar.deleteAll();
|
||||
activited = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onChange() async {}
|
||||
|
||||
@override
|
||||
Map<String, dynamic>? toJson() => null;
|
||||
|
||||
static final _instance = AnonymousAccount._();
|
||||
|
||||
AnonymousAccount._() {
|
||||
cookieJar = DefaultCookieJar(ignoreExpires: true);
|
||||
}
|
||||
|
||||
factory AnonymousAccount() => _instance;
|
||||
|
||||
@override
|
||||
int get hashCode => cookieJar.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
(other is Account && cookieJar == other.cookieJar);
|
||||
}
|
||||
|
||||
extension BiliCookie on Cookie {
|
||||
void setBiliDomain([String domain = '.bilibili.com']) {
|
||||
this
|
||||
..domain = domain
|
||||
..httpOnly = false
|
||||
..path = '/';
|
||||
}
|
||||
}
|
||||
|
||||
extension BiliCookieJar on DefaultCookieJar {
|
||||
Map<String, String> toJson() {
|
||||
final cookies = domainCookies['bilibili.com']?['/'] ?? {};
|
||||
return {for (var i in cookies.values) i.cookie.name: i.cookie.value};
|
||||
}
|
||||
|
||||
List<Cookie> toList() =>
|
||||
domainCookies['bilibili.com']?['/']
|
||||
?.entries
|
||||
.map((i) => i.value.cookie)
|
||||
.toList() ??
|
||||
[];
|
||||
|
||||
static DefaultCookieJar fromJson(Map json) =>
|
||||
DefaultCookieJar(ignoreExpires: true)
|
||||
..domainCookies['bilibili.com'] = {
|
||||
'/': {
|
||||
for (var i in json.entries)
|
||||
i.key: SerializableCookie(Cookie(i.key, i.value)..setBiliDomain())
|
||||
},
|
||||
};
|
||||
|
||||
static DefaultCookieJar fromList(List cookies) =>
|
||||
DefaultCookieJar(ignoreExpires: true)
|
||||
..domainCookies['bilibili.com'] = {
|
||||
'/': {
|
||||
for (var i in cookies)
|
||||
i['name']!: SerializableCookie(
|
||||
Cookie(i['name']!, i['value']!)..setBiliDomain()),
|
||||
},
|
||||
};
|
||||
}
|
||||
48
lib/utils/accounts/account_adapter.dart
Normal file
48
lib/utils/accounts/account_adapter.dart
Normal file
@@ -0,0 +1,48 @@
|
||||
import 'package:cookie_jar/cookie_jar.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
|
||||
import '../storage.dart';
|
||||
import 'account.dart';
|
||||
|
||||
class LoginAccountAdapter extends TypeAdapter<LoginAccount> {
|
||||
@override
|
||||
final int typeId = 9;
|
||||
|
||||
@override
|
||||
LoginAccount read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return LoginAccount(
|
||||
fields[0] as DefaultCookieJar,
|
||||
fields[1] as String?,
|
||||
fields[2] as String?,
|
||||
(fields[3] as List?)?.cast<AccountType>().toSet(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, LoginAccount obj) {
|
||||
writer
|
||||
..writeByte(4)
|
||||
..writeByte(0)
|
||||
..write(obj.cookieJar)
|
||||
..writeByte(1)
|
||||
..write(obj.accessKey)
|
||||
..writeByte(2)
|
||||
..write(obj.refresh)
|
||||
..writeByte(3)
|
||||
..write(obj.type.toList());
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is LoginAccountAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
22
lib/utils/accounts/account_manager/LICENSE
Normal file
22
lib/utils/accounts/account_manager/LICENSE
Normal file
@@ -0,0 +1,22 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 Wen Du (wendux)
|
||||
Copyright (c) 2022 The CFUG Team
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
90
lib/utils/accounts/account_manager/README.md
Normal file
90
lib/utils/accounts/account_manager/README.md
Normal file
@@ -0,0 +1,90 @@
|
||||
# dio_cookie_manager
|
||||
|
||||
[](https://pub.dev/packages/dio_cookie_manager)
|
||||
|
||||
A cookie manager combines cookie_jar and dio, based on the interceptor algorithm.
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Install
|
||||
|
||||
Add the `dio_cookie_manager` package to your
|
||||
[pubspec dependencies](https://pub.dev/packages/dio_cookie_manager/install).
|
||||
|
||||
### Usage
|
||||
|
||||
```dart
|
||||
import 'package:cookie_jar/cookie_jar.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:dio_cookie_manager/dio_cookie_manager.dart';
|
||||
|
||||
void main() async {
|
||||
final dio = Dio();
|
||||
final cookieJar = CookieJar();
|
||||
dio.interceptors.add(CookieManager(cookieJar));
|
||||
// First request, and save cookies (CookieManager do it).
|
||||
await dio.get("https://dart.dev");
|
||||
// Print cookies
|
||||
print(await cookieJar.loadForRequest(Uri.parse("https://dart.dev")));
|
||||
// Second request with the cookies
|
||||
await dio.get('https://dart.dev');
|
||||
}
|
||||
```
|
||||
|
||||
## Cookie Manager
|
||||
|
||||
`CookieManager` Interceptor can help us manage the request/response cookies automatically.
|
||||
`CookieManager` depends on the `cookie_jar` package:
|
||||
|
||||
> The dio_cookie_manager manage API is based on the withdrawn
|
||||
> [cookie_jar](https://github.com/flutterchina/cookie_jar).
|
||||
|
||||
You can create a `CookieJar` or `PersistCookieJar` to manage cookies automatically,
|
||||
and dio use the `CookieJar` by default, which saves the cookies **in RAM**.
|
||||
If you want to persists cookies, you can use the `PersistCookieJar` class, for example:
|
||||
|
||||
```dart
|
||||
dio.interceptors.add(CookieManager(PersistCookieJar()))
|
||||
```
|
||||
|
||||
`PersistCookieJar` persists the cookies in files,
|
||||
so if the application exit, the cookies always exist unless call `delete` explicitly.
|
||||
|
||||
> Note: In flutter, the path passed to `PersistCookieJar` must be valid (exists in phones and with write access).
|
||||
> Use [path_provider](https://pub.dev/packages/path_provider) package to get the right path.
|
||||
|
||||
In flutter:
|
||||
|
||||
```dart
|
||||
Future<void> prepareJar() async {
|
||||
final Directory appDocDir = await getApplicationDocumentsDirectory();
|
||||
final String appDocPath = appDocDir.path;
|
||||
final jar = PersistCookieJar(
|
||||
ignoreExpires: true,
|
||||
storage: FileStorage(appDocPath + "/.cookies/"),
|
||||
);
|
||||
dio.interceptors.add(CookieManager(jar));
|
||||
}
|
||||
```
|
||||
|
||||
## Handling Cookies with redirect requests
|
||||
|
||||
Redirect requests require extra configuration to parse cookies correctly.
|
||||
In shortly:
|
||||
- Set `followRedirects` to `false`.
|
||||
- Allow `statusCode` from `300` to `399` responses predicated as succeed.
|
||||
- Make further requests using the `HttpHeaders.locationHeader`.
|
||||
|
||||
For example:
|
||||
```dart
|
||||
final cookieJar = CookieJar();
|
||||
final dio = Dio()
|
||||
..interceptors.add(CookieManager(cookieJar))
|
||||
..options.followRedirects = false
|
||||
..options.validateStatus =
|
||||
(status) => status != null && status >= 200 && status < 400;
|
||||
final redirected = await dio.get('/redirection');
|
||||
final response = await dio.get(
|
||||
redirected.headers.value(HttpHeaders.locationHeader)!,
|
||||
);
|
||||
```
|
||||
258
lib/utils/accounts/account_manager/account_mgr.dart
Normal file
258
lib/utils/accounts/account_manager/account_mgr.dart
Normal file
@@ -0,0 +1,258 @@
|
||||
// edit from package:dio_cookie_manager
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:PiliPlus/http/api.dart';
|
||||
import 'package:PiliPlus/http/constants.dart';
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
|
||||
import '../account.dart';
|
||||
|
||||
final _setCookieReg = RegExp('(?<=)(,)(?=[^;]+?=)');
|
||||
|
||||
class AccountManager extends Interceptor {
|
||||
static final Map<AccountType, Set<String>> apiTypeSet = {
|
||||
AccountType.heartbeat: {
|
||||
Api.videoUrl,
|
||||
Api.videoIntro,
|
||||
Api.relatedList,
|
||||
Api.replyList,
|
||||
Api.replyReplyList,
|
||||
Api.searchSuggest,
|
||||
Api.searchByType,
|
||||
Api.heartBeat,
|
||||
Api.ab2c,
|
||||
Api.bangumiInfo,
|
||||
Api.liveRoomInfo,
|
||||
Api.onlineTotal,
|
||||
Api.dynamicDetail,
|
||||
Api.aiConclusion,
|
||||
Api.getSeasonDetailApi,
|
||||
Api.liveRoomDmToken,
|
||||
Api.liveRoomDmPrefetch,
|
||||
},
|
||||
AccountType.recommend: {
|
||||
Api.recommendListWeb,
|
||||
Api.recommendListApp,
|
||||
Api.feedDislike,
|
||||
Api.feedDislikeCancel,
|
||||
Api.hotList,
|
||||
Api.hotSearchList, // 不同账号搜索结果可能不一样
|
||||
Api.searchDefault,
|
||||
Api.searchSuggest,
|
||||
Api.searchByType
|
||||
},
|
||||
AccountType.video: {Api.videoUrl, Api.bangumiVideoUrl}
|
||||
};
|
||||
|
||||
static final loginApi = {
|
||||
Api.getTVCode,
|
||||
Api.qrcodePoll,
|
||||
Api.getCaptcha,
|
||||
Api.getWebKey,
|
||||
Api.appSmsCode,
|
||||
Api.loginByPwdApi,
|
||||
Api.logInByAppSms,
|
||||
Api.safeCenterGetInfo,
|
||||
Api.preCapture,
|
||||
Api.safeCenterSmsCode,
|
||||
Api.safeCenterSmsVerify,
|
||||
Api.oauth2AccessToken,
|
||||
};
|
||||
|
||||
const AccountManager();
|
||||
|
||||
static String getCookies(List<Cookie> cookies) {
|
||||
// Sort cookies by path (longer path first).
|
||||
cookies.sort((a, b) {
|
||||
if (a.path == null && b.path == null) {
|
||||
return 0;
|
||||
} else if (a.path == null) {
|
||||
return -1;
|
||||
} else if (b.path == null) {
|
||||
return 1;
|
||||
} else {
|
||||
return b.path!.length.compareTo(a.path!.length);
|
||||
}
|
||||
});
|
||||
return cookies.map((cookie) => '${cookie.name}=${cookie.value}').join('; ');
|
||||
}
|
||||
|
||||
@override
|
||||
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
|
||||
final path = options.path;
|
||||
|
||||
if (path.startsWith(GStorage.blockServer)) return handler.next(options);
|
||||
|
||||
final Account account = options.extra['account'] ?? _findAccount(path);
|
||||
|
||||
if (account.isLogin) options.headers.addAll(account.headers);
|
||||
|
||||
// app端不需要管理cookie
|
||||
if (path.startsWith(HttpString.appBaseUrl)) {
|
||||
// debugPrint('is app: ${options.path}');
|
||||
// bytes是grpc响应
|
||||
if (options.responseType != ResponseType.bytes) {
|
||||
final dataPtr = (options.method == 'POST' && options.data is Map
|
||||
? options.data as Map
|
||||
: options.queryParameters)
|
||||
.cast<String, dynamic>();
|
||||
if (dataPtr.isNotEmpty) {
|
||||
if (!account.accessKey.isNullOrEmpty) {
|
||||
dataPtr['access_key'] = account.accessKey!;
|
||||
}
|
||||
dataPtr['ts'] ??=
|
||||
(DateTime.now().millisecondsSinceEpoch ~/ 1000).toString();
|
||||
Utils.appSign(dataPtr);
|
||||
// debugPrint(dataPtr.toString());
|
||||
}
|
||||
}
|
||||
return handler.next(options);
|
||||
} else {
|
||||
account.cookieJar.loadForRequest(options.uri).then((cookies) {
|
||||
final previousCookies =
|
||||
options.headers[HttpHeaders.cookieHeader] as String?;
|
||||
final newCookies = getCookies([
|
||||
...?previousCookies
|
||||
?.split(';')
|
||||
.where((e) => e.isNotEmpty)
|
||||
.map((c) => Cookie.fromSetCookieValue(c)),
|
||||
...cookies,
|
||||
]);
|
||||
options.headers[HttpHeaders.cookieHeader] =
|
||||
newCookies.isNotEmpty ? newCookies : null;
|
||||
handler.next(options);
|
||||
}).catchError((dynamic e, StackTrace s) {
|
||||
final err = DioException(
|
||||
requestOptions: options,
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
);
|
||||
handler.reject(err, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onResponse(Response response, ResponseInterceptorHandler handler) {
|
||||
final path = response.requestOptions.path;
|
||||
if (path.startsWith(HttpString.appBaseUrl) ||
|
||||
path.startsWith(GStorage.blockServer)) {
|
||||
return handler.next(response);
|
||||
} else {
|
||||
_saveCookies(response).then((_) => handler.next(response)).catchError(
|
||||
(dynamic e, StackTrace s) {
|
||||
final error = DioException(
|
||||
requestOptions: response.requestOptions,
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
);
|
||||
handler.reject(error, true);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onError(DioException err, ErrorInterceptorHandler handler) {
|
||||
String url = err.requestOptions.uri.toString();
|
||||
debugPrint('🌹🌹ApiInterceptor: $url');
|
||||
if (url.contains('heartbeat') ||
|
||||
url.contains('seg.so') ||
|
||||
url.contains('online/total') ||
|
||||
url.contains('github') ||
|
||||
(url.contains('skipSegments') && err.requestOptions.method == 'GET')) {
|
||||
// skip
|
||||
} else {
|
||||
dioError(err).then((res) => SmartDialog.showToast(res + url));
|
||||
}
|
||||
if (err.response != null &&
|
||||
!err.response!.requestOptions.path.startsWith(HttpString.appBaseUrl)) {
|
||||
_saveCookies(err.response!).then((_) => handler.next(err)).catchError(
|
||||
(dynamic e, StackTrace s) {
|
||||
final error = DioException(
|
||||
requestOptions: err.response!.requestOptions,
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
);
|
||||
handler.next(error);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
handler.next(err);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _saveCookies(Response response) async {
|
||||
final account = (response.requestOptions.extra['account'] as Account? ??
|
||||
_findAccount(response.requestOptions.path));
|
||||
final setCookies = response.headers[HttpHeaders.setCookieHeader];
|
||||
if (setCookies == null || setCookies.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final List<Cookie> cookies = setCookies
|
||||
.map((str) => str.split(_setCookieReg))
|
||||
.expand((cookie) => cookie)
|
||||
.where((cookie) => cookie.isNotEmpty)
|
||||
.map((str) => Cookie.fromSetCookieValue(str))
|
||||
.toList();
|
||||
final statusCode = response.statusCode ?? 0;
|
||||
final locations = response.headers[HttpHeaders.locationHeader] ?? [];
|
||||
final isRedirectRequest = statusCode >= 300 && statusCode < 400;
|
||||
final originalUri = response.requestOptions.uri;
|
||||
final realUri = originalUri.resolveUri(response.realUri);
|
||||
await account.cookieJar.saveFromResponse(realUri, cookies);
|
||||
if (isRedirectRequest && locations.isNotEmpty) {
|
||||
final originalUri = response.realUri;
|
||||
await Future.wait(
|
||||
locations.map(
|
||||
(location) => account.cookieJar.saveFromResponse(
|
||||
// Resolves the location based on the current Uri.
|
||||
originalUri.resolve(location),
|
||||
cookies,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
await account.onChange();
|
||||
}
|
||||
|
||||
Account _findAccount(String path) => loginApi.contains(path)
|
||||
? AnonymousAccount()
|
||||
: Accounts.get(AccountType.values.firstWhere(
|
||||
(i) => apiTypeSet[i]?.contains(path) == true,
|
||||
orElse: () => AccountType.main));
|
||||
|
||||
static Future<String> dioError(DioException error) async {
|
||||
switch (error.type) {
|
||||
case DioExceptionType.badCertificate:
|
||||
return '证书有误!';
|
||||
case DioExceptionType.badResponse:
|
||||
return '服务器异常,请稍后重试!';
|
||||
case DioExceptionType.cancel:
|
||||
return '请求已被取消,请重新请求';
|
||||
case DioExceptionType.connectionError:
|
||||
return '连接错误,请检查网络设置';
|
||||
case DioExceptionType.connectionTimeout:
|
||||
return '网络连接超时,请检查网络设置';
|
||||
case DioExceptionType.receiveTimeout:
|
||||
return '响应超时,请稍后重试!';
|
||||
case DioExceptionType.sendTimeout:
|
||||
return '发送请求超时,请检查网络设置';
|
||||
case DioExceptionType.unknown:
|
||||
final String res =
|
||||
(await Connectivity().checkConnectivity()).first.title;
|
||||
return '$res网络异常 ${error.error}';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension _ConnectivityResultExt on ConnectivityResult {
|
||||
String get title => const ['蓝牙', 'Wi-Fi', '局域', '流量', '无', '代理', '其他'][index];
|
||||
}
|
||||
28
lib/utils/accounts/account_type_adapter.dart
Normal file
28
lib/utils/accounts/account_type_adapter.dart
Normal file
@@ -0,0 +1,28 @@
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
|
||||
import '../storage.dart' show AccountType;
|
||||
|
||||
class AccountTypeAdapter extends TypeAdapter<AccountType> {
|
||||
@override
|
||||
final int typeId = 10;
|
||||
|
||||
@override
|
||||
AccountType read(BinaryReader reader) =>
|
||||
AccountType.values.getOrNull(reader.readByte()) ?? AccountType.main;
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, AccountType obj) {
|
||||
writer.writeByte(obj.index);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is AccountTypeAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
28
lib/utils/accounts/cookie_jar_adapter.dart
Normal file
28
lib/utils/accounts/cookie_jar_adapter.dart
Normal file
@@ -0,0 +1,28 @@
|
||||
import 'package:cookie_jar/cookie_jar.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
|
||||
import 'account.dart';
|
||||
|
||||
class BiliCookieJarAdapter extends TypeAdapter<DefaultCookieJar> {
|
||||
@override
|
||||
final int typeId = 8;
|
||||
|
||||
@override
|
||||
DefaultCookieJar read(BinaryReader reader) =>
|
||||
BiliCookieJar.fromJson(reader.readMap().cast<String, String>());
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, DefaultCookieJar obj) {
|
||||
writer.writeMap(obj.toJson());
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is BiliCookieJarAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
@@ -8,7 +8,7 @@ class Data {
|
||||
}
|
||||
|
||||
static Future historyStatus() async {
|
||||
if (GStorage.userInfo.get('userInfoCache') == null) {
|
||||
if (!Accounts.main.isLogin) {
|
||||
return;
|
||||
}
|
||||
var res = await UserHttp.historyStatus();
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:PiliPlus/http/constants.dart';
|
||||
import 'package:PiliPlus/http/init.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/models/common/dynamics_type.dart';
|
||||
import 'package:PiliPlus/models/common/tab_type.dart' hide tabsConfig;
|
||||
@@ -12,6 +9,7 @@ import 'package:PiliPlus/pages/bangumi/controller.dart';
|
||||
import 'package:PiliPlus/pages/dynamics/tab/controller.dart';
|
||||
import 'package:PiliPlus/pages/live/controller.dart';
|
||||
import 'package:PiliPlus/pages/main/controller.dart';
|
||||
import 'package:PiliPlus/utils/accounts/account.dart';
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
@@ -30,52 +28,31 @@ import 'package:PiliPlus/http/user.dart';
|
||||
class LoginUtils {
|
||||
static final random = Random();
|
||||
|
||||
static Future onLogin(Map<String, dynamic> tokenInfo, jsonCookieInfo) async {
|
||||
static Future onLoginMain() async {
|
||||
final account = Accounts.main;
|
||||
try {
|
||||
GStorage.localCache.put(LocalCacheKey.accessKey, {
|
||||
'mid': tokenInfo['mid'],
|
||||
'value': tokenInfo['access_token'] ?? tokenInfo['value'],
|
||||
'refresh': tokenInfo['refresh_token'] ?? tokenInfo['refresh']
|
||||
});
|
||||
List<dynamic> cookieInfo = jsonCookieInfo['cookies'];
|
||||
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);
|
||||
}
|
||||
Request.dio.options.headers['cookie'] = cookieStrings;
|
||||
await WebviewCookieManager().setCookies(cookies);
|
||||
for (Cookie item in cookies) {
|
||||
await web.CookieManager().setCookie(
|
||||
url: web.WebUri(item.domain ?? ''),
|
||||
name: item.name,
|
||||
value: item.value,
|
||||
path: item.path ?? '',
|
||||
domain: item.domain,
|
||||
isSecure: item.secure,
|
||||
isHttpOnly: item.httpOnly,
|
||||
);
|
||||
}
|
||||
final cookies = account.cookieJar.toList();
|
||||
final webManager = web.CookieManager();
|
||||
Future.wait([
|
||||
WebviewCookieManager().setCookies(cookies),
|
||||
...cookies.map((item) => webManager.setCookie(
|
||||
url: web.WebUri(item.domain ?? ''),
|
||||
name: item.name,
|
||||
value: item.value,
|
||||
path: item.path ?? '',
|
||||
domain: item.domain,
|
||||
isSecure: item.secure,
|
||||
isHttpOnly: item.httpOnly,
|
||||
))
|
||||
]);
|
||||
} catch (e) {
|
||||
SmartDialog.showToast('设置登录态失败,$e');
|
||||
}
|
||||
final result = await UserHttp.userInfo();
|
||||
if (result['status'] && result['data'].isLogin) {
|
||||
SmartDialog.showToast('登录成功,当前采用「'
|
||||
'${GStorage.setting.get(SettingBoxKey.defaultRcmdType, defaultValue: 'app')}'
|
||||
'端」推荐');
|
||||
await GStorage.userInfo.put('userInfoCache', result['data']);
|
||||
final UserInfoData data = result['data'];
|
||||
if (result['status'] && data.isLogin!) {
|
||||
SmartDialog.showToast('main登录成功');
|
||||
await GStorage.userInfo.put('userInfoCache', data);
|
||||
try {
|
||||
Get.find<MineController>()
|
||||
..isLogin.value = true
|
||||
@@ -85,14 +62,14 @@ class LoginUtils {
|
||||
try {
|
||||
Get.find<HomeController>()
|
||||
..isLogin.value = true
|
||||
..userFace.value = result['data'].face;
|
||||
..userFace.value = data.face!;
|
||||
} catch (_) {}
|
||||
|
||||
try {
|
||||
Get.find<DynamicsController>()
|
||||
..isLogin.value = true
|
||||
..ownerMid = result['data'].mid
|
||||
..face = result['data'].face
|
||||
..ownerMid = data.mid
|
||||
..face = data.face
|
||||
..onRefresh();
|
||||
} catch (_) {}
|
||||
|
||||
@@ -105,7 +82,7 @@ class LoginUtils {
|
||||
|
||||
try {
|
||||
Get.find<MediaController>()
|
||||
..mid = result['data'].mid
|
||||
..mid = data.mid
|
||||
..onRefresh();
|
||||
} catch (_) {}
|
||||
|
||||
@@ -128,19 +105,18 @@ class LoginUtils {
|
||||
} catch (_) {}
|
||||
} else {
|
||||
// 获取用户信息失败
|
||||
await Accounts.set(AccountType.main, await account.logout());
|
||||
SmartDialog.showNotify(
|
||||
msg: '登录失败,请检查cookie是否正确,${result['message']}',
|
||||
notifyType: NotifyType.warning);
|
||||
}
|
||||
}
|
||||
|
||||
static Future onLogout() async {
|
||||
await Request.cookieManager.cookieJar.deleteAll();
|
||||
await web.CookieManager().deleteAllCookies();
|
||||
Request.dio.options.headers['cookie'] = '';
|
||||
|
||||
await GStorage.userInfo.delete('userInfoCache');
|
||||
await GStorage.localCache.delete(LocalCacheKey.accessKey);
|
||||
static Future onLogoutMain() async {
|
||||
await Future.wait([
|
||||
web.CookieManager().deleteAllCookies(),
|
||||
GStorage.userInfo.delete('userInfoCache'),
|
||||
]);
|
||||
|
||||
try {
|
||||
Get.find<MainController>().isLogin.value = false;
|
||||
@@ -151,7 +127,7 @@ class LoginUtils {
|
||||
..userInfo.value = UserInfoData()
|
||||
..userStat.value = UserStat()
|
||||
..isLogin.value = false;
|
||||
MineController.anonymity.value = false;
|
||||
// MineController.anonymity.value = false;
|
||||
} catch (_) {}
|
||||
|
||||
try {
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:PiliPlus/common/widgets/pair.dart';
|
||||
import 'package:PiliPlus/common/widgets/refresh_indicator.dart'
|
||||
show kDragContainerExtentPercentage, displacement;
|
||||
import 'package:PiliPlus/http/constants.dart';
|
||||
import 'package:PiliPlus/http/index.dart';
|
||||
import 'package:PiliPlus/models/common/dynamic_badge_mode.dart';
|
||||
import 'package:PiliPlus/models/common/sponsor_block/segment_type.dart';
|
||||
import 'package:PiliPlus/models/common/sponsor_block/skip_type.dart';
|
||||
@@ -15,8 +16,15 @@ import 'package:PiliPlus/models/video/play/CDN.dart';
|
||||
import 'package:PiliPlus/models/video/play/quality.dart';
|
||||
import 'package:PiliPlus/models/video/play/subtitle.dart';
|
||||
import 'package:PiliPlus/pages/member/new/controller.dart' show MemberTabType;
|
||||
import 'package:PiliPlus/pages/mine/index.dart';
|
||||
import 'package:PiliPlus/plugin/pl_player/models/bottom_progress_behavior.dart';
|
||||
import 'package:PiliPlus/plugin/pl_player/models/fullscreen_mode.dart';
|
||||
import 'package:PiliPlus/utils/accounts/account.dart';
|
||||
import 'package:PiliPlus/utils/accounts/account_adapter.dart';
|
||||
import 'package:PiliPlus/utils/accounts/cookie_jar_adapter.dart';
|
||||
import 'package:PiliPlus/utils/accounts/account_type_adapter.dart';
|
||||
import 'package:PiliPlus/utils/login.dart';
|
||||
import 'package:cookie_jar/cookie_jar.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
@@ -33,9 +41,9 @@ class GStorage {
|
||||
static late final Box<dynamic> setting;
|
||||
static late final Box<dynamic> video;
|
||||
|
||||
static bool get isLogin => userInfo.get('userInfoCache') != null;
|
||||
// static bool get isLogin => userInfo.get('userInfoCache') != null;
|
||||
|
||||
static get ownerMid => userInfo.get('userInfoCache')?.mid;
|
||||
// static get ownerMid => userInfo.get('userInfoCache')?.mid;
|
||||
|
||||
static List<double> get speedList => List<double>.from(
|
||||
video.get(
|
||||
@@ -192,8 +200,8 @@ class GStorage {
|
||||
static int get minLikeRatioForRecommend =>
|
||||
setting.get(SettingBoxKey.minLikeRatioForRecommend, defaultValue: 0);
|
||||
|
||||
static String get defaultRcmdType =>
|
||||
setting.get(SettingBoxKey.defaultRcmdType, defaultValue: 'app');
|
||||
static bool get appRcmd =>
|
||||
setting.get(SettingBoxKey.appRcmd, defaultValue: true);
|
||||
|
||||
static String get defaultSystemProxyHost =>
|
||||
setting.get(SettingBoxKey.systemProxyHost, defaultValue: '');
|
||||
@@ -493,6 +501,9 @@ class GStorage {
|
||||
video = await Hive.openBox('video');
|
||||
displacement = GStorage.refreshDisplacement;
|
||||
kDragContainerExtentPercentage = GStorage.refreshDragPercentage;
|
||||
|
||||
await Accounts.init();
|
||||
|
||||
// 设置全局变量
|
||||
GlobalData()
|
||||
..imgQuality = defaultPicQa
|
||||
@@ -521,6 +532,9 @@ class GStorage {
|
||||
Hive.registerAdapter(LevelInfoAdapter());
|
||||
Hive.registerAdapter(HotSearchModelAdapter());
|
||||
Hive.registerAdapter(HotSearchItemAdapter());
|
||||
Hive.registerAdapter(BiliCookieJarAdapter());
|
||||
Hive.registerAdapter(LoginAccountAdapter());
|
||||
Hive.registerAdapter(AccountTypeAdapter());
|
||||
}
|
||||
|
||||
static Future<void> close() async {
|
||||
@@ -536,6 +550,7 @@ class GStorage {
|
||||
setting.close();
|
||||
video.compact();
|
||||
video.close();
|
||||
Accounts.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -587,11 +602,11 @@ class SettingBoxKey {
|
||||
continuePlayInBackground = 'continuePlayInBackground',
|
||||
|
||||
/// 隐私
|
||||
anonymity = 'anonymity',
|
||||
// anonymity = 'anonymity',
|
||||
|
||||
/// 推荐
|
||||
enableRcmdDynamic = 'enableRcmdDynamic',
|
||||
defaultRcmdType = 'defaultRcmdType',
|
||||
appRcmd = 'appRcmd',
|
||||
enableSaveLastData = 'enableSaveLastData',
|
||||
minDurationForRcmd = 'minDurationForRcmd',
|
||||
minLikeRatioForRecommend = 'minLikeRatioForRecommend',
|
||||
@@ -742,8 +757,8 @@ class LocalCacheKey {
|
||||
blackMidsList = 'blackMidsList',
|
||||
// 弹幕屏蔽规则
|
||||
danmakuFilterRule = 'danmakuFilterRule',
|
||||
// access_key
|
||||
accessKey = 'accessKey',
|
||||
// // access_key
|
||||
// accessKey = 'accessKey',
|
||||
|
||||
//
|
||||
mixinKey = 'mixinKey',
|
||||
@@ -768,3 +783,112 @@ class VideoBoxKey {
|
||||
// 画面填充比例
|
||||
cacheVideoFit = 'cacheVideoFit';
|
||||
}
|
||||
|
||||
class Accounts {
|
||||
static late final Box<LoginAccount> account;
|
||||
static final Map<AccountType, Account> accountMode = {};
|
||||
static Account get main => accountMode[AccountType.main]!;
|
||||
// static set main(Account account) => set(AccountType.main, account);
|
||||
|
||||
static Future<void> init() async {
|
||||
account = await Hive.openBox('account',
|
||||
compactionStrategy: (int entries, int deletedEntries) {
|
||||
return deletedEntries > 2;
|
||||
});
|
||||
await _migrate();
|
||||
}
|
||||
|
||||
static Future<void> _migrate() async {
|
||||
final Directory tempDir = await getApplicationSupportDirectory();
|
||||
final String tempPath = "${tempDir.path}/.plpl/";
|
||||
final Directory dir = Directory(tempPath);
|
||||
if (await dir.exists()) {
|
||||
debugPrint('migrating...');
|
||||
final cookieJar =
|
||||
PersistCookieJar(ignoreExpires: true, storage: FileStorage(tempPath));
|
||||
await cookieJar.forceInit();
|
||||
final cookies = DefaultCookieJar(ignoreExpires: true)
|
||||
..domainCookies.addAll(cookieJar.domainCookies);
|
||||
final localAccessKey =
|
||||
GStorage.localCache.get('accessKey', defaultValue: {});
|
||||
|
||||
final isLogin =
|
||||
cookies.domainCookies['bilibili.com']?['/']?['SESSDATA'] != null;
|
||||
|
||||
await Future.wait([
|
||||
GStorage.localCache.delete('accessKey'),
|
||||
dir.delete(recursive: true),
|
||||
if (isLogin)
|
||||
LoginAccount(cookies, localAccessKey['value'],
|
||||
localAccessKey['refresh'], AccountType.values.toSet())
|
||||
.onChange()
|
||||
]);
|
||||
debugPrint('migrated successfully');
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> refresh() async {
|
||||
for (var a in account.values) {
|
||||
for (var t in a.type) {
|
||||
accountMode[t] = a;
|
||||
}
|
||||
}
|
||||
for (var type in AccountType.values) {
|
||||
accountMode[type] ??= AnonymousAccount();
|
||||
}
|
||||
await Future.wait((accountMode.values.toSet()
|
||||
..retainWhere((i) => !i.activited))
|
||||
.map((i) => Request.buvidActive(i)));
|
||||
}
|
||||
|
||||
static Future<void> clear() async {
|
||||
await account.clear();
|
||||
for (var i in AccountType.values) {
|
||||
accountMode[i] = AnonymousAccount();
|
||||
}
|
||||
if (!AnonymousAccount().activited) {
|
||||
Request.buvidActive(AnonymousAccount());
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> close() async {
|
||||
account.compact();
|
||||
account.close();
|
||||
}
|
||||
|
||||
static Future<void> set(AccountType key, Account account) async {
|
||||
await (accountMode[key]?..type.remove(key))?.onChange();
|
||||
accountMode[key] = account..type.add(key);
|
||||
await account.onChange();
|
||||
if (!account.activited) await Request.buvidActive(account);
|
||||
switch (key) {
|
||||
case AccountType.main:
|
||||
if (account.isLogin) {
|
||||
await LoginUtils.onLoginMain();
|
||||
} else {
|
||||
await LoginUtils.onLogoutMain();
|
||||
}
|
||||
break;
|
||||
case AccountType.heartbeat:
|
||||
MineController.anonymity.value = !account.isLogin;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static Account get(AccountType key) {
|
||||
return accountMode[key]!;
|
||||
}
|
||||
}
|
||||
|
||||
enum AccountType {
|
||||
main,
|
||||
heartbeat,
|
||||
recommend,
|
||||
video,
|
||||
}
|
||||
|
||||
extension AccountTypeExt on AccountType {
|
||||
String get title => const ['主账号', '记录观看', '推荐', '视频取流'][index];
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
import 'package:PiliPlus/build_config.dart';
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/common/widgets/interactiveviewer_gallery/interactiveviewer_gallery.dart';
|
||||
import 'package:PiliPlus/common/widgets/radio_widget.dart';
|
||||
import 'package:PiliPlus/grpc/app/main/community/reply/v1/reply.pb.dart';
|
||||
@@ -24,6 +25,7 @@ import 'package:PiliPlus/pages/dynamics/tab/controller.dart';
|
||||
import 'package:PiliPlus/pages/later/controller.dart';
|
||||
import 'package:PiliPlus/pages/video/detail/introduction/widgets/fav_panel.dart';
|
||||
import 'package:PiliPlus/pages/video/detail/introduction/widgets/group_panel.dart';
|
||||
import 'package:PiliPlus/utils/accounts/account.dart';
|
||||
import 'package:PiliPlus/utils/app_scheme.dart';
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:PiliPlus/utils/feed_back.dart';
|
||||
@@ -40,7 +42,6 @@ import 'package:flutter/services.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:get/get_navigation/src/dialog/dialog_route.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:html/dom.dart' as dom;
|
||||
@@ -778,7 +779,7 @@ class Utils {
|
||||
dynamic response = await Request().get(
|
||||
'${HttpString.spaceBaseUrl}/$mid/dynamic',
|
||||
options: Options(
|
||||
extra: {'clearCookie': true},
|
||||
extra: {'account': AnonymousAccount()},
|
||||
),
|
||||
);
|
||||
dom.Document document = html_parser.parse(response.data);
|
||||
@@ -1137,16 +1138,16 @@ class Utils {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<String> getCookiePath() async {
|
||||
final Directory tempDir = await getApplicationSupportDirectory();
|
||||
final String tempPath = "${tempDir.path}/.plpl/";
|
||||
final Directory dir = Directory(tempPath);
|
||||
final bool b = await dir.exists();
|
||||
if (!b) {
|
||||
dir.createSync(recursive: true);
|
||||
}
|
||||
return tempPath;
|
||||
}
|
||||
// static Future<String> getCookiePath() async {
|
||||
// final Directory tempDir = await getApplicationSupportDirectory();
|
||||
// final String tempPath = "${tempDir.path}/.plpl/";
|
||||
// final Directory dir = Directory(tempPath);
|
||||
// final bool b = await dir.exists();
|
||||
// if (!b) {
|
||||
// dir.createSync(recursive: true);
|
||||
// }
|
||||
// return tempPath;
|
||||
// }
|
||||
|
||||
static String numFormat(dynamic number) {
|
||||
if (number == null) {
|
||||
@@ -1596,18 +1597,17 @@ class Utils {
|
||||
return height;
|
||||
}
|
||||
|
||||
static String appSign(
|
||||
Map<String, String> params, String appkey, String appsec) {
|
||||
static void appSign(Map<String, dynamic> params,
|
||||
[String appkey = Constants.appKey, String appsec = Constants.appSec]) {
|
||||
params['appkey'] = appkey;
|
||||
var searchParams = Uri(queryParameters: params).query;
|
||||
var sortedParams = searchParams.split('&')..sort();
|
||||
var sortedQueryString = sortedParams.join('&');
|
||||
var searchParams = Uri(
|
||||
queryParameters:
|
||||
params.map((key, value) => MapEntry(key, value.toString()))).query;
|
||||
var sortedQueryString = (searchParams.split('&')..sort()).join('&');
|
||||
|
||||
var appsecString = sortedQueryString + appsec;
|
||||
var md5Digest = md5.convert(utf8.encode(appsecString));
|
||||
var md5String = md5Digest.toString(); // 获取MD5哈希值
|
||||
|
||||
return md5String;
|
||||
params['sign'] = md5
|
||||
.convert(utf8.encode(sortedQueryString + appsec))
|
||||
.toString(); // 获取MD5哈希值
|
||||
}
|
||||
|
||||
static List<int> generateRandomBytes(int minLength, int maxLength) {
|
||||
|
||||
Reference in New Issue
Block a user