From 006efbbb773799832a514667ff42afeabb0914ad Mon Sep 17 00:00:00 2001 From: Aria Moradi Date: Sat, 18 Sep 2021 19:40:44 +0430 Subject: [PATCH] add ChapterRecognition from tachiyomi, closes #10 --- .../tachiyomi/source/local/LocalSource.kt | 3 +- .../util/chapter/ChapterRecognition.kt | 152 ++++++++++++++++++ .../suwayomi/tachidesk/manga/impl/Chapter.kt | 20 ++- 3 files changed, 168 insertions(+), 7 deletions(-) create mode 100644 server/src/main/kotlin/eu/kanade/tachiyomi/util/chapter/ChapterRecognition.kt diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/source/local/LocalSource.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/source/local/LocalSource.kt index 7e452e93..74f48dbc 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/source/local/LocalSource.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/source/local/LocalSource.kt @@ -16,6 +16,7 @@ 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.chapter.ChapterRecognition import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder import eu.kanade.tachiyomi.util.storage.EpubFile import kotlinx.serialization.json.Json @@ -248,7 +249,7 @@ class LocalSource : HttpSource() { val chapNameCut = stripMangaTitle(name, manga.title) if (chapNameCut.isNotEmpty()) name = chapNameCut -// ChapterRecognition.parseChapterNumber(this, manga) + ChapterRecognition.parseChapterNumber(this, manga) } } .sortedWith { c1, c2 -> diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/util/chapter/ChapterRecognition.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/util/chapter/ChapterRecognition.kt new file mode 100644 index 00000000..dd44f42b --- /dev/null +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/util/chapter/ChapterRecognition.kt @@ -0,0 +1,152 @@ +package eu.kanade.tachiyomi.util.chapter + +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga + +/** + * -R> = regex conversion. + */ +object ChapterRecognition { + /** + * All cases with Ch.xx + * Mokushiroku Alice Vol.1 Ch. 4: Misrepresentation -R> 4 + */ + private val basic = Regex("""(?<=ch\.) *([0-9]+)(\.[0-9]+)?(\.?[a-z]+)?""") + + /** + * Regex used when only one number occurrence + * Example: Bleach 567: Down With Snowwhite -R> 567 + */ + private val occurrence = Regex("""([0-9]+)(\.[0-9]+)?(\.?[a-z]+)?""") + + /** + * Regex used when manga title removed + * Example: Solanin 028 Vol. 2 -> 028 Vol.2 -> 028Vol.2 -R> 028 + */ + private val withoutManga = Regex("""^([0-9]+)(\.[0-9]+)?(\.?[a-z]+)?""") + + /** + * Regex used to remove unwanted tags + * Example Prison School 12 v.1 vol004 version1243 volume64 -R> Prison School 12 + */ + private val unwanted = Regex("""(? One Piece 12special + */ + private val unwantedWhiteSpace = Regex("""(\s)(extra|special|omake)""") + + fun parseChapterNumber(chapter: SChapter, manga: SManga) { + // If chapter number is known return. + if (chapter.chapter_number == -2f || chapter.chapter_number > -1f) { + return + } + + // Get chapter title with lower case + var name = chapter.name.lowercase() + + // Remove comma's from chapter. + name = name.replace(',', '.') + + // Remove unwanted white spaces. + unwantedWhiteSpace.findAll(name).let { + it.forEach { occurrence -> name = name.replace(occurrence.value, occurrence.value.trim()) } + } + + // Remove unwanted tags. + unwanted.findAll(name).let { + it.forEach { occurrence -> name = name.replace(occurrence.value, "") } + } + + // Check base case ch.xx + if (updateChapter(basic.find(name), chapter)) { + return + } + + // Check one number occurrence. + val occurrences: MutableList = arrayListOf() + occurrence.findAll(name).let { + it.forEach { occurrence -> occurrences.add(occurrence) } + } + + if (occurrences.size == 1) { + if (updateChapter(occurrences[0], chapter)) { + return + } + } + + // Remove manga title from chapter title. + val nameWithoutManga = name.replace(manga.title.lowercase(), "").trim() + + // Check if first value is number after title remove. + if (updateChapter(withoutManga.find(nameWithoutManga), chapter)) { + return + } + + // Take the first number encountered. + if (updateChapter(occurrence.find(nameWithoutManga), chapter)) { + return + } + } + + /** + * Check if volume is found and update chapter + * @param match result of regex + * @param chapter chapter object + * @return true if volume is found + */ + private fun updateChapter(match: MatchResult?, chapter: SChapter): Boolean { + match?.let { + val initial = it.groups[1]?.value?.toFloat()!! + val subChapterDecimal = it.groups[2]?.value + val subChapterAlpha = it.groups[3]?.value + val addition = checkForDecimal(subChapterDecimal, subChapterAlpha) + chapter.chapter_number = initial.plus(addition) + return true + } + return false + } + + /** + * Check for decimal in received strings + * @param decimal decimal value of regex + * @param alpha alpha value of regex + * @return decimal/alpha float value + */ + private fun checkForDecimal(decimal: String?, alpha: String?): Float { + if (!decimal.isNullOrEmpty()) { + return decimal.toFloat() + } + + if (!alpha.isNullOrEmpty()) { + if (alpha.contains("extra")) { + return .99f + } + + if (alpha.contains("omake")) { + return .98f + } + + if (alpha.contains("special")) { + return .97f + } + + return if (alpha[0] == '.') { + // Take value after (.) + parseAlphaPostFix(alpha[1]) + } else { + parseAlphaPostFix(alpha[0]) + } + } + + return .0f + } + + /** + * x.a -> x.1, x.b -> x.2, etc + */ + private fun parseAlphaPostFix(alpha: Char): Float { + return ("0." + (alpha.code - 96).toString()).toFloat() + } +} diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Chapter.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Chapter.kt index 454d64f5..a42beba6 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Chapter.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Chapter.kt @@ -9,6 +9,7 @@ package suwayomi.tachidesk.manga.impl import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.util.chapter.ChapterRecognition import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.sql.SortOrder.DESC import org.jetbrains.exposed.sql.and @@ -52,12 +53,19 @@ object Chapter { private suspend fun getSourceChapters(mangaId: Int): List { val manga = getManga(mangaId) val source = getHttpSource(manga.sourceId.toLong()) - val chapterList = source.fetchChapterList( - SManga.create().apply { - title = manga.title - url = manga.url - } - ).awaitSingle() + + val sManga = SManga.create().apply { + title = manga.title + url = manga.url + } + + val chapterList = source.fetchChapterList(sManga).awaitSingle() + + // Recognize number for new chapters. + chapterList.forEach { + source.prepareNewChapter(it, sManga) + ChapterRecognition.parseChapterNumber(it, sManga) + } val chapterCount = chapterList.count()