Reformat code

Koitharu 3 years ago
parent 16698201f4
commit 553a6c709a
No known key found for this signature in database
GPG Key ID: 8E861F8CE6E7CE27

@ -11,4 +11,4 @@ package org.koitharu.kotatsu.parsers
@SinceKotlin("1.3") @SinceKotlin("1.3")
@RequiresOptIn @RequiresOptIn
@MustBeDocumented @MustBeDocumented
annotation class InternalParsersApi() annotation class InternalParsersApi

@ -12,7 +12,7 @@ abstract class MangaLoaderContext {
abstract val cookieJar: CookieJar abstract val cookieJar: CookieJar
fun newParserInstance(source: MangaSource): MangaParser = source.newParser(this) fun newParserInstance(source: MangaSource): MangaParser = this.newParserInstance(source)
open fun encodeBase64(data: ByteArray): String = Base64.getEncoder().encodeToString(data) open fun encodeBase64(data: ByteArray): String = Base64.getEncoder().encodeToString(data)

@ -3,8 +3,14 @@ package org.koitharu.kotatsu.parsers.site.madara
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.MangaState
import org.koitharu.kotatsu.parsers.util.assertNotNull
import org.koitharu.kotatsu.parsers.util.attrAsAbsoluteUrlOrNull
import org.koitharu.kotatsu.parsers.util.mapToSet
import org.koitharu.kotatsu.parsers.util.selectFirstOrThrow
@MangaSourceParser("ASTRALMANGA", "AstralManga", "fr") @MangaSourceParser("ASTRALMANGA", "AstralManga", "fr")
internal class AstralManga(context: MangaLoaderContext) : internal class AstralManga(context: MangaLoaderContext) :

@ -3,8 +3,14 @@ package org.koitharu.kotatsu.parsers.site.madara
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.MangaState
import org.koitharu.kotatsu.parsers.util.assertNotNull
import org.koitharu.kotatsu.parsers.util.attrAsAbsoluteUrlOrNull
import org.koitharu.kotatsu.parsers.util.mapToSet
import org.koitharu.kotatsu.parsers.util.selectFirstOrThrow
@MangaSourceParser("ATLANTISSCAN", "Atlantisscan", "pt") @MangaSourceParser("ATLANTISSCAN", "Atlantisscan", "pt")

@ -3,8 +3,14 @@ package org.koitharu.kotatsu.parsers.site.madara
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.MangaState
import org.koitharu.kotatsu.parsers.util.assertNotNull
import org.koitharu.kotatsu.parsers.util.attrAsAbsoluteUrlOrNull
import org.koitharu.kotatsu.parsers.util.mapToSet
import org.koitharu.kotatsu.parsers.util.selectFirstOrThrow
import java.util.* import java.util.*
@MangaSourceParser("FRSCAN", "FrScan", "fr") @MangaSourceParser("FRSCAN", "FrScan", "fr")

@ -3,8 +3,14 @@ package org.koitharu.kotatsu.parsers.site.madara
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.MangaState
import org.koitharu.kotatsu.parsers.util.assertNotNull
import org.koitharu.kotatsu.parsers.util.attrAsAbsoluteUrlOrNull
import org.koitharu.kotatsu.parsers.util.mapToSet
import org.koitharu.kotatsu.parsers.util.selectFirstOrThrow
@MangaSourceParser("HENTAITECA", "Hentaiteca", "pt") @MangaSourceParser("HENTAITECA", "Hentaiteca", "pt")
internal class Hentaiteca(context: MangaLoaderContext) : internal class Hentaiteca(context: MangaLoaderContext) :

@ -4,11 +4,13 @@ import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.MangaState
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
import java.time.LocalDate;
@MangaSourceParser("HENTAIZONE", "Hentaizone", "fr") @MangaSourceParser("HENTAIZONE", "Hentaizone", "fr")

@ -3,8 +3,14 @@ package org.koitharu.kotatsu.parsers.site.madara
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.MangaState
import org.koitharu.kotatsu.parsers.util.domain
import org.koitharu.kotatsu.parsers.util.insertCookies
import org.koitharu.kotatsu.parsers.util.mapToSet
import org.koitharu.kotatsu.parsers.util.selectFirstOrThrow
import java.util.* import java.util.*

@ -3,8 +3,13 @@ package org.koitharu.kotatsu.parsers.site.madara
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.MangaState
import org.koitharu.kotatsu.parsers.util.attrAsAbsoluteUrlOrNull
import org.koitharu.kotatsu.parsers.util.mapToSet
import org.koitharu.kotatsu.parsers.util.selectFirstOrThrow
import java.util.* import java.util.*

@ -193,7 +193,7 @@ internal abstract class MadaraParser(
parseRelativeDate(date) parseRelativeDate(date)
} }
// Handle translated 'ago' in french. // Handle translated 'ago' in french.
date.startsWith("il y a", ignoreCase = true)-> { date.startsWith("il y a", ignoreCase = true) -> {
parseRelativeDate(date) parseRelativeDate(date)
} }
// Handle 'yesterday' and 'today', using midnight // Handle 'yesterday' and 'today', using midnight

