Start adding documation and add host.ps1 for windows users (#725)
* Add docs v0.0.1 * Add icon size warning * Fix sourcenover caution * Fix semver descriptions * Filters fix * Quickfix quickstart * Add icon warning v2 * Fix typos * Add NovelItem * Update plugins host * Update plugins host * Remove unnecesarry .js and .dist * Update plugins host * Add host.ps1 * Fix readme.txt * Fix READNE.txt, again * Remove .js and .dist * readme update with ost * Add linux/windows hosting functions * Fix workflows
This commit is contained in:
@@ -23,6 +23,5 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
git config user.name rajarsheechatterjee
|
git config user.name rajarsheechatterjee
|
||||||
git config user.email rajarshee.adm@gmail.com
|
git config user.email rajarshee.adm@gmail.com
|
||||||
chmod +x ./host.sh
|
npm run host-linux
|
||||||
./host.sh
|
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|||||||
@@ -1,61 +1,42 @@
|
|||||||
|
|
||||||
|
|
||||||
# LNReader Plugins
|
# LNReader Plugins
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<img alt="GitHub issues by-label" src="https://img.shields.io/github/issues/lnreader/lnreader-sources/Source%20Request?color=success&label=source%20requests">
|
<img alt="GitHub issues by-label" src="https://img.shields.io/github/issues/lnreader/lnreader-sources/Source%20Request?color=success&label=source%20requests">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<img alt="GitHub issues by-label" src="https://img.shields.io/github/issues/lnreader/lnreader-sources/Bug?color=red&label=bugs">
|
<img alt="GitHub issues by-label" src="https://img.shields.io/github/issues/lnreader/lnreader-sources/Bug?color=red&label=bugs">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
Repository to host plugins and related issues, and requests for
|
||||||
|
[LNReader](https://github.com/LNReader/lnreader).
|
||||||
Repository to host plugins and related issues, and requests for [LNReader](https://github.com/LNReader/lnreader).
|
|
||||||
|
|
||||||
## Installing
|
## Installing
|
||||||
|
|
||||||
- Prerequisites: Nodejs >= 18
|
- Prerequisites: Nodejs >= 18
|
||||||
|
|
||||||
1. `npm install`
|
1. `npm install`
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
1. Choose your language in [plugins](./plugins)
|
- [Quick start](./docs/quickstart.md)
|
||||||
2. Write your scripts
|
- [Documentation](./docs/docs.md)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Examples:
|
|
||||||
+ [Hako](./plugins/vietnamese/LNHako.ts)
|
|
||||||
+ Multisrc: [multisrc](./scripts/multisrc)
|
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
- If you are making a [multisrc](./scripts/multisrc): `npm run generate`
|
|
||||||
- `npm start`
|
|
||||||
- Open http://localhost:3000 and test
|
|
||||||
|
|
||||||
### If you want to test in app side.
|
#### via the testing website
|
||||||
|
|
||||||
- `./host.sh`
|
1. Run `npm start` and open `localhost:3000` to test!
|
||||||
|
|
||||||
- Change these in [pluginManager.ts](https://github.com/LNReader/lnreader/blob/master/src/plugins/pluginManager.ts) (app repo) to yours
|
[Detailed tutorial for testing website](./docs/website-tutorial.md)
|
||||||
|
|
||||||
|
#### via an app
|
||||||
|
|
||||||
|
1. Run `npm run host-linux` or `npm run host-windows` (depending on your operating system)
|
||||||
|
2. Change the values in [pluginManager.ts](https://github.com/LNReader/lnreader/blob/master/src/plugins/pluginManager.ts) (in-app) to yours
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
const githubUsername = 'LNReader';
|
const githubUsername = "LNReader";
|
||||||
const githubRepository = 'lnreader-sources';
|
const githubRepository = "lnreader-sources";
|
||||||
```
|
```
|
||||||
----------
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
The developer of this application does not have any affiliation with the content providers available.
|
The developer of this application does not have any affiliation with the content providers available.
|
||||||
|
|||||||
+527
@@ -0,0 +1,527 @@
|
|||||||
|
## Documentation for LNReader plugins
|
||||||
|
|
||||||
|
- [PluginBase](#pluginbase)
|
||||||
|
- [NovelItem](#novelitem)
|
||||||
|
- [SourceNovel](#sourcenovel)
|
||||||
|
- [ChapterItem](#chapteritem)
|
||||||
|
- [Filters](#filters)
|
||||||
|
- [Using Cheerio](#using-cheerio)
|
||||||
|
- [Custom fetching functions](#custom-fetching-functions)
|
||||||
|
|
||||||
|
Most of the Plugin/Novel type definitions accessed using the `Plugin` namespace imported via
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { Plugin } from "@typings/plugin";
|
||||||
|
```
|
||||||
|
|
||||||
|
### PluginBase
|
||||||
|
|
||||||
|
PluginBase is a base class for all plugins.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
class ExamplePlugin implements Plugin.PluginBase {}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Field | Required | Description |
|
||||||
|
| -------------------------------------------------------------- | -------- | ----------------------------------------------------- |
|
||||||
|
| [id](#pluginbaseid) | yes | Plugin ID |
|
||||||
|
| [name](#pluginbasename) | yes | Plugin Name |
|
||||||
|
| [icon](#pluginbasename) | yes | Plugin Icon |
|
||||||
|
| [site](#pluginbasesite) | yes | Plugin site link |
|
||||||
|
| [version](#pluginbaseversion) | yes | Plugin version |
|
||||||
|
| [filters](#pluginbasefilters) | no | [Filter definition](#filter-definition-object) object |
|
||||||
|
| [popularNovels(page, options)](#pluginbasepopularnovels) | yes | Novel list getter |
|
||||||
|
| [parseNovelAndChapters(url)](#pluginbaseparsenovelandchapters) | yes | Novel info and chapter list getter |
|
||||||
|
| [parseChapter(url)](#pluginbaseparsechapter) | yes | Chapter text getter |
|
||||||
|
| [searchNovels(searchTerm, page)](#pluginbasesearchnovels) | yes | Novel searching getter |
|
||||||
|
| [fetchImage(url)](#pluginbasefetchimage) | yes | Customizable function for fetching images |
|
||||||
|
|
||||||
|
#### PluginBase::id
|
||||||
|
|
||||||
|
Unique ID of your plugin
|
||||||
|
|
||||||
|
```ts
|
||||||
|
class ExamplePlugin implements Plugin.PluginBase {
|
||||||
|
...
|
||||||
|
id = "templateID";
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### PluginBase::name
|
||||||
|
|
||||||
|
The name of your plugin that is shown in-app
|
||||||
|
|
||||||
|
```ts
|
||||||
|
class ExamplePlugin implements Plugin.PluginBase {
|
||||||
|
...
|
||||||
|
name = "template Plugin";
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### PluginBase::icon
|
||||||
|
|
||||||
|
The path to your plugin's icon inside of `icon` folder
|
||||||
|
|
||||||
|
```ts
|
||||||
|
class ExamplePlugin implements Plugin.PluginBase {
|
||||||
|
...
|
||||||
|
icon = "src/eng/templateplugin/icon.png";
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> Icons should be 96x96px
|
||||||
|
|
||||||
|
#### PluginBase::site
|
||||||
|
|
||||||
|
The url to the plugin's site
|
||||||
|
|
||||||
|
###### Example
|
||||||
|
|
||||||
|
```ts
|
||||||
|
class ExamplePlugin implements Plugin.PluginBase {
|
||||||
|
...
|
||||||
|
site = "https://example.com";
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### PluginBase::version
|
||||||
|
|
||||||
|
Version of your plugin formatted according to [semver2.0 spec](https://semver.org/) i.e. `<major>.<minor>.<patch>`
|
||||||
|
|
||||||
|
Where
|
||||||
|
|
||||||
|
- `patch` increments on small fixes that fix the plugin (like site changed a selector, filter had a typo etc.)
|
||||||
|
- `minor` increments on fixes that improve the plugin (like adding/removing filters, adding search options etc.)
|
||||||
|
- `major` increments on fixes that fix the major issues with the plugin (like changing site link)
|
||||||
|
|
||||||
|
###### Example
|
||||||
|
|
||||||
|
```ts
|
||||||
|
class ExamplePlugin implements Plugin.PluginBase {
|
||||||
|
...
|
||||||
|
version = "1.0.0";
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### PluginBase::filters
|
||||||
|
|
||||||
|
A [Filter definition]() object that holds filters used in [popularNovels](#pluginbasepopularnovels) function
|
||||||
|
|
||||||
|
###### Example
|
||||||
|
|
||||||
|
```ts
|
||||||
|
class ExamplePlugin implements Plugin.PluginBase {
|
||||||
|
...
|
||||||
|
filters = {
|
||||||
|
order: {
|
||||||
|
label:"Order",
|
||||||
|
options: [
|
||||||
|
{ label: "Popular", value: "" },
|
||||||
|
{ label: "Newest", value: "newest" }
|
||||||
|
],
|
||||||
|
type: FilterTypes.Picker,
|
||||||
|
value: ""
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
label: "Status",
|
||||||
|
options: [
|
||||||
|
{ label: "All", value: "" },
|
||||||
|
{ label: "Ongoing", value: "ongoing" },
|
||||||
|
{ label: "Hiatus", value: "hiatus" },
|
||||||
|
{ label: "Completed", value: "completed" },
|
||||||
|
],
|
||||||
|
type: FilterTypes.Picker,
|
||||||
|
value: "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### PluginBase::popularNovels
|
||||||
|
|
||||||
|
Function that is used to get the (filtered) list of novels from the front page of the site
|
||||||
|
|
||||||
|
```ts
|
||||||
|
async popularNovels(
|
||||||
|
page: number,
|
||||||
|
options: Plugin.PopularNovelsOptions<typeof this.filters>
|
||||||
|
): Promise<Plugin.NovelItem[]>
|
||||||
|
```
|
||||||
|
|
||||||
|
See [Using cheerio](#using-cheerio) for more information on how to parse HTML documents
|
||||||
|
|
||||||
|
###### Parameters
|
||||||
|
|
||||||
|
- `page` current page to fetch
|
||||||
|
- `options` [PopularNovelsOptions](#pluginbasepopularnovelsoptions)
|
||||||
|
|
||||||
|
###### Returns
|
||||||
|
|
||||||
|
`NovelItem[]` An array of filtered main-page [NovelItems](#novelitem)
|
||||||
|
|
||||||
|
###### Example:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
class ExamplePlugin implements Plugin.PluginBase {
|
||||||
|
...
|
||||||
|
async popularNovels(
|
||||||
|
page: number,
|
||||||
|
options: Plugin.PopularNovelsOptions<typeof this.filters>
|
||||||
|
): Promise<Plugin.NovelItem[]> {
|
||||||
|
const novels: Plugin.NovelItem[] = [];
|
||||||
|
if(options.filters.example.value === "test"){
|
||||||
|
novels.push({
|
||||||
|
name: "Novel1",
|
||||||
|
url: "https://example.com/novel1",
|
||||||
|
cover:defaultCover
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return novels;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### PluginBase::PopularNovelsOptions
|
||||||
|
|
||||||
|
This type is used for getting the options of the [popularNovels](#pluginbasepopularnovels) function
|
||||||
|
|
||||||
|
- <span id='popularnovelsoptions-showlatestnovels'></span>`showLatestNovels: boolean` flag set when opened with `Latest` button
|
||||||
|
|
||||||
|
- <span id='popularnovelsoptions-showlatestnovels'></span>`filters: FilterValues<typeof filters>` object containing all selected filter values. [More about Filters](#filters)
|
||||||
|
|
||||||
|
#### PluginBase::parseNovelAndChapters
|
||||||
|
|
||||||
|
Function that is used to get the information about particular novel and the list of it's chapters
|
||||||
|
|
||||||
|
```ts
|
||||||
|
async parseNovelAndChapters(novelUrl: string): Promise<Plugin.SourceNovel>
|
||||||
|
```
|
||||||
|
|
||||||
|
See [Using cheerio](#using-cheerio) for more information on how to parse HTML documents
|
||||||
|
|
||||||
|
###### Parameters
|
||||||
|
|
||||||
|
- `novelUrl` value from [NovelItem::url](#novelitemurl)
|
||||||
|
|
||||||
|
###### Returns
|
||||||
|
|
||||||
|
`SourceNovel` Novel information and chapter list as [SourceNovel](#sourcenovel) object
|
||||||
|
|
||||||
|
> [!CAUTION] > [SourceNovel::url]() should be the same value as [NovelItem::url]() provided as parameter!
|
||||||
|
|
||||||
|
###### Example:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
class ExamplePlugin implements Plugin.PluginBase {
|
||||||
|
...
|
||||||
|
async parseNovelAndChapters(novelUrl: string): Promise<Plugin.SourceNovel> {
|
||||||
|
const novel: Plugin.SourceNovel = {
|
||||||
|
url: novelUrl,
|
||||||
|
name: "test",
|
||||||
|
artist: "none",
|
||||||
|
author: "none",
|
||||||
|
cover: defaultCover,
|
||||||
|
genres: "Isekai, Neverland",
|
||||||
|
status: NovelStatus.Completed,
|
||||||
|
summary: ""
|
||||||
|
};
|
||||||
|
let chapters: Plugin.ChapterItem[] = [];
|
||||||
|
const chapter: Plugin.ChapterItem = {
|
||||||
|
name: "",
|
||||||
|
url: "",
|
||||||
|
releaseTime: "",
|
||||||
|
chapterNumber: 0,
|
||||||
|
};
|
||||||
|
chapters.push(chapter);
|
||||||
|
novel.chapters = chapters;
|
||||||
|
return novel;
|
||||||
|
}
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### PluginBase::parseChapter
|
||||||
|
|
||||||
|
Function that is used to get the information about particular novel and the list of it's chapters
|
||||||
|
|
||||||
|
```ts
|
||||||
|
async parseChapter(chapterUrl: string): Promise<string>
|
||||||
|
```
|
||||||
|
|
||||||
|
See [Using cheerio](#using-cheerio) for more information on how to parse HTML documents
|
||||||
|
|
||||||
|
###### Parameters
|
||||||
|
|
||||||
|
- `chapterUrl` value from [ChapterItem::url](#chapteritemurl)
|
||||||
|
|
||||||
|
###### Returns
|
||||||
|
|
||||||
|
`string` HTML content of the chapter
|
||||||
|
|
||||||
|
###### Example:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
class ExamplePlugin implements Plugin.PluginBase {
|
||||||
|
...
|
||||||
|
async parseChapter(chapterUrl: string): Promise<string>{
|
||||||
|
return "<h1>No chapter here</h1>";
|
||||||
|
}
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### PluginBase::searchNovels
|
||||||
|
|
||||||
|
Function that is used to find Novels in the source
|
||||||
|
|
||||||
|
```ts
|
||||||
|
async searchNovels(searchTerm: string, pageNo: number): Promise<Plugin.NovelItem[]>
|
||||||
|
```
|
||||||
|
|
||||||
|
See [Using cheerio](#using-cheerio) for more information on how to parse HTML documents
|
||||||
|
|
||||||
|
###### Parameters
|
||||||
|
|
||||||
|
- `searchTerm` the search term
|
||||||
|
- `page` search page number
|
||||||
|
|
||||||
|
###### Returns
|
||||||
|
|
||||||
|
`NovelItem[]` An array of found [NovelItems](#novelitem)
|
||||||
|
|
||||||
|
###### Example
|
||||||
|
|
||||||
|
```ts
|
||||||
|
class ExamplePlugin implements Plugin.PluginBase {
|
||||||
|
...
|
||||||
|
async searchNovels(
|
||||||
|
searchTerm: string,
|
||||||
|
pageNo: number
|
||||||
|
): Promise<Plugin.NovelItem[]> {
|
||||||
|
let novels: Plugin.NovelItem[] = [];
|
||||||
|
return novels;
|
||||||
|
}
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### PluginBase::fetchImage
|
||||||
|
|
||||||
|
Function used if images failed to load due to site's protection
|
||||||
|
|
||||||
|
```ts
|
||||||
|
async fetchImage(url: string): Promise<string | undefined>
|
||||||
|
```
|
||||||
|
|
||||||
|
See [Fetch functions]() for detailed list of fetch functions provided by us to help with fetching data
|
||||||
|
|
||||||
|
###### Parameter
|
||||||
|
|
||||||
|
- `url` Image's url to fetch
|
||||||
|
|
||||||
|
###### Returns
|
||||||
|
|
||||||
|
- `string` base64 representation of the image
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
- `undefined` on error
|
||||||
|
|
||||||
|
###### Example
|
||||||
|
|
||||||
|
```ts
|
||||||
|
class ExamplePlugin implements Plugin.PluginBase {
|
||||||
|
...
|
||||||
|
async fetchImage(url: string): Promise<string | undefined> {
|
||||||
|
const headers = {
|
||||||
|
Referer: "https://ln.hako.vn",
|
||||||
|
};
|
||||||
|
return await fetchFile(url, { headers: headers });
|
||||||
|
}
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### NovelItem
|
||||||
|
|
||||||
|
It is an object representing information how to store/access the novel
|
||||||
|
|
||||||
|
| Field | type | Required | Description |
|
||||||
|
| -------------------------------- | -------- | -------- | ------------------------------------------ |
|
||||||
|
| <p id="novelitemurl">url</p> | `string` | yes | The url to the site |
|
||||||
|
| <p id="novelitemname">name</p> | `string` | yes | The name of the novel shown in the library |
|
||||||
|
| <p id="novelitemcover">cover</p> | `string` | no | URL to novel's cover |
|
||||||
|
|
||||||
|
#### Default cover
|
||||||
|
|
||||||
|
You can use the default `Cover not available` cover by importing
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { defaultCover } from "@libs/defaultCover";
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### SourceNovel
|
||||||
|
|
||||||
|
| Field | Type | Required | Desciption |
|
||||||
|
| ----- | ------ | -------- | ---------- |
|
||||||
|
| url | string | yes | |
|
||||||
|
| name | string | no | string |
|
||||||
|
|cover|`string`|no||
|
||||||
|
|genres|`string`|no||
|
||||||
|
|summary|`string`|no||
|
||||||
|
|author|`string`|no||
|
||||||
|
|artist|`string`|no||
|
||||||
|
|status|[NovelStatus] or `string`|no||
|
||||||
|
chapters?: ChapterItem[];
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ChapterItem
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Filters
|
||||||
|
|
||||||
|
`Filters` and `FilterTypes` are not in the `Plugin` namespace and are from `@libs/filterInputs` file:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { FilterTypes, Filters } from "@libs/filterInputs";
|
||||||
|
```
|
||||||
|
|
||||||
|
There are 2 main objects when using filters:
|
||||||
|
|
||||||
|
- [Filter definition](#filter-definition-object) object
|
||||||
|
- [FilterValues](#filterValue) object
|
||||||
|
|
||||||
|
#### Filter definition object
|
||||||
|
|
||||||
|
This is the user-defined object that defines strictly what filters are available in the "filter" menu in app.
|
||||||
|
Every property of this object is a different filter. The key of the object is the name that will be used to reference this filter's value in the [FilterValues](#filtervalues-object) object
|
||||||
|
|
||||||
|
```ts
|
||||||
|
filters = {
|
||||||
|
order: {<FilterProperties>}
|
||||||
|
} satisfies Filters;
|
||||||
|
// accessible in popularNovels as
|
||||||
|
options.filters.order
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!CAUTION]
|
||||||
|
> Do not forget to add `satisfies Filters` after the Filter definition object!
|
||||||
|
|
||||||
|
##### FilterProperties
|
||||||
|
|
||||||
|
| Name | Type | Required | Desciption |
|
||||||
|
| ------- | ---------------------------- | ------------- | ------------------------------------------------------------------ |
|
||||||
|
| label | `string` | yes | in-app label |
|
||||||
|
| type | `FilterTypes` | yes | type of the filter |
|
||||||
|
| value | [check types](#filter-types) | yes | Default value for this filter and the starting filter state in-app |
|
||||||
|
| options | [check types](#filter-types) | in some types | The options available in the given type |
|
||||||
|
|
||||||
|
###### Example
|
||||||
|
|
||||||
|
```ts
|
||||||
|
filters = {
|
||||||
|
genre: {
|
||||||
|
type: FilterTypes.CheckboxGroup,
|
||||||
|
label: "Genres",
|
||||||
|
value: [],
|
||||||
|
options: [
|
||||||
|
{ label: "Isekai", value: "isekai" },
|
||||||
|
{ label: "Romance", value: "romans" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
} satisfies Filters;
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Filter types
|
||||||
|
|
||||||
|
Types of filters supported
|
||||||
|
|
||||||
|
| FilterType | Description | `value` | `options` |
|
||||||
|
| ------------------------- | ------------------------------------------------------------------ | ---------------------------------------------------------------------------- | ----------------------------------------------- |
|
||||||
|
| `Picker` | A spinner for choosing one of the choices provided in `options` | `string` the picked value | [Picker](#picker-options) options |
|
||||||
|
| `TextInput` | A filter allowing a free text input | `string` written value | N/A |
|
||||||
|
| `Switch` | A boolean switch | `boolean` state of the switch | N/A |
|
||||||
|
| `CheckboxGroup` | A grouping of checkboxes | `string[]` array containing selected values | [CheckboxGroup](#checkboxgroup-options) options |
|
||||||
|
| `ExcludableCheckboxGroup` | A filter allowing to pick one of the choices provided in `options` | [ExcludableCheckboxGroupValues](#excludablecheckboxgroupvalue-object) object | [CheckboxGroup](#checkboxgroup-options) options |
|
||||||
|
|
||||||
|
###### Picker options
|
||||||
|
|
||||||
|
```ts
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: "default", // in-app label
|
||||||
|
value: "", // in-code value
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Value ABC",
|
||||||
|
value: "abc",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
###### CheckboxGroup options
|
||||||
|
|
||||||
|
```ts
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: "Value ABC", // in-app label
|
||||||
|
value: "abc", // in-code value
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Value DEF",
|
||||||
|
value: "def",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
#### FilterValues object
|
||||||
|
|
||||||
|
It is an object used inisde of `popularNovels` that contains selected values for all filters defined in the [Filter definition](#filter-definition-object) object.
|
||||||
|
The keys of the filter values correspond to Filter definition keys
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// Filter definition object
|
||||||
|
filters = { abc: {} } satisfies Filters;
|
||||||
|
|
||||||
|
// then
|
||||||
|
options.filters; // FilterValues
|
||||||
|
options.filters.abc; // FilterValue for abc filter
|
||||||
|
```
|
||||||
|
|
||||||
|
##### FilterValue
|
||||||
|
|
||||||
|
Properties of FilterValue:
|
||||||
|
|
||||||
|
- `type: FilterType` type of the filter
|
||||||
|
- `value` value dependent on [FilterTypes](#filter-types)
|
||||||
|
|
||||||
|
```ts
|
||||||
|
options.filters.abc.value; // value of the filter
|
||||||
|
options.filters.abc.type; // type of the filter
|
||||||
|
```
|
||||||
|
|
||||||
|
###### ExcludableCheckboxGroupValue object
|
||||||
|
|
||||||
|
```ts
|
||||||
|
{
|
||||||
|
included: string[], // options with selected selected
|
||||||
|
excluded: string[] // options with excluded selected
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using Cheerio
|
||||||
|
|
||||||
|
### Custom fetching functions
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import { fetchFile } from "@libs/fetch";
|
||||||
|
import { Plugin } from "@typings/plugin";
|
||||||
|
import { Filters } from "@libs/filterInputs";
|
||||||
|
import { load as loadCheerio } from "cheerio";
|
||||||
|
import { defaultCover } from "@libs/defaultCover";
|
||||||
|
import { NovelStatus } from "@libs/novelStatus";
|
||||||
|
// import { isUrlAbsolute } from "@libs/isAbsoluteUrl";
|
||||||
|
// import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||||
|
|
||||||
|
class TemplatePlugin implements Plugin.PluginBase {
|
||||||
|
id = "";
|
||||||
|
name = "";
|
||||||
|
icon = "";
|
||||||
|
site = "";
|
||||||
|
version = "1.0.0";
|
||||||
|
filters: Filters | undefined = undefined;
|
||||||
|
|
||||||
|
async popularNovels(
|
||||||
|
pageNo: number,
|
||||||
|
{
|
||||||
|
showLatestNovels,
|
||||||
|
filters,
|
||||||
|
}: Plugin.PopularNovelsOptions<typeof this.filters>
|
||||||
|
): Promise<Plugin.NovelItem[]> {
|
||||||
|
const novels: Plugin.NovelItem[] = [];
|
||||||
|
|
||||||
|
/** Add your fetching code here */
|
||||||
|
novels.push({
|
||||||
|
name: "Novel1",
|
||||||
|
url: "example.com/novel1",
|
||||||
|
cover: defaultCover,
|
||||||
|
});
|
||||||
|
return novels;
|
||||||
|
}
|
||||||
|
async parseNovelAndChapters(novelUrl: string): Promise<Plugin.SourceNovel> {
|
||||||
|
const novel: Plugin.SourceNovel = {
|
||||||
|
url: novelUrl,
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: get here data from the site and
|
||||||
|
// un-comment and fill-in the relevant fields
|
||||||
|
|
||||||
|
// novel.name = "";
|
||||||
|
// novel.artist = "";
|
||||||
|
// novel.author = "";
|
||||||
|
novel.cover = defaultCover;
|
||||||
|
// novel.genres = "";
|
||||||
|
// novel.status = NovelStatus.Completed;
|
||||||
|
// novel.summary = "";
|
||||||
|
|
||||||
|
let chapters: Plugin.ChapterItem[] = [];
|
||||||
|
|
||||||
|
// TODO: here parse the chapter list
|
||||||
|
|
||||||
|
// TODO: add each chapter to the list using
|
||||||
|
const chapter: Plugin.ChapterItem = {
|
||||||
|
name: "",
|
||||||
|
url: "",
|
||||||
|
releaseTime: "",
|
||||||
|
chapterNumber: 0,
|
||||||
|
};
|
||||||
|
chapters.push(chapter);
|
||||||
|
|
||||||
|
novel.chapters = chapters;
|
||||||
|
return novel;
|
||||||
|
}
|
||||||
|
async parseChapter(chapterUrl: string): Promise<string> {
|
||||||
|
// parse chapter text here
|
||||||
|
const chapterText = "";
|
||||||
|
return chapterText;
|
||||||
|
}
|
||||||
|
async searchNovels(
|
||||||
|
searchTerm: string,
|
||||||
|
pageNo: number
|
||||||
|
): Promise<Plugin.NovelItem[]> {
|
||||||
|
let novels: Plugin.NovelItem[] = [];
|
||||||
|
|
||||||
|
// get novels using the search term
|
||||||
|
|
||||||
|
return novels;
|
||||||
|
}
|
||||||
|
async fetchImage(url: string): Promise<string | undefined> {
|
||||||
|
// if your plugin has images and they won't load
|
||||||
|
// this is the function to fiddle with
|
||||||
|
return fetchFile(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new TemplatePlugin();
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# Quick start
|
||||||
|
|
||||||
|
1. [Requirements](#requirements)
|
||||||
|
2. [Single plugin guide](#quick-guide)
|
||||||
|
3. [Multi-src guide](#creating-multi-src-plugins)
|
||||||
|
|
||||||
|
### Requirements
|
||||||
|
|
||||||
|
- [git](https://git-scm.com/doc/ext) basics
|
||||||
|
- Typescript or Javascript basics
|
||||||
|
- Node >=18
|
||||||
|
- Installing the dependencies with `npm i`
|
||||||
|
|
||||||
|
### Guide
|
||||||
|
|
||||||
|
1. Create plugin script in `/plugins` [<span style="font-size: 0.8rem;">(learn more)</span>](#creating-plugin-script)
|
||||||
|
2. Copy code from [plugin-template.ts](./plugin-template.ts)
|
||||||
|
3. Start coding [<span style="font-size:0.8rem">(documentation)</span>](./docs.md)
|
||||||
|
|
||||||
|
#### Creating plugin script
|
||||||
|
|
||||||
|
1. Remember to create your plugin inside the language folder corresponding to the language of the novels
|
||||||
|
2. File should have the `.ts` extension
|
||||||
|
Example `plugins/english/nobleMTL.ts`
|
||||||
|
3. Add an icon to `icons/src/<lang>/<plugin-name>/icon.png`
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> Icon size should be 96x96px!
|
||||||
|
|
||||||
|
### Creating multi-source plugins
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
$current=$(git rev-parse --abbrev-ref HEAD)
|
||||||
|
$dist='dist'
|
||||||
|
$exists=$(git show-ref refs/heads/$dist)
|
||||||
|
|
||||||
|
echo $current
|
||||||
|
echo $exists
|
||||||
|
|
||||||
|
if ($exists){
|
||||||
|
git checkout $dist
|
||||||
|
}else{
|
||||||
|
## Make a new one
|
||||||
|
git checkout -b $dist
|
||||||
|
}
|
||||||
|
|
||||||
|
if(-Not $?){
|
||||||
|
# If checkout failed
|
||||||
|
echo "=========="
|
||||||
|
echo "Could not checkout branch dist! See the error above and fix it!"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
git merge $current --strategy-option theirs
|
||||||
|
|
||||||
|
npm run generate
|
||||||
|
npm run json
|
||||||
|
git add .
|
||||||
|
git add -f .dist .js/plugins
|
||||||
|
git commit -m "Update plugins host"
|
||||||
|
git push -f origin $dist
|
||||||
|
|
||||||
|
git checkout $current
|
||||||
|
|
||||||
@@ -8,6 +8,13 @@ else
|
|||||||
git checkout -b $dist
|
git checkout -b $dist
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ $? -eq 1 ]; then
|
||||||
|
# If checkout failed
|
||||||
|
echo "=========="
|
||||||
|
echo "Could not checkout branch dist! See the error above and fix it!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
git merge $current --strategy-option theirs
|
git merge $current --strategy-option theirs
|
||||||
|
|
||||||
npm run generate
|
npm run generate
|
||||||
@@ -18,3 +25,4 @@ git commit -m "Update plugins host"
|
|||||||
git push -f origin $dist
|
git push -f origin $dist
|
||||||
|
|
||||||
git checkout $current
|
git checkout $current
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -10,7 +10,8 @@
|
|||||||
"generate": "ts-node ./scripts/multisrc/generate.ts",
|
"generate": "ts-node ./scripts/multisrc/generate.ts",
|
||||||
"clearMulti": "ts-node ./scripts/clearMultisrc.ts",
|
"clearMulti": "ts-node ./scripts/clearMultisrc.ts",
|
||||||
"less": "npx less ./test_web/static/css/index.less ./test_web/static/css/index.css",
|
"less": "npx less ./test_web/static/css/index.less ./test_web/static/css/index.css",
|
||||||
"host": "host.sh"
|
"host-linux": "chmod +x ./host.sh && ./host.sh",
|
||||||
|
"host-windows": "powershell ./host.ps1"
|
||||||
},
|
},
|
||||||
"author": "LNReader",
|
"author": "LNReader",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -7,14 +7,6 @@ import { Filters } from "@libs/filterInputs";
|
|||||||
// const isUrlAbsolute = require('@libs/isAbsoluteUrl');
|
// const isUrlAbsolute = require('@libs/isAbsoluteUrl');
|
||||||
// const parseDate = require('@libs/parseDate');
|
// const parseDate = require('@libs/parseDate');
|
||||||
|
|
||||||
const pluginId = "yomou.syosetu";
|
|
||||||
|
|
||||||
export const id = pluginId;
|
|
||||||
export const name = "Syosetu";
|
|
||||||
export const icon = "src/jp/syosetu/icon.png";
|
|
||||||
export const version = "1.0.0";
|
|
||||||
export const site = "https://yomou.syosetu.com/";
|
|
||||||
|
|
||||||
class Syosetu implements Plugin.PluginBase {
|
class Syosetu implements Plugin.PluginBase {
|
||||||
id = "yomou.syosetu";
|
id = "yomou.syosetu";
|
||||||
name = "Syosetu";
|
name = "Syosetu";
|
||||||
|
|||||||
+3
-1
@@ -22,7 +22,9 @@ export namespace Plugin {
|
|||||||
export interface SourceNovel {
|
export interface SourceNovel {
|
||||||
url: string;
|
url: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
|
/** Novel cover absolute URL */
|
||||||
cover?: string;
|
cover?: string;
|
||||||
|
/** Comma separated genre list */
|
||||||
genres?: string;
|
genres?: string;
|
||||||
summary?: string;
|
summary?: string;
|
||||||
author?: string;
|
author?: string;
|
||||||
@@ -68,7 +70,7 @@ export namespace Plugin {
|
|||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param url Image url
|
* @param url Image url
|
||||||
* @returns {string} Base64 of image
|
* @returns {Promise<string|undefined>} Base64 of image
|
||||||
* @example
|
* @example
|
||||||
* ```ts
|
* ```ts
|
||||||
* const headers = {
|
* const headers = {
|
||||||
|
|||||||
Reference in New Issue
Block a user