Compare commits

...

209 Commits

Author SHA1 Message Date
Draff e91f02af02
Bump M+ ver
CI / Prepare job (push) Successful in 4s Details
CI / Build individual modules (push) Successful in 8m11s Details
CI / Publish repo (push) Successful in 52s Details
2024-12-14 06:57:49 +00:00
AwkwardPeak7 44b820eca0
batoto: exception on removed comics and optimize (#6584)
* batoto: exception on removed comics and optimize

* date parsing

* lint

* add headers
2024-12-14 06:55:46 +00:00
Michał Marszałek 21d51725e5
InfinityScans: Update baseUrl (#6578) 2024-12-14 06:55:46 +00:00
Chopper 5df41c9b5f
ReadMangas: Fix chapter loading (#6581)
* Fix chapter loading

* Bump version
2024-12-14 06:55:46 +00:00
AlphaBoom eb41438fb3
Happymh: Fix chapter list parsing (#6572)
* Happymh: Fix chapter list parsing

* Update src/zh/happymh/src/eu/kanade/tachiyomi/extension/zh/happymh/Happymh.kt

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>

---------

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>
2024-12-14 06:55:46 +00:00
TheKingTermux 4cc9b24137
DoujinDesu : Fix and Add Some Feature (#6479)
* DoujinDesu : Fix and Add Some Feature

Add some New Filter
- Author Filter
- Character Filter

Add some Detail in MangaDetail
- Author
- Group
- Series / Serialization

Fix or Modified some Selector
- Title Selector
- Page Selector
- Author Selector
- Character Selector
- Series Selector
- Group Selector

Change Language from En to Id on Filter Tab and Change BaseUrl tab

* Change Filter Header

* Lint for Filter Group

* Apply suggestion

* Apply suggestion

* Update src/id/doujindesu/src/eu/kanade/tachiyomi/extension/id/doujindesu/DoujinDesu.kt

Thanks to vetleledaal

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

* Apply suggestion

* Lint

* Update Status Selector

Thanks to AwkwardPeak7

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>

* Apply suggestion

* Apply suggestion

Thanks to AwkwardPeak7

* Make description is not null

* Forget to change desc lang to id

---------

Co-authored-by: Vetle Ledaal <vetle.ledaal@gmail.com>
Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>
2024-12-14 06:55:46 +00:00
AlphaBoom b3f22ebf80
Baimangu: Fix page parsing (#6570) 2024-12-14 06:55:46 +00:00
AlphaBoom 3ecfb8529c
PixivComic: Use salt from chapter page (#6561) 2024-12-14 06:55:46 +00:00
are-are-are 3a05d381e5
OtakuSanctuary: Update domain (#6560)
Update domain
2024-12-14 06:55:46 +00:00
xdwil a39a339c8e
LuaScans: Update Domain (#6549)
* Bump

* Update Domain
2024-12-14 06:55:46 +00:00
Chopper a900c6cdfc
Remove MangaLivre (#6526) 2024-12-14 06:55:46 +00:00
Chopper d434d470f3
ReadMangas: Theme change (#6525)
* Migration

* Remove data class
2024-12-14 06:55:46 +00:00
Matthias Ahouansou ac8b118d90
mangadex: add option to use source language for title (#6471)
* mangadex: add option to use source language for title

closes #5747

* use 'extension' instead of 'source' for variable names & translations

* simplify title selection
2024-12-14 06:55:46 +00:00
Vetle Ledaal 614165b64c
MangaSwat: fix latest tab (#6531) 2024-12-14 06:55:46 +00:00
Vetle Ledaal b23bd214ff
Manga 18x: increase timeout (#6524) 2024-12-14 06:55:46 +00:00
Cuong-Tran e11aafbd0f
snowmtl: fix image intercept (#6529) 2024-12-14 06:55:46 +00:00
hatozuki-programmer 7abb7e3c16
Improve GigaViewer chapter list parse to fix NullPointerException (#6514)
* Improved chapter list parsing

Uses a more generic method of getting chapter information that resolves compatibility issues caused by sites that use a paginated version of the GigaViewer chapter list

* Increment GigaViewer base version code
2024-12-14 06:55:46 +00:00
Chopper 9ae0fcfc3e
Update domains (#6521)
* Update domains

* Update domain and enable genre filters
2024-12-14 06:55:46 +00:00
hatozuki-programmer b4f081e28b
Add Manga Koinu (#6516) 2024-12-14 06:55:46 +00:00
Creepler13 8719d05ee4
Add MangaKazani (#6505) 2024-12-14 06:55:46 +00:00
are-are-are e339eb3797
HentaiCB: Fix http404 popularMangaRequest & latestUpdatesRequest (#6501)
* Fix http404 popularManga & latestUpdates

* Remove mangasubtring and delete popularMangaRequest & latestUpdatesRequest
2024-12-14 06:55:46 +00:00
hatozuki-programmer da6869d9b4
Add KadoComi (Comic Walker) (#6473)
* KadoComi (Comic Walker) extension

Initial working commit for KadoComi (Comic Walker)

* Cleanup

* Clean up duplicate code

* Remove unnecessary dependencies from build.gradle

* Use book cover for thumbnail if available

* Additional code clean up

* Convert to using DTOs

* Remove unnecessary fragment handling

Fragment isn't sent to the server, so it's not necessary to do any handling here

* Use fragment from URL

* Fix author/artist names

* Fix thumbnails

Default of bookCover was interfering with thumbnail determination logic

* Implement changes from feedback

* Remove unneeded data keyword
2024-12-14 06:55:46 +00:00
Fioren dcf3bb75d5
add DocTruyen3Q (#6439)
* added DocTruyen3Q

* Update src/vi/doctruyen3q/src/eu/kanade/tachiyomi/extension/vi/doctruyen3q/DocTruyen3Q.kt

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>

* Fix space + avoid non null

* avoid non null

* update

---------

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>
2024-12-14 06:55:46 +00:00
Bui Dai 3b8e3cf5bd
update domains (#6483) 2024-12-14 06:55:46 +00:00
Cuong-Tran 23a4a22ad1
snowmtl: fix image intercept (#6474)
* fix image intercept

* bump version
2024-12-14 06:55:46 +00:00
Creepler13 d04dc0e876
Add Tojimangas (#6467)
* Add Tojimangas

* attr -> absUrl + stuff
2024-12-14 06:55:46 +00:00
dngonz 95900b50fa
Koreli Scans: fix popular chapters (#6461)
Koreli Scans: fix chapters
2024-12-14 06:55:46 +00:00
dngonz 98ed77b3c7
Add MangaKoleji (#6460) 2024-12-14 06:55:46 +00:00
dngonz 635fdcaf9e
Fix domains (#6459) 2024-12-14 06:55:46 +00:00
are-are-are 40d11c7278
Update some domain (#6457)
* Update domain yurineko & fix no image

* Update domain VlogTruyen

* Update domain NhatTruyen

* Update domain HentaiVNPlus

* Update domain HentaiCB
2024-12-14 06:55:45 +00:00
Creepler13 b91a9c90af
Add Zevep (#6453) 2024-12-14 06:55:45 +00:00
Emanuel Nibizi dc905e5f6d
Add Manhwalike (#6405)
* Cloudflare fix

* Fix searchMangaParse

* Added requested changes

* Fix requested changes

* Fixed incorrect use of toString()
2024-12-14 06:55:45 +00:00
Nguyen Ngoc Duy Bao 737a141bad
Nhentai | Fixed NPE in regex parser (#6389)
* Improve dataRegex

* Bump versionCode

* Use a more greedy regex

* Remove debug logging

* Remove unused import
2024-12-14 06:55:45 +00:00
Bui Dai 21aedafb7d
Add CoManhua (#6409)
* Add CoManhua

* perf: switch to cloudFlareClient for improved CF handling

* perf: improve seach and filter for staus and genre

* fix: ignore thead class in the chapter list

* chore: imports

* perf: use Filter Checkbox for genres

* fix: use custom dateformat and use default header for genre filter

* chore: icon launcher

* fix: reviewer suggestions

* fix: use selectFirst

* fix oversight in previous commit

* fix: override searchPath
2024-12-14 06:55:45 +00:00
KenjieDec 5f9d4e1dc6
Kemono | Fixed some chapters/images not loading due to missing 'name' (#6443)
Fixed some chapters/images not loading due to missing 'name'
2024-12-14 06:55:45 +00:00
KirinRaikage 0d1ac7b51e
Raijin Scans: Update domain (#6438) 2024-12-14 06:55:45 +00:00
Lucas Bernardineli de Queiroz 489c6a3fc0
Update luratoons url (#6436) 2024-12-14 06:55:45 +00:00
Creepler13 816133a4a8
Fix SirenKomik (#6433)
* "Deactivate Html img search"

* Verison

* Change selector and add explanation
2024-12-14 06:55:45 +00:00
Creepler13 023b928462
Fix HentaiRead (#6408)
* Fix HentaiRead

* Change to Regex

* Added Description
2024-12-14 06:55:45 +00:00
Creepler13 1c31319402
Add InfinityXScan (#6407)
* Add InfinityXScan

* Formatting

* newLine

* Fix Newlines
2024-12-14 06:55:45 +00:00
Creepler13 54bcdce9de
Add Yaoibar (#6406)
* Add Yaoibar

* Formatting

* new LINE

* Fix newline
2024-12-14 06:55:45 +00:00
BrutuZ 90ffe087db
Comick: Better first cover selection (#6399)
* Better first cover selection

* Reformat

* Deduplication of conditions
2024-12-14 06:55:45 +00:00
kana-shii c66131c809
Mangago regex update (#6380)
regex update
2024-12-14 06:55:45 +00:00
kana-shii ab800d59c8
Batoto regex update (#6379)
regex update
2024-12-14 06:55:45 +00:00
Creepler13 e3933d6f22
Fixed Kemono MultiSrc (#6375)
* Fixed Kemono MultiSrc

* lint
2024-12-14 06:55:45 +00:00
Vetle Ledaal f9566c55e4
Flame Comics: use friendly URL for latest tab (#6402) 2024-12-14 06:55:45 +00:00
Vetle Ledaal f80a060670
Flame Comics: add tags/genres (#6390)
* Flame Comics: add tags/genres

* also add genre, and make tags optional
2024-12-14 06:55:45 +00:00
Artem Kuznetcov 9ee5e17b37
Flame Comics: fix chapters naming (#6388)
* FlameComics: fix chapters naming

* FlameComics: replace DecimalFormat to toString and suffix removal
2024-12-14 06:55:45 +00:00
Vetle Ledaal bac2cb25fb
Flame Comics: fix double slash in manga/chapter URL (#6372) 2024-12-14 06:55:45 +00:00
Vetle Ledaal 7e0c565a4d
Weeb Central: use correct chapter URL for WebView (#6370) 2024-12-14 06:55:45 +00:00
Vetle Ledaal 1e09043247
YuraManga: exclude alternating broken images (#6233)
* YuraManga: fix page selector

* set locale for date format

* bump version, set nsfw
2024-12-14 06:55:45 +00:00
are-are-are 348c373bc0
Hennojin: Fix chapter not found for Suwayomi user (#6296)
* Fix chapter not found for Suwayomi user

* Update changes by vetleledaal

* Fix build

* Revert old code URL, because code new URL doesn't work

* Update url

* make thumbnail optional

* use .absUrl() where possible

---------

Co-authored-by: Vetle Ledaal <vetle.ledaal@gmail.com>
2024-12-14 06:55:45 +00:00
Creepler13 abcea6a91a
Removed FlameComics Multisrc/Added individuall solution (#6241)
* A Working version

* Fix Search Function

* updated verionID, added altTitles to search

* Cleanup

* lint

* Fix

* Update src/en/flamecomics/build.gradle

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

* Changed to HttpSource

* Changed to api

* Cleanup

* Fixed getMangaUrl and getChapterUrl

* Fix wrong url in db

* LINT

* lint?

---------

Co-authored-by: Vetle Ledaal <vetle.ledaal@gmail.com>
2024-12-14 06:55:45 +00:00
Nguyen Ngoc Duy Bao b462b1429b
NHentai | Add missing image type ".gif" (#6364)
* Add image type .gif to the selector

* Bump versioncode
2024-12-14 06:55:45 +00:00
bapeey aa5804b858
VCPVMP: Move to multisrc and add ChoChoX (#6356)
* move to multisrc and add chochox

* lint

* apply suggestions
2024-12-14 06:55:45 +00:00
Michał Marszałek bcf51e8138
Fix InfinityScans (#6269) 2024-12-14 06:55:45 +00:00
Chopper 5911343a9a
Add Snowmtl (#6195)
* Add Snowmtl

* Remove logs

* Remove file unused

* Fix status

* Improve Intercept

* Cleanup

* Refactoring

* Use StaticLayout

* Change the min version of the Android API

* Fix function name

* Fix dialog box

* Cleanup

* Typo

* Use custom font

* Refactoring

* Use font config

* Remove unused transfer data class

* Fix font color

* Add normal font

* Cleanup

* Use Color class
2024-12-14 06:55:42 +00:00
renovate[bot] 827fbace8d
Update dependency gradle to v8.11.1 (#6093)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-14 06:55:42 +00:00
are-are-are 91b673efbb
TruyenGG, TruyenQQ: Fix http 429 manga update (#6354)
* Fix http 429 manga update error TruyenGG

* Fix http 429 manga update error TruyenQQ

* Update TruyenQQ.kt

* Update TruyenQQ.kt
2024-12-14 06:55:42 +00:00
dngonz 050efe196b
Remove Read Comics Book (#6353)
remove readcomicsplus
2024-12-14 06:55:42 +00:00
dngonz aea608013b
HentaiDex: fix global searching (#6312)
* fix search

* apply reviewed changes
2024-12-14 06:55:42 +00:00
dngonz d4e8394524
NineAnime: Fix page view (#6298)
* fix page routing

* ?
2024-12-14 06:55:42 +00:00
Emanuel Nibizi f4c64f4386
Add Raw18 (#6291)
* Add Raw18

* Fix requests

* Added requested changes
2024-12-14 06:55:42 +00:00
dngonz 5e34b3aaee
Add manhwas.es (#6216)
* Add manhwas.es

* Add isNsfw

* suggested change

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>

* remove rate limit

---------

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>
Co-authored-by: Vetle Ledaal <vetle.ledaal@gmail.com>
2024-12-14 06:55:42 +00:00
Fioren 0b29192c10
Fix TopTruyen (#6346)
* Fix TopTruyen

Fix issue "Not page found"

* Update src/vi/toptruyen/src/eu/kanade/tachiyomi/extension/vi/toptruyen/TopTruyen.kt

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

---------

Co-authored-by: Vetle Ledaal <vetle.ledaal@gmail.com>
2024-12-14 06:55:42 +00:00
anenasa c8e113a524
Happymh: Fix page list (#6284) 2024-12-14 06:55:42 +00:00
Bui Dai 83bdb4d9b4
Kiryuu: update domain (#6255) 2024-12-14 06:55:42 +00:00
dngonz 989279dca6
Komik Cast: change domain (#6244)
change domain
2024-12-14 06:55:42 +00:00
Yush0DAN 7e226dbaa8
Olympus Scanlation: update domain and apiurl (#6303)
update domain and apiurl
2024-12-14 06:55:42 +00:00
dngonz b21cc255c8
Add MangaBari (#6299)
* add mangabari

* remove slash

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

* add isNsfw

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

---------

Co-authored-by: Vetle Ledaal <vetle.ledaal@gmail.com>
2024-12-14 06:55:42 +00:00
bapeey 4d8111b5a0
SlimeRead: Fix regex (#6246)
fix regex
2024-12-14 06:55:42 +00:00
dngonz a8aaf331b2
Yaoimangaoku: change domain .net (#6217)
* Yaoimangaoku: change domain .net

* change also domain also in .gradle
2024-12-14 06:55:42 +00:00
bapeey 361081d840
LMTO Online: Rebrand to Jobsibe (#6226)
* rebrand

* remove jobsibe
2024-12-14 06:55:42 +00:00
dngonz 639c9821ae
Add Philia Scan (#6223) 2024-12-14 06:55:42 +00:00
dngonz a424ae756f
Cocomic: hide paid chapters (#6222) 2024-12-14 06:55:42 +00:00
lamaxama ea644d7697
Weeb Central: Fix no pages found for Suwayomi users (#6219) 2024-12-14 06:55:42 +00:00
dngonz 2def743f23
Remove extensions (#6188)
* remove msyfanyi

* remove mangaholic

* remove linkstartscan

* revert linkstart
2024-12-14 06:55:41 +00:00
dngonz 7e3a434fc0
Add LegendScanlations (#6185) 2024-12-14 06:55:41 +00:00
dngonz fe4fa84e8a
MangaDemon: get only relevant images (#6200)
* MangaDemon: get only relevant images

* MangaDemon: bump
2024-12-14 06:55:41 +00:00
Vetle Ledaal 67c6278662
Manga Demon: update domain (#6183) 2024-12-14 06:55:41 +00:00
Vetle Ledaal dc1f6d22da
Hiperdex: update status selector (#6203) 2024-12-14 06:55:41 +00:00
Vetle Ledaal 49cd082a5e
Remove SaikoMangaRaw (#6202) 2024-12-14 06:55:41 +00:00
Vetle Ledaal 297cb4c6e4
Remove ColoredManga (#6201) 2024-12-14 06:55:41 +00:00
are-are-are e1f897f6c8
MeituaTop: Fix chapter not found for Suwayomi users (#6191)
Add parseDate
2024-12-14 06:55:41 +00:00
Bui Dai a420b0e1f6
VisorInari: update domain (#6187) 2024-12-14 06:55:41 +00:00
Vetle Ledaal e762d3ecb2
Lava Scans: update domain (#6184) 2024-12-14 06:55:41 +00:00
Vetle Ledaal 7dcf085233
Siikomik: update domain (#6182) 2024-12-14 06:55:41 +00:00
dngonz 5713b09ff2
WeebCentral: Use descending as default sort order (#6181)
* WeebCentral: Use descending as default sort order

* WeebCentral: bump
2024-12-14 06:55:41 +00:00
dngonz bb362356c2
Add LikeMangaIn (#6177)
* Add LikeMangaIn

* LikeMangaIn: fix import

* LikeMangaIn: change locale tu US
2024-12-14 06:55:41 +00:00
dngonz 151548c0f9
LikeManga: fix url (#6165)
* Likemanga: change domain .ink

* Likemanga: bump
2024-12-14 06:55:41 +00:00
dngonz 5b2ccea602
ResetScan: change theme to Madara (#6164)
* ResetScan: change theme to Madara

* ResetScan: fix dateFormat and add useNewChapterEndpoint

* ResetScans: leave year by default
2024-12-14 06:55:41 +00:00
are-are-are bcf57d6f73
Add domain switcher XXManhwa, TruyenVn, SayHentai & Fix author TruyenGG (#6157)
* Add url change feature XXManhwa, TruyenVn, SayHentai

* Fix author Truyengg

* Fix truyenvn
2024-12-14 06:55:41 +00:00
Chopper ea99590719
FoamGirl: Fix pages loading (#6146)
* Fix loading pages

* Use regex

* Cleanup

* Remove NUMB_REGEX

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

* Remove extra return

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

* Refactoring pageList

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

* Refactoring 'getPagesListByNumber'

---------

Co-authored-by: Vetle Ledaal <vetle.ledaal@gmail.com>
2024-12-14 06:55:41 +00:00
dngonz 855a25518b
Birdmanga: remove extension (#6161) 2024-12-14 06:55:41 +00:00
duongtra fa484f2b78
TeamLanhLung: Update domain (#6156)
update domain
2024-12-14 06:55:41 +00:00
dngonz 2e4c3010c0
Iken: add filter to only show accessible chapters (#6155) 2024-12-14 06:55:41 +00:00
dngonz e69b2141e8
RawXZ: source change to .to (#6153) 2024-12-14 06:55:41 +00:00
KenjieDec f60a0d37a7
WNACG | Added support for ".jpeg" and fixed domain update (#6101)
* Added support for ".jpeg" and fixed domain update

* Apply stevenyomi's suggestion

* Apply suggestion

Co-authored-by: stevenyomi <95685115+stevenyomi@users.noreply.github.com>

---------

Co-authored-by: stevenyomi <95685115+stevenyomi@users.noreply.github.com>
2024-12-14 06:55:41 +00:00
anenasa 8271cbd637
Happymh: Fix page list and chapter WebView (#6114) 2024-12-14 06:55:41 +00:00
AwkwardPeak7 3e2dee3690
fix build 2024-12-14 06:55:41 +00:00
bapeey ae5842be87
AsuraScans: Fix pages not found (#6134)
* fix chapter pages

* fix chapter name

* merge nextjs chunks
2024-12-14 06:55:41 +00:00
dngonz ad8db484bd
MangaPlus: add Ultra Jump label (#6133) 2024-12-14 06:55:41 +00:00
Samuel Pereira da Silva d4d400a52c
LuraToon: Refactor to use API JSON without scraper (#6029)
* LuraToon: Refactor to use API JSON without scraper

* Bump version code to 46

* LuraToon: fix problems details, search, latest and decrypt zip files images using AES

* LuraToon: fix pagination latest list

* Refactor create lib to zip interceptor and AES decrypt file for LuraToon and PeachScan

* LuraToon: Remove unused code

* LuraToon: fix problem with lint on lura zip interceptor

* Refactor for each list files

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>

* Refactor use another method to sort caps

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>

* Refactor move code decrypt file from lib CryptoAES to local extension

* Refactor add alert exception if not found list chapters

* Refactor functions to remove redundancy as suggested

* Update version id

---------

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>
2024-12-14 06:55:41 +00:00
Vetle Ledaal 3f57305313
Weeb Central: fix broken chapter link (#6129) 2024-12-14 06:55:41 +00:00
Bui Dai 345c6a09e2
update some domains (#6080)
* update some domains

* remove trailing slashes

* revert domains

* revert domains

* update xxmanhwa and truyenvn domains

* revert inarimanga domain

it worked yesterday but now this new domain is not working anymore
2024-12-14 06:55:41 +00:00
AlphaBoom e7b5987ed2
BiliBiliManga(zh-hans): Fix image load. (#6117)
* BiliBiliManga(zh-hans): Fix image load.

* Remove mistype unused field

* Remove extra data copy
2024-12-14 06:55:41 +00:00
KenjieDec ab8ea5a743
NHentai | Fixed details & chapter not showing when logged-in (#6115)
Fixed details & chapter not showing when logged-in

- Also fixed "null" in description if Japanese/English title isn't available
2024-12-14 06:55:41 +00:00
Chopper 3f74c33e2c
Add SSReading (#6108) 2024-12-14 06:55:41 +00:00
Chopper e875e266c5
Add XsScan (#6107) 2024-12-14 06:55:41 +00:00
Chopper bd97eb008b
NoxScans: Fix pages and mangaDetails (#6106)
* Fix pages and mangaDetails

* Refac
2024-12-14 06:55:41 +00:00
kana-shii 8253a0ae7a
Mangago Regex (#6099)
new regex
2024-12-14 06:55:41 +00:00
AwkwardPeak7 155a2d34a4
MangaDex: new languages (#6097) 2024-12-14 06:55:41 +00:00
kana-shii e1b8dd745e
Batoto regex update (#5853)
* fix bato

* check chapter list first

* throw normal exception

* only regex
2024-12-14 06:55:41 +00:00
Michał Marszałek 7288ec8122
Animated Glitched Comics: Update domain and theme (#6001)
* Animated Glitched Comics: Update domain and theme

* Update AnimatedGlitchedComics.kt

---------

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>
2024-12-14 06:55:41 +00:00
Chopper 516790a053
Webcomics: Fixes (#6096)
Fixes
2024-12-14 06:55:32 +00:00
are-are-are 3fc2029917
Update domain NhatTruyenS, VlogTruyen, HentaiVNPlus (#6095)
* Update domain NhatTruyenS

* Update domain VlogTruyen

* Update domain HentaiVNPlus
2024-12-14 06:55:32 +00:00
Chopper e062d5d30d
SirenKomik: Fix nullpointer (#6092)
Fix nullpointer
2024-12-14 06:55:32 +00:00
Cuong-Tran a3f8524300
SlimeRead: fix crash (#6088)
* fix crash

* bump version
2024-12-14 06:55:32 +00:00
Roman 07a466617d
Add Usagi and unparseable date fix (#6085)
* chapter numbers fix

* chapter numbers fix

* Add Usagi

* Unparseable date fix

---------

Co-authored-by: romshke <@>
2024-12-14 06:55:32 +00:00
Vetle Ledaal 2760945800
Update issue template with CTA for reaction (#6082) 2024-12-14 06:55:32 +00:00
bapeey 7365776e5e
SchaleNetwork: Fix http 400 (#6072)
* fix http 400

* add default headers

* requested changes

* minor changes
2024-12-14 06:55:32 +00:00
KenjieDec d4fcb880c4
NHentai | Fixed some images not showing for some titles (#6070)
* NHentai | Fixed some images not showing for some titles

* little

* Apply AwkwardPeak's suggestions

* comma
2024-12-14 06:55:32 +00:00
Cuong-Tran 9419e9b07a
Fix Manhwa18 (#6055)
* working browsing/latest/reading

* convert from old-theme url to new url

* using old-theme's url to avoid migration

* support filters

* split search results into page

* cleanup description

* minor fix to actual matching old-theme entries' url

* use HttpUrl.Builder

* remove chapter number & unused field

* add cache for search request
2024-12-14 06:55:32 +00:00
Chopper 4e49ac42e7
Add ReadMangas (#6064) 2024-12-14 06:55:32 +00:00
Chopper 0ec37d30aa
CosmicScansID: Fix page selector (#6063)
Fix page selector
2024-12-14 06:55:32 +00:00
Cuong-Tran b7cf468ffe
new: EveriaClub.com (unoriginal) (#6046)
* New source: EveriaClub.com

This source appears to be a clone of Everia.club with only recent posts but hosts content itself instead of ref to 3rd party sites (like Everia.club doing)
Close #1765 from keiyoushi

* EveriaClubCom: fix unable to extract manga url

Close #8

* Revert "Auxiliary commit to revert individual files from 76171ef7595d04b21e168ba0416cc8408220ed93"

This reverts commit 72a263d7802fd93ce56a413efb4f23aea18d7f9b.

* imgSrc using hasAttr & chapter number from 1
2024-12-14 06:55:31 +00:00
Chopper f42e5fa6de
Add RoliaScan (#6042)
* Add RoliaScan

* Cleanup
2024-12-14 06:55:31 +00:00
Chopper f84ec7c418
Taiyo: Fix token (#6045)
Fix token
2024-12-14 06:55:31 +00:00
are-are-are dbfe27e7a3
Lxhentai: Update domain (#6044)
* Update domain and update mangaDetailsParse (title & genre & author)

* update bump version
2024-12-14 06:55:31 +00:00
Chopper 875dbcde3a
Add MangaTX (#6041) 2024-12-14 06:55:31 +00:00
Chopper 51c238bb6e
Add IzanamiScans (#6040) 2024-12-14 06:55:31 +00:00
Chopper 3784abee0c
Update domains (#6025) 2024-12-14 06:55:31 +00:00
Chopper 11eaf8789e
Remove TempestFansub (#6024) 2024-12-14 06:55:31 +00:00
AlphaBoom 79f097eae3
Add Mangajikan(ja) (#6023) 2024-12-14 06:55:31 +00:00
AlphaBoom 1e7b9b3a73
Wnacg(zh): Support webp and gif (#6018) 2024-12-14 06:55:31 +00:00
Chopper e435f0f3b8
Add YaoiFlix (#6014) 2024-12-14 06:55:31 +00:00
Chopper 3843573b45
EvilFlowers: Theme changed (#6013)
Theme changed
2024-12-14 06:55:31 +00:00
Chopper c1a88e2b19
Add DarkNebulus (#6012) 2024-12-14 06:55:31 +00:00
Chopper 21beafbf88
Add RuaHapChanhDay (#6011) 2024-12-14 06:55:31 +00:00
Chopper c70d6a710b
Add MoonWitchScan (#6009) 2024-12-14 06:55:31 +00:00
Vetle Ledaal f841e5de70
Remove GuncelManga (#6005) 2024-12-14 06:55:31 +00:00
Chopper 1f7095dd08
Imperio: Fix manga details (#6004)
Fix mangaDetails
2024-12-14 06:55:31 +00:00
Vetle Ledaal 6c28238769
Refactor out usage of `* -1` (#6003) 2024-12-14 06:55:31 +00:00
AwkwardPeak7 098b3133b0
hentaicube: bump
closes #5988
2024-12-14 06:55:31 +00:00
Michał Marszałek ce3bced7e1
Vortex Scans: Update domain (#5991) 2024-12-14 06:55:31 +00:00
Chopper 6b73e1dfec
Roumanwu: Fix pages (#5986)
Fix pages
2024-12-14 06:55:31 +00:00
Yush0DAN 0c3a9dc8aa
TaurusFansub: fix mangaDetails (#5982)
* fix mangaDetails

* minus changes
2024-12-14 06:55:31 +00:00
Chopper 829076f604
Update domains (#5980)
* Update domain

* Update harimanga domain
2024-12-14 06:55:31 +00:00
Chopper 220221deec
Rawdevartart: Update page URL (#5979)
Update page URL
2024-12-14 06:55:31 +00:00
Chopper 81e02d01b3
YuraManga: Update domain (#5978)
Update domain
2024-12-14 06:55:31 +00:00
Chopper 097ad52daa
LScans: Theme changed (#5977)
Theme changed
2024-12-14 06:55:31 +00:00
Chopper 49e35d5223
MangaReader: Add support for Portuguese and Spanish (#5972)
Add support for Portuguese and Spanish
2024-12-14 06:55:31 +00:00
Chopper d07881f0b6
Atsumaru: Fix image loading (#5971)
Fix images loading
2024-12-14 06:55:31 +00:00
Chopper 50cce6c943
HuntersScans: Update domain (#5970)
Update domain
2024-12-14 06:55:31 +00:00
Deivid Gabriel Pereira de Oliveira 00b01225db
ModeScanlator - Closed (#5968) 2024-12-14 06:55:31 +00:00
Chopper 0fc23e3cb0
Taiyo: Fix popularManga, search and chapterList (#5963)
* Fix popularManga, search and chapterList

* Typo

* Change limit per page

* Remove useless code

* Cleanup

* Fix chapter date_upload
2024-12-14 06:55:31 +00:00
are-are-are a038b718b1
TruyenVN: Update domain (#5958)
Update domain
2024-12-14 06:55:31 +00:00
KenjieDec 85d977e407
Koharu: Moved to src/all | Spyfakku: Fixed Errors, Added "Per page" Filter | Pururin: Re-added, Fixed Some Details (#5956)
* Koharu: Moved to src/all | Fixed Spyfakku

- Added 2 language options for Koharu: japanese and english
- Spyfakku: use a cleaner api if available

* Delete src/en/koharu directory

* Fixed #5957

* Added Pururin | Koharu fixed language

- Pururin: Added back, Fixed tags not showing properly
- Koharu: Fixed "Multi" language search not showing anything, added Chinese language as an option

* Fixed Tag Separation in Description

- Fixed: tags were listed with spaces as the separator, instead of commas

* Added Chinese language as an option

- also: applied FourTOne5's suggestion
- I forgor

* Applied suggestion

- Applied FourTOne5's suggestion

* Deeplink support for mirror links
2024-12-14 06:55:31 +00:00
are-are-are 158ddc6c91
Hentaicube: Update domain (#5946)
Update domain
2024-12-14 06:55:31 +00:00
Lefan bcda90b107
update hwago domain (#5945) 2024-12-14 06:55:31 +00:00
are-are-are 6aedf4e884
Update domain Lxhentai and clean description (#5944) 2024-12-14 06:55:31 +00:00
eientei95 3205b7d3f7
Shinigami: Update URL (#5937) 2024-12-14 06:55:31 +00:00
eientei95 9ebdcdd53f
GalleryAdult: Add w for webp (#5924) 2024-12-14 06:55:31 +00:00
are-are-are 0c4cea69e6
Add VlogTruyen (#5820)
* Add vlogtruyen

* Updates changes

* Fix date_upload

* Update selector for filter

* Edit fetchchapter

* Parse Json Chapter

* Delete SerialName & variables not use

* Delete return popularMangaRequest & latestUpdatesRequest
2024-12-14 06:55:31 +00:00
eientei95 7720d8ac57
WeebCentral: Update to use search data (#5923)
* WeebCentral: Update to use search data

* WeebCentral: Remove "Last Read" from chapter titles
2024-12-14 06:55:31 +00:00
eientei95 9807d6c478
Koharu: Update domain, change name to SchaleNetwork (#5916) 2024-12-14 06:55:31 +00:00
Chopper 4ab8d2b615
Add MangaOnlineBlog (#5919) 2024-12-14 06:55:31 +00:00
Chopper cc48c9be5c
LimitedTimeProject: Fix mangaSubString (#5918)
Fix mangaSubString
2024-12-14 06:55:31 +00:00
Chopper 7d4082dffb
FenixProject: Update domain (#5917)
Update domain
2024-12-14 06:55:31 +00:00
Lefan cf86915f48
Pornhwa Scans: fix chapter list selector (#5907)
fix chapter list selector
2024-12-14 06:55:31 +00:00
Chopper 69e7bc75ef
CrystalComics: Theme changed (#5906)
* Theme changed

* Fix lint and update icon

* Use imageFromElement
2024-12-14 06:55:31 +00:00
are-are-are f871cdaf67
Update domain Yurineko (#5895)
Update domain yurineko
2024-12-14 06:55:31 +00:00
Forza06 df8f486b8e
Merlin Scans and Nox Scans updated (#5879)
* tr: Merlin Scans ve Nox Scans updated

* Update src/tr/merlinscans/src/eu/kanade/tachiyomi/extension/tr/merlinscans/MerlinScans.kt

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>

* Update src/tr/noxscans/src/eu/kanade/tachiyomi/extension/tr/noxscans/NoxScans.kt

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>

* Update src/tr/noxscans/src/eu/kanade/tachiyomi/extension/tr/noxscans/NoxScans.kt

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>

* Update NoxScans.kt

---------

Co-authored-by: Forza <email@ornek.com>
Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>
2024-12-14 06:55:31 +00:00
stevenyomi 1ad22ed47d
Revert "Add hanmanwuzhe source" (#5922)
Revert "Add hanmanwuzhe source (#5099)"

This reverts commit adc6d00a5b5fad6f7cd678300494f5cbc61edcfe.
2024-12-14 06:55:31 +00:00
Forza06 bdc4f38a6f
Added MangaRuhu extension for Turkish manga source. (#5830)
* Add: MangaRuhu extension

* res updated

* Update build.gradle

* Update MangaRuhu.kt

---------

Co-authored-by: Forza <email@ornek.com>
2024-12-14 06:55:31 +00:00
Chocobo Latos fefd4b85c3
Add hanmanwuzhe source (#5099)
* Add hanmanwuzhe source

* changed filter from CheckBox to Select

* Switched to MCCMS Multisrc

* Apply suggestions from code review

---------

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>
2024-12-14 06:55:31 +00:00
Lefan 76532be103
Update some domains (#5793)
* sayhentai update domain

* update grimelek domain

* update domain for ikigaimangas

* update domain for manytoon

* update domain for manhwadesu

* change load more strategy

* ikigaimangas baseurl getter
2024-12-14 06:55:31 +00:00
Maxim Molochkov 5ab2cea54b
AllHentai: fix image loading (#5868) 2024-12-14 06:55:31 +00:00
Chopper 578ef4dda4
MaidScan: Update domain (#5864)
Update domain
2024-12-14 06:55:31 +00:00
Chopper a7600082ab
Remove sources (#5846) 2024-12-14 06:55:31 +00:00
are-are-are df88c6535c
Add TruyenGG (#5687)
* Add TruyenGG

* Update src/vi/truyengg/src/eu/kanade/tachiyomi/extension/vi/truyengg/TruyenGG.kt

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>

* Clean code

---------

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>
2024-12-14 06:55:30 +00:00
AwkwardPeak7 ebf18fcd96
fix build 2024-12-14 06:55:30 +00:00
mr-brune 995240cb32
exhentai support + better tag system (#5855)
* exhentai support

* better tag

* new version
2024-12-14 06:55:30 +00:00
kana-shii ff3e4dff8f
Mangago update regex (#5852)
fix mangago
2024-12-14 06:55:30 +00:00
bapeey 1457da270e
MangaEsp: Add "All" filter (#5850)
add all filter
2024-12-14 06:55:30 +00:00
Chopper fd2bf762da
SilenceScan: Fix status (#5849)
Fix status
2024-12-14 06:55:30 +00:00
Chopper 562304145d
Add ZettaHQ (#5844)
* Add ZettaHQ

* Remove CategoryFilter class
2024-12-14 06:55:30 +00:00
Chopper be5bf59d77
Add ArcticScan (#5840) 2024-12-14 06:55:30 +00:00
Chopper 03cad6d580
Add BlackScans (#5833) 2024-12-14 06:55:30 +00:00
Chopper 5e42af04cb
MangaLivre: Migrate theme (#5826)
* Migrate theme

* Typo
2024-12-14 06:55:30 +00:00
bapeey 404c86ac09
Manhwa-Latino: Fix missing chapters (#5823)
paginated chapter list
2024-12-14 06:55:30 +00:00
BrutuZ 684a9a0972
Comick: Add Tag Grouping setting (#5816)
Add Tag Grouping setting
2024-12-14 06:55:30 +00:00
Chopper bd37268e7f
ResetScans: Migrate theme (#5794)
* Migrate theme

* Add latestFromHomePage and fix versionCode
2024-12-14 06:55:30 +00:00
duongtra 4064b06759
TeamLanhLung: Update domain (#5789)
update domain
2024-12-14 06:55:30 +00:00
bapeey 96e3f93416
SamuraiScan: Fix http 404 (#5802)
change mangaSubstring
2024-12-14 06:55:30 +00:00
bapeey da96de83ea
Remove RightDarkScan (#5800)
remove dead source
2024-12-14 06:55:30 +00:00
bapeey 874ca2af62
KoinoboriScan: Fix pages not found (#5799)
update selector
2024-12-14 06:55:30 +00:00
Chopper cf602b2fcb
Madara: Improve thumbnail (#5762)
* Improve thumbnail

* Bump version
2024-12-14 06:55:30 +00:00
Chopper 15a01b7baf
Update domains (#5787) 2024-12-14 06:55:30 +00:00
Chopper d570d9ca83
SeraphManga(HyperionScans): Migrate theme (#5786)
Migrate theme
2024-12-14 06:55:30 +00:00
zhongfly eed4186aa4
zaimanhua: revalidate token if error code not 0 (#5785) 2024-12-14 06:55:30 +00:00
anenasa 02e4201f7c
Colamanga: Fix thumbnail_url (#5784) 2024-12-14 06:55:30 +00:00
Lefan 3a7bfbbd6a
fix parseChapterPage selector (#5773) 2024-12-14 06:55:30 +00:00
Lefan e3ddf94daf
update domain for sssscanlator (#5771) 2024-12-14 06:55:30 +00:00
are-are-are c59a7ac0b7
Otaku Sanctuary: Update domain (#5764)
update domain otakusan
2024-12-14 06:55:30 +00:00
dangobruh 613ec7b37a
MeituaTop: Re-add extension with updated domain (#5556)
* Add files via upload

* Update src/all/meituatop/src/eu/kanade/tachiyomi/extension/all/meituatop/MeituaTop.kt

---------

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>
2024-12-14 06:55:30 +00:00
bapeey 666fb51186
EternalMangas: Fix data not found (#5731)
* fix

* edge case
2024-12-14 06:55:30 +00:00
Chopper 38de421bba
Keyoapp: Fix CDN URL (#5711)
* Fix CDN URL

* Fix theme cdn url

* Add Arabic and French translations

* Fix fallback method

* Update WickedScans domain

* Update messages
2024-12-14 06:55:30 +00:00
Chaos Pjeles 618b173d30
add XAsiat (#5709)
* add xasiat-album

* Update src/all/xasiatalbums/build.gradle

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>

* Update src/all/xasiatalbums/src/eu/kanade/tachiyomi/extension/all/xasiatalbums/XAsiatAlbums.kt

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>

* add missing categories

* handle relative url mangaDetailsRequest

* handle relative url fetchChapterList

---------

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>
2024-12-14 06:55:30 +00:00
788 changed files with 10262 additions and 3908 deletions

View File

@ -105,3 +105,15 @@ body:
required: true
- label: I will fill out all of the requested information in this form.
required: true
- type: textarea
attributes:
label: <!-- footer -->
description: Do **not** modify. This is a reminder for other users to vote.
value: |
---
Add a :+1: [reaction] to [issues you find important].
[reaction]: https://github.blog/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/
[issues you find important]: https://github.com/keiyoushi/extensions-source/issues?q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc

View File

@ -57,3 +57,15 @@ body:
required: true
- label: I will fill out all of the requested information in this form.
required: true
- type: textarea
attributes:
label: <!-- footer -->
description: Do **not** modify. This is a reminder for other users to vote.
value: |
---
Add a :+1: [reaction] to [issues you find important].
[reaction]: https://github.blog/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/
[issues you find important]: https://github.com/keiyoushi/extensions-source/issues?q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc

View File

@ -55,3 +55,15 @@ body:
required: true
- label: I will fill out all of the requested information in this form.
required: true
- type: textarea
attributes:
label: <!-- footer -->
description: Do **not** modify. This is a reminder for other users to vote.
value: |
---
Add a :+1: [reaction] to [issues you find important].
[reaction]: https://github.blog/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/
[issues you find important]: https://github.com/keiyoushi/extensions-source/issues?q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc

View File

@ -61,3 +61,15 @@ body:
required: true
- label: I will fill out all of the requested information in this form.
required: true
- type: textarea
attributes:
label: <!-- footer -->
description: Do **not** modify. This is a reminder for other users to vote.
value: |
---
Add a :+1: [reaction] to [issues you find important].
[reaction]: https://github.blog/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/
[issues you find important]: https://github.com/keiyoushi/extensions-source/issues?q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc

View File

@ -57,3 +57,15 @@ body:
required: true
- label: I will fill out all of the requested information in this form.
required: true
- type: textarea
attributes:
label: <!-- footer -->
description: Do **not** modify. This is a reminder for other users to vote.
value: |
---
Add a :+1: [reaction] to [issues you find important].
[reaction]: https://github.blog/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/
[issues you find important]: https://github.com/keiyoushi/extensions-source/issues?q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc

View File

@ -39,3 +39,15 @@ body:
required: true
- label: I will fill out all of the requested information in this form.
required: true
- type: textarea
attributes:
label: <!-- footer -->
description: Do **not** modify. This is a reminder for other users to vote.
value: |
---
Add a :+1: [reaction] to [issues you find important].
[reaction]: https://github.blog/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/
[issues you find important]: https://github.com/keiyoushi/extensions-source/issues?q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc

View File

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

View File

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

View File

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

View File

@ -153,7 +153,7 @@ abstract class ColaManga(
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
title = document.selectFirst("h1.fed-part-eone")!!.text()
thumbnail_url = document.selectFirst("a.fed-list-pics")?.absUrl("data-orignal")
thumbnail_url = document.selectFirst("a.fed-list-pics")?.absUrl("data-original")
author = document.selectFirst("span.fed-text-muted:contains($authorTitle) + a")?.text()
genre = document.select("span.fed-text-muted:contains($genreTitle) ~ a").joinToString { it.text() }
description = document

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<activity
android:name="eu.kanade.tachiyomi.multisrc.etoshore.EtoshoreUrlActivity"
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="${SOURCEHOST}"
android:pathPattern="/.*/..*"
android:scheme="${SOURCESCHEME}" />
</intent-filter>
</activity>
</application>
</manifest>

View File

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

View File

@ -0,0 +1,242 @@
package eu.kanade.tachiyomi.multisrc.etoshore
import eu.kanade.tachiyomi.network.GET
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.ParsedHttpSource
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
import okhttp3.Response
import org.jsoup.Jsoup
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import rx.Observable
abstract class Etoshore(
override val name: String,
override val baseUrl: String,
final override val lang: String,
) : ParsedHttpSource() {
override val supportsLatest = true
override val client = network.cloudflareClient
// ============================== Popular ==============================
open val popularFilter = FilterList(
SelectionList("", listOf(Tag(value = "views", query = "sort"))),
)
override fun popularMangaRequest(page: Int) = searchMangaRequest(page, "", popularFilter)
override fun popularMangaParse(response: Response) = searchMangaParse(response)
override fun popularMangaSelector() = throw UnsupportedOperationException()
override fun popularMangaNextPageSelector() = throw UnsupportedOperationException()
override fun popularMangaFromElement(element: Element) = throw UnsupportedOperationException()
// ============================== Latest ===============================
open val latestFilter = FilterList(
SelectionList("", listOf(Tag(value = "date", query = "sort"))),
)
override fun latestUpdatesRequest(page: Int) = searchMangaRequest(page, "", latestFilter)
override fun latestUpdatesParse(response: Response) = searchMangaParse(response)
override fun latestUpdatesSelector() = throw UnsupportedOperationException()
override fun latestUpdatesNextPageSelector() = throw UnsupportedOperationException()
override fun latestUpdatesFromElement(element: Element) = throw UnsupportedOperationException()
// ============================== Search ===============================
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val url = "$baseUrl/page/$page".toHttpUrl().newBuilder()
.addQueryParameter("s", query)
filters.forEach { filter ->
when (filter) {
is SelectionList -> {
val selected = filter.selected()
url.addQueryParameter(selected.query, selected.value)
}
else -> {}
}
}
return GET(url.build(), headers)
}
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
if (query.startsWith(PREFIX_SEARCH)) {
val slug = query.substringAfter(PREFIX_SEARCH)
return fetchMangaDetails(SManga.create().apply { url = "/manga/$slug/" })
.map { manga -> MangasPage(listOf(manga), false) }
}
return super.fetchSearchManga(page, query, filters)
}
override fun searchMangaSelector() = ".search-posts .chapter-box .poster a"
override fun searchMangaNextPageSelector() = ".navigation .naviright:has(a)"
override fun searchMangaFromElement(element: Element) = SManga.create().apply {
title = element.attr("title")
thumbnail_url = element.selectFirst("img")?.let(::imageFromElement)
setUrlWithoutDomain(element.absUrl("href"))
}
override fun searchMangaParse(response: Response): MangasPage {
if (filterList.isEmpty()) {
filterParse(response)
}
return super.searchMangaParse(response)
}
// ============================== Details ===============================
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
title = document.selectFirst("h1")!!.text()
description = document.selectFirst(".excerpt p")?.text()
document.selectFirst(".details-right-con img")?.let { thumbnail_url = imageFromElement(it) }
genre = document.select("div.meta-item span.meta-title:contains(Genres) + span a")
.joinToString { it.text() }
author = document.selectFirst("div.meta-item span.meta-title:contains(Author) + span a")
?.text()
document.selectFirst(".status")?.text()?.let {
status = it.toMangaStatus()
}
setUrlWithoutDomain(document.location())
}
protected open fun imageFromElement(element: Element): String? {
return when {
element.hasAttr("data-src") -> element.attr("abs:data-src")
element.hasAttr("data-lazy-src") -> element.attr("abs:data-lazy-src")
element.hasAttr("srcset") -> element.attr("abs:srcset").getSrcSetImage()
element.hasAttr("data-cfsrc") -> element.attr("abs:data-cfsrc")
else -> element.attr("abs:src")
}
}
protected open fun String.getSrcSetImage(): String? {
return this.split(" ")
.filter(URL_REGEX::matches)
.maxOfOrNull(String::toString)
}
protected val completedStatusList: Array<String> = arrayOf(
"Finished",
"Completo",
)
protected open val ongoingStatusList: Array<String> = arrayOf(
"Publishing",
"Ativo",
)
protected val hiatusStatusList: Array<String> = arrayOf(
"on hiatus",
)
protected val canceledStatusList: Array<String> = arrayOf(
"Canceled",
"Discontinued",
)
open fun String.toMangaStatus(): Int {
return when {
containsIn(completedStatusList) -> SManga.COMPLETED
containsIn(ongoingStatusList) -> SManga.ONGOING
containsIn(hiatusStatusList) -> SManga.ON_HIATUS
containsIn(canceledStatusList) -> SManga.CANCELLED
else -> SManga.UNKNOWN
}
}
// ============================== Chapters ============================
override fun chapterListSelector() = ".chapter-list li a"
override fun chapterFromElement(element: Element) = SChapter.create().apply {
name = element.selectFirst(".title")!!.text()
setUrlWithoutDomain(element.absUrl("href"))
}
// ============================== Pages ===============================
override fun pageListParse(document: Document): List<Page> {
return document.select(".chapter-images .chapter-item > img").mapIndexed { index, element ->
Page(index, imageUrl = imageFromElement(element))
}
}
override fun imageUrlParse(document: Document) = ""
// ============================= Filters ==============================
private var filterList = emptyList<Pair<String, List<Tag>>>()
override fun getFilterList(): FilterList {
val filters = mutableListOf<Filter<*>>()
filters += if (filterList.isNotEmpty()) {
filterList.map { SelectionList(it.first, it.second) }
} else {
listOf(Filter.Header("Aperte 'Redefinir' para tentar mostrar os filtros"))
}
return FilterList(filters)
}
protected open fun parseSelection(document: Document, selector: String): Pair<String, List<Tag>>? {
val selectorFilter = "#filter-form $selector .select-item-head .text"
return document.selectFirst(selectorFilter)?.text()?.let { displayName ->
displayName to document.select("#filter-form $selector li").map { element ->
element.selectFirst("input")!!.let { input ->
Tag(
name = element.selectFirst(".text")!!.text(),
value = input.attr("value"),
query = input.attr("name"),
)
}
}
}
}
open val filterListSelector: List<String> = listOf(
".filter-genre",
".filter-status",
".filter-type",
".filter-year",
".filter-sort",
)
open fun filterParse(response: Response) {
val document = Jsoup.parseBodyFragment(response.peekBody(Long.MAX_VALUE).string())
filterList = filterListSelector.mapNotNull { selector -> parseSelection(document, selector) }
}
protected data class Tag(val name: String = "", val value: String = "", val query: String = "")
private open class SelectionList(displayName: String, private val vals: List<Tag>, state: Int = 0) :
Filter.Select<String>(displayName, vals.map { it.name }.toTypedArray(), state) {
fun selected() = vals[state]
}
// ============================= Utils ==============================
private fun String.containsIn(array: Array<String>): Boolean {
return this.lowercase() in array.map { it.lowercase() }
}
companion object {
const val PREFIX_SEARCH = "id:"
val URL_REGEX = """^(https?://[^\s/$.?#].[^\s]*)${'$'}""".toRegex()
}
}

View File

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.extension.en.ninehentai
package eu.kanade.tachiyomi.multisrc.etoshore
import android.app.Activity
import android.content.ActivityNotFoundException
@ -7,29 +7,28 @@ import android.os.Bundle
import android.util.Log
import kotlin.system.exitProcess
/**
* Springboard that accepts https://9hentai.com/g/xxxxxx intents and redirects them to
* the main Tachiyomi process.
*/
class NineHentaiUrlActivity : Activity() {
class EtoshoreUrlActivity : Activity() {
private val tag = javaClass.simpleName
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val pathSegments = intent?.data?.pathSegments
if (pathSegments != null && pathSegments.size > 1) {
val id = pathSegments[1]
val item = pathSegments[1]
val mainIntent = Intent().apply {
action = "eu.kanade.tachiyomi.SEARCH"
putExtra("query", "id:$id")
putExtra("query", "${Etoshore.PREFIX_SEARCH}$item")
putExtra("filter", packageName)
}
try {
startActivity(mainIntent)
} catch (e: ActivityNotFoundException) {
Log.e("NineHentaiUrlActivity", e.toString())
Log.e(tag, e.toString())
}
} else {
Log.e("NineHentaiUrlActivity", "could not parse uri from intent $intent")
Log.e(tag, "could not parse uri from intent $intent")
}
finish()

View File

@ -269,32 +269,32 @@ abstract class FMReader(
// languages: en, vi, es, tr
return when (dateWord) {
"min", "minute", "phút", "minuto", "dakika" -> Calendar.getInstance().apply {
add(Calendar.MINUTE, value * -1)
add(Calendar.MINUTE, -value)
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}.timeInMillis
"hour", "giờ", "hora", "saat" -> Calendar.getInstance().apply {
add(Calendar.HOUR_OF_DAY, value * -1)
add(Calendar.HOUR_OF_DAY, -value)
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}.timeInMillis
"day", "ngày", "día", "gün" -> Calendar.getInstance().apply {
add(Calendar.DATE, value * -1)
add(Calendar.DATE, -value)
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}.timeInMillis
"week", "tuần", "semana", "hafta" -> Calendar.getInstance().apply {
add(Calendar.DATE, value * 7 * -1)
add(Calendar.DATE, -value * 7)
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}.timeInMillis
"month", "tháng", "mes", "ay" -> Calendar.getInstance().apply {
add(Calendar.MONTH, value * -1)
add(Calendar.MONTH, -value)
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}.timeInMillis
"year", "năm", "año", "yıl" -> Calendar.getInstance().apply {
add(Calendar.YEAR, value * -1)
add(Calendar.YEAR, -value)
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}.timeInMillis

View File

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

View File

@ -647,6 +647,7 @@ abstract class GalleryAdults(
"p" -> "png"
"b" -> "bmp"
"g" -> "gif"
"w" -> "webp"
else -> "jpg"
}
val idx = image.key.toInt()

View File

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

View File

@ -22,7 +22,6 @@ import kotlinx.serialization.json.jsonPrimitive
import okhttp3.Call
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.Interceptor
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.Request
@ -137,19 +136,22 @@ abstract class GigaViewer(
override fun chapterListParse(response: Response): List<SChapter> {
val document = response.asJsoup()
val readableProductList = document.selectFirst("div.js-readable-product-list")!!
val firstListEndpoint = readableProductList.attr("data-first-list-endpoint")
.toHttpUrl()
val latestListEndpoint = readableProductList.attr("data-latest-list-endpoint")
.toHttpUrlOrNull() ?: firstListEndpoint
val numberSince = latestListEndpoint.queryParameter("number_since")!!.toFloat()
.coerceAtLeast(firstListEndpoint.queryParameter("number_since")!!.toFloat())
val aggregateId = document.selectFirst("script.js-valve")!!.attr("data-giga_series")
val newHeaders = headers.newBuilder()
.set("Referer", response.request.url.toString())
.build()
var readMoreEndpoint = firstListEndpoint.newBuilder()
.setQueryParameter("number_since", numberSince.toString())
var readMoreEndpoint = baseUrl.toHttpUrl().newBuilder()
.addPathSegment("api")
.addPathSegment("viewer")
.addPathSegment("readable_products")
.addQueryParameter("aggregate_id", aggregateId)
.addQueryParameter("number_since", Int.MAX_VALUE.toString())
.addQueryParameter("number_until", "0")
.addQueryParameter("read_more_num", "150")
.addQueryParameter("type", "episode")
.build()
.toString()
val chapters = mutableListOf<SChapter>()

View File

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

View File

@ -65,7 +65,8 @@ abstract class GroupLe(
}
.build()
private var uagent: String = preferences.getString(UAGENT_TITLE, UAGENT_DEFAULT)!!
private var uagent = preferences.getString(UAGENT_TITLE, UAGENT_DEFAULT)!!
override fun headersBuilder() = Headers.Builder().apply {
add("User-Agent", uagent)
add("Referer", baseUrl)
@ -206,28 +207,44 @@ abstract class GroupLe(
}
}
protected open fun getChapterSearchParams(document: Document): String {
return "?mtr=true"
}
private fun chapterListParse(response: Response, manga: SManga): List<SChapter> {
val document = response.asJsoup()
if ((document.select(".expandable.hide-dn").isNotEmpty() && document.select(".user-avatar").isNullOrEmpty() && document.toString().contains("current_user_country_code = 'RU'")) || (document.select("img.logo").first()?.attr("title")?.contains("Allhentai") == true && document.select(".user-avatar").isNullOrEmpty())) {
if ((
document.select(".expandable.hide-dn").isNotEmpty() && document.select(".user-avatar")
.isEmpty() && document.toString()
.contains("current_user_country_code = 'RU'")
) || (
document.select("img.logo")
.first()?.attr("title")
?.contains("Allhentai") == true && document.select(".user-avatar").isEmpty()
)
) {
throw Exception("Для просмотра контента необходима авторизация через WebView\uD83C\uDF0E")
}
return document.select(chapterListSelector()).map { chapterFromElement(it, manga) }
val chapterSearchParams = getChapterSearchParams(document)
return document.select(chapterListSelector()).map { chapterFromElement(it, manga, chapterSearchParams) }
}
override fun chapterListSelector() =
"tr.item-row:has(td > a):has(td.date:not(.text-info))"
private fun chapterFromElement(element: Element, manga: SManga): SChapter {
private fun chapterFromElement(element: Element, manga: SManga, chapterSearchParams: String): SChapter {
val urlElement = element.select("a.chapter-link").first()!!
val chapterInf = element.select("td.item-title").first()!!
val urlText = urlElement.text()
val chapter = SChapter.create()
chapter.setUrlWithoutDomain(urlElement.attr("href") + "?mtr=true") // mtr is 18+ fractional skip
chapter.setUrlWithoutDomain(urlElement.attr("href") + chapterSearchParams)
val translatorElement = urlElement.attr("title")
chapter.scanlator = if (!translatorElement.isNullOrBlank()) {
chapter.scanlator = if (translatorElement.isNotBlank()) {
translatorElement
.replace("(Переводчик),", "&")
.removeSuffix(" (Переводчик)")
@ -251,10 +268,14 @@ abstract class GroupLe(
chapter.chapter_number = chapterInf.attr("data-num").toFloat() / 10
chapter.date_upload = element.select("td.d-none").last()?.text()?.let {
try {
SimpleDateFormat("dd.MM.yy", Locale.US).parse(it)?.time ?: 0L
} catch (e: ParseException) {
SimpleDateFormat("dd/MM/yy", Locale.US).parse(it)?.time ?: 0L
if (it.isEmpty()) {
0L
} else {
try {
SimpleDateFormat("dd.MM.yy", Locale.US).parse(it)?.time ?: 0L
} catch (e: ParseException) {
SimpleDateFormat("dd/MM/yy", Locale.US).parse(it)?.time ?: 0L
}
}
} ?: 0
return chapter
@ -292,15 +313,15 @@ abstract class GroupLe(
val html = document.html()
var readerMark = "rm_h.readerDoInit(["
// allhentai necessary
if (!html.contains(readerMark)) {
readerMark = "rm_h.readerInit( 0,["
}
val readerMark = "rm_h.readerDoInit(["
if (!html.contains(readerMark)) {
if (document.select(".input-lg").isNotEmpty() || (document.select(".user-avatar").isNullOrEmpty() && document.select("img.logo").first()?.attr("title")?.contains("Allhentai") == true)) {
if (document.select(".input-lg").isNotEmpty() || (
document.select(".user-avatar")
.isEmpty() && document.select("img.logo").first()?.attr("title")
?.contains("Allhentai") == true
)
) {
throw Exception("Для просмотра контента необходима авторизация через WebView\uD83C\uDF0E")
}
if (!response.request.url.toString().contains(baseUrl)) {

View File

@ -232,7 +232,7 @@ abstract class HentaiHand(
val date = it.jsonObject["added_at"]!!.jsonPrimitive.content
date_upload = if (date.contains("day")) {
Calendar.getInstance().apply {
add(Calendar.DATE, date.filter { it.isDigit() }.toInt() * -1)
add(Calendar.DATE, -date.filter { it.isDigit() }.toInt())
}.timeInMillis
} else {
DATE_FORMAT.parse(it.jsonObject["added_at"]!!.jsonPrimitive.content)?.time ?: 0
@ -248,7 +248,7 @@ abstract class HentaiHand(
val date = obj.jsonObject["uploaded_at"]!!.jsonPrimitive.content
date_upload = if (date.contains("day")) {
Calendar.getInstance().apply {
add(Calendar.DATE, date.filter { it.isDigit() }.toInt() * -1)
add(Calendar.DATE, -date.filter { it.isDigit() }.toInt())
}.timeInMillis
} else {
DATE_FORMAT.parse(obj.jsonObject["uploaded_at"]!!.jsonPrimitive.content)?.time ?: 0

View File

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

View File

@ -96,10 +96,13 @@ class Chapter(
private val createdBy: Name,
private val createdAt: String,
private val chapterStatus: String,
private val isAccessible: Boolean,
private val mangaPost: ChapterPostDetails,
) {
fun isPublic() = chapterStatus == "PUBLIC"
fun isAccessible() = isAccessible
fun toSChapter(mangaSlug: String?) = SChapter.create().apply {
val seriesSlug = mangaSlug ?: mangaPost.slug
url = "/series/$seriesSlug/$slug#$id"

View File

@ -128,7 +128,7 @@ abstract class Iken(
assert(!data.post.isNovel) { "Novels are unsupported" }
return data.post.chapters
.filter { it.isPublic() }
.filter { it.isPublic() && it.isAccessible() }
.map { it.toSChapter(data.post.slug) }
}

View File

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

View File

@ -233,8 +233,8 @@ open class Kemono(
GET("$baseUrl/$apiPath${chapter.url}", headers)
override fun pageListParse(response: Response): List<Page> {
val post: KemonoPostDto = response.parseAs()
return post.images.mapIndexed { i, path -> Page(i, imageUrl = baseUrl + path) }
val postData: KemonoPostDtoWrapped = response.parseAs()
return postData.post.images.mapIndexed { i, path -> Page(i, imageUrl = baseUrl + path) }
}
override fun imageRequest(page: Page): Request {

View File

@ -51,6 +51,11 @@ class KemonoCreatorDto(
}
}
@Serializable
class KemonoPostDtoWrapped(
val post: KemonoPostDto,
)
@Serializable
class KemonoPostDto(
private val id: String,
@ -65,7 +70,7 @@ class KemonoPostDto(
) {
val images: List<String>
get() = buildList(attachments.size + 1) {
if (file.path != null) add(KemonoAttachmentDto(file.name!!, file.path))
if (file.path != null) add(KemonoAttachmentDto(file.name, file.path))
addAll(attachments)
}.filter {
when (it.path.substringAfterLast('.').lowercase()) {
@ -101,8 +106,8 @@ class KemonoFileDto(val name: String? = null, val path: String? = null)
// name might have ".jpe" extension for JPEG, path might have ".m4v" extension for MP4
@Serializable
class KemonoAttachmentDto(val name: String, val path: String) {
override fun toString() = "$path?f=$name"
class KemonoAttachmentDto(var name: String? = null, val path: String) {
override fun toString() = path + if (name != null) "?f=$name" else ""
}
private fun getApiDateFormat() =

View File

@ -0,0 +1,4 @@
pref_show_paid_chapter_title=عرض الفصول المدفوعة
pref_show_paid_chapter_summary_on=سيتم عرض الفصول المدفوعة
pref_show_paid_chapter_summary_off=سيتم عرض الفصول المجانية فقط.
chapter_page_url_not_found=رابط الصفحة غير موجود

View File

@ -1,3 +1,4 @@
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.
chapter_page_url_not_found=Page URL not found

View File

@ -0,0 +1,4 @@
pref_show_paid_chapter_title=Afficher les chapitres payants
pref_show_paid_chapter_summary_on=Les chapitres payants apparaitront.
pref_show_paid_chapter_summary_off=Seuls les chapitres gratuits apparaitront.
chapter_page_url_not_found=Page URL non trouvée

View File

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

View File

@ -18,7 +18,6 @@ import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
@ -55,7 +54,7 @@ abstract class Keyoapp(
protected val intl = Intl(
language = lang,
baseLanguage = "en",
availableLanguages = setOf("en"),
availableLanguages = setOf("ar", "en", "fr"),
classLoader = this::class.java.classLoader!!,
)
@ -259,9 +258,11 @@ abstract class Keyoapp(
// Image list
override fun pageListParse(document: Document): List<Page> {
val cdnUrl = getCdnUrl(document)
document.select("#pages > img")
.map { it.attr("uid") }
.filter { it.isNotEmpty() }
.also { cdnUrl ?: throw Exception(intl["chapter_page_url_not_found"]) }
.mapIndexed { index, img ->
Page(index, document.location(), "$cdnUrl/$img")
}
@ -277,7 +278,16 @@ abstract class Keyoapp(
}
}
protected open val cdnUrl = "https://2xffbs-cn8.is1.buzz/uploads"
protected open fun getCdnUrl(document: Document): String? {
return document.select("script")
.firstOrNull { CDN_HOST_REGEX.containsMatchIn(it.html()) }
?.let {
val cdnHost = CDN_HOST_REGEX.find(it.html())
?.groups?.get("host")?.value
?.replace(CDN_CLEAN_REGEX, "")
"https://$cdnHost/uploads"
}
}
private val oldImgCdnRegex = Regex("""^(https?:)?//cdn\d*\.keyoapp\.com""")
@ -297,12 +307,7 @@ abstract class Keyoapp(
protected open fun Element.getImageUrl(selector: String): String? {
return this.selectFirst(selector)?.let { element ->
element.attr("style")
.substringAfter(":url(", "")
.substringBefore(")", "")
.takeIf { it.isNotEmpty() }
?.toHttpUrlOrNull()?.newBuilder()?.setQueryParameter("w", "480")?.build()
?.toString()
IMG_REGEX.find(element.attr("style"))?.groups?.get("url")?.value
}
}
@ -360,5 +365,8 @@ abstract class Keyoapp(
companion object {
private const val SHOW_PAID_CHAPTERS_PREF = "pref_show_paid_chap"
private const val SHOW_PAID_CHAPTERS_DEFAULT = false
val CDN_HOST_REGEX = """realUrl\s*=\s*`[^`]+//(?<host>[^/]+)""".toRegex()
val CDN_CLEAN_REGEX = """\$\{[^}]*\}""".toRegex()
val IMG_REGEX = """url\(['"]?(?<url>[^(['"\)])]+)""".toRegex()
}
}

View File

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

View File

@ -767,12 +767,21 @@ abstract class Madara(
return when {
element.hasAttr("data-src") -> element.attr("abs:data-src")
element.hasAttr("data-lazy-src") -> element.attr("abs:data-lazy-src")
element.hasAttr("srcset") -> element.attr("abs:srcset").substringBefore(" ")
element.hasAttr("srcset") -> element.attr("abs:srcset").getSrcSetImage()
element.hasAttr("data-cfsrc") -> element.attr("abs:data-cfsrc")
else -> element.attr("abs:src")
}
}
/**
* Get the best image quality available from srcset
*/
private fun String.getSrcSetImage(): String? {
return this.split(" ")
.filter(URL_REGEX::matches)
.maxOfOrNull(String::toString)
}
/**
* Set it to true if the source uses the new AJAX endpoint to
* fetch the manga chapters instead of the old admin-ajax.php one.
@ -1106,6 +1115,7 @@ abstract class Madara(
companion object {
const val URL_SEARCH_PREFIX = "slug:"
val URL_REGEX = """^(https?://[^\s/$.?#].[^\s]*)${'$'}""".toRegex()
}
}

View File

@ -221,9 +221,9 @@ abstract class MangaBox(
val value = date.split(' ')[0].toIntOrNull()
val cal = Calendar.getInstance()
when {
value != null && "min" in date -> cal.apply { add(Calendar.MINUTE, value * -1) }
value != null && "hour" in date -> cal.apply { add(Calendar.HOUR_OF_DAY, value * -1) }
value != null && "day" in date -> cal.apply { add(Calendar.DATE, value * -1) }
value != null && "min" in date -> cal.apply { add(Calendar.MINUTE, -value) }
value != null && "hour" in date -> cal.apply { add(Calendar.HOUR_OF_DAY, -value) }
value != null && "day" in date -> cal.apply { add(Calendar.DATE, -value) }
else -> null
}?.timeInMillis
} else {

View File

@ -7,6 +7,7 @@ sort_by_filter_views=Views
sort_by_filter_updated=Updated
sort_by_filter_added=Added
status_filter_title=Status
status_filter_all=All
status_filter_ongoing=Ongoing
status_filter_hiatus=Hiatus
status_filter_dropped=Dropped

View File

@ -7,6 +7,7 @@ sort_by_filter_views=Vistas
sort_by_filter_updated=Actualización
sort_by_filter_added=Agregado
status_filter_title=Estado
status_filter_all=Todos
status_filter_ongoing=En curso
status_filter_hiatus=En pausa
status_filter_dropped=Abandonado

View File

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

View File

@ -127,7 +127,9 @@ abstract class MangaEsp(
val statusFilter = filterList.firstInstanceOrNull<StatusFilter>()
if (statusFilter != null) {
filteredList = filteredList.filter { it.status == statusFilter.toUriPart() }.toMutableList()
if (statusFilter.toUriPart() != 0) {
filteredList = filteredList.filter { it.status == statusFilter.toUriPart() }.toMutableList()
}
}
val sortByFilter = filterList.firstInstanceOrNull<SortByFilter>()
@ -216,6 +218,7 @@ abstract class MangaEsp(
)
protected open fun getStatusList() = arrayOf(
Pair(intl["status_filter_all"], 0),
Pair(intl["status_filter_ongoing"], 1),
Pair(intl["status_filter_hiatus"], 2),
Pair(intl["status_filter_dropped"], 3),
@ -246,7 +249,7 @@ abstract class MangaEsp(
companion object {
private val UNESCAPE_REGEX = """\\(.)""".toRegex()
val MANGA_LIST_REGEX = """self\.__next_f\.push\(.*data\\":(\[.*trending.*])\}""".toRegex()
private val MANGA_DETAILS_REGEX = """self\.__next_f\.push\(.*data\\":(\{.*lastChapters.*\}).*\\"numFollow""".toRegex()
val MANGA_DETAILS_REGEX = """self\.__next_f\.push\(.*data\\":(\{.*lastChapters.*\}).*\\"numFollow""".toRegex()
const val MANGAS_PER_PAGE = 15
}
}

View File

@ -292,7 +292,7 @@ abstract class MangaThemesia(
listOf("canceled", "cancelled", "cancelado", "cancellato", "cancelados", "dropped", "discontinued", "abandonné")
.any { this.contains(it, ignoreCase = true) } -> SManga.CANCELLED
listOf("hiatus", "on hold", "pausado", "en espera", "en pause", "en attente")
listOf("hiatus", "on hold", "pausado", "en espera", "en pause", "en attente", "hiato")
.any { this.contains(it, ignoreCase = true) } -> SManga.ON_HIATUS
else -> SManga.UNKNOWN

View File

@ -5,5 +5,5 @@ plugins {
baseVersionCode = 9
dependencies {
compileOnly("com.github.tachiyomiorg:image-decoder:e08e9be535")
implementation(project(":lib:zipinterceptor"))
}

View File

@ -1,12 +1,7 @@
package eu.kanade.tachiyomi.multisrc.peachscan
import android.annotation.SuppressLint
import android.app.ActivityManager
import android.app.Application
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Rect
import android.util.Base64
import eu.kanade.tachiyomi.lib.zipinterceptor.ZipInterceptor
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.source.model.FilterList
@ -21,23 +16,16 @@ import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Interceptor
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.Request
import okhttp3.Response
import okhttp3.ResponseBody.Companion.toResponseBody
import org.jsoup.Jsoup
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import rx.Observable
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.io.ByteArrayOutputStream
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.TimeZone
import java.util.zip.ZipInputStream
@SuppressLint("WrongConstant")
abstract class PeachScan(
@ -53,7 +41,7 @@ abstract class PeachScan(
override val client = network.cloudflareClient
.newBuilder()
.addInterceptor(::zipImageInterceptor)
.addInterceptor(ZipInterceptor()::zipImageInterceptor)
.build()
private val json: Json by injectLazy()
@ -192,90 +180,6 @@ abstract class PeachScan(
return GET(page.imageUrl!!, imgHeaders)
}
private val dataUriRegex = Regex("""base64,([0-9a-zA-Z/+=\s]+)""")
private fun zipImageInterceptor(chain: Interceptor.Chain): Response {
val request = chain.request()
val response = chain.proceed(request)
val filename = request.url.pathSegments.last()
if (request.url.fragment != "page" || !filename.contains(".zip")) {
return response
}
val zis = ZipInputStream(response.body.byteStream())
val images = generateSequence { zis.nextEntry }
.mapNotNull {
val entryName = it.name
val splitEntryName = entryName.split('.')
val entryIndex = splitEntryName.first().toInt()
val entryType = splitEntryName.last()
val imageData = if (entryType == "avif" || splitEntryName.size == 1) {
zis.readBytes()
} else {
val svgBytes = zis.readBytes()
val svgContent = svgBytes.toString(Charsets.UTF_8)
val b64 = dataUriRegex.find(svgContent)?.groupValues?.get(1)
?: return@mapNotNull null
Base64.decode(b64, Base64.DEFAULT)
}
entryIndex to PeachScanUtils.decodeImage(imageData, isLowRamDevice, filename, entryName)
}
.sortedBy { it.first }
.toList()
zis.closeEntry()
zis.close()
val totalWidth = images.maxOf { it.second.width }
val totalHeight = images.sumOf { it.second.height }
val result = Bitmap.createBitmap(totalWidth, totalHeight, Bitmap.Config.ARGB_8888)
val canvas = Canvas(result)
var dy = 0
images.forEach {
val srcRect = Rect(0, 0, it.second.width, it.second.height)
val dstRect = Rect(0, dy, it.second.width, dy + it.second.height)
canvas.drawBitmap(it.second, srcRect, dstRect, null)
dy += it.second.height
}
val output = ByteArrayOutputStream()
result.compress(Bitmap.CompressFormat.JPEG, 90, output)
val image = output.toByteArray()
val body = image.toResponseBody("image/jpeg".toMediaType())
return response.newBuilder()
.body(body)
.build()
}
/**
* ActivityManager#isLowRamDevice is based on a system property, which isn't
* necessarily trustworthy. 1GB is supposedly the regular threshold.
*
* Instead, we consider anything with less than 3GB of RAM as low memory
* considering how heavy image processing can be.
*/
private val isLowRamDevice by lazy {
val ctx = Injekt.get<Application>()
val activityManager = ctx.getSystemService("activity") as ActivityManager
val memInfo = ActivityManager.MemoryInfo()
activityManager.getMemoryInfo(memInfo)
memInfo.totalMem < 3L * 1024 * 1024 * 1024
}
companion object {
const val URL_SEARCH_PREFIX = "slug:"
}

View File

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

View File

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.extension.es.vcpvmp
package eu.kanade.tachiyomi.multisrc.vercomics
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.source.model.Filter
@ -8,45 +8,79 @@ 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 okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import rx.Observable
open class VCPVMP(override val name: String, override val baseUrl: String) : ParsedHttpSource() {
override val lang = "es"
abstract class VerComics(
override val name: String,
override val baseUrl: String,
override val lang: String,
) : ParsedHttpSource() {
override val supportsLatest: Boolean = false
override fun headersBuilder(): Headers.Builder {
return Headers.Builder()
.add("Referer", "$baseUrl/")
}
protected open val urlSuffix = ""
protected open val genreSuffix = ""
protected open val useSuffixOnSearch = true
override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException()
override fun latestUpdatesSelector() = throw UnsupportedOperationException()
override fun latestUpdatesFromElement(element: Element) = throw UnsupportedOperationException()
override fun latestUpdatesNextPageSelector() = throw UnsupportedOperationException()
override fun headersBuilder() = super.headersBuilder()
.add("Referer", "$baseUrl/")
override fun popularMangaRequest(page: Int) = GET("$baseUrl/$urlSuffix/page/$page", headers)
override fun popularMangaSelector() = "div.blog-list-items > div.entry"
override fun popularMangaSelector() = "header:has(h1) ~ * .entry"
override fun popularMangaNextPageSelector() = "div.wp-pagenavi > span.current + a"
override fun popularMangaFromElement(element: Element) = SManga.create().apply {
element.select("a.popimg").first()!!.let {
setUrlWithoutDomain(it.attr("href"))
title = it.select("img").attr("alt")
thumbnail_url = it.select("img:not(noscript img)").attr("abs:data-src")
thumbnail_url = it.selectFirst("img:not(noscript img)")?.imgAttr()
}
}
override fun popularMangaNextPageSelector() = "div.wp-pagenavi > span.current + a"
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
var url = baseUrl.toHttpUrl().newBuilder()
if (query.isNotBlank()) {
url = baseUrl.toHttpUrl().newBuilder()
if (useSuffixOnSearch) {
url.addPathSegments(urlSuffix)
}
url.addPathSegments("page")
url.addPathSegments(page.toString())
url.addQueryParameter("s", query)
return GET(url.build(), headers)
}
filters.forEach { filter ->
when (filter) {
is Genre -> {
if (filter.toUriPart().isNotEmpty()) {
url.addPathSegments(genreSuffix)
url.addPathSegments(filter.toUriPart())
url.addPathSegments("page")
url.addPathSegments(page.toString())
}
}
else -> {}
}
}
return GET(url.build(), headers)
}
override fun searchMangaSelector() = popularMangaSelector()
override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element)
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
document.select("div.tax_post").let {
@ -81,50 +115,13 @@ open class VCPVMP(override val name: String, override val baseUrl: String) : Par
override fun chapterListSelector() = throw UnsupportedOperationException()
override fun chapterFromElement(element: Element) = throw UnsupportedOperationException()
protected open val pageListSelector = "div.wp-content p > img:not(noscript img)"
protected open val pageListSelector =
"div.wp-content p > img:not(noscript img), " +
"div.wp-content div#lector > img:not(noscript img), " +
"div.wp-content > figure img:not(noscript img)"
override fun pageListParse(document: Document): List<Page> = document.select(pageListSelector)
.mapIndexed { i, img -> Page(i, "", img.attr("abs:data-src")) }
override fun imageUrlParse(document: Document) = throw UnsupportedOperationException()
protected open val urlSuffix = ""
protected open val genreSuffix = ""
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
var url = baseUrl.toHttpUrl().newBuilder()
if (query.isNotBlank()) {
url = "$baseUrl/$urlSuffix".toHttpUrl().newBuilder()
url.addPathSegments("page")
url.addPathSegments(page.toString())
url.addQueryParameter("s", query)
return GET(url.build(), headers)
}
filters.forEach { filter ->
when (filter) {
is Genre -> {
if (filter.toUriPart().isNotEmpty()) {
url.addPathSegments(genreSuffix)
url.addPathSegments(filter.toUriPart())
url.addPathSegments("page")
url.addPathSegments(page.toString())
}
}
else -> {}
}
}
return GET(url.build(), headers)
}
override fun searchMangaSelector() = popularMangaSelector()
override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element)
override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
.mapIndexed { i, img -> Page(i, imageUrl = img.imgAttr()) }
protected open var genres = arrayOf(Pair("Ver todos", ""))
@ -138,11 +135,45 @@ open class VCPVMP(override val name: String, override val baseUrl: String) : Par
return FilterList(filters)
}
// Array.from(document.querySelectorAll('div.tagcloud a.tag-cloud-link')).map(a => `Pair("${a.innerText}", "${a.href.replace('https://vercomicsporno.com/etiquetas/', '')}")`).join(',\n')
// from https://vercomicsporno.com/
protected open fun Element.imgAttr(): String? {
return when {
this.hasAttr("data-src") -> this.attr("abs:data-src")
this.hasAttr("data-lazy-src") -> this.attr("abs:data-lazy-src")
this.hasAttr("srcset") -> this.attr("abs:srcset").getSrcSetImage()
this.hasAttr("data-cfsrc") -> this.attr("abs:data-cfsrc")
else -> this.attr("abs:src")
}
}
private class Genre(genres: Array<Pair<String, String>>) : UriPartFilter(
private fun String.getSrcSetImage(): String? {
return this.split(" ")
.filter(URL_REGEX::matches)
.maxOfOrNull(String::toString)
}
// Replace the baseUrl and genreSuffix in the following string
// Array.from(document.querySelectorAll('div.tagcloud a.tag-cloud-link')).map(a => `Pair("${a.innerText}", "${a.href.replace('$baseUrl/genreSuffix/', '')}")`).join(',\n')
class Genre(genres: Array<Pair<String, String>>) : UriPartFilter(
"Filtrar por género",
genres,
)
override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException()
override fun latestUpdatesSelector() = throw UnsupportedOperationException()
override fun latestUpdatesNextPageSelector() = throw UnsupportedOperationException()
override fun latestUpdatesFromElement(element: Element) = throw UnsupportedOperationException()
override fun imageUrlParse(document: Document) = throw UnsupportedOperationException()
companion object {
val URL_REGEX = """^(https?://[^\s/$.?#].[^\s]*)${'$'}""".toRegex()
}
open class UriPartFilter(displayName: String, private val vals: Array<Pair<String, String>>) :
Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
fun toUriPart() = vals[state].second
}
}

View File

@ -152,25 +152,25 @@ abstract class Zbulu(
val value = date.split(' ')[0].toInt()
when {
"second" in date -> Calendar.getInstance().apply {
add(Calendar.SECOND, value * -1)
add(Calendar.SECOND, -value)
}.timeInMillis
"minute" in date -> Calendar.getInstance().apply {
add(Calendar.MINUTE, value * -1)
add(Calendar.MINUTE, -value)
}.timeInMillis
"hour" in date -> Calendar.getInstance().apply {
add(Calendar.HOUR_OF_DAY, value * -1)
add(Calendar.HOUR_OF_DAY, -value)
}.timeInMillis
"day" in date -> Calendar.getInstance().apply {
add(Calendar.DATE, value * -1)
add(Calendar.DATE, -value)
}.timeInMillis
"week" in date -> Calendar.getInstance().apply {
add(Calendar.DATE, value * 7 * -1)
add(Calendar.DATE, -value * 7)
}.timeInMillis
"month" in date -> Calendar.getInstance().apply {
add(Calendar.MONTH, value * -1)
add(Calendar.MONTH, -value)
}.timeInMillis
"year" in date -> Calendar.getInstance().apply {
add(Calendar.YEAR, value * -1)
add(Calendar.YEAR, -value)
}.timeInMillis
else -> {
0L

View File

@ -5,6 +5,7 @@ import android.util.Base64
import java.security.MessageDigest
import java.util.Arrays
import javax.crypto.Cipher
import javax.crypto.SecretKey
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec

View File

@ -0,0 +1,7 @@
plugins {
id("lib-android")
}
dependencies {
compileOnly("com.github.tachiyomiorg:image-decoder:e08e9be535")
}

View File

@ -1,26 +1,29 @@
package eu.kanade.tachiyomi.multisrc.peachscan
package eu.kanade.tachiyomi.lib.zipinterceptor
import android.app.ActivityManager
import android.app.Application
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Rect
import android.util.Base64
import okhttp3.Interceptor
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.Request
import okhttp3.Response
import okhttp3.ResponseBody.Companion.toResponseBody
import tachiyomi.decoder.ImageDecoder
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.io.InputStream
import java.lang.reflect.Method
import java.util.zip.ZipInputStream
/**
* TachiyomiJ2K is on a 2-year-old version of ImageDecoder at the time of writing,
* with a different signature than the one being used as a compile-only dependency.
*
* Because of this, if [ImageDecoder.decode] is called as-is on TachiyomiJ2K, we
* end up with a [NoSuchMethodException].
*
* This is a hack for determining which signature to call when decoding images.
*/
object PeachScanUtils {
object ImageDecoderWrapper {
private var decodeMethod: Method
private var newInstanceMethod: Method
private var classSignature = ClassSignature.Newest
private enum class ClassSignature {
@ -35,7 +38,6 @@ object PeachScanUtils {
val inputStreamClass = InputStream::class.java
try {
// Mihon Preview r6595+
classSignature = ClassSignature.Newest
// decode(region, sampleSize)
@ -54,7 +56,6 @@ object PeachScanUtils {
)
} catch (_: NoSuchMethodException) {
try {
// Mihon Stable & forks
classSignature = ClassSignature.New
// decode(region, rgb565, sampleSize, applyColorManagement, displayProfile)
@ -74,7 +75,6 @@ object PeachScanUtils {
booleanClass,
)
} catch (_: NoSuchMethodException) {
// Tachiyomi J2k
classSignature = ClassSignature.Old
// decode(region, rgb565, sampleSize)
@ -122,3 +122,97 @@ object PeachScanUtils {
return bitmap
}
}
open class ZipInterceptor {
private val dataUriRegex = Regex("""base64,([0-9a-zA-Z/+=\s]+)""")
open fun zipGetByteStream(request: Request, response: Response): InputStream {
return response.body.byteStream()
}
open fun requestIsZipImage(request: Request): Boolean {
return request.url.fragment == "page" && request.url.pathSegments.last().contains(".zip")
}
fun zipImageInterceptor(chain: Interceptor.Chain): Response {
val request = chain.request()
val response = chain.proceed(request)
val filename = request.url.pathSegments.last()
if (requestIsZipImage(request).not()) {
return response
}
val zis = ZipInputStream(zipGetByteStream(request, response))
val images = generateSequence { zis.nextEntry }
.mapNotNull {
val entryName = it.name
val splitEntryName = entryName.split('.')
val entryIndex = splitEntryName.first().toInt()
val entryType = splitEntryName.last()
val imageData = if (entryType == "avif" || splitEntryName.size == 1) {
zis.readBytes()
} else {
val svgBytes = zis.readBytes()
val svgContent = svgBytes.toString(Charsets.UTF_8)
val b64 = dataUriRegex.find(svgContent)?.groupValues?.get(1)
?: return@mapNotNull null
Base64.decode(b64, Base64.DEFAULT)
}
entryIndex to ImageDecoderWrapper.decodeImage(imageData, isLowRamDevice, filename, entryName)
}
.sortedBy { it.first }
.toList()
zis.closeEntry()
zis.close()
val totalWidth = images.maxOf { it.second.width }
val totalHeight = images.sumOf { it.second.height }
val result = Bitmap.createBitmap(totalWidth, totalHeight, Bitmap.Config.ARGB_8888)
val canvas = Canvas(result)
var dy = 0
images.forEach {
val srcRect = Rect(0, 0, it.second.width, it.second.height)
val dstRect = Rect(0, dy, it.second.width, dy + it.second.height)
canvas.drawBitmap(it.second, srcRect, dstRect, null)
dy += it.second.height
}
val output = ByteArrayOutputStream()
result.compress(Bitmap.CompressFormat.JPEG, 90, output)
val image = output.toByteArray()
val body = image.toResponseBody("image/jpeg".toMediaType())
return response.newBuilder()
.body(body)
.build()
}
/**
* ActivityManager#isLowRamDevice is based on a system property, which isn't
* necessarily trustworthy. 1GB is supposedly the regular threshold.
*
* Instead, we consider anything with less than 3GB of RAM as low memory
* considering how heavy image processing can be.
*/
private val isLowRamDevice by lazy {
val ctx = Injekt.get<Application>()
val activityManager = ctx.getSystemService("activity") as ActivityManager
val memInfo = ActivityManager.MemoryInfo()
activityManager.getMemoryInfo(memInfo)
memInfo.totalMem < 3L * 1024 * 1024 * 1024
}
}

View File

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

View File

@ -38,6 +38,7 @@ import rx.Observable
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
@ -116,7 +117,7 @@ open class BatoTo(
.build()
override fun latestUpdatesRequest(page: Int): Request {
return GET("$baseUrl/browse?langs=$siteLang&sort=update&page=$page")
return GET("$baseUrl/browse?langs=$siteLang&sort=update&page=$page", headers)
}
override fun latestUpdatesSelector(): String {
@ -140,7 +141,7 @@ open class BatoTo(
override fun latestUpdatesNextPageSelector() = "div#mainer nav.d-none .pagination .page-item:last-of-type:not(.disabled)"
override fun popularMangaRequest(page: Int): Request {
return GET("$baseUrl/browse?langs=$siteLang&sort=views_a&page=$page")
return GET("$baseUrl/browse?langs=$siteLang&sort=views_a&page=$page", headers)
}
override fun popularMangaSelector() = latestUpdatesSelector()
@ -323,7 +324,7 @@ open class BatoTo(
return super.mangaDetailsRequest(manga)
}
private var titleRegex: Regex =
Regex("(?:\\([^()]*\\)|\\{[^{}]*\\}|\\[(?:(?!]).)*]|«[^»]*»|〘[^〙]*〙|「[^」]*」|『[^』]*』|≪[^≫]*≫|﹛[^﹜]*﹜|〖[^〖〗]*〗|𖤍.+?𖤍|/.+?)\\s*|([|/~].*)|-.*-")
Regex("(?:\\([^()]*\\)|\\{[^{}]*\\}|\\[(?:(?!]).)*]|«[^»]*»|〘[^〙]*〙|「[^」]*」|『[^』]*』|≪[^≫]*≫|﹛[^﹜]*﹜|〖[^〖〗]*〗|𖤍.+?𖤍|《[^》]*》|⌜.+?⌝|⟨[^⟩]*⟩|/.+)")
override fun mangaDetailsParse(document: Document): SManga {
val infoElement = document.select("div#mainer div.container-fluid")
@ -362,44 +363,55 @@ open class BatoTo(
else -> SManga.UNKNOWN
}
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
val url = client.newCall(
GET(
when {
manga.url.startsWith("http") -> manga.url
else -> "$baseUrl${manga.url}"
},
),
).execute().asJsoup()
if (getAltChapterListPref() || checkChapterLists(url)) {
val id = manga.url.substringBeforeLast("/").substringAfterLast("/").trim()
return client.newCall(GET("$baseUrl/rss/series/$id.xml"))
.asObservableSuccess()
.map { altChapterParse(it, manga.title) }
}
return super.fetchChapterList(manga)
}
private fun altChapterParse(response: Response, title: String): List<SChapter> {
private fun altChapterParse(response: Response): List<SChapter> {
return Jsoup.parse(response.body.string(), response.request.url.toString(), Parser.xmlParser())
.select("channel > item").map { item ->
SChapter.create().apply {
url = item.selectFirst("guid")!!.text()
name = item.selectFirst("title")!!.text().substringAfter(title).trim()
date_upload = SimpleDateFormat("E, dd MMM yyyy H:m:s Z", Locale.US).parse(item.selectFirst("pubDate")!!.text())?.time ?: 0L
name = item.selectFirst("title")!!.text()
date_upload = parseAltChapterDate(item.selectFirst("pubDate")!!.text())
}
}
}
private val altDateFormat = SimpleDateFormat("E, dd MMM yyyy H:m:s Z", Locale.US)
private fun parseAltChapterDate(date: String): Long {
return try {
altDateFormat.parse(date)!!.time
} catch (_: ParseException) {
0L
}
}
private fun checkChapterLists(document: Document): Boolean {
return document.select(".episode-list > .alert-warning").text().contains("This comic has been marked as deleted and the chapter list is not available.")
}
override fun chapterListRequest(manga: SManga): Request {
if (manga.url.startsWith("http")) {
return GET(manga.url, headers)
return if (getAltChapterListPref()) {
val id = manga.url.substringBeforeLast("/").substringAfterLast("/").trim()
GET("$baseUrl/rss/series/$id.xml", headers)
} else if (manga.url.startsWith("http")) {
GET(manga.url, headers)
} else {
super.chapterListRequest(manga)
}
return super.chapterListRequest(manga)
}
override fun chapterListParse(response: Response): List<SChapter> {
if (getAltChapterListPref()) {
return altChapterParse(response)
}
val document = response.asJsoup()
if (checkChapterLists(document)) {
throw Exception("Deleted from site")
}
return document.select(chapterListSelector())
.map(::chapterFromElement)
}
override fun chapterListSelector() = "div.main div.p-2"
@ -428,46 +440,46 @@ open class BatoTo(
return when {
"secs" in date -> Calendar.getInstance().apply {
add(Calendar.SECOND, value * -1)
add(Calendar.SECOND, -value)
}.timeInMillis
"mins" in date -> Calendar.getInstance().apply {
add(Calendar.MINUTE, value * -1)
add(Calendar.MINUTE, -value)
}.timeInMillis
"hours" in date -> Calendar.getInstance().apply {
add(Calendar.HOUR_OF_DAY, value * -1)
add(Calendar.HOUR_OF_DAY, -value)
}.timeInMillis
"days" in date -> Calendar.getInstance().apply {
add(Calendar.DATE, value * -1)
add(Calendar.DATE, -value)
}.timeInMillis
"weeks" in date -> Calendar.getInstance().apply {
add(Calendar.DATE, value * 7 * -1)
add(Calendar.DATE, -value * 7)
}.timeInMillis
"months" in date -> Calendar.getInstance().apply {
add(Calendar.MONTH, value * -1)
add(Calendar.MONTH, -value)
}.timeInMillis
"years" in date -> Calendar.getInstance().apply {
add(Calendar.YEAR, value * -1)
add(Calendar.YEAR, -value)
}.timeInMillis
"sec" in date -> Calendar.getInstance().apply {
add(Calendar.SECOND, value * -1)
add(Calendar.SECOND, -value)
}.timeInMillis
"min" in date -> Calendar.getInstance().apply {
add(Calendar.MINUTE, value * -1)
add(Calendar.MINUTE, -value)
}.timeInMillis
"hour" in date -> Calendar.getInstance().apply {
add(Calendar.HOUR_OF_DAY, value * -1)
add(Calendar.HOUR_OF_DAY, -value)
}.timeInMillis
"day" in date -> Calendar.getInstance().apply {
add(Calendar.DATE, value * -1)
add(Calendar.DATE, -value)
}.timeInMillis
"week" in date -> Calendar.getInstance().apply {
add(Calendar.DATE, value * 7 * -1)
add(Calendar.DATE, -value * 7)
}.timeInMillis
"month" in date -> Calendar.getInstance().apply {
add(Calendar.MONTH, value * -1)
add(Calendar.MONTH, -value)
}.timeInMillis
"year" in date -> Calendar.getInstance().apply {
add(Calendar.YEAR, value * -1)
add(Calendar.YEAR, -value)
}.timeInMillis
else -> {
return 0

View File

@ -3,6 +3,9 @@ ignored_groups_summary=Chapters from these groups won't be shown.\nOne group nam
include_tags_title=Include Tags
include_tags_on=More specific, but might contain spoilers!
include_tags_off=Only the broader genres
group_tags_title=Group Tags (fork must support grouping)
group_tags_on=Will prefix tags with their type
group_tags_off=List all tags together
update_cover_title=Update Covers
update_cover_on=Keep cover updated
update_cover_off=Prefer first cover

View File

@ -3,6 +3,9 @@ ignored_groups_summary=Capítulos desses grupos não aparecerão.\nUm grupo por
include_tags_title=Incluir Tags
include_tags_on=Mais detalhadas, mas podem conter spoilers
include_tags_off=Apenas os gêneros básicos
group_tags_title=Agrupar Tags (necessário fork compatível)
group_tags_on=Prefixar tags com o respectivo tipo
group_tags_off=Listar todas as tags juntas
update_cover_title=Atualizar Capas
update_cover_on=Manter capas atualizadas
update_cover_off=Usar apenas a primeira capa

View File

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

View File

@ -97,6 +97,20 @@ abstract class Comick(
}
}.also(screen::addPreference)
SwitchPreferenceCompat(screen.context).apply {
key = GROUP_TAGS_PREF
title = intl["group_tags_title"]
summaryOn = intl["group_tags_on"]
summaryOff = intl["group_tags_off"]
setDefaultValue(GROUP_TAGS_DEFAULT)
setOnPreferenceChangeListener { _, newValue ->
preferences.edit()
.putBoolean(GROUP_TAGS_PREF, newValue as Boolean)
.commit()
}
}.also(screen::addPreference)
SwitchPreferenceCompat(screen.context).apply {
key = FIRST_COVER_PREF
title = intl["update_cover_title"]
@ -149,6 +163,9 @@ abstract class Comick(
private val SharedPreferences.includeMuTags: Boolean
get() = getBoolean(INCLUDE_MU_TAGS_PREF, INCLUDE_MU_TAGS_DEFAULT)
private val SharedPreferences.groupTags: Boolean
get() = getBoolean(GROUP_TAGS_PREF, GROUP_TAGS_DEFAULT)
private val SharedPreferences.updateCover: Boolean
get() = getBoolean(FIRST_COVER_PREF, FIRST_COVER_DEFAULT)
@ -379,22 +396,23 @@ abstract class Comick(
val coversUrl =
"$apiUrl/comic/${mangaData.comic.slug ?: mangaData.comic.hid}/covers?tachiyomi=true"
val covers = client.newCall(GET(coversUrl)).execute()
.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() }
}
.parseAs<Covers>().mdCovers.reversed()
val firstVol = covers.filter { it.vol == "1" }.ifEmpty { covers }
val originalCovers = firstVol
.filter { mangaData.comic.isoLang.orEmpty().startsWith(it.locale.orEmpty()) }
val localCovers = firstVol
.filter { comickLang.startsWith(it.locale.orEmpty()) }
return mangaData.toSManga(
includeMuTags = preferences.includeMuTags,
scorePosition = preferences.scorePosition,
covers = covers,
covers = localCovers.ifEmpty { originalCovers }.ifEmpty { firstVol },
groupTags = preferences.groupTags,
)
}
return mangaData.toSManga(
includeMuTags = preferences.includeMuTags,
scorePosition = preferences.scorePosition,
groupTags = preferences.groupTags,
)
}
@ -448,9 +466,10 @@ abstract class Comick(
.map { it.toSChapter(mangaUrl) }
}
private val publishedDateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.ENGLISH).apply {
timeZone = TimeZone.getTimeZone("UTC")
}
private val publishedDateFormat =
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.ENGLISH).apply {
timeZone = TimeZone.getTimeZone("UTC")
}
override fun getChapterUrl(chapter: SChapter): String {
return "$baseUrl${chapter.url}"
@ -513,6 +532,8 @@ abstract class Comick(
private const val IGNORED_GROUPS_PREF = "IgnoredGroups"
private const val INCLUDE_MU_TAGS_PREF = "IncludeMangaUpdatesTags"
const val INCLUDE_MU_TAGS_DEFAULT = false
private const val GROUP_TAGS_PREF = "GroupTags"
const val GROUP_TAGS_DEFAULT = false
private const val MIGRATED_IGNORED_GROUPS = "MigratedIgnoredGroups"
private const val FIRST_COVER_PREF = "DefaultCover"
private const val FIRST_COVER_DEFAULT = true

View File

@ -1,5 +1,6 @@
package eu.kanade.tachiyomi.extension.all.comickfun
import eu.kanade.tachiyomi.extension.all.comickfun.Comick.Companion.GROUP_TAGS_DEFAULT
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
@ -29,13 +30,14 @@ class Manga(
val comic: Comic,
private val artists: List<Name> = emptyList(),
private val authors: List<Name> = emptyList(),
private val genres: List<Name> = emptyList(),
private val genres: List<Genre> = emptyList(),
private val demographic: String? = null,
) {
fun toSManga(
includeMuTags: Boolean = INCLUDE_MU_TAGS_DEFAULT,
scorePosition: String = SCORE_POSITION_DEFAULT,
covers: List<MDcovers>? = null,
groupTags: Boolean = GROUP_TAGS_DEFAULT,
) =
SManga.create().apply {
// appennding # at end as part of migration from slug to hid
@ -75,19 +77,23 @@ class Manga(
artist = artists.joinToString { it.name.trim() }
author = authors.joinToString { it.name.trim() }
genre = buildList {
comic.origination?.let(::add)
demographic?.let { add(Name(it)) }
addAll(genres)
addAll(comic.mdGenres.mapNotNull { it.name })
comic.origination?.let { add(Genre("Origination", it.name)) }
demographic?.let { add(Genre("Demographic", it)) }
addAll(
comic.mdGenres.mapNotNull { it.genre }.sortedBy { it.group }
.sortedBy { it.name },
)
addAll(genres.sortedBy { it.group }.sortedBy { it.name })
if (includeMuTags) {
comic.muGenres.categories.forEach { category ->
category?.category?.title?.let { add(Name(it)) }
}
addAll(
comic.muGenres.categories.mapNotNull { it?.category?.title }.sorted()
.map { Genre("Category", it) },
)
}
}
.distinctBy { it.name }
.filter { it.name.isNotBlank() }
.joinToString { it.name.trim() }
.filterNot { it.name.isNullOrBlank() || it.group.isNullOrBlank() }
.joinToString { if (groupTags) "${it.group}:${it.name?.trim()}" else "${it.name?.trim()}" }
}
}
@ -106,6 +112,7 @@ class Comic(
@SerialName("md_comic_md_genres") val mdGenres: List<MdGenres>,
@SerialName("mu_comics") val muGenres: MuComicCategories = MuComicCategories(emptyList()),
@SerialName("bayesian_rating") val score: String? = null,
@SerialName("iso639_1") val isoLang: String? = null,
) {
val origination = when (country) {
"jp" -> Name("Manga")
@ -128,7 +135,13 @@ class Comic(
@Serializable
class MdGenres(
@SerialName("md_genres") val name: Name? = null,
@SerialName("md_genres") val genre: Genre? = null,
)
@Serializable
class Genre(
val group: String? = null,
val name: String? = null,
)
@Serializable

View File

@ -1,7 +1,7 @@
ext {
extName = 'E-Hentai'
extClass = '.EHFactory'
extVersionCode = 20
extVersionCode = 22
isNsfw = true
}

View File

@ -4,17 +4,17 @@ import android.annotation.SuppressLint
import android.app.Application
import android.content.SharedPreferences
import android.net.Uri
import android.webkit.CookieManager
import androidx.preference.CheckBoxPreference
import androidx.preference.EditTextPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.source.ConfigurableSource
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.Filter.CheckBox
import eu.kanade.tachiyomi.source.model.Filter.Group
import eu.kanade.tachiyomi.source.model.Filter.Select
import eu.kanade.tachiyomi.source.model.Filter.Text
import eu.kanade.tachiyomi.source.model.Filter.TriState
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.Page
@ -39,13 +39,22 @@ abstract class EHentai(
private val ehLang: String,
) : ConfigurableSource, HttpSource() {
override val name = "E-Hentai"
private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
override val name = "E-Hentai"
private val webViewCookieManager: CookieManager by lazy { CookieManager.getInstance() }
private val memberId: String by lazy { getMemberIdPref() }
private val passHash: String by lazy { getPassHashPref() }
override val baseUrl = "https://e-hentai.org"
override val baseUrl: String
get() = when {
System.getenv("CI") == "true" -> "https://e-hentai.org"
memberId.isNotEmpty() && passHash.isNotEmpty() -> "https://exhentai.org"
else -> "https://e-hentai.org"
}
override val supportsLatest = true
@ -73,7 +82,7 @@ abstract class EHentai(
val manga = mangaElements[i].let {
SManga.create().apply {
// Get title
it.select("a")?.first()?.apply {
it.selectFirst("a")?.apply {
title = this.select(".glink").text()
url = ExGalleryMetadata.normalizeUrl(attr("href"))
if (i == mangaElements.lastIndex) {
@ -131,9 +140,9 @@ abstract class EHentai(
}
private fun parseChapterPage(response: Element) = with(response) {
select(".gdtm a").map {
Pair(it.child(0).attr("alt").toInt(), it.attr("href"))
}.sortedBy(Pair<Int, String>::first).map { it.second }
select("#gdt a").map {
it.attr("href")
}
}
private fun chapterPageCall(np: String) = client.newCall(chapterPageRequest(np)).asObservableSuccess()
@ -161,10 +170,23 @@ abstract class EHentai(
query.isBlank() -> languageTag(enforceLanguageFilter)
else -> languageTag(enforceLanguageFilter).let { if (it.isNotEmpty()) "$query,$it" else query }
}
modifiedQuery += filters.filterIsInstance<TagFilter>()
.flatMap { it.markedTags() }
.joinToString(",")
.let { if (it.isNotEmpty()) ",$it" else it }
filters.filterIsInstance<TextFilter>().forEach { it ->
if (it.state.isNotEmpty()) {
val splitted = it.state.split(",").filter(String::isNotBlank)
if (splitted.size < 2 && it.type != "tags") {
modifiedQuery += " ${it.type}:\"${it.state.replace(" ", "+")}\""
} else {
splitted.forEach { tag ->
val trimmed = tag.trim().lowercase()
if (trimmed.startsWith('-')) {
modifiedQuery += " -${it.type}:\"${trimmed.removePrefix("-").replace(" ", "+")}\""
} else {
modifiedQuery += " ${it.type}:\"${trimmed.replace(" ", "+")}\""
}
}
}
}
}
uri.appendQueryParameter("f_search", modifiedQuery)
// when attempting to search with no genres selected, will auto select all genres
filters.filterIsInstance<GenreGroup>().firstOrNull()?.state?.let {
@ -352,6 +374,12 @@ abstract class EHentai(
// Bypass "Offensive For Everyone" content warning
cookies["nw"] = "1"
cookies["ipb_member_id"] = memberId
cookies["ipb_pass_hash"] = passHash
cookies["igneous"] = ""
buildCookies(cookies)
}
@ -388,12 +416,17 @@ abstract class EHentai(
EnforceLanguageFilter(getEnforceLanguagePref()),
Watched(),
GenreGroup(),
TagFilter("Misc Tags", triStateBoxesFrom(miscTags), "other"),
TagFilter("Female Tags", triStateBoxesFrom(femaleTags), "female"),
TagFilter("Male Tags", triStateBoxesFrom(maleTags), "male"),
Filter.Header("Separate tags with commas (,)"),
Filter.Header("Prepend with dash (-) to exclude"),
Filter.Header("Use 'Female Tags' or 'Male Tags' for specific categories. 'Tags' searches all categories."),
TextFilter("Tags", "tag"),
TextFilter("Female Tags", "female"),
TextFilter("Male Tags", "male"),
AdvancedGroup(),
)
internal open class TextFilter(name: String, val type: String, val specific: String = "") : Filter.Text(name)
class Watched : CheckBox("Watched List"), UriFilter {
override fun addToUri(builder: Uri.Builder) {
if (state) {
@ -487,17 +520,6 @@ abstract class EHentai(
private class EnforceLanguageFilter(default: Boolean) : CheckBox("Enforce language", default)
private val miscTags = "3d, already uploaded, anaglyph, animal on animal, animated, anthology, arisa mizuhara, artbook, ashiya noriko, bailey jay, body swap, caption, chouzuki maryou, christian godard, comic, compilation, dakimakura, fe galvao, ffm threesome, figure, forbidden content, full censorship, full color, game sprite, goudoushi, group, gunyou mikan, harada shigemitsu, hardcore, helly von valentine, higurashi rin, hololive, honey select, how to, incest, incomplete, ishiba yoshikazu, jessica nigri, kalinka fox, kanda midori, kira kira, kitami eri, kuroi hiroki, lenfried, lincy leaw, marie claude bourbonnais, matsunaga ayaka, me me me, missing cover, mmf threesome, mmt threesome, mosaic censorship, mtf threesome, multi-work series, no penetration, non-nude, novel, nudity only, oakazaki joe, out of order, paperchild, pm02 colon 20, poor grammar, radio comix, realporn, redraw, replaced, sakaki kasa, sample, saotome love, scanmark, screenshots, sinful goddesses, sketch lines, stereoscopic, story arc, takeuti ken, tankoubon, themeless, tikuma jukou, time stop, tsubaki zakuro, ttm threesome, twins, uncensored, vandych alex, variant set, watermarked, webtoon, western cg, western imageset, western non-h, yamato nadeshiko club, yui okada, yukkuri, zappa go"
private val femaleTags = "ahegao, anal, angel, apron, bandages, bbw, bdsm, beauty mark, big areolae, big ass, big breasts, big clit, big lips, big nipples, bikini, blackmail, bloomers, blowjob, bodysuit, bondage, breast expansion, bukkake, bunny girl, business suit, catgirl, centaur, cheating, chinese dress, christmas, collar, corset, cosplaying, cowgirl, crossdressing, cunnilingus, dark skin, daughter, deepthroat, defloration, demon girl, double penetration, dougi, dragon, drunk, elf, exhibitionism, farting, females only, femdom, filming, fingering, fishnets, footjob, fox girl, furry, futanari, garter belt, ghost, giantess, glasses, gloves, goblin, gothic lolita, growth, guro, gyaru, hair buns, hairy, hairy armpits, handjob, harem, hidden sex, horns, huge breasts, humiliation, impregnation, incest, inverted nipples, kemonomimi, kimono, kissing, lactation, latex, leg lock, leotard, lingerie, lizard girl, maid, masked face, masturbation, midget, miko, milf, mind break, mind control, monster girl, mother, muscle, nakadashi, netorare, nose hook, nun, nurse, oil, paizuri, panda girl, pantyhose, piercing, pixie cut, policewoman, ponytail, pregnant, rape, rimjob, robot, scat, schoolgirl uniform, sex toys, shemale, sister, small breasts, smell, sole dickgirl, sole female, squirting, stockings, sundress, sweating, swimsuit, swinging, tail, tall girl, teacher, tentacles, thigh high boots, tomboy, transformation, twins, twintails, unusual pupils, urination, vore, vtuber, widow, wings, witch, wolf girl, x-ray, yuri, zombie"
private val maleTags = "anal, bbm, big ass, big penis, bikini, blood, blowjob, bondage, catboy, cheating, chikan, condom, crab, crossdressing, dark skin, deepthroat, demon, dickgirl on male, dilf, dog boy, double anal, double penetration, dragon, drunk, exhibitionism, facial hair, feminization, footjob, fox boy, furry, glasses, group, guro, hairy, handjob, hidden sex, horns, huge penis, human on furry, kimono, lingerie, lizard guy, machine, maid, males only, masturbation, mmm threesome, monster, muscle, nakadashi, ninja, octopus, oni, pillory, policeman, possession, prostate massage, public use, schoolboy uniform, schoolgirl uniform, sex toys, shotacon, sleeping, snuff, sole male, stockings, sunglasses, swimsuit, tall man, tentacles, tomgirl, unusual pupils, virginity, waiter, x-ray, yaoi, zombie"
private fun triStateBoxesFrom(tagString: String): List<TagTriState> = tagString.split(", ").map { TagTriState(it) }
class TagTriState(tag: String) : TriState(tag)
class TagFilter(name: String, private val triStateBoxes: List<TagTriState>, private val nameSpace: String) : Group<TagTriState>(name, triStateBoxes) {
fun markedTags() = triStateBoxes.filter { it.isIncluded() }.map { "$nameSpace:${it.name}" } + triStateBoxes.filter { it.isExcluded() }.map { "-$nameSpace:${it.name}" }
}
// map languages to their internal ids
private val languageMappings = listOf(
Pair("japanese", listOf("0", "1024", "2048")),
@ -529,6 +551,16 @@ abstract class EHentai(
private const val ENFORCE_LANGUAGE_PREF_TITLE = "Enforce Language"
private const val ENFORCE_LANGUAGE_PREF_SUMMARY = "If checked, forces browsing of manga matching a language tag"
private const val ENFORCE_LANGUAGE_PREF_DEFAULT_VALUE = false
private const val MEMBER_ID_PREF_KEY = "MEMBER_ID"
private const val MEMBER_ID_PREF_TITLE = "ipb_member_id"
private const val MEMBER_ID_PREF_SUMMARY = "ipb_member_id value"
private const val MEMBER_ID_PREF_DEFAULT_VALUE = ""
private const val PASS_HASH_PREF_KEY = "PASS_HASH"
private const val PASS_HASH_PREF_TITLE = "ipb_pass_hash"
private const val PASS_HASH_PREF_SUMMARY = "ipb_pass_hash value"
private const val PASS_HASH_PREF_DEFAULT_VALUE = ""
}
// Preferences
@ -545,8 +577,56 @@ abstract class EHentai(
preferences.edit().putBoolean("${ENFORCE_LANGUAGE_PREF_KEY}_$lang", checkValue).commit()
}
}
val memberIdPref = EditTextPreference(screen.context).apply {
key = MEMBER_ID_PREF_KEY
title = MEMBER_ID_PREF_TITLE
summary = MEMBER_ID_PREF_SUMMARY
setDefaultValue(MEMBER_ID_PREF_DEFAULT_VALUE)
}
val passHashPref = EditTextPreference(screen.context).apply {
key = PASS_HASH_PREF_KEY
title = PASS_HASH_PREF_TITLE
summary = PASS_HASH_PREF_SUMMARY
setDefaultValue(PASS_HASH_PREF_DEFAULT_VALUE)
}
screen.addPreference(memberIdPref)
screen.addPreference(passHashPref)
screen.addPreference(enforceLanguagePref)
}
private fun getEnforceLanguagePref(): Boolean = preferences.getBoolean("${ENFORCE_LANGUAGE_PREF_KEY}_$lang", ENFORCE_LANGUAGE_PREF_DEFAULT_VALUE)
private fun getCookieValue(cookieTitle: String, defaultValue: String, prefKey: String): String {
val cookies = webViewCookieManager.getCookie("https://forums.e-hentai.org")
var value: String? = null
if (cookies != null) {
val cookieArray = cookies.split("; ")
for (cookie in cookieArray) {
if (cookie.startsWith("$cookieTitle=")) {
value = cookie.split("=")[1]
break
}
}
}
if (value == null) {
value = preferences.getString(prefKey, defaultValue) ?: defaultValue
}
return value
}
private fun getPassHashPref(): String {
return getCookieValue(PASS_HASH_PREF_TITLE, PASS_HASH_PREF_DEFAULT_VALUE, PASS_HASH_PREF_KEY)
}
private fun getMemberIdPref(): String {
return getCookieValue(MEMBER_ID_PREF_TITLE, MEMBER_ID_PREF_DEFAULT_VALUE, MEMBER_ID_PREF_KEY)
}
}

View File

@ -3,7 +3,7 @@ ext {
extClass = '.EternalMangasFactory'
themePkg = 'mangaesp'
baseUrl = 'https://eternalmangas.com'
overrideVersionCode = 0
overrideVersionCode = 1
isNsfw = true
}

View File

@ -7,11 +7,17 @@ import eu.kanade.tachiyomi.network.POST
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.util.asJsoup
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import okhttp3.FormBody
import okhttp3.Response
import org.jsoup.Jsoup
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.Locale
open class EternalMangas(
lang: String,
@ -55,9 +61,62 @@ open class EternalMangas(
return parseComicsList(page, query, filters)
}
override fun pageListParse(response: Response): List<Page> {
var document = response.asJsoup()
override fun mangaDetailsParse(response: Response) = SManga.create().apply {
val body = jsRedirect(response)
MANGA_DETAILS_REGEX.find(body)?.groupValues?.get(1)?.let {
val unescapedJson = it.unescape()
return json.decodeFromString<SeriesDto>(unescapedJson).toSMangaDetails()
}
val document = Jsoup.parse(body)
with(document.selectFirst("div#info")!!) {
title = select("div:has(p.font-bold:contains(Títuto)) > p.text-sm").text()
author = select("div:has(p.font-bold:contains(Autor)) > p.text-sm").text()
artist = select("div:has(p.font-bold:contains(Artista)) > p.text-sm").text()
genre = select("div:has(p.font-bold:contains(Género)) > p.text-sm > span").joinToString { it.ownText() }
}
description = document.select("div#sinopsis p").text()
thumbnail_url = document.selectFirst("div.contenedor img.object-cover")?.imgAttr()
}
private val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US)
override fun chapterListParse(response: Response): List<SChapter> {
val body = jsRedirect(response)
MANGA_DETAILS_REGEX.find(body)?.groupValues?.get(1)?.let {
val unescapedJson = it.unescape()
val series = json.decodeFromString<SeriesDto>(unescapedJson)
return series.chapters.map { chapter -> chapter.toSChapter(seriesPath, series.slug) }
}
val document = Jsoup.parse(body)
return document.select("div.contenedor > div.grid > div > a").map {
SChapter.create().apply {
name = it.selectFirst("span.text-sm")!!.text()
date_upload = try {
it.selectFirst("span.chapter-date")?.attr("data-date")?.let { date ->
dateFormat.parse(date)?.time
} ?: 0
} catch (e: ParseException) {
0
}
setUrlWithoutDomain(it.selectFirst("a")!!.attr("href"))
}
}
}
override fun pageListParse(response: Response): List<Page> {
val doc = Jsoup.parse(jsRedirect(response))
return doc.select("main > img").mapIndexed { i, img ->
Page(i, imageUrl = img.imgAttr())
}
}
private fun jsRedirect(response: Response): String {
var body = response.body.string()
val document = Jsoup.parse(body)
document.selectFirst("body > form[method=post]")?.let {
val action = it.attr("action")
val inputs = it.select("input")
@ -67,12 +126,9 @@ open class EternalMangas(
form.add(input.attr("name"), input.attr("value"))
}
document = client.newCall(POST(action, headers, form.build())).execute().asJsoup()
}
return document.select("main > img").mapIndexed { i, img ->
Page(i, imageUrl = img.imgAttr())
body = client.newCall(POST(action, headers, form.build())).execute().body.string()
}
return body
}
@Serializable

View File

@ -0,0 +1,8 @@
ext {
extName = 'EveriaClub (unoriginal)'
extClass = '.EveriaClubCom'
extVersionCode = 1
isNsfw = true
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View File

@ -0,0 +1,152 @@
package eu.kanade.tachiyomi.extension.all.everiaclubcom
import eu.kanade.tachiyomi.network.GET
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 okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Element
import rx.Observable
class EveriaClubCom() : HttpSource() {
override val baseUrl = "https://www.everiaclub.com"
override val lang = "all"
override val name = "EveriaClub (unoriginal)"
override val supportsLatest = true
override fun headersBuilder() = super.headersBuilder()
.add("Referer", "$baseUrl/")
private val Element.imgSrc: String?
get() = when {
hasAttr("data-original") -> attr("data-original")
hasAttr("data-lazy-src") -> attr("data-lazy-src")
hasAttr("data-src") -> attr("data-src")
hasAttr("src") -> attr("src")
else -> null
}
private fun mangaFromElement(it: Element) = SManga.create().apply {
setUrlWithoutDomain(it.attr("abs:href").removePrefix(baseUrl))
with(it.selectFirst("img")!!) {
thumbnail_url = imgSrc
title = attr("title")
}
}
// Latest
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/?page=$page", headers)
override fun latestUpdatesParse(response: Response): MangasPage {
val document = response.asJsoup()
val mangas = document.select(".mainleft .leftp > a").map {
mangaFromElement(it)
}
val isLastPage = document.selectFirst("li:has(span.current) + li > a")
return MangasPage(mangas, isLastPage != null)
}
// Popular
override fun popularMangaRequest(page: Int) = GET(baseUrl, headers)
override fun popularMangaParse(response: Response): MangasPage {
val document = response.asJsoup()
val mangas = document.select(".mainright li a").map {
mangaFromElement(it)
}
return MangasPage(mangas, false)
}
// Search
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val tagFilter = filters.filterIsInstance<TagFilter>().first()
val categoryFilter = filters.filterIsInstance<CategoryFilter>().first()
val url = when {
tagFilter.state.isNotBlank() -> baseUrl.toHttpUrl().newBuilder()
.addPathSegment("tags")
.addPathSegment(tagFilter.state)
.addPathSegment(page.toString())
categoryFilter.state != 0 -> "$baseUrl/${categoryFilter.toUriPart()}?page=$page".toHttpUrl().newBuilder()
query.isNotBlank() -> baseUrl.toHttpUrl().newBuilder()
.addPathSegment("search")
.addPathSegment("")
.addQueryParameter("keyword", query)
.addQueryParameter("page", page.toString())
else -> "$baseUrl/?page=$page".toHttpUrl().newBuilder()
}
return GET(url.build(), headers)
}
override fun searchMangaParse(response: Response) = latestUpdatesParse(response)
// Details
override fun mangaDetailsParse(response: Response): SManga {
val document = response.asJsoup()
return SManga.create().apply {
genre = document.select("div.end span:contains(Tags:) ~ a > p.tags").joinToString {
it.ownText()
}
status = SManga.COMPLETED
}
}
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
val chapter = SChapter.create().apply {
url = manga.url
name = "Gallery"
chapter_number = 1f
date_upload = 0L
}
return Observable.just(listOf(chapter))
}
override fun chapterListParse(response: Response) = throw UnsupportedOperationException()
override fun pageListParse(response: Response): List<Page> {
val document = response.asJsoup()
val images = document.select(".mainleft img")
return images.mapIndexed { index, image ->
Page(index, imageUrl = image.imgSrc)
}
}
override fun imageUrlParse(response: Response) =
throw UnsupportedOperationException()
// Filters
override fun getFilterList(): FilterList = FilterList(
Filter.Header("NOTE: Only one filter will be applied!"),
Filter.Separator(),
TagFilter(),
CategoryFilter(),
)
open class UriPartFilter(
displayName: String,
private val valuePair: Array<Pair<String, String>>,
) : Filter.Select<String>(displayName, valuePair.map { it.first }.toTypedArray()) {
fun toUriPart() = valuePair[state].second
}
class CategoryFilter : UriPartFilter(
"Category",
arrayOf(
Pair("Any", ""),
Pair("Gravure", "Gravure.html"),
Pair("Japan", "Japan.html"),
Pair("Korea", "Korea.html"),
Pair("Thailand", "Thailand.html"),
Pair("Chinese", "Chinese.html"),
Pair("Cosplay", "Cosplay.html"),
),
)
class TagFilter : Filter.Text("Tag")
}

View File

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

View File

@ -6,6 +6,8 @@ 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.ParsedHttpSource
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
import org.jsoup.nodes.Document
@ -65,22 +67,43 @@ class FoamGirl() : ParsedHttpSource() {
override fun searchMangaSelector() = popularMangaSelector()
override fun pageListParse(document: Document): List<Page> {
val pages = mutableListOf<Page>()
val imageCount = document.select(".post_title_topimg").text().substringAfter("(").substringBefore("P").toInt()
val imageUrl = document.select(".imageclick-imgbox").attr("href").toHttpUrl()
val imagePrefix = imageUrl.pathSegments.last().substringBefore(".").toLong() / 10
for (i in 0 until imageCount) {
pages.add(
Page(
i,
imageUrl = imageUrl.newBuilder().apply {
removePathSegment(imageUrl.pathSize - 1)
addPathSegment("${imagePrefix}${i + 2}.jpg")
}.build().toString(),
),
val baseIndex = imageUrl.pathSegments.last().substringBefore(".")
return if (baseIndex.isNumber()) {
getPagesListByNumber(imageCount, imageUrl, baseIndex)
} else {
getPageListByDocument(document)
}
}
private fun getPagesListByNumber(imageCount: Int, imageUrl: HttpUrl, baseIndex: String): List<Page> {
val imagePrefix = baseIndex.toLong() / 10
return (0 until imageCount).map { index ->
Page(
index,
imageUrl = imageUrl.newBuilder().apply {
removePathSegment(imageUrl.pathSize - 1)
addPathSegment("${imagePrefix}${index + 2}.jpg")
}.build().toString(),
)
}
return pages
}
private fun getPageListByDocument(document: Document): List<Page> {
val pages = document.select("#image_div img").mapIndexed { index, element ->
Page(index, imageUrl = element.absUrl("src"))
}.toList()
val nextPageUrl = document.selectFirst(".page-numbers[title=Next]")
?.absUrl("href")
?.takeIf { HAS_NEXT_PAGE_REGEX in it }
?: return pages
return client.newCall(GET(nextPageUrl, headers)).execute().asJsoup().let {
pages + getPageListByDocument(it)
}
}
override fun chapterFromElement(element: Element) = SChapter.create().apply {
@ -119,7 +142,10 @@ class FoamGirl() : ParsedHttpSource() {
}
}
private fun String.isNumber() = isNotEmpty() && all { it.isDigit() }
companion object {
val HAS_NEXT_PAGE_REGEX = """(\d+_\d+)""".toRegex()
private val DATE_FORMAT by lazy {
SimpleDateFormat("yyyy.M.d", Locale.ENGLISH)
}

View File

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

View File

@ -1,30 +1,32 @@
package eu.kanade.tachiyomi.extension.all.hennojin
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.source.model.FilterList
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.ParsedHttpSource
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import org.jsoup.select.Evaluator
import java.text.SimpleDateFormat
import java.util.Locale
class Hennojin(override val lang: String, suffix: String) : ParsedHttpSource() {
override val baseUrl = "https://hennojin.com/home/$suffix"
class Hennojin(override val lang: String) : ParsedHttpSource() {
override val baseUrl = "https://hennojin.com"
override val name = "Hennojin"
// Popular is latest
override val supportsLatest = false
private val httpUrl by lazy { baseUrl.toHttpUrl() }
private val httpUrl by lazy { "$baseUrl/home".toHttpUrl() }
override fun latestUpdatesSelector() = popularMangaSelector()
@ -41,15 +43,23 @@ class Hennojin(override val lang: String, suffix: String) : ParsedHttpSource() {
override fun popularMangaNextPageSelector() = ".paginate .next"
override fun popularMangaRequest(page: Int) =
httpUrl.request { addEncodedPathSegments("page/$page") }
httpUrl.request {
when (lang) {
"ja" -> {
addEncodedPathSegments("page/$page/")
addQueryParameter("archive", "raw")
}
else -> addEncodedPathSegments("page/$page")
}
}
override fun popularMangaFromElement(element: Element) =
SManga.create().apply {
element.selectFirst(".title_link > a").let {
title = it!!.text()
setUrlWithoutDomain(it.attr("href"))
setUrlWithoutDomain(it.absUrl("href"))
}
thumbnail_url = element.selectFirst("img")!!.attr("src")
thumbnail_url = element.selectFirst("img")?.absUrl("src")
}
override fun searchMangaSelector() = popularMangaSelector()
@ -66,46 +76,68 @@ class Hennojin(override val lang: String, suffix: String) : ParsedHttpSource() {
override fun searchMangaFromElement(element: Element) =
popularMangaFromElement(element)
override fun mangaDetailsRequest(manga: SManga) =
GET("https://hennojin.com" + manga.url, headers)
override fun mangaDetailsParse(document: Document) =
SManga.create().apply {
description = document.selectFirst(
description = document.select(
".manga-subtitle + p + p",
)?.html()?.replace("<br> ", "\n")
).joinToString("\n") {
it
.apply { select(Evaluator.Tag("br")).prepend("\\n") }
.text()
.replace("\\n", "\n")
.replace("\n ", "\n")
}.trim()
genre = document.select(
".tags-list a[href*=/parody/]," +
".tags-list a[href*=/tags/]," +
".tags-list a[href*=/character/]",
)?.joinToString { it.text() }
artist = document.select(
).joinToString { it.text() }
artist = document.selectFirst(
".tags-list a[href*=/artist/]",
)?.joinToString { it.text() }
author = document.select(
)?.text()
author = document.selectFirst(
".tags-list a[href*=/group/]",
)?.joinToString { it.text() } ?: artist
)?.text() ?: artist
status = SManga.COMPLETED
}
override fun fetchChapterList(manga: SManga) =
Request.Builder().url(manga.thumbnail_url!!)
.head().build().run(client::newCall)
.asObservableSuccess().map { res ->
SChapter.create().apply {
name = "Chapter"
url = manga.reader
date_upload = res.date
chapter_number = -1f
}.let(::listOf)
}!!
override fun pageListRequest(chapter: SChapter) =
GET("https://hennojin.com" + chapter.url, headers)
override fun chapterListParse(response: Response): List<SChapter> {
val document = response.asJsoup(response.body.string())
val date = document
.selectFirst(".manga-thumbnail > img")
?.absUrl("src")
?.let { url ->
Request.Builder()
.url(url)
.head()
.build()
.run(client::newCall)
.execute()
.date
}
return document.select("a:contains(Read Online)").map {
SChapter.create().apply {
setUrlWithoutDomain(
it
?.absUrl("href")
?.toHttpUrlOrNull()
?.newBuilder()
?.removeAllQueryParameters("view")
?.addQueryParameter("view", "multi")
?.build()
?.toString()
?: it.absUrl("href"),
)
name = "Chapter"
date?.run { date_upload = this }
chapter_number = -1f
}
}
}
override fun pageListParse(document: Document) =
document.select(".slideshow-container > img")
.mapIndexed { idx, img -> Page(idx, "", img.absUrl("src")) }
.mapIndexed { idx, img -> Page(idx, imageUrl = img.absUrl("src")) }
private inline fun HttpUrl.request(
block: HttpUrl.Builder.() -> HttpUrl.Builder,
@ -114,9 +146,6 @@ class Hennojin(override val lang: String, suffix: String) : ParsedHttpSource() {
private inline val Response.date: Long
get() = headers["Last-Modified"]?.run(httpDate::parse)?.time ?: 0L
private inline val SManga.reader: String
get() = "/home/manga-reader/?manga=$title&view=multi"
override fun chapterListSelector() =
throw UnsupportedOperationException()

View File

@ -4,7 +4,7 @@ import eu.kanade.tachiyomi.source.SourceFactory
class HennojinFactory : SourceFactory {
override fun createSources() = listOf(
Hennojin("en", ""),
Hennojin("ja", "?archive=raw"),
Hennojin("en"),
Hennojin("ja"),
)
}

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<activity
android:name=".all.koharu.KoharuUrlActivity"
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:scheme="https" android:pathPattern="/g/..*/..*"/>
<data android:host="koharu.to" />
<data android:host="schale.network" />
<data android:host="gehenna.jp" />
<data android:host="niyaniya.moe" />
<data android:host="seia.to" />
<data android:host="shupogaki.moe" />
<data android:host="hoshino.one" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@ -0,0 +1,8 @@
ext {
extName = 'SchaleNetwork'
extClass = '.KoharuFactory'
extVersionCode = 11
isNsfw = true
}
apply from: "$rootDir/common.gradle"

View File

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

View File

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.extension.en.koharu
package eu.kanade.tachiyomi.extension.all.koharu
import android.app.Application
import android.content.SharedPreferences
@ -18,6 +18,7 @@ import eu.kanade.tachiyomi.source.online.HttpSource
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
@ -28,17 +29,21 @@ import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat
import java.util.Locale
class Koharu : HttpSource(), ConfigurableSource {
override val name = "Koharu"
class Koharu(
override val lang: String = "all",
private val searchLang: String = "",
) : HttpSource(), ConfigurableSource {
override val baseUrl = "https://koharu.to"
override val name = "SchaleNetwork"
override val baseUrl = "https://schale.network"
override val id = if (lang == "en") 1484902275639232927 else super.id
private val apiUrl = baseUrl.replace("://", "://api.")
private val apiBooksUrl = "$apiUrl/books"
override val lang = "en"
override val supportsLatest = true
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
@ -58,9 +63,25 @@ class Koharu : HttpSource(), ConfigurableSource {
private fun remadd() = preferences.getBoolean(PREF_REM_ADD, false)
override fun headersBuilder() = super.headersBuilder()
.add("Referer", "$baseUrl/")
.add("Origin", baseUrl)
private fun getDomain(): String {
try {
val noRedirectClient = client.newBuilder().followRedirects(false).build()
val host = noRedirectClient.newCall(GET(baseUrl, headers)).execute()
.headers["Location"]?.toHttpUrlOrNull()?.host
?: return baseUrl
return "https://$host"
} catch (_: Exception) {
return baseUrl
}
}
private val lazyHeaders by lazy {
val domain = getDomain()
headersBuilder()
.set("Referer", "$domain/")
.set("Origin", domain)
.build()
}
private fun getManga(book: Entry) = SManga.create().apply {
setUrlWithoutDomain("${book.id}/${book.public_key}")
@ -70,7 +91,6 @@ class Koharu : HttpSource(), ConfigurableSource {
private fun getImagesByMangaEntry(entry: MangaEntry): Pair<ImagesInfo, String> {
val data = entry.data
val quality = 0
fun getIPK(
ori: DataKey?,
alt1: DataKey?,
@ -103,19 +123,19 @@ class Koharu : HttpSource(), ConfigurableSource {
else -> "0"
}
val imagesResponse = client.newCall(GET("$apiBooksUrl/data/${entry.id}/${entry.public_key}/$id/$public_key?v=${entry.updated_at ?: entry.created_at}&w=$realQuality", headers)).execute()
val imagesResponse = client.newCall(GET("$apiBooksUrl/data/${entry.id}/${entry.public_key}/$id/$public_key?v=${entry.updated_at ?: entry.created_at}&w=$realQuality", lazyHeaders)).execute()
val images = imagesResponse.parseAs<ImagesInfo>() to realQuality
return images
}
// Latest
override fun latestUpdatesRequest(page: Int) = GET("$apiBooksUrl?page=$page", headers)
override fun latestUpdatesRequest(page: Int) = GET("$apiBooksUrl?page=$page" + if (searchLang.isNotBlank()) "&s=language!:\"$searchLang\"" else "", lazyHeaders)
override fun latestUpdatesParse(response: Response) = popularMangaParse(response)
// Popular
override fun popularMangaRequest(page: Int) = GET("$apiBooksUrl?sort=8&page=$page", headers)
override fun popularMangaRequest(page: Int) = GET("$apiBooksUrl?sort=8&page=$page" + if (searchLang.isNotBlank()) "&s=language!:\"$searchLang\"" else "", lazyHeaders)
override fun popularMangaParse(response: Response): MangasPage {
val data = response.parseAs<Books>()
@ -130,7 +150,7 @@ class Koharu : HttpSource(), ConfigurableSource {
return when {
query.startsWith(PREFIX_ID_KEY_SEARCH) -> {
val ipk = query.removePrefix(PREFIX_ID_KEY_SEARCH)
val response = client.newCall(GET("$apiBooksUrl/detail/$ipk", headers)).execute()
val response = client.newCall(GET("$apiBooksUrl/detail/$ipk", lazyHeaders)).execute()
Observable.just(searchMangaParse2(response))
}
else -> super.fetchSearchManga(page, query, filters)
@ -141,6 +161,7 @@ class Koharu : HttpSource(), ConfigurableSource {
val url = apiBooksUrl.toHttpUrl().newBuilder().apply {
val terms: MutableList<String> = mutableListOf()
if (lang != "all") terms += "language!:\"$searchLang\""
filters.forEach { filter ->
when (filter) {
is SortFilter -> addQueryParameter("sort", filter.getValue())
@ -156,7 +177,7 @@ class Koharu : HttpSource(), ConfigurableSource {
if (filter.state.isNotEmpty()) {
val tags = filter.state.split(",").filter(String::isNotBlank).joinToString(",")
if (tags.isNotBlank()) {
terms += "${filter.type}!:" + '"' + tags + '"'
terms += "${filter.type}!:" + if (filter.type == "pages") tags else '"' + tags + '"'
}
}
}
@ -168,7 +189,7 @@ class Koharu : HttpSource(), ConfigurableSource {
addQueryParameter("page", page.toString())
}.build()
return GET(url, headers)
return GET(url, lazyHeaders)
}
override fun searchMangaParse(response: Response) = popularMangaParse(response)
@ -190,7 +211,7 @@ class Koharu : HttpSource(), ConfigurableSource {
// Details
override fun mangaDetailsRequest(manga: SManga): Request {
return GET("$apiBooksUrl/detail/${manga.url}", headers)
return GET("$apiBooksUrl/detail/${manga.url}", lazyHeaders)
}
override fun mangaDetailsParse(response: Response): SManga {
@ -291,7 +312,7 @@ class Koharu : HttpSource(), ConfigurableSource {
// Chapter
override fun chapterListRequest(manga: SManga): Request {
return GET("$apiBooksUrl/detail/${manga.url}", headers)
return GET("$apiBooksUrl/detail/${manga.url}", lazyHeaders)
}
override fun chapterListParse(response: Response): List<SChapter> {
@ -310,7 +331,7 @@ class Koharu : HttpSource(), ConfigurableSource {
// Page List
override fun pageListRequest(chapter: SChapter): Request {
return GET("$apiBooksUrl/detail/${chapter.url}", headers)
return GET("$apiBooksUrl/detail/${chapter.url}", lazyHeaders)
}
override fun pageListParse(response: Response): List<Page> {
@ -322,6 +343,10 @@ class Koharu : HttpSource(), ConfigurableSource {
}
}
override fun imageRequest(page: Page): Request {
return GET(page.imageUrl!!, lazyHeaders)
}
override fun imageUrlParse(response: Response) = throw UnsupportedOperationException()
// Settings

View File

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.extension.en.koharu
package eu.kanade.tachiyomi.extension.all.koharu
import kotlinx.serialization.Serializable

View File

@ -0,0 +1,13 @@
package eu.kanade.tachiyomi.extension.all.koharu
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceFactory
class KoharuFactory : SourceFactory {
override fun createSources(): List<Source> = listOf(
Koharu(),
Koharu("en", "english"),
Koharu("ja", "japanese"),
Koharu("zh", "chinese"),
)
}

View File

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.extension.en.koharu
package eu.kanade.tachiyomi.extension.all.koharu
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList

View File

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.extension.en.koharu
package eu.kanade.tachiyomi.extension.all.koharu
import android.app.Activity
import android.content.ActivityNotFoundException

View File

@ -76,6 +76,8 @@ original_language=Original language
original_language_filter_chinese=%s (Manhua)
original_language_filter_japanese=%s (Manga)
original_language_filter_korean=%s (Manhwa)
prefer_title_in_extension_language=Use Alternate Titles
prefer_title_in_extension_language_summary=If there is an alternate title available which matches the extension language, it will be used
publication_demographic=Publication demographic
publication_demographic_josei=Josei
publication_demographic_none=None

View File

@ -1,7 +1,7 @@
ext {
extName = 'MangaDex'
extClass = '.MangaDexFactory'
extVersionCode = 194
extVersionCode = 196
isNsfw = true
}

View File

@ -138,6 +138,11 @@ object MDConstants {
return "${altTitlesInDescPref}_$dexLang"
}
private const val preferExtensionLangTitlePref = "preferExtensionLangTitle"
fun getPreferExtensionLangTitlePrefKey(dexLang: String): String {
return "${preferExtensionLangTitlePref}_$dexLang"
}
private const val tagGroupContent = "content"
private const val tagGroupFormat = "format"
private const val tagGroupGenre = "genre"

View File

@ -113,7 +113,7 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
.firstInstanceOrNull<CoverArtDto>()
?.attributes?.fileName
}
helper.createBasicManga(mangaDataDto, fileName, coverSuffix, dexLang)
helper.createBasicManga(mangaDataDto, fileName, coverSuffix, dexLang, preferences.preferExtensionLangTitle)
}
return MangasPage(mangaList, mangaListDto.hasNextPage)
@ -177,7 +177,7 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
.firstInstanceOrNull<CoverArtDto>()
?.attributes?.fileName
}
helper.createBasicManga(mangaDataDto, fileName, coverSuffix, dexLang)
helper.createBasicManga(mangaDataDto, fileName, coverSuffix, dexLang, preferences.preferExtensionLangTitle)
}
return MangasPage(mangaList, chapterListDto.hasNextPage)
@ -360,7 +360,7 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
.firstInstanceOrNull<CoverArtDto>()
?.attributes?.fileName
}
helper.createBasicManga(mangaDataDto, fileName, coverSuffix, dexLang)
helper.createBasicManga(mangaDataDto, fileName, coverSuffix, dexLang, preferences.preferExtensionLangTitle)
}
return mangaList
@ -423,6 +423,7 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
dexLang,
preferences.coverQuality,
preferences.altTitlesInDesc,
preferences.preferExtensionLangTitle,
)
}
@ -757,11 +758,27 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
}
}
val preferExtensionLangTitlePref = SwitchPreferenceCompat(screen.context).apply {
key = MDConstants.getPreferExtensionLangTitlePrefKey(dexLang)
title = helper.intl["prefer_title_in_extension_language"]
summary = helper.intl["prefer_title_in_extension_language_summary"]
setDefaultValue(true)
setOnPreferenceChangeListener { _, newValue ->
val checkValue = newValue as Boolean
preferences.edit()
.putBoolean(MDConstants.getPreferExtensionLangTitlePrefKey(dexLang), checkValue)
.commit()
}
}
screen.addPreference(coverQualityPref)
screen.addPreference(tryUsingFirstVolumeCoverPref)
screen.addPreference(dataSaverPref)
screen.addPreference(standardHttpsPortPref)
screen.addPreference(altTitlesInDescPref)
screen.addPreference(preferExtensionLangTitlePref)
screen.addPreference(contentRatingPref)
screen.addPreference(originalLanguagePref)
screen.addPreference(blockedGroupsPref)
@ -840,6 +857,9 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
private val SharedPreferences.altTitlesInDesc
get() = getBoolean(MDConstants.getAltTitlesInDescPrefKey(dexLang), false)
private val SharedPreferences.preferExtensionLangTitle
get() = getBoolean(MDConstants.getPreferExtensionLangTitlePrefKey(dexLang), true)
/**
* Previous versions of the extension allowed invalid UUID values to be stored in the
* preferences. This method clear invalid UUIDs in case the user have updated from

View File

@ -6,15 +6,19 @@ import eu.kanade.tachiyomi.source.SourceFactory
class MangaDexFactory : SourceFactory {
override fun createSources(): List<Source> = listOf(
MangaDexEnglish(),
MangadexAfrikaans(),
MangaDexAlbanian(),
MangaDexArabic(),
MangaDexAzerbaijani(),
MangaDexBasque(),
MangaDexBelarusian(),
MangaDexBengali(),
MangaDexBulgarian(),
MangaDexBurmese(),
MangaDexCatalan(),
MangaDexChineseSimplified(),
MangaDexChineseTraditional(),
MangaDexChuvash(),
MangaDexCroatian(),
MangaDexCzech(),
MangaDexDanish(),
@ -30,8 +34,10 @@ class MangaDexFactory : SourceFactory {
MangaDexHebrew(),
MangaDexHindi(),
MangaDexHungarian(),
MangaDexIrish(),
MangaDexIndonesian(),
MangaDexItalian(),
MangaDexJavanese(),
MangaDexJapanese(),
MangaDexKazakh(),
MangaDexKorean(),
@ -57,19 +63,25 @@ class MangaDexFactory : SourceFactory {
MangaDexThai(),
MangaDexTurkish(),
MangaDexUkrainian(),
MangaDexUrdu(),
MangaDexUzbek(),
MangaDexVietnamese(),
)
}
class MangadexAfrikaans : MangaDex("af")
class MangaDexAlbanian : MangaDex("sq")
class MangaDexArabic : MangaDex("ar")
class MangaDexAzerbaijani : MangaDex("az")
class MangaDexBasque : MangaDex("eu")
class MangaDexBelarusian : MangaDex("be")
class MangaDexBengali : MangaDex("bn")
class MangaDexBulgarian : MangaDex("bg")
class MangaDexBurmese : MangaDex("my")
class MangaDexCatalan : MangaDex("ca")
class MangaDexChineseSimplified : MangaDex("zh-Hans", "zh")
class MangaDexChineseTraditional : MangaDex("zh-Hant", "zh-hk")
class MangaDexChuvash : MangaDex("cv")
class MangaDexCroatian : MangaDex("hr")
class MangaDexCzech : MangaDex("cs")
class MangaDexDanish : MangaDex("da")
@ -86,9 +98,11 @@ class MangaDexGreek : MangaDex("el")
class MangaDexHebrew : MangaDex("he")
class MangaDexHindi : MangaDex("hi")
class MangaDexHungarian : MangaDex("hu")
class MangaDexIrish : MangaDex("ga")
class MangaDexIndonesian : MangaDex("id")
class MangaDexItalian : MangaDex("it")
class MangaDexJapanese : MangaDex("ja")
class MangaDexJavanese : MangaDex("jv")
class MangaDexKazakh : MangaDex("kk")
class MangaDexKorean : MangaDex("ko")
class MangaDexLatin : MangaDex("la")
@ -113,4 +127,6 @@ class MangaDexTelugu : MangaDex("te")
class MangaDexThai : MangaDex("th")
class MangaDexTurkish : MangaDex("tr")
class MangaDexUkrainian : MangaDex("uk")
class MangaDexUrdu : MangaDex("ur")
class MangaDexUzbek : MangaDex("uz")
class MangaDexVietnamese : MangaDex("vi")

View File

@ -275,6 +275,9 @@ class MangaDexHelper(lang: String) {
return GET(tokenRequestUrl, headers, cacheControl)
}
private fun List<Map<String, String>>.findTitleByLang(lang: String): String? =
firstOrNull { it[lang] != null }?.values?.singleOrNull()
/**
* Create a [SManga] from the JSON element with only basic attributes filled.
*/
@ -283,15 +286,24 @@ class MangaDexHelper(lang: String) {
coverFileName: String?,
coverSuffix: String?,
lang: String,
preferExtensionLangTitle: Boolean,
): SManga = SManga.create().apply {
url = "/manga/${mangaDataDto.id}"
val titleMap = mangaDataDto.attributes!!.title
val dirtyTitle =
titleMap.values.firstOrNull() // use literally anything from title as first resort
?: mangaDataDto.attributes.altTitles
.find { (it[lang] ?: it["en"]) !== null }
?.values?.singleOrNull() // find something else from alt titles
title = dirtyTitle?.removeEntities().orEmpty()
title = with(mangaDataDto.attributes) {
titleMap[lang] ?: altTitles.run {
val mainTitle = titleMap.values.firstOrNull()
val langTitle = findTitleByLang(lang)
val enTitle = findTitleByLang("en")
if (preferExtensionLangTitle) {
listOf(langTitle, mainTitle, enTitle)
} else {
listOf(mainTitle, langTitle, enTitle)
}.firstNotNullOfOrNull { it }
}
}?.removeEntities().orEmpty()
coverFileName?.let {
thumbnail_url = when (!coverSuffix.isNullOrEmpty()) {
@ -311,6 +323,7 @@ class MangaDexHelper(lang: String) {
lang: String,
coverSuffix: String?,
altTitlesInDesc: Boolean,
preferExtensionLangTitle: Boolean,
): SManga {
val attr = mangaDataDto.attributes!!
@ -370,7 +383,7 @@ class MangaDexHelper(lang: String) {
}
}
return createBasicManga(mangaDataDto, coverFileName, coverSuffix, lang).apply {
return createBasicManga(mangaDataDto, coverFileName, coverSuffix, lang, preferExtensionLangTitle).apply {
description = desc
author = authors.joinToString()
artist = artists.joinToString()

View File

@ -1,7 +1,7 @@
ext {
extName = 'MANGA Plus by SHUEISHA'
extClass = '.MangaPlusFactory'
extVersionCode = 53
extVersionCode = 54
}
apply from: "$rootDir/common.gradle"

View File

@ -210,6 +210,7 @@ class Label(val label: LabelCode? = LabelCode.WEEKLY_SHOUNEN_JUMP) {
LabelCode.SHOUNEN_JUMP_PLUS -> "Shounen Jump+"
LabelCode.MANGA_PLUS_CREATORS -> "MANGA Plus Creators"
LabelCode.SAIKYOU_JUMP -> "Saikyou Jump"
LabelCode.ULTRA_JUMP -> "Ultra Jump"
else -> null
}
}
@ -246,6 +247,9 @@ enum class LabelCode {
@SerialName("WSJ")
WEEKLY_SHOUNEN_JUMP,
@SerialName("UJ")
ULTRA_JUMP,
}
@Serializable

View File

@ -3,7 +3,7 @@ ext {
extClass = '.MangaReaderFactory'
themePkg = 'mangareader'
baseUrl = 'https://mangareader.to'
overrideVersionCode = 3
overrideVersionCode = 4
isNsfw = true
}

View File

@ -22,8 +22,11 @@ import org.jsoup.select.Evaluator
import rx.Observable
open class MangaReader(
override val lang: String,
val language: Language,
) : MangaReader() {
override val lang = language.code
override val name = "MangaReader"
override val baseUrl = "https://mangareader.to"
@ -33,10 +36,10 @@ open class MangaReader(
.build()
override fun latestUpdatesRequest(page: Int) =
GET("$baseUrl/filter?sort=latest-updated&language=$lang&page=$page", headers)
GET("$baseUrl/filter?sort=latest-updated&language=${language.infix}&page=$page", headers)
override fun popularMangaRequest(page: Int) =
GET("$baseUrl/filter?sort=most-viewed&language=$lang&page=$page", headers)
GET("$baseUrl/filter?sort=most-viewed&language=${language.infix}&page=$page", headers)
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val urlBuilder = baseUrl.toHttpUrl().newBuilder()
@ -47,7 +50,7 @@ open class MangaReader(
}
} else {
urlBuilder.addPathSegment("filter").apply {
addQueryParameter("language", lang)
addQueryParameter("language", language.infix)
addQueryParameter("page", page.toString())
filters.ifEmpty(::getFilterList).forEach { filter ->
when (filter) {
@ -142,7 +145,7 @@ open class MangaReader(
override fun parseChapterElements(response: Response, isVolume: Boolean): List<Element> {
val container = response.parseHtmlProperty().run {
val type = if (isVolume) "volumes" else "chapters"
selectFirst(Evaluator.Id("$lang-$type")) ?: return emptyList()
selectFirst(Evaluator.Id("${language.chapterInfix}-$type")) ?: return emptyList()
}
return container.children()
}

View File

@ -4,5 +4,19 @@ import eu.kanade.tachiyomi.source.SourceFactory
class MangaReaderFactory : SourceFactory {
override fun createSources() =
arrayOf("en", "fr", "ja", "ko", "zh").map(::MangaReader)
arrayOf(
Language("en"),
Language("es", chapterInfix = "es-mx"),
Language("fr"),
Language("ja"),
Language("ko"),
Language("pt-BR", infix = "pt"),
Language("zh"),
).map(::MangaReader)
}
data class Language(
val code: String,
val infix: String = code,
val chapterInfix: String = code.lowercase(),
)

View File

@ -1,6 +1,6 @@
ext {
extName = 'Koharu'
extClass = '.Koharu'
extName = 'Meitua.top'
extClass = '.MeituaTop'
extVersionCode = 8
isNsfw = true
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

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