import 'dart:convert'; import 'dart:typed_data'; import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/grpc/bilibili/metadata.pb.dart'; import 'package:PiliPlus/grpc/bilibili/metadata/device.pb.dart'; import 'package:PiliPlus/grpc/bilibili/metadata/fawkes.pb.dart'; import 'package:PiliPlus/grpc/bilibili/metadata/locale.pb.dart'; import 'package:PiliPlus/grpc/bilibili/metadata/network.pb.dart' as network; import 'package:PiliPlus/grpc/google/rpc/status.pb.dart'; import 'package:PiliPlus/http/constants.dart'; import 'package:PiliPlus/http/init.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/utils/accounts.dart'; import 'package:PiliPlus/utils/id_utils.dart'; import 'package:PiliPlus/utils/login_utils.dart'; import 'package:PiliPlus/utils/utils.dart'; import 'package:archive/archive.dart'; import 'package:dio/dio.dart'; import 'package:flutter/foundation.dart' show kDebugMode; import 'package:protobuf/protobuf.dart' show GeneratedMessage; class GrpcReq { static String? _accessKey = Accounts.main.accessKey; static const _build = 2001100; static const _versionName = '2.0.1'; static const _biliChannel = 'master'; static const _mobiApp = 'android_hd'; static const _device = 'android'; static final _buvid = LoginUtils.buvid; static final _traceId = IdUtils.genTraceId(); static final _sessionId = Utils.generateRandomString(8); static void updateHeaders(String? accessKey) { _accessKey = accessKey; if (_accessKey != null) { headers['authorization'] = 'identify_v1 $_accessKey'; } else { headers.remove('authorization'); } headers['x-bili-metadata-bin'] = base64Encode( Metadata( accessKey: _accessKey ?? '', mobiApp: _mobiApp, device: _device, build: _build, channel: _biliChannel, buvid: _buvid, platform: _device, ).writeToBuffer(), ); options = Options(headers: headers, responseType: ResponseType.bytes); } static final Map headers = { Headers.contentTypeHeader: 'application/grpc', 'grpc-encoding': 'gzip', 'gzip-accept-encoding': 'gzip,identity', 'user-agent': Constants.userAgent, 'x-bili-gaia-vtoken': '', 'x-bili-aurora-zone': '', 'x-bili-trace-id': _traceId, if (_accessKey != null) 'authorization': 'identify_v1 $_accessKey', 'buvid': _buvid, 'bili-http-engine': 'cronet', 'te': 'trailers', 'x-bili-fawkes-req-bin': base64Encode( FawkesReq( appkey: _mobiApp, env: 'prod', sessionId: _sessionId, ).writeToBuffer(), ), 'x-bili-metadata-bin': base64Encode( Metadata( accessKey: _accessKey ?? '', mobiApp: _mobiApp, device: _device, build: _build, channel: _biliChannel, buvid: _buvid, platform: _device, ).writeToBuffer(), ), 'x-bili-device-bin': base64Encode( Device( appId: 5, build: _build, buvid: _buvid, mobiApp: _mobiApp, platform: _device, channel: _biliChannel, brand: _device, model: _device, osver: '15', versionName: _versionName, ).writeToBuffer(), ), 'x-bili-network-bin': base64Encode( network.Network( type: network.NetworkType.WIFI, ).writeToBuffer(), ), 'x-bili-locale-bin': base64Encode( Locale( cLocale: LocaleIds(language: 'zh', region: 'CN', script: 'Hans'), sLocale: LocaleIds(language: 'zh', region: 'CN', script: 'Hans'), timezone: 'Asia/Shanghai', ).writeToBuffer(), ), 'x-bili-exps-bin': '', }; static Options options = Options( headers: headers, responseType: ResponseType.bytes, ); static Uint8List compressProtobuf(Uint8List proto) { proto = const GZipEncoder().encodeBytes(proto); var byteLength = ByteData(4)..setInt32(0, proto.length, Endian.big); return Uint8List(5 + proto.length) ..[0] = 1 ..setRange(1, 5, byteLength.buffer.asUint8List()) ..setAll(5, proto); } static Uint8List decompressProtobuf(Uint8List data) { var length = ByteData.sublistView(data, 1, 5).getInt32(0, Endian.big); if (data[0] == 1) { return const GZipDecoder().decodeBytes(data.sublist(5, length + 5)); } else { return data.sublist(5, length + 5); } } static Future> request( String url, GeneratedMessage request, T Function(Uint8List) grpcParser, ) async { final response = await Request().post( HttpString.appBaseUrl + url, data: compressProtobuf(request.writeToBuffer()), options: options, ); if (response.data is Map) { return Error(response.data['message']); } if (response.headers.value('Grpc-Status') == '0') { try { Uint8List data = response.data; data = decompressProtobuf(data); final grpcResponse = grpcParser(data); return Success(grpcResponse); } catch (e) { return Error(e.toString()); } } else { try { String msg = response.headers.value('Grpc-Status-Details-Bin') ?? ''; if (msg.isNotEmpty) { while (msg.length % 4 != 0) { msg += '='; } final msgBytes = base64Decode(msg); try { final grpcMsg = Status.fromBuffer(msgBytes); // UNKNOWN : -400 : msg final errMsg = grpcMsg.details .map((e) => e.status.message) .join('\n'); msg = kDebugMode ? 'CODE: ${grpcMsg.code}(${grpcMsg.message})\nMSG: $errMsg' : errMsg; } catch (e) { msg = utf8.decode(msgBytes, allowMalformed: true); } } return Error(msg); } catch (e) { return Error(e.toString()); } } } // static Future playerOnline({ // int aid = 0, // int cid = 0, // }) { // return _request( // GrpcUrl.playerOnline, // PlayerOnlineReq(aid: Int64(aid), cid: Int64(cid), playOpen: true), // PlayerOnlineReply.fromBuffer, // onSuccess: (response) => response.totalNumberText); // } // static Future popular(int idx) { // return _request(GrpcUrl.popular, PopularResultReq(idx: Int64(idx)), // PopularReply.fromBuffer, onSuccess: (response) { // response.items.retainWhere((item) => item.smallCoverV5.base.goto == 'av'); // return {'status': true, 'data': response.items}; // }); // } }