Add TruyenGG (#5687)
* Add TruyenGG * Update src/vi/truyengg/src/eu/kanade/tachiyomi/extension/vi/truyengg/TruyenGG.kt Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com> * Clean code --------- Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>
This commit is contained in:
parent
ebf18fcd96
commit
df88c6535c
|
@ -0,0 +1,7 @@
|
|||
ext {
|
||||
extName = 'TruyenGG'
|
||||
extClass = '.TruyenGG'
|
||||
extVersionCode = 1
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
Binary file not shown.
After Width: | Height: | Size: 5.3 KiB |
Binary file not shown.
After Width: | Height: | Size: 2.8 KiB |
Binary file not shown.
After Width: | Height: | Size: 8.1 KiB |
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
Binary file not shown.
After Width: | Height: | Size: 26 KiB |
|
@ -0,0 +1,300 @@
|
|||
package eu.kanade.tachiyomi.extension.vi.truyengg
|
||||
|
||||
import android.app.Application
|
||||
import android.content.SharedPreferences
|
||||
import android.widget.Toast
|
||||
import androidx.preference.EditTextPreference
|
||||
import androidx.preference.PreferenceScreen
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||
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.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
||||
class TruyenGG : ParsedHttpSource(), ConfigurableSource {
|
||||
|
||||
override val name = "TruyenGG"
|
||||
|
||||
override val lang = "vi"
|
||||
|
||||
private val defaultBaseUrl = "https://truyengg.com"
|
||||
|
||||
override val supportsLatest = true
|
||||
|
||||
override val baseUrl by lazy { getPrefBaseUrl() }
|
||||
|
||||
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
|
||||
.rateLimit(1)
|
||||
.build()
|
||||
|
||||
override fun headersBuilder(): Headers.Builder =
|
||||
super.headersBuilder().add("Referer", "$baseUrl/")
|
||||
|
||||
private val dateFormat = SimpleDateFormat("dd/MM/yyyy", Locale.US)
|
||||
|
||||
override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/truyen-moi-cap-nhat/trang-$page.html", headers)
|
||||
|
||||
override fun latestUpdatesFromElement(element: Element): SManga = SManga.create().apply {
|
||||
setUrlWithoutDomain(element.selectFirst("a.book_name")!!.attr("href"))
|
||||
title = element.select("a.book_name").text()
|
||||
thumbnail_url = element.selectFirst(".image-cover img")!!.attr("data-src")
|
||||
}
|
||||
|
||||
override fun latestUpdatesSelector() = ".list_item_home .item_home"
|
||||
|
||||
override fun latestUpdatesNextPageSelector() = ".pagination a.active + a"
|
||||
|
||||
override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/top-binh-chon/trang-$page.html", headers)
|
||||
|
||||
override fun popularMangaFromElement(element: Element) = latestUpdatesFromElement(element)
|
||||
|
||||
override fun popularMangaNextPageSelector() = latestUpdatesNextPageSelector()
|
||||
|
||||
override fun popularMangaSelector() = latestUpdatesSelector()
|
||||
|
||||
override fun chapterListSelector() = "ul.list_chap > li.item_chap"
|
||||
|
||||
override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply {
|
||||
setUrlWithoutDomain(element.selectFirst("a")!!.attr("href"))
|
||||
name = element.select("a").text()
|
||||
date_upload = parseDate(element.select("span.cl99").text().trim())
|
||||
}
|
||||
|
||||
private fun parseDate(date: String): Long = runCatching {
|
||||
dateFormat.parse(date)?.time
|
||||
}.getOrNull() ?: 0L
|
||||
|
||||
override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply {
|
||||
title = document.select("h1[itemprop=name]").text()
|
||||
author = document.select("p:contains(Tác Giả) + p").joinToString { it.text() }
|
||||
genre = document.select("a.clblue").joinToString { it.text() }
|
||||
description = document.select("div.story-detail-info").text().trim()
|
||||
thumbnail_url = document.selectFirst(".thumbblock img")!!.attr("abs:src")
|
||||
status = when (document.select("p:contains(Trạng Thái) + p").text()) {
|
||||
"Đang Cập Nhật" -> SManga.ONGOING
|
||||
"Hoàn Thành" -> SManga.COMPLETED
|
||||
else -> SManga.UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
override fun pageListParse(document: Document): List<Page> =
|
||||
document.select(".content_detail img")
|
||||
.mapIndexed { idx, it ->
|
||||
Page(idx, imageUrl = it.attr("abs:src"))
|
||||
}
|
||||
|
||||
override fun imageUrlParse(document: Document) = throw UnsupportedOperationException()
|
||||
|
||||
override fun searchMangaFromElement(element: Element) = latestUpdatesFromElement(element)
|
||||
|
||||
override fun searchMangaNextPageSelector() = latestUpdatesNextPageSelector()
|
||||
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||
val url = if (query.isNotBlank()) {
|
||||
"$baseUrl/tim-kiem/trang-$page.html".toHttpUrl().newBuilder()
|
||||
.addQueryParameter("q", query)
|
||||
.build()
|
||||
.toString()
|
||||
} else {
|
||||
val builder = "$baseUrl/tim-kiem-nang-cao/trang-$page.html".toHttpUrl().newBuilder()
|
||||
(if (filters.isEmpty()) getFilterList() else filters).filterIsInstance<UriFilter>()
|
||||
.forEach { it.addToUri(builder) }
|
||||
builder.build().toString()
|
||||
}
|
||||
return GET(url, headers)
|
||||
}
|
||||
|
||||
override fun searchMangaSelector() = latestUpdatesSelector()
|
||||
|
||||
override fun getFilterList(): FilterList = FilterList(
|
||||
Filter.Header("Không dùng chung với tìm kiếm bằng tên"),
|
||||
CountryFilter(),
|
||||
StatusFilter(),
|
||||
ChapterCountFilter(),
|
||||
SortByFilter(),
|
||||
GenreList(getGenreList()),
|
||||
)
|
||||
|
||||
interface UriFilter {
|
||||
fun addToUri(builder: HttpUrl.Builder)
|
||||
}
|
||||
|
||||
open class UriPartFilter(
|
||||
name: String,
|
||||
private val query: String,
|
||||
private val vals: Array<Pair<String, String>>,
|
||||
) : UriFilter, Filter.Select<String>(name, vals.map { it.first }.toTypedArray()) {
|
||||
override fun addToUri(builder: HttpUrl.Builder) {
|
||||
builder.addQueryParameter(query, vals[state].second)
|
||||
}
|
||||
}
|
||||
|
||||
class CountryFilter : UriPartFilter(
|
||||
"Quốc gia",
|
||||
"country",
|
||||
arrayOf(
|
||||
"Tất cả" to "0",
|
||||
"Trung Quốc" to "1",
|
||||
"Việt Nam" to "2",
|
||||
"Hàn Quốc" to "3",
|
||||
"Nhật Bản" to "4",
|
||||
"Mỹ" to "5",
|
||||
),
|
||||
)
|
||||
|
||||
class StatusFilter : UriPartFilter(
|
||||
"Tình trạng",
|
||||
"status",
|
||||
arrayOf(
|
||||
"Tất cả" to "-1",
|
||||
"Đang tiến hành" to "0",
|
||||
"Hoàn thành" to "2",
|
||||
),
|
||||
)
|
||||
|
||||
class ChapterCountFilter : UriPartFilter(
|
||||
"Số lượng chương",
|
||||
"minchapter",
|
||||
arrayOf(
|
||||
"0" to "0",
|
||||
">= 100" to "100",
|
||||
">= 200" to "200",
|
||||
">= 300" to "300",
|
||||
">= 400" to "400",
|
||||
">= 500" to "500",
|
||||
),
|
||||
)
|
||||
|
||||
class SortByFilter : UriFilter, Filter.Sort(
|
||||
"Sắp xếp",
|
||||
arrayOf("Ngày đăng", "Ngày cập nhật", "Lượt xem"),
|
||||
Selection(2, false),
|
||||
) {
|
||||
override fun addToUri(builder: HttpUrl.Builder) {
|
||||
val index = state?.index ?: 2
|
||||
val ascending = if (state?.ascending == true) 1 else 0
|
||||
builder.addQueryParameter("sort", (index * 2 + ascending).toString())
|
||||
}
|
||||
}
|
||||
|
||||
class Genre(name: String, val id: String) : Filter.TriState(name)
|
||||
|
||||
class GenreList(state: List<Genre>) : UriFilter, Filter.Group<Genre>("Thể loại", state) {
|
||||
override fun addToUri(builder: HttpUrl.Builder) {
|
||||
val genres = mutableListOf<String>()
|
||||
val genresEx = mutableListOf<String>()
|
||||
|
||||
state.forEach {
|
||||
when (it.state) {
|
||||
TriState.STATE_INCLUDE -> genres.add(it.id)
|
||||
TriState.STATE_EXCLUDE -> genresEx.add(it.id)
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
builder.addQueryParameter("category", genres.joinToString(","))
|
||||
builder.addQueryParameter("notcategory", genresEx.joinToString(","))
|
||||
}
|
||||
}
|
||||
|
||||
private fun getGenreList() = listOf(
|
||||
Genre("Action", "37"),
|
||||
Genre("Adventure", "38"),
|
||||
Genre("Anime", "39"),
|
||||
Genre("Cổ Đại", "40"),
|
||||
Genre("Comedy", "41"),
|
||||
Genre("Comic", "42"),
|
||||
Genre("Detective", "43"),
|
||||
Genre("Doujinshi", "44"),
|
||||
Genre("Drama", "45"),
|
||||
Genre("Ecchi", "80"),
|
||||
Genre("Fantasy", "46"),
|
||||
Genre("Gender Bender", "47"),
|
||||
Genre("Harem", "78"),
|
||||
Genre("Historical", "48"),
|
||||
Genre("Horror", "49"),
|
||||
Genre("Huyền Huyễn", "50"),
|
||||
Genre("Isekai", "51"),
|
||||
Genre("Josei", "52"),
|
||||
Genre("Magic", "53"),
|
||||
Genre("Manga", "81"),
|
||||
Genre("Manhua", "54"),
|
||||
Genre("Manhwa", "55"),
|
||||
Genre("Martial Arts", "56"),
|
||||
Genre("Mystery", "57"),
|
||||
Genre("Ngôn Tình", "58"),
|
||||
Genre("One shot", "59"),
|
||||
Genre("Psychological", "60"),
|
||||
Genre("Romance", "61"),
|
||||
Genre("School Life", "62"),
|
||||
Genre("Sci-fi", "63"),
|
||||
Genre("Seinen", "64"),
|
||||
Genre("Shoujo", "65"),
|
||||
Genre("Shoujo Ai", "66"),
|
||||
Genre("Shounen", "67"),
|
||||
Genre("Shounen Ai", "68"),
|
||||
Genre("Slice of life", "69"),
|
||||
Genre("Sports", "70"),
|
||||
Genre("Supernatural", "71"),
|
||||
Genre("Tragedy", "72"),
|
||||
Genre("Truyện Màu", "73"),
|
||||
Genre("Webtoon", "74"),
|
||||
Genre("Xuyên Không", "75"),
|
||||
Genre("Yuri", "76"),
|
||||
)
|
||||
|
||||
private val preferences: SharedPreferences =
|
||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||
|
||||
init {
|
||||
preferences.getString(DEFAULT_BASE_URL_PREF, null).let { prefDefaultBaseUrl ->
|
||||
if (prefDefaultBaseUrl != defaultBaseUrl) {
|
||||
preferences.edit()
|
||||
.putString(BASE_URL_PREF, defaultBaseUrl)
|
||||
.putString(DEFAULT_BASE_URL_PREF, defaultBaseUrl)
|
||||
.apply()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||
EditTextPreference(screen.context).apply {
|
||||
key = BASE_URL_PREF
|
||||
title = BASE_URL_PREF_TITLE
|
||||
summary = BASE_URL_PREF_SUMMARY
|
||||
setDefaultValue(defaultBaseUrl)
|
||||
dialogTitle = BASE_URL_PREF_TITLE
|
||||
dialogMessage = "Default: $defaultBaseUrl"
|
||||
|
||||
setOnPreferenceChangeListener { _, _ ->
|
||||
Toast.makeText(screen.context, RESTART_APP, Toast.LENGTH_LONG).show()
|
||||
true
|
||||
}
|
||||
}.let(screen::addPreference)
|
||||
}
|
||||
private fun getPrefBaseUrl(): String = preferences.getString(BASE_URL_PREF, defaultBaseUrl)!!
|
||||
|
||||
companion object {
|
||||
private const val DEFAULT_BASE_URL_PREF = "defaultBaseUrl"
|
||||
private const val RESTART_APP = "Khởi chạy lại ứng dụng để áp dụng thay đổi."
|
||||
private const val BASE_URL_PREF_TITLE = "Ghi đè URL cơ sở"
|
||||
private const val BASE_URL_PREF = "overrideBaseUrl"
|
||||
private const val BASE_URL_PREF_SUMMARY =
|
||||
"Dành cho sử dụng tạm thời, cập nhật tiện ích sẽ xóa cài đặt."
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue