import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'package:PiliPlus/http/api.dart'; import 'package:PiliPlus/http/constants.dart'; import 'package:PiliPlus/http/retry_interceptor.dart'; import 'package:PiliPlus/http/user.dart'; import 'package:PiliPlus/utils/accounts.dart'; import 'package:PiliPlus/utils/accounts/account.dart'; import 'package:PiliPlus/utils/accounts/account_manager/account_mgr.dart'; import 'package:PiliPlus/utils/global_data.dart'; import 'package:PiliPlus/utils/login_utils.dart'; import 'package:PiliPlus/utils/storage_pref.dart'; import 'package:PiliPlus/utils/utils.dart'; import 'package:archive/archive.dart'; import 'package:brotli/brotli.dart'; import 'package:dio/dio.dart'; import 'package:dio/io.dart'; import 'package:dio_http2_adapter/dio_http2_adapter.dart'; import 'package:flutter/foundation.dart' show kDebugMode; class Request { static const _gzipDecoder = GZipDecoder(); static const _brotilDecoder = BrotliDecoder(); static final Request _instance = Request._internal(); static late AccountManager accountManager; static late final Dio dio; factory Request() => _instance; /// 设置cookie static void setCookie() { accountManager = AccountManager(); dio.interceptors.add(accountManager); Accounts.refresh(); LoginUtils.setWebCookie(); if (Accounts.main.isLogin) { final coin = Pref.userInfoCache?.money; if (coin == null) { setCoin(); } else { GlobalData().coins = coin; } } } static Future setCoin() async { final res = await UserHttp.getCoin(); if (res['status']) { GlobalData().coins = res['data']; } } static Future 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.generate(32, (_) => Utils.random.nextInt(256)) + List.filled(4, 0) + [73, 69, 78, 68] + List.generate(4, (_) => Utils.random.nextInt(256)), ); String jsonData = json.encode({ '3064': 1, '39c8': '${account is AnonymousAccount ? '333.1365' : '333.788'}.fp.risk', '3c43': { 'adca': 'Linux', 'bfe9': randPngEnd.substring(randPngEnd.length - 50), }, }); await Request().post( Api.activateBuvidApi, data: {'payload': jsonData}, options: Options(contentType: Headers.jsonContentType), ); } catch (_) {} } /* * config it and create */ Request._internal() { //BaseOptions、Options、RequestOptions 都可以配置参数,优先级别依次递增,且可以根据优先级别覆盖参数 BaseOptions options = BaseOptions( //请求基地址,可以包含子路径 baseUrl: HttpString.apiBaseUrl, //连接服务器超时时间,单位是毫秒. connectTimeout: const Duration(milliseconds: 10000), //响应流上前后两次接受到数据的间隔,单位为毫秒。 receiveTimeout: const Duration(milliseconds: 10000), //Http请求头. headers: { 'user-agent': 'Dart/3.6 (dart:io)', // Http2Adapter不会自动添加标头 }, responseDecoder: _responseDecoder, // Http2Adapter没有自动解压 persistentConnection: true, ); final bool enableSystemProxy; late final String systemProxyHost; late final int? systemProxyPort; if (Pref.enableSystemProxy) { systemProxyHost = Pref.systemProxyHost; systemProxyPort = int.tryParse(Pref.systemProxyPort); enableSystemProxy = systemProxyPort != null && systemProxyHost.isNotEmpty; } else { enableSystemProxy = false; } final http11Adapter = IOHttpClientAdapter( createHttpClient: enableSystemProxy ? () => HttpClient() ..idleTimeout = const Duration(seconds: 15) ..autoUncompress = false ..findProxy = ((_) => 'PROXY $systemProxyHost:$systemProxyPort') ..badCertificateCallback = (X509Certificate cert, String host, int port) => true : () => HttpClient() ..idleTimeout = const Duration(seconds: 15) ..autoUncompress = false, // Http2Adapter没有自动解压, 统一行为 ); late final Uri proxy; if (enableSystemProxy) { proxy = Uri( scheme: 'http', host: systemProxyHost, port: systemProxyPort, ); } dio = Dio(options) ..httpClientAdapter = Pref.enableHttp2 ? Http2Adapter( ConnectionManager( idleTimeout: const Duration(seconds: 15), onClientCreate: enableSystemProxy ? (_, config) { config ..proxy = proxy ..onBadCertificate = (_) => true; } : Pref.badCertificateCallback ? (_, config) { config.onBadCertificate = (_) => true; } : null, ), fallbackAdapter: http11Adapter, ) : http11Adapter; // 先于其他Interceptor dio.interceptors.add(RetryInterceptor(Pref.retryCount, Pref.retryDelay)); // 日志拦截器 输出请求、响应内容 if (kDebugMode) { dio.interceptors.add( LogInterceptor( request: false, requestHeader: false, responseHeader: false, ), ); } dio.transformer = BackgroundTransformer(); dio.options.validateStatus = (int? status) { return status! >= 200 && status < 300; }; } /* * get请求 */ Future get( String url, { Map? queryParameters, Options? options, CancelToken? cancelToken, }) async { try { return await dio.get( url, queryParameters: queryParameters, options: options, cancelToken: cancelToken, ); } on DioException catch (e) { return Response( data: { 'message': await AccountManager.dioError(e), }, // 将自定义 Map 数据赋值给 Response 的 data 属性 statusCode: e.response?.statusCode ?? -1, requestOptions: e.requestOptions, ); } } /* * post请求 */ Future post( String url, { Object? data, Map? queryParameters, Options? options, CancelToken? cancelToken, }) async { // if (kDebugMode) debugPrint('post-data: $data'); try { return await dio.post( url, data: data, queryParameters: queryParameters, options: options, cancelToken: cancelToken, ); } on DioException catch (e) { AccountManager.toast(e); return Response( data: { 'message': await AccountManager.dioError(e), }, // 将自定义 Map 数据赋值给 Response 的 data 属性 statusCode: e.response?.statusCode ?? -1, requestOptions: e.requestOptions, ); } } /* * 下载文件 */ Future downloadFile( String urlPath, String savePath, { CancelToken? cancelToken, }) async { try { return await dio.download( urlPath, savePath, cancelToken: cancelToken, // onReceiveProgress: (int count, int total) { // 进度 // if (kDebugMode) debugPrint("$count $total"); // }, ); // if (kDebugMode) debugPrint('downloadFile success: ${response.data}'); } on DioException catch (e) { // if (kDebugMode) debugPrint('downloadFile error: $e'); return Response( data: { 'message': await AccountManager.dioError(e), }, statusCode: e.response?.statusCode ?? -1, requestOptions: e.requestOptions, ); } } static List responseBytesDecoder( List responseBytes, Map> headers, ) => switch (headers['content-encoding']?.firstOrNull) { 'gzip' => _gzipDecoder.decodeBytes(responseBytes), 'br' => _brotilDecoder.convert(responseBytes), _ => responseBytes, }; static String _responseDecoder( List responseBytes, RequestOptions options, ResponseBody responseBody, ) => utf8.decode( responseBytesDecoder(responseBytes, responseBody.headers), allowMalformed: true, ); }