add weeb central (#5178)

* add weeb central

* stuff
This commit is contained in:
Secozzi 2024-09-23 15:29:06 +02:00 committed by GitHub
parent bffcba79f5
commit e6cc5e0f56
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 356 additions and 0 deletions

View File

@ -0,0 +1,8 @@
ext {
extName = 'Weeb Central'
extClass = '.WeebCentral'
extVersionCode = 1
isNsfw = true
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@ -0,0 +1,164 @@
package eu.kanade.tachiyomi.extension.en.weebcentral
import eu.kanade.tachiyomi.source.model.Filter
import okhttp3.HttpUrl
interface UriFilter {
fun addToUri(builder: HttpUrl.Builder)
}
open class UriPartFilter(
name: String,
private val param: String,
private val vals: Array<Pair<String, String>>,
private val default: String = "",
) : UriFilter, Filter.Select<String>(
name,
vals.map { it.first }.toTypedArray(),
vals.indexOfFirst { it.second == default }.takeIf { it != -1 } ?: 0,
) {
override fun addToUri(builder: HttpUrl.Builder) {
builder.addQueryParameter(param, vals[state].second)
}
}
open class UriMultiSelectOption(name: String, val value: String) : Filter.CheckBox(name)
open class UriMultiSelectFilter(
name: String,
private val param: String,
private val options: Array<Pair<String, String>>,
) : UriFilter, Filter.Group<UriMultiSelectOption>(
name,
options.map { UriMultiSelectOption(it.first, it.second) },
) {
override fun addToUri(builder: HttpUrl.Builder) {
state.filter { it.state }.forEach {
builder.addQueryParameter(param, it.value)
}
}
}
open class UriMultiTriSelectOption(name: String, val value: String) : Filter.TriState(name)
open class UriMultiTriSelectFilter(
name: String,
private val includeUrlParameter: String,
private val excludeUrlParameter: String,
private val options: Array<Pair<String, String>>,
) : UriFilter, Filter.Group<UriMultiTriSelectOption>(
name,
options.map { UriMultiTriSelectOption(it.first, it.second) },
) {
override fun addToUri(builder: HttpUrl.Builder) {
state.forEach {
if (it.isIncluded()) {
builder.addQueryParameter(includeUrlParameter, it.value)
}
if (it.isExcluded()) {
builder.addQueryParameter(excludeUrlParameter, it.value)
}
}
}
}
class SortFilter(default: String = "") : UriPartFilter(
"Sort",
"sort",
arrayOf(
Pair("Best Match", "Best Match"),
Pair("Alphabet", "Alphabet"),
Pair("Popularity", "Popularity"),
Pair("Subscribers", "Subscribers"),
Pair("Recently Added", "Recently Added"),
Pair("Latest Updates", "Latest Updates"),
),
default,
)
class SortOrderFilter : UriPartFilter(
"Sort Order",
"order",
arrayOf(
Pair("Ascending", "Ascending"),
Pair("Descending", "Descending"),
),
)
class OfficialTranslationFilter : UriPartFilter(
"Official Translation",
"official",
arrayOf(
Pair("Any", "Any"),
Pair("True", "True"),
Pair("False", "False"),
),
)
class StatusFilter : UriMultiSelectFilter(
"Series Status",
"included_status",
arrayOf(
Pair("Ongoing", "Ongoing"),
Pair("Complete", "Complete"),
Pair("Hiatus", "Hiatus"),
Pair("Canceled", "Canceled"),
),
)
class TypeFilter : UriMultiSelectFilter(
"Series Type",
"included_type",
arrayOf(
Pair("Manga", "Manga"),
Pair("Manhwa", "Manhwa"),
Pair("Manhua", "Manhua"),
Pair("OEL", "OEL"),
),
)
class TagFilter : UriMultiTriSelectFilter(
"Tags",
"included_tag",
"excluded_tag",
arrayOf(
Pair("Action", "Action"),
Pair("Adult", "Adult"),
Pair("Adventure", "Adventure"),
Pair("Comedy", "Comedy"),
Pair("Doujinshi", "Doujinshi"),
Pair("Drama", "Drama"),
Pair("Ecchi", "Ecchi"),
Pair("Fantasy", "Fantasy"),
Pair("Gender Bender", "Gender Bender"),
Pair("Harem", "Harem"),
Pair("Hentai", "Hentai"),
Pair("Historical", "Historical"),
Pair("Horror", "Horror"),
Pair("Isekai", "Isekai"),
Pair("Josei", "Josei"),
Pair("Lolicon", "Lolicon"),
Pair("Martial Arts", "Martial Arts"),
Pair("Mature", "Mature"),
Pair("Mecha", "Mecha"),
Pair("Mystery", "Mystery"),
Pair("Psychological", "Psychological"),
Pair("Romance", "Romance"),
Pair("School Life", "School Life"),
Pair("Sci-fi", "Sci-fi"),
Pair("Seinen", "Seinen"),
Pair("Shotacon", "Shotacon"),
Pair("Shoujo", "Shoujo"),
Pair("Shoujo Ai", "Shoujo Ai"),
Pair("Shounen", "Shounen"),
Pair("Shounen Ai", "Shounen Ai"),
Pair("Slice of Life", "Slice of Life"),
Pair("Smut", "Smut"),
Pair("Sports", "Sports"),
Pair("Supernatural", "Supernatural"),
Pair("Tragedy", "Tragedy"),
Pair("Yaoi", "Yaoi"),
Pair("Yuri", "Yuri"),
Pair("Other", "Other"),
),
)

View File

@ -0,0 +1,184 @@
package eu.kanade.tachiyomi.extension.en.weebcentral
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import eu.kanade.tachiyomi.source.model.FilterList
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.ParsedHttpSource
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.Locale
class WeebCentral : ParsedHttpSource() {
override val name = "Weeb Central"
override val baseUrl = "https://weebcentral.com"
override val lang = "en"
override val supportsLatest = true
override val client = network.cloudflareClient.newBuilder()
.rateLimit(2)
.build()
override fun headersBuilder() = super.headersBuilder()
.add("Referer", "$baseUrl/")
private val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.ENGLISH)
// ============================== Popular ===============================
override fun popularMangaRequest(page: Int): Request = searchMangaRequest(
page,
"",
defaultFilterList(SortFilter("Popularity")),
)
override fun popularMangaSelector(): String = searchMangaSelector()
override fun popularMangaFromElement(element: Element): SManga = searchMangaFromElement(element)
override fun popularMangaNextPageSelector(): String = searchMangaNextPageSelector()
// =============================== Latest ===============================
override fun latestUpdatesRequest(page: Int): Request = searchMangaRequest(
page,
"",
defaultFilterList(SortFilter("Latest Updates")),
)
override fun latestUpdatesSelector(): String = searchMangaSelector()
override fun latestUpdatesFromElement(element: Element): SManga = searchMangaFromElement(element)
override fun latestUpdatesNextPageSelector(): String = searchMangaNextPageSelector()
// =============================== Search ===============================
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val filterList = filters.ifEmpty { getFilterList() }
val url = "$baseUrl/search".toHttpUrl().newBuilder().apply {
addQueryParameter("text", query)
filterList.filterIsInstance<UriFilter>().forEach {
it.addToUri(this)
}
addQueryParameter("limit", FETCH_LIMIT.toString())
addQueryParameter("offset", ((page - 1) * FETCH_LIMIT).toString())
}.build()
return GET(url, headers)
}
override fun searchMangaSelector(): String = "#search-results > article:not(#search-more-container)"
override fun searchMangaFromElement(element: Element): SManga = SManga.create().apply {
thumbnail_url = element.selectFirst("img")!!.attr("abs:src")
with(element.selectFirst("div > a")!!) {
title = text()
setUrlWithoutDomain(attr("abs:href"))
}
}
override fun searchMangaNextPageSelector(): String = "#search-more-container > button"
// =============================== Filters ==============================
override fun getFilterList(): FilterList = defaultFilterList(SortFilter())
// =========================== Manga Details ============================
override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply {
with(document.select("section[x-data] > section")[0]) {
thumbnail_url = selectFirst("img")!!.attr("abs:src")
author = select("ul > li:has(strong:contains(Author)) > span > a").joinToString { it.text() }
genre = select("ul > li:has(strong:contains(Tag)) > span > a").joinToString { it.text() }
status = selectFirst("ul > li:has(strong:contains(Status)) > a").parseStatus()
}
with(document.select("section[x-data] > section")[1]) {
title = selectFirst("h1")!!.text()
description = selectFirst("li:has(strong:contains(Description)) > p")?.text()
?.replace("NOTE: ", "\n\nNOTE: ")
}
}
private fun Element?.parseStatus(): Int = when (this?.text()?.lowercase()) {
"ongoing" -> SManga.ONGOING
"complete" -> SManga.COMPLETED
"hiatus" -> SManga.ON_HIATUS
"canceled" -> SManga.CANCELLED
else -> SManga.UNKNOWN
}
// ============================== Chapters ==============================
override fun chapterListRequest(manga: SManga): Request {
val url = (baseUrl + manga.url).toHttpUrl().newBuilder().apply {
removePathSegment(2)
addPathSegment("full-chapter-list")
}.build()
return GET(url, headers)
}
override fun chapterListSelector() = "a"
override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply {
name = element.selectFirst("span.flex")!!.text()
setUrlWithoutDomain(element.attr("abs:href"))
element.selectFirst("time[datetime]")?.also {
date_upload = it.attr("datetime").parseDate()
}
}
private fun String.parseDate(): Long {
return try {
dateFormat.parse(this)!!.time
} catch (_: ParseException) {
0L
}
}
// =============================== Pages ================================
override fun pageListParse(document: Document): List<Page> {
return document.select("section[x-data~=scroll] > img").mapIndexed { index, element ->
Page(index, imageUrl = element.attr("abs:src"))
}
}
override fun imageUrlParse(document: Document) =
throw UnsupportedOperationException()
override fun imageRequest(page: Page): Request {
val imgHeaders = headersBuilder().apply {
add("Accept", "image/avif,image/webp,*/*")
add("Host", page.imageUrl!!.toHttpUrl().host)
}.build()
return GET(page.imageUrl!!, imgHeaders)
}
// ============================= Utilities ==============================
private fun defaultFilterList(sortFilter: SortFilter): FilterList = FilterList(
sortFilter,
SortOrderFilter(),
OfficialTranslationFilter(),
StatusFilter(),
TypeFilter(),
TagFilter(),
)
companion object {
const val FETCH_LIMIT = 24
}
}