A browser extension that allows you to download EPub files directly from light novel websites. The generated EPub files follow standard formats, making them fully compatible with Calibre and easy to transfer to your Kindle (developed and tested on Kindle PaperWhite 3).
Tip
I also developed a KOReader plugin that allows you to read manga online directly on your Kindle without needing a computer!
- https://baka-tsuki.org
- https://beetruyen.net
- https://dammy.me
- https://docln.net
- https://docln.sbs
- https://foxaholic.com
- https://hako.vip
- https://hako.vip
- https://hako.vn
- https://ln.3ktan.com
- https://ln.hako.vn
- https://luvevaland.co
- https://mimieuuyen.com
- https://mintteanovel.com
- https://monkeydtruyen.com
- https://msvtruyen.com
- https://novest.me
- https://otruyen.vn
- https://phongphongtam2.com
- https://sonako.fandom.com
- https://truyenqqko.com
- https://truyenqqpro.com
- https://truyenqqvip.com
- https://valvrareteam.net
- https://www.baka-tsuki.org
- https://www.foxaholic.com
If you find this project useful please support me through:
![]() |
![]() |
|---|---|
| Momo | Timo or Bank |
You can find the extension on the following stores:
- Download the source code (or
.zipfile) from the Releases section and extract it. - Open your browser and navigate to
chrome://extensions/(oredge://extensions/). - Enable Developer mode in the top right corner.
- Click Load unpacked.
- Select the folder where you extracted the extension.
- Navigate to
about:debugging#/runtime/this-firefox. - Click Load Temporary Add-on....
- Select the
manifest.jsonfile from your extracted folder. (Note: Temporary add-ons in Firefox are removed when the browser is closed).
To add support for a new website, you need to create a registry configuration file in the registry/ directory.
Create a new .ts file in registry/ (e.g., registry/mysite.ts).
Use the defineRegistry helper to define your site configuration:
export default defineRegistry({
domains: ["mysite.com"],
lang: "vi", // Target language
publisher: "My Site",
// CSS Selector for elements where the download button should appear (e.g., volume headers)
findBlocks: ".volume-header",
// Logic to find the parent container of the volume to attach the component
findTarget: (h3) => h3.closest(".volume-box")!,
// Metadata extraction
title: (h3, target) => h3.textContent!.trim(),
findAuthor: (h3, target) => $(".author-name")?.textContent?.trim(),
extractCover: (h3, target) => $(".cover-img")?.getAttribute("src"),
description: (h3, target) => $(".summary")?.textContent?.trim(),
findTags: (h3, target) => Array.from($$(".genre")).map((a) => a.textContent?.trim()),
// Required queries for the crawler
targetQueries: {
bookTitle: ".series-title", // Selector for the main book title
chapters: ".chapter-list a", // Selector for chapter links
chaptersReverse: false, // Optional: Set to true if chapters are descending
container: "#chapter-content" // Selector for the actual story text
},
// Optional hooks for content processing
cleaner($) {
// Remove unwanted elements using Cheerio
$(".ads, .social-share").remove()
},
transformContainer($) {
// Modify the content container (e.g., handling protected text)
const content = $("#chapter-protected").text()
// decryption logic here...
return $
},
// Optional: Custom chapter title extraction
getChapterTitle: (anchor) => anchor.querySelector(".title")?.textContent?.trim() || "Chapter",
// Downloader configuration
fetcherOptions: {
concurrency: 5,
sleep: 3000,
retry: 3,
delayError429: 15000
},
lazyDom: false // Set to true if content is loaded dynamically
})| Option | Type | Description |
|---|---|---|
domains |
string[] |
Required. List of domains where this config will be active. |
lang |
string | Function |
Required. Language code (e.g., "vi", "en") or a function that returns it. |
publisher |
string |
Required. Name of the site/publisher. |
findBlocks |
string |
Required. CSS selector for elements that trigger the download button (usually volume titles). |
findTarget |
Function |
Required. Function to find the container element for a specific volume based on the header found by findBlocks. |
targetQueries |
Object |
Required. CSS selectors used for crawling: |
.bookTitle |
Selector for the main story title on the page. | |
.chapters |
Selector for links (<a>) to chapters within a volume. |
|
.chaptersReverse |
Optional. Set to true if chapters are listed in descending order. |
|
.container |
Selector for the main content area in the reading page. | |
title |
Function |
Required. Function to extract the volume title. |
findAuthor |
Function |
Required. Function to extract the author(s). Can return a string or an array of strings. |
extractCover |
Function |
Required. Function to extract the URL of the cover image. |
description |
Function |
Optional. Function to extract the book description. |
findTags |
Function |
Optional. Function to extract tags/genres. |
cleaner |
Function |
Optional. Hook to clean up the chapter content using Cheerio ($). |
transformContainer |
Function |
Optional. Hook to transform the content container (e.g., decryption logic). |
preParse |
Function |
Optional. Pre-process the raw HTML string before it is parsed by Cheerio. |
getChapterTitle |
Function |
Optional. Function to extract a custom chapter title from the link element. |
getChapterHref |
Function |
Optional. Function to extract a custom chapter href from the link element. |
fetchChapter |
Function |
Optional. Function to fetch the chapter content. |
fetcherOptions |
Object |
Optional. Configuration for the downloader: |
.concurrency |
Number of simultaneous requests (default: 5). | |
.sleep |
Delay in milliseconds after each chapter download. | |
.retry |
Number of retry attempts for failed requests (chapters and cover). | |
.delayError429 |
Delay in milliseconds when encountering a 429 (Rate Limit) error. | |
.retryResource |
Retry attempts for fonts/images. Use a lower value to avoid long waits for non-critical resources (default: 1). | |
.fetchTimeoutResource |
Timeout in milliseconds between retries for fonts/images (default: 100). | |
lazyDom |
boolean |
Optional. Set to true if the page loads content dynamically/lazily. |
You can use pre-defined utilities in registry/utils/ to make selection easier:
$: Equivalent todocument.querySelector.$$: Equivalent todocument.querySelectorAll.contains: Find an element containing specific text.
After adding your registry, run the following command to automatically update the list of supported websites in this README.md:
bun update-readme




