From d9bd90b02f366956b7c4689c70cc07addb362456 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Fri, 28 Mar 2025 09:25:08 +0200 Subject: [PATCH] Update parsers --- build.gradle.kts | 4 +- .../kotatsu/dl/download/MangaIndex.kt | 170 ++++++++++-------- 2 files changed, 99 insertions(+), 75 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 7d92265..7f19a11 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,7 +6,7 @@ plugins { } group = "org.koitharu" -version = "0.1" +version = "0.2" tasks.withType { manifest { @@ -31,7 +31,7 @@ dependencies { testImplementation(kotlin("test")) implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.9.0") implementation("com.github.ajalt.clikt:clikt-core:5.0.1") - implementation("com.github.KotatsuApp:kotatsu-parsers:1.6") + implementation("com.github.KotatsuApp:kotatsu-parsers:dbb04d2051") implementation("com.squareup.okhttp3:okhttp:4.12.0") implementation("com.squareup.okio:okio:3.9.0") implementation("io.webfolder:quickjs:1.1.0") diff --git a/src/main/kotlin/org/koitharu/kotatsu/dl/download/MangaIndex.kt b/src/main/kotlin/org/koitharu/kotatsu/dl/download/MangaIndex.kt index f03ac71..efb7ff6 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/dl/download/MangaIndex.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/dl/download/MangaIndex.kt @@ -6,110 +6,113 @@ import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.util.find import org.koitharu.kotatsu.parsers.util.json.* import org.koitharu.kotatsu.parsers.util.toTitleCase -import java.io.File class MangaIndex(source: String?) { private val json: JSONObject = source?.let(::JSONObject) ?: JSONObject() fun setMangaInfo(manga: Manga) { - json.put("id", manga.id) - json.put("title", manga.title) - json.put("title_alt", manga.altTitle) - json.put("url", manga.url) - json.put("public_url", manga.publicUrl) - json.put("author", manga.author) - json.put("cover", manga.coverUrl) - json.put("description", manga.description) - json.put("rating", manga.rating) - json.put("nsfw", manga.isNsfw) - json.put("state", manga.state?.name) - json.put("source", manga.source.name) - json.put("cover_large", manga.largeCoverUrl) + json.put(KEY_ID, manga.id) + json.put(KEY_TITLE, manga.title) + json.put(KEY_TITLE_ALT, manga.altTitle) // for backward compatibility + json.put(KEY_ALT_TITLES, JSONArray(manga.altTitles)) + json.put(KEY_URL, manga.url) + json.put(KEY_PUBLIC_URL, manga.publicUrl) + json.put(KEY_AUTHOR, manga.author) // for backward compatibility + json.put(KEY_AUTHORS, JSONArray(manga.authors)) + json.put(KEY_COVER, manga.coverUrl) + json.put(KEY_DESCRIPTION, manga.description) + json.put(KEY_RATING, manga.rating) + json.put(KEY_CONTENT_RATING, manga.contentRating) + json.put(KEY_NSFW, manga.isNsfw) // for backward compatibility + json.put(KEY_STATE, manga.state?.name) + json.put(KEY_SOURCE, manga.source.name) + json.put(KEY_COVER_LARGE, manga.largeCoverUrl) json.put( - "tags", + KEY_TAGS, JSONArray().also { a -> for (tag in manga.tags) { val jo = JSONObject() - jo.put("key", tag.key) - jo.put("title", tag.title) + jo.put(KEY_KEY, tag.key) + jo.put(KEY_TITLE, tag.title) a.put(jo) } }, ) - if (!json.has("chapters")) { - json.put("chapters", JSONObject()) + if (!json.has(KEY_CHAPTERS)) { + json.put(KEY_CHAPTERS, JSONObject()) } - json.put("app_id", "kotatsu-dl") - json.put("app_version", "0.1") + json.put(KEY_APP_ID, "kotatsu-dl") + json.put(KEY_APP_VERSION, "0.2") } fun getMangaInfo(): Manga? = if (json.length() == 0) null else runCatching { - val source = requireNotNull(MangaParserSource.entries.find(json.getString("source"))) { + val source = requireNotNull(MangaParserSource.entries.find(json.getString(KEY_SOURCE))) { "Invalid manga source " } Manga( - id = json.getLong("id"), - title = json.getString("title"), - altTitle = json.getStringOrNull("title_alt"), - url = json.getString("url"), - publicUrl = json.getStringOrNull("public_url").orEmpty(), - author = json.getStringOrNull("author"), - largeCoverUrl = json.getStringOrNull("cover_large"), + id = json.getLong(KEY_ID), + title = json.getString(KEY_TITLE), + altTitles = json.optJSONArray(KEY_ALT_TITLES)?.toStringSet() + ?: setOfNotNull(json.getStringOrNull(KEY_TITLE_ALT)), + url = json.getString(KEY_URL), + publicUrl = json.getStringOrNull(KEY_PUBLIC_URL).orEmpty(), + authors = json.optJSONArray(KEY_AUTHORS)?.toStringSet() + ?: setOfNotNull(json.getStringOrNull(KEY_AUTHOR)), + largeCoverUrl = json.getStringOrNull(KEY_COVER_LARGE), source = source, - rating = json.getDouble("rating").toFloat(), - isNsfw = json.getBooleanOrDefault("nsfw", false), - coverUrl = json.getString("cover"), - state = json.getStringOrNull("state")?.let { stateString -> - MangaState.entries.find(stateString) - }, - description = json.getStringOrNull("description"), - tags = json.getJSONArray("tags").mapJSONToSet { x -> + rating = json.getFloatOrDefault(KEY_RATING, RATING_UNKNOWN), + contentRating = json.getEnumValueOrNull(KEY_CONTENT_RATING, ContentRating::class.java) + ?: if (json.getBooleanOrDefault(KEY_NSFW, false)) ContentRating.ADULT else null, + coverUrl = json.getStringOrNull(KEY_COVER), + state = json.getEnumValueOrNull(KEY_STATE, MangaState::class.java), + description = json.getStringOrNull(KEY_DESCRIPTION), + tags = json.getJSONArray(KEY_TAGS).mapJSONToSet { x -> MangaTag( - title = x.getString("title").toTitleCase(), - key = x.getString("key"), + title = x.getString(KEY_TITLE).toTitleCase(), + key = x.getString(KEY_KEY), source = source, ) }, - chapters = getChapters(json.getJSONObject("chapters"), source), + chapters = getChapters(json.getJSONObject(KEY_CHAPTERS), source), ) }.getOrNull() - fun getCoverEntry(): String? = json.getStringOrNull("cover_entry") + fun getCoverEntry(): String? = json.getStringOrNull(KEY_COVER_ENTRY) fun addChapter(chapter: IndexedValue, filename: String?) { - val chapters = json.getJSONObject("chapters") + val chapters = json.getJSONObject(KEY_CHAPTERS) if (!chapters.has(chapter.value.id.toString())) { val jo = JSONObject() - jo.put("number", chapter.value.number) - jo.put("volume", chapter.value.volume) - jo.put("url", chapter.value.url) - jo.put("name", chapter.value.name) - jo.put("uploadDate", chapter.value.uploadDate) - jo.put("scanlator", chapter.value.scanlator) - jo.put("branch", chapter.value.branch) - jo.put("entries", "%08d_%03d\\d{3}".format(chapter.value.branch.hashCode(), chapter.index + 1)) - jo.put("file", filename) + jo.put(KEY_NUMBER, chapter.value.number) + jo.put(KEY_VOLUME, chapter.value.volume) + jo.put(KEY_URL, chapter.value.url) + jo.put(KEY_NAME, chapter.value.title.orEmpty()) + jo.put(KEY_UPLOAD_DATE, chapter.value.uploadDate) + jo.put(KEY_SCANLATOR, chapter.value.scanlator) + jo.put(KEY_BRANCH, chapter.value.branch) + jo.put(KEY_ENTRIES, "%08d_%04d\\d{4}".format(chapter.value.branch.hashCode(), chapter.index + 1)) + jo.put(KEY_FILE, filename) chapters.put(chapter.value.id.toString(), jo) } } fun removeChapter(id: Long): Boolean { - return json.has("chapters") && json.getJSONObject("chapters").remove(id.toString()) != null + return json.has(KEY_CHAPTERS) && json.getJSONObject(KEY_CHAPTERS).remove(id.toString()) != null } fun getChapterFileName(chapterId: Long): String? { - return json.optJSONObject("chapters")?.optJSONObject(chapterId.toString())?.getStringOrNull("file") + return json.optJSONObject(KEY_CHAPTERS)?.optJSONObject(chapterId.toString())?.getStringOrNull(KEY_FILE) } fun setCoverEntry(name: String) { - json.put("cover_entry", name) + json.put(KEY_COVER_ENTRY, name) } fun getChapterNamesPattern(chapter: MangaChapter) = Regex( - json.getJSONObject("chapters") + json.getJSONObject(KEY_CHAPTERS) .getJSONObject(chapter.id.toString()) - .getString("entries"), + .getString(KEY_ENTRIES), ) fun clear() { @@ -133,13 +136,13 @@ class MangaIndex(source: String?) { chapters.add( MangaChapter( id = k.toLong(), - name = v.getString("name"), - url = v.getString("url"), - number = v.getFloatOrDefault("number", 0f), - volume = v.getIntOrDefault("volume", 0), - uploadDate = v.getLongOrDefault("uploadDate", 0L), - scanlator = v.getStringOrNull("scanlator"), - branch = v.getStringOrNull("branch"), + title = v.getStringOrNull(KEY_NAME), + url = v.getString(KEY_URL), + number = v.getFloatOrDefault(KEY_NUMBER, 0f), + volume = v.getIntOrDefault(KEY_VOLUME, 0), + uploadDate = v.getLongOrDefault(KEY_UPLOAD_DATE, 0L), + scanlator = v.getStringOrNull(KEY_SCANLATOR), + branch = v.getStringOrNull(KEY_BRANCH), source = source, ), ) @@ -151,14 +154,35 @@ class MangaIndex(source: String?) { companion object { - fun read(file: File): MangaIndex? { - if (file.exists() && file.canRead()) { - val text = file.readText() - if (text.length > 2) { - return MangaIndex(text) - } - } - return null - } + private const val KEY_ID = "id" + private const val KEY_TITLE = "title" + private const val KEY_TITLE_ALT = "title_alt" + private const val KEY_ALT_TITLES = "alt_titles" + private const val KEY_URL = "url" + private const val KEY_PUBLIC_URL = "public_url" + private const val KEY_AUTHOR = "author" + private const val KEY_AUTHORS = "authors" + private const val KEY_COVER = "cover" + private const val KEY_DESCRIPTION = "description" + private const val KEY_RATING = "rating" + private const val KEY_CONTENT_RATING = "content_rating" + private const val KEY_NSFW = "nsfw" + private const val KEY_STATE = "state" + private const val KEY_SOURCE = "source" + private const val KEY_COVER_LARGE = "cover_large" + private const val KEY_TAGS = "tags" + private const val KEY_CHAPTERS = "chapters" + private const val KEY_NUMBER = "number" + private const val KEY_VOLUME = "volume" + private const val KEY_NAME = "name" + private const val KEY_UPLOAD_DATE = "uploadDate" + private const val KEY_SCANLATOR = "scanlator" + private const val KEY_BRANCH = "branch" + private const val KEY_ENTRIES = "entries" + private const val KEY_FILE = "file" + private const val KEY_COVER_ENTRY = "cover_entry" + private const val KEY_KEY = "key" + private const val KEY_APP_ID = "app_id" + private const val KEY_APP_VERSION = "app_version" } -} +} \ No newline at end of file