import 'dart:convert'; import 'dart:typed_data'; import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/grpc/bilibili/app/dynamic/v1.pb.dart'; import 'package:PiliPlus/grpc/bilibili/community/service/dm/v1.pb.dart'; import 'package:PiliPlus/grpc/bilibili/im/interfaces/v1.pb.dart'; import 'package:PiliPlus/grpc/bilibili/im/type.pb.dart'; import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.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/bilibili/metadata/restriction.pb.dart'; import 'package:PiliPlus/grpc/bilibili/pagination.pb.dart'; import 'package:PiliPlus/http/constants.dart'; import 'package:PiliPlus/http/init.dart'; import 'package:PiliPlus/utils/login_utils.dart'; import 'package:PiliPlus/utils/storage.dart'; import 'package:PiliPlus/utils/utils.dart'; import 'package:archive/archive.dart'; import 'package:dio/dio.dart'; import 'package:fixnum/fixnum.dart'; import 'package:protobuf/protobuf.dart' show GeneratedMessage; import 'package:uuid/uuid.dart'; class GrpcUrl { // static const playerOnline = // '/bilibili.app.playeronline.v1.PlayerOnline/PlayerOnline'; // static const popular = '/bilibili.app.show.v1.Popular/Index'; // dynamic static const dynRed = '/bilibili.app.dynamic.v1.Dynamic/DynRed'; // static const dynSpace = '/bilibili.app.dynamic.v2.Dynamic/DynSpace'; // danmaku static const dmSegMobile = '/bilibili.community.service.dm.v1.DM/DmSegMobile'; // reply static const reply = '/bilibili.main.community.reply.v1.Reply'; static const mainList = '$reply/MainList'; static const detailList = '$reply/DetailList'; static const dialogList = '$reply/DialogList'; // static const replyInfo = '$reply/ReplyInfo'; // im static const im = '/bilibili.im.interface.v1.ImInterface'; static const sendMsg = '$im/SendMsg'; static const shareList = '$im/ShareList'; } class GrpcRepo { static String? _accessKey = Accounts.main.accessKey; static const _build = 2001100; static const _biliChannel = 'bili'; static const _mobiApp = 'android_hd'; static const _phone = 'phone'; static final _buvid = LoginUtils.buvid; static final _traceId = Utils.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: _phone, build: _build, channel: _biliChannel, buvid: _buvid, platform: _mobiApp, ).writeToBuffer()); } static final Map headers = { Headers.contentTypeHeader: 'application/grpc', 'grpc-encoding': 'gzip', 'gzip-accept-encoding': 'gzip,identity', 'user-agent': '${Constants.userAgent} grpc-java-cronet/1.36.1', '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: _phone, build: _build, channel: _biliChannel, buvid: _buvid, platform: _mobiApp, ).writeToBuffer()), 'x-bili-device-bin': base64Encode(Device( appId: 1, build: _build, buvid: _buvid, mobiApp: _mobiApp, platform: _mobiApp, device: _phone, channel: _biliChannel, brand: _phone, model: _phone, osver: '14', fpLocal: '', fpRemote: '', versionName: _build.toString(), fp: '', fts: Int64()) .writeToBuffer()), 'x-bili-network-bin': base64Encode(network.Network( type: network.NetworkType.WIFI, tf: network.TFType.TF_UNKNOWN, oid: '') .writeToBuffer()), 'x-bili-restriction-bin': base64Encode(Restriction( teenagersMode: false, lessonsMode: false, mode: ModeType.NORMAL, review: false, disableRcmd: false, basicMode: false) .writeToBuffer()), 'x-bili-locale-bin': base64Encode(Locale( cLocale: LocaleIds(language: 'zh', region: 'CN'), sLocale: LocaleIds(language: 'zh', region: 'CN'), simCode: '', timezone: 'Asia/Shanghai') .writeToBuffer()), 'x-bili-exps-bin': '', }; static final unprintableRegExp = RegExp(r"[^\u4e00-\u9fa5,。;!?UP]"); 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( url, GeneratedMessage request, Function grpcParser, {Function? onSuccess}) async { final response = await Request().post(HttpString.appBaseUrl + url, data: compressProtobuf(request.writeToBuffer()), options: Options(headers: headers, responseType: ResponseType.bytes)); if (response.data is Map) { return {'status': false, 'msg': response.data['message']}; } if (response.headers.value('Grpc-Status') == '0') { try { Uint8List data = response.data; data = decompressProtobuf(data); final grpcResponse = grpcParser(data); return { 'status': true, 'data': onSuccess == null ? grpcResponse : onSuccess(grpcResponse), }; } catch (e) { return {'status': false, 'msg': e.toString()}; } } else { try { String msg = response.headers.value('Grpc-Status-Details-Bin') ?? ''; if (msg != '') { while (msg.length % 4 != 0) { msg += '='; } msg = utf8 .decode(base64Decode(msg), allowMalformed: true) .replaceAll(unprintableRegExp, ''); } return {'status': false, 'msg': msg}; } catch (e) { return {'status': false, 'msg': e.toString()}; } } } // static Future playerOnline({ // int aid = 0, // int cid = 0, // }) async { // return await _request( // GrpcUrl.playerOnline, // PlayerOnlineReq(aid: Int64(aid), cid: Int64(cid), playOpen: true), // PlayerOnlineReply.fromBuffer, // onSuccess: (response) => response.totalNumberText); // } // static Future popular(int idx) async { // return await _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}; // }); // } // static Future replyInfo({required int rpid}) async { // return await _request( // GrpcUrl.replyInfo, // ReplyInfoReq(rpid: Int64(rpid)), // ReplyInfoReply.fromBuffer, // onSuccess: (response) => response.reply, // ); // } // static Future dynSpace({ // required int uid, // required int page, // }) async { // return await _request( // GrpcUrl.dynSpace, // DynSpaceReq( // hostUid: Int64(uid), // localTime: 8, // page: Int64(page), // from: 'space', // ), // DynSpaceRsp.fromBuffer, // ); // } static Future mainList({ int type = 1, required int oid, required Mode mode, required String? offset, required Int64? cursorNext, }) async { return await _request( GrpcUrl.mainList, MainListReq( oid: Int64(oid), type: Int64(type), rpid: Int64(0), cursor: CursorReq( mode: mode, next: cursorNext, ), // pagination: FeedPagination(offset: offset ?? ''), ), MainListReply.fromBuffer, ); } static Future detailList({ int type = 1, required int oid, required int root, required int rpid, required Mode mode, required String? offset, }) async { return await _request( GrpcUrl.detailList, DetailListReq( oid: Int64(oid), type: Int64(type), root: Int64(root), rpid: Int64(rpid), scene: DetailListScene.REPLY, mode: mode, pagination: FeedPagination(offset: offset ?? ''), ), DetailListReply.fromBuffer, ); } static Future dialogList({ int type = 1, required int oid, required int root, required int dialog, required String? offset, }) async { return await _request( GrpcUrl.dialogList, DialogListReq( oid: Int64(oid), type: Int64(type), root: Int64(root), dialog: Int64(dialog), pagination: FeedPagination(offset: offset ?? ''), ), DialogListReply.fromBuffer, ); } static Future dynRed() async { return await _request( GrpcUrl.dynRed, DynRedReq(tabOffset: [TabOffset(tab: 1)]), DynRedReply.fromBuffer, onSuccess: (response) => response.dynRedItem.count.toInt(), ); } static Future dmSegMobile( {required int cid, required int segmentIndex, int type = 1}) async { return await _request( GrpcUrl.dmSegMobile, DmSegMobileReq( oid: Int64(cid), segmentIndex: Int64(segmentIndex), type: type, ), DmSegMobileReply.fromBuffer, ); } static Future sendMsg({ required int senderUid, required int receiverId, required String content, MsgType msgType = MsgType.EN_MSG_TYPE_TEXT, }) async { final devId = const Uuid().v4(); return await _request( GrpcUrl.sendMsg, ReqSendMsg( msg: Msg( senderUid: Int64(senderUid), receiverType: 1, receiverId: Int64(receiverId), msgType: msgType.value, content: content, timestamp: Int64(DateTime.now().millisecondsSinceEpoch ~/ 1000), msgStatus: 0, newFaceVersion: 1, ), devId: devId, ), RspSendMsg.fromBuffer, ); } static Future shareList({int size = 10}) async { return await _request( GrpcUrl.shareList, ReqShareList(size: size), RspShareList.fromBuffer, ); } }