Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 11 additions & 13 deletions .github/workflows/publish-plugins.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ on:
push:
branches:
- master
- Lunox-Scans
paths:
- 'plugins/**'
- 'public/**'
- 'scripts/publish-plugins.sh'
- '.github/workflows/publish-plugins.yml'

permissions:
contents: read
contents: write

concurrency:
group: ${{ github.workflow }}
Expand All @@ -23,29 +24,26 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@v4
with:
token: ${{ secrets.REPO_SCOPED_TOKEN }}
token: ${{ secrets.GITHUB_TOKEN }}
fetch-depth: 0

- name: Fetch All Remote Branches
run: git fetch origin '+refs/heads/*:refs/remotes/origin/*'

- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
uses: actions/setup-node@v4
with:
node-version: '24'

- name: Install Dependencies
run: npm ci --omit=dev --ignore-scripts
run: npm ci --ignore-scripts

- name: Configure Git
run: |
git config user.name "github-actions[bot]"
git config user.email 41898282+github-actions[bot]@users.noreply.github.com

- name: Publish Plugins (Main Repository)
if: github.repository == 'LNReader/lnreader-plugins'
run: npm run publish:plugins 2>> $GITHUB_STEP_SUMMARY
shell: bash

- name: Publish Plugins (Fork - All Branches)
if: github.repository != 'LNReader/lnreader-plugins'
- name: Publish Plugins
run: npm run publish:plugins -- --all-branches
shell: bash
264 changes: 264 additions & 0 deletions plugins/multisrc/madara/filters/lunoxscans.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
{
"filters": {
"genre[]": {
"type": "Checkbox",
"label": "Genre",
"value": [],
"options": [
{
"label": "Action",
"value": "action"
},
{
"label": "Adult",
"value": "adult"
},
{
"label": "Adventure",
"value": "adventure"
},
{
"label": "Chaebol",
"value": "chaebol"
},
{
"label": "Comedy",
"value": "comedy"
},
{
"label": "Drama",
"value": "drama"
},
{
"label": "Fantasy",
"value": "fantasy"
},
{
"label": "Growth",
"value": "growth"
},
{
"label": "Harem",
"value": "harem"
},
{
"label": "Historical",
"value": "historical"
},
{
"label": "Horror",
"value": "horror"
},
{
"label": "Isekai",
"value": "isekai"
},
{
"label": "Josei",
"value": "josei"
},
{
"label": "Magic",
"value": "magic"
},
{
"label": "Martial Arts",
"value": "martial-arts"
},
{
"label": "Mature",
"value": "mature"
},
{
"label": "Mecha",
"value": "mecha"
},
{
"label": "Modern",
"value": "modern"
},
{
"label": "Mystery",
"value": "mystery"
},
{
"label": "Possession",
"value": "possession"
},
{
"label": "Psychological",
"value": "psychological"
},
{
"label": "Regression",
"value": "regression"
},
{
"label": "Reincarnation",
"value": "reincarnation"
},
{
"label": "Revenge",
"value": "revenge"
},
{
"label": "Reverse",
"value": "reverse"
},
{
"label": "Romance",
"value": "romance"
},
{
"label": "Royalty",
"value": "royalty"
},
{
"label": "s",
"value": "s"
},
{
"label": "School Life",
"value": "school-life"
},
{
"label": "Sci-fi",
"value": "sci-fi"
},
{
"label": "Seinen",
"value": "seinen"
},
{
"label": "Shoujo",
"value": "shoujo"
},
{
"label": "Shounen",
"value": "shounen"
},
{
"label": "Slice of Life",
"value": "slice-of-life"
},
{
"label": "Sports",
"value": "sports"
},
{
"label": "Supernatural",
"value": "supernatural"
},
{
"label": "Tragedy",
"value": "tragedy"
},
{
"label": "Warrior",
"value": "warrior"
},
{
"label": "Wuxia",
"value": "wuxia"
},
{
"label": "x",
"value": "x"
}
]
},
"op": {
"type": "Switch",
"label": "having all selected genres",
"value": false
},
"author": {
"type": "Text",
"label": "Author",
"value": ""
},
"artist": {
"type": "Text",
"label": "Artist",
"value": ""
},
"release": {
"type": "Text",
"label": "Year of Released",
"value": ""
},
"adult": {
"type": "Picker",
"label": "Adult content",
"value": "",
"options": [
{
"label": "All",
"value": ""
},
{
"label": "None adult content",
"value": "0"
},
{
"label": "Only adult content",
"value": "1"
}
]
},
"status[]": {
"type": "Checkbox",
"label": "Status",
"value": [],
"options": [
{
"label": "OnGoing",
"value": "on-going"
},
{
"label": "Completed",
"value": "end"
},
{
"label": "Canceled",
"value": "canceled"
},
{
"label": "On Hold",
"value": "on-hold"
},
{
"label": "Upcoming",
"value": "upcoming"
}
]
},
"m_orderby": {
"type": "Picker",
"label": "Order by",
"value": "",
"options": [
{
"label": "Relevance",
"value": ""
},
{
"label": "Latest",
"value": "latest"
},
{
"label": "A-Z",
"value": "alphabet"
},
{
"label": "Most Views",
"value": "views"
},
{
"label": "New",
"value": "new-manga"
}
]
}
}
}
10 changes: 10 additions & 0 deletions plugins/multisrc/madara/sources.json
Original file line number Diff line number Diff line change
Expand Up @@ -651,5 +651,15 @@
"useNewChapterEndpoint": true,
"versionIncrements": 2
}
},
{
"id": "lunoxscans",
"sourceSite": "https://lunoxscans.com/",
"sourceName": "LunoxScans",
"options": {
"lang": "English",
"useNewChapterEndpoint": false,
"versionIncrements": 2
}
}
]
51 changes: 49 additions & 2 deletions plugins/multisrc/ranobes/template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,59 @@ export class RanobesPlugin implements Plugin.PluginBase {
this.name = metadata.sourceName;
this.icon = 'multisrc/ranobes/ranobes/icon.png';
this.site = metadata.sourceSite;
this.version = '2.0.2';
this.version = '2.0.3';
this.options = metadata.options as RanobesOptions;
}
private static requestQueue: Promise<void> = Promise.resolve();
private static lastRequestTime = 0;
private static readonly MIN_DELAY_MS = 1200;

async safeFecth(url: string, init?: FetchInit): Promise<string> {
private async throttle(): Promise<void> {
const previous = RanobesPlugin.requestQueue;
Comment thread
D3ICIDE marked this conversation as resolved.
let release!: () => void;
RanobesPlugin.requestQueue = new Promise<void>(res => {
release = res;
});
try {
await previous;
const now = Date.now();
const elapsed = now - RanobesPlugin.lastRequestTime;
const remaining = RanobesPlugin.MIN_DELAY_MS - elapsed;
if (remaining > 0) {
await new Promise(res => setTimeout(res, remaining));
}
RanobesPlugin.lastRequestTime = Date.now();
} finally {
release();
}
}

async safeFecth(url: string, init?: FetchInit, retries = 3): Promise<string> {
await this.throttle();
const r = await fetchApi(url, init);

if (r.status === 429) {
if (retries <= 0)
throw new Error('Rate limited (429), too many retries.');

const retryAfter = r.headers.get('Retry-After');
let waitMs = 3000; // safe default
if (retryAfter) {
if (/^\d+$/.test(retryAfter.trim())) {
// delta-seconds format
waitMs = parseInt(retryAfter, 10) * 1000;
} else {
// HTTP-date format
const dateMs = Date.parse(retryAfter);
if (!isNaN(dateMs)) {
waitMs = Math.max(0, dateMs - Date.now());
}
}
}
await new Promise(res => setTimeout(res, waitMs));
return this.safeFecth(url, init, retries - 1);
}

if (!r.ok)
throw new Error(
'Could not reach site (' + r.status + ') try to open in webview.',
Expand Down
Binary file added public/static/multisrc/madara/lunoxscans/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading