Iken Multisrc, move Vortex Scan and MangaGalaxy (#4043)

This commit is contained in:
AwkwardPeak7 2024-07-17 14:57:30 +05:00 committed by Draff
parent 29e4234bb8
commit 17ef845943
No known key found for this signature in database
GPG Key ID: E8A89F3211677653
13 changed files with 187 additions and 188 deletions

View File

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

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

@ -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,9 @@
ext {
extName = 'Manga Galaxy'
extClass = '.MangaGalaxy'
themePkg = 'mangathemesia'
baseUrl = 'https://mangagalaxy.me'
overrideVersionCode = 9
themePkg = 'iken'
baseUrl = 'https://mangagalaxy.org'
overrideVersionCode = 39
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -1,13 +1,12 @@
package eu.kanade.tachiyomi.extension.en.mangagalaxy
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
import eu.kanade.tachiyomi.multisrc.iken.Iken
class MangaGalaxy : MangaThemesia(
class MangaGalaxy : Iken(
"Manga Galaxy",
"https://mangagalaxy.me",
"en",
mangaUrlDirectory = "/series",
"https://mangagalaxy.org",
) {
// moved from Madara to MangaThemesia
override val versionId = 2
// moved from Madara to MangaThemesia to Iken
override val versionId = 3
}