@ -3,8 +3,14 @@ package org.koitharu.kotatsu.parsers.site.madara
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.MangaState
import org.koitharu.kotatsu.parsers.util.assertNotNull
import org.koitharu.kotatsu.parsers.util.attrAsAbsoluteUrlOrNull
import org.koitharu.kotatsu.parsers.util.mapToSet
import org.koitharu.kotatsu.parsers.util.selectFirstOrThrow
import java.util.* import java.util.*
@MangaSourceParser("MANGA_SCANTRAD", "Manga Scantrad", "fr") @MangaSourceParser("MANGA_SCANTRAD", "Manga Scantrad", "fr")

@ -12,35 +12,35 @@ import org.koitharu.kotatsu.parsers.util.*
@MangaSourceParser("MANGALINK_AR", "Mangalink", "ar") @MangaSourceParser("MANGALINK_AR", "Mangalink", "ar")
internal class MangalinkParser(context: MangaLoaderContext) : internal class MangalinkParser(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.MANGALINK_AR, "mangalink.online") { MadaraParser(context, MangaSource.MANGALINK_AR, "mangalink.online") {
override suspend fun getDetails(manga: Manga): Manga = coroutineScope { override suspend fun getDetails(manga: Manga): Manga = coroutineScope {
val fullUrl = manga.url.toAbsoluteUrl(domain) val fullUrl = manga.url.toAbsoluteUrl(domain)
val doc = webClient.httpGet(fullUrl).parseHtml() val doc = webClient.httpGet(fullUrl).parseHtml()
val chaptersDeferred = async { getChapters(manga, doc) } val chaptersDeferred = async { getChapters(manga, doc) }
val root = doc.body().selectFirst("div.profile-manga") val root = doc.body().selectFirst("div.profile-manga")
?.selectFirst("div.summary_content") ?.selectFirst("div.summary_content")
?.selectFirst("div.post-content") ?.selectFirst("div.post-content")
?: throw ParseException("Root not found", fullUrl) ?: throw ParseException("Root not found", fullUrl)
val root2 = doc.body().selectFirst("div.content-area") val root2 = doc.body().selectFirst("div.content-area")
?.selectFirst("div.c-page") ?.selectFirst("div.c-page")
?: throw ParseException("Root2 not found", fullUrl) ?: throw ParseException("Root2 not found", fullUrl)
manga.copy( manga.copy(
tags = root.selectFirst("div.genres-content")?.select("a") tags = root.selectFirst("div.genres-content")?.select("a")
?.mapNotNullToSet { a -> ?.mapNotNullToSet { a ->
MangaTag( MangaTag(
key = a.attr("href").removeSuffix("/").substringAfterLast('/'), key = a.attr("href").removeSuffix("/").substringAfterLast('/'),
title = a.text().toTitleCase(), title = a.text().toTitleCase(),
source = source, source = source,
) )
} ?: manga.tags, } ?: manga.tags,
description = root2.selectFirst("div.description-summary") description = root2.selectFirst("div.description-summary")
?.selectFirst("div.summary__content") ?.selectFirst("div.summary__content")
?.select("p") ?.select("p")
?.filterNot { it.ownText().startsWith("A brief description") } ?.filterNot { it.ownText().startsWith("A brief description") }
?.joinToString { it.html() }, ?.joinToString { it.html() },
chapters = chaptersDeferred.await(), chapters = chaptersDeferred.await(),
) )
} }
} }

