Split WPComics Extension (#5799)

* split WPComics

* remove source, it was moved to wpmangastream

* remove WPComics
This commit is contained in:
Riztard Lanthorn
2021-02-12 22:29:30 +07:00
committed by GitHub
parent ca5663ff3a
commit 6e24e80633
31 changed files with 295 additions and 226 deletions

View File

@ -1,2 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="eu.kanade.tachiyomi.extension" />

View File

@ -1,12 +0,0 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
ext {
extName = 'WP-Comics'
pkgNameSuffix = 'all.wpcomics'
extClass = '.WPComicsFactory'
extVersionCode = 21
libVersion = '1.2'
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

View File

@ -1,292 +0,0 @@
package eu.kanade.tachiyomi.extension.all.wpcomics
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.Headers
import okhttp3.HttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
abstract class WPComics(
override val name: String,
override val baseUrl: String,
override val lang: String,
private val dateFormat: SimpleDateFormat = SimpleDateFormat("HH:mm - dd/MM/yyyy Z", Locale.US),
private val gmtOffset: String? = "+0500"
) : ParsedHttpSource() {
override val supportsLatest = true
override val client: OkHttpClient = network.cloudflareClient
override fun headersBuilder(): Headers.Builder = Headers.Builder()
.add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:84.0) Gecko/20100101 Firefox/84.0")
.add("Referer", baseUrl)
private fun List<String>.doesInclude(thisWord: String): Boolean = this.any { it.contains(thisWord, ignoreCase = true) }
// Popular
open val popularPath = "hot"
override fun popularMangaRequest(page: Int): Request {
return GET("$baseUrl/$popularPath" + if (page > 1) "?page=$page" else "", headers)
}
override fun popularMangaSelector() = "div.items div.item"
override fun popularMangaFromElement(element: Element): SManga {
return SManga.create().apply {
element.select("h3 a").let {
title = it.text()
setUrlWithoutDomain(it.attr("abs:href"))
}
thumbnail_url = imageOrNull(element.select("div.image:first-of-type img").first())
}
}
override fun popularMangaNextPageSelector() = "a.next-page, a[rel=next]"
// Latest
override fun latestUpdatesRequest(page: Int): Request {
return GET(baseUrl + if (page > 1) "?page=$page" else "", headers)
}
override fun latestUpdatesSelector() = popularMangaSelector()
override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element)
override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector()
// Search
protected open val searchPath = "tim-truyen"
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val filterList = filters.let { if (it.isEmpty()) getFilterList() else it }
return if (filterList.isEmpty()) {
GET("$baseUrl/?s=$query&post_type=comics&page=$page")
} else {
val url = HttpUrl.parse("$baseUrl/$searchPath")!!.newBuilder()
filterList.forEach { filter ->
when (filter) {
is GenreFilter -> filter.toUriPart()?.let { url.addPathSegment(it) }
is StatusFilter -> filter.toUriPart()?.let { url.addQueryParameter("status", it) }
}
}
url.apply {
addQueryParameter("keyword", query)
addQueryParameter("page", page.toString())
addQueryParameter("sort", "0")
}
GET(url.toString().replace("www.nettruyen.com/tim-truyen?status=2&", "www.nettruyen.com/truyen-full?"), headers)
}
}
override fun searchMangaSelector() = "div.items div.item div.image a"
override fun searchMangaFromElement(element: Element): SManga {
return SManga.create().apply {
title = element.attr("title")
setUrlWithoutDomain(element.attr("href"))
thumbnail_url = imageOrNull(element.select("img").first())
}
}
override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
// Details
override fun mangaDetailsParse(document: Document): SManga {
return SManga.create().apply {
document.select("article#item-detail").let { info ->
author = info.select("li.author p.col-xs-8").text()
status = info.select("li.status p.col-xs-8").text().toStatus()
genre = info.select("li.kind p.col-xs-8 a").joinToString { it.text() }
description = info.select("div.detail-content p").text()
thumbnail_url = imageOrNull(info.select("div.col-image img").first())
}
}
}
open fun String?.toStatus(): Int {
val ongoingWords = listOf("Ongoing", "Updating", "Đang tiến hành")
val completedWords = listOf("Complete", "Hoàn thành")
return when {
this == null -> SManga.UNKNOWN
ongoingWords.doesInclude(this) -> SManga.ONGOING
completedWords.doesInclude(this) -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
}
// Chapters
override fun chapterListSelector() = "div.list-chapter li.row:not(.heading)"
override fun chapterFromElement(element: Element): SChapter {
return SChapter.create().apply {
element.select("a").let {
name = it.text()
setUrlWithoutDomain(it.attr("href"))
}
date_upload = element.select("div.col-xs-4").text().toDate()
}
}
private val currentYear by lazy { Calendar.getInstance(Locale.US)[1].toString().takeLast(2) }
private fun String?.toDate(): Long {
this ?: return 0
val secondWords = listOf("second", "giây")
val minuteWords = listOf("minute", "phút")
val hourWords = listOf("hour", "giờ")
val dayWords = listOf("day", "ngày")
val agoWords = listOf("ago", "trước")
return try {
if (agoWords.any { this.contains(it, ignoreCase = true) }) {
val trimmedDate = this.substringBefore(" ago").removeSuffix("s").split(" ")
val calendar = Calendar.getInstance()
when {
dayWords.doesInclude(trimmedDate[1]) -> calendar.apply { add(Calendar.DAY_OF_MONTH, -trimmedDate[0].toInt()) }
hourWords.doesInclude(trimmedDate[1]) -> calendar.apply { add(Calendar.HOUR_OF_DAY, -trimmedDate[0].toInt()) }
minuteWords.doesInclude(trimmedDate[1]) -> calendar.apply { add(Calendar.MINUTE, -trimmedDate[0].toInt()) }
secondWords.doesInclude(trimmedDate[1]) -> calendar.apply { add(Calendar.SECOND, -trimmedDate[0].toInt()) }
}
calendar.timeInMillis
} else {
(if (gmtOffset == null) this.substringAfterLast(" ") else "$this $gmtOffset").let {
// timestamp has year
if (Regex("""\d+/\d+/\d\d""").find(it)?.value != null) {
dateFormat.parse(it)?.time ?: 0
} else {
// MangaSum - timestamp sometimes doesn't have year (current year implied)
dateFormat.parse("$it/$currentYear")?.time ?: 0
}
}
}
} catch (_: Exception) {
0L
}
}
// Pages
// sources sometimes have an image element with an empty attr that isn't really an image
open fun imageOrNull(element: Element): String? {
fun Element.hasValidAttr(attr: String): Boolean {
val regex = Regex("""https?://.*""", RegexOption.IGNORE_CASE)
return when {
this.attr(attr).isNullOrBlank() -> false
this.attr("abs:$attr").matches(regex) -> true
else -> false
}
}
return when {
element.hasValidAttr("data-original") -> element.attr("abs:data-original")
element.hasValidAttr("data-src") -> element.attr("abs:data-src")
element.hasValidAttr("src") -> element.attr("abs:src")
else -> null
}
}
open val pageListSelector = "div.page-chapter > img, li.blocks-gallery-item img"
override fun pageListParse(document: Document): List<Page> {
return document.select(pageListSelector).mapNotNull { img -> imageOrNull(img) }
.distinct()
.mapIndexed { i, image -> Page(i, "", image) }
}
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used")
// Filters
protected class StatusFilter(vals: Array<Pair<String?, String>>) : UriPartFilter("Status", vals)
protected class GenreFilter(vals: Array<Pair<String?, String>>) : UriPartFilter("Genre", vals)
protected open fun getStatusList(): Array<Pair<String?, String>> = arrayOf(
Pair(null, "Tất cả"),
Pair("1", "Đang tiến hành"),
Pair("2", "Đã hoàn thành"),
Pair("3", "Tạm ngừng")
)
protected open fun getGenreList(): Array<Pair<String?, String>> = arrayOf(
null to "Tất cả",
"action" to "Action",
"adult" to "Adult",
"adventure" to "Adventure",
"anime" to "Anime",
"chuyen-sinh" to "Chuyển Sinh",
"comedy" to "Comedy",
"comic" to "Comic",
"cooking" to "Cooking",
"co-dai" to "Cổ Đại",
"doujinshi" to "Doujinshi",
"drama" to "Drama",
"dam-my" to "Đam Mỹ",
"ecchi" to "Ecchi",
"fantasy" to "Fantasy",
"gender-bender" to "Gender Bender",
"harem" to "Harem",
"historical" to "Historical",
"horror" to "Horror",
"josei" to "Josei",
"live-action" to "Live action",
"manga" to "Manga",
"manhua" to "Manhua",
"manhwa" to "Manhwa",
"martial-arts" to "Martial Arts",
"mature" to "Mature",
"mecha" to "Mecha",
"mystery" to "Mystery",
"ngon-tinh" to "Ngôn Tình",
"one-shot" to "One shot",
"psychological" to "Psychological",
"romance" to "Romance",
"school-life" to "School Life",
"sci-fi" to "Sci-fi",
"seinen" to "Seinen",
"shoujo" to "Shoujo",
"shoujo-ai" to "Shoujo Ai",
"shounen" to "Shounen",
"shounen-ai" to "Shounen Ai",
"slice-of-life" to "Slice of Life",
"smut" to "Smut",
"soft-yaoi" to "Soft Yaoi",
"soft-yuri" to "Soft Yuri",
"sports" to "Sports",
"supernatural" to "Supernatural",
"thieu-nhi" to "Thiếu Nhi",
"tragedy" to "Tragedy",
"trinh-tham" to "Trinh Thám",
"truyen-scan" to "Truyện scan",
"truyen-mau" to "Truyện Màu",
"webtoon" to "Webtoon",
"xuyen-khong" to "Xuyên Không"
)
protected open class UriPartFilter(displayName: String, val vals: Array<Pair<String?, String>>) :
Filter.Select<String>(displayName, vals.map { it.second }.toTypedArray()) {
fun toUriPart() = vals[state].first
}
}

