mirror of
https://github.com/venera-app/venera-configs.git
synced 2025-09-27 08:27:24 +00:00
612 lines
22 KiB
JavaScript
612 lines
22 KiB
JavaScript
class CopyManga extends ComicSource {
|
||
|
||
name = "拷贝漫画"
|
||
|
||
key = "copy_manga"
|
||
|
||
version = "1.0.3"
|
||
|
||
minAppVersion = "1.0.0"
|
||
|
||
url = "https://raw.githubusercontent.com/venera-app/venera-configs/refs/heads/main/copy_manga.js"
|
||
|
||
headers = {}
|
||
|
||
static copyVersion = "2.2.0"
|
||
|
||
init() {
|
||
let token = this.loadData("token");
|
||
if (!token) {
|
||
token = "";
|
||
} else {
|
||
token = " " + token;
|
||
}
|
||
this.headers = {
|
||
"User-Agent": "COPY/" + CopyManga.copyVersion,
|
||
"Accept": "*/*",
|
||
"Accept-Encoding": "gzip",
|
||
"source": "copyApp",
|
||
"webp": "1",
|
||
"region": "1",
|
||
"version": CopyManga.copyVersion,
|
||
"authorization": `Token${token}`,
|
||
"platform": "3",
|
||
}
|
||
this.author_path_word_dict = {}
|
||
}
|
||
|
||
/// 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(
|
||
"https://api.copymanga.tv/api/v3/login?platform=3",
|
||
{
|
||
...this.headers,
|
||
"Content-Type": "application/x-www-form-urlencoded;charset=utf-8"
|
||
},
|
||
`username=${account}&password=${base64}\n&salt=${salt}&platform=3&authorization=Token+&version=1.4.4&source=copyApp®ion=1&webp=1`
|
||
);
|
||
if (res.status === 200) {
|
||
let data = JSON.parse(res.body)
|
||
let token = data.results.token
|
||
this.saveData('token', token)
|
||
this.headers = {
|
||
"User-Agent": "COPY/" + CopyManga.copyVersion,
|
||
"Accept": "*/*",
|
||
"Accept-Encoding": "gzip",
|
||
"source": "copyApp",
|
||
"webp": "1",
|
||
"region": "1",
|
||
"version": CopyManga.copyVersion,
|
||
"authorization": `Token ${token}`,
|
||
"platform": "3",
|
||
}
|
||
return "ok"
|
||
} else {
|
||
throw `Invalid Status Code ${res.status}`
|
||
}
|
||
},
|
||
// callback when user log out
|
||
logout: () => {
|
||
this.deleteData('token')
|
||
},
|
||
registerWebsite: "https://www.copymanga.site/web/login/loginByAccount"
|
||
}
|
||
|
||
/// explore pages
|
||
explore = [
|
||
{
|
||
title: "拷贝漫画",
|
||
type: "singlePageWithMultiPart",
|
||
load: async () => {
|
||
let dataStr = await Network.get(
|
||
"https://api.copymanga.tv/api/v3/h5/homeIndex?platform=3",
|
||
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
|
||
}
|
||
}
|
||
]
|
||
|
||
category = {
|
||
title: "拷贝漫画",
|
||
parts: [
|
||
{
|
||
name: "主题",
|
||
type: "fixed",
|
||
categories: [ "全部",
|
||
"愛情", "歡樂向", "冒險", "奇幻", "百合", "校园", "科幻", "東方", "耽美", "生活",
|
||
"格鬥", "轻小说", "悬疑", "其他", "神鬼", "职场", "TL", "萌系", "治愈", "長條",
|
||
"四格", "节操", "舰娘", "竞技", "搞笑", "伪娘", "热血", "励志", "性转换", "彩色",
|
||
"後宮", "美食", "侦探", "AA", "音乐舞蹈", "魔幻", "战争", "历史", "异世界", "惊悚",
|
||
"机战", "都市", "穿越", "恐怖", "C100", "重生", "C99", "C101", "C97", "C96", "生存",
|
||
"宅系", "武侠", "C98", "C95", "FATE", "转生", "無修正", "仙侠", "LoveLive"
|
||
],
|
||
categoryParams: [ "",
|
||
"aiqing", "huanlexiang", "maoxian", "qihuan", "baihe", "xiaoyuan", "kehuan", "dongfang", "danmei", "shenghuo",
|
||
"gedou", "qingxiaoshuo", "xuanyi", "qita", "shengui", "zhichang", "teenslove", "mengxi", "zhiyu", "changtiao",
|
||
"sige", "jiecao", "jianniang", "jingji", "gaoxiao", "weiniang", "rexue", "lizhi", "xingzhuanhuan", "COLOR",
|
||
"hougong", "meishi", "zhentan", "aa", "yinyuewudao", "mohuan", "zhanzheng", "lishi", "yishijie", "jingsong",
|
||
"jizhan", "dushi", "chuanyue", "kongbu", "comiket100", "chongsheng", "comiket99", "comiket101", "comiket97", "comiket96", "shengcun",
|
||
"zhaixi", "wuxia", "C98", "comiket95", "fate", "zhuansheng", "Uncensored", "xianxia", "loveLive"
|
||
],
|
||
itemType: "category"
|
||
}
|
||
]
|
||
}
|
||
|
||
categoryComics = {
|
||
load: async (category, param, options, page) => {
|
||
// 如果传入了category,则匹配其对应的param
|
||
if (category && !param) {
|
||
const categories = this.category.parts[0].categories;
|
||
const categoryParams = this.category.parts[0].categoryParams;
|
||
const index = categories.indexOf(category);
|
||
if (index !== -1) {
|
||
param = categoryParams[index];
|
||
} else {
|
||
param = "";
|
||
}
|
||
}
|
||
options = options.map(e => e.replace("*", "-"))
|
||
let res = await Network.get(
|
||
`https://api.copymanga.tv/api/v3/comics?limit=21&offset=${(page - 1) * 21}&ordering=${options[1]}&theme=${param}&top=${options[0]}&platform=3`,
|
||
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: [
|
||
{
|
||
options: [
|
||
"-全部",
|
||
"japan-日漫",
|
||
"korea-韩漫",
|
||
"west-美漫",
|
||
"finish-已完结"
|
||
],
|
||
notShowWhen: null,
|
||
showWhen: null
|
||
},
|
||
{
|
||
options: [
|
||
"*datetime_updated-时间倒序",
|
||
"datetime_updated-时间正序",
|
||
"*popular-热度倒序",
|
||
"popular-热度正序",
|
||
],
|
||
notShowWhen: null,
|
||
showWhen: null
|
||
}
|
||
]
|
||
}
|
||
|
||
search = {
|
||
load: async (keyword, options, page) => {
|
||
let author;
|
||
if (keyword.startsWith("作者:")) {
|
||
author = keyword.substring("作者:".length).trim();
|
||
}
|
||
// 通过onClickTag传入时有"作者:"前缀,处理这种情况
|
||
if (author && author in this.author_path_word_dict){
|
||
let path_word = encodeURIComponent(this.author_path_word_dict[author]);
|
||
var 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`,
|
||
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" ? "https://www.copymanga.tv/api/kb/web/searchbc/comics" : "https://api.copymanga.tv/api/v3/search/comic"
|
||
var res = await Network.get(
|
||
`${search_url}?limit=21&offset=${(page - 1) * 21}&q=${keyword}&q_type=${q_type}&platform=3`,
|
||
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(
|
||
`https://api.copymanga.tv/api/v3/comic2/${comicId}?platform=3`,
|
||
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(
|
||
"https://api.copymanga.tv/api/v3/member/collect/comic?platform=3",
|
||
{
|
||
...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(
|
||
`https://api.copymanga.tv/api/v3/member/collect/comics?limit=21&offset=${(page - 1) * 21}&free_type=1&ordering=-datetime_updated&platform=3`,
|
||
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) => {
|
||
async function getChapters(id) {
|
||
var res = await Network.get(
|
||
`https://api.copymanga.tv/api/v3/comic/${id}/group/default/chapters?limit=500&offset=0&platform=3`,
|
||
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(
|
||
`https://api.copymanga.tv/api/v3/comic/chongjingchengweimofashaonv/group/default/chapters?limit=500&offset=${offset}&platform=3`,
|
||
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;
|
||
}
|
||
|
||
async function getFavoriteStatus(id) {
|
||
let res = await Network.get(`https://api.copymanga.tv/api/v3/comic2/${id}/query?platform=3`, 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(
|
||
`https://api.copymanga.tv/api/v3/comic2/${id}?platform=3`,
|
||
this.headers
|
||
),
|
||
getChapters.bind(this)(id),
|
||
getFavoriteStatus.bind(this)(id)
|
||
])
|
||
|
||
if (results[0].status !== 200) {
|
||
throw `Invalid status code: ${res.status}`;
|
||
}
|
||
|
||
let comicData = JSON.parse(results[0].body).results.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;
|
||
|
||
|
||
return {
|
||
title: title,
|
||
cover: cover,
|
||
description: description,
|
||
tags: {
|
||
"作者": authors,
|
||
"更新": [updateTime],
|
||
"标签": tags
|
||
},
|
||
chapters: results[1],
|
||
isFavorite: results[2],
|
||
subId: comicData.uuid
|
||
}
|
||
},
|
||
loadEp: async (comicId, epId) => {
|
||
let res = await Network.get(
|
||
`https://api.copymanga.tv/api/v3/comic/${comicId}/chapter2/${epId}?platform=3`,
|
||
this.headers
|
||
);
|
||
|
||
if (res.status !== 200){
|
||
throw `Invalid status code: ${res.status}`;
|
||
}
|
||
|
||
let data = JSON.parse(res.body);
|
||
|
||
let imagesUrls = data.results.chapter.contents.map(e => e.url)
|
||
|
||
let orders = data.results.chapter.words
|
||
|
||
let images = imagesUrls.map(e => "")
|
||
|
||
for(let i=0; i < imagesUrls.length; i++){
|
||
images[orders[i]] = imagesUrls[i]
|
||
}
|
||
|
||
return {
|
||
images: images
|
||
}
|
||
},
|
||
loadComments: async (comicId, subId, page, replyTo) => {
|
||
let url = `https://api.copymanga.tv/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){
|
||
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: e.user_name,
|
||
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(
|
||
`https://api.copymanga.tv/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 = {
|
||
search_api: {
|
||
// title
|
||
title: "搜索方式",
|
||
// type: input, select, switch
|
||
type: "select",
|
||
// options
|
||
options: [
|
||
{
|
||
value: 'baseAPI',
|
||
text: '基础API'
|
||
},
|
||
{
|
||
value: 'webAPI',
|
||
text: '网页端API(可搜屏蔽作)'
|
||
}
|
||
],
|
||
default: 'baseAPI'
|
||
}
|
||
}
|
||
}
|