diff --git a/src/all/mangapluscreators/AndroidManifest.xml b/src/all/mangapluscreators/AndroidManifest.xml
new file mode 100644
index 000000000..4fec49a4c
--- /dev/null
+++ b/src/all/mangapluscreators/AndroidManifest.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/all/mangapluscreators/build.gradle b/src/all/mangapluscreators/build.gradle
index fdc8822c8..dd6b8de10 100644
--- a/src/all/mangapluscreators/build.gradle
+++ b/src/all/mangapluscreators/build.gradle
@@ -1,7 +1,7 @@
ext {
extName = 'MANGA Plus Creators by SHUEISHA'
extClass = '.MangaPlusCreatorsFactory'
- extVersionCode = 1
+ extVersionCode = 2
}
apply from: "$rootDir/common.gradle"
diff --git a/src/all/mangapluscreators/src/eu/kanade/tachiyomi/extension/all/mangapluscreators/MPCUrlActivity.kt b/src/all/mangapluscreators/src/eu/kanade/tachiyomi/extension/all/mangapluscreators/MPCUrlActivity.kt
new file mode 100644
index 000000000..21ca9070f
--- /dev/null
+++ b/src/all/mangapluscreators/src/eu/kanade/tachiyomi/extension/all/mangapluscreators/MPCUrlActivity.kt
@@ -0,0 +1,63 @@
+package eu.kanade.tachiyomi.extension.all.mangapluscreators
+
+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 MPCUrlActivity : Activity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ val pathSegments = intent?.data?.pathSegments
+ if (pathSegments != null && pathSegments.size > 1) {
+ // {medibang.com/mpc,mangaplus-creators.jp}/{episodes,titles,authors}
+ // TODO: val pathIndex = if (intent?.data?.host?.startsWith("medibang") == true) 1 else 0
+ val host = intent?.data?.host ?: ""
+ val pathIndex = with(host) {
+ when {
+ equals("medibang.com") -> 1
+ else -> 0
+ }
+ }
+ val idIndex = pathIndex + 1
+ val query = when {
+ pathSegments[pathIndex].equals("episodes") -> {
+ MangaPlusCreators.PREFIX_EPISODE_ID_SEARCH + pathSegments[idIndex]
+ }
+ pathSegments[pathIndex].equals("authors") -> {
+ MangaPlusCreators.PREFIX_AUTHOR_ID_SEARCH + pathSegments[idIndex]
+ }
+ pathSegments[pathIndex].equals("titles") -> {
+ MangaPlusCreators.PREFIX_TITLE_ID_SEARCH + pathSegments[idIndex]
+ }
+ else -> null // TODO: is this required?
+ }
+
+ if (query != null) {
+ // TODO: val mainIntent = Intent().setAction("eu.kanade.tachiyomi.SEARCH").apply {
+ val mainIntent = Intent().apply {
+ setAction("eu.kanade.tachiyomi.SEARCH")
+ putExtra("query", query)
+ putExtra("filter", packageName)
+ }
+
+ try {
+ startActivity(mainIntent)
+ } catch (e: ActivityNotFoundException) {
+ Log.e("MPCUrlActivity", e.toString())
+ }
+ } else {
+ Log.e("MPCUrlActivity", "Missing alphanumeric ID from the URL")
+ }
+ } else {
+ Log.e("MPCUrlActivity", "Could not parse URI from intent $intent")
+ }
+
+ finish()
+ exitProcess(0)
+ }
+}
diff --git a/src/all/mangapluscreators/src/eu/kanade/tachiyomi/extension/all/mangapluscreators/MangaPlusCreators.kt b/src/all/mangapluscreators/src/eu/kanade/tachiyomi/extension/all/mangapluscreators/MangaPlusCreators.kt
index 880dabd53..f99d48eb2 100644
--- a/src/all/mangapluscreators/src/eu/kanade/tachiyomi/extension/all/mangapluscreators/MangaPlusCreators.kt
+++ b/src/all/mangapluscreators/src/eu/kanade/tachiyomi/extension/all/mangapluscreators/MangaPlusCreators.kt
@@ -1,6 +1,8 @@
package eu.kanade.tachiyomi.extension.all.mangapluscreators
import eu.kanade.tachiyomi.network.GET
+import eu.kanade.tachiyomi.network.asObservableSuccess
+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
@@ -8,102 +10,199 @@ 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 keiyoushi.utils.parseAs
+import keiyoushi.utils.tryParse
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
import okhttp3.Response
+import org.jsoup.nodes.Element
import rx.Observable
-import uy.kohesive.injekt.injectLazy
+import java.text.SimpleDateFormat
+import java.util.Locale
class MangaPlusCreators(override val lang: String) : HttpSource() {
override val name = "MANGA Plus Creators by SHUEISHA"
- override val baseUrl = "https://medibang.com/mpc"
+ override val baseUrl = "https://mangaplus-creators.jp"
+
+ private val apiUrl = "$baseUrl/api"
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()
-
+ // POPULAR Section
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)
+ val popularUrl = "$baseUrl/titles/popular/?p=m&l=$lang".toHttpUrl()
+ return GET(popularUrl, headers)
}
- override fun popularMangaParse(response: Response): MangasPage {
- val result = response.asMpcResponse()
+ override fun popularMangaParse(response: Response): MangasPage = parseMangasPageFromElement(
+ response,
+ "div.item-recent",
+ )
- checkNotNull(result.titles) { EMPTY_RESPONSE_ERROR }
+ private fun parseMangasPageFromElement(response: Response, selector: String): MangasPage {
+ val result = response.asJsoup()
- val titles = result.titles.titleList.orEmpty().map(MpcTitle::toSManga)
+ val mangas = result.select(selector).map { element ->
+ popularElementToSManga(element)
+ }
- return MangasPage(titles, result.titles.pagination?.hasNextPage ?: false)
+ return MangasPage(mangas, false)
}
+ private fun popularElementToSManga(element: Element): SManga {
+ val titleThumbnailUrl = element.selectFirst(".image-area img")!!.attr("src")
+ val titleContentId = titleThumbnailUrl.toHttpUrl().pathSegments[2]
+ return SManga.create().apply {
+ title = element.selectFirst(".title-area .title")!!.text()
+ thumbnail_url = titleThumbnailUrl
+ setUrlWithoutDomain("/titles/$titleContentId")
+ }
+ }
+
+ // LATEST Section
override fun latestUpdatesRequest(page: Int): Request {
- val newHeaders = headersBuilder()
- .set("Referer", "$baseUrl/titles/recent/?t=episode")
- .add("X-Requested-With", "XMLHttpRequest")
+ val apiUrl = "$apiUrl/titles/recent/".toHttpUrl().newBuilder()
+ .addQueryParameter("page", page.toString())
+ .addQueryParameter("l", lang)
+ .addQueryParameter("t", "episode")
.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)
+ return GET(apiUrl, headers)
}
- override fun latestUpdatesParse(response: Response): MangasPage = popularMangaParse(response)
+ override fun latestUpdatesParse(response: Response): MangasPage {
+ val result = response.parseAs()
+
+ val titles = result.titles.orEmpty().map { title -> title.toSManga() }
+
+ // TODO: handle last page of latest
+ return MangasPage(titles, result.status != "error")
+ }
+
+ private fun MpcTitle.toSManga(): SManga {
+ val mTitle = this.title
+ val mAuthor = this.author.name // TODO: maybe not required
+ return SManga.create().apply {
+ title = mTitle
+ thumbnail_url = thumbnail
+ setUrlWithoutDomain("/titles/${latestEpisode.titleConnectId}")
+ author = mAuthor
+ }
+ }
+
+ // SEARCH Section
+ override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable {
+ // TODO: HTTPSource::fetchSearchManga is deprecated? super.getSearchManga
+ if (query.startsWith(PREFIX_TITLE_ID_SEARCH)) {
+ val titleContentId = query.removePrefix(PREFIX_TITLE_ID_SEARCH)
+ val titleUrl = "$baseUrl/titles/$titleContentId"
+ return client.newCall(GET(titleUrl, headers))
+ .asObservableSuccess()
+ .map { response ->
+ val result = response.asJsoup()
+ val bookBox = result.selectFirst(".book-box")!!
+ val title = SManga.create().apply {
+ title = bookBox.selectFirst("div.title")!!.text()
+ thumbnail_url = bookBox.selectFirst("div.cover img")!!.attr("data-src")
+ setUrlWithoutDomain(titleUrl)
+ }
+ MangasPage(listOf(title), false)
+ }
+ }
+ if (query.startsWith(PREFIX_EPISODE_ID_SEARCH)) {
+ val episodeId = query.removePrefix(PREFIX_EPISODE_ID_SEARCH)
+ return client.newCall(GET("$baseUrl/episodes/$episodeId", headers))
+ .asObservableSuccess().map { response ->
+ val result = response.asJsoup()
+ val readerElement = result.selectFirst("div[react=viewer]")!!
+ val dataTitle = readerElement.attr("data-title")
+ val dataTitleResult = dataTitle.parseAs()
+ val episodeAsSManga = dataTitleResult.toSManga()
+ MangasPage(listOf(episodeAsSManga), false)
+ }
+ }
+ if (query.startsWith(PREFIX_AUTHOR_ID_SEARCH)) {
+ val authorId = query.removePrefix(PREFIX_AUTHOR_ID_SEARCH)
+ return client.newCall(GET("$baseUrl/authors/$authorId", headers))
+ .asObservableSuccess()
+ .map { response ->
+ val result = response.asJsoup()
+ val elements = result.select("#works .manga-list li .md\\:block")
+ val smangas = elements.map { element ->
+ val titleThumbnailUrl = element.selectFirst(".image-area img")!!.attr("src")
+ val titleContentId = titleThumbnailUrl.toHttpUrl().pathSegments[2]
+ SManga.create().apply {
+ title = element.selectFirst("p.text-white")!!.text().toString()
+ thumbnail_url = titleThumbnailUrl
+ setUrlWithoutDomain("/titles/$titleContentId")
+ }
+ }
+ MangasPage(smangas, false)
+ }
+ }
+ if (query.isNotBlank()) {
+ return super.fetchSearchManga(page, query, filters)
+ }
+
+ // nothing to search, filters active -> browsing /genres instead
+ // TODO: check if there's a better way (filters is independent of search but part of it)
+ val genreUrl = baseUrl.toHttpUrl().newBuilder()
+ .apply {
+ addPathSegment("genres")
+ addQueryParameter("l", lang)
+ filters.forEach { filter ->
+ when (filter) {
+ is SortFilter -> {
+ if (filter.selected.isNotEmpty()) {
+ addQueryParameter("s", filter.selected)
+ }
+ }
+ is GenreFilter -> addPathSegment(filter.selected)
+ else -> { /* Nothing else is supported for now */ }
+ }
+ }
+ }.build()
+
+ return client.newCall(GET(genreUrl, headers))
+ .asObservableSuccess()
+ .map { response ->
+ popularMangaParse(response)
+ }
+ }
+
+ private fun MpcReaderDataTitle.toSManga(): SManga {
+ val mTitle = title
+ return SManga.create().apply {
+ title = mTitle
+ thumbnail_url = thumbnail
+ setUrlWithoutDomain("/titles/$contentsId")
+ }
+ }
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
- val refererUrl = "$baseUrl/keywords".toHttpUrl().newBuilder()
+ // TODO: maybe this needn't be a new builder and just similar to `popularUrl` above?
+ val searchUrl = "$baseUrl/keywords".toHttpUrl().newBuilder()
.addQueryParameter("q", query)
- .toString()
-
- val newHeaders = headersBuilder()
- .set("Referer", refererUrl)
- .add("X-Requested-With", "XMLHttpRequest")
+ .addQueryParameter("s", "date")
+ .addQueryParameter("lang", lang)
.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)
+ return GET(searchUrl, headers)
}
- override fun searchMangaParse(response: Response): MangasPage = popularMangaParse(response)
+ override fun searchMangaParse(response: Response): MangasPage = parseMangasPageFromElement(
+ response,
+ "div.item-search",
+ )
+ // MANGA Section
override fun mangaDetailsParse(response: Response): SManga {
val result = response.asJsoup()
val bookBox = result.selectFirst(".book-box")!!
@@ -119,62 +218,82 @@ class MangaPlusCreators(override val lang: String) : HttpSource() {
else -> SManga.UNKNOWN
}
genre = bookBox.select("div.genre-area div.tag-genre")
- .joinToString { it.text() }
+ .joinToString(", ") { it.text() }
thumbnail_url = bookBox.selectFirst("div.cover img")!!.attr("data-src")
}
}
+ // CHAPTER Section
override fun chapterListRequest(manga: SManga): Request {
- val titleId = manga.url.substringAfterLast("/")
+ val titleContentId = (baseUrl + manga.url).toHttpUrl().pathSegments[1]
+ return chapterListPageRequest(1, titleContentId)
+ }
- 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)
+ private fun chapterListPageRequest(page: Int, titleContentId: String): Request {
+ return GET("$baseUrl/titles/$titleContentId/?page=$page", headers)
}
override fun chapterListParse(response: Response): List {
- val result = response.asMpcResponse()
+ val chapterListResponse = chapterListPageParse(response)
+ val chapterListResult = chapterListResponse.chapters.toMutableList()
- checkNotNull(result.episodes) { EMPTY_RESPONSE_ERROR }
+ var hasNextPage = chapterListResponse.hasNextPage
+ val titleContentId = response.request.url.pathSegments[1]
+ var page = 1
+ while (hasNextPage) {
+ page += 1
+ val nextPageRequest = chapterListPageRequest(page, titleContentId)
+ val nextPageResponse = client.newCall(nextPageRequest).execute()
+ val nextPageResult = chapterListPageParse(nextPageResponse)
+ if (nextPageResult.chapters.isEmpty()) {
+ break
+ }
+ chapterListResult.addAll(nextPageResult.chapters)
+ hasNextPage = nextPageResult.hasNextPage
+ }
- return result.episodes.episodeList.orEmpty()
- .sortedByDescending(MpcEpisode::numbering)
- .map(MpcEpisode::toSChapter)
+ return chapterListResult.asReversed()
}
- 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)
+ private fun chapterListPageParse(response: Response): ChaptersPage {
+ val result = response.asJsoup()
+ val chapters = result.select(".mod-item-series").map {
+ element ->
+ chapterElementToSChapter(element)
+ }
+ val hasResult = result.select(".mod-pagination .next").isNotEmpty()
+ return ChaptersPage(
+ chapters,
+ hasResult,
+ )
}
+ private fun chapterElementToSChapter(element: Element): SChapter {
+ val episode = element.attr("href").substringAfterLast("/")
+ val latestUpdatedDate = element.selectFirst(".first-update")!!.text()
+ val chapterNumberElement = element.selectFirst(".number")!!.text()
+ val chapterNumber = chapterNumberElement.substringAfter("#").toFloatOrNull()
+ return SChapter.create().apply {
+ setUrlWithoutDomain("/episodes/$episode")
+ date_upload = CHAPTER_DATE_FORMAT.tryParse(latestUpdatedDate)
+ name = chapterNumberElement
+ chapter_number = if (chapterNumberElement == "One-shot") {
+ 0F
+ } else {
+ chapterNumber ?: -1F
+ }
+ }
+ }
+
+ // PAGES & IMAGES Section
override fun pageListParse(response: Response): List {
- 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)
+ val result = response.asJsoup()
+ val readerElement = result.selectFirst("div[react=viewer]")!!
+ val dataPages = readerElement.attr("data-pages")
+ val refererUrl = response.request.url.toString()
+ return dataPages.parseAs().pc.map {
+ page ->
+ Page(page.pageNo, refererUrl, page.imageUrl)
}
}
@@ -191,18 +310,66 @@ class MangaPlusCreators(override val lang: String) : HttpSource() {
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 val CHAPTER_DATE_FORMAT by lazy {
+ SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH)
+ }
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."
+ const val PREFIX_TITLE_ID_SEARCH = "title:"
+ const val PREFIX_EPISODE_ID_SEARCH = "episode:"
+ const val PREFIX_AUTHOR_ID_SEARCH = "author:"
}
+
+ // FILTERS Section
+ override fun getFilterList() = FilterList(
+ Filter.Separator(),
+ Filter.Header("NOTE: Ignored if using text search!"),
+ Filter.Separator(),
+ SortFilter(),
+ GenreFilter(),
+ Filter.Separator(),
+ )
+
+ private class SortFilter() : SelectFilter(
+ "Sort",
+ listOf(
+ SelectFilterOption("Popularity", ""),
+ SelectFilterOption("Date", "latest_desc"),
+ SelectFilterOption("Likes", "like_desc"),
+ ),
+ 0,
+ )
+
+ private class GenreFilter() : SelectFilter(
+ "Genres",
+ listOf(
+ SelectFilterOption("Fantasy", "fantasy"),
+ SelectFilterOption("Action", "action"),
+ SelectFilterOption("Romance", "romance"),
+ SelectFilterOption("Horror", "horror"),
+ SelectFilterOption("Slice of Life", "slice_of_life"),
+ SelectFilterOption("Comedy", "comedy"),
+ SelectFilterOption("Sports", "sports"),
+ SelectFilterOption("Sci-Fi", "sf"),
+ SelectFilterOption("Mystery", "mystery"),
+ SelectFilterOption("Others", "others"),
+ ),
+ 0,
+ )
+
+ private abstract class SelectFilter(
+ name: String,
+ private val options: List,
+ default: Int = 0,
+ ) : Filter.Select(
+ name,
+ options.map { it.name }.toTypedArray(),
+ default,
+ ) {
+ val selected: String
+ get() = options[state].value
+ }
+
+ private class SelectFilterOption(val name: String, val value: String)
}
diff --git a/src/all/mangapluscreators/src/eu/kanade/tachiyomi/extension/all/mangapluscreators/MangaPlusCreatorsDto.kt b/src/all/mangapluscreators/src/eu/kanade/tachiyomi/extension/all/mangapluscreators/MangaPlusCreatorsDto.kt
index 8c3ce2171..066d047f2 100644
--- a/src/all/mangapluscreators/src/eu/kanade/tachiyomi/extension/all/mangapluscreators/MangaPlusCreatorsDto.kt
+++ b/src/all/mangapluscreators/src/eu/kanade/tachiyomi/extension/all/mangapluscreators/MangaPlusCreatorsDto.kt
@@ -1,68 +1,51 @@
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? = emptyList(),
+class MpcResponse(
+ val status: String,
+ val titles: List? = null,
)
@Serializable
-data class MpcEpisodesDto(
- val pagination: MpcPagination? = null,
- val episodeList: List? = emptyList(),
-)
-
-@Serializable
-data class MpcTitlesDto(
- val pagination: MpcPagination? = null,
- val titleList: List? = 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,
+class MpcTitle(
val title: String,
- val thumbnailUrl: String,
-) {
-
- fun toSManga(): SManga = SManga.create().apply {
- title = this@MpcTitle.title
- thumbnail_url = thumbnailUrl
- url = "/titles/$id"
- }
-}
+ val thumbnail: String,
+ @SerialName("is_one_shot") val isOneShot: Boolean,
+ val author: MpcAuthorDto,
+ @SerialName("latest_episode") val latestEpisode: MpcLatestEpisode,
+)
@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"
- }
-}
+class MpcAuthorDto(
+ val name: String,
+)
@Serializable
-data class MpcPage(val publicBgImage: String)
+class MpcLatestEpisode(
+ @SerialName("title_connect_id") val titleConnectId: String,
+)
+
+@Serializable
+class MpcReaderDataPages(
+ val pc: List,
+)
+
+@Serializable
+class MpcReaderPage(
+ @SerialName("page_no") val pageNo: Int,
+ @SerialName("image_url") val imageUrl: String,
+)
+
+@Serializable
+class MpcReaderDataTitle(
+ val title: String,
+ val thumbnail: String,
+ @SerialName("is_oneshot") val isOneShot: Boolean,
+ @SerialName("contents_id") val contentsId: String,
+)
+
+class ChaptersPage(val chapters: List, val hasNextPage: Boolean)