View File

@ -1,210 +0,0 @@
package eu.kanade.tachiyomi.extension.all.wpcomics
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceFactory
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.util.asJsoup
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import java.text.SimpleDateFormat
import java.util.Locale
class WPComicsFactory : SourceFactory {
override fun createSources(): List<Source> = listOf(
ManhuaES(),
MangaSum(),
MangaSumRAW(),
XoxoComics(),
NhatTruyen(),
NetTruyen(),
TruyenChon(),
ComicLatest()
)
}
private class ManhuaES : WPComics("Manhua ES", "https://manhuaes.com", "en", SimpleDateFormat("HH:mm - dd/MM/yyyy Z", Locale.US), "+0700") {
override val popularPath = "category-comics/manga"
override fun popularMangaRequest(page: Int): Request {
return GET("$baseUrl/$popularPath" + if (page > 1) "/page/$page" else "", headers)
}
override fun latestUpdatesRequest(page: Int): Request {
return GET(baseUrl + if (page > 1) "/page/$page" else "", headers)
}
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
return GET("$baseUrl/page/$page/?s=$query&post_type=comics")
}
override fun popularMangaNextPageSelector() = ".pagination li:last-child:not(.active)"
override fun popularMangaFromElement(element: Element): SManga {
return SManga.create().apply {
element.select("div.overlay a:has(h2)").let {
title = it.text()
setUrlWithoutDomain(it.attr("href"))
}
thumbnail_url = element.select("img").firstOrNull()?.attr("abs:src")
}
}
override fun mangaDetailsParse(document: Document): SManga {
return SManga.create().apply {
document.select("article#item-detail").let { info ->
author = info.select("li.author p.col-xs-8").text()
status = info.select("li.status p.col-xs-8").text().toStatus()
genre = info.select(".tags-genre a").joinToString { it.text() }
thumbnail_url = imageOrNull(info.select("div.col-image img").first())
val h3 = info.select(".detail-content h3").text()
val strong = info.select(".detail-content strong").text()
val showMoreFake = info.select(".detail-content .content-readmore").text()
val showMore = info.select(".detail-content .morelink").text()
val rawDesc = info.select("div.detail-content").text()
if (showMoreFake == null || showMoreFake == "") {
description = rawDesc.substringAfter(h3).substringAfter(strong).substringBefore(showMore)
} else {
description = rawDesc.substringAfter(h3).substringAfter(strong).substringBefore(showMoreFake)
}
}
}
}
override val pageListSelector = "div.chapter-detail ul img, div.chapter-detail div:not(.container) > img, div.chapter-detail p > img"
}
private class MangaSumRAW : WPComics("MangaSum RAW", "https://mangasum.com", "ja", SimpleDateFormat("MM/dd/yy", Locale.US), null) {
override fun popularMangaRequest(page: Int): Request {
return GET("$baseUrl/raw" + if (page > 1) "?page=$page" else "", headers)
}
override fun popularMangaSelector() = "div.items div.item"
override fun latestUpdatesRequest(page: Int) = popularMangaRequest(page)
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = GET("$baseUrl/genres?keyword=$query&page=$page", headers)
override fun searchMangaSelector() = "div.items div.item div.image a[title*=' - Raw']"
}
private class MangaSum : WPComics("MangaSum", "https://mangasum.com", "en", SimpleDateFormat("MM/dd/yy", Locale.US), null) {
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = GET("$baseUrl/genres?keyword=$query&page=$page", headers)
override fun searchMangaSelector() = "div.items div.item div.image a:not([title*=' - Raw'])"
}
private class XoxoComics : WPComics("XOXO Comics", "https://xoxocomics.com", "en", SimpleDateFormat("MM/dd/yy", Locale.US), null) {
override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/comic-updates?page=$page", headers)
override fun latestUpdatesSelector() = "li.row"
override fun latestUpdatesFromElement(element: Element): SManga {
return SManga.create().apply {
element.select("h3 a").let {
title = it.text()
setUrlWithoutDomain(it.attr("href"))
}
thumbnail_url = element.select("img").attr("data-original")
}
}
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
return GET("$baseUrl/search?keyword=$query&page=$page", headers)
}
override fun chapterListParse(response: Response): List<SChapter> {
val chapters = mutableListOf<SChapter>()
// recursively add chapters from paginated chapter list
fun parseChapters(document: Document) {
document.select(chapterListSelector()).map { chapters.add(chapterFromElement(it)) }
document.select("ul.pagination a[rel=next]").firstOrNull()?.let { a ->
parseChapters(client.newCall(GET(a.attr("abs:href"), headers)).execute().asJsoup())
}
}
parseChapters(response.asJsoup())
return chapters
}
override fun pageListRequest(chapter: SChapter): Request = GET(baseUrl + "${chapter.url}/all")
}
private class NhatTruyen : WPComics("NhatTruyen", "http://nhattruyen.com", "vi", SimpleDateFormat("dd/MM/yy", Locale.getDefault()), null) {
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = GET("$baseUrl/the-loai?keyword=$query&page=$page", headers)
override fun imageRequest(page: Page): Request = GET(page.imageUrl!!, headersBuilder().add("Referer", baseUrl).build())
}
private class NetTruyen : WPComics("NetTruyen", "http://www.nettruyen.com", "vi", SimpleDateFormat("dd/MM/yy", Locale.getDefault()), null) {
override fun imageRequest(page: Page): Request = GET(page.imageUrl!!, headersBuilder().add("Referer", baseUrl).build())
override fun getFilterList(): FilterList {
return FilterList(
StatusFilter(getStatusList()),
GenreFilter(getGenreList())
)
}
}
private class TruyenChon : WPComics("TruyenChon", "http://truyenchon.com", "vi", SimpleDateFormat("dd/MM/yy", Locale.getDefault()), null) {
override val searchPath = "the-loai"
override fun imageRequest(page: Page): Request = GET(page.imageUrl!!, headersBuilder().add("Referer", baseUrl).build())
override fun getFilterList(): FilterList {
return FilterList(
StatusFilter(getStatusList()),
GenreFilter(getGenreList())
)
}
}
private class ComicLatest : WPComics("ComicLatest", "https://comiclatest.com", "en", SimpleDateFormat("MM/dd/yyyy", Locale.US), null) {
// Hot only has one page
override val popularPath = "popular-comics"
override fun popularMangaFromElement(element: Element) = SManga.create().apply {
element.select("h3 a").let {
title = it.text()
setUrlWithoutDomain(it.attr("href"))
}
thumbnail_url = element.select("img").attr("data-original")
}
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
filters.forEach { filter ->
when (filter) {
is AuthorFilter -> {
val author = filter.state.trim().replace(" ", "-").toLowerCase()
return GET("$baseUrl/author/$author?page=$page", headers)
}
}
}
return GET("$baseUrl/search?keyword=$query&page=$page", headers)
}
override fun searchMangaSelector() = "div.item div.box_img > a[title]"
// For whatever reason, errors with author search if this isn't overridden
override fun searchMangaFromElement(element: Element): SManga {
return SManga.create().apply {
title = element.attr("title")
setUrlWithoutDomain(element.attr("href"))
}
}
override fun chapterListParse(response: Response): List<SChapter> {
val chapters = mutableListOf<SChapter>()
fun parseChapters(document: Document) {
document.select(chapterListSelector()).map { chapters.add(chapterFromElement(it)) }
document.select("ul.pagination a[rel=next]").firstOrNull()?.let { a ->
parseChapters(client.newCall(GET(a.attr("abs:href"), headers)).execute().asJsoup())
}
}
parseChapters(response.asJsoup())
return chapters
}
override fun pageListRequest(chapter: SChapter) = GET("$baseUrl${chapter.url}/all", headers)
private class AuthorFilter : Filter.Text("Author")
override fun getFilterList() = FilterList(
Filter.Header("NOTE: Cannot be used with search"),
Filter.Separator(),
AuthorFilter()
)
}