Add MANGA Plus Creators extension. (#13294)

This commit is contained in:
Alessandro Jean 2022-08-31 17:48:15 -03:00 committed by GitHub
parent 856b78ced4
commit 1c42cc427c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 302 additions and 0 deletions

View File

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

View File

@ -0,0 +1,12 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization'
ext {
extName = 'MANGA Plus Creators by SHUEISHA'
pkgNameSuffix = 'all.mangapluscreators'
extClass = '.MangaPlusCreatorsFactory'
extVersionCode = 1
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

View File

@ -0,0 +1,208 @@
package eu.kanade.tachiyomi.extension.all.mangapluscreators
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.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
import okhttp3.Response
import rx.Observable
import uy.kohesive.injekt.injectLazy
class MangaPlusCreators(override val lang: String) : HttpSource() {
override val name = "MANGA Plus Creators by SHUEISHA"
override val baseUrl = "https://medibang.com/mpc"
override val supportsLatest = true
override fun headersBuilder(): Headers.Builder = Headers.Builder()
.add("Origin", baseUrl.substringBeforeLast("/"))
.add("Referer", baseUrl)
.add("User-Agent", USER_AGENT)
private val json: Json by injectLazy()
override fun popularMangaRequest(page: Int): Request {
val newHeaders = headersBuilder()
.set("Referer", "$baseUrl/titles/popular/?p=m")
.add("X-Requested-With", "XMLHttpRequest")
.build()
val apiUrl = "$API_URL/titles/popular/list".toHttpUrl().newBuilder()
.addQueryParameter("page", page.toString())
.addQueryParameter("pageSize", POPULAR_PAGE_SIZE)
.addQueryParameter("l", lang)
.addQueryParameter("p", "m")
.addQueryParameter("isWebview", "false")
.addQueryParameter("_", System.currentTimeMillis().toString())
.toString()
return GET(apiUrl, newHeaders)
}
override fun popularMangaParse(response: Response): MangasPage {
val result = response.asMpcResponse()
checkNotNull(result.titles) { EMPTY_RESPONSE_ERROR }
val titles = result.titles.titleList.orEmpty().map(MpcTitle::toSManga)
return MangasPage(titles, result.titles.pagination?.hasNextPage ?: false)
}
override fun latestUpdatesRequest(page: Int): Request {
val newHeaders = headersBuilder()
.set("Referer", "$baseUrl/titles/recent/?t=episode")
.add("X-Requested-With", "XMLHttpRequest")
.build()
val apiUrl = "$API_URL/titles/recent/list".toHttpUrl().newBuilder()
.addQueryParameter("page", page.toString())
.addQueryParameter("pageSize", POPULAR_PAGE_SIZE)
.addQueryParameter("l", lang)
.addQueryParameter("c", "episode")
.addQueryParameter("isWebview", "false")
.addQueryParameter("_", System.currentTimeMillis().toString())
.toString()
return GET(apiUrl, newHeaders)
}
override fun latestUpdatesParse(response: Response): MangasPage = popularMangaParse(response)
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val refererUrl = "$baseUrl/keywords".toHttpUrl().newBuilder()
.addQueryParameter("q", query)
.toString()
val newHeaders = headersBuilder()
.set("Referer", refererUrl)
.add("X-Requested-With", "XMLHttpRequest")
.build()
val apiUrl = "$API_URL/search/titles".toHttpUrl().newBuilder()
.addQueryParameter("keyword", query)
.addQueryParameter("page", page.toString())
.addQueryParameter("pageSize", POPULAR_PAGE_SIZE)
.addQueryParameter("sort", "newly")
.addQueryParameter("lang", lang)
.addQueryParameter("_", System.currentTimeMillis().toString())
.toString()
return GET(apiUrl, newHeaders)
}
override fun searchMangaParse(response: Response): MangasPage = popularMangaParse(response)
override fun mangaDetailsParse(response: Response): SManga {
val result = response.asJsoup()
val bookBox = result.selectFirst(".book-box")
return SManga.create().apply {
title = bookBox.selectFirst("div.title").text()
author = bookBox.selectFirst("div.mod-btn-profile div.name").text()
description = bookBox.select("div.summary p")
.joinToString("\n\n") { it.text() }
status = when (bookBox.selectFirst("div.book-submit-type").text()) {
"Series" -> SManga.ONGOING
"One-shot" -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
genre = bookBox.select("div.genre-area div.tag-genre")
.joinToString { it.text() }
thumbnail_url = bookBox.selectFirst("div.cover img").attr("data-src")
}
}
override fun chapterListRequest(manga: SManga): Request {
val titleId = manga.url.substringAfterLast("/")
val newHeaders = headersBuilder()
.set("Referer", baseUrl + manga.url)
.add("X-Requested-With", "XMLHttpRequest")
.build()
val apiUrl = "$API_URL/titles/$titleId/episodes/".toHttpUrl().newBuilder()
.addQueryParameter("page", "1")
.addQueryParameter("pageSize", CHAPTER_PAGE_SIZE)
.addQueryParameter("_", System.currentTimeMillis().toString())
.toString()
return GET(apiUrl, newHeaders)
}
override fun chapterListParse(response: Response): List<SChapter> {
val result = response.asMpcResponse()
checkNotNull(result.episodes) { EMPTY_RESPONSE_ERROR }
return result.episodes.episodeList.orEmpty()
.sortedByDescending(MpcEpisode::numbering)
.map(MpcEpisode::toSChapter)
}
override fun pageListRequest(chapter: SChapter): Request {
val chapterId = chapter.url.substringAfterLast("/")
val newHeaders = headersBuilder()
.set("Referer", baseUrl + chapter.url)
.add("X-Requested-With", "XMLHttpRequest")
.build()
val apiUrl = "$API_URL/episodes/pageList/$chapterId/".toHttpUrl().newBuilder()
.addQueryParameter("_", System.currentTimeMillis().toString())
.toString()
return GET(apiUrl, newHeaders)
}
override fun pageListParse(response: Response): List<Page> {
val result = response.asMpcResponse()
checkNotNull(result.pageList) { EMPTY_RESPONSE_ERROR }
val referer = response.request.header("Referer")!!
return result.pageList.mapIndexed { i, page ->
Page(i, referer, page.publicBgImage)
}
}
override fun fetchImageUrl(page: Page): Observable<String> = Observable.just(page.imageUrl!!)
override fun imageUrlParse(response: Response): String = ""
override fun imageRequest(page: Page): Request {
val newHeaders = headersBuilder()
.removeAll("Origin")
.set("Referer", page.url)
.build()
return GET(page.imageUrl!!, newHeaders)
}
private fun Response.asMpcResponse(): MpcResponse = use {
json.decodeFromString(body!!.string())
}
companion object {
private const val API_URL = "https://medibang.com/api/mpc"
private const val USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " +
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36"
private const val POPULAR_PAGE_SIZE = "30"
private const val CHAPTER_PAGE_SIZE = "200"
private const val EMPTY_RESPONSE_ERROR = "Empty response from the API. Try again later."
}
}

View File

@ -0,0 +1,68 @@
package eu.kanade.tachiyomi.extension.all.mangapluscreators
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class MpcResponse(
@SerialName("mpcEpisodesDto") val episodes: MpcEpisodesDto? = null,
@SerialName("mpcTitlesDto") val titles: MpcTitlesDto? = null,
val pageList: List<MpcPage>? = emptyList()
)
@Serializable
data class MpcEpisodesDto(
val pagination: MpcPagination? = null,
val episodeList: List<MpcEpisode>? = emptyList()
)
@Serializable
data class MpcTitlesDto(
val pagination: MpcPagination? = null,
val titleList: List<MpcTitle>? = emptyList()
)
@Serializable
data class MpcPagination(
val page: Int,
val maxPage: Int
) {
val hasNextPage: Boolean
get() = page < maxPage
}
@Serializable
data class MpcTitle(
@SerialName("titleId") val id: String,
val title: String,
val thumbnailUrl: String,
) {
fun toSManga(): SManga = SManga.create().apply {
title = this@MpcTitle.title
thumbnail_url = thumbnailUrl
url = "/titles/$id"
}
}
@Serializable
data class MpcEpisode(
@SerialName("episodeId") val id: String,
@SerialName("episodeTitle") val title: String,
val numbering: Int,
val oneshot: Boolean = false,
val publishDate: Long
) {
fun toSChapter(): SChapter = SChapter.create().apply {
name = if (oneshot) "One-shot" else title
date_upload = publishDate
url = "/episodes/$id"
}
}
@Serializable
data class MpcPage(val publicBgImage: String)

View File

@ -0,0 +1,11 @@
package eu.kanade.tachiyomi.extension.all.mangapluscreators
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceFactory
class MangaPlusCreatorsFactory : SourceFactory {
override fun createSources(): List<Source> = listOf(
MangaPlusCreators("en"),
MangaPlusCreators("es")
)
}