mirror of
https://github.com/venera-app/venera-configs.git
synced 2025-09-27 00:27:23 +00:00
update template & add picacg
This commit is contained in:
649
picacg.js
Normal file
649
picacg.js
Normal file
@@ -0,0 +1,649 @@
|
||||
class Picacg extends ComicSource {
|
||||
name = "Picacg"
|
||||
|
||||
key = "picacg"
|
||||
|
||||
version = "1.0.0"
|
||||
|
||||
minAppVersion = "1.0.0"
|
||||
|
||||
url = "https://raw.githubusercontent.com/venera-app/venera_configs/master/picacg.js"
|
||||
|
||||
api = "https://picaapi.picacomic.com"
|
||||
|
||||
apiKey = "C69BAF41DA5ABD1FFEDC6D2FEA56B";
|
||||
|
||||
createSignature(path, nonce, time, method) {
|
||||
let data = path + time + nonce + method + this.apiKey
|
||||
let key = '~d}$Q7$eIni=V)9\\RK/P.RM4;9[7|@/CA}b~OW!3?EV`:<>M7pddUBL5n|0/*Cn'
|
||||
let s = Convert.encodeUtf8(key)
|
||||
let h = Convert.encodeUtf8(data.toLowerCase())
|
||||
return Convert.hmacString(s, h, 'sha256')
|
||||
}
|
||||
|
||||
buildHeaders(method, path, token) {
|
||||
let uuid = createUuid()
|
||||
let nonce = uuid.replace(/-/g, '')
|
||||
let time = (new Date().getTime() / 1000).toFixed(0)
|
||||
let signature = this.createSignature(path, nonce, time, method.toUpperCase())
|
||||
return {
|
||||
"api-key": "C69BAF41DA5ABD1FFEDC6D2FEA56B",
|
||||
"accept": "application/vnd.picacomic.com.v1+json",
|
||||
"app-channel": this.loadSetting('appChannel'),
|
||||
"authorization": token ?? "",
|
||||
"time": time,
|
||||
"nonce": nonce,
|
||||
"app-version": "2.2.1.3.3.4",
|
||||
"app-uuid": "defaultUuid",
|
||||
"image-quality": this.loadSetting('imageQuality'),
|
||||
"app-platform": "android",
|
||||
"app-build-version": "45",
|
||||
"Content-Type": "application/json; charset=UTF-8",
|
||||
"user-agent": "okhttp/3.8.1",
|
||||
"version": "v1.4.1",
|
||||
"Host": "picaapi.picacomic.com",
|
||||
"signature": signature,
|
||||
}
|
||||
}
|
||||
|
||||
account = {
|
||||
login: async (account, pwd) => {
|
||||
let res = await Network.post(
|
||||
`${this.api}/auth/sign-in`,
|
||||
this.buildHeaders('POST', 'auth/sign-in'),
|
||||
{
|
||||
email: account,
|
||||
password: pwd
|
||||
})
|
||||
|
||||
if (res.status === 200) {
|
||||
let json = JSON.parse(res.body)
|
||||
if (!json.data?.token) {
|
||||
throw 'Failed to get token\nResponse: ' + res.body
|
||||
}
|
||||
this.saveData('token', json.data.token)
|
||||
return 'ok'
|
||||
}
|
||||
|
||||
throw 'Failed to login'
|
||||
},
|
||||
|
||||
logout: () => {
|
||||
this.deleteData('token')
|
||||
},
|
||||
|
||||
registerWebsite: "https://manhuabika.com/pregister/?"
|
||||
}
|
||||
|
||||
parseComic(comic) {
|
||||
let tags = []
|
||||
tags.push(...(comic.tags ?? []))
|
||||
tags.push(...(comic.categories ?? []))
|
||||
return new Comic({
|
||||
id: comic._id,
|
||||
title: comic.title,
|
||||
subTitle: comic.author,
|
||||
cover: comic.thumb.fileServer + '/static/' + comic.thumb.path,
|
||||
tags: tags,
|
||||
description: `${comic.totalLikes} likes`,
|
||||
maxPage: comic.pagesCount,
|
||||
})
|
||||
}
|
||||
|
||||
explore = [
|
||||
{
|
||||
title: "Picacg Random",
|
||||
type: "multiPageComicList",
|
||||
load: async (page) => {
|
||||
if (!this.isLogged) {
|
||||
throw 'Not logged in'
|
||||
}
|
||||
let res = await Network.get(
|
||||
`${this.api}/comics/random`,
|
||||
this.buildHeaders('GET', 'comics/random', this.loadData('token'))
|
||||
)
|
||||
if (res.status !== 200) {
|
||||
throw 'Invalid status code: ' + res.status
|
||||
}
|
||||
let data = JSON.parse(res.body)
|
||||
let comics = []
|
||||
data.data.comics.forEach(c => {
|
||||
comics.push(this.parseComic(c))
|
||||
})
|
||||
return {
|
||||
comics: comics
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "Picacg Latest",
|
||||
type: "multiPageComicList",
|
||||
load: async (page) => {
|
||||
if (!this.isLogged) {
|
||||
throw 'Not logged in'
|
||||
}
|
||||
let res = await Network.get(
|
||||
`${this.api}/comics?page=${page}&s=dd`,
|
||||
this.buildHeaders('GET', `comics?page=${page}&s=dd`, this.loadData('token'))
|
||||
)
|
||||
if (res.status !== 200) {
|
||||
throw 'Invalid status code: ' + res.status
|
||||
}
|
||||
let data = JSON.parse(res.body)
|
||||
let comics = []
|
||||
data.data.comics.docs.forEach(c => {
|
||||
comics.push(this.parseComic(c))
|
||||
})
|
||||
return {
|
||||
comics: comics
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
/// 分类页面
|
||||
/// 一个漫画源只能有一个分类页面, 也可以没有, 设置为null禁用分类页面
|
||||
category = {
|
||||
/// 标题, 同时为标识符, 不能与其他漫画源的分类页面重复
|
||||
title: "Picacg",
|
||||
parts: [
|
||||
{
|
||||
name: "主题",
|
||||
type: "fixed",
|
||||
categories: [
|
||||
"大家都在看",
|
||||
"大濕推薦",
|
||||
"那年今天",
|
||||
"官方都在看",
|
||||
"嗶咔漢化",
|
||||
"全彩",
|
||||
"長篇",
|
||||
"同人",
|
||||
"短篇",
|
||||
"圓神領域",
|
||||
"碧藍幻想",
|
||||
"CG雜圖",
|
||||
"英語 ENG",
|
||||
"生肉",
|
||||
"純愛",
|
||||
"百合花園",
|
||||
"耽美花園",
|
||||
"偽娘哲學",
|
||||
"後宮閃光",
|
||||
"扶他樂園",
|
||||
"單行本",
|
||||
"姐姐系",
|
||||
"妹妹系",
|
||||
"SM",
|
||||
"性轉換",
|
||||
"足の恋",
|
||||
"人妻",
|
||||
"NTR",
|
||||
"強暴",
|
||||
"非人類",
|
||||
"艦隊收藏",
|
||||
"Love Live",
|
||||
"SAO 刀劍神域",
|
||||
"Fate",
|
||||
"東方",
|
||||
"WEBTOON",
|
||||
"禁書目錄",
|
||||
"歐美",
|
||||
"Cosplay",
|
||||
"重口地帶"
|
||||
],
|
||||
itemType: "category",
|
||||
}
|
||||
],
|
||||
enableRankingPage: true,
|
||||
}
|
||||
|
||||
/// 分类漫画页面, 即点击分类标签后进入的页面
|
||||
categoryComics = {
|
||||
load: async (category, param, options, page) => {
|
||||
let type = param ?? 'c'
|
||||
let res = await Network.get(
|
||||
`${this.api}/comics?page=${page}&${type}=${encodeURIComponent(category)}&s=${options[0]}`,
|
||||
this.buildHeaders('GET', `comics?page=${page}&${type}=${encodeURIComponent(category)}&s=${options[0]}`, this.loadData('token'))
|
||||
)
|
||||
if (res.status !== 200) {
|
||||
throw 'Invalid status code: ' + res.status
|
||||
}
|
||||
let data = JSON.parse(res.body)
|
||||
let comics = []
|
||||
data.data.comics.docs.forEach(c => {
|
||||
comics.push(this.parseComic(c))
|
||||
})
|
||||
return {
|
||||
comics: comics,
|
||||
maxPage: data.data.comics.pages
|
||||
}
|
||||
},
|
||||
// 提供选项
|
||||
optionList: [
|
||||
{
|
||||
options: [
|
||||
"dd-New to old",
|
||||
"da-Old to new",
|
||||
"ld-Most likes",
|
||||
"vd-Most nominated",
|
||||
],
|
||||
}
|
||||
],
|
||||
ranking: {
|
||||
options: [
|
||||
"H24-Day",
|
||||
"D7-Week",
|
||||
"D30-Month",
|
||||
],
|
||||
load: async (option, page) => {
|
||||
let res = await Network.get(
|
||||
`${this.api}/comics/leaderboard?tt=${option}&ct=VC`,
|
||||
this.buildHeaders('GET', `comics/leaderboard?tt=${option}&ct=VC`, this.loadData('token'))
|
||||
)
|
||||
if (res.status !== 200) {
|
||||
throw 'Invalid status code: ' + res.status
|
||||
}
|
||||
let data = JSON.parse(res.body)
|
||||
let comics = []
|
||||
data.data.comics.forEach(c => {
|
||||
comics.push(this.parseComic(c))
|
||||
})
|
||||
return {
|
||||
comics: comics,
|
||||
maxPage: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 搜索
|
||||
search = {
|
||||
load: async (keyword, options, page) => {
|
||||
let res = await Network.post(
|
||||
`${this.api}/comics/advanced-search?page=${page}`,
|
||||
this.buildHeaders('POST', `comics/advanced-search?page=${page}`, this.loadData('token')),
|
||||
JSON.stringify({
|
||||
keyword: keyword,
|
||||
sort: options[0],
|
||||
})
|
||||
)
|
||||
if (res.status !== 200) {
|
||||
throw 'Invalid status code: ' + res.status
|
||||
}
|
||||
let data = JSON.parse(res.body)
|
||||
let comics = []
|
||||
data.data.comics.docs.forEach(c => {
|
||||
comics.push(this.parseComic(c))
|
||||
})
|
||||
return {
|
||||
comics: comics,
|
||||
maxPage: data.data.comics.pages
|
||||
}
|
||||
},
|
||||
optionList: [
|
||||
{
|
||||
options: [
|
||||
"dd-New to old",
|
||||
"da-Old to new",
|
||||
"ld-Most likes",
|
||||
"vd-Most nominated",
|
||||
],
|
||||
label: "Sort"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
/// 收藏
|
||||
favorites = {
|
||||
multiFolder: false,
|
||||
/// 添加或者删除收藏
|
||||
addOrDelFavorite: async (comicId, folderId, isAdding) => {
|
||||
let res = await Network.post(
|
||||
`${this.api}/comics/${comicId}/favourite`,
|
||||
this.buildHeaders('POST', `comics/${comicId}/favourite`, this.loadData('token')),
|
||||
'{}'
|
||||
)
|
||||
if(res.status !== 200) {
|
||||
throw 'Invalid status code: ' + res.status
|
||||
}
|
||||
return 'ok'
|
||||
},
|
||||
/// 加载漫画
|
||||
loadComics: async (page, folder) => {
|
||||
let sort = this.loadSetting('favoriteSort')
|
||||
let res = await Network.get(
|
||||
`${this.api}/users/favourite?page=${page}&s=${sort}`,
|
||||
this.buildHeaders('GET', `users/favourite?page=${page}&s=${sort}`, this.loadData('token'))
|
||||
)
|
||||
if (res.status !== 200) {
|
||||
throw 'Invalid status code: ' + res.status
|
||||
}
|
||||
let data = JSON.parse(res.body)
|
||||
let comics = []
|
||||
data.data.comics.docs.forEach(c => {
|
||||
comics.push(this.parseComic(c))
|
||||
})
|
||||
return {
|
||||
comics: comics,
|
||||
maxPage: data.data.comics.pages
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 单个漫画相关
|
||||
comic = {
|
||||
// 加载漫画信息
|
||||
loadInfo: async (id) => {
|
||||
let infoLoader = async () => {
|
||||
let res = await Network.get(
|
||||
`${this.api}/comics/${id}`,
|
||||
this.buildHeaders('GET', `comics/${id}`, this.loadData('token'))
|
||||
)
|
||||
if (res.status !== 200) {
|
||||
throw 'Invalid status code: ' + res.status
|
||||
}
|
||||
let data = JSON.parse(res.body)
|
||||
return data.data.comic
|
||||
}
|
||||
let epsLoader = async () => {
|
||||
let eps = new Map()
|
||||
let i = 1;
|
||||
let j = 1;
|
||||
while(true) {
|
||||
let res = await Network.get(
|
||||
`${this.api}/comics/${id}/eps?page=${i}`,
|
||||
this.buildHeaders('GET', `comics/${id}/eps?page=${i}`, this.loadData('token'))
|
||||
)
|
||||
if (res.status !== 200) {
|
||||
throw 'Invalid status code: ' + res.status
|
||||
}
|
||||
let data = JSON.parse(res.body)
|
||||
data.data.eps.docs.forEach(e => {
|
||||
eps.set(j.toString(), e.title)
|
||||
j++
|
||||
})
|
||||
if(data.data.eps.pages === i) {
|
||||
break
|
||||
}
|
||||
i++
|
||||
}
|
||||
return eps
|
||||
}
|
||||
let relatedLoader = async () => {
|
||||
let res = await Network.get(
|
||||
`${this.api}/comics/${id}/recommendation`,
|
||||
this.buildHeaders('GET', `comics/${id}/recommendation`, this.loadData('token'))
|
||||
)
|
||||
if (res.status !== 200) {
|
||||
throw 'Invalid status code: ' + res.status
|
||||
}
|
||||
let data = JSON.parse(res.body)
|
||||
let comics = []
|
||||
data.data.comics.forEach(c => {
|
||||
comics.push(this.parseComic(c))
|
||||
})
|
||||
return comics
|
||||
}
|
||||
let [info, eps, related] = await Promise.all([infoLoader(), epsLoader(), relatedLoader()])
|
||||
let tags = {}
|
||||
if(info.author) {
|
||||
tags['Author'] = [info.author];
|
||||
}
|
||||
if(info.chineseTeam) {
|
||||
tags['Chinese Team'] = [info.chineseTeam];
|
||||
}
|
||||
return new ComicDetails({
|
||||
title: info.title,
|
||||
cover: info.thumb.fileServer + '/static/' + info.thumb.path,
|
||||
description: info.description,
|
||||
tags: {
|
||||
...tags,
|
||||
'Categories': info.categories,
|
||||
'Tags': info.tags,
|
||||
},
|
||||
chapters: eps,
|
||||
isFavorite: info.isFavourite ?? false,
|
||||
isLiked: info.isLiked ?? false,
|
||||
recommend: related,
|
||||
commentCount: info.commentsCount,
|
||||
likesCount: info.likesCount,
|
||||
uploader: info._creator.name,
|
||||
updateTime: info.updated_at,
|
||||
})
|
||||
},
|
||||
// 获取章节图片
|
||||
loadEp: async (comicId, epId) => {
|
||||
let images = []
|
||||
let i = 1
|
||||
while(true) {
|
||||
let res = await Network.get(
|
||||
`${this.api}/comics/${comicId}/order/${epId}/pages?page=${i}`,
|
||||
this.buildHeaders('GET', `comics/${comicId}/order/${epId}/pages?page=${i}`, this.loadData('token'))
|
||||
)
|
||||
if (res.status !== 200) {
|
||||
throw 'Invalid status code: ' + res.status
|
||||
}
|
||||
let data = JSON.parse(res.body)
|
||||
data.data.pages.docs.forEach(p => {
|
||||
images.push(p.media.fileServer + '/static/' + p.media.path)
|
||||
})
|
||||
if(data.data.pages.pages === i) {
|
||||
break
|
||||
}
|
||||
i++
|
||||
}
|
||||
return {
|
||||
images: images
|
||||
}
|
||||
},
|
||||
likeComic: async (id, isLike) => {
|
||||
var res = await Network.post(
|
||||
`${this.api}/comics/${id}/like`,
|
||||
this.buildHeaders('POST', `comics/${id}/like`, this.loadData('token')),
|
||||
{}
|
||||
);
|
||||
if (res.status !== 200) {
|
||||
throw 'Invalid status code: ' + res.status
|
||||
}
|
||||
return 'ok'
|
||||
},
|
||||
// 加载评论
|
||||
loadComments: async (comicId, subId, page, replyTo) => {
|
||||
function parseComment(c) {
|
||||
return new Comment({
|
||||
userName: c._user.name,
|
||||
avatar: c._user.avatar ? c._user.avatar.fileServer + '/static/' + c._user.avatar.path : undefined,
|
||||
id: c._id,
|
||||
content: c.content,
|
||||
isLiked: c.isLiked,
|
||||
score: c.likesCount ?? 0,
|
||||
replyCount: c.commentsCount,
|
||||
time: c.created_at,
|
||||
})
|
||||
}
|
||||
let comments = []
|
||||
|
||||
let maxPage = 1
|
||||
|
||||
if(replyTo) {
|
||||
let res = await Network.get(
|
||||
`${this.api}/comments/${replyTo}/childrens?page=${page}`,
|
||||
this.buildHeaders('GET', `comments/${replyTo}/childrens?page=${page}`, this.loadData('token'))
|
||||
)
|
||||
if (res.status !== 200) {
|
||||
throw 'Invalid status code: ' + res.status
|
||||
}
|
||||
let data = JSON.parse(res.body)
|
||||
data.data.comments.docs.forEach(c => {
|
||||
comments.push(parseComment(c))
|
||||
})
|
||||
maxPage = data.data.comments.pages
|
||||
} else {
|
||||
let res = await Network.get(
|
||||
`${this.api}/comics/${comicId}/comments?page=${page}`,
|
||||
this.buildHeaders('GET', `comics/${comicId}/comments?page=${page}`, this.loadData('token'))
|
||||
)
|
||||
if (res.status !== 200) {
|
||||
throw 'Invalid status code: ' + res.status
|
||||
}
|
||||
let data = JSON.parse(res.body)
|
||||
data.data.comments.docs.forEach(c => {
|
||||
comments.push(parseComment(c))
|
||||
})
|
||||
maxPage = data.data.comments.pages
|
||||
}
|
||||
return {
|
||||
comments: comments,
|
||||
maxPage: maxPage
|
||||
}
|
||||
},
|
||||
// 发送评论, 返回任意值表示成功
|
||||
sendComment: async (comicId, subId, content, replyTo) => {
|
||||
if(replyTo) {
|
||||
let res = await Network.post(
|
||||
`${this.api}/comments/${replyTo}`,
|
||||
this.buildHeaders('POST', `/comments/${replyTo}`, this.loadData('token')),
|
||||
JSON.stringify({
|
||||
content: content
|
||||
})
|
||||
)
|
||||
if (res.status !== 200) {
|
||||
throw 'Invalid status code: ' + res.status
|
||||
}
|
||||
} else {
|
||||
let res = await Network.post(
|
||||
`${this.api}/comics/${comicId}/comments`,
|
||||
this.buildHeaders('POST', `/comics/${comicId}/comments`, this.loadData('token')),
|
||||
JSON.stringify({
|
||||
content: content
|
||||
})
|
||||
)
|
||||
if (res.status !== 200) {
|
||||
throw 'Invalid status code: ' + res.status
|
||||
}
|
||||
}
|
||||
return 'ok'
|
||||
},
|
||||
likeComment: async (comicId, subId, commentId, isLike) => {
|
||||
let res = await Network.post(
|
||||
`${this.api}/comments/${commentId}/like`,
|
||||
this.buildHeaders('POST', `/comments/${commentId}/like`, this.loadData('token')),
|
||||
'{}'
|
||||
)
|
||||
if (res.status !== 200) {
|
||||
throw 'Invalid status code: ' + res.status
|
||||
}
|
||||
return 'ok'
|
||||
},
|
||||
onClickTag: (namespace, tag) => {
|
||||
if(namespace === 'Author') {
|
||||
return {
|
||||
action: 'category',
|
||||
keyword: tag,
|
||||
param: 'a',
|
||||
}
|
||||
} else if (namespace === 'Categories') {
|
||||
return {
|
||||
action: 'category',
|
||||
keyword: tag,
|
||||
param: 'c',
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
action: 'search',
|
||||
keyword: tag,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
settings = {
|
||||
'imageQuality': {
|
||||
type: 'select',
|
||||
title: 'Image quality',
|
||||
options: [
|
||||
{
|
||||
value: 'original',
|
||||
},
|
||||
{
|
||||
value: 'medium'
|
||||
},
|
||||
{
|
||||
value: 'low'
|
||||
}
|
||||
],
|
||||
default: 'original',
|
||||
},
|
||||
'appChannel': {
|
||||
type: 'select',
|
||||
title: 'App channel',
|
||||
options: [
|
||||
{
|
||||
value: '1',
|
||||
},
|
||||
{
|
||||
value: '2'
|
||||
},
|
||||
{
|
||||
value: '3'
|
||||
}
|
||||
],
|
||||
default: '3',
|
||||
},
|
||||
'favoriteSort': {
|
||||
type: 'select',
|
||||
title: 'Favorite sort',
|
||||
options: [
|
||||
{
|
||||
value: 'dd',
|
||||
text: 'New to old'
|
||||
},
|
||||
{
|
||||
value: 'da',
|
||||
text: 'Old to new'
|
||||
},
|
||||
],
|
||||
default: 'dd',
|
||||
}
|
||||
}
|
||||
|
||||
translation = {
|
||||
'zh_CN': {
|
||||
'Picacg Random': "哔咔随机",
|
||||
'Picacg Latest': "哔咔最新",
|
||||
'New to old': "新到旧",
|
||||
'Old to new': "旧到新",
|
||||
'Most likes': "最多喜欢",
|
||||
'Most nominated': "最多指名",
|
||||
'Day': "日",
|
||||
'Week': "周",
|
||||
'Month': "月",
|
||||
'Author': "作者",
|
||||
'Chinese Team': "汉化组",
|
||||
'Categories': "分类",
|
||||
'Tags': "标签",
|
||||
'Image quality': "图片质量",
|
||||
'App channel': "分流",
|
||||
'Favorite sort': "收藏排序",
|
||||
},
|
||||
'zh_TW': {
|
||||
'Picacg Random': "哔咔隨機",
|
||||
'Picacg Latest': "哔咔最新",
|
||||
'New to old': "新到舊",
|
||||
'Old to new': "舊到新",
|
||||
'Most likes': "最多喜歡",
|
||||
'Most nominated': "最多指名",
|
||||
'Day': "日",
|
||||
'Week': "周",
|
||||
'Month': "月",
|
||||
'Author': "作者",
|
||||
'Chinese Team': "漢化組",
|
||||
'Categories': "分類",
|
||||
'Tags': "標籤",
|
||||
'Image quality': "圖片質量",
|
||||
'App channel': "分流",
|
||||
'Favorite sort': "收藏排序",
|
||||
},
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user