@ -4,8 +4,9 @@ import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.exception.ParseException import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
@MangaSourceParser("REAPERSCANS_FR", "ReaperScansFr", "fr") @MangaSourceParser("REAPERSCANS_FR", "ReaperScansFr", "fr")

@ -9,78 +9,78 @@ import java.util.*
@MangaSourceParser("REAPER_SCANS_ID", "ReaperScansID", "in") @MangaSourceParser("REAPER_SCANS_ID", "ReaperScansID", "in")
internal class ReaperScansParser(context: MangaLoaderContext) : internal class ReaperScansParser(context: MangaLoaderContext) :
Madara6Parser(context, MangaSource.REAPER_SCANS_ID, "reaperscans.id") { Madara6Parser(context, MangaSource.REAPER_SCANS_ID, "reaperscans.id") {
override val datePattern = "MMMM dd, yyyy" override val datePattern = "MMMM dd, yyyy"
override val tagPrefix = "genre/" override val tagPrefix = "genre/"
override val sourceLocale: Locale = Locale.ENGLISH override val sourceLocale: Locale = Locale.ENGLISH
override fun String.asMangaState(): MangaState? = when (this) { override fun String.asMangaState(): MangaState? = when (this) {
"OnGoing", "OnGoing",
"Upcoming", "Upcoming",
-> MangaState.ONGOING -> MangaState.ONGOING
"Completed", "Completed",
"Dropped", "Dropped",
-> MangaState.FINISHED -> MangaState.FINISHED
else -> null else -> null
} }
override fun parseDetails(manga: Manga, body: Element, chapters: List<MangaChapter>): Manga { override fun parseDetails(manga: Manga, body: Element, chapters: List<MangaChapter>): Manga {
val root = body.selectFirstOrThrow(".site-content") val root = body.selectFirstOrThrow(".site-content")
val postContent = root.requireElementById("nav-info") val postContent = root.requireElementById("nav-info")
val tags = postContent.getElementsContainingOwnText("Gênero") val tags = postContent.getElementsContainingOwnText("Gênero")
.firstOrNull()?.tableValue() .firstOrNull()?.tableValue()
?.getElementsByAttributeValueContaining("href", tagPrefix) ?.getElementsByAttributeValueContaining("href", tagPrefix)
?.mapToSet { a -> a.asMangaTag() } ?: manga.tags ?.mapToSet { a -> a.asMangaTag() } ?: manga.tags
return manga.copy( return manga.copy(
rating = postContent.selectFirstOrThrow(".post-rating") rating = postContent.selectFirstOrThrow(".post-rating")
.selectFirstOrThrow(".total_votes").text().toFloat() / 5f, .selectFirstOrThrow(".total_votes").text().toFloat() / 5f,
largeCoverUrl = root.selectFirst(".summary_image") largeCoverUrl = root.selectFirst(".summary_image")
?.selectFirst("img[data-src]") ?.selectFirst("img[data-src]")
?.attrAsAbsoluteUrlOrNull("data-src") ?.attrAsAbsoluteUrlOrNull("data-src")
.assertNotNull("largeCoverUrl"), .assertNotNull("largeCoverUrl"),
description = root.requireElementById("nav-profile") description = root.requireElementById("nav-profile")
.selectFirstOrThrow(".description-summary") .selectFirstOrThrow(".description-summary")
.firstElementChild()?.html(), .firstElementChild()?.html(),
author = postContent.getElementsContainingOwnText("Author(s)") author = postContent.getElementsContainingOwnText("Author(s)")
.firstOrNull()?.tableValue()?.text()?.trim(), .firstOrNull()?.tableValue()?.text()?.trim(),
altTitle = postContent.getElementsContainingOwnText("Alternative") altTitle = postContent.getElementsContainingOwnText("Alternative")
.firstOrNull()?.tableValue()?.text()?.trim(), .firstOrNull()?.tableValue()?.text()?.trim(),
state = postContent.getElementsContainingOwnText("Status") state = postContent.getElementsContainingOwnText("Status")
.firstOrNull()?.tableValue()?.text()?.asMangaState(), .firstOrNull()?.tableValue()?.text()?.asMangaState(),
tags = tags, tags = tags,
isNsfw = body.hasClass("adult-content"), isNsfw = body.hasClass("adult-content"),
chapters = chapters, chapters = chapters,
) )
} }
override suspend fun getTags(): Set<MangaTag> { override suspend fun getTags(): Set<MangaTag> {
val doc = webClient.httpGet("https://${domain}/semua-komik/").parseHtml() val doc = webClient.httpGet("https://${domain}/semua-komik/").parseHtml()
val body = doc.body() val body = doc.body()
val root1 = body.selectFirst("header")?.selectFirst("ul.second-menu") val root1 = body.selectFirst("header")?.selectFirst("ul.second-menu")
val root2 = body.selectFirst("div.genres_wrap")?.selectFirst("ul.list-unstyled") val root2 = body.selectFirst("div.genres_wrap")?.selectFirst("ul.list-unstyled")
if (root1 == null && root2 == null) { if (root1 == null && root2 == null) {
doc.parseFailed("Root not found") doc.parseFailed("Root not found")
} }
val list = root1?.select("li").orEmpty() + root2?.select("li").orEmpty() val list = root1?.select("li").orEmpty() + root2?.select("li").orEmpty()
val keySet = HashSet<String>(list.size) val keySet = HashSet<String>(list.size)
return list.mapNotNullToSet { li -> return list.mapNotNullToSet { li ->
val a = li.selectFirst("a") ?: return@mapNotNullToSet null val a = li.selectFirst("a") ?: return@mapNotNullToSet null
val href = a.attr("href").removeSuffix("/") val href = a.attr("href").removeSuffix("/")
.substringAfterLast(tagPrefix, "") .substringAfterLast(tagPrefix, "")
if (href.isEmpty() || !keySet.add(href)) { if (href.isEmpty() || !keySet.add(href)) {
return@mapNotNullToSet null return@mapNotNullToSet null
} }
MangaTag( MangaTag(
key = href, key = href,
title = a.ownText().trim().ifEmpty { title = a.ownText().trim().ifEmpty {
a.selectFirst(".menu-image-title")?.text()?.trim() ?: return@mapNotNullToSet null a.selectFirst(".menu-image-title")?.text()?.trim() ?: return@mapNotNullToSet null
}.toTitleCase(), }.toTitleCase(),
source = source, source = source,
) )
} }
} }
} }

