mirror of
https://github.com/venera-app/venera-configs.git
synced 2025-09-27 08:27:24 +00:00
901 lines
31 KiB
JavaScript
901 lines
31 KiB
JavaScript
class CopyManga extends ComicSource {
|
||
|
||
name = "拷贝漫画"
|
||
|
||
key = "copy_manga"
|
||
|
||
version = "1.3.5"
|
||
|
||
minAppVersion = "1.2.1"
|
||
|
||
url = "https://cdn.jsdelivr.net/gh/venera-app/venera-configs@main/copy_manga.js"
|
||
|
||
get headers() {
|
||
let token = this.loadData("token");
|
||
if (!token) {
|
||
token = "";
|
||
} else {
|
||
token = " " + token;
|
||
}
|
||
return {
|
||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/114.0",
|
||
"Origin": `https://${this.loadSetting('base_url')}`,
|
||
"Accept": "application/json",
|
||
"platform": "1",
|
||
"version": "2025.05.09",
|
||
"region": this.copyRegion,
|
||
"authorization": `Token${token}`,
|
||
}
|
||
}
|
||
|
||
// static defaultCopyVersion = "2.2.9-dev"
|
||
|
||
// static defaultCopyPlatform = "2"
|
||
|
||
static defaultCopyRegion = "1"
|
||
|
||
static defaultImageQuality = "1500"
|
||
|
||
static defaultApiUrl = 'mapi.copy20.com'
|
||
|
||
static searchApi = "/api/kb/web/searchb/comics"
|
||
|
||
// get copyVersion() {
|
||
// return this.loadSetting('version')
|
||
// }
|
||
|
||
// get copyPlatform()
|
||
// return this.loadSetting('platform')
|
||
// }
|
||
|
||
get apiUrl() {
|
||
return `https://${this.loadSetting('base_url')}`
|
||
}
|
||
|
||
get copyRegion() {
|
||
return this.loadSetting('region') || this.defaultCopyRegion
|
||
}
|
||
|
||
get imageQuality() {
|
||
return this.loadSetting('image_quality') || this.defaultImageQuality
|
||
}
|
||
|
||
init() {
|
||
// 用于储存 { 作者名 : 英文参数 }
|
||
this.author_path_word_dict = {}
|
||
this.refreshSearchApi()
|
||
}
|
||
|
||
/// account
|
||
/// set this to null to desable account feature
|
||
account = {
|
||
/// login func
|
||
login: async (account, pwd) => {
|
||
let salt = randomInt(1000, 9999)
|
||
let base64 = Convert.encodeBase64(Convert.encodeUtf8(`${pwd}-${salt}`))
|
||
let res = await Network.post(
|
||
`${this.apiUrl}/api/v3/login`,
|
||
{
|
||
...this.headers,
|
||
"Content-Type": "application/x-www-form-urlencoded;charset=utf-8"
|
||
},
|
||
`username=${account}&password=${base64}\n&salt=${salt}&authorization=Token+`
|
||
);
|
||
if (res.status === 200) {
|
||
let data = JSON.parse(res.body)
|
||
let token = data.results.token
|
||
this.saveData('token', token)
|
||
return "ok"
|
||
} else {
|
||
throw `Invalid Status Code ${res.status}`
|
||
}
|
||
},
|
||
// callback when user log out
|
||
logout: () => {
|
||
this.deleteData('token')
|
||
},
|
||
registerWebsite: null
|
||
}
|
||
|
||
/// explore pages
|
||
explore = [
|
||
{
|
||
title: "拷贝漫画",
|
||
type: "singlePageWithMultiPart",
|
||
load: async () => {
|
||
let dataStr = await Network.get(
|
||
`${this.apiUrl}/api/v3/h5/homeIndex`,
|
||
this.headers
|
||
)
|
||
|
||
if (dataStr.status !== 200) {
|
||
throw `Invalid status code: ${dataStr.status}`
|
||
}
|
||
|
||
let data = JSON.parse(dataStr.body)
|
||
|
||
function parseComic(comic) {
|
||
if (comic["comic"] !== null && comic["comic"] !== undefined) {
|
||
comic = comic["comic"]
|
||
}
|
||
let tags = []
|
||
if (comic["theme"] !== null && comic["theme"] !== undefined) {
|
||
tags = comic["theme"].map(t => t["name"])
|
||
}
|
||
let author = null
|
||
|
||
if (Array.isArray(comic["author"]) && comic["author"].length > 0) {
|
||
author = comic["author"][0]["name"]
|
||
}
|
||
|
||
return {
|
||
id: comic["path_word"],
|
||
title: comic["name"],
|
||
subTitle: author,
|
||
cover: comic["cover"],
|
||
tags: tags
|
||
}
|
||
}
|
||
|
||
let res = {}
|
||
res["推荐"] = data["results"]["recComics"]["list"].map(parseComic)
|
||
res["热门"] = data["results"]["hotComics"].map(parseComic)
|
||
res["最新"] = data["results"]["newComics"].map(parseComic)
|
||
res["完结"] = data["results"]["finishComics"]["list"].map(parseComic)
|
||
res["今日排行"] = data["results"]["rankDayComics"]["list"].map(parseComic)
|
||
res["本周排行"] = data["results"]["rankWeekComics"]["list"].map(parseComic)
|
||
res["本月排行"] = data["results"]["rankMonthComics"]["list"].map(parseComic)
|
||
|
||
return res
|
||
}
|
||
}
|
||
]
|
||
|
||
static category_param_dict = {
|
||
"全部": "",
|
||
"愛情": "aiqing",
|
||
"歡樂向": "huanlexiang",
|
||
"冒險": "maoxian",
|
||
"奇幻": "qihuan",
|
||
"百合": "baihe",
|
||
"校园": "xiaoyuan",
|
||
"科幻": "kehuan",
|
||
"東方": "dongfang",
|
||
"耽美": "danmei",
|
||
"生活": "shenghuo",
|
||
"格鬥": "gedou",
|
||
"轻小说": "qingxiaoshuo",
|
||
"悬疑": "xuanyi",
|
||
"其他": "qita",
|
||
"神鬼": "shengui",
|
||
"职场": "zhichang",
|
||
"TL": "teenslove",
|
||
"萌系": "mengxi",
|
||
"治愈": "zhiyu",
|
||
"長條": "changtiao",
|
||
"四格": "sige",
|
||
"节操": "jiecao",
|
||
"舰娘": "jianniang",
|
||
"竞技": "jingji",
|
||
"搞笑": "gaoxiao",
|
||
"伪娘": "weiniang",
|
||
"热血": "rexue",
|
||
"励志": "lizhi",
|
||
"性转换": "xingzhuanhuan",
|
||
"彩色": "COLOR",
|
||
"後宮": "hougong",
|
||
"美食": "meishi",
|
||
"侦探": "zhentan",
|
||
"AA": "aa",
|
||
"音乐舞蹈": "yinyuewudao",
|
||
"魔幻": "mohuan",
|
||
"战争": "zhanzheng",
|
||
"历史": "lishi",
|
||
"异世界": "yishijie",
|
||
"惊悚": "jingsong",
|
||
"机战": "jizhan",
|
||
"都市": "dushi",
|
||
"穿越": "chuanyue",
|
||
"恐怖": "kongbu",
|
||
"C100": "comiket100",
|
||
"重生": "chongsheng",
|
||
"C99": "comiket99",
|
||
"C101": "comiket101",
|
||
"C97": "comiket97",
|
||
"C96": "comiket96",
|
||
"生存": "shengcun",
|
||
"宅系": "zhaixi",
|
||
"武侠": "wuxia",
|
||
"C98": "C98",
|
||
"C95": "comiket95",
|
||
"FATE": "fate",
|
||
"转生": "zhuansheng",
|
||
"無修正": "Uncensored",
|
||
"仙侠": "xianxia",
|
||
"LoveLive": "loveLive"
|
||
}
|
||
|
||
category = {
|
||
title: "拷贝漫画",
|
||
parts: [
|
||
{
|
||
name: "拷贝漫画",
|
||
type: "fixed",
|
||
categories: ["排行"],
|
||
categoryParams: ["ranking"],
|
||
itemType: "category"
|
||
},
|
||
{
|
||
name: "主题",
|
||
type: "fixed",
|
||
categories: Object.keys(CopyManga.category_param_dict),
|
||
categoryParams: Object.values(CopyManga.category_param_dict),
|
||
itemType: "category"
|
||
}
|
||
]
|
||
}
|
||
|
||
categoryComics = {
|
||
load: async (category, param, options, page) => {
|
||
let category_url;
|
||
// 分类-排行
|
||
if (category === "排行" || param === "ranking") {
|
||
category_url = `${this.apiUrl}/api/v3/ranks?limit=30&offset=${(page - 1) * 30}&_update=true&type=1&audience_type=${options[0]}&date_type=${options[1]}`
|
||
} else {
|
||
// 分类-主题
|
||
if (category !== undefined && category !== null) {
|
||
// 若传入category,则转化为对应param
|
||
param = CopyManga.category_param_dict[category] || "";
|
||
}
|
||
options = options.map(e => e.replace("*", "-"))
|
||
category_url = `${this.apiUrl}/api/v3/comics?limit=30&offset=${(page - 1) * 30}&ordering=${options[1]}&theme=${param}&top=${options[0]}`
|
||
}
|
||
|
||
|
||
let res = await Network.get(
|
||
category_url,
|
||
this.headers
|
||
)
|
||
if (res.status !== 200) {
|
||
throw `Invalid status code: ${res.status}`
|
||
}
|
||
|
||
let data = JSON.parse(res.body)
|
||
|
||
function parseComic(comic) {
|
||
//判断是否是漫画排名格式
|
||
let sort = null
|
||
let popular = 0
|
||
let rise_sort = 0;
|
||
if (comic["sort"] !== null && comic["sort"] !== undefined) {
|
||
sort = comic["sort"]
|
||
rise_sort = comic["rise_sort"]
|
||
popular = comic["popular"]
|
||
}
|
||
|
||
if (comic["comic"] !== null && comic["comic"] !== undefined) {
|
||
comic = comic["comic"]
|
||
}
|
||
let tags = []
|
||
if (comic["theme"] !== null && comic["theme"] !== undefined) {
|
||
tags = comic["theme"].map(t => t["name"])
|
||
}
|
||
let author = null
|
||
let author_num = 0
|
||
if (Array.isArray(comic["author"]) && comic["author"].length > 0) {
|
||
author = comic["author"][0]["name"]
|
||
author_num = comic["author"].length
|
||
}
|
||
|
||
//如果是漫画排名,则描述为 排名(+升降箭头)+作者+人气
|
||
if (sort !== null) {
|
||
return {
|
||
id: comic["path_word"],
|
||
title: comic["name"],
|
||
subTitle: author,
|
||
cover: comic["cover"],
|
||
tags: tags,
|
||
description: `${sort} ${rise_sort > 0 ? '▲' : rise_sort < 0 ? '▽' : '-'}\n` +
|
||
`${author_num > 1 ? `${author} 等${author_num}位` : author}\n` +
|
||
`🔥${(popular / 10000).toFixed(1)}W`
|
||
}
|
||
//正常情况的描述为更新时间
|
||
} else {
|
||
return {
|
||
id: comic["path_word"],
|
||
title: comic["name"],
|
||
subTitle: author,
|
||
cover: comic["cover"],
|
||
tags: tags,
|
||
description: comic["datetime_updated"]
|
||
}
|
||
}
|
||
}
|
||
|
||
return {
|
||
comics: data["results"]["list"].map(parseComic),
|
||
maxPage: (data["results"]["total"] - (data["results"]["total"] % 21)) / 21 + 1
|
||
}
|
||
},
|
||
optionList: [
|
||
{
|
||
options: [
|
||
"-全部",
|
||
"japan-日漫",
|
||
"korea-韩漫",
|
||
"west-美漫",
|
||
"finish-已完结"
|
||
],
|
||
notShowWhen: null,
|
||
showWhen: Object.keys(CopyManga.category_param_dict)
|
||
},
|
||
{
|
||
options: [
|
||
"*datetime_updated-时间倒序",
|
||
"datetime_updated-时间正序",
|
||
"*popular-热度倒序",
|
||
"popular-热度正序",
|
||
],
|
||
notShowWhen: null,
|
||
showWhen: Object.keys(CopyManga.category_param_dict)
|
||
},
|
||
{
|
||
options: [
|
||
"male-男频",
|
||
"female-女频"
|
||
],
|
||
notShowWhen: null,
|
||
showWhen: ["排行"]
|
||
},
|
||
{
|
||
options: [
|
||
"day-上升最快",
|
||
"week-最近7天",
|
||
"month-最近30天",
|
||
"total-總榜單"
|
||
],
|
||
notShowWhen: null,
|
||
showWhen: ["排行"]
|
||
}
|
||
]
|
||
}
|
||
|
||
search = {
|
||
load: async (keyword, options, page) => {
|
||
let author;
|
||
if (keyword.startsWith("作者:")) {
|
||
author = keyword.substring("作者:".length).trim();
|
||
}
|
||
let res;
|
||
// 通过onClickTag传入时有"作者:"前缀,处理这种情况
|
||
if (author && author in this.author_path_word_dict) {
|
||
let path_word = encodeURIComponent(this.author_path_word_dict[author]);
|
||
res = await Network.get(
|
||
`${this.apiUrl}/api/v3/comics?limit=30&offset=${(page - 1) * 30}&ordering=-datetime_updated&author=${path_word}`,
|
||
this.headers
|
||
)
|
||
}
|
||
// 一般的搜索情况
|
||
else {
|
||
let q_type = "";
|
||
if (options && options[0]) {
|
||
q_type = options[0];
|
||
}
|
||
keyword = encodeURIComponent(keyword)
|
||
let search_url = this.loadSetting('search_api') === "webAPI"
|
||
? `${this.apiUrl}${CopyManga.searchApi}`
|
||
: `${this.apiUrl}/api/v3/search/comic`
|
||
res = await Network.get(
|
||
`${search_url}?limit=30&offset=${(page - 1) * 30}&q=${keyword}&q_type=${q_type}`,
|
||
this.headers
|
||
)
|
||
}
|
||
if (res.status !== 200) {
|
||
throw `Invalid status code: ${res.status}`
|
||
}
|
||
|
||
let data = JSON.parse(res.body)
|
||
|
||
function parseComic(comic) {
|
||
if (comic["comic"] !== null && comic["comic"] !== undefined) {
|
||
comic = comic["comic"]
|
||
}
|
||
let tags = []
|
||
if (comic["theme"] !== null && comic["theme"] !== undefined) {
|
||
tags = comic["theme"].map(t => t["name"])
|
||
}
|
||
let author = null
|
||
|
||
if (Array.isArray(comic["author"]) && comic["author"].length > 0) {
|
||
author = comic["author"][0]["name"]
|
||
}
|
||
|
||
return {
|
||
id: comic["path_word"],
|
||
title: comic["name"],
|
||
subTitle: author,
|
||
cover: comic["cover"],
|
||
tags: tags,
|
||
description: comic["datetime_updated"]
|
||
}
|
||
}
|
||
|
||
return {
|
||
comics: data["results"]["list"].map(parseComic),
|
||
maxPage: (data["results"]["total"] - (data["results"]["total"] % 21)) / 21 + 1
|
||
}
|
||
},
|
||
optionList: [
|
||
{
|
||
type: "select",
|
||
options: [
|
||
"-全部",
|
||
"name-名称",
|
||
"author-作者",
|
||
"local-汉化组"
|
||
],
|
||
label: "搜索选项"
|
||
}
|
||
]
|
||
}
|
||
|
||
favorites = {
|
||
multiFolder: false,
|
||
addOrDelFavorite: async (comicId, folderId, isAdding) => {
|
||
let is_collect = isAdding ? 1 : 0
|
||
let token = this.loadData("token");
|
||
let comicData = await Network.get(
|
||
`${this.apiUrl}/api/v3/comic2/${comicId}`,
|
||
this.headers
|
||
)
|
||
if (comicData.status !== 200) {
|
||
throw `Invalid status code: ${comicData.status}`
|
||
}
|
||
let comic_id = JSON.parse(comicData.body).results.comic.uuid
|
||
let res = await Network.post(
|
||
`${this.apiUrl}/api/v3/member/collect/comic`,
|
||
{
|
||
...this.headers,
|
||
"Content-Type": "application/x-www-form-urlencoded;charset=utf-8",
|
||
},
|
||
`comic_id=${comic_id}&is_collect=${is_collect}&authorization=Token+${token}`
|
||
)
|
||
if (res.status === 401) {
|
||
throw `Login expired`;
|
||
}
|
||
if (res.status !== 200) {
|
||
throw `Invalid status code: ${res.status}`
|
||
}
|
||
return "ok"
|
||
},
|
||
loadComics: async (page, folder) => {
|
||
var res = await Network.get(
|
||
`${this.apiUrl}/api/v3/member/collect/comics?limit=30&offset=${(page - 1) * 30}&free_type=1&ordering=-datetime_updated`,
|
||
this.headers
|
||
)
|
||
|
||
if (res.status === 401) {
|
||
throw `Login expired`
|
||
}
|
||
|
||
if (res.status !== 200) {
|
||
throw `Invalid status code: ${res.status}`
|
||
}
|
||
|
||
let data = JSON.parse(res.body)
|
||
|
||
function parseComic(comic) {
|
||
if (comic["comic"] !== null && comic["comic"] !== undefined) {
|
||
comic = comic["comic"]
|
||
}
|
||
let tags = []
|
||
if (comic["theme"] !== null && comic["theme"] !== undefined) {
|
||
tags = comic["theme"].map(t => t["name"])
|
||
}
|
||
let author = null
|
||
|
||
if (Array.isArray(comic["author"]) && comic["author"].length > 0) {
|
||
author = comic["author"][0]["name"]
|
||
}
|
||
|
||
return {
|
||
id: comic["path_word"],
|
||
title: comic["name"],
|
||
subTitle: author,
|
||
cover: comic["cover"],
|
||
tags: tags,
|
||
description: comic["datetime_updated"]
|
||
}
|
||
}
|
||
|
||
return {
|
||
comics: data["results"]["list"].map(parseComic),
|
||
maxPage: (data["results"]["total"] - (data["results"]["total"] % 21)) / 21 + 1
|
||
}
|
||
}
|
||
}
|
||
|
||
comic = {
|
||
loadInfo: async (id) => {
|
||
let getChapters = async (id, groups) => {
|
||
let fetchSingle = async (id, path) => {
|
||
let res = await Network.get(
|
||
`${this.apiUrl}/api/v3/comic/${id}/group/${path}/chapters?limit=500&offset=0`,
|
||
this.headers
|
||
);
|
||
if (res.status !== 200) {
|
||
throw `Invalid status code: ${res.status}`;
|
||
}
|
||
let data = JSON.parse(res.body);
|
||
let eps = new Map();
|
||
data.results.list.forEach((e) => {
|
||
let title = e.name;
|
||
let id = e.uuid;
|
||
eps.set(id, title);
|
||
});
|
||
let maxChapter = data.results.total;
|
||
if (maxChapter > 500) {
|
||
let offset = 500;
|
||
while (offset < maxChapter) {
|
||
res = await Network.get(
|
||
`${this.apiUrl}/api/v3/comic/${id}/group/${path}/chapters?limit=500&offset=${offset}`,
|
||
this.headers
|
||
);
|
||
if (res.status !== 200) {
|
||
throw `Invalid status code: ${res.status}`;
|
||
}
|
||
data = JSON.parse(res.body);
|
||
data.results.list.forEach((e) => {
|
||
let title = e.name;
|
||
let id = e.uuid;
|
||
eps.set(id, title)
|
||
});
|
||
offset += 500;
|
||
}
|
||
}
|
||
return eps;
|
||
};
|
||
let keys = Object.keys(groups);
|
||
let result = {};
|
||
let futures = [];
|
||
for (let group of keys) {
|
||
let path = groups[group]["path_word"];
|
||
futures.push((async () => {
|
||
result[group] = await fetchSingle(id, path);
|
||
})());
|
||
}
|
||
await Promise.all(futures);
|
||
if (this.isAppVersionAfter("1.3.0")) {
|
||
// 支持多分组
|
||
let sortedResult = new Map();
|
||
for (let key of keys) {
|
||
let name = groups[key]["name"];
|
||
sortedResult.set(name, result[key]);
|
||
}
|
||
return sortedResult;
|
||
} else {
|
||
// 合并所有分组
|
||
let merged = new Map();
|
||
for (let key of keys) {
|
||
for (let [k, v] of result[key]) {
|
||
merged.set(k, v);
|
||
}
|
||
}
|
||
return merged;
|
||
}
|
||
}
|
||
|
||
let getFavoriteStatus = async (id) => {
|
||
let res = await Network.get(`${this.apiUrl}/api/v3/comic2/${id}/query`, this.headers);
|
||
if (res.status !== 200) {
|
||
throw `Invalid status code: ${res.status}`;
|
||
}
|
||
return JSON.parse(res.body).results.collect != null;
|
||
}
|
||
|
||
let results = await Promise.all([
|
||
Network.get(
|
||
`${this.apiUrl}/api/v3/comic2/${id}`,
|
||
this.headers
|
||
),
|
||
getFavoriteStatus.bind(this)(id)
|
||
])
|
||
|
||
if (results[0].status !== 200) {
|
||
throw `Invalid status code: ${res.status}`;
|
||
}
|
||
|
||
let data = JSON.parse(results[0].body).results;
|
||
let comicData = data.comic;
|
||
|
||
let title = comicData.name;
|
||
let cover = comicData.cover;
|
||
let authors = comicData.author.map(e => e.name);
|
||
// author_path_word_dict长度限制为最大100
|
||
if (Object.keys(this.author_path_word_dict).length > 100) {
|
||
this.author_path_word_dict = {};
|
||
}
|
||
// 储存author对应的path_word
|
||
comicData.author.forEach(e => (this.author_path_word_dict[e.name] = e.path_word));
|
||
let tags = comicData.theme.map(e => e?.name).filter(name => name !== undefined && name !== null);
|
||
let updateTime = comicData.datetime_updated ? comicData.datetime_updated : "";
|
||
let description = comicData.brief;
|
||
let chapters = await getChapters(id, data.groups);
|
||
let status = comicData.status.display;
|
||
|
||
return {
|
||
title: title,
|
||
cover: cover,
|
||
description: description,
|
||
tags: {
|
||
"作者": authors,
|
||
"更新": [updateTime],
|
||
"标签": tags,
|
||
"状态": [status],
|
||
},
|
||
chapters: chapters,
|
||
isFavorite: results[1],
|
||
subId: comicData.uuid
|
||
}
|
||
},
|
||
loadEp: async (comicId, epId) => {
|
||
let attempt = 0;
|
||
const maxAttempts = 5;
|
||
let res;
|
||
let data;
|
||
|
||
while (attempt < maxAttempts) {
|
||
try {
|
||
res = await Network.get(
|
||
`${this.apiUrl}/api/v3/comic/${comicId}/chapter2/${epId}`,
|
||
{
|
||
...this.headers
|
||
}
|
||
);
|
||
|
||
if (res.status === 210) {
|
||
// 210 indicates too frequent access, extract wait time
|
||
let waitTime = 40000; // Default wait time 40s
|
||
try {
|
||
let responseBody = JSON.parse(res.body);
|
||
if (
|
||
responseBody.message &&
|
||
responseBody.message.includes("Expected available in")
|
||
) {
|
||
let match = responseBody.message.match(/(\d+)\s*seconds/);
|
||
if (match && match[1]) {
|
||
waitTime = parseInt(match[1]) * 1000;
|
||
}
|
||
}
|
||
} catch (e) {
|
||
console.log(
|
||
"Unable to parse wait time, using default wait time 40s"
|
||
);
|
||
}
|
||
console.log(`Chapter${epId} access too frequent, waiting ${waitTime / 1000}s`);
|
||
await new Promise((resolve) => setTimeout(resolve, waitTime));
|
||
throw "Retry";
|
||
}
|
||
|
||
if (res.status !== 200) {
|
||
throw `Invalid status code: ${res.status}`;
|
||
}
|
||
|
||
data = JSON.parse(res.body);
|
||
// console.log(data.results.chapter);
|
||
// Handle image link sorting
|
||
let imagesUrls = data.results.chapter.contents.map((e) => e.url);
|
||
let orders = data.results.chapter.words;
|
||
|
||
// Replace origin images urls to selected quality images urls
|
||
let hdImagesUrls = imagesUrls.map((url) =>
|
||
url.replace(/([./])c\d+x\.[a-zA-Z]+$/, `$1c${this.imageQuality}x.webp`)
|
||
)
|
||
|
||
let images = new Array(hdImagesUrls.length).fill(""); // Initialize an array with the same length as imagesUrls
|
||
|
||
// Arrange images according to orders
|
||
for (let i = 0; i < hdImagesUrls.length; i++) {
|
||
images[orders[i]] = hdImagesUrls[i];
|
||
}
|
||
|
||
return {
|
||
images: images,
|
||
};
|
||
} catch (error) {
|
||
if (error !== "Retry") {
|
||
throw error;
|
||
}
|
||
attempt++;
|
||
if (attempt >= maxAttempts) {
|
||
throw error;
|
||
}
|
||
}
|
||
}
|
||
},
|
||
loadComments: async (comicId, subId, page, replyTo) => {
|
||
let url = `${this.apiUrl}/api/v3/comments?comic_id=${subId}&limit=20&offset=${(page - 1) * 20}`;
|
||
if (replyTo) {
|
||
url = url + `&reply_id=${replyTo}&_update=true`;
|
||
}
|
||
let res = await Network.get(
|
||
url,
|
||
this.headers,
|
||
);
|
||
|
||
if (res.status !== 200) {
|
||
if(res.status === 210){
|
||
throw "210:注冊用戶一天可以發5條評論"
|
||
}
|
||
throw `Invalid status code: ${res.status}`;
|
||
}
|
||
|
||
let data = JSON.parse(res.body);
|
||
|
||
let total = data.results.total;
|
||
|
||
return {
|
||
comments: data.results.list.map(e => {
|
||
return {
|
||
userName: replyTo ? `${e.user_name} 👉 ${e.parent_user_name}` : e.user_name, // 拷贝的回复页并没有楼中楼(所有回复都在一个response中),但会显示谁回复了谁。所以加上👉显示。
|
||
avatar: e.user_avatar,
|
||
content: e.comment,
|
||
time: e.create_at,
|
||
replyCount: e.count,
|
||
id: e.id,
|
||
}
|
||
}),
|
||
maxPage: (total - (total % 20)) / 20 + 1,
|
||
}
|
||
},
|
||
sendComment: async (comicId, subId, content, replyTo) => {
|
||
let token = this.loadData("token");
|
||
if (!token) {
|
||
throw "未登录"
|
||
}
|
||
if (!replyTo) {
|
||
replyTo = '';
|
||
}
|
||
let res = await Network.post(
|
||
`${this.apiUrl}/api/v3/member/comment`,
|
||
{
|
||
...this.headers,
|
||
"Content-Type": "application/x-www-form-urlencoded;charset=utf-8",
|
||
},
|
||
`comic_id=${subId}&comment=${encodeURIComponent(content)}&reply_id=${replyTo}`,
|
||
);
|
||
|
||
if (res.status === 401) {
|
||
error(`Login expired`);
|
||
return;
|
||
}
|
||
|
||
if (res.status !== 200) {
|
||
throw `Invalid status code: ${res.status}`;
|
||
} else {
|
||
return "ok"
|
||
}
|
||
},
|
||
onClickTag: (namespace, tag) => {
|
||
if (namespace === "标签") {
|
||
return {
|
||
// 'search' or 'category'
|
||
action: 'category',
|
||
keyword: `${tag}`,
|
||
// {string?} only for category action
|
||
param: null,
|
||
}
|
||
}
|
||
if (namespace === "作者") {
|
||
return {
|
||
// 'search' or 'category'
|
||
action: 'search',
|
||
keyword: `${namespace}:${tag}`,
|
||
// {string?} only for category action
|
||
param: null,
|
||
}
|
||
}
|
||
throw "未支持此类Tag检索"
|
||
}
|
||
}
|
||
|
||
settings = {
|
||
region: {
|
||
title: "CDN线路",
|
||
type: "select",
|
||
options: [
|
||
{
|
||
value: "1",
|
||
text: '大陆线路'
|
||
},
|
||
{
|
||
value: "0",
|
||
text: '海外线路'
|
||
},
|
||
],
|
||
default: CopyManga.defaultCopyRegion,
|
||
},
|
||
image_quality: {
|
||
title: "图片质量",
|
||
type: "select",
|
||
options: [
|
||
{
|
||
value: '800',
|
||
text: '低 (800)'
|
||
},
|
||
{
|
||
value: '1200',
|
||
text: '中 (1200)'
|
||
},
|
||
{
|
||
value: '1500',
|
||
text: '高 (1500)'
|
||
}
|
||
],
|
||
default: CopyManga.defaultImageQuality,
|
||
},
|
||
search_api: {
|
||
title: "搜索方式",
|
||
type: "select",
|
||
options: [
|
||
{
|
||
value: 'baseAPI',
|
||
text: '基础API'
|
||
},
|
||
{
|
||
value: 'webAPI',
|
||
text: '网页端API'
|
||
}
|
||
],
|
||
default: 'baseAPI'
|
||
},
|
||
base_url: {
|
||
title: "API地址",
|
||
type: "input",
|
||
validator: '^(?!:\\/\\/)(?=.{1,253})([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z]{2,}$',
|
||
default: CopyManga.defaultApiUrl,
|
||
}
|
||
// version: {
|
||
// title: "拷贝版本(重启APP生效)",
|
||
// type: "input",
|
||
// default: CopyManga.defaultCopyVersion,
|
||
// },
|
||
// platform: {
|
||
// title: "平台代号(重启APP生效)",
|
||
// type: "input",
|
||
// validator: '^\\d+(?:\\.\\d+)*$',
|
||
// default: CopyManga.defaultCopyPlatform,
|
||
// },
|
||
}
|
||
|
||
/**
|
||
* Check if the current app version is after the target version
|
||
* @param target {string} target version
|
||
* @returns {boolean} true if the current app version is after the target version
|
||
*/
|
||
isAppVersionAfter(target) {
|
||
let current = APP.version
|
||
let targetArr = target.split('.')
|
||
let currentArr = current.split('.')
|
||
for (let i = 0; i < 3; i++) {
|
||
if (parseInt(currentArr[i]) < parseInt(targetArr[i])) {
|
||
return false
|
||
}
|
||
}
|
||
return true
|
||
}
|
||
|
||
async refreshSearchApi() {
|
||
let url = "https://www.copy20.com/search"
|
||
let res = await fetch(url)
|
||
let searchApi = ""
|
||
if (res.status === 200) {
|
||
let text = await res.text()
|
||
let match = text.match(/const countApi = "([^"]+)"/)
|
||
if (match && match[1]) {
|
||
CopyManga.searchApi = match[1]
|
||
}
|
||
}
|
||
}
|
||
}
|