mirror of
https://github.com/keiyoushi/extensions-source.git
synced 2024-11-25 19:52:56 +01:00
parent
bffcba79f5
commit
e6cc5e0f56
8
src/en/weebcentral/build.gradle
Normal file
8
src/en/weebcentral/build.gradle
Normal file
@ -0,0 +1,8 @@
|
||||
ext {
|
||||
extName = 'Weeb Central'
|
||||
extClass = '.WeebCentral'
|
||||
extVersionCode = 1
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
BIN
src/en/weebcentral/res/mipmap-hdpi/ic_launcher.png
Normal file
BIN
src/en/weebcentral/res/mipmap-hdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.5 KiB |
BIN
src/en/weebcentral/res/mipmap-mdpi/ic_launcher.png
Normal file
BIN
src/en/weebcentral/res/mipmap-mdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.9 KiB |
BIN
src/en/weebcentral/res/mipmap-xhdpi/ic_launcher.png
Normal file
BIN
src/en/weebcentral/res/mipmap-xhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.8 KiB |
BIN
src/en/weebcentral/res/mipmap-xxhdpi/ic_launcher.png
Normal file
BIN
src/en/weebcentral/res/mipmap-xxhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
BIN
src/en/weebcentral/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
BIN
src/en/weebcentral/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
@ -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"),
|
||||
),
|
||||
)
|
@ -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
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user