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
epikaigle444 7 months ago committed by GitHub
parent 58357a3745
commit a084909507
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -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…
Cancel
Save