Compare commits

...

320 Commits

Author SHA1 Message Date
renovate[bot] feb6c74f5c Update dependency gradle to v8.8 (#3347)
CI / Prepare job (push) Successful in 5s Details
CI / Build individual modules (push) Successful in 7m7s Details
CI / Publish repo (push) Successful in 55s Details
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-03 05:44:48 +01:00
bapeey e2d97e0860 HentaiRead: Fix images (#3343)
* Fix images

* yep
2024-06-03 05:44:48 +01:00
bapeey ba7836b0a1 SlimeRead: Fix regex (#3350)
* fix regex

* mfw the script is minified
2024-06-03 05:44:48 +01:00
Chopper df5abd131b Remove BlackoutScans (#3349) 2024-06-03 05:44:48 +01:00
bapeey 7d55908956 Comick: Fix dateFormat (#3342)
fix dateFormat
2024-06-03 05:44:48 +01:00
ZIDOUZI 60f5f67479 Hitomi: Add language to manga description, add a preferences to show gender (#3314)
* 1. Add language for manga description
2. Add a custom preference to choice if show gender as text or icon

* fix code style

* revert extra formatting

* change preference into switch

* remove unused import, increase extVersionCode

* Apply suggestions from code review

---------

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>
2024-06-03 05:44:48 +01:00
Chopper 258578c413 Add Marmota (#3340) 2024-06-03 05:44:48 +01:00
Norsze 7348576150 remove Anchira (#3339) 2024-06-03 05:44:48 +01:00
Chopper 9e8bbb7f0b PeachScan: Fix deeplink support (#3334)
* Adds support for any path in peachscan

* Deeplink: Ignore manga collection path

* Cleanup
2024-06-03 05:44:48 +01:00
Smol Ame 4dba7261af Update Flame Comics URL (#3331) 2024-06-03 05:44:48 +01:00
bapeey 8a8f22583b LuraToon: Update domain (#3330)
Coauthor for the intention

Co-authored-by: Smol Ame <155411819+Smol-Ame@users.noreply.github.com>
2024-06-03 05:44:48 +01:00
Chopper c2ffc63cc4 DemonSect: Add interceptor (#3326)
* Add an interceptor to show a message about login

* Use pathSegments
2024-06-03 05:44:48 +01:00
Smol Ame ed05461925 Update VortexScans URL (#3327) 2024-06-03 05:44:48 +01:00
stevenyomi 92bcce4f0d Make GoDa multisrc and add sources (#3318) 2024-06-03 05:44:48 +01:00
AwkwardPeak7 4ede181f9d Mangago: fix descrambling (#3313)
* Mangago: fix descrambling

* bump
2024-06-03 05:44:48 +01:00
GoonGooner 93d6a9b328 Add filters to Hitomi Extension (#3312)
* hitomi filters

* move filters to seperate file

* rename category filter to area filter, tag->area in parse
2024-06-03 05:44:48 +01:00
Vetle Ledaal e277149bc5 Kai Scans: update domain (#3294)
* Kai Scans: update domain

* switch to MangaThemesiaAlt
2024-06-03 05:44:48 +01:00
Chopper 70302c2d30 Add TuManhwas (#3285)
* Add TuManhwas

* Add icons

* Add isNsfw

* Fix manga title

* Move to ParsedHttpSource

* Add chapter date

* Move imgSrc to function

* Remove extra map

* Move regex to companion object

* Refactoring latestUpdatesFromElement
2024-06-03 05:44:48 +01:00
Chopper 3402c77336 Fix Baozimh.org and rename to GoDa Manhua (#3087)
* Fix Baozimh

* Cleanup

* Fix author info

* Use parent request

* Bump versionID

* Update

* Update code
* Rename source to GoDa Manhua and update icon (to distinguish from the "legit" Baozi Manhua)
* Add English source

* Update

* Update

* Keep the Chinese source only for now

* Add comments to explain why the English source is not added

* Error message on old chapter keys

---------

Co-authored-by: stevenyomi <95685115+stevenyomi@users.noreply.github.com>
2024-06-03 05:44:48 +01:00
bapeey 3310e274d5 CelestialMoon: Update domain and change theme (#3298)
update domain and theme
2024-06-03 05:44:48 +01:00
stevenyomi 08ad68a631 Comic Gamma: fix chapter list (#3296) 2024-06-03 05:44:48 +01:00
Vetle Ledaal 71ebbabdeb Guilda Tier Draw: update domain (#3293) 2024-06-03 05:44:48 +01:00
Vetle Ledaal 6892001319 Add Ghost Fansub (#3290) 2024-06-03 05:44:48 +01:00
Chopper 1e85171288 Add KomikPix (#3284) 2024-06-03 05:44:48 +01:00
AwkwardPeak7 17e79b4b79 Webtoon Hatti: update page selector (#3281) 2024-06-03 05:44:48 +01:00
Chopper 81827ef0d1 Add OtaScans (#3280) 2024-06-03 05:44:48 +01:00
AwkwardPeak7 be806a0771 Japscan: update chapter data selector (#3275) 2024-06-03 05:44:48 +01:00
KenjieDec b335be2148 MangaGeko Next Page Selector Fix ( Popular & Filter ) (#3274)
* Fix next page

Fixed next page selector ( Popular & Filter )

* change line endings

crlf to lf
2024-06-03 05:44:48 +01:00
bapeey 9a4258a47e EternalMangas: Move to Multi and fix pages not found (#3258)
* Move to Multi and fix pages

* skill issue

* mmmm

* remode id
2024-06-03 05:44:48 +01:00
kana-shii adfd31c749 Niverafansub url (#3273)
* nivera fansub url change

* Update build.gradle
2024-06-03 05:44:48 +01:00
bapeey 1b34ce2ddd HeanCMS: Fix headers (#3263)
bruh
2024-06-03 05:44:48 +01:00
kana-shii b514456b03 add yaoihub (#3252)
* add yaoihub

* updated icon (ty vetleledaal)
2024-06-03 05:44:48 +01:00
Chopper 611cf69a39 FRScan: Fix pages loading (#3230)
* Fix pages loading

* Change group name

* Remove variable

* Remove parseAs function and add page url
2024-06-03 05:44:48 +01:00
Smol Ame 29b2b23a86 Remove PotatoManga (#3243) 2024-06-03 05:44:48 +01:00
Chopper be2daf0c4a SayHentai: Update domain and fix popularManga (#3240)
* Update domain

* Fix popularManga
2024-06-03 05:44:48 +01:00
Chopper c900e6c0a0 TruyenVN: Update domain (#3239)
Update domain
2024-06-03 05:44:48 +01:00
Chopper d7b79fcc0f LxHentai: Update domain and fix deep linking (#3238)
* Fix domain

* Fix deep linking
2024-06-03 05:44:48 +01:00
Chopper dc236041b1 MangaCrab: Update domain (#3237)
Update domain
2024-06-03 05:44:48 +01:00
bapeey f7b2ff60ac SlimeRead: Use cloudflareClient (#3235)
* j2k fix

* here too
2024-06-03 05:44:48 +01:00
Chopper 5966f9699b Remove MangaTak (#3232) 2024-06-03 05:44:48 +01:00
Chopper 02af60dcf5 MangaMate: Add random user agent (#3231)
* Add random user agent

* Cleanup
2024-06-03 05:44:48 +01:00
Vetle Ledaal e2bca12ccd Remove NetTruyen (#3229) 2024-06-03 05:44:48 +01:00
sinkableShip 2c00628e87 Add Pixiv Comic (#3199)
* working, with webview to load page

doubt:
1. wrong episode number (using list index instead of real chapter number)
2. should we add the unavailable chapter to show or not (start with ※)
3. webview approach (slow and might get in error, too uncontrollable)
4. differentiating tags (using #) and category might bring problem sinces the added #

* check converting from response to ubytearray to image

* works fine, keep logs

* get rid of logs and another small things

* add logo

* clean forgotten things

* lint check: fix comment

* Apply suggestions from code review

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

* some refactoring, rename extension name and package to Pixiv Comic

* delete unused dependency

* use serial name on model

* Apply suggestions from code review

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

* prioritizing search over filter, change manga and chapter parse to just store the id, add tag interceptor in the case of tag not found

---------

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>
2024-06-03 05:44:48 +01:00
Smol Ame 89f53742bb Add Dream Scan (#3225)
* Add Dream Scan

* Added `Em Progresso` to "Ongoing" (didn't bump)
2024-06-03 05:44:48 +01:00
Chopper 9c5e1b3482 Siikomik: Update domain, name and icons (#3223)
Fix Siikomik
2024-06-03 05:44:48 +01:00
bapeey c33c22062c JeazScans: Update domain (#3221)
update domain
2024-06-03 05:44:48 +01:00
bapeey 642e90ccc7 DynastyScans: Add ratelimit (#3218)
add ratelimit
2024-06-03 05:44:48 +01:00
2Loong6 6a1cd83b51 fix kisslove (#3208)
* fix kisslove

* update overrideVersionCode

* fix function name

* fix lint

* fix code

* improve code quality
2024-06-03 05:44:48 +01:00
bapeey f860926491 Add Bymichi Scan (#3195)
add
2024-06-03 05:44:48 +01:00
Chopper 6770a0672a Yugen Mangas: bugfix (#3193)
Update paths and fix chapter pages
2024-06-03 05:44:48 +01:00
Chopper f1a59ba618 PeachScan: Deep linking support (#3162)
* Add support to deep linking

* Fix baseUrl

* Remove redirect

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

---------

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>
2024-06-03 05:44:48 +01:00
kana-shii 241578b0a2 add hot cabaret scan (#3155)
* add hot cabaret scan

* exception message

* exception message 2
2024-06-03 05:44:48 +01:00
kana-shii 5228acc900 add prazeres violentos (#3154)
* add prazeres violentos

* exception message

* exception message 2
2024-06-03 05:44:48 +01:00
Chopper fd9d8d39f0 FlowerManga: Rename source (#3159)
* Raname source

* Fix name
2024-06-03 05:44:48 +01:00
Chopper a8c69a300d Add FlowerMangaDotCom (#3131)
* Add FlowerMangaB

* Fix extClass

* Fix date format

* Rename source

* Fix name
2024-06-03 05:44:48 +01:00
Chopper 4cb500b888 Add ArabsDoujin (#3176)
* Add ArabsDoujin

* Update src/ar/arabsdoujin/build.gradle

---------

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>
2024-06-03 05:44:48 +01:00
Smol Ame 0fe5108c9a Add Manhua Español (#3188)
* Add Manhua Español

* Adjust Date Format & useNewChapterEndpoint

* Removed accent mark in extension name

* Re-dd accent mark back into source name
2024-06-03 05:44:48 +01:00
bapeey f99fc3cce6 CelestialMoon: Fix selectors (#3187)
fix selectors
2024-06-03 05:44:48 +01:00
Chopper 34ee8297d2 PlotTwistNoFansub: Fix page loading (#3186)
* Fix page loading

* Cleanup
2024-06-03 05:44:48 +01:00
bapeey ad0b1a041d SamuraiScan: Update domain (#3183)
update domain
2024-06-03 05:44:48 +01:00
Chopper d5c8b1dd1f Add MangaTrend (#3181) 2024-06-03 05:44:48 +01:00
Chopper cc7cc7c9f7 Add ManhaTok (#3180)
* Add ManhaTok

* Change rateLimit

* Remove unused import
2024-06-03 05:44:48 +01:00
Chopper e1879e0528 Add Kishisan (#3179)
* Add Kishisan

* Change rateLimit
2024-06-03 05:44:48 +01:00
Chopper ca386889d5 Add YuriMoonSub (#3177)
* Add YuriMoonSub

* Change rateLimit
2024-06-03 05:44:48 +01:00
Chopper c7c002322d Add Nimemob (#3175) 2024-06-03 05:44:48 +01:00
bapeey 04a60a51eb Manhastro: fix downloads (#3171)
fix
2024-06-03 05:44:48 +01:00
Smol Ame 0cbbe84fbb MangaSco: Update URL (#3169) 2024-06-03 05:44:48 +01:00
Smol Ame c97b4e5e9e Manhwa18cc: Latest Updates "fix" (#3168)
Use unordered page for Latest Updates instead
2024-06-03 05:44:48 +01:00
Vetle Ledaal 943ac6e891 Add Ler Hentai (#3165) 2024-06-03 05:44:48 +01:00
AwkwardPeak7 d430eb8286 Comick: filter out delayed chapters (#3161)
filter out delayed chapters
2024-06-03 05:44:47 +01:00
Vetle Ledaal 608a2c5d2a Japscan: fix browse, thumbnail, pagination (#3157)
Also removed the 'page number' filter - would have to reimplement it
with the new format, which seems like effort. Also, such features are
better suited for the app to implement directly.
2024-06-03 05:44:47 +01:00
Chopper 986683e93d UnionMangas: Fix deep linking (#3151)
* Fix deep linking

* Remove if/else
2024-06-03 05:44:47 +01:00
mohamedotaku 410977c9df Change Url and theme for Hijala "ar" (#3149)
* Change Url and theme for Hijala "ar"

* Update Hijala.kt

* Update Hijala.kt
2024-06-03 05:44:47 +01:00
bapeey 850c5dce7e MangaTale: Fix pages not found in downloads (#3148)
* Fix

* review
2024-06-03 05:44:47 +01:00
Chopper cf75a1f995 MangaTerra - Fix deep linking (#3142)
* Fix deep linking

* Bump version

* Fix path files
2024-06-03 05:44:47 +01:00
Chopper f497bddc71 Remove NexoScans (#3133) 2024-06-03 05:44:47 +01:00
sinkableShip febc0ba112 DMC Scans: Fix selectors (#2529)
* fix manga description selector

* fix chapter feed selector

* fix selector to script that contain pages link

* bump overrideVersionCode

* bump baseVersionCode

* Revert "bump baseVersionCode"

This reverts commit 7b2cc6a937193130e7ce9532597dc4d23547985e.

* change rate limit

* change few selectors that affected because recent changes

* change some selectors and other things

* change calling super.pageListParse(response) into just copying the code

calling super.pageListParse(response) will cause java.lang.IllegalStateException: closed since response.asJsoup() will be called twice

* fix indentation

* add excluded category: web novel
2024-06-03 05:44:47 +01:00
Vetle Ledaal a4e879bba9 Manga District: fix thumbnail (#3137)
* Manga District: fix thumbnail

* use absolute URL always
2024-06-03 05:44:47 +01:00
Chopper 6d3f09e1df SlimeRead: Fix deep linking (#3153)
Fix deep linking
2024-06-03 05:44:47 +01:00
Chopper ceb953ff96 HattoriManga: Fix deep linking (#3147)
Fix deep linking
2024-06-03 05:44:47 +01:00
Chopper f4d1f0d244 Brasil Hentail: Fix deep linking (#3144)
Fix deep linking
2024-06-03 05:44:47 +01:00
bapeey 72812dc898 Remove DeManhuas and the old SamuraiScans (#3140)
* remove

* XD
2024-06-03 05:44:47 +01:00
bapeey 934ca4a97f ReaperScans(en): Fix webview block (#3139)
Fix webview
2024-06-03 05:44:47 +01:00
Vetle Ledaal 86e45b2678 Toonily: fix page selector (#3138)
Restored back to default Madara behavior - this will make it so the
"Chapters have been replaced for better quality." banner isn't read as
an image.
2024-06-03 05:44:47 +01:00
Chopper 7b2e00811f Add MangaCan (#3113)
* Add MangaCan

* Fix baseUrl

* Fix deep linking

* Cleanup

* Use setUrlWithoutDomain

* Move regex to comanion object
2024-06-03 05:44:47 +01:00
Secozzi 1d6a48c189 move sunshinebutterflyscans away from multisrc (#3092)
* move sunshinebutterflyscans away from multisrc

* suggestions
2024-06-03 05:44:44 +01:00
bapeey 9440fc3b1a IkigaiMangas: Add preference for nsfw content (#3124)
pref for nsfw
2024-06-03 05:44:44 +01:00
Chopper 065c277c40 LuraToon: Update domain (#3117)
Update domain
2024-06-03 05:44:44 +01:00
Chopper 5c9980dec9 Remove NgonPhong (#3112) 2024-06-03 05:44:44 +01:00
Chopper 4085d94f9a Remove OCuMeo (#3111) 2024-06-03 05:44:44 +01:00
Fermín Cirella 871c4d7a22 Anchira: Fix API changes (#3089)
* Anchira: Fix API changes

* Anchira: Update versin code

* Anchira: Add interceptor for resampled images, update data URL

* Apply suggestions from code review
2024-06-03 05:44:44 +01:00
Vetle Ledaal 6bbb9d0da9 NIGHT SCANS: update domain (#3103) 2024-06-03 05:44:44 +01:00
Vetle Ledaal 5f5e160cfb Add MangaMonk, Remove TrueManga (#3102) 2024-06-03 05:44:44 +01:00
Vetle Ledaal fb6ae7f7c0 Add Euphoria Scan (#3101) 2024-06-03 05:44:44 +01:00
Vetle Ledaal e0bffdb80c Add MSY Fanyi (#3100) 2024-06-03 05:44:44 +01:00
bapeey d9e487bfb3 Rename "ManhuaOnline" to "SamuraiScans" and remove the old extension (#3095)
rebrand
2024-06-03 05:44:44 +01:00
bapeey bc808a6bc4 PeachScans: Fix image can't be loaded (#3090)
* fix decode

* bump
2024-06-03 05:44:44 +01:00
bapeey 51dba16677 IkigaiMangas: Fix page selector (#3086)
* update domain and fix pages

* they reenable the domain

* add missing cookie
2024-06-03 05:44:44 +01:00
bapeey 76faf0ab3d Traducciones Moonlight: Change theme to MangaEsp (#3073)
* move to mangaesp

* bruh
2024-06-03 05:44:44 +01:00
AwkwardPeak7 8ac86c6e8e MangaDex: set `Origin` header (#3082)
* MangaDex: set `Origin` header

* standard origin header without trailing slash
2024-06-03 05:44:44 +01:00
aaronisles 312bb8c3b0
Update issue_moderator.yml (#2740)
* Update issue_moderator.yml

Swaps Tachi Cloudflare link to Keiyoushi's link

* Update issue_moderator.yml

Swapped Cloudflare link to keiyoushi's site, removed period

* Update issue_moderator.yml

Re-adds periods back
2024-06-03 05:44:35 +01:00
Vetle Ledaal 025c675714 Update MadTheme, migrate ManhuaScan to MadTheme (#3072)
* MadTheme: general cleanup

* MadTheme: add support for both site formats

* Remove ManhuaScan

* Add KaliScan.com, KaliScan.io, MGJinx

* MadTheme: bump base version

* Add KaliScan.me

* Only set genreKey once
2024-06-03 05:43:53 +01:00
are-are-are 3c4c79eea4 Fix no description NettruyenCO and Update domain NettruyenCO (#3051)
* Update domain and fix no description NettruyenCO

* Update bump
2024-06-03 05:43:53 +01:00
Secozzi 769280bbb6 Dragontea: update pageListParse and imageRequest (#3083)
* update pageListParse and imageRequest

* didn't even need to override

* didn't even need to override part 2
2024-06-03 05:43:53 +01:00
AwkwardPeak7 6c5462309d Keyoapp: filter dummy pages (#3081) 2024-06-03 05:43:53 +01:00
are-are-are b1c691394c Update domain TruyenQQ (#3077)
* Update domain truyenqq

* update bump
2024-06-03 05:43:53 +01:00
Vetle Ledaal 1ef30094ca NIGHT SCANS: update domain (#3065) 2024-06-03 05:43:53 +01:00
Vetle Ledaal 4b8fd246c4 Add Algodão Doce (#3064) 2024-06-03 05:43:53 +01:00
bapeey d4191959e4 OlympusScanlation: Update domain (#3061)
* update domain

* copilot made this
2024-06-03 05:43:53 +01:00
are-are-are f09a232033 Fix no description & update domain NhattruyenS (#3052)
* Update domain and fix no description NhattruyenS

* Update bump
2024-06-03 05:43:53 +01:00
Fermín Cirella 3cb723b12b Anchira: Fix API changes (#3038)
* Anchira: Fix API changes

* Fix thumbnails on library response

* Anchira: Remove data class

* Update src/en/anchira/src/eu/kanade/tachiyomi/extension/en/anchira/AnchiraDto.kt

---------

Co-authored-by: BrutuZ <BrutuZ@users.noreply.github.com>
2024-06-03 05:43:53 +01:00
KirinRaikage 47af581f6d Raijin Scans: Update domain (#3056)
Raijin Scans: Update URL
2024-06-03 05:43:53 +01:00
bapeey 9e6f5efa92 PeachScan: Update image urls selector (#3048)
Regex better
2024-06-03 05:43:53 +01:00
Vetle Ledaal 73a5340c58 Arabs Hentai: fix search title + only manga, cover (#3043) 2024-06-03 05:43:53 +01:00
Davide 0a7ed30e02 MangaGeko: fixed latest next page selector (#3037) 2024-06-03 05:43:53 +01:00
Vetle Ledaal bf8a3bf3ce Comick: show error message from API (#3025)
* Comick: show error message from API

* move error handling to an interceptor
2024-06-03 05:43:53 +01:00
bapeey db035c7ad3 DrakeScans: Remove Jetpack CDN from pages (#3030)
Remove jetpack
2024-06-03 05:43:53 +01:00
bapeey dbe3eaa77c Mantraz Scan: Update domain (#3029)
update domain
2024-06-03 05:43:53 +01:00
bapeey 49ccc61b6a TresDaosScan: Move to Madara (#3028)
mv to madara
2024-06-03 05:43:53 +01:00
Chaos Pjeles 271898bd58 Add 3600000.xyz (#3014)
* implement 3600000.xyz

* set thumbnail_url in mangadetailsparse: search doesn't come with thumbnail

* changed launcher icons

* Update src/all/beauty3600000/src/eu/kanade/tachiyomi/extension/all/beauty3600000/Beauty3600000.kt

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

* remove wildcard import

---------

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>
2024-06-03 05:43:53 +01:00
Vetle Ledaal a41eeebc50 Add Hunlight Scans (#3022) 2024-06-03 05:43:53 +01:00
bapeey 75db04eca2 LeerCapitulo: Update domain (#3019)
update domain
2024-06-03 05:43:53 +01:00
Vetle Ledaal 78847a26e6 Add ManhwaClub.net, Remove ManhwaClub (#3013)
* Add ManhwaClub.net

* Remove ManhwaClub
2024-06-03 05:43:53 +01:00
Vetle Ledaal 34beb05922 Team Lanh Lung: update domain (#3011) 2024-06-03 05:43:53 +01:00
Vetle Ledaal 8a5578250d Vortex Scans: fix chapter pages (#3010) 2024-06-03 05:43:53 +01:00
stevenyomi fbffcbb557 Roumanwu: update URL (#3009) 2024-06-03 05:43:53 +01:00
mohamedotaku a3f9894b5c update url ThunderScans "all" and use random url part for ThunderScans "En" (#2986)
* update url ThunderScans "all" and use random url part for ThunderScans "En"

* Update src/all/thunderscans/build.gradle

---------

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>
2024-06-03 05:43:53 +01:00
Chopper f3314852bd HattoriManga: Theme change (#2960)
* Migrate HattoriManga

* Cleanup

* Sorted chapters

* Cleanup

* Add searchManga

* Cleanup

* Remove unneeded code

* Add search by intent

* Cleanup

* Add headers request

* Fix names

* Add nextPage

* Add limit in nextPage

* Fix AndroidManifest reference

* Move rateLimit

* Move fetchLatestUpdates parse to latestUpdatesParse

* Remove setUrlWithoutDomain

* Implements HttpSource

* Cleanup

* Fix author, artist and genres from mangaDetails

* Update src/tr/hattorimanga/src/eu/kanade/tachiyomi/extension/tr/hattorimanga/HattoriManga.kt

* Update src/tr/hattorimanga/src/eu/kanade/tachiyomi/extension/tr/hattorimanga/HattoriManga.kt

---------

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>
2024-06-03 05:43:53 +01:00
Chopper 1ef8153eca Add BrasilHentai (#2918)
* Add BrasilHentai

* Fix popularMangaNextPage

* Fix popularMangaRequest and set rateLimit

* Fix names

* Fix filter

* Fix intent pathSegment

* Remove extra request and add Initilize in popularMangaFromElement

* Update src/pt/brasilhentai/src/eu/kanade/tachiyomi/extension/pt/brasilhentai/BrasilHentai.kt

* Update src/pt/brasilhentai/src/eu/kanade/tachiyomi/extension/pt/brasilhentai/BrasilHentai.kt

---------

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>
2024-06-03 05:43:53 +01:00
mohamedotaku 9fe6e06574 Theme Hijala "ar" Changed to ZeistManga (#2999)
change Theme Hijala "ar" to ZeistManga
2024-06-03 05:43:53 +01:00
bapeey 65f9de384e TecnoScan: Move to Madara (#2993)
Move to Madara
2024-06-03 05:43:53 +01:00
Vetle Ledaal 6e21329ada Add Galinha Samurai Scan (#2989) 2024-06-03 05:43:53 +01:00
anenasa af4b442cc0 Happymh: fix referer for pageListRequest (#2984) 2024-06-03 05:43:53 +01:00
AwkwardPeak7 5a229cd8cf Keyoapp: filter hidden images (#2980)
* Keyoapp: filter hidden images

* unused imports
2024-06-03 05:43:53 +01:00
Bartu Özen dc225dba18 Bato.to: Add deep link support for v3x (#2971) 2024-06-03 05:43:53 +01:00
Vetle Ledaal dbeb67b3e9 HentaiRead: fix page parsing (#2903)
* HentaiRead: fix page parsing

* address PR comments

* code was never published, simplifying

* copy SChapter object to avoid mutating db

* nvm, just return GET directly
2024-06-03 05:43:53 +01:00
Cuong M. Tran 6abded47de Update theme GalleryAdults (#2911)
* Organizing code

* remove unnecessary files

* improve base class

- allow override search’s uri
- add shared method to get description & page count
- base class’s request for gallery’s all pages now support all sources without needs to override (almost)
- extract method to parse JSON

* Avoid request for more pages when no needed

* auto add more tags to filter while viewing manga;
add spit-tag

* filter for getting Random manga

* Always add page=1 to uri so it will exclude some non-latest mangas from homepage, happened with some sources.

* reorganize code

* Allow source which doesn't need shortTitle to hide it.

* Extract default advanced search's Uri
change base class's galleryUri value

* Fix getInfoPages

* Fix missing category filter

* open for override

* bump base class version
2024-06-03 05:43:53 +01:00
Chopper 87157d8aa9 LuraToon: Add random user agent (#2967)
Add random user agent
2024-06-03 05:43:48 +01:00
Chopper 58aec56373 Add MoonDaisyScans (#2966) 2024-06-03 05:43:48 +01:00
Vetle Ledaal 3873976886 PTNF: fix mangaId, fix pagination, script order (#2964)
* PTNF: fix mangaId, fix pagination, script order

* actually use the most common mangaId
2024-06-03 05:43:48 +01:00
Vetle Ledaal 5d6d9463b7 MG Komik: fix access denied with Cloudflare (#2963) 2024-06-03 05:43:48 +01:00
bapeey 87548ae9ee MHScans: Update domain (#2959)
Update domain
2024-06-03 05:43:48 +01:00
Chopper caf462e781 Add AthenaManga (#2956) 2024-06-03 05:43:48 +01:00
Chopper a9b5fbd2aa Add EcchiDoujin (#2955)
* Add EcchiDoujin

* Fix domain
2024-06-03 05:43:48 +01:00
Vetle Ledaal 6423049771 Remove dead sources (at least 1wk) (#2953) 2024-06-03 05:43:48 +01:00
mohamedotaku 3df4684c88 add mirrors link MangaAe "ar" (#2930)
* add mirrors link MangaAe "ar"

* rephrasing
2024-06-03 05:43:48 +01:00
bapeey 6ed5e31f37 Danbooru: Fix crash and titles (#2954)
Fix crash
2024-06-03 05:43:48 +01:00
Vetle Ledaal 307540a2aa Shinigami: update domain (#2949) 2024-06-03 05:43:48 +01:00
Vetle Ledaal 43a3249da3 Add Manga-Titan (#2948) 2024-06-03 05:43:48 +01:00
Vetle Ledaal 56a10df09d Add Truyen tranh dam my (#2946) 2024-06-03 05:43:48 +01:00
Vetle Ledaal b81469035f Add MeowSubs (#2945) 2024-06-03 05:43:48 +01:00
Vetle Ledaal 15aacb0eb9 Add Manga Tx.to (#2942) 2024-06-03 05:43:48 +01:00
Vetle Ledaal 154b4c50f5 Add Retsu (#2941) 2024-06-03 05:43:48 +01:00
Vetle Ledaal be5622f93d Add SaikoMangaRaw (#2940) 2024-06-03 05:43:48 +01:00
Vetle Ledaal a7178fc20e Add Zinmanga.cc (#2939) 2024-06-03 05:43:48 +01:00
Vetle Ledaal 64fe0ecdf5 Add MangaClick (#2937) 2024-06-03 05:43:48 +01:00
Vetle Ledaal cdb5b6f488 Add Zin-Manga.com (#2936) 2024-06-03 05:43:48 +01:00
Vetle Ledaal 14c1195ff3 Add Zinmanga.ms (#2935) 2024-06-03 05:43:48 +01:00
Vetle Ledaal 008f348d38 Add Zinmanga.net (#2934) 2024-06-03 05:43:48 +01:00
Chopper 39e153cc67 Add KomikSay (#2917) 2024-06-03 05:43:48 +01:00
Chopper 6a329074e1 Add IrisScans (#2916) 2024-06-03 05:43:48 +01:00
Vetle Ledaal c1e5450946 Ninja Scan: update domain (#2915) 2024-06-03 05:43:48 +01:00
Vetle Ledaal ffa8ba4a45 Komiku: use new API endpoint (#2913) 2024-06-03 05:43:48 +01:00
Vetle Ledaal 43c934d1f8 MangaGezgini: fix chapter name, notify on captcha (#2910) 2024-06-03 05:43:48 +01:00
Vetle Ledaal daecf41724 Nitro Manga -> Nitro Scans: update domain (#2909) 2024-06-03 05:43:48 +01:00
Vetle Ledaal b389d4089e Elarc Toon: update domain (#2908) 2024-06-03 05:43:48 +01:00
Vetle Ledaal 78f01bf837 Add NvManga (#2901) 2024-06-03 05:43:48 +01:00
Vetle Ledaal 804208d4f8 Add RawXZ (#2900) 2024-06-03 05:43:48 +01:00
bapeey 9af69afccd RightdarkScan(es): Update domain and remove DarkScan(en) (#2894)
* Update domain

* Remove EN
Id: 7467592837760360366
2024-06-03 05:43:48 +01:00
Vetle Ledaal 22efdcc2a8 Jiangzaitoon: update domain (#2892) 2024-06-03 05:43:48 +01:00
Vetle Ledaal a6caf6dabe Add Türkçe Manga Oku (#2891) 2024-06-03 05:43:48 +01:00
Vetle Ledaal 5727e8a8aa Add MangaTR.net (#2890) 2024-06-03 05:43:48 +01:00
Eshlender ad275eb542 [RU]Desu serialization (#2816)
* [RU]Desu serialization

* extVersionCode

* clean excess

* clean excess
2024-06-03 05:43:48 +01:00
Fermín Cirella edad8aed4b HN: Set status for manga to Completed (#2888) 2024-06-03 05:43:48 +01:00
bapeey bb912b9570 Add Akaya (#2881)
* Add Akaya

* add formbody in interceptor

* Minor change
2024-06-03 05:43:48 +01:00
Trevor Paley 21f03c9454 Add Mangamo (#2862)
* Add Mangamo

* Mangamo: Remove excess fields and use assertions

* Mangamo: improve Firestore queries

* Mangamo: synchronize auth

* Mangamo: fix serialization bug when no fields are returned from query

* Mangamo: exclude disabled manga from latest and improve chapter update query

* Mangamo: clean up DTO objects

* Mangamo: add custom 401 messaging, use emoji for payment indication

* Mangamo: make manga/chapter URLs relative
2024-06-03 05:43:48 +01:00
bapeey 20b50323b9 Ragnarok Scanlation: Update domain (#2874)
Update domain
2024-06-03 05:43:48 +01:00
Chopper 483a9bbc70 SussyScan: Fix load mangas (#2870)
* Remove mangaSubString

* Keeps thumbnail from popular, latest and search

* Remove date format

* Remove unused import

* Add date format

* Fix lint

* Add thumbnail from manga datails
2024-06-03 05:43:48 +01:00
Vetle Ledaal f767aa8b6d Flower Manga: update domain, icon (#2871) 2024-06-03 05:43:48 +01:00
Chopper 4161c3f51d Maidscan: update domain and bug fix (#2866)
* Update domain

* Update rateLimit

* Update icons

* Fix date format

* Add LoadMoreStrategy.Never

* Cleanup
2024-06-03 05:43:48 +01:00
Chopper 437ee8b4bc Add AduManga (#2861)
* Add AduManga

* Add icons
2024-06-03 05:43:47 +01:00
Chopper e257636ded Add MangaSiginagi (#2860)
* Add MangaSiginagi

* Add isNsfw
2024-06-03 05:43:47 +01:00
Chopper 47aadb3873 Add MerlinShoujo (#2859)
* Add MerlinShoujo

* Add icons
2024-06-03 05:43:47 +01:00
Chopper 61e0817e90 Add Gaiatoon (#2858)
* Add Gaiatoon

* Add icons
2024-06-03 05:43:47 +01:00
Chopper fd0b45a71e Add ArcuraFansub (#2857) 2024-06-03 05:43:47 +01:00
Chopper a58e72f4ec Add PatiManga (#2856)
* Add PatiManga

* Add icons
2024-06-03 05:43:47 +01:00
Chopper 631f2fbdb7 Add AfroditScans (#2855) 2024-06-03 05:43:47 +01:00
Vetle Ledaal 552c349632 Galaxy Manga: updatre manga / chapter / page URL format (#2853) 2024-06-03 05:43:47 +01:00
Vetle Ledaal ab6e43b1df RavenManga: update domain (#2851) 2024-06-03 05:43:47 +01:00
Vetle Ledaal 4d76106169 Add Manga Şehri.net (#2847) 2024-06-03 05:43:47 +01:00
Vetle Ledaal 1c50b086a1 Add LowerWorld (#2846) 2024-06-03 05:43:47 +01:00
mohamedotaku 2b23047ee0 Add Source AreaScans "ar" (#2843) 2024-06-03 05:43:47 +01:00
Vetle Ledaal b531e1fc39 Add Mundo Manhwa (#2845) 2024-06-03 05:43:47 +01:00
Vetle Ledaal c84a1acd0b Add Hentai Origines (#2839) 2024-06-03 05:43:47 +01:00
mohamedotaku 512c1c4b8e add mirror link to mangalek "ar" (#2844) 2024-06-03 05:43:47 +01:00
Vetle Ledaal cc45142058 Add MangaHoNa (#2838) 2024-06-03 05:43:47 +01:00
Vetle Ledaal 18ff934880 Add TopManhua.net (#2837) 2024-06-03 05:43:47 +01:00
Vetle Ledaal 8c2d6c3e3e Add Komikuzan (#2787)
* Add Komikuzan

* dateFormat
2024-06-03 05:43:47 +01:00
Vetle Ledaal d2a6999b3e Add Free Comic Online (#2834) 2024-06-03 05:43:47 +01:00
Vetle Ledaal 44964e777d Add HentaiVN.plus (#2832) 2024-06-03 05:43:47 +01:00
Vetle Ledaal 496cc82ee4 Add Mahua USS (#2833) 2024-06-03 05:43:47 +01:00
Vetle Ledaal 4fa30fc8cb GuncelManga: update domain, fix date and description (#2831) 2024-06-03 05:43:47 +01:00
Vetle Ledaal 5249c98336 Add Zanman Manga (#2830) 2024-06-03 05:43:47 +01:00
Vetle Ledaal 313b43f78c Add Stick Horse (#2823) 2024-06-03 05:43:47 +01:00
Vetle Ledaal 486914692d Add DoujinZa (#2821)
* Add DoujinZa

* icon
2024-06-03 05:43:47 +01:00
Vetle Ledaal 90b8740e32 Add Dianxia Traduções (#2820) 2024-06-03 05:43:47 +01:00
Vetle Ledaal d3aa8adb8a Add ManhwaHub (#2819) 2024-06-03 05:43:47 +01:00
Vetle Ledaal 0c137fb39c Top Manhua: update icon, parity with #2817 (#2818) 2024-06-03 05:43:47 +01:00
Vetle Ledaal 91ed215f0b Add ManhuaTop (#2817) 2024-06-03 05:43:47 +01:00
bapeey 38cf9505d2 MangaCrab: Update domain (#2813)
pam
2024-06-03 05:43:47 +01:00
Chopper 1e46d2fdc7 MangaGezgini: Fix chapter name and date format (#2811)
* Fix chapter name

* Fix date format

* Update icons

* Add useLoadMoreRequest and useNewChapterEndpoint settings
2024-06-03 05:43:47 +01:00
Chopper d2bc63650b ManhuaOnline: Fix domain (#2810)
Fix domain
2024-06-03 05:43:47 +01:00
Chopper 2aa1e1f76e KakuseiProject: Fix domain (#2809)
* Fix domain

* Add LoadMoreStrategy
2024-06-03 05:43:47 +01:00
Vetle Ledaal 4251468894 Remove dead sources, domain for sale (#2807) 2024-06-03 05:43:47 +01:00
Secozzi cfc2fcbf4b Luratoon: add referer (#2799)
* add referer

* move fix to base theme

* add location to non zipped images
2024-06-03 05:43:47 +01:00
Vetle Ledaal a848d1bb2e Add TopComicPorno (#2798) 2024-06-03 05:43:47 +01:00
Vetle Ledaal 0622230f4b Sandra and Woo: Switch from http to https (#2796)
Did not change `archive` URL, as that would require migration, or show
the manga as not your library. The current 301 redirect is fine. If it
really matters I can add an interceptor for it.
2024-06-03 05:43:47 +01:00
Vetle Ledaal fd0ad36ac4 Gone with the Blastwave: Switch from http to https (#2795) 2024-06-03 05:43:47 +01:00
Vetle Ledaal 9c13009267 MangaReader.cc: Switch from http to https (#2794)
Scheme for thumbnail might change, but that is to be expected.
2024-06-03 05:43:47 +01:00
Vetle Ledaal a91cda1806 Voyce.Me: Switch from http to https (#2793)
The baseUrl is only used:
- in headers
- for WebView button

The change does not affect any existing users, as all URLs are set
without the domain in VoyceMeDto.kt
2024-06-03 05:43:47 +01:00
bapeey f189ddfa55 AsuraScans: Update domain (#2791)
Update domain
2024-06-03 05:43:47 +01:00
Vetle Ledaal 9c152bb303 Add Battle In 5 Seconds After Meeting (#2788)
* Add Battle In 5 Seconds After Meeting

* Short-circuit search, disable filter
2024-06-03 05:43:47 +01:00
Vetle Ledaal 0eb3298f82 Add Mangaowl Yaoi (#2786) 2024-06-03 05:43:47 +01:00
Vetle Ledaal d96d692209 Add Paritehaber (#2785) 2024-06-03 05:43:47 +01:00
Vetle Ledaal d7929ab6be Add Catzaa (#2784) 2024-06-03 05:43:47 +01:00
Vetle Ledaal 94ee09037d Add Indo18h (#2783) 2024-06-03 05:43:47 +01:00
Vetle Ledaal f436ebf6ab Add Mangazavr (#2782) 2024-06-03 05:43:47 +01:00
Vetle Ledaal bd8979d4b8 Add Manga1k (#2781) 2024-06-03 05:43:47 +01:00
Vetle Ledaal 7cd7706644 Add Doujin-Lc (#2780) 2024-06-03 05:43:47 +01:00
Vetle Ledaal 2f91187312 Add Manga-Lc (#2779) 2024-06-03 05:43:47 +01:00
Vetle Ledaal b50abc61c6 Add ManhwaDen (#2775) 2024-06-03 05:43:47 +01:00
Vetle Ledaal 50ef399d3a Manga Time: update domain (#2774) 2024-06-03 05:43:47 +01:00
Vetle Ledaal 01e2f9db9f Add L Scans (#2772)
* Add L Scans

* isNsfw = false

* lint
2024-06-03 05:43:47 +01:00
Vetle Ledaal 158402d0db Add Kabus Manga (#2771) 2024-06-03 05:43:47 +01:00
Vetle Ledaal 64bd4df45c Add Garcia Manga (#2770) 2024-06-03 05:43:47 +01:00
Vetle Ledaal 9572c9f863 Add Titan Manga (#2753) 2024-06-03 05:43:47 +01:00
Vetle Ledaal 325ec1421c Add Luna Scans (#2749) 2024-06-03 05:43:47 +01:00
bapeey 0d7c58d326 RC: Fix webview partially (#2744)
* Fix webview

* Keep header in apiRequest
2024-06-03 05:43:47 +01:00
Chopper 82ddc7daab Add Remangas (#2742)
* Add Remangas

* Fix lint

* Replace icons

* Add useLoadMoreRequest
2024-06-03 05:43:47 +01:00
Chopper 2cf4736da0 RaadKomik: Update domain (#2683)
* Update domain

* Enable project page

* Update versionId

* Fix filters, popularManga and LatestManga

* Remove unneeded conditional

* Remove break statement

* Remove versionId and add compatibility with old url
2024-06-03 05:43:47 +01:00
Vetle Ledaal cd602e0fcc Add Manhwa Hentai (#2738)
* Add Manhwa Hentai

* data class -> class
2024-06-03 05:43:24 +01:00
KenjieDec 9936531d44 Added Manga18Me (#2647)
* Added Manga18Me

* Update Manga18Me.kt

* Update Manga18Me.kt

* Fixed search

search pages

* Move to all

* Language move

en -> all
2024-06-03 05:43:24 +01:00
Vetle Ledaal 5549c629a3 Add Rocks Manga (#2739) 2024-06-03 05:43:24 +01:00
Chopper 4c8950ffb2 Add LunarScansHentai (#2737) 2024-06-03 05:43:24 +01:00
Chopper aa221b1adc Add MonzeeKomik (#2736) 2024-06-03 05:43:24 +01:00
Vetle Ledaal 4516e2f208 Add ManhwaBreakup (#2735) 2024-06-03 05:43:24 +01:00
Chopper 02a6fb32ba Add LuaScans (#2734) 2024-06-03 05:43:24 +01:00
Vetle Ledaal 31cca03e4e Add MangaIsekaiThai (#2733) 2024-06-03 05:43:24 +01:00
Vetle Ledaal a6439e6316 Add ManhuaBug (#2732) 2024-06-03 05:43:23 +01:00
Chopper e84664029d Add KomikLovers (#2731) 2024-06-03 05:43:23 +01:00
Vetle Ledaal 126a586a21 Add Opiatoon (#2730) 2024-06-03 05:43:23 +01:00
Vetle Ledaal 4fa0b2b320 Add Nivera Fansub (#2729) 2024-06-03 05:43:23 +01:00
Vetle Ledaal 622146d821 Add Stray Fansub (#2728) 2024-06-03 05:43:23 +01:00
Vetle Ledaal 713363838a Add Domal Fansub (#2727) 2024-06-03 05:43:23 +01:00
bapeey 71e6e4b10c SlimeRead: Get api url from page (#2712)
* find api

* specify regex
2024-06-03 05:43:23 +01:00
Pedro Azevedo a5d5c805fd Demon Sect: update domain (#2726) 2024-06-03 05:43:23 +01:00
Vetle Ledaal 0fd72be73b Add Hoi Fansub (#2725) 2024-06-03 05:43:23 +01:00
Vetle Ledaal caab4716b4 Add Kuroi Manga (#2723) 2024-06-03 05:43:23 +01:00
Vetle Ledaal e93b41ce7f Add Sarcasm Scans (#2722) 2024-06-03 05:43:23 +01:00
Vetle Ledaal 4058612bca Add Pewpiece (#2721) 2024-06-03 05:43:23 +01:00
Vetle Ledaal e867689cc5 Manhwa Freak: update domain (#2720) 2024-06-03 05:43:23 +01:00
Vetle Ledaal 0c27089d2f LXHentai: update domain (#2719) 2024-06-03 05:43:23 +01:00
Vetle Ledaal e807908b9f Ikigai Mangas: update domain, skip chapter redir (#2718) 2024-06-03 05:43:23 +01:00
Vetle Ledaal 14ad5b9027 Add ManhuaThai (#2704)
* Add ManhuaThai

* supportsLatest = false
2024-06-03 05:43:23 +01:00
mohamedotaku 5b5657ff14 MangaTak "ar" use random Url (#2703)
* Update build.gradle

* Update MangaTak.kt
2024-06-03 05:43:23 +01:00
Vetle Ledaal 6ce176226a Add Pied Piper Fansubyy (#2702) 2024-06-03 05:43:23 +01:00
Vetle Ledaal feeec15597 Add Siimanga (#2699) 2024-06-03 05:43:23 +01:00
Vetle Ledaal fe705dab68 Add 1st-Kiss Manga.net (#2698) 2024-06-03 05:43:23 +01:00
Vetle Ledaal c50d959323 Add MilaSub (#2696) 2024-06-03 05:43:23 +01:00
Vetle Ledaal df3cdcf1fc Add Lavinia Fansub (#2695) 2024-06-03 05:43:23 +01:00
Vetle Ledaal a0e0b81bda Add Allied Fansub (#2694) 2024-06-03 05:43:23 +01:00
Vetle Ledaal 5670bb6899 Add TruyenVN (#2692) 2024-06-03 05:43:23 +01:00
Vetle Ledaal 2ffb3930d2 Add Yaoi TR (#2691) 2024-06-03 05:43:23 +01:00
Vetle Ledaal b653340c6e Add Yaoi Manga Oku (#2690) 2024-06-03 05:43:23 +01:00
Vetle Ledaal 48cb2265c1 Add Minda Fansub (#2689) 2024-06-03 05:43:23 +01:00
kana-shii 19532c2f33 Lunar Scans [FR] (#2669)
add lunar scans
2024-06-03 05:43:23 +01:00
kana-shii 97cab8fc8f Cocomic (#2668)
* wip

* Update Cocomic.kt
2024-06-03 05:43:23 +01:00
AwkwardPeak7 81a3a9375f FleksyScans: filter out vip chapters (#2666) 2024-06-03 05:43:23 +01:00
AwkwardPeak7 5797b9638d KewnScans: remove hidden images (#2665)
PeepoJoker
2024-06-03 05:43:23 +01:00
Vetle Ledaal c3045a2d81 NIGHT SCANS: update domain (#2642)
* NIGHT SCANS: update domain

* Change to MangaThemesiaAlt
2024-06-03 05:43:23 +01:00
bapeey 6a498537ce SlimeRead: Update api url (#2656)
mmmm
2024-06-03 05:43:23 +01:00
bapeey 3f6a8b9fd0 TecnoScans: Update domain (#2654)
Update domain
2024-06-03 05:43:23 +01:00
bapeey 0522aaf29e MiauScan: Update domain (#2653)
Update domain
2024-06-03 05:43:23 +01:00
bapeey d4bdbbe059 Remove SDLScans (#2650) 2024-06-03 05:43:23 +01:00
Vetle Ledaal 07c41e78fb Add Lich Mangas (#2644) 2024-06-03 05:43:23 +01:00
Vetle Ledaal 5db5f710dd Luminous Scans: update domain (#2641) 2024-06-03 05:43:23 +01:00
Vetle Ledaal 094ef457b0 Add GEDE Comix (#2640) 2024-06-03 05:43:23 +01:00
Vetle Ledaal 5a7386cd6c Add Manga Oku (#2639) 2024-06-03 05:43:23 +01:00
Vetle Ledaal f191400e7d Add Pied Piper Fansub (#2638) 2024-06-03 05:43:23 +01:00
mohamedotaku ca26ce4167 Add source Prince Ediciones "es" (#2616)
* Add source Prince Ediciones "es"

* useNewChapterEndpoint
2024-06-03 05:43:23 +01:00
Cuong M. Tran 935bd089fc add multi-src: GalleryAdults (#2553)
* Convert AsmHentai into multisrc GalleryAdults

Also convert some selector into function

* Move HentaiFox to theme GalleryAdults

* GalleryAdults: Fix search

* MangaFox: fix TagFilter

* fast page load & preference for slowly parsing image’s URL

* AsmHentai: change shortTitle reference from list to swith

* HentaiFox: add Korean

* move HentaiFox from en to all

* fix build

* fix search: convert space to +

* Request for tags list from site

* Fix request for user’s favorites

* - Optimize popular/latest request

- Improve ‘page’ param
- AsmHentai: support Latest/Popular

* add SortFilter

* Support multiple tags filter

* Support exact match query

* getTime

* Fix Lang when searching

* fix searchById

* add language companion

* Fix URL action

* renovate

* Support parsing json for page list

Fix generating page if less than 10 pages
HentaiFox: Random server selection

* Migrate IMHentai to GalleryAdults

* Preferences to support all methods for page querying

* IMHentai: tagList

* Expose some filters to child class, add more space to description

* Fix Factory lang

* Support browsing tags, speechless & favorite

* IMHentai:

- support favorite browsing (require login)
- tag filter with queried popular tags
- advanced search for artist, group, character, parody, tag (include/exclude)
- remove language filters
- Fix language search

* Move advance search to multi-src

* Fix: hide speechless when not supported

* add Hint to use comma

* split code to Filters & Utils

* bump version all 3 extensions

* fix getTime

* fix lint

* Fix alternative name

* improve cleanTag

* move out of Object

* move Regex out

* remove RandomUA

* fix build

* remove images parsing setting, pick a default one

* fix build

* Move shortTitle to base clash

* HentaiFox: add language keyword to search query

* if all mangas in current searching page is of other language then include at least 1 entry so it can request for next page

* Alternative methods for images parsing
Revert "remove images parsing setting, pick a default one"

This reverts commit e49e3aaeb74e3643abc2e303924da18a52491793.

# Conflicts:
#	lib-multisrc/galleryadults/src/eu/kanade/tachiyomi/multisrc/galleryadults/GalleryAdults.kt
#	src/all/asmhentai/src/eu/kanade/tachiyomi/extension/all/asmhentai/AsmHentai.kt
#	src/all/imhentai/src/eu/kanade/tachiyomi/extension/all/imhentai/IMHentai.kt

* Fall back if failed to decode JSON

* remove supportLatest from base class

* Remove preference for parsing page by page, switch to override val instead.

* Split searchRequest into parts

* Don't using generic Filter.Text to avoid other kind of text field which extensions might have
2024-06-03 05:43:23 +01:00
Vetle Ledaal 1077a96940 Add Pink Tea Comic (#2626)
* Add Pink Tea Comic

* Bump base version

* Add cancelled text for #1249

* Add hiatus text for #1088

* Add hiatus/cancelled text for #867

* Add hiatus/cancelled text for #498
2024-06-03 05:43:23 +01:00
Vetle Ledaal 071342ecbe Add Yubikiri (#2610)
* Add Yubikiri

* Bump Madara base version
2024-06-03 05:43:23 +01:00
Hasan 964adc3624 Add Raindrop Fansub (#2386)
* Add Zenith Scans

* Add Raindrop Fansub

* renamed

* revised

* Remove unnecessary overrides, sort chapters

* Make chapterListParse more concise

* Make chapterListParse more more concise

---------

Co-authored-by: hasanturkylmz <hasanturkylmz@outlook.com>
Co-authored-by: Vetle Ledaal <vetle.ledaal@gmail.com>
2024-06-03 05:43:23 +01:00
Vetle Ledaal a6baf39b2e Add İmparator Manga (#2625) 2024-06-03 05:42:15 +01:00
Vetle Ledaal c6e6fc9aaa Add Arya Scans (#2624) 2024-06-03 05:42:15 +01:00
Vetle Ledaal 1c07987326 Add Hwago (#2618) 2024-06-03 05:42:15 +01:00
Chopper 2d6fd42c9d Remove LerManga (#2617) 2024-06-03 05:42:15 +01:00
mohamedotaku 3a1c58f65e update url DrakeScans "en" (#2614) 2024-06-03 05:42:15 +01:00
Chopper cabf481d6f Migrate CeriseScan (#2596)
* Migrate CeriseScan

* Fix rateLimit

* Remove id
2024-06-03 05:42:15 +01:00
Chaos Pjeles 239c1634cf implement ahottie (#2551)
* implement ahottie

* fix linting issue

* resolve suggestions

* Update src/all/ahottie/src/eu/kanade/tachiyomi/extension/all/ahottie/AHottie.kt

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

* Update src/all/ahottie/src/eu/kanade/tachiyomi/extension/all/ahottie/AHottie.kt

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

* remove unnecessary query.isEmpty() check in search

* reemove background from ic_launcher icons

* remove padding from icon

---------

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>
2024-06-03 05:42:15 +01:00
Vetle Ledaal 7e52eac427 MANGARAW+: update domain (#2609)
* MANGARAW+: update domain

* LoadMoreStrategy.AutoDetect
2024-06-03 05:42:15 +01:00
Vetle Ledaal bb6221038e NetTruyen: update domain (#2608) 2024-06-03 05:42:15 +01:00
Vetle Ledaal e3fca411f3 Top Manhua: update domain (#2607) 2024-06-03 05:42:15 +01:00
Vetle Ledaal 1b456b875a Emperor Scan: update domain (#2606) 2024-06-03 05:42:15 +01:00
Vetle Ledaal 411c3218fd Remove MMScans (#2604) 2024-06-03 05:42:15 +01:00
Vetle Ledaal b54330f88f Remove Cosmic Scans (#2603) 2024-06-03 05:42:15 +01:00
bapeey dd5afb8bfc RightdarkScan: Update domain (#2602)
Update domain
2024-06-03 05:42:15 +01:00
Mon 4f18cd9fb1 Rizzcomic: Updated domain (#2592)
* Rizzcomic: Updated domain

* fix: removed trailing slashes

it didn't have any impact in my tests but it wasn't present before so it's probably a good idea to remove it
2024-06-03 05:42:15 +01:00
bapeey 41c95e1d14 KnightNoScanlation: Update domain (#2601)
Update domain
2024-06-03 05:42:15 +01:00
Chopper 5334569924 Remove ComicsWorld (#2597) 2024-06-03 05:42:15 +01:00
Chopper 3845041ef4 Remove MidnightManga (#2595) 2024-06-03 05:42:15 +01:00
Chopper c1a0d2af2d Remove GekkouScan (#2594) 2024-06-03 05:42:15 +01:00
KenjieDec 8e9e4f02f6 Comick tags filter fix (#2543)
* Fixed Tags

Fixed tags search that contains space and slash.

* Update build.gradle

* Fixed Tags Filter

Fixed: Uppercase -> lowercase. Single quotation marks

* Taking out the regex object

Taking out the regex object so it doesn't get created every time
2024-06-03 05:42:15 +01:00
KirinRaikage 22469d8a51 Add Astrames (#2588) 2024-06-03 05:42:15 +01:00
KirinRaikage 4abf193718 Add Rimu Scans (#2586) 2024-06-03 05:42:15 +01:00
KirinRaikage 226766fdd0 Add Mangas Scans (#2581) 2024-06-03 05:42:15 +01:00
nomaxsnx 6f8cae7f96 Add Icons for extension: "PT-BR". (#2572)
* Add Icons for extension: "PT-BR".

* Fix and Update Icon: "AkiMangá".

* Update Icon: Amuy

* Update Icon: AstrumScan

* Update Icon: CafeComYaoi

* Update Icon: DesertScan

* Update Icon: FlowerManga

* Update Icon: GhostScan

* Update Icon: HentaiTeca

* Update Icon: KakuseiProject

* Fix and Upate Icon: LadyEstelarScan

* Update Icon: LerYaoi

* Update Icon: LimbosScan

* Update Icon: LinkStartScan

* Update Icon: MaidScan

* Update Icon: MangaNanquim

* Update Icon: ssscanlator

* Update Again: HentaiTeca

* Update Again: Flower Manga

* Update Again: GhostScan
2024-06-03 05:42:15 +01:00
Vetle Ledaal b2d5c77ee6 Add DoujinsHell (#2555)
* Add DoujinsHell

* Replace let with also
2024-06-03 05:42:15 +01:00
Akshar Kalathiya 1da73af727 Manga Clash Url change Fix (#2568)
* MangaDemon Url change Fix

* Manga Clash Url Fix

* Update gradle.build in MangaClash
2024-06-03 05:42:15 +01:00
1702 changed files with 11627 additions and 4384 deletions

View File

@ -1,3 +1,4 @@
import html
import json
import os
import re
@ -107,3 +108,10 @@ with (REPO_DIR / "index.json").open("w", encoding="utf-8") as f:
with (REPO_DIR / "index.min.json").open("w", encoding="utf-8") as f:
json.dump(index_min_data, f, ensure_ascii=False, separators=(",", ":"))
with (REPO_DIR / "index.html").open("w", encoding="utf-8") as f:
f.write('<!DOCTYPE html>\n<html>\n<head>\n<meta charset="UTF-8">\n<title>apks</title>\n</head>\n<body>\n<pre>\n')
for entry in index_data:
apk_escaped = 'apk/' + html.escape(entry["apk"])
name_escaped = html.escape(entry["name"])
f.write(f'<a href="{apk_escaped}">{name_escaped}</a>\n')
f.write('</pre>\n</body>\n</html>\n')

View File

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-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.7-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

View File

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

View File

@ -84,7 +84,7 @@ open class ComicGamma(
}
}
override fun chapterListSelector() = ".read__area > .read__outer > a:not([href=#comics])"
override fun chapterListSelector() = ".read__area .read__outer > a:not([href=#comics])"
override fun chapterFromElement(element: Element) = SChapter.create().apply {
url = element.attr("href").toOldChapterUrl()
val number = url.removeSuffix("/").substringAfterLast('/').replace('_', '.')

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.galleryadults.GalleryAdultsUrlActivity"
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="/g.*/..*/"
android:scheme="${SOURCESCHEME}" />
</intent-filter>
</activity>
</application>
</manifest>

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -0,0 +1,941 @@
package eu.kanade.tachiyomi.multisrc.galleryadults
import android.app.Application
import android.content.SharedPreferences
import android.util.Log
import androidx.preference.PreferenceScreen
import androidx.preference.SwitchPreferenceCompat
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
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.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.model.UpdateStrategy
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.joinAll
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.SerializationException
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonObject
import okhttp3.FormBody
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
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.text.SimpleDateFormat
abstract class GalleryAdults(
override val name: String,
override val baseUrl: String,
override val lang: String = "all",
protected open val mangaLang: String = LANGUAGE_MULTI,
protected val simpleDateFormat: SimpleDateFormat? = null,
) : ConfigurableSource, ParsedHttpSource() {
override val client: OkHttpClient = network.cloudflareClient
protected open val xhrHeaders = headers.newBuilder()
.add("X-Requested-With", "XMLHttpRequest")
.build()
/* Preferences */
protected val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
protected open val useShortTitlePreference = true
override fun setupPreferenceScreen(screen: PreferenceScreen) {
SwitchPreferenceCompat(screen.context).apply {
key = PREF_SHORT_TITLE
title = "Display Short Titles"
summaryOff = "Showing Long Titles"
summaryOn = "Showing short Titles"
setDefaultValue(false)
setVisible(useShortTitlePreference)
}.also(screen::addPreference)
}
protected val SharedPreferences.shortTitle
get() = getBoolean(PREF_SHORT_TITLE, false)
/* List detail */
protected class SMangaDto(
val title: String,
val url: String,
val thumbnail: String?,
val lang: String,
)
protected open fun Element.mangaTitle(selector: String = ".caption"): String? =
mangaFullTitle(selector).let {
if (preferences.shortTitle) it?.shortenTitle() else it
}
protected open fun Element.mangaFullTitle(selector: String) =
selectFirst(selector)?.text()
protected open fun String.shortenTitle() = this.replace(shortenTitleRegex, "").trim()
protected open val shortenTitleRegex = Regex("""(\[[^]]*]|[({][^)}]*[)}])""")
protected open fun Element.mangaUrl() =
selectFirst(".inner_thumb a")?.attr("abs:href")
protected open fun Element.mangaThumbnail() =
selectFirst(".inner_thumb img")?.imgAttr()
// Overwrite this to filter other languages' manga from search result.
// Default to [mangaLang] won't filter anything
protected open fun Element.mangaLang() = mangaLang
protected open fun HttpUrl.Builder.addPageUri(page: Int): HttpUrl.Builder {
val url = toString()
if (!url.endsWith('/') && !url.contains('?')) {
addPathSegment("") // trailing slash (/)
}
addQueryParameter("page", page.toString())
return this
}
/* Popular */
override fun popularMangaRequest(page: Int): Request {
val url = baseUrl.toHttpUrl().newBuilder().apply {
if (mangaLang.isNotBlank()) addPathSegments("language/$mangaLang")
if (supportsLatest) addPathSegment("popular")
addPageUri(page)
}
return GET(url.build(), headers)
}
override fun popularMangaSelector() = "div.thumb"
override fun popularMangaFromElement(element: Element): SManga {
return SManga.create().apply {
title = element.mangaTitle()!!
setUrlWithoutDomain(element.mangaUrl()!!)
thumbnail_url = element.mangaThumbnail()
}
}
override fun popularMangaNextPageSelector() = ".pagination li.active + li:not(.disabled)"
/* Latest */
override fun latestUpdatesRequest(page: Int): Request {
val url = baseUrl.toHttpUrl().newBuilder().apply {
if (mangaLang.isNotBlank()) addPathSegments("language/$mangaLang")
addPageUri(page)
}
return GET(url.build(), headers)
}
override fun latestUpdatesSelector() = popularMangaSelector()
override fun latestUpdatesFromElement(element: Element) = popularMangaFromElement(element)
override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector()
/* Search */
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
val randomEntryFilter = filters.filterIsInstance<RandomEntryFilter>().firstOrNull()
return when {
randomEntryFilter?.state == true -> {
client.newCall(randomEntryRequest())
.asObservableSuccess()
.map { response -> randomEntryParse(response) }
}
query.startsWith(PREFIX_ID_SEARCH) -> {
val id = query.removePrefix(PREFIX_ID_SEARCH)
client.newCall(searchMangaByIdRequest(id))
.asObservableSuccess()
.map { response -> searchMangaByIdParse(response, id) }
}
query.toIntOrNull() != null -> {
client.newCall(searchMangaByIdRequest(query))
.asObservableSuccess()
.map { response -> searchMangaByIdParse(response, query) }
}
else -> {
client.newCall(searchMangaRequest(page, query, filters))
.asObservableSuccess()
.map { response -> searchMangaParse(response) }
}
}
}
protected open fun randomEntryRequest(): Request = GET("$baseUrl/random/", headers)
protected open fun randomEntryParse(response: Response): MangasPage {
val document = response.asJsoup()
val url = response.request.url.toString()
val id = url.removeSuffix("/").substringAfterLast('/')
return MangasPage(
listOf(
SManga.create().apply {
title = document.mangaTitle("h1")!!
setUrlWithoutDomain("$baseUrl/$idPrefixUri/$id/")
thumbnail_url = document.getCover()
},
),
false,
)
}
/**
* Manga URL: $baseUrl/$idPrefixUri/<id>/
*/
protected open val idPrefixUri = "gallery"
protected open fun searchMangaByIdRequest(id: String): Request {
val url = baseUrl.toHttpUrl().newBuilder().apply {
addPathSegment(idPrefixUri)
addPathSegments("$id/")
}
return GET(url.build(), headers)
}
protected open fun searchMangaByIdParse(response: Response, id: String): MangasPage {
val details = mangaDetailsParse(response.asJsoup())
details.url = "/$idPrefixUri/$id/"
return MangasPage(listOf(details), false)
}
protected open val useIntermediateSearch: Boolean = false
protected open val supportAdvancedSearch: Boolean = false
protected open val supportSpeechless: Boolean = false
protected open val useBasicSearch: Boolean
get() = !useIntermediateSearch
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
// Basic search
val sortOrderFilter = filters.filterIsInstance<SortOrderFilter>().firstOrNull()
val genresFilter = filters.filterIsInstance<GenresFilter>().firstOrNull()
val selectedGenres = genresFilter?.state?.filter { it.state } ?: emptyList()
val favoriteFilter = filters.filterIsInstance<FavoriteFilter>().firstOrNull()
// Speechless
val speechlessFilter = filters.filterIsInstance<SpeechlessFilter>().firstOrNull()
// Advanced search
val advancedSearchFilters = filters.filterIsInstance<AdvancedTextFilter>()
return when {
favoriteFilter?.state == true ->
favoriteFilterSearchRequest(page, query, filters)
supportSpeechless && speechlessFilter?.state == true ->
speechlessFilterSearchRequest(page, query, filters)
supportAdvancedSearch && advancedSearchFilters.any { it.state.isNotBlank() } ->
advancedSearchRequest(page, query, filters)
selectedGenres.size == 1 && query.isBlank() ->
tagBrowsingSearchRequest(page, query, filters)
useIntermediateSearch ->
intermediateSearchRequest(page, query, filters)
useBasicSearch && (selectedGenres.size > 1 || query.isNotBlank()) ->
basicSearchRequest(page, query, filters)
sortOrderFilter?.state == 1 ->
latestUpdatesRequest(page)
else ->
popularMangaRequest(page)
}
}
protected open val basicSearchKey = "q"
/**
* Basic Search: support query string with multiple-genres filter by adding genres to query string.
*/
protected open fun basicSearchRequest(page: Int, query: String, filters: FilterList): Request {
// Basic search
val sortOrderFilter = filters.filterIsInstance<SortOrderFilter>().firstOrNull()
val genresFilter = filters.filterIsInstance<GenresFilter>().firstOrNull()
val selectedGenres = genresFilter?.state?.filter { it.state } ?: emptyList()
val url = baseUrl.toHttpUrl().newBuilder().apply {
addPathSegments("search/")
addEncodedQueryParameter(basicSearchKey, buildQueryString(selectedGenres.map { it.name }, query))
if (sortOrderFilter?.state == 0) addQueryParameter("sort", "popular")
addPageUri(page)
}
return GET(url.build(), headers)
}
protected open val intermediateSearchKey = "key"
/**
* This supports filter query search with languages, categories (manga, doujinshi...)
* with additional sort orders.
*/
protected open fun intermediateSearchRequest(page: Int, query: String, filters: FilterList): Request {
// Basic search
val sortOrderFilter = filters.filterIsInstance<SortOrderFilter>().firstOrNull()
val genresFilter = filters.filterIsInstance<GenresFilter>().firstOrNull()
val selectedGenres = genresFilter?.state?.filter { it.state } ?: emptyList()
// Intermediate search
val categoryFilters = filters.filterIsInstance<CategoryFilters>().firstOrNull()
// Only for query string or multiple tags
val url = "$baseUrl/search".toHttpUrl().newBuilder().apply {
getSortOrderURIs().forEachIndexed { index, pair ->
addQueryParameter(pair.second, toBinary(sortOrderFilter?.state == index))
}
categoryFilters?.state?.forEach {
addQueryParameter(it.uri, toBinary(it.state))
}
getLanguageURIs().forEach { pair ->
addQueryParameter(
pair.second,
toBinary(mangaLang == pair.first || mangaLang == LANGUAGE_MULTI),
)
}
addEncodedQueryParameter(intermediateSearchKey, buildQueryString(selectedGenres.map { it.name }, query))
addPageUri(page)
}
return GET(url.build())
}
protected open val advancedSearchKey = "key"
protected open val advancedSearchUri = "advsearch"
/**
* Advanced Search normally won't support search for string but allow include/exclude specific
* tags/artists/groups/parodies/characters
*/
protected open fun advancedSearchRequest(page: Int, query: String, filters: FilterList): Request {
// Basic search
val sortOrderFilter = filters.filterIsInstance<SortOrderFilter>().firstOrNull()
val genresFilter = filters.filterIsInstance<GenresFilter>().firstOrNull()
val selectedGenres = genresFilter?.state?.filter { it.state } ?: emptyList()
// Intermediate search
val categoryFilters = filters.filterIsInstance<CategoryFilters>().firstOrNull()
// Advanced search
val advancedSearchFilters = filters.filterIsInstance<AdvancedTextFilter>()
val url = "$baseUrl/$advancedSearchUri".toHttpUrl().newBuilder().apply {
getSortOrderURIs().forEachIndexed { index, pair ->
addQueryParameter(pair.second, toBinary(sortOrderFilter?.state == index))
}
categoryFilters?.state?.forEach {
addQueryParameter(it.uri, toBinary(it.state))
}
getLanguageURIs().forEach { pair ->
addQueryParameter(
pair.second,
toBinary(
mangaLang == pair.first ||
mangaLang == LANGUAGE_MULTI,
),
)
}
// Build this query string: +tag:"bat+man"+-tag:"cat"+artist:"Joe"...
// +tag must be encoded into %2Btag while the rest are not needed to encode
val keys = emptyList<String>().toMutableList()
keys.addAll(selectedGenres.map { "%2Btag:\"${it.name}\"" })
advancedSearchFilters.forEach { filter ->
val key = when (filter) {
is TagsFilter -> "tag"
is ParodiesFilter -> "parody"
is ArtistsFilter -> "artist"
is CharactersFilter -> "character"
is GroupsFilter -> "group"
else -> null
}
if (key != null) {
keys.addAll(
filter.state.trim()
.replace(regexSpaceNotAfterComma, "+")
.replace(" ", "")
.split(',')
.mapNotNull {
val match = regexExcludeTerm.find(it)
match?.groupValues?.let { groups ->
"${if (groups[1].isNotBlank()) "-" else "%2B"}$key:\"${groups[2]}\""
}
},
)
}
}
addEncodedQueryParameter(advancedSearchKey, keys.joinToString("+"))
addPageUri(page)
}
return GET(url.build())
}
/**
* Convert space( ) typed in search-box into plus(+) in URL. Then:
* - uses plus(+) to search for exact match
* - use comma(,) for separate terms, as AND condition.
* Plus(+) after comma(,) doesn't have any effect.
*/
protected open fun buildQueryString(tags: List<String>, query: String): String {
return (tags + query).filterNot { it.isBlank() }.joinToString(",") {
// any space except after a comma (we're going to replace spaces only between words)
it.trim()
.replace(regexSpaceNotAfterComma, "+")
.replace(" ", "")
}
}
protected open fun tagBrowsingSearchRequest(page: Int, query: String, filters: FilterList): Request {
// Basic search
val sortOrderFilter = filters.filterIsInstance<SortOrderFilter>().firstOrNull()
val genresFilter = filters.filterIsInstance<GenresFilter>().firstOrNull()
val selectedGenres = genresFilter?.state?.filter { it.state } ?: emptyList()
// Browsing single tag's catalog
val url = baseUrl.toHttpUrl().newBuilder().apply {
addPathSegment("tag")
addPathSegment(selectedGenres.single().uri)
if (sortOrderFilter?.state == 0) addPathSegment("popular")
addPageUri(page)
}
return GET(url.build(), headers)
}
/**
* Browsing speechless titles. Some sites exclude speechless titles from normal search and
* allow browsing separately.
*/
protected open fun speechlessFilterSearchRequest(page: Int, query: String, filters: FilterList): Request {
// Basic search
val sortOrderFilter = filters.filterIsInstance<SortOrderFilter>().firstOrNull()
val url = baseUrl.toHttpUrl().newBuilder().apply {
addPathSegment("language")
addPathSegment(LANGUAGE_SPEECHLESS)
if (sortOrderFilter?.state == 0) addPathSegment("popular")
addPageUri(page)
}
return GET(url.build(), headers)
}
/**
* Browsing user's personal favorites saved on site. This requires login in view WebView.
*/
protected open fun favoriteFilterSearchRequest(page: Int, query: String, filters: FilterList): Request {
val url = "$baseUrl/$favoritePath".toHttpUrl().newBuilder()
return POST(
url.build().toString(),
xhrHeaders,
FormBody.Builder()
.add("page", page.toString())
.build(),
)
}
protected open val favoritePath = "user/fav_pags.php"
protected open fun loginRequired(document: Document, url: String): Boolean {
return (
url.contains("/login/") &&
document.select("input[value=Login]").isNotEmpty()
)
}
override fun searchMangaParse(response: Response): MangasPage {
val document = response.asJsoup()
if (loginRequired(document, response.request.url.toString())) {
throw Exception("Log in via WebView to view favorites")
} else {
val hasNextPage = document.select(searchMangaNextPageSelector()).isNotEmpty()
val mangas = document.select(searchMangaSelector())
.map {
SMangaDto(
title = it.mangaTitle()!!,
url = it.mangaUrl()!!,
thumbnail = it.mangaThumbnail(),
lang = it.mangaLang(),
)
}
.let { unfiltered ->
val results = unfiltered.filter { mangaLang.isBlank() || it.lang == mangaLang }
// return at least 1 title if all mangas in current page is of other languages
if (results.isEmpty() && hasNextPage) listOf(unfiltered[0]) else results
}
.map {
SManga.create().apply {
title = it.title
setUrlWithoutDomain(it.url)
thumbnail_url = it.thumbnail
}
}
return MangasPage(mangas, hasNextPage)
}
}
override fun searchMangaSelector() = popularMangaSelector()
override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element)
override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
/* Details */
protected open val mangaDetailInfoSelector = ".gallery_top"
override fun mangaDetailsParse(document: Document): SManga {
return document.selectFirst(mangaDetailInfoSelector)!!.run {
SManga.create().apply {
update_strategy = UpdateStrategy.ONLY_FETCH_ONCE
status = SManga.COMPLETED
title = mangaTitle("h1")!!
thumbnail_url = getCover()
genre = getInfo("Tags")
author = getInfo("Artists")
description = getDescription(document)
}
}
}
protected open fun Element.getCover() =
selectFirst(".cover img")?.imgAttr()
protected val regexTag = Regex("Tags?")
/**
* Parsing document to extract info related to [tag].
*/
protected abstract fun Element.getInfo(tag: String): String
protected open fun Element.getDescription(document: Document? = null): String = (
listOf("Parodies", "Characters", "Languages", "Categories", "Category")
.mapNotNull { tag ->
getInfo(tag)
.takeIf { it.isNotBlank() }
?.let { "$tag: $it" }
} +
listOfNotNull(
getInfoPages(document),
getInfoAlternativeTitle(),
getInfoFullTitle(),
)
)
.joinToString("\n\n")
protected open fun Element.getInfoPages(document: Document? = null): String? =
document?.inputIdValueOf(totalPagesSelector)
?.takeIf { it.isNotBlank() }
?.let { "Pages: $it" }
protected open fun Element.getInfoAlternativeTitle(): String? =
selectFirst("h1 + h2, .subtitle")?.ownText()
.takeIf { !it.isNullOrBlank() }
?.let { "Alternative title: $it" }
protected open fun Element.getInfoFullTitle(): String? =
if (preferences.shortTitle) "Full title: ${mangaFullTitle("h1")}" else null
protected open fun Element.getTime(): Long =
selectFirst(".uploaded")
?.ownText()
.toDate(simpleDateFormat)
/* Chapters */
override fun chapterListParse(response: Response): List<SChapter> {
val document = response.asJsoup()
return listOf(
SChapter.create().apply {
name = "Chapter"
scanlator = document.selectFirst(mangaDetailInfoSelector)
?.getInfo("Groups")
date_upload = document.getTime()
setUrlWithoutDomain(response.request.url.encodedPath)
},
)
}
override fun chapterListSelector() = throw UnsupportedOperationException()
override fun chapterFromElement(element: Element): SChapter = throw UnsupportedOperationException()
/* Pages */
protected open fun Element.inputIdValueOf(string: String): String {
return select("input[id=$string]").attr("value")
}
protected open val pagesRequest = "inc/thumbs_loader.php"
protected open val galleryIdSelector = "gallery_id"
protected open val loadIdSelector = "load_id"
protected open val loadDirSelector = "load_dir"
protected open val totalPagesSelector = "load_pages"
protected open val serverSelector = "load_server"
protected open fun pageRequestForm(document: Document, totalPages: String, loadedPages: Int): FormBody {
val token = document.select("[name=csrf-token]").attr("content")
val serverNumber = document.serverNumber()
return FormBody.Builder()
.add("u_id", document.inputIdValueOf(galleryIdSelector))
.add("g_id", document.inputIdValueOf(loadIdSelector))
.add("img_dir", document.inputIdValueOf(loadDirSelector))
.add("visible_pages", loadedPages.toString())
.add("total_pages", totalPages)
.add("type", "2") // 1 would be "more", 2 is "all remaining"
.apply {
if (token.isNotBlank()) add("_token", token)
if (serverNumber != null) add("server", serverNumber)
}
.build()
}
protected open val thumbnailSelector = ".gallery_thumb"
private val jsonFormat: Json by injectLazy()
protected open fun Element.getServer(): String {
val domain = baseUrl.toHttpUrl().host
return serverNumber()
?.let { "m$it.$domain" }
?: getCover()!!.toHttpUrl().host
}
protected open fun Element.serverNumber(): String? =
inputIdValueOf(serverSelector)
.takeIf { it.isNotBlank() }
protected open fun Element.parseJson(): String? =
selectFirst("script:containsData(parseJSON)")?.data()
?.substringAfter("$.parseJSON('")
?.substringBefore("');")?.trim()
/**
* Page URL: $baseUrl/$pageUri/<id>/<page>
*/
protected open val pageUri = "g"
override fun pageListParse(document: Document): List<Page> {
val json = document.parseJson()
if (json != null) {
val loadDir = document.inputIdValueOf(loadDirSelector)
val loadId = document.inputIdValueOf(loadIdSelector)
val galleryId = document.inputIdValueOf(galleryIdSelector)
val pageUrl = "$baseUrl/$pageUri/$galleryId"
val server = document.getServer()
val imagesUri = "https://$server/$loadDir/$loadId"
try {
val pages = mutableListOf<Page>()
val images = jsonFormat.parseToJsonElement(json).jsonObject
// JSON string in this form: {"1":"j,1100,1148","2":"j,728,689",...
for (image in images) {
val ext = image.value.toString().replace("\"", "").split(",")[0]
val imageExt = when (ext) {
"p" -> "png"
"b" -> "bmp"
"g" -> "gif"
else -> "jpg"
}
val idx = image.key.toInt()
pages.add(
Page(
index = idx,
imageUrl = "$imagesUri/${image.key}.$imageExt",
url = "$pageUrl/$idx/",
),
)
}
return pages
} catch (e: SerializationException) {
Log.e("GalleryAdults", "Failed to decode JSON")
return this.pageListParseAlternative(document)
}
} else {
return this.pageListParseAlternative(document)
}
}
/**
* Overwrite this to force extension not blindly converting thumbnails to full image
* by simply removing the trailing "t" from file name. Instead, it will open each page,
* one by one, then parsing for actual image's URL.
* This will be much slower but guaranteed work.
*
* This only apply if site doesn't provide 'parseJSON'.
*/
protected open val parsingImagePageByPage: Boolean = false
/**
* Either:
* - Load all thumbnails then convert thumbnails to full images.
* - Or request then parse for a list of manga's page's URL,
* which will then request one by one to parse for page's image's URL using [imageUrlParse].
*/
protected open fun pageListParseAlternative(document: Document): List<Page> {
val totalPages = document.inputIdValueOf(totalPagesSelector)
val galleryId = document.inputIdValueOf(galleryIdSelector)
val pageUrl = "$baseUrl/$pageUri/$galleryId"
val pages = document.select("$thumbnailSelector a")
.map {
if (parsingImagePageByPage) {
it.absUrl("href")
} else {
it.selectFirst("img")!!.imgAttr()
}
}
.toMutableList()
if (totalPages.isNotBlank() && totalPages.toInt() > pages.size) {
val form = pageRequestForm(document, totalPages, pages.size)
val morePages = client.newCall(POST("$baseUrl/$pagesRequest", xhrHeaders, form))
.execute()
.asJsoup()
.select("a")
.map {
if (parsingImagePageByPage) {
it.absUrl("href")
} else {
it.selectFirst("img")!!.imgAttr()
}
}
if (morePages.isNotEmpty()) {
pages.addAll(morePages)
} else {
return pageListParseDummy(document)
}
}
return pages.mapIndexed { idx, url ->
if (parsingImagePageByPage) {
Page(idx, url)
} else {
Page(
index = idx,
imageUrl = url.thumbnailToFull(),
url = "$pageUrl/$idx/",
)
}
}
}
/**
* Generate all images using `totalPages`. Supposedly they are sequential.
* Use in case any extension doesn't know how to request for "All thumbnails"
*/
protected open fun pageListParseDummy(document: Document): List<Page> {
val loadDir = document.inputIdValueOf(loadDirSelector)
val loadId = document.inputIdValueOf(loadIdSelector)
val galleryId = document.inputIdValueOf(galleryIdSelector)
val pageUrl = "$baseUrl/$pageUri/$galleryId"
val server = document.getServer()
val imagesUri = "https://$server/$loadDir/$loadId"
val images = document.select("$thumbnailSelector img")
val thumbUrls = images.map { it.imgAttr() }.toMutableList()
val totalPages = document.inputIdValueOf(totalPagesSelector)
if (totalPages.isNotBlank() && totalPages.toInt() > thumbUrls.size) {
val imagesExt = images.first()?.imgAttr()!!
.substringAfterLast('.')
thumbUrls.addAll(
listOf((images.size + 1)..totalPages.toInt()).flatten().map {
"$imagesUri/${it}t.$imagesExt"
},
)
}
return thumbUrls.mapIndexed { idx, url ->
Page(
index = idx,
imageUrl = url.thumbnailToFull(),
url = "$pageUrl/$idx/",
)
}
}
override fun imageUrlParse(document: Document): String {
return document.selectFirst("img#gimg, img#fimg")?.imgAttr()!!
}
/* Filters */
private val scope = CoroutineScope(Dispatchers.IO)
private fun launchIO(block: () -> Unit) = scope.launch { block() }
private var tagsFetched = false
private var tagsFetchAttempt = 0
/**
* List of tags in <name, uri> pairs
*/
protected var genres: MutableMap<String, String> = mutableMapOf()
protected open fun tagsRequest(page: Int): Request {
val url = baseUrl.toHttpUrl().newBuilder().apply {
addPathSegments("tags/popular")
addPageUri(page)
}
return GET(url.build(), headers)
}
/**
* Parsing [document] to return a list of tags in <name, uri> pairs.
*/
protected open fun tagsParser(document: Document): List<Genre> {
return document.select("a.tag_btn")
.mapNotNull {
Genre(
it.select(".list_tag, .tag_name").text(),
it.attr("href")
.removeSuffix("/").substringAfterLast('/'),
)
}
}
protected open fun requestTags() {
if (!tagsFetched && tagsFetchAttempt < 3) {
launchIO {
val tags = mutableListOf<Genre>()
runBlocking {
val jobsPool = mutableListOf<Job>()
// Get first 3 pages
(1..3).forEach { page ->
jobsPool.add(
launchIO {
runCatching {
tags.addAll(
client.newCall(tagsRequest(page))
.execute().asJsoup().let { tagsParser(it) },
)
}
},
)
}
jobsPool.joinAll()
tags.sortedWith(compareBy { it.name })
.forEach {
genres[it.name] = it.uri
}
tagsFetched = true
}
tagsFetchAttempt++
}
}
}
override fun getFilterList(): FilterList {
requestTags()
val filters = emptyList<Filter<*>>().toMutableList()
if (useIntermediateSearch) {
filters.add(Filter.Header("HINT: Separate search term with comma (,)"))
}
filters.add(SortOrderFilter(getSortOrderURIs()))
if (genres.isEmpty()) {
filters.add(Filter.Header("Press 'reset' to attempt to load tags"))
} else {
filters.add(GenresFilter(genres))
}
if (useIntermediateSearch || supportAdvancedSearch) {
filters.addAll(
listOf(
Filter.Separator(),
CategoryFilters(getCategoryURIs()),
),
)
}
if (supportAdvancedSearch) {
filters.addAll(
listOf(
Filter.Separator(),
Filter.Header("Advanced filters will ignore query search. Separate terms by comma (,) and precede term with minus (-) to exclude."),
TagsFilter(),
ParodiesFilter(),
ArtistsFilter(),
CharactersFilter(),
GroupsFilter(),
),
)
}
filters.add(Filter.Separator())
if (supportSpeechless) {
filters.add(SpeechlessFilter())
}
filters.add(FavoriteFilter())
filters.add(RandomEntryFilter())
return FilterList(filters)
}
protected open fun getSortOrderURIs() = listOf(
Pair("Popular", "pp"),
Pair("Latest", "lt"),
) + if (useIntermediateSearch || supportAdvancedSearch) {
listOf(
Pair("Downloads", "dl"),
Pair("Top Rated", "tr"),
)
} else {
emptyList()
}
protected open fun getCategoryURIs() = listOf(
SearchFlagFilter("Manga", "m"),
SearchFlagFilter("Doujinshi", "d"),
SearchFlagFilter("Western", "w"),
SearchFlagFilter("Image Set", "i"),
SearchFlagFilter("Artist CG", "a"),
SearchFlagFilter("Game CG", "g"),
)
protected open fun getLanguageURIs() = listOf(
Pair(LANGUAGE_ENGLISH, "en"),
Pair(LANGUAGE_JAPANESE, "jp"),
Pair(LANGUAGE_SPANISH, "es"),
Pair(LANGUAGE_FRENCH, "fr"),
Pair(LANGUAGE_KOREAN, "kr"),
Pair(LANGUAGE_GERMAN, "de"),
Pair(LANGUAGE_RUSSIAN, "ru"),
)
companion object {
const val PREFIX_ID_SEARCH = "id:"
private const val PREF_SHORT_TITLE = "pref_short_title"
// references to be used in factory
const val LANGUAGE_MULTI = ""
const val LANGUAGE_ENGLISH = "english"
const val LANGUAGE_JAPANESE = "japanese"
const val LANGUAGE_CHINESE = "chinese"
const val LANGUAGE_KOREAN = "korean"
const val LANGUAGE_SPANISH = "spanish"
const val LANGUAGE_FRENCH = "french"
const val LANGUAGE_GERMAN = "german"
const val LANGUAGE_RUSSIAN = "russian"
const val LANGUAGE_SPEECHLESS = "speechless"
const val LANGUAGE_TRANSLATED = "translated"
}
}

View File

@ -0,0 +1,31 @@
package eu.kanade.tachiyomi.multisrc.galleryadults
import eu.kanade.tachiyomi.source.model.Filter
class Genre(name: String, val uri: String) : Filter.CheckBox(name)
class GenresFilter(genres: Map<String, String>) : Filter.Group<Genre>(
"Tags",
genres.map { Genre(it.key, it.value) },
)
class SortOrderFilter(sortOrderURIs: List<Pair<String, String>>) :
Filter.Select<String>("Sort By", sortOrderURIs.map { it.first }.toTypedArray())
class FavoriteFilter : Filter.CheckBox("Show favorites only (login via WebView)", false)
class RandomEntryFilter : Filter.CheckBox("Random manga", false)
// Speechless
class SpeechlessFilter : Filter.CheckBox("Show speechless items only", false)
// Intermediate search
class SearchFlagFilter(name: String, val uri: String, state: Boolean = true) : Filter.CheckBox(name, state)
class CategoryFilters(flags: List<SearchFlagFilter>) : Filter.Group<SearchFlagFilter>("Categories", flags)
// Advance search
abstract class AdvancedTextFilter(name: String) : Filter.Text(name)
class TagsFilter : AdvancedTextFilter("Tags")
class ParodiesFilter : AdvancedTextFilter("Parodies")
class ArtistsFilter : AdvancedTextFilter("Artists")
class CharactersFilter : AdvancedTextFilter("Characters")
class GroupsFilter : AdvancedTextFilter("Groups")

View File

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.extension.all.imhentai
package eu.kanade.tachiyomi.multisrc.galleryadults
import android.app.Activity
import android.content.ActivityNotFoundException
@ -8,10 +8,9 @@ import android.util.Log
import kotlin.system.exitProcess
/**
* Springboard that accepts https://imhentai.xxx/gallery/xxxxxx intents and redirects them to
* the main Tachiyomi process.
* Springboard that accepts https://<domain>/g/xxxxxx intents and redirects them to main app process.
*/
class IMHentaiUrlActivity : Activity() {
class GalleryAdultsUrlActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val pathSegments = intent?.data?.pathSegments
@ -19,17 +18,17 @@ class IMHentaiUrlActivity : Activity() {
val id = pathSegments[1]
val mainIntent = Intent().apply {
action = "eu.kanade.tachiyomi.SEARCH"
putExtra("query", "id:$id")
putExtra("query", "${GalleryAdults.PREFIX_ID_SEARCH}$id")
putExtra("filter", packageName)
}
try {
startActivity(mainIntent)
} catch (e: ActivityNotFoundException) {
Log.e("IMHentaiUrlActivity", e.toString())
Log.e("GalleryAdultsUrl", e.toString())
}
} else {
Log.e("IMHentaiUrlActivity", "could not parse uri from intent $intent")
Log.e("GalleryAdultsUrl", "could not parse uri from intent $intent")
}
finish()

View File

@ -0,0 +1,144 @@
package eu.kanade.tachiyomi.multisrc.galleryadults
import org.jsoup.nodes.Element
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.Calendar
// any space except after a comma (we're going to replace spaces only between words)
val regexSpaceNotAfterComma = Regex("""(?<!,)\s+""")
// extract preceding minus (-) and term
val regexExcludeTerm = Regex("""^(-?)"?(.+)"?""")
val regexTagCountNumber = Regex("\\([0-9,]*\\)")
val regexDateSuffix = Regex("""\d(st|nd|rd|th)""")
val regexDate = Regex("""\d\D\D""")
val regexNotNumber = Regex("""\D""")
val regexRelativeDateTime = Regex("""\d*[^0-9]*(\d+)""")
fun Element.imgAttr() = when {
hasAttr("data-cfsrc") -> absUrl("data-cfsrc")
hasAttr("data-src") -> absUrl("data-src")
hasAttr("data-lazy-src") -> absUrl("data-lazy-src")
hasAttr("srcset") -> absUrl("srcset").substringBefore(" ")
else -> absUrl("src")
}
fun Element.cleanTag(): String = text().cleanTag()
fun String.cleanTag(): String = replace(regexTagCountNumber, "").trim()
// convert thumbnail URLs to full image URLs
fun String.thumbnailToFull(): String {
val ext = substringAfterLast(".")
return replace("t.$ext", ".$ext")
}
fun String?.toDate(simpleDateFormat: SimpleDateFormat?): Long {
this ?: return 0L
return if (simpleDateFormat != null) {
if (contains(regexDateSuffix)) {
// Clean date (e.g. 5th December 2019 to 5 December 2019) before parsing it
split(" ").map {
if (it.contains(regexDate)) {
it.replace(regexNotNumber, "")
} else {
it
}
}
.let { simpleDateFormat.tryParse(it.joinToString(" ")) }
} else {
simpleDateFormat.tryParse(this)
}
} else {
parseDate(this)
}
}
private fun parseDate(date: String?): Long {
date ?: return 0L
return when {
// Handle 'yesterday' and 'today', using midnight
WordSet("yesterday", "يوم واحد").startsWith(date) -> {
Calendar.getInstance().apply {
add(Calendar.DAY_OF_MONTH, -1) // yesterday
set(Calendar.HOUR_OF_DAY, 0)
set(Calendar.MINUTE, 0)
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}.timeInMillis
}
WordSet("today", "just now").startsWith(date) -> {
Calendar.getInstance().apply {
set(Calendar.HOUR_OF_DAY, 0)
set(Calendar.MINUTE, 0)
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}.timeInMillis
}
WordSet("يومين").startsWith(date) -> {
Calendar.getInstance().apply {
add(Calendar.DAY_OF_MONTH, -2) // day before yesterday
set(Calendar.HOUR_OF_DAY, 0)
set(Calendar.MINUTE, 0)
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}.timeInMillis
}
WordSet("ago", "atrás", "önce", "قبل").endsWith(date) -> {
parseRelativeDate(date)
}
WordSet("hace").startsWith(date) -> {
parseRelativeDate(date)
}
else -> 0L
}
}
// Parses dates in this form: 21 hours ago OR "2 days ago (Updated 19 hours ago)"
private fun parseRelativeDate(date: String): Long {
val number = regexRelativeDateTime.find(date)?.value?.toIntOrNull()
?: date.split(" ").firstOrNull()
?.replace("one", "1")
?.replace("a", "1")
?.toIntOrNull()
?: return 0L
val now = Calendar.getInstance()
// Sort by order
return when {
WordSet("detik", "segundo", "second", "วินาที").anyWordIn(date) ->
now.apply { add(Calendar.SECOND, -number) }.timeInMillis
WordSet("menit", "dakika", "min", "minute", "minuto", "นาที", "دقائق").anyWordIn(date) ->
now.apply { add(Calendar.MINUTE, -number) }.timeInMillis
WordSet("jam", "saat", "heure", "hora", "hour", "ชั่วโมง", "giờ", "ore", "ساعة", "小时").anyWordIn(date) ->
now.apply { add(Calendar.HOUR, -number) }.timeInMillis
WordSet("hari", "gün", "jour", "día", "dia", "day", "วัน", "ngày", "giorni", "أيام", "").anyWordIn(date) ->
now.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis
WordSet("week", "semana").anyWordIn(date) ->
now.apply { add(Calendar.DAY_OF_MONTH, -number * 7) }.timeInMillis
WordSet("month", "mes").anyWordIn(date) ->
now.apply { add(Calendar.MONTH, -number) }.timeInMillis
WordSet("year", "año").anyWordIn(date) ->
now.apply { add(Calendar.YEAR, -number) }.timeInMillis
else -> 0L
}
}
private fun SimpleDateFormat.tryParse(string: String): Long {
return try {
parse(string)?.time ?: 0L
} catch (_: ParseException) {
0L
}
}
class WordSet(private vararg val words: String) {
fun anyWordIn(dateString: String): Boolean = words.any { dateString.contains(it, ignoreCase = true) }
fun startsWith(dateString: String): Boolean = words.any { dateString.startsWith(it, ignoreCase = true) }
fun endsWith(dateString: String): Boolean = words.any { dateString.endsWith(it, ignoreCase = true) }
}
fun toBinary(boolean: Boolean) = if (boolean) "1" else "0"

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@ -0,0 +1,178 @@
package eu.kanade.tachiyomi.multisrc.goda
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.Document
import org.jsoup.nodes.Element
import org.jsoup.nodes.Entities
import rx.Observable
open class GoDa(
override val name: String,
override val baseUrl: String,
override val lang: String,
) : HttpSource() {
override val supportsLatest get() = true
private val enableGenres = true
override fun headersBuilder() = super.headersBuilder().add("Referer", "$baseUrl/")
override val client = network.cloudflareClient
private fun getKey(link: String): String {
return link.substringAfter("/manga/").removeSuffix("/")
}
override fun popularMangaRequest(page: Int) = GET("$baseUrl/hots/page/$page", headers)
override fun popularMangaParse(response: Response): MangasPage {
val document = response.asJsoup().also(::parseGenres)
val mangas = document.select(".cardlist .pb-2 a").map { element ->
SManga.create().apply {
val imgSrc = element.selectFirst("img")!!.attr("src")
url = getKey(element.attr("href"))
title = element.selectFirst("h3")!!.ownText()
thumbnail_url = if ("url=" in imgSrc) imgSrc.toHttpUrl().queryParameter("url")!! else imgSrc
}
}
val nextPage = if (lang == "zh") "下一頁" else "NEXT"
val hasNextPage = document.selectFirst("a[aria-label=$nextPage] button") != null
return MangasPage(mangas, hasNextPage)
}
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/newss/page/$page", headers)
override fun latestUpdatesParse(response: Response) = popularMangaParse(response)
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
if (query.isNotEmpty()) {
val url = "$baseUrl/s".toHttpUrl().newBuilder()
.addPathSegment(query)
.addEncodedQueryParameter("page", "$page")
.build()
return GET(url, headers)
}
for (filter in filters) {
if (filter is UriPartFilter) return GET(baseUrl + filter.toUriPart() + "/page/$page", headers)
}
return popularMangaRequest(page)
}
override fun searchMangaParse(response: Response) = popularMangaParse(response)
override fun getMangaUrl(manga: SManga) = "$baseUrl/manga/${manga.url}"
override fun mangaDetailsRequest(manga: SManga): Request {
return GET(getMangaUrl(manga), headers)
}
private fun Element.getMangaId() = selectFirst("#mangachapters")!!.attr("data-mid")
override fun mangaDetailsParse(response: Response) = SManga.create().apply {
val document = response.asJsoup().selectFirst("main")!!
val titleElement = document.selectFirst("h1")!!
val elements = titleElement.parent()!!.parent()!!.children()
check(elements.size == 6)
title = titleElement.ownText()
status = when (titleElement.child(0).text()) {
"連載中", "Ongoing" -> SManga.ONGOING
"完結" -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
author = Entities.unescape(elements[1].children().drop(1).joinToString { it.text().removeSuffix(" ,") })
genre = buildList {
elements[2].children().drop(1).mapTo(this) { it.text().removeSuffix(" ,") }
elements[3].children().mapTo(this) { it.text().removePrefix("#") }
}.joinToString()
description = (elements[4].text() + "\n\nID: ${document.getMangaId()}").trim()
thumbnail_url = document.selectFirst("img.object-cover")!!.attr("src")
}
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> = Observable.fromCallable {
val mangaId = manga.description
?.substringAfterLast("ID: ", "")
?.takeIf { it.toIntOrNull() != null }
?: client.newCall(mangaDetailsRequest(manga)).execute().asJsoup().getMangaId()
fetchChapterList(mangaId)
}
override fun chapterListParse(response: Response): List<SChapter> {
throw UnsupportedOperationException()
}
open fun fetchChapterList(mangaId: String): List<SChapter> {
val response = client.newCall(GET("$baseUrl/manga/get?mid=$mangaId&mode=all", headers)).execute()
return response.asJsoup().select(".chapteritem").asReversed().map { element ->
val anchor = element.selectFirst("a")!!
SChapter.create().apply {
url = getKey(anchor.attr("href")) + "#$mangaId/" + anchor.attr("data-cs")
name = anchor.attr("data-ct")
}
}
}
override fun getChapterUrl(chapter: SChapter) = "$baseUrl/manga/" + chapter.url.substringBeforeLast('#')
override fun pageListRequest(chapter: SChapter): Request {
val id = chapter.url.substringAfterLast('#', "")
val mangaId = id.substringBefore('/', "")
val chapterId = id.substringAfter('/', "")
return pageListRequest(mangaId, chapterId)
}
open fun pageListRequest(mangaId: String, chapterId: String) = GET("$baseUrl/chapter/getcontent?m=$mangaId&c=$chapterId", headers)
override fun pageListParse(response: Response): List<Page> {
val document = response.asJsoup()
return document.select("#chapcontent > div > img").mapIndexed { index, element ->
Page(index, imageUrl = element.attr("data-src").ifEmpty { element.attr("src") })
}
}
override fun imageUrlParse(response: Response) = throw UnsupportedOperationException()
private var genres: Array<Pair<String, String>> = emptyArray()
private fun parseGenres(document: Document) {
if (!enableGenres || genres.isNotEmpty()) return
val box = document.selectFirst("h2")?.parent()?.parent() ?: return
val items = box.select("a")
genres = Array(items.size) { i ->
val item = items[i]
Pair(item.text().removePrefix("#"), item.attr("href"))
}
}
override fun getFilterList(): FilterList =
if (!enableGenres) {
FilterList()
} else if (genres.isEmpty()) {
FilterList(listOf(Filter.Header(if (lang == "zh") "点击“重置”刷新分类" else "Tap 'Reset' to load genres")))
} else {
val list = listOf(
Filter.Header(if (lang == "zh") "分类(搜索文本时无效)" else "Filters are ignored when using text search."),
UriPartFilter(if (lang == "zh") "分类" else "Genre", genres),
)
FilterList(list)
}
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

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

View File

@ -76,7 +76,7 @@ abstract class HeanCms(
protected open val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ", Locale.US)
override fun headersBuilder(): Headers.Builder = Headers.Builder()
override fun headersBuilder() = super.headersBuilder()
.add("Origin", baseUrl)
.add("Referer", "$baseUrl/")

View File

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

View File

@ -223,42 +223,38 @@ abstract class Keyoapp(
// Image list
override fun pageListParse(document: Document): List<Page> {
return document.select("#pages > img").map {
val index = it.attr("count").toInt()
Page(index, document.location(), it.imgAttr("150"))
}
return document.select("#pages > img")
.map { it.imgAttr() }
.filter { it.contains(imgCdnRegex) }
.mapIndexed { index, img ->
Page(index, document.location(), img)
}
}
private val imgCdnRegex = Regex("""^(https?:)?//cdn\d*\.keyoapp\.com""")
override fun imageUrlParse(document: Document) = ""
// Utilities
// From mangathemesia
private fun Element.imgAttr(width: String): String {
private fun Element.imgAttr(): String {
val url = when {
hasAttr("data-lazy-src") -> attr("abs:data-lazy-src")
hasAttr("data-src") -> attr("abs:data-src")
else -> attr("abs:src")
}
return url.toHttpUrl()
.newBuilder()
.addQueryParameter("w", width)
.build()
.toString()
return url
}
private fun Element.getImageUrl(selector: String): String? {
return this.selectFirst(selector)?.let {
it.attr("style")
return this.selectFirst(selector)?.let { element ->
element.attr("style")
.substringAfter(":url(", "")
.substringBefore(")", "")
.takeIf { it.isNotEmpty() }
?.toHttpUrlOrNull()?.let {
it.newBuilder()
.setQueryParameter("w", "480")
.build()
.toString()
}
?.toHttpUrlOrNull()?.newBuilder()?.setQueryParameter("w", "480")?.build()
?.toString()
}
}

View File

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

View File

@ -612,6 +612,9 @@ abstract class Madara(
"مكتمل",
"已完结",
"Tamamlandı",
"Đã hoàn thành",
"Завершено",
"Tamamlanan",
)
protected val ongoingStatusList: Array<String> = arrayOf(
@ -619,6 +622,7 @@ abstract class Madara(
"Em Andamento", "En cours", "En Cours", "En cours de publication", "Ativo", "Lançando", "Đang Tiến Hành", "Devam Ediyor",
"Devam ediyor", "In Corso", "In Arrivo", "مستمرة", "مستمر", "En Curso", "En curso", "Emision",
"Curso", "En marcha", "Publicandose", "En emision", "连载中", "Em Lançamento", "Devam Ediyo",
"Đang làm", "Em postagem", "Devam Eden", "Em progresso",
)
protected val hiatusStatusList: Array<String> = arrayOf(
@ -626,12 +630,22 @@ abstract class Madara(
"Pausado",
"En espera",
"Durduruldu",
"Beklemede",
"Đang chờ",
"متوقف",
"En Pause",
"Заморожено",
)
protected val canceledStatusList: Array<String> = arrayOf(
"Canceled",
"Cancelado",
"İptal Edildi",
"Güncel",
"Đã hủy",
"ملغي",
"Abandonné",
"Заброшено",
)
override fun mangaDetailsParse(document: Document): SManga {
@ -946,7 +960,11 @@ abstract class Madara(
val imageUrl = element.selectFirst("img")?.let { imageFromElement(it) }
Page(index, document.location(), imageUrl)
}
val chapterProtectorHtml = chapterProtector.html()
val chapterProtectorHtml = chapterProtector.attr("src")
.takeIf { it.startsWith("data:text/javascript;base64,") }
?.substringAfter("data:text/javascript;base64,")
?.let { Base64.decode(it, Base64.DEFAULT).toString(Charsets.UTF_8) }
?: chapterProtector.html()
val password = chapterProtectorHtml
.substringAfter("wpmangaprotectornonce='")
.substringBefore("';")

View File

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

View File

@ -21,6 +21,7 @@ import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.jsoup.Jsoup
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import rx.Observable
@ -29,24 +30,25 @@ import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
import java.util.concurrent.TimeUnit
abstract class MadTheme(
override val name: String,
override val baseUrl: String,
override val lang: String,
private val dateFormat: SimpleDateFormat = SimpleDateFormat("MMM dd, yyy", Locale.US),
private val dateFormat: SimpleDateFormat = SimpleDateFormat("MMM dd, yyyy", Locale.ENGLISH),
) : ParsedHttpSource() {
override val supportsLatest = true
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.rateLimit(1, 1)
.rateLimit(1, 1, TimeUnit.SECONDS)
.build()
// TODO: better cookie sharing
// TODO: don't count cached responses against rate limit
private val chapterClient: OkHttpClient = network.cloudflareClient.newBuilder()
.rateLimit(1, 12)
.rateLimit(1, 12, TimeUnit.SECONDS)
.build()
override fun headersBuilder() = Headers.Builder().apply {
@ -55,6 +57,8 @@ abstract class MadTheme(
private val json: Json by injectLazy()
private var genreKey = "genre[]"
// Popular
override fun popularMangaRequest(page: Int): Request =
searchMangaRequest(page, "", FilterList(OrderFilter(0)))
@ -100,7 +104,7 @@ abstract class MadTheme(
.filter { it.state }
.let { list ->
if (list.isNotEmpty()) {
list.forEach { genre -> url.addQueryParameter("genre[]", genre.id) }
list.forEach { genre -> url.addQueryParameter(genreKey, genre.id) }
}
}
}
@ -120,11 +124,11 @@ abstract class MadTheme(
override fun searchMangaSelector(): String = ".book-detailed-item"
override fun searchMangaFromElement(element: Element): SManga = SManga.create().apply {
setUrlWithoutDomain(element.select("a").first()!!.attr("abs:href"))
title = element.select("a").first()!!.attr("title")
description = element.select(".summary").first()?.text()
genre = element.select(".genres > *").joinToString { it.text() }
thumbnail_url = element.select("img").first()!!.attr("abs:data-src")
setUrlWithoutDomain(element.selectFirst("a")!!.attr("abs:href"))
title = element.selectFirst("a")!!.attr("title")
element.selectFirst(".summary")?.text()?.let { description = it }
element.select(".genres > *").joinToString { it.text() }.takeIf { it.isNotEmpty() }?.let { genre = it }
thumbnail_url = element.selectFirst("img")!!.attr("abs:data-src")
}
/*
@ -135,23 +139,25 @@ abstract class MadTheme(
// Details
override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply {
title = document.select(".detail h1").first()!!.text()
title = document.selectFirst(".detail h1")!!.text()
author = document.select(".detail .meta > p > strong:contains(Authors) ~ a").joinToString { it.text().trim(',', ' ') }
genre = document.select(".detail .meta > p > strong:contains(Genres) ~ a").joinToString { it.text().trim(',', ' ') }
thumbnail_url = document.select("#cover img").first()!!.attr("abs:data-src")
thumbnail_url = document.selectFirst("#cover img")!!.attr("abs:data-src")
val altNames = document.select(".detail h2").first()?.text()
val altNames = document.selectFirst(".detail h2")?.text()
?.split(',', ';')
?.mapNotNull { it.trim().takeIf { it != title } }
?: listOf()
description = document.select(".summary .content").first()?.text() +
description = document.select(".summary .content, .summary .content ~ p").text() +
(altNames.takeIf { it.isNotEmpty() }?.let { "\n\nAlt name(s): ${it.joinToString()}" } ?: "")
val statusText = document.select(".detail .meta > p > strong:contains(Status) ~ a").first()!!.text()
status = when (statusText.lowercase(Locale.US)) {
val statusText = document.selectFirst(".detail .meta > p > strong:contains(Status) ~ a")!!.text()
status = when (statusText.lowercase(Locale.ENGLISH)) {
"ongoing" -> SManga.ONGOING
"completed" -> SManga.COMPLETED
"on-hold" -> SManga.ON_HIATUS
"canceled" -> SManga.CANCELLED
else -> SManga.UNKNOWN
}
}
@ -187,7 +193,14 @@ abstract class MadTheme(
}
override fun chapterListRequest(manga: SManga): Request =
GET("$baseUrl/api/manga${manga.url}/chapters?source=detail", headers)
MANGA_ID_REGEX.find(manga.url)?.groupValues?.get(1)?.let { mangaId ->
val url = "$baseUrl/service/backend/chaplist/".toHttpUrl().newBuilder()
.addQueryParameter("manga_id", mangaId)
.addQueryParameter("manga_name", manga.title)
.build()
GET(url, headers)
} ?: GET("$baseUrl/api/manga${manga.url}/chapters?source=detail", headers)
override fun searchMangaParse(response: Response): MangasPage {
if (genresList == null) {
@ -204,16 +217,25 @@ abstract class MadTheme(
.absUrl("href")
.removePrefix(baseUrl)
name = element.select(".chapter-title").first()!!.text()
date_upload = parseChapterDate(element.select(".chapter-update").first()?.text())
name = element.selectFirst(".chapter-title")!!.text()
date_upload = parseChapterDate(element.selectFirst(".chapter-update")?.text())
}
// Pages
override fun pageListParse(document: Document): List<Page> {
val html = document.html()
val mangaId = MANGA_ID_REGEX.find(document.location())?.groupValues?.get(1)
val chapterId = CHAPTER_ID_REGEX.find(document.html())?.groupValues?.get(1)
val html = if (mangaId != null && chapterId != null) {
val url = GET("$baseUrl/service/backend/chapterServer/?server_id=1&chapter_id=$chapterId", headers)
client.newCall(url).execute().body.string()
} else {
document.html()
}
val realDocument = Jsoup.parse(html, document.location())
if (!html.contains("var mainServer = \"")) {
val chapterImagesFromHtml = document.select("#chapter-images img")
val chapterImagesFromHtml = realDocument.select("#chapter-images img, .chapter-image[data-src]")
// 17/03/2023: Certain hosts only embed two pages in their "#chapter-images" and leave
// the rest to be lazily(?) loaded by javascript. Let's extract `chapImages` and compare
@ -292,7 +314,7 @@ abstract class MadTheme(
}
return when {
"ago".endsWith(date) -> {
" ago" in date -> {
parseRelativeDate(date)
}
else -> dateFormat.tryParse(date)
@ -300,10 +322,12 @@ abstract class MadTheme(
}
private fun parseRelativeDate(date: String): Long {
val number = Regex("""(\d+)""").find(date)?.value?.toIntOrNull() ?: return 0
val number = NUMBER_REGEX.find(date)?.groupValues?.getOrNull(0)?.toIntOrNull() ?: return 0
val cal = Calendar.getInstance()
return when {
date.contains("year") -> cal.apply { add(Calendar.YEAR, -number) }.timeInMillis
date.contains("month") -> cal.apply { add(Calendar.MONTH, -number) }.timeInMillis
date.contains("day") -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis
date.contains("hour") -> cal.apply { add(Calendar.HOUR, -number) }.timeInMillis
date.contains("minute") -> cal.apply { add(Calendar.MINUTE, -number) }.timeInMillis
@ -314,13 +338,21 @@ abstract class MadTheme(
// Dynamic genres
private fun parseGenres(document: Document): List<Genre>? {
return document.select(".checkbox-group.genres").first()?.select("label")?.map {
Genre(it.select(".radio__label").first()!!.text(), it.select("input").`val`())
return document.selectFirst(".checkbox-group.genres")?.select(".checkbox-wrapper")?.run {
firstOrNull()?.selectFirst("input")?.attr("name")?.takeIf { it.isNotEmpty() }?.let { genreKey = it }
map {
Genre(it.selectFirst(".radio__label")!!.text(), it.selectFirst("input")!!.`val`())
}
}
}
// Filters
override fun getFilterList() = FilterList(
// TODO: Filters for sites that support it:
// excluded genres
// genre inclusion mode
// bookmarks
// author
GenreFilter(getGenreList()),
StatusFilter(),
OrderFilter(),
@ -352,6 +384,7 @@ abstract class MadTheme(
Pair("Updated", "updated_at"),
Pair("Created", "created_at"),
Pair("Name A-Z", "name"),
// Pair("Number of Chapters", "total_chapters"),
Pair("Rating", "rating"),
),
state,
@ -365,4 +398,10 @@ abstract class MadTheme(
Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray(), state) {
fun toUriPart() = vals[state].second
}
companion object {
private val MANGA_ID_REGEX = """/manga/(\d+)-""".toRegex()
private val CHAPTER_ID_REGEX = """chapterId\s*=\s*(\d+)""".toRegex()
private val NUMBER_REGEX = """\d+""".toRegex()
}
}

View File

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

View File

@ -73,7 +73,7 @@ abstract class MangaEsp(
return MangasPage(mangas, false)
}
private var comicsList = mutableListOf<SeriesDto>()
protected var comicsList = mutableListOf<SeriesDto>()
override fun fetchSearchManga(
page: Int,
@ -93,7 +93,7 @@ abstract class MangaEsp(
override fun searchMangaParse(response: Response): MangasPage = throw UnsupportedOperationException()
private fun searchMangaParse(response: Response, page: Int, query: String, filters: FilterList): MangasPage {
protected open fun searchMangaParse(response: Response, page: Int, query: String, filters: FilterList): MangasPage {
val document = response.asJsoup()
val script = document.select("script:containsData(self.__next_f.push)").joinToString { it.data() }
val jsonString = MANGA_LIST_REGEX.find(script)?.groupValues?.get(1)
@ -105,7 +105,7 @@ abstract class MangaEsp(
private var filteredList = mutableListOf<SeriesDto>()
private fun parseComicsList(page: Int, query: String, filterList: FilterList): MangasPage {
protected open fun parseComicsList(page: Int, query: String, filterList: FilterList): MangasPage {
if (page == 1) {
filteredList.clear()
@ -228,21 +228,21 @@ abstract class MangaEsp(
override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException()
private fun Element.imgAttr(): String = when {
protected open fun Element.imgAttr(): String = when {
hasAttr("data-lazy-src") -> attr("abs:data-lazy-src")
hasAttr("data-src") -> attr("abs:data-src")
hasAttr("data-cfsrc") -> attr("abs:data-cfsrc")
else -> attr("abs:src")
}
private fun String.unescape(): String {
fun String.unescape(): String {
return UNESCAPE_REGEX.replace(this, "$1")
}
companion object {
private val UNESCAPE_REGEX = """\\(.)""".toRegex()
private val MANGA_LIST_REGEX = """self\.__next_f\.push\(.*data\\":(\[.*trending.*])\}""".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()
private const val MANGAS_PER_PAGE = 15
const val MANGAS_PER_PAGE = 15
}
}

View File

@ -44,7 +44,9 @@ class SeriesDto(
val trending: TrendingDto? = null,
@SerialName("autors") private val authors: List<AuthorDto> = emptyList(),
private val artists: List<ArtistDto> = emptyList(),
@Suppress("unused") // Used in some sources
@SerialName("idioma")
val language: String? = null,
) {
fun toSManga(): SManga {
return SManga.create().apply {

View File

@ -3,7 +3,7 @@
<application>
<activity
android:name=".en.anchira.AnchiraUrlActivity"
android:name="eu.kanade.tachiyomi.multisrc.peachscan.PeachScanUrlActivity"
android:excludeFromRecents="true"
android:exported="true"
android:theme="@android:style/Theme.NoDisplay">
@ -12,10 +12,10 @@
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="anchira.to" />
<data android:pathPattern="/g/.*/..*" />
<data
android:host="${SOURCEHOST}"
android:pathPattern="/..*"
android:scheme="${SOURCESCHEME}" />
</intent-filter>
</activity>
</application>

View File

@ -2,7 +2,7 @@ plugins {
id("lib-multisrc")
}
baseVersionCode = 4
baseVersionCode = 9
dependencies {
compileOnly("com.github.tachiyomiorg:image-decoder:e08e9be535")

View File

@ -8,6 +8,7 @@ import android.graphics.Canvas
import android.graphics.Rect
import android.util.Base64
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.MangasPage
import eu.kanade.tachiyomi.source.model.Page
@ -28,6 +29,7 @@ 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
@ -82,6 +84,18 @@ abstract class PeachScan(
override fun latestUpdatesNextPageSelector() = null
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
if (query.startsWith(URL_SEARCH_PREFIX)) {
val manga = SManga.create().apply { url = query.substringAfter(URL_SEARCH_PREFIX) }
return client.newCall(mangaDetailsRequest(manga))
.asObservableSuccess()
.map {
MangasPage(listOf(mangaDetailsParse(it).apply { url = manga.url }), false)
}
}
return super.fetchSearchManga(page, query, filters)
}
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val url = baseUrl.toHttpUrl().newBuilder().apply {
addPathSegments("auto-complete/")
@ -153,21 +167,31 @@ abstract class PeachScan(
}.getOrDefault(0L)
}
private val urlsRegex = """const\s+urls\s*=\s*\[(.*?)]\s*;""".toRegex()
override fun pageListParse(document: Document): List<Page> {
val scriptElement = document.selectFirst("script:containsData(const urls =[)")
val scriptElement = document.selectFirst("script:containsData(const urls)")
?: return document.select("#imageContainer img").mapIndexed { i, it ->
Page(i, imageUrl = it.attr("abs:src"))
Page(i, document.location(), it.attr("abs:src"))
}
val urls = scriptElement.html().substringAfter("const urls =[").substringBefore("];")
val urls = urlsRegex.find(scriptElement.data())?.groupValues?.get(1)
?: throw Exception("Could not find image URLs")
return urls.split(",").mapIndexed { i, it ->
Page(i, imageUrl = baseUrl + it.trim().removeSurrounding("'") + "#page")
Page(i, document.location(), baseUrl + it.trim().removeSurrounding("'") + "#page")
}
}
override fun imageUrlParse(document: Document) = throw UnsupportedOperationException()
override fun imageRequest(page: Page): Request {
val imgHeaders = headersBuilder()
.add("Referer", page.url)
.build()
return GET(page.imageUrl!!, imgHeaders)
}
private val dataUriRegex = Regex("""base64,([0-9a-zA-Z/+=\s]+)""")
private fun zipImageInterceptor(chain: Interceptor.Chain): Response {
@ -188,7 +212,7 @@ abstract class PeachScan(
val entryIndex = splitEntryName.first().toInt()
val entryType = splitEntryName.last()
val imageData = if (entryType == "avif") {
val imageData = if (entryType == "avif" || splitEntryName.size == 1) {
zis.readBytes()
} else {
val svgBytes = zis.readBytes()
@ -251,4 +275,8 @@ abstract class PeachScan(
memInfo.totalMem < 3L * 1024 * 1024 * 1024
}
companion object {
const val URL_SEARCH_PREFIX = "slug:"
}
}

View File

@ -0,0 +1,32 @@
package eu.kanade.tachiyomi.multisrc.peachscan
import android.app.Activity
import android.content.ActivityNotFoundException
import android.content.Intent
import android.os.Bundle
import android.util.Log
import kotlin.system.exitProcess
class PeachScanUrlActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val path = intent?.data?.path
if (path != null) {
val mainIntent = Intent().apply {
action = "eu.kanade.tachiyomi.SEARCH"
putExtra("query", "${PeachScan.URL_SEARCH_PREFIX}$path")
putExtra("filter", packageName)
}
try {
startActivity(mainIntent)
} catch (e: ActivityNotFoundException) {
Log.e("PeachScanUrlActivity", e.toString())
}
} else {
Log.e("PeachScanUrlActivity", "could not parse uri from intent $intent")
}
finish()
exitProcess(0)
}
}

View File

@ -212,8 +212,8 @@ abstract class ZeistManga(
protected open val useNewChapterFeed = false
protected open val useOldChapterFeed = false
private val chapterFeedRegex = """clwd\.run\(["'](.*?)["']\)""".toRegex()
private val scriptSelector = "#clwd > script"
protected open val chapterFeedRegex = """clwd\.run\(["'](.*?)["']\)""".toRegex()
protected open val scriptSelector = "#clwd > script"
open fun getChapterFeedUrl(doc: Document): String {
if (useNewChapterFeed) return newChapterFeedUrl(doc)
@ -434,6 +434,6 @@ abstract class ZeistManga(
companion object {
private const val maxMangaResults = 20
private const val maxChapterResults = 999999
const val maxChapterResults = 999999
}
}

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -0,0 +1,122 @@
package eu.kanade.tachiyomi.extension.all.ahottie
import eu.kanade.tachiyomi.network.GET
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.Companion.toHttpUrl
import okhttp3.Request
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.Locale
class AHottie() : ParsedHttpSource() {
override val baseUrl = "https://ahottie.net"
override val lang = "all"
override val name = "AHottie"
override val supportsLatest = false
// Popular
override fun popularMangaFromElement(element: Element) = SManga.create().apply {
thumbnail_url = element.select(".relative img").attr("src")
genre = element.select(".flex a").joinToString(", ") {
it.text()
}
title = element.select("h2").text()
setUrlWithoutDomain(element.select("a").attr("href"))
initialized = true
}
override fun popularMangaNextPageSelector() = "a[rel=next]"
override fun popularMangaRequest(page: Int): Request {
return GET("$baseUrl/?page=$page", headers)
}
override fun popularMangaSelector() = "#main > div > div"
// Search
override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element)
override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
return GET(
baseUrl.toHttpUrl().newBuilder().apply {
addPathSegment("search")
addQueryParameter("kw", query)
addQueryParameter("page", page.toString())
}.build(),
headers,
)
}
override fun searchMangaSelector() = popularMangaSelector()
// Details
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
title = document.select("h1").text()
genre = document.select("div.pl-3 > a").joinToString(", ") {
it.text()
}
}
override fun pageListParse(document: Document): List<Page> {
val pages = mutableListOf<Page>()
var doc = document
while (true) {
doc.select("#main img.block").map {
pages.add(Page(pages.size, imageUrl = it.attr("src")))
}
val nextPageUrl = doc.select("a[rel=next]").attr("abs:href")
if (nextPageUrl.isEmpty()) break
doc = client.newCall(GET(nextPageUrl, headers)).execute().asJsoup()
}
return pages
}
override fun chapterFromElement(element: Element) = SChapter.create().apply {
setUrlWithoutDomain(element.select("link[rel=canonical]").attr("abs:href"))
chapter_number = 0F
name = "GALLERY"
date_upload = getDate(element.select("time").text())
}
override fun chapterListSelector() = "html"
// Pages
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException()
override fun latestUpdatesFromElement(element: Element): SManga {
throw UnsupportedOperationException()
}
override fun latestUpdatesNextPageSelector(): String? {
throw UnsupportedOperationException()
}
override fun latestUpdatesRequest(page: Int): Request {
throw UnsupportedOperationException()
}
override fun latestUpdatesSelector(): String {
throw UnsupportedOperationException()
}
private fun getDate(str: String): Long {
return try {
DATE_FORMAT.parse(str)?.time ?: 0L
} catch (e: ParseException) {
0L
}
}
companion object {
private val DATE_FORMAT by lazy {
SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH)
}
}
}

View File

@ -1,7 +1,9 @@
ext {
extName = 'AsmHentai'
extClass = '.ASMHFactory'
extVersionCode = 1
themePkg = 'galleryadults'
baseUrl = 'https://asmhentai.com'
overrideVersionCode = 2
isNsfw = true
}

View File

@ -1,13 +1,14 @@
package eu.kanade.tachiyomi.extension.all.asmhentai
import eu.kanade.tachiyomi.multisrc.galleryadults.GalleryAdults
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceFactory
class ASMHFactory : SourceFactory {
override fun createSources(): List<Source> = listOf(
AsmHentai("en", "english"),
AsmHentai("ja", "japanese"),
AsmHentai("zh", "chinese"),
AsmHentai("all", ""),
AsmHentai("en", GalleryAdults.LANGUAGE_ENGLISH),
AsmHentai("ja", GalleryAdults.LANGUAGE_JAPANESE),
AsmHentai("zh", GalleryAdults.LANGUAGE_CHINESE),
AsmHentai("all", GalleryAdults.LANGUAGE_MULTI),
)
}

View File

@ -1,274 +1,103 @@
package eu.kanade.tachiyomi.extension.all.asmhentai
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.multisrc.galleryadults.GalleryAdults
import eu.kanade.tachiyomi.multisrc.galleryadults.Genre
import eu.kanade.tachiyomi.multisrc.galleryadults.imgAttr
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.model.UpdateStrategy
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.FormBody
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import rx.Observable
open class AsmHentai(override val lang: String, private val tlTag: String) : ParsedHttpSource() {
class AsmHentai(
lang: String = "all",
override val mangaLang: String = LANGUAGE_MULTI,
) : GalleryAdults(
"AsmHentai",
"https://asmhentai.com",
lang = lang,
) {
override val supportsLatest = mangaLang.isNotBlank()
override val client: OkHttpClient = network.cloudflareClient
override fun Element.mangaUrl() =
selectFirst(".image a")?.attr("abs:href")
override val baseUrl = "https://asmhentai.com"
override fun Element.mangaThumbnail() =
selectFirst(".image img")?.imgAttr()
override val name = "AsmHentai"
override fun Element.mangaLang() =
select("a:has(.flag)").attr("href")
.removeSuffix("/").substringAfterLast("/")
override val supportsLatest = false
override fun popularMangaSelector() = ".preview_item"
// Popular
override val favoritePath = "inc/user.php?act=favs"
override fun popularMangaRequest(page: Int): Request {
val url = baseUrl.toHttpUrl().newBuilder().apply {
if (tlTag.isNotEmpty()) addPathSegments("language/$tlTag/")
if (page > 1) addQueryParameter("page", page.toString())
}
return GET(url.build(), headers)
}
override fun popularMangaSelector(): String = ".preview_item"
private fun Element.mangaTitle() = select("h2").text()
private fun Element.mangaUrl() = select(".image a").attr("abs:href")
private fun Element.mangaThumbnail() = select(".image img").attr("abs:src")
override fun popularMangaFromElement(element: Element): SManga {
return SManga.create().apply {
title = element.mangaTitle()
setUrlWithoutDomain(element.mangaUrl())
thumbnail_url = element.mangaThumbnail()
}
}
override fun popularMangaNextPageSelector(): String = "li.active + li:not(.disabled)"
// Latest
override fun latestUpdatesNextPageSelector(): String? {
throw UnsupportedOperationException()
}
override fun latestUpdatesRequest(page: Int): Request {
throw UnsupportedOperationException()
}
override fun latestUpdatesFromElement(element: Element): SManga {
throw UnsupportedOperationException()
}
override fun latestUpdatesSelector(): String {
throw UnsupportedOperationException()
}
// Search
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
return when {
query.startsWith(PREFIX_ID_SEARCH) -> {
val id = query.removePrefix(PREFIX_ID_SEARCH)
client.newCall(searchMangaByIdRequest(id))
.asObservableSuccess()
.map { response -> searchMangaByIdParse(response, id) }
override fun Element.getInfo(tag: String): String {
return select(".tags:contains($tag:) .tag_list a")
.joinToString {
val name = it.selectFirst(".tag")?.ownText() ?: ""
if (tag.contains(regexTag)) {
genres[name] = it.attr("href")
.removeSuffix("/").substringAfterLast('/')
}
listOf(
name,
it.select(".split_tag").text()
.removePrefix("| ")
.trim(),
)
.filter { s -> s.isNotBlank() }
.joinToString()
}
query.toIntOrNull() != null -> {
client.newCall(searchMangaByIdRequest(query))
.asObservableSuccess()
.map { response -> searchMangaByIdParse(response, query) }
}
override fun Element.getInfoPages(document: Document?) =
selectFirst(".book_page .pages h3")?.ownText()
override val mangaDetailInfoSelector = ".book_page"
/**
* [totalPagesSelector] only exists if pages > 10
*/
override val totalPagesSelector = "t_pages"
override val galleryIdSelector = "load_id"
override val thumbnailSelector = ".preview_thumb"
override val idPrefixUri = "g"
override val pageUri = "gallery"
override fun pageRequestForm(document: Document, totalPages: String, loadedPages: Int): FormBody {
val token = document.select("[name=csrf-token]").attr("content")
return FormBody.Builder()
.add("id", document.inputIdValueOf(loadIdSelector))
.add("dir", document.inputIdValueOf(loadDirSelector))
.add("visible_pages", loadedPages.toString())
.add("t_pages", totalPages)
.add("type", "2") // 1 would be "more", 2 is "all remaining"
.apply {
if (token.isNotBlank()) add("_token", token)
}
else -> super.fetchSearchManga(page, query, filters)
}
.build()
}
// any space except after a comma (we're going to replace spaces only between words)
private val spaceRegex = Regex("""(?<!,)\s+""")
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val tags = (filters.last() as TagFilter).state
val q = when {
tags.isBlank() -> query
query.isBlank() -> tags
else -> "$query,$tags"
}.replace(spaceRegex, "+")
val url = baseUrl.toHttpUrl().newBuilder().apply {
addPathSegments("search/")
addEncodedQueryParameter("q", q)
if (page > 1) addQueryParameter("page", page.toString())
}
return GET(url.build(), headers)
}
private class SMangaDto(
val title: String,
val url: String,
val thumbnail: String,
val lang: String,
)
override fun searchMangaParse(response: Response): MangasPage {
val doc = response.asJsoup()
val mangas = doc.select(searchMangaSelector())
.map {
SMangaDto(
title = it.mangaTitle(),
url = it.mangaUrl(),
thumbnail = it.mangaThumbnail(),
lang = it.select("a:has(.flag)").attr("href").removeSuffix("/").substringAfterLast("/"),
override fun tagsParser(document: Document): List<Genre> {
return document.select(".tags_page .tags a.tag")
.mapNotNull {
Genre(
it.ownText(),
it.attr("href")
.removeSuffix("/").substringAfterLast('/'),
)
}
.let { unfiltered ->
if (tlTag.isNotEmpty()) unfiltered.filter { it.lang == tlTag } else unfiltered
}
.map {
SManga.create().apply {
title = it.title
setUrlWithoutDomain(it.url)
thumbnail_url = it.thumbnail
}
}
return MangasPage(mangas, doc.select(searchMangaNextPageSelector()).isNotEmpty())
}
private fun searchMangaByIdRequest(id: String) = GET("$baseUrl/g/$id/", headers)
private fun searchMangaByIdParse(response: Response, id: String): MangasPage {
val details = mangaDetailsParse(response)
details.url = "/g/$id/"
return MangasPage(listOf(details), false)
}
override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element)
override fun searchMangaSelector() = popularMangaSelector()
override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
// Details
private fun Element.get(tag: String): String {
return select(".tags:contains($tag) .tag").joinToString { it.ownText() }
}
override fun mangaDetailsParse(document: Document): SManga {
return SManga.create().apply {
update_strategy = UpdateStrategy.ONLY_FETCH_ONCE
document.select(".book_page").first()!!.let { element ->
thumbnail_url = element.select(".cover img").attr("abs:src")
title = element.select("h1").text()
genre = element.get("Tags")
artist = element.get("Artists")
author = artist
description = listOf("Parodies", "Groups", "Languages", "Category")
.mapNotNull { tag ->
element.get(tag).let { if (it.isNotEmpty()) "$tag: $it" else null }
}
.joinToString("\n", postfix = "\n") +
element.select(".pages h3").text() +
element.select("h1 + h2").text()
.let { altTitle -> if (altTitle.isNotEmpty()) "\nAlternate Title: $altTitle" else "" }
}
}
}
// Chapters
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
return Observable.just(
listOf(
SChapter.create().apply {
name = "Chapter"
url = manga.url
},
),
)
}
override fun chapterListSelector(): String {
throw UnsupportedOperationException()
}
override fun chapterFromElement(element: Element): SChapter {
throw UnsupportedOperationException()
}
// Pages
// convert thumbnail URLs to full image URLs
private fun String.full(): String {
val fType = substringAfterLast("t")
return replace("t$fType", fType)
}
private fun Document.inputIdValueOf(string: String): String {
return select("input[id=$string]").attr("value")
}
override fun pageListParse(document: Document): List<Page> {
val thumbUrls = document.select(".preview_thumb img")
.map { it.attr("abs:data-src") }
.toMutableList()
// input only exists if pages > 10 and have to make a request to get the other thumbnails
val totalPages = document.inputIdValueOf("t_pages")
if (totalPages.isNotEmpty()) {
val token = document.select("[name=csrf-token]").attr("content")
val form = FormBody.Builder()
.add("_token", token)
.add("id", document.inputIdValueOf("load_id"))
.add("dir", document.inputIdValueOf("load_dir"))
.add("visible_pages", "10")
.add("t_pages", totalPages)
.add("type", "2") // 1 would be "more", 2 is "all remaining"
.build()
val xhrHeaders = headers.newBuilder()
.add("X-Requested-With", "XMLHttpRequest")
.build()
client.newCall(POST("$baseUrl/inc/thumbs_loader.php", xhrHeaders, form))
.execute()
.asJsoup()
.select("img")
.mapTo(thumbUrls) { it.attr("abs:data-src") }
}
return thumbUrls.mapIndexed { i, url -> Page(i, "", url.full()) }
}
override fun imageUrlParse(document: Document) = throw UnsupportedOperationException()
// Filters
override fun getFilterList(): FilterList = FilterList(
Filter.Header("Separate tags with commas (,)"),
TagFilter(),
override fun getFilterList() = FilterList(
listOf(
Filter.Header("HINT: Separate search term with comma (,)"),
Filter.Header("String query search doesn't support Sort"),
) + super.getFilterList().list,
)
class TagFilter : Filter.Text("Tags")
companion object {
const val PREFIX_ID_SEARCH = "id:"
}
}

View File

@ -47,6 +47,9 @@
<data
android:pathPattern="/subject-overview/..*"
android:scheme="https" />
<data
android:pathPattern="/title/..*"
android:scheme="https" />
</intent-filter>
</activity>
</application>

View File

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

View File

@ -42,8 +42,25 @@ class BatoToUrlActivity : Activity() {
private fun fromBatoTo(pathSegments: MutableList<String>): String? {
return if (pathSegments.size >= 2) {
val id = pathSegments[1]
"ID:$id"
val path = pathSegments[1] as java.lang.String?
if (path != null) {
var index = -1
for (i in path.indices) {
if (path[i] == '-') {
index = i
break
}
}
val id = if (index == -1) {
path
} else {
path.substring(0, index)
}
"ID:$id"
} else {
null
}
} else {
null
}

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,175 @@
package eu.kanade.tachiyomi.extension.all.beauty3600000
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.Page
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.model.UpdateStrategy
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.TimeZone
class Beauty3600000 : ParsedHttpSource() {
override val baseUrl = "https://3600000.xyz"
override val lang = "all"
override val name = "3600000 Beauty"
override val supportsLatest = false
override fun headersBuilder() = super.headersBuilder()
.add("Referer", "$baseUrl/")
// Latest
override fun latestUpdatesNextPageSelector() = throw UnsupportedOperationException()
override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException()
override fun latestUpdatesFromElement(element: Element) = throw UnsupportedOperationException()
override fun latestUpdatesSelector() = throw UnsupportedOperationException()
// Popular
override fun popularMangaFromElement(element: Element) = SManga.create().apply {
thumbnail_url = element.select("a img.ls_lazyimg").attr("file")
title = element.select(".entry-title").text()
setUrlWithoutDomain(element.select(".entry-title > a").attr("abs:href"))
status = SManga.COMPLETED
}
override fun popularMangaNextPageSelector() = ".next"
override fun popularMangaRequest(page: Int) = GET("$baseUrl/page/$page/", headers)
override fun popularMangaSelector() = "#blog-entries > article"
// Search
override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element)
override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val filterList = if (filters.isEmpty()) getFilterList() else filters
val tagFilter = filterList.findInstance<TagFilter>()!!
val categoryFilter = filterList.findInstance<CategoryFilter>()!!
var searchQuery = query
val searchPath: String = when {
tagFilter.state.isNotEmpty() -> "$baseUrl/tag/${tagFilter.state}/page/$page/"
categoryFilter.state != 0 -> "$baseUrl/category/${categoryFilter.toUriPart()}/page/$page/"
query.startsWith("tag:") -> {
tagFilter.state = searchQuery.substringAfter("tag:")
searchQuery = ""
"$baseUrl/tag/${tagFilter.state}/page/$page/"
}
else -> "$baseUrl/page/$page/"
}
return when {
searchQuery.isNotEmpty() -> GET(
searchPath.toHttpUrl().newBuilder().apply {
addQueryParameter("s", searchQuery)
}.build(),
headers,
)
else -> GET(searchPath, headers)
}
}
override fun searchMangaSelector() = popularMangaSelector()
// Details
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
val main = document.selectFirst("#main")!!
title = main.select(".entry-title").text()
description = main.select(".entry-title").text()
genre = getGenres(document).joinToString(", ")
thumbnail_url = main.select(".entry-content img.ls_lazyimg").attr("file")
status = SManga.COMPLETED
update_strategy = UpdateStrategy.ONLY_FETCH_ONCE
}
private fun getGenres(element: Element): List<String> {
val genres = mutableListOf<String>()
element.select(".cat-links a").forEach {
genres.add(it.text())
}
element.select(".tags-links a").forEach {
val tag = it.attr("href").toHttpUrl().pathSegments[1]
genres.add("tag:$tag")
}
return genres
}
override fun chapterFromElement(element: Element) = SChapter.create().apply {
setUrlWithoutDomain(element.select("link[rel=\"shortlink\"]").attr("href"))
name = "Gallery"
date_upload = getDate(element.select("#main time").attr("datetime"))
}
override fun chapterListSelector() = "html"
// Pages
override fun pageListParse(document: Document): List<Page> {
val pages = mutableListOf<Page>()
document.select("noscript").remove()
document.select(".entry-content img").forEachIndexed { i, it ->
val itUrl = it.select("img.ls_lazyimg").attr("file")
pages.add(Page(i, imageUrl = itUrl))
}
return pages
}
override fun imageUrlParse(document: Document): String =
throw UnsupportedOperationException()
// Filters
override fun getFilterList(): FilterList = FilterList(
Filter.Header("NOTE: Only one filter will be applied!"),
Filter.Separator(),
CategoryFilter(),
TagFilter(),
)
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"),
Pair("Aidol", "aidol"),
Pair("Magazine", "magazine"),
Pair("Korea", "korea"),
Pair("Thailand", "thailand"),
Pair("Chinese", "chinese"),
Pair("Japan", "japan"),
Pair("China", "china"),
Pair("Uncategorized", "uncategorized"),
Pair("Magazine", "magazine"),
Pair("Photobook", "photobook"),
Pair("Western", "western"),
),
)
class TagFilter : Filter.Text("Tag")
private inline fun <reified T> Iterable<*>.findInstance() = find { it is T } as? T
private fun getDate(str: String): Long {
return try {
DATE_FORMAT.parse(str).time
} catch (e: ParseException) {
0L
}
}
companion object {
private val DATE_FORMAT by lazy {
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss+SSS", Locale.US)
.apply { timeZone = TimeZone.getTimeZone("UTC") }
}
}
}

View File

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

View File

@ -21,11 +21,17 @@ import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Interceptor
import okhttp3.Request
import okhttp3.Response
import rx.Observable
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.TimeZone
import java.util.concurrent.TimeUnit
import kotlin.math.min
abstract class Comick(
@ -155,9 +161,31 @@ abstract class Comick(
}
override val client = network.client.newBuilder()
.rateLimit(3, 1)
.addNetworkInterceptor(::errorInterceptor)
.rateLimit(3, 1, TimeUnit.SECONDS)
.build()
private fun errorInterceptor(chain: Interceptor.Chain): Response {
val response = chain.proceed(chain.request())
if (
response.isSuccessful ||
"application/json" !in response.header("Content-Type").orEmpty()
) {
return response
}
val error = try {
response.parseAs<Error>()
} catch (_: Exception) {
null
}
error?.run {
throw Exception("$name error $statusCode: $message")
} ?: throw Exception("HTTP error ${response.code}")
}
/** Popular Manga **/
override fun popularMangaRequest(page: Int): Request {
val url = "$apiUrl/v1.0/search?sort=follow&limit=$LIMIT&page=$page&tachiyomi=true"
@ -301,7 +329,7 @@ abstract class Comick(
is TagFilter -> {
if (it.state.isNotEmpty()) {
it.state.split(",").forEach {
addQueryParameter("tags", it.trim())
addQueryParameter("tags", it.trim().lowercase().replace(SPACE_AND_SLASH_REGEX, "-").replace("'-", "-and-039-").replace("'", "-and-039-"))
}
}
}
@ -398,13 +426,31 @@ abstract class Comick(
.substringBefore("/chapters")
.substringAfter(apiUrl)
val currentTimestamp = System.currentTimeMillis()
return chapterListResponse.chapters
.filter {
it.groups.map { g -> g.lowercase() }.intersect(preferences.ignoredGroups).isEmpty()
val publishTime = try {
publishedDateFormat.parse(it.publishedAt)!!.time
} catch (_: ParseException) {
0L
}
val publishedChapter = publishTime <= currentTimestamp
val noGroupBlock = it.groups.map { g -> g.lowercase() }
.intersect(preferences.ignoredGroups)
.isEmpty()
publishedChapter && noGroupBlock
}
.map { it.toSChapter(mangaUrl) }
}
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}"
}
@ -453,6 +499,7 @@ abstract class Comick(
companion object {
const val SLUG_SEARCH_PREFIX = "id:"
private val SPACE_AND_SLASH_REGEX = Regex("[ /]")
private const val IGNORED_GROUPS_PREF = "IgnoredGroups"
private const val INCLUDE_MU_TAGS_PREF = "IncludeMangaUpdatesTags"
private const val INCLUDE_MU_TAGS_DEFAULT = false

View File

@ -170,7 +170,8 @@ class Chapter(
private val hid: String,
private val lang: String = "",
private val title: String = "",
@SerialName("created_at") val createdAt: String = "",
@SerialName("created_at") private val createdAt: String = "",
@SerialName("publish_at") val publishedAt: String = "",
private val chap: String = "",
private val vol: String = "",
@SerialName("group_name") val groups: List<String> = emptyList(),
@ -197,3 +198,9 @@ class ChapterPageData(
class Page(
val url: String? = null,
)
@Serializable
class Error(
val statusCode: Int,
val message: String,
)

View File

@ -7,7 +7,7 @@ import java.util.Locale
import java.util.TimeZone
private val dateFormat by lazy {
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ENGLISH).apply {
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX", Locale.ENGLISH).apply {
timeZone = TimeZone.getTimeZone("UTC")
}
}

View File

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

View File

@ -47,54 +47,52 @@ class Danbooru : ParsedHttpSource() {
override fun popularMangaSelector(): String =
searchMangaSelector()
override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = Request(
url = "$baseUrl/pools/gallery".toHttpUrl().newBuilder().run {
setEncodedQueryParameter("search[category]", "series")
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val url = "$baseUrl/pools/gallery".toHttpUrl().newBuilder()
filters.forEach {
when (it) {
is FilterTags -> if (it.state.isNotBlank()) {
addQueryParameter("search[post_tags_match]", it.state)
}
url.setEncodedQueryParameter("search[category]", "series")
is FilterDescription -> if (it.state.isNotBlank()) {
addQueryParameter("search[description_matches]", it.state)
}
is FilterIsDeleted -> if (it.state) {
addEncodedQueryParameter("search[is_deleted]", "true")
}
is FilterCategory -> {
setEncodedQueryParameter("search[category]", it.selected)
}
is FilterOrder -> if (it.selected != null) {
addEncodedQueryParameter("search[order]", it.selected)
}
else -> throw IllegalStateException("Unrecognized filter")
filters.forEach {
when (it) {
is FilterTags -> if (it.state.isNotBlank()) {
url.addQueryParameter("search[post_tags_match]", it.state)
}
is FilterDescription -> if (it.state.isNotBlank()) {
url.addQueryParameter("search[description_matches]", it.state)
}
is FilterIsDeleted -> if (it.state) {
url.addEncodedQueryParameter("search[is_deleted]", "true")
}
is FilterCategory -> {
url.setEncodedQueryParameter("search[category]", it.selected)
}
is FilterOrder -> if (it.selected != null) {
url.addEncodedQueryParameter("search[order]", it.selected)
}
else -> throw IllegalStateException("Unrecognized filter")
}
}
addEncodedQueryParameter("page", page.toString())
url.addEncodedQueryParameter("page", page.toString())
if (query.isNotBlank()) {
addQueryParameter("search[name_contains]", query)
}
if (query.isNotBlank()) {
url.addQueryParameter("search[name_contains]", query)
}
build()
},
headers = headers,
)
return GET(url.build(), headers)
}
override fun searchMangaSelector(): String =
".post-preview"
"article.post-preview"
override fun searchMangaFromElement(element: Element) = SManga.create().apply {
url = element.selectFirst(".post-preview-link")?.attr("href")!!
title = element.selectFirst(".desc")?.text() ?: ""
title = element.selectFirst("div.text-center")?.text() ?: ""
thumbnail_url = element.selectFirst("source")?.attr("srcset")
?.substringAfterLast(',')?.trim()

View File

@ -1,6 +1,6 @@
ext {
extName = 'EternalMangas'
extClass = '.EternalMangas'
extClass = '.EternalMangasFactory'
themePkg = 'mangaesp'
baseUrl = 'https://eternalmangas.com'
overrideVersionCode = 0

View File

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View File

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View File

@ -0,0 +1,82 @@
package eu.kanade.tachiyomi.extension.all.eternalmangas
import eu.kanade.tachiyomi.multisrc.mangaesp.MangaEsp
import eu.kanade.tachiyomi.multisrc.mangaesp.SeriesDto
import eu.kanade.tachiyomi.multisrc.mangaesp.TopSeriesDto
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.util.asJsoup
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import okhttp3.FormBody
import okhttp3.Response
open class EternalMangas(
lang: String,
private val internalLang: String,
) : MangaEsp(
"EternalMangas",
"https://eternalmangas.com",
lang,
) {
override fun popularMangaParse(response: Response): MangasPage {
val body = response.body.string()
val responseData = json.decodeFromString<TopSeriesDto>(body)
val topDaily = responseData.response.topDaily.flatten().map { it.data }
val topWeekly = responseData.response.topWeekly.flatten().map { it.data }
val topMonthly = responseData.response.topMonthly.flatten().map { it.data }
val mangas = (topDaily + topWeekly + topMonthly).distinctBy { it.slug }
.filter { it.language == internalLang }
.map { it.toSManga() }
return MangasPage(mangas, false)
}
override fun latestUpdatesParse(response: Response): MangasPage {
val responseData = json.decodeFromString<LatestUpdatesDto>(response.body.string())
val mangas = responseData.updates[internalLang]?.flatten()?.map { it.toSManga() } ?: emptyList()
return MangasPage(mangas, false)
}
override fun searchMangaParse(response: Response, page: Int, query: String, filters: FilterList): MangasPage {
val document = response.asJsoup()
val script = document.select("script:containsData(self.__next_f.push)").joinToString { it.data() }
val jsonString = MANGA_LIST_REGEX.find(script)?.groupValues?.get(1)
?: throw Exception(intl["comics_list_error"])
val unescapedJson = jsonString.unescape()
comicsList = json.decodeFromString<List<SeriesDto>>(unescapedJson)
.filter { it.language == internalLang }
.toMutableList()
return parseComicsList(page, query, filters)
}
override fun pageListParse(response: Response): List<Page> {
var document = response.asJsoup()
document.selectFirst("body > form[method=post]")?.let {
val action = it.attr("action")
val inputs = it.select("input")
val form = FormBody.Builder()
inputs.forEach { input ->
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())
}
}
@Serializable
class LatestUpdatesDto(
val updates: Map<String, List<List<SeriesDto>>>,
)
}

View File

@ -0,0 +1,15 @@
package eu.kanade.tachiyomi.extension.all.eternalmangas
import eu.kanade.tachiyomi.source.SourceFactory
class EternalMangasFactory : SourceFactory {
override fun createSources() = listOf(
EternalMangasES(),
EternalMangasEN(),
EternalMangasPTBR(),
)
}
class EternalMangasES : EternalMangas("es", "es")
class EternalMangasEN : EternalMangas("en", "en")
class EternalMangasPTBR : EternalMangas("pt-BR", "pt")

View File

@ -0,0 +1,10 @@
ext {
extName = 'HentaiFox'
extClass = '.HentaiFoxFactory'
themePkg = 'galleryadults'
baseUrl = 'https://hentaifox.com'
overrideVersionCode = 6
isNsfw = true
}
apply from: "$rootDir/common.gradle"

View File

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -0,0 +1,103 @@
package eu.kanade.tachiyomi.extension.all.hentaifox
import eu.kanade.tachiyomi.multisrc.galleryadults.GalleryAdults
import eu.kanade.tachiyomi.multisrc.galleryadults.toDate
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList
import okhttp3.HttpUrl
import org.jsoup.nodes.Element
class HentaiFox(
lang: String = "all",
override val mangaLang: String = LANGUAGE_MULTI,
) : GalleryAdults(
"HentaiFox",
"https://hentaifox.com",
lang = lang,
) {
override val supportsLatest = mangaLang.isNotBlank()
private val languages: List<Pair<String, String>> = listOf(
Pair(LANGUAGE_ENGLISH, "1"),
Pair(LANGUAGE_TRANSLATED, "2"),
Pair(LANGUAGE_JAPANESE, "5"),
Pair(LANGUAGE_CHINESE, "6"),
Pair(LANGUAGE_KOREAN, "11"),
)
private val langCode = languages.firstOrNull { lang -> lang.first == mangaLang }?.second
override fun Element.mangaLang() = attr("data-languages")
.split(' ').let {
when {
it.contains(langCode) -> mangaLang
// search result doesn't have "data-languages" which will return a list with 1 blank element
it.size > 1 || (it.size == 1 && it.first().isNotBlank()) -> "other"
// if we don't know which language to filter then set to mangaLang to not filter at all
else -> mangaLang
}
}
override val useShortTitlePreference = false
override fun Element.mangaTitle(selector: String): String? = mangaFullTitle(selector)
override fun Element.getInfo(tag: String): String {
return select("ul.${tag.lowercase()} a")
.joinToString {
val name = it.ownText()
if (tag.contains(regexTag)) {
genres[name] = it.attr("href")
.removeSuffix("/").substringAfterLast('/')
}
listOf(
name,
it.select(".split_tag").text()
.removePrefix("| ")
.trim(),
)
.filter { s -> s.isNotBlank() }
.joinToString()
}
}
override fun Element.getTime(): Long =
selectFirst(".pages:contains(Posted:)")?.ownText()
?.removePrefix("Posted: ")
.toDate(simpleDateFormat)
override fun HttpUrl.Builder.addPageUri(page: Int): HttpUrl.Builder {
val url = toString()
when {
url == "$baseUrl/" && page == 2 ->
addPathSegments("page/$page")
url.contains('?') ->
addQueryParameter("page", page.toString())
else ->
addPathSegments("pag/$page")
}
addPathSegment("") // trailing slash (/)
return this
}
/**
* Convert space( ) typed in search-box into plus(+) in URL. Then:
* - ignore the word preceding by a special character (e.g. 'school-girl' will ignore 'girl')
* => replace to plus(+),
* - use plus(+) for separate terms, as AND condition.
* - use double quote(") to search for exact match.
*/
override fun buildQueryString(tags: List<String>, query: String): String {
val regexSpecialCharacters = Regex("""[^a-zA-Z0-9"]+(?=[a-zA-Z0-9"])""")
return (tags + query + mangaLang).filterNot { it.isBlank() }.joinToString("+") {
it.trim().replace(regexSpecialCharacters, "+")
}
}
override val favoritePath = "includes/user_favs.php"
override val pagesRequest = "includes/thumbs_loader.php"
override fun getFilterList() = FilterList(
listOf(
Filter.Header("HINT: Use double quote (\") for exact match"),
) + super.getFilterList().list,
)
}

View File

@ -0,0 +1,15 @@
package eu.kanade.tachiyomi.extension.all.hentaifox
import eu.kanade.tachiyomi.multisrc.galleryadults.GalleryAdults
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceFactory
class HentaiFoxFactory : SourceFactory {
override fun createSources(): List<Source> = listOf(
HentaiFox("en", GalleryAdults.LANGUAGE_ENGLISH),
HentaiFox("ja", GalleryAdults.LANGUAGE_JAPANESE),
HentaiFox("zh", GalleryAdults.LANGUAGE_CHINESE),
HentaiFox("ko", GalleryAdults.LANGUAGE_KOREAN),
HentaiFox("all", GalleryAdults.LANGUAGE_MULTI),
)
}

View File

@ -1,7 +1,7 @@
ext {
extName = 'Hitomi'
extClass = '.HitomiFactory'
extVersionCode = 26
extVersionCode = 28
isNsfw = true
}

View File

@ -0,0 +1,81 @@
package eu.kanade.tachiyomi.extension.all.hitomi
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList
typealias OrderType = Pair<String?, String>
typealias ParsedFilter = Pair<String, OrderType>
private fun parseFilter(query: StringBuilder, area: String, filterState: String) {
filterState
.trim()
.split(',')
.filter { it.isNotBlank() }
.forEach {
val trimmed = it.trim()
val negativePrefix = if (trimmed.startsWith("-")) "-" else ""
query.append(" $negativePrefix$area:${trimmed.removePrefix("-").replace(" ", "_")}")
}
}
fun parseFilters(filters: FilterList): ParsedFilter {
val query = StringBuilder()
var order: OrderType = Pair("date", "added")
filters.forEach { filter ->
when (filter) {
is SortFilter -> {
order = filter.getOrder
}
is AreaFilter -> {
parseFilter(query, filter.getAreaName, filter.state)
}
else -> { /* Do Nothing */ }
}
}
return Pair(query.toString(), order)
}
private class OrderFilter(val name: String, val order: OrderType) {
val getFilterName: String
get() = name
val getOrder: OrderType
get() = order
}
private class SortFilter : UriPartFilter(
"Sort By",
arrayOf(
OrderFilter("Date Added", Pair(null, "index")),
OrderFilter("Date Published", Pair("date", "published")),
OrderFilter("Popular: Today", Pair("popular", "today")),
OrderFilter("Popular: Week", Pair("popular", "week")),
OrderFilter("Popular: Month", Pair("popular", "month")),
OrderFilter("Popular: Year", Pair("popular", "year")),
),
)
private open class UriPartFilter(displayName: String, val vals: Array<OrderFilter>) :
Filter.Select<String>(displayName, vals.map { it.getFilterName }.toTypedArray()) {
val getOrder: OrderType
get() = vals[state].getOrder
}
private class AreaFilter(displayName: String, val areaName: String) :
Filter.Text(displayName) {
val getAreaName: String
get() = areaName
}
fun getFilterListInternal(): FilterList = FilterList(
SortFilter(),
Filter.Header("Separate tags with commas (,)"),
Filter.Header("Prepend with dash (-) to exclude"),
AreaFilter("Artist(s)", "artist"),
AreaFilter("Character(s)", "character"),
AreaFilter("Group(s)", "group"),
AreaFilter("Series", "series"),
AreaFilter("Female Tag(s)", "female"),
AreaFilter("Male Tag(s)", "male"),
Filter.Header("Don't put Female/Male tags here, they won't work!"),
AreaFilter("Tag(s)", "tag"),
)

View File

@ -1,8 +1,12 @@
package eu.kanade.tachiyomi.extension.all.hitomi
import android.app.Application
import android.content.SharedPreferences
import androidx.preference.PreferenceScreen
import androidx.preference.SwitchPreferenceCompat
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.ConfigurableSource
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.Page
@ -22,6 +26,8 @@ import okhttp3.Call
import okhttp3.Request
import okhttp3.Response
import rx.Observable
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.nio.ByteBuffer
import java.nio.ByteOrder
@ -35,7 +41,7 @@ import kotlin.math.min
class Hitomi(
override val lang: String,
private val nozomiLang: String,
) : HttpSource() {
) : ConfigurableSource, HttpSource() {
override val name = "Hitomi"
@ -51,6 +57,12 @@ class Hitomi(
override val client = network.cloudflareClient
private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
private var iconified = preferences.getBoolean(PREF_TAG_GENDER_ICON, false)
override fun headersBuilder() = super.headersBuilder()
.set("referer", "$baseUrl/")
.set("origin", baseUrl)
@ -76,11 +88,13 @@ class Hitomi(
private lateinit var searchResponse: List<Int>
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> = Observable.fromCallable {
val parsedFilter = parseFilters(filters)
runBlocking {
if (page == 1) {
searchResponse = hitomiSearch(
query.trim(),
filters.filterIsInstance<SortFilter>().firstOrNull()?.state == 1,
"$query${parsedFilter.first}".trim(),
parsedFilter.second,
nozomiLang,
).toList()
}
@ -93,11 +107,7 @@ class Hitomi(
}
}
private class SortFilter : Filter.Select<String>("Sort By", arrayOf("Updated", "Popularity"))
override fun getFilterList(): FilterList {
return FilterList(SortFilter())
}
override fun getFilterList(): FilterList = getFilterListInternal()
private fun Int.nextPageRange(): LongRange {
val byteOffset = ((this - 1) * 25) * 4L
@ -117,7 +127,7 @@ class Hitomi(
private suspend fun hitomiSearch(
query: String,
sortByPopularity: Boolean = false,
order: OrderType,
language: String = "all",
): Set<Int> =
coroutineScope {
@ -126,9 +136,6 @@ class Hitomi(
.replace(Regex("""^\?"""), "")
.lowercase()
.split(Regex("\\s+"))
.map {
it.replace('_', ' ')
}
val positiveTerms = LinkedList<String>()
val negativeTerms = LinkedList<String>()
@ -144,7 +151,7 @@ class Hitomi(
val positiveResults = positiveTerms.map {
async {
runCatching {
getGalleryIDsForQuery(it, language)
getGalleryIDsForQuery(it, language, order)
}.getOrDefault(emptySet())
}
}
@ -152,14 +159,13 @@ class Hitomi(
val negativeResults = negativeTerms.map {
async {
runCatching {
getGalleryIDsForQuery(it, language)
getGalleryIDsForQuery(it, language, order)
}.getOrDefault(emptySet())
}
}
val results = when {
sortByPopularity -> getGalleryIDsFromNozomi(null, "popular", language)
positiveTerms.isEmpty() -> getGalleryIDsFromNozomi(null, "index", language)
positiveTerms.isEmpty() -> getGalleryIDsFromNozomi(order.first, order.second, language)
else -> emptySet()
}.toMutableSet()
@ -191,6 +197,7 @@ class Hitomi(
private suspend fun getGalleryIDsForQuery(
query: String,
language: String = "all",
order: OrderType,
): Set<Int> {
query.replace("_", " ").let {
if (it.indexOf(':') > -1) {
@ -213,6 +220,20 @@ class Hitomi(
}
}
if (area != null) {
if (order.first != null) {
area = "$area/${order.first}"
if (tag.isBlank()) {
tag = order.second
} else {
area = "$area/${order.second}"
}
}
} else {
area = order.first
tag = order.second
}
return getGalleryIDsFromNozomi(area, tag, lang)
}
@ -429,7 +450,7 @@ class Hitomi(
url = galleryurl
author = groups?.joinToString { it.formatted }
artist = artists?.joinToString { it.formatted }
genre = tags?.joinToString { it.formatted }
genre = tags?.joinToString { it.getFormatted(iconified) }
thumbnail_url = files.first().let {
val hash = it.hash
val imageId = imageIdFromHash(hash)
@ -444,7 +465,8 @@ class Hitomi(
parodys?.joinToString { it.formatted }?.let {
append("Parodies: ", it, "\n")
}
append("Pages: ", files.size)
append("Pages: ", files.size, "\n")
append("Language: ", language)
}
status = SManga.COMPLETED
update_strategy = UpdateStrategy.ONLY_FETCH_ONCE
@ -598,9 +620,28 @@ class Hitomi(
override fun popularMangaParse(response: Response) = throw UnsupportedOperationException()
override fun popularMangaRequest(page: Int) = throw UnsupportedOperationException()
override fun setupPreferenceScreen(screen: PreferenceScreen) {
SwitchPreferenceCompat(screen.context).apply {
key = PREF_TAG_GENDER_ICON
title = "Show gender as text or icon in tags (requires refresh)"
summaryOff = "Show gender as text"
summaryOn = "Show gender as icon"
setOnPreferenceChangeListener { _, newValue ->
iconified = newValue == true
true
}
}.also(screen::addPreference)
}
override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException()
override fun latestUpdatesParse(response: Response) = throw UnsupportedOperationException()
override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = throw UnsupportedOperationException()
override fun searchMangaParse(response: Response) = throw UnsupportedOperationException()
override fun imageUrlParse(response: Response) = throw UnsupportedOperationException()
companion object {
private const val PREF_TAG_GENDER_ICON = "pref_tag_gender_icon"
}
}

View File

@ -9,6 +9,7 @@ data class Gallery(
val title: String,
val date: String,
val type: String,
val language: String,
val tags: List<Tag>?,
val artists: List<Artist>?,
val groups: List<Group>?,
@ -28,10 +29,10 @@ data class Tag(
val male: JsonPrimitive?,
val tag: String,
) {
val formatted get() = if (female?.content == "1") {
"${tag.toCamelCase()} (Female)"
fun getFormatted(iconified: Boolean) = if (female?.content == "1") {
tag.toCamelCase() + if (iconified) "" else " (Female)"
} else if (male?.content == "1") {
"${tag.toCamelCase()} (Male)"
tag.toCamelCase() + if (iconified) "" else " (Male)"
} else {
tag.toCamelCase()
}

View File

@ -1,7 +1,9 @@
ext {
extName = 'IMHentai'
extClass = '.IMHentaiFactory'
extVersionCode = 14
themePkg = 'galleryadults'
baseUrl = 'https://imhentai.xxx'
overrideVersionCode = 15
isNsfw = true
}

View File

@ -1,33 +1,33 @@
package eu.kanade.tachiyomi.extension.all.imhentai
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.asObservableSuccess
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 kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonObject
import okhttp3.HttpUrl.Companion.toHttpUrl
import eu.kanade.tachiyomi.multisrc.galleryadults.GalleryAdults
import eu.kanade.tachiyomi.multisrc.galleryadults.imgAttr
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import okhttp3.ResponseBody.Companion.toResponseBody
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import org.jsoup.select.Elements
import rx.Observable
import uy.kohesive.injekt.injectLazy
import java.io.IOException
class IMHentai(override val lang: String, private val imhLang: String) : ParsedHttpSource() {
override val baseUrl: String = "https://imhentai.xxx"
override val name: String = "IMHentai"
class IMHentai(
lang: String = "all",
override val mangaLang: String = LANGUAGE_MULTI,
) : GalleryAdults(
"IMHentai",
"https://imhentai.xxx",
lang = lang,
) {
override val supportsLatest = true
override val useIntermediateSearch: Boolean = true
override val supportAdvancedSearch: Boolean = true
override val supportSpeechless: Boolean = true
override fun Element.mangaLang() =
select("a:has(.thumb_flag)").attr("href")
.removeSuffix("/").substringAfterLast("/")
.let {
// Include Speechless in search results
if (it == LANGUAGE_SPEECHLESS) mangaLang else it
}
override val client: OkHttpClient = network.cloudflareClient
.newBuilder()
@ -57,271 +57,32 @@ class IMHentai(override val lang: String, private val imhLang: String) : ParsedH
},
).build()
// Popular
override fun popularMangaFromElement(element: Element): SManga {
return SManga.create().apply {
thumbnail_url = element.selectFirst(".inner_thumb img")?.let {
it.absUrl(if (it.hasAttr("data-src")) "data-src" else "src")
}
with(element.select(".caption a")) {
url = this.attr("href")
title = this.text()
}
}
}
override fun popularMangaNextPageSelector(): String = ".pagination li a:contains(Next):not([tabindex])"
override fun popularMangaSelector(): String = ".thumbs_container .thumb"
override fun popularMangaRequest(page: Int): Request = searchMangaRequest(page, "", getFilterList(SORT_ORDER_POPULAR))
// Latest
override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element)
override fun latestUpdatesNextPageSelector(): String = popularMangaNextPageSelector()
override fun latestUpdatesRequest(page: Int): Request = searchMangaRequest(page, "", getFilterList(SORT_ORDER_LATEST))
override fun latestUpdatesSelector(): String = popularMangaSelector()
// Search
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
if (query.startsWith("id:")) {
val id = query.substringAfter("id:")
return client.newCall(GET("$baseUrl/gallery/$id/"))
.asObservableSuccess()
.map { response ->
val manga = mangaDetailsParse(response)
manga.url = "/gallery/$id/"
MangasPage(listOf(manga), false)
/* Details */
override fun Element.getInfo(tag: String): String {
return select("li:has(.tags_text:contains($tag:)) a.tag")
.joinToString {
val name = it.ownText()
if (tag.contains(regexTag)) {
genres[name] = it.attr("href")
.removeSuffix("/").substringAfterLast('/')
}
}
return super.fetchSearchManga(page, query, filters)
}
override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element)
override fun searchMangaNextPageSelector(): String = popularMangaNextPageSelector()
private fun toBinary(boolean: Boolean) = if (boolean) "1" else "0"
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
if (filters.any { it is LanguageFilters && it.state.any { it.name == LANGUAGE_SPEECHLESS && it.state } }) { // edge case for language = speechless
val url = "$baseUrl/language/speechless/".toHttpUrl().newBuilder()
if ((if (filters.isEmpty()) getFilterList() else filters).filterIsInstance<SortOrderFilter>()[0].state == 0) {
url.addPathSegment("popular")
listOf(
name,
it.select(".split_tag").text()
.trim()
.removePrefix("| "),
)
.filter { s -> s.isNotBlank() }
.joinToString()
}
return GET(url.build())
} else {
val url = "$baseUrl/search".toHttpUrl().newBuilder()
.addQueryParameter("key", query)
.addQueryParameter("page", page.toString())
.addQueryParameter(getLanguageURIByName(imhLang).uri, toBinary(true)) // main language always enabled
(if (filters.isEmpty()) getFilterList() else filters).forEach { filter ->
when (filter) {
is LanguageFilters -> {
filter.state.forEach {
url.addQueryParameter(it.uri, toBinary(it.state))
}
}
is CategoryFilters -> {
filter.state.forEach {
url.addQueryParameter(it.uri, toBinary(it.state))
}
}
is SortOrderFilter -> {
getSortOrderURIs().forEachIndexed { index, pair ->
url.addQueryParameter(pair.second, toBinary(filter.state == index))
}
}
else -> {}
}
}
return GET(url.build())
}
}
override fun searchMangaSelector(): String = popularMangaSelector()
override fun Element.getCover() =
selectFirst(".left_cover img")?.imgAttr()
// Details
override val mangaDetailInfoSelector = ".gallery_first"
private fun Elements.csvText(splitTagSeparator: String = ", "): String {
return this.joinToString {
listOf(
it.ownText(),
it.select(".split_tag").text()
.trim()
.removePrefix("| "),
)
.filter { s -> !s.isNullOrBlank() }
.joinToString(splitTagSeparator)
}
}
override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply {
title = document.selectFirst("div.right_details > h1")!!.text()
thumbnail_url = document.selectFirst("div.left_cover img")?.let {
it.absUrl(if (it.hasAttr("data-src")) "data-src" else "src")
}
val mangaInfoElement = document.select(".galleries_info")
val infoMap = mangaInfoElement.select("li:not(.pages)").associate {
it.select("span.tags_text").text().removeSuffix(":") to it.select(".tag")
}
artist = infoMap["Artists"]?.csvText(" | ")
author = artist
genre = infoMap["Tags"]?.csvText()
status = SManga.COMPLETED
val pages = mangaInfoElement.select("li.pages").text().substringAfter("Pages: ")
val altTitle = document.select(".subtitle").text().ifBlank { null }
description = listOf(
"Parodies",
"Characters",
"Groups",
"Languages",
"Category",
).map { it to infoMap[it]?.csvText() }
.let { listOf(Pair("Alternate Title", altTitle)) + it + listOf(Pair("Pages", pages)) }
.filter { !it.second.isNullOrEmpty() }
.joinToString("\n\n") { "${it.first}:\n${it.second}" }
}
// Chapters
override fun chapterListParse(response: Response): List<SChapter> {
return listOf(
SChapter.create().apply {
setUrlWithoutDomain(response.request.url.toString().replace("gallery", "view") + "1")
name = "Chapter"
chapter_number = 1f
},
)
}
override fun chapterFromElement(element: Element): SChapter = throw UnsupportedOperationException()
override fun chapterListSelector(): String = throw UnsupportedOperationException()
// Pages
private val json: Json by injectLazy()
override fun pageListParse(document: Document): List<Page> {
val imageDir = document.select("#image_dir").`val`()
val galleryId = document.select("#gallery_id").`val`()
val uId = document.select("#u_id").`val`().toInt()
val randomServer = when (uId) {
in 1..274825 -> "m1.imhentai.xxx"
in 274826..403818 -> "m2.imhentai.xxx"
in 403819..527143 -> "m3.imhentai.xxx"
in 527144..632481 -> "m4.imhentai.xxx"
in 632482..816010 -> "m5.imhentai.xxx"
in 816011..970098 -> "m6.imhentai.xxx"
in 970099..1121113 -> "m7.imhentai.xxx"
else -> "m8.imhentai.xxx"
}
val images = json.parseToJsonElement(
document.selectFirst("script:containsData(var g_th)")!!.data()
.substringAfter("$.parseJSON('").substringBefore("');").trim(),
).jsonObject
val pages = mutableListOf<Page>()
for (image in images) {
val iext = image.value.toString().replace("\"", "").split(",")[0]
val iextPr = when (iext) {
"p" -> "png"
"b" -> "bmp"
"g" -> "gif"
else -> "jpg"
}
pages.add(Page(image.key.toInt() - 1, "", "https://$randomServer/$imageDir/$galleryId/${image.key}.$iextPr"))
}
return pages
}
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException()
// Filters
private class SortOrderFilter(sortOrderURIs: List<Pair<String, String>>, state: Int) :
Filter.Select<String>("Sort By", sortOrderURIs.map { it.first }.toTypedArray(), state)
private open class SearchFlagFilter(name: String, val uri: String, state: Boolean = true) : Filter.CheckBox(name, state)
private class LanguageFilter(name: String, uri: String = name) : SearchFlagFilter(name, uri, false)
private class LanguageFilters(flags: List<LanguageFilter>) : Filter.Group<LanguageFilter>("Other Languages", flags)
private class CategoryFilters(flags: List<SearchFlagFilter>) : Filter.Group<SearchFlagFilter>("Categories", flags)
override fun getFilterList() = getFilterList(SORT_ORDER_DEFAULT)
private fun getFilterList(sortOrderState: Int) = FilterList(
SortOrderFilter(getSortOrderURIs(), sortOrderState),
CategoryFilters(getCategoryURIs()),
LanguageFilters(getLanguageURIs().filter { it.name != imhLang }), // exclude main lang
Filter.Header("Speechless language: ignores all filters except \"Popular\" and \"Latest\" in Sorting Filter"),
)
private fun getCategoryURIs() = listOf(
SearchFlagFilter("Manga", "manga"),
SearchFlagFilter("Doujinshi", "doujinshi"),
SearchFlagFilter("Western", "western"),
SearchFlagFilter("Image Set", "imageset"),
SearchFlagFilter("Artist CG", "artistcg"),
SearchFlagFilter("Game CG", "gamecg"),
)
// update sort order indices in companion object if order is changed
private fun getSortOrderURIs() = listOf(
Pair("Popular", "pp"),
Pair("Latest", "lt"),
Pair("Downloads", "dl"),
Pair("Top Rated", "tr"),
)
private fun getLanguageURIs() = listOf(
LanguageFilter(LANGUAGE_ENGLISH, "en"),
LanguageFilter(LANGUAGE_JAPANESE, "jp"),
LanguageFilter(LANGUAGE_SPANISH, "es"),
LanguageFilter(LANGUAGE_FRENCH, "fr"),
LanguageFilter(LANGUAGE_KOREAN, "kr"),
LanguageFilter(LANGUAGE_GERMAN, "de"),
LanguageFilter(LANGUAGE_RUSSIAN, "ru"),
LanguageFilter(LANGUAGE_SPEECHLESS, ""),
)
private fun getLanguageURIByName(name: String): LanguageFilter {
return getLanguageURIs().first { it.name == name }
}
companion object {
// references to sort order indices
private const val SORT_ORDER_POPULAR = 0
private const val SORT_ORDER_LATEST = 1
private const val SORT_ORDER_DEFAULT = SORT_ORDER_POPULAR
// references to be used in factory
const val LANGUAGE_ENGLISH = "English"
const val LANGUAGE_JAPANESE = "Japanese"
const val LANGUAGE_SPANISH = "Spanish"
const val LANGUAGE_FRENCH = "French"
const val LANGUAGE_KOREAN = "Korean"
const val LANGUAGE_GERMAN = "German"
const val LANGUAGE_RUSSIAN = "Russian"
const val LANGUAGE_SPEECHLESS = "Speechless"
}
/* Pages */
override val thumbnailSelector = ".gthumb"
override val pageUri = "view"
}

View File

@ -1,17 +1,19 @@
package eu.kanade.tachiyomi.extension.all.imhentai
import eu.kanade.tachiyomi.multisrc.galleryadults.GalleryAdults
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceFactory
class IMHentaiFactory : SourceFactory {
override fun createSources(): List<Source> = listOf(
IMHentai("en", IMHentai.LANGUAGE_ENGLISH),
IMHentai("ja", IMHentai.LANGUAGE_JAPANESE),
IMHentai("es", IMHentai.LANGUAGE_SPANISH),
IMHentai("fr", IMHentai.LANGUAGE_FRENCH),
IMHentai("ko", IMHentai.LANGUAGE_KOREAN),
IMHentai("de", IMHentai.LANGUAGE_GERMAN),
IMHentai("ru", IMHentai.LANGUAGE_RUSSIAN),
IMHentai("en", GalleryAdults.LANGUAGE_ENGLISH),
IMHentai("ja", GalleryAdults.LANGUAGE_JAPANESE),
IMHentai("es", GalleryAdults.LANGUAGE_SPANISH),
IMHentai("fr", GalleryAdults.LANGUAGE_FRENCH),
IMHentai("ko", GalleryAdults.LANGUAGE_KOREAN),
IMHentai("de", GalleryAdults.LANGUAGE_GERMAN),
IMHentai("ru", GalleryAdults.LANGUAGE_RUSSIAN),
IMHentai("all", GalleryAdults.LANGUAGE_MULTI),
)
}

View File

@ -0,0 +1,9 @@
ext {
extName = 'Manga18Me'
extClass = '.M18MFactory'
extVersionCode = 1
isNsfw = true
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,84 @@
package eu.kanade.tachiyomi.extension.all.manga18me
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList
fun getFilters(): FilterList {
return FilterList(
Filter.Header(name = "The filter is ignored when using text search."),
GenreFilter("Genre", getGenresList),
SortFilter("Sort", getSortsList),
RawFilter("Raw"),
CompletedFilter("Completed"),
)
}
/** Filters **/
internal class GenreFilter(name: String, genreList: List<Pair<String, String>>, state: Int = 0) :
SelectFilter(name, genreList, state)
internal class SortFilter(name: String, sortList: List<Pair<String, String>>, state: Int = 0) :
SelectFilter(name, sortList, state)
internal class CompletedFilter(name: String) : CheckBoxFilter(name)
internal class RawFilter(name: String) : CheckBoxFilter(name)
internal open class CheckBoxFilter(name: String, val value: String = "") : Filter.CheckBox(name)
internal open class SelectFilter(name: String, private val vals: List<Pair<String, String>>, state: Int = 0) :
Filter.Select<String>(name, vals.map { it.first }.toTypedArray(), state) {
fun getValue() = vals[state].second
}
/** Filters Data **/
private val getGenresList: List<Pair<String, String>> = listOf(
Pair("Manga", "manga"),
Pair("Drama", "drama"),
Pair("Mature", "mature"),
Pair("Romance", "romance"),
Pair("Adult", "adult"),
Pair("Hentai", "hentai"),
Pair("Comedy", "comedy"),
Pair("Ecchi", "ecchi"),
Pair("School Life", "school-life"),
Pair("Shounen", "shounen"),
Pair("Slice of Life", "slice-of-life"),
Pair("Seinen", "seinen"),
Pair("Yuri", "yuri"),
Pair("Action", "action"),
Pair("Fantasy", "fantasy"),
Pair("Harem", "harem"),
Pair("Supernatural", "supernatural"),
Pair("Sci-Fi", "sci-fi"),
Pair("Isekai", "isekai"),
Pair("Shoujo", "shoujo"),
Pair("Horror", "horror"),
Pair("Psychological", "psychological"),
Pair("Smut", "smut"),
Pair("Tragedy", "tragedy"),
Pair("Raw", "raw"),
Pair("Historical", "historical"),
Pair("Adventure", "adventure"),
Pair("Martial Arts", "martial-arts"),
Pair("Manhwa", "manhwa"),
Pair("Manhua", "manhua"),
Pair("Mystery", "mystery"),
Pair("BL", "bl"),
Pair("Yaoi", "yaoi"),
Pair("Gender Bender", "gender-bender"),
Pair("Thriller", "thriller"),
Pair("Josei", "josei"),
Pair("Sports", "sports"),
Pair("GL", "gl"),
Pair("Family", "family"),
Pair("Magic", "magic"),
)
private val getSortsList: List<Pair<String, String>> = listOf(
Pair("Latest", "latest"),
Pair("A-Z", "alphabet"),
Pair("Rating", "rating"),
Pair("Trending", "trending"),
)

View File

@ -0,0 +1,12 @@
package eu.kanade.tachiyomi.extension.all.manga18me
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceFactory
class M18MFactory : SourceFactory {
override fun createSources(): List<Source> =
listOf(
Manga18Me("all"),
Manga18Me("en"),
)
}

View File

@ -0,0 +1,194 @@
package eu.kanade.tachiyomi.extension.all.manga18me
import eu.kanade.tachiyomi.network.GET
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 eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import java.lang.Exception
import java.text.SimpleDateFormat
import java.util.Locale
open class Manga18Me(override val lang: String) : ParsedHttpSource() {
override val name = "Manga18.me"
override val baseUrl = "https://manga18.me"
override val supportsLatest = true
override val client = network.cloudflareClient
override fun headersBuilder() = Headers.Builder().apply {
add("Referer", "$baseUrl/")
}
override fun popularMangaRequest(page: Int): Request {
return GET("$baseUrl/manga/$page?orderby=trending", headers)
}
override fun popularMangaParse(response: Response): MangasPage {
val document = response.asJsoup()
val entries = document.select(popularMangaSelector())
val hasNextPage = document.selectFirst(popularMangaNextPageSelector()) != null
if (lang == "en") {
val searchText = document.selectFirst("div.section-heading h1")?.text() ?: ""
val raw = document.selectFirst("div.canonical")?.attr("href") ?: ""
return MangasPage(
entries
.filter { it ->
val title = it.selectFirst("div.item-thumb.wleft a")?.attr("href") ?: ""
searchText.lowercase().contains("raw") ||
raw.contains("raw") ||
!title.contains("raw")
}
.map(::popularMangaFromElement),
hasNextPage,
)
}
return MangasPage(entries.map(::popularMangaFromElement), hasNextPage)
}
override fun popularMangaSelector() = "div.page-item-detail"
override fun popularMangaNextPageSelector() = ".next"
override fun popularMangaFromElement(element: Element) = SManga.create().apply {
setUrlWithoutDomain(element.selectFirst("a")!!.absUrl("href"))
title = element.selectFirst("div.item-thumb.wleft a")!!.attr("title")
thumbnail_url = element.selectFirst("img")?.absUrl("src")
}
override fun latestUpdatesRequest(page: Int): Request {
return GET("$baseUrl/manga/$page?orderby=latest", headers)
}
override fun latestUpdatesParse(response: Response) = popularMangaParse(response)
override fun latestUpdatesSelector() = popularMangaSelector()
override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector()
override fun latestUpdatesFromElement(element: Element) = popularMangaFromElement(element)
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val url = baseUrl.toHttpUrl().newBuilder().apply {
if (query.isEmpty()) {
var completed = false
var raw = false
var genre = ""
filters.forEach {
when (it) {
is GenreFilter -> {
genre = it.getValue()
}
is CompletedFilter -> {
completed = it.state
}
is RawFilter -> {
raw = it.state
}
is SortFilter -> {
addQueryParameter("orderby", it.getValue())
}
else -> {}
}
}
if (raw) {
addPathSegment("raw")
} else if (completed) {
addPathSegment("completed")
} else {
if (genre != "manga") addPathSegment("genre")
addPathSegment(genre)
}
addPathSegment(page.toString())
} else {
addPathSegment("search")
addQueryParameter("q", query)
addQueryParameter("page", page.toString())
}
}.build()
return GET(url, headers)
}
override fun searchMangaParse(response: Response) = popularMangaParse(response)
override fun searchMangaSelector() = popularMangaSelector()
override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element)
override fun getFilterList() = getFilters()
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
val info = document.selectFirst("div.post_content")!!
title = document.select("div.post-title.wleft > h1").text()
description = buildString {
document.select("div.ss-manga > p")
.eachText().onEach {
append(it.trim())
append("\n\n")
}
info.selectFirst("div.post-content_item.wleft:contains(Alternative) div.summary-content")
?.text()
?.takeIf { it != "Updating" && it.isNotEmpty() }
?.let {
append("Alternative Names:\n")
append(it.trim())
}
}
status = when (info.select("div.post-content_item.wleft:contains(Status) div.summary-content").text()) {
"Ongoing" -> SManga.ONGOING
"Completed" -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
author = info.selectFirst("div.href-content.artist-content > a")?.text()?.takeIf { it != "Updating" }
artist = info.selectFirst("div.href-content.artist-content > a")?.text()?.takeIf { it != "Updating" }
genre = info.select("div.href-content.genres-content > a[href*=/manga-list/]").eachText().joinToString()
thumbnail_url = document.selectFirst("div.summary_image > img")?.absUrl("src")
}
override fun chapterListSelector() = "ul.row-content-chapter.wleft .a-h.wleft"
private val dateFormat = SimpleDateFormat("dd MMM yyyy", Locale.ENGLISH)
override fun chapterFromElement(element: Element) = SChapter.create().apply {
element.selectFirst("a")!!.run {
setUrlWithoutDomain(absUrl("href"))
name = text()
}
date_upload = try {
dateFormat.parse(element.selectFirst("span")!!.text())!!.time
} catch (_: Exception) {
0L
}
}
override fun pageListParse(document: Document): List<Page> {
val contents = document.select("div.read-content.wleft img")
if (contents.isEmpty()) {
throw Exception("Unable to find script with image data")
}
return contents.mapIndexed { idx, image ->
val imageUrl = image.attr("src")
Page(idx, imageUrl = imageUrl)
}
}
override fun imageUrlParse(document: Document) = throw UnsupportedOperationException()
}

View File

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

View File

@ -10,6 +10,7 @@ import androidx.preference.MultiSelectListPreference
import androidx.preference.PreferenceScreen
import androidx.preference.SwitchPreferenceCompat
import eu.kanade.tachiyomi.AppInfo
import eu.kanade.tachiyomi.extension.BuildConfig
import eu.kanade.tachiyomi.extension.all.mangadex.dto.AggregateDto
import eu.kanade.tachiyomi.extension.all.mangadex.dto.AggregateVolume
import eu.kanade.tachiyomi.extension.all.mangadex.dto.AtHomeDto
@ -62,10 +63,12 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
final override fun headersBuilder(): Headers.Builder {
val extraHeader = "Android/${Build.VERSION.RELEASE} " +
"Tachiyomi/${AppInfo.getVersionName()} " +
"MangaDex/1.4.190"
"MangaDex/1.4.${BuildConfig.VERSION_CODE} " +
"Keiyoushi"
val builder = super.headersBuilder().apply {
set("Referer", "$baseUrl/")
set("Origin", baseUrl)
set("Extra", extraHeader)
}

View File

@ -3,7 +3,7 @@ ext {
extClass = '.Manhwa18CcFactory'
themePkg = 'madara'
baseUrl = 'https://manhwa18.cc'
overrideVersionCode = 4
overrideVersionCode = 5
isNsfw = true
}

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