add ZManga multi-source (#8779)
* add ZManga multi-source - add KomikPlay source - remove MangaKane - add MaidManga and KomikPlay to ZManga Factory * add default icon Co-Authored-By: Ankit Singh <as280093@gmail.com> Co-authored-by: Ankit Singh <as280093@gmail.com>
After Width: | Height: | Size: 4.5 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 6.0 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 66 KiB |
|
@ -0,0 +1,57 @@
|
||||||
|
package eu.kanade.tachiyomi.extension.id.komikplay
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.multisrc.zmanga.ZManga
|
||||||
|
import eu.kanade.tachiyomi.network.GET
|
||||||
|
import eu.kanade.tachiyomi.source.model.Filter
|
||||||
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
|
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||||
|
import okhttp3.Request
|
||||||
|
import org.jsoup.nodes.Element
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
class KomikPlay : ZManga("KomikPlay", "https://komikplay.com", "id", SimpleDateFormat("d MMM yyyy", Locale.US)) {
|
||||||
|
|
||||||
|
override fun popularMangaRequest(page: Int): Request {
|
||||||
|
return GET("$baseUrl/${pagePathSegment(page)}/?s")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun latestUpdatesRequest(page: Int): Request {
|
||||||
|
return GET("$baseUrl/${pagePathSegment(page)}")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun latestUpdatesSelector() = "h2:contains(New) + .flexbox3 .flexbox3-item"
|
||||||
|
|
||||||
|
override fun latestUpdatesFromElement(element: Element): SManga {
|
||||||
|
return SManga.create().apply {
|
||||||
|
setUrlWithoutDomain(element.select("div.flexbox3-content a").attr("href"))
|
||||||
|
title = element.select("div.flexbox3-content a").attr("title")
|
||||||
|
thumbnail_url = element.select("img").attr("abs:src")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||||
|
var url = "$baseUrl/${pagePathSegment(page)}".toHttpUrlOrNull()!!.newBuilder()
|
||||||
|
url.addQueryParameter("s", query)
|
||||||
|
(if (filters.isEmpty()) getFilterList() else filters).forEach { filter ->
|
||||||
|
when (filter) {
|
||||||
|
// if site has project page, default value "hasProjectPage" = false
|
||||||
|
is ProjectFilter -> {
|
||||||
|
if (filter.toUriPart() == "project-filter-on") {
|
||||||
|
url = "$baseUrl$projectPageString/page/$page".toHttpUrlOrNull()!!.newBuilder()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return GET(url.toString(), headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getFilterList() = FilterList(
|
||||||
|
Filter.Header("NOTE: cant be used with other filter!"),
|
||||||
|
Filter.Header("$name Project List page"),
|
||||||
|
ProjectFilter(),
|
||||||
|
)
|
||||||
|
|
||||||
|
override val hasProjectPage = true
|
||||||
|
}
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.1 KiB |
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 7.9 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
@ -0,0 +1,9 @@
|
||||||
|
package eu.kanade.tachiyomi.extension.id.maidmanga
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.multisrc.zmanga.ZManga
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
class MaidManga : ZManga("Maid - Manga", "https://www.maid.my.id", "id", SimpleDateFormat("MMM d, yyyy", Locale("id"))) {
|
||||||
|
override val hasProjectPage = true
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package eu.kanade.tachiyomi.extension.id.maidmanga
|
package eu.kanade.tachiyomi.multisrc.zmanga
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
import eu.kanade.tachiyomi.source.model.Filter
|
import eu.kanade.tachiyomi.source.model.Filter
|
||||||
|
@ -15,40 +15,48 @@ import org.jsoup.nodes.Element
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
class MaidManga : ParsedHttpSource() {
|
abstract class ZManga(
|
||||||
|
override val name: String,
|
||||||
override val name = "Maid - Manga"
|
override val baseUrl: String,
|
||||||
|
override val lang: String,
|
||||||
override val baseUrl = "https://www.maid.my.id"
|
private val dateFormat: SimpleDateFormat = SimpleDateFormat("MMM d, yyyy", Locale.US),
|
||||||
|
) : ParsedHttpSource() {
|
||||||
override val lang = "id"
|
|
||||||
|
|
||||||
override val supportsLatest = true
|
override val supportsLatest = true
|
||||||
|
|
||||||
override val client: OkHttpClient = network.cloudflareClient
|
override val client: OkHttpClient = network.cloudflareClient
|
||||||
|
|
||||||
private fun pagePathSegment(page: Int): String = if (page > 1) "page/$page/" else ""
|
protected fun pagePathSegment(page: Int): String = if (page > 1) "page/$page/" else ""
|
||||||
|
|
||||||
override fun latestUpdatesSelector() = searchMangaSelector()
|
// popular
|
||||||
|
override fun popularMangaRequest(page: Int): Request {
|
||||||
|
return GET("$baseUrl/advanced-search/${pagePathSegment(page)}?order=popular")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun popularMangaSelector() = "div.flexbox2-item"
|
||||||
|
|
||||||
|
override fun popularMangaFromElement(element: Element): SManga {
|
||||||
|
return SManga.create().apply {
|
||||||
|
setUrlWithoutDomain(element.select("div.flexbox2-content a").attr("href"))
|
||||||
|
title = element.select("div.flexbox2-title > span").first().text()
|
||||||
|
thumbnail_url = element.select("img").attr("abs:src")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun popularMangaNextPageSelector() = "div.pagination .next"
|
||||||
|
|
||||||
|
// latest
|
||||||
|
override fun latestUpdatesSelector() = popularMangaSelector()
|
||||||
|
|
||||||
override fun latestUpdatesRequest(page: Int): Request {
|
override fun latestUpdatesRequest(page: Int): Request {
|
||||||
return GET("$baseUrl/advanced-search/${pagePathSegment(page)}?order=update")
|
return GET("$baseUrl/advanced-search/${pagePathSegment(page)}?order=update")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun latestUpdatesFromElement(element: Element): SManga = searchMangaFromElement(element)
|
override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element)
|
||||||
|
|
||||||
override fun latestUpdatesNextPageSelector() = searchMangaNextPageSelector()
|
override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector()
|
||||||
|
|
||||||
override fun popularMangaRequest(page: Int): Request {
|
|
||||||
return GET("$baseUrl/advanced-search/${pagePathSegment(page)}?order=popular")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun popularMangaSelector() = searchMangaSelector()
|
|
||||||
|
|
||||||
override fun popularMangaFromElement(element: Element): SManga = searchMangaFromElement(element)
|
|
||||||
|
|
||||||
override fun popularMangaNextPageSelector() = searchMangaNextPageSelector()
|
|
||||||
|
|
||||||
|
// search
|
||||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||||
var url = "$baseUrl/advanced-search/${pagePathSegment(page)}".toHttpUrlOrNull()!!.newBuilder()
|
var url = "$baseUrl/advanced-search/${pagePathSegment(page)}".toHttpUrlOrNull()!!.newBuilder()
|
||||||
url.addQueryParameter("title", query)
|
url.addQueryParameter("title", query)
|
||||||
|
@ -79,9 +87,10 @@ class MaidManga : ParsedHttpSource() {
|
||||||
.filter { it.state }
|
.filter { it.state }
|
||||||
.forEach { url.addQueryParameter("genre[]", it.id) }
|
.forEach { url.addQueryParameter("genre[]", it.id) }
|
||||||
}
|
}
|
||||||
|
// if site has project page, default value "hasProjectPage" = false
|
||||||
is ProjectFilter -> {
|
is ProjectFilter -> {
|
||||||
if (filter.toUriPart() == "project-filter-on") {
|
if (filter.toUriPart() == "project-filter-on") {
|
||||||
url = "$baseUrl/project-list/page/$page".toHttpUrlOrNull()!!.newBuilder()
|
url = "$baseUrl$projectPageString/page/$page".toHttpUrlOrNull()!!.newBuilder()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -89,36 +98,33 @@ class MaidManga : ParsedHttpSource() {
|
||||||
return GET(url.toString(), headers)
|
return GET(url.toString(), headers)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun searchMangaSelector() = "div.flexbox2-item"
|
open val projectPageString = "/project-list"
|
||||||
|
|
||||||
override fun searchMangaFromElement(element: Element): SManga {
|
override fun searchMangaSelector() = popularMangaSelector()
|
||||||
return SManga.create().apply {
|
|
||||||
setUrlWithoutDomain(element.select("div.flexbox2-content a").attr("href"))
|
|
||||||
title = element.select("div.flexbox2-title > span").first().text()
|
|
||||||
thumbnail_url = element.select("img").attr("abs:src")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun searchMangaNextPageSelector() = "div.pagination span.current + a"
|
override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element)
|
||||||
|
|
||||||
|
override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
|
||||||
|
|
||||||
|
// manga details
|
||||||
override fun mangaDetailsParse(document: Document): SManga {
|
override fun mangaDetailsParse(document: Document): SManga {
|
||||||
return SManga.create().apply {
|
return SManga.create().apply {
|
||||||
genre = document.select("div.series-genres a").joinToString { it.text() }
|
|
||||||
description = document.select("div.series-synops").text()
|
|
||||||
thumbnail_url = document.select("div.series-thumb img").attr("abs:src")
|
thumbnail_url = document.select("div.series-thumb img").attr("abs:src")
|
||||||
status = parseStatus(document.select("div.block span.status").text())
|
author = document.select(".series-infolist li:contains(Author) span").text()
|
||||||
author = document.select("ul.series-infolist li b:contains(Author) + span").text()
|
artist = document.select(".series-infolist li:contains(Artist) span").text()
|
||||||
|
status = parseStatus(document.select(".series-infoz .status").firstOrNull()?.ownText())
|
||||||
|
description = document.select("div.series-synops").text()
|
||||||
|
genre = document.select("div.series-genres a").joinToString { it.text() }
|
||||||
|
|
||||||
// add series type(manga/manhwa/manhua/other) thinggy to genre
|
// add series type(manga/manhwa/manhua/other) thinggy to genre
|
||||||
document.select("div.block span.type").firstOrNull()?.ownText()?.let {
|
document.select(seriesTypeSelector).firstOrNull()?.ownText()?.let {
|
||||||
if (it.isEmpty().not() && it != "-" && genre!!.contains(it, true).not()) {
|
if (it.isEmpty().not() && it != "-" && genre!!.contains(it, true).not()) {
|
||||||
genre += if (genre!!.isEmpty()) it else ", $it"
|
genre += if (genre!!.isEmpty()) it else ", $it"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// add alternative name to manga description
|
// add alternative name to manga description
|
||||||
val altName = "Alternative Name: "
|
document.select(altNameSelector).firstOrNull()?.ownText()?.let {
|
||||||
document.select(".series-title span").firstOrNull()?.ownText()?.let {
|
|
||||||
if (it.isBlank().not()) {
|
if (it.isBlank().not()) {
|
||||||
description = when {
|
description = when {
|
||||||
description.isNullOrBlank() -> altName + it
|
description.isNullOrBlank() -> altName + it
|
||||||
|
@ -129,19 +135,28 @@ class MaidManga : ParsedHttpSource() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open val seriesTypeSelector = "div.block span.type"
|
||||||
|
open val altNameSelector = ".series-title span"
|
||||||
|
open val altName = "Alternative Name" + ": "
|
||||||
|
|
||||||
private fun parseStatus(status: String?) = when {
|
private fun parseStatus(status: String?) = when {
|
||||||
status == null -> SManga.UNKNOWN
|
status == null -> SManga.UNKNOWN
|
||||||
status.contains("Ongoing") -> SManga.ONGOING
|
status.contains("Ongoing", true) -> SManga.ONGOING
|
||||||
status.contains("Completed") -> SManga.COMPLETED
|
status.contains("Completed", true) -> SManga.COMPLETED
|
||||||
else -> SManga.UNKNOWN
|
else -> SManga.UNKNOWN
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseDate(date: String): Long {
|
private fun parseDate(date: String): Long {
|
||||||
return SimpleDateFormat("MMM d, yyyy", Locale("id")).parse(date)?.time ?: 0L
|
return try {
|
||||||
|
dateFormat.parse(date)?.time ?: 0
|
||||||
|
} catch (_: Exception) {
|
||||||
|
0L
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// chapters
|
||||||
// careful not to include download links
|
// careful not to include download links
|
||||||
override fun chapterListSelector() = "ul.series-chapterlist div.flexch-infoz > a"
|
override fun chapterListSelector() = "ul.series-chapterlist div.flexch-infoz a"
|
||||||
|
|
||||||
override fun chapterFromElement(element: Element): SChapter {
|
override fun chapterFromElement(element: Element): SChapter {
|
||||||
return SChapter.create().apply {
|
return SChapter.create().apply {
|
||||||
|
@ -151,6 +166,7 @@ class MaidManga : ParsedHttpSource() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// pages
|
||||||
override fun pageListParse(document: Document): List<Page> {
|
override fun pageListParse(document: Document): List<Page> {
|
||||||
return document.select("div.reader-area img").mapIndexed { i, img ->
|
return document.select("div.reader-area img").mapIndexed { i, img ->
|
||||||
Page(i, "", img.attr("abs:src"))
|
Page(i, "", img.attr("abs:src"))
|
||||||
|
@ -159,22 +175,35 @@ class MaidManga : ParsedHttpSource() {
|
||||||
|
|
||||||
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used")
|
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used")
|
||||||
|
|
||||||
override fun getFilterList() = FilterList(
|
open val hasProjectPage = false
|
||||||
Filter.Header("You can combine filter."),
|
|
||||||
Filter.Separator(),
|
|
||||||
AuthorFilter(),
|
|
||||||
YearFilter(),
|
|
||||||
StatusFilter(),
|
|
||||||
TypeFilter(),
|
|
||||||
OrderByFilter(),
|
|
||||||
GenreList(getGenreList()),
|
|
||||||
Filter.Separator(),
|
|
||||||
Filter.Header("NOTE: cant be used with other filter!"),
|
|
||||||
Filter.Header("$name Project List page"),
|
|
||||||
ProjectFilter(),
|
|
||||||
)
|
|
||||||
|
|
||||||
private class ProjectFilter : UriPartFilter(
|
// filters
|
||||||
|
|
||||||
|
override fun getFilterList(): FilterList {
|
||||||
|
val filters = mutableListOf<Filter<*>>(
|
||||||
|
Filter.Header("You can combine filter."),
|
||||||
|
Filter.Separator(),
|
||||||
|
AuthorFilter(),
|
||||||
|
YearFilter(),
|
||||||
|
StatusFilter(),
|
||||||
|
TypeFilter(),
|
||||||
|
OrderByFilter(),
|
||||||
|
GenreList(getGenreList()),
|
||||||
|
)
|
||||||
|
if (hasProjectPage) {
|
||||||
|
filters.addAll(
|
||||||
|
mutableListOf<Filter<*>>(
|
||||||
|
Filter.Separator(),
|
||||||
|
Filter.Header("NOTE: cant be used with other filter!"),
|
||||||
|
Filter.Header("$name Project List page"),
|
||||||
|
ProjectFilter(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return FilterList(filters)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected class ProjectFilter : UriPartFilter(
|
||||||
"Filter Project",
|
"Filter Project",
|
||||||
arrayOf(
|
arrayOf(
|
||||||
Pair("Show all manga", ""),
|
Pair("Show all manga", ""),
|
||||||
|
@ -270,7 +299,7 @@ class MaidManga : ParsedHttpSource() {
|
||||||
Tag("yuri", "Yuri")
|
Tag("yuri", "Yuri")
|
||||||
)
|
)
|
||||||
|
|
||||||
private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) :
|
open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) :
|
||||||
Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
|
Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
|
||||||
fun toUriPart() = vals[state].second
|
fun toUriPart() = vals[state].second
|
||||||
}
|
}
|
||||||
|
@ -278,4 +307,5 @@ class MaidManga : ParsedHttpSource() {
|
||||||
private class Tag(val id: String, name: String) : Filter.CheckBox(name)
|
private class Tag(val id: String, name: String) : Filter.CheckBox(name)
|
||||||
|
|
||||||
private class GenreList(genres: List<Tag>) : Filter.Group<Tag>("Genres", genres)
|
private class GenreList(genres: List<Tag>) : Filter.Group<Tag>("Genres", genres)
|
||||||
|
|
||||||
}
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package eu.kanade.tachiyomi.multisrc.zmanga
|
||||||
|
|
||||||
|
import generator.ThemeSourceData.SingleLang
|
||||||
|
import generator.ThemeSourceGenerator
|
||||||
|
|
||||||
|
class ZMangaGenerator : ThemeSourceGenerator {
|
||||||
|
|
||||||
|
override val themePkg = "zmanga"
|
||||||
|
|
||||||
|
override val themeClass = "ZManga"
|
||||||
|
|
||||||
|
override val baseVersionCode: Int = 1
|
||||||
|
|
||||||
|
override val sources = listOf(
|
||||||
|
SingleLang("Maid - Manga", "https://www.maid.my.id", "id", overrideVersionCode = 9, className = "MaidManga"),
|
||||||
|
SingleLang("KomikPlay", "https://komikplay.com", "id"),
|
||||||
|
)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmStatic
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
ZMangaGenerator().createAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,2 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<manifest package="eu.kanade.tachiyomi.extension" />
|
|
|
@ -1,12 +0,0 @@
|
||||||
apply plugin: 'com.android.application'
|
|
||||||
apply plugin: 'kotlin-android'
|
|
||||||
|
|
||||||
ext {
|
|
||||||
extName = 'Maid - Manga'
|
|
||||||
pkgNameSuffix = 'id.maidmanga'
|
|
||||||
extClass = '.MaidManga'
|
|
||||||
extVersionCode = 9
|
|
||||||
libVersion = '1.2'
|
|
||||||
}
|
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
|
|
@ -1,2 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<manifest package="eu.kanade.tachiyomi.extension" />
|
|
|
@ -1,12 +0,0 @@
|
||||||
apply plugin: 'com.android.application'
|
|
||||||
apply plugin: 'kotlin-android'
|
|
||||||
|
|
||||||
ext {
|
|
||||||
extName = 'MangaKane'
|
|
||||||
pkgNameSuffix = 'id.mangakane'
|
|
||||||
extClass = '.MangaKane'
|
|
||||||
extVersionCode = 2
|
|
||||||
libVersion = '1.2'
|
|
||||||
}
|
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
|
Before Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 4.5 KiB |
Before Width: | Height: | Size: 8.8 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 61 KiB |
|
@ -1,131 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.extension.id.mangakane
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.network.GET
|
|
||||||
import eu.kanade.tachiyomi.source.model.Filter
|
|
||||||
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.toHttpUrlOrNull
|
|
||||||
import okhttp3.Request
|
|
||||||
import org.jsoup.nodes.Document
|
|
||||||
import org.jsoup.nodes.Element
|
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
class MangaKane : ParsedHttpSource() {
|
|
||||||
|
|
||||||
override val name = "MangaKane"
|
|
||||||
override val baseUrl = "https://mangakane.com"
|
|
||||||
override val lang = "id"
|
|
||||||
override val supportsLatest = true
|
|
||||||
|
|
||||||
override fun popularMangaRequest(page: Int): Request {
|
|
||||||
return GET("$baseUrl/series/page/$page", headers)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun latestUpdatesRequest(page: Int): Request {
|
|
||||||
return GET("$baseUrl/page/$page", headers)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
|
||||||
var url = "$baseUrl/page/$page/".toHttpUrlOrNull()!!.newBuilder()
|
|
||||||
url.addQueryParameter("s", query)
|
|
||||||
filters.forEach { filter ->
|
|
||||||
when (filter) {
|
|
||||||
is ProjectFilter -> {
|
|
||||||
if (filter.toUriPart() == "project-filter-on") {
|
|
||||||
url = "$baseUrl/project-list/page/$page".toHttpUrlOrNull()!!.newBuilder()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return GET(url.build().toString(), headers)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun popularMangaSelector() = ".container .flexbox2 .flexbox2-item"
|
|
||||||
override fun latestUpdatesSelector() = "h2:not(:has(a)) + .flexbox3 .flexbox3-item"
|
|
||||||
override fun searchMangaSelector() = popularMangaSelector()
|
|
||||||
|
|
||||||
override fun popularMangaFromElement(element: Element): SManga {
|
|
||||||
val manga = SManga.create()
|
|
||||||
manga.setUrlWithoutDomain(element.select("a").attr("href"))
|
|
||||||
manga.title = element.select("a").attr("title")
|
|
||||||
manga.thumbnail_url = element.select("a img").attr("abs:src")
|
|
||||||
|
|
||||||
return manga
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element)
|
|
||||||
override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element)
|
|
||||||
|
|
||||||
override fun popularMangaNextPageSelector() = ".pagination .next"
|
|
||||||
override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector()
|
|
||||||
override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
|
|
||||||
|
|
||||||
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
|
|
||||||
author = document.select(".series-infolist li:contains(Author) span").text()
|
|
||||||
artist = document.select(".series-infolist li:contains(Artist) span").text()
|
|
||||||
status = parseStatus(document.select(".series-infoz .status").firstOrNull()?.ownText())
|
|
||||||
description = document.select(".series-synops p").text()
|
|
||||||
genre = document.select(".series-genres a").joinToString { it.text() }
|
|
||||||
}
|
|
||||||
|
|
||||||
protected fun parseStatus(element: String?): Int = when {
|
|
||||||
element == null -> SManga.UNKNOWN
|
|
||||||
listOf("ongoing", "publishing").any { it.contains(element, ignoreCase = true) } -> SManga.ONGOING
|
|
||||||
listOf("completed").any { it.contains(element, ignoreCase = true) } -> SManga.COMPLETED
|
|
||||||
else -> SManga.UNKNOWN
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun chapterListSelector() = ".series-chapterlist li"
|
|
||||||
|
|
||||||
override fun chapterFromElement(element: Element) = SChapter.create().apply {
|
|
||||||
setUrlWithoutDomain(element.select(".flexch-infoz a").attr("href"))
|
|
||||||
name = element.select(".flexch-infoz span:not(.date)").first().ownText()
|
|
||||||
date_upload = parseChapterDate(element.select(".flexch-infoz .date").text()) ?: 0
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun parseChapterDate(date: String): Long {
|
|
||||||
var parsedDate = 0L
|
|
||||||
try {
|
|
||||||
parsedDate = SimpleDateFormat("MMM dd, yyyy", Locale.US).parse(date)?.time ?: 0L
|
|
||||||
} catch (_: Exception) { /*nothing to do, parsedDate is initialized with 0L*/ }
|
|
||||||
return parsedDate
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun pageListParse(document: Document): List<Page> {
|
|
||||||
val pages = mutableListOf<Page>()
|
|
||||||
var i = 0
|
|
||||||
document.select(".reader-area img").forEach { element ->
|
|
||||||
val url = element.attr("abs:src")
|
|
||||||
i++
|
|
||||||
if (url.isNotEmpty()) {
|
|
||||||
pages.add(Page(i, "", url))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return pages
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun imageUrlParse(document: Document) = ""
|
|
||||||
|
|
||||||
override fun getFilterList() = FilterList(
|
|
||||||
Filter.Header("NOTE: cant be used with search or other filter!"),
|
|
||||||
Filter.Header("$name Project List page"),
|
|
||||||
ProjectFilter(),
|
|
||||||
)
|
|
||||||
|
|
||||||
private class ProjectFilter : UriPartFilter(
|
|
||||||
"Filter Project",
|
|
||||||
arrayOf(
|
|
||||||
Pair("Show all manga", ""),
|
|
||||||
Pair("Show only project manga", "project-filter-on")
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) :
|
|
||||||
Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
|
|
||||||
fun toUriPart() = vals[state].second
|
|
||||||
}
|
|
||||||
}
|
|