mirror of
https://github.com/venera-app/venera.git
synced 2025-09-26 23:47:23 +00:00
Add doc
This commit is contained in:
@@ -31,7 +31,7 @@ A comic reader that support reading local and network comics.
|
||||
|
||||
## Create a new comic source
|
||||
|
||||
See [venera-configs](https://github.com/venera-app/venera-configs)
|
||||
See [Comic Source](doc/comic_source.md)
|
||||
|
||||
## Thanks
|
||||
|
||||
|
655
doc/comic_source.md
Normal file
655
doc/comic_source.md
Normal file
@@ -0,0 +1,655 @@
|
||||
# Comic Source
|
||||
|
||||
## Introduction
|
||||
|
||||
Venera is a comic reader that can read comics from various sources.
|
||||
|
||||
All comic sources are written in javascript.
|
||||
Venera uses [flutter_qjs](https://github.com/wgh136/flutter_qjs) as js engine which is forked from [ekibun](https://github.com/ekibun/flutter_qjs).
|
||||
|
||||
This document will describe how to write a comic source for Venera.
|
||||
|
||||
## Preparation
|
||||
|
||||
- Install Venera. Using flutter to run the project is recommended since it's easier to debug.
|
||||
- An editor that supports javascript.
|
||||
- Download template and venera javascript api from [here](https://github.com/venera-app/venera-configs).
|
||||
|
||||
## Start Writing
|
||||
|
||||
The template contains detailed comments and examples. You can refer to it when writing your own comic source.
|
||||
|
||||
Here is a brief introduction to the template:
|
||||
|
||||
> Note: Javascript api document is [here](js_api.md).
|
||||
|
||||
### Write basic information
|
||||
|
||||
```javascript
|
||||
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 = ""
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
In this part, you need to do the following:
|
||||
- Change the class name to your source name.
|
||||
- Fill in the name, key, version, minAppVersion, and url fields.
|
||||
|
||||
### init function
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* [Optional] init function
|
||||
*/
|
||||
init() {
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
The function will be called when the source is initialized. You can do some initialization work here.
|
||||
|
||||
Remove this function if not used.
|
||||
|
||||
### Account
|
||||
|
||||
```javascript
|
||||
// [Optional] account related
|
||||
account = {
|
||||
/**
|
||||
* [Optional] login with account and password, return any value to indicate success
|
||||
* @param account {string}
|
||||
* @param pwd {string}
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
login: async (account, pwd) => {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* [Optional] login with webview
|
||||
*/
|
||||
loginWithWebview: {
|
||||
url: "",
|
||||
/**
|
||||
* check login status
|
||||
* @param url {string} - current url
|
||||
* @param title {string} - current title
|
||||
* @returns {boolean} - return true if login success
|
||||
*/
|
||||
checkStatus: (url, title) => {
|
||||
|
||||
},
|
||||
/**
|
||||
* [Optional] Callback when login success
|
||||
*/
|
||||
onLoginSuccess: () => {
|
||||
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* [Optional] login with cookies
|
||||
* Note: If `this.account.login` is implemented, this will be ignored
|
||||
*/
|
||||
loginWithCookies: {
|
||||
fields: [
|
||||
"ipb_member_id",
|
||||
"ipb_pass_hash",
|
||||
"igneous",
|
||||
"star",
|
||||
],
|
||||
/**
|
||||
* Validate cookies, return false if cookies are invalid.
|
||||
*
|
||||
* Use `Network.setCookies` to set cookies before validate.
|
||||
* @param values {string[]} - same order as `fields`
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
validate: async (values) => {
|
||||
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* logout function, clear account related data
|
||||
*/
|
||||
logout: () => {
|
||||
|
||||
},
|
||||
|
||||
// {string?} - register url
|
||||
registerWebsite: null
|
||||
}
|
||||
```
|
||||
|
||||
In this part, you can implement login, logout, and register functions.
|
||||
|
||||
Remove this part if not used.
|
||||
|
||||
### Explore page
|
||||
|
||||
```javascript
|
||||
// explore page list
|
||||
explore = [
|
||||
{
|
||||
// title of the page.
|
||||
// title is used to identify the page, it should be unique
|
||||
title: "",
|
||||
|
||||
/// multiPartPage or multiPageComicList or mixed
|
||||
type: "multiPartPage",
|
||||
|
||||
/**
|
||||
* load function
|
||||
* @param page {number | null} - page number, null for `singlePageWithMultiPart` type
|
||||
* @returns {{}}
|
||||
* - for `multiPartPage` type, return {title: string, comics: Comic[], viewMore: string?}[]
|
||||
* - for `multiPageComicList` type, for each page(1-based), return {comics: Comic[], maxPage: number}
|
||||
* - for `mixed` type, use param `page` as index. for each index(0-based), return {data: [], maxPage: number?}, data is an array contains Comic[] or {title: string, comics: Comic[], viewMore: string?}
|
||||
*/
|
||||
load: async (page) => {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Only use for `multiPageComicList` type.
|
||||
* `loadNext` would be ignored if `load` function is implemented.
|
||||
* @param next {string | null} - next page token, null if first page
|
||||
* @returns {Promise<{comics: Comic[], next: string?}>} - next is null if no next page.
|
||||
*/
|
||||
loadNext(next) {},
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
In this part, you can implement the explore page.
|
||||
|
||||
A comic source can have multiple explore pages.
|
||||
|
||||
There are three types of explore pages:
|
||||
- multiPartPage: An explore page contains multiple parts, each part contains multiple comics.
|
||||
- multiPageComicList: An explore page contains multiple comics, the comics are loaded page by page.
|
||||
- mixed: An explore page contains multiple parts, each part can be a list of comics or a block of comics which have a title and a view more button.
|
||||
|
||||
### Category Page
|
||||
|
||||
```javascript
|
||||
// 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] {string[]?} must have same length as categories, used to provide loading param for each category
|
||||
categoryParams: ["all", "adventure", "school"],
|
||||
|
||||
// [Optional] {string} cannot be used with `categoryParams`, set all category params to this value
|
||||
groupParam: null,
|
||||
}
|
||||
],
|
||||
// enable ranking page
|
||||
enableRankingPage: false,
|
||||
}
|
||||
```
|
||||
|
||||
Category page is a static page that contains multiple parts, each part contains multiple categories.
|
||||
|
||||
A comic source can only have one category page.
|
||||
|
||||
### Category Comics Page
|
||||
|
||||
```javascript
|
||||
/// 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) => {
|
||||
|
||||
},
|
||||
// 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) => {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
When user clicks on a category, the category comics page will be displayed.
|
||||
|
||||
This part is used to load comics of a category.
|
||||
|
||||
### Search
|
||||
|
||||
```javascript
|
||||
/// search related
|
||||
search = {
|
||||
/**
|
||||
* load search result
|
||||
* @param keyword {string}
|
||||
* @param options {(string | null)[]} - options from optionList
|
||||
* @param page {number}
|
||||
* @returns {Promise<{comics: Comic[], maxPage: number}>}
|
||||
*/
|
||||
load: async (keyword, options, page) => {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* load search result with next page token.
|
||||
* The field will be ignored if `load` function is implemented.
|
||||
* @param keyword {string}
|
||||
* @param options {(string)[]} - options from optionList
|
||||
* @param next {string | null}
|
||||
* @returns {Promise<{comics: Comic[], maxPage: number}>}
|
||||
*/
|
||||
loadNext: async (keyword, options, next) => {
|
||||
|
||||
},
|
||||
|
||||
// provide options for search
|
||||
optionList: [
|
||||
{
|
||||
// [Optional] default is `select`
|
||||
// type: select, multi-select, dropdown
|
||||
// For select, there is only one selected value
|
||||
// For multi-select, there are multiple selected values or none. The `load` function will receive a json string which is an array of selected values
|
||||
// For dropdown, there is one selected value at most. If no selected value, the `load` function will receive a null
|
||||
type: "select",
|
||||
// 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",
|
||||
// default selected options
|
||||
default: null,
|
||||
}
|
||||
],
|
||||
|
||||
// enable tags suggestions
|
||||
enableTagsSuggestions: false,
|
||||
}
|
||||
```
|
||||
|
||||
This part is used to load search results.
|
||||
|
||||
`load` and `loadNext` functions are used to load search results.
|
||||
If `load` function is implemented, `loadNext` function will be ignored.
|
||||
|
||||
### Favorites
|
||||
|
||||
```javascript
|
||||
// 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
|
||||
* @param favoriteId {string?} - [Comic.favoriteId]
|
||||
* @returns {Promise<any>} - return any value to indicate success
|
||||
*/
|
||||
addOrDelFavorite: async (comicId, folderId, isAdding, favoriteId) => {
|
||||
|
||||
},
|
||||
/**
|
||||
* 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) => {
|
||||
|
||||
},
|
||||
/**
|
||||
* add a folder
|
||||
* @param name {string}
|
||||
* @returns {Promise<any>} - return any value to indicate success
|
||||
*/
|
||||
addFolder: async (name) => {
|
||||
|
||||
},
|
||||
/**
|
||||
* delete a folder
|
||||
* @param folderId {string}
|
||||
* @returns {Promise<void>} - return any value to indicate success
|
||||
*/
|
||||
deleteFolder: async (folderId) => {
|
||||
|
||||
},
|
||||
/**
|
||||
* 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) => {
|
||||
|
||||
},
|
||||
/**
|
||||
* load comics with next page token
|
||||
* @param next {string | null} - next page token, null for first page
|
||||
* @param folder {string}
|
||||
* @returns {Promise<{comics: Comic[], next: string?}>}
|
||||
*/
|
||||
loadNext: async (next, folder) => {
|
||||
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
This part is used to manage network favorites of the source.
|
||||
|
||||
`load` and `loadNext` functions are used to load search results.
|
||||
If `load` function is implemented, `loadNext` function will be ignored.
|
||||
|
||||
### Comic Details
|
||||
|
||||
```javascript
|
||||
/// single comic related
|
||||
comic = {
|
||||
/**
|
||||
* load comic info
|
||||
* @param id {string}
|
||||
* @returns {Promise<ComicDetails>}
|
||||
*/
|
||||
loadInfo: async (id) => {
|
||||
|
||||
},
|
||||
/**
|
||||
* [Optional] load thumbnails of a comic
|
||||
*
|
||||
* To render a part of an image as thumbnail, return `${url}@x=${start}-${end}&y=${start}-${end}`
|
||||
* - If width is not provided, use full width
|
||||
* - If height is not provided, use full height
|
||||
* @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) => {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* rate a comic
|
||||
* @param id
|
||||
* @param rating {number} - [0-10] app use 5 stars, 1 rating = 0.5 stars,
|
||||
* @returns {Promise<any>} - return any value to indicate success
|
||||
*/
|
||||
starRating: async (id, rating) => {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* load images of a chapter
|
||||
* @param comicId {string}
|
||||
* @param epId {string?}
|
||||
* @returns {Promise<{images: string[]}>}
|
||||
*/
|
||||
loadEp: async (comicId, epId) => {
|
||||
|
||||
},
|
||||
/**
|
||||
* [Optional] provide configs for an image loading
|
||||
* @param url
|
||||
* @param comicId
|
||||
* @param epId
|
||||
* @returns {ImageLoadingConfig | Promise<ImageLoadingConfig>}
|
||||
*/
|
||||
onImageLoad: (url, comicId, epId) => {
|
||||
return {}
|
||||
},
|
||||
/**
|
||||
* [Optional] provide configs for a thumbnail loading
|
||||
* @param url {string}
|
||||
* @returns {ImageLoadingConfig | Promise<ImageLoadingConfig>}
|
||||
*
|
||||
* `ImageLoadingConfig.modifyImage` and `ImageLoadingConfig.onLoadFailed` will be ignored.
|
||||
* They are not supported for thumbnails.
|
||||
*/
|
||||
onThumbnailLoad: (url) => {
|
||||
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
|
||||
*
|
||||
* Since app version 1.0.6, rich text is supported in comments.
|
||||
* Following html tags are supported: ['a', 'b', 'i', 'u', 's', 'br', 'span', 'img'].
|
||||
* span tag supports style attribute, but only support font-weight, font-style, text-decoration.
|
||||
* All images will be placed at the end of the comment.
|
||||
* Auto link detection is enabled, but only http/https links are supported.
|
||||
* @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) => {
|
||||
|
||||
},
|
||||
/**
|
||||
* [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) => {
|
||||
|
||||
},
|
||||
/**
|
||||
* [Optional] Handle links
|
||||
*/
|
||||
link: {
|
||||
/**
|
||||
* set accepted domains
|
||||
*/
|
||||
domains: [
|
||||
'example.com'
|
||||
],
|
||||
/**
|
||||
* parse url to comic id
|
||||
* @param url {string}
|
||||
* @returns {string | null}
|
||||
*/
|
||||
linkToId: (url) => {
|
||||
|
||||
}
|
||||
},
|
||||
// enable tags translate
|
||||
enableTagsTranslate: false,
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
This part is used to load comic details.
|
||||
|
||||
### Settings
|
||||
|
||||
```javascript
|
||||
/*
|
||||
[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: '',
|
||||
},
|
||||
setting4: {
|
||||
title: "Setting4",
|
||||
type: "callback",
|
||||
buttonText: "Click me",
|
||||
/**
|
||||
* callback function
|
||||
*
|
||||
* If the callback function returns a Promise, the button will show a loading indicator until the promise is resolved.
|
||||
* @returns {void | Promise<any>}
|
||||
*/
|
||||
callback: () => {
|
||||
// do something
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This part is used to provide settings for the source.
|
||||
|
||||
|
||||
### Translations
|
||||
|
||||
```javascript
|
||||
// [Optional] translations for the strings in this config
|
||||
translation = {
|
||||
'zh_CN': {
|
||||
'Setting1': '设置1',
|
||||
'Setting2': '设置2',
|
||||
'Setting3': '设置3',
|
||||
},
|
||||
'zh_TW': {},
|
||||
'en': {}
|
||||
}
|
||||
```
|
||||
|
||||
This part is used to provide translations for the source.
|
||||
|
||||
> Note: strings in the UI api will not be translated automatically. You need to translate them manually.
|
59
doc/import_comic.md
Normal file
59
doc/import_comic.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# Import Comic
|
||||
|
||||
## Introduction
|
||||
|
||||
Venera supports importing comics from local files.
|
||||
However, the comic files must be in a specific format.
|
||||
|
||||
## Comic Directory
|
||||
|
||||
A directory considered as a comic directory only if it follows the following two types of structure:
|
||||
|
||||
The file name can be anything, but the extension must be a valid image extension.
|
||||
|
||||
The page order is determined by the file name. App will sort the files by name and display them in that order.
|
||||
|
||||
Cover image is optional.
|
||||
If there is a file named `cover.[ext]` in the directory, it will be considered as the cover image.
|
||||
Otherwise, the first image will be considered as the cover image.
|
||||
|
||||
### Without Chapter
|
||||
|
||||
```
|
||||
comic_directory
|
||||
├── cover.[ext]
|
||||
├── img1.[ext]
|
||||
├── img2.[ext]
|
||||
├── img3.[ext]
|
||||
├── ...
|
||||
```
|
||||
|
||||
### With Chapter
|
||||
|
||||
```
|
||||
comic_directory
|
||||
├── cover.[ext]
|
||||
├── chapter1
|
||||
│ ├── img1.[ext]
|
||||
│ ├── img2.[ext]
|
||||
│ ├── img3.[ext]
|
||||
│ ├── ...
|
||||
├── chapter2
|
||||
│ ├── img1.[ext]
|
||||
│ ├── img2.[ext]
|
||||
│ ├── img3.[ext]
|
||||
│ ├── ...
|
||||
├── ...
|
||||
```
|
||||
|
||||
## Archive
|
||||
|
||||
Venera supports importing comics from archive files.
|
||||
|
||||
The archive file must follow [Comic Book Archive](https://en.wikipedia.org/wiki/Comic_book_archive_file) format.
|
||||
|
||||
Currently, Venera supports the following archive formats:
|
||||
- `.cbz`
|
||||
- `.cb7`
|
||||
- `.zip`
|
||||
- `.7z`
|
513
doc/js_api.md
Normal file
513
doc/js_api.md
Normal file
@@ -0,0 +1,513 @@
|
||||
# Javascript API
|
||||
|
||||
## Overview
|
||||
|
||||
The Javascript API is a set of functions that used to interact application.
|
||||
|
||||
There are following parts in the API:
|
||||
- [Convert](#Convert)
|
||||
- [Network](#Network)
|
||||
- [Html](#Html)
|
||||
- [UI](#UI)
|
||||
- [Utils](#Utils)
|
||||
- [Types](#Types)
|
||||
|
||||
|
||||
## Convert
|
||||
|
||||
Convert is a set of functions that used to convert data between different types.
|
||||
|
||||
### `Convert.encodeUtf8(str: string): ArrayBuffer`
|
||||
|
||||
Convert a string to an ArrayBuffer.
|
||||
|
||||
### `Convert.decodeUtf8(value: ArrayBuffer): string`
|
||||
|
||||
Convert an ArrayBuffer to a string.
|
||||
|
||||
### `Convert.encodeBase64(value: ArrayBuffer): string`
|
||||
|
||||
Convert an ArrayBuffer to a base64 string.
|
||||
|
||||
### `Convert.decodeBase64(value: string): ArrayBuffer`
|
||||
|
||||
Convert a base64 string to an ArrayBuffer.
|
||||
|
||||
### `Convert.md5(value: ArrayBuffer): ArrayBuffer`
|
||||
|
||||
Calculate the md5 hash of an ArrayBuffer.
|
||||
|
||||
### `Convert.sha1(value: ArrayBuffer): ArrayBuffer`
|
||||
|
||||
Calculate the sha1 hash of an ArrayBuffer.
|
||||
|
||||
### `Convert.sha256(value: ArrayBuffer): ArrayBuffer`
|
||||
|
||||
Calculate the sha256 hash of an ArrayBuffer.
|
||||
|
||||
### `Convert.sha512(value: ArrayBuffer): ArrayBuffer`
|
||||
|
||||
Calculate the sha512 hash of an ArrayBuffer.
|
||||
|
||||
### `Convert.hmac(key: ArrayBuffer, value: ArrayBuffer, hash: string): ArrayBuffer`
|
||||
|
||||
Calculate the hmac hash of an ArrayBuffer.
|
||||
|
||||
### `Convert.hmacString(key: ArrayBuffer, value: ArrayBuffer, hash: string): string`
|
||||
|
||||
Calculate the hmac hash of an ArrayBuffer and return a string.
|
||||
|
||||
### `Convert.decryptAesEcb(value: ArrayBuffer, key: ArrayBuffer): ArrayBuffer`
|
||||
|
||||
Decrypt an ArrayBuffer with AES ECB mode.
|
||||
|
||||
### `Convert.decryptAesCbc(value: ArrayBuffer, key: ArrayBuffer, iv: ArrayBuffer): ArrayBuffer`
|
||||
|
||||
Decrypt an ArrayBuffer with AES CBC mode.
|
||||
|
||||
### `Convert.decryptAesCfb(value: ArrayBuffer, key: ArrayBuffer, iv: ArrayBuffer): ArrayBuffer`
|
||||
|
||||
Decrypt an ArrayBuffer with AES CFB mode.
|
||||
|
||||
### `Convert.decryptAesOfb(value: ArrayBuffer, key: ArrayBuffer, iv: ArrayBuffer): ArrayBuffer`
|
||||
|
||||
Decrypt an ArrayBuffer with AES OFB mode.
|
||||
|
||||
### `Convert.decryptRsa(value: ArrayBuffer, key: ArrayBuffer): ArrayBuffer`
|
||||
|
||||
Decrypt an ArrayBuffer with RSA.
|
||||
|
||||
### `Convert.hexEncode(value: ArrayBuffer): string`
|
||||
|
||||
Convert an ArrayBuffer to a hex string.
|
||||
|
||||
## Network
|
||||
|
||||
Network is a set of functions that used to send network requests and manage network resources.
|
||||
|
||||
### `Network.fetchBytes(method: string, url: string, headers: object, data: ArrayBuffer): Promise<{status: number, headers: object, body: ArrayBuffer}>`
|
||||
|
||||
Send a network request and return the response as an ArrayBuffer.
|
||||
|
||||
### `Network.sendRequest(method: string, url: string, headers: object, data: ArrayBuffer): Promise<{status: number, headers: object, body: string}>`
|
||||
|
||||
Send a network request and return the response as a string.
|
||||
|
||||
### `Network.get(url: string, headers: object): Promise<{status: number, headers: object, body: string}>`
|
||||
|
||||
Send a GET request and return the response as a string.
|
||||
|
||||
### `Network.post(url: string, headers: object, data: ArrayBuffer): Promise<{status: number, headers: object, body: string}>`
|
||||
|
||||
Send a POST request and return the response as a string.
|
||||
|
||||
### `Network.put(url: string, headers: object, data: ArrayBuffer): Promise<{status: number, headers: object, body: string}>`
|
||||
|
||||
Send a PUT request and return the response as a string.
|
||||
|
||||
### `Network.delete(url: string, headers: object): Promise<{status: number, headers: object, body: string}>`
|
||||
|
||||
Send a DELETE request and return the response as a string.
|
||||
|
||||
### `Network.patch(url: string, headers: object, data: ArrayBuffer): Promise<{status: number, headers: object, body: string}>`
|
||||
|
||||
Send a PATCH request and return the response as a string.
|
||||
|
||||
### `Network.setCookies(url: string, cookies: Cookie[]): void`
|
||||
|
||||
Set cookies for a specific url.
|
||||
|
||||
### `Network.getCookies(url: string): Cookie[]`
|
||||
|
||||
Get cookies for a specific url.
|
||||
|
||||
### `Network.deleteCookies(url: string): void`
|
||||
|
||||
Delete cookies for a specific url.
|
||||
|
||||
### `fetch`
|
||||
|
||||
The fetch function is a wrapper of the `Network.fetchBytes` function. Same as the `fetch` function in the browser.
|
||||
|
||||
## Html
|
||||
|
||||
Api for parsing HTML.
|
||||
|
||||
### `new HtmlDocument(html: string): HtmlDocument`
|
||||
|
||||
Create a HtmlDocument object from a html string.
|
||||
|
||||
### `HtmlDocument.querySelector(selector: string): HtmlElement`
|
||||
|
||||
Find the first element that matches the selector.
|
||||
|
||||
### `HtmlDocument.querySelectorAll(selector: string): HtmlElement[]`
|
||||
|
||||
Find all elements that match the selector.
|
||||
|
||||
### `HtmlDocument.getElementById(id: string): HtmlElement`
|
||||
|
||||
Find the element with the id.
|
||||
|
||||
### `HtmlDocument.dispose(): void`
|
||||
|
||||
Dispose the HtmlDocument object.
|
||||
|
||||
### `HtmlElement.querySelector(selector: string): HtmlElement`
|
||||
|
||||
Find the first element that matches the selector.
|
||||
|
||||
### `HtmlElement.querySelectorAll(selector: string): HtmlElement[]`
|
||||
|
||||
Find all elements that match the selector.
|
||||
|
||||
### `HtmlElement.getElementById(id: string): HtmlElement`
|
||||
|
||||
Find the element with the id.
|
||||
|
||||
### `get HtmlElement.text(): string`
|
||||
|
||||
Get the text content of the element.
|
||||
|
||||
### `get HtmlElement.attributes(): object`
|
||||
|
||||
Get the attributes of the element.
|
||||
|
||||
### `get HtmlElement.children(): HtmlElement[]`
|
||||
|
||||
Get the children
|
||||
|
||||
### `get HtmlElement.nodes(): HtmlNode[]`
|
||||
|
||||
Get the child nodes
|
||||
|
||||
### `get HtmlElement.parent(): HtmlElement | null`
|
||||
|
||||
Get the parent element
|
||||
|
||||
### `get HtmlElement.innerHtml(): string`
|
||||
|
||||
Get the inner html
|
||||
|
||||
### `get HtmlElement.classNames(): string[]`
|
||||
|
||||
Get the class names
|
||||
|
||||
### `get HtmlElement.id(): string | null`
|
||||
|
||||
Get the id
|
||||
|
||||
### `get HtmlElement.localName(): string`
|
||||
|
||||
Get the local name
|
||||
|
||||
### `get HtmlElement.previousSibling(): HtmlElement | null`
|
||||
|
||||
Get the previous sibling
|
||||
|
||||
### `get HtmlElement.nextSibling(): HtmlElement | null`
|
||||
|
||||
Get the next sibling
|
||||
|
||||
### `get HtmlNode.type(): string`
|
||||
|
||||
Get the node type ("text", "element", "comment", "document", "unknown")
|
||||
|
||||
### `HtmlNode.toElement(): HtmlElement | null`
|
||||
|
||||
Convert the node to an element
|
||||
|
||||
### `get HtmlNode.text(): string`
|
||||
|
||||
Get the text content of the node
|
||||
|
||||
## UI
|
||||
|
||||
### `UI.showMessage(message: string): void`
|
||||
|
||||
Show a message.
|
||||
|
||||
### `UI.showDialog(title: string, content: string, actions: {text: string, callback: () => void | Promise<void>, style: "text"|"filled"|"danger"}[]): void`
|
||||
|
||||
Show a dialog. Any action will close the dialog.
|
||||
|
||||
### `UI.launchUrl(url: string): void`
|
||||
|
||||
Open a url in external browser.
|
||||
|
||||
### `UI.showLoading(onCancel: () => void | null | undefined): number`
|
||||
|
||||
Show a loading dialog.
|
||||
|
||||
### `UI.cancelLoading(id: number): void`
|
||||
|
||||
Cancel a loading dialog.
|
||||
|
||||
### `UI.showInputDialog(title: string, validator: (string) => string | null | undefined): string | null`
|
||||
|
||||
Show an input dialog.
|
||||
|
||||
### `UI.showSelectDialog(title: string, options: string[], initialIndex?: number): number | null`
|
||||
|
||||
Show a select dialog.
|
||||
|
||||
## Utils
|
||||
|
||||
### `createUuid(): string`
|
||||
|
||||
create a time-based uuid.
|
||||
|
||||
### `randomInt(min: number, max: number): number`
|
||||
|
||||
Generate a random integer between min and max.
|
||||
|
||||
### `randomDouble(min: number, max: number): number`
|
||||
|
||||
Generate a random double between min and max.
|
||||
|
||||
### console
|
||||
|
||||
Send log to application console. Same api as the browser console.
|
||||
|
||||
## Types
|
||||
|
||||
### `Cookie`
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* Create a cookie object.
|
||||
* @param name {string}
|
||||
* @param value {string}
|
||||
* @param domain {string}
|
||||
* @constructor
|
||||
*/
|
||||
function Cookie({name, value, domain}) {
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
this.domain = domain;
|
||||
}
|
||||
```
|
||||
|
||||
### `Comic`
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* Create a comic object
|
||||
* @param id {string}
|
||||
* @param title {string}
|
||||
* @param subtitle {string}
|
||||
* @param subTitle {string} - equal to subtitle
|
||||
* @param cover {string}
|
||||
* @param tags {string[]}
|
||||
* @param description {string}
|
||||
* @param maxPage {number?}
|
||||
* @param language {string?}
|
||||
* @param favoriteId {string?} - Only set this field if the comic is from favorites page
|
||||
* @param stars {number?} - 0-5, double
|
||||
* @constructor
|
||||
*/
|
||||
function Comic({id, title, subtitle, subTitle, cover, tags, description, maxPage, language, favoriteId, stars}) {
|
||||
this.id = id;
|
||||
this.title = title;
|
||||
this.subtitle = subtitle;
|
||||
this.subTitle = subTitle;
|
||||
this.cover = cover;
|
||||
this.tags = tags;
|
||||
this.description = description;
|
||||
this.maxPage = maxPage;
|
||||
this.language = language;
|
||||
this.favoriteId = favoriteId;
|
||||
this.stars = stars;
|
||||
}
|
||||
```
|
||||
|
||||
### `ComicDetails`
|
||||
```javascript
|
||||
/**
|
||||
* Create a comic details object
|
||||
* @param title {string}
|
||||
* @param subtitle {string}
|
||||
* @param subTitle {string} - equal to subtitle
|
||||
* @param cover {string}
|
||||
* @param description {string?}
|
||||
* @param tags {Map<string, string[]> | {} | null | undefined}
|
||||
* @param chapters {Map<string, string> | {} | null | undefined} - key: chapter id, value: chapter title
|
||||
* @param isFavorite {boolean | null | undefined} - favorite status. If the comic source supports multiple folders, this field should be null
|
||||
* @param subId {string?} - a param which is passed to comments api
|
||||
* @param thumbnails {string[]?} - for multiple page thumbnails, set this to null, and use `loadThumbnails` api to load thumbnails
|
||||
* @param recommend {Comic[]?} - related comics
|
||||
* @param commentCount {number?}
|
||||
* @param likesCount {number?}
|
||||
* @param isLiked {boolean?}
|
||||
* @param uploader {string?}
|
||||
* @param updateTime {string?}
|
||||
* @param uploadTime {string?}
|
||||
* @param url {string?}
|
||||
* @param stars {number?} - 0-5, double
|
||||
* @param maxPage {number?}
|
||||
* @param comments {Comment[]?}- `since 1.0.7` App will display comments in the details page.
|
||||
* @constructor
|
||||
*/
|
||||
function ComicDetails({title, subtitle, subTitle, cover, description, tags, chapters, isFavorite, subId, thumbnails, recommend, commentCount, likesCount, isLiked, uploader, updateTime, uploadTime, url, stars, maxPage, comments}) {
|
||||
this.title = title;
|
||||
this.subtitle = subtitle ?? subTitle;
|
||||
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;
|
||||
this.stars = stars;
|
||||
this.maxPage = maxPage;
|
||||
this.comments = comments;
|
||||
}
|
||||
```
|
||||
|
||||
### `Comment`
|
||||
```javascript
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
```
|
||||
|
||||
### `ImageLoadingConfig`
|
||||
```javascript
|
||||
/**
|
||||
* Create image loading config
|
||||
* @param url {string?}
|
||||
* @param method {string?} - http method, uppercase
|
||||
* @param data {any} - request data, may be null
|
||||
* @param headers {Object?} - request headers
|
||||
* @param onResponse {((ArrayBuffer) => ArrayBuffer)?} - modify response data
|
||||
* @param modifyImage {string?}
|
||||
* A js script string.
|
||||
* The script will be executed in a new Isolate.
|
||||
* A function named `modifyImage` should be defined in the script, which receives an [Image] as the only argument, and returns an [Image]..
|
||||
* @param onLoadFailed {(() => ImageLoadingConfig)?} - called when the image loading failed
|
||||
* @constructor
|
||||
* @since 1.0.5
|
||||
*
|
||||
* To keep the compatibility with the old version, do not use the constructor. Consider creating a new object with the properties directly.
|
||||
*/
|
||||
function ImageLoadingConfig({url, method, data, headers, onResponse, modifyImage, onLoadFailed}) {
|
||||
this.url = url;
|
||||
this.method = method;
|
||||
this.data = data;
|
||||
this.headers = headers;
|
||||
this.onResponse = onResponse;
|
||||
this.modifyImage = modifyImage;
|
||||
this.onLoadFailed = onLoadFailed;
|
||||
}
|
||||
```
|
||||
|
||||
### `ComicSource`
|
||||
```javascript
|
||||
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 = {}
|
||||
}
|
||||
```
|
Reference in New Issue
Block a user