Compare commits

...

192 Commits

Author SHA1 Message Date
Chopper cb27948307
Remove LaidBackScans and Astrames (#5715)
CI / Prepare job (push) Successful in 7s Details
CI / Build individual modules (push) Successful in 6m54s Details
CI / Publish repo (push) Successful in 48s Details
2024-10-28 00:21:11 +00:00
Chaos Pjeles dc64c90e7e
Fix NullPointerException in Cosplaytele (#5708)
* fix #5232

* lint
2024-10-28 00:21:11 +00:00
Vetle Ledaal 17fc1acd6c
Split VyvyManga and VyvyManga.org extensions (#5704)
* Revert "VyvyManga: Migrate theme (#5573)"

This reverts commit bba2693814d306417366fc03dcc8b21bf2e1d740.

* VyvyManga: bump version

* VyvyManga.org: move to other extension
2024-10-28 00:21:11 +00:00
Chopper da0bb1b8eb
Add LeerManga (#5684)
* Add LeerManga

* Remove the fetchGenres call

* Remove Separator

* Use toString

Co-authored-by: Vetle Ledaal <vetle.ledaal@gmail.com>

---------

Co-authored-by: Vetle Ledaal <vetle.ledaal@gmail.com>
2024-10-28 00:21:11 +00:00
Chopper eb4a349e5e
Add PlumaComics (#5705) 2024-10-28 00:21:11 +00:00
Chopper 7ce559d436
LXHentai: Update domain and selectors (#5702)
Update domain and selectors
2024-10-28 00:21:11 +00:00
Chopper 647126a841
InmortalScan: Update domain (#5700)
Update domain
2024-10-28 00:21:11 +00:00
Chopper 26dfd922c4
RezoScans: Update cdn url (#5699)
* Update cdn url

* Add isNsfw = false
2024-10-28 00:21:11 +00:00
BrutuZ aafa0be66d
Comick: Fix permanently cached First Cover URLs and Score on Description (#5686)
* Remove permanent cache of First Cover URL

Because images can be replaced or even removed altogether

* Reuse preference default constants for `.toSManga` parameters

* Score on description was removed at some point?

* Filter covers earlier and try to narrow it down further by language

* Reformat long line

* Don't assume a default cover language

* Only use country code part of language code

* Reformat for Lint complainer
2024-10-28 00:21:11 +00:00
AwkwardPeak7 5b7cfb5ca8
fix adultwebtoon & hentaiwebtoon (#5679) 2024-10-28 00:21:11 +00:00
KirinRaikage 996b1138d1
Reaper Scans (FR): Migrate theme (#5678) 2024-10-28 00:21:11 +00:00
AwkwardPeak7 a8a540de77
remove unavailable intl 2024-10-28 00:21:11 +00:00
Chopper 5cc6963a46
XXManhwa/Komikindo: Update domains (#5671)
Update domain
2024-10-28 00:21:11 +00:00
bapeey c5395cae72
VisorInari: Update domain (#5670)
update domain
2024-10-28 00:21:11 +00:00
bapeey 49ab904eaa
MiauScan: Update domain (#5669)
* update domain

* bump
2024-10-28 00:21:11 +00:00
bapeey d7418f4b5f
Keyoapp: Update cdn of sources and add paid chapters preference (#5666)
* update cdn and move preference to theme

* lint
2024-10-28 00:21:11 +00:00
Chopper b1c2054428
MangasOriginesFr: Fix dateFormat (#5663)
Fix dateFormat
2024-10-28 00:21:10 +00:00
Chopper 00c5695acf
KemonoTheme: Fix chapter URL in Webview (#5662)
Fix chapter URL in Webview
2024-10-28 00:21:10 +00:00
Chopper 4b35ae0c78
TraducoesDoLipe: Refactoring (#5659)
Refactoring
2024-10-28 00:21:10 +00:00
Chopper a6da43e9aa
FenixManhwas: Fix chapters (#5656)
Fix chapters
2024-10-28 00:21:10 +00:00
kana-shii 401232bc33
MyReadingManga: add "Filed Under" to manga (#5654)
Add filed under
2024-10-28 00:21:10 +00:00
kana-shii dd897e1499
Clean ZinChanManga.com titles (#5646) 2024-10-28 00:21:10 +00:00
Chopper 18943c8c1f
HikariScan: Migrate theme (#5616)
* Migrate theme

* Remove versionId
2024-10-28 00:21:10 +00:00
bapeey f740c319ba
GenzToons: Fix wrong image url and increase timeouts (#5625)
fix regex
2024-10-28 00:21:10 +00:00
bapeey a05b16088f
LibGroup: Fix app crash (#5624)
fix crash
2024-10-28 00:21:10 +00:00
Chopper 0baa45742a
AnimeXNovel: Bugfix (#5620)
Bugfix
2024-10-28 00:21:10 +00:00
Chopper 3804bafd6c
Remove AstrumScans (#5617) 2024-10-28 00:21:10 +00:00
Chopper 00a70bc406
Add TraducoesDoLipe (#5615)
* Add TraducoesDoLipe

* Change to method reference

* Fix lint
2024-10-28 00:21:10 +00:00
Luqman dc66595ecd
LumosKomik: update domain, fix description (#5607)
Closes #5429
2024-10-28 00:21:10 +00:00
Luqman 1376e3e5dd
YuraManga: change theme (#5606)
* YuraManga: change theme

Closes #5604

* update version
2024-10-28 00:21:10 +00:00
bapeey 01b431754f
GenzToons: Actually update domain and cdn url (#5603)
* update cdn

* search cdn in page

* bruh i dont update the domain

* revert changes in keyoapp
2024-10-28 00:21:10 +00:00
bapeey ec2dcf0e7b
HentaiRead: Fix url selector (#5601)
fix url selector
2024-10-28 00:21:10 +00:00
vifixurl 2f02d090b8
TeamLanhLung: Update domain (#5595)
update domain
2024-10-28 00:21:10 +00:00
bapeey 38567075ae
GenzToons: Update theme and domain (#5587)
* update theme and domain

* add paid chapters preference
2024-10-28 00:21:10 +00:00
Chopper 033f5cb3e6
Add LichSubs (#5586) 2024-10-28 00:21:10 +00:00
Chopper d2d69b3dcf
Add YaoiManga (#5585) 2024-10-28 00:21:10 +00:00
Chopper 1290a32547
Update some domains (#5574)
* Update domains

* Update more domains
2024-10-28 00:21:10 +00:00
Chopper 421c4076d5
VyvyManga: Migrate theme (#5573)
* Migrate theme

* Add isNsfw
2024-10-28 00:21:10 +00:00
bapeey b404fe1ab0
Remove Ukiyotoon and rebrand InariManga to VisorInari (#5567)
rebrand againnnnn
2024-10-28 00:21:10 +00:00
Chopper 5ada452655
Add FenixManhwas (#5565)
* Add FenixManhwas

* Typo
2024-10-28 00:21:10 +00:00
TheKingTermux 63f23e44a2
TukangKomik : Update Domain (#5552) 2024-10-28 00:21:10 +00:00
Chopper 779560170b
Add ManhwaFreake (#5545)
* Add ManhwaFreake

* Add isNsfw
2024-10-28 00:21:10 +00:00
Chopper e6a3201790
Add AsiaLotus (#5544) 2024-10-28 00:21:10 +00:00
Chopper ce931d0f1b
Add CatharsisFantasy (#5543) 2024-10-28 00:21:10 +00:00
Eddiesti 6605a0af10
Hwago: Domain URL Updated (#5539)
* KomikIndoCo: URL changed

* Hwago: URL Changed
2024-10-28 00:21:10 +00:00
Eshlender 3d060492e6
[RU]ComX fix looping Search (#5524)
* [RU]ComX fix looping Search

* addPathSegment
2024-10-28 00:21:10 +00:00
bapeey 28d71dd743
KoinoboriScan: Apply API changes (#5535)
* update api

* remove comment
2024-10-28 00:21:10 +00:00
Chopper 8914fc42dd
DiskusScan: Update domain (#5532)
Update domain
2024-10-28 00:21:10 +00:00
Chopper 61f7d83728
HikariGaNai: Migrate theme (#5527)
Migrate theme
2024-10-28 00:21:10 +00:00
bapeey 1767c5a29d
BlogTruyen: Wrap async call in try-catch (#5526)
add try catch
2024-10-28 00:21:10 +00:00
bapeey e91a347360
ManhwasNet: Remove sucuri interceptor (#5521)
remove sucuri
2024-10-28 00:21:10 +00:00
Vetle Ledaal 3eb426c197
Update domain for 7 extensions (#5513)
* Knight No Scanlation: update domain

* SummerToon: update domain

* TruyenVN: update domain

* Eros Scans: update domain

* Tecno Scans: update domain

* MangaBTT: update domain

* NetTruyenCO (unoriginal): update domain
2024-10-28 00:21:10 +00:00
Lefan 3477924565
ArazNovel: fix chapterlist parse (#5509)
* fix selectors

* default madara works

* update baseurl
2024-10-28 00:21:10 +00:00
Romain 07d68def99
Updated Contrib to match current repo (#5506)
* Updated Contrib to match current repo

* For the one who is different from everyone else

* How to build a debug catalog apk

* Added multisrc in default pulled folders
2024-10-28 00:21:10 +00:00
Romain 7dd51c9b1d
Add InovaScanManga (#5502)
* Add InovaScanManga

* Use Long Month format

Co-authored-by: Vetle Ledaal <vetle.ledaal@gmail.com>

* Added missing selectors

---------

Co-authored-by: Vetle Ledaal <vetle.ledaal@gmail.com>
2024-10-28 00:21:10 +00:00
oalieno e1538a3be9
Roumanwu: Rewrite to make it works on new website (#5492)
* Roumanwu: Rewrite to make it works on new website

* Roumanwu: refactor and change MIRROR url
2024-10-28 00:21:10 +00:00
Chopper 1cfbe474eb
Add BRMangasTop (#5499) 2024-10-28 00:21:09 +00:00
Lefan a248121b04
New source: Rezo Scans (#5498)
* rezoscans copied from luascans

* remove trailing slash from base url
2024-10-28 00:21:09 +00:00
Chopper 1015e91677
HuntersScans: Update domain (#5496)
Update domain
2024-10-28 00:21:09 +00:00
oalieno 24fc1ba08a
Magcomi: fix popular manga css selector (#5495) 2024-10-28 00:21:09 +00:00
oalieno dcd0ea5b37
Baozimhorg: handle new api (#5494) 2024-10-28 00:21:08 +00:00
zhongfly d1b75272d0
Zaimanhua: fix image url expire problem & incorrect chapter upload time (#5455)
* Zaimanhua: fix incorrect chapter upload time

For some chapters, api will always return current time as upload time.

* Zaimanhua: fix image url expire problem

Store params in the fragment of the imageUrl, re-fetch the image URL if loading fails
2024-10-28 00:21:08 +00:00
Lefan 86c7d2e64b
update flame comics domain (#5487) 2024-10-28 00:21:08 +00:00
anenasa bcb6f6972b
Manwa: Fix page list (#5479) 2024-10-28 00:21:08 +00:00
AwkwardPeak7 72d2c38a6d
Arven: bump 2024-10-28 00:21:08 +00:00
KirinRaikage f59cd7fd0d
Arven Scans: Migrate theme (#5475) 2024-10-28 00:21:08 +00:00
Chopper 2451072c34
LScans: Migrate theme (#5470)
Migrate theme
2024-10-28 00:21:08 +00:00
ringosham 23e2192290
Nicovideo Seiga: Fix image intercept (#5469)
Fix images not decrypting
2024-10-28 00:21:08 +00:00
Chopper a1efe96b9d
MantrazScan: Update domain (#5468)
Update domain
2024-10-28 00:21:08 +00:00
Chopper 15006c7e96
Add NirvanaScan (#5467) 2024-10-28 00:21:08 +00:00
Chopper 4fa56b785d
Remove KingdomBrasilScantrad (#5466) 2024-10-28 00:21:08 +00:00
Chopper 76741e7ce5
SussyScan: Fix null pointer (#5456)
Fix nullpointer
2024-10-28 00:21:08 +00:00
Chopper ca55ea1d3b
Add MangaLivre (#5447) 2024-10-28 00:21:08 +00:00
Chopper 651ed9f3cf
KappaBeast: Fix popular, latest and search (#5446)
Fix popular, latest and search
2024-10-28 00:21:08 +00:00
AlphaBoom 2d7101e19b
Add Hanime1.me (#5440)
Add Hanime1
2024-10-28 00:21:08 +00:00
KenjieDec eb397b2b5f
Kemono & Coomer | Fixed Latest, Added Filters, Increased Post Limit (#5383)
* Fixed Latest, Added Filters, Increased Post Limit

* Fix Lint Errors

- There's no "Coomer" multisrc.

* Add Favourites Filter

- For #547

* Added `KemonoFavouritesDto`

- also a `fav` var in KemonoCreatorDTo

* Apply suggestion

* Apply suggestion

* Apply Suggestions

- Apply AwkwardPeak's suggestions

* Fix Lint Errors
2024-10-28 00:21:08 +00:00
Paco Chrispijn 4a105eb6ed
MangaDemon domain change (#5430) 2024-10-28 00:21:08 +00:00
bapeey bb5c5a92fe
Remove dead sources (#5428)
remove dead sources
2024-10-28 00:21:08 +00:00
KenjieDec c56fd24df9
SpyFakku: Add `Random` Filter (#5411) 2024-10-28 00:21:08 +00:00
Chopper 458fde8e7f
InfernalVoidScans: Update domain (#5397)
* Update domain

* Remove 'isNsfw'
2024-10-28 00:21:08 +00:00
bapeey 1e21288bed
Fix HotComics only showing one chapter (#5405)
fix
2024-10-28 00:21:08 +00:00
bapeey 351e1ce48e
Add Ragnarok No Chikara (#5402)
add
2024-10-28 00:21:08 +00:00
bapeey 6deb8233fc
Mangas.in: Update domain (#5401)
update domain
2024-10-28 00:21:08 +00:00
bapeey bb9103e44a
Traducciones Amistosas: Update domain and change theme (#5400)
* madara

* keep nsfw true just in case
2024-10-28 00:21:08 +00:00
Chopper 91543c7e10
Add FirstKissManhua (#5396)
* Add FirstKissManhua

* Fix lint
2024-10-28 00:21:08 +00:00
Chopper 6231261e72
Add MugiManga (#5395) 2024-10-28 00:21:08 +00:00
Chopper 91e8561f76
CatharsisWorld: Add SSL ignore (#5394)
Add SSL ignore
2024-10-28 00:21:08 +00:00
Eddiesti e140f6e543
KomikIndoCo: URL changed (#5369) 2024-10-28 00:21:08 +00:00
vifixurl b00aedee39
TeamLanhLung: Update domain (#5384)
update domain
2024-10-28 00:21:08 +00:00
Luqman 8003ab180d
Soul Scans: tweak page selector, filter novel series (#5382)
- exclude ads from page list
- filter novel series when browsing
2024-10-28 00:21:08 +00:00
Vetle Ledaal b1bda6d46a
Area Manga: update domain (#5373) 2024-10-28 00:21:08 +00:00
Vetle Ledaal bfbd83b47c
Ikiru: update domain (#5372) 2024-10-28 00:21:08 +00:00
Vetle Ledaal 2a247808f1
Bato.to: add mirror fto.to and jto.to (#5371) 2024-10-28 00:21:08 +00:00
Chopper c13d6b16e2
MangaNoon: Update domain (#5367)
Update domain
2024-10-28 00:21:08 +00:00
Chopper 0b8fdbad93
Add YushukeMangas (#5330)
* Add YushukeMangas

* Use cloudflareClient
2024-10-28 00:21:08 +00:00
SummonHIM db21462070
Add Komiic source (#5224)
* Init commit of the Komiic

* komiic: set ext to nsfw

* Komiic: Add fetchAPILimit

* Komiic: Refactor entire project.

* komiic: save date format as class val and wrap the parsing in try catch

* Comiic: remove unnecessary private function
rename some vars
remove unnecessary SerialName
remove unnecessary interface

* komiic:
add private val json
payload use simple classes
change some companion object to capital

* Komiic:
imports go ordered in lexicographic
commet function fetchAPILimit

* Komiic: restore previous import order

* Komiic: optimize some variables
2024-10-28 00:21:08 +00:00
Chopper ced338c8e2
Brakeout: Fix chapters and filter (#5353)
Fix chapters and filter
2024-10-28 00:21:08 +00:00
Chopper c24b4d6b98
Remove MangaChill (#5352) 2024-10-28 00:21:08 +00:00
Chopper c423d85e2e
TheBlank: Add random UA (#5351)
Add random UA
2024-10-28 00:21:08 +00:00
bapeey f584c2d169
IkigaiMangas: Update domain and fix app freeze (#5345)
fix thread block
2024-10-28 00:21:08 +00:00
Chopper 4a2951e1f0
Update some domain (#5331)
* Update domain

* Fix SSL mirrors
2024-10-28 00:21:08 +00:00
Vetle Ledaal 74ad28a16e
Remove Manga Queen.com (#5320) 2024-10-28 00:21:08 +00:00
Vetle Ledaal fcb27dae61
Remove MangaDig (#5319) 2024-10-28 00:21:08 +00:00
KenjieDec e919ddfe06
SpyFakku | Fixed SpyFakku (#5314)
* Fixed Spyfakku

* Fixed circles, and others

- Fixed circles ( Original API's currently giving full circle list, not the specific comic circle )
- Added attempts for fetching manga details
- Apply AwkwardPeak's suggestion
2024-10-28 00:21:08 +00:00
Chopper a145f79f35
Rackus(PMScans): Migrate theme and rebranding (#5311)
* Migrate theme

* Rebranding
2024-10-28 00:21:08 +00:00
Smol Ame 4125032228
ResetScans: Update domain (#5332)
* ResetScans: Bump versionCode

* ResetScans: Update baseUrl
2024-10-28 00:21:08 +00:00
Luqman 843fa52764
Tenshi.id: update domain (#5326)
Closes #5183
2024-10-28 00:21:08 +00:00
AwkwardPeak7 ac7b2a2dbb
add Batcave (#5304)
* add BatCave

* owntext

* semicolon proof

* better filter error parse
2024-10-28 00:21:08 +00:00
Chopper 6c88ef39cc
Ikiru(MangaTale): Rebranding and update domain (#5312)
Rebranding
2024-10-28 00:21:08 +00:00
Chopper 25cca5e9db
Add KofiScans (#5309) 2024-10-28 00:21:08 +00:00
Chopper d74fec37c2
Add CatharsisWorld (#5308) 2024-10-28 00:21:08 +00:00
Chopper 9fe318655e
Add SkyManga (#5307) 2024-10-28 00:21:08 +00:00
anenasa 2e672c3c45
Manwa: Add preference for mirror selection (#5286) 2024-10-28 00:20:42 +00:00
lamaxama be7c3d774a
RCO: fix the image couldn't be loaded (#5288)
* RCO: fix the image couldn't be loaded

* add random UA

* only chrome
2024-10-28 00:20:42 +00:00
Chopper 3c4efce496
Remove Miaoshang (#5271)
* Remove Miaoshang

* Revert changes in ba0f13242271b83d79288981bab6245be9681c0c (#2556)

---------

Co-authored-by: stevenyomi <95685115+stevenyomi@users.noreply.github.com>
2024-10-28 00:20:42 +00:00
bapeey 1e2b6e01df
TMO: Update domain and fix image headers (#5284)
update domain and headers
2024-10-28 00:20:42 +00:00
Vetle Ledaal 4a90f72f0a
MangaworldAdult: update domain (#5277) 2024-10-28 00:20:42 +00:00
Vetle Ledaal fd131cfffb
MangaPoisk: update domain (#5276) 2024-10-28 00:20:42 +00:00
Vetle Ledaal 243a7e9cd7
Mangahasu: update domain (#5275) 2024-10-28 00:20:42 +00:00
Vetle Ledaal 9dca49b7d9
KomikMama: update domain (#5274) 2024-10-28 00:20:42 +00:00
Vetle Ledaal 3d7434f88f
Jimanga -> S2Manga.io: update domain (#5273) 2024-10-28 00:20:42 +00:00
AwkwardPeak7 cd5b952013
update gradle wrapper and agp (#5270) 2024-10-28 00:20:42 +00:00
lamaxama e0f76c24bd
Komga: fix pdf image file issue (#5266) 2024-10-28 00:20:42 +00:00
Chopper f1cb28d2ad
Bakai: Fix headers and change network client (#5272)
Fix headers and cloudflare
2024-10-28 00:20:42 +00:00
kana-shii 5507a0e6b2
MyReadingManga: add tags to manga (#5269)
add tags to manga
2024-10-28 00:20:42 +00:00
Chopper 38c3ab9647
Remove ScanHentaiFR (#5268) 2024-10-28 00:20:42 +00:00
Chopper bacae6ab8e
Remove UnionMangas (#5267) 2024-10-28 00:20:42 +00:00
KenjieDec b86a8c4137
CosmicScansID | Fixed NullPointerException (#5243)
* Fixed NullPointerException when there's no thumbnail

* Apply suggestion
2024-10-28 00:20:42 +00:00
bapeey 79690a5934
MangaHere: Remove no-cache header (#5259)
remove no cache
2024-10-28 00:20:42 +00:00
bapeey 1f160d3352
Add LectorJPG (#5258)
* lectorjpg

* nsfw
2024-10-28 00:20:42 +00:00
bapeey f9c1ba9daf
Move SenshiManga to multisrc and add Taikutsu (#5256)
* lectormoe

* lectorjpg

* wrong branch

This reverts commit b957d5d68827066b913704c5f6c73a5785f75d42.
2024-10-28 00:20:42 +00:00
bapeey eef6f38cd7
AsuraScans: Prevent slug map break (#5254)
* prevent slugmap break

* reset slug map
2024-10-28 00:20:42 +00:00
bapeey 12528d72a5
InariPikav: Update domain and rebrand to Inari Manga again (#5252)
update
2024-10-28 00:20:42 +00:00
Vetle Ledaal a38e1bcab1
Remove 2 dead extensions (#5246)
* Remove Manga Bee, replaced by Zinmanga

* Remove MangaBob, redirects to unrelated site
2024-10-28 00:20:42 +00:00
Vetle Ledaal cefd29ff16
Update domain for 3 extensions (#5245)
* Manga168: update domain

* MangaDenizi: update domain

* Manga Galaxy: update domain
2024-10-28 00:20:42 +00:00
Vetle Ledaal 87207fcbfd
NekoPost.co (unoriginal) -> Superdoujin.org: update domain (#5205)
* NekoPost.co (unoriginal) -> Superdoujin.org: update domain

* set static chapter name if there's only 1 chapter
2024-10-28 00:20:42 +00:00
Milo Mighdoll 39cbf99fa5
InfernalVoidScans: update domain (#5235)
* InfernalVoidScans: update domain

* Update build.gradle
2024-10-28 00:20:42 +00:00
Eddiesti 3e2037df5a
KomikIndoID: baseURL updated (#5216) 2024-10-28 00:20:42 +00:00
Vetle Ledaal 0b9daed773
HentaiVN.plus: update domain (#5204)
Skips some redirect.
2024-10-28 00:20:42 +00:00
Vetle Ledaal 51c1961ec5
Emperor Scan: update domain (#5203)
Skips a redirect, fixes HTTP 520 on chapter fetch (might be
intermittent).
2024-10-28 00:20:42 +00:00
Vetle Ledaal 8c78d137ca
Drake Scans: update domain (#5202)
Change skips some redirects.
2024-10-28 00:20:42 +00:00
bapeey 5a827719cf
AsuraScans: Fix selectors (#5199)
wa
2024-10-28 00:20:42 +00:00
ringosham cddbfcfc1a
Nicovideo: Adding early access prefix to chapters (#5196)
Adding prefix on early access
2024-10-28 00:20:42 +00:00
anenasa dd882e091b
YKMH: Update url (#5194) 2024-10-28 00:20:42 +00:00
Vetle Ledaal 0353829445
Remove Kings-Manga (#5185)
Remove Kings-Manga, redirects to Superdoujin

Removing instead of updating the domain since the new site is NSFW.
2024-10-28 00:20:42 +00:00
Secozzi ad7b208e4b
add weeb central (#5178)
* add weeb central

* stuff
2024-10-28 00:20:42 +00:00
Vetle Ledaal 4938b5b609
Remove 3 invalid domains from comments (#5174)
* Fix invalid domain in comment (e-hentai.net -> e-hentai.org)

* Fix invalid domain in comment (hentaivn.autos -> hentaihvn.tv)

* Fix invalid domain in comment (rouman01.xyz -> none)
2024-10-28 00:20:41 +00:00
Vetle Ledaal 7477160454
Remove 34 broken extensions (#5173)
* Remove Clover Manga, domain redirects to Webtoon Hatti

* Remove Comic1000, domain parked

* Remove Hentai Cafe, domain parked

* Remove KomikManhwa, domain parked

* Remove Manga Action, domain parked

* Remove Manga Nerds, domain parked

* Remove MangaOwl.blog (unoriginal), domain parked

* Remove MangaOwl.one (unoriginal), domain parked

* Remove MangaOwl.us (unoriginal), domain parked

* Remove Mangaowl Yaoi, domain parked

* Remove Manga Rocky, domain parked

* Remove MangaRuby.com, domain parked

* Remove MangaSiro, domain parked

* Remove MangaX1, domain parked

* Remove ManhuaBox, domain parked

* Remove ManhuaManhwa.online, domain parked

* Remove Manhua Mix, domain parked

* Remove Manhwa2Read, domain parked

* Remove Manhwa365, domain parked

* Remove Manhwa Lover, domain parked

* Remove IsekaiScan.to (unoriginal), domain parked

* Remove NekoScan, domain parked

* Remove OnlyManhwa, domain parked

* Remove Pornwha, domain parked

* Remove ShavelProiection, domain parked

* Remove Shield Manga, domain parked

* Remove SkyManga.xyz, domain parked

* Remove The Apollo Team, domain parked

* Remove Wakamics, domain parked

* Remove WebToonily, domain parked

* Remove Yaoi Hentai, domain parked

* Remove Yaoi.mobi, domain parked

* Remove Komik Chan, domain parked

* Remove Global Bloging, domain parked
2024-10-28 00:20:41 +00:00
kana-shii 683f874db5
mangago update regex (#5172) 2024-10-28 00:20:41 +00:00
kana-shii 631709f29a
Batoto: fix regex + update regex (#5171)
* fix regex + update regex

* Update BatoTo.kt
2024-10-28 00:20:41 +00:00
vifixurl 1e7fe5c466
TeamLanhLung: Update domain (#5168)
update domain
2024-10-28 00:20:41 +00:00
Vetle Ledaal dce175cef5
Jiangzaitoon: update chapter URL selector (#5165) 2024-10-28 00:20:41 +00:00
Vetle Ledaal 1a6c21f9f3
Cartoon18: fix page selector (#5164) 2024-10-28 00:20:41 +00:00
Vetle Ledaal e34377d2ad
Lua Scans: exclude paid chapters (#5163) 2024-10-28 00:20:41 +00:00
Vetle Ledaal 2e1a7dc649
MangaGeek -> MangaHolic: update domain (#5162) 2024-10-28 00:20:41 +00:00
Eddiesti dd9b394388
CosmicScans: Fix URL (#5161)
* Fix URL CosmicScans.id

* CosmicScans: Fix URL
2024-10-28 00:20:41 +00:00
Vetle Ledaal 41039a1bc0
LXHentai: update domain and override setting, fix page selector (#5156) 2024-10-28 00:20:41 +00:00
Vetle Ledaal 0d6631ee22
Ninja Scan: update timeout limit (#5155) 2024-10-28 00:20:41 +00:00
bapeey e935556bff
IkigaiMangas: Search domain automatically (#5152)
domain
2024-10-28 00:20:41 +00:00
Cuong-Tran 394cfb9b13
Photos18: Fix blank chapter name (#5151)
* Fix blank chapter name

* reverse observable
2024-10-28 00:20:41 +00:00
lamaxama a2917fa1f0
Pixiv: fix search repeats itself (#5149)
* Pixiv: fix search repeats itself

* bump version
2024-10-28 00:20:41 +00:00
kana-shii 38a6e8b12b
Batoto: new regex update (#5137)
new regex
2024-10-28 00:20:41 +00:00
kana-shii 660abdf599
Mangago: new regex update (#5136)
new regex
2024-10-28 00:20:41 +00:00
KenjieDec e145bdb6bc
HentaiRead | Fixed No Results Found, Added Filters (#5127)
* Fixed HentaiRead, Added Filters

* Apply suggestion
2024-10-28 00:20:41 +00:00
Chopper 1eb5b7e347
TeamLanhLung: Update domain (#5119)
* Update domain

* Bump version
2024-10-28 00:20:41 +00:00
zhongfly 5f905c713d
Add Zaimanhua (#5092) 2024-10-28 00:20:41 +00:00
Chopper 59e9181bf9
Add TopTruyen (#5133) 2024-10-28 00:20:41 +00:00
Chopper 74b97ebc23
CeriseScan: Update domain (#5132)
Update domain
2024-10-28 00:20:41 +00:00
Chopper 79b98463cd
SinensisScan: Update domain (#5131)
* Update domain

* Remove date format
2024-10-28 00:20:41 +00:00
bapeey 7f71f15629
LectorTmo: Increase ratelimit slightly (#5125)
increase ratelimit
2024-10-28 00:20:41 +00:00
Chopper a0d504b109
ShijieScans: Fix mangaUrlDirectory (#5123)
Fix mangaUrlDirectory
2024-10-28 00:20:41 +00:00
Chopper ceb13d67d9
Remove MortalsGroove (#5122) 2024-10-28 00:20:41 +00:00
Chopper d1d53c61fe
Add MangaRAW (#5121) 2024-10-28 00:20:41 +00:00
Chopper 6b9659a92c
Shinigami: Update domain (#5120)
Update domain
2024-10-28 00:20:41 +00:00
Chopper de5048fb63
DocTruyen5s: Update domain (#5118)
Update domain
2024-10-28 00:20:41 +00:00
Chopper c352aedae1
VarnaScan: Update domain (#5117)
Update domain
2024-10-28 00:20:41 +00:00
Chopper 9c71b92ecf
MangaNoon: Update domain (#5116)
Update domain
2024-10-28 00:20:41 +00:00
Dani ec8a7f0190
Add MyShojo (#5100) 2024-10-28 00:20:41 +00:00
Cuong-Tran 660f49ee96
fix (GoDa multitheme): avoid the case when no search result is found (#5098)
* fix (GoDa multitheme): avoid the case when no search result is found

* bump version
2024-10-28 00:20:41 +00:00
KirinRaikage 931d8fe2f6
Cleanup dead sources (#5094)
* Cleanup dead sources

* Add Myreadingmanga FR and Myrockmanga FR lang

* Bump myreadingmanga version
2024-10-28 00:20:41 +00:00
anenasa d1b65b192f
Comicabc: Fixes (#5089)
Update baseUrl
Fix popularMangaSelector
Fix popularMangaFromElement
Fix latestUpdatesSelector
Fix mangaDetailsParse
Fix chapterListSelector
Fix pageListParse
2024-10-28 00:20:41 +00:00
bapeey 5aa0cd2afa
Koinobori Scan: Update to custom theme (#5097)
* first attempt

* termux my beloved
2024-10-28 00:20:41 +00:00
Chopper 66eb921df6
Remove GoodGirlsScan (#5096) 2024-10-28 00:20:41 +00:00
anenasa a0715ec24a
Happymh: Fix Content-Type of images (#5090) 2024-10-28 00:20:41 +00:00
kana-shii 3ab425a549
Batoto title regex and alternative titles in description (#5038)
* custom regex

* Update BatoTo.kt

* Update BatoTo.kt

* Update BatoTo.kt

* remove custom regex

* Update BatoTo.kt

* Update BatoTo.kt
2024-10-28 00:20:41 +00:00
kana-shii 8d408422a4
Mangago regex edit (#5039)
* mangago custom regex

* Update build.gradle

* Update Mangago.kt

* Update Mangago.kt

* Update Mangago.kt

* Update Mangago.kt

* remove custom regex
2024-10-28 00:20:41 +00:00
bapeey 3a811582b0
Fix FoyScans (#5081)
* fix

* fix override on EternalMangas
2024-10-28 00:20:41 +00:00
bapeey 89f6711e2e
Add TecnoProjects (#5080)
add
2024-10-28 00:20:41 +00:00
sinkableShip e45ee68991
Pixiv Comic: update salt (#5078)
* update salt that used for loading images

* bump extVersionCode
2024-10-28 00:20:41 +00:00
Chopper 0cbd5cc915
Mangasail: Fix content loading (#5076)
Fix content loading
2024-10-28 00:20:41 +00:00
Vetle Ledaal 99605056e3
HentaiVN.plus: update domain, add override URL setting (#5073) 2024-10-28 00:20:41 +00:00
anenasa 0e08a7c946
Manwa: Fix page list (#5072) 2024-10-28 00:20:41 +00:00
932 changed files with 5908 additions and 3613 deletions

View File

@ -86,12 +86,9 @@ small, just do a normal full clone instead.**
```bash
git sparse-checkout set --cone --sparse-index
# add project folders
git sparse-checkout add .run buildSrc core gradle lib multisrc/src/main/java/generator
git sparse-checkout add buildSrc core gradle lib lib-multisrc
# add a single source
git sparse-checkout add src/<lang>/<source>
# add a multisrc theme
git sparse-checkout add multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/<source>
git sparse-checkout add multisrc/overrides/<source>
```
To remove a source, open `.git/info/sparse-checkout` and delete the exact
@ -112,13 +109,11 @@ small, just do a normal full clone instead.**
```bash
/*
!/src/*
!/multisrc/overrides/*
!/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/*
!/multisrc-lib/*
# allow a single source
/src/<lang>/<source>
# allow a multisrc theme
/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/<source>
/multisrc/overrides/<source>
/lib-multisrc/<source>
# or type the source name directly
<source>
```
@ -842,6 +837,15 @@ of `mitmweb`.
APKs can be created in Android Studio via `Build > Build Bundle(s) / APK(s) > Build APK(s)` or
`Build > Generate Signed Bundle / APK`.
If for some reason you decide to build the APK from the command line, you can use the following
command (because you're doing things differently than expected, I assume you have some
knowledge of gradlew and your OS):
```console
// For a single apk, use this command
$ ./gradlew src:<lang>:<source>:assembleDebug
```
## Submitting the changes
When you feel confident about your changes, submit a new Pull Request so your code can be reviewed

View File

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

View File

@ -4,7 +4,7 @@ coroutines_version = "1.6.4"
serialization_version = "1.4.0"
[libraries]
gradle-agp = { module = "com.android.tools.build:gradle", version = "8.4.1" }
gradle-agp = { module = "com.android.tools.build:gradle", version = "8.6.1" }
gradle-kotlin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin_version" }
gradle-serialization = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kotlin_version" }
gradle-kotlinter = { module = "org.jmailen.gradle:kotlinter-gradle", version = "3.13.0" }

Binary file not shown.

View File

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

7
gradlew generated vendored
View File

@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@ -84,7 +86,8 @@ done
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum

2
gradlew.bat generated vendored
View File

@ -13,6 +13,8 @@
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################

View File

@ -2,4 +2,4 @@ plugins {
id("lib-multisrc")
}
baseVersionCode = 2
baseVersionCode = 3

View File

@ -1,5 +1,6 @@
package eu.kanade.tachiyomi.multisrc.blogtruyen
import android.util.Log
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.source.model.Filter
@ -366,7 +367,11 @@ abstract class BlogTruyen(
)
Single.fromCallable {
try {
client.newCall(request).execute().close()
} catch (e: Exception) {
Log.e("BlogTruyen", "Error updating view count", e)
}
}
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())

View File

@ -2,4 +2,4 @@ plugins {
id("lib-multisrc")
}
baseVersionCode = 1
baseVersionCode = 2

View File

@ -39,7 +39,7 @@ open class GoDa(
override fun popularMangaParse(response: Response): MangasPage {
val document = response.asJsoup().also(::parseGenres)
val mangas = document.select(".cardlist .pb-2 a").map { element ->
val mangas = document.select(".container > .cardlist .pb-2 a").map { element ->
SManga.create().apply {
val imgSrc = element.selectFirst("img")!!.attr("src")
url = getKey(element.attr("href"))

View File

@ -2,7 +2,7 @@ plugins {
id("lib-multisrc")
}
baseVersionCode = 1
baseVersionCode = 2
dependencies {
api(project(":lib:cookieinterceptor"))

View File

@ -120,7 +120,7 @@ abstract class HotComics(
override fun chapterListParse(response: Response): List<SChapter> {
return response.asJsoup().select("#tab-chapter a").map { element ->
SChapter.create().apply {
setUrlWithoutDomain(element.absUrl("href"))
setUrlWithoutDomain(element.attr("onclick").substringAfter("popupLogin('").substringBefore("'"))
name = element.selectFirst(".cell-num")!!.text()
date_upload = parseDate(element.selectFirst(".cell-time")?.text())
}

View File

@ -2,4 +2,4 @@ plugins {
id("lib-multisrc")
}
baseVersionCode = 12
baseVersionCode = 14

View File

@ -4,29 +4,26 @@ import android.app.Application
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import androidx.preference.SwitchPreferenceCompat
import eu.kanade.tachiyomi.multisrc.kemono.KemonoCreatorDto.Companion.serviceName
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import eu.kanade.tachiyomi.source.ConfigurableSource
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream
import okhttp3.Call
import okhttp3.Callback
import okhttp3.Request
import okhttp3.Response
import okio.blackholeSink
import org.jsoup.select.Evaluator
import rx.Observable
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.io.IOException
import java.lang.Thread.sleep
import java.util.TimeZone
import kotlin.math.min
@ -51,6 +48,8 @@ open class Kemono(
private val imgCdnUrl = baseUrl.replace("//", "//img.")
private var mangasCache: List<KemonoCreatorDto> = emptyList()
private fun String.formatAvatarUrl(): String = removePrefix("https://").replaceBefore('/', imgCdnUrl)
override fun popularMangaRequest(page: Int) = throw UnsupportedOperationException()
@ -63,82 +62,123 @@ open class Kemono(
override fun fetchPopularManga(page: Int): Observable<MangasPage> {
return Observable.fromCallable {
fetchNewDesignListing(page, "/artists", compareByDescending { it.favorited })
searchMangas(page, sortBy = "pop" to "desc")
}
}
override fun fetchLatestUpdates(page: Int): Observable<MangasPage> {
return Observable.fromCallable {
fetchNewDesignListing(page, "/artists/updated", compareByDescending { it.updatedDate })
}
}
private fun fetchNewDesignListing(
page: Int,
path: String,
comparator: Comparator<KemonoCreatorDto>,
): MangasPage {
val baseUrl = baseUrl
return if (page == 1) {
val document = client.newCall(GET(baseUrl + path, headers)).execute().asJsoup()
val cardList = document.selectFirst(Evaluator.Class("card-list__items"))!!
val creators = cardList.children().map {
SManga.create().apply {
url = it.attr("href")
title = it.selectFirst(Evaluator.Class("user-card__name"))!!.ownText()
author = it.selectFirst(Evaluator.Class("user-card__service"))!!.ownText()
thumbnail_url = it.selectFirst(Evaluator.Tag("img"))!!.absUrl("src").formatAvatarUrl()
description = PROMPT
initialized = true
}
}.filterUnsupported()
MangasPage(creators, true).also { cacheCreators() }
} else {
fetchCreatorsPage(page) { it.apply { sortWith(comparator) } }
searchMangas(page, sortBy = "lat" to "desc")
}
}
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> = Observable.fromCallable {
if (query.isBlank()) throw Exception("Query is empty")
fetchCreatorsPage(page) { all ->
val result = all.filterTo(ArrayList()) { it.name.contains(query, ignoreCase = true) }
if (result.isEmpty()) return@fetchCreatorsPage emptyList()
if (result[0].favorited != -1) {
result.sortByDescending { it.favorited }
} else {
result.sortByDescending { it.updatedDate }
searchMangas(page, query, filters)
}
result
private fun searchMangas(page: Int = 1, title: String = "", filters: FilterList? = null, sortBy: Pair<String, String> = "" to ""): MangasPage {
var sort = sortBy
val typeIncluded: MutableList<String> = mutableListOf()
val typeExcluded: MutableList<String> = mutableListOf()
var fav: Boolean? = null
filters?.forEach { filter ->
when (filter) {
is SortFilter -> {
sort = filter.getValue() to if (filter.state!!.ascending) "asc" else "desc"
}
is TypeFilter -> {
filter.state.filter { state -> state.isIncluded() }.forEach { tri ->
typeIncluded.add(tri.value)
}
filter.state.filter { state -> state.isExcluded() }.forEach { tri ->
typeExcluded.add(tri.value)
}
}
is FavouritesFilter -> {
fav = when (filter.state[0].state) {
0 -> null
1 -> true
else -> false
}
}
else -> {}
}
}
private fun fetchCreatorsPage(
page: Int,
block: (ArrayList<KemonoCreatorDto>) -> List<KemonoCreatorDto>,
): MangasPage {
val imgCdnUrl = this.imgCdnUrl
var mangas = mangasCache
if (page == 1) {
var favourites: List<KemonoFavouritesDto> = emptyList()
if (fav != null) {
val favores = client.newCall(GET("$baseUrl/$apiPath/account/favorites", headers)).execute()
if (favores.code == 401) throw Exception("You are not Logged In")
favourites = favores.parseAs<List<KemonoFavouritesDto>>().filterNot { it.service.lowercase() == "discord" }
}
val response = client.newCall(GET("$baseUrl/$apiPath/creators", headers)).execute()
val allCreators = block(response.parseAs())
val count = allCreators.size
val fromIndex = (page - 1) * NEW_PAGE_SIZE
val toIndex = min(count, fromIndex + NEW_PAGE_SIZE)
val creators = allCreators.subList(fromIndex, toIndex)
.map { it.toSManga(imgCdnUrl) }
.filterUnsupported()
return MangasPage(creators, toIndex < count)
val allCreators = response.parseAs<List<KemonoCreatorDto>>().filterNot { it.service.lowercase() == "discord" }
mangas = allCreators.filter {
val includeType = typeIncluded.isEmpty() || typeIncluded.contains(it.service.serviceName().lowercase())
val excludeType = typeExcluded.isNotEmpty() && typeExcluded.contains(it.service.serviceName().lowercase())
val regularSearch = it.name.contains(title, true)
val isFavourited = when (fav) {
true -> favourites.any { f -> f.id == it.id.also { _ -> it.fav = f.faved_seq } }
false -> favourites.none { f -> f.id == it.id }
else -> true
}
private fun cacheCreators() {
val callback = object : Callback {
override fun onResponse(call: Call, response: Response) =
response.body.source().run {
readAll(blackholeSink())
close()
includeType && !excludeType && isFavourited &&
regularSearch
}.also { mangasCache = mangas }
}
override fun onFailure(call: Call, e: IOException) = Unit
val sorted = when (sort.first) {
"pop" -> {
if (sort.second == "desc") {
mangas.sortedByDescending { it.favorited }
} else {
mangas.sortedBy { it.favorited }
}
client.newCall(GET("$baseUrl/$apiPath/creators", headers)).enqueue(callback)
}
"tit" -> {
if (sort.second == "desc") {
mangas.sortedByDescending { it.name }
} else {
mangas.sortedBy { it.name }
}
}
"new" -> {
if (sort.second == "desc") {
mangas.sortedByDescending { it.id }
} else {
mangas.sortedBy { it.id }
}
}
"fav" -> {
if (fav != true) throw Exception("Please check 'Favourites Only' Filter")
if (sort.second == "desc") {
mangas.sortedByDescending { it.fav }
} else {
mangas.sortedBy { it.fav }
}
}
else -> {
if (sort.second == "desc") {
mangas.sortedByDescending { it.updatedDate }
} else {
mangas.sortedBy { it.updatedDate }
}
}
}
val maxIndex = mangas.size
val fromIndex = (page - 1) * PAGE_CREATORS_LIMIT
val toIndex = min(maxIndex, fromIndex + PAGE_CREATORS_LIMIT)
val final = sorted.subList(fromIndex, toIndex).map { it.toSManga(imgCdnUrl) }
return MangasPage(final, toIndex != maxIndex)
}
override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = throw UnsupportedOperationException()
@ -151,33 +191,38 @@ open class Kemono(
override fun mangaDetailsParse(response: Response) = throw UnsupportedOperationException()
override fun getChapterUrl(chapter: SChapter) = "$baseUrl${chapter.url.replace("$apiPath/", "")}"
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> = Observable.fromCallable {
KemonoPostDto.dateFormat.timeZone = when (manga.author) {
"Pixiv Fanbox", "Fantia" -> TimeZone.getTimeZone("GMT+09:00")
else -> TimeZone.getTimeZone("GMT")
}
val maxPosts = preferences.getString(POST_PAGES_PREF, POST_PAGES_DEFAULT)!!
.toInt().coerceAtMost(POST_PAGES_MAX) * POST_PAGE_SIZE
val prefMaxPost = preferences.getString(POST_PAGES_PREF, POST_PAGES_DEFAULT)!!
.toInt().coerceAtMost(POST_PAGES_MAX) * PAGE_POST_LIMIT
var offset = 0
var hasNextPage = true
val result = ArrayList<SChapter>()
while (offset < maxPosts && hasNextPage) {
val request = GET("$baseUrl/$apiPath${manga.url}?limit=$POST_PAGE_SIZE&o=$offset", headers)
while (offset < prefMaxPost && hasNextPage) {
val request = GET("$baseUrl/$apiPath${manga.url}?o=$offset", headers)
val page: List<KemonoPostDto> = retry(request).parseAs()
page.forEach { post -> if (post.images.isNotEmpty()) result.add(post.toSChapter()) }
offset += POST_PAGE_SIZE
hasNextPage = page.size == POST_PAGE_SIZE
offset += PAGE_POST_LIMIT
hasNextPage = page.size == PAGE_POST_LIMIT
}
result
}
private fun retry(request: Request): Response {
var code = 0
repeat(3) {
repeat(5) {
val response = client.newCall(request).execute()
if (response.isSuccessful) return response
response.close()
code = response.code
if (code == 429) {
sleep(10000)
}
}
throw Exception("HTTP error $code")
}
@ -217,10 +262,8 @@ open class Kemono(
key = POST_PAGES_PREF
title = "Maximum posts to load"
summary = "Loading more posts costs more time and network traffic.\nCurrently: %s"
entryValues = (1..POST_PAGES_MAX).map { it.toString() }.toTypedArray()
entries = (1..POST_PAGES_MAX).map {
if (it == 1) "1 page ($POST_PAGE_SIZE posts)" else "$it pages (${it * POST_PAGE_SIZE} posts)"
}.toTypedArray()
entryValues = Array(POST_PAGES_MAX) { (it + 1).toString() }
entries = Array(POST_PAGES_MAX) { "${(it + 1)} pages (${(it + 1) * PAGE_POST_LIMIT} posts)" }
setDefaultValue(POST_PAGES_DEFAULT)
}.let { screen.addPreference(it) }
@ -232,16 +275,55 @@ open class Kemono(
}.let(screen::addPreference)
}
// Filters
override fun getFilterList(): FilterList =
FilterList(
SortFilter(
"Sort by",
Filter.Sort.Selection(0, false),
getSortsList,
),
TypeFilter("Types", getTypes),
FavouritesFilter(),
)
open val getTypes: List<String> = emptyList()
open val getSortsList: List<Pair<String, String>> = listOf(
Pair("Popularity", "pop"),
Pair("Date Indexed", "new"),
Pair("Date Updated", "lat"),
Pair("Alphabetical Order", "tit"),
Pair("Service", "serv"),
Pair("Date Favourited", "fav"),
)
internal open class TypeFilter(name: String, vals: List<String>) :
Filter.Group<TriFilter>(
name,
vals.map { TriFilter(it, it.lowercase()) },
)
internal class FavouritesFilter() :
Filter.Group<TriFilter>(
"Favourites",
listOf(TriFilter("Favourites Only", "fav")),
)
internal open class TriFilter(name: String, val value: String) : Filter.TriState(name)
internal open class SortFilter(name: String, selection: Selection, private val vals: List<Pair<String, String>>) :
Filter.Sort(name, vals.map { it.first }.toTypedArray(), selection) {
fun getValue() = vals[state!!.index].second
}
companion object {
private const val NEW_PAGE_SIZE = 50
private const val PAGE_POST_LIMIT = 50
private const val PAGE_CREATORS_LIMIT = 50
const val PROMPT = "You can change how many posts to load in the extension preferences."
private const val POST_PAGE_SIZE = 50
private const val POST_PAGES_PREF = "POST_PAGES"
private const val POST_PAGES_DEFAULT = "1"
private const val POST_PAGES_MAX = 50
private fun List<SManga>.filterUnsupported() = filterNot { it.author == "Discord" }
private const val POST_PAGES_MAX = 75
// private const val BASE_URL_PREF = "BASE_URL"
private const val USE_LOW_RES_IMG = "USE_LOW_RES_IMG"

View File

@ -7,15 +7,23 @@ import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.double
import java.text.SimpleDateFormat
import java.util.Locale
@Serializable
class KemonoFavouritesDto(
val id: String,
val name: String,
val service: String,
val faved_seq: Long,
)
@Serializable
class KemonoCreatorDto(
private val id: String,
val id: String,
val name: String,
private val service: String,
val service: String,
private val updated: JsonPrimitive,
val favorited: Int = -1,
) {
var fav: Long = 0
val updatedDate get() = when {
updated.isString -> dateFormat.parse(updated.content)?.time ?: 0
else -> (updated.double * 1000).toLong()

View File

@ -0,0 +1,3 @@
pref_show_paid_chapter_title=Display paid chapters
pref_show_paid_chapter_summary_on=Paid chapters will appear.
pref_show_paid_chapter_summary_off=Only free chapters will be displayed.

View File

@ -2,4 +2,8 @@ plugins {
id("lib-multisrc")
}
baseVersionCode = 7
baseVersionCode = 8
dependencies {
api(project(":lib:i18n"))
}

View File

@ -1,6 +1,12 @@
package eu.kanade.tachiyomi.multisrc.keyoapp
import android.app.Application
import android.content.SharedPreferences
import androidx.preference.PreferenceScreen
import androidx.preference.SwitchPreferenceCompat
import eu.kanade.tachiyomi.lib.i18n.Intl
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.source.ConfigurableSource
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
@ -17,6 +23,8 @@ import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.text.ParseException
import java.text.SimpleDateFormat
@ -27,7 +35,12 @@ abstract class Keyoapp(
override val name: String,
override val baseUrl: String,
final override val lang: String,
) : ParsedHttpSource() {
) : ParsedHttpSource(), ConfigurableSource {
protected val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
override val supportsLatest = true
override val client = network.cloudflareClient
@ -39,6 +52,13 @@ abstract class Keyoapp(
private val dateFormat = SimpleDateFormat("MMM d, yyyy", Locale.ENGLISH)
protected val intl = Intl(
language = lang,
baseLanguage = "en",
availableLanguages = setOf("en"),
classLoader = this::class.java.classLoader!!,
)
// Popular
override fun popularMangaRequest(page: Int): Request = GET(baseUrl, headers)
@ -218,7 +238,12 @@ abstract class Keyoapp(
// Chapter list
override fun chapterListSelector(): String = "#chapters > a:not(:has(.text-sm span:matches(Upcoming)))"
override fun chapterListSelector(): String {
if (!preferences.showPaidChapters) {
return "#chapters > a:not(:has(.text-sm span:matches(Upcoming))):not(:has(img[src*=Coin.svg]))"
}
return "#chapters > a:not(:has(.text-sm span:matches(Upcoming)))"
}
override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply {
setUrlWithoutDomain(element.selectFirst("a[href]")!!.attr("href"))
@ -226,6 +251,9 @@ abstract class Keyoapp(
element.selectFirst(".text-xs")?.run {
date_upload = text().trim().parseDate()
}
if (element.select("img[src*=Coin.svg]").isNotEmpty()) {
name = "🔒 $name"
}
}
// Image list
@ -235,7 +263,7 @@ abstract class Keyoapp(
.map { it.attr("uid") }
.filter { it.isNotEmpty() }
.mapIndexed { index, img ->
Page(index, document.location(), "$cdnUrl/uploads/$img")
Page(index, document.location(), "$cdnUrl/$img")
}
.takeIf { it.isNotEmpty() }
?.also { return it }
@ -249,7 +277,7 @@ abstract class Keyoapp(
}
}
protected val cdnUrl = "https://cdn.igniscans.com"
protected open val cdnUrl = "https://2xffbs-cn8.is1.buzz/uploads"
private val oldImgCdnRegex = Regex("""^(https?:)?//cdn\d*\.keyoapp\.com""")
@ -315,4 +343,22 @@ abstract class Keyoapp(
}
return now.timeInMillis
}
override fun setupPreferenceScreen(screen: PreferenceScreen) {
SwitchPreferenceCompat(screen.context).apply {
key = SHOW_PAID_CHAPTERS_PREF
title = intl["pref_show_paid_chapter_title"]
summaryOn = intl["pref_show_paid_chapter_summary_on"]
summaryOff = intl["pref_show_paid_chapter_summary_off"]
setDefaultValue(SHOW_PAID_CHAPTERS_DEFAULT)
}.also(screen::addPreference)
}
protected val SharedPreferences.showPaidChapters: Boolean
get() = getBoolean(SHOW_PAID_CHAPTERS_PREF, SHOW_PAID_CHAPTERS_DEFAULT)
companion object {
private const val SHOW_PAID_CHAPTERS_PREF = "pref_show_paid_chap"
private const val SHOW_PAID_CHAPTERS_DEFAULT = false
}
}

View File

@ -0,0 +1,5 @@
plugins {
id("lib-multisrc")
}
baseVersionCode = 1

View File

@ -0,0 +1,143 @@
package eu.kanade.tachiyomi.multisrc.lectormoe
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.interceptor.rateLimitHost
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import uy.kohesive.injekt.injectLazy
abstract class LectorMoe(
override val name: String,
override val baseUrl: String,
override val lang: String,
private val organizationDomain: String = baseUrl.substringAfter("://"),
private val apiBaseUrl: String = "https://api.lector.moe",
) : HttpSource() {
override val supportsLatest = true
private val json: Json by injectLazy()
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.rateLimitHost(baseUrl.toHttpUrl(), 3)
.rateLimitHost(apiBaseUrl.toHttpUrl(), 3)
.build()
final override fun headersBuilder(): Headers.Builder = super.headersBuilder()
.add("Referer", "$baseUrl/")
private val apiHeaders: Headers = headersBuilder()
.add("Organization-Domain", organizationDomain)
.build()
override fun popularMangaRequest(page: Int): Request =
GET("$apiBaseUrl/api/manga-custom?page=$page&limit=$PAGE_LIMIT&order=popular", apiHeaders)
override fun popularMangaParse(response: Response): MangasPage = searchMangaParse(response)
override fun latestUpdatesRequest(page: Int): Request =
GET("$apiBaseUrl/api/manga-custom?page=$page&limit=$PAGE_LIMIT&order=latest", apiHeaders)
override fun latestUpdatesParse(response: Response): MangasPage = searchMangaParse(response)
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val url = "$apiBaseUrl/api/manga-custom".toHttpUrl().newBuilder()
url.setQueryParameter("page", page.toString())
url.setQueryParameter("limit", PAGE_LIMIT.toString())
filters.forEach { filter ->
when (filter) {
is SortByFilter -> url.setQueryParameter("order", filter.toUriPart())
else -> {}
}
}
if (query.isNotBlank()) url.setQueryParameter("title", query)
return GET(url.build(), apiHeaders)
}
override fun searchMangaParse(response: Response): MangasPage {
val page = response.request.url.queryParameter("page")!!.toInt()
val result = json.decodeFromString<Data<SeriesListDataDto>>(response.body.string())
val mangas = result.data.series.map { it.toSManga() }
val hasNextPage = page < result.data.maxPage
return MangasPage(mangas, hasNextPage)
}
override fun getFilterList() = FilterList(
SortByFilter("Ordenar por", getSortList()),
)
private fun getSortList() = arrayOf(
Pair("Popularidad", "popular"),
Pair("Recientes", "latest"),
)
override fun getMangaUrl(manga: SManga): String = "$baseUrl/manga/${manga.url}"
override fun mangaDetailsRequest(manga: SManga): Request =
GET("$apiBaseUrl/api/manga-custom/${manga.url}", apiHeaders)
override fun mangaDetailsParse(response: Response): SManga {
val result = json.decodeFromString<Data<SeriesDto>>(response.body.string())
return result.data.toSMangaDetails()
}
override fun getChapterUrl(chapter: SChapter): String {
val seriesSlug = chapter.url.substringBefore("/")
val chapterSlug = chapter.url.substringAfter("/")
return "$baseUrl/manga/$seriesSlug/chapters/$chapterSlug"
}
override fun chapterListRequest(manga: SManga): Request = mangaDetailsRequest(manga)
override fun chapterListParse(response: Response): List<SChapter> {
val result = json.decodeFromString<Data<SeriesDto>>(response.body.string())
val seriesSlug = result.data.slug
return result.data.chapters?.map { it.toSChapter(seriesSlug) } ?: emptyList()
}
override fun pageListRequest(chapter: SChapter): Request {
val seriesSlug = chapter.url.substringBefore("/")
val chapterSlug = chapter.url.substringAfter("/")
return GET("$apiBaseUrl/api/manga-custom/$seriesSlug/chapter/$chapterSlug/pages", apiHeaders)
}
override fun pageListParse(response: Response): List<Page> {
val result = json.decodeFromString<Data<List<PageDto>>>(response.body.string())
return result.data.mapIndexed { i, page ->
Page(i, imageUrl = page.imageUrl)
}
}
override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException()
class SortByFilter(title: String, list: Array<Pair<String, String>>) : UriPartFilter(title, list)
open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) :
Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
fun toUriPart() = vals[state].second
}
companion object {
private const val PAGE_LIMIT = 36
}
}

View File

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.extension.es.senshimanga
package eu.kanade.tachiyomi.multisrc.lectormoe
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga

View File

@ -2,4 +2,4 @@ plugins {
id("lib-multisrc")
}
baseVersionCode = 31
baseVersionCode = 32

View File

@ -5,6 +5,7 @@ import android.app.Application
import android.content.SharedPreferences
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.webkit.WebView
import android.webkit.WebViewClient
import android.widget.Toast
@ -102,18 +103,18 @@ abstract class LibGroup(
}
private var _constants: Constants? = null
private fun getConstants(): Constants {
private fun getConstants(): Constants? {
if (_constants == null) {
try {
_constants = client.newCall(
GET("$apiDomain/api/constants?fields[]=genres&fields[]=tags&fields[]=types&fields[]=scanlateStatus&fields[]=status&fields[]=format&fields[]=ageRestriction&fields[]=imageServers", headers),
).execute().parseAs<Data<Constants>>().data
return _constants!!
} catch (ex: SerializationException) {
throw Exception("Ошибка сериализации. Проверьте сайт.")
return _constants
} catch (ex: Exception) {
Log.d("LibGroup", "Error getting constants: $ex")
}
}
return _constants!!
return _constants
}
private fun checkForToken(chain: Interceptor.Chain): Response {
@ -376,7 +377,7 @@ abstract class LibGroup(
if (page.imageUrl != null) {
return Observable.just(page.imageUrl)
}
val server = getConstants().getServer(isServer(), siteId).url
val server = getConstants()?.getServer(isServer(), siteId)?.url ?: throw Exception("Ошибка получения сервера изображений")
return Observable.just("$server${page.url}")
}
@ -508,13 +509,13 @@ abstract class LibGroup(
filters += if (_constants != null) {
listOf(
CategoryList(getConstants().getCategories(siteId).map { CheckFilter(it.label, it.id.toString()) }),
FormatList(getConstants().getFormats(siteId).map { SearchFilter(it.name, it.id.toString()) }),
GenreList(getConstants().getGenres(siteId).map { SearchFilter(it.name, it.id.toString()) }),
TagList(getConstants().getTags(siteId).map { SearchFilter(it.name, it.id.toString()) }),
StatusList(getConstants().getScanlateStatuses(siteId).map { CheckFilter(it.label, it.id.toString()) }),
StatusTitleList(getConstants().getTitleStatuses(siteId).map { CheckFilter(it.label, it.id.toString()) }),
AgeList(getConstants().getAgeRestrictions(siteId).map { CheckFilter(it.label, it.id.toString()) }),
CategoryList(getConstants()!!.getCategories(siteId).map { CheckFilter(it.label, it.id.toString()) }),
FormatList(getConstants()!!.getFormats(siteId).map { SearchFilter(it.name, it.id.toString()) }),
GenreList(getConstants()!!.getGenres(siteId).map { SearchFilter(it.name, it.id.toString()) }),
TagList(getConstants()!!.getTags(siteId).map { SearchFilter(it.name, it.id.toString()) }),
StatusList(getConstants()!!.getScanlateStatuses(siteId).map { CheckFilter(it.label, it.id.toString()) }),
StatusTitleList(getConstants()!!.getTitleStatuses(siteId).map { CheckFilter(it.label, it.id.toString()) }),
AgeList(getConstants()!!.getAgeRestrictions(siteId).map { CheckFilter(it.label, it.id.toString()) }),
)
} else {
listOf(

View File

@ -42,6 +42,10 @@ abstract class MangaEsp(
classLoader = this::class.java.classLoader!!,
)
protected open val apiPath = "/api"
protected open val seriesPath = "/ver"
override val client: OkHttpClient = network.client.newBuilder()
.rateLimitHost(baseUrl.toHttpUrl(), 2)
.build()
@ -49,7 +53,7 @@ abstract class MangaEsp(
override fun headersBuilder(): Headers.Builder = Headers.Builder()
.add("Referer", "$baseUrl/")
override fun popularMangaRequest(page: Int): Request = GET("$apiBaseUrl/api/topSerie", headers)
override fun popularMangaRequest(page: Int): Request = GET("$apiBaseUrl$apiPath/topSerie", headers)
override fun popularMangaParse(response: Response): MangasPage {
val responseData = json.decodeFromString<TopSeriesDto>(response.body.string())
@ -58,17 +62,17 @@ abstract class MangaEsp(
val topWeekly = responseData.response.topWeekly.flatten().map { it.data }
val topMonthly = responseData.response.topMonthly.flatten().map { it.data }
val mangas = (topDaily + topWeekly + topMonthly).distinctBy { it.slug }.map { it.toSManga() }
val mangas = (topDaily + topWeekly + topMonthly).distinctBy { it.slug }.map { it.toSManga(seriesPath) }
return MangasPage(mangas, false)
}
override fun latestUpdatesRequest(page: Int): Request = GET("$apiBaseUrl/api/lastUpdates", headers)
override fun latestUpdatesRequest(page: Int): Request = GET("$apiBaseUrl$apiPath/lastUpdates", headers)
override fun latestUpdatesParse(response: Response): MangasPage {
val responseData = json.decodeFromString<LastUpdatesDto>(response.body.string())
val mangas = responseData.response.map { it.toSManga() }
val mangas = responseData.response.map { it.toSManga(seriesPath) }
return MangasPage(mangas, false)
}
@ -151,7 +155,7 @@ abstract class MangaEsp(
return MangasPage(
filteredList.subList((page - 1) * MANGAS_PER_PAGE, min(page * MANGAS_PER_PAGE, filteredList.size))
.map { it.toSManga() },
.map { it.toSManga(seriesPath) },
hasNextPage,
)
}
@ -171,7 +175,7 @@ abstract class MangaEsp(
?: throw Exception(intl["comic_data_error"])
val unescapedJson = mangaDetailsJson.unescape()
val series = json.decodeFromString<SeriesDto>(unescapedJson)
return series.chapters.map { it.toSChapter(series.slug) }
return series.chapters.map { it.toSChapter(seriesPath, series.slug) }
}
override fun pageListParse(response: Response): List<Page> {

View File

@ -48,11 +48,11 @@ class SeriesDto(
@SerialName("idioma")
val language: String? = null,
) {
fun toSManga(): SManga {
fun toSManga(seriesPath: String): SManga {
return SManga.create().apply {
title = name
thumbnail_url = thumbnail
url = "/ver/$slug"
url = "$seriesPath/$slug"
}
}
@ -104,7 +104,7 @@ class ChapterDto(
private val slug: String,
@SerialName("created_at") private val date: String,
) {
fun toSChapter(seriesSlug: String): SChapter {
fun toSChapter(seriesPath: String, seriesSlug: String): SChapter {
return SChapter.create().apply {
name = "Capítulo ${number.toString().removeSuffix(".0")}"
if (!this@ChapterDto.name.isNullOrBlank()) {
@ -115,7 +115,7 @@ class ChapterDto(
} catch (e: Exception) {
0L
}
url = "/ver/$seriesSlug/$slug"
url = "$seriesPath/$seriesSlug/$slug"
}
}

View File

@ -16,11 +16,11 @@ open class MCCMSConfig(
hasCategoryPage: Boolean = true,
val textSearchOnlyPageOne: Boolean = false,
val useMobilePageList: Boolean = false,
protected val lazyLoadImageAttr: String = "data-original",
private val lazyLoadImageAttr: String = "data-original",
) {
val genreData = GenreData(hasCategoryPage)
open fun pageListParse(response: Response): List<Page> {
fun pageListParse(response: Response): List<Page> {
val document = response.asJsoup()
return if (useMobilePageList) {

View File

@ -131,8 +131,8 @@ abstract class WPComics(
}
open fun String?.toStatus(): Int {
val ongoingWords = listOf("Ongoing", "Updating", "Đang tiến hành", "連載中")
val completedWords = listOf("Complete", "Completed", "Hoàn thành", "完結済み")
val ongoingWords = listOf("Ongoing", "Updating", "Đang tiến hành", "Đang cập nhật", "連載中")
val completedWords = listOf("Complete", "Completed", "Hoàn thành", "Đã hoàn thành", "完結済み")
return when {
this == null -> SManga.UNKNOWN
ongoingWords.doesInclude(this) -> SManga.ONGOING

View File

@ -114,7 +114,7 @@ abstract class ZeistManga(
val result = json.decodeFromString<ZeistMangaDto>(jsonString)
val mangas = result.feed?.entry.orEmpty()
.filter { it.category.orEmpty().any { category -> category.term == "Series" } } // Default category for all series
.filter { it.category.orEmpty().any { category -> category.term == mangaCategory } }
.filterNot { it.category.orEmpty().any { category -> excludedCategories.contains(category.term) } }
.map { it.toSManga(baseUrl) }

View File

@ -1,7 +1,7 @@
ext {
extName = 'Bato.to'
extClass = '.BatoToFactory'
extVersionCode = 37
extVersionCode = 41
isNsfw = true
}

View File

@ -53,7 +53,7 @@ open class BatoTo(
}
override val name: String = "Bato.to"
override val baseUrl: String = getMirrorPref()!!
override val baseUrl: String by lazy { getMirrorPref()!! }
override val id: Long = when (lang) {
"zh-Hans" -> 2818874445640189582
"zh-Hant" -> 38886079663327225
@ -88,12 +88,25 @@ open class BatoTo(
preferences.edit().putBoolean("${ALT_CHAPTER_LIST_PREF_KEY}_$lang", checkValue).commit()
}
}
val removeOfficialPref = CheckBoxPreference(screen.context).apply {
key = "${REMOVE_TITLE_VERSION_PREF}_$lang"
title = "Remove version information from entry titles"
summary = "This removes version tags like '(Official)' or '(Yaoi)' from entry titles " +
"and helps identify duplicate entries in your library. " +
"To update existing entries, remove them from your library (unfavorite) and refresh manually. " +
"You might also want to clear the database in advanced settings."
setDefaultValue(false)
}
screen.addPreference(mirrorPref)
screen.addPreference(altChapterListPref)
screen.addPreference(removeOfficialPref)
}
private fun getMirrorPref(): String? = preferences.getString("${MIRROR_PREF_KEY}_$lang", MIRROR_PREF_DEFAULT_VALUE)
private fun getAltChapterListPref(): Boolean = preferences.getBoolean("${ALT_CHAPTER_LIST_PREF_KEY}_$lang", ALT_CHAPTER_LIST_PREF_DEFAULT_VALUE)
private fun isRemoveTitleVersion(): Boolean {
return preferences.getBoolean("${REMOVE_TITLE_VERSION_PREF}_$lang", false)
}
override val supportsLatest = true
private val json: Json by injectLazy()
@ -309,23 +322,34 @@ open class BatoTo(
}
return super.mangaDetailsRequest(manga)
}
private var titleRegex: Regex =
Regex("(?:\\([^()]*\\)|\\{[^{}]*\\}|\\[(?:(?!]).)*]|«[^»]*»|〘[^〙]*〙|「[^」]*」|『[^』]*』|≪[^≫]*≫|﹛[^﹜]*﹜|〖[^〖〗]*〗|𖤍.+?𖤍|/.+?)\\s*|([|/~].*)|-.*-")
override fun mangaDetailsParse(document: Document): SManga {
val infoElement = document.select("div#mainer div.container-fluid")
val manga = SManga.create()
val workStatus = infoElement.select("div.attr-item:contains(original work) span").text()
val uploadStatus = infoElement.select("div.attr-item:contains(upload status) span").text()
manga.title = infoElement.select("h3").text().removeEntities()
val originalTitle = infoElement.select("h3").text().removeEntities()
val alternativeTitles = document.select("div.pb-2.alias-set.line-b-f").text()
val description = infoElement.select("div.limit-html").text() + "\n" +
infoElement.select(".episode-list > .alert-warning").text().trim()
val cleanedTitle = if (isRemoveTitleVersion()) {
originalTitle.replace(titleRegex, "").trim()
} else {
originalTitle
}
manga.title = cleanedTitle
manga.author = infoElement.select("div.attr-item:contains(author) span").text()
manga.artist = infoElement.select("div.attr-item:contains(artist) span").text()
manga.status = parseStatus(workStatus, uploadStatus)
manga.genre = infoElement.select(".attr-item b:contains(genres) + span ").joinToString { it.text() }
manga.description = infoElement.select("div.limit-html").text() + "\n" + infoElement.select(".episode-list > .alert-warning").text().trim()
manga.thumbnail_url = document.select("div.attr-cover img")
.attr("abs:src")
manga.description = description +
if (alternativeTitles.isNotBlank()) "\n\nAlternative Titles:\n$alternativeTitles" else ""
manga.thumbnail_url = document.select("div.attr-cover img").attr("abs:src")
return manga
}
private fun parseStatus(workStatus: String?, uploadStatus: String?) = when {
workStatus == null -> SManga.UNKNOWN
workStatus.contains("Ongoing") -> SManga.ONGOING
@ -945,6 +969,7 @@ open class BatoTo(
companion object {
private const val MIRROR_PREF_KEY = "MIRROR"
private const val MIRROR_PREF_TITLE = "Mirror"
private const val REMOVE_TITLE_VERSION_PREF = "REMOVE_TITLE_VERSION"
private val MIRROR_PREF_ENTRIES = arrayOf(
"bato.to",
"batocomic.com",
@ -962,6 +987,8 @@ open class BatoTo(
"readtoto.net",
"readtoto.org",
"dto.to",
"fto.to",
"jto.to",
"hto.to",
"mto.to",
"wto.to",

View File

@ -1,7 +1,7 @@
ext {
extName = 'Comick'
extClass = '.ComickFactory'
extVersionCode = 47
extVersionCode = 48
isNsfw = true
}

View File

@ -329,7 +329,11 @@ abstract class Comick(
is TagFilter -> {
if (it.state.isNotEmpty()) {
it.state.split(",").forEach {
addQueryParameter("tags", it.trim().lowercase().replace(SPACE_AND_SLASH_REGEX, "-").replace("'-", "-and-039-").replace("'", "-and-039-"))
addQueryParameter(
"tags",
it.trim().lowercase().replace(SPACE_AND_SLASH_REGEX, "-")
.replace("'-", "-and-039-").replace("'", "-and-039-"),
)
}
}
}
@ -372,29 +376,26 @@ abstract class Comick(
private fun mangaDetailsParse(response: Response, manga: SManga): SManga {
val mangaData = response.parseAs<Manga>()
if (!preferences.updateCover && manga.thumbnail_url != mangaData.comic.cover) {
if (manga.thumbnail_url.toString().endsWith("#1")) {
return mangaData.toSManga(
includeMuTags = preferences.includeMuTags,
scorePosition = preferences.scorePosition,
covers = listOf(
MDcovers(
b2key = manga.thumbnail_url?.substringBeforeLast("#")
?.substringAfterLast("/"),
vol = "1",
),
),
)
}
val coversUrl =
"$apiUrl/comic/${mangaData.comic.slug ?: mangaData.comic.hid}/covers?tachiyomi=true"
val covers = client.newCall(GET(coversUrl)).execute()
.parseAs<Covers>().mdCovers.reversed()
.parseAs<Covers>().mdCovers.reversed().toMutableList()
if (covers.any { it.vol == "1" }) covers.retainAll { it.vol == "1" }
if (
covers.any { it.locale == comickLang.split('-').first() }
) {
covers.retainAll { it.locale == comickLang.split('-').first() }
}
return mangaData.toSManga(
includeMuTags = preferences.includeMuTags,
covers = if (covers.any { it.vol == "1" }) covers.filter { it.vol == "1" } else covers,
scorePosition = preferences.scorePosition,
covers = covers,
)
}
return mangaData.toSManga(includeMuTags = preferences.includeMuTags)
return mangaData.toSManga(
includeMuTags = preferences.includeMuTags,
scorePosition = preferences.scorePosition,
)
}
override fun getMangaUrl(manga: SManga): String {
@ -511,12 +512,12 @@ abstract class Comick(
private val SPACE_AND_SLASH_REGEX = Regex("[ /]")
private const val IGNORED_GROUPS_PREF = "IgnoredGroups"
private const val INCLUDE_MU_TAGS_PREF = "IncludeMangaUpdatesTags"
private const val INCLUDE_MU_TAGS_DEFAULT = false
const val INCLUDE_MU_TAGS_DEFAULT = false
private const val MIGRATED_IGNORED_GROUPS = "MigratedIgnoredGroups"
private const val FIRST_COVER_PREF = "DefaultCover"
private const val FIRST_COVER_DEFAULT = true
private const val SCORE_POSITION_PREF = "ScorePosition"
private const val SCORE_POSITION_DEFAULT = "top"
const val SCORE_POSITION_DEFAULT = "top"
private const val LIMIT = 20
private const val CHAPTERS_LIMIT = 99999
}

View File

@ -1,5 +1,7 @@
package eu.kanade.tachiyomi.extension.all.comickfun
import eu.kanade.tachiyomi.extension.all.comickfun.Comick.Companion.INCLUDE_MU_TAGS_DEFAULT
import eu.kanade.tachiyomi.extension.all.comickfun.Comick.Companion.SCORE_POSITION_DEFAULT
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import kotlinx.serialization.SerialName
@ -31,8 +33,8 @@ class Manga(
private val demographic: String? = null,
) {
fun toSManga(
includeMuTags: Boolean = false,
scorePosition: String = "",
includeMuTags: Boolean = INCLUDE_MU_TAGS_DEFAULT,
scorePosition: String = SCORE_POSITION_DEFAULT,
covers: List<MDcovers>? = null,
) =
SManga.create().apply {
@ -148,6 +150,7 @@ class Covers(
class MDcovers(
val b2key: String?,
val vol: String? = null,
val locale: String? = null,
)
@Serializable

View File

@ -2,4 +2,10 @@ package eu.kanade.tachiyomi.extension.all.coomer
import eu.kanade.tachiyomi.multisrc.kemono.Kemono
class Coomer : Kemono("Coomer", "https://coomer.su", "all")
class Coomer : Kemono("Coomer", "https://coomer.su", "all") {
override val getTypes = listOf(
"OnlyFans",
"Fansly",
"CandFans",
)
}

View File

@ -1,7 +1,7 @@
ext {
extName = 'CosplayTele'
extClass = '.CosplayTele'
extVersionCode = 2
extVersionCode = 3
isNsfw = true
}

View File

@ -66,17 +66,17 @@ class CosplayTele : ParsedHttpSource() {
}
private val popularPageLimit = 20
override fun popularMangaRequest(page: Int) = GET("$baseUrl/wp-json/wordpress-popular-posts/v1/popular-posts?offset=${page * popularPageLimit}&limit=$popularPageLimit&range=last7days")
override fun popularMangaRequest(page: Int) = GET("$baseUrl/wp-json/wordpress-popular-posts/v1/popular-posts?offset=${page * popularPageLimit}&limit=$popularPageLimit&range=last7days&embed=true&_embed=wp:featuredmedia&_fields=title,link,_embedded,_links.wp:featuredmedia")
override fun popularMangaSelector(): String = ""
override fun popularMangaParse(response: Response): MangasPage {
val jsonObject = json.decodeFromString<JsonArray>(response.body.string())
val mangas = jsonObject.map { item ->
val head = item.jsonObject["yoast_head_json"]!!.jsonObject
val respObject = json.decodeFromString<JsonArray>(response.body.string())
val mangas = respObject.map { item ->
SManga.create().apply {
title = head["og_title"]!!.jsonPrimitive.content
thumbnail_url = head["og_image"]!!.jsonArray[0].jsonObject["url"]!!.jsonPrimitive.content
setUrlWithoutDomain(head["og_url"]!!.jsonPrimitive.content)
title = item.jsonObject!!["title"]!!.jsonObject!!["rendered"]!!.jsonPrimitive.content
thumbnail_url = item.jsonObject!!["_embedded"]!!.jsonObject!!["wp:featuredmedia"]!!.jsonArray[0]!!.jsonObject["source_url"]!!.jsonPrimitive.content
setUrlWithoutDomain(item.jsonObject!!["link"]!!.jsonPrimitive.content)
}
}
return MangasPage(mangas, mangas.size >= popularPageLimit)

View File

@ -8,7 +8,7 @@ import android.util.Log
import kotlin.system.exitProcess
/**
* Springboard that accepts https://e-hentai.net/g/xxxxx/yyyyy/ intents and redirects them to
* Springboard that accepts https://e-hentai.org/g/xxxxx/yyyyy/ intents and redirects them to
* the main Tachiyomi process.
*/
class EHUrlActivity : Activity() {

View File

@ -32,14 +32,14 @@ open class EternalMangas(
val mangas = (topDaily + topWeekly + topMonthly).distinctBy { it.slug }
.filter { it.language == internalLang }
.map { it.toSManga() }
.map { it.toSManga(seriesPath) }
return MangasPage(mangas, false)
}
override fun latestUpdatesParse(response: Response): MangasPage {
val responseData = json.decodeFromString<LatestUpdatesDto>(response.body.string())
val mangas = responseData.updates[internalLang]?.flatten()?.map { it.toSManga() } ?: emptyList()
val mangas = responseData.updates[internalLang]?.flatten()?.map { it.toSManga(seriesPath) } ?: emptyList()
return MangasPage(mangas, false)
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

View File

@ -1,167 +0,0 @@
package eu.kanade.tachiyomi.extension.all.hentaicafe
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.network.interceptor.rateLimitHost
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.model.UpdateStrategy
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import rx.Observable
class HentaiCafe : ParsedHttpSource() {
override val name = "Hentai Cafe"
override val baseUrl = "https://hentaicafe.xxx"
override val lang = "all"
override val supportsLatest = true
override val client by lazy {
network.client.newBuilder()
.rateLimitHost(baseUrl.toHttpUrl(), 2)
// Image CDN
.rateLimitHost("https://cdn.hentaibomb.com".toHttpUrl(), 2)
.build()
}
override fun headersBuilder() = super.headersBuilder()
.add("Referer", "$baseUrl/")
.add("Accept-Language", "en-US,en;q=0.5")
// ============================== Popular ===============================
override fun popularMangaRequest(page: Int) = GET(baseUrl, headers)
override fun popularMangaSelector() = "div.index-popular > div.gallery > a"
override fun popularMangaFromElement(element: Element) = SManga.create().apply {
setUrlWithoutDomain(element.attr("href"))
thumbnail_url = element.selectFirst("img")?.getImageUrl()
title = element.selectFirst("div.caption")!!.text()
}
override fun popularMangaNextPageSelector() = null
// =============================== Latest ===============================
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/?page=$page", headers)
override fun latestUpdatesSelector() = "div.index-container:contains(new uploads) > div.gallery > a"
override fun latestUpdatesFromElement(element: Element) = popularMangaFromElement(element)
override fun latestUpdatesNextPageSelector() = "section.pagination > a.last:not(.disabled)"
// =============================== Search ===============================
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
return if (query.startsWith(PREFIX_SEARCH)) { // URL intent handler
val id = query.removePrefix(PREFIX_SEARCH)
client.newCall(GET("$baseUrl/g/$id"))
.asObservableSuccess()
.map(::searchMangaByIdParse)
} else {
super.fetchSearchManga(page, query, filters)
}
}
private fun searchMangaByIdParse(response: Response): MangasPage {
val details = mangaDetailsParse(response.use { it.asJsoup() })
return MangasPage(listOf(details), false)
}
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val url = "$baseUrl/search".toHttpUrl().newBuilder()
.addQueryParameter("q", query)
.addQueryParameter("page", page.toString())
.build()
return GET(url, headers)
}
override fun searchMangaSelector() = "div.index-container > div.gallery > a"
override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element)
override fun searchMangaNextPageSelector() = latestUpdatesNextPageSelector()
// =========================== Manga Details ============================
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
thumbnail_url = document.selectFirst("#cover > a > img")?.getImageUrl()
with(document.selectFirst("div#bigcontainer > div > div#info")!!) {
title = selectFirst("h1.title")!!.text()
artist = getInfo("Artists")
genre = getInfo("Tags")
description = buildString {
select(".title > span").eachText().joinToString("\n").also {
append("Full titles:\n$it\n")
}
getInfo("Groups")?.also { append("\nGroups: $it") }
getInfo("Languages")?.also { append("\nLanguages: $it") }
getInfo("Parodies")?.also { append("\nParodies: $it") }
getInfo("Pages")?.also { append("\nPages: $it") }
}
}
status = SManga.COMPLETED
update_strategy = UpdateStrategy.ONLY_FETCH_ONCE
}
private fun Element.getInfo(item: String) =
select("div.field-name:containsOwn($item) a.tag > span.name")
.eachText()
.takeUnless { it.isEmpty() }
?.joinToString()
// ============================== Chapters ==============================
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
val chapter = SChapter.create().apply {
url = manga.url
name = "Chapter"
chapter_number = 1F
}
return Observable.just(listOf(chapter))
}
override fun chapterListSelector(): String {
throw UnsupportedOperationException()
}
override fun chapterFromElement(element: Element): SChapter {
throw UnsupportedOperationException()
}
// =============================== Pages ================================
override fun pageListParse(document: Document): List<Page> {
return document.select("div.thumbs a.gallerythumb > img").mapIndexed { index, item ->
val url = item.getImageUrl()
// Show original images instead of previews
val imageUrl = url.substringBeforeLast('/') + "/" + url.substringAfterLast('/').replace("t.", ".")
Page(index, "", imageUrl)
}
}
override fun imageUrlParse(document: Document): String {
throw UnsupportedOperationException()
}
// ============================= Utilities ==============================
private fun Element.getImageUrl() = absUrl("data-src").ifEmpty { absUrl("src") }
companion object {
const val PREFIX_SEARCH = "id:"
}
}

View File

@ -2,4 +2,15 @@ package eu.kanade.tachiyomi.extension.all.kemono
import eu.kanade.tachiyomi.multisrc.kemono.Kemono
class Kemono : Kemono("Kemono", "https://kemono.su", "all")
class Kemono : Kemono("Kemono", "https://kemono.su", "all") {
override val getTypes = listOf(
"Patreon",
"Pixiv Fanbox",
"Discord",
"Fantia",
"Afdian",
"Boosty",
"Gumroad",
"SubscribeStar",
)
}

View File

@ -1,7 +1,7 @@
ext {
extName = 'Komga'
extClass = '.KomgaFactory'
extVersionCode = 57
extVersionCode = 58
}
apply from: "$rootDir/common.gradle"

View File

@ -251,6 +251,10 @@ open class Komga(private val suffix: String = "") : ConfigurableSource, Unmetere
override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException()
override fun imageRequest(page: Page): Request {
return GET(page.imageUrl!!, headers = headersBuilder().add("Accept", "image/*,*/*;q=0.8").build())
}
override fun getFilterList(): FilterList {
fetchFilterOptions()

View File

@ -2,8 +2,8 @@ ext {
extName = 'Miau Scan'
extClass = '.MiauScanFactory'
themePkg = 'mangathemesia'
baseUrl = 'https://lectormiau.com'
overrideVersionCode = 4
baseUrl = 'https://zonamiau.com'
overrideVersionCode = 5
}
apply from: "$rootDir/common.gradle"

View File

@ -20,7 +20,7 @@ class MiauScanFactory : SourceFactory {
open class MiauScan(lang: String) : MangaThemesia(
name = "Miau Scan",
baseUrl = "https://lectormiau.com",
baseUrl = "https://zonamiau.com",
lang = lang,
dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("es")),
) {

View File

@ -1,7 +1,7 @@
ext {
extName = 'MyReadingManga'
extClass = '.MyReadingMangaFactory'
extVersionCode = 50
extVersionCode = 53
isNsfw = true
}

View File

@ -147,7 +147,7 @@ open class MyReadingManga(override val lang: String, private val siteLang: Strin
title = cleanTitle(document.select("h1").text())
author = cleanAuthor(document.select("h1").text())
artist = author
genre = document.select(".entry-header p a[href*=genre]").joinToString { it.text() }
genre = document.select(".entry-header p a[href*=genre], [href*=tag], span.entry-categories a").joinToString { it.text() }
val basicDescription = document.select("h1").text()
// too troublesome to achieve 100% accuracy assigning scanlator group during chapterListParse
val scanlatedBy = document.select(".entry-terms:has(a[href*=group])").firstOrNull()

View File

@ -23,7 +23,7 @@ private val languageList = listOf(
// Source("", "Finnish"),
// Source("", "Flemish", "flemish-dutch"),
// Source("", "Dutch"),
Source("fr", "French"),
// Source("fr", "French"),
Source("de", "German"),
// Source("", "Greek"),
// Source("", "Hebrew"),

View File

@ -9,7 +9,6 @@ class MyRockMangaFactory : SourceFactory {
OtakuSanctuary("MyRockManga", "https://myrockmanga.com", "vi"),
OtakuSanctuary("MyRockManga", "https://myrockmanga.com", "en"),
OtakuSanctuary("MyRockManga", "https://myrockmanga.com", "it"),
OtakuSanctuary("MyRockManga", "https://myrockmanga.com", "fr"),
OtakuSanctuary("MyRockManga", "https://myrockmanga.com", "es"),
)
}

View File

@ -1,7 +1,7 @@
ext {
extName = 'Photos18'
extClass = '.Photos18'
extVersionCode = 3
extVersionCode = 4
isNsfw = true
}

View File

@ -88,8 +88,8 @@ class Photos18 : HttpSource(), ConfigurableSource {
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
val chapter = SChapter.create().apply {
url = manga.url
name = manga.title
chapter_number = -2f
name = "Gallery"
chapter_number = 0f
}
return Observable.just(listOf(chapter))
}

View File

@ -1,7 +1,7 @@
ext {
extName = 'Pixiv'
extClass = '.PixivFactory'
extVersionCode = 8
extVersionCode = 9
isNsfw = true
}

View File

@ -86,7 +86,7 @@ class Pixiv(override val lang: String) : HttpSource() {
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
val filters = filters.list as PixivFilters
val hash = Pair(query, filters).hashCode()
val hash = Pair(query, filters.toList()).hashCode()
if (hash != searchHash || page == 1) {
searchHash = hash

View File

@ -1,25 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<activity
android:name=".all.unionmangas.UnionMangasUrlActivity"
android:excludeFromRecents="true"
android:exported="true"
android:theme="@android:style/Theme.NoDisplay">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:host="unionmangasbr.org" />
<data android:scheme="https"/>
<data android:pathPattern="/manga-br/..*"/>
<data android:scheme="https"/>
<data android:pathPattern="/italy/..*"/>
</intent-filter>
</activity>
</application>
</manifest>

View File

@ -1,12 +0,0 @@
ext {
extName = 'Union Mangas'
extClass = '.UnionMangasFactory'
extVersionCode = 6
isNsfw = true
}
apply from: "$rootDir/common.gradle"
dependencies {
implementation(project(':lib:cryptoaes'))
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

View File

@ -1,210 +0,0 @@
package eu.kanade.tachiyomi.extension.all.unionmangas
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
import okhttp3.Response
import rx.Observable
import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat
import java.util.Locale
class UnionMangas(private val langOption: LanguageOption) : HttpSource() {
override val lang = langOption.lang
override val name: String = "Union Mangas"
override val baseUrl: String = "https://unionmangasbr.org"
override val supportsLatest = true
private val json: Json by injectLazy()
override val client = network.client.newBuilder()
.rateLimit(2)
.build()
override fun headersBuilder(): Headers.Builder = super.headersBuilder()
.set("Referer", "$baseUrl/")
override fun chapterListParse(response: Response) = throw UnsupportedOperationException()
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
val chapters = mutableListOf<SChapter>()
var currentPage = 0
do {
val chaptersDto = fetchChapterListPageable(manga, currentPage)
chapters += chaptersDto.data.map { chapter ->
SChapter.create().apply {
name = chapter.name
date_upload = chapter.date.toDate()
url = chapter.toChapterUrl(langOption.infix)
}
}
currentPage++
} while (chaptersDto.hasNextPage())
return Observable.just(chapters)
}
private fun fetchChapterListPageable(manga: SManga, page: Int): Pageable<ChapterDto> {
manga.apply {
url = getURLCompatibility(url)
}
val maxResult = 16
val url = "$apiUrl/${langOption.infix}/GetChapterListFilter/${manga.slug()}/$maxResult/$page/all/ASC"
return client.newCall(GET(url, headers)).execute()
.parseAs<Pageable<ChapterDto>>()
}
override fun latestUpdatesParse(response: Response) = popularMangaParse(response)
override fun latestUpdatesRequest(page: Int): Request {
val maxResult = 24
val url = "$apiUrl/${langOption.infix}/HomeLastUpdate".toHttpUrl().newBuilder()
.addPathSegment("$maxResult")
.addPathSegment("${page - 1}")
.build()
return GET(url, headers)
}
override fun getMangaUrl(manga: SManga): String {
manga.apply {
url = getURLCompatibility(url)
}
return baseUrl + manga.url.replace(langOption.infix, langOption.mangaSubstring)
}
override fun mangaDetailsRequest(manga: SManga): Request {
manga.apply {
url = getURLCompatibility(url)
}
val url = "$apiUrl/${langOption.infix}/getInfoManga".toHttpUrl().newBuilder()
.addPathSegment(manga.slug())
.build()
return GET(url, headers)
}
override fun mangaDetailsParse(response: Response): SManga {
val dto = response.parseAs<MangaDetailsDto>()
return mangaParse(dto.details)
}
override fun pageListRequest(chapter: SChapter): Request {
val chapterSlug = getURLCompatibility(chapter.url)
.substringAfter(langOption.infix)
val url = "$apiUrl/${langOption.infix}/GetImageChapter$chapterSlug"
return GET(url, headers)
}
override fun pageListParse(response: Response): List<Page> {
val location = response.request.url.toString()
val dto = response.parseAs<PageDto>()
return dto.pages.mapIndexed { index, url ->
Page(index, location, imageUrl = url)
}
}
override fun popularMangaParse(response: Response): MangasPage {
val dto = response.parseAs<Pageable<MangaDto>>()
val mangas = dto.data.map(::mangaParse)
return MangasPage(
mangas = mangas,
hasNextPage = dto.hasNextPage(),
)
}
override fun popularMangaRequest(page: Int): Request {
val maxResult = 24
return GET("$apiUrl/${langOption.infix}/HomeTopFllow/$maxResult/${page - 1}")
}
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val maxResult = 20
val url = "$apiUrl/${langOption.infix}/QuickSearch/".toHttpUrl().newBuilder()
.addPathSegment(query)
.addPathSegment("$maxResult")
.build()
return GET(url, headers)
}
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
if (query.startsWith(SEARCH_PREFIX)) {
val url = "$baseUrl/${langOption.infix}/${query.substringAfter(SEARCH_PREFIX)}"
return client.newCall(GET(url, headers))
.asObservableSuccess().map { response ->
val mangas = try { listOf(mangaDetailsParse(response)) } catch (_: Exception) { emptyList() }
MangasPage(mangas, false)
}
}
return super.fetchSearchManga(page, query, filters)
}
override fun imageUrlParse(response: Response): String = ""
override fun searchMangaParse(response: Response): MangasPage {
val dto = response.parseAs<SearchDto>()
return MangasPage(
dto.mangas.map(::mangaParse),
false,
)
}
/*
* Keeps compatibility with pt-BR previous version
* */
private fun getURLCompatibility(url: String): String {
val slugSuffix = "-br"
val mangaSubString = "manga-br"
val oldSlug = url.substringAfter(mangaSubString)
.substring(1)
.split("/")
.first()
val newSlug = oldSlug.substringBeforeLast(slugSuffix)
return url.replace(oldSlug, newSlug)
}
private inline fun <reified T> Response.parseAs(): T {
return json.decodeFromString(body.string())
}
private fun SManga.slug() = this.url.split("/").last()
private fun mangaParse(dto: MangaDto): SManga {
return SManga.create().apply {
title = dto.title
thumbnail_url = dto.thumbnailUrl
status = dto.status
url = "/${langOption.infix}/${dto.slug}"
genre = dto.genres
initialized = true
}
}
private fun String.toDate(): Long =
try { dateFormat.parse(trim())!!.time } catch (_: Exception) { 0L }
companion object {
const val SEARCH_PREFIX = "slug:"
val apiUrl = "https://api.novelfull.us/api"
val oldApiUrl = "https://api.unionmanga.xyz"
val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSS", Locale.ENGLISH)
}
}

View File

@ -1,68 +0,0 @@
package eu.kanade.tachiyomi.extension.all.unionmangas
import eu.kanade.tachiyomi.source.model.SManga
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
class MangaDetailsDto(private val data: Props) {
val details: MangaDto get() = data.details
@Serializable
class Props(
@SerialName("infoDoc") val details: MangaDto,
)
}
@Serializable
open class Pageable<T>(
var currentPage: Int,
var totalPage: Int,
val data: List<T>,
) {
fun hasNextPage() = (currentPage + 1) <= totalPage
}
@Serializable
class ChapterDto(
val date: String,
@SerialName("idDoc") val slugManga: String,
@SerialName("idDetail") val id: String,
@SerialName("nameChapter") val name: String,
) {
fun toChapterUrl(lang: String) = "/$lang/${this.slugManga}/$id"
}
@Serializable
class MangaDto(
@SerialName("name") val title: String,
@SerialName("image") private val _thumbnailUrl: String,
@SerialName("idDoc") val slug: String,
@SerialName("genresName") val genres: String,
@SerialName("status") val _status: String,
) {
val thumbnailUrl get() = "${UnionMangas.oldApiUrl}$_thumbnailUrl"
val status get() = when (_status) {
"ongoing" -> SManga.ONGOING
"completed" -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
}
@Serializable
class SearchDto(
@SerialName("data")
val mangas: List<MangaDto>,
)
@Serializable
class PageDto(val `data`: Data) {
val pages: List<String> get() = `data`.detailDocuments.source.split("#")
@Serializable
class Data(@SerialName("detail_documents") val detailDocuments: DetailDocuments)
@Serializable
class DetailDocuments(val source: String)
}

View File

@ -1,15 +0,0 @@
package eu.kanade.tachiyomi.extension.all.unionmangas
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceFactory
class UnionMangasFactory : SourceFactory {
override fun createSources(): List<Source> = languages.map { UnionMangas(it) }
}
class LanguageOption(val lang: String, val infix: String = lang, val mangaSubstring: String = infix)
val languages = listOf(
LanguageOption("pt-BR", "manga-br"),
LanguageOption("ru", "manga-ru", "mangas"),
)

View File

@ -2,8 +2,9 @@ ext {
extName = 'Area Manga'
extClass = '.AreaManga'
themePkg = 'mangathemesia'
baseUrl = 'https://www.areascans.net'
overrideVersionCode = 0
baseUrl = 'https://ar.kenmanga.com'
overrideVersionCode = 1
isNsfw = false
}
apply from: "$rootDir/common.gradle"

View File

@ -6,7 +6,7 @@ import java.util.Locale
class AreaManga : MangaThemesia(
"أريا مانجا",
"https://www.areascans.net",
"https://ar.kenmanga.com",
"ar",
dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("ar")),
)

View File

@ -2,8 +2,8 @@ ext {
extName = 'MangaNoon'
extClass = '.MangaNoon'
themePkg = 'mangathemesia'
baseUrl = 'https://manjanoon.xyz'
overrideVersionCode = 5
baseUrl = 'https://axztu.com'
overrideVersionCode = 7
isNsfw = false
}

View File

@ -7,7 +7,7 @@ import java.util.Calendar
class MangaNoon : MangaThemesia(
"مانجا نون",
"https://manjanoon.xyz",
"https://axztu.com",
"ar",
) {

View File

@ -2,8 +2,8 @@ ext {
extName = 'MangaSwat'
extClass = '.MangaSwat'
themePkg = 'mangathemesia'
baseUrl = 'https://healteer.com'
overrideVersionCode = 23
baseUrl = 'https://swatscans.com'
overrideVersionCode = 24
}
apply from: "$rootDir/common.gradle"

View File

@ -22,7 +22,7 @@ import java.util.Locale
class MangaSwat :
MangaThemesia(
"MangaSwat",
"https://healteer.com",
"https://swatscans.com",
"ar",
dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("ar")),
),

View File

@ -2,4 +2,12 @@ package eu.kanade.tachiyomi.extension.ar.scans4u
import eu.kanade.tachiyomi.multisrc.keyoapp.Keyoapp
class Scans4u : Keyoapp("Scans 4u", "https://4uscans.com", "ar")
class Scans4u : Keyoapp("Scans 4u", "https://4uscans.com", "ar") {
override fun chapterListSelector(): String {
if (!preferences.showPaidChapters) {
return "#chapters > a:not(:has(.text-sm span:matches(قادم))):not(:has(img[src*=Coin.svg]))"
}
return "#chapters > a:not(:has(.text-sm span:matches(قادم)))"
}
}

View File

@ -1,9 +0,0 @@
ext {
extName = 'Toomics.Top'
extClass = '.ToomicsTop'
themePkg = 'hotcomics'
overrideVersionCode = 0
isNsfw = true
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@ -1,44 +0,0 @@
package eu.kanade.tachiyomi.extension.de.toomicstop
import eu.kanade.tachiyomi.multisrc.hotcomics.HotComics
import eu.kanade.tachiyomi.source.model.MangasPage
import okhttp3.Response
class ToomicsTop : HotComics(
"Toomics.Top",
"de",
"https://toomics.top",
) {
override fun searchMangaParse(response: Response): MangasPage {
val mangasPage = super.searchMangaParse(response)
mangasPage.mangas.apply {
for (i in indices) {
this[i].url = this[i].url.replace(urlIdRegex, ".html")
}
}
return mangasPage
}
private val urlIdRegex = Regex("""(/\w+).html""")
override val browseList = listOf(
Pair("Home", "en"),
Pair("Weekly", "en/weekly"),
Pair("New", "en/new"),
Pair("Genre: All", "en/genres"),
Pair("Genre: Romantik", "en/genres/Romantik"),
Pair("Genre: Drama", "en/genres/Drama"),
Pair("Genre: BL", "en/genres/BL"),
Pair("Genre: Action", "en/genres/Action"),
Pair("Genre: Schulleben", "en/genres/Schulleben"),
Pair("Genre: Fantasy", "en/genres/Fantasy"),
Pair("Genre: Comedy", "en/genres/Comedy"),
Pair("Genre: Historisch", "en/genres/Historisch"),
Pair("Genre: Sci-Fi", "en/genres/Sci-Fi"),
Pair("Genre: Thriller", "en/genres/Thriller"),
Pair("Genre: Horror", "en/genres/Horror"),
Pair("Genre: Sport", "en/genres/Sport"),
Pair("Genre: GL", "en/genres/GL"),
)
}

View File

@ -3,7 +3,7 @@ ext {
extClass = '.AdultWebtoon'
themePkg = 'madara'
baseUrl = 'https://adultwebtoon.com'
overrideVersionCode = 3
overrideVersionCode = 4
isNsfw = true
}

View File

@ -2,10 +2,8 @@ package eu.kanade.tachiyomi.extension.en.adultwebtoon
import eu.kanade.tachiyomi.multisrc.madara.Madara
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.source.model.SManga
import okhttp3.FormBody
import okhttp3.Request
import org.jsoup.nodes.Element
class AdultWebtoon : Madara("Adult Webtoon", "https://adultwebtoon.com", "en") {
override val mangaSubString = "adult-webtoon"
@ -15,15 +13,8 @@ class AdultWebtoon : Madara("Adult Webtoon", "https://adultwebtoon.com", "en") {
override val useLoadMoreRequest = LoadMoreStrategy.Never
override fun popularMangaNextPageSelector() = "a.next"
override fun searchMangaSelector() = "li.movie-item > a"
override fun searchMangaNextPageSelector() = "a.next"
override fun searchMangaFromElement(element: Element): SManga {
return SManga.create().apply {
setUrlWithoutDomain(element.absUrl("href"))
title = element.attr("title")
}
}
override fun searchMangaSelector() = popularMangaSelector()
override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
override fun oldXhrChaptersRequest(mangaId: String): Request {
val form = FormBody.Builder()

View File

@ -1,9 +1,9 @@
ext {
extName = 'Arven Scans'
extClass = '.ArvenComics'
themePkg = 'mangathemesia'
themePkg = 'keyoapp'
baseUrl = 'https://arvencomics.com'
overrideVersionCode = 0
overrideVersionCode = 24
isNsfw = false
}

View File

@ -1,10 +1,14 @@
package eu.kanade.tachiyomi.extension.en.arvencomics
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
import eu.kanade.tachiyomi.multisrc.keyoapp.Keyoapp
class ArvenComics : MangaThemesia(
class ArvenComics : Keyoapp(
"Arven Scans",
"https://arvencomics.com",
"en",
mangaUrlDirectory = "/series",
)
) {
// migrated from Mangathemesia to Keyoapp
override val versionId = 2
override val cdnUrl = "https://3xfsjdlc.is1.buzz/uploads"
}

View File

@ -1,7 +1,7 @@
ext {
extName = 'Asura Scans'
extClass = '.AsuraScans'
extVersionCode = 39
extVersionCode = 41
}
apply from: "$rootDir/common.gradle"

View File

@ -57,6 +57,9 @@ class AsuraScans : ParsedHttpSource(), ConfigurableSource {
if (contains("pref_permanent_manga_url_2_en")) {
edit().remove("pref_permanent_manga_url_2_en").apply()
}
if (contains("pref_slug_map")) {
edit().remove("pref_slug_map").apply()
}
}
}
@ -192,10 +195,12 @@ class AsuraScans : ParsedHttpSource(), ConfigurableSource {
override fun mangaDetailsParse(response: Response): SManga {
if (preferences.dynamicUrl()) {
val url = response.request.url.toString()
val newSlug = url.substringAfter("/series/").substringBefore("/")
val newSlug = url.substringAfter("/series/", "").substringBefore("/")
if (newSlug.isNotEmpty()) {
val absSlug = newSlug.substringBeforeLast("-")
preferences.slugMap = preferences.slugMap.apply { put(absSlug, newSlug) }
}
}
return super.mangaDetailsParse(response)
}
@ -225,10 +230,12 @@ class AsuraScans : ParsedHttpSource(), ConfigurableSource {
override fun chapterListParse(response: Response): List<SChapter> {
if (preferences.dynamicUrl()) {
val url = response.request.url.toString()
val newSlug = url.substringAfter("/series/").substringBefore("/")
val newSlug = url.substringAfter("/series/", "").substringBefore("/")
if (newSlug.isNotEmpty()) {
val absSlug = newSlug.substringBeforeLast("-")
preferences.slugMap = preferences.slugMap.apply { put(absSlug, newSlug) }
}
}
return super.chapterListParse(response)
}
@ -238,9 +245,9 @@ class AsuraScans : ParsedHttpSource(), ConfigurableSource {
override fun chapterFromElement(element: Element) = SChapter.create().apply {
setUrlWithoutDomain(element.selectFirst("a")!!.attr("abs:href").toPermSlugIfNeeded())
name = element.selectFirst("h3:eq(0)")!!.text()
name = element.selectFirst("h3")!!.text()
date_upload = try {
val text = element.selectFirst("h3:eq(1)")!!.ownText()
val text = element.selectFirst("h3 + h3")!!.ownText()
val cleanText = text.replace(CLEAN_DATE_REGEX, "$1")
dateFormat.parse(cleanText)?.time ?: 0
} catch (_: Exception) {
@ -308,7 +315,7 @@ class AsuraScans : ParsedHttpSource(), ConfigurableSource {
private val CLEAN_DATE_REGEX = """(\d+)(st|nd|rd|th)""".toRegex()
private val OLD_FORMAT_MANGA_REGEX = """^/manga/(\d+-)?([^/]+)/?$""".toRegex()
private val OLD_FORMAT_CHAPTER_REGEX = """^/(\d+-)?[^/]*-chapter-\d+(-\d+)*/?$""".toRegex()
private const val PREF_SLUG_MAP = "pref_slug_map"
private const val PREF_SLUG_MAP = "pref_slug_map_2"
private const val PREF_DYNAMIC_URL = "pref_dynamic_url"
}
}

View File

@ -0,0 +1,7 @@
ext {
extName = 'BatCave'
extClass = '.BatCave'
extVersionCode = 1
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

@ -0,0 +1,239 @@
package eu.kanade.tachiyomi.extension.en.batcave
import android.util.Log
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.SerializationException
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import okhttp3.FormBody
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import uy.kohesive.injekt.injectLazy
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.Locale
class BatCave : HttpSource() {
override val name = "BatCave"
override val lang = "en"
override val supportsLatest = true
override val baseUrl = "https://batcave.biz"
private val json: Json by injectLazy()
override fun popularMangaRequest(page: Int) = searchMangaRequest(page, "", SortFilter.POPULAR)
override fun popularMangaParse(response: Response) = searchMangaParse(response)
override fun latestUpdatesRequest(page: Int) = searchMangaRequest(page, "", SortFilter.LATEST)
override fun latestUpdatesParse(response: Response) = searchMangaParse(response)
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
if (query.isNotBlank()) {
val url = "$baseUrl/search/".toHttpUrl().newBuilder().apply {
addPathSegment(query.trim())
if (page > 1) {
addPathSegments("page/$page/")
}
}.build()
return GET(url, headers)
}
var filtersApplied = false
val url = "$baseUrl/comix/".toHttpUrl().newBuilder().apply {
filters.get<YearFilter>()?.addFilterToUrl(this)
?.also { filtersApplied = it }
filters.get<PublisherFilter>()?.addFilterToUrl(this)
?.also { filtersApplied = filtersApplied || it }
filters.get<GenreFilter>()?.addFilterToUrl(this)
?.also { filtersApplied = filtersApplied || it }
if (filtersApplied) {
setPathSegment(0, "ComicList")
}
if (page > 1) {
addPathSegments("page/$page/")
}
}.build().toString()
val sort = filters.get<SortFilter>()!!
return if (sort.getSort() == "") {
GET(url, headers)
} else {
val form = FormBody.Builder().apply {
add("dlenewssortby", sort.getSort())
add("dledirection", sort.getDirection())
if (filtersApplied) {
add("set_new_sort", "dle_sort_xfilter")
add("set_direction_sort", "dle_direction_xfilter")
} else {
add("set_new_sort", "dle_sort_cat_1")
add("set_direction_sort", "dle_direction_cat_1")
}
}.build()
POST(url, headers, form)
}
}
private var publishers: List<Pair<String, Int>> = emptyList()
private var genres: List<Pair<String, Int>> = emptyList()
private var filterParseFailed = false
override fun getFilterList(): FilterList {
val filters: MutableList<Filter<*>> = mutableListOf(
Filter.Header("Doesn't work with text search"),
SortFilter(),
YearFilter(),
)
if (publishers.isNotEmpty()) {
filters.add(
PublisherFilter(publishers),
)
}
if (genres.isNotEmpty()) {
filters.add(
GenreFilter(genres),
)
}
if (filters.size < 5) {
filters.add(
Filter.Header(
if (filterParseFailed) {
"Unable to load more filters"
} else {
"Press 'reset' to load more filters"
},
),
)
}
return FilterList(filters)
}
private fun parseFilters(documented: Document) {
val script = documented.selectFirst("script:containsData(__XFILTER__)")
if (script == null) {
filterParseFailed = true
return
}
val data = try {
script.data()
.substringAfter("=")
.trim()
.removeSuffix(";")
.parseAs<XFilters>()
} catch (e: SerializationException) {
Log.e(name, "filters", e)
filterParseFailed = true
return
}
publishers = data.filterItems.publisher.values.map { it.value to it.id }
genres = data.filterItems.genre.values.map { it.value to it.id }
filterParseFailed = false
return
}
override fun searchMangaParse(response: Response): MangasPage {
val document = response.asJsoup()
if (response.request.url.pathSegments[0] != "search") {
parseFilters(document)
}
val entries = document.select("#dle-content > .readed").map { element ->
SManga.create().apply {
with(element.selectFirst(".readed__title > a")!!) {
setUrlWithoutDomain(absUrl("href"))
title = ownText()
}
thumbnail_url = element.selectFirst("img")?.absUrl("data-src")
}
}
val hasNextPage = document.selectFirst("div.pagination__pages")
?.children()?.last()?.tagName() == "a"
return MangasPage(entries, hasNextPage)
}
override fun mangaDetailsParse(response: Response): SManga {
val document = response.asJsoup()
return SManga.create().apply {
title = document.selectFirst("header.page__header h1")!!.text()
thumbnail_url = document.selectFirst("div.page__poster img")?.absUrl("src")
description = document.selectFirst("div.page__text")?.wholeText()
author = document.selectFirst(".page__list > li:has(> div:contains(Publisher))")?.ownText()
status = when (document.selectFirst(".page__list > li:has(> div:contains(release type))")?.ownText()?.trim()) {
"Ongoing" -> SManga.ONGOING
"Complete" -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
}
}
override fun chapterListParse(response: Response): List<SChapter> {
val document = response.asJsoup()
val data = document.selectFirst(".page__chapters-list script:containsData(__DATA__)")!!.data()
.substringAfter("=")
.trim()
.removeSuffix(";")
.parseAs<Chapters>()
return data.chapters.map { chap ->
SChapter.create().apply {
url = "/reader/${data.comicId}/${chap.id}${data.xhash}"
name = chap.title
chapter_number = chap.number
date_upload = try {
dateFormat.parse(chap.date)?.time ?: 0
} catch (_: ParseException) {
0
}
}
}
}
private val dateFormat = SimpleDateFormat("dd.MM.yyyy", Locale.US)
override fun pageListParse(response: Response): List<Page> {
val document = response.asJsoup()
val data = document.selectFirst("script:containsData(__DATA__)")!!.data()
.substringAfter("=")
.trim()
.removeSuffix(";")
.parseAs<Images>()
return data.images.mapIndexed { idx, img ->
Page(idx, imageUrl = baseUrl + img.trim())
}
}
override fun imageUrlParse(response: Response): String {
throw UnsupportedOperationException()
}
private inline fun <reified T> FilterList.get(): T? {
return filterIsInstance<T>().firstOrNull()
}
private inline fun <reified T> String.parseAs(): T {
return json.decodeFromString(this)
}
}

View File

@ -0,0 +1,47 @@
package eu.kanade.tachiyomi.extension.en.batcave
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
class XFilters(
@SerialName("filter_items") val filterItems: XFilterItems = XFilterItems(),
)
@Serializable
class XFilterItems(
@SerialName("p") val publisher: XFilterItem = XFilterItem(),
@SerialName("g") var genre: XFilterItem = XFilterItem(),
)
@Serializable
class XFilterItem(
val values: ArrayList<Values> = arrayListOf(),
)
@Serializable
class Values(
val id: Int,
val value: String,
)
@Serializable
class Chapters(
@SerialName("news_id") val comicId: Int,
val chapters: List<Chapter>,
val xhash: String,
)
@Serializable
class Chapter(
val id: Int,
@SerialName("posi") val number: Float,
val title: String,
val date: String,
)
@Serializable
class Images(
val images: List<String>,
)

View File

@ -0,0 +1,113 @@
package eu.kanade.tachiyomi.extension.en.batcave
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList
import okhttp3.HttpUrl
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
interface UrlPartFilter {
fun addFilterToUrl(url: HttpUrl.Builder): Boolean
}
class CheckBoxItem(name: String, val value: Int) : Filter.CheckBox(name)
open class CheckBoxFilter(
name: String,
private val queryParameter: String,
values: List<Pair<String, Int>>,
) : Filter.Group<CheckBoxItem>(
name,
values.map { CheckBoxItem(it.first, it.second) },
),
UrlPartFilter {
override fun addFilterToUrl(url: HttpUrl.Builder): Boolean {
val checked = state.filter { it.state }
.also { if (it.isEmpty()) return false }
.joinToString(",") { it.value.toString() }
url.addPathSegments("$queryParameter=$checked/")
return true
}
}
class PublisherFilter(values: List<Pair<String, Int>>) :
CheckBoxFilter("Publisher", "p", values)
class GenreFilter(values: List<Pair<String, Int>>) :
CheckBoxFilter("Genre", "g", values)
class TextBox(name: String) : Filter.Text(name)
class YearFilter :
Filter.Group<TextBox>(
"Year of Issue",
listOf(
TextBox("from"),
TextBox("to"),
),
),
UrlPartFilter {
override fun addFilterToUrl(url: HttpUrl.Builder): Boolean {
var applied = false
val currentYear = yearFormat.format(Date()).toInt()
if (state[0].state.isNotBlank()) {
val from = try {
state[0].state.toInt()
} catch (_: NumberFormatException) {
throw Exception("year must be number")
}
assert(from in 1929..currentYear) {
"invalid start year (must be between 1929 and $currentYear)"
}
url.addPathSegments("y[from]=$from/")
applied = true
}
if (state[1].state.isNotBlank()) {
val to = try {
state[1].state.toInt()
} catch (_: NumberFormatException) {
throw Exception("year must be number")
}
assert(to in 1929..currentYear) {
"invalid start year (must be between 1929 and $currentYear)"
}
url.addPathSegments("y[to]=$to/")
applied = true
}
return applied
}
}
private val yearFormat = SimpleDateFormat("yyyy", Locale.ENGLISH)
class SortFilter(
select: Selection = Selection(0, false),
) : Filter.Sort(
"Sort",
sorts.map { it.first }.toTypedArray(),
select,
) {
fun getSort() = sorts[state?.index ?: 0].second
fun getDirection() = if (state?.ascending != false) {
"asc"
} else {
"desc"
}
companion object {
val POPULAR = FilterList(SortFilter(Selection(3, false)))
val LATEST = FilterList(SortFilter(Selection(2, false)))
}
}
private val sorts = listOf(
"Default" to "",
"Date" to "date",
"Date of change" to "editdate",
"Rating" to "rating",
"Read" to "news_read",
"Comments" to "comm_num",
"Title" to "title",
)

View File

@ -1,10 +0,0 @@
ext {
extName = 'Comic1000'
extClass = '.Comic1000'
themePkg = 'manga18'
baseUrl = 'https://comic1000.com'
overrideVersionCode = 0
isNsfw = true
}
apply from: "$rootDir/common.gradle"

View File

@ -1,5 +0,0 @@
package eu.kanade.tachiyomi.extension.en.comic1000
import eu.kanade.tachiyomi.multisrc.manga18.Manga18
class Comic1000 : Manga18("Comic1000", "https://comic1000.com", "en")

View File

@ -2,8 +2,9 @@ ext {
extName = 'Drake Scans'
extClass = '.DrakeScans'
themePkg = 'mangathemesia'
baseUrl = 'https://drakecomic.com'
overrideVersionCode = 13
baseUrl = 'https://drakecomic.org'
overrideVersionCode = 14
isNsfw = false
}
apply from: "$rootDir/common.gradle"

Some files were not shown because too many files have changed in this diff Show More