Files
PiliPlus/lib/http/video.dart
bggRGjQaUbCoE a260b1640a remove boolext
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-30 22:54:46 +08:00

1152 lines
32 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'dart:convert';
import 'dart:developer';
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/models/common/account_type.dart';
import 'package:PiliPlus/models/home/rcmd/result.dart';
import 'package:PiliPlus/models/member/article.dart';
import 'package:PiliPlus/models/model_hot_video_item.dart';
import 'package:PiliPlus/models/model_rec_video_item.dart';
import 'package:PiliPlus/models/pgc/pgc_rank/pgc_rank_item_model.dart';
import 'package:PiliPlus/models/pgc_lcf.dart';
import 'package:PiliPlus/models/play_info/data.dart';
import 'package:PiliPlus/models/triple/pgc_triple.dart';
import 'package:PiliPlus/models/user/fav_folder.dart';
import 'package:PiliPlus/models/video/ai.dart';
import 'package:PiliPlus/models/video/note_list/data.dart';
import 'package:PiliPlus/models/video/play/url.dart';
import 'package:PiliPlus/models/video_detail/video_detail_response.dart';
import 'package:PiliPlus/models/video_relation/data.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/id_utils.dart';
import 'package:PiliPlus/utils/recommend_filter.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:PiliPlus/utils/wbi_sign.dart';
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
/// view层根据 status 判断渲染逻辑
class VideoHttp {
static bool enableRcmdDynamic =
GStorage.setting.get(SettingBoxKey.enableRcmdDynamic, defaultValue: true);
static RegExp zoneRegExp =
RegExp(GStorage.banWordForZone, caseSensitive: false);
// 首页推荐视频
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>[];
Set<int> blackMids = GStorage.blackMids;
for (var i in res.data['data']['item']) {
//过滤掉live与ad以及拉黑用户
if (i['goto'] == 'av' &&
(i['owner'] != null && !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 {
Map<String, String> data = {
'build': '2001100',
'c_locale': 'zh_CN',
'channel': 'yingyongbao',
'column': '4',
'device': 'pad',
'device_name': 'vivo',
'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.toString(),
'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: data,
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>[];
Set<int> blackMids = GStorage.blackMids;
for (var i in res.data['data']['items']) {
// 屏蔽推广和拉黑用户
if (i['card_goto'] != 'ad_av' &&
i['card_goto'] != 'ad_web_s' &&
i['ad_info'] == null &&
(!enableRcmdDynamic ? i['card_goto'] != 'picture' : true) &&
(i['args'] != null && !blackMids.contains(i['args']['up_id']))) {
if (zoneRegExp.pattern.isNotEmpty &&
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>[];
Set<int> blackMids = GStorage.blackMids;
for (var i in res.data['data']['list']) {
if (!blackMids.contains(i['owner']['mid']) &&
!RecommendFilter.filterTitle(i['title']) &&
!RecommendFilter.filterLikeRatio(
i['stat']['like'], i['stat']['view'])) {
if (zoneRegExp.pattern.isNotEmpty &&
i['tname'] != null &&
zoneRegExp.hasMatch(i['tname'])) {
continue;
}
list.add(HotVideoItemModel.fromJson(i));
}
}
return Success(list);
} else {
return Error(res.data['message']);
}
}
// 视频流
static Future videoUrl({
int? avid,
String? bvid,
required int cid,
int? qn,
dynamic epid,
dynamic seasonId,
bool? forcePgcApi,
}) async {
final params = await WbiSign.makSign({
if (avid != null) 'avid': avid,
if (bvid != null) 'bvid': bvid,
if (epid != null) 'ep_id': epid,
if (seasonId != null) '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 (!Accounts.get(AccountType.video).isLogin &&
GStorage.setting.get(SettingBoxKey.p1080, defaultValue: true))
'try_look': 1,
});
late final usePgcApi =
forcePgcApi == true || Accounts.get(AccountType.video).isLogin;
try {
var res = await Request().get(
epid != null && usePgcApi ? Api.bangumiVideoUrl : Api.videoUrl,
queryParameters: params,
);
if (res.data['code'] == 0) {
late PlayUrlModel data;
if (epid != null && usePgcApi) {
data = PlayUrlModel.fromJson(res.data['result']['video_info'])
..lastPlayTime = res.data['result']['play_view_business_info']
['user_status']['watch_progress']['current_watch_progress'];
} else {
data = PlayUrlModel.fromJson(res.data['data']);
}
return {
'status': true,
'data': data,
};
} else {
if (epid != null && !usePgcApi && forcePgcApi != true) {
return videoUrl(
avid: avid,
bvid: bvid,
cid: cid,
qn: qn,
epid: epid,
seasonId: seasonId,
forcePgcApi: true,
);
}
return {
'status': false,
'code': res.data['code'],
'msg': res.data['message'],
};
}
} catch (err) {
return {'status': false, 'msg': err.toString()};
}
}
// 视频信息 标题、简介
static Future videoIntro({required String bvid}) async {
var res =
await Request().get(Api.videoIntro, queryParameters: {'bvid': bvid});
VideoDetailResponse result = VideoDetailResponse.fromJson(res.data);
if (result.code == 0) {
return {
'status': true,
'data': result.data!,
};
} else {
// Map errMap = {
// -400: '请求错误',
// -403: '权限不足',
// -404: '视频资源失效',
// 62002: '稿件不可见',
// 62004: '稿件审核中',
// };
return {
'status': false,
'data': result.data,
'code': result.code,
'msg': result.message,
// 'msg': errMap[result.code] ?? '请求异常',
};
}
}
static Future seasonFav({
required bool isFav,
required dynamic seasonId,
}) async {
var res = await Request().post(
Api.seasonFav + (isFav ? 'unfav' : 'fav'),
data: {
'platform': 'web',
'season_id': seasonId,
'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 videoRelation({required dynamic bvid}) async {
var res = await Request().get(
Api.videoRelation,
queryParameters: {
'aid': IdUtils.bv2av(bvid),
'bvid': bvid,
},
);
if (res.data['code'] == 0) {
return {
'status': true,
'data': VideoRelation.fromJson(res.data['data']),
};
} else {
return {
'status': false,
'msg': 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']);
}
}
// 获取点赞/投币/收藏状态 bangumi
static Future bangumiLikeCoinFav({dynamic epId}) async {
var res = await Request().get(
Api.bangumiLikeCoinFav,
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']};
}
}
// 一键三连 bangumi
static Future triple({dynamic epId, required dynamic seasonId}) async {
var res = await Request().post(
Api.triple,
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': Request.headerUa(type: 'pc'),
},
),
);
if (res.data['code'] == 0) {
return {'status': true, 'data': PgcTriple.fromJson(res.data['data'])};
} else {
return {'status': false, 'msg': res.data['message']};
}
}
// 一键三连
static Future oneThree({required String bvid}) async {
var res = await Request().post(
Api.oneThree,
data: {
'aid': IdUtils.bv2av(bvid),
'eab_x': 2,
'ramval': 0,
'source': 'web_normal',
'ga': 1,
'csrf': Accounts.main.csrf,
},
options: Options(
contentType: Headers.formUrlEncodedContentType,
headers: {
'origin': 'https://www.bilibili.com',
'referer': 'https://www.bilibili.com/video/$bvid',
'user-agent': Request.headerUa(type: 'pc'),
},
),
);
if (res.data['code'] == 0) {
return {'status': true, 'data': 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,
if (reasonId != null) 'reason_id': reasonId,
if (feedbackId != null) '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,
if (reasonId != null) 'reason_id': reasonId,
if (feedbackId != null) '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 delFav({
List? ids,
String? delIds,
}) async {
var res = await Request().post(
Api.delFav,
data: {
'resources': ids?.join(','),
'media_id': delIds,
'platform': 'web',
'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']};
}
}
// (取消)收藏
static Future favVideo({
int? aid,
String? addIds,
String? delIds,
int? type,
}) async {
var res = await Request().post(
Api.favVideo,
data: {
'rid': aid,
'type': type ?? 2,
'add_media_ids': addIds ?? '',
'del_media_ids': delIds ?? '',
'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']};
}
}
static Future copyOrMoveFav({
required bool isCopy,
required bool isFav,
required dynamic srcMediaId,
required dynamic tarMediaId,
dynamic mid,
required List resources,
}) async {
var res = await Request().post(
isFav
? isCopy
? Api.copyFav
: Api.moveFav
: isCopy
? Api.copyToview
: Api.moveToview,
data: {
if (srcMediaId != null) 'src_media_id': srcMediaId,
'tar_media_id': tarMediaId,
if (mid != null) 'mid': mid,
'resources': resources.join(','),
'platform': 'web',
'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 allFavFolders(mid) async {
var res = await Request().get(
Api.favFolder,
queryParameters: {'up_mid': mid},
);
if (res.data['code'] == 0) {
FavFolderData data = FavFolderData.fromJson(res.data['data']);
return {'status': true, 'data': data};
} else {
return {'status': false, 'msg': res.data['message']};
}
}
// 查看视频被收藏在哪个文件夹
static Future videoInFolder({
dynamic mid,
dynamic rid,
dynamic type,
}) async {
var res = await Request().get(
Api.favFolder,
queryParameters: {
'up_mid': mid,
'rid': rid,
if (type != null) 'type': type,
},
);
if (res.data['code'] == 0) {
FavFolderData data = FavFolderData.fromJson(res.data['data']);
return {'status': true, 'data': data};
} 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 发送平台标识 非必要 1web端 2安卓客户端 3ios客户端 4wp客户端
static Future replyAdd({
required int type,
required int oid,
required String message,
int? root,
int? parent,
List? pictures,
bool? syncToDynamic,
}) async {
if (message == '') {
return {'status': false, 'msg': '请输入评论内容'};
}
Map<String, dynamic> data = {
'type': type,
'oid': oid,
if (root != null && root != 0) 'root': root,
if (parent != null && parent != 0) 'parent': parent,
'message': message,
if (pictures != null) 'pictures': jsonEncode(pictures),
if (syncToDynamic == true) 'sync_to_dynamic': 1,
'csrf': Accounts.main.csrf,
};
var res = await Request().post(
Api.replyAdd,
data: FormData.fromMap(data),
);
log(res.toString());
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, queryParameters: {
'type': type, //type.index
'oid': oid,
'rpid': rpid,
'csrf': Accounts.main.csrf,
});
log(res.toString());
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': Request.headerUa(type: 'pc'),
}),
'csrf': Accounts.main.csrf,
},
options: Options(
contentType: Headers.formUrlEncodedContentType,
headers: {
'origin': 'https://space.bilibili.com',
'referer': 'https://space.bilibili.com/$mid/dynamic',
'user-agent': Request.headerUa(type: 'pc'),
},
),
);
if (res.data['code'] == 0) {
return {'status': true};
} else {
return {'status': false, 'msg': res.data['message']};
}
}
static Future roomEntryAction({
roomId,
}) async {
await Request().post(
Api.roomEntryAction,
queryParameters: {
'csrf': Accounts.main.csrf,
},
data: {
'room_id': roomId,
'platform': 'pc',
},
);
}
static Future historyReport({
aid,
type,
}) async {
await Request().post(Api.historyReport, queryParameters: {
if (aid != null) 'aid': aid,
if (type != null) 'type': type,
'csrf': Accounts.main.csrf,
});
}
// 视频播放进度
static Future heartBeat({
bvid,
cid,
progress,
epid,
seasonId,
subType,
}) async {
await Request().post(Api.heartBeat, queryParameters: {
'bvid': bvid,
'cid': cid,
if (epid != null) 'epid': epid,
if (seasonId != null) 'sid': seasonId,
if (epid != null) 'type': 4,
if (subType != null) 'sub_type': subType,
'played_time': progress,
'csrf': Accounts.main.csrf,
});
}
static Future medialistHistory({
required int desc,
required dynamic oid,
required dynamic upperMid,
}) async {
await Request().post(Api.mediaListHistory, queryParameters: {
'desc': desc,
'oid': oid,
'upper_mid': upperMid,
'csrf': Accounts.main.csrf,
});
}
// 添加追番
static Future bangumiAdd({int? seasonId}) async {
var res = await Request().post(Api.bangumiAdd, queryParameters: {
'season_id': seasonId,
'csrf': Accounts.main.csrf,
});
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 bangumiDel({int? seasonId}) async {
var res = await Request().post(Api.bangumiDel, queryParameters: {
'season_id': seasonId,
'csrf': Accounts.main.csrf,
});
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 bangumiUpdate({
required List seasonId,
required dynamic status,
}) async {
var res = await Request().post(
Api.bangumiUpdate,
data: {
'season_id': seasonId.join(','),
'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);
if (res.data['code'] == 0 && res.data['data']['code'] == 0) {
return {
'status': true,
'data': AiConclusionModel.fromJson(res.data['data']),
};
} else {
return {'status': false, 'msg': res.data['message']};
}
}
static Future playInfo({String? aid, String? bvid, required int cid}) async {
assert(aid != null || bvid != null);
var res = await Request().get(
Api.playInfo,
queryParameters: {
if (aid != null) 'aid': aid,
if (bvid != null) '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 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'] is List) {
return await compute(processList, res.data['body'] as List);
}
return null;
}
// 视频排行
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>[];
Set<int> blackMids = GStorage.blackMids;
for (var i in res.data['data']['list']) {
if (!blackMids.contains(i['owner']['mid']) &&
!RecommendFilter.filterTitle(i['title']) &&
!RecommendFilter.filterLikeRatio(
i['stat']['like'], i['stat']['view'])) {
if (zoneRegExp.pattern.isNotEmpty &&
i['tname'] != null &&
zoneRegExp.hasMatch(i['tname'])) {
continue;
}
list.add(HotVideoItemModel.fromJson(i));
}
}
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<NoteListData>> 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,
if (uperMid != null) 'uper_mid': uperMid,
},
);
if (res.data['code'] == 0) {
return Success(NoteListData.fromJson(res.data['data']));
} else {
return Error(res.data['message']);
}
}
static Future<LoadingState<List<FavNoteModel>?>> noteList({
required int page,
}) async {
var res = await Request().get(
Api.noteList,
queryParameters: {
'pn': page,
'ps': 10,
'csrf': Accounts.main.csrf,
},
);
if (res.data['code'] == 0) {
List<FavNoteModel>? list = (res.data['data']?['list'] as List?)
?.map((e) => FavNoteModel.fromJson(e))
.toList();
return Success(list);
} else {
return Error(res.data['message']);
}
}
static Future<LoadingState<List<FavNoteModel>?>> userNoteList({
required int page,
}) async {
var res = await Request().get(
Api.userNoteList,
queryParameters: {
'pn': page,
'ps': 10,
'csrf': Accounts.main.csrf,
},
);
if (res.data['code'] == 0) {
List<FavNoteModel>? list = (res.data['data']?['list'] as List?)
?.map((e) => FavNoteModel.fromJson(e))
.toList();
return Success(list);
} else {
return Error(res.data['message']);
}
}
static Future<LoadingState> addNote({
required oid,
required String title,
required String summary,
}) async {
String noteId = '';
try {
final res = await Request().get(Api.archiveNote, queryParameters: {
'oid': oid,
'oid_type': 0,
'csrf': Accounts.main.csrf,
});
if (res.data['code'] == 0) {
if (res.data['data']?['noteIds'] != null) {
noteId = res.data['data']['noteIds'].first;
}
}
} catch (_) {}
final res = await Request().post(
Api.addNote,
data: {
'cont_len': summary.length,
'note_id': noteId,
'oid': oid,
'oid_type': 0,
'platform': 'web',
'title': title,
'summary': summary,
'content': jsonEncode([
{"insert": summary},
]),
'from': 'close',
'hash': DateTime.now().millisecondsSinceEpoch,
'tags': '',
'csrf': Accounts.main.csrf,
},
options: Options(
contentType: Headers.formUrlEncodedContentType,
),
);
if (res.data['code'] == 0) {
return Success(res.data['data']?['list']);
} else {
return Error(res.data['message']);
}
}
static Future delNote({
required bool isPublish,
required List noteIds,
}) async {
final res = await Request().post(
isPublish ? Api.delPublishNote : Api.delNote,
data: {
isPublish ? 'cvids' : 'note_ids': noteIds.join(','),
'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']};
}
}
}