Remove deprecated extensions (Renascans, NetTruyen) (#3523)
|
@ -1,12 +0,0 @@
|
||||||
apply plugin: 'com.android.application'
|
|
||||||
apply plugin: 'kotlin-android'
|
|
||||||
|
|
||||||
ext {
|
|
||||||
appName = 'Tachiyomi: Renaissance Scans'
|
|
||||||
pkgNameSuffix = 'en.renascans'
|
|
||||||
extClass = '.Renascans'
|
|
||||||
extVersionCode = 3
|
|
||||||
libVersion = '1.2'
|
|
||||||
}
|
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
|
Before Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 7.7 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 178 KiB |
|
@ -1,154 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.extension.en.renascans
|
|
||||||
|
|
||||||
import com.github.salomonbrys.kotson.fromJson
|
|
||||||
import com.github.salomonbrys.kotson.get
|
|
||||||
import com.google.gson.Gson
|
|
||||||
import com.google.gson.JsonObject
|
|
||||||
import eu.kanade.tachiyomi.network.GET
|
|
||||||
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.ParsedHttpSource
|
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
import java.util.Locale
|
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
import okhttp3.Request
|
|
||||||
import okhttp3.Response
|
|
||||||
import org.jsoup.nodes.Document
|
|
||||||
import org.jsoup.nodes.Element
|
|
||||||
|
|
||||||
class Renascans : ParsedHttpSource() {
|
|
||||||
|
|
||||||
override val name = "Renascence Scans (Renascans)"
|
|
||||||
|
|
||||||
override val baseUrl = "https://renascans.com"
|
|
||||||
|
|
||||||
override val lang = "en"
|
|
||||||
|
|
||||||
override val supportsLatest = true
|
|
||||||
|
|
||||||
override val client: OkHttpClient = network.cloudflareClient
|
|
||||||
|
|
||||||
override fun popularMangaSelector() = "div.media"
|
|
||||||
|
|
||||||
override fun popularMangaRequest(page: Int): Request {
|
|
||||||
return GET("$baseUrl/manga-list?page=$page")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun latestUpdatesSelector() = "div.events"
|
|
||||||
|
|
||||||
override fun latestUpdatesRequest(page: Int): Request {
|
|
||||||
return GET("$baseUrl/latest-release?page=$page")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun popularMangaFromElement(element: Element): SManga {
|
|
||||||
val manga = SManga.create()
|
|
||||||
element.select("a.chart-title").first().let {
|
|
||||||
manga.setUrlWithoutDomain(it.attr("href"))
|
|
||||||
manga.title = it.text()
|
|
||||||
}
|
|
||||||
manga.thumbnail_url = element.select("img").first().attr("src")
|
|
||||||
return manga
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun latestUpdatesFromElement(element: Element): SManga {
|
|
||||||
val manga = SManga.create()
|
|
||||||
element.select("h3 a").first().let {
|
|
||||||
manga.setUrlWithoutDomain(it.attr("href"))
|
|
||||||
manga.title = it.text()
|
|
||||||
}
|
|
||||||
manga.thumbnail_url = element.select("img").first().attr("src")
|
|
||||||
return manga
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun popularMangaNextPageSelector() = "li a:contains(»)"
|
|
||||||
|
|
||||||
override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector()
|
|
||||||
|
|
||||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
|
||||||
return GET("$baseUrl/search?query=$query")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun searchMangaParse(response: Response): MangasPage {
|
|
||||||
val searchMatches = mutableListOf<SManga>()
|
|
||||||
|
|
||||||
val array = Gson().fromJson<JsonObject>(response.body()!!.string())["suggestions"].asJsonArray
|
|
||||||
for (i in 0 until array.size()) {
|
|
||||||
val manga = SManga.create()
|
|
||||||
manga.title = array[i]["value"].asString
|
|
||||||
manga.url = "/manga/" + array[i]["data"].asString
|
|
||||||
manga.thumbnail_url = "$baseUrl/uploads/manga/" + array[i]["data"].asString + "/cover/cover_250x350"
|
|
||||||
searchMatches.add(manga)
|
|
||||||
}
|
|
||||||
return MangasPage(searchMatches, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun searchMangaSelector() = popularMangaSelector()
|
|
||||||
|
|
||||||
override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element)
|
|
||||||
|
|
||||||
override fun searchMangaNextPageSelector() = "None"
|
|
||||||
|
|
||||||
override fun mangaDetailsParse(document: Document): SManga {
|
|
||||||
val infoElement = document.select("div.row")
|
|
||||||
|
|
||||||
val manga = SManga.create()
|
|
||||||
manga.title = infoElement.select("h2").first().text()
|
|
||||||
manga.author = infoElement.select("dt:contains(author) + dd").text()
|
|
||||||
manga.artist = infoElement.select("dt:contains(artist) + dd").text()
|
|
||||||
manga.genre = infoElement.select("dt:contains(categories) + dd").text()
|
|
||||||
val status = infoElement.select("dt:contains(status) + dd").text()
|
|
||||||
manga.status = parseStatus(status)
|
|
||||||
manga.description = document.select("h5 + p").text()
|
|
||||||
manga.thumbnail_url = document.select("img.img-responsive").attr("src")
|
|
||||||
return manga
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun parseStatus(status: String?) = when {
|
|
||||||
status == null -> SManga.UNKNOWN
|
|
||||||
status.contains("Ongoing") -> SManga.ONGOING
|
|
||||||
status.contains("Completed") -> SManga.COMPLETED
|
|
||||||
else -> SManga.UNKNOWN
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun chapterListSelector() = "li:has([class])"
|
|
||||||
|
|
||||||
override fun chapterFromElement(element: Element): SChapter {
|
|
||||||
val urlElement = element.select("h3 a").first()
|
|
||||||
|
|
||||||
val chapter = SChapter.create()
|
|
||||||
chapter.setUrlWithoutDomain(urlElement.attr("href"))
|
|
||||||
chapter.name = element.select("h3").text()
|
|
||||||
chapter.date_upload = parseChapterDate(element.select("div.date-chapter-title-rtl").text()) ?: 0
|
|
||||||
return chapter
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
val dateFormat by lazy {
|
|
||||||
SimpleDateFormat("dd MMM. yyyy", Locale.US)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun parseChapterDate(string: String): Long? {
|
|
||||||
return dateFormat.parse(string.substringAfter("on ")).time
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun pageListParse(document: Document): List<Page> {
|
|
||||||
val pages = mutableListOf<Page>()
|
|
||||||
|
|
||||||
document.select("div.col-xs-12 img")?.forEach {
|
|
||||||
val page = it.attr("data-src")
|
|
||||||
if (page.isNotEmpty()) {
|
|
||||||
pages.add(Page(pages.size, "", page))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return pages
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used")
|
|
||||||
|
|
||||||
override fun getFilterList() = FilterList()
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
apply plugin: 'com.android.application'
|
|
||||||
apply plugin: 'kotlin-android'
|
|
||||||
|
|
||||||
ext {
|
|
||||||
appName = 'Tachiyomi: NetTruyen'
|
|
||||||
pkgNameSuffix = 'vi.nettruyen'
|
|
||||||
extClass = '.NetTruyen'
|
|
||||||
extVersionCode = 5
|
|
||||||
libVersion = '1.2'
|
|
||||||
}
|
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
|
Before Width: | Height: | Size: 4.5 KiB |
Before Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 6.8 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 81 KiB |
|
@ -1,277 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.extension.vi.nettruyen
|
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
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 java.util.Calendar
|
|
||||||
import javax.crypto.Cipher
|
|
||||||
import javax.crypto.spec.IvParameterSpec
|
|
||||||
import javax.crypto.spec.SecretKeySpec
|
|
||||||
import okhttp3.HttpUrl
|
|
||||||
import okhttp3.Request
|
|
||||||
import org.json.JSONArray
|
|
||||||
import org.jsoup.nodes.Document
|
|
||||||
import org.jsoup.nodes.Element
|
|
||||||
|
|
||||||
class NetTruyen : ParsedHttpSource() {
|
|
||||||
|
|
||||||
override val name = "NetTruyen"
|
|
||||||
|
|
||||||
override val baseUrl = "http://www.nettruyen.com"
|
|
||||||
|
|
||||||
override val lang = "vi"
|
|
||||||
|
|
||||||
override val supportsLatest = true
|
|
||||||
|
|
||||||
override val client = network.cloudflareClient.newBuilder().addInterceptor {
|
|
||||||
// Intercept any image requests and add a referer to them
|
|
||||||
// Enables bandwidth stealing feature
|
|
||||||
val request =
|
|
||||||
if (it.request().url().host().contains("cloud"))
|
|
||||||
it.request().newBuilder().addHeader("Referer", baseUrl).build()
|
|
||||||
else it.request()
|
|
||||||
it.proceed(request)
|
|
||||||
}.build()!!
|
|
||||||
|
|
||||||
override fun popularMangaSelector() = "#ctl00_divCenter div.items div.item"
|
|
||||||
|
|
||||||
override fun latestUpdatesSelector() = popularMangaSelector()
|
|
||||||
|
|
||||||
override fun popularMangaRequest(page: Int): Request {
|
|
||||||
return GET("$baseUrl/tim-truyen?status=-1&sort=11&page=$page", headers)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun latestUpdatesRequest(page: Int): Request {
|
|
||||||
return GET("$baseUrl/tim-truyen?page=$page", headers)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun popularMangaFromElement(element: Element): SManga {
|
|
||||||
val manga = SManga.create()
|
|
||||||
element.select("a").first().let {
|
|
||||||
manga.setUrlWithoutDomain(it.attr("href"))
|
|
||||||
manga.title = it.attr("title").replace("Truyện tranh", "").trim()
|
|
||||||
manga.thumbnail_url = it.select("img").first()?.attr("data-original")
|
|
||||||
}
|
|
||||||
return manga
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun latestUpdatesFromElement(element: Element): SManga {
|
|
||||||
return popularMangaFromElement(element)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun popularMangaNextPageSelector() = "ul a.next-page"
|
|
||||||
|
|
||||||
override fun latestUpdatesNextPageSelector(): String = popularMangaNextPageSelector()
|
|
||||||
|
|
||||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
|
||||||
var url = HttpUrl.parse("$baseUrl/tim-truyen?")!!.newBuilder()
|
|
||||||
(if (filters.isEmpty()) getFilterList() else filters).forEach { filter ->
|
|
||||||
when (filter) {
|
|
||||||
is Genre -> {
|
|
||||||
url = if (filter.state == 0) url else
|
|
||||||
HttpUrl.parse(url.toString()
|
|
||||||
.replace("tim-truyen?",
|
|
||||||
"tim-truyen/${getGenreList().map { it.first }[filter.state]}?"))!!
|
|
||||||
.newBuilder()
|
|
||||||
}
|
|
||||||
is Status -> {
|
|
||||||
url.addQueryParameter("status", if (filter.state == 0) "hajau" else filter.state.toString())
|
|
||||||
url.addQueryParameter("sort", "0")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
url.addQueryParameter("keyword", query)
|
|
||||||
return GET(url.toString(), headers)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun searchMangaSelector() = popularMangaSelector()
|
|
||||||
|
|
||||||
override fun searchMangaFromElement(element: Element): SManga {
|
|
||||||
return popularMangaFromElement(element)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
|
|
||||||
|
|
||||||
override fun mangaDetailsParse(document: Document): SManga {
|
|
||||||
val infoElement = document.select("#item-detail").first()
|
|
||||||
|
|
||||||
val manga = SManga.create()
|
|
||||||
manga.author = infoElement.select(".author a").first()?.text()
|
|
||||||
manga.genre = infoElement.select(".kind a").joinToString { it.text() }
|
|
||||||
manga.description = infoElement.select("#item-detail > div.detail-content > p").text()
|
|
||||||
manga.status = infoElement.select(".status > p.col-xs-8").first()?.text().orEmpty().let { parseStatus(it) }
|
|
||||||
manga.thumbnail_url = infoElement.select("img").attr("src")
|
|
||||||
return manga
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun parseStatus(status: String) = when {
|
|
||||||
status.contains("Đang tiến hành") -> SManga.ONGOING
|
|
||||||
status.contains("Hoàn thành") -> SManga.COMPLETED
|
|
||||||
else -> SManga.UNKNOWN
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun chapterListSelector() = "#nt_listchapter li:not(.heading)"
|
|
||||||
|
|
||||||
override fun chapterFromElement(element: Element): SChapter {
|
|
||||||
val urlElement = element.select("a").first()
|
|
||||||
val chapter = SChapter.create()
|
|
||||||
chapter.setUrlWithoutDomain(urlElement.attr("href"))
|
|
||||||
chapter.name = urlElement.text()
|
|
||||||
chapter.date_upload = element.select(".col-xs-4.text-center").last()?.text()?.let { parseChapterDate(it) }
|
|
||||||
?: 0
|
|
||||||
return chapter
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun parseChapterDate(date: String): Long {
|
|
||||||
val dates: Calendar = Calendar.getInstance()
|
|
||||||
if (date.contains("/")) {
|
|
||||||
return if (date.contains(":")) {
|
|
||||||
// Format eg 17:02 20/04
|
|
||||||
val dateDM = date.split(" ")[1].split("/")
|
|
||||||
dates.set(dates.get(Calendar.YEAR), dateDM[1].toInt() - 1, dateDM[0].toInt())
|
|
||||||
dates.timeInMillis
|
|
||||||
} else {
|
|
||||||
// Format eg 18/11/17
|
|
||||||
val dateDMY = date.split("/")
|
|
||||||
dates.set(2000 + dateDMY[2].toInt(), dateDMY[1].toInt() - 1, dateDMY[0].toInt())
|
|
||||||
dates.timeInMillis
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Format eg 1 ngày trước
|
|
||||||
val dateWords: List<String> = date.split(" ")
|
|
||||||
if (dateWords.size == 3) {
|
|
||||||
val timeAgo = Integer.parseInt(dateWords[0])
|
|
||||||
when {
|
|
||||||
dateWords[1].contains("phút") -> dates.add(Calendar.MINUTE, -timeAgo)
|
|
||||||
dateWords[1].contains("giờ") -> dates.add(Calendar.HOUR_OF_DAY, -timeAgo)
|
|
||||||
dateWords[1].contains("ngày") -> dates.add(Calendar.DAY_OF_YEAR, -timeAgo)
|
|
||||||
dateWords[1].contains("tuần") -> dates.add(Calendar.WEEK_OF_YEAR, -timeAgo)
|
|
||||||
dateWords[1].contains("tháng") -> dates.add(Calendar.MONTH, -timeAgo)
|
|
||||||
dateWords[1].contains("năm") -> dates.add(Calendar.YEAR, -timeAgo)
|
|
||||||
}
|
|
||||||
return dates.timeInMillis
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0L
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun decrypt(strToDecrypt: String, secret: String): String? {
|
|
||||||
try {
|
|
||||||
val k = secret + "x77_x6E_x50_x"
|
|
||||||
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
|
|
||||||
val keyBytes = k.toByteArray()
|
|
||||||
val secretKeySpec = SecretKeySpec(keyBytes, "AES")
|
|
||||||
val ivParameterSpec = IvParameterSpec(keyBytes)
|
|
||||||
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec)
|
|
||||||
return String(cipher.doFinal(android.util.Base64.decode(strToDecrypt, android.util.Base64.DEFAULT)))
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e("Decrypt", "Has error $e")
|
|
||||||
}
|
|
||||||
return "https://www.upsieutoc.com/images/2019/09/20/1c1b688884689165b.png"
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getEncryptedPage(document: Document): List<Page> {
|
|
||||||
val pages = mutableListOf<Page>()
|
|
||||||
val scriptTag = document.select("#ctl00_divCenter > div > .container script").html()
|
|
||||||
val keyIndex = scriptTag.indexOf("var k=") + 5
|
|
||||||
val listIndex = scriptTag.indexOf("var l=") + 5
|
|
||||||
val key = scriptTag.substring(keyIndex + 2, scriptTag.indexOf('"', keyIndex + 2))
|
|
||||||
val list = scriptTag.substring(listIndex + 1, scriptTag.indexOf(";", listIndex))
|
|
||||||
val jsonList = JSONArray(list)
|
|
||||||
|
|
||||||
for (i in 0 until jsonList.length()) {
|
|
||||||
val imageUrl = decrypt(jsonList.getString(i), key)
|
|
||||||
val imageUri = if (imageUrl!!.startsWith("//")) "http:$imageUrl" else imageUrl
|
|
||||||
pages.add(Page(pages.size, "", imageUri))
|
|
||||||
}
|
|
||||||
return pages
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun pageListParse(document: Document): List<Page> {
|
|
||||||
val pages = mutableListOf<Page>()
|
|
||||||
document.select(".page-chapter img").forEach {
|
|
||||||
val imageUrl = it.attr("data-original")
|
|
||||||
if (imageUrl.contains("1c1b688884689165b.png")) {
|
|
||||||
return getEncryptedPage(document)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
document.select(".page-chapter img").forEach {
|
|
||||||
val imageUrl = it.attr("data-original")
|
|
||||||
pages.add(Page(pages.size, "", if (imageUrl.startsWith("//")) "http:$imageUrl" else imageUrl))
|
|
||||||
}
|
|
||||||
return pages
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun imageUrlParse(document: Document) = ""
|
|
||||||
|
|
||||||
private fun getStatusList() = arrayOf("Tất cả", "Đang tiến hành", "Đã hoàn thành", "Tạm ngừng")
|
|
||||||
|
|
||||||
private class Status(status: Array<String>) : Filter.Select<String>("Status", status)
|
|
||||||
private class Genre(genreList: Array<String>) : Filter.Select<String>("Thể loại", genreList)
|
|
||||||
|
|
||||||
override fun getFilterList() = FilterList(
|
|
||||||
Status(getStatusList()),
|
|
||||||
Genre(getGenreList().map { it.second }.toTypedArray())
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun getGenreList() = arrayOf(
|
|
||||||
"tim-truyen" 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"
|
|
||||||
)
|
|
||||||
}
|
|