MangaHub: move to multisrc (#10735)

* MangaHub: move to multisrc

* MangaHub: add rate limiting

* MangaHub: add icons

* Fix duplicate manga

* MangaHub: Fix non-mangahub.io sources

* MangaHub: Add isNsfw where appropriate
This commit is contained in:
Vetle Ledaal 2022-02-06 21:17:40 +00:00 committed by GitHub
parent 32f76a2462
commit 651480617d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
83 changed files with 471 additions and 123 deletions

View File

@ -0,0 +1,4 @@
dependencies {
implementation project(':lib-ratelimit')
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

View File

@ -0,0 +1,20 @@
package eu.kanade.tachiyomi.extension.en.mangafoxfun
import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor
import eu.kanade.tachiyomi.multisrc.mangahub.MangaHub
import okhttp3.OkHttpClient
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.concurrent.TimeUnit
class MangaFoxFun : MangaHub(
"MangaFox.fun",
"https://mangafox.fun",
"en"
) {
override val client: OkHttpClient = super.client.newBuilder()
.addInterceptor(RateLimitInterceptor(1, 2, TimeUnit.SECONDS))
.build()
override val serverId = "mf01"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

View File

@ -0,0 +1,20 @@
package eu.kanade.tachiyomi.extension.en.mangahereonl
import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor
import eu.kanade.tachiyomi.multisrc.mangahub.MangaHub
import okhttp3.OkHttpClient
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.concurrent.TimeUnit
class MangaHereOnl : MangaHub(
"MangaHere.onl",
"https://mangahere.onl",
"en"
) {
override val client: OkHttpClient = super.client.newBuilder()
.addInterceptor(RateLimitInterceptor(1, 2, TimeUnit.SECONDS))
.build()
override val serverId = "mh01"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

View File

@ -0,0 +1,20 @@
package eu.kanade.tachiyomi.extension.en.mangahubio
import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor
import eu.kanade.tachiyomi.multisrc.mangahub.MangaHub
import okhttp3.OkHttpClient
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.concurrent.TimeUnit
class MangaHubIo : MangaHub(
"MangaHub",
"https://mangahub.io",
"en"
) {
override val client: OkHttpClient = super.client.newBuilder()
.addInterceptor(RateLimitInterceptor(1, 2, TimeUnit.SECONDS))
.build()
override val serverId = "m01"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

View File

@ -0,0 +1,20 @@
package eu.kanade.tachiyomi.extension.en.mangakalotfun
import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor
import eu.kanade.tachiyomi.multisrc.mangahub.MangaHub
import okhttp3.OkHttpClient
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.concurrent.TimeUnit
class MangakakalotFun : MangaHub(
"Mangakakalot.fun",
"https://mangakakalot.fun",
"en"
) {
override val client: OkHttpClient = super.client.newBuilder()
.addInterceptor(RateLimitInterceptor(1, 2, TimeUnit.SECONDS))
.build()
override val serverId = "mn01"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

View File

@ -0,0 +1,20 @@
package eu.kanade.tachiyomi.extension.en.manganel
import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor
import eu.kanade.tachiyomi.multisrc.mangahub.MangaHub
import okhttp3.OkHttpClient
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.concurrent.TimeUnit
class MangaNel : MangaHub(
"MangaNel",
"https://manganel.me",
"en"
) {
override val client: OkHttpClient = super.client.newBuilder()
.addInterceptor(RateLimitInterceptor(1, 2, TimeUnit.SECONDS))
.build()
override val serverId = "mn05"
}

View File

@ -0,0 +1,20 @@
package eu.kanade.tachiyomi.extension.en.mangaonlinefun
import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor
import eu.kanade.tachiyomi.multisrc.mangahub.MangaHub
import okhttp3.OkHttpClient
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.concurrent.TimeUnit
class MangaOnlineFun : MangaHub(
"MangaOnline.fun",
"https://mangaonline.fun",
"en"
) {
override val client: OkHttpClient = super.client.newBuilder()
.addInterceptor(RateLimitInterceptor(1, 2, TimeUnit.SECONDS))
.build()
override val serverId = "m02"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

View File

@ -0,0 +1,20 @@
package eu.kanade.tachiyomi.extension.en.mangapandaonl
import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor
import eu.kanade.tachiyomi.multisrc.mangahub.MangaHub
import okhttp3.OkHttpClient
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.concurrent.TimeUnit
class MangaPandaOnl : MangaHub(
"MangaPanda.onl",
"https://mangapanda.onl",
"en"
) {
override val client: OkHttpClient = super.client.newBuilder()
.addInterceptor(RateLimitInterceptor(1, 2, TimeUnit.SECONDS))
.build()
override val serverId = "mr02"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

View File

@ -0,0 +1,20 @@
package eu.kanade.tachiyomi.extension.en.mangareadersite
import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor
import eu.kanade.tachiyomi.multisrc.mangahub.MangaHub
import okhttp3.OkHttpClient
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.concurrent.TimeUnit
class MangaReaderSite : MangaHub(
"MangaReader.site",
"https://mangareader.site",
"en"
) {
override val client: OkHttpClient = super.client.newBuilder()
.addInterceptor(RateLimitInterceptor(1, 2, TimeUnit.SECONDS))
.build()
override val serverId = "mr01"
}

View File

@ -0,0 +1,20 @@
package eu.kanade.tachiyomi.extension.en.mangatoday
import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor
import eu.kanade.tachiyomi.multisrc.mangahub.MangaHub
import okhttp3.OkHttpClient
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.concurrent.TimeUnit
class MangaToday : MangaHub(
"MangaToday",
"https://mangatoday.fun",
"en"
) {
override val client: OkHttpClient = super.client.newBuilder()
.addInterceptor(RateLimitInterceptor(1, 2, TimeUnit.SECONDS))
.build()
override val serverId = "m03"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

View File

@ -0,0 +1,20 @@
package eu.kanade.tachiyomi.extension.en.mangatownhub
import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor
import eu.kanade.tachiyomi.multisrc.mangahub.MangaHub
import okhttp3.OkHttpClient
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.concurrent.TimeUnit
class MangaTownHub : MangaHub(
"MangaTown (unoriginal)",
"https://manga.town",
"en"
) {
override val client: OkHttpClient = super.client.newBuilder()
.addInterceptor(RateLimitInterceptor(1, 2, TimeUnit.SECONDS))
.build()
override val serverId = "mt01"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

View File

@ -0,0 +1,20 @@
package eu.kanade.tachiyomi.extension.en.onemangaco
import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor
import eu.kanade.tachiyomi.multisrc.mangahub.MangaHub
import okhttp3.OkHttpClient
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.concurrent.TimeUnit
class OneMangaCo : MangaHub(
"1Manga.co",
"https://1manga.co",
"en"
) {
override val client: OkHttpClient = super.client.newBuilder()
.addInterceptor(RateLimitInterceptor(1, 2, TimeUnit.SECONDS))
.build()
override val serverId = "mn03"
}

View File

@ -0,0 +1,20 @@
package eu.kanade.tachiyomi.extension.en.onemangainfo
import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor
import eu.kanade.tachiyomi.multisrc.mangahub.MangaHub
import okhttp3.OkHttpClient
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.concurrent.TimeUnit
class OneMangaInfo : MangaHub(
"OneManga.info",
"https://onemanga.info",
"en"
) {
override val client: OkHttpClient = super.client.newBuilder()
.addInterceptor(RateLimitInterceptor(1, 2, TimeUnit.SECONDS))
.build()
override val serverId = "mn02"
}

View File

@ -1,117 +1,181 @@
package eu.kanade.tachiyomi.extension.en.mangahub
package eu.kanade.tachiyomi.multisrc.mangahub
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import uy.kohesive.injekt.injectLazy
import java.net.URL
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
class Mangahub : ParsedHttpSource() {
override val name = "Mangahub"
override val baseUrl = "https://www.mangahub.io"
override val lang = "en"
abstract class MangaHub(
override val name: String,
override val baseUrl: String,
override val lang: String,
private val dateFormat: SimpleDateFormat = SimpleDateFormat("MM-dd-yyyy", Locale.US)
) : ParsedHttpSource() {
override val supportsLatest = true
override fun popularMangaSelector() = "#mangalist div.media-manga.media"
private val json: Json by injectLazy()
override fun latestUpdatesSelector() = popularMangaSelector()
protected abstract val serverId: String
override fun popularMangaRequest(page: Int): Request {
return GET("$baseUrl/popular/page/$page", headers)
}
// Popular
override fun popularMangaRequest(page: Int): Request =
GET("$baseUrl/popular/page/$page", headers)
override fun latestUpdatesRequest(page: Int): Request {
return GET("$baseUrl/updates/page/$page", headers)
}
override fun popularMangaParse(response: Response): MangasPage =
searchMangaParse(response)
override fun popularMangaFromElement(element: Element): SManga {
val manga = SManga.create()
override fun popularMangaSelector(): String =
"#mangalist div.media-manga.media"
val titleElement = element.select(".media-heading > a").first()
manga.title = titleElement.text()
manga.setUrlWithoutDomain(URL(titleElement.attr("href")).path)
manga.thumbnail_url = element.select("img.manga-thumb.list-item-thumb")
?.first()?.attr("src")
override fun popularMangaFromElement(element: Element): SManga =
searchMangaFromElement(element)
return manga
}
override fun popularMangaNextPageSelector(): String? =
searchMangaNextPageSelector()
override fun latestUpdatesFromElement(element: Element): SManga {
return popularMangaFromElement(element)
}
// Latest
override fun latestUpdatesRequest(page: Int): Request =
GET("$baseUrl/updates/page/$page", headers)
override fun popularMangaNextPageSelector() = "ul.pager li.next > a"
override fun latestUpdatesParse(response: Response): MangasPage =
searchMangaParse(response)
override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector()
override fun latestUpdatesSelector(): String =
popularMangaSelector()
override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
override fun latestUpdatesFromElement(element: Element): SManga =
searchMangaFromElement(element)
override fun mangaDetailsParse(document: Document): SManga {
val manga = SManga.create()
manga.title = document.select("h1._3xnDj").first().ownText()
manga.author = document.select("._3QCtP > div:nth-child(2) > div:nth-child(1) > span:nth-child(2)")?.first()?.text()
manga.artist = document.select("._3QCtP > div:nth-child(2) > div:nth-child(2) > span:nth-child(2)")?.first()?.text()
manga.genre = document.select("._3Czbn a")?.joinToString { it.text() }
manga.description = document.select("div#noanim-content-tab-pane-99 p.ZyMp7")?.first()?.text()
manga.thumbnail_url = document.select("img.img-responsive")?.first()
?.attr("src")
override fun latestUpdatesNextPageSelector(): String? =
searchMangaNextPageSelector()
document.select("._3QCtP > div:nth-child(2) > div:nth-child(3) > span:nth-child(2)")?.first()?.text()?.also { statusText ->
when {
statusText.contains("ongoing", true) -> manga.status = SManga.ONGOING
statusText.contains("completed", true) -> manga.status = SManga.COMPLETED
else -> manga.status = SManga.UNKNOWN
// Search
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
// https://mangahub.io/search/page/1?q=a&order=POPULAR&genre=all
val url = "$baseUrl/search/page/$page".toHttpUrl().newBuilder()
.addQueryParameter("q", query)
(if (filters.isEmpty()) getFilterList() else filters).forEach { filter ->
when (filter) {
is OrderBy -> {
val order = filter.values[filter.state]
url.addQueryParameter("order", order.key)
}
is GenreList -> {
val genre = filter.values[filter.state]
url.addQueryParameter("genre", genre.key)
}
else -> {}
}
}
return GET(url.toString(), headers)
}
override fun searchMangaParse(response: Response): MangasPage {
val document = response.asJsoup()
/*
* To remove duplicates we group by the thumbnail_url, which is
* common between duplicates. The duplicates have a suffix in the
* url "-by-{name}". Here we select the shortest url, to avoid
* removing manga that has "by" in the title already.
* Example:
* /manga/tales-of-demons-and-gods (kept)
* /manga/tales-of-demons-and-gods-by-mad-snail (removed)
* /manga/leveling-up-by-only-eating (kept)
*/
val mangas = document.select(searchMangaSelector()).map { element ->
searchMangaFromElement(element)
}.groupBy { it.thumbnail_url }.mapValues { (_, values) ->
values.minByOrNull { it.url.length }!!
}.values.toList()
val hasNextPage = searchMangaNextPageSelector()?.let { selector ->
document.select(selector).first()
} != null
return MangasPage(mangas, hasNextPage)
}
override fun searchMangaSelector() = "div#mangalist div.media-manga.media"
override fun searchMangaFromElement(element: Element): SManga = SManga.create().apply {
val titleElement = element.select(".media-heading > a").first()
setUrlWithoutDomain(titleElement.attr("abs:href"))
title = titleElement.text()
thumbnail_url = element
.select("img.manga-thumb.list-item-thumb")
?.first()?.attr("abs:src")
}
override fun searchMangaNextPageSelector() = "ul.pager li.next > a"
// Details
override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply {
title = document.select("h1._3xnDj").first().ownText()
author = document
.select("._3QCtP > div:nth-child(2) > div:nth-child(1) > span:nth-child(2)")
?.first()?.text()
artist = document
.select("._3QCtP > div:nth-child(2) > div:nth-child(2) > span:nth-child(2)")
?.first()?.text()
genre = document.select("._3Czbn a")?.joinToString { it.text() }
description = document.select("div#noanim-content-tab-pane-99 p.ZyMp7")?.first()?.text()
thumbnail_url = document.select("img.img-responsive")?.first()?.attr("abs:src")
document.select("._3QCtP > div:nth-child(2) > div:nth-child(3) > span:nth-child(2)")
?.first()?.text()?.also { statusText ->
status = when {
statusText.contains("ongoing", true) -> SManga.ONGOING
statusText.contains("completed", true) -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
}
// add alternative name to manga description
val altName = "Alternative Name: "
document.select("h1 small").firstOrNull()?.ownText()?.let {
if (it.isBlank().not()) {
manga.description = when {
manga.description.isNullOrBlank() -> altName + it
else -> manga.description + "\n\n$altName" + it
description = when {
description.isNullOrBlank() -> altName + it
else -> description + "\n\n$altName" + it
}
}
}
return manga
}
// Chapters
override fun chapterListSelector() = ".tab-content .tab-pane li.list-group-item > a"
override fun chapterFromElement(element: Element): SChapter {
val chapter = SChapter.create()
chapter.setUrlWithoutDomain(element.attr("href"))
chapter.name = element.select("span._8Qtbo").text()
chapter.date_upload = element.select("small.UovLc").first()?.text()?.let { parseChapterDate(it) } ?: 0
return chapter
override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply {
setUrlWithoutDomain(element.attr("abs:href"))
name = element.select("span._8Qtbo").text()
date_upload = element.select("small.UovLc").first()?.text()
?.let { parseChapterDate(it) } ?: 0
}
private fun parseChapterDate(date: String): Long {
@ -144,80 +208,59 @@ class Mangahub : ParsedHttpSource() {
// parses: "12-20-2019" and defaults everything that wasn't taken into account to 0
else -> {
try {
parsedDate = SimpleDateFormat("MM-dd-yyyy", Locale.US).parse(date)?.time ?: 0L
} catch (e: ParseException) { /*nothing to do, parsedDate is initialized with 0L*/ }
parsedDate = dateFormat.parse(date)?.time ?: 0L
} catch (e: ParseException) {
/* nothing to do, parsedDate is initialized with 0L */
}
}
}
return parsedDate
}
// Pages
override fun pageListRequest(chapter: SChapter): Request {
val jsonHeaders = headers.newBuilder().add("Content-Type", "application/json").build()
val slug = chapter.url.substringAfter("chapter/").substringBefore("/")
val number = chapter.url.substringAfter("chapter-").removeSuffix("/")
val slug = chapter.url
.substringAfter("chapter/")
.substringBefore("/")
val number = chapter.url
.substringAfter("chapter-")
.removeSuffix("/")
val body =
"{\"query\":\"{chapter(x:m01,slug:\\\"$slug\\\",number:$number){id,title,mangaID,number,slug,date,pages,noAd,manga{id,title,slug,mainSlug,author,isWebtoon,isYaoi,isPorn,isSoftPorn,unauthFile,isLicensed}}}\"}".toRequestBody(
"{\"query\":\"{chapter(x:$serverId,slug:\\\"$slug\\\",number:$number){id,title,mangaID,number,slug,date,pages,noAd,manga{id,title,slug,mainSlug,author,isWebtoon,isYaoi,isPorn,isSoftPorn,unauthFile,isLicensed}}}\"}".toRequestBody(
null
)
return POST("https://api.mghubcdn.com/graphql", jsonHeaders, body)
}
private val json: Json by injectLazy()
override fun pageListParse(response: Response): List<Page> {
val cdn = "https://img.mghubcdn.com/file/imghub"
val chapterObject = json
.decodeFromString<GraphQLDataDto<ChapterDto>>(response.body!!.string())
return json.decodeFromString<JsonObject>(response.body!!.string())["data"]!!
.jsonObject["chapter"]!!
.jsonObject["pages"]!!.jsonPrimitive.content
.removeSurrounding("\"").replace("\\", "")
.let { cleaned ->
val jsonObject = json.decodeFromString<JsonObject>(cleaned)
jsonObject.keys.map { key -> jsonObject[key]!!.jsonPrimitive.content }
}
.mapIndexed { i, tail -> Page(i, "", "$cdn/$tail") }
val pagesObject = json
.decodeFromString<JsonObject>(chapterObject.data.chapter.pages)
val pages = pagesObject.values.map { it.jsonPrimitive.content }
return pages.mapIndexed { i, path -> Page(i, "", "$cdn/$path") }
}
override fun pageListParse(document: Document): List<Page> = throw UnsupportedOperationException("Not used")
override fun pageListParse(document: Document): List<Page> =
throw UnsupportedOperationException("Not used.")
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used")
// https://mangahub.io/search/page/1?q=a&order=POPULAR&genre=all
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val url = "$baseUrl/search/page/$page".toHttpUrlOrNull()?.newBuilder()!!.addQueryParameter("q", query)
(if (filters.isEmpty()) getFilterList() else filters).forEach { filter ->
when (filter) {
is OrderBy -> {
val order = filter.values[filter.state]
url.addQueryParameter("order", order.key)
}
is GenreList -> {
val genre = filter.values[filter.state]
url.addQueryParameter("genre", genre.key)
}
}
}
return GET(url.toString(), headers)
}
override fun searchMangaSelector() = "div#mangalist div.media-manga.media"
override fun searchMangaFromElement(element: Element): SManga {
return popularMangaFromElement(element)
}
// Image
override fun imageUrlParse(document: Document): String =
throw UnsupportedOperationException("Not used.")
// Filters
private class Genre(title: String, val key: String) : Filter.TriState(title) {
override fun toString(): String {
return name
}
override fun toString(): String = name
}
private class Order(title: String, val key: String) : Filter.TriState(title) {
override fun toString(): String {
return name
}
override fun toString(): String = name
}
private class OrderBy(orders: Array<Order>) : Filter.Select<Order>("Order", orders, 0)
@ -305,3 +348,42 @@ class Mangahub : ParsedHttpSource() {
Genre("Yuri", "yuri")
)
}
// DTO
@Serializable
data class GraphQLDataDto<T>(
val data: T
)
@Serializable
data class ChapterDto(
val chapter: ChapterInnerDto
)
@Serializable
data class ChapterInnerDto(
val date: String,
val id: Int,
val manga: MangaInnerDto,
@SerialName("mangaID") val mangaId: Int,
val noAd: Boolean,
val number: Int,
val pages: String,
val slug: String,
val title: String
)
@Serializable
data class MangaInnerDto(
val author: String,
val id: Int,
val isLicensed: Boolean,
val isPorn: Boolean,
val isSoftPorn: Boolean,
val isWebtoon: Boolean,
val isYaoi: Boolean,
val mainSlug: String,
val slug: String,
val title: String,
val unauthFile: Boolean
)

View File

@ -0,0 +1,36 @@
package eu.kanade.tachiyomi.multisrc.mangahub
import generator.ThemeSourceData.SingleLang
import generator.ThemeSourceGenerator
class MangaHubGenerator : ThemeSourceGenerator {
override val themePkg = "mangahub"
override val themeClass = "MangaHub"
override val baseVersionCode: Int = 1
override val sources = listOf(
SingleLang("1Manga.co", "https://1manga.co", "en", isNsfw = true, className = "OneMangaCo"),
SingleLang("MangaFox.fun", "https://mangafox.fun", "en", isNsfw = true, className = "MangaFoxFun"),
SingleLang("MangaHere.onl", "https://mangahere.onl", "en", isNsfw = true, className = "MangaHereOnl"),
SingleLang("MangaHub", "https://mangahub.io", "en", isNsfw = true, overrideVersionCode = 10, className = "MangaHubIo"),
SingleLang("Mangakakalot.fun", "https://mangakakalot.fun", "en", isNsfw = true, className = "MangakakalotFun"),
SingleLang("MangaNel", "https://manganel.me", "en", isNsfw = true),
SingleLang("MangaOnline.fun", "https://mangaonline.fun", "en", isNsfw = true, className = "MangaOnlineFun"),
SingleLang("MangaPanda.onl", "https://mangapanda.onl", "en", className = "MangaPandaOnl"),
SingleLang("MangaReader.site", "https://mangareader.site", "en", className = "MangaReaderSite"),
SingleLang("MangaToday", "https://mangatoday.fun", "en", isNsfw = true),
SingleLang("MangaTown (unoriginal)", "https://manga.town", "en", isNsfw = true, className = "MangaTownHub"),
// SingleLang("MF Read Online", "https://mangafreereadonline.com", "en", isNsfw = true), // different pageListParse logic
// SingleLang("OneManga.info", "https://onemanga.info", "en", isNsfw = true, className = "OneMangaInfo"), // Some chapters link to 1manga.co, hard to filter
)
companion object {
@JvmStatic
fun main(args: Array<String>) {
MangaHubGenerator().createAll()
}
}
}

View File

@ -1,2 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="eu.kanade.tachiyomi.extension" />

View File

@ -1,12 +0,0 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization'
ext {
extName = 'Mangahub'
pkgNameSuffix = 'en.mangahub'
extClass = '.Mangahub'
extVersionCode = 10
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB