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.GET
import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList 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.Page
import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.ParsedHttpSource 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.decodeFromString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.jsonPrimitive
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response import okhttp3.Response
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.net.URL
import java.text.ParseException import java.text.ParseException
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Calendar import java.util.Calendar
import java.util.Locale import java.util.Locale
class Mangahub : ParsedHttpSource() { abstract class MangaHub(
override val name: String,
override val name = "Mangahub" override val baseUrl: String,
override val lang: String,
override val baseUrl = "https://www.mangahub.io" private val dateFormat: SimpleDateFormat = SimpleDateFormat("MM-dd-yyyy", Locale.US)
) : ParsedHttpSource() {
override val lang = "en"
override val supportsLatest = true 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 { // Popular
return GET("$baseUrl/popular/page/$page", headers) override fun popularMangaRequest(page: Int): Request =
} GET("$baseUrl/popular/page/$page", headers)
override fun latestUpdatesRequest(page: Int): Request { override fun popularMangaParse(response: Response): MangasPage =
return GET("$baseUrl/updates/page/$page", headers) searchMangaParse(response)
}
override fun popularMangaFromElement(element: Element): SManga { override fun popularMangaSelector(): String =
val manga = SManga.create() "#mangalist div.media-manga.media"
val titleElement = element.select(".media-heading > a").first() override fun popularMangaFromElement(element: Element): SManga =
manga.title = titleElement.text() searchMangaFromElement(element)
manga.setUrlWithoutDomain(URL(titleElement.attr("href")).path)
manga.thumbnail_url = element.select("img.manga-thumb.list-item-thumb")
?.first()?.attr("src")
return manga override fun popularMangaNextPageSelector(): String? =
} searchMangaNextPageSelector()
override fun latestUpdatesFromElement(element: Element): SManga { // Latest
return popularMangaFromElement(element) 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 { override fun latestUpdatesNextPageSelector(): String? =
val manga = SManga.create() searchMangaNextPageSelector()
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")
document.select("._3QCtP > div:nth-child(2) > div:nth-child(3) > span:nth-child(2)")?.first()?.text()?.also { statusText -> // Search
when { override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
statusText.contains("ongoing", true) -> manga.status = SManga.ONGOING // https://mangahub.io/search/page/1?q=a&order=POPULAR&genre=all
statusText.contains("completed", true) -> manga.status = SManga.COMPLETED val url = "$baseUrl/search/page/$page".toHttpUrl().newBuilder()
else -> manga.status = SManga.UNKNOWN .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 // add alternative name to manga description
val altName = "Alternative Name: " val altName = "Alternative Name: "
document.select("h1 small").firstOrNull()?.ownText()?.let { document.select("h1 small").firstOrNull()?.ownText()?.let {
if (it.isBlank().not()) { if (it.isBlank().not()) {
manga.description = when { description = when {
manga.description.isNullOrBlank() -> altName + it description.isNullOrBlank() -> altName + it
else -> manga.description + "\n\n$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 chapterListSelector() = ".tab-content .tab-pane li.list-group-item > a"
override fun chapterFromElement(element: Element): SChapter { override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply {
val chapter = SChapter.create() setUrlWithoutDomain(element.attr("abs:href"))
name = element.select("span._8Qtbo").text()
chapter.setUrlWithoutDomain(element.attr("href")) date_upload = element.select("small.UovLc").first()?.text()
chapter.name = element.select("span._8Qtbo").text() ?.let { parseChapterDate(it) } ?: 0
chapter.date_upload = element.select("small.UovLc").first()?.text()?.let { parseChapterDate(it) } ?: 0
return chapter
} }
private fun parseChapterDate(date: String): Long { 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 // parses: "12-20-2019" and defaults everything that wasn't taken into account to 0
else -> { else -> {
try { try {
parsedDate = SimpleDateFormat("MM-dd-yyyy", Locale.US).parse(date)?.time ?: 0L parsedDate = dateFormat.parse(date)?.time ?: 0L
} catch (e: ParseException) { /*nothing to do, parsedDate is initialized with 0L*/ } } catch (e: ParseException) {
/* nothing to do, parsedDate is initialized with 0L */
}
} }
} }
return parsedDate return parsedDate
} }
// Pages
override fun pageListRequest(chapter: SChapter): Request { override fun pageListRequest(chapter: SChapter): Request {
val jsonHeaders = headers.newBuilder().add("Content-Type", "application/json").build() val jsonHeaders = headers.newBuilder().add("Content-Type", "application/json").build()
val slug = chapter.url.substringAfter("chapter/").substringBefore("/") val slug = chapter.url
val number = chapter.url.substringAfter("chapter-").removeSuffix("/") .substringAfter("chapter/")
.substringBefore("/")
val number = chapter.url
.substringAfter("chapter-")
.removeSuffix("/")
val body = 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 null
) )
return POST("https://api.mghubcdn.com/graphql", jsonHeaders, body) return POST("https://api.mghubcdn.com/graphql", jsonHeaders, body)
} }
private val json: Json by injectLazy()
override fun pageListParse(response: Response): List<Page> { override fun pageListParse(response: Response): List<Page> {
val cdn = "https://img.mghubcdn.com/file/imghub" 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"]!! val pagesObject = json
.jsonObject["chapter"]!! .decodeFromString<JsonObject>(chapterObject.data.chapter.pages)
.jsonObject["pages"]!!.jsonPrimitive.content val pages = pagesObject.values.map { it.jsonPrimitive.content }
.removeSurrounding("\"").replace("\\", "")
.let { cleaned -> return pages.mapIndexed { i, path -> Page(i, "", "$cdn/$path") }
val jsonObject = json.decodeFromString<JsonObject>(cleaned)
jsonObject.keys.map { key -> jsonObject[key]!!.jsonPrimitive.content }
}
.mapIndexed { i, tail -> Page(i, "", "$cdn/$tail") }
} }
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") // Image
override fun imageUrlParse(document: Document): String =
// https://mangahub.io/search/page/1?q=a&order=POPULAR&genre=all throw UnsupportedOperationException("Not used.")
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)
}
// Filters
private class Genre(title: String, val key: String) : Filter.TriState(title) { private class Genre(title: String, val key: String) : Filter.TriState(title) {
override fun toString(): String { override fun toString(): String = name
return name
}
} }
private class Order(title: String, val key: String) : Filter.TriState(title) { private class Order(title: String, val key: String) : Filter.TriState(title) {
override fun toString(): String { override fun toString(): String = name
return name
}
} }
private class OrderBy(orders: Array<Order>) : Filter.Select<Order>("Order", orders, 0) private class OrderBy(orders: Array<Order>) : Filter.Select<Order>("Order", orders, 0)
@ -305,3 +348,42 @@ class Mangahub : ParsedHttpSource() {
Genre("Yuri", "yuri") 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