copy_manga: Avoid loading chapters too quickly

* Update copy_manga.js

修复loadEp逻辑,防止访问过快风控

* format

---------

Co-authored-by: nyne <me@nyne.dev>
This commit is contained in:
Shapaper
2025-01-30 17:39:36 +08:00
committed by GitHub
parent 71fa8716f9
commit 139288c064
2 changed files with 91 additions and 48 deletions

View File

@@ -4,7 +4,7 @@ class CopyManga extends ComicSource {
key = "copy_manga" key = "copy_manga"
version = "1.0.4" version = "1.0.5"
minAppVersion = "1.0.0" minAppVersion = "1.0.0"
@@ -220,9 +220,9 @@ class CopyManga extends ComicSource {
load: async (category, param, options, page) => { load: async (category, param, options, page) => {
let category_url; let category_url;
// 分类-排行 // 分类-排行
if (category === "排行" || param === "ranking"){ if (category === "排行" || param === "ranking") {
category_url = `https://api.copymanga.tv/api/v3/ranks?limit=21&offset=${(page - 1) * 21}&_update=true&type=1&audience_type=${options[0]}&date_type=${options[1]}` category_url = `https://api.copymanga.tv/api/v3/ranks?limit=21&offset=${(page - 1) * 21}&_update=true&type=1&audience_type=${options[0]}&date_type=${options[1]}`
}else{ } else {
// 分类-主题 // 分类-主题
if (category !== undefined && category !== null) { if (category !== undefined && category !== null) {
// 若传入category则转化为对应param // 若传入category则转化为对应param
@@ -248,7 +248,7 @@ class CopyManga extends ComicSource {
let sort = null let sort = null
let popular = 0 let popular = 0
let rise_sort = 0; let rise_sort = 0;
if (comic["sort"] !== null && comic["sort"] !== undefined){ if (comic["sort"] !== null && comic["sort"] !== undefined) {
sort = comic["sort"] sort = comic["sort"]
rise_sort = comic["rise_sort"] rise_sort = comic["rise_sort"]
popular = comic["popular"] popular = comic["popular"]
@@ -269,19 +269,19 @@ class CopyManga extends ComicSource {
} }
//如果是漫画排名,则描述为 排名(+升降箭头)+作者+人气 //如果是漫画排名,则描述为 排名(+升降箭头)+作者+人气
if(sort !== null){ if (sort !== null) {
return { return {
id: comic["path_word"], id: comic["path_word"],
title: comic["name"], title: comic["name"],
subTitle: author, subTitle: author,
cover: comic["cover"], cover: comic["cover"],
tags: tags, tags: tags,
description:`${sort} ${rise_sort > 0 ? '▲' : rise_sort < 0 ? '▽' : '-'}\n` + description: `${sort} ${rise_sort > 0 ? '▲' : rise_sort < 0 ? '▽' : '-'}\n` +
`${author_num > 1 ? `${author}${author_num}` : author}\n` + `${author_num > 1 ? `${author}${author_num}` : author}\n` +
`🔥${(popular / 10000).toFixed(1)}W` `🔥${(popular / 10000).toFixed(1)}W`
} }
//正常情况的描述为更新时间 //正常情况的描述为更新时间
}else{ } else {
return { return {
id: comic["path_word"], id: comic["path_word"],
title: comic["name"], title: comic["name"],
@@ -347,23 +347,24 @@ class CopyManga extends ComicSource {
if (keyword.startsWith("作者:")) { if (keyword.startsWith("作者:")) {
author = keyword.substring("作者:".length).trim(); author = keyword.substring("作者:".length).trim();
} }
let res;
// 通过onClickTag传入时有"作者:"前缀,处理这种情况 // 通过onClickTag传入时有"作者:"前缀,处理这种情况
if (author && author in this.author_path_word_dict){ if (author && author in this.author_path_word_dict) {
let path_word = encodeURIComponent(this.author_path_word_dict[author]); let path_word = encodeURIComponent(this.author_path_word_dict[author]);
var res = await Network.get( res = await Network.get(
`https://api.copymanga.tv/api/v3/comics?limit=21&offset=${(page - 1) * 21}&ordering=-datetime_updated&author=${path_word}&platform=3`, `https://api.copymanga.tv/api/v3/comics?limit=21&offset=${(page - 1) * 21}&ordering=-datetime_updated&author=${path_word}&platform=3`,
this.headers this.headers
) )
} }
// 一般的搜索情况 // 一般的搜索情况
else{ else {
let q_type = ""; let q_type = "";
if(options && options[0]){ if (options && options[0]) {
q_type = options[0]; q_type = options[0];
} }
keyword = encodeURIComponent(keyword) keyword = encodeURIComponent(keyword)
let search_url = this.loadSetting('search_api') == "webAPI" ? "https://www.copymanga.tv/api/kb/web/searchbc/comics" : "https://api.copymanga.tv/api/v3/search/comic" let search_url = this.loadSetting('search_api') === "webAPI" ? "https://www.copymanga.tv/api/kb/web/searchbc/comics" : "https://api.copymanga.tv/api/v3/search/comic"
var res = await Network.get( res = await Network.get(
`${search_url}?limit=21&offset=${(page - 1) * 21}&q=${keyword}&q_type=${q_type}&platform=3`, `${search_url}?limit=21&offset=${(page - 1) * 21}&q=${keyword}&q_type=${q_type}&platform=3`,
this.headers this.headers
) )
@@ -495,8 +496,8 @@ class CopyManga extends ComicSource {
comic = { comic = {
loadInfo: async (id) => { loadInfo: async (id) => {
async function getChapters(id) { let getChapters = async (id) => {
var res = await Network.get( let res = await Network.get(
`https://api.copymanga.tv/api/v3/comic/${id}/group/default/chapters?limit=500&offset=0&platform=3`, `https://api.copymanga.tv/api/v3/comic/${id}/group/default/chapters?limit=500&offset=0&platform=3`,
this.headers this.headers
); );
@@ -533,7 +534,7 @@ class CopyManga extends ComicSource {
return eps; return eps;
} }
async function getFavoriteStatus(id) { let getFavoriteStatus = async (id) => {
let res = await Network.get(`https://api.copymanga.tv/api/v3/comic2/${id}/query?platform=3`, this.headers); let res = await Network.get(`https://api.copymanga.tv/api/v3/comic2/${id}/query?platform=3`, this.headers);
if (res.status !== 200) { if (res.status !== 200) {
throw `Invalid status code: ${res.status}`; throw `Invalid status code: ${res.status}`;
@@ -564,7 +565,7 @@ class CopyManga extends ComicSource {
this.author_path_word_dict = {}; this.author_path_word_dict = {};
} }
// 储存author对应的path_word // 储存author对应的path_word
comicData.author.forEach(e=>(this.author_path_word_dict[e.name] = e.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 tags = comicData.theme.map(e => e?.name).filter(name => name !== undefined && name !== null);
let updateTime = comicData.datetime_updated ? comicData.datetime_updated : ""; let updateTime = comicData.datetime_updated ? comicData.datetime_updated : "";
let description = comicData.brief; let description = comicData.brief;
@@ -585,34 +586,76 @@ class CopyManga extends ComicSource {
} }
}, },
loadEp: async (comicId, epId) => { loadEp: async (comicId, epId) => {
let res = await Network.get( let attempt = 0;
const maxAttempts = 5;
let res;
let data;
while (attempt < maxAttempts) {
try {
res = await Network.get(
`https://api.copymanga.tv/api/v3/comic/${comicId}/chapter2/${epId}?platform=3`, `https://api.copymanga.tv/api/v3/comic/${comicId}/chapter2/${epId}?platform=3`,
this.headers this.headers
); );
if (res.status !== 200){ 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}`; throw `Invalid status code: ${res.status}`;
} }
let data = JSON.parse(res.body); 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;
let imagesUrls = data.results.chapter.contents.map(e => e.url) let images = new Array(imagesUrls.length).fill(""); // Initialize an array with the same length as imagesUrls
let orders = data.results.chapter.words // Arrange images according to orders
for (let i = 0; i < imagesUrls.length; i++) {
let images = imagesUrls.map(e => "") images[orders[i]] = imagesUrls[i];
for(let i=0; i < imagesUrls.length; i++){
images[orders[i]] = imagesUrls[i]
} }
return { return {
images: images images: images,
};
} catch (error) {
if (error !== "Retry") {
throw error;
}
attempt++;
if (attempt >= maxAttempts) {
throw error;
}
}
} }
}, },
loadComments: async (comicId, subId, page, replyTo) => { loadComments: async (comicId, subId, page, replyTo) => {
let url = `https://api.copymanga.tv/api/v3/comments?comic_id=${subId}&limit=20&offset=${(page-1)*20}`; let url = `https://api.copymanga.tv/api/v3/comments?comic_id=${subId}&limit=20&offset=${(page - 1) * 20}`;
if(replyTo){ if (replyTo) {
url = url + `&reply_id=${replyTo}&_update=true`; url = url + `&reply_id=${replyTo}&_update=true`;
} }
let res = await Network.get( let res = await Network.get(
@@ -620,7 +663,7 @@ class CopyManga extends ComicSource {
this.headers, this.headers,
); );
if (res.status !== 200){ if (res.status !== 200) {
throw `Invalid status code: ${res.status}`; throw `Invalid status code: ${res.status}`;
} }
@@ -644,10 +687,10 @@ class CopyManga extends ComicSource {
}, },
sendComment: async (comicId, subId, content, replyTo) => { sendComment: async (comicId, subId, content, replyTo) => {
let token = this.loadData("token"); let token = this.loadData("token");
if(!token){ if (!token) {
throw "未登录" throw "未登录"
} }
if(!replyTo){ if (!replyTo) {
replyTo = ''; replyTo = '';
} }
let res = await Network.post( let res = await Network.post(
@@ -659,19 +702,19 @@ class CopyManga extends ComicSource {
`comic_id=${subId}&comment=${encodeURIComponent(content)}&reply_id=${replyTo}`, `comic_id=${subId}&comment=${encodeURIComponent(content)}&reply_id=${replyTo}`,
); );
if (res.status === 401){ if (res.status === 401) {
error(`Login expired`); error(`Login expired`);
return; return;
} }
if (res.status !== 200){ if (res.status !== 200) {
throw `Invalid status code: ${res.status}`; throw `Invalid status code: ${res.status}`;
} else { } else {
return "ok" return "ok"
} }
}, },
onClickTag: (namespace, tag) => { onClickTag: (namespace, tag) => {
if(namespace == "标签"){ if (namespace === "标签") {
return { return {
// 'search' or 'category' // 'search' or 'category'
action: 'category', action: 'category',
@@ -680,7 +723,7 @@ class CopyManga extends ComicSource {
param: null, param: null,
} }
} }
if(namespace == "作者"){ if (namespace === "作者") {
return { return {
// 'search' or 'category' // 'search' or 'category'
action: 'search', action: 'search',

View File

@@ -3,7 +3,7 @@
"name": "拷贝漫画", "name": "拷贝漫画",
"fileName": "copy_manga.js", "fileName": "copy_manga.js",
"key": "copy_manga", "key": "copy_manga",
"version": "1.0.4" "version": "1.0.5"
}, },
{ {
"name": "Komiic", "name": "Komiic",