add source request
parent
7c4bffede2
commit
e67beae235
@ -0,0 +1,13 @@
|
|||||||
|
package org.koitharu.kotatsu.parsers.site.madara.pt
|
||||||
|
|
||||||
|
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||||
|
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||||
|
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||||
|
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
|
||||||
|
|
||||||
|
@MangaSourceParser("HIKARISCAN", "Hikari Scan", "pt")
|
||||||
|
internal class HikariScan(context: MangaLoaderContext) :
|
||||||
|
MadaraParser(context, MangaSource.HIKARISCAN, "hikariscan.com.br", pageSize = 10) {
|
||||||
|
|
||||||
|
override val datePattern: String = "dd 'de' MMMMM 'de' yyyy"
|
||||||
|
}
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
package org.koitharu.kotatsu.parsers.site.mangareader.ar
|
||||||
|
|
||||||
|
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||||
|
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||||
|
import org.koitharu.kotatsu.parsers.config.ConfigKey
|
||||||
|
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||||
|
import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
@MangaSourceParser("MANGAPROTM", "MangaProtm", "ar")
|
||||||
|
internal class MangaProtm(context: MangaLoaderContext) :
|
||||||
|
MangaReaderParser(context, MangaSource.MANGAPROTM, pageSize = 20, searchPageSize = 20) {
|
||||||
|
|
||||||
|
override val configKeyDomain: ConfigKey.Domain
|
||||||
|
get() = ConfigKey.Domain("mangaprotm.com")
|
||||||
|
|
||||||
|
override val listUrl = "/series"
|
||||||
|
override val chapterDateFormat: SimpleDateFormat = SimpleDateFormat("MMMM d, yyyy", Locale("ar", "AR"))
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
package org.koitharu.kotatsu.parsers.site.mangareader.en
|
||||||
|
|
||||||
|
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||||
|
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||||
|
import org.koitharu.kotatsu.parsers.config.ConfigKey
|
||||||
|
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||||
|
import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
|
||||||
|
@MangaSourceParser("REALMSCANS", "RealmScans", "en")
|
||||||
|
internal class RealmScans(context: MangaLoaderContext) :
|
||||||
|
MangaReaderParser(context, MangaSource.REALMSCANS, pageSize = 30, searchPageSize = 50) {
|
||||||
|
override val configKeyDomain: ConfigKey.Domain
|
||||||
|
get() = ConfigKey.Domain("realmscans.xyz")
|
||||||
|
|
||||||
|
override val listUrl = "/series"
|
||||||
|
override val chapterDateFormat: SimpleDateFormat = SimpleDateFormat("dd MMM yyyy", Locale.ENGLISH)
|
||||||
|
}
|
||||||
@ -0,0 +1,216 @@
|
|||||||
|
package org.koitharu.kotatsu.parsers.site.mangareader.id
|
||||||
|
|
||||||
|
import org.json.JSONObject
|
||||||
|
import org.jsoup.nodes.Document
|
||||||
|
import org.jsoup.nodes.Element
|
||||||
|
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||||
|
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||||
|
import org.koitharu.kotatsu.parsers.config.ConfigKey
|
||||||
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
|
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
||||||
|
import org.koitharu.kotatsu.parsers.model.MangaPage
|
||||||
|
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||||
|
import org.koitharu.kotatsu.parsers.model.MangaState
|
||||||
|
import org.koitharu.kotatsu.parsers.model.RATING_UNKNOWN
|
||||||
|
import org.koitharu.kotatsu.parsers.model.WordSet
|
||||||
|
import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser
|
||||||
|
import org.koitharu.kotatsu.parsers.util.attrAsAbsoluteUrl
|
||||||
|
import org.koitharu.kotatsu.parsers.util.attrAsAbsoluteUrlOrNull
|
||||||
|
import org.koitharu.kotatsu.parsers.util.attrAsRelativeUrl
|
||||||
|
import org.koitharu.kotatsu.parsers.util.domain
|
||||||
|
import org.koitharu.kotatsu.parsers.util.generateUid
|
||||||
|
import org.koitharu.kotatsu.parsers.util.mapChapters
|
||||||
|
import org.koitharu.kotatsu.parsers.util.mapNotNullToSet
|
||||||
|
import org.koitharu.kotatsu.parsers.util.parseHtml
|
||||||
|
import org.koitharu.kotatsu.parsers.util.selectFirstOrThrow
|
||||||
|
import org.koitharu.kotatsu.parsers.util.toAbsoluteUrl
|
||||||
|
import org.koitharu.kotatsu.parsers.util.tryParse
|
||||||
|
import java.text.DateFormat
|
||||||
|
import java.util.Calendar
|
||||||
|
|
||||||
|
@MangaSourceParser("KOMIKCAST", "Komikcast", "id")
|
||||||
|
internal class Komikcast(context: MangaLoaderContext) :
|
||||||
|
MangaReaderParser(context, MangaSource.KOMIKCAST, pageSize = 60, searchPageSize = 28) {
|
||||||
|
override val configKeyDomain: ConfigKey.Domain
|
||||||
|
get() = ConfigKey.Domain("komikcast.io")
|
||||||
|
|
||||||
|
override val listUrl = "/daftar-komik"
|
||||||
|
|
||||||
|
override suspend fun getDetails(manga: Manga): Manga {
|
||||||
|
val docs = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
|
||||||
|
val chapters = docs.select("#chapter-wrapper > li").mapChapters(reversed = true) { index, element ->
|
||||||
|
val url = element.selectFirst("a")?.attrAsRelativeUrl("href") ?: return@mapChapters null
|
||||||
|
MangaChapter(
|
||||||
|
id = generateUid(url),
|
||||||
|
name = docs.selectFirst("a.chapter-link-item")?.ownText().orEmpty(),
|
||||||
|
url = url,
|
||||||
|
number = index + 1,
|
||||||
|
scanlator = null,
|
||||||
|
uploadDate = parseChapterDate(
|
||||||
|
chapterDateFormat,
|
||||||
|
element.selectFirst("div.chapter-link-time")?.text(),
|
||||||
|
),
|
||||||
|
branch = null,
|
||||||
|
source = source,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return parseInfo(docs, manga, chapters)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun parseInfo(docs: Document, manga: Manga, chapters: List<MangaChapter>): Manga {
|
||||||
|
|
||||||
|
/// set if is table
|
||||||
|
|
||||||
|
val tagMap = getOrCreateTagMap()
|
||||||
|
|
||||||
|
val tags = docs.select(".komik_info-content-genre > a").mapNotNullToSet { tagMap[it.text()] }
|
||||||
|
|
||||||
|
val state = docs.selectFirst(".komik_info-content-meta span:contains(Status)")?.lastElementChild()
|
||||||
|
|
||||||
|
val mangaState = state?.let {
|
||||||
|
when (it.text()) {
|
||||||
|
"Ongoing" -> MangaState.ONGOING
|
||||||
|
"Completed" -> MangaState.FINISHED
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val author = docs.selectFirst(".komik_info-content-meta span:contains(Author)")?.lastElementChild()?.text()
|
||||||
|
|
||||||
|
val nsfw = docs.selectFirst(".restrictcontainer") != null
|
||||||
|
|| docs.selectFirst(".info-right .alr") != null
|
||||||
|
|| docs.selectFirst(".postbody .alr") != null
|
||||||
|
|
||||||
|
return manga.copy(
|
||||||
|
description = docs.selectFirst("div.komik_info-description-sinopsis")?.text(),
|
||||||
|
state = mangaState,
|
||||||
|
author = author,
|
||||||
|
isNsfw = manga.isNsfw || nsfw,
|
||||||
|
tags = tags,
|
||||||
|
chapters = chapters,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun parseMangaList(docs: Document): List<Manga> {
|
||||||
|
return docs.select("div.list-update_item").mapNotNull {
|
||||||
|
val a = it.selectFirst("a") ?: return@mapNotNull null
|
||||||
|
val relativeUrl = a.attrAsRelativeUrl("href")
|
||||||
|
val rating = it.selectFirst(".numscore")?.text()
|
||||||
|
?.toFloatOrNull()?.div(10) ?: RATING_UNKNOWN
|
||||||
|
|
||||||
|
val name = it.selectFirst("h3.title")?.text().orEmpty()
|
||||||
|
Manga(
|
||||||
|
id = generateUid(relativeUrl),
|
||||||
|
url = relativeUrl,
|
||||||
|
title = name,
|
||||||
|
altTitle = null,
|
||||||
|
publicUrl = a.attrAsAbsoluteUrl("href"),
|
||||||
|
rating = rating,
|
||||||
|
isNsfw = isNsfwSource,
|
||||||
|
coverUrl = it.selectFirst("img.ts-post-image")?.imageUrl().orEmpty(),
|
||||||
|
tags = emptySet(),
|
||||||
|
state = null,
|
||||||
|
author = null,
|
||||||
|
source = source,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Element.imageUrl(): String {
|
||||||
|
return attrAsAbsoluteUrlOrNull("src")
|
||||||
|
?: attrAsAbsoluteUrlOrNull("data-src")
|
||||||
|
?: attrAsAbsoluteUrlOrNull("data-cfsrc")
|
||||||
|
?: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
|
||||||
|
val chapterUrl = chapter.url.toAbsoluteUrl(domain)
|
||||||
|
val docs = webClient.httpGet(chapterUrl).parseHtml()
|
||||||
|
|
||||||
|
val test = docs.select("script:containsData(ts_reader)")
|
||||||
|
if (test.isNullOrEmpty()) {
|
||||||
|
return docs.select("div#chapter_body img").map { img ->
|
||||||
|
val url = img.imageUrl()
|
||||||
|
MangaPage(
|
||||||
|
id = generateUid(url),
|
||||||
|
url = url,
|
||||||
|
preview = null,
|
||||||
|
source = source,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val script = docs.selectFirstOrThrow("script:containsData(ts_reader)")
|
||||||
|
val images = JSONObject(script.data().substringAfter('(').substringBeforeLast(')'))
|
||||||
|
.getJSONArray("sources")
|
||||||
|
.getJSONObject(0)
|
||||||
|
.getJSONArray("images")
|
||||||
|
val pages = ArrayList<MangaPage>(images.length())
|
||||||
|
for (i in 0 until images.length()) {
|
||||||
|
pages.add(
|
||||||
|
MangaPage(
|
||||||
|
id = generateUid(images.getString(i)),
|
||||||
|
url = images.getString(i),
|
||||||
|
preview = null,
|
||||||
|
source = source,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return pages
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun parseChapterDate(dateFormat: DateFormat, date: String?): Long {
|
||||||
|
date ?: return 0
|
||||||
|
return when {
|
||||||
|
date.endsWith(" ago", ignoreCase = true) -> {
|
||||||
|
parseRelativeDate(date)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> dateFormat.tryParse(date)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseRelativeDate(date: String): Long {
|
||||||
|
val number = Regex("""(\d+)""").find(date)?.value?.toIntOrNull() ?: return 0
|
||||||
|
val cal = Calendar.getInstance()
|
||||||
|
|
||||||
|
return when {
|
||||||
|
WordSet(
|
||||||
|
"day",
|
||||||
|
"days",
|
||||||
|
).anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis
|
||||||
|
|
||||||
|
WordSet("hour", "hours").anyWordIn(date) -> cal.apply {
|
||||||
|
add(
|
||||||
|
Calendar.HOUR,
|
||||||
|
-number,
|
||||||
|
)
|
||||||
|
}.timeInMillis
|
||||||
|
|
||||||
|
WordSet(
|
||||||
|
"mins",
|
||||||
|
).anyWordIn(date) -> cal.apply {
|
||||||
|
add(
|
||||||
|
Calendar.MINUTE,
|
||||||
|
-number,
|
||||||
|
)
|
||||||
|
}.timeInMillis
|
||||||
|
|
||||||
|
WordSet("second").anyWordIn(date) -> cal.apply {
|
||||||
|
add(
|
||||||
|
Calendar.SECOND,
|
||||||
|
-number,
|
||||||
|
)
|
||||||
|
}.timeInMillis
|
||||||
|
|
||||||
|
WordSet("month", "months").anyWordIn(date) -> cal.apply { add(Calendar.MONTH, -number) }.timeInMillis
|
||||||
|
WordSet("year").anyWordIn(date) -> cal.apply { add(Calendar.YEAR, -number) }.timeInMillis
|
||||||
|
else -> 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue