[Shinigami] Fixes

Koitharu 1 year ago
parent b24741678c
commit d2b2578a3a
Signed by: Koitharu
GPG Key ID: 676DEE768C17A9D7

@ -6,282 +6,279 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.network.UserAgents
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
import org.koitharu.kotatsu.parsers.util.json.* import org.koitharu.kotatsu.parsers.util.json.*
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
import org.koitharu.kotatsu.parsers.Broken
@Broken("TODO: Add author search")
@MangaSourceParser("SHINIGAMI", "Shinigami", "id") @MangaSourceParser("SHINIGAMI", "Shinigami", "id")
internal class Shinigami(context: MangaLoaderContext) : internal class Shinigami(context: MangaLoaderContext) :
LegacyPagedMangaParser(context, MangaParserSource.SHINIGAMI, 24) { LegacyPagedMangaParser(context, MangaParserSource.SHINIGAMI, 24) {
override val configKeyDomain = ConfigKey.Domain("id.shinigami.asia") override val configKeyDomain = ConfigKey.Domain("id.shinigami.asia")
private val apiSuffix = "api.shngm.io/v1" private val apiSuffix = "api.shngm.io/v1"
private val cdnSuffix = "storage.shngm.id" private val cdnSuffix = "storage.shngm.id"
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) { override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
super.onCreateConfig(keys) super.onCreateConfig(keys)
keys.add(userAgentKey) keys.add(userAgentKey)
} }
override fun getRequestHeaders(): Headers = Headers.Builder()
.add("referer", "https://$domain/")
.add("sec-fetch-dest", "empty")
.build()
override val availableSortOrders: Set<SortOrder> = EnumSet.of( override fun getRequestHeaders(): Headers = Headers.Builder()
SortOrder.POPULARITY, .add("referer", "https://$domain/")
SortOrder.POPULARITY_ASC, .add("sec-fetch-dest", "empty")
SortOrder.NEWEST, .build()
SortOrder.NEWEST_ASC,
SortOrder.RATING,
SortOrder.RATING_ASC,
)
override suspend fun getFilterOptions(): MangaListFilterOptions { override val availableSortOrders: Set<SortOrder> = EnumSet.of(
return MangaListFilterOptions( SortOrder.POPULARITY,
availableTags = fetchTags(), SortOrder.POPULARITY_ASC,
availableStates = EnumSet.of( SortOrder.NEWEST,
MangaState.ONGOING, SortOrder.NEWEST_ASC,
MangaState.FINISHED, SortOrder.RATING,
MangaState.PAUSED SortOrder.RATING_ASC,
), )
availableContentTypes = EnumSet.of(
ContentType.MANGA,
ContentType.MANHWA,
ContentType.MANHUA
),
)
}
override val filterCapabilities: MangaListFilterCapabilities override suspend fun getFilterOptions(): MangaListFilterOptions {
get() = MangaListFilterCapabilities( return MangaListFilterOptions(
isMultipleTagsSupported = true, availableTags = fetchTags(),
isTagsExclusionSupported = true, availableStates = EnumSet.of(
isSearchSupported = true, MangaState.ONGOING,
isSearchWithFiltersSupported = true, MangaState.FINISHED,
) MangaState.PAUSED,
),
availableContentTypes = EnumSet.of(
ContentType.MANGA,
ContentType.MANHWA,
ContentType.MANHUA,
),
)
}
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> { override val filterCapabilities: MangaListFilterCapabilities
val url = buildString { get() = MangaListFilterCapabilities(
append("https://") isMultipleTagsSupported = true,
append(apiSuffix) isTagsExclusionSupported = true,
append("/manga/list") isSearchSupported = true,
append("?page=") isSearchWithFiltersSupported = true,
append(page.toString()) )
append("&page_size=")
append(pageSize)
append("&sort=") override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {
append( val url = buildString {
when (order) { append("https://")
SortOrder.POPULARITY, SortOrder.POPULARITY_ASC -> "popularity" append(apiSuffix)
SortOrder.NEWEST, SortOrder.NEWEST_ASC -> "latest" append("/manga/list")
SortOrder.RATING, SortOrder.RATING_ASC -> "rating" append("?page=")
else -> "latest" append(page.toString())
} append("&page_size=")
) append(pageSize)
append("&sort_order=")
append(
when (order) {
SortOrder.POPULARITY_ASC, SortOrder.NEWEST_ASC, SortOrder.RATING_ASC -> "asc"
else -> "desc"
}
)
filter.states.oneOrThrowIfMany()?.let { append("&sort=")
append("&status=") append(
append( when (order) {
when (it) { SortOrder.POPULARITY, SortOrder.POPULARITY_ASC -> "popularity"
MangaState.FINISHED -> "completed" SortOrder.NEWEST, SortOrder.NEWEST_ASC -> "latest"
MangaState.ONGOING -> "ongoing" SortOrder.RATING, SortOrder.RATING_ASC -> "rating"
MangaState.PAUSED -> "hiatus" else -> "latest"
else -> "" },
} )
) append("&sort_order=")
} append(
when (order) {
SortOrder.POPULARITY_ASC, SortOrder.NEWEST_ASC, SortOrder.RATING_ASC -> "asc"
else -> "desc"
},
)
if (filter.types.isNotEmpty()) { filter.states.oneOrThrowIfMany()?.let {
filter.types.forEach { append("&status=")
append("&format=") append(
append( when (it) {
when (it) { MangaState.FINISHED -> "completed"
ContentType.MANGA -> "manga" MangaState.ONGOING -> "ongoing"
ContentType.MANHUA -> "manhua" MangaState.PAUSED -> "hiatus"
ContentType.MANHWA -> "manhwa" else -> ""
else -> "" },
} )
) }
}
}
if (filter.tags.isNotEmpty()) { if (filter.types.isNotEmpty()) {
append("&genre_include=") filter.types.forEach {
filter.tags.joinTo(this, separator = ",") { it.key } append("&format=")
append("&genre_include_mode=and") append(
} when (it) {
ContentType.MANGA -> "manga"
ContentType.MANHUA -> "manhua"
ContentType.MANHWA -> "manhwa"
else -> ""
},
)
}
}
if (filter.tagsExclude.isNotEmpty()) { if (filter.tags.isNotEmpty()) {
append("&genre_exclude=") append("&genre_include=")
filter.tagsExclude.joinTo(this, separator = ",") { it.key } filter.tags.joinTo(this, separator = ",") { it.key }
append("&genre_exclude_mode=and") append("&genre_include_mode=and")
} }
if (!filter.query.isNullOrEmpty()) { if (filter.tagsExclude.isNotEmpty()) {
append("&q=") append("&genre_exclude=")
val encodedQuery = filter.query.splitByWhitespace() filter.tagsExclude.joinTo(this, separator = ",") { it.key }
.joinToString(separator = "+") { part -> append("&genre_exclude_mode=and")
part.urlEncoded() }
}
append(encodedQuery)
}
}
val json = webClient.httpGet(url).parseJson() if (!filter.author.isNullOrEmpty()) {
val data = json.getJSONArray("data") append("&author=")
append(filter.author.lowercase().replace(' ', '-').urlEncoded())
return data.mapJSON { jo -> }
val id = jo.getString("manga_id")
Manga(
id = generateUid(id),
url = "/manga/detail/$id",
publicUrl = "https://$domain/series/$id",
title = jo.getString("title"),
altTitles = setOf(jo.optString("alternative_title") ?: ""),
coverUrl = jo.getString("cover_image_url"),
largeCoverUrl = jo.optString("cover_portrait_url")
.takeIf { it.isNotEmpty() },
authors = jo.optJSONObject("taxonomy")
?.optJSONArray("Author")
?.mapJSONToSet { x ->
x.getString("name")
}.orEmpty(),
tags = jo.optJSONObject("taxonomy")
?.optJSONArray("Genre")
?.mapJSONToSet { x ->
MangaTag(
key = x.getString("slug"),
title = x.getString("name"),
source = source
)
}.orEmpty(),
state = when (jo.getInt("status")) {
1 -> MangaState.ONGOING
2 -> MangaState.FINISHED
3 -> MangaState.PAUSED
else -> null
},
description = jo.optString("description"),
contentRating = null,
source = source,
rating = RATING_UNKNOWN
)
}
}
override suspend fun getDetails(manga: Manga): Manga { if (!filter.query.isNullOrEmpty()) {
val json = webClient.httpGet("https://$apiSuffix" + manga.url).parseJson() append("&q=")
val jo = json.getJSONObject("data") val encodedQuery = filter.query.splitByWhitespace()
val id = jo.getString("manga_id") .joinToString(separator = "+") { part ->
part.urlEncoded()
return manga.copy( }
tags = jo.optJSONObject("taxonomy") append(encodedQuery)
?.optJSONArray("Genre") }
?.mapJSONToSet { x -> }
MangaTag(
key = x.getString("slug"),
title = x.getString("name"),
source = source,
)
}.orEmpty(),
authors = jo.optJSONObject("taxonomy")
?.optJSONArray("Author")
?.mapJSONToSet { x ->
x.getString("name")
}.orEmpty(),
state = when (jo.getInt("status")) {
1 -> MangaState.ONGOING
2 -> MangaState.FINISHED
3 -> MangaState.PAUSED
else -> null
},
description = jo.optString("description"),
largeCoverUrl = jo.optString("cover_portrait_url")
.takeIf { it.isNotEmpty() },
chapters = getChapters(id)
)
}
private suspend fun getChapters(mangaId: String): List<MangaChapter> { val json = webClient.httpGet(url).parseJson()
val url = "https://$apiSuffix/chapter/$mangaId/list?page=1&page_size=9999&sort_by=chapter_number&sort_order=asc" val data = json.getJSONArray("data")
val json = webClient.httpGet(url).parseJson()
val data = json.getJSONArray("data")
return data.mapJSON { jo ->
val chapterId = jo.getString("chapter_id")
val number = jo.getInt("chapter_number")
val title = jo.optString("chapter_title")
.takeIf { it.isNotEmpty() }
?: "Chapter $number"
MangaChapter(
id = generateUid(chapterId),
title = title,
number = number.toFloat(),
url = "chapter/detail/$chapterId",
scanlator = null,
uploadDate = jo.getString("release_date").parseDate(),
branch = null,
source = source,
volume = 0
)
}
}
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> { return data.mapJSON { jo ->
val json = webClient.httpGet("https://$apiSuffix/${chapter.url}").parseJson() val id = jo.getString("manga_id")
val data = json.getJSONObject("data") Manga(
val chapterData = data.getJSONObject("chapter") id = generateUid(id),
val basePath = chapterData.getString("path") url = "/manga/detail/$id",
val datas = chapterData.getJSONArray("data") publicUrl = "https://$domain/series/$id",
title = jo.getString("title"),
return List(datas.length()) { i -> altTitles = setOfNotNull(jo.getStringOrNull("alternative_title")),
val imgExt = datas.getString(i) coverUrl = jo.getStringOrNull("cover_image_url"),
val imageUrl = "https://$cdnSuffix" + basePath + imgExt largeCoverUrl = jo.getStringOrNull("cover_portrait_url"),
authors = jo.optJSONObject("taxonomy")
MangaPage( ?.optJSONArray("Author")
id = generateUid(imageUrl), ?.mapJSONToSet { x ->
url = imageUrl, x.getString("name")
preview = null, }.orEmpty(),
source = source, tags = jo.optJSONObject("taxonomy")
) ?.optJSONArray("Genre")
} ?.mapJSONToSet { x ->
} MangaTag(
key = x.getString("slug"),
title = x.getString("name").toTitleCase(sourceLocale),
source = source,
)
}.orEmpty(),
state = when (jo.getIntOrDefault("status", 0)) {
1 -> MangaState.ONGOING
2 -> MangaState.FINISHED
3 -> MangaState.PAUSED
else -> null
},
description = jo.getStringOrNull("description"),
contentRating = null,
source = source,
rating = RATING_UNKNOWN,
)
}
}
private suspend fun fetchTags(): Set<MangaTag> { override suspend fun getDetails(manga: Manga): Manga {
val json = webClient.httpGet("https://$apiSuffix/genre/list").parseJson() val json = webClient.httpGet("https://$apiSuffix${manga.url}").parseJson()
val jo = json.getJSONObject("data")
return json.getJSONArray("data").mapJSONToSet { x -> val id = jo.getString("manga_id")
MangaTag(
key = x.getString("slug"),
title = x.getString("name"),
source = source,
)
}
}
private fun String.parseDate(): Long { return manga.copy(
return try { tags = jo.optJSONObject("taxonomy")
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US) ?.optJSONArray("Genre")
.apply { ?.mapJSONToSet { x ->
timeZone = TimeZone.getTimeZone("UTC") MangaTag(
} key = x.getString("slug"),
.parse(this)?.time ?: 0L title = x.getString("name"),
} catch (e: Exception) { source = source,
0L )
} } ?: manga.tags,
} authors = jo.optJSONObject("taxonomy")
} ?.optJSONArray("Author")
?.mapJSONToSet { x ->
x.getString("name")
} ?: manga.authors,
state = when (jo.getIntOrDefault("status", 0)) {
1 -> MangaState.ONGOING
2 -> MangaState.FINISHED
3 -> MangaState.PAUSED
else -> null
},
description = jo.getStringOrNull("description"),
largeCoverUrl = jo.getStringOrNull("cover_portrait_url"),
chapters = getChapters(id),
)
}
private suspend fun getChapters(mangaId: String): List<MangaChapter> {
val url = "https://$apiSuffix/chapter/$mangaId/list?page=1&page_size=9999&sort_by=chapter_number&sort_order=asc"
val json = webClient.httpGet(url).parseJson()
val data = json.getJSONArray("data")
return data.mapJSON { jo ->
val chapterId = jo.getString("chapter_id")
val title = jo.getStringOrNull("chapter_title")
MangaChapter(
id = generateUid(chapterId),
title = title,
number = jo.getFloatOrDefault("chapter_number", 0f),
url = "chapter/detail/$chapterId",
scanlator = null,
uploadDate = jo.getString("release_date").parseDate(),
branch = null,
source = source,
volume = 0,
)
}
}
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
val json = webClient.httpGet("https://$apiSuffix/${chapter.url}").parseJson()
val data = json.getJSONObject("data")
val chapterData = data.getJSONObject("chapter")
val basePath = chapterData.getString("path")
val datas = chapterData.getJSONArray("data")
return List(datas.length()) { i ->
val imgExt = datas.getString(i)
val imageUrl = "https://$cdnSuffix$basePath$imgExt"
MangaPage(
id = generateUid(imageUrl),
url = imageUrl,
preview = null,
source = source,
)
}
}
private suspend fun fetchTags(): Set<MangaTag> {
val json = webClient.httpGet("https://$apiSuffix/genre/list").parseJson()
return json.getJSONArray("data").mapJSONToSet { x ->
MangaTag(
key = x.getString("slug"),
title = x.getString("name").toTitleCase(sourceLocale),
source = source,
)
}
}
private fun String.parseDate(): Long {
return try {
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US)
.apply {
timeZone = TimeZone.getTimeZone("UTC")
}
.parse(this)?.time ?: 0L
} catch (e: Exception) {
0L
}
}
}

Loading…
Cancel
Save