├── guide.md ├── .vscode └── settings.json ├── defaultRes ├── res │ ├── raw │ │ └── gradle.properties │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_background.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_monochrome.png │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_background.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_monochrome.png │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_background.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_monochrome.png │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_background.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_monochrome.png │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_background.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_monochrome.png │ └── mipmap-anydpi-v26 │ │ └── ic_launcher.xml ├── AndroidManifest.xml └── build.gradle.kts ├── common ├── src │ └── jsMain │ │ └── kotlin │ │ └── ireader │ │ └── js │ │ └── runtime │ │ └── Utils.kt └── build.gradle.kts ├── docs ├── source-api │ ├── .gitignore │ ├── src │ │ ├── commonMain │ │ │ └── kotlin │ │ │ │ └── ireader │ │ │ │ └── core │ │ │ │ ├── source │ │ │ │ ├── model │ │ │ │ │ ├── Listing.kt │ │ │ │ │ ├── PageListEmpty.kt │ │ │ │ │ ├── DeepLink.kt │ │ │ │ │ ├── LocalNovelDetails.kt │ │ │ │ │ ├── FilterList.kt │ │ │ │ │ └── MangasPageInfo.kt │ │ │ │ ├── Dependencies.kt │ │ │ │ ├── DeepLinkSource.kt │ │ │ │ ├── CatalogSource.kt │ │ │ │ └── Source.kt │ │ │ │ ├── http │ │ │ │ ├── WebViewUtil.kt │ │ │ │ ├── DEFAULT_USER_AGENT.kt │ │ │ │ ├── HttpModule.kt │ │ │ │ ├── NetworkConfig.kt │ │ │ │ ├── JSFactory.kt │ │ │ │ ├── RatelimitException.kt │ │ │ │ ├── SSLConfiguration.kt │ │ │ │ ├── CookieSynchronizer.kt │ │ │ │ ├── HttpClients.kt │ │ │ │ ├── WebViewManger.kt │ │ │ │ ├── JS.kt │ │ │ │ └── Exception.kt │ │ │ │ └── util │ │ │ │ ├── TimeUtils.kt │ │ │ │ └── CoroutineExt.kt │ │ ├── iosMain │ │ │ └── kotlin │ │ │ │ └── ireader │ │ │ │ └── core │ │ │ │ ├── http │ │ │ │ ├── HttpModule.ios.kt │ │ │ │ └── HttpClients.ios.kt │ │ │ │ └── util │ │ │ │ └── CoroutineExt.ios.kt │ │ ├── androidMain │ │ │ ├── kotlin │ │ │ │ └── ireader │ │ │ │ │ └── core │ │ │ │ │ ├── http │ │ │ │ │ ├── HttpModule.kt │ │ │ │ │ ├── OkHttpExtension.kt │ │ │ │ │ ├── CookieSynchronizer.kt │ │ │ │ │ └── AndroidCookieJar.kt │ │ │ │ │ └── util │ │ │ │ │ └── createCoroutineScope.kt │ │ │ ├── AndroidManifest.xml │ │ │ └── res │ │ │ │ └── values │ │ │ │ └── string.xml │ │ ├── jsMain │ │ │ └── kotlin │ │ │ │ └── ireader │ │ │ │ └── core │ │ │ │ ├── http │ │ │ │ ├── HttpModule.js.kt │ │ │ │ ├── SSLConfiguration.js.kt │ │ │ │ └── CookieSynchronizer.js.kt │ │ │ │ └── util │ │ │ │ └── CoroutineExt.js.kt │ │ ├── desktopMain │ │ │ └── kotlin │ │ │ │ └── ireader │ │ │ │ └── core │ │ │ │ ├── http │ │ │ │ ├── HttpModule.kt │ │ │ │ ├── OkHttpExtension.kt │ │ │ │ ├── CookieSynchronizer.kt │ │ │ │ ├── CookieStore.kt │ │ │ │ ├── BrowserEngine.kt │ │ │ │ ├── WebViewManger.kt │ │ │ │ └── PersistentCookieJar.kt │ │ │ │ ├── util │ │ │ │ └── createCoroutineScope.kt │ │ │ │ └── storage │ │ │ │ └── CacheDir.kt │ │ └── jvmMain │ │ │ └── kotlin │ │ │ └── ireader │ │ │ └── core │ │ │ └── http │ │ │ └── JSFactory.kt │ └── gradle.properties ├── example-annotated │ └── build.gradle.kts ├── example-autoid │ └── build.gradle.kts ├── example-madara │ └── main │ │ └── src │ │ └── ireader │ │ └── examplemadara │ │ └── ExampleMadara.kt └── ADD_SOURCE_GUIDE.md ├── deeplink ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── tachiyomix │ │ └── deeplink │ │ └── SourceDeepLinkActivity.java └── build.gradle.kts ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── sources ├── ar │ ├── riwyat │ │ ├── main │ │ │ └── assets │ │ │ │ └── icon.png │ │ └── build.gradle.kts │ ├── kolnovel │ │ ├── main │ │ │ └── assets │ │ │ │ └── icon.png │ │ └── build.gradle.kts │ ├── sunovels │ │ ├── main │ │ │ └── assets │ │ │ │ └── icon.png │ │ └── build.gradle.kts │ ├── realmnovel │ │ ├── main │ │ │ └── assets │ │ │ │ └── icon.png │ │ └── build.gradle.kts │ ├── rewayatfans │ │ ├── main │ │ │ └── assets │ │ │ │ └── icon.png │ │ └── build.gradle.kts │ └── novelparadise │ │ ├── main │ │ └── assets │ │ │ └── icon.png │ │ └── build.gradle.kts ├── cn │ ├── aixdzs │ │ ├── main │ │ │ └── assets │ │ │ │ └── icon.png │ │ └── build.gradle.kts │ └── sexinsex │ │ ├── main │ │ └── assets │ │ │ └── ic_launcher.png │ │ └── build.gradle.kts ├── en │ ├── fanmtl │ │ ├── main │ │ │ └── assets │ │ │ │ └── icon.png │ │ └── build.gradle.kts │ ├── lnmtl │ │ ├── main │ │ │ ├── assets │ │ │ │ └── icon.png │ │ │ └── src │ │ │ │ └── ireader │ │ │ │ └── lnmtl │ │ │ │ ├── volume │ │ │ │ ├── LNMTLVolumeResponse.kt │ │ │ │ └── LNMTLVolumnResponseItem.kt │ │ │ │ └── chapters │ │ │ │ ├── LNMTLResponse.kt │ │ │ │ └── Data.kt │ │ └── build.gradle.kts │ ├── pawread │ │ ├── main │ │ │ └── assets │ │ │ │ └── icon.png │ │ └── build.gradle.kts │ ├── ranobes │ │ ├── main │ │ │ ├── assets │ │ │ │ └── icon.png │ │ │ └── src │ │ │ │ └── ireader │ │ │ │ └── ranobes │ │ │ │ ├── Chapter.kt │ │ │ │ └── ChapterDTO.kt │ │ └── build.gradle.kts │ ├── readmtl │ │ ├── main │ │ │ └── assets │ │ │ │ └── icon.png │ │ └── build.gradle.kts │ ├── wnmtl │ │ ├── main │ │ │ ├── assets │ │ │ │ └── icon.png │ │ │ └── src │ │ │ │ └── ireader │ │ │ │ ├── wnmtl │ │ │ │ ├── chapter │ │ │ │ │ ├── ChapterDTO.kt │ │ │ │ │ ├── Data.kt │ │ │ │ │ └── Result.kt │ │ │ │ ├── content │ │ │ │ │ ├── ContentDTO.kt │ │ │ │ │ └── Data.kt │ │ │ │ └── explore │ │ │ │ │ ├── ExploreDTO.kt │ │ │ │ │ ├── Data.kt │ │ │ │ │ └── Result.kt │ │ │ │ └── Constants.kt │ │ └── build.gradle.kts │ ├── coolnovel │ │ ├── main │ │ │ └── assets │ │ │ │ └── icon.png │ │ └── build.gradle.kts │ ├── daonovel │ │ ├── main │ │ │ └── assets │ │ │ │ └── icon.png │ │ └── build.gradle.kts │ ├── fastnovel │ │ ├── main │ │ │ └── assets │ │ │ │ └── icon.png │ │ └── build.gradle.kts │ ├── mostnovel │ │ ├── main │ │ │ └── assets │ │ │ │ └── icon.png │ │ └── build.gradle.kts │ ├── mtlnation │ │ ├── main │ │ │ ├── assets │ │ │ │ └── icon.png │ │ │ └── src │ │ │ │ └── ireader │ │ │ │ └── mtlnation │ │ │ │ ├── ChapterDTO.kt │ │ │ │ └── Data.kt │ │ └── build.gradle.kts │ ├── novelfire │ │ ├── main │ │ │ └── assets │ │ │ │ └── icon.png │ │ ├── build.gradle.kts │ │ └── README.md │ ├── novelfull │ │ ├── main │ │ │ └── assets │ │ │ │ └── icon.png │ │ └── build.gradle.kts │ ├── novelhall │ │ ├── main │ │ │ └── assets │ │ │ │ └── icon.png │ │ └── build.gradle.kts │ ├── novelstic │ │ ├── main │ │ │ └── assets │ │ │ │ └── icon.png │ │ └── build.gradle.kts │ ├── royalroad │ │ ├── main │ │ │ └── assets │ │ │ │ └── icon.png │ │ └── build.gradle.kts │ ├── skynovel │ │ ├── main │ │ │ └── assets │ │ │ │ └── icon.png │ │ └── build.gradle.kts │ ├── allnovelfull │ │ ├── main │ │ │ └── assets │ │ │ │ └── icon.png │ │ └── build.gradle.kts │ ├── boxnovelcom │ │ ├── main │ │ │ └── assets │ │ │ │ └── icon.png │ │ └── build.gradle.kts │ ├── comrademao │ │ ├── main │ │ │ └── assets │ │ │ │ └── icon.png │ │ └── build.gradle.kts │ ├── freewebnovel │ │ ├── main │ │ │ └── assets │ │ │ │ └── icon.png │ │ └── build.gradle.kts │ ├── lightnovels │ │ ├── main │ │ │ ├── assets │ │ │ │ └── icon.png │ │ │ └── src │ │ │ │ └── ireader │ │ │ │ └── lightnovels │ │ │ │ ├── detail_dto │ │ │ │ ├── Query.kt │ │ │ │ ├── RuntimeConfig.kt │ │ │ │ ├── Tag.kt │ │ │ │ ├── Props.kt │ │ │ │ ├── Author.kt │ │ │ │ ├── Genre.kt │ │ │ │ ├── GenreX.kt │ │ │ │ ├── CachedHotNovel.kt │ │ │ │ ├── CachedLatestChapter.kt │ │ │ │ ├── NovelDetail.kt │ │ │ │ ├── PageProps.kt │ │ │ │ └── NovelInfo.kt │ │ │ │ ├── content_dto │ │ │ │ ├── RuntimeConfig.kt │ │ │ │ ├── Props.kt │ │ │ │ ├── Query.kt │ │ │ │ ├── PageProps.kt │ │ │ │ ├── Novel.kt │ │ │ │ ├── ContentDTO.kt │ │ │ │ └── CachedChapterInfo.kt │ │ │ │ ├── search_dto │ │ │ │ ├── SearchDTO.kt │ │ │ │ ├── SearchResult.kt │ │ │ │ ├── Result.kt │ │ │ │ └── ResultX.kt │ │ │ │ ├── books_dto │ │ │ │ ├── BookListDTO.kt │ │ │ │ └── Result.kt │ │ │ │ └── chapter_dto │ │ │ │ ├── ChapterDTO.kt │ │ │ │ └── Result.kt │ │ └── build.gradle.kts │ ├── mylovenovel │ │ ├── main │ │ │ ├── assets │ │ │ │ └── icon.png │ │ │ └── src │ │ │ │ └── ireader │ │ │ │ ├── constants │ │ │ │ └── Constants.kt │ │ │ │ └── mylovenovel │ │ │ │ └── merge.kt │ │ └── build.gradle.kts │ ├── novelbuddy │ │ ├── main │ │ │ └── assets │ │ │ │ └── icon.png │ │ ├── build.gradle.kts │ │ └── README.md │ ├── novelfullme │ │ ├── main │ │ │ └── assets │ │ │ │ └── icon.png │ │ └── build.gradle.kts │ ├── novelowlcom │ │ ├── main │ │ │ └── assets │ │ │ │ └── icon.png │ │ └── build.gradle.kts │ ├── novelscafes │ │ ├── main │ │ │ └── assets │ │ │ │ └── icon.png │ │ └── build.gradle.kts │ ├── noveltop1net │ │ ├── main │ │ │ └── assets │ │ │ │ └── icon.png │ │ └── build.gradle.kts │ ├── novelupdates │ │ ├── main │ │ │ └── assets │ │ │ │ └── icon.png │ │ └── build.gradle.kts │ ├── pandanovel │ │ ├── main │ │ │ ├── assets │ │ │ │ └── icon.png │ │ │ └── src │ │ │ │ └── ireader │ │ │ │ └── pandanovel │ │ │ │ └── chapter │ │ │ │ ├── ChapterDTO.kt │ │ │ │ ├── Data.kt │ │ │ │ └── Info.kt │ │ └── build.gradle.kts │ ├── realwebnovel │ │ ├── main │ │ │ └── assets │ │ │ │ └── icon.png │ │ └── build.gradle.kts │ ├── reaperscans │ │ ├── main │ │ │ └── assets │ │ │ │ └── icon.png │ │ └── build.gradle.kts │ ├── scribblehub │ │ ├── main │ │ │ └── assets │ │ │ │ └── icon.png │ │ └── build.gradle.kts │ ├── webnovelcom │ │ ├── main │ │ │ └── assets │ │ │ │ └── icon.png │ │ └── build.gradle.kts │ ├── webnovelsite │ │ ├── main │ │ │ └── assets │ │ │ │ └── icon.png │ │ └── build.gradle.kts │ ├── wuxiaworld │ │ ├── main │ │ │ ├── assets │ │ │ │ └── icon.png │ │ │ └── src │ │ │ │ └── ireader │ │ │ │ └── wuxiaworld │ │ │ │ ├── PopularDTO.kt │ │ │ │ └── Item.kt │ │ └── build.gradle.kts │ ├── lightnovelpub │ │ ├── main │ │ │ ├── assets │ │ │ │ └── icon.png │ │ │ └── src │ │ │ │ └── ireader │ │ │ │ └── lightnovelpub │ │ │ │ └── SearchResponse.kt │ │ ├── webnovelpub │ │ │ ├── assets │ │ │ │ └── icon.png │ │ │ └── src │ │ │ │ └── WebNovelPub.kt │ │ └── build.gradle.kts │ ├── novelsemperor │ │ ├── main │ │ │ └── assets │ │ │ │ └── icon.png │ │ └── build.gradle.kts │ ├── wuxiaworldsite │ │ ├── main │ │ │ └── assets │ │ │ │ └── icon.png │ │ └── build.gradle.kts │ ├── genesistranslator │ │ ├── main │ │ │ └── assets │ │ │ │ └── icon.png │ │ └── build.gradle.kts │ ├── kissnovellove │ │ ├── main │ │ │ └── assets │ │ │ │ └── icon.png.png │ │ └── build.gradle.kts │ ├── koreanonline │ │ ├── main │ │ │ └── assets │ │ │ │ └── icon.png.png │ │ └── build.gradle.kts │ ├── lightnovelreader │ │ ├── main │ │ │ ├── assets │ │ │ │ └── icon.png │ │ │ └── src │ │ │ │ └── ireader │ │ │ │ └── lightnovelreader │ │ │ │ ├── SearchResponse.kt │ │ │ │ └── Result.kt │ │ └── build.gradle.kts │ ├── qidianundergrond │ │ ├── main │ │ │ ├── assets │ │ │ │ └── icon.png │ │ │ └── src │ │ │ │ └── ireader │ │ │ │ └── qidianundergrond │ │ │ │ ├── QUGroup.kt │ │ │ │ ├── ChapterGroup.kt │ │ │ │ ├── ChapterGroupItem.kt │ │ │ │ └── QUGroupItem.kt │ │ └── build.gradle.kts │ ├── wuxiaworldsiteco │ │ ├── main │ │ │ └── assets │ │ │ │ └── icon.png │ │ └── build.gradle.kts │ ├── README.md │ ├── fictionzone │ │ └── build.gradle.kts │ ├── libread │ │ └── build.gradle.kts │ ├── dreambigtl │ │ └── build.gradle.kts │ ├── novelonline │ │ └── build.gradle.kts │ ├── storyseedling │ │ └── build.gradle.kts │ ├── bestlightnovel │ │ └── build.gradle.kts │ ├── wuxiaclick │ │ └── build.gradle.kts │ ├── build.gradle.kts │ ├── fenrir │ │ ├── build.gradle.kts │ │ └── README.md │ └── mydramanovel │ │ └── build.gradle.kts ├── fa │ └── uptvs │ │ ├── main │ │ └── assets │ │ │ └── icon.png │ │ └── build.gradle.kts ├── fr │ ├── chireads │ │ ├── main │ │ │ └── assets │ │ │ │ └── icon.png │ │ └── build.gradle.kts │ └── noveldeglace │ │ ├── main │ │ └── assets │ │ │ └── icon.png │ │ └── build.gradle.kts ├── tu │ ├── epiknovel │ │ ├── main │ │ │ └── assets │ │ │ │ └── icon.png │ │ └── build.gradle.kts │ └── novelgecesi │ │ ├── main │ │ └── assets │ │ │ └── icon.png │ │ └── build.gradle.kts ├── in │ └── indowebnovel │ │ ├── main │ │ └── assets │ │ │ └── icon.png │ │ └── build.gradle.kts ├── multisrc │ ├── madara │ │ ├── armtl │ │ │ ├── assets │ │ │ │ └── icon.png │ │ │ └── src │ │ │ │ └── ireader │ │ │ │ └── armtl │ │ │ │ ├── ArMtl.kt │ │ │ │ └── Constants.kt │ │ ├── azora │ │ │ └── assets │ │ │ │ └── icon.png │ │ ├── hizomanga │ │ │ ├── assets │ │ │ │ └── icon.png │ │ │ └── src │ │ │ │ └── ireader │ │ │ │ └── hizomang │ │ │ │ ├── HizoManga.kt │ │ │ │ └── Constants.kt │ │ ├── arnovel │ │ │ └── assets │ │ │ │ └── arnovel.png │ │ ├── freenovel │ │ │ ├── assets │ │ │ │ └── freenovel.png │ │ │ └── src │ │ │ │ └── ireader │ │ │ │ └── freenovel │ │ │ │ └── Freenovel.kt │ │ ├── meionovel │ │ │ ├── assets │ │ │ │ └── meionovel.png │ │ │ └── src │ │ │ │ └── ireader │ │ │ │ └── meionovel │ │ │ │ └── MeioNovel.kt │ │ ├── morenovel │ │ │ ├── assets │ │ │ │ └── morenovel.png │ │ │ └── src │ │ │ │ └── ireader │ │ │ │ └── morenovel │ │ │ │ └── MoreNovel.kt │ │ ├── novel4up │ │ │ ├── assets │ │ │ │ └── novel4up.png │ │ │ └── src │ │ │ │ └── ireader │ │ │ │ └── novel4up │ │ │ │ └── Novel4Up.kt │ │ ├── zinnovel │ │ │ ├── assets │ │ │ │ └── zinnovel.png │ │ │ └── src │ │ │ │ └── ireader │ │ │ │ └── zinnovel │ │ │ │ └── Zinnovel.kt │ │ ├── clicknovel │ │ │ ├── assets │ │ │ │ └── clicknovel.png │ │ │ └── src │ │ │ │ └── ireader │ │ │ │ └── clicknovel │ │ │ │ └── ClickNovel.kt │ │ ├── mtlnovelclub │ │ │ ├── assets │ │ │ │ ├── zinnovel.png │ │ │ │ └── mtlnovelclub.png │ │ │ └── src │ │ │ │ └── ireader │ │ │ │ └── mtlnovelclub │ │ │ │ └── MTLNovelClub.kt │ │ ├── lunarletters │ │ │ ├── assets │ │ │ │ └── lunarletters.png │ │ │ └── src │ │ │ │ └── ireader │ │ │ │ └── lunarletters │ │ │ │ └── LunarLetters.kt │ │ ├── webnovelover │ │ │ ├── assets │ │ │ │ └── webnovellover.png │ │ │ └── src │ │ │ │ └── ireader │ │ │ │ └── webnovelover │ │ │ │ └── WebNovelLover.kt │ │ ├── firstkissnovel │ │ │ ├── assets │ │ │ │ └── 1stkissnovel.png │ │ │ └── src │ │ │ │ └── ireader │ │ │ │ └── firstkissnovel │ │ │ │ └── FirstKissNovel.kt │ │ ├── mysticalseries │ │ │ ├── assets │ │ │ │ └── mysticalseries.png │ │ │ └── src │ │ │ │ └── ireader │ │ │ │ └── mysticalseries │ │ │ │ └── MysticalSeries.kt │ │ ├── noveltranslate │ │ │ ├── assets │ │ │ │ └── noveltranslate.png │ │ │ └── src │ │ │ │ └── ireader │ │ │ │ └── noveltranslate │ │ │ │ └── NovelTranslate.kt │ │ ├── readwebnovels │ │ │ ├── assets │ │ │ │ └── readwebnovels.png │ │ │ └── src │ │ │ │ └── ireader │ │ │ │ └── readwebnovels │ │ │ │ └── ReadWebNovels.kt │ │ ├── main │ │ │ └── src │ │ │ │ └── ireader │ │ │ │ └── madara │ │ │ │ └── Path.kt │ │ ├── novelmultiverse │ │ │ ├── assets │ │ │ │ └── novelmultiverse.png │ │ │ └── src │ │ │ │ └── ireader │ │ │ │ └── novelmultiverse │ │ │ │ └── NovelMultiverse.kt │ │ ├── lightnovelheaven │ │ │ ├── assets │ │ │ │ └── lightnovelheaven.png │ │ │ └── src │ │ │ │ └── ireader │ │ │ │ └── lightnovelheaven │ │ │ │ └── LightNovelHeaven.kt │ │ ├── turkcelightnovels │ │ │ ├── assets │ │ │ │ └── turkcelightnovels.png │ │ │ └── src │ │ │ │ └── ireader │ │ │ │ └── turkcelightnovels │ │ │ │ └── TurkceLightNovels.kt │ │ ├── sleepytranslations │ │ │ ├── assets │ │ │ │ └── sleepytranslations.png │ │ │ └── src │ │ │ │ └── ireader │ │ │ │ └── sleepytranslations │ │ │ │ └── SleepyTranslations.kt │ │ ├── neosekaitranslations │ │ │ ├── assets │ │ │ │ └── noobchantranslation.png │ │ │ └── src │ │ │ │ └── ireader │ │ │ │ └── neosekaitranslations │ │ │ │ ├── Constants.kt │ │ │ │ └── Neosekaitranslations.kt │ │ ├── sonicmtl │ │ │ └── src │ │ │ │ └── ireader │ │ │ │ └── sonicmtl │ │ │ │ └── SonicMTL.kt │ │ └── cratenovel │ │ │ └── src │ │ │ └── ireader │ │ │ └── cratenovel │ │ │ └── CrateNovel.kt │ ├── readwn │ │ ├── readwn │ │ │ ├── assets │ │ │ │ └── icon.png │ │ │ └── src │ │ │ │ └── ireader │ │ │ │ └── readwn │ │ │ │ └── Readwn.kt │ │ ├── ltnovel │ │ │ ├── assets │ │ │ │ └── ltnovel.png │ │ │ └── src │ │ │ │ └── ireader │ │ │ │ └── ltnovel │ │ │ │ └── Ltnovel.kt │ │ ├── novelmt │ │ │ ├── assets │ │ │ │ └── novelmt.png │ │ │ └── src │ │ │ │ └── ireader │ │ │ │ └── novelmt │ │ │ │ └── Readwn.kt │ │ └── build.gradle.kts │ ├── skynovel │ │ ├── wbnovel │ │ │ ├── assets │ │ │ │ └── icon.png │ │ │ └── src │ │ │ │ └── ireader │ │ │ │ └── wbnovel │ │ │ │ └── WbNovel.kt │ │ ├── skynovel │ │ │ ├── assets │ │ │ │ └── icon.png │ │ │ └── src │ │ │ │ └── ireader │ │ │ │ └── skynovel │ │ │ │ └── SkyNovel.kt │ │ └── build.gradle.kts │ └── mtlnovel │ │ ├── mtlnovelen │ │ ├── assets │ │ │ └── icon.png │ │ └── src │ │ │ └── ireader │ │ │ └── mtlnovelen │ │ │ └── MtlNovelEn.kt │ │ ├── mtlnoveles │ │ ├── assets │ │ │ └── icon.png │ │ └── src │ │ │ └── ireader │ │ │ └── mtlnoveles │ │ │ └── MtlNovelEs.kt │ │ ├── mtlnovelfr │ │ ├── assets │ │ │ └── icon.png │ │ └── src │ │ │ └── ireader │ │ │ └── mtlnovelfr │ │ │ └── MtlNovelFr.kt │ │ ├── mtlnovelin │ │ ├── assets │ │ │ └── icon.png │ │ └── src │ │ │ └── ireader │ │ │ └── mtlnovelin │ │ │ └── MtlNovelIn.kt │ │ ├── main │ │ └── src │ │ │ └── ireader │ │ │ └── mtlnovelmodel │ │ │ ├── mtlSearchItem.kt │ │ │ ├── Item.kt │ │ │ └── Result.kt │ │ └── build.gradle.kts └── common │ └── build.gradle.kts ├── extensions ├── res │ └── mipmap-mdpi │ │ └── ic_launcher.png └── AndroidManifest.xml ├── test-extensions ├── src │ ├── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── ireader │ │ │ ├── app │ │ │ └── Constatns.kt │ │ │ └── constants │ │ │ └── Constants.kt │ ├── androidTest │ │ └── java │ │ │ └── ireader │ │ │ └── app │ │ │ ├── README.md │ │ │ ├── tests │ │ │ ├── ContentChecker.kt │ │ │ ├── BookListChecker.kt │ │ │ └── InfoChecked.kt │ │ │ └── Constants.kt │ └── test │ │ └── java │ │ └── ireader │ │ └── app │ │ ├── Cosntants.kt │ │ ├── mockcomponents │ │ ├── FakeHttpClients.kt │ │ └── FakePreferencesStore.kt │ │ └── tests │ │ ├── ContentChecker.kt │ │ ├── BookListChecker.kt │ │ ├── InfoChecked.kt │ │ └── ChapterChecker.kt └── build.gradle.kts ├── .idea └── inspectionProfiles │ ├── ktlint.xml │ ├── profiles_settings.xml │ └── Project_Default.xml ├── scripts ├── converter_v5 │ └── __init__.py ├── bump-version-codes.ps1 └── bump-version-codes.py ├── .github ├── runner-files │ └── ci-gradle.properties ├── workflows │ ├── cancel_pull_request.yml │ └── code_quality.yml ├── scripts │ ├── sign-apks.sh │ └── commit-repo.sh ├── PULL_REQUEST_TEMPLATE.md └── ISSUE_TEMPLATE │ └── feature_request.yml ├── annotations ├── build.gradle.kts └── src │ └── main │ └── kotlin │ └── Extension.kt ├── multisrc ├── src │ └── main │ │ └── java │ │ └── ireader │ │ └── utility │ │ └── TestConstants.kt └── build.gradle.kts ├── .editorconfig ├── compiler ├── build.gradle.kts └── src │ └── main │ └── resources │ └── META-INF │ └── services │ └── com.google.devtools.ksp.processing.SymbolProcessorProvider ├── gradle.properties ├── e1be5cc3-0144-44d0-8517-801c039c045f.json └── js-sources └── webpack.config.d └── bundle.js /guide.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | } -------------------------------------------------------------------------------- /defaultRes/res/raw/gradle.properties: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /common/src/jsMain/kotlin/ireader/js/runtime/Utils.kt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /defaultRes/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/source-api/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | 3 | key.properties 4 | keys.properties 5 | -------------------------------------------------------------------------------- /deeplink/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /sources/ar/riwyat/main/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/ar/riwyat/main/assets/icon.png -------------------------------------------------------------------------------- /sources/cn/aixdzs/main/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/cn/aixdzs/main/assets/icon.png -------------------------------------------------------------------------------- /sources/en/fanmtl/main/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/en/fanmtl/main/assets/icon.png -------------------------------------------------------------------------------- /sources/en/lnmtl/main/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/en/lnmtl/main/assets/icon.png -------------------------------------------------------------------------------- /sources/en/pawread/main/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/en/pawread/main/assets/icon.png -------------------------------------------------------------------------------- /sources/en/ranobes/main/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/en/ranobes/main/assets/icon.png -------------------------------------------------------------------------------- /sources/en/readmtl/main/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/en/readmtl/main/assets/icon.png -------------------------------------------------------------------------------- /sources/en/wnmtl/main/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/en/wnmtl/main/assets/icon.png -------------------------------------------------------------------------------- /sources/fa/uptvs/main/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/fa/uptvs/main/assets/icon.png -------------------------------------------------------------------------------- /sources/ar/kolnovel/main/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/ar/kolnovel/main/assets/icon.png -------------------------------------------------------------------------------- /sources/ar/sunovels/main/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/ar/sunovels/main/assets/icon.png -------------------------------------------------------------------------------- /sources/en/coolnovel/main/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/en/coolnovel/main/assets/icon.png -------------------------------------------------------------------------------- /sources/en/daonovel/main/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/en/daonovel/main/assets/icon.png -------------------------------------------------------------------------------- /sources/en/fastnovel/main/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/en/fastnovel/main/assets/icon.png -------------------------------------------------------------------------------- /sources/en/mostnovel/main/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/en/mostnovel/main/assets/icon.png -------------------------------------------------------------------------------- /sources/en/mtlnation/main/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/en/mtlnation/main/assets/icon.png -------------------------------------------------------------------------------- /sources/en/novelfire/main/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/en/novelfire/main/assets/icon.png -------------------------------------------------------------------------------- /sources/en/novelfull/main/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/en/novelfull/main/assets/icon.png -------------------------------------------------------------------------------- /sources/en/novelhall/main/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/en/novelhall/main/assets/icon.png -------------------------------------------------------------------------------- /sources/en/novelstic/main/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/en/novelstic/main/assets/icon.png -------------------------------------------------------------------------------- /sources/en/royalroad/main/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/en/royalroad/main/assets/icon.png -------------------------------------------------------------------------------- /sources/en/skynovel/main/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/en/skynovel/main/assets/icon.png -------------------------------------------------------------------------------- /sources/fr/chireads/main/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/fr/chireads/main/assets/icon.png -------------------------------------------------------------------------------- /sources/tu/epiknovel/main/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/tu/epiknovel/main/assets/icon.png -------------------------------------------------------------------------------- /defaultRes/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/defaultRes/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /defaultRes/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/defaultRes/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /defaultRes/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/defaultRes/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /defaultRes/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/defaultRes/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /extensions/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/extensions/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /sources/ar/realmnovel/main/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/ar/realmnovel/main/assets/icon.png -------------------------------------------------------------------------------- /sources/ar/rewayatfans/main/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/ar/rewayatfans/main/assets/icon.png -------------------------------------------------------------------------------- /sources/en/allnovelfull/main/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/en/allnovelfull/main/assets/icon.png -------------------------------------------------------------------------------- /sources/en/boxnovelcom/main/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/en/boxnovelcom/main/assets/icon.png -------------------------------------------------------------------------------- /sources/en/comrademao/main/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/en/comrademao/main/assets/icon.png -------------------------------------------------------------------------------- /sources/en/freewebnovel/main/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/en/freewebnovel/main/assets/icon.png -------------------------------------------------------------------------------- /sources/en/lightnovels/main/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/en/lightnovels/main/assets/icon.png -------------------------------------------------------------------------------- /sources/en/mylovenovel/main/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/en/mylovenovel/main/assets/icon.png -------------------------------------------------------------------------------- /sources/en/mylovenovel/main/src/ireader/constants/Constants.kt: -------------------------------------------------------------------------------- 1 | package ireader.constants 2 | 3 | // Test constants removed - not compatible with KMP 4 | -------------------------------------------------------------------------------- /sources/en/novelbuddy/main/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/en/novelbuddy/main/assets/icon.png -------------------------------------------------------------------------------- /sources/en/novelfullme/main/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/en/novelfullme/main/assets/icon.png -------------------------------------------------------------------------------- /sources/en/novelowlcom/main/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/en/novelowlcom/main/assets/icon.png -------------------------------------------------------------------------------- /sources/en/novelscafes/main/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/en/novelscafes/main/assets/icon.png -------------------------------------------------------------------------------- /sources/en/noveltop1net/main/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/en/noveltop1net/main/assets/icon.png -------------------------------------------------------------------------------- /sources/en/novelupdates/main/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/en/novelupdates/main/assets/icon.png -------------------------------------------------------------------------------- /sources/en/pandanovel/main/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/en/pandanovel/main/assets/icon.png -------------------------------------------------------------------------------- /sources/en/realwebnovel/main/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/en/realwebnovel/main/assets/icon.png -------------------------------------------------------------------------------- /sources/en/reaperscans/main/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/en/reaperscans/main/assets/icon.png -------------------------------------------------------------------------------- /sources/en/scribblehub/main/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/en/scribblehub/main/assets/icon.png -------------------------------------------------------------------------------- /sources/en/webnovelcom/main/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/en/webnovelcom/main/assets/icon.png -------------------------------------------------------------------------------- /sources/en/webnovelsite/main/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/en/webnovelsite/main/assets/icon.png -------------------------------------------------------------------------------- /sources/en/wuxiaworld/main/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/en/wuxiaworld/main/assets/icon.png -------------------------------------------------------------------------------- /sources/fr/noveldeglace/main/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/fr/noveldeglace/main/assets/icon.png -------------------------------------------------------------------------------- /sources/in/indowebnovel/main/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/in/indowebnovel/main/assets/icon.png -------------------------------------------------------------------------------- /sources/tu/novelgecesi/main/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/tu/novelgecesi/main/assets/icon.png -------------------------------------------------------------------------------- /defaultRes/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/defaultRes/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sources/ar/novelparadise/main/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/ar/novelparadise/main/assets/icon.png -------------------------------------------------------------------------------- /sources/en/lightnovelpub/main/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/en/lightnovelpub/main/assets/icon.png -------------------------------------------------------------------------------- /sources/en/novelsemperor/main/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/en/novelsemperor/main/assets/icon.png -------------------------------------------------------------------------------- /sources/en/wuxiaworldsite/main/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/en/wuxiaworldsite/main/assets/icon.png -------------------------------------------------------------------------------- /sources/multisrc/madara/armtl/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/multisrc/madara/armtl/assets/icon.png -------------------------------------------------------------------------------- /sources/multisrc/madara/azora/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/multisrc/madara/azora/assets/icon.png -------------------------------------------------------------------------------- /sources/multisrc/readwn/readwn/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/multisrc/readwn/readwn/assets/icon.png -------------------------------------------------------------------------------- /test-extensions/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /sources/cn/sexinsex/main/assets/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/cn/sexinsex/main/assets/ic_launcher.png -------------------------------------------------------------------------------- /sources/en/genesistranslator/main/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/en/genesistranslator/main/assets/icon.png -------------------------------------------------------------------------------- /sources/en/kissnovellove/main/assets/icon.png.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/en/kissnovellove/main/assets/icon.png.png -------------------------------------------------------------------------------- /sources/en/koreanonline/main/assets/icon.png.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/en/koreanonline/main/assets/icon.png.png -------------------------------------------------------------------------------- /sources/en/lightnovelreader/main/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/en/lightnovelreader/main/assets/icon.png -------------------------------------------------------------------------------- /sources/en/mtlnation/main/src/ireader/mtlnation/ChapterDTO.kt: -------------------------------------------------------------------------------- 1 | package ireader.mtlnation 2 | 3 | data class ChapterDTO( 4 | val `data`: List 5 | ) 6 | -------------------------------------------------------------------------------- /sources/en/qidianundergrond/main/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/en/qidianundergrond/main/assets/icon.png -------------------------------------------------------------------------------- /sources/en/wuxiaworldsiteco/main/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/en/wuxiaworldsiteco/main/assets/icon.png -------------------------------------------------------------------------------- /sources/multisrc/madara/hizomanga/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/multisrc/madara/hizomanga/assets/icon.png -------------------------------------------------------------------------------- /sources/multisrc/skynovel/wbnovel/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/multisrc/skynovel/wbnovel/assets/icon.png -------------------------------------------------------------------------------- /sources/multisrc/madara/arnovel/assets/arnovel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/multisrc/madara/arnovel/assets/arnovel.png -------------------------------------------------------------------------------- /sources/multisrc/readwn/ltnovel/assets/ltnovel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/multisrc/readwn/ltnovel/assets/ltnovel.png -------------------------------------------------------------------------------- /sources/multisrc/readwn/novelmt/assets/novelmt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/multisrc/readwn/novelmt/assets/novelmt.png -------------------------------------------------------------------------------- /sources/multisrc/skynovel/skynovel/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/multisrc/skynovel/skynovel/assets/icon.png -------------------------------------------------------------------------------- /defaultRes/res/mipmap-hdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/defaultRes/res/mipmap-hdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /defaultRes/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/defaultRes/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /defaultRes/res/mipmap-hdpi/ic_launcher_monochrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/defaultRes/res/mipmap-hdpi/ic_launcher_monochrome.png -------------------------------------------------------------------------------- /defaultRes/res/mipmap-mdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/defaultRes/res/mipmap-mdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /defaultRes/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/defaultRes/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /defaultRes/res/mipmap-mdpi/ic_launcher_monochrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/defaultRes/res/mipmap-mdpi/ic_launcher_monochrome.png -------------------------------------------------------------------------------- /defaultRes/res/mipmap-xhdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/defaultRes/res/mipmap-xhdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /defaultRes/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/defaultRes/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /defaultRes/res/mipmap-xhdpi/ic_launcher_monochrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/defaultRes/res/mipmap-xhdpi/ic_launcher_monochrome.png -------------------------------------------------------------------------------- /docs/source-api/src/commonMain/kotlin/ireader/core/source/model/Listing.kt: -------------------------------------------------------------------------------- 1 | 2 | 3 | package ireader.core.source.model 4 | 5 | abstract class Listing(val name: String) 6 | -------------------------------------------------------------------------------- /docs/source-api/src/commonMain/kotlin/ireader/core/source/model/PageListEmpty.kt: -------------------------------------------------------------------------------- 1 | 2 | 3 | package ireader.core.source.model 4 | 5 | class PageListEmpty : Exception() 6 | -------------------------------------------------------------------------------- /sources/en/lightnovelpub/webnovelpub/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/en/lightnovelpub/webnovelpub/assets/icon.png -------------------------------------------------------------------------------- /sources/en/lightnovels/main/src/ireader/lightnovels/detail_dto/Query.kt: -------------------------------------------------------------------------------- 1 | package ireader.lightnovels.detail_dto 2 | 3 | data class Query( 4 | val slug: String 5 | ) 6 | -------------------------------------------------------------------------------- /sources/multisrc/madara/freenovel/assets/freenovel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/multisrc/madara/freenovel/assets/freenovel.png -------------------------------------------------------------------------------- /sources/multisrc/madara/meionovel/assets/meionovel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/multisrc/madara/meionovel/assets/meionovel.png -------------------------------------------------------------------------------- /sources/multisrc/madara/morenovel/assets/morenovel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/multisrc/madara/morenovel/assets/morenovel.png -------------------------------------------------------------------------------- /sources/multisrc/madara/novel4up/assets/novel4up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/multisrc/madara/novel4up/assets/novel4up.png -------------------------------------------------------------------------------- /sources/multisrc/madara/zinnovel/assets/zinnovel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/multisrc/madara/zinnovel/assets/zinnovel.png -------------------------------------------------------------------------------- /sources/multisrc/mtlnovel/mtlnovelen/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/multisrc/mtlnovel/mtlnovelen/assets/icon.png -------------------------------------------------------------------------------- /sources/multisrc/mtlnovel/mtlnoveles/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/multisrc/mtlnovel/mtlnoveles/assets/icon.png -------------------------------------------------------------------------------- /sources/multisrc/mtlnovel/mtlnovelfr/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/multisrc/mtlnovel/mtlnovelfr/assets/icon.png -------------------------------------------------------------------------------- /sources/multisrc/mtlnovel/mtlnovelin/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/multisrc/mtlnovel/mtlnovelin/assets/icon.png -------------------------------------------------------------------------------- /defaultRes/res/mipmap-xxhdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/defaultRes/res/mipmap-xxhdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /defaultRes/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/defaultRes/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /defaultRes/res/mipmap-xxhdpi/ic_launcher_monochrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/defaultRes/res/mipmap-xxhdpi/ic_launcher_monochrome.png -------------------------------------------------------------------------------- /defaultRes/res/mipmap-xxxhdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/defaultRes/res/mipmap-xxxhdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /defaultRes/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/defaultRes/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /defaultRes/res/mipmap-xxxhdpi/ic_launcher_monochrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/defaultRes/res/mipmap-xxxhdpi/ic_launcher_monochrome.png -------------------------------------------------------------------------------- /sources/multisrc/madara/clicknovel/assets/clicknovel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/multisrc/madara/clicknovel/assets/clicknovel.png -------------------------------------------------------------------------------- /sources/multisrc/madara/mtlnovelclub/assets/zinnovel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/multisrc/madara/mtlnovelclub/assets/zinnovel.png -------------------------------------------------------------------------------- /sources/multisrc/madara/lunarletters/assets/lunarletters.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/multisrc/madara/lunarletters/assets/lunarletters.png -------------------------------------------------------------------------------- /sources/multisrc/madara/mtlnovelclub/assets/mtlnovelclub.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/multisrc/madara/mtlnovelclub/assets/mtlnovelclub.png -------------------------------------------------------------------------------- /sources/multisrc/madara/webnovelover/assets/webnovellover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/multisrc/madara/webnovelover/assets/webnovellover.png -------------------------------------------------------------------------------- /sources/en/lightnovelreader/main/src/ireader/lightnovelreader/SearchResponse.kt: -------------------------------------------------------------------------------- 1 | package ireader.lightnovelreader 2 | 3 | data class SearchResponse( 4 | val results: List 5 | ) 6 | -------------------------------------------------------------------------------- /sources/en/lightnovels/main/src/ireader/lightnovels/detail_dto/RuntimeConfig.kt: -------------------------------------------------------------------------------- 1 | package ireader.lightnovels.detail_dto 2 | 3 | data class RuntimeConfig( 4 | val SHOW_AD: Boolean 5 | ) 6 | -------------------------------------------------------------------------------- /sources/multisrc/madara/firstkissnovel/assets/1stkissnovel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/multisrc/madara/firstkissnovel/assets/1stkissnovel.png -------------------------------------------------------------------------------- /sources/multisrc/madara/mysticalseries/assets/mysticalseries.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/multisrc/madara/mysticalseries/assets/mysticalseries.png -------------------------------------------------------------------------------- /sources/multisrc/madara/noveltranslate/assets/noveltranslate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/multisrc/madara/noveltranslate/assets/noveltranslate.png -------------------------------------------------------------------------------- /sources/multisrc/madara/readwebnovels/assets/readwebnovels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/multisrc/madara/readwebnovels/assets/readwebnovels.png -------------------------------------------------------------------------------- /.idea/inspectionProfiles/ktlint.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /scripts/converter_v5/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Converter V5 - Complete Rewrite 3 | AI-powered with proper TypeScript analysis 4 | """ 5 | 6 | __version__ = "5.0.0" 7 | __status__ = "In Development" 8 | -------------------------------------------------------------------------------- /sources/en/lightnovels/main/src/ireader/lightnovels/content_dto/RuntimeConfig.kt: -------------------------------------------------------------------------------- 1 | package ireader.lightnovels.content_dto 2 | 3 | data class RuntimeConfig( 4 | val SHOW_AD: Boolean 5 | ) 6 | -------------------------------------------------------------------------------- /sources/multisrc/madara/main/src/ireader/madara/Path.kt: -------------------------------------------------------------------------------- 1 | package ireader.madara 2 | 3 | data class Path( 4 | val novel: String, 5 | val novels: String, 6 | val chapter: String, 7 | ) 8 | -------------------------------------------------------------------------------- /sources/multisrc/madara/novelmultiverse/assets/novelmultiverse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/multisrc/madara/novelmultiverse/assets/novelmultiverse.png -------------------------------------------------------------------------------- /sources/en/lightnovels/main/src/ireader/lightnovels/detail_dto/Tag.kt: -------------------------------------------------------------------------------- 1 | package ireader.lightnovels.detail_dto 2 | 3 | data class Tag( 4 | val tag_name: String, 5 | val tag_slug: String 6 | ) 7 | -------------------------------------------------------------------------------- /sources/multisrc/madara/lightnovelheaven/assets/lightnovelheaven.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/multisrc/madara/lightnovelheaven/assets/lightnovelheaven.png -------------------------------------------------------------------------------- /sources/multisrc/madara/turkcelightnovels/assets/turkcelightnovels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/multisrc/madara/turkcelightnovels/assets/turkcelightnovels.png -------------------------------------------------------------------------------- /sources/en/lightnovels/main/src/ireader/lightnovels/content_dto/Props.kt: -------------------------------------------------------------------------------- 1 | package ireader.lightnovels.content_dto 2 | 3 | data class Props( 4 | val __N_SSP: Boolean, 5 | val pageProps: PageProps 6 | ) 7 | -------------------------------------------------------------------------------- /sources/en/lightnovels/main/src/ireader/lightnovels/content_dto/Query.kt: -------------------------------------------------------------------------------- 1 | package ireader.lightnovels.content_dto 2 | 3 | data class Query( 4 | val chapterSlug: String, 5 | val novelSlug: String 6 | ) 7 | -------------------------------------------------------------------------------- /sources/en/lightnovels/main/src/ireader/lightnovels/detail_dto/Props.kt: -------------------------------------------------------------------------------- 1 | package ireader.lightnovels.detail_dto 2 | 3 | data class Props( 4 | val __N_SSG: Boolean, 5 | val pageProps: PageProps 6 | ) 7 | -------------------------------------------------------------------------------- /sources/multisrc/madara/sleepytranslations/assets/sleepytranslations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/multisrc/madara/sleepytranslations/assets/sleepytranslations.png -------------------------------------------------------------------------------- /.github/runner-files/ci-gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.daemon=false 2 | org.gradle.jvmargs=-Xmx5120m 3 | org.gradle.workers.max=2 4 | 5 | kotlin.incremental=false 6 | kotlin.compiler.execution.strategy=in-process 7 | -------------------------------------------------------------------------------- /sources/common/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") 3 | } 4 | 5 | dependencies { 6 | compileOnly(libs.stdlib) 7 | compileOnly(libs.jsoup) 8 | compileOnly(libs.ireader.core) 9 | } 10 | -------------------------------------------------------------------------------- /sources/multisrc/madara/neosekaitranslations/assets/noobchantranslation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IReaderorg/IReader-extensions/HEAD/sources/multisrc/madara/neosekaitranslations/assets/noobchantranslation.png -------------------------------------------------------------------------------- /sources/en/lightnovels/main/src/ireader/lightnovels/detail_dto/Author.kt: -------------------------------------------------------------------------------- 1 | package ireader.lightnovels.detail_dto 2 | 3 | data class Author( 4 | val id: Int, 5 | val name: String, 6 | val slug: String 7 | ) 8 | -------------------------------------------------------------------------------- /sources/en/lightnovels/main/src/ireader/lightnovels/detail_dto/Genre.kt: -------------------------------------------------------------------------------- 1 | package ireader.lightnovels.detail_dto 2 | 3 | data class Genre( 4 | val id: Int, 5 | val name: String, 6 | val slug: String 7 | ) 8 | -------------------------------------------------------------------------------- /sources/en/lightnovels/main/src/ireader/lightnovels/detail_dto/GenreX.kt: -------------------------------------------------------------------------------- 1 | package ireader.lightnovels.detail_dto 2 | 3 | data class GenreX( 4 | val id: Int, 5 | val name: String, 6 | val slug: String 7 | ) 8 | -------------------------------------------------------------------------------- /sources/en/qidianundergrond/main/src/ireader/qidianundergrond/QUGroup.kt: -------------------------------------------------------------------------------- 1 | package ireader.qidianundergrond 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | class QUGroup : ArrayList() 7 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /docs/source-api/src/commonMain/kotlin/ireader/core/http/WebViewUtil.kt: -------------------------------------------------------------------------------- 1 | package ireader.core.http 2 | 3 | object WebViewUtil { 4 | const val SPOOF_PACKAGE_NAME = "org.chromium.chrome" 5 | const val MINIMUM_WEBVIEW_VERSION = 99 6 | } -------------------------------------------------------------------------------- /sources/en/lnmtl/main/src/ireader/lnmtl/volume/LNMTLVolumeResponse.kt: -------------------------------------------------------------------------------- 1 | package ireader.lnmtl.volume 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | class LNMTLVolumeResponse : ArrayList() -------------------------------------------------------------------------------- /sources/en/qidianundergrond/main/src/ireader/qidianundergrond/ChapterGroup.kt: -------------------------------------------------------------------------------- 1 | package ireader.qidianundergrond 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | class ChapterGroup : ArrayList() 7 | -------------------------------------------------------------------------------- /sources/en/lightnovels/main/src/ireader/lightnovels/content_dto/PageProps.kt: -------------------------------------------------------------------------------- 1 | package ireader.lightnovels.content_dto 2 | 3 | data class PageProps( 4 | val cachedChapterInfo: CachedChapterInfo, 5 | val pandaLinks: List 6 | ) 7 | -------------------------------------------------------------------------------- /sources/multisrc/mtlnovel/main/src/ireader/mtlnovelmodel/mtlSearchItem.kt: -------------------------------------------------------------------------------- 1 | package ireader.mtlnovelmodel 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class mtlSearchItem( 7 | val items: List, 8 | ) 9 | -------------------------------------------------------------------------------- /docs/source-api/src/commonMain/kotlin/ireader/core/http/DEFAULT_USER_AGENT.kt: -------------------------------------------------------------------------------- 1 | package ireader.core.http 2 | 3 | const val DEFAULT_USER_AGENT = "Mozilla/5.0 (Linux; Android 13) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.5304.141 Mobile Safari/537.36" -------------------------------------------------------------------------------- /sources/en/lightnovelreader/main/src/ireader/lightnovelreader/Result.kt: -------------------------------------------------------------------------------- 1 | package ireader.lightnovelreader 2 | 3 | data class Result( 4 | val image: String, 5 | val link: String, 6 | val original_title: String, 7 | val overview: String 8 | ) 9 | -------------------------------------------------------------------------------- /sources/en/lightnovels/main/src/ireader/lightnovels/search_dto/SearchDTO.kt: -------------------------------------------------------------------------------- 1 | package ireader.lightnovels.search_dto 2 | 3 | data class SearchDTO( 4 | val index: Int, 5 | val limit: Int, 6 | val results: List, 7 | val total: Int 8 | ) 9 | -------------------------------------------------------------------------------- /test-extensions/src/androidTest/java/ireader/app/README.md: -------------------------------------------------------------------------------- 1 | ## don't use androidTest for now, because gradle version is lower than expected. 2 | 3 | ## if someone can upgrade kotlin version and gradle version of this project without error, I really appreciate it. -------------------------------------------------------------------------------- /docs/source-api/src/iosMain/kotlin/ireader/core/http/HttpModule.ios.kt: -------------------------------------------------------------------------------- 1 | package ireader.core.http 2 | 3 | /** 4 | * iOS HTTP module placeholder 5 | * Note: HttpClients should be created in DomainModule with proper DI 6 | */ 7 | actual val httpModule: Any = Unit 8 | -------------------------------------------------------------------------------- /sources/ar/kolnovel/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("ar").map { lang -> 2 | Extension( 3 | name = "KolNovel", 4 | versionCode = 16, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "", 8 | nsfw = false) 9 | }.also(::register) 10 | -------------------------------------------------------------------------------- /sources/en/lightnovels/main/src/ireader/lightnovels/search_dto/SearchResult.kt: -------------------------------------------------------------------------------- 1 | package ireader.lightnovels.search_dto 2 | 3 | data class SearchResult( 4 | val index: Int, 5 | val limit: Int, 6 | val results: List, 7 | val total: Int 8 | ) 9 | -------------------------------------------------------------------------------- /sources/en/lightnovelpub/main/src/ireader/lightnovelpub/SearchResponse.kt: -------------------------------------------------------------------------------- 1 | package ireader.lightnovelpub 2 | @kotlinx.serialization.Serializable 3 | data class SearchResponse( 4 | val `$id`: String, 5 | val resultview: String, 6 | val success: Boolean 7 | ) 8 | -------------------------------------------------------------------------------- /sources/en/lightnovels/main/src/ireader/lightnovels/detail_dto/CachedHotNovel.kt: -------------------------------------------------------------------------------- 1 | package ireader.lightnovels.detail_dto 2 | 3 | data class CachedHotNovel( 4 | val genres: List, 5 | val id: Int, 6 | val slug: String, 7 | val title: String 8 | ) 9 | -------------------------------------------------------------------------------- /sources/multisrc/mtlnovel/main/src/ireader/mtlnovelmodel/Item.kt: -------------------------------------------------------------------------------- 1 | package ireader.mtlnovelmodel 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class Item( 7 | val query: String, 8 | val results: List, 9 | ) 10 | -------------------------------------------------------------------------------- /docs/source-api/src/commonMain/kotlin/ireader/core/http/HttpModule.kt: -------------------------------------------------------------------------------- 1 | package ireader.core.http 2 | 3 | /** 4 | * Common HTTP module declaration 5 | * Platform-specific implementations are provided in androidMain and desktopMain 6 | */ 7 | expect val httpModule: Any 8 | -------------------------------------------------------------------------------- /sources/ar/novelparadise/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("ar").map { lang -> 2 | Extension( 3 | name = "NovelParadise", 4 | versionCode = 4, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "", 8 | nsfw = false) 9 | }.also(::register) 10 | -------------------------------------------------------------------------------- /sources/en/README.md: -------------------------------------------------------------------------------- 1 | # ReadNovelFullPlugin 2 | 3 | **100% Accurate Conversion** 4 | 5 | Source: 6 | Version: 1.0.0 7 | Converter: Ultimate V4 8 | 9 | ## Status 10 | ✅ Production Ready 11 | ✅ All selectors validated 12 | ✅ Icon downloaded 13 | ✅ 100% test coverage expected 14 | -------------------------------------------------------------------------------- /sources/en/qidianundergrond/main/src/ireader/qidianundergrond/ChapterGroupItem.kt: -------------------------------------------------------------------------------- 1 | package ireader.qidianundergrond 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class ChapterGroupItem( 7 | val Href: String, 8 | val Text: String 9 | ) 10 | -------------------------------------------------------------------------------- /sources/en/wnmtl/main/src/ireader/wnmtl/chapter/ChapterDTO.kt: -------------------------------------------------------------------------------- 1 | package ireader.wnmtl.chapter 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class ChapterDTO( 7 | val code: Int, 8 | val `data`: Data, 9 | val message: String 10 | ) 11 | -------------------------------------------------------------------------------- /sources/en/wnmtl/main/src/ireader/wnmtl/content/ContentDTO.kt: -------------------------------------------------------------------------------- 1 | package ireader.wnmtl.content 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class ContentDTO( 7 | val code: Int, 8 | val `data`: Data, 9 | val message: String 10 | ) 11 | -------------------------------------------------------------------------------- /sources/en/wnmtl/main/src/ireader/wnmtl/explore/ExploreDTO.kt: -------------------------------------------------------------------------------- 1 | package ireader.wnmtl.explore 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class ExploreDTO( 7 | val code: Int, 8 | val `data`: Data, 9 | val message: String 10 | ) 11 | -------------------------------------------------------------------------------- /docs/source-api/src/androidMain/kotlin/ireader/core/http/HttpModule.kt: -------------------------------------------------------------------------------- 1 | package ireader.core.http 2 | 3 | /** 4 | * Android HTTP module providing network components 5 | * Note: This is a placeholder. HttpClients should be created in DomainModule 6 | */ 7 | actual val httpModule: Any = Unit 8 | -------------------------------------------------------------------------------- /sources/en/lightnovels/main/src/ireader/lightnovels/books_dto/BookListDTO.kt: -------------------------------------------------------------------------------- 1 | package ireader.lightnovels.books_dto 2 | 3 | data class BookListDTO( 4 | val index: Int, 5 | val limit: Int, 6 | val results: List, 7 | val size: Int, 8 | val total: Int 9 | ) 10 | -------------------------------------------------------------------------------- /sources/en/lightnovels/main/src/ireader/lightnovels/chapter_dto/ChapterDTO.kt: -------------------------------------------------------------------------------- 1 | package ireader.lightnovels.chapter_dto 2 | 3 | data class ChapterDTO( 4 | val limit: Int, 5 | val results: List, 6 | val size: Int, 7 | val start: Int, 8 | val total: Int 9 | ) 10 | -------------------------------------------------------------------------------- /sources/en/lightnovels/main/src/ireader/lightnovels/detail_dto/CachedLatestChapter.kt: -------------------------------------------------------------------------------- 1 | package ireader.lightnovels.detail_dto 2 | 3 | data class CachedLatestChapter( 4 | val chapter_index: Int, 5 | val chapter_name: String, 6 | val slug: String, 7 | val updated_at: String 8 | ) 9 | -------------------------------------------------------------------------------- /sources/en/lnmtl/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("en").map { lang -> 2 | Extension( 3 | name = "LnMtl", 4 | versionCode = 2, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /sources/en/wnmtl/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("en").map { lang -> 2 | Extension( 3 | name = "Wnmtl", 4 | versionCode = 6, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /sources/en/wuxiaworld/main/src/ireader/wuxiaworld/PopularDTO.kt: -------------------------------------------------------------------------------- 1 | package ireader.wuxiaworld 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class PopularDTO( 7 | val items: List, 8 | val result: Boolean, 9 | val total: Int 10 | ) 11 | -------------------------------------------------------------------------------- /sources/fa/uptvs/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("fa").map { lang -> 2 | Extension( 3 | name = "Uptvs", 4 | versionCode = 2, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Apr 23 18:36:03 GMT+03:30 2024 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /sources/ar/riwyat/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("ar").map { lang -> 2 | Extension( 3 | name = "Riwyat", 4 | versionCode = 10, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /sources/ar/sunovels/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("ar").map { lang -> 2 | Extension( 3 | name = "Sunovels", 4 | versionCode = 2, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /sources/cn/aixdzs/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("cn").map { lang -> 2 | Extension( 3 | name = "Aixdzs", 4 | versionCode = 3, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /sources/cn/sexinsex/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("cn").map { lang -> 2 | Extension( 3 | name = "sexinsex", 4 | versionCode = 3, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "", 8 | nsfw = true, 9 | icon = DEFAULT_ICON, 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /sources/en/daonovel/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("en").map { lang -> 2 | Extension( 3 | name = "DaoNovel", 4 | versionCode = 7, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /sources/en/fanmtl/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("en").map { lang -> 2 | Extension( 3 | name = "fanmtl", 4 | versionCode = 2, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /sources/en/pandanovel/main/src/ireader/pandanovel/chapter/ChapterDTO.kt: -------------------------------------------------------------------------------- 1 | package ireader.pandanovel.chapter 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class ChapterDTO( 7 | val code: Int, 8 | val msg: String, 9 | val data: Data 10 | ) 11 | -------------------------------------------------------------------------------- /sources/en/pawread/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("en").map { lang -> 2 | Extension( 3 | name = "Pawread", 4 | versionCode = 2, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /sources/en/ranobes/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("en").map { lang -> 2 | Extension( 3 | name = "Ranobes", 4 | versionCode = 9, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /sources/en/readmtl/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("en").map { lang -> 2 | Extension( 3 | name = "ReadMtl", 4 | versionCode = 3, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /sources/fr/chireads/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("fr").map { lang -> 2 | Extension( 3 | name = "Chireads", 4 | versionCode = 2, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /sources/ar/realmnovel/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("ar").map { lang -> 2 | Extension( 3 | name = "Realmnovel", 4 | versionCode = 2, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /sources/en/comrademao/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("en").map { lang -> 2 | Extension( 3 | name = "Comrademao", 4 | versionCode = 8, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /sources/en/coolnovel/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("en").map { lang -> 2 | Extension( 3 | name = "CoolNovel", 4 | versionCode = 2, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /sources/en/fastnovel/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("en").map { lang -> 2 | Extension( 3 | name = "FastNovel", 4 | versionCode = 4, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /sources/en/lightnovels/main/src/ireader/lightnovels/chapter_dto/Result.kt: -------------------------------------------------------------------------------- 1 | package ireader.lightnovels.chapter_dto 2 | 3 | data class Result( 4 | val chapter_index: Int, 5 | val chapter_name: String, 6 | val id: Int, 7 | val slug: String, 8 | val updated_at: String 9 | ) 10 | -------------------------------------------------------------------------------- /sources/en/mostnovel/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("en").map { lang -> 2 | Extension( 3 | name = "MostNovel", 4 | versionCode = 3, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /sources/en/mtlnation/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("en").map { lang -> 2 | Extension( 3 | name = "MtlNation", 4 | versionCode = 10, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /sources/en/novelfull/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("en").map { lang -> 2 | Extension( 3 | name = "NovelFull", 4 | versionCode = 9, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /sources/en/novelhall/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("en").map { lang -> 2 | Extension( 3 | name = "NovelHall", 4 | versionCode = 6, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /sources/en/novelstic/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("en").map { lang -> 2 | Extension( 3 | name = "NovelStic", 4 | versionCode = 3, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /sources/en/pandanovel/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("en").map { lang -> 2 | Extension( 3 | name = "PandaNovel", 4 | versionCode = 5, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /sources/en/royalroad/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("en").map { lang -> 2 | Extension( 3 | name = "RoyalRoad", 4 | versionCode = 2, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /sources/en/wuxiaworld/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("en").map { lang -> 2 | Extension( 3 | name = "Wuxiaworld", 4 | versionCode = 4, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /sources/tu/epiknovel/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("tu").map { lang -> 2 | Extension( 3 | name = "EpikNovel", 4 | versionCode = 2, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /test-extensions/src/main/java/ireader/app/Constatns.kt: -------------------------------------------------------------------------------- 1 | package ireader.app 2 | 3 | import ireader.constants.Constants 4 | 5 | 6 | val BOOK_URL = Constants.bookUrl 7 | val BOOK_NAME = Constants.bookName 8 | val CHAPTER_NAME = Constants.chapterName 9 | val CHAPTER_URL = Constants.chapterName 10 | -------------------------------------------------------------------------------- /sources/ar/rewayatfans/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("ar").map { lang -> 2 | Extension( 3 | name = "Rewayatfans", 4 | versionCode = 2, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /sources/en/boxnovelcom/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("en").map { lang -> 2 | Extension( 3 | name = "BoxNovelCom", 4 | versionCode = 5, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /sources/en/fictionzone/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("en").map { lang -> 2 | Extension( 3 | name = "FictionZone", 4 | versionCode = 2, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /sources/en/freewebnovel/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("en").map { lang -> 2 | Extension( 3 | name = "FreeWebNovel", 4 | versionCode = 12, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "", 8 | nsfw = false, 9 | icon = DEFAULT_ICON 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /sources/en/kissnovellove/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("en").map { lang -> 2 | Extension( 3 | name = "KissNovelLove", 4 | versionCode = 4, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /sources/en/koreanonline/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("en").map { lang -> 2 | Extension( 3 | name = "KoreanOnline", 4 | versionCode = 8, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /sources/en/lightnovels/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("en").map { lang -> 2 | Extension( 3 | name = "LightNovelsMe", 4 | versionCode = 6, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /sources/en/mylovenovel/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("en").map { lang -> 2 | Extension( 3 | name = "MyLoveNovel", 4 | versionCode = 9, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /sources/en/novelfullme/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("en").map { lang -> 2 | Extension( 3 | name = "NovelFullMe", 4 | versionCode = 3, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /sources/en/novelowlcom/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("en").map { lang -> 2 | Extension( 3 | name = "NovelOwlCom", 4 | versionCode = 4, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /sources/en/novelscafes/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("en").map { lang -> 2 | Extension( 3 | name = "NovelsCafes", 4 | versionCode = 3, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /sources/en/novelsemperor/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("en").map { lang -> 2 | Extension( 3 | name = "NovelsEmperor", 4 | versionCode = 2, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /sources/en/noveltop1net/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("en").map { lang -> 2 | Extension( 3 | name = "NovelTop1Net", 4 | versionCode = 3, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /sources/en/novelupdates/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("en").map { lang -> 2 | Extension( 3 | name = "NovelUpdates", 4 | versionCode = 4, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /sources/en/ranobes/main/src/ireader/ranobes/Chapter.kt: -------------------------------------------------------------------------------- 1 | package ireader.ranobes 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | 6 | @Serializable 7 | data class Chapter( 8 | val date: String, 9 | val id: String, 10 | val showDate: String, 11 | val title: String 12 | ) 13 | -------------------------------------------------------------------------------- /sources/en/realwebnovel/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("en").map { lang -> 2 | Extension( 3 | name = "RealWebNovel", 4 | versionCode = 8, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /sources/en/reaperscans/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("en").map { lang -> 2 | Extension( 3 | name = "ReaperScans", 4 | versionCode = 4, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /sources/en/scribblehub/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("en").map { lang -> 2 | Extension( 3 | name = "ScribbleHub", 4 | versionCode = 4, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /sources/en/skynovel/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("en").map { lang -> 2 | Extension( 3 | name = "SkyNovel", 4 | versionCode = 6, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "", 8 | nsfw = false, 9 | 10 | icon = DEFAULT_ICON, 11 | ) 12 | }.also(::register) 13 | -------------------------------------------------------------------------------- /sources/en/webnovelcom/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("en").map { lang -> 2 | Extension( 3 | name = "WebNovelCom", 4 | versionCode = 8, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /sources/en/webnovelsite/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("en").map { lang -> 2 | Extension( 3 | name = "WebNovelSite", 4 | versionCode = 2, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /sources/fr/noveldeglace/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("fr").map { lang -> 2 | Extension( 3 | name = "NovelDeGlace", 4 | versionCode = 2, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /sources/in/indowebnovel/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("in").map { lang -> 2 | Extension( 3 | name = "IndoWebNovel", 4 | versionCode = 2, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /sources/en/allnovelfull/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("en").map { lang -> 2 | Extension( 3 | name = "AllNovelFull", 4 | versionCode = 3, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | 11 | ) 12 | }.also(::register) 13 | -------------------------------------------------------------------------------- /sources/en/wnmtl/main/src/ireader/wnmtl/chapter/Data.kt: -------------------------------------------------------------------------------- 1 | package ireader.wnmtl.chapter 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class Data( 7 | val list: List, 8 | val pageSize: Int, 9 | val totalCount: Int, 10 | val totalPages: Int 11 | ) 12 | -------------------------------------------------------------------------------- /sources/en/wnmtl/main/src/ireader/wnmtl/explore/Data.kt: -------------------------------------------------------------------------------- 1 | package ireader.wnmtl.explore 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class Data( 7 | val list: List, 8 | val pageSize: Int, 9 | val totalCount: Int, 10 | val totalPages: Int 11 | ) 12 | -------------------------------------------------------------------------------- /sources/en/wuxiaworldsite/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("en").map { lang -> 2 | Extension( 3 | name = "WuxiaWorldSite", 4 | versionCode = 11, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /sources/en/genesistranslator/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("en").map { lang -> 2 | Extension( 3 | name = "GenesisTranslator", 4 | versionCode = 2, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /sources/en/lightnovelreader/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("en").map { lang -> 2 | Extension( 3 | name = "LightNovelReader", 4 | versionCode = 5, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /sources/en/lightnovels/main/src/ireader/lightnovels/books_dto/Result.kt: -------------------------------------------------------------------------------- 1 | package ireader.lightnovels.books_dto 2 | 3 | data class Result( 4 | val chapter_name: String, 5 | val chapter_slug: String, 6 | val novel_image: String, 7 | val novel_name: String, 8 | val novel_slug: String 9 | ) 10 | -------------------------------------------------------------------------------- /sources/en/mylovenovel/main/src/ireader/mylovenovel/merge.kt: -------------------------------------------------------------------------------- 1 | package ireader.mylovenovel 2 | 3 | fun merge(first: List, second: List): List { 4 | return object : ArrayList() { 5 | init { 6 | addAll(first) 7 | addAll(second) 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /sources/en/qidianundergrond/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("en").map { lang -> 2 | Extension( 3 | name = "QidianUndergrond", 4 | versionCode = 5, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /sources/en/wnmtl/main/src/ireader/Constants.kt: -------------------------------------------------------------------------------- 1 | package ireader 2 | 3 | const val _BOOK_URL = "https://novel4up.com/novel/soul-land-5/" 4 | const val _BOOK_NAME = "The Legend of the Dragon King" 5 | const val _CHAPTER_NAME = "Awakening Day" 6 | const val _CHAPTER_URL = "https://wnmtl.org/chapter/165721-awakening-day" 7 | -------------------------------------------------------------------------------- /sources/en/wuxiaworldsiteco/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("en").map { lang -> 2 | Extension( 3 | name = "WuxiaWorldSiteco", 4 | versionCode = 8, 5 | libVersion = "1.0", 6 | lang = lang, 7 | description = "", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /docs/source-api/src/jsMain/kotlin/ireader/core/http/HttpModule.js.kt: -------------------------------------------------------------------------------- 1 | package ireader.core.http 2 | 3 | /** 4 | * JavaScript HTTP module. 5 | * 6 | * In JS context, we don't use dependency injection modules like Koin. 7 | * This is a placeholder that returns Unit. 8 | */ 9 | actual val httpModule: Any = Unit 10 | -------------------------------------------------------------------------------- /sources/en/libread/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("en").map { lang -> 2 | Extension( 3 | name = "LibRead", 4 | versionCode = 2, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "Novel source based on libread.com", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /docs/source-api/src/commonMain/kotlin/ireader/core/source/model/DeepLink.kt: -------------------------------------------------------------------------------- 1 | 2 | 3 | package ireader.core.source.model 4 | 5 | sealed class DeepLink { 6 | abstract val key: String 7 | 8 | data class Manga(override val key: String) : DeepLink() 9 | data class Chapter(override val key: String) : DeepLink() 10 | } 11 | -------------------------------------------------------------------------------- /sources/en/dreambigtl/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("en").map { lang -> 2 | Extension( 3 | name = "DreamBigTL", 4 | versionCode = 2, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "Novel source based on dreambigtl.com", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /sources/en/qidianundergrond/main/src/ireader/qidianundergrond/QUGroupItem.kt: -------------------------------------------------------------------------------- 1 | package ireader.qidianundergrond 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class QUGroupItem( 7 | val ID: String, 8 | val LastUpdated: Int, 9 | val Name: String, 10 | val Status: String 11 | ) 12 | -------------------------------------------------------------------------------- /sources/en/lightnovels/main/src/ireader/lightnovels/content_dto/Novel.kt: -------------------------------------------------------------------------------- 1 | package ireader.lightnovels.content_dto 2 | 3 | data class Novel( 4 | val genre_name: String, 5 | val genre_slug: String, 6 | val novel_id: Int, 7 | val novel_image: String, 8 | val novel_name: String, 9 | val novel_slug: String 10 | ) 11 | -------------------------------------------------------------------------------- /sources/en/novelonline/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("en").map { lang -> 2 | Extension( 3 | name = "NovelOnline", 4 | versionCode = 2, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "Novel source based on novelsonline.net", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /sources/en/pandanovel/main/src/ireader/pandanovel/chapter/Data.kt: -------------------------------------------------------------------------------- 1 | package ireader.pandanovel.chapter 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class Data( 7 | val selectList: List, 8 | val pages: Int, 9 | val list: List, 10 | val selectNumList: List 11 | ) 12 | -------------------------------------------------------------------------------- /sources/multisrc/skynovel/skynovel/src/ireader/skynovel/SkyNovel.kt: -------------------------------------------------------------------------------- 1 | package ireader.skynovel 2 | 3 | import ireader.core.source.Dependencies 4 | import ireader.skynovelmodel.SkyNovelModel 5 | import tachiyomix.annotations.Extension 6 | 7 | 8 | @Extension 9 | abstract class SkyNovel(val deps: Dependencies) : SkyNovelModel(deps,) 10 | -------------------------------------------------------------------------------- /docs/source-api/src/commonMain/kotlin/ireader/core/source/Dependencies.kt: -------------------------------------------------------------------------------- 1 | 2 | 3 | package ireader.core.source 4 | 5 | import ireader.core.http.HttpClientsInterface 6 | import ireader.core.prefs.PreferenceStore 7 | 8 | class Dependencies( 9 | val httpClients: HttpClientsInterface, 10 | val preferences: PreferenceStore 11 | ) 12 | -------------------------------------------------------------------------------- /sources/en/storyseedling/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("en").map { lang -> 2 | Extension( 3 | name = "StorySeedling", 4 | versionCode = 2, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "Novel source based on storyseedling.com", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /docs/source-api/src/desktopMain/kotlin/ireader/core/http/HttpModule.kt: -------------------------------------------------------------------------------- 1 | package ireader.core.http 2 | 3 | import ireader.core.prefs.PreferenceStore 4 | 5 | /** 6 | * Desktop HTTP module providing network components 7 | * Note: This is a placeholder. HttpClients should be created in DomainModule 8 | */ 9 | actual val httpModule: Any = Unit 10 | -------------------------------------------------------------------------------- /sources/en/bestlightnovel/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("en").map { lang -> 2 | Extension( 3 | name = "BestLightNovel", 4 | versionCode = 2, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "Novel source based on bestlightnovel.com", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /docs/source-api/src/commonMain/kotlin/ireader/core/source/DeepLinkSource.kt: -------------------------------------------------------------------------------- 1 | 2 | 3 | package ireader.core.source 4 | 5 | import ireader.core.source.model.DeepLink 6 | 7 | interface DeepLinkSource : ireader.core.source.Source { 8 | 9 | fun handleLink(url: String): DeepLink? 10 | 11 | fun findMangaKey(chapterKey: String): String? 12 | } 13 | -------------------------------------------------------------------------------- /sources/multisrc/mtlnovel/main/src/ireader/mtlnovelmodel/Result.kt: -------------------------------------------------------------------------------- 1 | package ireader.mtlnovelmodel 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class Result( 7 | val cn: String, 8 | val permalink: String, 9 | val shortname: String, 10 | val thumbnail: String, 11 | val title: String, 12 | ) 13 | -------------------------------------------------------------------------------- /annotations/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("multiplatform") 3 | } 4 | 5 | kotlin { 6 | jvm() 7 | 8 | js(IR) { 9 | browser() 10 | nodejs() 11 | } 12 | 13 | sourceSets { 14 | commonMain.dependencies { 15 | // No dependencies needed for annotations 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /sources/tu/novelgecesi/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("tu").map { lang -> 2 | Extension( 3 | name = "NovelGecesi", 4 | versionCode = 1, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "Türkçe novel okuma sitesi", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /sources/en/lightnovels/main/src/ireader/lightnovels/content_dto/ContentDTO.kt: -------------------------------------------------------------------------------- 1 | package ireader.lightnovels.content_dto 2 | 3 | data class ContentDTO( 4 | val buildId: String, 5 | val gssp: Boolean, 6 | val isFallback: Boolean, 7 | val page: String, 8 | val props: Props, 9 | val query: Query, 10 | val runtimeConfig: RuntimeConfig 11 | ) 12 | -------------------------------------------------------------------------------- /docs/example-annotated/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("en").map { lang -> 2 | Extension( 3 | name = "ExampleSource", 4 | versionCode = 1, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "Example source demonstrating KSP annotations", 8 | nsfw = false, 9 | icon = DEFAULT_ICON 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /sources/en/wuxiaclick/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("en").map { lang -> 2 | Extension( 3 | name = "WuxiaClick", 4 | versionCode = 1, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "Read Wuxia, Light and Korean Novels at WuxiaClick", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /sources/multisrc/madara/freenovel/src/ireader/freenovel/Freenovel.kt: -------------------------------------------------------------------------------- 1 | package ireader.freenovel 2 | 3 | import tachiyomix.annotations.MadaraSource 4 | 5 | /** 6 | * 📖 FreeNovel - Zero-code Madara source 7 | */ 8 | @MadaraSource( 9 | name = "FreeNovel", 10 | baseUrl = "https://freenovel.me", 11 | lang = "en", 12 | id = 59 13 | ) 14 | object FreeNovelConfig 15 | -------------------------------------------------------------------------------- /defaultRes/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /multisrc/src/main/java/ireader/utility/TestConstants.kt: -------------------------------------------------------------------------------- 1 | package ireader.utility 2 | 3 | import ireader.core.source.Dependencies 4 | import ireader.core.source.HttpSource 5 | 6 | interface TestConstants { 7 | val bookUrl:String 8 | val bookName:String 9 | val chapterUrl:String 10 | val chapterName:String 11 | fun getExtension(deps: Dependencies): HttpSource 12 | } -------------------------------------------------------------------------------- /sources/en/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("en").map { lang -> 2 | Extension( 3 | name = "ReadNovelFullPlugin", 4 | versionCode = 11, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "Read novels from ", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | assetsDir = "en//main/assets", 11 | ) 12 | }.also(::register) 13 | -------------------------------------------------------------------------------- /sources/multisrc/madara/meionovel/src/ireader/meionovel/MeioNovel.kt: -------------------------------------------------------------------------------- 1 | package ireader.meionovel 2 | 3 | import tachiyomix.annotations.MadaraSource 4 | 5 | /** 6 | * 📖 MeioNovel - Zero-code Indonesian Madara source 7 | */ 8 | @MadaraSource( 9 | name = "MeioNovel", 10 | baseUrl = "https://meionovel.id", 11 | lang = "in", 12 | id = 64 13 | ) 14 | object MeioNovelConfig 15 | -------------------------------------------------------------------------------- /sources/en/mtlnation/main/src/ireader/mtlnation/Data.kt: -------------------------------------------------------------------------------- 1 | package ireader.mtlnation 2 | 3 | data class Data( 4 | val content: String, 5 | val created_at: String, 6 | val id: Int, 7 | val index: Int, 8 | val novel: String?, 9 | val novel_id: Int, 10 | val slug: String, 11 | val title: String, 12 | val updated_at: String, 13 | val word_count: Int 14 | ) 15 | -------------------------------------------------------------------------------- /.github/workflows/cancel_pull_request.yml: -------------------------------------------------------------------------------- 1 | name: Cancel old pull request workflows 2 | 3 | on: 4 | workflow_run: 5 | workflows: ["PR build check"] 6 | types: 7 | - requested 8 | 9 | jobs: 10 | cancel: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: styfle/cancel-workflow-action@0.9.1 14 | with: 15 | workflow_id: ${{ github.event.workflow.id }} 16 | -------------------------------------------------------------------------------- /sources/en/fenrir/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("en").map { lang -> 2 | Extension( 3 | name = "Fenrir", 4 | versionCode = 11, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "Read novels from Fenrir Realm", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | assetsDir = "en/fenrir/main/assets", 11 | ) 12 | }.also(::register) 13 | -------------------------------------------------------------------------------- /sources/en/lightnovels/main/src/ireader/lightnovels/detail_dto/NovelDetail.kt: -------------------------------------------------------------------------------- 1 | package ireader.lightnovels.detail_dto 2 | 3 | data class NovelDetail( 4 | val buildId: String, 5 | val gsp: Boolean, 6 | val isFallback: Boolean, 7 | val nextExport: Boolean, 8 | val page: String, 9 | val props: Props, 10 | val query: Query, 11 | val runtimeConfig: RuntimeConfig 12 | ) 13 | -------------------------------------------------------------------------------- /sources/en/novelfire/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("en").map { lang -> 2 | Extension( 3 | name = "Novelfire", 4 | versionCode = 11, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "Read novels from Novel Fire", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | assetsDir = "en/novelfire/main/assets", 11 | ) 12 | }.also(::register) 13 | -------------------------------------------------------------------------------- /sources/en/mydramanovel/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("en").map { lang -> 2 | Extension( 3 | name = "MyDramaNovel", 4 | versionCode = 2, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "Translating Tomorrow's TV Drama Hits, Straight from the Pages of Chinese Novels", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | ) 11 | }.also(::register) 12 | -------------------------------------------------------------------------------- /sources/en/novelbuddy/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("en").map { lang -> 2 | Extension( 3 | name = "Novelbuddy", 4 | versionCode = 11, 5 | libVersion = "2", 6 | lang = lang, 7 | description = "Read novels from NovelBuddy.io", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | assetsDir = "en/novelbuddy/main/assets", 11 | ) 12 | }.also(::register) 13 | -------------------------------------------------------------------------------- /sources/multisrc/madara/webnovelover/src/ireader/webnovelover/WebNovelLover.kt: -------------------------------------------------------------------------------- 1 | package ireader.webnovelover 2 | 3 | import tachiyomix.annotations.MadaraSource 4 | 5 | /** 6 | * 💕 WebNovelLover - Zero-code Madara source 7 | */ 8 | @MadaraSource( 9 | name = "WebNovelLover", 10 | baseUrl = "https://www.webnovelover.com", 11 | lang = "en", 12 | id = 66 13 | ) 14 | object WebNovelLoverConfig 15 | -------------------------------------------------------------------------------- /sources/multisrc/madara/firstkissnovel/src/ireader/firstkissnovel/FirstKissNovel.kt: -------------------------------------------------------------------------------- 1 | package ireader.firstkissnovel 2 | 3 | import tachiyomix.annotations.MadaraSource 4 | 5 | /** 6 | * 💋 FirstKissNovel - Zero-code Madara source 7 | */ 8 | @MadaraSource( 9 | name = "FirstKissNovel", 10 | baseUrl = "https://1stkissnovel.love", 11 | lang = "en", 12 | id = 57 13 | ) 14 | object FirstKissNovelConfig 15 | -------------------------------------------------------------------------------- /sources/multisrc/madara/noveltranslate/src/ireader/noveltranslate/NovelTranslate.kt: -------------------------------------------------------------------------------- 1 | package ireader.noveltranslate 2 | 3 | import tachiyomix.annotations.MadaraSource 4 | 5 | /** 6 | * 📝 NovelTranslate - Zero-code Madara source 7 | */ 8 | @MadaraSource( 9 | name = "NovelTranslate", 10 | baseUrl = "https://noveltranslate.com", 11 | lang = "en", 12 | id = 55 13 | ) 14 | object NovelTranslateConfig 15 | -------------------------------------------------------------------------------- /sources/multisrc/readwn/novelmt/src/ireader/novelmt/Readwn.kt: -------------------------------------------------------------------------------- 1 | package ireader.readwn 2 | 3 | 4 | import ireader.core.source.Dependencies 5 | import tachiyomix.annotations.Extension 6 | 7 | @Extension 8 | abstract class Readwn(val deps: Dependencies) : ReaderWnScraper( 9 | deps, 10 | key = "https://www.novelmt.com", 11 | sourceName = "Novelmt", 12 | sourceId = 61, 13 | language = "en", 14 | ) 15 | -------------------------------------------------------------------------------- /sources/multisrc/readwn/readwn/src/ireader/readwn/Readwn.kt: -------------------------------------------------------------------------------- 1 | package ireader.readwn 2 | 3 | 4 | import ireader.core.source.Dependencies 5 | import tachiyomix.annotations.Extension 6 | 7 | @Extension 8 | abstract class Readwn(val deps: Dependencies) : ReaderWnScraper( 9 | deps, 10 | key = "https://www.readwn.com", 11 | sourceName = "Readwn.com", 12 | sourceId = 60, 13 | language = "en", 14 | ) 15 | -------------------------------------------------------------------------------- /sources/multisrc/madara/armtl/src/ireader/armtl/ArMtl.kt: -------------------------------------------------------------------------------- 1 | package ireader.armtl 2 | 3 | import ireader.madara.Madara 4 | import ireader.core.source.Dependencies 5 | 6 | import tachiyomix.annotations.Extension 7 | 8 | @Extension 9 | abstract class ArMtl(val deps: Dependencies) : Madara( 10 | deps, 11 | key = "https://ar-mtl.club", 12 | sourceName = "ArMtl", 13 | sourceId = 46, 14 | language = "ar" 15 | ) 16 | -------------------------------------------------------------------------------- /docs/source-api/src/commonMain/kotlin/ireader/core/util/TimeUtils.kt: -------------------------------------------------------------------------------- 1 | package ireader.core.util 2 | 3 | import kotlin.time.ExperimentalTime 4 | 5 | /** 6 | * Get current time in milliseconds (epoch). 7 | * This is a KMP-compatible replacement for System.currentTimeMillis() 8 | */ 9 | @OptIn(ExperimentalTime::class) 10 | fun currentTimeMillis(): Long { 11 | return kotlin.time.Clock.System.now().toEpochMilliseconds() 12 | } 13 | -------------------------------------------------------------------------------- /sources/multisrc/madara/novelmultiverse/src/ireader/novelmultiverse/NovelMultiverse.kt: -------------------------------------------------------------------------------- 1 | package ireader.novelmultiverse 2 | 3 | import tachiyomix.annotations.MadaraSource 4 | 5 | /** 6 | * 🌌 NovelMultiverse - Zero-code Madara source 7 | */ 8 | @MadaraSource( 9 | name = "NovelMultiverse", 10 | baseUrl = "https://www.novelmultiverse.com", 11 | lang = "en", 12 | id = 83 13 | ) 14 | object NovelMultiverseConfig 15 | -------------------------------------------------------------------------------- /sources/en/lightnovels/main/src/ireader/lightnovels/detail_dto/PageProps.kt: -------------------------------------------------------------------------------- 1 | package ireader.lightnovels.detail_dto 2 | 3 | data class PageProps( 4 | val authors: List, 5 | val cachedChapterResults: String, 6 | val cachedHotNovels: List, 7 | val cachedLatestChapters: List, 8 | val genres: List, 9 | val novelInfo: NovelInfo, 10 | val tags: List 11 | ) 12 | -------------------------------------------------------------------------------- /sources/multisrc/madara/morenovel/src/ireader/morenovel/MoreNovel.kt: -------------------------------------------------------------------------------- 1 | package ireader.morenovel 2 | 3 | import tachiyomix.annotations.MadaraSource 4 | 5 | /** 6 | * 📚 MoreNovel - Zero-code Madara source! 7 | * 8 | * Indonesian novel site using Madara theme. 9 | */ 10 | @MadaraSource( 11 | name = "MoreNovel", 12 | baseUrl = "https://morenovel.net", 13 | lang = "in", 14 | id = 73 15 | ) 16 | object MoreNovelConfig 17 | -------------------------------------------------------------------------------- /sources/multisrc/mtlnovel/mtlnoveles/src/ireader/mtlnoveles/MtlNovelEs.kt: -------------------------------------------------------------------------------- 1 | package ireader.mtlnoveles 2 | 3 | import ireader.core.source.Dependencies 4 | import ireader.mtlnovelmodel.MtlNovelModel 5 | import tachiyomix.annotations.Extension 6 | 7 | 8 | @Extension 9 | abstract class MtlNovelEs(val deps: Dependencies) : MtlNovelModel(deps,) { 10 | override val baseUrl: String 11 | get() = "https://es.mtlnovel.com" 12 | } 13 | -------------------------------------------------------------------------------- /docs/source-api/src/androidMain/kotlin/ireader/core/http/OkHttpExtension.kt: -------------------------------------------------------------------------------- 1 | package ireader.core.http 2 | 3 | import io.ktor.client.HttpClient 4 | import io.ktor.client.engine.okhttp.OkHttpEngine 5 | import okhttp3.OkHttpClient 6 | 7 | /** 8 | * Extension to get OkHttpClient from Ktor HttpClient on Android. 9 | */ 10 | val HttpClient.okhttp: OkHttpClient 11 | get() = (engine as? OkHttpEngine)?.config?.preconfigured ?: OkHttpClient() 12 | -------------------------------------------------------------------------------- /docs/source-api/src/desktopMain/kotlin/ireader/core/http/OkHttpExtension.kt: -------------------------------------------------------------------------------- 1 | package ireader.core.http 2 | 3 | import io.ktor.client.HttpClient 4 | import io.ktor.client.engine.okhttp.OkHttpEngine 5 | import okhttp3.OkHttpClient 6 | 7 | /** 8 | * Extension to get OkHttpClient from Ktor HttpClient on Desktop. 9 | */ 10 | val HttpClient.okhttp: OkHttpClient 11 | get() = (engine as? OkHttpEngine)?.config?.preconfigured ?: OkHttpClient() 12 | -------------------------------------------------------------------------------- /sources/en/lnmtl/main/src/ireader/lnmtl/volume/LNMTLVolumnResponseItem.kt: -------------------------------------------------------------------------------- 1 | package ireader.lnmtl.volume 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class LNMTLVolumnResponseItem( 7 | val created_at: String?, 8 | val id: Int, 9 | val is_fake: Int?, 10 | val novel_id: Int?, 11 | val number: Int?, 12 | val slug: String?, 13 | val title: String?, 14 | val updated_at: String? 15 | ) -------------------------------------------------------------------------------- /sources/multisrc/madara/hizomanga/src/ireader/hizomang/HizoManga.kt: -------------------------------------------------------------------------------- 1 | package ireader.hizomanga 2 | 3 | import ireader.madara.Madara 4 | import ireader.core.source.Dependencies 5 | 6 | import tachiyomix.annotations.Extension 7 | 8 | @Extension 9 | abstract class HizoManga(val deps: Dependencies) : Madara( 10 | deps, 11 | key = "https://hizomanga.net", 12 | sourceName = "HizoManga", 13 | sourceId = 46, 14 | language = "ar" 15 | ) 16 | -------------------------------------------------------------------------------- /sources/multisrc/madara/sonicmtl/src/ireader/sonicmtl/SonicMTL.kt: -------------------------------------------------------------------------------- 1 | package ireader.sonicmtl 2 | 3 | import tachiyomix.annotations.MadaraSource 4 | 5 | /** 6 | * 🔊 SonicMTL - Zero-code Madara source! 7 | * 8 | * Just define the annotation - KSP generates everything else. 9 | */ 10 | @MadaraSource( 11 | name = "SonicMTL", 12 | baseUrl = "https://www.sonicmtl.com", 13 | lang = "en", 14 | id = 77 15 | ) 16 | object SonicMTLConfig 17 | -------------------------------------------------------------------------------- /sources/multisrc/madara/clicknovel/src/ireader/clicknovel/ClickNovel.kt: -------------------------------------------------------------------------------- 1 | package ireader.clicknovel 2 | 3 | import tachiyomix.annotations.MadaraSource 4 | 5 | /** 6 | * 📖 ClickNovel - Zero-code Madara source! 7 | * 8 | * Just define the annotation - KSP generates everything else. 9 | */ 10 | @MadaraSource( 11 | name = "ClickNovel", 12 | baseUrl = "https://clicknovel.net", 13 | lang = "en", 14 | id = 68 15 | ) 16 | object ClickNovelConfig 17 | -------------------------------------------------------------------------------- /sources/multisrc/madara/cratenovel/src/ireader/cratenovel/CrateNovel.kt: -------------------------------------------------------------------------------- 1 | package ireader.cratenovel 2 | 3 | import tachiyomix.annotations.MadaraSource 4 | 5 | /** 6 | * 📦 CrateNovel - Zero-code Madara source! 7 | * 8 | * Just define the annotation - KSP generates everything else. 9 | */ 10 | @MadaraSource( 11 | name = "CrateNovel", 12 | baseUrl = "https://cratenovel.com", 13 | lang = "en", 14 | id = 67 15 | ) 16 | object CrateNovelConfig 17 | -------------------------------------------------------------------------------- /sources/multisrc/readwn/ltnovel/src/ireader/ltnovel/Ltnovel.kt: -------------------------------------------------------------------------------- 1 | package ireader.ltnovel 2 | 3 | 4 | import ireader.core.source.Dependencies 5 | import ireader.readwn.ReaderWnScraper 6 | import tachiyomix.annotations.Extension 7 | 8 | @Extension 9 | abstract class Ltnovel(val deps: Dependencies) : ReaderWnScraper( 10 | deps, 11 | key = "https://www.ltnovel.com", 12 | sourceName = "Ltnovel", 13 | sourceId = 62, 14 | language = "en", 15 | ) 16 | -------------------------------------------------------------------------------- /sources/en/lnmtl/main/src/ireader/lnmtl/chapters/LNMTLResponse.kt: -------------------------------------------------------------------------------- 1 | package ireader.lnmtl.chapters 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class LNMTLResponse( 7 | val current_page: Int?, 8 | val `data`: List, 9 | val from: Int?, 10 | val last_page: Int, 11 | val next_page_url: String?, 12 | val per_page: Int?, 13 | val prev_page_url: String?, 14 | val to: Int?, 15 | val total: Int? 16 | ) -------------------------------------------------------------------------------- /sources/multisrc/madara/novel4up/src/ireader/novel4up/Novel4Up.kt: -------------------------------------------------------------------------------- 1 | package ireader.novel4up 2 | 3 | import tachiyomix.annotations.MadaraSource 4 | 5 | /** 6 | * 📖 Novel4Up (Madara) - Zero-code Arabic Madara source! 7 | * 8 | * Just define the annotation - KSP generates everything else. 9 | */ 10 | @MadaraSource( 11 | name = "Novel4Up", 12 | baseUrl = "https://novel4up.com", 13 | lang = "ar", 14 | id = 74 15 | ) 16 | object Novel4UpMadaraConfig 17 | -------------------------------------------------------------------------------- /sources/multisrc/mtlnovel/mtlnovelen/src/ireader/mtlnovelen/MtlNovelEn.kt: -------------------------------------------------------------------------------- 1 | package ireader.mtlnovelen 2 | 3 | import ireader.core.source.Dependencies 4 | import ireader.mtlnovelmodel.MtlNovelModel 5 | import tachiyomix.annotations.Extension 6 | 7 | 8 | @Extension 9 | abstract class MtlNovelEn(val deps: Dependencies) : MtlNovelModel(deps,) { 10 | override val baseUrl: String 11 | get() = "https://www.mtlnovel.com" 12 | override val lang = "en" 13 | } 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | 9 | [*.{kt,kts}] 10 | indent_size = 4 11 | indent_style = space 12 | max_line_length = 120 13 | ij_kotlin_allow_trailing_comma = true 14 | ij_kotlin_allow_trailing_comma_on_call_site = true 15 | 16 | [*.{json,yml,yaml}] 17 | indent_size = 2 18 | indent_style = space 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | -------------------------------------------------------------------------------- /sources/multisrc/madara/mtlnovelclub/src/ireader/mtlnovelclub/MTLNovelClub.kt: -------------------------------------------------------------------------------- 1 | package ireader.mtlnovelclub 2 | 3 | import tachiyomix.annotations.MadaraSource 4 | 5 | /** 6 | * 📖 MTLNovelClub - Zero-code Madara source! 7 | * 8 | * Just define the annotation - KSP generates everything else. 9 | */ 10 | @MadaraSource( 11 | name = "MTLNovelClub", 12 | baseUrl = "https://mtlnovel.club", 13 | lang = "en", 14 | id = 78 15 | ) 16 | object MTLNovelClubConfig 17 | -------------------------------------------------------------------------------- /docs/source-api/src/androidMain/kotlin/ireader/core/util/createCoroutineScope.kt: -------------------------------------------------------------------------------- 1 | package ireader.core.util 2 | 3 | import kotlinx.coroutines.CoroutineDispatcher 4 | import kotlinx.coroutines.CoroutineScope 5 | import kotlinx.coroutines.Dispatchers 6 | import kotlin.coroutines.CoroutineContext 7 | 8 | actual fun createCoroutineScope(coroutineContext: CoroutineContext): CoroutineScope = CoroutineScope(coroutineContext) 9 | actual val DefaultDispatcher: CoroutineDispatcher = Dispatchers.Main -------------------------------------------------------------------------------- /sources/en/lightnovels/main/src/ireader/lightnovels/content_dto/CachedChapterInfo.kt: -------------------------------------------------------------------------------- 1 | package ireader.lightnovels.content_dto 2 | 3 | data class CachedChapterInfo( 4 | val chapter_id: Int, 5 | val chapter_index: Int, 6 | val chapter_name: String, 7 | val chapter_slug: String, 8 | val chapter_source_id: Int, 9 | val content: String, 10 | val novel: Novel, 11 | val novel_id: Int, 12 | val novel_source_id: Int, 13 | val site_id: Int 14 | ) 15 | -------------------------------------------------------------------------------- /sources/multisrc/madara/zinnovel/src/ireader/zinnovel/Zinnovel.kt: -------------------------------------------------------------------------------- 1 | package ireader.zinnovel 2 | 3 | import tachiyomix.annotations.MadaraSource 4 | 5 | /** 6 | * 📖 Zinnovel - Zero-code Madara source with custom paths 7 | */ 8 | @MadaraSource( 9 | name = "Zinnovel", 10 | baseUrl = "https://zinnovel.com", 11 | lang = "en", 12 | id = 54, 13 | novelsPath = "manga", 14 | novelPath = "manga", 15 | chapterPath = "novel" 16 | ) 17 | object ZinnovelConfig 18 | -------------------------------------------------------------------------------- /annotations/src/main/kotlin/Extension.kt: -------------------------------------------------------------------------------- 1 | package tachiyomix.annotations 2 | /* 3 | Copyright (C) 2018 The Tachiyomi Open Source Project 4 | 5 | This Source Code Form is subject to the terms of the Mozilla Public 6 | License, v. 2.0. If a copy of the MPL was not distributed with this 7 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | */ 9 | 10 | @Retention(AnnotationRetention.SOURCE) 11 | @Target(AnnotationTarget.CLASS) 12 | annotation class Extension 13 | -------------------------------------------------------------------------------- /sources/en/lightnovels/main/src/ireader/lightnovels/detail_dto/NovelInfo.kt: -------------------------------------------------------------------------------- 1 | package ireader.lightnovels.detail_dto 2 | 3 | data class NovelInfo( 4 | val created_at: String, 5 | val novel_alternatives: String, 6 | val novel_description: String, 7 | val novel_id: Int, 8 | val novel_image: String, 9 | val novel_name: String, 10 | val novel_score: Double, 11 | val novel_slug: String, 12 | val novel_status: String, 13 | val updated_at: String 14 | ) 15 | -------------------------------------------------------------------------------- /sources/multisrc/madara/readwebnovels/src/ireader/readwebnovels/ReadWebNovels.kt: -------------------------------------------------------------------------------- 1 | package ireader.readwebnovels 2 | 3 | import tachiyomix.annotations.MadaraSource 4 | 5 | /** 6 | * 📚 ReadWebNovels - Zero-code Madara source! 7 | * 8 | * Just define the annotation - KSP generates everything else. 9 | */ 10 | @MadaraSource( 11 | name = "ReadWebNovels", 12 | baseUrl = "https://readwebnovels.net", 13 | lang = "en", 14 | id = 69 15 | ) 16 | object ReadWebNovelsConfig 17 | -------------------------------------------------------------------------------- /sources/multisrc/madara/mysticalseries/src/ireader/mysticalseries/MysticalSeries.kt: -------------------------------------------------------------------------------- 1 | package ireader.mysticalseries 2 | 3 | import tachiyomix.annotations.MadaraSource 4 | 5 | /** 6 | * ✨ MysticalSeries - Zero-code Madara source! 7 | * 8 | * Just define the annotation - KSP generates everything else. 9 | */ 10 | @MadaraSource( 11 | name = "MysticalSeries", 12 | baseUrl = "https://mysticalseries.com", 13 | lang = "en", 14 | id = 71 15 | ) 16 | object MysticalSeriesConfig 17 | -------------------------------------------------------------------------------- /test-extensions/src/test/java/ireader/app/Cosntants.kt: -------------------------------------------------------------------------------- 1 | package ireader.app 2 | 3 | import ireader.constants.Constants 4 | import ireader.app.mockcomponents.FakeHttpClients 5 | import ireader.app.mockcomponents.FakePreferencesStore 6 | import ireader.core.source.Dependencies 7 | import ireader.core.source.HttpSource 8 | 9 | val dependencies = Dependencies( 10 | FakeHttpClients(), 11 | FakePreferencesStore() 12 | ) 13 | val extension: HttpSource = Constants.getExtension(deps = dependencies) 14 | -------------------------------------------------------------------------------- /sources/en/wnmtl/main/src/ireader/wnmtl/chapter/Result.kt: -------------------------------------------------------------------------------- 1 | package ireader.wnmtl.chapter 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class Result( 7 | val balance: Int, 8 | val bookId: Int, 9 | val chapterOrder: Int, 10 | val createTime: Long, 11 | val id: Int, 12 | val paid: Boolean, 13 | val paywallStatus: String, 14 | val status: String, 15 | val title: String, 16 | val updateTime: Long, 17 | val wordCounts: Int 18 | ) 19 | -------------------------------------------------------------------------------- /docs/example-autoid/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf("en").map { lang -> 2 | Extension( 3 | name = "ExampleAutoId", 4 | versionCode = 1, 5 | libVersion = "1", 6 | lang = lang, 7 | description = "Example source demonstrating @AutoSourceId usage", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | // Note: sourceId is auto-generated! No need to specify manually. 11 | // The ID comes from: hash("exampleautoid/en/1") 12 | ) 13 | }.also(::register) 14 | -------------------------------------------------------------------------------- /sources/en/fenrir/README.md: -------------------------------------------------------------------------------- 1 | # Fenrir Realm 2 | 3 | **Generated by Converter V5 AI** 4 | 5 | ## Information 6 | - **Source**: https://fenrirealm.com 7 | - **Version**: 1.0.12 8 | - **Type**: SCRAPING 9 | - **Generator**: Full AI-Powered 10 | 11 | ## Status 12 | ✅ Generated with Gemini AI 13 | ✅ Complete implementation 14 | ⚠️ Needs testing 15 | 16 | ## Notes 17 | This extension was fully generated by AI based on the TypeScript source code. 18 | The AI analyzed the code and generated working Kotlin implementations. 19 | -------------------------------------------------------------------------------- /sources/en/lightnovelpub/webnovelpub/src/WebNovelPub.kt: -------------------------------------------------------------------------------- 1 | package ireader.lightnovelpub 2 | 3 | import ireader.core.source.Dependencies 4 | import tachiyomix.annotations.Extension 5 | 6 | @Extension 7 | abstract class WebNovelPub(private val deps: Dependencies) : LightNovelPub(deps) { 8 | 9 | override val name: String 10 | get() = "WebNovelPub" 11 | 12 | override val id: Long 13 | get() = 48 14 | 15 | override val baseUrl: String 16 | get() = "https://www.webnovelpub.com/" 17 | } 18 | -------------------------------------------------------------------------------- /sources/en/novelfire/README.md: -------------------------------------------------------------------------------- 1 | # Novel Fire 2 | 3 | **Generated by Converter V5 AI** 4 | 5 | ## Information 6 | - **Source**: https://novelfire.net/ 7 | - **Version**: 1.0.4 8 | - **Type**: SCRAPING 9 | - **Generator**: Full AI-Powered 10 | 11 | ## Status 12 | ✅ Generated with Gemini AI 13 | ✅ Complete implementation 14 | ⚠️ Needs testing 15 | 16 | ## Notes 17 | This extension was fully generated by AI based on the TypeScript source code. 18 | The AI analyzed the code and generated working Kotlin implementations. 19 | -------------------------------------------------------------------------------- /sources/multisrc/madara/lunarletters/src/ireader/lunarletters/LunarLetters.kt: -------------------------------------------------------------------------------- 1 | package ireader.lunarletters 2 | 3 | import tachiyomix.annotations.MadaraSource 4 | 5 | /** 6 | * 🌙 LunarLetters - Zero-code Madara source with custom paths 7 | */ 8 | @MadaraSource( 9 | name = "LunarLetters", 10 | baseUrl = "https://www.lunarletters.com", 11 | lang = "en", 12 | id = 81, 13 | novelsPath = "series", 14 | novelPath = "series", 15 | chapterPath = "series" 16 | ) 17 | object LunarLettersConfig 18 | -------------------------------------------------------------------------------- /sources/en/novelbuddy/README.md: -------------------------------------------------------------------------------- 1 | # NovelBuddy.io 2 | 3 | **Generated by Converter V5 AI** 4 | 5 | ## Information 6 | - **Source**: https://novelbuddy.io/ 7 | - **Version**: 1.0.3 8 | - **Type**: SCRAPING 9 | - **Generator**: Full AI-Powered 10 | 11 | ## Status 12 | ✅ Generated with Gemini AI 13 | ✅ Complete implementation 14 | ⚠️ Needs testing 15 | 16 | ## Notes 17 | This extension was fully generated by AI based on the TypeScript source code. 18 | The AI analyzed the code and generated working Kotlin implementations. 19 | -------------------------------------------------------------------------------- /sources/en/ranobes/main/src/ireader/ranobes/ChapterDTO.kt: -------------------------------------------------------------------------------- 1 | package ireader.ranobes 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | 6 | @Serializable 7 | data class ChapterDTO( 8 | val book_id: Int, 9 | val book_link: String, 10 | val book_title: String, 11 | val chapters: List, 12 | val count_all: Int, 13 | val cstart: Int, 14 | val default: List, 15 | val limit: Int, 16 | val pages_count: Int, 17 | val search: String, 18 | val searchTimeout: Long 19 | ) 20 | -------------------------------------------------------------------------------- /sources/multisrc/madara/neosekaitranslations/src/ireader/neosekaitranslations/Constants.kt: -------------------------------------------------------------------------------- 1 | package neosekaitranslations 2 | 3 | const val _BOOK_URL = "https://www.neosekaitranslations.com/novel/i-quit-the-going-home-club-for-a-girl-with-a-venomous-tongue/" 4 | const val _BOOK_NAME = "I Can Level Up By Staying Idle" 5 | const val _CHAPTER_NAME = "Chapter 886 Ancestor Shen Yin, Temporarily Dead" 6 | const val _CHAPTER_URL = "https://www.neosekaitranslations.com/novel/i-quit-the-going-home-club-for-a-girl-with-a-venomous-tongue/chapter-115/" 7 | -------------------------------------------------------------------------------- /sources/multisrc/madara/neosekaitranslations/src/ireader/neosekaitranslations/Neosekaitranslations.kt: -------------------------------------------------------------------------------- 1 | package ireader.neosekaitranslations 2 | 3 | 4 | import ireader.madara.Madara 5 | import ireader.core.source.Dependencies 6 | import tachiyomix.annotations.Extension 7 | 8 | @Extension 9 | abstract class Neosekaitranslations(val deps: Dependencies) : Madara( 10 | deps, 11 | key = "https://www.neosekaitranslations.com", 12 | sourceName = "Neosekaitranslations", 13 | sourceId = 45, 14 | language = "en" 15 | ) 16 | -------------------------------------------------------------------------------- /docs/source-api/src/iosMain/kotlin/ireader/core/util/CoroutineExt.ios.kt: -------------------------------------------------------------------------------- 1 | package ireader.core.util 2 | 3 | import kotlinx.coroutines.CoroutineDispatcher 4 | import kotlinx.coroutines.CoroutineScope 5 | import kotlinx.coroutines.Dispatchers 6 | import kotlinx.coroutines.IO 7 | import kotlin.coroutines.CoroutineContext 8 | 9 | actual fun createCoroutineScope(coroutineContext: CoroutineContext): CoroutineScope { 10 | return CoroutineScope(coroutineContext) 11 | } 12 | 13 | actual val DefaultDispatcher: CoroutineDispatcher = Dispatchers.IO 14 | -------------------------------------------------------------------------------- /sources/multisrc/madara/lightnovelheaven/src/ireader/lightnovelheaven/LightNovelHeaven.kt: -------------------------------------------------------------------------------- 1 | package ireader.lightnovelheaven 2 | 3 | import tachiyomix.annotations.MadaraSource 4 | 5 | /** 6 | * 🌟 LightNovelHeaven - Zero-code Madara source with custom paths 7 | */ 8 | @MadaraSource( 9 | name = "LightNovelHeaven", 10 | baseUrl = "https://lightnovelheaven.com", 11 | lang = "en", 12 | id = 63, 13 | novelsPath = "series", 14 | novelPath = "series", 15 | chapterPath = "series" 16 | ) 17 | object LightNovelHeavenConfig 18 | -------------------------------------------------------------------------------- /docs/source-api/src/desktopMain/kotlin/ireader/core/util/createCoroutineScope.kt: -------------------------------------------------------------------------------- 1 | package ireader.core.util 2 | 3 | import kotlinx.coroutines.CoroutineDispatcher 4 | import kotlinx.coroutines.CoroutineScope 5 | import kotlinx.coroutines.Dispatchers 6 | import kotlinx.coroutines.SupervisorJob 7 | import kotlin.coroutines.CoroutineContext 8 | 9 | actual fun createCoroutineScope(coroutineContext: CoroutineContext): CoroutineScope = CoroutineScope(SupervisorJob() + DefaultDispatcher) 10 | actual val DefaultDispatcher: CoroutineDispatcher = Dispatchers.IO -------------------------------------------------------------------------------- /sources/multisrc/madara/sleepytranslations/src/ireader/sleepytranslations/SleepyTranslations.kt: -------------------------------------------------------------------------------- 1 | package ireader.sleepytranslations 2 | 3 | import tachiyomix.annotations.MadaraSource 4 | 5 | /** 6 | * 😴 SleepyTranslations - Zero-code Madara source with custom paths 7 | */ 8 | @MadaraSource( 9 | name = "SleepyTranslations", 10 | baseUrl = "https://sleepytranslations.com", 11 | lang = "en", 12 | id = 56, 13 | novelsPath = "series", 14 | novelPath = "series", 15 | chapterPath = "series" 16 | ) 17 | object SleepyTranslationsConfig 18 | -------------------------------------------------------------------------------- /sources/en/lightnovels/main/src/ireader/lightnovels/search_dto/Result.kt: -------------------------------------------------------------------------------- 1 | package ireader.lightnovels.search_dto 2 | 3 | data class Result( 4 | val chapter_name: String, 5 | val chapter_slug: String, 6 | val chapter_updated_at: String, 7 | val id: Int, 8 | val latest_chapter_id: Int, 9 | val novel_alternatives: String, 10 | val novel_image: String, 11 | val novel_name: String, 12 | val novel_slug: String, 13 | val novel_updated_at: String, 14 | val score: Int, 15 | val site_id: Int, 16 | val status: String 17 | ) 18 | -------------------------------------------------------------------------------- /sources/en/lightnovels/main/src/ireader/lightnovels/search_dto/ResultX.kt: -------------------------------------------------------------------------------- 1 | package ireader.lightnovels.search_dto 2 | 3 | data class ResultX( 4 | val chapter_name: String, 5 | val chapter_slug: String, 6 | val chapter_updated_at: String, 7 | val id: Int, 8 | val latest_chapter_id: Int, 9 | val novel_alternatives: String, 10 | val novel_image: Any, 11 | val novel_name: String, 12 | val novel_slug: String, 13 | val novel_updated_at: String, 14 | val score: Double, 15 | val site_id: Int, 16 | val status: String 17 | ) 18 | -------------------------------------------------------------------------------- /sources/en/lightnovelpub/build.gradle.kts: -------------------------------------------------------------------------------- 1 | register( 2 | Extension( 3 | name = "LightNovelPub", 4 | versionCode = 4, 5 | libVersion = "2", 6 | lang = "en", 7 | description = "", 8 | nsfw = false, 9 | icon = DEFAULT_ICON 10 | ), 11 | Extension( 12 | name = "WebNovelPub", 13 | versionCode = 2, 14 | libVersion = "2", 15 | lang = "en", 16 | description = "", 17 | nsfw = false, 18 | icon = DEFAULT_ICON, 19 | assetsDir = "en/lightnovelpub/webnovelpub/assets", 20 | sourceDir = "webnovelpub" 21 | ) 22 | ) 23 | 24 | -------------------------------------------------------------------------------- /docs/source-api/src/commonMain/kotlin/ireader/core/util/CoroutineExt.kt: -------------------------------------------------------------------------------- 1 | package ireader.core.util 2 | 3 | import kotlinx.coroutines.CoroutineDispatcher 4 | import kotlinx.coroutines.CoroutineScope 5 | import kotlinx.coroutines.SupervisorJob 6 | import kotlin.coroutines.CoroutineContext 7 | 8 | expect fun createCoroutineScope(coroutineContext: CoroutineContext) : CoroutineScope 9 | expect val DefaultDispatcher : CoroutineDispatcher 10 | fun createICoroutineScope(dispatcher: CoroutineContext = SupervisorJob() + DefaultDispatcher) : CoroutineScope = createCoroutineScope(dispatcher) -------------------------------------------------------------------------------- /sources/en/pandanovel/main/src/ireader/pandanovel/chapter/Info.kt: -------------------------------------------------------------------------------- 1 | package ireader.pandanovel.chapter 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class Info( 7 | val id: Int, 8 | val bookId: Int, 9 | val author: String, 10 | val name: String, 11 | val content: String, 12 | val status: Int, 13 | val goodNum: Int, 14 | val createdAt: String, 15 | val updatedAt: String, 16 | val chapterUrl: String, 17 | val currentChapterCount: String?, 18 | val isEncryption: Int, 19 | val isSort: Int 20 | ) 21 | -------------------------------------------------------------------------------- /docs/source-api/src/androidMain/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /compiler/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2018 The Tachiyomi Open Source Project 3 | 4 | This Source Code Form is subject to the terms of the Mozilla Public 5 | License, v. 2.0. If a copy of the MPL was not distributed with this 6 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | */ 8 | 9 | plugins { 10 | kotlin("jvm") 11 | kotlin("plugin.serialization") 12 | } 13 | 14 | dependencies { 15 | implementation(libs.kotlinpoet) 16 | implementation(libs.ksp.api) 17 | implementation(libs.serialization.json) 18 | 19 | implementation(project(":annotations")) 20 | } 21 | -------------------------------------------------------------------------------- /docs/source-api/src/commonMain/kotlin/ireader/core/http/NetworkConfig.kt: -------------------------------------------------------------------------------- 1 | package ireader.core.http 2 | 3 | /** 4 | * Centralized network configuration 5 | */ 6 | data class NetworkConfig( 7 | val connectTimeoutSeconds: Long = 30, 8 | val readTimeoutMinutes: Long = 5, 9 | val callTimeoutMinutes: Long = 5, 10 | val cacheSize: Long = 15L * 1024 * 1024, // 15MB 11 | val cacheDurationMs: Long = 5 * 60 * 1000, // 5 minutes 12 | val userAgent: String = DEFAULT_USER_AGENT, 13 | val enableCaching: Boolean = true, 14 | val enableCookies: Boolean = true, 15 | val enableCompression: Boolean = true 16 | ) 17 | -------------------------------------------------------------------------------- /docs/source-api/src/desktopMain/kotlin/ireader/core/http/CookieSynchronizer.kt: -------------------------------------------------------------------------------- 1 | package ireader.core.http 2 | 3 | /** 4 | * Desktop implementation of cookie synchronization 5 | * Desktop doesn't have WebView, so this is a no-op implementation 6 | */ 7 | actual class CookieSynchronizer { 8 | actual fun syncFromWebView(url: String) { 9 | // No-op: Desktop doesn't have WebView 10 | } 11 | 12 | actual fun syncToWebView(url: String) { 13 | // No-op: Desktop doesn't have WebView 14 | } 15 | 16 | actual fun clearAll() { 17 | // No-op: Desktop doesn't have WebView 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /deeplink/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2018 The Tachiyomi Open Source Project 3 | 4 | This Source Code Form is subject to the terms of the Mozilla Public 5 | License, v. 2.0. If a copy of the MPL was not distributed with this 6 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | */ 8 | 9 | 10 | plugins { 11 | id("com.android.library") 12 | kotlin("android") 13 | } 14 | 15 | android { 16 | compileSdk = Config.compileSdk 17 | namespace = "tachiyomix.deeplink" 18 | defaultConfig { 19 | minSdk = Config.minSdk 20 | } 21 | } 22 | 23 | dependencies { 24 | compileOnly(libs.stdlib) 25 | } 26 | -------------------------------------------------------------------------------- /sources/multisrc/madara/turkcelightnovels/src/ireader/turkcelightnovels/TurkceLightNovels.kt: -------------------------------------------------------------------------------- 1 | package ireader.turkcelightnovels 2 | 3 | import tachiyomix.annotations.MadaraSource 4 | 5 | /** 6 | * 🇹🇷 TurkceLightNovels - Zero-code Madara source! 7 | * 8 | * Turkish novel site using Madara theme with custom paths. 9 | */ 10 | @MadaraSource( 11 | name = "TurkceLightNovels", 12 | baseUrl = "https://turkcelightnovels.com", 13 | lang = "tu", 14 | id = 76, 15 | // Custom paths for this site 16 | novelsPath = "light-novel", 17 | novelPath = "light-novel", 18 | chapterPath = "light-novel" 19 | ) 20 | object TurkceLightNovelsConfig 21 | -------------------------------------------------------------------------------- /docs/source-api/src/jsMain/kotlin/ireader/core/util/CoroutineExt.js.kt: -------------------------------------------------------------------------------- 1 | package ireader.core.util 2 | 3 | import kotlinx.coroutines.CoroutineDispatcher 4 | import kotlinx.coroutines.CoroutineScope 5 | import kotlinx.coroutines.Dispatchers 6 | import kotlin.coroutines.CoroutineContext 7 | 8 | /** 9 | * JavaScript implementation of coroutine utilities. 10 | */ 11 | actual fun createCoroutineScope(coroutineContext: CoroutineContext): CoroutineScope { 12 | return CoroutineScope(coroutineContext) 13 | } 14 | 15 | /** 16 | * Default dispatcher for JS - uses Dispatchers.Default 17 | */ 18 | actual val DefaultDispatcher: CoroutineDispatcher = Dispatchers.Default 19 | -------------------------------------------------------------------------------- /docs/source-api/src/commonMain/kotlin/ireader/core/http/JSFactory.kt: -------------------------------------------------------------------------------- 1 | ///* 2 | // * Copyright (C) 2018 The Tachiyomi Open Source Project 3 | // * 4 | // * This Source Code Form is subject to the terms of the Mozilla Public 5 | // * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // * file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // */ 8 | // 9 | //package ireader.core.http 10 | // 11 | ///** NOT USED ANYMORE 12 | // * A factory for creating [JS] instances. 13 | // */ 14 | //@Suppress("NO_ACTUAL_FOR_EXPECT") 15 | //expect class JSFactory { 16 | // 17 | // /** 18 | // * Returns a new instance of [JS]. 19 | // */ 20 | // fun create(): JS 21 | // 22 | //} 23 | -------------------------------------------------------------------------------- /sources/en/wnmtl/main/src/ireader/wnmtl/content/Data.kt: -------------------------------------------------------------------------------- 1 | package ireader.wnmtl.content 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class Data( 7 | val balance: Int, 8 | val bookId: Int, 9 | val bookTitle: String, 10 | val chapterOrder: Int, 11 | val content: String, 12 | val createTime: Long, 13 | val id: Int, 14 | val nextId: Int, 15 | val nextTitle: String, 16 | val orgId: Long?, 17 | val paid: Boolean, 18 | val paywallStatus: String, 19 | val preId: Int, 20 | val preTitle: String, 21 | val status: String, 22 | val title: String, 23 | val updateTime: Long, 24 | val wordCounts: Int 25 | ) 26 | -------------------------------------------------------------------------------- /docs/source-api/src/commonMain/kotlin/ireader/core/http/RatelimitException.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 The Tachiyomi Open Source Project 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | */ 8 | 9 | package ireader.core.http 10 | 11 | @Suppress("unused") 12 | class RatelimitException : Exception { 13 | 14 | constructor() : super() 15 | 16 | constructor(message: String) : super(message) 17 | 18 | constructor(cause: Exception) : super(cause) 19 | 20 | constructor(message: String, cause: Exception) : super(message, cause) 21 | } 22 | -------------------------------------------------------------------------------- /sources/multisrc/skynovel/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf( 2 | Extension( 3 | name = "SkyNovel", 4 | versionCode = 6, 5 | libVersion = "2", 6 | lang = "en", 7 | description = "", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | assetsDir = "multisrc/skynovel/skynovel/assets", 11 | sourceDir = "skynovel", 12 | ), 13 | Extension( 14 | name = "WbNovel", 15 | versionCode = 2, 16 | libVersion = "2", 17 | lang = "in", 18 | description = "", 19 | nsfw = false, 20 | icon = DEFAULT_ICON, 21 | assetsDir = "multisrc/skynovel/wbnovel/assets", 22 | sourceDir = "wbnovel", 23 | ), 24 | ).also(::register) 25 | -------------------------------------------------------------------------------- /compiler/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider: -------------------------------------------------------------------------------- 1 | tachiyomix.compiler.SourceFactoryProcessorProvider 2 | tachiyomix.compiler.SourceBoilerplateProcessorProvider 3 | tachiyomix.compiler.SourceIdProcessorProvider 4 | tachiyomix.compiler.ThemeSourceProcessorProvider 5 | tachiyomix.compiler.ExtensionProcessorFactory 6 | tachiyomix.compiler.JsExtensionProcessorFactory 7 | tachiyomix.compiler.SourceIndexProcessorProvider 8 | tachiyomix.compiler.SelectorValidatorProcessorProvider 9 | tachiyomix.compiler.TestGeneratorProcessorProvider 10 | tachiyomix.compiler.HttpClientProcessorProvider 11 | tachiyomix.compiler.DeepLinkProcessorProvider 12 | tachiyomix.compiler.PackageAutoCorrectProcessorProvider 13 | -------------------------------------------------------------------------------- /.github/scripts/sign-apks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | TOOLS="$(ls -d ${ANDROID_HOME}/build-tools/* | tail -1)" 5 | 6 | shopt -s globstar nullglob extglob 7 | APKS=( **/*".apk" ) 8 | 9 | # Take base64 encoded key input and put it into a file 10 | STORE_PATH=$PWD/signingkey.jks 11 | rm -f $STORE_PATH && touch $STORE_PATH 12 | echo $1 | base64 -d > $STORE_PATH 13 | 14 | STORE_ALIAS=$2 15 | export KEY_STORE_PASSWORD=$3 16 | export KEY_PASSWORD=$4 17 | 18 | # Sign all of the APKs 19 | for APK in ${APKS[@]}; do 20 | ${TOOLS}/zipalign -c -v -p 4 $APK 21 | ${TOOLS}/apksigner sign --ks $STORE_PATH --ks-key-alias $STORE_ALIAS --ks-pass env:KEY_STORE_PASSWORD --key-pass env:KEY_PASSWORD $APK 22 | done 23 | 24 | rm $STORE_PATH 25 | unset KEY_STORE_PASSWORD 26 | unset KEY_PASSWORD -------------------------------------------------------------------------------- /docs/source-api/src/jvmMain/kotlin/ireader/core/http/JSFactory.kt: -------------------------------------------------------------------------------- 1 | ///* 2 | // * Copyright (C) 2018 The Tachiyomi Open Source Project 3 | // * 4 | // * This Source Code Form is subject to the terms of the Mozilla Public 5 | // * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // * file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // */ 8 | // 9 | //package ireader.core.http 10 | // 11 | //import app.cash.quickjs.QuickJs 12 | // 13 | // 14 | ///** 15 | // * A factory for creating instances of [JS]. 16 | // */ 17 | // 18 | //actual class JSFactory internal constructor() { 19 | // 20 | // /** 21 | // * Returns a new instance of [JS]. 22 | // */ 23 | // actual fun create(): JS { 24 | // return JS(QuickJs.create()) 25 | // } 26 | // 27 | //} 28 | -------------------------------------------------------------------------------- /test-extensions/src/test/java/ireader/app/mockcomponents/FakeHttpClients.kt: -------------------------------------------------------------------------------- 1 | package ireader.app.mockcomponents 2 | 3 | import io.ktor.client.HttpClient 4 | import io.ktor.client.engine.okhttp.OkHttp 5 | import io.ktor.client.plugins.BrowserUserAgent 6 | import ireader.core.http.BrowserEngine 7 | import ireader.core.http.HttpClientsInterface 8 | 9 | class FakeHttpClients : HttpClientsInterface { 10 | override val browser: BrowserEngine 11 | get() = throw Exception("This test need to be run on real app") 12 | override val default: HttpClient 13 | get() = HttpClient(OkHttp) { 14 | BrowserUserAgent() 15 | } 16 | override val cloudflareClient: HttpClient 17 | get() = HttpClient(OkHttp) { 18 | BrowserUserAgent() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /defaultRes/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2018 The Tachiyomi Open Source Project 3 | 4 | This Source Code Form is subject to the terms of the Mozilla Public 5 | License, v. 2.0. If a copy of the MPL was not distributed with this 6 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | */ 8 | 9 | 10 | plugins { 11 | id("com.android.library") 12 | kotlin("android") 13 | } 14 | 15 | android { 16 | namespace = "tachiyomix.defaultres" 17 | compileSdk = Config.compileSdk 18 | 19 | defaultConfig { 20 | minSdk = Config.minSdk 21 | } 22 | 23 | sourceSets { 24 | named("main") { 25 | manifest.srcFile("AndroidManifest.xml") 26 | res.setSrcDirs(listOf("res")) 27 | } 28 | } 29 | } 30 | 31 | dependencies { 32 | compileOnly(libs.stdlib) 33 | } 34 | -------------------------------------------------------------------------------- /sources/multisrc/madara/armtl/src/ireader/armtl/Constants.kt: -------------------------------------------------------------------------------- 1 | package ireader.armtl 2 | 3 | import ireader.core.source.Dependencies 4 | import ireader.core.source.HttpSource 5 | import ireader.utility.TestConstants 6 | 7 | object Constants : TestConstants { 8 | override val bookUrl: String 9 | get() = "https://ar-mtl.club/novel/astral-pet-store/" 10 | override val bookName: String 11 | get() = "Astral Pet Store" 12 | override val chapterUrl: String 13 | get() = "Astral Pet Store novel - Chapter 1193" 14 | override val chapterName: String 15 | get() = "https://ar-mtl.club/novel/astral-pet-store/chapter-1193/" 16 | 17 | override fun getExtension(deps: Dependencies): HttpSource { 18 | return object : ArMtl(deps) { 19 | 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /sources/multisrc/madara/hizomanga/src/ireader/hizomang/Constants.kt: -------------------------------------------------------------------------------- 1 | package ireader.hizomanga 2 | 3 | import ireader.core.source.Dependencies 4 | import ireader.core.source.HttpSource 5 | import ireader.utility.TestConstants 6 | 7 | object Constants : TestConstants { 8 | override val bookUrl: String 9 | get() = "https://hizomanga.net/serie/astral-pet-store/" 10 | override val bookName: String 11 | get() = "Astral Pet Store" 12 | override val chapterUrl: String 13 | get() = "Astral Pet Store novel - Chapter 1193" 14 | override val chapterName: String 15 | get() = "https://hizomanga.net/serie/astral-pet-store/chapter-1193/" 16 | 17 | override fun getExtension(deps: Dependencies): HttpSource { 18 | return object : HizoManga(deps) { 19 | 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /docs/source-api/src/commonMain/kotlin/ireader/core/http/SSLConfiguration.kt: -------------------------------------------------------------------------------- 1 | package ireader.core.http 2 | 3 | /** 4 | * SSL/TLS configuration for HTTP clients. 5 | * Platform-specific implementations handle the actual configuration. 6 | */ 7 | expect class SSLConfiguration() { 8 | /** 9 | * Enable certificate pinning for specific hosts 10 | */ 11 | fun enableCertificatePinning(pins: Map>) 12 | 13 | /** 14 | * Allow self-signed certificates (for development only) 15 | */ 16 | fun allowSelfSignedCertificates() 17 | 18 | /** 19 | * Set minimum TLS version 20 | */ 21 | fun setMinimumTlsVersion(version: TlsVersion) 22 | } 23 | 24 | /** 25 | * TLS version enum 26 | */ 27 | enum class TlsVersion { 28 | TLS_1_0, 29 | TLS_1_1, 30 | TLS_1_2, 31 | TLS_1_3 32 | } 33 | -------------------------------------------------------------------------------- /test-extensions/src/test/java/ireader/app/tests/ContentChecker.kt: -------------------------------------------------------------------------------- 1 | package ireader.app.tests 2 | 3 | import com.google.common.truth.Truth.assertThat 4 | import ireader.app.CHAPTER_NAME 5 | import ireader.app.CHAPTER_URL 6 | import ireader.app.extension 7 | import ireader.core.source.model.ChapterInfo 8 | import ireader.core.source.model.Page 9 | import org.junit.Before 10 | import org.junit.Test 11 | 12 | class ContentChecker { 13 | var page: List = emptyList() 14 | @Before 15 | fun setup() { 16 | kotlinx.coroutines.runBlocking { 17 | page = extension.getPageList(ChapterInfo(key = CHAPTER_URL, name = CHAPTER_NAME), emptyList()) 18 | print(page) 19 | } 20 | } 21 | 22 | @Test 23 | fun `check whether page list is empty `() { 24 | assertThat(page.isNotEmpty()).isTrue() 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test-extensions/src/main/java/ireader/constants/Constants.kt: -------------------------------------------------------------------------------- 1 | package ireader.constants 2 | 3 | import ireader.core.source.Dependencies 4 | import ireader.core.source.HttpSource 5 | import ireader.chrysanthemumgarden.Chrysanthemumgarden 6 | import ireader.utility.TestConstants 7 | 8 | object Constants : TestConstants { 9 | override val bookUrl: String 10 | get() = "https://chrysanthemumgarden.com/novel/test" 11 | override val bookName: String 12 | get() = "Test Novel" 13 | override val chapterUrl: String 14 | get() = "https://chrysanthemumgarden.com/novel/test/chapter-1" 15 | override val chapterName: String 16 | get() = "Chapter 1" 17 | 18 | override fun getExtension(deps: Dependencies): HttpSource { 19 | return object : Chrysanthemumgarden(deps) { 20 | // Test instance 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /sources/multisrc/skynovel/wbnovel/src/ireader/wbnovel/WbNovel.kt: -------------------------------------------------------------------------------- 1 | package ireader.wbnovel 2 | 3 | import ireader.core.source.Dependencies 4 | import ireader.skynovelmodel.SkyNovelModel 5 | import tachiyomix.annotations.Extension 6 | 7 | 8 | @Extension 9 | abstract class WbNovel(val deps: Dependencies) : SkyNovelModel(deps,) { 10 | 11 | override val id: Long 12 | get() = 51 13 | 14 | override val name: String 15 | get() = "WbNovel" 16 | override val lang: String 17 | get() = "in" 18 | 19 | override val baseUrl: String 20 | get() = "https://wbnovel.com" 21 | 22 | override val mainEndpoint: String 23 | get() = "all-novel" 24 | 25 | override val descriptionSelector: String 26 | get() = "summary__content p" 27 | 28 | override val contentSelector: String 29 | get() = ".reading-content p" 30 | } 31 | -------------------------------------------------------------------------------- /docs/source-api/gradle.properties: -------------------------------------------------------------------------------- 1 | GROUP=io.github.kazemcodes 2 | POM_ARTIFACT_ID=source-api 3 | VERSION_NAME=1.4.0 4 | POM_NAME=IReader Source API 5 | POM_DESCRIPTION=Source API for IReader with improved error handling, validation, and performance 6 | POM_INCEPTION_YEAR=22 7 | POM_URL=https://github.com/kazemcodes/IReader 8 | 9 | POM_LICENSE_NAME=The Apache Software License, Version 2.0 10 | POM_LICENSE_URL=https://www.apache.org/licenses/LICENSE-2.0.txt 11 | POM_LICENSE_DIST=repo 12 | 13 | POM_SCM_URL=https://github.com/kazemcodes/IReader 14 | POM_SCM_CONNECTION=scm:git:git:github.com:kazemcodes/IReader.git 15 | POM_SCM_DEV_CONNECTION=scm:git:github.com:kazemcodes/IReader.git 16 | 17 | POM_DEVELOPER_ID=kazemcodes 18 | POM_DEVELOPER_NAME=kazem.codes 19 | POM_DEVELOPER_URL=https://github.com/kazemcodes 20 | 21 | 22 | SONATYPE_HOST=S01 23 | RELEASE_SIGNING_ENABLED=false 24 | 25 | 26 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | 2 | org.gradle.jvmargs=-Xmx4096m -XX:+HeapDumpOnOutOfMemoryError -XX:+UseParallelGC 3 | 4 | org.gradle.parallel=true 5 | org.gradle.caching=true 6 | org.gradle.configureondemand=true 7 | 8 | kotlin.code.style=official 9 | kotlin.incremental=true 10 | kotlin.incremental.java=true 11 | 12 | android.useAndroidX=true 13 | android.enableJetifier=false 14 | 15 | android.defaults.buildfeatures.aidl=false 16 | android.defaults.buildfeatures.buildconfig=false 17 | android.defaults.buildfeatures.renderscript=false 18 | android.defaults.buildfeatures.shaders=false 19 | android.defaults.buildfeatures.resvalues=false 20 | 21 | org.gradle.unsafe.configuration-cache=true 22 | # Use this flag sparingly, in case some of the plugins are not fully compatible 23 | org.gradle.unsafe.configuration-cache-problems=warn 24 | 25 | # Kotlin compiler options 26 | kotlin.daemon.jvmargs=-Xmx2048m 27 | -------------------------------------------------------------------------------- /docs/source-api/src/desktopMain/kotlin/ireader/core/http/CookieStore.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 The Tachiyomi Open Source Project 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | */ 8 | 9 | package ireader.core.http 10 | 11 | /** 12 | * An interface for a persistent cookie store. 13 | */ 14 | interface CookieStore { 15 | 16 | /** 17 | * Returns a map of all the cookies stored by domain. 18 | */ 19 | fun load(): Map> 20 | 21 | /** 22 | * Updates the cookies stored for this [domain] with the provided by [cookies]. 23 | */ 24 | fun update(domain: String, cookies: Set) 25 | 26 | /** 27 | * Clears all the cookies saved in this store. 28 | */ 29 | fun clear() 30 | } 31 | -------------------------------------------------------------------------------- /docs/source-api/src/desktopMain/kotlin/ireader/core/http/BrowserEngine.kt: -------------------------------------------------------------------------------- 1 | package ireader.core.http 2 | 3 | /** 4 | * Desktop implementation of BrowserEngine 5 | * Currently a stub - could be enhanced with JavaFX WebView or JCEF 6 | */ 7 | actual class BrowserEngine actual constructor() : BrowserEngineInterface { 8 | actual override suspend fun fetch( 9 | url: String, 10 | selector: String?, 11 | headers: Headers, 12 | timeout: Long, 13 | userAgent: String 14 | ): BrowserResult { 15 | return BrowserResult( 16 | responseBody = "", 17 | cookies = emptyList(), 18 | statusCode = 501, 19 | error = "BrowserEngine not available on desktop platform. Consider using JavaFX WebView or JCEF for full browser capabilities." 20 | ) 21 | } 22 | 23 | actual override fun isAvailable(): Boolean = false 24 | } 25 | -------------------------------------------------------------------------------- /sources/multisrc/mtlnovel/mtlnovelfr/src/ireader/mtlnovelfr/MtlNovelFr.kt: -------------------------------------------------------------------------------- 1 | package ireader.mtlnovelfr 2 | 3 | import ireader.core.source.Dependencies 4 | import ireader.mtlnovelmodel.MtlNovelModel 5 | import tachiyomix.annotations.Extension 6 | import tachiyomix.annotations.AutoSourceId 7 | 8 | /** 9 | * 🇫🇷 MTLNovel French - Machine Translation Source 10 | * 11 | * French version of MTLNovel. 12 | * Uses @AutoSourceId for automatic ID generation. 13 | */ 14 | @Extension 15 | @AutoSourceId(seed = "MtlNovelFr") 16 | abstract class MtlNovelFr(val deps: Dependencies) : MtlNovelModel(deps) { 17 | 18 | // ═══════════════════════════════════════════════════════════════ 19 | // 📋 BASIC SOURCE INFO - Override base class 20 | // ═══════════════════════════════════════════════════════════════ 21 | override val baseUrl: String get() = "https://fr.mtlnovel.com" 22 | override val lang = "fr" 23 | } 24 | -------------------------------------------------------------------------------- /sources/multisrc/mtlnovel/mtlnovelin/src/ireader/mtlnovelin/MtlNovelIn.kt: -------------------------------------------------------------------------------- 1 | package ireader.mtlnovelin 2 | 3 | import ireader.core.source.Dependencies 4 | import ireader.mtlnovelmodel.MtlNovelModel 5 | import tachiyomix.annotations.Extension 6 | import tachiyomix.annotations.AutoSourceId 7 | 8 | /** 9 | * 🇮🇩 MTLNovel Indonesian - Machine Translation Source 10 | * 11 | * Indonesian version of MTLNovel. 12 | * Uses @AutoSourceId for automatic ID generation. 13 | */ 14 | @Extension 15 | @AutoSourceId(seed = "MtlNovelIn") 16 | abstract class MtlNovelIn(val deps: Dependencies) : MtlNovelModel(deps) { 17 | 18 | // ═══════════════════════════════════════════════════════════════ 19 | // 📋 BASIC SOURCE INFO - Override base class 20 | // ═══════════════════════════════════════════════════════════════ 21 | override val baseUrl: String get() = "https://id.mtlnovel.com" 22 | override val lang = "in" 23 | } 24 | -------------------------------------------------------------------------------- /common/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("multiplatform") 3 | kotlin("plugin.serialization") 4 | } 5 | 6 | kotlin { 7 | jvm() 8 | 9 | js(IR) { 10 | browser { 11 | webpackTask { 12 | mainOutputFileName = "ireader-common.js" 13 | } 14 | } 15 | nodejs() 16 | binaries.library() 17 | } 18 | 19 | sourceSets { 20 | commonMain.dependencies { 21 | // Use api for KMP dependencies that need to be available to consumers 22 | api(libs.ksoup) 23 | api(libs.ireader.core) 24 | api(libs.ktor.core) 25 | api(libs.kotlinx.datetime) 26 | api(libs.serialization.json) 27 | } 28 | 29 | jvmMain.dependencies { 30 | compileOnly(libs.ktor.cio) 31 | } 32 | 33 | jsMain.dependencies { 34 | api(libs.ktor.client.js) 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /sources/en/wnmtl/main/src/ireader/wnmtl/explore/Result.kt: -------------------------------------------------------------------------------- 1 | package ireader.wnmtl.explore 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class Result( 7 | val authorId: Int, 8 | val authorPseudonym: String, 9 | val coverImgUrl: String, 10 | val createTime: String, 11 | val gender: Int, 12 | val genre: Int, 13 | val genreName: String, 14 | val id: Int, 15 | val language: String, 16 | val lastUpdateChapterId: Int, 17 | val lastUpdateChapterOrder: Int, 18 | val lastUpdateChapterTitle: String, 19 | val lastUpdateTime: Long, 20 | val ratingCounts: Double, 21 | val readCounts: Int, 22 | val shareFlag: Boolean, 23 | val source: String, 24 | val sourceOrgId: Int, 25 | val status: Int, 26 | val synopsis: String, 27 | val tag: String, 28 | val title: String, 29 | val type: String, 30 | val updateTime: String, 31 | val version: Int, 32 | val wordCounts: Int 33 | ) 34 | -------------------------------------------------------------------------------- /test-extensions/src/androidTest/java/ireader/app/tests/ContentChecker.kt: -------------------------------------------------------------------------------- 1 | package ireader.app.tests 2 | 3 | import androidx.test.ext.junit.runners.AndroidJUnit4 4 | import com.google.common.truth.Truth.assertThat 5 | import ireader.app.CHAPTER_NAME 6 | import ireader.app.CHAPTER_URL 7 | import ireader.app.extension 8 | import ireader.core.source.model.ChapterInfo 9 | import ireader.core.source.model.Page 10 | import org.junit.Before 11 | import org.junit.Test 12 | import org.junit.runner.RunWith 13 | 14 | @RunWith(AndroidJUnit4::class) 15 | class ContentChecker { 16 | var page: List = emptyList() 17 | @Before 18 | fun setup() { 19 | kotlinx.coroutines.runBlocking { 20 | page = extension.getPageList(ChapterInfo(key = CHAPTER_URL, name = CHAPTER_NAME), emptyList()) 21 | print(page) 22 | } 23 | } 24 | 25 | @Test 26 | fun checkContentListIsNotEmpty() { 27 | assertThat(page.isNotEmpty()).isTrue() 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /docs/source-api/src/commonMain/kotlin/ireader/core/http/CookieSynchronizer.kt: -------------------------------------------------------------------------------- 1 | package ireader.core.http 2 | 3 | /** 4 | * Synchronizes cookies between WebView and HTTP client. 5 | * Platform-specific implementations handle the actual synchronization. 6 | */ 7 | expect class CookieSynchronizer { 8 | /** 9 | * Sync cookies from WebView to HTTP client storage 10 | * @param url The URL string to sync cookies for 11 | */ 12 | fun syncFromWebView(url: String) 13 | 14 | /** 15 | * Sync cookies from HTTP client to WebView 16 | * @param url The URL string to sync cookies for 17 | */ 18 | fun syncToWebView(url: String) 19 | 20 | /** 21 | * Clear all cookies 22 | */ 23 | fun clearAll() 24 | } 25 | 26 | /** 27 | * Common cookie synchronizer interface for platform implementations 28 | */ 29 | interface CookieSynchronizerInterface { 30 | fun syncFromWebView(url: String) 31 | fun syncToWebView(url: String) 32 | fun clearAll() 33 | } 34 | -------------------------------------------------------------------------------- /sources/multisrc/readwn/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf( 2 | Extension( 3 | name = "Readwn", 4 | versionCode = 2, 5 | libVersion = "2", 6 | lang = "en", 7 | description = "", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | assetsDir = "multisrc/madara/readwn/assets", 11 | sourceDir = "readwn", 12 | ), 13 | Extension( 14 | name = "Novelmt", 15 | versionCode = 2, 16 | libVersion = "2", 17 | lang = "en", 18 | description = "", 19 | nsfw = false, 20 | icon = DEFAULT_ICON, 21 | assetsDir = "multisrc/madara/novelmt/assets", 22 | sourceDir = "novelmt", 23 | ), 24 | Extension( 25 | name = "Ltnovel", 26 | versionCode = 2, 27 | libVersion = "2", 28 | lang = "en", 29 | description = "", 30 | nsfw = false, 31 | icon = DEFAULT_ICON, 32 | assetsDir = "multisrc/madara/ltnovel/assets", 33 | sourceDir = "ltnovel", 34 | ), 35 | ).also(::register) 36 | -------------------------------------------------------------------------------- /test-extensions/src/androidTest/java/ireader/app/Constants.kt: -------------------------------------------------------------------------------- 1 | package ireader.app 2 | 3 | import android.content.Context 4 | import androidx.test.core.app.ApplicationProvider 5 | import ireader.core.http.AcceptAllCookiesStorage 6 | import ireader.core.http.BrowserEngine 7 | import ireader.core.http.HttpClients 8 | import ireader.core.http.WebViewCookieJar 9 | import ireader.core.http.WebViewManger 10 | import ireader.core.prefs.AndroidPreferenceStore 11 | import ireader.core.source.CatalogSource 12 | import ireader.core.source.Dependencies 13 | import ireader.core.source.TestSource 14 | 15 | val context: Context = ApplicationProvider.getApplicationContext() 16 | val cookie = AcceptAllCookiesStorage() 17 | val cookieJar = WebViewCookieJar(cookie) 18 | val httpClients = HttpClients(context, BrowserEngine(WebViewManger(context), cookieJar), cookie, cookieJar) 19 | val androidPreferenceStore = AndroidPreferenceStore(context, "test-preferences") 20 | val dependencies = Dependencies(httpClients, androidPreferenceStore) 21 | 22 | val extension: CatalogSource = TestSource() 23 | -------------------------------------------------------------------------------- /.github/scripts/commit-repo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # Sync built APKs from master/repo/ to repov2 branch 5 | # Note: The workflow checks out repov2 branch into 'repo' folder 6 | # and master branch into 'master' folder 7 | # Exclude js/ folder to preserve JS sources deployed by build_js.yml workflow 8 | rsync -a --delete --exclude .git --exclude .gitignore --exclude js/ ../master/repo/ . 9 | 10 | # Ensure .gitignore in repo branch doesn't exclude APKs 11 | # Create/update .gitignore to allow all repo files 12 | cat > .gitignore << 'EOF' 13 | # Repo branch - allow all built files 14 | # Only ignore OS/editor files 15 | .DS_Store 16 | Thumbs.db 17 | *.swp 18 | *~ 19 | EOF 20 | 21 | git config --global user.email "github-actions[bot]@users.noreply.github.com" 22 | git config --global user.name "github-actions[bot]" 23 | 24 | # Force add all files including APKs (in case .gitignore was excluding them) 25 | git add -f . 26 | 27 | git status 28 | if [ -n "$(git status --porcelain)" ]; then 29 | git commit -m "Update extensions repo" 30 | git push 31 | else 32 | echo "No changes to commit" 33 | fi 34 | -------------------------------------------------------------------------------- /docs/example-madara/main/src/ireader/examplemadara/ExampleMadara.kt: -------------------------------------------------------------------------------- 1 | package ireader.examplemadara 2 | 3 | import tachiyomix.annotations.MadaraSource 4 | import tachiyomix.annotations.SourceMeta 5 | 6 | /** 7 | * Example Madara-based source using the @MadaraSource annotation. 8 | * 9 | * Instead of manually creating a class that extends Madara, 10 | * KSP generates the source class from this configuration. 11 | * 12 | * This reduces boilerplate from ~20 lines to just annotations! 13 | */ 14 | @MadaraSource( 15 | name = "ExampleMadara", 16 | baseUrl = "https://example-madara.com", 17 | lang = "en", 18 | id = 888888, 19 | novelsPath = "novel", 20 | novelPath = "novel", 21 | chapterPath = "novel" 22 | ) 23 | @SourceMeta( 24 | description = "Example Madara theme source", 25 | nsfw = false, 26 | tags = ["english", "madara", "example"] 27 | ) 28 | object ExampleMadaraConfig 29 | 30 | // The KSP processor will generate: 31 | // - ExampleMadaraGenerated class that extends Madara 32 | // - Proper constructor with Dependencies 33 | // - All required overrides (name, id, lang, baseUrl) 34 | -------------------------------------------------------------------------------- /docs/source-api/src/commonMain/kotlin/ireader/core/source/model/LocalNovelDetails.kt: -------------------------------------------------------------------------------- 1 | package ireader.core.source.model 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | /** 6 | * Metadata structure for details.json in local novel folders 7 | * Allows users to manually add rich metadata 8 | */ 9 | @Serializable 10 | data class LocalNovelDetails( 11 | val title: String? = null, 12 | val author: String? = null, 13 | val artist: String? = null, 14 | val description: String? = null, 15 | val genre: List? = null, 16 | val status: String? = null // "Ongoing", "Completed", "Hiatus", etc. 17 | ) { 18 | fun toStatus(): Long { 19 | return when (status?.lowercase()) { 20 | "ongoing" -> MangaInfo.ONGOING 21 | "completed" -> MangaInfo.COMPLETED 22 | "licensed" -> MangaInfo.LICENSED 23 | "finished", "publishing_finished" -> MangaInfo.PUBLISHING_FINISHED 24 | "cancelled" -> MangaInfo.CANCELLED 25 | "hiatus", "on_hiatus" -> MangaInfo.ON_HIATUS 26 | else -> MangaInfo.UNKNOWN 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /e1be5cc3-0144-44d0-8517-801c039c045f.json: -------------------------------------------------------------------------------- 1 | { 2 | "attributes": { 3 | "chapter-item": "innerHTML", 4 | "chapter-link": "href", 5 | "content": "innerHTML", 6 | "cover": "src", 7 | "explore-cover": "src", 8 | "explore-link": "href", 9 | "explore-title": "title", 10 | "genres": "innerHTML", 11 | "novel-item": "innerHTML" 12 | }, 13 | "lang": "en", 14 | "latestUrl": "/latest?page={{page}}", 15 | "name": "NovelBuddy", 16 | "popularUrl": "/top/month", 17 | "searchUrl": "/search?q={{query}}", 18 | "selectors": { 19 | "author": "span", 20 | "chapter-item": "#c-100", 21 | "chapter-link": "#c-100 a", 22 | "chapter-name": ".chapter-title", 23 | "content": "#chapter__content", 24 | "cover": "img", 25 | "description": ".content", 26 | "explore-cover": "img", 27 | "explore-link": "a", 28 | "explore-title": "a", 29 | "genres": "p", 30 | "novel-item": ".book-detailed-item", 31 | "status": "span", 32 | "title": "h1" 33 | }, 34 | "baseUrl": "https://novelbuddy.io", 35 | "exportedAt": "2025-11-28T21:41:09.132Z", 36 | "version": "1.0" 37 | } -------------------------------------------------------------------------------- /sources/en/lnmtl/main/src/ireader/lnmtl/chapters/Data.kt: -------------------------------------------------------------------------------- 1 | package ireader.lnmtl.chapters 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class Data( 7 | val comments_count: Int?, 8 | val created_at: String?, 9 | val downloaded: Boolean?, 10 | val hide_ads: Int?, 11 | val id: Int, 12 | val is_special: Int?, 13 | val length: Int?, 14 | val novel_id: Int?, 15 | val number: Int, 16 | val number_special: Int?, 17 | val original: Int?, 18 | val part: Int?, 19 | val position: Int?, 20 | val ratings_count: Int?, 21 | val ratings_negative: Int?, 22 | val ratings_neutral: Int?, 23 | val ratings_positive: Int?, 24 | val retranslated_at: String?, 25 | val retranslations_count: Int?, 26 | val site_url: String?, 27 | val slug: String?, 28 | val title: String, 29 | val title_json: String?, 30 | val title_raw: String?, 31 | val translated_body: Boolean?, 32 | val translated_title: Boolean?, 33 | val updated_at: String?, 34 | val updater_id: Int?, 35 | val url: String, 36 | val volume_id: Int 37 | ) -------------------------------------------------------------------------------- /docs/source-api/src/desktopMain/kotlin/ireader/core/http/WebViewManger.kt: -------------------------------------------------------------------------------- 1 | package ireader.core.http 2 | 3 | import com.fleeksoft.ksoup.nodes.Document 4 | 5 | /** 6 | * Desktop implementation of WebViewManager 7 | * Currently a stub - could be enhanced with JavaFX WebView or JCEF 8 | */ 9 | actual class WebViewManger { 10 | actual var isInit: Boolean = false 11 | actual var userAgent: String = DEFAULT_USER_AGENT 12 | actual var selector: String? = null 13 | actual var html: Document? = null 14 | actual var webUrl: String? = null 15 | actual var inProgress: Boolean = false 16 | 17 | actual fun init(): Any { 18 | return 0 19 | } 20 | 21 | actual fun update() { 22 | // No-op on desktop 23 | } 24 | 25 | actual fun destroy() { 26 | // No-op on desktop 27 | } 28 | 29 | actual fun loadInBackground(url: String, selector: String?, onReady: (String) -> Unit) { 30 | // Not supported on desktop 31 | onReady("") 32 | } 33 | 34 | actual fun isProcessingInBackground(): Boolean = false 35 | 36 | actual fun isAvailable(): Boolean = false 37 | } 38 | -------------------------------------------------------------------------------- /docs/source-api/src/commonMain/kotlin/ireader/core/http/HttpClients.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 The Tachiyomi Open Source Project 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | */ 8 | 9 | package ireader.core.http 10 | 11 | import io.ktor.client.* 12 | 13 | /** 14 | * Unified HTTP client interface providing different client configurations 15 | * for various use cases (default, Cloudflare bypass, browser engine) 16 | */ 17 | interface HttpClientsInterface { 18 | val browser: BrowserEngine 19 | val default: HttpClient 20 | val cloudflareClient: HttpClient 21 | val config: NetworkConfig 22 | val sslConfig: SSLConfiguration 23 | val cookieSynchronizer: CookieSynchronizer 24 | } 25 | 26 | expect class HttpClients : HttpClientsInterface { 27 | override val browser: BrowserEngine 28 | override val default: HttpClient 29 | override val cloudflareClient: HttpClient 30 | override val config: NetworkConfig 31 | override val sslConfig: SSLConfiguration 32 | override val cookieSynchronizer: CookieSynchronizer 33 | } 34 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 20 | -------------------------------------------------------------------------------- /sources/en/wuxiaworld/main/src/ireader/wuxiaworld/Item.kt: -------------------------------------------------------------------------------- 1 | package ireader.wuxiaworld 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class Item( 7 | val abbreviation: String, 8 | val active: Boolean, 9 | val authorName: String?, 10 | val chapterGroups: String?, 11 | val coverUrl: String, 12 | val description: String, 13 | val ebooks: List, 14 | val excludedFromVipSelection: Boolean, 15 | val genres: List, 16 | val id: Int, 17 | val isFree: Boolean, 18 | val karmaActive: Boolean, 19 | val language: String, 20 | val languageAbbreviation: String, 21 | val latestAnnouncement: Long?, 22 | val name: String, 23 | val novelHasSponsorPlans: Boolean, 24 | val reviewCount: Int, 25 | val reviewScore: Double?, 26 | val siteCreditsEnabled: Boolean?, 27 | val slug: String, 28 | val sponsorPlans: String?, 29 | val synopsis: String?, 30 | val tags: List?, 31 | val teaserMessage: String?, 32 | val translatorId: String?, 33 | val translatorName: String?, 34 | val translatorUserName: String?, 35 | val userHasEbook: Boolean?, 36 | val userHasNovelUnlocked: Boolean?, 37 | val visible: Boolean? 38 | ) 39 | -------------------------------------------------------------------------------- /.github/workflows/code_quality.yml: -------------------------------------------------------------------------------- 1 | name: Code Quality 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - '**.kt' 7 | - '**.kts' 8 | - 'detekt.yml' 9 | - '.editorconfig' 10 | 11 | jobs: 12 | detekt: 13 | name: Detekt Check 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout code 17 | uses: actions/checkout@v4 18 | 19 | - name: Set up JDK 20 | uses: actions/setup-java@v4 21 | with: 22 | java-version: 21 23 | distribution: adopt 24 | 25 | - name: Run detekt 26 | run: ./gradlew detekt 27 | continue-on-error: true 28 | 29 | - name: Upload detekt report 30 | uses: actions/upload-artifact@v4 31 | if: always() 32 | with: 33 | name: detekt-report 34 | path: build/reports/detekt/ 35 | 36 | ktlint: 37 | name: Ktlint Check 38 | runs-on: ubuntu-latest 39 | steps: 40 | - name: Checkout code 41 | uses: actions/checkout@v4 42 | 43 | - name: Set up JDK 44 | uses: actions/setup-java@v4 45 | with: 46 | java-version: 21 47 | distribution: adopt 48 | 49 | - name: Run ktlint 50 | run: ./gradlew ktlintCheck 51 | continue-on-error: true 52 | -------------------------------------------------------------------------------- /test-extensions/src/test/java/ireader/app/mockcomponents/FakePreferencesStore.kt: -------------------------------------------------------------------------------- 1 | package ireader.app.mockcomponents 2 | 3 | import ireader.core.prefs.Preference 4 | import ireader.core.prefs.PreferenceStore 5 | 6 | class FakePreferencesStore : PreferenceStore { 7 | override fun getString(key: String, defaultValue: String): Preference { 8 | throw Exception() 9 | } 10 | 11 | override fun getLong(key: String, defaultValue: Long): Preference { 12 | throw Exception() 13 | } 14 | 15 | override fun getInt(key: String, defaultValue: Int): Preference { 16 | throw Exception() 17 | } 18 | 19 | override fun getFloat(key: String, defaultValue: Float): Preference { 20 | throw Exception() 21 | } 22 | 23 | override fun getBoolean(key: String, defaultValue: Boolean): Preference { 24 | throw Exception() 25 | } 26 | 27 | override fun getStringSet(key: String, defaultValue: Set): Preference> { 28 | throw Exception() 29 | } 30 | 31 | override fun getObject( 32 | key: String, 33 | defaultValue: T, 34 | serializer: (T) -> String, 35 | deserializer: (String) -> T 36 | ): Preference { 37 | throw Exception() 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /test-extensions/src/test/java/ireader/app/tests/BookListChecker.kt: -------------------------------------------------------------------------------- 1 | package ireader.app.tests 2 | 3 | import com.google.common.truth.Truth 4 | import ireader.app.extension 5 | import ireader.core.source.model.Listing 6 | import ireader.core.source.model.MangasPageInfo 7 | import org.junit.Before 8 | import org.junit.Test 9 | 10 | class BookListChecker { 11 | lateinit var books: MangasPageInfo 12 | @Before 13 | fun setup() { 14 | kotlinx.coroutines.runBlocking { 15 | 16 | books = extension.getMangaList(LatestListing(), 1) 17 | print(books) 18 | } 19 | } 20 | 21 | @Test 22 | fun `check whether books list is empty `() { 23 | Truth.assertThat(books.mangas.isNotEmpty()).isTrue() 24 | } 25 | @Test 26 | fun `check whether book has title `() { 27 | Truth.assertThat(books.mangas.any { book -> book.title.isNotBlank() }).isTrue() 28 | } 29 | @Test 30 | fun `check whether book has key `() { 31 | Truth.assertThat(books.mangas.any { book -> book.key.isNotBlank() }).isTrue() 32 | } 33 | @Test 34 | fun `check whether book has cover `() { 35 | Truth.assertThat(books.mangas.any { book -> book.cover.isNotBlank() }).isTrue() 36 | } 37 | } 38 | 39 | class LatestListing() : Listing(name = "Latest") 40 | -------------------------------------------------------------------------------- /scripts/bump-version-codes.ps1: -------------------------------------------------------------------------------- 1 | # Bump versionCode by +1 in all build.gradle.kts files under sources/ 2 | # Usage: .\scripts\bump-version-codes.ps1 3 | 4 | param( 5 | [switch]$DryRun = $false, 6 | [string]$Path = "sources" 7 | ) 8 | 9 | $files = Get-ChildItem -Path $Path -Recurse -Filter "build.gradle.kts" 10 | $totalUpdates = 0 11 | 12 | foreach ($file in $files) { 13 | $content = Get-Content $file.FullName -Raw 14 | $updated = $false 15 | 16 | # Match versionCode = X, and increment X 17 | $newContent = [regex]::Replace($content, 'versionCode\s*=\s*(\d+)', { 18 | param($match) 19 | $oldVersion = [int]$match.Groups[1].Value 20 | $newVersion = $oldVersion + 1 21 | $script:updated = $true 22 | $script:totalUpdates++ 23 | Write-Host " $($file.Name): versionCode $oldVersion -> $newVersion" -ForegroundColor Cyan 24 | return "versionCode = $newVersion" 25 | }) 26 | 27 | if ($updated -and -not $DryRun) { 28 | Set-Content -Path $file.FullName -Value $newContent -NoNewline 29 | } 30 | } 31 | 32 | if ($DryRun) { 33 | Write-Host "`nDry run complete. $totalUpdates version codes would be updated." -ForegroundColor Yellow 34 | } else { 35 | Write-Host "`nDone! Updated $totalUpdates version codes." -ForegroundColor Green 36 | } 37 | -------------------------------------------------------------------------------- /deeplink/src/main/java/tachiyomix/deeplink/SourceDeepLinkActivity.java: -------------------------------------------------------------------------------- 1 | package tachiyomix.deeplink; 2 | 3 | import android.app.Activity; 4 | import android.content.ActivityNotFoundException; 5 | import android.content.Intent; 6 | import android.net.Uri; 7 | import android.os.Bundle; 8 | import android.util.Log; 9 | 10 | /* 11 | Copyright (C) 2018 The Tachiyomi Open Source Project 12 | 13 | This Source Code Form is subject to the terms of the Mozilla Public 14 | License, v. 2.0. If a copy of the MPL was not distributed with this 15 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 16 | */ 17 | 18 | public class SourceDeepLinkActivity extends Activity { 19 | 20 | private static final String TAG = "SourceDeepLinkActivity"; 21 | 22 | @Override 23 | protected void onCreate(Bundle savedInstanceState) { 24 | super.onCreate(savedInstanceState); 25 | 26 | Intent forward = new Intent(Intent.ACTION_VIEW); 27 | forward.setData(Uri.parse(String.format( 28 | "tachiyomi://deeplink/%s?url=%s", 29 | getPackageName(), 30 | getIntent().getData().toString() 31 | ))); 32 | 33 | try { 34 | startActivity(forward); 35 | } catch (ActivityNotFoundException e) { 36 | Log.e(TAG, e.toString()); 37 | } 38 | 39 | finish(); 40 | System.exit(0); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /test-extensions/src/androidTest/java/ireader/app/tests/BookListChecker.kt: -------------------------------------------------------------------------------- 1 | package ireader.app.tests 2 | 3 | import androidx.test.ext.junit.runners.AndroidJUnit4 4 | import com.google.common.truth.Truth 5 | import ireader.app.extension 6 | import ireader.core.source.model.Listing 7 | import ireader.core.source.model.MangasPageInfo 8 | import org.junit.Before 9 | import org.junit.Test 10 | import org.junit.runner.RunWith 11 | 12 | @RunWith(AndroidJUnit4::class) 13 | class BookListChecker { 14 | lateinit var books: MangasPageInfo 15 | @Before 16 | fun setup() { 17 | kotlinx.coroutines.runBlocking { 18 | books = extension.getMangaList(LatestListing(), 1) 19 | print(books) 20 | } 21 | } 22 | 23 | @Test 24 | fun checkBookListIsNotEmpty() { 25 | Truth.assertThat(books.mangas.isNotEmpty()).isTrue() 26 | } 27 | @Test 28 | fun checkBookHasTitle() { 29 | Truth.assertThat(books.mangas.any { book -> book.title.isNotBlank() }).isTrue() 30 | } 31 | @Test 32 | fun checkBookHasKey() { 33 | Truth.assertThat(books.mangas.any { book -> book.key.isNotBlank() }).isTrue() 34 | } 35 | @Test 36 | fun checkBookHasCover() { 37 | Truth.assertThat(books.mangas.any { book -> book.cover.isNotBlank() }).isTrue() 38 | } 39 | } 40 | 41 | class LatestListing() : Listing(name = "Latest") 42 | -------------------------------------------------------------------------------- /docs/source-api/src/androidMain/res/values/string.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | need webview to view this content 4 | webview is outdated. 5 | Can\'t bypass the cloudflare 6 | 7 | 8 | Fatal error for 9 | Error while (un)installing package 10 | PackageManger: Cancelled 11 | PackageManger: There is another apk installed with higher version than this package 12 | PackageManger: InSufficient Storage 13 | PackageManger: Incompatible with installed package 14 | PackageManger: Conflicted with installed package 15 | PackageManger: Installer was blocked 16 | 17 | Package installer failed to install packages: STATUS_CODE: 18 | 19 | Success 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /docs/source-api/src/jsMain/kotlin/ireader/core/http/SSLConfiguration.js.kt: -------------------------------------------------------------------------------- 1 | package ireader.core.http 2 | 3 | /** 4 | * JavaScript implementation of SSL/TLS configuration. 5 | * 6 | * In JavaScript/browser context, SSL is handled by the browser/runtime, 7 | * so this is mostly a no-op implementation. 8 | */ 9 | actual class SSLConfiguration actual constructor() { 10 | 11 | /** 12 | * Enable certificate pinning for specific hosts. 13 | * Not supported in JS - browser handles SSL. 14 | */ 15 | actual fun enableCertificatePinning(pins: Map>) { 16 | // No-op in JS - browser handles SSL 17 | console.log("SSLConfiguration: Certificate pinning not supported in JS") 18 | } 19 | 20 | /** 21 | * Allow self-signed certificates. 22 | * Not supported in JS - browser handles SSL. 23 | */ 24 | actual fun allowSelfSignedCertificates() { 25 | // No-op in JS - browser handles SSL 26 | console.log("SSLConfiguration: Self-signed certificates setting not supported in JS") 27 | } 28 | 29 | /** 30 | * Set minimum TLS version. 31 | * Not supported in JS - browser handles SSL. 32 | */ 33 | actual fun setMinimumTlsVersion(version: TlsVersion) { 34 | // No-op in JS - browser handles SSL 35 | console.log("SSLConfiguration: TLS version setting not supported in JS") 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /sources/multisrc/mtlnovel/build.gradle.kts: -------------------------------------------------------------------------------- 1 | listOf( 2 | Extension( 3 | name = "MtlNovelEn", 4 | versionCode = 2, 5 | libVersion = "2", 6 | lang = "en", 7 | description = "", 8 | nsfw = false, 9 | icon = DEFAULT_ICON, 10 | assetsDir = "multisrc/mtlnovel/mtlnovelen/assets", 11 | sourceDir = "mtlnovelen", 12 | ), 13 | Extension( 14 | name = "MtlNovelFr", 15 | versionCode = 2, 16 | libVersion = "2", 17 | lang = "fr", 18 | description = "", 19 | nsfw = false, 20 | icon = DEFAULT_ICON, 21 | assetsDir = "multisrc/mtlnovel/mtlnovelen/assets", 22 | sourceDir = "mtlnovelfr", 23 | ), 24 | Extension( 25 | name = "MtlNovelEs", 26 | versionCode = 2, 27 | libVersion = "2", 28 | lang = "es", 29 | description = "", 30 | nsfw = false, 31 | icon = DEFAULT_ICON, 32 | assetsDir = "multisrc/mtlnovel/mtlnovelen/assets", 33 | sourceDir = "mtlnoveles", 34 | ), 35 | Extension( 36 | name = "MtlNovelIn", 37 | versionCode = 2, 38 | libVersion = "2", 39 | lang = "in", 40 | description = "", 41 | nsfw = false, 42 | icon = DEFAULT_ICON, 43 | assetsDir = "multisrc/mtlnovel/mtlnovelen/assets", 44 | sourceDir = "mtlnovelin", 45 | ), 46 | ).also(::register) 47 | -------------------------------------------------------------------------------- /test-extensions/src/test/java/ireader/app/tests/InfoChecked.kt: -------------------------------------------------------------------------------- 1 | package ireader.app.tests 2 | 3 | import com.google.common.truth.Truth.assertThat 4 | import ireader.app.BOOK_NAME 5 | import ireader.app.BOOK_URL 6 | import ireader.app.extension 7 | import ireader.core.source.model.MangaInfo 8 | import org.junit.Before 9 | import org.junit.Test 10 | 11 | class InfoChecked { 12 | 13 | var book: MangaInfo = MangaInfo(key = "", title = "") 14 | 15 | @Before 16 | fun setup() { 17 | kotlinx.coroutines.runBlocking { 18 | book = extension.getMangaDetails(MangaInfo(key = BOOK_URL, title = BOOK_NAME), emptyList()) 19 | print(book) 20 | } 21 | } 22 | 23 | @Test 24 | fun `check book name`() { 25 | assertThat(book.title.isNotBlank()).isTrue() 26 | } 27 | @Test 28 | fun `check book author`() { 29 | assertThat(book.author.isNotBlank()).isTrue() 30 | } 31 | 32 | @Test 33 | fun `check book cover`() { 34 | assertThat(book.cover.isNotBlank()).isTrue() 35 | } 36 | @Test 37 | fun `check book description`() { 38 | assertThat(book.description.isNotBlank()).isTrue() 39 | } 40 | @Test 41 | fun `check book genres`() { 42 | assertThat(book.genres.isNotEmpty()).isTrue() 43 | } 44 | @Test 45 | fun `check book status`() { 46 | assertThat(book.status != MangaInfo.UNKNOWN).isTrue() 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /docs/source-api/src/commonMain/kotlin/ireader/core/http/WebViewManger.kt: -------------------------------------------------------------------------------- 1 | package ireader.core.http 2 | 3 | import com.fleeksoft.ksoup.nodes.Document 4 | 5 | /** 6 | * WebView manager for handling web content loading and Cloudflare bypass 7 | * Provides platform-specific WebView implementations 8 | */ 9 | expect class WebViewManger { 10 | var isInit: Boolean 11 | var userAgent: String 12 | var selector: String? 13 | var html: Document? 14 | var webUrl: String? 15 | var inProgress: Boolean 16 | 17 | /** 18 | * Initialize the WebView 19 | * @return Platform-specific WebView instance 20 | */ 21 | fun init(): Any 22 | 23 | /** 24 | * Update WebView state 25 | */ 26 | fun update() 27 | 28 | /** 29 | * Destroy and cleanup WebView resources 30 | */ 31 | fun destroy() 32 | 33 | /** 34 | * Load URL in background mode (invisible to user) 35 | * @param url The URL to load 36 | * @param selector CSS selector to wait for 37 | * @param onReady Callback when content is ready 38 | */ 39 | fun loadInBackground(url: String, selector: String?, onReady: (String) -> Unit) 40 | 41 | /** 42 | * Check if WebView is currently processing in background 43 | */ 44 | fun isProcessingInBackground(): Boolean 45 | 46 | /** 47 | * Check if WebView is available on this platform 48 | */ 49 | fun isAvailable(): Boolean 50 | } 51 | -------------------------------------------------------------------------------- /docs/source-api/src/androidMain/kotlin/ireader/core/http/CookieSynchronizer.kt: -------------------------------------------------------------------------------- 1 | package ireader.core.http 2 | 3 | import android.webkit.CookieManager 4 | import okhttp3.Cookie 5 | import okhttp3.HttpUrl.Companion.toHttpUrlOrNull 6 | 7 | /** 8 | * Android implementation of cookie synchronization between WebView and OkHttp 9 | */ 10 | actual class CookieSynchronizer( 11 | private val webViewCookieJar: WebViewCookieJar 12 | ) { 13 | private val cookieManager = CookieManager.getInstance() 14 | 15 | actual fun syncFromWebView(url: String) { 16 | val httpUrl = url.toHttpUrlOrNull() ?: return 17 | val webViewCookies = cookieManager.getCookie(url) 18 | if (!webViewCookies.isNullOrEmpty()) { 19 | val cookies = webViewCookies.split(";") 20 | .mapNotNull { Cookie.parse(httpUrl, it.trim()) } 21 | webViewCookieJar.saveFromResponse(httpUrl, cookies) 22 | } 23 | } 24 | 25 | actual fun syncToWebView(url: String) { 26 | val httpUrl = url.toHttpUrlOrNull() ?: return 27 | val cookies = webViewCookieJar.loadForRequest(httpUrl) 28 | cookies.forEach { cookie -> 29 | cookieManager.setCookie(url, cookie.toString()) 30 | } 31 | cookieManager.flush() 32 | } 33 | 34 | actual fun clearAll() { 35 | cookieManager.removeAllCookies(null) 36 | cookieManager.flush() 37 | webViewCookieJar.removeAll() 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /docs/source-api/src/commonMain/kotlin/ireader/core/http/JS.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 The Tachiyomi Open Source Project 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | */ 8 | 9 | package ireader.core.http 10 | 11 | /** 12 | * A wrapper to allow executing JavaScript code without knowing the implementation details. 13 | */ 14 | @Suppress("NO_ACTUAL_FOR_EXPECT") 15 | expect class JS { 16 | 17 | /** 18 | * Evaluates the given JavaScript [script] and returns its result as [String] or throws an 19 | * exception. 20 | */ 21 | fun evaluateAsString(script: String): String 22 | 23 | /** 24 | * Evaluates the given JavaScript [script] and returns its result as [Int] or throws an exception. 25 | */ 26 | fun evaluateAsInt(script: String): Int 27 | 28 | /** 29 | * Evaluates the given JavaScript [script] and returns its result as [Double] or throws an 30 | * exception. 31 | */ 32 | fun evaluateAsDouble(script: String): Double 33 | 34 | /** 35 | * Evaluates the given JavaScript [script] and returns its result as [Boolean] or throws an 36 | * exception. 37 | */ 38 | fun evaluateAsBoolean(script: String): Boolean 39 | 40 | /** 41 | * Closes this instance. No evaluations can be made on this instance after calling this method. 42 | */ 43 | fun close() 44 | } 45 | -------------------------------------------------------------------------------- /docs/source-api/src/desktopMain/kotlin/ireader/core/storage/CacheDir.kt: -------------------------------------------------------------------------------- 1 | package ireader.core.storage 2 | 3 | import java.io.File 4 | 5 | val AppDir : File = getCacheDir() 6 | 7 | val ExtensionDir = File(AppDir, "/Extensions/") 8 | val BackupDir = File(AppDir, "/Backup/") 9 | 10 | enum class OperatingSystem { 11 | Android, IOS, Windows, Linux, MacOS, Unknown 12 | } 13 | 14 | private val currentOperatingSystem: OperatingSystem 15 | get() { 16 | val operSys = System.getProperty("os.name").lowercase() 17 | return if (operSys.contains("win")) { 18 | OperatingSystem.Windows 19 | } else if (operSys.contains("nix") || operSys.contains("nux") || 20 | operSys.contains("aix") 21 | ) { 22 | OperatingSystem.Linux 23 | } else if (operSys.contains("mac")) { 24 | OperatingSystem.MacOS 25 | } else { 26 | OperatingSystem.Unknown 27 | } 28 | } 29 | 30 | private fun getCacheDir(): File { 31 | val ApplicationName = "IReader" 32 | return when (currentOperatingSystem) { 33 | OperatingSystem.Windows -> File(System.getenv("AppData"), "$ApplicationName/cache/") 34 | OperatingSystem.Linux -> File(System.getProperty("user.home"), ".cache/$ApplicationName/") 35 | OperatingSystem.MacOS -> File(System.getProperty("user.home"), "Library/Caches/$ApplicationName/") 36 | else -> throw IllegalStateException("Unsupported operating system") 37 | } 38 | } 39 | 40 | -------------------------------------------------------------------------------- /docs/source-api/src/commonMain/kotlin/ireader/core/source/model/FilterList.kt: -------------------------------------------------------------------------------- 1 | 2 | 3 | package ireader.core.source.model 4 | 5 | typealias FilterList = List> 6 | typealias CommandList = List> 7 | 8 | /** 9 | * Extension functions for FilterList 10 | */ 11 | 12 | /** 13 | * Get all filters that are not at default value 14 | */ 15 | fun FilterList.getActiveFilters(): FilterList { 16 | return this.filter { !it.isDefaultValue() } 17 | } 18 | 19 | /** 20 | * Reset all filters to default values 21 | */ 22 | fun FilterList.resetAllFilters() { 23 | this.forEach { it.reset() } 24 | } 25 | 26 | /** 27 | * Check if any filter is active 28 | */ 29 | fun FilterList.hasActiveFilters(): Boolean { 30 | return this.any { !it.isDefaultValue() } 31 | } 32 | 33 | /** 34 | * Get filter by name 35 | */ 36 | fun FilterList.findByName(name: String): Filter<*>? { 37 | return this.firstOrNull { it.name == name } 38 | } 39 | 40 | /** 41 | * Extension functions for CommandList 42 | */ 43 | 44 | /** 45 | * Get all commands that are not at default value 46 | */ 47 | fun CommandList.getActiveCommands(): CommandList { 48 | return this.filter { !it.isDefaultValue() } 49 | } 50 | 51 | /** 52 | * Reset all commands to default values 53 | */ 54 | fun CommandList.resetAllCommands() { 55 | this.forEach { it.reset() } 56 | } 57 | 58 | /** 59 | * Check if any command is active 60 | */ 61 | fun CommandList.hasActiveCommands(): Boolean { 62 | return this.any { !it.isDefaultValue() } 63 | } 64 | -------------------------------------------------------------------------------- /scripts/bump-version-codes.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Bump versionCode by +1 in all build.gradle.kts files under sources/ 4 | 5 | Usage: 6 | python scripts/bump-version-codes.py # Apply changes 7 | python scripts/bump-version-codes.py --dry-run # Preview changes 8 | """ 9 | 10 | import re 11 | import sys 12 | from pathlib import Path 13 | 14 | def bump_version_codes(base_path: str = "sources", dry_run: bool = False): 15 | total_updates = 0 16 | 17 | for gradle_file in Path(base_path).rglob("build.gradle.kts"): 18 | content = gradle_file.read_text(encoding="utf-8") 19 | 20 | def replace_version(match): 21 | nonlocal total_updates 22 | old_version = int(match.group(1)) 23 | new_version = old_version + 1 24 | total_updates += 1 25 | print(f" {gradle_file.name}: versionCode {old_version} -> {new_version}") 26 | return f"versionCode = {new_version}" 27 | 28 | new_content = re.sub(r'versionCode\s*=\s*(\d+)', replace_version, content) 29 | 30 | if new_content != content and not dry_run: 31 | gradle_file.write_text(new_content, encoding="utf-8") 32 | 33 | if dry_run: 34 | print(f"\nDry run complete. {total_updates} version codes would be updated.") 35 | else: 36 | print(f"\nDone! Updated {total_updates} version codes.") 37 | 38 | if __name__ == "__main__": 39 | dry_run = "--dry-run" in sys.argv 40 | bump_version_codes(dry_run=dry_run) 41 | -------------------------------------------------------------------------------- /test-extensions/src/androidTest/java/ireader/app/tests/InfoChecked.kt: -------------------------------------------------------------------------------- 1 | package ireader.app.tests 2 | 3 | import androidx.test.ext.junit.runners.AndroidJUnit4 4 | import com.google.common.truth.Truth.assertThat 5 | import ireader.app.BOOK_NAME 6 | import ireader.app.BOOK_URL 7 | import ireader.app.extension 8 | import ireader.core.source.model.MangaInfo 9 | import org.junit.Before 10 | import org.junit.Test 11 | import org.junit.runner.RunWith 12 | 13 | @RunWith(AndroidJUnit4::class) 14 | class InfoChecked { 15 | 16 | var book: MangaInfo = MangaInfo(key = "", title = "") 17 | 18 | @Before 19 | fun setup() { 20 | kotlinx.coroutines.runBlocking { 21 | book = extension.getMangaDetails(MangaInfo(key = BOOK_URL, title = BOOK_NAME), emptyList()) 22 | print(book) 23 | } 24 | } 25 | 26 | @Test 27 | fun checkBookName() { 28 | assertThat(book.title.isNotBlank()).isTrue() 29 | } 30 | @Test 31 | fun checkBookAuthor() { 32 | assertThat(book.author.isNotBlank()).isTrue() 33 | } 34 | 35 | @Test 36 | fun checkBookCover() { 37 | assertThat(book.cover.isNotBlank()).isTrue() 38 | } 39 | @Test 40 | fun checkBookDescription() { 41 | assertThat(book.description.isNotBlank()).isTrue() 42 | } 43 | @Test 44 | fun checkBookGenres() { 45 | assertThat(book.genres.isNotEmpty()).isTrue() 46 | } 47 | @Test 48 | fun checkBookStatus() { 49 | assertThat(book.status != MangaInfo.UNKNOWN).isTrue() 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /docs/source-api/src/commonMain/kotlin/ireader/core/source/CatalogSource.kt: -------------------------------------------------------------------------------- 1 | package ireader.core.source 2 | 3 | import ireader.core.source.model.CommandList 4 | import ireader.core.source.model.FilterList 5 | import ireader.core.source.model.Listing 6 | import ireader.core.source.model.MangasPageInfo 7 | 8 | interface CatalogSource : ireader.core.source.Source { 9 | 10 | companion object { 11 | const val TYPE_NOVEL = 0 12 | const val TYPE_MANGA = 1 13 | const val TYPE_MOVIE = 2 14 | 15 | fun getTypeName(type: Int): String { 16 | return when (type) { 17 | TYPE_NOVEL -> "Novel" 18 | TYPE_MANGA -> "Manga" 19 | TYPE_MOVIE -> "Movie" 20 | else -> "Unknown" 21 | } 22 | } 23 | } 24 | 25 | override val lang: String 26 | 27 | suspend fun getMangaList(sort: Listing?, page: Int): MangasPageInfo 28 | suspend fun getMangaList(filters: FilterList, page: Int): MangasPageInfo 29 | 30 | fun getListings(): List31 | 32 | fun getFilters(): FilterList 33 | 34 | fun getCommands(): CommandList 35 | 36 | fun supportsSearch(): Boolean { 37 | return getFilters().isNotEmpty() 38 | } 39 | 40 | fun supportsLatest(): Boolean { 41 | return getListings().isNotEmpty() 42 | } 43 | 44 | fun hasFilters(): Boolean { 45 | return getFilters().isNotEmpty() 46 | } 47 | 48 | fun hasCommands(): Boolean { 49 | return getCommands().isNotEmpty() 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Pull Request 2 | 3 | ## Description 4 | 5 | 6 | ## Type of Change 7 | 8 | 9 | - [ ] New extension 10 | - [ ] Extension update/fix 11 | - [ ] Core improvement 12 | - [ ] Bug fix 13 | - [ ] Documentation update 14 | - [ ] Other (please describe): 15 | 16 | ## Extension Checklist 17 | 18 | 19 | - [ ] Updated `versionCode` in `build.gradle.kts` 20 | - [ ] Added `isNsfw = true` flag if applicable 21 | - [ ] Tested the extension by compiling and running through Android Studio 22 | - [ ] Extension follows the coding standards (uses common utilities where applicable) 23 | - [ ] No hardcoded strings for common selectors (uses `SelectorConstants` if applicable) 24 | - [ ] Date parsing uses `DateParser` utility 25 | - [ ] Status parsing uses `StatusParser` utility 26 | - [ ] Proper error handling implemented 27 | 28 | ## Testing 29 | 30 | 31 | - [ ] Compiled successfully 32 | - [ ] Tested on device/emulator 33 | - [ ] Verified search functionality 34 | - [ ] Verified chapter list loading 35 | - [ ] Verified content loading 36 | - [ ] Checked for memory leaks 37 | 38 | ## Screenshots (if applicable) 39 | 40 | 41 | ## Additional Notes 42 | 43 | 44 | ## Related Issues 45 | 46 | Closes # 47 | -------------------------------------------------------------------------------- /js-sources/webpack.config.d/bundle.js: -------------------------------------------------------------------------------- 1 | // Webpack configuration for self-contained bundle 2 | // This ensures all dependencies are bundled and the library is exported properly 3 | 4 | config.output = config.output || {}; 5 | 6 | // UMD (Universal Module Definition) - works in browser, Node.js, and AMD 7 | config.output.libraryTarget = 'umd'; 8 | 9 | // Global variable name when loaded in browser 10 | config.output.library = 'IReaderSources'; 11 | 12 | // Ensure compatibility with both browser and Node.js 13 | config.output.globalObject = 'typeof self !== "undefined" ? self : this'; 14 | 15 | // Don't externalize any dependencies - bundle everything 16 | config.externals = []; 17 | 18 | // Optimization settings for production 19 | if (config.mode === 'production') { 20 | config.optimization = config.optimization || {}; 21 | 22 | // Keep function names for debugging (sources use reflection) 23 | config.optimization.minimize = true; 24 | config.optimization.minimizer = config.optimization.minimizer || []; 25 | 26 | // Configure terser to keep important names 27 | const TerserPlugin = require('terser-webpack-plugin'); 28 | config.optimization.minimizer.push( 29 | new TerserPlugin({ 30 | terserOptions: { 31 | keep_classnames: true, 32 | keep_fnames: true, 33 | mangle: { 34 | keep_classnames: true, 35 | keep_fnames: true 36 | } 37 | } 38 | }) 39 | ); 40 | } 41 | 42 | // Ensure source maps are generated 43 | config.devtool = 'source-map'; 44 | -------------------------------------------------------------------------------- /docs/source-api/src/commonMain/kotlin/ireader/core/source/Source.kt: -------------------------------------------------------------------------------- 1 | package ireader.core.source 2 | 3 | import ireader.core.source.model.ChapterInfo 4 | import ireader.core.source.model.Command 5 | import ireader.core.source.model.MangaInfo 6 | import ireader.core.source.model.Page 7 | 8 | /** 9 | * A basic interface for creating a source. It could be an online source, a local source, etc... 10 | */ 11 | interface Source { 12 | 13 | /** 14 | * Id for the source. Must be unique. 15 | */ 16 | val id: Long 17 | 18 | /** 19 | * Name of the source. 20 | */ 21 | val name: String 22 | 23 | val lang: String 24 | 25 | /** 26 | * Returns an observable with the updated details for a manga. 27 | */ 28 | suspend fun getMangaDetails(manga: MangaInfo, commands: List>): MangaInfo 29 | 30 | /** 31 | * Returns an observable with all the available chapters for a manga. 32 | */ 33 | suspend fun getChapterList(manga: MangaInfo, commands: List>): List 34 | 35 | /** 36 | * Returns an observable with the list of pages a chapter has. 37 | */ 38 | suspend fun getPageList(chapter: ChapterInfo, commands: List>): List 39 | 40 | /** 41 | * Returns a regex used to determine chapter information. 42 | */ 43 | fun getRegex(): Regex { 44 | return Regex("") 45 | } 46 | 47 | fun getSourceKey(): String { 48 | return "$name-$lang-$id" 49 | } 50 | 51 | fun matchesId(sourceId: Long): Boolean { 52 | return this.id == sourceId 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /docs/source-api/src/commonMain/kotlin/ireader/core/http/Exception.kt: -------------------------------------------------------------------------------- 1 | package ireader.core.http 2 | 3 | /** 4 | * Base exception for HTTP-related errors. 5 | * Replaces okio.IOException for KMP compatibility. 6 | */ 7 | open class HttpException( 8 | message: String? = null, 9 | cause: Throwable? = null 10 | ) : Exception(message, cause) 11 | 12 | /** 13 | * Exception thrown when a network request fails 14 | */ 15 | class NetworkException( 16 | message: String? = null, 17 | cause: Throwable? = null 18 | ) : HttpException(message, cause) 19 | 20 | /** 21 | * Exception thrown when a request times out 22 | */ 23 | class TimeoutException( 24 | message: String? = null, 25 | cause: Throwable? = null 26 | ) : HttpException(message, cause) 27 | 28 | /** 29 | * Exception thrown for SSL/TLS errors 30 | */ 31 | class SSLException( 32 | message: String? = null, 33 | cause: Throwable? = null 34 | ) : HttpException(message, cause) 35 | 36 | /** 37 | * Exception thrown when Cloudflare bypass fails 38 | */ 39 | class CloudflareBypassFailed( 40 | message: String? = "Cloudflare bypass failed", 41 | cause: Throwable? = null 42 | ) : HttpException(message, cause) 43 | 44 | /** 45 | * Exception thrown when a WebView is required 46 | */ 47 | class NeedWebView( 48 | message: String? = "WebView required", 49 | cause: Throwable? = null 50 | ) : HttpException(message, cause) 51 | 52 | /** 53 | * Exception thrown when WebView is out of date 54 | */ 55 | class OutOfDateWebView( 56 | message: String? = "WebView is out of date", 57 | cause: Throwable? = null 58 | ) : HttpException(message, cause) 59 | -------------------------------------------------------------------------------- /docs/source-api/src/commonMain/kotlin/ireader/core/source/model/MangasPageInfo.kt: -------------------------------------------------------------------------------- 1 | 2 | package ireader.core.source.model 3 | 4 | import kotlinx.serialization.Serializable 5 | 6 | /** 7 | * Paginated list of manga results. 8 | * 9 | * This class is serializable for iOS JS bridge support. 10 | */ 11 | @Serializable 12 | data class MangasPageInfo( 13 | val mangas: List, 14 | val hasNextPage: Boolean 15 | ) { 16 | companion object { 17 | /** 18 | * Create empty page info 19 | */ 20 | fun empty(): MangasPageInfo { 21 | return MangasPageInfo(emptyList(), false) 22 | } 23 | 24 | /** 25 | * Create page info with no next page 26 | */ 27 | fun lastPage(mangas: List): MangasPageInfo { 28 | return MangasPageInfo(mangas, false) 29 | } 30 | } 31 | 32 | /** 33 | * Check if page is empty 34 | */ 35 | fun isEmpty(): Boolean = mangas.isEmpty() 36 | 37 | /** 38 | * Check if page has content 39 | */ 40 | fun isNotEmpty(): Boolean = mangas.isNotEmpty() 41 | 42 | /** 43 | * Get manga count 44 | */ 45 | fun size(): Int = mangas.size 46 | 47 | /** 48 | * Filter mangas by predicate 49 | */ 50 | fun filter(predicate: (MangaInfo) -> Boolean): MangasPageInfo { 51 | return copy(mangas = mangas.filter(predicate)) 52 | } 53 | 54 | /** 55 | * Map mangas with transform 56 | */ 57 | fun map(transform: (MangaInfo) -> MangaInfo): MangasPageInfo { 58 | return copy(mangas = mangas.map(transform)) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /docs/source-api/src/jsMain/kotlin/ireader/core/http/CookieSynchronizer.js.kt: -------------------------------------------------------------------------------- 1 | package ireader.core.http 2 | 3 | /** 4 | * JavaScript implementation of CookieSynchronizer. 5 | * 6 | * In browser/JS context, cookies are handled automatically by the browser. 7 | * For iOS JavaScriptCore, cookies are managed by the native layer. 8 | */ 9 | actual class CookieSynchronizer : CookieSynchronizerInterface { 10 | 11 | /** 12 | * Sync cookies from WebView to HTTP client storage. 13 | * In JS context, this is a no-op as cookies are shared. 14 | */ 15 | actual override fun syncFromWebView(url: String) { 16 | // In browser context, cookies are automatically shared 17 | // For iOS JavaScriptCore, the native layer handles this 18 | } 19 | 20 | /** 21 | * Sync cookies from HTTP client to WebView. 22 | * In JS context, this is a no-op as cookies are shared. 23 | */ 24 | actual override fun syncToWebView(url: String) { 25 | // In browser context, cookies are automatically shared 26 | // For iOS JavaScriptCore, the native layer handles this 27 | } 28 | 29 | /** 30 | * Clear all cookies. 31 | */ 32 | actual override fun clearAll() { 33 | try { 34 | js(""" 35 | document.cookie.split(';').forEach(function(c) { 36 | document.cookie = c.replace(/^ +/, '').replace(/=.*/, '=;expires=' + new Date().toUTCString() + ';path=/'); 37 | }); 38 | """) 39 | } catch (e: Exception) { 40 | // Ignore errors in non-browser context (e.g., JavaScriptCore) 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: Suggest a new feature or improvement 3 | title: "[Feature] " 4 | labels: ["enhancement"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Thanks for suggesting a new feature! 10 | 11 | - type: dropdown 12 | id: feature-type 13 | attributes: 14 | label: Feature Type 15 | description: What type of feature is this? 16 | options: 17 | - New extension request 18 | - Extension improvement 19 | - Core system improvement 20 | - Developer tooling 21 | - Documentation 22 | - Other 23 | validations: 24 | required: true 25 | 26 | - type: textarea 27 | id: problem 28 | attributes: 29 | label: Problem Description 30 | description: Is your feature request related to a problem? Please describe. 31 | placeholder: I'm always frustrated when... 32 | validations: 33 | required: true 34 | 35 | - type: textarea 36 | id: solution 37 | attributes: 38 | label: Proposed Solution 39 | description: Describe the solution you'd like 40 | placeholder: I would like to see... 41 | validations: 42 | required: true 43 | 44 | - type: textarea 45 | id: alternatives 46 | attributes: 47 | label: Alternatives Considered 48 | description: Describe any alternative solutions or features you've considered 49 | 50 | - type: textarea 51 | id: additional 52 | attributes: 53 | label: Additional Context 54 | description: Add any other context or screenshots about the feature request here 55 | -------------------------------------------------------------------------------- /docs/ADD_SOURCE_GUIDE.md: -------------------------------------------------------------------------------- 1 | # Adding a New Source 2 | 3 | ## Quick Start 4 | 5 | ```bash 6 | python scripts/add-source.py 7 | ``` 8 | 9 | Answer 4 questions: 10 | 1. Source name (e.g., `NovelFull`) 11 | 2. Website URL (e.g., `https://novelfull.com`) 12 | 3. Language code (e.g., `en`) 13 | 4. Is it a Madara site? (y/n) 14 | 15 | ## What Gets Generated 16 | 17 | ### Madara Sites (zero-code) 18 | ```kotlin 19 | @MadaraSource( 20 | name = "MySite", 21 | baseUrl = "https://mysite.com", 22 | lang = "en", 23 | id = 12345L 24 | ) 25 | object MySiteConfig 26 | ``` 27 | That's it! KSP generates everything else. 28 | 29 | ### Regular Sites (with KSP annotations) 30 | ```kotlin 31 | @Extension 32 | @AutoSourceId(seed = "MySite") 33 | @GenerateFilters(title = true, sort = true, sortOptions = ["Latest", "Popular"]) 34 | @GenerateCommands(detailFetch = true, chapterFetch = true, contentFetch = true) 35 | abstract class MySite(deps: Dependencies) : SourceFactory(deps = deps) { 36 | // Just update the CSS selectors 37 | } 38 | ``` 39 | 40 | ## Finding CSS Selectors 41 | 42 | 1. Open the website in Chrome/Firefox 43 | 2. Right-click any element → "Inspect" 44 | 3. Find the class name or tag 45 | 4. Test: `document.querySelector(".your-selector")` 46 | 47 | ### Common Selectors 48 | 49 | | Element | Try These | 50 | |---------|-----------| 51 | | Novel card | `.novel-item`, `.book-item`, `.post` | 52 | | Title | `h1`, `.title`, `h3.name` | 53 | | Cover | `img`, `.cover img` | 54 | | Chapters | `.chapter-list li`, `ul.chapters a` | 55 | | Content | `.chapter-content p`, `.text p` | 56 | 57 | ## Build & Test 58 | 59 | ```bash 60 | ./gradlew :sources:en:mysource:assembleDebug 61 | ``` 62 | -------------------------------------------------------------------------------- /docs/source-api/src/iosMain/kotlin/ireader/core/http/HttpClients.ios.kt: -------------------------------------------------------------------------------- 1 | package ireader.core.http 2 | 3 | import io.ktor.client.* 4 | import io.ktor.client.engine.darwin.* 5 | import io.ktor.client.plugins.* 6 | import io.ktor.client.plugins.contentnegotiation.* 7 | import io.ktor.serialization.kotlinx.json.* 8 | import kotlinx.serialization.json.Json 9 | 10 | /** 11 | * iOS implementation of HttpClients using Darwin engine 12 | */ 13 | actual class HttpClients : HttpClientsInterface { 14 | 15 | actual override val browser: BrowserEngine = BrowserEngine() 16 | actual override val config: NetworkConfig = NetworkConfig() 17 | actual override val sslConfig: SSLConfiguration = SSLConfiguration() 18 | actual override val cookieSynchronizer: CookieSynchronizer = CookieSynchronizer() 19 | 20 | actual override val default: HttpClient = HttpClient(Darwin) { 21 | engine { 22 | configureRequest { 23 | setAllowsCellularAccess(true) 24 | } 25 | } 26 | 27 | install(HttpTimeout) { 28 | requestTimeoutMillis = config.connectTimeoutSeconds * 1000 29 | connectTimeoutMillis = config.connectTimeoutSeconds * 1000 30 | socketTimeoutMillis = config.readTimeoutMinutes * 60 * 1000 31 | } 32 | 33 | install(ContentNegotiation) { 34 | json(Json { 35 | ignoreUnknownKeys = true 36 | isLenient = true 37 | }) 38 | } 39 | 40 | install(UserAgent) { 41 | agent = config.userAgent 42 | } 43 | } 44 | 45 | actual override val cloudflareClient: HttpClient = default 46 | } 47 | -------------------------------------------------------------------------------- /test-extensions/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.library") 3 | kotlin("android") 4 | } 5 | 6 | android { 7 | compileSdk = Config.compileSdk 8 | namespace = "ireader.test.extensions" 9 | defaultConfig { 10 | minSdk = Config.minSdk 11 | targetSdk = Config.targetSdk 12 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 13 | } 14 | compileOptions { 15 | sourceCompatibility = JavaVersion.VERSION_21 16 | targetCompatibility = JavaVersion.VERSION_21 17 | } 18 | kotlinOptions { 19 | jvmTarget = "21" 20 | } 21 | } 22 | 23 | // Extension dependency is configured dynamically by test scripts 24 | // To test manually: 25 | // 1. Uncomment ONE of the lines below (or add your own) 26 | // 2. Run: ./gradlew :test-extensions:test 27 | // 28 | // Example extensions: 29 | // implementation(project(":extensions:v5:en:novelbuddy")) 30 | // implementation(project(":extensions:individual:en:mylovenovel")) 31 | 32 | dependencies { 33 | // Core dependencies - always included 34 | implementation(project(":multisrc")) 35 | implementation(libs.bundles.common) 36 | implementation(libs.bundles.commonTesting) 37 | testRuntimeOnly("org.junit.vintage:junit-vintage-engine:5.8.2") 38 | implementation(project(":annotations")) 39 | implementation(project(":compiler")) 40 | 41 | // Extension to test - ADD YOUR EXTENSION HERE 42 | // The test scripts will automatically configure this 43 | // For manual testing, uncomment and modify one of these: 44 | // implementation(project(":extensions:v5:en:novelbuddy")) 45 | // implementation(project(":extensions:individual:en:mylovenovel")) 46 | } 47 | -------------------------------------------------------------------------------- /multisrc/build.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | 3 | plugins { 4 | id("com.android.library") 5 | kotlin("android") 6 | } 7 | 8 | android { 9 | compileSdk = Config.compileSdk 10 | namespace = "ireader.test_extensions" 11 | defaultConfig { 12 | minSdk = Config.minSdk 13 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 14 | } 15 | compileOptions { 16 | sourceCompatibility = JavaVersion.VERSION_21 17 | targetCompatibility = JavaVersion.VERSION_21 18 | } 19 | kotlin { 20 | compilerOptions { 21 | jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_21) 22 | } 23 | } 24 | } 25 | 26 | dependencies { 27 | val libs = project.extensions.getByType() 28 | .named("libs") 29 | 30 | // Core dependencies (KMP-compatible) 31 | compileOnly(libs.findLibrary("ireader-core").get()) { isChanging = true } 32 | compileOnly(libs.findLibrary("stdlib").get()) 33 | 34 | // HTML parsing (KMP) 35 | compileOnly(libs.findLibrary("ksoup").get()) 36 | 37 | // Date/Time (KMP) 38 | compileOnly(libs.findLibrary("kotlinx-datetime").get()) 39 | 40 | // HTTP client (KMP) 41 | compileOnly(libs.findLibrary("ktor-core").get()) 42 | compileOnly(libs.findLibrary("ktor-contentNegotiation").get()) 43 | compileOnly(libs.findLibrary("ktor-serialization").get()) 44 | 45 | // Android-specific Ktor engines 46 | compileOnly(libs.findLibrary("ktor-cio").get()) 47 | compileOnly(libs.findLibrary("ktor-android").get()) 48 | compileOnly(libs.findLibrary("ktor-okhttp").get()) 49 | 50 | compileOnly(project(":annotations")) 51 | compileOnly(project(":common")) 52 | } 53 | -------------------------------------------------------------------------------- /test-extensions/src/test/java/ireader/app/tests/ChapterChecker.kt: -------------------------------------------------------------------------------- 1 | package ireader.app.tests 2 | 3 | import com.google.common.truth.Truth.assertThat 4 | import ireader.app.BOOK_NAME 5 | import ireader.app.BOOK_URL 6 | import ireader.app.extension 7 | import ireader.core.source.model.ChapterInfo 8 | import ireader.core.source.model.MangaInfo 9 | import org.junit.Before 10 | import org.junit.Test 11 | 12 | class ChapterChecker { 13 | var chepters: List = emptyList() 14 | @Before 15 | fun setup() { 16 | kotlinx.coroutines.runBlocking { 17 | chepters = extension.getChapterList(MangaInfo(key = BOOK_URL, title = BOOK_NAME), emptyList()) 18 | print(chepters) 19 | } 20 | } 21 | 22 | @Test 23 | fun `check whether chapter list is empty `() { 24 | assertThat(chepters.isNotEmpty()).isTrue() 25 | } 26 | @Test 27 | fun `check whether chapters has name `() { 28 | assertThat(chepters.any { chapterInfo -> chapterInfo.name.isNotBlank() }).isTrue() 29 | } 30 | @Test 31 | fun `check whether chapters has keys `() { 32 | assertThat(chepters.any { chapterInfo -> chapterInfo.key.isNotBlank() }).isTrue() 33 | } 34 | @Test 35 | fun `check whether chapters has dateUpload `() { 36 | assertThat(chepters.any { chapterInfo -> chapterInfo.dateUpload != 0L }).isTrue() 37 | } 38 | @Test 39 | fun `check whether chapters has number `() { 40 | assertThat(chepters.any { chapterInfo -> chapterInfo.number != -1f }).isTrue() 41 | } 42 | @Test 43 | fun `check whether chapters has translator `() { 44 | assertThat(chepters.any { chapterInfo -> chapterInfo.scanlator.isNotBlank() }).isTrue() 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /extensions/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 16 | 17 | 18 | 22 | 23 | 26 | 29 | 32 | 35 | 38 | 41 | 44 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /docs/source-api/src/androidMain/kotlin/ireader/core/http/AndroidCookieJar.kt: -------------------------------------------------------------------------------- 1 | package ireader.core.http 2 | 3 | import android.webkit.CookieManager 4 | import okhttp3.Cookie 5 | import okhttp3.CookieJar 6 | import okhttp3.HttpUrl 7 | 8 | class AndroidCookieJar : CookieJar { 9 | 10 | private val manager = CookieManager.getInstance() 11 | 12 | override fun saveFromResponse(url: HttpUrl, cookies: List) { 13 | val urlString = url.toString() 14 | 15 | cookies.forEach { manager.setCookie(urlString, it.toString()) } 16 | } 17 | 18 | override fun loadForRequest(url: HttpUrl): List { 19 | return get(url) 20 | } 21 | 22 | fun get(url: HttpUrl): List { 23 | val cookies = manager.getCookie(url.toString()) 24 | 25 | return if (cookies != null && cookies.isNotEmpty()) { 26 | cookies.split(";").mapNotNull { Cookie.parse(url, it) } 27 | } else { 28 | emptyList() 29 | } 30 | } 31 | 32 | fun remove(url: HttpUrl, cookieNames: List? = null, maxAge: Int = -1): Int { 33 | val urlString = url.toString() 34 | val cookies = manager.getCookie(urlString) ?: return 0 35 | 36 | fun List.filterNames(): List { 37 | return if (cookieNames != null) { 38 | this.filter { it in cookieNames } 39 | } else { 40 | this 41 | } 42 | } 43 | 44 | return cookies.split(";") 45 | .map { it.substringBefore("=") } 46 | .filterNames() 47 | .onEach { manager.setCookie(urlString, "$it=;Max-Age=$maxAge") } 48 | .count() 49 | } 50 | 51 | fun removeAll() { 52 | manager.removeAllCookies {} 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /docs/source-api/src/desktopMain/kotlin/ireader/core/http/PersistentCookieJar.kt: -------------------------------------------------------------------------------- 1 | package ireader.core.http 2 | 3 | import ireader.core.prefs.PreferenceStore 4 | import kotlinx.coroutines.runBlocking 5 | import okhttp3.Cookie 6 | import okhttp3.CookieJar 7 | import okhttp3.HttpUrl 8 | 9 | class PersistentCookieJar(preferencesStore: PreferenceStore) : CookieJar { 10 | 11 | val store = PersistentCookieStore() 12 | 13 | override fun saveFromResponse(url: HttpUrl, cookies: List) { 14 | val commonCookies = cookies.map { it.toCommonCookie() } 15 | runBlocking { 16 | store.addCookies(url.toString(), commonCookies) 17 | } 18 | } 19 | 20 | override fun loadForRequest(url: HttpUrl): List { 21 | return runBlocking { 22 | store.getCookies(url.toString()).mapNotNull { it.toOkHttpCookie(url) } 23 | } 24 | } 25 | 26 | private fun Cookie.toCommonCookie(): ireader.core.http.Cookie { 27 | return ireader.core.http.Cookie( 28 | name = name, 29 | value = value, 30 | domain = domain, 31 | path = path, 32 | expiresAt = expiresAt, 33 | secure = secure, 34 | httpOnly = httpOnly, 35 | persistent = persistent 36 | ) 37 | } 38 | 39 | private fun ireader.core.http.Cookie.toOkHttpCookie(url: HttpUrl): Cookie? { 40 | return Cookie.Builder() 41 | .name(name) 42 | .value(value) 43 | .domain(domain) 44 | .path(path) 45 | .apply { 46 | if (expiresAt > 0) expiresAt(expiresAt) 47 | if (secure) secure() 48 | if (httpOnly) httpOnly() 49 | } 50 | .build() 51 | } 52 | } 53 | --------------------------------------------------------------------------------