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:
@@ -1,3 +1,11 @@
|
|||||||
# venera-configs
|
# venera-configs
|
||||||
|
|
||||||
Configuration file repository for venera
|
Configuration file repository for venera
|
||||||
|
|
||||||
|
## Create a new configuration
|
||||||
|
|
||||||
|
1. Download `_template_.js`, `_venera_.js`, put them in the same directory
|
||||||
|
2. Rename `_template_.js` to `your_config_name.js`
|
||||||
|
3. Edit `your_config_name.js` to your needs.
|
||||||
|
- The `_template_.js` file contains comments to help you with that.
|
||||||
|
- The `_venera_.js` is used for code completion in your IDE.
|
626
_template_.js
Normal file
626
_template_.js
Normal file
@@ -0,0 +1,626 @@
|
|||||||
|
class NewComicSource extends ComicSource {
|
||||||
|
// Note: The fields which are marked as [Optional] should be removed if not used
|
||||||
|
|
||||||
|
// name of the source
|
||||||
|
name = ""
|
||||||
|
|
||||||
|
// unique id of the source
|
||||||
|
key = ""
|
||||||
|
|
||||||
|
version = "1.0.0"
|
||||||
|
|
||||||
|
minAppVersion = "1.0.0"
|
||||||
|
|
||||||
|
// update url
|
||||||
|
url = ""
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [Optional] init function
|
||||||
|
*/
|
||||||
|
init() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// [Optional] account related
|
||||||
|
account = {
|
||||||
|
/**
|
||||||
|
* login, return any value to indicate success
|
||||||
|
* @param account {string}
|
||||||
|
* @param pwd {string}
|
||||||
|
* @returns {Promise<any>}
|
||||||
|
*/
|
||||||
|
login: async (account, pwd) => {
|
||||||
|
/*
|
||||||
|
Use Network to send request
|
||||||
|
Use this.saveData to save data
|
||||||
|
`account` and `pwd` will be saved to local storage automatically if login success
|
||||||
|
```
|
||||||
|
let res = await Network.post('https://example.com/login', {
|
||||||
|
'content-type': 'application/x-www-form-urlencoded;charset=utf-8'
|
||||||
|
}, `account=${account}&password=${pwd}`)
|
||||||
|
|
||||||
|
if(res.status == 200) {
|
||||||
|
let json = JSON.parse(res.body)
|
||||||
|
this.saveData('token', json.token)
|
||||||
|
return 'ok'
|
||||||
|
}
|
||||||
|
|
||||||
|
throw 'Failed to login'
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* logout function, clear account related data
|
||||||
|
*/
|
||||||
|
logout: () => {
|
||||||
|
/*
|
||||||
|
```
|
||||||
|
this.deleteData('token')
|
||||||
|
Network.deleteCookies('https://example.com')
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
},
|
||||||
|
|
||||||
|
// {string?} - register url
|
||||||
|
registerWebsite: null
|
||||||
|
}
|
||||||
|
|
||||||
|
// explore page list
|
||||||
|
explore = [
|
||||||
|
{
|
||||||
|
// title of the page.
|
||||||
|
// title is used to identify the page, it should be unique
|
||||||
|
title: "",
|
||||||
|
|
||||||
|
/// singlePageWithMultiPart or multiPageComicList
|
||||||
|
type: "singlePageWithMultiPart",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* load function
|
||||||
|
* @param page {number | null} - page number, null for `singlePageWithMultiPart` type
|
||||||
|
* @returns {{}} - for `singlePageWithMultiPart` type, return {[string]: Comic[]}; for `multiPageComicList` type, return {comics: Comic[], maxPage: number}
|
||||||
|
*/
|
||||||
|
load: async (page) => {
|
||||||
|
/*
|
||||||
|
```
|
||||||
|
let res = await Network.get("https://example.com")
|
||||||
|
|
||||||
|
if (res.status !== 200) {
|
||||||
|
throw `Invalid status code: ${res.status}`
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = JSON.parse(res.body)
|
||||||
|
|
||||||
|
function parseComic(comic) {
|
||||||
|
// ...
|
||||||
|
|
||||||
|
return new Comic({
|
||||||
|
id: id,
|
||||||
|
title: title,
|
||||||
|
subTitle: author,
|
||||||
|
cover: cover,
|
||||||
|
tags: tags,
|
||||||
|
description: description
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let comics = {}
|
||||||
|
comics["hot"] = data["results"]["recComics"].map(parseComic)
|
||||||
|
comics["latest"] = data["results"]["newComics"].map(parseComic)
|
||||||
|
|
||||||
|
return comics
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
// categories
|
||||||
|
category = {
|
||||||
|
/// title of the category page, used to identify the page, it should be unique
|
||||||
|
title: "",
|
||||||
|
parts: [
|
||||||
|
{
|
||||||
|
// title of the part
|
||||||
|
name: "Theme",
|
||||||
|
|
||||||
|
// fixed or random
|
||||||
|
// if random, need to provide `randomNumber` field, which indicates the number of comics to display at the same time
|
||||||
|
type: "fixed",
|
||||||
|
|
||||||
|
// number of comics to display at the same time
|
||||||
|
// randomNumber: 5,
|
||||||
|
|
||||||
|
categories: ["All", "Adventure", "School"],
|
||||||
|
|
||||||
|
// category or search
|
||||||
|
// if `category`, use categoryComics.load to load comics
|
||||||
|
// if `search`, use search.load to load comics
|
||||||
|
itemType: "category",
|
||||||
|
|
||||||
|
// [Optional] must have same length as categories, used to provide loading param for each category
|
||||||
|
categoryParams: ["all", "adventure", "school"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
// enable ranking page
|
||||||
|
enableRankingPage: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// category comic loading related
|
||||||
|
categoryComics = {
|
||||||
|
/**
|
||||||
|
* load comics of a category
|
||||||
|
* @param category {string} - category name
|
||||||
|
* @param param {string?} - category param
|
||||||
|
* @param options {string[]} - options from optionList
|
||||||
|
* @param page {number} - page number
|
||||||
|
* @returns {Promise<{comics: Comic[], maxPage: number}>}
|
||||||
|
*/
|
||||||
|
load: async (category, param, options, page) => {
|
||||||
|
/*
|
||||||
|
```
|
||||||
|
let data = JSON.parse((await Network.get('...')).body)
|
||||||
|
let maxPage = data.maxPage
|
||||||
|
|
||||||
|
function parseComic(comic) {
|
||||||
|
// ...
|
||||||
|
|
||||||
|
return new Comic({
|
||||||
|
id: id,
|
||||||
|
title: title,
|
||||||
|
subTitle: author,
|
||||||
|
cover: cover,
|
||||||
|
tags: tags,
|
||||||
|
description: description
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
comics: data.list.map(parseComic),
|
||||||
|
maxPage: maxPage
|
||||||
|
}
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
},
|
||||||
|
// provide options for category comic loading
|
||||||
|
optionList: [
|
||||||
|
{
|
||||||
|
// For a single option, use `-` to separate the value and text, left for value, right for text
|
||||||
|
options: [
|
||||||
|
"newToOld-New to Old",
|
||||||
|
"oldToNew-Old to New"
|
||||||
|
],
|
||||||
|
// [Optional] {string[]} - show this option only when the value not in the list
|
||||||
|
notShowWhen: null,
|
||||||
|
// [Optional] {string[]} - show this option only when the value in the list
|
||||||
|
showWhen: null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
ranking: {
|
||||||
|
// For a single option, use `-` to separate the value and text, left for value, right for text
|
||||||
|
options: [
|
||||||
|
"day-Day",
|
||||||
|
"week-Week"
|
||||||
|
],
|
||||||
|
/**
|
||||||
|
* load ranking comics
|
||||||
|
* @param option {string} - option from optionList
|
||||||
|
* @param page {number} - page number
|
||||||
|
* @returns {Promise<{comics: Comic[], maxPage: number}>}
|
||||||
|
*/
|
||||||
|
load: async (option, page) => {
|
||||||
|
/*
|
||||||
|
```
|
||||||
|
let data = JSON.parse((await Network.get('...')).body)
|
||||||
|
let maxPage = data.maxPage
|
||||||
|
|
||||||
|
function parseComic(comic) {
|
||||||
|
// ...
|
||||||
|
|
||||||
|
return new Comic({
|
||||||
|
id: id,
|
||||||
|
title: title,
|
||||||
|
subTitle: author,
|
||||||
|
cover: cover,
|
||||||
|
tags: tags,
|
||||||
|
description: description
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
comics: data.list.map(parseComic),
|
||||||
|
maxPage: maxPage
|
||||||
|
}
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// search related
|
||||||
|
search = {
|
||||||
|
/**
|
||||||
|
* load search result
|
||||||
|
* @param keyword {string}
|
||||||
|
* @param options {string[]} - options from optionList
|
||||||
|
* @param page {number}
|
||||||
|
* @returns {Promise<{comics: Comic[], maxPage: number}>}
|
||||||
|
*/
|
||||||
|
load: async (keyword, options, page) => {
|
||||||
|
/*
|
||||||
|
```
|
||||||
|
let data = JSON.parse((await Network.get('...')).body)
|
||||||
|
let maxPage = data.maxPage
|
||||||
|
|
||||||
|
function parseComic(comic) {
|
||||||
|
// ...
|
||||||
|
|
||||||
|
return new Comic({
|
||||||
|
id: id,
|
||||||
|
title: title,
|
||||||
|
subTitle: author,
|
||||||
|
cover: cover,
|
||||||
|
tags: tags,
|
||||||
|
description: description
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
comics: data.list.map(parseComic),
|
||||||
|
maxPage: maxPage
|
||||||
|
}
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
},
|
||||||
|
|
||||||
|
// provide options for search
|
||||||
|
optionList: [
|
||||||
|
{
|
||||||
|
// For a single option, use `-` to separate the value and text, left for value, right for text
|
||||||
|
options: [
|
||||||
|
"0-time",
|
||||||
|
"1-popular"
|
||||||
|
],
|
||||||
|
// option label
|
||||||
|
label: "sort"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
// favorite related
|
||||||
|
favorites = {
|
||||||
|
// whether support multi folders
|
||||||
|
multiFolder: false,
|
||||||
|
/**
|
||||||
|
* add or delete favorite.
|
||||||
|
* throw `Login expired` to indicate login expired, App will automatically re-login and re-add/delete favorite
|
||||||
|
* @param comicId {string}
|
||||||
|
* @param folderId {string}
|
||||||
|
* @param isAdding {boolean} - true for add, false for delete
|
||||||
|
* @returns {Promise<any>} - return any value to indicate success
|
||||||
|
*/
|
||||||
|
addOrDelFavorite: async (comicId, folderId, isAdding) => {
|
||||||
|
/*
|
||||||
|
```
|
||||||
|
let res = await Network.post('...')
|
||||||
|
if (res.status === 401) {
|
||||||
|
throw `Login expired`;
|
||||||
|
}
|
||||||
|
return 'ok'
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* load favorite folders.
|
||||||
|
* throw `Login expired` to indicate login expired, App will automatically re-login retry.
|
||||||
|
* if comicId is not null, return favorite folders which contains the comic.
|
||||||
|
* @param comicId {string?}
|
||||||
|
* @returns {Promise<{folders: {[p: string]: string}, favorited: string[]}>} - `folders` is a map of folder id to folder name, `favorited` is a list of folder id which contains the comic
|
||||||
|
*/
|
||||||
|
loadFolders: async (comicId) => {
|
||||||
|
/*
|
||||||
|
```
|
||||||
|
let data = JSON.parse((await Network.get('...')).body)
|
||||||
|
|
||||||
|
let folders = {}
|
||||||
|
|
||||||
|
data.folders.forEach((f) => {
|
||||||
|
folders[f.id] = f.name
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
folders: folders,
|
||||||
|
favorited: data.favorited
|
||||||
|
}
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* load comics in a folder
|
||||||
|
* throw `Login expired` to indicate login expired, App will automatically re-login retry.
|
||||||
|
* @param page {number}
|
||||||
|
* @param folder {string?} - folder id, null for non-multi-folder
|
||||||
|
* @returns {Promise<{comics: Comic[], maxPage: number}>}
|
||||||
|
*/
|
||||||
|
loadComics: async (page, folder) => {
|
||||||
|
/*
|
||||||
|
```
|
||||||
|
let data = JSON.parse((await Network.get('...')).body)
|
||||||
|
let maxPage = data.maxPage
|
||||||
|
|
||||||
|
function parseComic(comic) {
|
||||||
|
// ...
|
||||||
|
|
||||||
|
return new Comic{
|
||||||
|
id: id,
|
||||||
|
title: title,
|
||||||
|
subTitle: author,
|
||||||
|
cover: cover,
|
||||||
|
tags: tags,
|
||||||
|
description: description
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
comics: data.list.map(parseComic),
|
||||||
|
maxPage: maxPage
|
||||||
|
}
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// single comic related
|
||||||
|
comic = {
|
||||||
|
/**
|
||||||
|
* load comic info
|
||||||
|
* @param id {string}
|
||||||
|
* @returns {Promise<ComicDetails>}
|
||||||
|
*/
|
||||||
|
loadInfo: async (id) => {
|
||||||
|
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* [Optional] load thumbnails of a comic
|
||||||
|
* @param id {string}
|
||||||
|
* @param next {string?} - next page token, null for first page
|
||||||
|
* @returns {Promise<{thumbnails: string[], next: string?}>} - `next` is next page token, null for no more
|
||||||
|
*/
|
||||||
|
loadThumbnails: async (id, next) => {
|
||||||
|
/*
|
||||||
|
```
|
||||||
|
let data = JSON.parse((await Network.get('...')).body)
|
||||||
|
|
||||||
|
return {
|
||||||
|
thumbnails: data.list,
|
||||||
|
next: next,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* load images of a chapter
|
||||||
|
* @param comicId {string}
|
||||||
|
* @param epId {string?}
|
||||||
|
* @returns {Promise<{images: string[]}>}
|
||||||
|
*/
|
||||||
|
loadEp: async (comicId, epId) => {
|
||||||
|
/*
|
||||||
|
```
|
||||||
|
return {
|
||||||
|
// string[]
|
||||||
|
images: images
|
||||||
|
}
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* [Optional] provide configs for an image loading
|
||||||
|
* @param url
|
||||||
|
* @param comicId
|
||||||
|
* @param epId
|
||||||
|
* @returns {{}}
|
||||||
|
*/
|
||||||
|
onImageLoad: (url, comicId, epId) => {
|
||||||
|
/*
|
||||||
|
```
|
||||||
|
return {
|
||||||
|
url: `${url}?id=comicId`,
|
||||||
|
// http method
|
||||||
|
method: 'GET',
|
||||||
|
// any
|
||||||
|
data: null,
|
||||||
|
headers: {
|
||||||
|
'user-agent': 'pica_comic/v3.1.0',
|
||||||
|
},
|
||||||
|
// * modify response data
|
||||||
|
// * @param data {ArrayBuffer}
|
||||||
|
// * @returns {ArrayBuffer}
|
||||||
|
onResponse: (data) => {
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* [Optional] provide configs for a thumbnail loading
|
||||||
|
* @param url {string}
|
||||||
|
* @returns {{}}
|
||||||
|
*/
|
||||||
|
onThumbnailLoad: (url) => {
|
||||||
|
/*
|
||||||
|
```
|
||||||
|
return {
|
||||||
|
url: `${url}?id=comicId`,
|
||||||
|
// http method
|
||||||
|
method: 'GET',
|
||||||
|
// {any}
|
||||||
|
data: null,
|
||||||
|
headers: {
|
||||||
|
'user-agent': 'pica_comic/v3.1.0',
|
||||||
|
},
|
||||||
|
// modify response data
|
||||||
|
onResponse: (data) => {
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* [Optional] like or unlike a comic
|
||||||
|
* @param id {string}
|
||||||
|
* @param isLike {boolean} - true for like, false for unlike
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
likeComic: async (id, isLike) => {
|
||||||
|
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* [Optional] load comments
|
||||||
|
* @param comicId {string}
|
||||||
|
* @param subId {string?} - ComicDetails.subId
|
||||||
|
* @param page {number}
|
||||||
|
* @param replyTo {string?} - commentId to reply, not null when reply to a comment
|
||||||
|
* @returns {Promise<{comments: Comment[], maxPage: number?}>}
|
||||||
|
*/
|
||||||
|
loadComments: async (comicId, subId, page, replyTo) => {
|
||||||
|
/*
|
||||||
|
```
|
||||||
|
// ...
|
||||||
|
|
||||||
|
return {
|
||||||
|
comments: data.results.list.map(e => {
|
||||||
|
return new Comment({
|
||||||
|
// string
|
||||||
|
userName: e.user_name,
|
||||||
|
// string
|
||||||
|
avatar: e.user_avatar,
|
||||||
|
// string
|
||||||
|
content: e.comment,
|
||||||
|
// string?
|
||||||
|
time: e.create_at,
|
||||||
|
// number?
|
||||||
|
replyCount: e.count,
|
||||||
|
// string
|
||||||
|
id: e.id,
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
// number
|
||||||
|
maxPage: data.results.maxPage,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* [Optional] send a comment, return any value to indicate success
|
||||||
|
* @param comicId {string}
|
||||||
|
* @param subId {string?} - ComicDetails.subId
|
||||||
|
* @param content {string}
|
||||||
|
* @param replyTo {string?} - commentId to reply, not null when reply to a comment
|
||||||
|
* @returns {Promise<any>}
|
||||||
|
*/
|
||||||
|
sendComment: async (comicId, subId, content, replyTo) => {
|
||||||
|
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* [Optional] like or unlike a comment
|
||||||
|
* @param comicId {string}
|
||||||
|
* @param subId {string?} - ComicDetails.subId
|
||||||
|
* @param commentId {string}
|
||||||
|
* @param isLike {boolean} - true for like, false for unlike
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
likeComment: async (comicId, subId, commentId, isLike) => {
|
||||||
|
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* [Optional] vote a comment
|
||||||
|
* @param id {string} - comicId
|
||||||
|
* @param subId {string?} - ComicDetails.subId
|
||||||
|
* @param commentId {string} - commentId
|
||||||
|
* @param isUp {boolean} - true for up, false for down
|
||||||
|
* @param isCancel {boolean} - true for cancel, false for vote
|
||||||
|
* @returns {Promise<number>} - new score
|
||||||
|
*/
|
||||||
|
voteComment: async (id, subId, commentId, isUp, isCancel) => {
|
||||||
|
|
||||||
|
},
|
||||||
|
// {string?} - regex string, used to identify comic id from user input
|
||||||
|
idMatch: null,
|
||||||
|
/**
|
||||||
|
* [Optional] Handle tag click event
|
||||||
|
* @param namespace {string}
|
||||||
|
* @param tag {string}
|
||||||
|
* @returns {{action: string, keyword: string, param: string?}}
|
||||||
|
*/
|
||||||
|
onClickTag: (namespace, tag) => {
|
||||||
|
/*
|
||||||
|
```
|
||||||
|
return {
|
||||||
|
// 'search' or 'category'
|
||||||
|
action: 'search',
|
||||||
|
keyword: tag,
|
||||||
|
// {string?} only for category action
|
||||||
|
param: null,
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
[Optional] settings related
|
||||||
|
Use this.loadSetting to load setting
|
||||||
|
```
|
||||||
|
let setting1Value = this.loadSetting('setting1')
|
||||||
|
console.log(setting1Value)
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
settings = {
|
||||||
|
setting1: {
|
||||||
|
// title
|
||||||
|
title: "Setting1",
|
||||||
|
// type: input, select, switch
|
||||||
|
type: "select",
|
||||||
|
// options
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
// value
|
||||||
|
value: 'o1',
|
||||||
|
// [Optional] text, if not set, use value as text
|
||||||
|
text: 'Option 1',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'o1',
|
||||||
|
},
|
||||||
|
setting2: {
|
||||||
|
title: "Setting2",
|
||||||
|
type: "switch",
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
setting3: {
|
||||||
|
title: "Setting3",
|
||||||
|
type: "input",
|
||||||
|
validator: null, // string | null, regex string
|
||||||
|
default: '',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// [Optional] translations for the strings in this config
|
||||||
|
translation = {
|
||||||
|
'zh_CN': {
|
||||||
|
'Setting1': '设置1',
|
||||||
|
'Setting2': '设置2',
|
||||||
|
'Setting3': '设置3',
|
||||||
|
},
|
||||||
|
'zh_TW': {},
|
||||||
|
'en': {}
|
||||||
|
}
|
||||||
|
}
|
767
_venera_.js
Normal file
767
_venera_.js
Normal file
@@ -0,0 +1,767 @@
|
|||||||
|
/*
|
||||||
|
Venera JavaScript Library
|
||||||
|
|
||||||
|
This library provides a set of APIs for interacting with the Venera app.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/// encode, decode, hash, decrypt
|
||||||
|
let Convert = {
|
||||||
|
/**
|
||||||
|
* @param str {string}
|
||||||
|
* @returns {ArrayBuffer}
|
||||||
|
*/
|
||||||
|
encodeUtf8: (str) => {
|
||||||
|
return sendMessage({
|
||||||
|
method: "convert",
|
||||||
|
type: "utf8",
|
||||||
|
value: str,
|
||||||
|
isEncode: true
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param value {ArrayBuffer}
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
decodeUtf8: (value) => {
|
||||||
|
return sendMessage({
|
||||||
|
method: "convert",
|
||||||
|
type: "utf8",
|
||||||
|
value: value,
|
||||||
|
isEncode: false
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {ArrayBuffer} value
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
encodeBase64: (value) => {
|
||||||
|
return sendMessage({
|
||||||
|
method: "convert",
|
||||||
|
type: "base64",
|
||||||
|
value: value,
|
||||||
|
isEncode: true
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} value
|
||||||
|
* @returns {ArrayBuffer}
|
||||||
|
*/
|
||||||
|
decodeBase64: (value) => {
|
||||||
|
return sendMessage({
|
||||||
|
method: "convert",
|
||||||
|
type: "base64",
|
||||||
|
value: value,
|
||||||
|
isEncode: false
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {ArrayBuffer} value
|
||||||
|
* @returns {ArrayBuffer}
|
||||||
|
*/
|
||||||
|
md5: (value) => {
|
||||||
|
return sendMessage({
|
||||||
|
method: "convert",
|
||||||
|
type: "md5",
|
||||||
|
value: value,
|
||||||
|
isEncode: true
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {ArrayBuffer} value
|
||||||
|
* @returns {ArrayBuffer}
|
||||||
|
*/
|
||||||
|
sha1: (value) => {
|
||||||
|
return sendMessage({
|
||||||
|
method: "convert",
|
||||||
|
type: "sha1",
|
||||||
|
value: value,
|
||||||
|
isEncode: true
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {ArrayBuffer} value
|
||||||
|
* @returns {ArrayBuffer}
|
||||||
|
*/
|
||||||
|
sha256: (value) => {
|
||||||
|
return sendMessage({
|
||||||
|
method: "convert",
|
||||||
|
type: "sha256",
|
||||||
|
value: value,
|
||||||
|
isEncode: true
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {ArrayBuffer} value
|
||||||
|
* @returns {ArrayBuffer}
|
||||||
|
*/
|
||||||
|
sha512: (value) => {
|
||||||
|
return sendMessage({
|
||||||
|
method: "convert",
|
||||||
|
type: "sha512",
|
||||||
|
value: value,
|
||||||
|
isEncode: true
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param key {ArrayBuffer}
|
||||||
|
* @param value {ArrayBuffer}
|
||||||
|
* @param hash {string} - md5, sha1, sha256, sha512
|
||||||
|
* @returns {ArrayBuffer}
|
||||||
|
*/
|
||||||
|
hmac: (key, value, hash) => {
|
||||||
|
return sendMessage({
|
||||||
|
method: "convert",
|
||||||
|
type: "hmac",
|
||||||
|
value: value,
|
||||||
|
key: key,
|
||||||
|
hash: hash,
|
||||||
|
isEncode: true
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param key {ArrayBuffer}
|
||||||
|
* @param value {ArrayBuffer}
|
||||||
|
* @param hash {string} - md5, sha1, sha256, sha512
|
||||||
|
* @returns {string} - hex string
|
||||||
|
*/
|
||||||
|
hmacString: (key, value, hash) => {
|
||||||
|
return sendMessage({
|
||||||
|
method: "convert",
|
||||||
|
type: "hmac",
|
||||||
|
value: value,
|
||||||
|
key: key,
|
||||||
|
hash: hash,
|
||||||
|
isEncode: true,
|
||||||
|
isString: true
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {ArrayBuffer} value
|
||||||
|
* @param {ArrayBuffer} key
|
||||||
|
* @returns {ArrayBuffer}
|
||||||
|
*/
|
||||||
|
decryptAesEcb: (value, key) => {
|
||||||
|
return sendMessage({
|
||||||
|
method: "convert",
|
||||||
|
type: "aes-ecb",
|
||||||
|
value: value,
|
||||||
|
key: key,
|
||||||
|
isEncode: false
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {ArrayBuffer} value
|
||||||
|
* @param {ArrayBuffer} key
|
||||||
|
* @param {ArrayBuffer} iv
|
||||||
|
* @returns {ArrayBuffer}
|
||||||
|
*/
|
||||||
|
decryptAesCbc: (value, key, iv) => {
|
||||||
|
return sendMessage({
|
||||||
|
method: "convert",
|
||||||
|
type: "aes-ecb",
|
||||||
|
value: value,
|
||||||
|
key: key,
|
||||||
|
iv: iv,
|
||||||
|
isEncode: false
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {ArrayBuffer} value
|
||||||
|
* @param {ArrayBuffer} key
|
||||||
|
* @param {number} blockSize
|
||||||
|
* @returns {ArrayBuffer}
|
||||||
|
*/
|
||||||
|
decryptAesCfb: (value, key, blockSize) => {
|
||||||
|
return sendMessage({
|
||||||
|
method: "convert",
|
||||||
|
type: "aes-cfb",
|
||||||
|
value: value,
|
||||||
|
key: key,
|
||||||
|
blockSize: blockSize,
|
||||||
|
isEncode: false
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {ArrayBuffer} value
|
||||||
|
* @param {ArrayBuffer} key
|
||||||
|
* @param {number} blockSize
|
||||||
|
* @returns {ArrayBuffer}
|
||||||
|
*/
|
||||||
|
decryptAesOfb: (value, key, blockSize) => {
|
||||||
|
return sendMessage({
|
||||||
|
method: "convert",
|
||||||
|
type: "aes-ofb",
|
||||||
|
value: value,
|
||||||
|
key: key,
|
||||||
|
blockSize: blockSize,
|
||||||
|
isEncode: false
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {ArrayBuffer} value
|
||||||
|
* @param {ArrayBuffer} key
|
||||||
|
* @returns {ArrayBuffer}
|
||||||
|
*/
|
||||||
|
decryptRsa: (value, key) => {
|
||||||
|
return sendMessage({
|
||||||
|
method: "convert",
|
||||||
|
type: "rsa",
|
||||||
|
value: value,
|
||||||
|
key: key,
|
||||||
|
isEncode: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* create a time-based uuid
|
||||||
|
*
|
||||||
|
* Note: the engine will generate a new uuid every time it is called
|
||||||
|
*
|
||||||
|
* To get the same uuid, please save it to the local storage
|
||||||
|
*
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function createUuid() {
|
||||||
|
return sendMessage({
|
||||||
|
method: "uuid"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function randomInt(min, max) {
|
||||||
|
return sendMessage({
|
||||||
|
method: 'random',
|
||||||
|
min: min,
|
||||||
|
max: max
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class _Timer {
|
||||||
|
delay = 0;
|
||||||
|
|
||||||
|
callback = () => { };
|
||||||
|
|
||||||
|
status = false;
|
||||||
|
|
||||||
|
constructor(delay, callback) {
|
||||||
|
this.delay = delay;
|
||||||
|
this.callback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
run() {
|
||||||
|
this.status = true;
|
||||||
|
this._interval();
|
||||||
|
}
|
||||||
|
|
||||||
|
_interval() {
|
||||||
|
if (!this.status) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.callback();
|
||||||
|
setTimeout(this._interval.bind(this), this.delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel() {
|
||||||
|
this.status = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setInterval(callback, delay) {
|
||||||
|
let timer = new _Timer(delay, callback);
|
||||||
|
timer.run();
|
||||||
|
return timer;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Cookie(name, value, domain = null) {
|
||||||
|
let obj = {};
|
||||||
|
obj.name = name;
|
||||||
|
obj.value = value;
|
||||||
|
if (domain) {
|
||||||
|
obj.domain = domain;
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Network object for sending HTTP requests and managing cookies.
|
||||||
|
* @namespace Network
|
||||||
|
*/
|
||||||
|
let Network = {
|
||||||
|
/**
|
||||||
|
* Sends an HTTP request.
|
||||||
|
* @param {string} method - The HTTP method (e.g., GET, POST, PUT, PATCH, DELETE).
|
||||||
|
* @param {string} url - The URL to send the request to.
|
||||||
|
* @param {Object} headers - The headers to include in the request.
|
||||||
|
* @param data - The data to send with the request.
|
||||||
|
* @returns {Promise<ArrayBuffer>} The response from the request.
|
||||||
|
*/
|
||||||
|
async fetchBytes(method, url, headers, data) {
|
||||||
|
let result = await sendMessage({
|
||||||
|
method: 'http',
|
||||||
|
http_method: method,
|
||||||
|
bytes: true,
|
||||||
|
url: url,
|
||||||
|
headers: headers,
|
||||||
|
data: data,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.error) {
|
||||||
|
throw result.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends an HTTP request.
|
||||||
|
* @param {string} method - The HTTP method (e.g., GET, POST, PUT, PATCH, DELETE).
|
||||||
|
* @param {string} url - The URL to send the request to.
|
||||||
|
* @param {Object} headers - The headers to include in the request.
|
||||||
|
* @param data - The data to send with the request.
|
||||||
|
* @returns {Promise<Object>} The response from the request.
|
||||||
|
*/
|
||||||
|
async sendRequest(method, url, headers, data) {
|
||||||
|
let result = await sendMessage({
|
||||||
|
method: 'http',
|
||||||
|
http_method: method,
|
||||||
|
url: url,
|
||||||
|
headers: headers,
|
||||||
|
data: data,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.error) {
|
||||||
|
throw result.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends an HTTP GET request.
|
||||||
|
* @param {string} url - The URL to send the request to.
|
||||||
|
* @param {Object} headers - The headers to include in the request.
|
||||||
|
* @returns {Promise<Object>} The response from the request.
|
||||||
|
*/
|
||||||
|
async get(url, headers) {
|
||||||
|
return this.sendRequest('GET', url, headers);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends an HTTP POST request.
|
||||||
|
* @param {string} url - The URL to send the request to.
|
||||||
|
* @param {Object} headers - The headers to include in the request.
|
||||||
|
* @param data - The data to send with the request.
|
||||||
|
* @returns {Promise<Object>} The response from the request.
|
||||||
|
*/
|
||||||
|
async post(url, headers, data) {
|
||||||
|
return this.sendRequest('POST', url, headers, data);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends an HTTP PUT request.
|
||||||
|
* @param {string} url - The URL to send the request to.
|
||||||
|
* @param {Object} headers - The headers to include in the request.
|
||||||
|
* @param data - The data to send with the request.
|
||||||
|
* @returns {Promise<Object>} The response from the request.
|
||||||
|
*/
|
||||||
|
async put(url, headers, data) {
|
||||||
|
return this.sendRequest('PUT', url, headers, data);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends an HTTP PATCH request.
|
||||||
|
* @param {string} url - The URL to send the request to.
|
||||||
|
* @param {Object} headers - The headers to include in the request.
|
||||||
|
* @param data - The data to send with the request.
|
||||||
|
* @returns {Promise<Object>} The response from the request.
|
||||||
|
*/
|
||||||
|
async patch(url, headers, data) {
|
||||||
|
return this.sendRequest('PATCH', url, headers, data);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends an HTTP DELETE request.
|
||||||
|
* @param {string} url - The URL to send the request to.
|
||||||
|
* @param {Object} headers - The headers to include in the request.
|
||||||
|
* @returns {Promise<Object>} The response from the request.
|
||||||
|
*/
|
||||||
|
async delete(url, headers) {
|
||||||
|
return this.sendRequest('DELETE', url, headers);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets cookies for a specific URL.
|
||||||
|
* @param {string} url - The URL to set the cookies for.
|
||||||
|
* @param {Cookie[]} cookies - The cookies to set.
|
||||||
|
*/
|
||||||
|
setCookies(url, cookies) {
|
||||||
|
sendMessage({
|
||||||
|
method: 'cookie',
|
||||||
|
function: 'set',
|
||||||
|
url: url,
|
||||||
|
cookies: cookies,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves cookies for a specific URL.
|
||||||
|
* @param {string} url - The URL to get the cookies from.
|
||||||
|
* @returns {Promise<Cookie[]>} The cookies for the given URL.
|
||||||
|
*/
|
||||||
|
getCookies(url) {
|
||||||
|
return sendMessage({
|
||||||
|
method: 'cookie',
|
||||||
|
function: 'get',
|
||||||
|
url: url,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes cookies for a specific URL.
|
||||||
|
* @param {string} url - The URL to delete the cookies from.
|
||||||
|
*/
|
||||||
|
deleteCookies(url) {
|
||||||
|
sendMessage({
|
||||||
|
method: 'cookie',
|
||||||
|
function: 'delete',
|
||||||
|
url: url,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HtmlDocument class for parsing HTML and querying elements.
|
||||||
|
*/
|
||||||
|
class HtmlDocument {
|
||||||
|
static _key = 0;
|
||||||
|
|
||||||
|
key = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for HtmlDocument.
|
||||||
|
* @param {string} html - The HTML string to parse.
|
||||||
|
*/
|
||||||
|
constructor(html) {
|
||||||
|
this.key = HtmlDocument._key;
|
||||||
|
HtmlDocument._key++;
|
||||||
|
sendMessage({
|
||||||
|
method: "html",
|
||||||
|
function: "parse",
|
||||||
|
key: this.key,
|
||||||
|
data: html
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query a single element from the HTML document.
|
||||||
|
* @param {string} query - The query string.
|
||||||
|
* @returns {HtmlElement} The first matching element.
|
||||||
|
*/
|
||||||
|
querySelector(query) {
|
||||||
|
let k = sendMessage({
|
||||||
|
method: "html",
|
||||||
|
function: "querySelector",
|
||||||
|
key: this.key,
|
||||||
|
query: query
|
||||||
|
})
|
||||||
|
if(!k) return null;
|
||||||
|
return new HtmlElement(k);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query all matching elements from the HTML document.
|
||||||
|
* @param {string} query - The query string.
|
||||||
|
* @returns {HtmlElement[]} An array of matching elements.
|
||||||
|
*/
|
||||||
|
querySelectorAll(query) {
|
||||||
|
let ks = sendMessage({
|
||||||
|
method: "html",
|
||||||
|
function: "querySelectorAll",
|
||||||
|
key: this.key,
|
||||||
|
query: query
|
||||||
|
})
|
||||||
|
return ks.map(k => new HtmlElement(k));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HtmlDom class for interacting with HTML elements.
|
||||||
|
*/
|
||||||
|
class HtmlElement {
|
||||||
|
key = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for HtmlDom.
|
||||||
|
* @param {number} k - The key of the element.
|
||||||
|
*/
|
||||||
|
constructor(k) {
|
||||||
|
this.key = k;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the text content of the element.
|
||||||
|
* @returns {string} The text content.
|
||||||
|
*/
|
||||||
|
get text() {
|
||||||
|
return sendMessage({
|
||||||
|
method: "html",
|
||||||
|
function: "getText",
|
||||||
|
key: this.key
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the attributes of the element.
|
||||||
|
* @returns {Object} The attributes.
|
||||||
|
*/
|
||||||
|
get attributes() {
|
||||||
|
return sendMessage({
|
||||||
|
method: "html",
|
||||||
|
function: "getAttributes",
|
||||||
|
key: this.key
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query a single element from the current element.
|
||||||
|
* @param {string} query - The query string.
|
||||||
|
* @returns {HtmlElement} The first matching element.
|
||||||
|
*/
|
||||||
|
querySelector(query) {
|
||||||
|
let k = sendMessage({
|
||||||
|
method: "html",
|
||||||
|
function: "dom_querySelector",
|
||||||
|
key: this.key,
|
||||||
|
query: query
|
||||||
|
})
|
||||||
|
if(!k) return null;
|
||||||
|
return new HtmlElement(k);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query all matching elements from the current element.
|
||||||
|
* @param {string} query - The query string.
|
||||||
|
* @returns {HtmlElement[]} An array of matching elements.
|
||||||
|
*/
|
||||||
|
querySelectorAll(query) {
|
||||||
|
let ks = sendMessage({
|
||||||
|
method: "html",
|
||||||
|
function: "dom_querySelectorAll",
|
||||||
|
key: this.key,
|
||||||
|
query: query
|
||||||
|
})
|
||||||
|
return ks.map(k => new HtmlElement(k));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the children of the current element.
|
||||||
|
* @returns {HtmlElement[]} An array of child elements.
|
||||||
|
*/
|
||||||
|
get children() {
|
||||||
|
let ks = sendMessage({
|
||||||
|
method: "html",
|
||||||
|
function: "getChildren",
|
||||||
|
key: this.key
|
||||||
|
})
|
||||||
|
return ks.map(k => new HtmlElement(k));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function log(level, title, content) {
|
||||||
|
sendMessage({
|
||||||
|
method: 'log',
|
||||||
|
level: level,
|
||||||
|
title: title,
|
||||||
|
content: content,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let console = {
|
||||||
|
log: (content) => {
|
||||||
|
log('info', 'JS Console', content)
|
||||||
|
},
|
||||||
|
warn: (content) => {
|
||||||
|
log('warning', 'JS Console', content)
|
||||||
|
},
|
||||||
|
error: (content) => {
|
||||||
|
log('error', 'JS Console', content)
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a comic object
|
||||||
|
* @param id {string}
|
||||||
|
* @param title {string}
|
||||||
|
* @param subtitle {string}
|
||||||
|
* @param cover {string}
|
||||||
|
* @param tags {string[]}
|
||||||
|
* @param description {string}
|
||||||
|
* @param maxPage {number | null}
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function Comic({id, title, subtitle, cover, tags, description, maxPage}) {
|
||||||
|
this.id = id;
|
||||||
|
this.title = title;
|
||||||
|
this.subtitle = subtitle;
|
||||||
|
this.cover = cover;
|
||||||
|
this.tags = tags;
|
||||||
|
this.description = description;
|
||||||
|
this.maxPage = maxPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a comic details object
|
||||||
|
* @param title {string}
|
||||||
|
* @param cover {string}
|
||||||
|
* @param description {string | null}
|
||||||
|
* @param tags {Map<string, string[]> | {} | null}
|
||||||
|
* @param chapters {Map<string, string> | {} | null} - key: chapter id, value: chapter title
|
||||||
|
* @param isFavorite {boolean | null} - favorite status. If the comic source supports multiple folders, this field should be null
|
||||||
|
* @param subId {string | null} - a param which is passed to comments api
|
||||||
|
* @param thumbnails {string[] | null} - for multiple page thumbnails, set this to null, and use `loadThumbnails` api to load thumbnails
|
||||||
|
* @param recommend {Comic[] | null} - related comics
|
||||||
|
* @param commentCount {number | null}
|
||||||
|
* @param likesCount {number | null}
|
||||||
|
* @param isLiked {boolean | null}
|
||||||
|
* @param uploader {string | null}
|
||||||
|
* @param updateTime {string | null}
|
||||||
|
* @param uploadTime {string | null}
|
||||||
|
* @param url {string | null}
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function ComicDetails({title, cover, description, tags, chapters, isFavorite, subId, thumbnails, recommend, commentCount, likesCount, isLiked, uploader, updateTime, uploadTime, url}) {
|
||||||
|
this.title = title;
|
||||||
|
this.cover = cover;
|
||||||
|
this.description = description;
|
||||||
|
this.tags = tags;
|
||||||
|
this.chapters = chapters;
|
||||||
|
this.isFavorite = isFavorite;
|
||||||
|
this.subId = subId;
|
||||||
|
this.thumbnails = thumbnails;
|
||||||
|
this.recommend = recommend;
|
||||||
|
this.commentCount = commentCount;
|
||||||
|
this.likesCount = likesCount;
|
||||||
|
this.isLiked = isLiked;
|
||||||
|
this.uploader = uploader;
|
||||||
|
this.updateTime = updateTime;
|
||||||
|
this.uploadTime = uploadTime;
|
||||||
|
this.url = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a comment object
|
||||||
|
* @param userName {string}
|
||||||
|
* @param avatar {string?}
|
||||||
|
* @param content {string}
|
||||||
|
* @param time {string?}
|
||||||
|
* @param replyCount {number?}
|
||||||
|
* @param id {string?}
|
||||||
|
* @param isLiked {boolean?}
|
||||||
|
* @param score {number?}
|
||||||
|
* @param voteStatus {number?} - 1: upvote, -1: downvote, 0: none
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function Comment({userName, avatar, content, time, replyCount, id, isLiked, score, voteStatus}) {
|
||||||
|
this.userName = userName;
|
||||||
|
this.avatar = avatar;
|
||||||
|
this.content = content;
|
||||||
|
this.time = time;
|
||||||
|
this.replyCount = replyCount;
|
||||||
|
this.id = id;
|
||||||
|
this.isLiked = isLiked;
|
||||||
|
this.score = score;
|
||||||
|
this.voteStatus = voteStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ComicSource {
|
||||||
|
name = ""
|
||||||
|
|
||||||
|
key = ""
|
||||||
|
|
||||||
|
version = ""
|
||||||
|
|
||||||
|
minAppVersion = ""
|
||||||
|
|
||||||
|
url = ""
|
||||||
|
|
||||||
|
/**
|
||||||
|
* load data with its key
|
||||||
|
* @param {string} dataKey
|
||||||
|
* @returns {any}
|
||||||
|
*/
|
||||||
|
loadData(dataKey) {
|
||||||
|
return sendMessage({
|
||||||
|
method: 'load_data',
|
||||||
|
key: this.key,
|
||||||
|
data_key: dataKey
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* load a setting with its key
|
||||||
|
* @param key {string}
|
||||||
|
* @returns {any}
|
||||||
|
*/
|
||||||
|
loadSetting(key) {
|
||||||
|
return sendMessage({
|
||||||
|
method: 'load_setting',
|
||||||
|
key: this.key,
|
||||||
|
setting_key: key
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* save data
|
||||||
|
* @param {string} dataKey
|
||||||
|
* @param data
|
||||||
|
*/
|
||||||
|
saveData(dataKey, data) {
|
||||||
|
return sendMessage({
|
||||||
|
method: 'save_data',
|
||||||
|
key: this.key,
|
||||||
|
data_key: dataKey,
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* delete data
|
||||||
|
* @param {string} dataKey
|
||||||
|
*/
|
||||||
|
deleteData(dataKey) {
|
||||||
|
return sendMessage({
|
||||||
|
method: 'delete_data',
|
||||||
|
key: this.key,
|
||||||
|
data_key: dataKey,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
get isLogged() {
|
||||||
|
return sendMessage({
|
||||||
|
method: 'isLogged',
|
||||||
|
key: this.key,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
init() { }
|
||||||
|
|
||||||
|
static sources = {}
|
||||||
|
}
|
@@ -22,5 +22,11 @@
|
|||||||
"fileName": "baozi.js",
|
"fileName": "baozi.js",
|
||||||
"key": "baozi",
|
"key": "baozi",
|
||||||
"version": "1.0.0"
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Picacg",
|
||||||
|
"fileName": "picacg.js",
|
||||||
|
"key": "picacg",
|
||||||
|
"version": "1.0.0"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
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