Compare commits

..

83 Commits

Author SHA1 Message Date
bapeey ac57f5e3dd
SenshiManga: Update domain (#4251)
CI / Prepare job (push) Successful in 6s Details
CI / Build individual modules (push) Successful in 7m23s Details
CI / Publish repo (push) Successful in 44s Details
update domain
2024-07-26 23:35:09 +01:00
Yush0DAN 9f307df515
Ikigai Mangas: Update domain (#4247)
Update domain
2024-07-26 23:35:09 +01:00
KenjieDec 1c9ae26c1e
ManhwaHentaiMe: Fix Chapters Not Found (#4245)
* Fix Chapters

* LF
2024-07-26 23:35:09 +01:00
aoba-seragakii 1f3c64e5c5
yaoi fan club (#4244)
* yfc

* icon update
2024-07-26 23:35:09 +01:00
bapeey c58f4c3449
AsuraScans: Show full chapter title (#4239)
show full title
2024-07-26 23:35:09 +01:00
bapeey 55cad2ee8f
Fix AsuraScans (#4198)
* basics

* bruh

* Add filters

* they will need to migrate

* cloudflareClient

* dynamic url

* remove old prefences

* rename function

* automigration?

* bruh2

* Apply review

* bruh3

* a
2024-07-26 23:35:09 +01:00
Evrey f8a94f9717
add a Darths & Droids extension (#4157)
* add Darths & Droids, though lacking support for one book

* refactors Darths&Droids to auto-detect available books

* fix April’s Fools pages, fix thumbnail assignment and cache

* fix page links for the Solo book

* apply improvements suggested in #4157

* add rate limiting, better document `/archive.html`, apply more fixes suggested in #4157
2024-07-26 23:35:09 +01:00
Nyarunaa ae10943664
NhatTruyen: Update the domain (#4206)
* Update build.gradle

* Update NhatTruyen.kt

* bump versionCode
2024-07-26 23:35:09 +01:00
AwkwardPeak7 2c6e3b45fd
add ReadComicsBook (#4149)
* ReadComicsBook

* http -> https; nullable covers

* center alligned icons

Co-authored-by: Vetle Ledaal <13540478+vetleledaal@users.noreply.github.com>

* https thumbnail in details too

* probably better s0

---------

Co-authored-by: Vetle Ledaal <13540478+vetleledaal@users.noreply.github.com>
2024-07-26 23:35:09 +01:00
bapeey 4c68d5f8d3
HeanCMS: Replace mangaDetails endpoint and enable login in TempleScan (#4226)
* fix mangadetails

* temple now has paid chapters
2024-07-26 23:35:09 +01:00
Chopper c12d0d302f
Remove NovelsTown (#4223) 2024-07-26 23:35:04 +01:00
Chopper 0e9d4b5c3d
Remove ManhuaSY (#4201) 2024-07-26 23:35:04 +01:00
Chopper 94539a9923
Crowscans(Hadess) rebranding (#4169)
Crowscans rebranding
2024-07-26 23:35:04 +01:00
Chopper 888ec79b29
Add MangaNinja (#4168) 2024-07-26 23:35:04 +01:00
bapeey 637d3a6cdd
999Hentai: Rebrand to AnimeH (#4166)
* rebrand

* icon

* why is cropped
2024-07-26 23:35:04 +01:00
bapeey a1252ffd93
StrayFansub: Change theme (#4165)
change theme
2024-07-26 23:35:04 +01:00
bapeey 7cfd7eecc9
SamuraiScan: Update domain (#4164)
update domain
2024-07-26 23:35:04 +01:00
AwkwardPeak7 7c31254a7d
remove mangabin (#4154) 2024-07-26 23:35:04 +01:00
inipew e07b44263d
Shinigami: add baseUrl override (#4152) 2024-07-26 23:35:04 +01:00
Chopper 0cda850385
Bakai: Fix thumbnail url (#4120)
* Fix image url

* Update icons

* Use toHttpUrl

* Remove toHttpUrl uneeded

* Replace icons
2024-07-26 23:35:04 +01:00
KenjieDec 14c5eec0de
Hitomi: Add Image Format Preferences (#4101)
* Add image filetype preferences

* Update extVersionCode

* Apply suggestions

- Confirm jxl by using its signatures when passing through interceptor
- & others

* Update HitomiDto.kt

* Fix

* Fix - Apply Suggestion

* Apply suggestion

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

* Fix

* Lint Fix?

---------

Co-authored-by: Vetle Ledaal <vetle.ledaal@gmail.com>
2024-07-26 23:35:04 +01:00
Vetle Ledaal b3a4bf4697
MangaNoon: update domain (#4138)
* MangaNoon: update domain

`dateFormat` is wrong, doesn't translate cleanly so left as-is. Set NSFW to false, all the 18+ series checked had at most implied intimacy.

* use parseChapterDate() from Galaxy
2024-07-26 23:35:04 +01:00
AwkwardPeak7 2be998797f
Remove ReadAllManga & ReadAllComic cleanup (#4124)
* Remove ReadAllManga

* requested changes and popular title cleanup

* better name?

* reorder if conditions
2024-07-26 23:35:04 +01:00
Vetle Ledaal 3908c908ca
Umi Scans -> Kai Scans: update domain (#4132)
* Umi Scans -> Kai Scans: update domain

* use git for history, not comments
2024-07-26 23:35:04 +01:00
Vetle Ledaal 33b9118f16
Add NoonScan (#4137)
* Add NoonScan

* remove junk
2024-07-26 23:35:04 +01:00
Vetle Ledaal a4db6edd2c
Galaxy Manga: add baseUrl override (#4143) 2024-07-26 23:35:04 +01:00
Secozzi e380f9b974
add necroscans (#4136) 2024-07-26 23:34:43 +01:00
Vetle Ledaal ff29d26ace
ComicExtra: update domain (#4131) 2024-07-26 23:34:43 +01:00
Vetle Ledaal 77943d4f37
Add MangaGojo (#4128) 2024-07-26 23:34:43 +01:00
Vetle Ledaal 46fbc6a591
Add Shojo Scans (#4127) 2024-07-26 23:34:43 +01:00
AwkwardPeak7 16eff66f2e
remove dead sources (#4126)
* remove manhwa freak

* remove mangamonks & readm

* remove suger babies

* remove freakscans
2024-07-26 23:34:43 +01:00
Chopper 3786ec8cfc
MangaSwat: Update domain and icons (#4123)
* Update domain

* Update icons
2024-07-26 23:34:43 +01:00
Chopper c23c65c165
Remove ReadGojo (#4122) 2024-07-26 23:34:43 +01:00
Chopper a5ff37e47a
HentaiHere: Fix manga url (#4121)
Fix manga url
2024-07-26 23:34:43 +01:00
bapeey e7b098cdfe
SenshiManga: Update domain (#4114)
Update domain
2024-07-26 23:34:43 +01:00
inipew ba8f7ac4b4
Shinigami: Update domain (#4105) 2024-07-26 23:34:43 +01:00
AwkwardPeak7 36b5061699
RCO: fix page list (#4110)
* more brute force

* can be negative
2024-07-26 23:34:43 +01:00
Yush0DAN 476e950291
Remove Manhuaespanol & MantrazScan: Update domain (#4104)
*Remove Manhuaespanol - redirect to MantrazScan
*MantrazScan: update url
2024-07-26 23:34:43 +01:00
Vetle Ledaal 682bbb4703
FreeMangaTop: general fixes (#4091)
- Add icon
- Fix date format
- Reduce network calls
- Clean description (best effort)
2024-07-26 23:34:43 +01:00
Vetle Ledaal fe4676497a
Add Hachi (#4078)
* Add Hachi

* PR suggestions

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

---------

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>
2024-07-26 23:34:43 +01:00
AwkwardPeak7 ec59467da4
fix page list
closes  #4093
closes  #4092
2024-07-26 23:34:43 +01:00
AwkwardPeak7 424021dac5
RCO: fix page list x4 2024-07-26 23:34:43 +01:00
AwkwardPeak7 48e449a67e
Remove King of Manga
redirects to furymanga
2024-07-26 23:34:43 +01:00
Chopper 704af6a046
ManhwaList(id): Update domain (#4087)
Update domain
2024-07-26 23:34:43 +01:00
Chopper 4206c918bf
Add Utoon (#4086) 2024-07-26 23:34:43 +01:00
Chopper e5815aaf29
ModeScanlator: Update domain (#4084)
* Update domain

* Add rateLimit
2024-07-26 23:34:43 +01:00
bapeey 3b2fc2be9e
RCO: Clean script (#4079)
* fix

* remove lines

* move regex out
2024-07-26 23:34:43 +01:00
Chopper bd4df286af
UnionMangas: Update api url (#4077)
Update api url
2024-07-26 23:34:43 +01:00
nedius 1ca13b4697
LibGroup: fix crash on null token (#4071)
fix crash on null token
2024-07-26 23:34:43 +01:00
Chopper b28987c34c
Blazescans(FuryManga): Rebrand (#4075)
* Rebrand

* Add isNsfw
2024-07-26 23:34:43 +01:00
Chopper 3249f02716
InfernalVoidScans: Update domain and fix loading mangas (#4061)
* Update domain and fix loading mangas

* Fix genre filter

* Remove duplicate code
2024-07-26 23:34:43 +01:00
AwkwardPeak7 fec86f2276
RCO: fix page list again
_papa :)
2024-07-26 23:34:43 +01:00
AwkwardPeak7 d741e353e7
Koharu: fix regex removing all title 2024-07-26 23:34:43 +01:00
AwkwardPeak7 1a3acc77d3
fix regex 2024-07-26 23:34:43 +01:00
AwkwardPeak7 e721db72a2
Galaxy (AR): update domain (#4047)
* Galaxy (AR): update domain

* bruh
2024-07-26 23:34:43 +01:00
KenjieDec 734c7a1e85
Panda Chaika: Add Character Filter, Fix Sort Filters, Add ID search (#4049)
* Add Character Filter, ID search

* Fix Sort Filters

* Apply Suggestions

* ‎

* Apply Suggestion

* typo fix
2024-07-26 23:34:43 +01:00
KenjieDec 2ef807ca07
Koharu: Add More Filters, fix popular (#4044)
* Add More Filters, fix popular

- Website added more filters
- Apply feature suggestions? -> Title Preferences
- Little change to description

* Apply Suggestions

- Apply vetleledaal's suggestions

* Apply Suggestion

* kB

* Apply suggestion
2024-07-26 23:34:43 +01:00
Chopper cb508becbb
LaviniaFansub: Fix chapter name (#4066)
Fix chapter name
2024-07-26 23:34:43 +01:00
Chopper a53cb1bd8d
LXHentai: Update domain (#4065)
Update domain
2024-07-26 23:34:43 +01:00
Chopper 89308ebcac
Remove BerserkerScan (#4058) 2024-07-26 23:34:43 +01:00
Chopper 8c648b7a74
TruyenVN: Update domain (#4056)
Update domain
2024-07-26 23:34:42 +01:00
Chopper b83956c970
LuaScans: Update domain (#4055)
Update domain
2024-07-26 23:34:42 +01:00
KenjieDec 4754f27a16
SpyFakku: Fix Thumbnail (#4054)
* Fix Thumbnail

* Apply suggestion
2024-07-26 23:34:42 +01:00
bapeey 02ebd645ad
InariManga: Rebrand to Inari Pikav (#4053)
* Rebrand

* bump
2024-07-26 23:34:42 +01:00
Chopper 0391029417
EZmanga: Update domain and icons (#4052)
* Update domain

* Update icons
2024-07-26 23:34:42 +01:00
Chopper ef6466408b
AncientComics: Add a reverse list of chapters (#4051)
Add a reverse list of chapters
2024-07-26 23:34:42 +01:00
AwkwardPeak7 17ef845943
Iken Multisrc, move Vortex Scan and MangaGalaxy (#4043) 2024-07-26 23:34:42 +01:00
AwkwardPeak7 29e4234bb8
RCO: fix page list (#4041) 2024-07-26 23:34:42 +01:00
AwkwardPeak7 5393c9dd11
get original image quality (#4040) 2024-07-26 23:34:42 +01:00
AwkwardPeak7 07546397ac
Quantum scan: update domain (#4038)
* quantim scan: update domain

* remove spac
2024-07-26 23:34:42 +01:00
Roman c9280eb5b2
LibGroup chapter numbers fix (#4012)
* chapter numbers fix

* chapter numbers fix

---------

Co-authored-by: romshke <@>
2024-07-26 23:34:42 +01:00
KenjieDec 41812dd97b
Add Koharu (#3981)
* Add Koharu.to

* Remove some useless things

* Fixed Filters, and More

- Fixed Filters/Search not working when query is empty
- Changed image resolution default value to match website's
- Use `when` instead of kotlin's reflect
- Added search by "id", and url intent filter?

* Apply Suggestions

- Apply vetleledaal's suggestions
- Fix UrlActivity?

* Apply suggestions

* Apply Suggestions

* Add files via upload
2024-07-26 23:34:42 +01:00
renovate[bot] 7257dc89a8
Update dependency gradle to v8.9 (#4006)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-26 23:34:42 +01:00
Chopper 73b30510f5
ImperioDaBritannia: Add LoadMoreStrategy.Never (#4018)
Add LoadMoreStrategy.Never
2024-07-26 23:34:42 +01:00
Chopper 0f91d36deb
DrakeScans: Update domain (#4017)
Update domain
2024-07-26 23:34:42 +01:00
Chopper 6c1d3fb563
Siyahmelek: Update domain (#4016)
Update domain
2024-07-26 23:34:42 +01:00
kana-shii dccd4920ec
Pink sea unicorn exception and icon (#3961)
* icon

* pink sea unicorn exception password message

* Update PinkSeaUnicorn.kt

* Update PinkSeaUnicorn.kt

* Update PinkSeaUnicorn.kt

---------

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>
2024-07-26 23:34:42 +01:00
Chopper 133cb56ef3
SayHentai: Update domain (#4015)
Update domain
2024-07-26 23:34:42 +01:00
Chopper 61988cdfe5
ElarcPage: Update domain (#4014)
Update domain
2024-07-26 23:34:42 +01:00
Chopper a1b627001f
Remove ToshiWaYume (#4013) 2024-07-26 23:34:42 +01:00
bapeey 3c13e2f1ec
SenshiManga: Update api url (#4002)
* Update apiUrl

* Ratelimit
2024-07-26 23:34:42 +01:00
bapeey e81fe09571
NekoScans (es): Change theme to ZeistManga (#4000)
Change theme
2024-07-26 23:34:42 +01:00
bapeey 990ea76c28
Add AnonimusTLS (#3998) 2024-07-26 23:34:42 +01:00
366 changed files with 3446 additions and 1528 deletions

View File

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

View File

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

View File

@ -130,8 +130,8 @@ abstract class GravureBlogger(
override fun pageListParse(response: Response): List<Page> {
val document = response.asJsoup()
return document.select("div.post-body img").mapIndexed { i, it ->
Page(i, imageUrl = it.absUrl("src"))
return document.select("div.post-body a:has(> img)").mapIndexed { i, it ->
Page(i, imageUrl = it.absUrl("href"))
}
}

View File

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

View File

@ -225,13 +225,13 @@ abstract class HeanCms(
throw Exception(intl.format("url_changed_error", name, name))
}
val seriesId = manga.url.substringAfterLast("#")
val seriesSlug = manga.url.substringAfterLast("/").substringBefore("#")
val apiHeaders = headersBuilder()
.add("Accept", ACCEPT_JSON)
.build()
return GET("$apiUrl/series/id/$seriesId", apiHeaders)
return GET("$apiUrl/series/$seriesSlug", apiHeaders)
}
override fun mangaDetailsParse(response: Response): SManga {

View File

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.extension.en.arvenscans
package eu.kanade.tachiyomi.multisrc.iken
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
@ -18,7 +18,7 @@ class SearchResponse(
@Serializable
class Manga(
val id: Int,
private val id: Int,
val slug: String,
private val postTitle: String,
private val postContent: String? = null,
@ -29,7 +29,7 @@ class Manga(
private val artist: String? = null,
private val seriesType: String? = null,
private val seriesStatus: String? = null,
private val genres: List<Name>? = emptyList(),
val genres: List<Genre> = emptyList(),
) {
fun toSManga(baseUrl: String) = SManga.create().apply {
url = "$slug#$id"
@ -70,10 +70,16 @@ class Manga(
"MANHWA" -> add("Manhwa")
else -> {}
}
genres?.forEach { add(it.name) }
genres.forEach { add(it.name) }
}.distinct().joinToString()
}
@Serializable
class Genre(
val id: Int,
val name: String,
)
@Serializable
class Name(val name: String)

View File

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.extension.en.arvenscans
package eu.kanade.tachiyomi.multisrc.iken
import eu.kanade.tachiyomi.source.model.Filter
import okhttp3.HttpUrl
@ -65,37 +65,8 @@ class TypeFilter : SelectFilter(
),
)
class GenreFilter : CheckBoxGroup(
class GenreFilter(genres: List<Pair<String, String>>) : CheckBoxGroup(
"Genres",
"genreIds",
listOf(
Pair("Action", "1"),
Pair("Adventure", "13"),
Pair("Comedy", "7"),
Pair("Drama", "2"),
Pair("elf", "25"),
Pair("Fantas", "28"),
Pair("Fantasy", "8"),
Pair("Historical", "19"),
Pair("Horror", "9"),
Pair("Josei", "21"),
Pair("Manhwa", "5"),
Pair("Martial Arts", "6"),
Pair("Mature", "12"),
Pair("Monsters", "14"),
Pair("Reincarnation", "16"),
Pair("Revenge", "17"),
Pair("Romance", "20"),
Pair("School Life", "23"),
Pair("Seinen", "10"),
Pair("shojo", "26"),
Pair("Shoujo", "22"),
Pair("Shounen", "3"),
Pair("Slice Of Life", "18"),
Pair("Sports", "4"),
Pair("Supernatural", "11"),
Pair("System", "15"),
Pair("terror", "24"),
Pair("Video Games", "27"),
),
genres,
)

View File

@ -0,0 +1,153 @@
package eu.kanade.tachiyomi.multisrc.iken
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 kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
import okhttp3.Response
import uy.kohesive.injekt.injectLazy
abstract class Iken(
override val name: String,
override val lang: String,
override val baseUrl: String,
) : HttpSource() {
override val supportsLatest = true
override val client = network.cloudflareClient
private val json by injectLazy<Json>()
override fun headersBuilder() = super.headersBuilder()
.set("Referer", "$baseUrl/")
private var genres = emptyList<Pair<String, String>>()
private val titleCache by lazy {
val response = client.newCall(GET("$baseUrl/api/query?perPage=9999", headers)).execute()
val data = response.parseAs<SearchResponse>()
data.posts
.filterNot { it.isNovel }
.also { posts ->
genres = posts.flatMap {
it.genres.map { genre ->
genre.name to genre.id.toString()
}
}
}
.associateBy { it.slug }
}
override fun popularMangaRequest(page: Int) = GET("$baseUrl/home", headers)
override fun popularMangaParse(response: Response): MangasPage {
val document = response.asJsoup()
val slugs = document.select("div:contains(Popular) + div.swiper div.manga-swipe > a")
.map { it.absUrl("href").substringAfterLast("/series/") }
val entries = slugs.mapNotNull {
titleCache[it]?.toSManga(baseUrl)
}
return MangasPage(entries, false)
}
override fun latestUpdatesRequest(page: Int) = searchMangaRequest(page, "", getFilterList())
override fun latestUpdatesParse(response: Response) = searchMangaParse(response)
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val url = "$baseUrl/api/query".toHttpUrl().newBuilder().apply {
addQueryParameter("page", page.toString())
addQueryParameter("perPage", perPage.toString())
addQueryParameter("searchTerm", query.trim())
filters.filterIsInstance<UrlPartFilter>().forEach {
it.addUrlParameter(this)
}
}.build()
return GET(url, headers)
}
override fun searchMangaParse(response: Response): MangasPage {
val data = response.parseAs<SearchResponse>()
val page = response.request.url.queryParameter("page")!!.toInt()
val entries = data.posts
.filterNot { it.isNovel }
.map { it.toSManga(baseUrl) }
val hasNextPage = data.totalCount > (page * perPage)
return MangasPage(entries, hasNextPage)
}
override fun getFilterList() = FilterList(
StatusFilter(),
TypeFilter(),
GenreFilter(genres),
Filter.Header("Open popular mangas if genre filter is empty"),
)
override fun mangaDetailsRequest(manga: SManga): Request {
val id = manga.url.substringAfterLast("#")
val url = "$baseUrl/api/chapters?postId=$id&skip=0&take=1000&order=desc&userid="
return GET(url, headers)
}
override fun getMangaUrl(manga: SManga): String {
val slug = manga.url.substringBeforeLast("#")
return "$baseUrl/series/$slug"
}
override fun mangaDetailsParse(response: Response): SManga {
val data = response.parseAs<Post<Manga>>()
assert(!data.post.isNovel) { "Novels are unsupported" }
// genres are only returned in search call
// and not when fetching details
return data.post.toSManga(baseUrl).apply {
genre = titleCache[data.post.slug]?.getGenres()
}
}
override fun chapterListRequest(manga: SManga) = mangaDetailsRequest(manga)
override fun chapterListParse(response: Response): List<SChapter> {
val data = response.parseAs<Post<ChapterListResponse>>()
assert(!data.post.isNovel) { "Novels are unsupported" }
return data.post.chapters
.filter { it.isPublic() }
.map { it.toSChapter(data.post.slug) }
}
override fun pageListParse(response: Response): List<Page> {
val document = response.asJsoup()
return document.select("main section > img").mapIndexed { idx, img ->
Page(idx, imageUrl = img.absUrl("src"))
}
}
override fun imageUrlParse(response: Response) =
throw UnsupportedOperationException()
private inline fun <reified T> Response.parseAs(): T =
json.decodeFromString(body.string())
}
private const val perPage = 18

View File

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

View File

@ -188,7 +188,11 @@ abstract class LibGroup(
} else {
it.replace("\\", "")
}
returnValue = str.parseAs<AuthToken>()
str.parseAs<AuthToken>().let { auth ->
if (auth.isValid()) {
returnValue = auth
}
}
}
latch.countDown()
}
@ -324,7 +328,7 @@ abstract class LibGroup(
if (currentBranch.value.branchId == defaultBranchId && sortingList == "ms_mixing") { // ms_mixing with default branch from api
chapters.add(it.value.toSChapter(slugUrl, defaultBranchId, isScanUser()))
} else if (defaultBranchId == null && sortingList == "ms_mixing") { // ms_mixing with first branch in chapter
if (chapters.any { chpIt -> chpIt.chapter_number == it.value.itemNumber }) {
if (chapters.any { chpIt -> chpIt.chapter_number == it.value.number.toFloat() }) {
chapters.add(it.value.toSChapter(slugUrl, currentBranch.value.branchId, isScanUser()))
}
} else if (sortingList == "ms_combining") { // ms_combining

View File

@ -202,7 +202,6 @@ class Chapter(
val name: String?,
val number: String,
val volume: String,
@SerialName("item_number") val itemNumber: Float?,
) {
@Serializable
class Branch(
@ -241,7 +240,7 @@ class Chapter(
url = "/$slugUrl/chapter?$branchStr&volume=$volume&number=$number"
scanlator = getTeamName(branchId) ?: if (isScanUser) getUserName(branchId) else null
date_upload = runCatching { LibGroup.simpleDateFormat.parse(first(branchId)!!.createdAt)!!.time }.getOrDefault(0L)
chapter_number = itemNumber ?: -1f
chapter_number = number.toFloat()
}
}
@ -267,8 +266,8 @@ class Pages(
@Serializable
class AuthToken(
private val auth: Auth,
private val token: Token,
private val auth: Auth?,
private val token: Token?,
) {
@Serializable
class Auth(
@ -283,13 +282,15 @@ class AuthToken(
@SerialName("access_token") val accessToken: String,
)
fun isValid(): Boolean = auth != null && token != null
fun isExpired(): Boolean {
val currentTime = System.currentTimeMillis()
val expiresIn = token.timestamp + (token.expiresIn * 1000)
val expiresIn = token!!.timestamp + (token.expiresIn * 1000)
return expiresIn < currentTime
}
fun getToken(): String = "${token.tokenType} ${token.accessToken}"
fun getToken(): String = "${token!!.tokenType} ${token.accessToken}"
fun getUserId(): Int = auth.id
fun getUserId(): Int = auth!!.id
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

View File

@ -1,7 +1,8 @@
ext {
extName = 'Galaxy'
extClass = '.GalaxyFactory'
extVersionCode = 2
extVersionCode = 4
isNsfw = false
}
apply from: "$rootDir/common.gradle"

View File

@ -1,6 +1,13 @@
package eu.kanade.tachiyomi.extension.all.galaxy
import android.app.Application
import android.content.SharedPreferences
import android.widget.Toast
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.source.ConfigurableSource
import eu.kanade.tachiyomi.source.SourceFactory
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class GalaxyFactory : SourceFactory {
@ -8,8 +15,53 @@ class GalaxyFactory : SourceFactory {
override val id = 2602904659965278831
}
class GalaxyManga : Galaxy("Galaxy Manga", "https://ayoub-zrr.xyz", "ar") {
class GalaxyManga :
Galaxy("Galaxy Manga", "https://galaxymanga.net", "ar"),
ConfigurableSource {
override val id = 2729515745226258240
override val baseUrl by lazy { getPrefBaseUrl() }
private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
companion object {
private const val RESTART_APP = ".لتطبيق الإعدادات الجديدة أعد تشغيل التطبيق"
private const val BASE_URL_PREF_TITLE = "تعديل الرابط"
private const val BASE_URL_PREF = "overrideBaseUrl"
private const val BASE_URL_PREF_SUMMARY = ".للاستخدام المؤقت. تحديث التطبيق سيؤدي الى حذف الإعدادات"
private const val DEFAULT_BASE_URL_PREF = "defaultBaseUrl"
}
override fun setupPreferenceScreen(screen: PreferenceScreen) {
val baseUrlPref = androidx.preference.EditTextPreference(screen.context).apply {
key = BASE_URL_PREF
title = BASE_URL_PREF_TITLE
summary = BASE_URL_PREF_SUMMARY
this.setDefaultValue(super.baseUrl)
dialogTitle = BASE_URL_PREF_TITLE
dialogMessage = "Default: ${super.baseUrl}"
setOnPreferenceChangeListener { _, _ ->
Toast.makeText(screen.context, RESTART_APP, Toast.LENGTH_LONG).show()
true
}
}
screen.addPreference(baseUrlPref)
}
private fun getPrefBaseUrl(): String = preferences.getString(BASE_URL_PREF, super.baseUrl)!!
init {
preferences.getString(DEFAULT_BASE_URL_PREF, null).let { prefDefaultBaseUrl ->
if (prefDefaultBaseUrl != super.baseUrl) {
preferences.edit()
.putString(BASE_URL_PREF, super.baseUrl)
.putString(DEFAULT_BASE_URL_PREF, super.baseUrl)
.apply()
}
}
}
}
override fun createSources() = listOf(

View File

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

View File

@ -1,7 +1,12 @@
package eu.kanade.tachiyomi.extension.all.hitomi
import android.app.Application
import android.content.SharedPreferences
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.await
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
@ -19,9 +24,14 @@ import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import okhttp3.CacheControl
import okhttp3.Call
import okhttp3.Interceptor
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.Request
import okhttp3.Response
import okhttp3.ResponseBody.Companion.toResponseBody
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
@ -30,13 +40,14 @@ import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.LinkedList
import java.util.Locale
import kotlin.math.max
import kotlin.math.min
@OptIn(ExperimentalUnsignedTypes::class)
class Hitomi(
override val lang: String,
private val nozomiLang: String,
) : HttpSource() {
) : HttpSource(), ConfigurableSource {
override val name = "Hitomi"
@ -50,7 +61,14 @@ class Hitomi(
private val json: Json by injectLazy()
override val client = network.cloudflareClient
override val client = network.cloudflareClient.newBuilder()
.addInterceptor(::Intercept)
.build()
private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
private fun imageType() = preferences.getString(PREF_IMAGETYPE, "webp")!!
override fun headersBuilder() = super.headersBuilder()
.set("referer", "$baseUrl/")
@ -488,7 +506,7 @@ class Hitomi(
private suspend fun Gallery.toSManga() = SManga.create().apply {
title = this@toSManga.title
url = galleryurl
author = groups?.joinToString { it.formatted }
author = groups?.joinToString { it.formatted } ?: artists?.joinToString { it.formatted }
artist = artists?.joinToString { it.formatted }
genre = tags?.joinToString { it.formatted }
thumbnail_url = files.first().let {
@ -567,14 +585,25 @@ class Hitomi(
gallery.files.mapIndexed { idx, img ->
val hash = img.hash
val typePref = imageType()
val avif = img.hasavif == 1 && typePref == "avif"
val jxl = img.hasjxl == 1 && typePref == "jxl"
val commonId = commonImageId()
val imageId = imageIdFromHash(hash)
val subDomain = 'a' + subdomainOffset(imageId)
val imageUrl = when {
jxl -> "https://${subDomain}a.$domain/jxl/$commonId$imageId/$hash.jxl"
avif -> "https://${subDomain}a.$domain/avif/$commonId$imageId/$hash.avif"
else -> "https://${subDomain}a.$domain/webp/$commonId$imageId/$hash.webp"
}
Page(
idx,
"$baseUrl/reader/$id.html",
"https://${subDomain}a.$domain/webp/$commonId$imageId/$hash.webp",
imageUrl,
)
}
}
@ -657,6 +686,45 @@ class Hitomi(
return hash.replace(Regex("""^.*(..)(.)$"""), "$2/$1")
}
override fun setupPreferenceScreen(screen: PreferenceScreen) {
ListPreference(screen.context).apply {
key = PREF_IMAGETYPE
title = "Images Type"
entries = arrayOf("webp", "avif", "jxl")
entryValues = arrayOf("webp", "avif", "jxl")
summary = "Clear chapter cache to apply changes"
setDefaultValue("webp")
}.also(screen::addPreference)
}
private fun List<Int>.toBytesList(): ByteArray = this.map { it.toByte() }.toByteArray()
private val signatureOne = listOf(0xFF, 0x0A).toBytesList()
private val signatureTwo = listOf(0x00, 0x00, 0x00, 0x0C, 0x4A, 0x58, 0x4C, 0x20, 0x0D, 0x0A, 0x87, 0x0A).toBytesList()
fun ByteArray.startsWith(byteArray: ByteArray): Boolean {
if (this.size < byteArray.size) return false
return this.sliceArray(byteArray.indices).contentEquals(byteArray)
}
private fun Intercept(chain: Interceptor.Chain): Response {
val response = chain.proceed(chain.request())
if (response.headers["Content-Type"] != "application/octet-stream") {
return response
}
val bytesPeek = max(signatureOne.size, signatureTwo.size).toLong()
val bytesArray = response.peekBody(bytesPeek).bytes()
if (!(bytesArray.startsWith(signatureOne) || bytesArray.startsWith(signatureTwo))) {
return response
}
val type = "image/jxl"
val body = response.body.bytes().toResponseBody(type.toMediaType())
return response.newBuilder()
.body(body)
.header("Content-Type", type)
.build()
}
override fun popularMangaParse(response: Response) = throw UnsupportedOperationException()
override fun popularMangaRequest(page: Int) = throw UnsupportedOperationException()
override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException()
@ -664,4 +732,8 @@ class Hitomi(
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 {
const val PREF_IMAGETYPE = "pref_image_type"
}
}

View File

@ -21,6 +21,9 @@ class Gallery(
@Serializable
class ImageFile(
val hash: String,
val haswebp: Int,
val hasavif: Int,
val hasjxl: Int,
)
@Serializable

View File

@ -2,7 +2,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application android:icon="@mipmap/ic_launcher">
<activity
android:name=".all.ninenineninehentai.NineNineNineHentaiUrlActivity"
android:name=".all.ninenineninehentai.AnimeHUrlActivity"
android:excludeFromRecents="true"
android:exported="true"
android:theme="@android:style/Theme.NoDisplay">
@ -12,7 +12,7 @@
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:host="999hentai.net"/>
<data android:host="animeh.to"/>
<data android:scheme="https"/>
<data android:pathPattern="/hchapter/..*"/>
</intent-filter>

View File

@ -1,7 +1,7 @@
ext {
extName = '999Hentai'
extClass = '.NineNineNineHentaiFactory'
extVersionCode = 6
extName = 'AnimeH'
extClass = '.AnimeHFactory'
extVersionCode = 7
isNsfw = true
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 47 KiB

View File

@ -35,16 +35,16 @@ import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat
import java.util.Locale
open class NineNineNineHentai(
open class AnimeH(
final override val lang: String,
private val siteLang: String = lang,
) : HttpSource(), ConfigurableSource {
override val name = "999Hentai"
override val name = "AnimeH"
override val baseUrl = "https://999hentai.net"
override val baseUrl = "https://animeh.to"
private val apiUrl = "https://hapi.999hentai.net/api"
private val apiUrl = "https://api.animeh.to/api"
override val supportsLatest = true

View File

@ -0,0 +1,19 @@
package eu.kanade.tachiyomi.extension.all.ninenineninehentai
import eu.kanade.tachiyomi.source.SourceFactory
class AnimeHFactory : SourceFactory {
override fun createSources() = listOf(
AnimeHAll(),
AnimeHEn(),
AnimeHJa(),
AnimeHZh(),
AnimeHEs(),
)
}
class AnimeHAll : AnimeH("all") { override val id = 5098173700376022513 }
class AnimeHEn : AnimeH("en") { override val id = 4370122548313941497 }
class AnimeHJa : AnimeH("ja", "jp") { override val id = 8948948503520127713 }
class AnimeHZh : AnimeH("zh", "cn") { override val id = 3874510362699054213 }
class AnimeHEs : AnimeH("es") { override val id = 2790053117909987291 }

View File

@ -7,7 +7,7 @@ import android.os.Bundle
import android.util.Log
import kotlin.system.exitProcess
class NineNineNineHentaiUrlActivity : Activity() {
class AnimeHUrlActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val pathSegments = intent?.data?.pathSegments
@ -15,17 +15,17 @@ class NineNineNineHentaiUrlActivity : Activity() {
val id = pathSegments[1]
val mainIntent = Intent().apply {
action = "eu.kanade.tachiyomi.SEARCH"
putExtra("query", "${NineNineNineHentai.SEARCH_PREFIX}$id")
putExtra("query", "${AnimeH.SEARCH_PREFIX}$id")
putExtra("filter", packageName)
}
try {
startActivity(mainIntent)
} catch (e: ActivityNotFoundException) {
Log.e("999HentaiUrlActivity", e.toString())
Log.e("AnimeHUrlActivity", e.toString())
}
} else {
Log.e("999HentaiUrlActivity", "could not parse uri from intent $intent")
Log.e("AnimeHUrlActivity", "could not parse uri from intent $intent")
}
finish()

View File

@ -1,13 +0,0 @@
package eu.kanade.tachiyomi.extension.all.ninenineninehentai
import eu.kanade.tachiyomi.source.SourceFactory
class NineNineNineHentaiFactory : SourceFactory {
override fun createSources() = listOf(
NineNineNineHentai("all"),
NineNineNineHentai("en"),
NineNineNineHentai("ja", "jp"),
NineNineNineHentai("zh", "cn"),
NineNineNineHentai("es"),
)
}

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<activity
android:name=".all.pandachaika.PandaChaikaUrlActivity"
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="panda.chaika.moe"
android:pathPattern="/archive/..*"
android:scheme="https"/>
</intent-filter>
</activity>
</application>
</manifest>

View File

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

View File

@ -1,6 +1,8 @@
package eu.kanade.tachiyomi.extension.all.pandachaika
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.asObservable
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
@ -42,6 +44,9 @@ class PandaChaika(
private val json: Json by injectLazy()
private val fakkuRegex = Regex("""(?:https?://)?(?:www\.)?fakku\.net/hentai/""")
private val ehentaiRegex = Regex("""(?:https?://)?e-hentai\.org/g/""")
// Popular
override fun popularMangaRequest(page: Int): Request {
return GET("$baseSearchUrl/?tags=$searchLang&sort=rating&apply=&json=&page=$page", headers)
@ -73,6 +78,76 @@ class PandaChaika(
}
}
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).toInt()
client.newCall(GET("$baseUrl/api?archive=$id", headers))
.asObservable()
.map { response ->
searchMangaByIdParse(response, id)
}
}
query.startsWith(PREFIX_EHEN_ID_SEARCH) -> {
val id = query.removePrefix(PREFIX_EHEN_ID_SEARCH).replace(ehentaiRegex, "")
val baseLink = "https://e-hentai.org/g/"
val fullLink = baseSearchUrl.toHttpUrl().newBuilder().apply {
addQueryParameter("qsearch", baseLink + id)
addQueryParameter("json", "")
}.build()
client.newCall(GET(fullLink, headers))
.asObservableSuccess()
.map {
val archive = it.parseAs<ArchiveResponse>().archives.getOrNull(0)?.toSManga() ?: throw Exception("Not Found")
MangasPage(listOf(archive), false)
}
}
query.startsWith(PREFIX_FAK_ID_SEARCH) -> {
val slug = query.removePrefix(PREFIX_FAK_ID_SEARCH).replace(fakkuRegex, "")
val baseLink = "https://www.fakku.net/hentai/"
val fullLink = baseSearchUrl.toHttpUrl().newBuilder().apply {
addQueryParameter("qsearch", baseLink + slug)
addQueryParameter("json", "")
}.build()
client.newCall(GET(fullLink, headers))
.asObservableSuccess()
.map {
val archive = it.parseAs<ArchiveResponse>().archives.getOrNull(0)?.toSManga() ?: throw Exception("Not Found")
MangasPage(listOf(archive), false)
}
}
query.startsWith(PREFIX_SOURCE_SEARCH) -> {
val url = query.removePrefix(PREFIX_SOURCE_SEARCH)
client.newCall(GET("$baseSearchUrl/?qsearch=$url&json=", headers))
.asObservableSuccess()
.map {
val archive = it.parseAs<ArchiveResponse>().archives.getOrNull(0)?.toSManga() ?: throw Exception("Not Found")
MangasPage(listOf(archive), false)
}
}
else -> super.fetchSearchManga(page, query, filters)
}
}
private fun searchMangaByIdParse(response: Response, id: Int = 0): MangasPage {
val title = response.parseAs<Archive>().title
val fullLink = baseSearchUrl.toHttpUrl().newBuilder().apply {
addQueryParameter("qsearch", title)
addQueryParameter("json", "")
}.build()
val archive = client.newCall(GET(fullLink, headers))
.execute()
.parseAs<ArchiveResponse>().archives
.find {
it.id == id
}
?.toSManga()
?: throw Exception("Invalid ID")
return MangasPage(listOf(archive), false)
}
override fun searchMangaParse(response: Response): MangasPage {
val library = response.parseAs<ArchiveResponse>()
@ -250,4 +325,11 @@ class PandaChaika(
override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException()
override fun pageListParse(response: Response): List<Page> = throw UnsupportedOperationException()
override fun mangaDetailsParse(response: Response): SManga = throw UnsupportedOperationException()
companion object {
const val PREFIX_ID_SEARCH = "id:"
const val PREFIX_FAK_ID_SEARCH = "fakku:"
const val PREFIX_EHEN_ID_SEARCH = "ehentai:"
const val PREFIX_SOURCE_SEARCH = "source:"
}
}

View File

@ -8,7 +8,7 @@ import java.util.Date
import java.util.Locale
val dateReformat = SimpleDateFormat("EEEE, d MMM yyyy HH:mm (z)", Locale.ENGLISH)
fun filterTags(include: String = "", exclude: List<String> = emptyList(), tags: List<String>): String {
fun filterTags(include: String = "", exclude: List<String> = emptyList(), tags: List<String>): String? {
return tags.filter { it.startsWith("$include:") && exclude.none { substring -> it.startsWith("$substring:") } }
.joinToString {
it.substringAfter(":").replace("_", " ").split(" ").joinToString(" ") { s ->
@ -16,13 +16,13 @@ fun filterTags(include: String = "", exclude: List<String> = emptyList(), tags:
if (sr.isLowerCase()) sr.titlecase(Locale.getDefault()) else sr.toString()
}
}
}
}.takeIf { it.isNotBlank() }
}
fun getReadableSize(bytes: Double): String {
return when {
bytes >= 300 * 1024 * 1024 -> "${"%.2f".format(bytes / (1024.0 * 1024.0 * 1024.0))} GB"
bytes >= 100 * 1024 -> "${"%.2f".format(bytes / (1024.0 * 1024.0))} MB"
bytes >= 1024 -> "${"%.2f".format(bytes / (1024.0))} KB"
bytes >= 300 * 1000 * 1000 -> "${"%.2f".format(bytes / (1000.0 * 1000.0 * 1000.0))} GB"
bytes >= 100 * 1000 -> "${"%.2f".format(bytes / (1000.0 * 1000.0))} MB"
bytes >= 1000 -> "${"%.2f".format(bytes / (1000.0))} kB"
else -> "$bytes B"
}
}
@ -31,13 +31,14 @@ fun getReadableSize(bytes: Double): String {
class Archive(
val download: String,
val posted: Long,
val title: String,
)
@Serializable
class LongArchive(
private val thumbnail: String,
private val title: String,
private val id: Int,
val id: Int,
private val posted: Long?,
private val public_date: Long?,
private val filecount: Int,
@ -50,35 +51,47 @@ class LongArchive(
val groups = filterTags("group", tags = tags)
val artists = filterTags("artist", tags = tags)
val publishers = filterTags("publisher", tags = tags)
val characters = filterTags("character", tags = tags)
val male = filterTags("male", tags = tags)
val female = filterTags("female", tags = tags)
val others = filterTags(exclude = listOf("female", "male", "artist", "publisher", "group", "parody"), tags = tags)
val parodies = filterTags("parody", tags = tags)
var appended = false
url = id.toString()
title = this@LongArchive.title
thumbnail_url = thumbnail
author = groups.ifEmpty { artists }
author = groups ?: artists
artist = artists
genre = listOf(male, female, others).joinToString()
description = buildString {
append("Uploader: ", uploader.ifEmpty { "Anonymous" }, "\n")
publishers.takeIf { it.isNotBlank() }?.let {
append("Publishers: ", it, "\n\n")
publishers?.let {
append("Publishers: ", it, "\n")
}
parodies.takeIf { it.isNotBlank() }?.let {
append("Parodies: ", it, "\n\n")
append("\n")
parodies?.let {
append("Parodies: ", it, "\n")
appended = true
}
male.takeIf { it.isNotBlank() }?.let {
characters?.let {
append("Characters: ", it, "\n")
appended = true
}
if (appended) append("\n")
male?.let {
append("Male tags: ", it, "\n\n")
}
female.takeIf { it.isNotBlank() }?.let {
female?.let {
append("Female tags: ", it, "\n\n")
}
others.takeIf { it.isNotBlank() }?.let {
others?.let {
append("Other tags: ", it, "\n\n")
}
title_jpn?.let { append("Japanese Title: ", it, "\n") }
title_jpn?.takeIf { it.isNotEmpty() }?.let { append("Japanese Title: ", it, "\n") }
append("Pages: ", filecount, "\n")
append("File Size: ", getReadableSize(filesize), "\n")

View File

@ -17,6 +17,7 @@ fun getFilters(): FilterList {
TextFilter("Female Tags", "female"),
TextFilter("Artists", "artist"),
TextFilter("Parodies", "parody"),
TextFilter("Characters", "character"),
Filter.Separator(),
TextFilter("Reason", "reason"),
TextFilter("Uploader", "reason"),
@ -52,11 +53,11 @@ private val getTypes = listOf(
private val getSortsList: List<Pair<String, String>> = listOf(
Pair("Public Date", "public_date"),
Pair("Posted Date", "posted_date"),
Pair("Posted Date", "posted"),
Pair("Title", "title"),
Pair("Japanese Title", "title_jpn"),
Pair("Rating", "rating"),
Pair("Images", "images"),
Pair("File Size", "size"),
Pair("Images", "filecount"),
Pair("File Size", "filesize"),
Pair("Category", "category"),
)

View File

@ -0,0 +1,34 @@
package eu.kanade.tachiyomi.extension.all.pandachaika
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 PandaChaikaUrlActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val pathSegments = intent?.data?.pathSegments
if (pathSegments != null && pathSegments.size > 2) {
val id = "${pathSegments[1]}/${pathSegments[2]}"
val mainIntent = Intent().apply {
action = "eu.kanade.tachiyomi.SEARCH"
putExtra("query", "${PandaChaika.PREFIX_ID_SEARCH}$id")
putExtra("filter", packageName)
}
try {
startActivity(mainIntent)
} catch (e: ActivityNotFoundException) {
Log.e("KoharuUrlActivity", "Could not start activity", e)
}
} else {
Log.e("KoharuUrlActivity", "Could not parse URI from intent $intent")
}
finish()
exitProcess(0)
}
}

View File

@ -1,7 +1,7 @@
ext {
extName = 'Union Mangas'
extClass = '.UnionMangasFactory'
extVersionCode = 5
extVersionCode = 6
isNsfw = true
}

View File

@ -203,7 +203,7 @@ class UnionMangas(private val langOption: LanguageOption) : HttpSource() {
companion object {
const val SEARCH_PREFIX = "slug:"
val apiUrl = "https://app.unionmanga.xyz/api"
val apiUrl = "https://api.novelfull.us/api"
val oldApiUrl = "https://api.unionmanga.xyz"
val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSS", Locale.ENGLISH)
}

View File

@ -1,8 +1,8 @@
ext {
extName = 'Crow Scans'
extClass = '.CrowScans'
themePkg = 'mangathemesia'
baseUrl = 'https://crowscans.com'
extName = 'Hadess'
extClass = '.Hadess'
themePkg = 'madara'
baseUrl = 'https://www.hadess.xyz'
overrideVersionCode = 0
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

@ -1,12 +0,0 @@
package eu.kanade.tachiyomi.extension.ar.crowscans
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
import java.text.SimpleDateFormat
import java.util.Locale
class CrowScans : MangaThemesia(
"Crow Scans",
"https://crowscans.com",
"ar",
dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("ar")),
)

View File

@ -0,0 +1,26 @@
package eu.kanade.tachiyomi.extension.ar.crowscans
import eu.kanade.tachiyomi.multisrc.madara.Madara
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import java.text.SimpleDateFormat
import java.util.Locale
class Hadess : Madara(
"Hadess",
"https://www.hadess.xyz",
"ar",
dateFormat = SimpleDateFormat("dd MMMM، yyyy", Locale("ar")),
) {
override val versionId = 2
override val client = super.client.newBuilder()
.rateLimit(3)
.build()
override val useNewChapterEndpoint = true
override val useLoadMoreRequest = LoadMoreStrategy.Always
override val mangaDetailsSelectorStatus =
".summary-heading:contains(الحالة) + ${super.mangaDetailsSelectorStatus}"
}

View File

@ -2,8 +2,9 @@ ext {
extName = 'MangaNoon'
extClass = '.MangaNoon'
themePkg = 'mangathemesia'
baseUrl = 'https://manjanoon.org'
overrideVersionCode = 2
baseUrl = 'https://manjanoon.co'
overrideVersionCode = 3
isNsfw = false
}
apply from: "$rootDir/common.gradle"

View File

@ -1,12 +1,89 @@
package eu.kanade.tachiyomi.extension.ar.manganoon
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
import java.text.SimpleDateFormat
import java.util.Locale
import eu.kanade.tachiyomi.source.model.SChapter
import org.jsoup.nodes.Element
import java.util.Calendar
class MangaNoon : MangaThemesia(
"مانجا نون",
"https://manjanoon.org",
"https://manjanoon.co",
"ar",
dateFormat = SimpleDateFormat("MMM d, yyy", Locale("ar")),
)
) {
override fun chapterFromElement(element: Element): SChapter {
return super.chapterFromElement(element).apply {
date_upload = element.selectFirst(".chapterdate")?.text().parseChapterDate()
}
}
// From Galaxy
override fun String?.parseChapterDate(): Long {
this ?: return 0L
val number = Regex("""(\d+)""").find(this)?.value?.toIntOrNull() ?: 0
val cal = Calendar.getInstance()
return when {
listOf("second", "ثانية").any { contains(it, true) } -> {
cal.apply { add(Calendar.SECOND, -number) }.timeInMillis
}
contains("دقيقتين", true) -> {
cal.apply { add(Calendar.MINUTE, -2) }.timeInMillis
}
listOf("minute", "دقائق").any { contains(it, true) } -> {
cal.apply { add(Calendar.MINUTE, -number) }.timeInMillis
}
contains("ساعتان", true) -> {
cal.apply { add(Calendar.HOUR, -2) }.timeInMillis
}
listOf("hour", "ساعات").any { contains(it, true) } -> {
cal.apply { add(Calendar.HOUR, -number) }.timeInMillis
}
contains("يوم", true) -> {
cal.apply { add(Calendar.DAY_OF_YEAR, -1) }.timeInMillis
}
contains("يومين", true) -> {
cal.apply { add(Calendar.DAY_OF_YEAR, -2) }.timeInMillis
}
listOf("day", "أيام").any { contains(it, true) } -> {
cal.apply { add(Calendar.DAY_OF_YEAR, -number) }.timeInMillis
}
contains("أسبوع", true) -> {
cal.apply { add(Calendar.WEEK_OF_YEAR, -1) }.timeInMillis
}
contains("أسبوعين", true) -> {
cal.apply { add(Calendar.WEEK_OF_YEAR, -2) }.timeInMillis
}
listOf("week", "أسابيع").any { contains(it, true) } -> {
cal.apply { add(Calendar.WEEK_OF_YEAR, -number) }.timeInMillis
}
contains("شهر", true) -> {
cal.apply { add(Calendar.MONTH, -1) }.timeInMillis
}
contains("شهرين", true) -> {
cal.apply { add(Calendar.MONTH, -2) }.timeInMillis
}
listOf("month", "أشهر").any { contains(it, true) } -> {
cal.apply { add(Calendar.MONTH, -number) }.timeInMillis
}
contains("سنة", true) -> {
cal.apply { add(Calendar.YEAR, -1) }.timeInMillis
}
contains("سنتان", true) -> {
cal.apply { add(Calendar.YEAR, -2) }.timeInMillis
}
listOf("year", "سنوات").any { contains(it, true) } -> {
cal.apply { add(Calendar.YEAR, -number) }.timeInMillis
}
else -> 0L
}
}
}

View File

@ -2,8 +2,8 @@ ext {
extName = 'MangaSwat'
extClass = '.MangaSwat'
themePkg = 'mangathemesia'
baseUrl = 'https://t1manga.com'
overrideVersionCode = 19
baseUrl = 'https://maxlevelteam.com'
overrideVersionCode = 20
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

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

View File

@ -0,0 +1,10 @@
ext {
extName = 'NoonScan'
extClass = '.NoonScan'
themePkg = 'mangathemesia'
baseUrl = 'https://noonscan.com'
overrideVersionCode = 0
isNsfw = false
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

View File

@ -0,0 +1,12 @@
package eu.kanade.tachiyomi.extension.ar.noonscan
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
import java.text.SimpleDateFormat
import java.util.Locale
class NoonScan : MangaThemesia(
"نون سكان",
"https://noonscan.com",
"ar",
dateFormat = SimpleDateFormat("MMMM d, yyyy", Locale("ar")),
)

View File

@ -1,9 +0,0 @@
ext {
extName = 'Novels Town'
extClass = '.NovelsTown'
themePkg = 'madara'
baseUrl = 'https://novelstown.com'
overrideVersionCode = 1
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

View File

@ -1,7 +0,0 @@
package eu.kanade.tachiyomi.extension.ar.novelstown
import eu.kanade.tachiyomi.multisrc.madara.Madara
class NovelsTown : Madara("Novels Town", "https://novelstown.com", "ar") {
override val mangaSubString = "الاعمال"
}

View File

@ -1,7 +1,8 @@
ext {
extName = 'Vortex Scans'
extClass = '.VortexScans'
extVersionCode = 33
themePkg = 'iken'
overrideVersionCode = 33
isNsfw = false
}

View File

@ -1,145 +1,9 @@
package eu.kanade.tachiyomi.extension.en.arvenscans
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.HttpSource
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
import okhttp3.Response
import uy.kohesive.injekt.injectLazy
import eu.kanade.tachiyomi.multisrc.iken.Iken
class VortexScans : HttpSource() {
override val name = "Vortex Scans"
override val baseUrl = "https://vortexscans.org"
override val lang = "en"
override val supportsLatest = true
override val client = network.cloudflareClient
private val json by injectLazy<Json>()
override fun headersBuilder() = super.headersBuilder()
.set("Referer", "$baseUrl/")
private val titleCache by lazy {
val response = client.newCall(GET("$baseUrl/api/query?perPage=9999", headers)).execute()
val data = response.parseAs<SearchResponse>()
data.posts
.filterNot { it.isNovel }
.associateBy { it.slug }
}
override fun popularMangaRequest(page: Int) = GET("$baseUrl/home", headers)
override fun popularMangaParse(response: Response): MangasPage {
val document = response.asJsoup()
val slugs = document.select("div:contains(Popular) + div.swiper div.manga-swipe > a")
.map { it.absUrl("href").substringAfterLast("/series/") }
val entries = slugs.mapNotNull {
titleCache[it]?.toSManga(baseUrl)
}
return MangasPage(entries, false)
}
override fun latestUpdatesRequest(page: Int) = searchMangaRequest(page, "", getFilterList())
override fun latestUpdatesParse(response: Response) = searchMangaParse(response)
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val url = "$baseUrl/api/query".toHttpUrl().newBuilder().apply {
addQueryParameter("page", page.toString())
addQueryParameter("perPage", perPage.toString())
addQueryParameter("searchTerm", query.trim())
filters.filterIsInstance<UrlPartFilter>().forEach {
it.addUrlParameter(this)
}
}.build()
return GET(url, headers)
}
override fun searchMangaParse(response: Response): MangasPage {
val data = response.parseAs<SearchResponse>()
val page = response.request.url.queryParameter("page")!!.toInt()
val entries = data.posts
.filterNot { it.isNovel }
.map { it.toSManga(baseUrl) }
val hasNextPage = data.totalCount > (page * perPage)
return MangasPage(entries, hasNextPage)
}
override fun getFilterList() = FilterList(
StatusFilter(),
TypeFilter(),
GenreFilter(),
)
override fun mangaDetailsRequest(manga: SManga): Request {
val id = manga.url.substringAfterLast("#")
val url = "$baseUrl/api/chapters?postId=$id&skip=0&take=1000&order=desc&userid="
return GET(url, headers)
}
override fun getMangaUrl(manga: SManga): String {
val slug = manga.url.substringBeforeLast("#")
return "$baseUrl/series/$slug"
}
override fun mangaDetailsParse(response: Response): SManga {
val data = response.parseAs<Post<Manga>>()
assert(!data.post.isNovel) { "Novels are unsupported" }
// genres are only returned in search call
// and not when fetching details
return data.post.toSManga(baseUrl).apply {
genre = titleCache[data.post.slug]?.getGenres()
}
}
override fun chapterListRequest(manga: SManga) = mangaDetailsRequest(manga)
override fun chapterListParse(response: Response): List<SChapter> {
val data = response.parseAs<Post<ChapterListResponse>>()
assert(!data.post.isNovel) { "Novels are unsupported" }
return data.post.chapters
.filter { it.isPublic() }
.map { it.toSChapter(data.post.slug) }
}
override fun pageListParse(response: Response): List<Page> {
val document = response.asJsoup()
return document.select("main section > img").mapIndexed { idx, img ->
Page(idx, imageUrl = img.absUrl("src"))
}
}
override fun imageUrlParse(response: Response) =
throw UnsupportedOperationException()
private inline fun <reified T> Response.parseAs(): T =
json.decodeFromString(body.string())
}
private const val perPage = 18
class VortexScans : Iken(
"Vortex Scans",
"en",
"https://vortexscans.org",
)

View File

@ -1,9 +1,7 @@
ext {
extName = 'Asura Scans'
extClass = '.AsuraScans'
themePkg = 'mangathemesia'
baseUrl = 'https://asuracomic.net'
overrideVersionCode = 4
extVersionCode = 36
}
apply from: "$rootDir/common.gradle"

View File

@ -1,21 +1,50 @@
package eu.kanade.tachiyomi.extension.en.asurascans
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesiaAlt
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.interceptor.rateLimit
import eu.kanade.tachiyomi.source.ConfigurableSource
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.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.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat
import java.util.Locale
import kotlin.concurrent.thread
class AsuraScans : ParsedHttpSource(), ConfigurableSource {
override val name = "Asura Scans"
override val baseUrl = "https://asuracomic.net"
private val apiUrl = "https://gg.asuracomic.net/api"
override val lang = "en"
override val supportsLatest = true
private val dateFormat = SimpleDateFormat("MMMM d yyyy", Locale.US)
private val preferences: SharedPreferences =
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
class AsuraScans : MangaThemesiaAlt(
"Asura Scans",
"https://asuracomic.net",
"en",
dateFormat = SimpleDateFormat("MMM d, yyyy", Locale.US),
randomUrlPrefKey = "pref_permanent_manga_url_2_en",
) {
init {
// remove legacy preferences
preferences.run {
@ -25,47 +54,256 @@ class AsuraScans : MangaThemesiaAlt(
if (contains("pref_base_url_host")) {
edit().remove("pref_base_url_host").apply()
}
}
}
override val client = super.client.newBuilder()
.rateLimit(1, 3)
.apply {
val interceptors = interceptors()
val index = interceptors.indexOfFirst { "Brotli" in it.javaClass.simpleName }
if (index >= 0) {
interceptors.add(interceptors.removeAt(index))
if (contains("pref_permanent_manga_url_2_en")) {
edit().remove("pref_permanent_manga_url_2_en").apply()
}
}
}
private val json: Json by injectLazy()
override val client = network.cloudflareClient.newBuilder()
.rateLimit(1, 3)
.build()
override val seriesDescriptionSelector = "div.desc p, div.entry-content p, div[itemprop=description]:not(:has(p))"
override val seriesArtistSelector = ".fmed b:contains(artist)+span, .infox span:contains(artist)"
override val seriesAuthorSelector = ".fmed b:contains(author)+span, .infox span:contains(author)"
override fun headersBuilder() = super.headersBuilder()
.add("Referer", "$baseUrl/")
override val pageSelector = "div.rdminimal > img, div.rdminimal > p > img, div.rdminimal > a > img, div.rdminimal > p > a > img, " +
"div.rdminimal > noscript > img, div.rdminimal > p > noscript > img, div.rdminimal > a > noscript > img, div.rdminimal > p > a > noscript > img"
override fun popularMangaRequest(page: Int): Request =
GET("$baseUrl/series?genres=&status=-1&types=-1&order=rating&page=$page", headers)
override fun popularMangaSelector() = searchMangaSelector()
override fun popularMangaFromElement(element: Element) = searchMangaFromElement(element)
override fun popularMangaNextPageSelector() = searchMangaNextPageSelector()
override fun latestUpdatesRequest(page: Int): Request =
GET("$baseUrl/series?genres=&status=-1&types=-1&order=update&page=$page", headers)
override fun latestUpdatesSelector() = searchMangaSelector()
override fun latestUpdatesFromElement(element: Element) = searchMangaFromElement(element)
override fun latestUpdatesNextPageSelector() = searchMangaNextPageSelector()
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val request = super.searchMangaRequest(page, query, filters)
if (query.isBlank()) return request
val url = "$baseUrl/series".toHttpUrl().newBuilder()
val url = request.url.newBuilder()
.addPathSegment("page/$page/")
.removeAllQueryParameters("page")
.removeAllQueryParameters("title")
.addQueryParameter("s", query)
.build()
url.addQueryParameter("page", page.toString())
return request.newBuilder()
.url(url)
.build()
if (query.isNotBlank()) {
url.addQueryParameter("name", query)
}
val genres = filters.firstInstanceOrNull<GenreFilter>()?.state.orEmpty()
.filter(Genre::state)
.map(Genre::id)
.joinToString(",")
val status = filters.firstInstanceOrNull<StatusFilter>()?.toUriPart() ?: "-1"
val types = filters.firstInstanceOrNull<TypeFilter>()?.toUriPart() ?: "-1"
val order = filters.firstInstanceOrNull<OrderFilter>()?.toUriPart() ?: "rating"
url.addQueryParameter("genres", genres)
url.addQueryParameter("status", status)
url.addQueryParameter("types", types)
url.addQueryParameter("order", order)
return GET(url.build(), headers)
}
override fun searchMangaSelector() = "div.grid > a[href]"
override fun searchMangaFromElement(element: Element) = SManga.create().apply {
setUrlWithoutDomain(element.attr("abs:href").toPermSlugIfNeeded())
title = element.selectFirst("div.block > span.block")!!.ownText()
thumbnail_url = element.selectFirst("img")?.attr("abs:src")
}
override fun searchMangaNextPageSelector() = "div.flex > a.flex.bg-themecolor:contains(Next)"
override fun getFilterList(): FilterList {
fetchFilters()
val filters = mutableListOf<Filter<*>>()
if (filtersState == FiltersState.FETCHED) {
filters += listOf(
GenreFilter("Genres", getGenreFilters()),
StatusFilter("Status", getStatusFilters()),
TypeFilter("Types", getTypeFilters()),
)
} else {
filters += Filter.Header("Press 'Reset' to attempt to fetch the filters")
}
filters += OrderFilter(
"Order by",
listOf(
Pair("Rating", "rating"),
Pair("Update", "update"),
Pair("Latest", "latest"),
Pair("Z-A", "desc"),
Pair("A-Z", "asc"),
),
)
return FilterList(filters)
}
private fun getGenreFilters(): List<Genre> = genresList.map { Genre(it.first, it.second) }
private fun getStatusFilters(): List<Pair<String, String>> = statusesList.map { it.first to it.second.toString() }
private fun getTypeFilters(): List<Pair<String, String>> = typesList.map { it.first to it.second.toString() }
private var genresList: List<Pair<String, Int>> = emptyList()
private var statusesList: List<Pair<String, Int>> = emptyList()
private var typesList: List<Pair<String, Int>> = emptyList()
private var fetchFiltersAttempts = 0
private var filtersState = FiltersState.NOT_FETCHED
private fun fetchFilters() {
if (filtersState != FiltersState.NOT_FETCHED || fetchFiltersAttempts >= 3) return
filtersState = FiltersState.FETCHING
fetchFiltersAttempts++
thread {
try {
val response = client.newCall(GET("$apiUrl/series/filters", headers)).execute()
val filters = json.decodeFromString<FiltersDto>(response.body.string())
genresList = filters.genres.filter { it.id > 0 }.map { it.name.trim() to it.id }
statusesList = filters.statuses.map { it.name.trim() to it.id }
typesList = filters.types.map { it.name.trim() to it.id }
filtersState = FiltersState.FETCHED
} catch (e: Throwable) {
filtersState = FiltersState.NOT_FETCHED
}
}
}
override fun mangaDetailsRequest(manga: SManga): Request {
if (!preferences.dynamicUrl()) return super.mangaDetailsRequest(manga)
val match = OLD_FORMAT_MANGA_REGEX.find(manga.url)?.groupValues?.get(2)
val slug = match ?: manga.url.substringAfter("/series/").substringBefore("/")
val savedSlug = preferences.slugMap[slug] ?: "$slug-"
return GET("$baseUrl/series/$savedSlug", headers)
}
override fun mangaDetailsParse(response: Response): SManga {
if (preferences.dynamicUrl()) {
val url = response.request.url.toString()
val newSlug = url.substringAfter("/series/").substringBefore("/")
val absSlug = newSlug.substringBeforeLast("-")
preferences.slugMap = preferences.slugMap.apply { put(absSlug, newSlug) }
}
return super.mangaDetailsParse(response)
}
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
title = document.selectFirst("span.text-xl.font-bold")!!.ownText()
thumbnail_url = document.selectFirst("img[alt=poster]")?.attr("abs:src")
description = document.selectFirst("span.font-medium.text-sm")?.text()
author = document.selectFirst("div.grid > div:has(h3:eq(0):containsOwn(Author)) > h3:eq(1)")?.ownText()
artist = document.selectFirst("div.grid > div:has(h3:eq(0):containsOwn(Artist)) > h3:eq(1)")?.ownText()
genre = document.select("div[class^=space] > div.flex > button.text-white").joinToString { it.ownText() }
status = parseStatus(document.selectFirst("div.flex:has(h3:eq(0):containsOwn(Status)) > h3:eq(1)")?.ownText())
}
private fun parseStatus(status: String?) = when (status) {
"Ongoing", "Season End" -> SManga.ONGOING
"Hiatus" -> SManga.ON_HIATUS
"Completed" -> SManga.COMPLETED
"Dropped" -> SManga.CANCELLED
else -> SManga.UNKNOWN
}
override fun chapterListParse(response: Response): List<SChapter> {
if (preferences.dynamicUrl()) {
val url = response.request.url.toString()
val newSlug = url.substringAfter("/series/").substringBefore("/")
val absSlug = newSlug.substringBeforeLast("-")
preferences.slugMap = preferences.slugMap.apply { put(absSlug, newSlug) }
}
return super.chapterListParse(response)
}
override fun chapterListRequest(manga: SManga) = mangaDetailsRequest(manga)
override fun chapterListSelector() = "div.scrollbar-thumb-themecolor > a.block"
override fun chapterFromElement(element: Element) = SChapter.create().apply {
setUrlWithoutDomain(element.attr("abs:href").toPermSlugIfNeeded())
name = element.selectFirst("h3:eq(0)")!!.text()
date_upload = try {
val text = element.selectFirst("h3:eq(1)")!!.ownText()
val cleanText = text.replace(CLEAN_DATE_REGEX, "$1")
dateFormat.parse(cleanText)?.time ?: 0
} catch (_: Exception) {
0L
}
}
override fun pageListRequest(chapter: SChapter): Request {
if (!preferences.dynamicUrl()) return super.pageListRequest(chapter)
val match = OLD_FORMAT_CHAPTER_REGEX.containsMatchIn(chapter.url)
if (match) throw Exception("Please refresh the chapter list before reading.")
val slug = chapter.url.substringAfter("/series/").substringBefore("/")
val savedSlug = preferences.slugMap[slug] ?: "$slug-"
return GET(baseUrl + chapter.url.replace(slug, savedSlug), headers)
}
// Skip scriptPages
override fun pageListParse(document: Document): List<Page> {
return document.select(pageSelector)
.filterNot { it.attr("src").isNullOrEmpty() }
.mapIndexed { i, img -> Page(i, document.location(), img.attr("abs:src")) }
return document.select("div > img[alt=chapter]").mapIndexed { i, element ->
Page(i, imageUrl = element.attr("abs:src"))
}
}
override fun imageUrlParse(document: Document) = throw UnsupportedOperationException()
private enum class FiltersState { NOT_FETCHED, FETCHING, FETCHED }
private inline fun <reified R> List<*>.firstInstanceOrNull(): R? =
filterIsInstance<R>().firstOrNull()
override fun setupPreferenceScreen(screen: PreferenceScreen) {
SwitchPreferenceCompat(screen.context).apply {
key = PREF_DYNAMIC_URL
title = "Automatically update dynamic URLs"
summary = "Automatically update random numbers in manga URLs.\nHelps mitigating HTTP 404 errors during update and \"in library\" marks when browsing.\nNote: This setting may require clearing database in advanced settings and migrating all manga to the same source."
setDefaultValue(true)
}.let(screen::addPreference)
}
private var SharedPreferences.slugMap: MutableMap<String, String>
get() {
val jsonMap = getString(PREF_SLUG_MAP, "{}")!!
return try {
json.decodeFromString<Map<String, String>>(jsonMap).toMutableMap()
} catch (_: Exception) {
mutableMapOf()
}
}
set(newSlugMap) {
edit()
.putString(PREF_SLUG_MAP, json.encodeToString(newSlugMap))
.apply()
}
private fun SharedPreferences.dynamicUrl(): Boolean = getBoolean(PREF_DYNAMIC_URL, true)
private fun String.toPermSlugIfNeeded(): String {
if (!preferences.dynamicUrl()) return this
val slug = this.substringAfter("/series/").substringBefore("/")
val absSlug = slug.substringBeforeLast("-")
preferences.slugMap = preferences.slugMap.apply { put(absSlug, slug) }
return this.replace(slug, absSlug)
}
companion object {
private val CLEAN_DATE_REGEX = """(\d+)(st|nd|rd|th)""".toRegex()
private val OLD_FORMAT_MANGA_REGEX = """^/manga/(\d+-)?([^/]+)/?$""".toRegex()
private val OLD_FORMAT_CHAPTER_REGEX = """^/(\d+-)?[^/]*-chapter-\d+(-\d+)*/?$""".toRegex()
private const val PREF_SLUG_MAP = "pref_slug_map"
private const val PREF_DYNAMIC_URL = "pref_dynamic_url"
}
}

View File

@ -0,0 +1,16 @@
package eu.kanade.tachiyomi.extension.en.asurascans
import kotlinx.serialization.Serializable
@Serializable
class FiltersDto(
val genres: List<FilterItemDto>,
val statuses: List<FilterItemDto>,
val types: List<FilterItemDto>,
)
@Serializable
class FilterItemDto(
val id: Int,
val name: String,
)

View File

@ -0,0 +1,17 @@
package eu.kanade.tachiyomi.extension.en.asurascans
import eu.kanade.tachiyomi.source.model.Filter
class Genre(title: String, val id: Int) : Filter.CheckBox(title)
class GenreFilter(title: String, genres: List<Genre>) : Filter.Group<Genre>(title, genres)
class StatusFilter(title: String, statuses: List<Pair<String, String>>) : UriPartFilter(title, statuses)
class TypeFilter(title: String, types: List<Pair<String, String>>) : UriPartFilter(title, types)
class OrderFilter(title: String, orders: List<Pair<String, String>>) : UriPartFilter(title, orders)
open class UriPartFilter(displayName: String, val vals: List<Pair<String, String>>) :
Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
fun toUriPart() = vals[state].second
}

View File

@ -1,9 +1,10 @@
ext {
extName = 'Blazescans'
extClass = '.Blazescans'
extName = 'Fury Manga'
extClass = '.FuryManga'
themePkg = 'mangathemesia'
baseUrl = 'https://blazetoon.com'
overrideVersionCode = 1
baseUrl = 'https://furymanga.com'
overrideVersionCode = 2
isNsfw = true
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -4,7 +4,14 @@ import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import java.util.concurrent.TimeUnit
class Blazescans : MangaThemesia("Blazescans", "https://blazetoon.com", "en") {
class FuryManga : MangaThemesia(
"Fury Manga",
"https://furymanga.com",
"en",
"/comics",
) {
override val id = 3912200442923601567
override val client = super.client.newBuilder()
.rateLimit(1, 2, TimeUnit.SECONDS)
.build()

View File

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

View File

@ -23,7 +23,7 @@ class ComicExtra : ParsedHttpSource() {
override val name = "ComicExtra"
override val baseUrl = "https://comicextra.org"
override val baseUrl = "https://comixextra.com"
override val lang = "en"

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

View File

@ -0,0 +1,247 @@
package eu.kanade.tachiyomi.extension.en.darthsdroids
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.interceptor.rateLimitHost
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.model.UpdateStrategy
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
import okhttp3.Response
import rx.Observable
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.concurrent.TimeUnit
// Dear Darths & Droids creators:
// Im sorry if this extension causes too much traffic for your site.
// Unfortunately we cant just download and use your Zip downloads.
// Shall problems arise, well reduce the rate limit.
class DarthsDroids : HttpSource() {
override val name = "Darths & Droids"
override val baseUrl = "https://www.darthsanddroids.net"
override val lang = "en"
override val supportsLatest = false
override val client = network.cloudflareClient.newBuilder()
.rateLimitHost(baseUrl.toHttpUrl(), 10, 1, TimeUnit.SECONDS)
.build()
// Picks a thumbnail from the profile pictures of the »cast« pages:
// https://www.darthsanddroids.net/cast/
//
// Where possible, pick a thumbnail from the corresponding books
// cast page. Try to avoid having a character appear more than once
// as thumbnail, giving all main characters equal amounts of spotlight.
// Pick a character people would intuïtively associate with the
// corresponding film, like Qui-Gon for Phantom Menace or Leia for
// A New Hope.
//
// If a book doesnt have its own cast page, try source a fitting
// profile picture from a different page. Avoid sourcing thumbnails
// from a different website.
private fun dndThumbnailUrlForTitle(nthManga: Int): String = when (nthManga) {
// The numbers are assigned in order of appearance of a book on the archive page.
0 -> "$baseUrl/cast/QuiGon.jpg" // D&D1
1 -> "$baseUrl/cast/Anakin2.jpg" // D&D2
2 -> "$baseUrl/cast/ObiWan3.jpg" // D&D3
3 -> "$baseUrl/cast/JarJar2.jpg" // JJ
4 -> "$baseUrl/cast/Leia4.jpg" // D&D4
5 -> "$baseUrl/cast/Han5.jpg" // D&D5
6 -> "$baseUrl/cast/Luke6.jpg" // D&D6
7 -> "$baseUrl/cast/Cassian.jpg" // R1
8 -> "$baseUrl/cast/C3PO4.jpg" // Muppets
9 -> "$baseUrl/cast/Finn7.jpg" // D&D7
10 -> "$baseUrl/cast/Han4.jpg" // Solo
11 -> "$baseUrl/cast/Hux8.jpg" // D&D8
// Just some nonsense fallback that screams »Star Wars« but is also so recognisably
// OT that one can understand its a mere fallback. Better thumbnails require an
// extension update.
else -> "$baseUrl/cast/Vader4.jpg"
}
private fun dndManga(archiveUrl: String, mangaTitle: String, mangaStatus: Int, nthManga: Int): SManga = SManga.create().apply {
setUrlWithoutDomain(archiveUrl)
thumbnail_url = dndThumbnailUrlForTitle(nthManga)
title = mangaTitle
author = "David Morgan-Mar & Co."
artist = "David Morgan-Mar & Co."
description = """What if Star Wars as we know it didn't exist, but instead the
|plot of the movies was being made up on the spot by players of
|a Tabletop Game?
|
|Well, for one, the results might actually make a lot more sense,
|from an out-of-story point of view
""".trimMargin()
genre = "Campaign Comic, Comedy, Space Opera, Science Fiction"
status = mangaStatus
update_strategy = when (mangaStatus) {
SManga.COMPLETED -> UpdateStrategy.ONLY_FETCH_ONCE
else -> UpdateStrategy.ALWAYS_UPDATE
}
initialized = true
}
override fun popularMangaRequest(page: Int): Request =
GET("$baseUrl/archive.html", headers)
// The book and page archive feeds are rather special for this webcomic.
// The main archive page `/archive.html` is a combined feed for both,
// all previous and finished books, as well as all pages of the book that
// is currently releasing. Every finished book gets its own archive page
// like `/archive4.html` or `/archiveJJ.html` into which all page links
// are moved. So whatever book is currently releasing in `/archive.html`
// will eventually be moved into its own archive, and itll instead
// appear as a book-archive link in `/archive.html`.
//
// This means a few things:
// • The currently releasing book eventually changes its `url`!
// • The URL of the currently releasing book will be taken over by
// whichever new book comes next.
// • There is no deterministic way of guessing a books future
// archive name.
// ◦ This is especially apparent with the »Solo« book, whichs
// archive page is `/solo/`, while all others are `/archiveX.html`.
//
// So eventually, Tachiyomi & Co. will glitch out once a currently
// releasing book finishes. People will find the current books page
// feed to be empty. Even worse, they may find it starting anew with
// different pages. A manual refresh *should* change the books `url`
// to its new archive page, and all reading progress should be preserved.
// Then the user will have to manually add the new book to their library.
//
// The alternative would be to have a pseudo book »<Title> (ongoing)«
// that just disappears, being replaced by »<Title>«. But i think thats
// even worse in terms of user experience. Maybe one day well have new
// extension APIs for dealing with unique webcomic weirdnesses. cause
// trust me, theres worse.
override fun popularMangaParse(response: Response): MangasPage {
val mainArchive = response.asJsoup()
val archiveData = mainArchive.select("div.text > table.text > tbody > tr")
val mangas = mutableListOf<SManga>()
var nextMangaTitle = name
var nthManga = 0
run stop@{
archiveData.forEach {
val maybeTitle = it.selectFirst("th")?.text()
if (maybeTitle != null) {
nextMangaTitle = "$name $maybeTitle"
} else {
val maybeArchive = it.selectFirst("""td[colspan="3"] > a""")?.absUrl("href")
if (maybeArchive != null) {
mangas.add(dndManga(maybeArchive, nextMangaTitle, SManga.COMPLETED, nthManga++))
} else {
// We reached the end, assuming the page layout stays consistent beyond D&D8.
// Thus, we append our final manga with this current page as its archive.
// Unfortunately this means we will needlessly fetch this page twice.
mangas.add(dndManga("/archive.html", nextMangaTitle, SManga.ONGOING, nthManga))
return@stop
}
}
}
}
return MangasPage(mangas, false)
}
// Not efficient, but the simplest way for me to refresh.
// We also cant really use the `mangaDetailsRequest + mangaDetailsParse`
// approach, for we actually expect one of the books `url`s to change.
override fun fetchMangaDetails(manga: SManga): Observable<SManga> =
fetchPopularManga(0)
.map { mangasPage ->
mangasPage
.mangas
// Do not test for URL-equality, for the last book will always
// eventually migrate its archive page from `/archive.html` to
// its own page.
.first { it.title == manga.title }
}
// This implementation here is needlessly complicated, for it has to automatically detect
// whether were in a date-annotated archive, the main archive, or a dateless archive.
// All three are largely similar, there are just *some* (annoying) differences we have to
// deal with.
override fun chapterListParse(response: Response): List<SChapter> {
val archivePages = response.asJsoup()
// For books where all pages released the same day, there is no page date column,
// so instead we grab the release date of the archive page itself from its footer.
val pageDate = archivePages
.select("""br + i""")
.mapNotNull { EXTR_PAGE_DATE.find(it.text())?.groupValues?.getOrNull(1) }
.map { PAGE_DATE_FMT.parse(it)?.time }
.firstOrNull()
?: 0L
var i = 0
return archivePages
.select("""div.text > table.text > tbody > tr""")
.mapNotNull {
val pageData = it.select("""td""")
var pageAnchor = pageData.getOrNull(2)?.selectFirst("a")
// null for »Intermission«, main archive, dateless archive,…
if (pageAnchor != null) {
SChapter.create().apply {
name = pageAnchor!!.text()
chapter_number = (i++).toFloat()
date_upload = runCatching {
DATE_FMT.parse(pageData[0].text())!!.time
}.getOrDefault(0L)
setUrlWithoutDomain(pageAnchor!!.absUrl("href"))
}
} else if (!pageData.hasAttr("colspan")) {
// Are we in a dateless archive?
pageAnchor = pageData.getOrNull(0)?.selectFirst("a")
if (pageAnchor != null) {
SChapter.create().apply {
name = pageAnchor.text()
chapter_number = (i++).toFloat()
date_upload = pageDate
setUrlWithoutDomain(pageAnchor.absUrl("href"))
}
} else { null }
} else { null }
}
.reversed()
}
override fun pageListParse(response: Response): List<Page> =
// Careful. For almost all images its `div.center>p>img`, except for pages released on
// Aprils Fools day, when its `div.center>p>a>img`. We could still add the `p` in
// between, but it was decided to leave it out, in case yet another *almost* same
// page layout pops up in the future.
//
// For example, this episode was released during Aprils Fools day.
// https://www.darthsanddroids.net/episodes/0082.html
response
.asJsoup()
.select("""div.center img""")
.mapIndexed { i, img ->
Page(
index = i,
imageUrl = img.absUrl("src"),
)
}
override fun mangaDetailsParse(response: Response): SManga = throw UnsupportedOperationException()
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> = throw UnsupportedOperationException()
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = throw UnsupportedOperationException()
override fun searchMangaParse(response: Response): MangasPage = throw UnsupportedOperationException()
override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException()
override fun latestUpdatesParse(response: Response): MangasPage = throw UnsupportedOperationException()
override fun imageUrlRequest(page: Page): Request = throw UnsupportedOperationException()
override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException()
companion object {
private val DATE_FMT = SimpleDateFormat("EEE d MMM, yyyy", Locale.US)
private val EXTR_PAGE_DATE = """Published\:\s+(\w+,\s+\d+\s+\w+,\s+\d+\;\s+\d+\:\d+\:\d+\s+\w+)""".toRegex()
private val PAGE_DATE_FMT = SimpleDateFormat("EEEEE, d MMMMM, yyyy; HH:mm:ss zzz", Locale.US)
}
}

View File

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

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