@ -5,7 +5,6 @@ import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
import java.util.*
@MangaSourceParser("SCANTRADVF", "ScantradVf", "fr") @MangaSourceParser("SCANTRADVF", "ScantradVf", "fr")
internal class ScantradVf(context: MangaLoaderContext) : internal class ScantradVf(context: MangaLoaderContext) :

@ -535,7 +535,7 @@ internal abstract class MangaReaderParser(
} }
@MangaSourceParser("PHENIXSCANS", "Phenixscans", "fr") @MangaSourceParser("PHENIXSCANS", "Phenixscans", "fr")
class PhenixscansParser(context: MangaLoaderContext) : class PhenixscansParser(context: MangaLoaderContext) :
MangaReaderParser(context, MangaSource.PHENIXSCANS, pageSize = 20, searchPageSize = 10) { MangaReaderParser(context, MangaSource.PHENIXSCANS, pageSize = 20, searchPageSize = 10) {
override val configKeyDomain: ConfigKey.Domain override val configKeyDomain: ConfigKey.Domain
get() = ConfigKey.Domain("phenixscans.fr") get() = ConfigKey.Domain("phenixscans.fr")
@ -561,7 +561,7 @@ internal abstract class MangaReaderParser(
} }
@MangaSourceParser("EPSILONSCAN", "Epsilonscan", "fr") @MangaSourceParser("EPSILONSCAN", "Epsilonscan", "fr")
class EpsilonscanParser(context: MangaLoaderContext) : class EpsilonscanParser(context: MangaLoaderContext) :
MangaReaderParser(context, MangaSource.EPSILONSCAN, pageSize = 20, searchPageSize = 10) { MangaReaderParser(context, MangaSource.EPSILONSCAN, pageSize = 20, searchPageSize = 10) {
override val configKeyDomain: ConfigKey.Domain override val configKeyDomain: ConfigKey.Domain
get() = ConfigKey.Domain("epsilonscan.fr") get() = ConfigKey.Domain("epsilonscan.fr")
@ -570,7 +570,7 @@ internal abstract class MangaReaderParser(
get() = "/manga" get() = "/manga"
override val tableMode: Boolean override val tableMode: Boolean
get() = false get() = false
override val isNsfwSource: Boolean = true override val isNsfwSource: Boolean = true
override val chapterDateFormat: SimpleDateFormat = SimpleDateFormat("MMMM d, yyyy", Locale.FRENCH) override val chapterDateFormat: SimpleDateFormat = SimpleDateFormat("MMMM d, yyyy", Locale.FRENCH)
@ -588,7 +588,7 @@ internal abstract class MangaReaderParser(
} }
@MangaSourceParser("LEGACY_SCANS", "Legacy Scans", "fr") @MangaSourceParser("LEGACY_SCANS", "Legacy Scans", "fr")
class LegacyScansParser(context: MangaLoaderContext) : class LegacyScansParser(context: MangaLoaderContext) :
MangaReaderParser(context, MangaSource.LEGACY_SCANS, pageSize = 20, searchPageSize = 10) { MangaReaderParser(context, MangaSource.LEGACY_SCANS, pageSize = 20, searchPageSize = 10) {
override val configKeyDomain: ConfigKey.Domain override val configKeyDomain: ConfigKey.Domain
get() = ConfigKey.Domain("legacy-scans.com") get() = ConfigKey.Domain("legacy-scans.com")

@ -7,86 +7,86 @@ import org.koitharu.kotatsu.parsers.network.WebClient
import org.koitharu.kotatsu.parsers.util.json.mapJSON import org.koitharu.kotatsu.parsers.util.json.mapJSON
class FaviconParser( class FaviconParser(
private val webClient: WebClient, private val webClient: WebClient,
private val domain: String, private val domain: String,
) { ) {
suspend fun parseFavicons(): Favicons { suspend fun parseFavicons(): Favicons {
val url = "https://$domain" val url = "https://$domain"
val doc = webClient.httpGet(url).parseHtml() val doc = webClient.httpGet(url).parseHtml()
val result = HashSet<Favicon>() val result = HashSet<Favicon>()
val manifestLink = doc.getElementsByAttributeValue("rel", "manifest").firstOrNull() val manifestLink = doc.getElementsByAttributeValue("rel", "manifest").firstOrNull()
?.attrAsAbsoluteUrlOrNull("href") ?.attrAsAbsoluteUrlOrNull("href")
if (manifestLink != null) { if (manifestLink != null) {
result += parseManifest(manifestLink) result += parseManifest(manifestLink)
} }
val links = doc.getElementsByAttributeValueContaining("rel", "icon") val links = doc.getElementsByAttributeValueContaining("rel", "icon")
links.mapNotNullTo(result) { link -> links.mapNotNullTo(result) { link ->
parseLink(link) parseLink(link)
} }
if (result.isEmpty()) { if (result.isEmpty()) {
result.add(createFallback()) result.add(createFallback())
} }
return Favicons(result, url) return Favicons(result, url)
} }
private fun parseLink(link: Element): Favicon? { private fun parseLink(link: Element): Favicon? {
val href = link.attrAsAbsoluteUrlOrNull("href") val href = link.attrAsAbsoluteUrlOrNull("href")
if (href == null || href.endsWith('/')) { if (href == null || href.endsWith('/')) {
return null return null
} }
val sizes = link.attr("sizes") val sizes = link.attr("sizes")
return Favicon( return Favicon(
url = href, url = href,
size = parseSize(sizes), size = parseSize(sizes),
rel = link.attrOrNull("rel"), rel = link.attrOrNull("rel"),
) )
} }
private fun parseSize(sizes: String): Int { private fun parseSize(sizes: String): Int {
if (sizes.isEmpty() || sizes == "any") { if (sizes.isEmpty() || sizes == "any") {
return 0 return 0
} }
return sizes.substringBefore(' ') return sizes.substringBefore(' ')
.split('x', 'X', '*') .split('x', 'X', '*')
.firstNotNullOfOrNull { it.toIntOrNull() } .firstNotNullOfOrNull { it.toIntOrNull() }
?: 0 ?: 0
} }
private suspend fun parseManifest(url: String): List<Favicon> { private suspend fun parseManifest(url: String): List<Favicon> {
val json = webClient.httpGet(url).parseJson() val json = webClient.httpGet(url).parseJson()
val icons = json.getJSONArray("icons") val icons = json.getJSONArray("icons")
return icons.mapJSON { jo -> return icons.mapJSON { jo ->
Favicon( Favicon(
url = jo.getString("src").resolveLink(), url = jo.getString("src").resolveLink(),
size = parseSize(jo.getString("sizes")), size = parseSize(jo.getString("sizes")),
rel = null, rel = null,
) )
} }
} }
private fun createFallback(): Favicon { private fun createFallback(): Favicon {
val href = "https://$domain/favicon.ico" val href = "https://$domain/favicon.ico"
return Favicon( return Favicon(
url = href, url = href,
size = 0, size = 0,
rel = null, rel = null,
) )
} }
private fun String.resolveLink(): String { private fun String.resolveLink(): String {
return when { return when {
startsWith("http:") || startsWith("https:") -> { startsWith("http:") || startsWith("https:") -> {
this this
} }
startsWith('/') -> { startsWith('/') -> {
"https://$domain$this" "https://$domain$this"
} }
else -> { else -> {
"https://$domain/$this" "https://$domain/$this"
} }
} }
} }
} }

@ -20,14 +20,14 @@ import org.koitharu.kotatsu.parsers.model.MangaTag
*/ */
@InternalParsersApi @InternalParsersApi
fun MangaParser.generateUid(url: String): Long { fun MangaParser.generateUid(url: String): Long {
var h = 1125899906842597L var h = 1125899906842597L
source.name.forEach { c -> source.name.forEach { c ->
h = 31 * h + c.code h = 31 * h + c.code
} }
url.forEach { c -> url.forEach { c ->
h = 31 * h + c.code h = 31 * h + c.code
} }
return h return h
} }
/** /**
@ -39,40 +39,40 @@ fun MangaParser.generateUid(url: String): Long {
*/ */
@InternalParsersApi @InternalParsersApi
fun MangaParser.generateUid(id: Long): Long { fun MangaParser.generateUid(id: Long): Long {
var h = 1125899906842597L var h = 1125899906842597L
source.name.forEach { c -> source.name.forEach { c ->
h = 31 * h + c.code h = 31 * h + c.code
} }
h = 31 * h + id h = 31 * h + id
return h return h
} }
@InternalParsersApi @InternalParsersApi
fun Element.parseFailed(message: String? = null): Nothing { fun Element.parseFailed(message: String? = null): Nothing {
throw ParseException(message, ownerDocument()?.location() ?: baseUri(), null) throw ParseException(message, ownerDocument()?.location() ?: baseUri(), null)
} }
@InternalParsersApi @InternalParsersApi
fun Set<MangaTag>?.oneOrThrowIfMany(): MangaTag? { fun Set<MangaTag>?.oneOrThrowIfMany(): MangaTag? {
return when { return when {
isNullOrEmpty() -> null isNullOrEmpty() -> null
size == 1 -> first() size == 1 -> first()
else -> throw IllegalArgumentException("Multiple genres are not supported by this source") else -> throw IllegalArgumentException("Multiple genres are not supported by this source")
} }
} }
val MangaParser.domain: String val MangaParser.domain: String
get() { get() {
return config[configKeyDomain] return config[configKeyDomain]
} }
fun MangaParser.getDomain(subdomain: String): String { fun MangaParser.getDomain(subdomain: String): String {
val domain = domain val domain = domain
return subdomain + "." + domain.removePrefix("www.") return subdomain + "." + domain.removePrefix("www.")
} }
fun MangaParser.urlBuilder(): HttpUrl.Builder { fun MangaParser.urlBuilder(): HttpUrl.Builder {
return HttpUrl.Builder() return HttpUrl.Builder()
.scheme("https") .scheme("https")
.host(domain) .host(domain)
} }

@ -23,6 +23,7 @@ fun Number.format(decimals: Int = 0, decPoint: Char = '.', thousandsSep: Char? =
is Float, is Float,
is Double, is Double,
-> formatter.format(this.toDouble()) -> formatter.format(this.toDouble())
else -> formatter.format(this.toLong()) else -> formatter.format(this.toLong())
} }
} }

@ -8,22 +8,22 @@ import okhttp3.Headers
import okhttp3.Response import okhttp3.Response
suspend fun Call.await(): Response = suspendCancellableCoroutine { continuation -> suspend fun Call.await(): Response = suspendCancellableCoroutine { continuation ->
val callback = ContinuationCallCallback(this, continuation) val callback = ContinuationCallCallback(this, continuation)
enqueue(callback) enqueue(callback)
continuation.invokeOnCancellation(callback) continuation.invokeOnCancellation(callback)
} }
val Response.mimeType: String? val Response.mimeType: String?
get() = body?.contentType()?.run { "$type/$subtype" } get() = body?.contentType()?.run { "$type/$subtype" }
val Response.contentDisposition: String? val Response.contentDisposition: String?
get() = header("Content-Disposition") get() = header("Content-Disposition")
fun Headers.Builder.mergeWith(other: Headers, replaceExisting: Boolean): Headers.Builder { fun Headers.Builder.mergeWith(other: Headers, replaceExisting: Boolean): Headers.Builder {
for ((name, value) in other) { for ((name, value) in other) {
if (replaceExisting || this[name] == null) { if (replaceExisting || this[name] == null) {
this[name] = value this[name] = value
} }
} }
return this return this
} }

@ -3,7 +3,7 @@ package org.koitharu.kotatsu.parsers.util
import androidx.collection.SparseArrayCompat import androidx.collection.SparseArrayCompat
import androidx.collection.set import androidx.collection.set
class Paginator constructor(private val initialPageSize: Int) { class Paginator(private val initialPageSize: Int) {
var firstPage = 1 var firstPage = 1
private var pages = SparseArrayCompat<Int>() private var pages = SparseArrayCompat<Int>()

Loading…
Cancel
Save