add quark
This commit is contained in:
parent
9f39be7bb8
commit
a382044329
4 changed files with 600 additions and 0 deletions
|
|
@ -330,6 +330,28 @@ class $MProvider extends MProvider with $Bridge<MProvider> {
|
|||
false),
|
||||
]),
|
||||
),
|
||||
'quarkFilesExtractor': BridgeMethodDef(
|
||||
BridgeFunctionDef(
|
||||
returns: BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.future, [
|
||||
BridgeTypeRef(CoreTypes.list, [
|
||||
BridgeTypeRef(CoreTypes.map, [
|
||||
BridgeTypeRef(CoreTypes.string),
|
||||
BridgeTypeRef(CoreTypes.string)
|
||||
])
|
||||
])
|
||||
])),
|
||||
params: [
|
||||
BridgeParameter(
|
||||
'url',
|
||||
BridgeTypeAnnotation(BridgeTypeRef(
|
||||
CoreTypes.list, [BridgeTypeRef(CoreTypes.string)])),
|
||||
false),
|
||||
BridgeParameter(
|
||||
'cookie',
|
||||
BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)),
|
||||
false),
|
||||
]),
|
||||
),
|
||||
'substringAfter': BridgeMethodDef(
|
||||
BridgeFunctionDef(
|
||||
returns: BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)),
|
||||
|
|
@ -904,6 +926,17 @@ class $MProvider extends MProvider with $Bridge<MProvider> {
|
|||
args[0]!.$value, args[1]?.$value ?? "", args[2]?.$value ?? "")
|
||||
.then((value) =>
|
||||
$List.wrap(value.map((e) => _toMVideo(e)).toList())))),
|
||||
"quarkFilesExtractor" => $Function((_, __, List<$Value?> args) =>
|
||||
$Future.wrap(
|
||||
MBridge.quarkFilesExtractor(args[0]!.$value, args[1]!.$value)
|
||||
.then((value) {
|
||||
return $List.wrap(value
|
||||
.map((e) => $Map.wrap({
|
||||
$String('name'): $String(e['name'] ?? ''),
|
||||
$String('url'): $String(e['url'] ?? ''),
|
||||
}))
|
||||
.toList());
|
||||
}))),
|
||||
"toVideo" => $Function((_, __, List<$Value?> args) {
|
||||
final value = MBridge.toVideo(
|
||||
args[0]!.$value,
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import 'package:mangayomi/utils/extensions/string_extensions.dart';
|
|||
import 'package:mangayomi/utils/reg_exp_matcher.dart';
|
||||
import 'package:xpath_selector_html_parser/xpath_selector_html_parser.dart';
|
||||
import 'package:encrypt/encrypt.dart' as encrypt;
|
||||
import 'package:mangayomi/services/anime_extractors/quark_extractor.dart';
|
||||
|
||||
class WordSet {
|
||||
final List<String> words;
|
||||
|
|
@ -344,6 +345,13 @@ class MBridge {
|
|||
.videosFromUrl(url, newHeaders, prefix: prefix, suffix: suffix);
|
||||
}
|
||||
|
||||
static Future<List<Map<String, String>>> quarkFilesExtractor(
|
||||
List<String> url, String cookie) async {
|
||||
QuarkExtractor quark = QuarkExtractor();
|
||||
await quark.initQuark(cookie);
|
||||
return await quark.videoFilesFromUrl(url);
|
||||
}
|
||||
|
||||
static Future<List<Video>> streamTapeExtractor(
|
||||
String url, String? quality) async {
|
||||
return await StreamTapeExtractor()
|
||||
|
|
|
|||
|
|
@ -25,6 +25,10 @@ class JsVideosExtractors {
|
|||
runtime.onMessage('vidBomExtractor', (dynamic args) async {
|
||||
return (await MBridge.vidBomExtractor(args[0])).encodeToJson();
|
||||
});
|
||||
runtime.onMessage('quarkFilesExtractor', (dynamic args) async {
|
||||
List<String> urls = (args[0] as List).cast<String>();
|
||||
return (await MBridge.quarkFilesExtractor(urls, args[1]));
|
||||
});
|
||||
runtime.onMessage('streamlareExtractor', (dynamic args) async {
|
||||
return (await MBridge.streamlareExtractor(
|
||||
args[0], args[1] ?? "", args[2] ?? ""))
|
||||
|
|
@ -178,6 +182,13 @@ async function filemoonExtractor(url, prefix, suffix) {
|
|||
);
|
||||
return JSON.parse(result);
|
||||
}
|
||||
async function quarkFilesExtractor(urls, cookie) {
|
||||
const result = await sendMessage(
|
||||
"quarkFilesExtractor",
|
||||
JSON.stringify([urls, cookie])
|
||||
);
|
||||
return result;
|
||||
}
|
||||
''');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
548
lib/services/anime_extractors/quark_extractor.dart
Normal file
548
lib/services/anime_extractors/quark_extractor.dart
Normal file
|
|
@ -0,0 +1,548 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
import 'package:http_interceptor/http_interceptor.dart';
|
||||
import 'package:mangayomi/models/video.dart';
|
||||
import 'package:mangayomi/services/http/m_client.dart';
|
||||
|
||||
class QuarkExtractor {
|
||||
final String apiUrl = "https://drive-pc.quark.cn/1/clouddrive/";
|
||||
String cookie = "";
|
||||
Map<String, dynamic> shareTokenCache = {};
|
||||
final String pr = "pr=ucpro&fr=pc";
|
||||
final List<String> subtitleExts = ['.srt', '.ass', '.scc', '.stl', '.ttml'];
|
||||
Map<String, String> saveFileIdCaches = {};
|
||||
String? saveDirId;
|
||||
final String saveDirName = 'TV';
|
||||
|
||||
Future<void> initQuark(String cookie) async {
|
||||
this.cookie = cookie;
|
||||
print('夸克云盘初始化完成,Cookie为:$cookie');
|
||||
}
|
||||
|
||||
Map<String, String> getHeaders() {
|
||||
return {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) quark-cloud-drive/2.5.20 Chrome/100.0.4896.160 Electron/18.3.5.4-b478491100 Safari/537.36 Channel/pckk_other_ch',
|
||||
'Referer': 'https://pan.quark.cn/',
|
||||
"Content-Type": "application/json",
|
||||
"Cookie": cookie,
|
||||
"Host": "drive-pc.quark.cn"
|
||||
};
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> api(
|
||||
String url, dynamic data, String method) async {
|
||||
InterceptedClient client =
|
||||
MClient.init(reqcopyWith: {'useDartHttpClient': true});
|
||||
late Response resp;
|
||||
if (method != "get") {
|
||||
resp = await client.post(Uri.parse(apiUrl + url),
|
||||
body: jsonEncode(data), headers: getHeaders());
|
||||
} else {
|
||||
resp = await client.get(Uri.parse(apiUrl + url), headers: getHeaders());
|
||||
}
|
||||
if (resp.headers['set-cookie'] != null) {
|
||||
final puus = resp.headers['set-cookie']!
|
||||
.split(';;;')
|
||||
.join()
|
||||
.split(';')
|
||||
.firstWhere((element) => element.startsWith('__puus='),
|
||||
orElse: () => '');
|
||||
if (puus.isNotEmpty) {
|
||||
final newPuus = puus.split('=')[1];
|
||||
if (cookie.contains('__puus=')) {
|
||||
cookie =
|
||||
cookie.replaceFirst(RegExp(r'__puus=[^;]+'), '__puus=$newPuus');
|
||||
}
|
||||
}
|
||||
}
|
||||
return jsonDecode(resp.body);
|
||||
}
|
||||
|
||||
Map<String, String>? getShareData(String url) {
|
||||
final regex = RegExp(r'https://pan\.quark\.cn/s/([^\\|#/]+)');
|
||||
final matches = regex.firstMatch(url);
|
||||
if (matches != null) {
|
||||
return {
|
||||
'shareId': matches.group(1)!,
|
||||
'folderId': '0',
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
List<String> getPlayFormatList() {
|
||||
return ["4K", "超清", "高清", "普画"];
|
||||
}
|
||||
|
||||
List<String> getPlayFormtQuarkList() {
|
||||
return ["4k", "2k", "super", "high", "normal", "low"];
|
||||
}
|
||||
|
||||
Future<void> getShareToken(Map<String, String> shareData) async {
|
||||
if (!shareTokenCache.containsKey(shareData['shareId'])) {
|
||||
shareTokenCache.remove(shareData['shareId']);
|
||||
final shareToken = await api(
|
||||
'share/sharepage/token?$pr',
|
||||
{
|
||||
'pwd_id': shareData['shareId'],
|
||||
'passcode': shareData['sharePwd'] ?? '',
|
||||
},
|
||||
'post');
|
||||
if (shareToken['data'] != null && shareToken['data']['stoken'] != null) {
|
||||
shareTokenCache[shareData['shareId']!] = shareToken['data'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<dynamic>> listFile(
|
||||
int shareIndex,
|
||||
Map<String, String> shareData,
|
||||
List<dynamic> videos,
|
||||
List<dynamic> subtitles,
|
||||
String shareId,
|
||||
String folderId,
|
||||
{int page = 1}) async {
|
||||
const int prePage = 200;
|
||||
final listData = await api(
|
||||
'share/sharepage/detail?$pr&pwd_id=$shareId&stoken=${Uri.encodeComponent(shareTokenCache[shareId]['stoken'])}&pdir_fid=$folderId&force=0&_page=$page&_size=$prePage&_sort=file_type:asc,file_name:asc',
|
||||
null,
|
||||
'get');
|
||||
if (listData['data'] == null) return [];
|
||||
final items = listData['data']['list'];
|
||||
if (items == null) return [];
|
||||
final subDir = [];
|
||||
for (final item in items) {
|
||||
if (item['dir'] == true) {
|
||||
subDir.add(item);
|
||||
} else if (item['file'] == true && item['obj_category'] == 'video') {
|
||||
if (item['size'] < 1024 * 1024 * 5) continue;
|
||||
item['stoken'] = shareTokenCache[shareData['shareId']]['stoken'];
|
||||
videos.add(Item.objectFrom(item, shareData['shareId']!, shareIndex));
|
||||
} else if (item['type'] == 'file' &&
|
||||
subtitleExts.any((x) => item['file_name'].endsWith(x))) {
|
||||
subtitles.add(Item.objectFrom(item, shareData['shareId']!, shareIndex));
|
||||
}
|
||||
}
|
||||
if (page < (listData['metadata']['_total'] / prePage).ceil()) {
|
||||
final nextItems = await listFile(
|
||||
shareIndex, shareData, videos, subtitles, shareId, folderId,
|
||||
page: page + 1);
|
||||
items.addAll(nextItems);
|
||||
}
|
||||
for (final dir in subDir) {
|
||||
final subItems = await listFile(
|
||||
shareIndex, shareData, videos, subtitles, shareId, dir['fid']);
|
||||
items.addAll(subItems);
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
Map<String, dynamic> findBestLCS(Item mainItem, List<Item> targetItems) {
|
||||
final results = [];
|
||||
var bestMatchIndex = 0;
|
||||
for (var i = 0; i < targetItems.length; i++) {
|
||||
final currentLCS = lcs(mainItem.name, targetItems[i].name);
|
||||
results.add({'target': targetItems[i], 'lcs': currentLCS});
|
||||
if (currentLCS['length'] > results[bestMatchIndex]['lcs']['length']) {
|
||||
bestMatchIndex = i;
|
||||
}
|
||||
}
|
||||
final bestMatch = results[bestMatchIndex];
|
||||
return {
|
||||
'allLCS': results,
|
||||
'bestMatch': bestMatch,
|
||||
'bestMatchIndex': bestMatchIndex
|
||||
};
|
||||
}
|
||||
|
||||
Future<void> getFilesByShareUrl(int shareIndex, dynamic shareInfo,
|
||||
List<dynamic> videos, List<dynamic> subtitles) async {
|
||||
final shareData = shareInfo is String ? getShareData(shareInfo) : shareInfo;
|
||||
if (shareData == null) return;
|
||||
await getShareToken(shareData);
|
||||
if (!shareTokenCache.containsKey(shareData['shareId'])) return;
|
||||
await listFile(shareIndex, shareData, videos, subtitles,
|
||||
shareData['shareId']!, shareData['folderId']!);
|
||||
if (subtitles.isNotEmpty) {
|
||||
for (var item in videos) {
|
||||
var matchSubtitle = findBestLCS(item, subtitles as List<Item>);
|
||||
if (matchSubtitle['bestMatch'] != null) {
|
||||
item.subtitle = matchSubtitle['bestMatch']['target'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void clean() {
|
||||
saveFileIdCaches.clear();
|
||||
}
|
||||
|
||||
Future<void> clearSaveDir() async {
|
||||
final listData = await api(
|
||||
'file/sort?$pr&pdir_fid=$saveDirId&_page=1&_size=200&_sort=file_type:asc,updated_at:desc',
|
||||
{},
|
||||
'get');
|
||||
if (listData['data'] != null &&
|
||||
listData['data']['list'] != null &&
|
||||
listData['data']['list'].isNotEmpty) {
|
||||
await api(
|
||||
'file/delete?$pr',
|
||||
{
|
||||
'action_type': 2,
|
||||
'filelist': listData['data']['list'].map((v) => v['fid']).toList(),
|
||||
'exclude_fids': [],
|
||||
},
|
||||
'post');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> createSaveDir(bool clean) async {
|
||||
if (saveDirId != null) {
|
||||
if (clean) await clearSaveDir();
|
||||
return;
|
||||
}
|
||||
final listData = await api(
|
||||
'file/sort?$pr&pdir_fid=0&_page=1&_size=200&_sort=file_type:asc,updated_at:desc',
|
||||
{},
|
||||
'get');
|
||||
if (listData['data'] != null && listData['data']['list'] != null) {
|
||||
for (final item in listData['data']['list']) {
|
||||
if (item['file_name'] == saveDirName) {
|
||||
saveDirId = item['fid'];
|
||||
await clearSaveDir();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (saveDirId == null) {
|
||||
final create = await api(
|
||||
'file?$pr',
|
||||
{
|
||||
'pdir_fid': '0',
|
||||
'file_name': saveDirName,
|
||||
'dir_path': '',
|
||||
'dir_init_lock': false,
|
||||
},
|
||||
'post');
|
||||
if (create['data'] != null && create['data']['fid'] != null) {
|
||||
saveDirId = create['data']['fid'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<String?> save(String shareId, String stoken, String fileId,
|
||||
String fileToken, bool clean) async {
|
||||
await createSaveDir(clean);
|
||||
if (clean) {
|
||||
this.clean();
|
||||
}
|
||||
if (saveDirId == null) return null;
|
||||
if (stoken.isEmpty) {
|
||||
await getShareToken({'shareId': shareId});
|
||||
if (!shareTokenCache.containsKey(shareId)) return null;
|
||||
}
|
||||
final saveResult = await api(
|
||||
'share/sharepage/save?$pr',
|
||||
{
|
||||
'fid_list': [fileId],
|
||||
'fid_token_list': [fileToken],
|
||||
'to_pdir_fid': saveDirId,
|
||||
'pwd_id': shareId,
|
||||
'stoken':
|
||||
stoken.isNotEmpty ? stoken : shareTokenCache[shareId]['stoken'],
|
||||
'pdir_fid': '0',
|
||||
'scene': 'link',
|
||||
},
|
||||
'post');
|
||||
if (saveResult['data'] != null && saveResult['data']['task_id'] != null) {
|
||||
var retry = 0;
|
||||
while (true) {
|
||||
final taskResult = await api(
|
||||
'task?$pr&task_id=${saveResult['data']['task_id']}&retry_index=$retry',
|
||||
{},
|
||||
'get');
|
||||
if (taskResult['data'] != null &&
|
||||
taskResult['data']['save_as'] != null &&
|
||||
taskResult['data']['save_as']['save_as_top_fids'] != null &&
|
||||
taskResult['data']['save_as']['save_as_top_fids'].isNotEmpty) {
|
||||
return taskResult['data']['save_as']['save_as_top_fids'][0];
|
||||
}
|
||||
retry++;
|
||||
if (retry > 2) break;
|
||||
await Future.delayed(Duration(seconds: 1));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<String?> getLiveTranscoding(String shareId, String stoken,
|
||||
String fileId, String fileToken, String flag) async {
|
||||
if (!saveFileIdCaches.containsKey(fileId)) {
|
||||
final saveFileId = await save(shareId, stoken, fileId, fileToken, true);
|
||||
if (saveFileId == null) return null;
|
||||
saveFileIdCaches[fileId] = saveFileId;
|
||||
}
|
||||
final transcoding = await api(
|
||||
'file/v2/play?$pr',
|
||||
{
|
||||
'fid': saveFileIdCaches[fileId],
|
||||
'resolutions': 'normal,low,high,super,2k,4k',
|
||||
'supports': 'fmp4',
|
||||
},
|
||||
'post');
|
||||
if (transcoding['data'] != null &&
|
||||
transcoding['data']['video_list'] != null) {
|
||||
final flagId = flag.split("-").last;
|
||||
final index = getPlayFormatList().indexOf(flagId);
|
||||
final quarkFormat = getPlayFormtQuarkList()[index];
|
||||
for (final video in transcoding['data']['video_list']) {
|
||||
if (video['resolution'] == quarkFormat) {
|
||||
return video['video_info']['url'];
|
||||
}
|
||||
}
|
||||
return transcoding['data']['video_list'][index]['video_info']['url'];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>?> getDownload(String shareId, String stoken,
|
||||
String fileId, String fileToken, bool clean) async {
|
||||
if (!saveFileIdCaches.containsKey(fileId)) {
|
||||
final saveFileId = await save(shareId, stoken, fileId, fileToken, clean);
|
||||
if (saveFileId == null) return null;
|
||||
saveFileIdCaches[fileId] = saveFileId;
|
||||
}
|
||||
final down = await api(
|
||||
'file/download?$pr&uc_param_str=',
|
||||
{
|
||||
'fids': [saveFileIdCaches[fileId]],
|
||||
},
|
||||
'post');
|
||||
if (down['data'] != null) {
|
||||
return down['data'][0];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<List<Map<String, String>>> videoFilesFromUrl(List<String> shareUrlList,
|
||||
{String typeName = "电影"}) async {
|
||||
List<dynamic> videoItems = [];
|
||||
List<dynamic> subItems = [];
|
||||
|
||||
for (int i = 0; i < shareUrlList.length; i++) {
|
||||
String shareUrl = shareUrlList[i];
|
||||
await getFilesByShareUrl(i + 1, shareUrl, videoItems, subItems);
|
||||
}
|
||||
|
||||
if (videoItems.isNotEmpty) {
|
||||
print('获取播放链接成功,分享链接为:${shareUrlList.join("\t")}');
|
||||
} else {
|
||||
print('获取播放链接失败,检查分享链接为:${shareUrlList.join("\t")}');
|
||||
}
|
||||
|
||||
return await getVodFile(videoItems, subItems, typeName);
|
||||
}
|
||||
|
||||
Future<List<Map<String, String>>> getVodFile(List<dynamic> videoItemList,
|
||||
List<dynamic> subItemList, String typeName) async {
|
||||
if (videoItemList.isEmpty) {
|
||||
return [];
|
||||
}
|
||||
List<Map<String, String>> vodItems = [];
|
||||
for (var videoItem in videoItemList) {
|
||||
String episodeUrl = videoItem.getEpisodeUrl(typeName);
|
||||
String subtitles = findSubs(videoItem.getName(), subItemList);
|
||||
String fullUrl = episodeUrl + subtitles;
|
||||
List<String> parts = fullUrl.split('\$');
|
||||
String name = parts[0].trim();
|
||||
String url = parts[1];
|
||||
vodItems.add({"name": name, "url": url});
|
||||
}
|
||||
print(vodItems);
|
||||
return vodItems;
|
||||
}
|
||||
// Future<List<Video>> getVod(List<dynamic> videoItemList,
|
||||
// List<dynamic> subItemList, String typeName) async {
|
||||
// if (videoItemList.isEmpty) {
|
||||
// return [];
|
||||
// }
|
||||
// List<Video> vodItems = [];
|
||||
// for (var videoItem in videoItemList) {
|
||||
// String episodeUrl = videoItem.getEpisodeUrl(typeName);
|
||||
// String subtitles = findSubs(videoItem.getName(), subItemList);
|
||||
// String fullUrl = episodeUrl + subtitles;
|
||||
// List<String> parts = fullUrl.split('\$');
|
||||
// String name = parts[0].trim();
|
||||
// String url = parts[1];
|
||||
// vodItems.add(Video(url, name, url));
|
||||
// }
|
||||
// return vodItems;
|
||||
// }
|
||||
|
||||
String findSubs(String name, List<dynamic> itemList) {
|
||||
List<dynamic> subItemList = [];
|
||||
pair(removeExt(name).toLowerCase(), itemList, subItemList);
|
||||
if (subItemList.isEmpty) {
|
||||
subItemList.addAll(itemList);
|
||||
}
|
||||
String subStr = "";
|
||||
for (var item in subItemList) {
|
||||
subStr +=
|
||||
"+${removeExt(item.getName())}@@@${item.getFileExtension()}@@@${item.getFileId()}";
|
||||
}
|
||||
return subStr;
|
||||
}
|
||||
|
||||
void pair(String name, List<dynamic> itemList, List<dynamic> subItemList) {
|
||||
for (var item in itemList) {
|
||||
final subName = removeExt(item.getName()).toLowerCase();
|
||||
if (name.contains(subName) || subName.contains(name)) {
|
||||
subItemList.add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String removeExt(String text) {
|
||||
return text.contains('.') ? text.substring(0, text.lastIndexOf(".")) : text;
|
||||
}
|
||||
|
||||
Map<String, dynamic> lcs(String str1, String str2) {
|
||||
if (str1.isEmpty || str2.isEmpty) {
|
||||
return {
|
||||
'length': 0,
|
||||
'sequence': '',
|
||||
'offset': 0,
|
||||
};
|
||||
}
|
||||
var sequence = '';
|
||||
var str1Length = str1.length;
|
||||
var str2Length = str2.length;
|
||||
var num = List.generate(str1Length, (_) => List<int>.filled(str2Length, 0));
|
||||
var maxlen = 0;
|
||||
var lastSubsBegin = 0;
|
||||
var thisSubsBegin = 0;
|
||||
for (var i = 0; i < str1Length; i++) {
|
||||
for (var j = 0; j < str2Length; j++) {
|
||||
if (str1[i] != str2[j]) {
|
||||
num[i][j] = 0;
|
||||
} else {
|
||||
if (i == 0 || j == 0) {
|
||||
num[i][j] = 1;
|
||||
} else {
|
||||
num[i][j] = 1 + num[i - 1][j - 1];
|
||||
}
|
||||
if (num[i][j] > maxlen) {
|
||||
maxlen = num[i][j];
|
||||
thisSubsBegin = i - num[i][j] + 1;
|
||||
if (lastSubsBegin == thisSubsBegin) {
|
||||
sequence += str1[i];
|
||||
} else {
|
||||
lastSubsBegin = thisSubsBegin;
|
||||
sequence = str1.substring(lastSubsBegin, i + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
'length': maxlen,
|
||||
'sequence': sequence,
|
||||
'offset': thisSubsBegin,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class Item {
|
||||
String fileId = "";
|
||||
String shareId = "";
|
||||
String shareToken = "";
|
||||
String shareFileToken = "";
|
||||
String seriesId = "";
|
||||
String name = "";
|
||||
String type = "";
|
||||
String formatType = "";
|
||||
String size = "";
|
||||
String parent = "";
|
||||
dynamic shareData;
|
||||
int shareIndex = 0;
|
||||
int lastUpdateAt = 0;
|
||||
dynamic subtitle;
|
||||
|
||||
static Item objectFrom(
|
||||
Map<String, dynamic> itemJson, String shareId, int shareIndex) {
|
||||
Item item = Item();
|
||||
item.fileId = itemJson['fid'] ?? "";
|
||||
item.shareId = shareId;
|
||||
item.shareToken = itemJson['stoken'] ?? "";
|
||||
item.shareFileToken = itemJson['share_fid_token'] ?? "";
|
||||
item.seriesId = itemJson['series_id'] ?? "";
|
||||
item.name = itemJson['file_name'] ?? "";
|
||||
item.type = itemJson['obj_category'] ?? "";
|
||||
item.formatType = itemJson['format_type'] ?? "";
|
||||
item.size = (itemJson['size'] ?? 0).toString();
|
||||
item.parent = itemJson['pdir_fid'] ?? "";
|
||||
item.lastUpdateAt = itemJson['last_update_at'] ?? 0;
|
||||
item.shareIndex = shareIndex;
|
||||
return item;
|
||||
}
|
||||
|
||||
String getFileExtension() {
|
||||
return name.split(".").last;
|
||||
}
|
||||
|
||||
String getFileId() {
|
||||
return fileId;
|
||||
}
|
||||
|
||||
String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
String getParent() {
|
||||
return "[$parent]";
|
||||
}
|
||||
|
||||
String getSize() {
|
||||
return size == "0" ? "" : "[${getHumanReadableSize(int.parse(size))}]";
|
||||
}
|
||||
|
||||
int getShareIndex() {
|
||||
return shareIndex;
|
||||
}
|
||||
|
||||
String getDisplayName(String typeName) {
|
||||
String displayName = getName();
|
||||
if (typeName == "电视剧") {
|
||||
List<String> replaceNameList = ["4k", "4K"];
|
||||
displayName = displayName.replaceAll(".$getFileExtension()", "");
|
||||
displayName = displayName.replaceAll(" ", "").replaceAll(" ", "");
|
||||
for (String replaceName in replaceNameList) {
|
||||
displayName = displayName.replaceAll(replaceName, "");
|
||||
}
|
||||
displayName =
|
||||
RegExp(r'\.S01E(.*?)\.').firstMatch(displayName)?.group(1) ??
|
||||
displayName;
|
||||
final numbers = RegExp(r'\d+')
|
||||
.allMatches(displayName)
|
||||
.map((m) => m.group(0))
|
||||
.toList();
|
||||
if (numbers.isNotEmpty) {
|
||||
displayName = numbers[0]!;
|
||||
}
|
||||
}
|
||||
return "$displayName ${getSize()}";
|
||||
}
|
||||
|
||||
String getEpisodeUrl(String typeName) {
|
||||
return "${getDisplayName(typeName)}\$${getFileId()}++$shareFileToken++$shareId++$shareToken";
|
||||
}
|
||||
|
||||
String getHumanReadableSize(int bytes) {
|
||||
if (bytes <= 0) return "";
|
||||
final units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
int digitGroups = (log(bytes) / log(1024)).floor();
|
||||
return '${(bytes / pow(1024, digitGroups)).toStringAsFixed(2)} ${units[digitGroups]}';
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue