Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4763afecfc |
+2578
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -1,7 +0,0 @@
|
||||
# Override the default user content base path
|
||||
# Default when empty: https://raw.githubusercontent.com/${USERNAME}/${REPO}/${CURRENT_BRANCH}
|
||||
#
|
||||
# for android emulator use 10.0.2.2
|
||||
# for iOS simulator use 127.0.0.1 or localhost
|
||||
# for a real device use the IP address of your computer in the local network (e.g. 192.168.21.37)
|
||||
USER_CONTENT_BASE=http://localhost:3000
|
||||
@@ -1 +0,0 @@
|
||||
blank_issues_enabled: false
|
||||
@@ -1,407 +0,0 @@
|
||||
# DO NOT EDIT THIS FILE DIRECTLY
|
||||
# This file is auto-generated from blank_report_issue.yml in .github/scripts/
|
||||
# Any changes made here will be overwritten when plugins are published.
|
||||
|
||||
name: Report Issue
|
||||
description: Report a plugin issue in LNReader
|
||||
labels: [bug]
|
||||
body:
|
||||
- type: dropdown
|
||||
id: source
|
||||
attributes:
|
||||
label: Plugin
|
||||
description: Select the affected plugin(s).
|
||||
multiple: true
|
||||
options:
|
||||
- "العربية: ArNovel"
|
||||
- "العربية: Azora"
|
||||
- "العربية: dilar tube"
|
||||
- "العربية: Free Kol Novel"
|
||||
- "العربية: Galaxy Novels"
|
||||
- "العربية: HizoManga"
|
||||
- "العربية: Kol Novel"
|
||||
- "العربية: مركز الروايات"
|
||||
- "العربية: Novel4Up"
|
||||
- "العربية: Novels Paradise"
|
||||
- "العربية: Olaoe.cyou"
|
||||
- "العربية: روايه فانز"
|
||||
- "العربية: Rewayat Club"
|
||||
- "العربية: روايات فانز"
|
||||
- "العربية: Riwyat"
|
||||
- "العربية: Seanovel"
|
||||
- "العربية: Sunovels"
|
||||
- "中文, 汉语, 漢語: 69书吧"
|
||||
- "中文, 汉语, 漢語: 爱下电子书"
|
||||
- "中文, 汉语, 漢語: linovel"
|
||||
- "中文, 汉语, 漢語: Linovelib"
|
||||
- "中文, 汉语, 漢語: Linovelib(繁體)"
|
||||
- "中文, 汉语, 漢語: Novel543"
|
||||
- "中文, 汉语, 漢語: Quanben"
|
||||
- "English: FirstKissNovel"
|
||||
- "English: AllNovel"
|
||||
- "English: AllNovelFull"
|
||||
- "English: Anime Anyway"
|
||||
- "English: Arcane Translations"
|
||||
- "English: Archive Of Our Own"
|
||||
- "English: Srank Manga"
|
||||
- "English: Baka-Tsuki"
|
||||
- "English: Belle Reservoir"
|
||||
- "English: BlumeVerse"
|
||||
- "English: BoxNovel"
|
||||
- "English: Cherry Mist Cafe"
|
||||
- "English: Chikari"
|
||||
- "English: Chrysanthemum Garden"
|
||||
- "English: Citrus Aurora"
|
||||
- "English: Coral Boutique"
|
||||
- "English: CPUnovel"
|
||||
- "English: Crimson Scrolls"
|
||||
- "English: Daoist Quest"
|
||||
- "English: DaoNovel"
|
||||
- "English: DaoTekno"
|
||||
- "English: DaoTranslate"
|
||||
- "English: Divine Dao Library"
|
||||
- "English: Dearest Rosalie"
|
||||
- "English: DobyNovels"
|
||||
- "English: Dragonholic"
|
||||
- "English: Dragon Tea"
|
||||
- "English: Dream Big Translations"
|
||||
- "English: Dreamy Translations"
|
||||
- "English: Dusk Blossoms"
|
||||
- "English: ElloTL"
|
||||
- "English: Eternalune"
|
||||
- "English: Etude Translations"
|
||||
- "English: FanNovel"
|
||||
- "English: Fans Translations"
|
||||
- "English: Fenrir Realm"
|
||||
- "English: Fiction Zone"
|
||||
- "English: Firebird's Nest"
|
||||
- "English: Foxaholic"
|
||||
- "English: Foxteller"
|
||||
- "English: FuckNovelpia"
|
||||
- "English: Faq Wiki"
|
||||
- "English: Free Web Novel"
|
||||
- "English: Galaxy Translations"
|
||||
- "English: Genesis"
|
||||
- "English: Guavaread"
|
||||
- "English: HangulPlanet"
|
||||
- "English: Hiraeth Translation"
|
||||
- "English: HotNovelPub"
|
||||
- "English: Hyacinth in Bloom"
|
||||
- "English: Indra Translations"
|
||||
- "English: Inkitt"
|
||||
- "English: iNovelTranslation"
|
||||
- "English: Ippotranslations"
|
||||
- "English: KDT Novels"
|
||||
- "English: Keopi Translations"
|
||||
- "English: KnoxT"
|
||||
- "English: Konkon"
|
||||
- "English: Lazy Girl Translations"
|
||||
- "English: LeafStudio"
|
||||
- "English: Lib Read"
|
||||
- "English: LightNovelCave"
|
||||
- "English: LightNovelPlus"
|
||||
- "English: LightNovelPub Vip"
|
||||
- "English: Light Novel Translations"
|
||||
- "English: Light Novel Updates"
|
||||
- "English: LightNovelWorld"
|
||||
- "English: Lily on the Valley"
|
||||
- "English: LightNovelHeaven"
|
||||
- "English: LnMTL"
|
||||
- "English: LNORI"
|
||||
- "English: Lovelyblossoms"
|
||||
- "English: Ltnovel"
|
||||
- "English: LulloBox"
|
||||
- "English: LunarLetters"
|
||||
- "English: Meownovel"
|
||||
- "English: Moonlight Novels"
|
||||
- "English: MostNovel"
|
||||
- "English: MTL-Novel"
|
||||
- "English: MTL Novel"
|
||||
- "English: MVLEMPYR"
|
||||
- "English: Mynovels"
|
||||
- "English: MysticalSeries"
|
||||
- "English: NeoSekai Translations"
|
||||
- "English: Nitro Manga"
|
||||
- "English: novelsOnline"
|
||||
- "English: NobleMTL"
|
||||
- "English: Noice Translations"
|
||||
- "English: Novel7s"
|
||||
- "English: Novel Arrow"
|
||||
- "English: Novel Bin"
|
||||
- "English: NovelBuddy"
|
||||
- "English: NovelCool"
|
||||
- "English: NovelDex"
|
||||
- "English: Novel Fire"
|
||||
- "English: NovelFull"
|
||||
- "English: Novel Hall"
|
||||
- "English: NovelHi"
|
||||
- "English: NovelLib"
|
||||
- "English: Novelight"
|
||||
- "English: NovelMultiverse"
|
||||
- "English: Novel Nice"
|
||||
- "English: Novel Ninja"
|
||||
- "English: Novel Phoenix"
|
||||
- "English: NovelRest"
|
||||
- "English: Novel Short"
|
||||
- "English: NovelsKnight"
|
||||
- "English: NovelTranslate"
|
||||
- "English: Novel Updates"
|
||||
- "English: Panda Machine Translations"
|
||||
- "English: Pastel Tales"
|
||||
- "English: PawRead"
|
||||
- "English: Peach Puff Translations"
|
||||
- "English: Penguin Squad"
|
||||
- "English: Prizma"
|
||||
- "English: Puffin Folio"
|
||||
- "English: Rainofsnow"
|
||||
- "English: Ranobes"
|
||||
- "English: Ranovel"
|
||||
- "English: Read Fanfic"
|
||||
- "English: Read From Net"
|
||||
- "English: ReadNovelFull"
|
||||
- "English: Re:Library"
|
||||
- "English: Requiem Translations"
|
||||
- "English: Royal Road"
|
||||
- "English: Salmon Latte"
|
||||
- "English: Scribble Hub"
|
||||
- "English: Skythewood Translations"
|
||||
- "English: SleepyTranslations"
|
||||
- "English: SonicMTL"
|
||||
- "English: StorySeedling"
|
||||
- "English: Sweet Escape"
|
||||
- "English: System Translation"
|
||||
- "English: Tangerine Archive"
|
||||
- "English: TranslatinOtaku"
|
||||
- "English: Translation Weaver"
|
||||
- "English: Universal Novel"
|
||||
- "English: Vandy Translate"
|
||||
- "English: Violet Lily"
|
||||
- "English: VyNovel"
|
||||
- "English: Webnovel"
|
||||
- "English: WebNovelLover"
|
||||
- "English: Web Novel Translation"
|
||||
- "English: Web Novel Pub"
|
||||
- "English: White Moonlight Novels"
|
||||
- "English: Witch Cult Translations"
|
||||
- "English: Wook's Teahouse"
|
||||
- "English: WordExcerpt"
|
||||
- "English: WTR-LAB"
|
||||
- "English: Wuxiafox"
|
||||
- "English: Fans MTL"
|
||||
- "English: Wuxiabox"
|
||||
- "English: Wuxia Space"
|
||||
- "English: WuxiaV"
|
||||
- "English: Wuxia World"
|
||||
- "English: WuxiaWorld.Site"
|
||||
- "English: Zetro Translation"
|
||||
- "Français: Chireads"
|
||||
- "Français: HarkenEliwood"
|
||||
- "Français: J-Garden"
|
||||
- "Français: KissWood"
|
||||
- "Français: Ligh Novel FR"
|
||||
- "Français: LightNovelVF"
|
||||
- "Français: MassNovel"
|
||||
- "Français: MTL Novel (FR)"
|
||||
- "Français: NovelDeGlace"
|
||||
- "Français: NovelFrance"
|
||||
- "Français: Novhell"
|
||||
- "Français: Re:Zero Web Novel FR"
|
||||
- "Français: Trad-Index"
|
||||
- "Français: Warrior Legend Trad"
|
||||
- "Français: WorldNovel"
|
||||
- "Français: WuxiaLnScantrad"
|
||||
- "Français: Xiaowaz"
|
||||
- "Bahasa Indonesia: Baca Light Novel"
|
||||
- "Bahasa Indonesia: IndoWebNovel"
|
||||
- "Bahasa Indonesia: MeioNovel"
|
||||
- "Bahasa Indonesia: Vanovel"
|
||||
- "Bahasa Indonesia: MTL Novel (ID)"
|
||||
- "Bahasa Indonesia: NovelBookID"
|
||||
- "Bahasa Indonesia: SakuraNovel"
|
||||
- "Bahasa Indonesia: Sekte Novel"
|
||||
- "Bahasa Indonesia: WBNovel"
|
||||
- "日本語: kakuyomu"
|
||||
- "日本語: Syosetu"
|
||||
- "조선말, 한국어: Agitoon"
|
||||
- "조선말, 한국어: Fortune Eternal"
|
||||
- "Polski: Novelki"
|
||||
- "Português: Better Novels"
|
||||
- "Português: Blog do Amon Novels"
|
||||
- "Português: Central Novel"
|
||||
- "Português: Illusia"
|
||||
- "Português: Kiniga"
|
||||
- "Português: LaNovels"
|
||||
- "Português: Light Novel Brasil"
|
||||
- "Português: MTL Novel (PT)"
|
||||
- "Português: Novel Mania"
|
||||
- "Português: Tsundoku Traduções"
|
||||
- "Русский: Автор Тудей"
|
||||
- "Русский: Bllate (API)"
|
||||
- "Русский: Bookhamster"
|
||||
- "Русский: Bookriver"
|
||||
- "Русский: Erolate (API)"
|
||||
- "Русский: EzNovels"
|
||||
- "Русский: ficbook"
|
||||
- "Русский: Свободный Мир Ранобэ"
|
||||
- "Русский: Jaomix"
|
||||
- "Русский: MTL Novel (RU)"
|
||||
- "Русский: Neobook"
|
||||
- "Русский: NovelCool (RU)"
|
||||
- "Русский: Ranobes (RU)"
|
||||
- "Русский: Renovels"
|
||||
- "Русский: RanobeLib"
|
||||
- "Русский: RanobeHub"
|
||||
- "Русский: РанобэРФ"
|
||||
- "Русский: Rulate (API)"
|
||||
- "Русский: NovelTL"
|
||||
- "Русский: ТопЛиба"
|
||||
- "Русский: Целлюлоза"
|
||||
- "Español: AllNovelRead"
|
||||
- "Español: AnimesHoy12"
|
||||
- "Español: Hasu Translations"
|
||||
- "Español: LightNovelDaily"
|
||||
- "Español: MTL Novel (ES)"
|
||||
- "Español: NOVA"
|
||||
- "Español: Novelas Ligera"
|
||||
- "Español: Novelyra"
|
||||
- "Español: Oasis Translations"
|
||||
- "Español: Pancho Translations"
|
||||
- "Español: ReinoWuxia"
|
||||
- "Español: SkyNovels"
|
||||
- "Español: TC & Sega"
|
||||
- "Español: Traducciones Amistosas"
|
||||
- "Español: TuNovelaLigera"
|
||||
- "Español: Yuuki Tls"
|
||||
- "ไทย: Novel Lucky"
|
||||
- "ไทย: Novel PDF"
|
||||
- "Türkçe: Araz Novel"
|
||||
- "Türkçe: E-KİTAPLAR"
|
||||
- "Türkçe: EpikNovel"
|
||||
- "Türkçe: kakikata"
|
||||
- "Türkçe: Kodeks Library"
|
||||
- "Türkçe: MangaTR"
|
||||
- "Türkçe: NABİ SCANS"
|
||||
- "Türkçe: Namevt"
|
||||
- "Türkçe: Novel oku"
|
||||
- "Türkçe: NovelTR"
|
||||
- "Türkçe: Ragnar Scans"
|
||||
- "Türkçe: ThNovels"
|
||||
- "Türkçe: TurkceLightNovels"
|
||||
- "Türkçe: WebNovelOku"
|
||||
- "Українська: BakaInUA"
|
||||
- "Українська: Смаколики"
|
||||
- "Tiếng Việt: Light Novel VN"
|
||||
- "Tiếng Việt: Hako"
|
||||
- "Tiếng Việt: Nettruyen"
|
||||
- "Tiếng Việt: TruyenDich"
|
||||
- "Tiếng Việt: TruyenSS"
|
||||
- "Multi: Komga"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: severity
|
||||
attributes:
|
||||
label: Severity
|
||||
description: Select the issue severity level.
|
||||
multiple: false
|
||||
options:
|
||||
- Wrong Formatting
|
||||
- Wrong Content
|
||||
- Missing Chapter
|
||||
- Missing Images
|
||||
- Can't Load Novels
|
||||
- Domain Changed
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: reproduce-steps
|
||||
attributes:
|
||||
label: Steps To Reproduce
|
||||
description: Describe the steps to reproduce the issue.
|
||||
placeholder: |
|
||||
Example:
|
||||
1. Open the plugin
|
||||
2. Search for a novel
|
||||
3. Error appears
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: expected-behavior
|
||||
attributes:
|
||||
label: Expected Behavior
|
||||
description: What should happen?
|
||||
placeholder: |
|
||||
Example:
|
||||
"The search results should display correctly..."
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: actual-behavior
|
||||
attributes:
|
||||
label: Actual Behavior
|
||||
description: What actually happened?
|
||||
placeholder: |
|
||||
Example:
|
||||
"An error message appeared instead..."
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: lnreader-version
|
||||
attributes:
|
||||
label: LNReader Version
|
||||
description: Find this in **More → About**.
|
||||
placeholder: |
|
||||
Example: "2.0.0"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: plugin-version
|
||||
attributes:
|
||||
label: Plugin Version
|
||||
description: Find this in **Browse → Installed** next to the plugin name.
|
||||
placeholder: |
|
||||
Example: "1.0.1"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: android-version
|
||||
attributes:
|
||||
label: Android Version
|
||||
description: Find this in your device settings.
|
||||
placeholder: |
|
||||
Example: "Android 15"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: other-details
|
||||
attributes:
|
||||
label: Other Details
|
||||
description: Any additional information or screenshots.
|
||||
placeholder: |
|
||||
Additional context, screenshots, or relevant information.
|
||||
|
||||
- type: checkboxes
|
||||
id: acknowledgements
|
||||
attributes:
|
||||
label: Acknowledgements
|
||||
description: Your issue will be closed if these requirements are not met.
|
||||
options:
|
||||
- label: I have searched existing issues and confirmed this is not a duplicate.
|
||||
required: true
|
||||
- label: I have written a clear and descriptive title.
|
||||
required: true
|
||||
- label: I have updated the app to the latest version **[2.0.0](https://github.com/LNReader/lnreader/releases/tag/v2.0.0-beta.3)**.
|
||||
required: true
|
||||
- label: I have updated all installed plugins to the latest version.
|
||||
required: true
|
||||
- label: I understand that app-specific issues should be reported in the [app repository](https://github.com/LNReader/lnreader/issues/new/choose).
|
||||
required: true
|
||||
@@ -1,39 +0,0 @@
|
||||
name: Request Feature
|
||||
description: Suggest an improvement for an existing plugin
|
||||
labels: [feature request]
|
||||
body:
|
||||
- type: textarea
|
||||
id: feature-description
|
||||
attributes:
|
||||
label: Feature Description
|
||||
description: Describe the feature you would like to see added.
|
||||
placeholder: |
|
||||
Example:
|
||||
"Add support for latest novels list in Royal Road plugin..."
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: other-details
|
||||
attributes:
|
||||
label: Other Details
|
||||
description: Any additional context or mockups.
|
||||
placeholder: |
|
||||
Additional information, use cases, or examples.
|
||||
|
||||
- type: checkboxes
|
||||
id: acknowledgements
|
||||
attributes:
|
||||
label: Acknowledgements
|
||||
description: Your issue will be closed if these requirements are not met.
|
||||
options:
|
||||
- label: I have searched existing issues and confirmed this is not a duplicate.
|
||||
required: true
|
||||
- label: I have written a clear and descriptive title.
|
||||
required: true
|
||||
- label: I understand that app-specific features should be requested in the [app repository](https://github.com/LNReader/lnreader/issues/new/choose).
|
||||
required: true
|
||||
- label: I have updated the app to the latest version **[1.1.19](https://github.com/LNReader/lnreader/releases/latest)**.
|
||||
required: true
|
||||
- label: I have provided all requested information in this form.
|
||||
required: true
|
||||
@@ -1,61 +0,0 @@
|
||||
name: Request Plugin
|
||||
description: Request a new plugin for LNReader
|
||||
labels: [plugin request]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
> **Note:** Creating a plugin request does not guarantee it will be added. Please wait for a maintainer or contributor to pick it up — or, since this is open source, feel free to contribute the plugin yourself!
|
||||
|
||||
- type: input
|
||||
id: name
|
||||
attributes:
|
||||
label: Plugin Name
|
||||
description: The name of the website or platform.
|
||||
placeholder: |
|
||||
Example: "Novel Updates"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: link
|
||||
attributes:
|
||||
label: Website URL
|
||||
description: The URL of the website.
|
||||
placeholder: |
|
||||
Example: "https://example.com"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: language
|
||||
attributes:
|
||||
label: Language
|
||||
description: The primary language of the website.
|
||||
placeholder: |
|
||||
Example: "English"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: other-details
|
||||
attributes:
|
||||
label: Other Details
|
||||
description: Any additional information about the website.
|
||||
placeholder: |
|
||||
Why this plugin would be useful, special features, etc.
|
||||
|
||||
- type: checkboxes
|
||||
id: acknowledgements
|
||||
attributes:
|
||||
label: Acknowledgements
|
||||
description: Your issue will be closed if these requirements are not met.
|
||||
options:
|
||||
- label: I have searched existing issues and confirmed this plugin has not been requested before.
|
||||
required: true
|
||||
- label: I have used the plugin name as the issue title.
|
||||
required: true
|
||||
- label: I have verified this plugin does not already exist in the [repository](https://github.com/LNReader/lnreader-plugins).
|
||||
required: true
|
||||
- label: I have provided all requested information in this form.
|
||||
required: true
|
||||
@@ -1,6 +0,0 @@
|
||||
#### Checklist
|
||||
|
||||
- [ ] Update version code if an existing plugin was modified
|
||||
- [ ] Test changes in Plugin Playground or the app
|
||||
- [ ] Reference related issues in the PR body (e.g. Closes #xyz)
|
||||
- [ ] Commit messages follow `type(scope): description` (e.g. `feat(<generator>): add new source`)
|
||||
@@ -1,172 +0,0 @@
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const themeDirectories = {
|
||||
madara: 'madara',
|
||||
lightnovelwp: 'lightnovelwp',
|
||||
hotnovelpub: 'hotnovelpub',
|
||||
fictioneer: 'fictioneer',
|
||||
};
|
||||
|
||||
function issueField(body, label) {
|
||||
const escapedLabel = label.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
const match = body.match(
|
||||
new RegExp(`(?:^|\\n)### ${escapedLabel}\\s*\\n+([^\\n]+)`, 'i'),
|
||||
);
|
||||
const value = match?.[1]?.trim();
|
||||
return value && value !== '_No response_' ? value : '';
|
||||
}
|
||||
|
||||
function sourceId(name, hostname) {
|
||||
const id = name
|
||||
.normalize('NFKD')
|
||||
.replace(/[\u0300-\u036f]/g, '')
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, '');
|
||||
|
||||
return (
|
||||
id ||
|
||||
hostname
|
||||
.replace(/^www\./, '')
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, '')
|
||||
);
|
||||
}
|
||||
|
||||
function canonicalLanguage(language) {
|
||||
const directory = path.join(
|
||||
process.cwd(),
|
||||
'plugins',
|
||||
language.trim().toLowerCase(),
|
||||
);
|
||||
if (!language || !fs.existsSync(directory)) {
|
||||
throw new Error(`Unsupported language from issue: "${language}"`);
|
||||
}
|
||||
|
||||
return (
|
||||
language.trim().charAt(0).toUpperCase() +
|
||||
language.trim().slice(1).toLowerCase()
|
||||
);
|
||||
}
|
||||
|
||||
function hotNovelPubLanguage(language) {
|
||||
const codes = {
|
||||
English: 'en',
|
||||
Russian: 'ru',
|
||||
Spanish: 'es',
|
||||
Portuguese: 'pt',
|
||||
Turkish: 'th',
|
||||
};
|
||||
const code = codes[language];
|
||||
if (!code) {
|
||||
throw new Error(
|
||||
`HotNovelPub does not map the requested language "${language}"`,
|
||||
);
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
function writeOutput(key, value) {
|
||||
if (!process.env.GITHUB_OUTPUT) return;
|
||||
fs.appendFileSync(process.env.GITHUB_OUTPUT, `${key}=${value}\n`);
|
||||
}
|
||||
|
||||
const body = process.env.ISSUE_BODY || '';
|
||||
const theme = (process.env.THEME || '').toLowerCase();
|
||||
const themeDirectory = themeDirectories[theme];
|
||||
|
||||
if (!themeDirectory) {
|
||||
throw new Error(`Unsupported detected theme: "${theme}"`);
|
||||
}
|
||||
|
||||
const submittedUrl = issueField(body, 'Website URL');
|
||||
const name = issueField(body, 'Plugin Name');
|
||||
const language = canonicalLanguage(issueField(body, 'Language'));
|
||||
|
||||
if (!submittedUrl || !name) {
|
||||
throw new Error('The issue is missing a Website URL or Plugin Name');
|
||||
}
|
||||
if (name.length > 100 || /[\/\\\0\r\n]/.test(name)) {
|
||||
throw new Error('The Plugin Name contains unsupported characters');
|
||||
}
|
||||
|
||||
let url;
|
||||
try {
|
||||
url = new URL(submittedUrl);
|
||||
} catch {
|
||||
throw new Error(`Invalid Website URL from issue: "${submittedUrl}"`);
|
||||
}
|
||||
|
||||
if (!['http:', 'https:'].includes(url.protocol)) {
|
||||
throw new Error(`Unsupported Website URL protocol: "${url.protocol}"`);
|
||||
}
|
||||
|
||||
const sourceSite = url.origin;
|
||||
const id = sourceId(name, url.hostname);
|
||||
const sourcesPath = path.join(
|
||||
process.cwd(),
|
||||
'plugins',
|
||||
'multisrc',
|
||||
themeDirectory,
|
||||
'sources.json',
|
||||
);
|
||||
const sourcesText = fs.readFileSync(sourcesPath, 'utf8');
|
||||
const sources = JSON.parse(sourcesText);
|
||||
|
||||
const duplicate = sources.find(source => {
|
||||
try {
|
||||
return (
|
||||
source.id.toLowerCase() === id.toLowerCase() ||
|
||||
new URL(source.sourceSite).hostname
|
||||
.replace(/^www\./, '')
|
||||
.toLowerCase() === url.hostname.replace(/^www\./, '').toLowerCase()
|
||||
);
|
||||
} catch {
|
||||
return source.id.toLowerCase() === id.toLowerCase();
|
||||
}
|
||||
});
|
||||
|
||||
if (duplicate) {
|
||||
console.log(
|
||||
`Skipping ${sourceSite}: it already matches source "${duplicate.id}"`,
|
||||
);
|
||||
writeOutput('changed', 'false');
|
||||
writeOutput('source_name', name.replace(/[\r\n]/g, ' '));
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const source = {
|
||||
id,
|
||||
sourceSite,
|
||||
sourceName: name,
|
||||
};
|
||||
|
||||
if (theme === 'fictioneer') {
|
||||
source.options = {
|
||||
browsePage: 'browse',
|
||||
...(language === 'English' ? {} : { lang: language }),
|
||||
};
|
||||
} else if (theme === 'hotnovelpub') {
|
||||
const lang = hotNovelPubLanguage(language);
|
||||
if (lang !== 'en') source.options = { lang };
|
||||
} else if (language !== 'English') {
|
||||
source.options = { lang: language };
|
||||
}
|
||||
|
||||
const serializedSource = JSON.stringify(source, null, 2)
|
||||
.split('\n')
|
||||
.map(line => ` ${line}`)
|
||||
.join('\n');
|
||||
const updatedSources = sourcesText.replace(
|
||||
/\n\]\s*$/,
|
||||
`,\n${serializedSource}\n]\n`,
|
||||
);
|
||||
if (updatedSources === sourcesText) {
|
||||
throw new Error(`Could not append a source to ${sourcesPath}`);
|
||||
}
|
||||
fs.writeFileSync(sourcesPath, updatedSources);
|
||||
|
||||
console.log(`Added ${id} to ${path.relative(process.cwd(), sourcesPath)}`);
|
||||
writeOutput('changed', 'true');
|
||||
writeOutput('source_name', name.replace(/[\r\n]/g, ' '));
|
||||
writeOutput('source_id', id);
|
||||
@@ -1,125 +0,0 @@
|
||||
# DO NOT EDIT THIS FILE DIRECTLY
|
||||
# This file is auto-generated from blank_report_issue.yml in .github/scripts/
|
||||
# Any changes made here will be overwritten when plugins are published.
|
||||
|
||||
name: Report Issue
|
||||
description: Report a plugin issue in LNReader
|
||||
labels: [bug]
|
||||
body:
|
||||
- type: dropdown
|
||||
id: source
|
||||
attributes:
|
||||
label: Plugin
|
||||
description: Select the affected plugin(s).
|
||||
multiple: true
|
||||
options:
|
||||
{#CHANGE#}
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: severity
|
||||
attributes:
|
||||
label: Severity
|
||||
description: Select the issue severity level.
|
||||
multiple: false
|
||||
options:
|
||||
- Wrong Formatting
|
||||
- Wrong Content
|
||||
- Missing Chapter
|
||||
- Missing Images
|
||||
- Can't Load Novels
|
||||
- Domain Changed
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: reproduce-steps
|
||||
attributes:
|
||||
label: Steps To Reproduce
|
||||
description: Describe the steps to reproduce the issue.
|
||||
placeholder: |
|
||||
Example:
|
||||
1. Open the plugin
|
||||
2. Search for a novel
|
||||
3. Error appears
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: expected-behavior
|
||||
attributes:
|
||||
label: Expected Behavior
|
||||
description: What should happen?
|
||||
placeholder: |
|
||||
Example:
|
||||
"The search results should display correctly..."
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: actual-behavior
|
||||
attributes:
|
||||
label: Actual Behavior
|
||||
description: What actually happened?
|
||||
placeholder: |
|
||||
Example:
|
||||
"An error message appeared instead..."
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: lnreader-version
|
||||
attributes:
|
||||
label: LNReader Version
|
||||
description: Find this in **More → About**.
|
||||
placeholder: |
|
||||
Example: "2.0.0"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: plugin-version
|
||||
attributes:
|
||||
label: Plugin Version
|
||||
description: Find this in **Browse → Installed** next to the plugin name.
|
||||
placeholder: |
|
||||
Example: "1.0.1"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: android-version
|
||||
attributes:
|
||||
label: Android Version
|
||||
description: Find this in your device settings.
|
||||
placeholder: |
|
||||
Example: "Android 15"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: other-details
|
||||
attributes:
|
||||
label: Other Details
|
||||
description: Any additional information or screenshots.
|
||||
placeholder: |
|
||||
Additional context, screenshots, or relevant information.
|
||||
|
||||
- type: checkboxes
|
||||
id: acknowledgements
|
||||
attributes:
|
||||
label: Acknowledgements
|
||||
description: Your issue will be closed if these requirements are not met.
|
||||
options:
|
||||
- label: I have searched existing issues and confirmed this is not a duplicate.
|
||||
required: true
|
||||
- label: I have written a clear and descriptive title.
|
||||
required: true
|
||||
- label: I have updated the app to the latest version **[2.0.0](https://github.com/LNReader/lnreader/releases/tag/v2.0.0-beta.3)**.
|
||||
required: true
|
||||
- label: I have updated all installed plugins to the latest version.
|
||||
required: true
|
||||
- label: I understand that app-specific issues should be reported in the [app repository](https://github.com/LNReader/lnreader/issues/new/choose).
|
||||
required: true
|
||||
@@ -1,86 +0,0 @@
|
||||
const version = require('../../package.json').version;
|
||||
const dist = `plugins/v${version}`;
|
||||
const fs = require('fs');
|
||||
|
||||
const rawText = fs.readFileSync(
|
||||
'.github/scripts/blank_report_issue.yml',
|
||||
'utf8',
|
||||
);
|
||||
|
||||
async function main() {
|
||||
console.log(`Fetching published plugin manifest from ${dist}...`);
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
`https://raw.githubusercontent.com/LNReader/lnreader-plugins/${dist}/.dist/plugins.min.json`,
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Failed to fetch plugins: ${response.status} ${response.statusText}`,
|
||||
);
|
||||
}
|
||||
|
||||
const pluginsRaw = await response.json();
|
||||
console.log(`Loaded ${pluginsRaw.length} plugins from the manifest.`);
|
||||
|
||||
const plugins = pluginsRaw.reduce((arr, plugin) => {
|
||||
arr[plugin.lang + ': ' + plugin.name] = plugin.id;
|
||||
return arr;
|
||||
}, {});
|
||||
|
||||
let newKeys = Object.keys(plugins);
|
||||
let savedKeys = [];
|
||||
try {
|
||||
let keys = JSON.parse(
|
||||
fs.readFileSync('.github/scripts/keys.json', 'utf8'),
|
||||
);
|
||||
savedKeys = Object.keys(keys);
|
||||
console.log(`Loaded ${savedKeys.length} saved plugin keys.`);
|
||||
} catch (err) {
|
||||
console.log('No saved plugin keys found; a new key map will be created.');
|
||||
}
|
||||
|
||||
const text = newKeys.join('"\n - "');
|
||||
const issueTemplate = rawText.replace(/{#CHANGE#}/g, '- "' + text + '"');
|
||||
|
||||
const keysChanged = !sameKeys(newKeys, savedKeys) && Array.isArray(newKeys);
|
||||
if (keysChanged) {
|
||||
console.log(
|
||||
`Plugin keys changed (${savedKeys.length} → ${newKeys.length}); writing keys.json.`,
|
||||
);
|
||||
fs.writeFileSync('.github/scripts/keys.json', JSON.stringify(plugins));
|
||||
} else {
|
||||
console.log('Plugin keys are up to date.');
|
||||
}
|
||||
|
||||
const templateChanged =
|
||||
fs.readFileSync('.github/ISSUE_TEMPLATE/report_issue.yml', 'utf8') !==
|
||||
issueTemplate;
|
||||
if (templateChanged) {
|
||||
fs.writeFileSync(
|
||||
'.github/ISSUE_TEMPLATE/report_issue.yml',
|
||||
issueTemplate,
|
||||
);
|
||||
console.log('Issue template changed; writing report_issue.yml.');
|
||||
} else {
|
||||
console.log('Issue template is up to date.');
|
||||
}
|
||||
|
||||
console.log(
|
||||
`Generation complete (${keysChanged || templateChanged ? 'changes written' : 'no changes'}).`,
|
||||
);
|
||||
|
||||
function sameKeys(a, b) {
|
||||
return a.length === b.length && a.every(value => b.includes(value));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error generating issue template options:', error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error('Fatal error:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
File diff suppressed because one or more lines are too long
@@ -1,97 +0,0 @@
|
||||
name: Auto Label Issues
|
||||
|
||||
on:
|
||||
issues:
|
||||
types:
|
||||
- opened
|
||||
- edited
|
||||
|
||||
permissions: {}
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.issue.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
auto-label:
|
||||
name: Auto Label Issues
|
||||
if: ${{ github.event.issue.body && contains(github.event.issue.body, '### Plugin') && contains(github.event.issue.labels.*.name, 'bug') }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
env:
|
||||
ISSUE_BODY: ${{ github.event.issue.body }}
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Extract Severity From Issue Body
|
||||
id: extract-severity
|
||||
run: |
|
||||
USER_SELECTED_SEVERITY=$(printf '%s\n' "$ISSUE_BODY" | grep "### Severity" -A 2 | tail -n 1 | sed 's/[[:space:]]*$//')
|
||||
SELECTED_SEVERITY=""
|
||||
|
||||
case "$USER_SELECTED_SEVERITY" in
|
||||
"Wrong Formatting"|"Wrong Content"|"Missing Chapter"|"Missing Images"|"Can't Load Novels"|"Domain Changed")
|
||||
SELECTED_SEVERITY="$USER_SELECTED_SEVERITY"
|
||||
;;
|
||||
*)
|
||||
SELECTED_SEVERITY="Other"
|
||||
;;
|
||||
esac
|
||||
echo "SELECTED_SEVERITY=$SELECTED_SEVERITY" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Extract Plugin From Issue Body
|
||||
id: parse-issue
|
||||
run: |
|
||||
SELECTED_PLUGIN=$(printf '%s\n' "$ISSUE_BODY" | awk '/### Plugin/ {f=1; next} f && NF {print; exit}' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
|
||||
|
||||
echo "SELECTED_PLUGIN=$SELECTED_PLUGIN" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Extract Language From Issue Body
|
||||
id: extract-language
|
||||
run: |
|
||||
SELECTED_LANGUAGE=$(printf '%s\n' "$ISSUE_BODY" | awk '/### Language/ {f=1; next} f && NF {print; exit}' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
|
||||
|
||||
echo "SELECTED_LANGUAGE=$SELECTED_LANGUAGE" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Determine Corresponding Label
|
||||
id: determine-label
|
||||
run: |
|
||||
LABELS="$(jq --arg keys "$SELECTED_PLUGIN" '. as $data | $keys | split(", ") as $keyList | $keyList[] | . as $key | if $data[$key] != null then "\($key)[\($data[$key])]" else empty end' ./.github/scripts/keys.json | sed 's/"//g' | sed 's/^[^:]*: //')"
|
||||
|
||||
LABELS="$(printf '%s' "$LABELS" | node -e "process.stdin.on('data', data => process.stdout.write(data.toString().toLowerCase()))")"
|
||||
|
||||
# Add "plugin: " in front of each label
|
||||
LABELS_WITH_PREFIX="$(echo "$LABELS" | sed 's/^/plugin: /')"
|
||||
|
||||
FINAL_LABELS="$LABELS_WITH_PREFIX"
|
||||
|
||||
if [ -n "$SELECTED_LANGUAGE" ]; then
|
||||
LOWERCASE_LANGUAGE="$(printf '%s' "$SELECTED_LANGUAGE" | node -e "process.stdin.on('data', data => process.stdout.write(data.toString().toLowerCase()))")"
|
||||
FINAL_LABELS="${FINAL_LABELS}"$'\n'"language: $LOWERCASE_LANGUAGE"
|
||||
fi
|
||||
|
||||
# Save to GitHub environment
|
||||
printf "LABELS<<EOF\n%s\nEOF\n" "$FINAL_LABELS" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Add Labels To Issue
|
||||
if: env.LABELS != ''
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
ISSUE_NUMBER: ${{ github.event.issue.number }}
|
||||
REPOSITORY: ${{ github.repository }}
|
||||
run: |
|
||||
jq -n \
|
||||
--arg severity "$(printf '%s' "$SELECTED_SEVERITY" | node -e "process.stdin.on('data', data => process.stdout.write(data.toString().toLowerCase()))")" \
|
||||
--arg labels "$LABELS" \
|
||||
'{labels: (["severity: " + $severity] + ($labels | split("\n") | map(select(length > 0))))}' \
|
||||
| gh api --method POST "repos/$REPOSITORY/issues/$ISSUE_NUMBER/labels" --input -
|
||||
|
||||
- name: Handle Missing Label
|
||||
if: env.LABELS == ''
|
||||
run: echo "No matching label found for the selected plugin."
|
||||
@@ -1,48 +0,0 @@
|
||||
name: Issue Moderator
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened, edited, reopened]
|
||||
issue_comment:
|
||||
types: [created]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.issue.number }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
autoclose:
|
||||
name: Auto Close Invalid Issues
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Moderate Issues
|
||||
uses: tachiyomiorg/issue-moderator-action@v2
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
duplicate-label: duplicate
|
||||
|
||||
duplicate-check-enabled: true
|
||||
duplicate-check-labels: |
|
||||
["plugin request", "domain changed"]
|
||||
|
||||
existing-check-enabled: false
|
||||
|
||||
auto-close-rules: |
|
||||
[
|
||||
{
|
||||
"type": "body",
|
||||
"regex": ".*DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT.*",
|
||||
"message": "The acknowledgment section was not removed."
|
||||
},
|
||||
{
|
||||
"type": "body",
|
||||
"regex": ".*\\* (LNReader version|Android version|Device): \\?.*",
|
||||
"message": "Requested information in the template was not filled out."
|
||||
},
|
||||
{
|
||||
"type": "title",
|
||||
"regex": ".*(Plugin name|Short description).*",
|
||||
"message": "You did not fill out the description in the title"
|
||||
}
|
||||
]
|
||||
auto-close-ignore-label: do-not-autoclose
|
||||
@@ -1,51 +0,0 @@
|
||||
name: Lint Check
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Lint Check
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Get Changed Files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@v45
|
||||
|
||||
- name: Setup Node.js
|
||||
if: steps.changed-files.outputs.any_changed == 'true'
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install Dependencies
|
||||
if: steps.changed-files.outputs.any_changed == 'true'
|
||||
run: npm ci
|
||||
|
||||
- name: Run ESLint with Reviewdog
|
||||
if: steps.changed-files.outputs.any_changed == 'true'
|
||||
uses: reviewdog/action-eslint@v1
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
reporter: github-pr-review
|
||||
eslint_flags: "${{ steps.changed-files.outputs.all_changed_files }}"
|
||||
filter_mode: diff_context
|
||||
fail_on_error: false
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
name: Lock Threads
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions: {}
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
lock:
|
||||
name: Lock Inactive Threads
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Lock Inactive Issues And Pull Requests
|
||||
uses: dessant/lock-threads@89ae32b08ed1a541efecbab17912962a5e38981c # v6.0.2
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
issue-inactive-days: '2'
|
||||
pr-inactive-days: '2'
|
||||
@@ -1,80 +0,0 @@
|
||||
name: Plugin Live Check
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [master]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
plugin_path:
|
||||
description: 'Plugin file(s) to check (space-separated relative paths)'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
live-check:
|
||||
name: Plugin Live Check
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Get Changed Plugin Files
|
||||
id: changed-files
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: tj-actions/changed-files@v45
|
||||
with:
|
||||
files: |
|
||||
plugins/**/*.ts
|
||||
files_ignore: |
|
||||
plugins/**/*\[*\]*.ts
|
||||
plugins/multisrc/**
|
||||
|
||||
- name: Determine Targets
|
||||
id: targets
|
||||
if: github.event_name == 'workflow_dispatch' || steps.changed-files.outputs.any_changed == 'true'
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
echo "files=${{ inputs.plugin_path }}" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "files=${{ steps.changed-files.outputs.all_changed_files }}" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Setup Node.js
|
||||
if: steps.targets.outputs.files != ''
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install Dependencies
|
||||
if: steps.targets.outputs.files != ''
|
||||
run: npm ci
|
||||
|
||||
# Multisrc-generated files (plugins/**/*[...].ts) are excluded above.
|
||||
# They're produced from plugins/multisrc/*/template.ts + sources.json,
|
||||
# not hand-authored, so testing the generator is out of scope here.
|
||||
- name: Run Live Check
|
||||
id: live-check
|
||||
if: steps.targets.outputs.files != ''
|
||||
run: |
|
||||
set +e
|
||||
node scripts/live-check-plugin.js ${{ steps.targets.outputs.files }} > live-check-output.txt 2>&1
|
||||
echo "exit_code=$?" >> "$GITHUB_OUTPUT"
|
||||
cat live-check-output.txt
|
||||
cat live-check-output.txt >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: Fail On Real Errors
|
||||
if: steps.targets.outputs.files != '' && steps.live-check.outputs.exit_code != '0'
|
||||
run: |
|
||||
echo "Live check reported at least one FAIL — see the job log or workflow summary."
|
||||
exit 1
|
||||
@@ -1,31 +0,0 @@
|
||||
name: Format Check
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [master]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
format-check:
|
||||
name: Check Code Formatting
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: '24'
|
||||
|
||||
- name: Check Code Formatting
|
||||
run: npx --yes prettier@3.2.5 --check "./src/**/*.{ts,tsx,js,css}"
|
||||
@@ -1,51 +0,0 @@
|
||||
name: Publish Plugins
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- 'plugins/**'
|
||||
- 'public/**'
|
||||
- 'scripts/publish-plugins.sh'
|
||||
- '.github/workflows/publish-plugins.yml'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
name: Publish Plugins
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
token: ${{ secrets.REPO_SCOPED_TOKEN }}
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: '24'
|
||||
|
||||
- name: Install Dependencies
|
||||
run: npm ci --omit=dev --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'
|
||||
run: npm run publish:plugins -- --all-branches
|
||||
shell: bash
|
||||
@@ -1,202 +0,0 @@
|
||||
name: Auto Theme Labeler
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [labeled]
|
||||
|
||||
permissions: {}
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.issue.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
auto-label:
|
||||
name: Auto Label Theme
|
||||
if: contains(github.event.issue.labels.*.name, 'plugin request')
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
outputs:
|
||||
theme: ${{ steps.add_theme_label.outputs.theme }}
|
||||
env:
|
||||
REPO: ${{ github.repository }}
|
||||
ISSUE_NUMBER: ${{ github.event.issue.number }}
|
||||
steps:
|
||||
- name: Get Plugin Website URL
|
||||
id: get_url
|
||||
env:
|
||||
ISSUE_BODY: ${{ github.event.issue.body }}
|
||||
run: |
|
||||
URL=$(echo "$ISSUE_BODY" | sed -n '/### Website URL/,+2p' | sed '1,2d' | tr -d '[:space:]')
|
||||
echo "URL=$URL" >> $GITHUB_OUTPUT
|
||||
echo "Using URL: \`$URL\`" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Get Base URL
|
||||
id: get_base_url
|
||||
env:
|
||||
URL: ${{ steps.get_url.outputs.URL }}
|
||||
run: |
|
||||
BASE_URL=$(echo "$URL" | sed 's/https\?:\/\///' | cut -d'/' -f1)
|
||||
echo "BASE_URL=$BASE_URL" >> $GITHUB_OUTPUT
|
||||
echo "Using Base URL: \`$BASE_URL\`" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Get User Agent
|
||||
id: get_user_agent
|
||||
run: |
|
||||
CHROME_VERSION=$(curl -s https://chromiumdash.appspot.com/fetch_releases?channel=Stable\&platform=Windows\&num=1 | jq -r '.[0].version // "126.0.6478.36"')
|
||||
echo "USER_AGENT=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/$CHROME_VERSION Safari/537.36" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Check Madara Theme
|
||||
id: check_madara
|
||||
env:
|
||||
BASE_URL: ${{ steps.get_base_url.outputs.BASE_URL }}
|
||||
USER_AGENT: ${{ steps.get_user_agent.outputs.USER_AGENT }}
|
||||
run: |
|
||||
echo "🔍 Checking for Madara theme..." >> $GITHUB_STEP_SUMMARY
|
||||
if curl --location --output /dev/null --silent --head --fail --max-time 10 "https://$BASE_URL/wp-content/themes/madara/style.css" -H "User-Agent: $USER_AGENT" 2>/dev/null || \
|
||||
curl --location --output /dev/null --silent --head --fail --max-time 10 "http://$BASE_URL/wp-content/themes/madara/style.css" -H "User-Agent: $USER_AGENT" 2>/dev/null; then
|
||||
echo "madara=true" >> $GITHUB_OUTPUT
|
||||
echo "✅ Madara Theme Detected" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "madara=false" >> $GITHUB_OUTPUT
|
||||
echo "❌ Madara Theme Not Found" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
- name: Check Lightnovelwp Theme
|
||||
id: check_lighnovelwp
|
||||
if: always() && steps.check_madara.outputs.madara != 'true'
|
||||
env:
|
||||
BASE_URL: ${{ steps.get_base_url.outputs.BASE_URL }}
|
||||
USER_AGENT: ${{ steps.get_user_agent.outputs.USER_AGENT }}
|
||||
run: |
|
||||
echo "🔍 Checking for Lightnovelwp theme..." >> $GITHUB_STEP_SUMMARY
|
||||
if curl --location --output /dev/null --silent --head --fail --max-time 10 "https://$BASE_URL/wp-content/themes/lightnovel/style.css" -H "User-Agent: $USER_AGENT" 2>/dev/null || \
|
||||
curl --location --output /dev/null --silent --head --fail --max-time 10 "http://$BASE_URL/wp-content/themes/lightnovel/style.css" -H "User-Agent: $USER_AGENT" 2>/dev/null; then
|
||||
echo "lighnovelwp=true" >> $GITHUB_OUTPUT
|
||||
echo "✅ Lightnovelwp Theme Detected" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "lighnovelwp=false" >> $GITHUB_OUTPUT
|
||||
echo "❌ Lightnovelwp Theme Not Found" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
- name: Check HotNovelPub Theme
|
||||
id: check_hotnovelpub
|
||||
if: always() && steps.check_madara.outputs.madara != 'true' && steps.check_lighnovelwp.outputs.lighnovelwp != 'true'
|
||||
env:
|
||||
BASE_URL: ${{ steps.get_base_url.outputs.BASE_URL }}
|
||||
USER_AGENT: ${{ steps.get_user_agent.outputs.USER_AGENT }}
|
||||
run: |
|
||||
echo "🔍 Checking for HotNovelPub theme..." >> $GITHUB_STEP_SUMMARY
|
||||
if curl --location --silent --fail --max-time 10 "https://api.$BASE_URL" -H "User-Agent: $USER_AGENT" 2>/dev/null | grep -q "Hello\!" || \
|
||||
curl --location --silent --fail --max-time 10 "http://api.$BASE_URL" -H "User-Agent: $USER_AGENT" 2>/dev/null | grep -q "Hello\!"; then
|
||||
echo "hotnovelpub=true" >> $GITHUB_OUTPUT
|
||||
echo "✅ HotNovelPub Theme Detected" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "hotnovelpub=false" >> $GITHUB_OUTPUT
|
||||
echo "❌ HotNovelPub Theme Not Found" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
- name: Check Fictioneer Theme
|
||||
id: check_fictioneer
|
||||
if: always() && steps.check_madara.outputs.madara != 'true' && steps.check_lighnovelwp.outputs.lighnovelwp != 'true' && steps.check_hotnovelpub.outputs.hotnovelpub != 'true'
|
||||
env:
|
||||
BASE_URL: ${{ steps.get_base_url.outputs.BASE_URL }}
|
||||
USER_AGENT: ${{ steps.get_user_agent.outputs.USER_AGENT }}
|
||||
run: |
|
||||
echo "🔍 Checking for Fictioneer theme..." >> $GITHUB_STEP_SUMMARY
|
||||
if curl --location --output /dev/null --silent --head --fail --max-time 10 "https://$BASE_URL/wp-content/themes/fictioneer/css/application.css" -H "User-Agent: $USER_AGENT" 2>/dev/null || \
|
||||
curl --location --output /dev/null --silent --head --fail --max-time 10 "http://$BASE_URL/wp-content/themes/fictioneer/css/application.css" -H "User-Agent: $USER_AGENT" 2>/dev/null; then
|
||||
echo "fictioneer=true" >> $GITHUB_OUTPUT
|
||||
echo "✅ Fictioneer Theme Detected" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "fictioneer=false" >> $GITHUB_OUTPUT
|
||||
echo "❌ Fictioneer Theme Not Found" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
- name: Add Theme Label To Issue
|
||||
id: add_theme_label
|
||||
if: always()
|
||||
env:
|
||||
MADARA: ${{ steps.check_madara.outputs.madara }}
|
||||
LIGHNOVELWP: ${{ steps.check_lighnovelwp.outputs.lighnovelwp }}
|
||||
HOTNOVELPUB: ${{ steps.check_hotnovelpub.outputs.hotnovelpub }}
|
||||
FICTIONEER: ${{ steps.check_fictioneer.outputs.fictioneer }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
echo "## Theme Detection Result" >> $GITHUB_STEP_SUMMARY
|
||||
if [[ "$MADARA" == "true" ]]; then
|
||||
echo "🏷️ Adding label: theme: madara" >> $GITHUB_STEP_SUMMARY
|
||||
gh issue edit $ISSUE_NUMBER --add-label "theme: madara" -R $REPO
|
||||
echo "theme=madara" >> "$GITHUB_OUTPUT"
|
||||
elif [[ "$LIGHNOVELWP" == "true" ]]; then
|
||||
echo "🏷️ Adding label: theme: lighnovelwp" >> $GITHUB_STEP_SUMMARY
|
||||
gh issue edit $ISSUE_NUMBER --add-label "theme: lighnovelwp" -R $REPO
|
||||
echo "theme=lightnovelwp" >> "$GITHUB_OUTPUT"
|
||||
elif [[ "$HOTNOVELPUB" == "true" ]]; then
|
||||
echo "🏷️ Adding label: theme: hotnovelpub" >> $GITHUB_STEP_SUMMARY
|
||||
gh issue edit $ISSUE_NUMBER --add-label "theme: hotnovelpub" -R $REPO
|
||||
echo "theme=hotnovelpub" >> "$GITHUB_OUTPUT"
|
||||
elif [[ "$FICTIONEER" == "true" ]]; then
|
||||
echo "🏷️ Adding label: theme: fictioneer" >> $GITHUB_STEP_SUMMARY
|
||||
gh issue edit $ISSUE_NUMBER --add-label "theme: fictioneer" -R $REPO
|
||||
echo "theme=fictioneer" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "❌ No Theme Detected" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
create-source-pr:
|
||||
name: Create MultiSrc Draft PR
|
||||
needs: auto-label
|
||||
if: needs.auto-label.outputs.theme != ''
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: master
|
||||
|
||||
- name: Add Source Configuration
|
||||
id: add_source
|
||||
env:
|
||||
ISSUE_BODY: ${{ github.event.issue.body }}
|
||||
THEME: ${{ needs.auto-label.outputs.theme }}
|
||||
run: node .github/scripts/add-multisrc-source.cjs
|
||||
|
||||
- name: Setup Node.js
|
||||
if: steps.add_source.outputs.changed == 'true'
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: '24'
|
||||
cache: npm
|
||||
|
||||
- name: Install Dependencies
|
||||
if: steps.add_source.outputs.changed == 'true'
|
||||
run: npm ci --omit=dev --ignore-scripts
|
||||
|
||||
- name: Validate Source Configuration
|
||||
if: steps.add_source.outputs.changed == 'true'
|
||||
run: |
|
||||
node -e "JSON.parse(require('fs').readFileSync('plugins/multisrc/${{ needs.auto-label.outputs.theme }}/sources.json', 'utf8'))"
|
||||
npm run build:multisrc
|
||||
|
||||
- name: Create Draft Pull Request
|
||||
if: steps.add_source.outputs.changed == 'true'
|
||||
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
|
||||
with:
|
||||
commit-message: 'feat: Add ${{ steps.add_source.outputs.source_name }} MultiSrc Source'
|
||||
branch: 'automation/multisrc-issue-${{ github.event.issue.number }}'
|
||||
title: 'feat: Add ${{ steps.add_source.outputs.source_name }} MultiSrc Source'
|
||||
body: |
|
||||
Adds `${{ steps.add_source.outputs.source_name }}` to the `${{ needs.auto-label.outputs.theme }}` multi-source configuration.
|
||||
|
||||
Closes #${{ github.event.issue.number }}
|
||||
|
||||
This draft was generated from the theme detected for the plugin request. Before marking it ready, verify theme-specific options such as chapter ordering, browse paths, filters, custom parsing, and language.
|
||||
labels: 'bot'
|
||||
base: 'master'
|
||||
delete-branch: true
|
||||
draft: true
|
||||
@@ -1,51 +0,0 @@
|
||||
name: Update Issue Template
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows:
|
||||
- Publish Plugins
|
||||
types:
|
||||
- completed
|
||||
branches:
|
||||
- master
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
update_template:
|
||||
name: Update Issue Template
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
if: |
|
||||
github.repository == 'LNReader/lnreader-plugins' &&
|
||||
github.event.workflow_run.conclusion == 'success' &&
|
||||
github.event.workflow_run.event == 'push'
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: master
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: '24'
|
||||
|
||||
- name: Generate Issue Template Options
|
||||
run: node ./.github/scripts/generate-issue-template-options.cjs
|
||||
|
||||
- name: Commit And Push Changes
|
||||
run: |
|
||||
if git diff --quiet -- .github/ISSUE_TEMPLATE/report_issue.yml .github/scripts/keys.json; then
|
||||
echo "No issue template changes to commit." >> "$GITHUB_STEP_SUMMARY"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git add .github/ISSUE_TEMPLATE/report_issue.yml .github/scripts/keys.json
|
||||
git commit -m "chore: Update Issue Template Plugin Options [skip ci]"
|
||||
git push origin HEAD:master
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
node_modules
|
||||
.vscode
|
||||
.idea
|
||||
.env
|
||||
config.json
|
||||
|
||||
# generated multisrc .ts files
|
||||
plugins/*/*\[*\]*.ts
|
||||
|
||||
# ignore dist stuffs
|
||||
.js
|
||||
.dist
|
||||
.DS_Store
|
||||
dist
|
||||
total.svg
|
||||
.history
|
||||
broken-sites-report.json
|
||||
|
||||
.agents/
|
||||
.claude/
|
||||
.cursor/
|
||||
.plugin-cache/
|
||||
request-jobs.json*
|
||||
@@ -1 +0,0 @@
|
||||
_
|
||||
@@ -1,4 +0,0 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
npx lint-staged
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
var t=this&&this.__awaiter||function(t,e,n,r){return new(n||(n=Promise))(function(i,o){function a(t){try{s(r.next(t))}catch(t){o(t)}}function c(t){try{s(r.throw(t))}catch(t){o(t)}}function s(t){var e;t.done?i(t.value):(e=t.value,e instanceof n?e:new n(function(t){t(e)})).then(a,c)}s((r=r.apply(t,e||[])).next())})},e=this&&this.__generator||function(t,e){var n,r,i,o={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]},a=Object.create(("function"==typeof Iterator?Iterator:Object).prototype);return a.next=c(0),a.throw=c(1),a.return=c(2),"function"==typeof Symbol&&(a[Symbol.iterator]=function(){return this}),a;function c(c){return function(s){return function(c){if(n)throw new TypeError("Generator is already executing.");for(;a&&(a=0,c[0]&&(o=0)),o;)try{if(n=1,r&&(i=2&c[0]?r.return:c[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,c[1])).done)return i;switch(r=0,i&&(c=[2&c[0],i.value]),c[0]){case 0:case 1:i=c;break;case 4:return o.label++,{value:c[1],done:!1};case 5:o.label++,r=c[1],c=[0];continue;case 7:c=o.ops.pop(),o.trys.pop();continue;default:if(!(i=o.trys,(i=i.length>0&&i[i.length-1])||6!==c[0]&&2!==c[0])){o=0;continue}if(3===c[0]&&(!i||c[1]>i[0]&&c[1]<i[3])){o.label=c[1];break}if(6===c[0]&&o.label<i[1]){o.label=i[1],i=c;break}if(i&&o.label<i[2]){o.label=i[2],o.ops.push(c);break}i[2]&&o.ops.pop(),o.trys.pop();continue}c=e.call(t,o)}catch(t){c=[6,t],r=0}finally{n=i=0}if(5&c[0])throw c[1];return{value:c[0]?c[1]:void 0,done:!0}}([c,s])}}};Object.defineProperty(exports,"__esModule",{value:!0});var n=require("@libs/fetch"),r=require("cheerio"),i=require("@libs/novelStatus"),o=function(){function o(){this.id="linovel",this.name="linovel",this.icon="src/cn/linovel/icon.png",this.site="https://www.linovel.net",this.version="1.0.1",this.filters=void 0,this.imageRequestInit=void 0,this.userAgent="Mozilla/5.0 (X11; Linux x86_64; rv:136.0) Gecko/20100101 Firefox/136.0"}return o.prototype.fetchHTML=function(i){return t(this,void 0,void 0,function(){var t;return e(this,function(e){switch(e.label){case 0:return[4,(0,n.fetchText)(i,{headers:{"User-Agent":this.userAgent}})];case 1:if(""===(t=e.sent()))throw Error("无法获取内容,请检查网络");return[2,(0,r.load)(t)]}})})},o.prototype.popularNovels=function(){return t(this,void 0,void 0,function(){var t,n;return e(this,function(e){switch(e.label){case 0:return t=[],[4,this.fetchHTML(this.site)];case 1:return(n=e.sent())("a.book-item-inner").each(function(e,r){var i,o,a=n(r).find(".book-item-name").text().trim(),c=null!==(i=n(r).attr("href"))&&void 0!==i?i:"",s=(null!==(o=n(r).find("img").attr("data-original"))&&void 0!==o?o:"").replace(/!min300jpg$/,"");t.push({name:a,path:c,cover:s})}),[2,t]}})})},o.prototype.parseNovel=function(n){return t(this,void 0,void 0,function(){var t,r,o;return e(this,function(e){switch(e.label){case 0:return t={path:n,name:"Untitled"},[4,this.fetchHTML(this.site+n)];case 1:return(r=e.sent())("div.name").find("a").each(function(e,n){t.author=r(n).text().trim()}),r(".book-title").each(function(e,n){t.name=r(n).text().trim()}),r(".book-data").find("span").each(function(e,n){var o=r(n).text().trim();o.includes("字数")||o.includes("热度")||o.includes("收藏")||("连载中"===o?t.status=i.NovelStatus.Ongoing:"已完结"===o&&(t.status=i.NovelStatus.Completed))}),r(".book-cover").find("img").each(function(e,n){var i;t.cover=null!==(i=r(n).attr("src"))&&void 0!==i?i:""}),t.genres=r(".book-cats").children("a").map(function(t,e){return r(e).text()}).toArray().join(","),t.summary=r(".about-text").text().trim(),o=[],r("div.chapter").find("a").each(function(t,e){var n,i=r(e).text().trim(),a=null!==(n=r(e).attr("href"))&&void 0!==n?n:"";o.push({name:i,path:a})}),t.chapters=o,[2,t]}})})},o.prototype.parseChapter=function(n){return t(this,void 0,void 0,function(){var t,r;return e(this,function(e){switch(e.label){case 0:return[4,this.fetchHTML(this.site+n)];case 1:if(t=e.sent(),r="",t(".fufei-app-download-hint").length)throw Error("本章节需订阅后才能阅览,请下载轻之文库App订阅");return t(".article-text").find("p, img").each(function(e,n){if("img"===n.tagName){var i=t(n).attr("src");r+='<img src="'.concat(i,'">\n')}else{var o=t(n).text().trim();r+=" "===o?"<br>\n":"<p>".concat(o,"</p>\n")}}),[2,r.trim()]}})})},o.prototype.searchNovels=function(n){return t(this,void 0,void 0,function(){var t,r;return e(this,function(e){switch(e.label){case 0:return[4,this.fetchHTML(this.site+"/search/?kw="+n)];case 1:return t=e.sent(),r=[],t(".rank-book-list").find("a.search-book").each(function(e,n){var i,o,a=t(n).find(".book-name").text().trim(),c=null!==(i=t(n).attr("href"))&&void 0!==i?i:"",s=(null!==(o=t(n).find("img").attr("src"))&&void 0!==o?o:"").replace(/!min300jpg$/,"");r.push({name:a,path:c,cover:s})}),[2,r]}})})},o}();exports.default=new o;
|
||||
@@ -0,0 +1 @@
|
||||
var e=this&&this.__awaiter||function(e,t,r,n){return new(r||(r=Promise))(function(o,a){function i(e){try{s(n.next(e))}catch(e){a(e)}}function l(e){try{s(n.throw(e))}catch(e){a(e)}}function s(e){var t;e.done?o(e.value):(t=e.value,t instanceof r?t:new r(function(e){e(t)})).then(i,l)}s((n=n.apply(e,t||[])).next())})},t=this&&this.__generator||function(e,t){var r,n,o,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]},i=Object.create(("function"==typeof Iterator?Iterator:Object).prototype);return i.next=l(0),i.throw=l(1),i.return=l(2),"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function l(l){return function(s){return function(l){if(r)throw new TypeError("Generator is already executing.");for(;i&&(i=0,l[0]&&(a=0)),a;)try{if(r=1,n&&(o=2&l[0]?n.return:l[0]?n.throw||((o=n.return)&&o.call(n),0):n.next)&&!(o=o.call(n,l[1])).done)return o;switch(n=0,o&&(l=[2&l[0],o.value]),l[0]){case 0:case 1:o=l;break;case 4:return a.label++,{value:l[1],done:!1};case 5:a.label++,n=l[1],l=[0];continue;case 7:l=a.ops.pop(),a.trys.pop();continue;default:if(!(o=a.trys,(o=o.length>0&&o[o.length-1])||6!==l[0]&&2!==l[0])){a=0;continue}if(3===l[0]&&(!o||l[1]>o[0]&&l[1]<o[3])){a.label=l[1];break}if(6===l[0]&&a.label<o[1]){a.label=o[1],o=l;break}if(o&&a.label<o[2]){a.label=o[2],a.ops.push(l);break}o[2]&&a.ops.pop(),a.trys.pop();continue}l=t.call(e,a)}catch(e){l=[6,e],n=0}finally{r=o=0}if(5&l[0])throw l[1];return{value:l[0]?l[1]:void 0,done:!0}}([l,s])}}};Object.defineProperty(exports,"__esModule",{value:!0});var r=require("cheerio"),n=require("@libs/fetch"),o=require("@libs/filterInputs"),a=require("@libs/storage"),i=function(){function i(){this.id="linovelib",this.name="Linovelib",this.icon="src/cn/linovelib/icon.png",this.site="https://www.bilinovel.com",this.version="1.2.1",this.imageRequestInit={method:"GET",headers:{"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36 Edg/144.0.0.0",Referer:"https://www.linovelib.com",Accept:"image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8"}},this.webStorageUtilized=!0,this.pluginSettings={host:{value:"http://example.com",label:"Custom LDS Host",type:"Text"}},this.serverUrl=a.storage.get("host")||"http://localhost:5301",this.filters={rank:{label:"排行榜",value:"monthvisit",options:[{label:"月点击榜",value:"monthvisit"},{label:"周点击榜",value:"weekvisit"},{label:"月推荐榜",value:"monthvote"},{label:"周推荐榜",value:"weekvote"},{label:"月鲜花榜",value:"monthflower"},{label:"周鲜花榜",value:"weekflower"},{label:"月鸡蛋榜",value:"monthegg"},{label:"周鸡蛋榜",value:"weekegg"},{label:"最近更新",value:"lastupdate"},{label:"最新入库",value:"postdate"},{label:"收藏榜",value:"goodnum"},{label:"新书榜",value:"newhot"}],type:o.FilterTypes.Picker}}}return i.prototype.popularNovels=function(o,a){return e(this,arguments,void 0,function(e,o){var a,i,l,s,c,u=o.showLatestNovels,h=o.filters;return t(this,function(t){switch(t.label){case 0:return a=u?"lastupdate":h.rank.value,i="".concat(this.site,"/top/").concat(a,"/").concat(e,".html"),[4,(0,n.fetchText)(i)];case 1:if(""===(l=t.sent()))throw Error("无法获取小说列表,请检查网络");return s=(0,r.load)(l),c=[],s(".module-rank-booklist .book-layout").each(function(e,t){var r,n=s(t).attr("href"),o=s(t).find(".book-title").text(),a=null===(r=s(t).find("div.book-cover > img").attr("data-src"))||void 0===r?void 0:r.replace("/https","https");if(n){var i={name:o,cover:a,path:n};c.push(i)}}),[2,c]}})})},i.prototype.parseNovel=function(r){return e(this,void 0,void 0,function(){var e;return t(this,function(t){switch(t.label){case 0:return[4,(0,n.fetchText)("".concat(this.serverUrl,"/api/novel?path=").concat(r))];case 1:return e=t.sent(),[2,JSON.parse(e)]}})})},i.prototype.parseChapter=function(r){return e(this,void 0,void 0,function(){var e,o,i;return t(this,function(t){switch(t.label){case 0:return e=Number(a.storage.get("lastFetchChapterTime_"+r))||0,Date.now()-e<1e4?[2,a.storage.get("chapterContent_"+r)||""]:[4,(0,n.fetchText)("".concat(this.serverUrl,"/api/chapter?path=").concat(r))];case 1:return o=t.sent(),i=JSON.parse(o),a.storage.set("lastFetchChapterTime_"+r,Date.now()),a.storage.set("chapterContent_"+r,i.content),[2,i.content]}})})},i.prototype.searchNovels=function(r){return e(this,void 0,void 0,function(){var e,o,i;return t(this,function(t){switch(t.label){case 0:return e=Number(a.storage.get("lastSearchTime_"+this.id))||0,Date.now()-e<5e3?[2,[]]:[4,(0,n.fetchText)("".concat(this.serverUrl,"/api/search?keyword=").concat(encodeURIComponent(r)))];case 1:return o=t.sent(),i=JSON.parse(o).results,a.storage.set("lastSearchTime_"+this.id,Date.now()),[2,i]}})})},i}();exports.default=new i;
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user