Files
venera-configs/copy_manga.js
2024-12-24 12:48:40 +08:00

612 lines
22 KiB
JavaScript
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.

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&region=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'
}
}
}