Magazine Pocket: fix descrambler and refactor (#11687)
* fix descrambler and refactor * getChapterUrl * toSChapter
This commit is contained in:
parent
b1ee9c1589
commit
2f9626a2f7
@ -1,7 +1,7 @@
|
|||||||
ext {
|
ext {
|
||||||
extName = 'Magazine Pocket'
|
extName = 'Magazine Pocket'
|
||||||
extClass = '.MagazinePocket'
|
extClass = '.MagazinePocket'
|
||||||
extVersionCode = 9
|
extVersionCode = 10
|
||||||
isNsfw = false
|
isNsfw = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,41 +1,46 @@
|
|||||||
package eu.kanade.tachiyomi.extension.ja.magazinepocket
|
package eu.kanade.tachiyomi.extension.ja.magazinepocket
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
|
import keiyoushi.utils.tryParse
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.json.JsonNames
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
class RankingApiResponse(
|
class RankingApiResponse(
|
||||||
@SerialName("ranking_title_list") val rankingTitleList: List<RankingTitleId>,
|
@JsonNames("title_list", "ranking_title_list")
|
||||||
|
val rankingTitleList: List<RankingTitleId>,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
class RankingTitleId(
|
class RankingTitleId(
|
||||||
|
@JsonNames("title_id")
|
||||||
val id: Int,
|
val id: Int,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
class TitleListResponse(
|
class TitleListResponse(
|
||||||
@SerialName("title_list") val titleList: List<TitleDetail>,
|
@JsonNames("title_list", "search_title_list")
|
||||||
|
val titleList: List<TitleDetail>,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
class TitleDetail(
|
class TitleDetail(
|
||||||
@SerialName("title_id") val titleId: Int,
|
@SerialName("title_id") private val titleId: Int,
|
||||||
@SerialName("title_name") val titleName: String,
|
@SerialName("title_name") private val titleName: String,
|
||||||
@SerialName("thumbnail_image_url") val thumbnailImageUrl: String? = null,
|
@SerialName("thumbnail_image_url") private val thumbnailImageUrl: String? = null,
|
||||||
)
|
@SerialName("banner_image_url") private val bannerImageUrl: String? = null,
|
||||||
|
@SerialName("thumbnail_rect_image_url") private val thumbnailRectImageUrl: String? = null,
|
||||||
@Serializable
|
) {
|
||||||
class LatestTitleListResponse(
|
fun toSManga(): SManga = SManga.create().apply {
|
||||||
@SerialName("title_list") val titleList: List<LatestTitleDetail>,
|
val paddedId = titleId.toString().padStart(5, '0')
|
||||||
)
|
url = "/title/$paddedId"
|
||||||
|
title = titleName
|
||||||
@Serializable
|
thumbnail_url = thumbnailImageUrl ?: bannerImageUrl ?: thumbnailRectImageUrl
|
||||||
class LatestTitleDetail(
|
}
|
||||||
@SerialName("title_id") val titleId: Int,
|
}
|
||||||
@SerialName("title_name") val titleName: String,
|
|
||||||
@SerialName("thumbnail_rect_image_url") val thumbnailImageUrl: String? = null,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
class EpisodeListResponse(
|
class EpisodeListResponse(
|
||||||
@ -44,13 +49,43 @@ class EpisodeListResponse(
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
class Episode(
|
class Episode(
|
||||||
@SerialName("episode_id") val episodeId: Int,
|
@SerialName("episode_id") private val episodeId: Int,
|
||||||
@SerialName("episode_name") val episodeName: String,
|
@SerialName("episode_name") private val episodeName: String,
|
||||||
@SerialName("start_time") val startTime: String,
|
private val index: Int,
|
||||||
val point: Int,
|
@SerialName("start_time") private val startTime: String,
|
||||||
@SerialName("title_id") val titleId: Int,
|
private val point: Int,
|
||||||
val badge: Int,
|
@SerialName("title_id") private val titleId: Int,
|
||||||
@SerialName("rental_finish_time") val rentalFinishTime: String? = null,
|
private val badge: Int,
|
||||||
|
@SerialName("rental_finish_time") private val rentalFinishTime: String? = null,
|
||||||
|
) {
|
||||||
|
fun toSChapter(dateFormat: SimpleDateFormat): SChapter = SChapter.create().apply {
|
||||||
|
val paddedId = titleId.toString().padStart(5, '0')
|
||||||
|
url = "/title/$paddedId/episode/$episodeId"
|
||||||
|
name = if (point > 0 && badge != 3 && rentalFinishTime == null) {
|
||||||
|
"🔒 $episodeName"
|
||||||
|
} else {
|
||||||
|
episodeName
|
||||||
|
}
|
||||||
|
chapter_number = index.toFloat()
|
||||||
|
date_upload = dateFormat.tryParse(startTime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class DetailResponse(
|
||||||
|
@SerialName("web_title") val webTitle: WebTitle,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class WebTitle(
|
||||||
|
@SerialName("title_name") val titleName: String,
|
||||||
|
@SerialName("author_text") val authorText: String,
|
||||||
|
@SerialName("introduction_text") val introductionText: String,
|
||||||
|
@SerialName("genre_id_list") val genreIdList: List<Int>,
|
||||||
|
@SerialName("episode_id_list") val episodeIdList: List<Int>,
|
||||||
|
@SerialName("thumbnail_image_url") val thumbnailImageUrl: String? = null,
|
||||||
|
@SerialName("thumbnail_rect_image_url") val thumbnailRectImageUrl: String? = null,
|
||||||
|
@SerialName("banner_image_url") val bannerImageUrl: String? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@ -60,13 +95,11 @@ class ViewerApiResponse(
|
|||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
class SearchApiResponse(
|
class GenreListResponse(
|
||||||
@SerialName("search_title_list") val searchTitleList: List<SearchTitleDetail>,
|
@SerialName("genre_list") val genreList: List<GenreDetail>,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
class SearchTitleDetail(
|
class GenreDetail(
|
||||||
@SerialName("title_id") val titleId: Int,
|
@SerialName("genre_name") val genreName: String,
|
||||||
@SerialName("title_name") val titleName: String,
|
|
||||||
@SerialName("banner_image_url") val bannerImageUrl: String? = null,
|
|
||||||
)
|
)
|
||||||
|
|||||||
@ -21,7 +21,14 @@ class ImageInterceptor : Interceptor {
|
|||||||
|
|
||||||
val seed = fragment.substringAfter("scramble_seed=").toLong()
|
val seed = fragment.substringAfter("scramble_seed=").toLong()
|
||||||
val response = chain.proceed(request)
|
val response = chain.proceed(request)
|
||||||
val descrambledBody = descrambleImage(response.body, seed)
|
val imageBytes = response.body.bytes()
|
||||||
|
|
||||||
|
val options = BitmapFactory.Options().apply { inJustDecodeBounds = true }
|
||||||
|
BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size, options)
|
||||||
|
|
||||||
|
val version = if (options.outHeight == 1600 || options.outHeight == 1024) 2 else 1
|
||||||
|
val originalBitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
|
||||||
|
val descrambledBody = descrambleImage(originalBitmap, seed, version)
|
||||||
|
|
||||||
return response.newBuilder().body(descrambledBody).build()
|
return response.newBuilder().body(descrambledBody).build()
|
||||||
}
|
}
|
||||||
@ -58,10 +65,8 @@ class ImageInterceptor : Interceptor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun descrambleImage(responseBody: ResponseBody, seed: Long): ResponseBody {
|
private fun descrambleImage(originalBitmap: Bitmap, seed: Long, version: Int): ResponseBody {
|
||||||
val unscrambledCoords = getUnscrambledCoords(seed)
|
val unscrambledCoords = getUnscrambledCoords(seed)
|
||||||
val originalBitmap = BitmapFactory.decodeStream(responseBody.byteStream())
|
|
||||||
?: throw Exception("Failed to decode image stream")
|
|
||||||
|
|
||||||
val originalWidth = originalBitmap.width
|
val originalWidth = originalBitmap.width
|
||||||
val originalHeight = originalBitmap.height
|
val originalHeight = originalBitmap.height
|
||||||
@ -69,9 +74,16 @@ class ImageInterceptor : Interceptor {
|
|||||||
val descrambledBitmap = Bitmap.createBitmap(originalWidth, originalHeight, originalBitmap.config)
|
val descrambledBitmap = Bitmap.createBitmap(originalWidth, originalHeight, originalBitmap.config)
|
||||||
val canvas = Canvas(descrambledBitmap)
|
val canvas = Canvas(descrambledBitmap)
|
||||||
|
|
||||||
val getTileDimension = { size: Int -> (size / 8 * 8) / 4 }
|
val (tileWidth, tileHeight) = when (version) {
|
||||||
val tileWidth = getTileDimension(originalWidth)
|
2 -> {
|
||||||
val tileHeight = getTileDimension(originalHeight)
|
val getTile = { size: Int -> (size / 32) * 8 }
|
||||||
|
Pair(getTile(originalWidth), getTile(originalHeight))
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
val getTile = { size: Int -> (size / 8 * 8) / 4 }
|
||||||
|
Pair(getTile(originalWidth), getTile(originalHeight))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
unscrambledCoords.forEach { coord ->
|
unscrambledCoords.forEach { coord ->
|
||||||
val sx = coord.source.x * tileWidth
|
val sx = coord.source.x * tileWidth
|
||||||
@ -84,6 +96,22 @@ class ImageInterceptor : Interceptor {
|
|||||||
|
|
||||||
canvas.drawBitmap(originalBitmap, srcRect, destRect, null)
|
canvas.drawBitmap(originalBitmap, srcRect, destRect, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (version == 2) {
|
||||||
|
val processedWidth = tileWidth * 4
|
||||||
|
val processedHeight = tileHeight * 4
|
||||||
|
if (originalWidth > processedWidth) {
|
||||||
|
val srcRect = Rect(processedWidth, 0, originalWidth, originalHeight)
|
||||||
|
val destRect = Rect(processedWidth, 0, originalWidth, originalHeight)
|
||||||
|
canvas.drawBitmap(originalBitmap, srcRect, destRect, null)
|
||||||
|
}
|
||||||
|
if (originalHeight > processedHeight) {
|
||||||
|
val srcRect = Rect(0, processedHeight, processedWidth, originalHeight)
|
||||||
|
val destRect = Rect(0, processedHeight, processedWidth, originalHeight)
|
||||||
|
canvas.drawBitmap(originalBitmap, srcRect, destRect, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
originalBitmap.recycle()
|
originalBitmap.recycle()
|
||||||
|
|
||||||
val outputStream = ByteArrayOutputStream()
|
val outputStream = ByteArrayOutputStream()
|
||||||
|
|||||||
@ -10,16 +10,8 @@ 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.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import eu.kanade.tachiyomi.util.asJsoup
|
|
||||||
import keiyoushi.utils.firstInstance
|
import keiyoushi.utils.firstInstance
|
||||||
import keiyoushi.utils.parseAs
|
import keiyoushi.utils.parseAs
|
||||||
import keiyoushi.utils.tryParse
|
|
||||||
import kotlinx.serialization.json.JsonArray
|
|
||||||
import kotlinx.serialization.json.JsonElement
|
|
||||||
import kotlinx.serialization.json.JsonObject
|
|
||||||
import kotlinx.serialization.json.jsonArray
|
|
||||||
import kotlinx.serialization.json.jsonObject
|
|
||||||
import kotlinx.serialization.json.jsonPrimitive
|
|
||||||
import okhttp3.FormBody
|
import okhttp3.FormBody
|
||||||
import okhttp3.HttpUrl
|
import okhttp3.HttpUrl
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
@ -66,6 +58,7 @@ class MagazinePocket : HttpSource() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun popularMangaParse(response: Response): MangasPage {
|
override fun popularMangaParse(response: Response): MangasPage {
|
||||||
|
val requestUrl = response.request.url.toString()
|
||||||
val rankingResult = response.parseAs<RankingApiResponse>()
|
val rankingResult = response.parseAs<RankingApiResponse>()
|
||||||
val titleIds = rankingResult.rankingTitleList.map { it.id.toString().padStart(5, '0') }
|
val titleIds = rankingResult.rankingTitleList.map { it.id.toString().padStart(5, '0') }
|
||||||
if (titleIds.isEmpty()) {
|
if (titleIds.isEmpty()) {
|
||||||
@ -87,13 +80,10 @@ class MagazinePocket : HttpSource() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val detailsResult = detailsResponse.parseAs<TitleListResponse>()
|
val detailsResult = detailsResponse.parseAs<TitleListResponse>()
|
||||||
val mangas = detailsResult.titleList.map { manga ->
|
val mangas = detailsResult.titleList.map { it.toSManga() }
|
||||||
SManga.create().apply {
|
|
||||||
val paddedId = manga.titleId.toString().padStart(5, '0')
|
if (requestUrl.contains("/genre/")) {
|
||||||
url = "/title/$paddedId"
|
return MangasPage(mangas.reversed(), hasNextPage)
|
||||||
title = manga.titleName
|
|
||||||
thumbnail_url = manga.thumbnailImageUrl
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return MangasPage(mangas, hasNextPage)
|
return MangasPage(mangas, hasNextPage)
|
||||||
}
|
}
|
||||||
@ -122,16 +112,9 @@ class MagazinePocket : HttpSource() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun latestUpdatesParse(response: Response): MangasPage {
|
override fun latestUpdatesParse(response: Response): MangasPage {
|
||||||
val result = response.parseAs<LatestTitleListResponse>()
|
val result = response.parseAs<TitleListResponse>()
|
||||||
val manga = result.titleList.map { manga ->
|
val mangas = result.titleList.map { it.toSManga() }
|
||||||
SManga.create().apply {
|
return MangasPage(mangas, true)
|
||||||
val paddedId = manga.titleId.toString().padStart(5, '0')
|
|
||||||
url = "/title/$paddedId"
|
|
||||||
title = manga.titleName
|
|
||||||
thumbnail_url = manga.thumbnailImageUrl
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return MangasPage(manga, true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||||
@ -147,119 +130,90 @@ class MagazinePocket : HttpSource() {
|
|||||||
|
|
||||||
val genreFilter = filters.firstInstance<GenreFilter>()
|
val genreFilter = filters.firstInstance<GenreFilter>()
|
||||||
val uriPart = genreFilter.toUriPart()
|
val uriPart = genreFilter.toUriPart()
|
||||||
if (uriPart.startsWith("/search/genre/")) {
|
val url = if (uriPart.contains("/genre/")) {
|
||||||
val url = baseUrl.toHttpUrl().newBuilder()
|
val genreId = uriPart.substringAfter("/genre/")
|
||||||
.addPathSegments(uriPart.removePrefix("/"))
|
apiUrl.toHttpUrl().newBuilder()
|
||||||
|
.addPathSegments("search/title")
|
||||||
|
.addQueryParameter("platform", "3")
|
||||||
|
.addQueryParameter("genre_id", genreId)
|
||||||
|
.addQueryParameter("limit", "99999")
|
||||||
|
.build()
|
||||||
|
} else {
|
||||||
|
val rankingId = uriPart.substringAfter("/ranking/")
|
||||||
|
val offset = (page - 1) * pageLimit
|
||||||
|
apiUrl.toHttpUrl().newBuilder()
|
||||||
|
.addPathSegments("ranking/all")
|
||||||
|
.addQueryParameter("platform", "3")
|
||||||
|
.addQueryParameter("ranking_id", rankingId)
|
||||||
|
.addQueryParameter("offset", offset.toString())
|
||||||
|
.addQueryParameter("limit", "26")
|
||||||
.build()
|
.build()
|
||||||
return GET(url, headers)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val rankingId = uriPart.substringAfter("/ranking/")
|
|
||||||
val offset = (page - 1) * pageLimit
|
|
||||||
val url = apiUrl.toHttpUrl().newBuilder()
|
|
||||||
.addPathSegments("ranking/all")
|
|
||||||
.addQueryParameter("platform", "3")
|
|
||||||
.addQueryParameter("ranking_id", rankingId)
|
|
||||||
.addQueryParameter("offset", offset.toString())
|
|
||||||
.addQueryParameter("limit", "26")
|
|
||||||
.build()
|
|
||||||
return hashedGet(url)
|
return hashedGet(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun searchMangaParse(response: Response): MangasPage {
|
override fun searchMangaParse(response: Response): MangasPage {
|
||||||
val requestUrl = response.request.url.toString()
|
val requestUrl = response.request.url.toString()
|
||||||
if (requestUrl.contains("/web/search/")) {
|
if (requestUrl.contains("/web/search/")) {
|
||||||
val result = response.parseAs<SearchApiResponse>()
|
val result = response.parseAs<TitleListResponse>()
|
||||||
val mangas = result.searchTitleList.map { manga ->
|
val mangas = result.titleList.map { it.toSManga() }
|
||||||
SManga.create().apply {
|
|
||||||
val paddedId = manga.titleId.toString().padStart(5, '0')
|
|
||||||
url = "/title/$paddedId"
|
|
||||||
title = manga.titleName
|
|
||||||
thumbnail_url = manga.bannerImageUrl
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return MangasPage(mangas.reversed(), false)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.request.url.toString().contains("/search/genre/")) {
|
|
||||||
val document = response.asJsoup()
|
|
||||||
val nuxtData = document.selectFirst("script#__NUXT_DATA__")?.data()
|
|
||||||
?: return MangasPage(emptyList(), false)
|
|
||||||
|
|
||||||
val rootArray = nuxtData.parseAs<JsonArray>()
|
|
||||||
fun resolve(ref: JsonElement): JsonElement = rootArray[ref.jsonPrimitive.content.toInt()]
|
|
||||||
|
|
||||||
val genreResultObject = rootArray.firstOrNull { it is JsonObject && "search_title_list" in it.jsonObject }
|
|
||||||
?: return MangasPage(emptyList(), false)
|
|
||||||
|
|
||||||
val mangaRefs = resolve(genreResultObject.jsonObject["search_title_list"]!!).jsonArray
|
|
||||||
|
|
||||||
val mangas = mangaRefs.map { ref ->
|
|
||||||
val mangaObject = resolve(ref).jsonObject
|
|
||||||
val id = resolve(mangaObject["title_id"]!!).jsonPrimitive
|
|
||||||
SManga.create().apply {
|
|
||||||
val paddedId = id.toString().padStart(5, '0')
|
|
||||||
url = "/title/$paddedId"
|
|
||||||
title = resolve(mangaObject["title_name"]!!).jsonPrimitive.content
|
|
||||||
thumbnail_url = mangaObject["banner_image_url"]?.let { resolve(it).jsonPrimitive.content }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return MangasPage(mangas, false)
|
return MangasPage(mangas, false)
|
||||||
}
|
}
|
||||||
return popularMangaParse(response)
|
return popularMangaParse(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Details
|
// Details
|
||||||
|
override fun getMangaUrl(manga: SManga): String {
|
||||||
|
return baseUrl + manga.url
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun mangaDetailsRequest(manga: SManga): Request {
|
||||||
|
val titleId = manga.url.substringAfter("/title/")
|
||||||
|
val url = apiUrl.toHttpUrl().newBuilder()
|
||||||
|
.addPathSegments("web/title/detail")
|
||||||
|
.addQueryParameter("platform", "3")
|
||||||
|
.addQueryParameter("title_id", titleId)
|
||||||
|
.build()
|
||||||
|
return hashedGet(url)
|
||||||
|
}
|
||||||
|
|
||||||
override fun mangaDetailsParse(response: Response): SManga {
|
override fun mangaDetailsParse(response: Response): SManga {
|
||||||
val document = response.asJsoup()
|
val result = response.parseAs<DetailResponse>().webTitle
|
||||||
val nuxtData = document.selectFirst("script#__NUXT_DATA__")?.data()
|
return SManga.create().apply {
|
||||||
?: throw Exception("Could not find Nuxt data")
|
title = result.titleName
|
||||||
val rootArray = nuxtData.parseAs<JsonArray>()
|
author = result.authorText
|
||||||
|
description = result.introductionText
|
||||||
|
thumbnail_url = result.thumbnailImageUrl ?: result.bannerImageUrl ?: result.thumbnailRectImageUrl
|
||||||
|
if (result.genreIdList.isNotEmpty()) {
|
||||||
|
val genreApiUrl = apiUrl.toHttpUrl().newBuilder()
|
||||||
|
.addPathSegments("genre/list")
|
||||||
|
.addQueryParameter("platform", "3")
|
||||||
|
.addQueryParameter("genre_id_list", result.genreIdList.joinToString(","))
|
||||||
|
.build()
|
||||||
|
|
||||||
fun resolve(ref: JsonElement): JsonElement = rootArray[ref.jsonPrimitive.content.toInt()]
|
val genreRequest = hashedGet(genreApiUrl)
|
||||||
|
val genreResponse = client.newCall(genreRequest).execute()
|
||||||
|
|
||||||
val titleDetailsObject = rootArray
|
if (genreResponse.isSuccessful) {
|
||||||
.filterIsInstance<JsonObject>()
|
val genreResult = genreResponse.parseAs<GenreListResponse>()
|
||||||
.findLast { "title_name" in it && "author_text" in it && "introduction_text" in it && "genre_id_list" in it }
|
genre = genreResult.genreList.joinToString { it.genreName }
|
||||||
?.jsonObject
|
|
||||||
|
|
||||||
val genreMap = buildMap {
|
|
||||||
rootArray.forEach { element ->
|
|
||||||
if (element is JsonObject && element.jsonObject.containsKey("genre_id")) {
|
|
||||||
val genreObject = element.jsonObject
|
|
||||||
val id = resolve(genreObject["genre_id"]!!).jsonPrimitive.content.toInt()
|
|
||||||
val name = resolve(genreObject["genre_name"]!!).jsonPrimitive.content
|
|
||||||
put(id, name)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return SManga.create().apply {
|
|
||||||
title = resolve(titleDetailsObject?.get("title_name")!!).jsonPrimitive.content
|
|
||||||
author = resolve(titleDetailsObject["author_text"]!!).jsonPrimitive.content
|
|
||||||
description = resolve(titleDetailsObject["introduction_text"]!!).jsonPrimitive.content
|
|
||||||
val genreIdRefs = resolve(titleDetailsObject["genre_id_list"]!!).jsonArray
|
|
||||||
val genreIds = genreIdRefs.map { resolve(it).jsonPrimitive.content.toInt() }
|
|
||||||
genre = genreIds.mapNotNull { genreMap[it] }.joinToString()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chapters
|
// Chapters
|
||||||
override fun chapterListRequest(manga: SManga): Request {
|
override fun chapterListRequest(manga: SManga): Request {
|
||||||
return GET(baseUrl + manga.url, headers)
|
return mangaDetailsRequest(manga)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun chapterListParse(response: Response): List<SChapter> {
|
override fun chapterListParse(response: Response): List<SChapter> {
|
||||||
val document = response.asJsoup()
|
val resultIds = response.parseAs<DetailResponse>()
|
||||||
val nuxtData = document.selectFirst("script#__NUXT_DATA__")?.data()
|
val episodeIds = resultIds.webTitle.episodeIdList.map { it.toString() }
|
||||||
?: throw Exception("Could not find Nuxt data")
|
|
||||||
|
|
||||||
val rootArray = nuxtData.parseAs<JsonArray>()
|
if (episodeIds.isEmpty()) {
|
||||||
fun resolve(ref: JsonElement): JsonElement = rootArray[ref.jsonPrimitive.content.toInt()]
|
return emptyList()
|
||||||
|
}
|
||||||
val titleDetailsObject = rootArray.first { it is JsonObject && it.jsonObject.containsKey("episode_id_list") }.jsonObject
|
|
||||||
val episodeIdRefs = resolve(titleDetailsObject["episode_id_list"]!!).jsonArray
|
|
||||||
val episodeIds = episodeIdRefs.map { resolve(it).jsonPrimitive.content }
|
|
||||||
|
|
||||||
val formBody = FormBody.Builder()
|
val formBody = FormBody.Builder()
|
||||||
.add("platform", "3")
|
.add("platform", "3")
|
||||||
@ -284,18 +238,13 @@ class MagazinePocket : HttpSource() {
|
|||||||
|
|
||||||
val result = apiResponse.parseAs<EpisodeListResponse>()
|
val result = apiResponse.parseAs<EpisodeListResponse>()
|
||||||
|
|
||||||
return result.episodeList.map { chapter ->
|
return result.episodeList
|
||||||
SChapter.create().apply {
|
.map { it.toSChapter(dateFormat) }
|
||||||
val paddedId = chapter.titleId.toString().padStart(5, '0')
|
.reversed()
|
||||||
url = "/title/$paddedId/episode/${chapter.episodeId}"
|
}
|
||||||
name = if (chapter.point > 0 && chapter.badge != 3 && chapter.rentalFinishTime == null) {
|
|
||||||
"🔒 ${chapter.episodeName}"
|
override fun getChapterUrl(chapter: SChapter): String {
|
||||||
} else {
|
return baseUrl + chapter.url
|
||||||
chapter.episodeName
|
|
||||||
}
|
|
||||||
date_upload = dateFormat.tryParse(chapter.startTime)
|
|
||||||
}
|
|
||||||
}.reversed()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pages
|
// Pages
|
||||||
@ -381,72 +330,72 @@ class MagazinePocket : HttpSource() {
|
|||||||
Pair("(ランキング) ドラマ", "/ranking/27"),
|
Pair("(ランキング) ドラマ", "/ranking/27"),
|
||||||
Pair("(ランキング) ファンタジー", "/ranking/28"),
|
Pair("(ランキング) ファンタジー", "/ranking/28"),
|
||||||
Pair("(ランキング) 日常", "/ranking/29"),
|
Pair("(ランキング) 日常", "/ranking/29"),
|
||||||
Pair("恋愛・ラブコメ", "/search/genre/1"),
|
Pair("恋愛・ラブコメ", "/genre/1"),
|
||||||
Pair("ホラー・ミステリー・サスペンス", "/search/genre/2"),
|
Pair("ホラー・ミステリー・サスペンス", "/genre/2"),
|
||||||
Pair("ギャグ・コメディー・日常", "/search/genre/3"),
|
Pair("ギャグ・コメディー・日常", "/genre/3"),
|
||||||
Pair("SF・ファンタジー", "/search/genre/4"),
|
Pair("SF・ファンタジー", "/genre/4"),
|
||||||
Pair("スポーツ", "/search/genre/5"),
|
Pair("スポーツ", "/genre/5"),
|
||||||
Pair("ヒューマンドラマ", "/search/genre/6"),
|
Pair("ヒューマンドラマ", "/genre/6"),
|
||||||
Pair("裏社会・アングラ・ヤンキー", "/search/genre/7"),
|
Pair("裏社会・アングラ・ヤンキー", "/genre/7"),
|
||||||
Pair("アクション・バトル", "/search/genre/8"),
|
Pair("アクション・バトル", "/genre/8"),
|
||||||
Pair("異世界・異能力", "/search/genre/9"),
|
Pair("異世界・異能力", "/genre/9"),
|
||||||
Pair("読切", "/search/genre/10"),
|
Pair("読切", "/genre/10"),
|
||||||
Pair("MGP", "/search/genre/11"),
|
Pair("MGP", "/genre/11"),
|
||||||
Pair("第98回新人漫画賞", "/search/genre/12"),
|
Pair("第98回新人漫画賞", "/genre/12"),
|
||||||
Pair("第99回新人漫画賞", "/search/genre/13"),
|
Pair("第99回新人漫画賞", "/genre/13"),
|
||||||
Pair("第100回新人漫画賞", "/search/genre/14"),
|
Pair("第100回新人漫画賞", "/genre/14"),
|
||||||
Pair("第101回新人漫画賞", "/search/genre/15"),
|
Pair("第101回新人漫画賞", "/genre/15"),
|
||||||
Pair("第102回新人漫画賞", "/search/genre/16"),
|
Pair("第102回新人漫画賞", "/genre/16"),
|
||||||
Pair("第103回新人漫画賞", "/search/genre/17"),
|
Pair("第103回新人漫画賞", "/genre/17"),
|
||||||
Pair("第104回新人漫画賞", "/search/genre/18"),
|
Pair("第104回新人漫画賞", "/genre/18"),
|
||||||
Pair("第105回新人漫画賞", "/search/genre/19"),
|
Pair("第105回新人漫画賞", "/genre/19"),
|
||||||
Pair("第106回新人漫画賞", "/search/genre/20"),
|
Pair("第106回新人漫画賞", "/genre/20"),
|
||||||
Pair("第107回新人漫画賞", "/search/genre/21"),
|
Pair("第107回新人漫画賞", "/genre/21"),
|
||||||
Pair("第108回新人漫画賞", "/search/genre/22"),
|
Pair("第108回新人漫画賞", "/genre/22"),
|
||||||
Pair("第109回新人漫画賞", "/search/genre/23"),
|
Pair("第109回新人漫画賞", "/genre/23"),
|
||||||
Pair("第110回新人漫画賞", "/search/genre/24"),
|
Pair("第110回新人漫画賞", "/genre/24"),
|
||||||
Pair("2023真夏の読み切り15連弾", "/search/genre/25"),
|
Pair("2023真夏の読み切り15連弾", "/genre/25"),
|
||||||
Pair("マガジンライズ", "/search/genre/26"),
|
Pair("マガジンライズ", "/genre/26"),
|
||||||
Pair("第111回新人漫画賞", "/search/genre/27"),
|
Pair("第111回新人漫画賞", "/genre/27"),
|
||||||
Pair("少女/女性", "/search/genre/28"),
|
Pair("少女/女性", "/genre/28"),
|
||||||
Pair("新人漫画大賞", "/search/genre/29"),
|
Pair("新人漫画大賞", "/genre/29"),
|
||||||
Pair("第75回新人漫画賞", "/search/genre/30"),
|
Pair("第75回新人漫画賞", "/genre/30"),
|
||||||
Pair("第79回新人漫画賞", "/search/genre/31"),
|
Pair("第79回新人漫画賞", "/genre/31"),
|
||||||
Pair("第85回新人漫画賞", "/search/genre/32"),
|
Pair("第85回新人漫画賞", "/genre/32"),
|
||||||
Pair("第88回新人漫画賞", "/search/genre/33"),
|
Pair("第88回新人漫画賞", "/genre/33"),
|
||||||
Pair("第89回新人漫画賞", "/search/genre/34"),
|
Pair("第89回新人漫画賞", "/genre/34"),
|
||||||
Pair("第91回新人漫画賞", "/search/genre/35"),
|
Pair("第91回新人漫画賞", "/genre/35"),
|
||||||
Pair("第92回新人漫画賞", "/search/genre/36"),
|
Pair("第92回新人漫画賞", "/genre/36"),
|
||||||
Pair("第94回新人漫画賞", "/search/genre/37"),
|
Pair("第94回新人漫画賞", "/genre/37"),
|
||||||
Pair("第95回新人漫画賞", "/search/genre/38"),
|
Pair("第95回新人漫画賞", "/genre/38"),
|
||||||
Pair("第96回新人漫画賞", "/search/genre/39"),
|
Pair("第96回新人漫画賞", "/genre/39"),
|
||||||
Pair("第97回新人漫画賞", "/search/genre/40"),
|
Pair("第97回新人漫画賞", "/genre/40"),
|
||||||
Pair("第112回新人漫画賞", "/search/genre/41"),
|
Pair("第112回新人漫画賞", "/genre/41"),
|
||||||
Pair("第113回新人漫画大賞", "/search/genre/42"),
|
Pair("第113回新人漫画大賞", "/genre/42"),
|
||||||
Pair("サッカー", "/search/genre/43"),
|
Pair("サッカー", "/genre/43"),
|
||||||
Pair("テニス", "/search/genre/44"),
|
Pair("テニス", "/genre/44"),
|
||||||
Pair("バスケ", "/search/genre/45"),
|
Pair("バスケ", "/genre/45"),
|
||||||
Pair("格闘技", "/search/genre/46"),
|
Pair("格闘技", "/genre/46"),
|
||||||
Pair("野球", "/search/genre/47"),
|
Pair("野球", "/genre/47"),
|
||||||
Pair("女性向け異世界", "/search/genre/48"),
|
Pair("女性向け異世界", "/genre/48"),
|
||||||
Pair("アニメ化", "/search/genre/49"),
|
Pair("アニメ化", "/genre/49"),
|
||||||
Pair("実写化", "/search/genre/50"),
|
Pair("実写化", "/genre/50"),
|
||||||
Pair("車・バイク", "/search/genre/51"),
|
Pair("車・バイク", "/genre/51"),
|
||||||
Pair("グルメ・料理", "/search/genre/52"),
|
Pair("グルメ・料理", "/genre/52"),
|
||||||
Pair("医療", "/search/genre/53"),
|
Pair("医療", "/genre/53"),
|
||||||
Pair("頭脳戦", "/search/genre/54"),
|
Pair("頭脳戦", "/genre/54"),
|
||||||
Pair("サバイバル", "/search/genre/55"),
|
Pair("サバイバル", "/genre/55"),
|
||||||
Pair("復讐劇", "/search/genre/56"),
|
Pair("復讐劇", "/genre/56"),
|
||||||
Pair("70~80年代", "/search/genre/57"),
|
Pair("70~80年代", "/genre/57"),
|
||||||
Pair("90年代", "/search/genre/58"),
|
Pair("90年代", "/genre/58"),
|
||||||
Pair("金田一シリーズ", "/search/genre/59"),
|
Pair("金田一シリーズ", "/genre/59"),
|
||||||
Pair("第114回新人漫画大賞", "/search/genre/60"),
|
Pair("第114回新人漫画大賞", "/genre/60"),
|
||||||
Pair("連載獲得ダービー", "/search/genre/61"),
|
Pair("連載獲得ダービー", "/genre/61"),
|
||||||
Pair("有名漫画賞", "/search/genre/62"),
|
Pair("有名漫画賞", "/genre/62"),
|
||||||
Pair("探偵・警察", "/search/genre/63"),
|
Pair("探偵・警察", "/genre/63"),
|
||||||
Pair("歴史・時代", "/search/genre/64"),
|
Pair("歴史・時代", "/genre/64"),
|
||||||
Pair("不倫・浮気", "/search/genre/65"),
|
Pair("不倫・浮気", "/genre/65"),
|
||||||
Pair("犬・猫", "/search/genre/66"),
|
Pair("犬・猫", "/genre/66"),
|
||||||
)
|
)
|
||||||
|
|
||||||
// Unsupported
|
// Unsupported
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user