add Hotcomics (#2249)

* HotComics

* Filters

* add other similar sources

Tomics.Top, de
ToomicsFree, en

* newline

* implementation -> api

* daycomics.me

* ToomicsFree.info
This commit is contained in:
AwkwardPeak7 2024-04-06 12:28:39 +05:00 committed by GitHub
parent 958fd9a045
commit ab5cb5bbcf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
37 changed files with 373 additions and 0 deletions

View File

@ -0,0 +1,9 @@
plugins {
id("lib-multisrc")
}
baseVersionCode = 1
dependencies {
api(project(":lib:cookieinterceptor"))
}

View File

@ -0,0 +1,158 @@
package eu.kanade.tachiyomi.multisrc.hotcomics
import eu.kanade.tachiyomi.lib.cookieinterceptor.CookieInterceptor
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.MangasPage
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.HttpSource
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Element
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.Locale
abstract class HotComics(
final override val name: String,
final override val lang: String,
final override val baseUrl: String,
) : HttpSource() {
override val supportsLatest = true
override val client = network.cloudflareClient.newBuilder()
.addNetworkInterceptor(
CookieInterceptor(baseUrl.removePrefix("https://"), "hc_vfs" to "Y"),
)
.build()
override fun headersBuilder() = super.headersBuilder()
.set("Referer", "$baseUrl/")
override fun popularMangaRequest(page: Int) = GET("$baseUrl/en", headers)
override fun popularMangaParse(response: Response) = searchMangaParse(response)
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/en/new", headers)
override fun latestUpdatesParse(response: Response) = popularMangaParse(response)
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val url = baseUrl.toHttpUrl().newBuilder().apply {
if (query.isNotEmpty()) {
addEncodedPathSegments("en/search")
addQueryParameter("keyword", query.trim())
} else {
val filter = filters.filterIsInstance<BrowseFilter>().first()
addEncodedPathSegments(filter.selected)
addQueryParameter("page", page.toString())
}
}.build()
return GET(url, headers)
}
abstract class SelectFilter(
name: String,
private val options: List<Pair<String, String>>,
) : Filter.Select<String>(
name,
options.map { it.first }.toTypedArray(),
) {
val selected get() = options[state].second
}
abstract val browseList: List<Pair<String, String>>
class BrowseFilter(browseList: List<Pair<String, String>>) : SelectFilter("Browse", browseList)
override fun getFilterList() = FilterList(
Filter.Header("Doesn't work with Text search"),
Filter.Separator(),
BrowseFilter(browseList),
)
override fun searchMangaParse(response: Response): MangasPage {
val document = response.asJsoup()
val entries = document.select("li[itemtype*=ComicSeries]:not(.no-comic) > a").map { element ->
SManga.create().apply {
setUrlWithoutDomain(element.absUrl("href"))
thumbnail_url = element.selectFirst("div.visual img")?.imgAttr()
title = element.selectFirst("div.main-text > h4.title")!!.text()
}
}.distinctBy { it.url }
val hasNextPage = document.selectFirst("div.pagination a.vnext:not(.disabled)") != null
return MangasPage(entries, hasNextPage)
}
override fun mangaDetailsParse(response: Response) = SManga.create().apply {
val document = response.asJsoup()
title = document.selectFirst("h2.episode-title")!!.text()
with(document.selectFirst("p.type_box")!!) {
author = selectFirst("span.writer")?.text()
?.substringAfter("")?.trim()
genre = selectFirst("span.type")?.text()
?.split("/")?.joinToString { it.trim() }
status = when (selectFirst("span.date")?.text()) {
"End", "Ende" -> SManga.COMPLETED
null -> SManga.UNKNOWN
else -> SManga.ONGOING
}
}
description = buildString {
document.selectFirst("div.episode-contents header")
?.text()?.let {
append(it)
append("\n\n")
}
document.selectFirst("div.title_content > h2:not(.episode-title)")
?.text()?.let { append(it) }
}.trim()
}
override fun chapterListParse(response: Response): List<SChapter> {
return response.asJsoup().select("#tab-chapter a").map { element ->
SChapter.create().apply {
setUrlWithoutDomain(element.absUrl("href"))
name = element.selectFirst(".cell-num")!!.text()
date_upload = parseDate(element.selectFirst(".cell-time")?.text())
}
}.reversed()
}
private val dateFormat = SimpleDateFormat("MMM dd, yyyy", Locale.ENGLISH)
private fun parseDate(date: String?): Long {
date ?: return 0L
return try {
dateFormat.parse(date)!!.time
} catch (_: ParseException) {
0L
}
}
override fun pageListParse(response: Response): List<Page> {
return response.asJsoup().select("#viewer-img img").mapIndexed { idx, img ->
Page(idx, imageUrl = img.imgAttr())
}
}
private fun Element.imgAttr(): String {
return when {
hasAttr("data-src") -> absUrl("data-src")
else -> absUrl("src")
}
}
override fun imageUrlParse(response: Response): String {
throw UnsupportedOperationException()
}
}

View File

@ -0,0 +1,9 @@
ext {
extName = 'Toomics.Top'
extClass = '.ToomicsTop'
themePkg = 'hotcomics'
overrideVersionCode = 0
isNsfw = true
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -0,0 +1,44 @@
package eu.kanade.tachiyomi.extension.de.toomicstop
import eu.kanade.tachiyomi.multisrc.hotcomics.HotComics
import eu.kanade.tachiyomi.source.model.MangasPage
import okhttp3.Response
class ToomicsTop : HotComics(
"Toomics.Top",
"de",
"https://toomics.top",
) {
override fun searchMangaParse(response: Response): MangasPage {
val mangasPage = super.searchMangaParse(response)
mangasPage.mangas.apply {
for (i in indices) {
this[i].url = this[i].url.replace(urlIdRegex, ".html")
}
}
return mangasPage
}
private val urlIdRegex = Regex("""(/\w+).html""")
override val browseList = listOf(
Pair("Home", "en"),
Pair("Weekly", "en/weekly"),
Pair("New", "en/new"),
Pair("Genre: All", "en/genres"),
Pair("Genre: Romantik", "en/genres/Romantik"),
Pair("Genre: Drama", "en/genres/Drama"),
Pair("Genre: BL", "en/genres/BL"),
Pair("Genre: Action", "en/genres/Action"),
Pair("Genre: Schulleben", "en/genres/Schulleben"),
Pair("Genre: Fantasy", "en/genres/Fantasy"),
Pair("Genre: Comedy", "en/genres/Comedy"),
Pair("Genre: Historisch", "en/genres/Historisch"),
Pair("Genre: Sci-Fi", "en/genres/Sci-Fi"),
Pair("Genre: Thriller", "en/genres/Thriller"),
Pair("Genre: Horror", "en/genres/Horror"),
Pair("Genre: Sport", "en/genres/Sport"),
Pair("Genre: GL", "en/genres/GL"),
)
}

View File

@ -0,0 +1,9 @@
ext {
extName = 'DAYcomics.me'
extClass = '.DAYcomicsMe'
themePkg = 'hotcomics'
overrideVersionCode = 0
isNsfw = true
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -0,0 +1,30 @@
package eu.kanade.tachiyomi.extension.en.daycomicsme
import eu.kanade.tachiyomi.multisrc.hotcomics.HotComics
class DAYcomicsMe : HotComics(
"DAYcomics.me",
"en",
"https://daycomics.me",
) {
override val browseList = listOf(
Pair("Home", "en"),
Pair("Weekly", "en/weekly"),
Pair("New", "en/new"),
Pair("Genre: All", "en/genres"),
Pair("Genre: Romance", "en/genres/Romance"),
Pair("Genre: Office", "en/genres/Office"),
Pair("Genre: College", "en/genres/College"),
Pair("Genre: Drama", "en/genres/Drama"),
Pair("Genre: Isekai", "en/genres/Isekai"),
Pair("Genre: UNCENSORED", "en/genres/UNCENSORED"),
Pair("Genre: Action", "en/genres/Action"),
Pair("Genre: BL", "en/genres/BL"),
Pair("Genre: New", "en/genres/New"),
Pair("Genre: Slice of Life", "en/genres/Slice_of_Life"),
Pair("Genre: Supernatural", "en/genres/Supernatural"),
Pair("Genre: Historical", "en/genres/Historical"),
Pair("Genre: School Life", "en/genres/School_Life"),
Pair("Genre: Horror Thriller", "en/genres/Horror_Thriller"),
)
}

View File

@ -0,0 +1,9 @@
ext {
extName = 'HotComics'
extClass = '.HotComics'
themePkg = 'hotcomics'
overrideVersionCode = 0
isNsfw = true
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -0,0 +1,29 @@
package eu.kanade.tachiyomi.extension.en.hotcomics
import eu.kanade.tachiyomi.multisrc.hotcomics.HotComics
class HotComics : HotComics(
"HotComics",
"en",
"https://hotcomics.me",
) {
override val browseList = listOf(
Pair("Home", "en"),
Pair("Weekly", "en/weekly"),
Pair("New", "en/new"),
Pair("Genre: All", "en/genres"),
Pair("Genre: Sports", "en/genres/Sports"),
Pair("Genre: Historical", "en/genres/Historical"),
Pair("Genre: Drama", "en/genres/Drama"),
Pair("Genre: BL", "en/genres/BL"),
Pair("Genre: Thriller", "en/genres/Thriller"),
Pair("Genre: School life", "en/genres/School_life"),
Pair("Genre: Comedy", "en/genres/Comedy"),
Pair("Genre: GL", "en/genres/GL"),
Pair("Genre: Action", "en/genres/Action"),
Pair("Genre: Sci-fi", "en/genres/Sci-fi"),
Pair("Genre: Horror", "en/genres/Horror"),
Pair("Genre: Fantasy", "en/genres/Fantasy"),
Pair("Genre: Romance", "en/genres/Romance"),
)
}

View File

@ -0,0 +1,9 @@
ext {
extName = 'ToomicsFree.com'
extClass = '.ToomicsFree'
themePkg = 'hotcomics'
overrideVersionCode = 0
isNsfw = true
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -0,0 +1,29 @@
package eu.kanade.tachiyomi.extension.en.toomicsfree
import eu.kanade.tachiyomi.multisrc.hotcomics.HotComics
class ToomicsFree : HotComics(
"ToomicsFree.com",
"en",
"https://toomicsfree.com",
) {
override val browseList = listOf(
Pair("Home", "en"),
Pair("Weekly", "en/weekly"),
Pair("New", "en/new"),
Pair("Genre: All", "en/genres"),
Pair("Genre: Sports", "en/genres/Sports"),
Pair("Genre: Historical", "en/genres/Historical"),
Pair("Genre: Drama", "en/genres/Drama"),
Pair("Genre: BL", "en/genres/BL"),
Pair("Genre: Thriller", "en/genres/Thriller"),
Pair("Genre: School life", "en/genres/School_life"),
Pair("Genre: Comedy", "en/genres/Comedy"),
Pair("Genre: GL", "en/genres/GL"),
Pair("Genre: Action", "en/genres/Action"),
Pair("Genre: Sci-fi", "en/genres/Sci-fi"),
Pair("Genre: Horror", "en/genres/Horror"),
Pair("Genre: Fantasy", "en/genres/Fantasy"),
Pair("Genre: Romance", "en/genres/Romance"),
)
}

View File

@ -0,0 +1,9 @@
ext {
extName = 'ToomicsFree.info'
extClass = '.ToomicsFreeInfo'
themePkg = 'hotcomics'
overrideVersionCode = 0
isNsfw = true
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -0,0 +1,29 @@
package eu.kanade.tachiyomi.extension.en.toomicsfreeinfo
import eu.kanade.tachiyomi.multisrc.hotcomics.HotComics
class ToomicsFreeInfo : HotComics(
"ToomicsFree.info",
"en",
"https://toomicsfree.info",
) {
override val browseList = listOf(
Pair("Home", "en"),
Pair("Weekly", "en/weekly"),
Pair("New", "en/new"),
Pair("Genre: All", "en/genres"),
Pair("Genre: Sports", "en/genres/Sports"),
Pair("Genre: Historical", "en/genres/Historical"),
Pair("Genre: Drama", "en/genres/Drama"),
Pair("Genre: BL", "en/genres/BL"),
Pair("Genre: Thriller", "en/genres/Thriller"),
Pair("Genre: School life", "en/genres/School_life"),
Pair("Genre: Comedy", "en/genres/Comedy"),
Pair("Genre: GL", "en/genres/GL"),
Pair("Genre: Action", "en/genres/Action"),
Pair("Genre: Sci-fi", "en/genres/Sci-fi"),
Pair("Genre: Horror", "en/genres/Horror"),
Pair("Genre: Fantasy", "en/genres/Fantasy"),
Pair("Genre: Romance", "en/genres/Romance"),
)
}