mirror of
https://github.com/HChaZZY/PiliPlus.git
synced 2025-12-06 09:13:48 +08:00
1053 lines
29 KiB
Dart
1053 lines
29 KiB
Dart
import 'dart:convert';
|
||
|
||
import 'package:PiliPlus/common/constants.dart';
|
||
import 'package:PiliPlus/http/api.dart';
|
||
import 'package:PiliPlus/http/init.dart';
|
||
import 'package:PiliPlus/http/loading_state.dart';
|
||
import 'package:PiliPlus/http/login.dart';
|
||
import 'package:PiliPlus/http/ua_type.dart';
|
||
import 'package:PiliPlus/models/common/account_type.dart';
|
||
import 'package:PiliPlus/models/common/video/video_type.dart';
|
||
import 'package:PiliPlus/models/home/rcmd/result.dart';
|
||
import 'package:PiliPlus/models/model_hot_video_item.dart';
|
||
import 'package:PiliPlus/models/model_rec_video_item.dart';
|
||
import 'package:PiliPlus/models/pgc_lcf.dart';
|
||
import 'package:PiliPlus/models/video/play/url.dart';
|
||
import 'package:PiliPlus/models_new/pgc/pgc_rank/pgc_rank_item_model.dart';
|
||
import 'package:PiliPlus/models_new/popular/popular_precious/data.dart';
|
||
import 'package:PiliPlus/models_new/popular/popular_series_list/list.dart';
|
||
import 'package:PiliPlus/models_new/popular/popular_series_one/data.dart';
|
||
import 'package:PiliPlus/models_new/triple/pgc_triple.dart';
|
||
import 'package:PiliPlus/models_new/triple/ugc_triple.dart';
|
||
import 'package:PiliPlus/models_new/video/video_ai_conclusion/data.dart';
|
||
import 'package:PiliPlus/models_new/video/video_detail/data.dart';
|
||
import 'package:PiliPlus/models_new/video/video_detail/video_detail_response.dart';
|
||
import 'package:PiliPlus/models_new/video/video_note_list/data.dart';
|
||
import 'package:PiliPlus/models_new/video/video_play_info/data.dart';
|
||
import 'package:PiliPlus/models_new/video/video_relation/data.dart';
|
||
import 'package:PiliPlus/utils/accounts.dart';
|
||
import 'package:PiliPlus/utils/extension.dart';
|
||
import 'package:PiliPlus/utils/global_data.dart';
|
||
import 'package:PiliPlus/utils/id_utils.dart';
|
||
import 'package:PiliPlus/utils/recommend_filter.dart';
|
||
import 'package:PiliPlus/utils/storage_pref.dart';
|
||
import 'package:PiliPlus/utils/wbi_sign.dart';
|
||
import 'package:dio/dio.dart';
|
||
import 'package:flutter/foundation.dart';
|
||
|
||
/// view层根据 status 判断渲染逻辑
|
||
class VideoHttp {
|
||
static RegExp zoneRegExp = RegExp(Pref.banWordForZone, caseSensitive: false);
|
||
static bool enableFilter = zoneRegExp.pattern.isNotEmpty;
|
||
|
||
// 首页推荐视频
|
||
static Future<LoadingState> rcmdVideoList({
|
||
required int ps,
|
||
required int freshIdx,
|
||
}) async {
|
||
var res = await Request().get(
|
||
Api.recommendListWeb,
|
||
queryParameters: {
|
||
'version': 1,
|
||
'feed_version': 'V8',
|
||
'homepage_ver': 1,
|
||
'ps': ps,
|
||
'fresh_idx': freshIdx,
|
||
'brush': freshIdx,
|
||
'fresh_type': 4,
|
||
},
|
||
);
|
||
if (res.data['code'] == 0) {
|
||
List<RecVideoItemModel> list = <RecVideoItemModel>[];
|
||
for (var i in res.data['data']['item']) {
|
||
//过滤掉live与ad,以及拉黑用户
|
||
if (i['goto'] == 'av' &&
|
||
(i['owner'] != null &&
|
||
!GlobalData().blackMids.contains(i['owner']['mid']))) {
|
||
RecVideoItemModel videoItem = RecVideoItemModel.fromJson(i);
|
||
if (!RecommendFilter.filter(videoItem)) {
|
||
list.add(videoItem);
|
||
}
|
||
}
|
||
}
|
||
return Success(list);
|
||
} else {
|
||
return Error(res.data['message']);
|
||
}
|
||
}
|
||
|
||
// 添加额外的loginState变量模拟未登录状态
|
||
static Future<LoadingState> rcmdVideoListApp({required int freshIdx}) async {
|
||
final params = {
|
||
'build': 2001100,
|
||
'c_locale': 'zh_CN',
|
||
'channel': 'master',
|
||
'column': 4,
|
||
'device': 'pad',
|
||
'device_name': 'android',
|
||
'device_type': 0,
|
||
'disable_rcmd': 0,
|
||
'flush': 5,
|
||
'fnval': 976,
|
||
'fnver': 0,
|
||
'force_host': 2, //使用https
|
||
'fourk': 1,
|
||
'guidance': 0,
|
||
'https_url_req': 0,
|
||
'idx': freshIdx,
|
||
'mobi_app': 'android_hd',
|
||
'network': 'wifi',
|
||
'platform': 'android',
|
||
'player_net': 1,
|
||
'pull': freshIdx == 0 ? 'true' : 'false',
|
||
'qn': 32,
|
||
'recsys_mode': 0,
|
||
's_locale': 'zh_CN',
|
||
'splash_id': '',
|
||
'statistics': Constants.statistics,
|
||
'voice_balance': 0,
|
||
};
|
||
var res = await Request().get(
|
||
Api.recommendListApp,
|
||
queryParameters: params,
|
||
options: Options(
|
||
headers: {
|
||
'buvid': LoginHttp.buvid,
|
||
'fp_local':
|
||
'1111111111111111111111111111111111111111111111111111111111111111',
|
||
'fp_remote':
|
||
'1111111111111111111111111111111111111111111111111111111111111111',
|
||
'session_id': '11111111',
|
||
'env': 'prod',
|
||
'app-key': 'android_hd',
|
||
'User-Agent': Constants.userAgent,
|
||
'x-bili-trace-id': Constants.traceId,
|
||
'x-bili-aurora-eid': '',
|
||
'x-bili-aurora-zone': '',
|
||
'bili-http-engine': 'cronet',
|
||
},
|
||
),
|
||
);
|
||
if (res.data['code'] == 0) {
|
||
List<RecVideoItemAppModel> list = <RecVideoItemAppModel>[];
|
||
for (var i in res.data['data']['items']) {
|
||
// 屏蔽推广和拉黑用户
|
||
if (i['card_goto'] != 'ad_av' &&
|
||
i['card_goto'] != 'ad_web_s' &&
|
||
i['ad_info'] == null &&
|
||
(i['args'] != null &&
|
||
!GlobalData().blackMids.contains(i['args']['up_id']))) {
|
||
if (enableFilter &&
|
||
i['args']?['tname'] != null &&
|
||
zoneRegExp.hasMatch(i['args']['tname'])) {
|
||
continue;
|
||
}
|
||
RecVideoItemAppModel videoItem = RecVideoItemAppModel.fromJson(i);
|
||
if (!RecommendFilter.filter(videoItem)) {
|
||
list.add(videoItem);
|
||
}
|
||
}
|
||
}
|
||
return Success(list);
|
||
} else {
|
||
return Error(res.data['message']);
|
||
}
|
||
}
|
||
|
||
// 最热视频
|
||
static Future<LoadingState<List<HotVideoItemModel>>> hotVideoList({
|
||
required int pn,
|
||
required int ps,
|
||
}) async {
|
||
var res = await Request().get(
|
||
Api.hotList,
|
||
queryParameters: {'pn': pn, 'ps': ps},
|
||
);
|
||
if (res.data['code'] == 0) {
|
||
List<HotVideoItemModel> list = <HotVideoItemModel>[];
|
||
for (var i in res.data['data']['list']) {
|
||
if (!GlobalData().blackMids.contains(i['owner']['mid']) &&
|
||
!RecommendFilter.filterTitle(i['title']) &&
|
||
!RecommendFilter.filterLikeRatio(
|
||
i['stat']['like'],
|
||
i['stat']['view'],
|
||
)) {
|
||
if (enableFilter &&
|
||
i['tname'] != null &&
|
||
zoneRegExp.hasMatch(i['tname'])) {
|
||
continue;
|
||
}
|
||
list.add(HotVideoItemModel.fromJson(i));
|
||
}
|
||
}
|
||
return Success(list);
|
||
} else {
|
||
return Error(res.data['message']);
|
||
}
|
||
}
|
||
|
||
// 视频流
|
||
static Future<LoadingState<PlayUrlModel>> videoUrl({
|
||
int? avid,
|
||
String? bvid,
|
||
required int cid,
|
||
int? qn,
|
||
dynamic epid,
|
||
dynamic seasonId,
|
||
required bool tryLook,
|
||
required VideoType videoType,
|
||
String? language,
|
||
}) async {
|
||
final params = await WbiSign.makSign({
|
||
'avid': ?avid,
|
||
'bvid': ?bvid,
|
||
'ep_id': ?epid,
|
||
'season_id': ?seasonId,
|
||
'cid': cid,
|
||
'qn': qn ?? 80,
|
||
// 获取所有格式的视频
|
||
'fnval': 4048,
|
||
'fourk': 1,
|
||
'fnver': 0,
|
||
'voice_balance': 1,
|
||
'gaia_source': 'pre-load',
|
||
'isGaiaAvoided': true,
|
||
'web_location': 1315873,
|
||
// 免登录查看1080p
|
||
if (tryLook) 'try_look': 1,
|
||
'cur_language': ?language,
|
||
});
|
||
|
||
try {
|
||
var res = await Request().get(
|
||
videoType.api,
|
||
queryParameters: params,
|
||
);
|
||
|
||
if (res.data['code'] == 0) {
|
||
late PlayUrlModel data;
|
||
switch (videoType) {
|
||
case VideoType.ugc:
|
||
data = PlayUrlModel.fromJson(res.data['data']);
|
||
|
||
case VideoType.pugv:
|
||
var result = res.data['data'];
|
||
data = PlayUrlModel.fromJson(result)
|
||
..lastPlayTime =
|
||
result?['play_view_business_info']?['user_status']?['watch_progress']?['current_watch_progress'];
|
||
|
||
case VideoType.pgc:
|
||
var result = res.data['result'];
|
||
data = PlayUrlModel.fromJson(result['video_info'])
|
||
..lastPlayTime =
|
||
result?['play_view_business_info']?['user_status']?['watch_progress']?['current_watch_progress'];
|
||
}
|
||
return Success(data);
|
||
} else if (epid != null && videoType == VideoType.ugc) {
|
||
return videoUrl(
|
||
avid: avid,
|
||
bvid: bvid,
|
||
cid: cid,
|
||
qn: qn,
|
||
epid: epid,
|
||
seasonId: seasonId,
|
||
tryLook: tryLook,
|
||
videoType: VideoType.pgc,
|
||
);
|
||
}
|
||
return Error(_parseVideoErr(res.data['code'], res.data['message']));
|
||
} catch (err) {
|
||
return Error(err.toString());
|
||
}
|
||
}
|
||
|
||
static String _parseVideoErr(int? code, String? msg) {
|
||
return switch (code) {
|
||
-404 => '视频不存在或已被删除',
|
||
87008 => '当前视频可能是专属视频,可能需包月充电观看($msg})',
|
||
_ => '错误($code): $msg',
|
||
};
|
||
}
|
||
|
||
// 视频信息 标题、简介
|
||
static Future<LoadingState<VideoDetailData>> videoIntro({
|
||
required String bvid,
|
||
}) async {
|
||
var res = await Request().get(
|
||
Api.videoIntro,
|
||
queryParameters: {'bvid': bvid},
|
||
);
|
||
VideoDetailResponse data = VideoDetailResponse.fromJson(res.data);
|
||
if (data.code == 0) {
|
||
return Success(data.data!);
|
||
} else {
|
||
return Error(data.message);
|
||
}
|
||
}
|
||
|
||
static Future<LoadingState<VideoRelation>> videoRelation({
|
||
required String bvid,
|
||
}) async {
|
||
var res = await Request().get(
|
||
Api.videoRelation,
|
||
queryParameters: {
|
||
'aid': IdUtils.bv2av(bvid),
|
||
'bvid': bvid,
|
||
},
|
||
);
|
||
if (res.data['code'] == 0) {
|
||
return Success(VideoRelation.fromJson(res.data['data']));
|
||
} else {
|
||
return Error(res.data['message']);
|
||
}
|
||
}
|
||
|
||
// 相关视频
|
||
static Future<LoadingState<List<HotVideoItemModel>?>> relatedVideoList({
|
||
required String bvid,
|
||
}) async {
|
||
var res = await Request().get(
|
||
Api.relatedList,
|
||
queryParameters: {'bvid': bvid},
|
||
);
|
||
if (res.data['code'] == 0) {
|
||
final items = (res.data['data'] as List?)?.map(
|
||
(i) => HotVideoItemModel.fromJson(i),
|
||
);
|
||
final list = RecommendFilter.applyFilterToRelatedVideos
|
||
? items?.where((i) => !RecommendFilter.filterAll(i)).toList()
|
||
: items?.toList();
|
||
return Success(list);
|
||
} else {
|
||
return Error(res.data['message']);
|
||
}
|
||
}
|
||
|
||
// 获取点赞/投币/收藏状态 pgc
|
||
static Future pgcLikeCoinFav({dynamic epId}) async {
|
||
var res = await Request().get(
|
||
Api.pgcLikeCoinFav,
|
||
queryParameters: {'ep_id': epId},
|
||
);
|
||
if (res.data['code'] == 0) {
|
||
return {'status': true, 'data': PgcLCF.fromJson(res.data['data'])};
|
||
} else {
|
||
return {'status': false, 'msg': res.data['message']};
|
||
}
|
||
}
|
||
|
||
// 投币
|
||
static Future coinVideo({
|
||
required String bvid,
|
||
required int multiply,
|
||
int selectLike = 0,
|
||
}) async {
|
||
var res = await Request().post(
|
||
Api.coinVideo,
|
||
data: {
|
||
'aid': IdUtils.bv2av(bvid).toString(),
|
||
// 'bvid': bvid,
|
||
'multiply': multiply.toString(),
|
||
'select_like': selectLike.toString(),
|
||
// 'csrf': Accounts.main.csrf,
|
||
},
|
||
options: Options(contentType: Headers.formUrlEncodedContentType),
|
||
);
|
||
if (res.data['code'] == 0) {
|
||
return {'status': true, 'data': res.data['data']};
|
||
} else {
|
||
return {'status': false, 'msg': res.data['message']};
|
||
}
|
||
}
|
||
|
||
// 一键三连 pgc
|
||
static Future pgcTriple({dynamic epId, required dynamic seasonId}) async {
|
||
var res = await Request().post(
|
||
Api.pgcTriple,
|
||
data: {
|
||
'ep_id': epId,
|
||
'csrf': Accounts.main.csrf,
|
||
},
|
||
options: Options(
|
||
contentType: Headers.formUrlEncodedContentType,
|
||
headers: {
|
||
'origin': 'https://www.bilibili.com',
|
||
'referer': 'https://www.bilibili.com/bangumi/play/ss$seasonId',
|
||
'user-agent': UaType.pc.ua,
|
||
},
|
||
),
|
||
);
|
||
if (res.data['code'] == 0) {
|
||
return {'status': true, 'data': PgcTriple.fromJson(res.data['data'])};
|
||
} else {
|
||
return {'status': false, 'msg': res.data['message']};
|
||
}
|
||
}
|
||
|
||
// 一键三连
|
||
static Future ugcTriple({required String bvid}) async {
|
||
var res = await Request().post(
|
||
Api.ugcTriple,
|
||
data: {
|
||
'aid': IdUtils.bv2av(bvid),
|
||
'eab_x': 2,
|
||
'ramval': 0,
|
||
'source': 'web_normal',
|
||
'ga': 1,
|
||
'csrf': Accounts.main.csrf,
|
||
'spmid': '333.788.0.0',
|
||
'statistics': '{"appId":100,"platform":5}',
|
||
},
|
||
options: Options(
|
||
contentType: Headers.formUrlEncodedContentType,
|
||
headers: {
|
||
'origin': 'https://www.bilibili.com',
|
||
'referer': 'https://www.bilibili.com/video/$bvid',
|
||
'user-agent': UaType.pc.ua,
|
||
},
|
||
),
|
||
);
|
||
if (res.data['code'] == 0) {
|
||
return {'status': true, 'data': UgcTriple.fromJson(res.data['data'])};
|
||
} else {
|
||
return {'status': false, 'msg': res.data['message']};
|
||
}
|
||
}
|
||
|
||
// (取消)点赞
|
||
static Future likeVideo({required String bvid, required bool type}) async {
|
||
var res = await Request().post(
|
||
Api.likeVideo,
|
||
data: {
|
||
'aid': IdUtils.bv2av(bvid).toString(),
|
||
'like': type ? '0' : '1',
|
||
},
|
||
options: Options(contentType: Headers.formUrlEncodedContentType),
|
||
);
|
||
if (res.data['code'] == 0) {
|
||
return {'status': true, 'data': res.data['data']};
|
||
} else {
|
||
return {'status': false, 'msg': res.data['message']};
|
||
}
|
||
}
|
||
|
||
// (取消)点踩
|
||
static Future dislikeVideo({required String bvid, required bool type}) async {
|
||
if (Accounts.main.accessKey.isNullOrEmpty) {
|
||
return {'status': false, 'msg': "请退出账号后重新登录"};
|
||
}
|
||
var res = await Request().post(
|
||
Api.dislikeVideo,
|
||
data: {
|
||
'aid': IdUtils.bv2av(bvid).toString(),
|
||
'dislike': type ? '0' : '1',
|
||
},
|
||
options: Options(contentType: Headers.formUrlEncodedContentType),
|
||
);
|
||
if (res.data is! String && res.data['code'] == 0) {
|
||
return {'status': true};
|
||
} else {
|
||
return {
|
||
'status': false,
|
||
'msg': res.data is String ? res.data : res.data['message'],
|
||
};
|
||
}
|
||
}
|
||
|
||
// 推送不感兴趣反馈
|
||
static Future feedDislike({
|
||
required String goto,
|
||
required int id,
|
||
int? reasonId,
|
||
int? feedbackId,
|
||
}) async {
|
||
if (Accounts.get(AccountType.recommend).accessKey.isNullOrEmpty) {
|
||
return {'status': false, 'msg': "请退出账号后重新登录"};
|
||
}
|
||
assert((reasonId != null) ^ (feedbackId != null));
|
||
var res = await Request().get(
|
||
Api.feedDislike,
|
||
queryParameters: {
|
||
'goto': goto,
|
||
'id': id,
|
||
'reason_id': ?reasonId,
|
||
'feedback_id': ?feedbackId,
|
||
'build': 1,
|
||
'mobi_app': 'android',
|
||
},
|
||
);
|
||
if (res.data['code'] == 0) {
|
||
return {'status': true};
|
||
} else {
|
||
return {'status': false, 'msg': res.data['message']};
|
||
}
|
||
}
|
||
|
||
// 推送不感兴趣取消
|
||
static Future feedDislikeCancel({
|
||
required String goto,
|
||
required int id,
|
||
int? reasonId,
|
||
int? feedbackId,
|
||
}) async {
|
||
if (Accounts.get(AccountType.recommend).accessKey.isNullOrEmpty) {
|
||
return {'status': false, 'msg': "请退出账号后重新登录"};
|
||
}
|
||
var res = await Request().get(
|
||
Api.feedDislikeCancel,
|
||
queryParameters: {
|
||
'goto': goto,
|
||
'id': id,
|
||
'reason_id': ?reasonId,
|
||
'feedback_id': ?feedbackId,
|
||
'build': 1,
|
||
'mobi_app': 'android',
|
||
},
|
||
);
|
||
if (res.data['code'] == 0) {
|
||
return {'status': true};
|
||
} else {
|
||
return {'status': false, 'msg': res.data['message']};
|
||
}
|
||
}
|
||
|
||
// 发表评论 replyAdd
|
||
|
||
// type num 评论区类型代码 必要 类型代码见表
|
||
// oid num 目标评论区id 必要
|
||
// root num 根评论rpid 非必要 二级评论以上使用
|
||
// parent num 父评论rpid 非必要 二级评论同根评论id 大于二级评论为要回复的评论id
|
||
// message str 发送评论内容 必要 最大1000字符
|
||
// plat num 发送平台标识 非必要 1:web端 2:安卓客户端 3:ios客户端 4:wp客户端
|
||
static Future replyAdd({
|
||
required int type,
|
||
required int oid,
|
||
required String message,
|
||
int? root,
|
||
int? parent,
|
||
List? pictures,
|
||
bool syncToDynamic = false,
|
||
Map<String, int>? atNameToMid,
|
||
}) async {
|
||
if (message == '') {
|
||
return {'status': false, 'msg': '请输入评论内容'};
|
||
}
|
||
final data = {
|
||
'type': type,
|
||
'oid': oid,
|
||
if (root != null && root != 0) 'root': root,
|
||
if (parent != null && parent != 0) 'parent': parent,
|
||
'message': message,
|
||
if (atNameToMid?.isNotEmpty == true)
|
||
'at_name_to_mid': jsonEncode(atNameToMid), // {"name":uid}
|
||
if (pictures != null) 'pictures': jsonEncode(pictures),
|
||
if (syncToDynamic) 'sync_to_dynamic': 1,
|
||
'csrf': Accounts.main.csrf,
|
||
};
|
||
var res = await Request().post(
|
||
Api.replyAdd,
|
||
data: data,
|
||
options: Options(contentType: Headers.formUrlEncodedContentType),
|
||
);
|
||
if (res.data['code'] == 0) {
|
||
return {'status': true, 'data': res.data['data']};
|
||
} else {
|
||
return {'status': false, 'msg': res.data['message']};
|
||
}
|
||
}
|
||
|
||
static Future replyDel({
|
||
required int type, //replyType
|
||
required int oid,
|
||
required int rpid,
|
||
}) async {
|
||
var res = await Request().post(
|
||
Api.replyDel,
|
||
data: {
|
||
'type': type, //type.index
|
||
'oid': oid,
|
||
'rpid': rpid,
|
||
'csrf': Accounts.main.csrf,
|
||
},
|
||
options: Options(contentType: Headers.formUrlEncodedContentType),
|
||
);
|
||
if (res.data['code'] == 0) {
|
||
return {'status': true};
|
||
} else {
|
||
return {'status': false, 'msg': res.data['message']};
|
||
}
|
||
}
|
||
|
||
// 操作用户关系
|
||
static Future relationMod({
|
||
required int mid,
|
||
required int act,
|
||
required int reSrc,
|
||
}) async {
|
||
var res = await Request().post(
|
||
Api.relationMod,
|
||
queryParameters: {
|
||
'statistics': '{"appId":100,"platform":5}',
|
||
'x-bili-device-req-json':
|
||
'{"platform":"web","device":"pc","spmid":"333.1387"}',
|
||
},
|
||
data: {
|
||
'fid': mid,
|
||
'act': act,
|
||
're_src': reSrc,
|
||
'gaia_source': 'web_main',
|
||
'spmid': '333.1387',
|
||
'extend_content': jsonEncode({
|
||
"entity": "user",
|
||
"entity_id": mid,
|
||
'fp': UaType.pc.ua,
|
||
}),
|
||
'csrf': Accounts.main.csrf,
|
||
},
|
||
options: Options(
|
||
contentType: Headers.formUrlEncodedContentType,
|
||
headers: {
|
||
'origin': 'https://space.bilibili.com',
|
||
'referer': 'https://space.bilibili.com/$mid/dynamic',
|
||
'user-agent': UaType.pc.ua,
|
||
},
|
||
),
|
||
);
|
||
if (res.data['code'] == 0) {
|
||
if (act == 5) {
|
||
// block
|
||
Pref.setBlackMid(mid);
|
||
} else if (act == 6) {
|
||
// unblock
|
||
Pref.removeBlackMid(mid);
|
||
}
|
||
return {'status': true};
|
||
} else {
|
||
return {'status': false, 'msg': res.data['message']};
|
||
}
|
||
}
|
||
|
||
static Future roomEntryAction({
|
||
roomId,
|
||
}) async {
|
||
await Request().post(
|
||
Api.roomEntryAction,
|
||
queryParameters: {
|
||
'csrf': Accounts.heartbeat.csrf,
|
||
},
|
||
data: {
|
||
'room_id': roomId,
|
||
'platform': 'pc',
|
||
},
|
||
);
|
||
}
|
||
|
||
static Future historyReport({
|
||
aid,
|
||
type,
|
||
}) async {
|
||
await Request().post(
|
||
Api.historyReport,
|
||
data: {
|
||
'aid': ?aid,
|
||
'type': ?type,
|
||
'csrf': Accounts.heartbeat.csrf,
|
||
},
|
||
options: Options(contentType: Headers.formUrlEncodedContentType),
|
||
);
|
||
}
|
||
|
||
// 视频播放进度
|
||
static Future heartBeat({
|
||
aid,
|
||
bvid,
|
||
cid,
|
||
progress,
|
||
epid,
|
||
seasonId,
|
||
subType,
|
||
required VideoType videoType,
|
||
}) async {
|
||
final isPugv = videoType == VideoType.pugv;
|
||
await Request().post(
|
||
Api.heartBeat,
|
||
data: {
|
||
if (isPugv) 'aid': ?aid else 'bvid': ?bvid,
|
||
'cid': cid,
|
||
'epid': ?epid,
|
||
'sid': ?seasonId,
|
||
'type': videoType.type,
|
||
'sub_type': ?subType,
|
||
'played_time': progress,
|
||
'csrf': Accounts.heartbeat.csrf,
|
||
},
|
||
options: Options(contentType: Headers.formUrlEncodedContentType),
|
||
);
|
||
}
|
||
|
||
static Future medialistHistory({
|
||
required int desc,
|
||
required dynamic oid,
|
||
required dynamic upperMid,
|
||
}) async {
|
||
await Request().post(
|
||
Api.mediaListHistory,
|
||
data: {
|
||
'desc': desc,
|
||
'oid': oid,
|
||
'upper_mid': upperMid,
|
||
'csrf': Accounts.heartbeat.csrf,
|
||
},
|
||
options: Options(contentType: Headers.formUrlEncodedContentType),
|
||
);
|
||
}
|
||
|
||
// 添加追番
|
||
static Future pgcAdd({int? seasonId}) async {
|
||
var res = await Request().post(
|
||
Api.pgcAdd,
|
||
data: {
|
||
'season_id': seasonId,
|
||
'csrf': Accounts.main.csrf,
|
||
},
|
||
options: Options(contentType: Headers.formUrlEncodedContentType),
|
||
);
|
||
if (res.data['code'] == 0) {
|
||
return {
|
||
'status': true,
|
||
'msg': res.data['result'] == null
|
||
? 'failed'
|
||
: res.data['result']['toast'],
|
||
};
|
||
} else {
|
||
return {
|
||
'status': false,
|
||
'msg': res.data['result'] == null
|
||
? 'failed'
|
||
: res.data['result']['toast'],
|
||
};
|
||
}
|
||
}
|
||
|
||
// 取消追番
|
||
static Future pgcDel({int? seasonId}) async {
|
||
var res = await Request().post(
|
||
Api.pgcDel,
|
||
data: {
|
||
'season_id': seasonId,
|
||
'csrf': Accounts.main.csrf,
|
||
},
|
||
options: Options(contentType: Headers.formUrlEncodedContentType),
|
||
);
|
||
if (res.data['code'] == 0) {
|
||
return {
|
||
'status': true,
|
||
'msg': res.data['result'] == null
|
||
? 'failed'
|
||
: res.data['result']['toast'],
|
||
};
|
||
} else {
|
||
return {
|
||
'status': false,
|
||
'msg': res.data['result'] == null
|
||
? 'failed'
|
||
: res.data['result']['toast'],
|
||
};
|
||
}
|
||
}
|
||
|
||
static Future pgcUpdate({
|
||
required String seasonId,
|
||
required int status,
|
||
}) async {
|
||
var res = await Request().post(
|
||
Api.pgcUpdate,
|
||
data: {
|
||
'season_id': seasonId,
|
||
'status': status,
|
||
'csrf': Accounts.main.csrf,
|
||
},
|
||
options: Options(
|
||
contentType: Headers.formUrlEncodedContentType,
|
||
),
|
||
);
|
||
return {
|
||
'status': res.data['code'] == 0,
|
||
'msg': res.data['result'] == null
|
||
? 'failed'
|
||
: res.data['result']['toast'],
|
||
};
|
||
}
|
||
|
||
// 查看视频同时在看人数
|
||
static Future onlineTotal({int? aid, String? bvid, int? cid}) async {
|
||
var res = await Request().get(
|
||
Api.onlineTotal,
|
||
queryParameters: {
|
||
'aid': aid,
|
||
'bvid': bvid,
|
||
'cid': cid,
|
||
},
|
||
);
|
||
if (res.data['code'] == 0) {
|
||
return {'status': true, 'data': res.data['data']['total']};
|
||
} else {
|
||
return {'status': false, 'data': null, 'msg': res.data['message']};
|
||
}
|
||
}
|
||
|
||
static Future aiConclusion({
|
||
String? bvid,
|
||
int? cid,
|
||
int? upMid,
|
||
}) async {
|
||
final params = await WbiSign.makSign({
|
||
'bvid': bvid,
|
||
'cid': cid,
|
||
'up_mid': upMid,
|
||
});
|
||
var res = await Request().get(Api.aiConclusion, queryParameters: params);
|
||
final code = res.data['code'];
|
||
final dataCode = res.data['data']?['code'];
|
||
if (code == 0 && dataCode == 0) {
|
||
return {
|
||
'status': true,
|
||
'data': AiConclusionData.fromJson(res.data['data']),
|
||
};
|
||
} else {
|
||
final handling = code == 0 && dataCode == 1;
|
||
return {
|
||
'status': false,
|
||
'msg': res.data['message'],
|
||
'handling': handling,
|
||
};
|
||
}
|
||
}
|
||
|
||
static Future playInfo({String? aid, String? bvid, required int cid}) async {
|
||
assert(aid != null || bvid != null);
|
||
var res = await Request().get(
|
||
Api.playInfo,
|
||
queryParameters: await WbiSign.makSign({
|
||
'aid': ?aid,
|
||
'bvid': ?bvid,
|
||
'cid': cid,
|
||
}),
|
||
);
|
||
if (res.data['code'] == 0) {
|
||
return {
|
||
'status': true,
|
||
'data': PlayInfoData.fromJson(res.data['data']),
|
||
};
|
||
} else {
|
||
return {'status': false, 'msg': res.data['message']};
|
||
}
|
||
}
|
||
|
||
static Future<String?> vttSubtitles(String subtitleUrl) async {
|
||
String subtitleTimecode(num seconds) {
|
||
int h = seconds ~/ 3600;
|
||
seconds %= 3600;
|
||
int m = seconds ~/ 60;
|
||
seconds %= 60;
|
||
String sms = seconds.toStringAsFixed(3).padLeft(6, '0');
|
||
return h == 0
|
||
? "${m.toString().padLeft(2, '0')}:$sms"
|
||
: "${h.toString().padLeft(2, '0')}:${m.toString().padLeft(2, '0')}:$sms";
|
||
}
|
||
|
||
String processList(List list) {
|
||
final sb = StringBuffer('WEBVTT\n\n')
|
||
..writeAll(
|
||
list.map(
|
||
(item) =>
|
||
'${item?['sid'] ?? 0}\n${subtitleTimecode(item['from'])} --> ${subtitleTimecode(item['to'])}\n${item['content'].trim()}',
|
||
),
|
||
'\n\n',
|
||
);
|
||
return sb.toString();
|
||
}
|
||
|
||
var res = await Request().get("https:$subtitleUrl");
|
||
|
||
if (res.data?['body'] case List list) {
|
||
return compute<List, String>(processList, list);
|
||
}
|
||
return null;
|
||
}
|
||
|
||
static bool _canAddRank(Map i) {
|
||
if (!GlobalData().blackMids.contains(i['owner']['mid']) &&
|
||
!RecommendFilter.filterTitle(i['title']) &&
|
||
!RecommendFilter.filterLikeRatio(
|
||
i['stat']['like'],
|
||
i['stat']['view'],
|
||
)) {
|
||
if (enableFilter &&
|
||
i['tname'] != null &&
|
||
zoneRegExp.hasMatch(i['tname'])) {
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
// 视频排行
|
||
static Future<LoadingState<List<HotVideoItemModel>>> getRankVideoList(
|
||
int rid,
|
||
) async {
|
||
var res = await Request().get(
|
||
Api.getRankApi,
|
||
queryParameters: await WbiSign.makSign({
|
||
'rid': rid,
|
||
'type': 'all',
|
||
}),
|
||
);
|
||
if (res.data['code'] == 0) {
|
||
List<HotVideoItemModel> list = <HotVideoItemModel>[];
|
||
for (var i in res.data['data']['list']) {
|
||
if (_canAddRank(i)) {
|
||
list.add(HotVideoItemModel.fromJson(i));
|
||
final List? others = i['others'];
|
||
if (others != null && others.isNotEmpty) {
|
||
for (var j in others) {
|
||
if (_canAddRank(j)) {
|
||
list.add(HotVideoItemModel.fromJson(j));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return Success(list);
|
||
} else {
|
||
return Error(res.data['message']);
|
||
}
|
||
}
|
||
|
||
// pgc 排行
|
||
static Future<LoadingState> pgcRankList({
|
||
int day = 3,
|
||
required int seasonType,
|
||
}) async {
|
||
var res = await Request().get(
|
||
Api.pgcRank,
|
||
queryParameters: await WbiSign.makSign({
|
||
'day': day,
|
||
'season_type': seasonType,
|
||
}),
|
||
);
|
||
if (res.data['code'] == 0) {
|
||
return Success(
|
||
(res.data['result']?['list'] as List?)
|
||
?.map((e) => PgcRankItemModel.fromJson(e))
|
||
.toList(),
|
||
);
|
||
} else {
|
||
return Error(res.data['message']);
|
||
}
|
||
}
|
||
|
||
// pgc season 排行
|
||
static Future<LoadingState> pgcSeasonRankList({
|
||
int day = 3,
|
||
required int seasonType,
|
||
}) async {
|
||
var res = await Request().get(
|
||
Api.pgcSeasonRank,
|
||
queryParameters: await WbiSign.makSign({
|
||
'day': day,
|
||
'season_type': seasonType,
|
||
}),
|
||
);
|
||
if (res.data['code'] == 0) {
|
||
return Success(
|
||
(res.data['data']?['list'] as List?)
|
||
?.map((e) => PgcRankItemModel.fromJson(e))
|
||
.toList(),
|
||
);
|
||
} else {
|
||
return Error(res.data['message']);
|
||
}
|
||
}
|
||
|
||
static Future<LoadingState<VideoNoteData>> getVideoNoteList({
|
||
dynamic oid,
|
||
dynamic uperMid,
|
||
required int page,
|
||
}) async {
|
||
var res = await Request().get(
|
||
Api.archiveNoteList,
|
||
queryParameters: {
|
||
'csrf': Accounts.main.csrf,
|
||
'oid': oid,
|
||
'oid_type': 0,
|
||
'pn': page,
|
||
'ps': 10,
|
||
'uper_mid': ?uperMid,
|
||
},
|
||
);
|
||
if (res.data['code'] == 0) {
|
||
return Success(VideoNoteData.fromJson(res.data['data']));
|
||
} else {
|
||
return Error(res.data['message']);
|
||
}
|
||
}
|
||
|
||
static Future<LoadingState<List<PopularSeriesListItem>?>>
|
||
popularSeriesList() async {
|
||
var res = await Request().get(
|
||
Api.popularSeriesList,
|
||
queryParameters: await WbiSign.makSign({
|
||
'web_location': 333.934,
|
||
}),
|
||
);
|
||
if (res.data['code'] == 0) {
|
||
return Success(
|
||
(res.data['data']?['list'] as List<dynamic>?)
|
||
?.map(
|
||
(e) => PopularSeriesListItem.fromJson(e as Map<String, dynamic>),
|
||
)
|
||
.toList(),
|
||
);
|
||
} else {
|
||
return Error(res.data['message']);
|
||
}
|
||
}
|
||
|
||
static Future<LoadingState<PopularSeriesOneData>> popularSeriesOne({
|
||
required int number,
|
||
}) async {
|
||
var res = await Request().get(
|
||
Api.popularSeriesOne,
|
||
queryParameters: await WbiSign.makSign({
|
||
'number': number,
|
||
'web_location': 333.934,
|
||
}),
|
||
);
|
||
if (res.data['code'] == 0) {
|
||
return Success(PopularSeriesOneData.fromJson(res.data['data']));
|
||
} else {
|
||
return Error(res.data['message']);
|
||
}
|
||
}
|
||
|
||
static Future<LoadingState<PopularPreciousData>> popularPrecious({
|
||
required int page,
|
||
}) async {
|
||
var res = await Request().get(
|
||
Api.popularPrecious,
|
||
queryParameters: await WbiSign.makSign({
|
||
'page_size': 100,
|
||
'page': page,
|
||
'web_location': 333.934,
|
||
}),
|
||
);
|
||
if (res.data['code'] == 0) {
|
||
return Success(PopularPreciousData.fromJson(res.data['data']));
|
||
} else {
|
||
return Error(res.data['message']);
|
||
}
|
||
}
|
||
}
|