MangaMoins: Add source (#2284)
Co-authored-by: epikaigle444 <epikaigle444@gmail.com> Co-authored-by: Koitharu <nvasya95@gmail.com> Co-authored-by: dragonx943 <premieregirl26@gmail.com>master
parent
58357a3745
commit
a084909507
@ -1 +1 @@
|
|||||||
total: 1253
|
total: 1254
|
||||||
@ -0,0 +1,230 @@
|
|||||||
|
package org.koitharu.kotatsu.parsers.site.fr
|
||||||
|
|
||||||
|
import org.koitharu.kotatsu.parsers.Broken
|
||||||
|
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||||
|
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||||
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
|
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
||||||
|
import org.koitharu.kotatsu.parsers.model.MangaListFilter
|
||||||
|
import org.koitharu.kotatsu.parsers.model.MangaListFilterCapabilities
|
||||||
|
import org.koitharu.kotatsu.parsers.model.MangaListFilterOptions
|
||||||
|
import org.koitharu.kotatsu.parsers.model.MangaPage
|
||||||
|
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||||
|
import org.koitharu.kotatsu.parsers.model.MangaState
|
||||||
|
import org.koitharu.kotatsu.parsers.model.RATING_UNKNOWN
|
||||||
|
import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||||
|
import org.koitharu.kotatsu.parsers.util.generateUid
|
||||||
|
import org.koitharu.kotatsu.parsers.util.parseHtml
|
||||||
|
import org.koitharu.kotatsu.parsers.util.toAbsoluteUrl
|
||||||
|
import org.koitharu.kotatsu.parsers.config.ConfigKey
|
||||||
|
import org.koitharu.kotatsu.parsers.core.SinglePageMangaParser
|
||||||
|
import java.util.EnumSet
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
@Broken
|
||||||
|
@MangaSourceParser("MANGAMOINS", "MangaMoins", "fr")
|
||||||
|
internal class MangaMoins(context: MangaLoaderContext) :
|
||||||
|
SinglePageMangaParser(context, MangaParserSource.MANGAMOINS) {
|
||||||
|
|
||||||
|
override val configKeyDomain = ConfigKey.Domain("mangamoins.com")
|
||||||
|
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED)
|
||||||
|
override val filterCapabilities = MangaListFilterCapabilities()
|
||||||
|
|
||||||
|
override suspend fun getFilterOptions(): MangaListFilterOptions = MangaListFilterOptions()
|
||||||
|
|
||||||
|
override suspend fun getList(order: SortOrder, filter: MangaListFilter): List<Manga> {
|
||||||
|
return listOf(
|
||||||
|
Manga(
|
||||||
|
id = generateUid("OP"),
|
||||||
|
title = "One Piece",
|
||||||
|
altTitles = emptySet(),
|
||||||
|
url = "OP",
|
||||||
|
publicUrl = "https://mangamoins.com/",
|
||||||
|
rating = RATING_UNKNOWN,
|
||||||
|
contentRating = null,
|
||||||
|
coverUrl = "https://mangamoins.com/files/scans/OP1161/thumbnail.png",
|
||||||
|
tags = emptySet(),
|
||||||
|
state = MangaState.ONGOING,
|
||||||
|
authors = setOf("Eiichiro Oda"),
|
||||||
|
source = source,
|
||||||
|
),
|
||||||
|
Manga(
|
||||||
|
id = generateUid("LCDL"),
|
||||||
|
title = "Les Carnets de l'Apothicaire",
|
||||||
|
altTitles = emptySet(),
|
||||||
|
url = "LCDL",
|
||||||
|
publicUrl = "https://mangamoins.com/",
|
||||||
|
rating = RATING_UNKNOWN,
|
||||||
|
contentRating = null,
|
||||||
|
coverUrl = "https://mangamoins.com/files/scans/LCDL76.2/thumbnail.png",
|
||||||
|
tags = emptySet(),
|
||||||
|
state = MangaState.ONGOING,
|
||||||
|
authors = setOf("Itsuki Nanao", "Nekokurage"),
|
||||||
|
source = source,
|
||||||
|
),
|
||||||
|
Manga(
|
||||||
|
id = generateUid("JKM"),
|
||||||
|
title = "Jujutsu Kaisen Modulo",
|
||||||
|
altTitles = emptySet(),
|
||||||
|
url = "JKM",
|
||||||
|
publicUrl = "https://mangamoins.com/",
|
||||||
|
rating = RATING_UNKNOWN,
|
||||||
|
contentRating = null,
|
||||||
|
coverUrl = "https://mangamoins.com/files/scans/JKM1/thumbnail.png",
|
||||||
|
tags = emptySet(),
|
||||||
|
state = MangaState.ONGOING,
|
||||||
|
authors = setOf("Gege Akutami", "Yuji Iwasaki"),
|
||||||
|
source = source,
|
||||||
|
),
|
||||||
|
Manga(
|
||||||
|
id = generateUid("OPC"),
|
||||||
|
title = "One Piece Colo",
|
||||||
|
altTitles = emptySet(),
|
||||||
|
url = "OPC",
|
||||||
|
publicUrl = "https://mangamoins.com/",
|
||||||
|
rating = RATING_UNKNOWN,
|
||||||
|
contentRating = null,
|
||||||
|
coverUrl = "https://mangamoins.com/files/scans/OPC1160/thumbnail.png",
|
||||||
|
tags = emptySet(),
|
||||||
|
state = MangaState.ONGOING,
|
||||||
|
authors = setOf("Eiichiro Oda"),
|
||||||
|
source = source,
|
||||||
|
),
|
||||||
|
Manga(
|
||||||
|
id = generateUid("LDS"),
|
||||||
|
title = "L'Atelier des Sorciers",
|
||||||
|
altTitles = emptySet(),
|
||||||
|
url = "LDS",
|
||||||
|
publicUrl = "https://mangamoins.com/",
|
||||||
|
rating = RATING_UNKNOWN,
|
||||||
|
contentRating = null,
|
||||||
|
coverUrl = "https://sceneario.com/wp-content/uploads/2023/05/9782811641344-1.jpg",
|
||||||
|
tags = emptySet(),
|
||||||
|
state = MangaState.ONGOING,
|
||||||
|
authors = setOf("Kamome Shirahama"),
|
||||||
|
source = source,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getDetails(manga: Manga): Manga {
|
||||||
|
val doc = webClient.httpGet("https://$domain").parseHtml()
|
||||||
|
val prefix = manga.url
|
||||||
|
val latestSiteChapterNumber = doc.select("div.sortie a").mapNotNull { a ->
|
||||||
|
val href = a.attr("href")
|
||||||
|
if (href.startsWith("?scan=$prefix")) {
|
||||||
|
href.substringAfter(prefix).toFloatOrNull()
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}.maxOrNull()
|
||||||
|
|
||||||
|
if (latestSiteChapterNumber == null) return manga
|
||||||
|
|
||||||
|
val cachedChapters = manga.chapters
|
||||||
|
val lastKnownChapterNumber = cachedChapters?.maxByOrNull { it.number }?.number
|
||||||
|
|
||||||
|
if (lastKnownChapterNumber != null && cachedChapters.isNotEmpty()) {
|
||||||
|
// INCREMENTAL SCAN (CACHE EXISTS)
|
||||||
|
if (latestSiteChapterNumber <= lastKnownChapterNumber) {
|
||||||
|
return manga.copy(chapters = cachedChapters.sortedBy { it.number })
|
||||||
|
}
|
||||||
|
|
||||||
|
val newChapters = mutableListOf<MangaChapter>()
|
||||||
|
var currentChapterInt = latestSiteChapterNumber.toInt()
|
||||||
|
|
||||||
|
while (currentChapterInt > lastKnownChapterNumber.toInt()) {
|
||||||
|
val chaptersForGroup = doChecks(prefix, currentChapterInt)
|
||||||
|
newChapters.addAll(chaptersForGroup)
|
||||||
|
currentChapterInt--
|
||||||
|
}
|
||||||
|
|
||||||
|
val combinedChapters = (cachedChapters + newChapters).distinctBy { it.number }
|
||||||
|
return manga.copy(chapters = combinedChapters.sortedBy { it.number })
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// FULL SCAN (NO CACHE)
|
||||||
|
val allChapters = mutableListOf<MangaChapter>()
|
||||||
|
var currentChapterInt = latestSiteChapterNumber.toInt()
|
||||||
|
var misses = 0
|
||||||
|
|
||||||
|
while (currentChapterInt >= 1 && misses < 4) {
|
||||||
|
val chaptersForGroup = doChecks(prefix, currentChapterInt)
|
||||||
|
if (chaptersForGroup.isNotEmpty()) {
|
||||||
|
misses = 0
|
||||||
|
allChapters.addAll(chaptersForGroup)
|
||||||
|
} else {
|
||||||
|
misses++
|
||||||
|
}
|
||||||
|
currentChapterInt--
|
||||||
|
}
|
||||||
|
return manga.copy(chapters = allChapters.sortedBy { it.number })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun doChecks(prefix: String, chapterInt: Int): List<MangaChapter> {
|
||||||
|
val foundChapters = mutableListOf<MangaChapter>()
|
||||||
|
|
||||||
|
// Check for integer chapter
|
||||||
|
val integerChapter = checkChapter(prefix, chapterInt.toString(), chapterInt.toFloat())
|
||||||
|
if (integerChapter != null) {
|
||||||
|
foundChapters.add(integerChapter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Conditionally and sequentially check for decimal chapters
|
||||||
|
if (prefix == "LCDL") {
|
||||||
|
// Start from 2 because .1 never exists
|
||||||
|
for (i in 2..9) {
|
||||||
|
val decimalNum = chapterInt + (i / 10.0f)
|
||||||
|
val decimalNumStr = String.format(Locale.US, "%.1f", decimalNum)
|
||||||
|
val decimalChapter = checkChapter(prefix, decimalNumStr, decimalNum)
|
||||||
|
if (decimalChapter != null) {
|
||||||
|
foundChapters.add(decimalChapter)
|
||||||
|
} else {
|
||||||
|
break // Stop if there's a gap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return foundChapters
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun checkChapter(prefix: String, chapterNumStr: String, chapterNumFloat: Float): MangaChapter? {
|
||||||
|
val thumbUrl = "https://mangamoins.com/files/scans/$prefix$chapterNumStr/thumbnail.png"
|
||||||
|
return try {
|
||||||
|
val response = webClient.httpHead(thumbUrl)
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
response.close()
|
||||||
|
val chapterUrl = "https://mangamoins.com/?scan=$prefix$chapterNumStr"
|
||||||
|
MangaChapter(
|
||||||
|
id = generateUid(chapterUrl),
|
||||||
|
title = null,
|
||||||
|
number = chapterNumFloat,
|
||||||
|
volume = 0,
|
||||||
|
url = chapterUrl,
|
||||||
|
scanlator = null,
|
||||||
|
uploadDate = 0,
|
||||||
|
branch = null,
|
||||||
|
source = source
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
response.close()
|
||||||
|
null
|
||||||
|
}
|
||||||
|
} catch (_: Exception) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
|
||||||
|
val doc = webClient.httpGet(chapter.url).parseHtml()
|
||||||
|
return doc.select("link[rel=preload][as=image]").map { element ->
|
||||||
|
val url = element.attr("href").toAbsoluteUrl(domain)
|
||||||
|
MangaPage(
|
||||||
|
id = generateUid(url),
|
||||||
|
url = url,
|
||||||
|
preview = null,
|
||||||
|
source = source
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue