Add HWTManga extension (#9365)

This commit is contained in:
ObserverOfTime 2021-10-04 20:03:14 +03:00 committed by GitHub
parent 1c53f50265
commit 2d7556ba8d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 404 additions and 0 deletions

View File

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

View File

@ -0,0 +1,13 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization'
ext {
extName = 'Hardworking Translations'
pkgNameSuffix = 'en.hwtmanga'
extClass = '.HWTManga'
extVersionCode = 1
containsNsfw = true
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

View File

@ -0,0 +1,132 @@
package eu.kanade.tachiyomi.extension.en.hwtmanga
import eu.kanade.tachiyomi.source.model.Filter
class Tag(name: String, private val id: String) : Filter.CheckBox(name) {
override fun toString() = id
}
private val tags: List<Tag>
get() = listOf(
Tag("Action", "action"),
Tag("Adventure", "adventure"),
Tag("Comedy", "comedy"),
Tag("Cooking", "cooking"),
Tag("Drama", "drama"),
Tag("Fantasy", "fantasy"),
Tag("Horror", "horror"),
Tag("Mystery", "mystery"),
Tag("Martial Arts", "martialarts"),
Tag("Romance", "romance"),
Tag("School Life", "school"),
Tag("Shoujo", "shoujo"),
Tag("Shounen", "shounen"),
Tag("Supernatural", "supernatural"),
Tag("Sci-fi", "sci-fi"),
Tag("Slice of Life", "slice of life"),
Tag("Adult", "adult"),
Tag("Ancient era", "ancient era"),
Tag("Arranged Marriage", "arranged_marriage"),
Tag("Age gap", "age_gap"),
Tag("Betrayal", "betrayal"),
Tag("Clan", "clan"),
Tag("Childhood Friends", "childhood_friends"),
Tag("Couple", "couple"),
Tag("Crime", "crime"),
Tag("Cultivation", "cultivation"),
Tag("Comic", "comic"),
Tag("Delinquent", "delinquent"),
Tag("Doujinshi", "doujinshi"),
Tag("Ecchi", "ecchi"),
Tag("Family", "family"),
Tag("Fetishes", "fetish"),
Tag("Gender Bender", "gender_bender"),
Tag("Gyaru", "gyaru"),
Tag("Harem", "harem"),
Tag("Historical", "historical"),
Tag("Isekai", "isekai"),
Tag("Josei", "josei"),
Tag("Lolicon", "lolicon"),
Tag("Leader or Politician", "leader_politician"),
Tag("Mature", "mature"),
Tag("Magic", "magic"),
Tag("Mangaka", "mangaka"),
Tag("Masochist", "masochist"),
Tag("Monsters", "monsters"),
Tag("Mecha", "mecha"),
Tag("Music", "music"),
Tag("Medical", "medical"),
Tag("Misunderstands", "misunderstands"),
Tag("OneShot", "oneshot"),
Tag("Public figure", "public figure"),
Tag("Psychological", "psychological"),
Tag("Powerful Lead Character", "powerful"),
Tag("Rushed ending", "rushed end"),
Tag("Revenge", "revenge"),
Tag("Reverse Harem", "reverse_harem"),
Tag("Sadist", "sadist"),
Tag("Seinen", "seinen"),
Tag("Shotacon", "shotacon"),
Tag("Secret Crush", "secret_crush"),
Tag("Secret Relationship", "secret_relationship"),
Tag("Smart MC", "smart_mc"),
Tag("Sports", "sports"),
Tag("Smut", "smut"),
Tag("Tragedy", "tragedy"),
Tag("Tomboy", "tomboy"),
Tag("Triangles", "triangles"),
Tag("Unusual Pupils", "unusual_pupils"),
Tag("Vampires", "vampires"),
Tag("Webtoon", "webtoon"),
Tag("Work", "work"),
Tag("Zombies", "zombies"),
Tag("4-Koma", "4koma"),
Tag("Manga", "manga"),
Tag("Manhwa", "manhwa"),
Tag("Manhua", "manhua"),
)
class TagFilter(
values: List<Tag> = tags
) : Filter.Group<Tag>("Tag Match", values) {
override fun toString() =
state.filter { it.state }.joinToString(";").ifEmpty { "all;" }
}
private val states: Array<String>
get() = arrayOf("ALL", "Completed", "Ongoing")
class StateFilter(
values: Array<String> = states
) : Filter.Select<String>("State", values) {
private val ids = arrayOf("all", "complete", "ongoing")
override fun toString() = ids[state]
}
private val orders: Array<String>
get() = arrayOf(
"A~Z",
"Z~A",
"Newest",
"Oldest",
"Most Liked",
"Most Viewed",
"Most Favourite"
)
class OrderFilter(
values: Array<String> = orders
) : Filter.Select<String>("Order By", values) {
private val ids = arrayOf(
"az",
"za",
"newest",
"oldest",
"liked",
"viewed",
"fav"
)
override fun toString() = ids[state]
}

View File

@ -0,0 +1,215 @@
package eu.kanade.tachiyomi.extension.en.hwtmanga
import eu.kanade.tachiyomi.annotations.Nsfw
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.asObservableSuccess
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 kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromJsonElement
import kotlinx.serialization.json.jsonObject
import okhttp3.Cookie
import okhttp3.CookieJar
import okhttp3.FormBody
import okhttp3.HttpUrl
import okhttp3.Request
import okhttp3.Response
import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat
import java.util.Locale
@Nsfw class HWTManga : HttpSource() {
override val name = "Hardworking Translations"
override val baseUrl = "https://www.hwtmanga.com/hwt/"
override val lang = "en"
override val supportsLatest = true
override val client = network.client.newBuilder().cookieJar(
object : CookieJar {
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {}
override fun loadForRequest(url: HttpUrl) =
listOf(
Cookie.Builder()
.domain("www.hwtmanga.com")
.path("/hwt")
.name("PHPSESSID")
.value(sessionID)
.build(),
Cookie.Builder()
.domain("www.hwtmanga.com")
.path("/")
.name("manga_security_id")
.value(postID)
.build()
)
}
).build()
private var postID = ""
private var sessionID = ""
private val json by injectLazy<Json>()
override fun latestUpdatesRequest(page: Int) =
FormBody.Builder().search(order = "newest", pid = page)
override fun latestUpdatesParse(response: Response) =
searchMangaParse(response)
override fun popularMangaRequest(page: Int) =
FormBody.Builder().search(order = "viewed", pid = page)
override fun popularMangaParse(response: Response) =
searchMangaParse(response)
override fun searchMangaRequest(page: Int, query: String, filters: FilterList) =
FormBody.Builder().search(
query = query,
pid = page,
tags = filters.get<TagFilter>("all;"),
state = filters.get<StateFilter>("all"),
order = filters.get<OrderFilter>("az")
)
override fun searchMangaParse(response: Response) =
response.parse<List<HWTQuery>>("query").map {
SManga.create().apply {
title = it.title
thumbnail_url = it.cimage
url = "?page=manga&vid=${it.postID}"
}
}.let { MangasPage(it, false) }
override fun fetchMangaDetails(manga: SManga) =
FormBody.Builder().post("GET_MANGA_INFO") {
add("scom", "0")
add("pageid", "1")
add("pid", manga.id)
}.let(client::newCall).asObservableSuccess().map { res ->
// Session cookie is required to view pages
if (sessionID == "") {
val request = Request.Builder()
.url(baseUrl)
.headers(headers)
.head().build()
client.newCall(request).execute().header("Set-Cookie")?.let {
sessionID = Cookie.parse(request.url, it)?.value ?: ""
}
}
val info = res.parse<HWTMangaInfo>("mangaInfo")
info.tags[0].value = info.mtag.value
manga.title = info.title
manga.thumbnail_url = info.cover
manga.description = info.desc + "\n\n\n" +
info.onames.replace(",", " | ")
manga.genre = info.tags.joinToString { it.value!! }
manga.status = when (info.statue) {
1 -> SManga.ONGOING
else -> SManga.UNKNOWN
}
manga.initialized = true
return@map manga
}!!
override fun chapterListRequest(manga: SManga) =
FormBody.Builder().post("GET_CHAPTER_LIST") {
add("pageid", "1")
add("pid", manga.id)
}
override fun chapterListParse(response: Response) =
response.parse<HWTChapterList>("all_data").mapIndexed { idx, ch ->
SChapter.create().apply {
chapter_number = idx + 1f
url = "?page=watch_manga&cid=${ch.fid}&pid=${ch.pid}"
date_upload = dateFormat.parse(ch.cdate)?.time ?: 0L
name = buildString {
append("Chapter %.0f".format(chapter_number))
if (ch.name != "-") append(" | ${ch.name}")
if (ch.is_locked != "false") append(LOCK)
}
}
}
override fun pageListRequest(chapter: SChapter) =
FormBody.Builder().post("GET_CHA_DATA", "manga_viewer") {
val tokens = chapter.tokens
postID = tokens[5]
add("pageid", "1")
add("cid", tokens[3])
add("pid", postID)
}
override fun pageListParse(response: Response) =
response.parse<List<HWTPage>>("clist")
.mapIndexed { idx, page -> Page(idx, "", page.image) }
override fun getFilterList() =
FilterList(TagFilter(), StateFilter(), OrderFilter())
override fun mangaDetailsParse(response: Response) =
throw UnsupportedOperationException("Not used")
override fun imageUrlParse(response: Response) =
throw UnsupportedOperationException("Not used")
private inline val SManga.id: String
get() = url.substringAfterLast('=')
private inline val SChapter.tokens: List<String>
get() = url.split('&', '=')
private inline val HWTPage.image: String
get() = if (base.startsWith("http")) base else baseUrl + base
private fun FormBody.Builder.post(
subpage: String,
page: String = "mangaData",
block: FormBody.Builder.() -> FormBody.Builder
) = add("page", page).add("subpage", subpage).run {
POST(baseUrl + "callback.php", headers, block().build())
}
private fun FormBody.Builder.search(
query: String = "",
tags: String = "all;",
state: String = "all",
order: String = "az",
pid: Int = 1
) = post("MANGASEARCH") {
add("searchbox", query)
add("byg", tags)
add("bys", state)
add("byo", order)
add("pid", pid.toString())
}
private inline fun <reified T> Response.parse(key: String) =
body!!.string().let { body ->
if ("success" !in body) error(body)
json.decodeFromJsonElement<T>(
json.parseToJsonElement(body).jsonObject[key]!!
)
}
private inline fun <reified T> FilterList.get(default: String) =
find { it is T }?.toString() ?: default
companion object {
private const val LOCK = " \uD83D\uDD12"
private val dateFormat by lazy {
SimpleDateFormat("MMM dd, yyyy", Locale.ROOT)
}
}
}

View File

@ -0,0 +1,42 @@
package eu.kanade.tachiyomi.extension.en.hwtmanga
import kotlinx.serialization.Serializable
@Serializable
data class HWTQuery(
val cimage: String,
val postID: String,
val title: String
)
@Serializable
data class HWTMangaInfo(
val cover: String,
val desc: String,
val mtag: HWTTag,
val onames: String,
val statue: Int,
val postID: Int,
val tags: List<HWTTag>,
val title: String
)
@Serializable
data class HWTTag(var value: String?)
@Serializable
data class HWTChapterList(
private val chapterList: List<HWTChapter>
) : List<HWTChapter> by chapterList
@Serializable
data class HWTChapter(
val fid: String,
val pid: String,
val name: String,
val cdate: String,
val is_locked: String
)
@Serializable
data class HWTPage(val base: String)