1 Commits

Author SHA1 Message Date
masi 4763afecfc chore: Publish Plugins 2026-09-25 13:41:04 -03:00
740 changed files with 2888 additions and 190525 deletions
+2578
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
-7
View File
@@ -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
View File
@@ -1 +0,0 @@
blank_issues_enabled: false
-407
View File
@@ -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
-61
View File
@@ -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
-6
View File
@@ -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`)
-172
View File
@@ -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);
-125
View File
@@ -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
-97
View File
@@ -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."
-48
View File
@@ -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
-51
View File
@@ -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
-27
View File
@@ -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'
-80
View File
@@ -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
-31
View File
@@ -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}"
-51
View File
@@ -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
-202
View File
@@ -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
View File
@@ -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
View File
@@ -1 +0,0 @@
_
-4
View File
@@ -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
+1
View File
@@ -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+="&nbsp;"===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;
+1
View File
@@ -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