diff --git a/lib-multisrc/wpcomics/assets/i18n/messages_en.properties b/lib-multisrc/wpcomics/assets/i18n/messages_en.properties new file mode 100644 index 000000000..9787c3889 --- /dev/null +++ b/lib-multisrc/wpcomics/assets/i18n/messages_en.properties @@ -0,0 +1,7 @@ +STATUS=Status +STATUS_ALL=All +STATUS_ONGOING=Ongoing +STATUS_COMPLETED=Completed +GENRE=Genre +GENRES_RESET=Tap 'Reset' to load genres +OTHER_NAME=Alternate Name diff --git a/lib-multisrc/wpcomics/assets/i18n/messages_ja.properties b/lib-multisrc/wpcomics/assets/i18n/messages_ja.properties new file mode 100644 index 000000000..add01ca37 --- /dev/null +++ b/lib-multisrc/wpcomics/assets/i18n/messages_ja.properties @@ -0,0 +1,5 @@ +STATUS=状態 +STATUS_ALL=全て +STATUS_ONGOING=連載中 +STATUS_COMPLETED=完結済み +GENRE=ジャンル diff --git a/lib-multisrc/wpcomics/assets/i18n/messages_vi.properties b/lib-multisrc/wpcomics/assets/i18n/messages_vi.properties new file mode 100644 index 000000000..72cbffe65 --- /dev/null +++ b/lib-multisrc/wpcomics/assets/i18n/messages_vi.properties @@ -0,0 +1,7 @@ +STATUS=Trạng thái +STATUS_ALL=Tất cả +STATUS_ONGOING=Đang tiến hành +STATUS_COMPLETED=Hoàn thành +GENRE=Thể loại +GENRES_RESET=Ấn 'Reset' để tải danh sách thể loại +OTHER_NAME=Tên khác diff --git a/lib-multisrc/wpcomics/build.gradle.kts b/lib-multisrc/wpcomics/build.gradle.kts index 6e70fd158..b21dd0bb4 100644 --- a/lib-multisrc/wpcomics/build.gradle.kts +++ b/lib-multisrc/wpcomics/build.gradle.kts @@ -2,4 +2,8 @@ plugins { id("lib-multisrc") } -baseVersionCode = 4 +baseVersionCode = 5 + +dependencies { + api(project(":lib:i18n")) +} diff --git a/lib-multisrc/wpcomics/src/eu/kanade/tachiyomi/multisrc/wpcomics/WPComics.kt b/lib-multisrc/wpcomics/src/eu/kanade/tachiyomi/multisrc/wpcomics/WPComics.kt index 2b3c7c688..92962053e 100644 --- a/lib-multisrc/wpcomics/src/eu/kanade/tachiyomi/multisrc/wpcomics/WPComics.kt +++ b/lib-multisrc/wpcomics/src/eu/kanade/tachiyomi/multisrc/wpcomics/WPComics.kt @@ -1,5 +1,6 @@ package eu.kanade.tachiyomi.multisrc.wpcomics +import eu.kanade.tachiyomi.lib.i18n.Intl import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.FilterList @@ -7,7 +8,10 @@ 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 eu.kanade.tachiyomi.util.asJsoup +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.OkHttpClient import okhttp3.Request @@ -20,23 +24,28 @@ 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", + final override val lang: String, + protected val dateFormat: SimpleDateFormat = SimpleDateFormat("HH:mm - dd/MM/yyyy Z", Locale.US), + protected 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) + override fun headersBuilder() = super.headersBuilder() + .add("Referer", "$baseUrl/") - private fun List.doesInclude(thisWord: String): Boolean = this.any { it.contains(thisWord, ignoreCase = true) } + open val intl = Intl( + language = lang, + baseLanguage = "en", + availableLanguages = setOf("en", "vi", "ja"), + classLoader = this::class.java.classLoader!!, + ) + + protected fun List.doesInclude(thisWord: String): Boolean = this.any { it.contains(thisWord, ignoreCase = true) } // Popular - open val popularPath = "hot" override fun popularMangaRequest(page: Int): Request { @@ -58,7 +67,6 @@ abstract class WPComics( 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) } @@ -70,35 +78,27 @@ abstract class WPComics( override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() // Search - protected open val searchPath = "tim-truyen" protected open val queryParam = "keyword" - protected open fun String.replaceSearchPath() = this - 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 = "$baseUrl/$searchPath".toHttpUrl().newBuilder() + val url = "$baseUrl/$searchPath".toHttpUrl().newBuilder() - filterList.forEach { filter -> - when (filter) { - is GenreFilter -> filter.toUriPart()?.let { url.addPathSegment(it) } - is StatusFilter -> filter.toUriPart()?.let { url.addQueryParameter("status", it) } - else -> {} - } + filters.forEach { filter -> + when (filter) { + is GenreFilter -> filter.toUriPart()?.let { url.addPathSegment(it) } + is StatusFilter -> filter.toUriPart()?.let { url.addQueryParameter("status", it) } + else -> {} } - - url.apply { - addQueryParameter(queryParam, query) - addQueryParameter("page", page.toString()) - addQueryParameter("sort", "0") - } - - GET(url.toString().replaceSearchPath(), headers) } + + url.apply { + addQueryParameter(queryParam, query) + addQueryParameter("page", page.toString()) + addQueryParameter("sort", "0") + } + + return GET(url.toString(), headers) } override fun searchMangaSelector() = "div.items div.item" @@ -116,22 +116,23 @@ abstract class WPComics( 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() + val otherName = info.select("h2.other-name").text() + description = info.select("div.detail-content p").text() + + if (otherName.isNotBlank()) "\n\n ${intl["OTHER_NAME"]}: $otherName" else "" 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", "Completed", "Hoàn thành") + val ongoingWords = listOf("Ongoing", "Updating", "Đang tiến hành", "連載中") + val completedWords = listOf("Complete", "Completed", "Hoàn thành", "完結済み") return when { this == null -> SManga.UNKNOWN ongoingWords.doesInclude(this) -> SManga.ONGOING @@ -141,7 +142,6 @@ abstract class WPComics( } // Chapters - override fun chapterListSelector() = "div.list-chapter li.row:not(.heading)" override fun chapterFromElement(element: Element): SChapter { @@ -154,10 +154,10 @@ abstract class WPComics( } } - private val currentYear by lazy { Calendar.getInstance(Locale.US)[1].toString().takeLast(2) } + protected val currentYear by lazy { Calendar.getInstance(Locale.US)[1].toString().takeLast(2) } - protected fun String?.toDate(): Long { - this ?: return 0 + protected open fun String?.toDate(): Long { + this ?: return 0L val secondWords = listOf("second", "giây") val minuteWords = listOf("minute", "phút") @@ -182,10 +182,10 @@ abstract class WPComics( (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 + dateFormat.parse(it)?.time ?: 0L } else { // MangaSum - timestamp sometimes doesn't have year (current year implied) - dateFormat.parse("$it/$currentYear")?.time ?: 0 + dateFormat.parse("$it/$currentYear")?.time ?: 0L } } } @@ -195,9 +195,8 @@ abstract class WPComics( } // Pages - - // sources sometimes have an image element with an empty attr that isn't really an image open fun imageOrNull(element: Element): String? { + // sources sometimes have an image element with an empty attr that isn't really an image fun Element.hasValidAttr(attr: String): Boolean { val regex = Regex("""https?://.*""", RegexOption.IGNORE_CASE) return when { @@ -226,80 +225,74 @@ abstract class WPComics( override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException() // Filters + protected class StatusFilter(name: String, pairs: List>) : UriPartFilter(name, pairs) - protected class StatusFilter(vals: Array>) : UriPartFilter("Status", vals) - protected class GenreFilter(vals: Array>) : UriPartFilter("Genre", vals) + protected class GenreFilter(name: String, pairs: List>) : UriPartFilter(name, pairs) - protected open fun getStatusList(): Array> = 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> = 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 fun getStatusList(): List> = + listOf( + Pair(null, intl["STATUS_ALL"]), + Pair("1", intl["STATUS_ONGOING"]), + Pair("2", intl["STATUS_COMPLETED"]), + ) + + protected var genreList: List> = emptyList() + + private val scope = CoroutineScope(Dispatchers.IO) + + protected fun launchIO(block: () -> Unit) = scope.launch { block() } + + private var fetchGenresAttempts: Int = 0 + + protected fun fetchGenres() { + if (fetchGenresAttempts < 3 && genreList.isEmpty()) { + try { + genreList = + client.newCall(genresRequest()).execute() + .asJsoup() + .let(::parseGenres) + } catch (_: Exception) { + } finally { + fetchGenresAttempts++ + } + } + } + + protected open fun genresRequest() = GET("$baseUrl/$searchPath", headers) + + protected open val genresSelector = ".genres ul.nav li:not(.active) a" + + protected open val genresUrlDelimiter = "/" + + protected open fun parseGenres(document: Document): List> { + val items = document.select(genresSelector) + return buildList(items.size + 1) { + add(Pair(null, intl["STATUS_ALL"])) + items.mapTo(this) { + Pair( + it.attr("href") + .removeSuffix("/") + .substringAfterLast(genresUrlDelimiter), + it.text(), + ) + } + } + } override fun getFilterList(): FilterList { + launchIO { fetchGenres() } return FilterList( - StatusFilter(getStatusList()), - GenreFilter(getGenreList()), + StatusFilter(intl["STATUS"], getStatusList()), + if (genreList.isEmpty()) { + Filter.Header(intl["GENRES_RESET"]) + } else { + GenreFilter(intl["GENRE"], genreList) + }, ) } - protected open class UriPartFilter(displayName: String, val vals: Array>) : - Filter.Select(displayName, vals.map { it.second }.toTypedArray()) { - fun toUriPart() = vals[state].first + protected open class UriPartFilter(displayName: String, private val pairs: List>) : + Filter.Select(displayName, pairs.map { it.second }.toTypedArray()) { + fun toUriPart() = pairs[state].first } } diff --git a/src/en/xoxocomics/src/eu/kanade/tachiyomi/extension/en/xoxocomics/XoxoComics.kt b/src/en/xoxocomics/src/eu/kanade/tachiyomi/extension/en/xoxocomics/XoxoComics.kt index dce0050fc..d193fa479 100644 --- a/src/en/xoxocomics/src/eu/kanade/tachiyomi/extension/en/xoxocomics/XoxoComics.kt +++ b/src/en/xoxocomics/src/eu/kanade/tachiyomi/extension/en/xoxocomics/XoxoComics.kt @@ -15,7 +15,13 @@ import org.jsoup.nodes.Element import java.text.SimpleDateFormat import java.util.Locale -class XoxoComics : WPComics("XOXO Comics", "https://xoxocomic.com", "en", SimpleDateFormat("MM/dd/yyyy", Locale.US), null) { +class XoxoComics : WPComics( + "XOXO Comics", + "https://xoxocomic.com", + "en", + dateFormat = SimpleDateFormat("MM/dd/yyyy", Locale.US), + gmtOffset = null, +) { override val searchPath = "search-comic" override val popularPath = "hot-comic" override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/comic-update?page=$page", headers) @@ -84,72 +90,20 @@ class XoxoComics : WPComics("XOXO Comics", "https://xoxocomic.com", "en", Simple override fun pageListRequest(chapter: SChapter): Request = GET(baseUrl + "${chapter.url}/all") - override fun getStatusList(): Array> = arrayOf( - Pair(null, "All"), - Pair("ongoing", "Ongoing"), - Pair("completed", "Completed"), - ) - override fun getGenreList(): Array> = arrayOf( - null to "All", - "marvel-comic" to "Marvel", - "dc-comics-comic" to "DC Comics", - "dark-horse-comic" to "Dark Horse", - "action-comic" to "Action", - "adventure-comic" to "Adventure", - "anthology-comic" to "Anthology", - "anthropomorphic-comic" to "Anthropomorphic", - "biography-comic" to "Biography", - "children-comic" to "Children", - "comedy-comic" to "Comedy", - "crime-comic" to "Crime", - "drama-comic" to "Drama", - "family-comic" to "Family", - "fantasy-comic" to "Fantasy", - "fighting-comic" to "Fighting", - "graphic-novels-comic" to "Graphic Novels", - "historical-comic" to "Historical", - "horror-comic" to "Horror", - "leading-ladies-comic" to "Leading Ladies", - "lgbtq-comic" to "LGBTQ", - "literature-comic" to "Literature", - "manga-comic" to "Manga", - "martial-arts-comic" to "Martial Arts", - "military-comic" to "Military", - "mini-series-comic" to "Mini-Series", - "movies-tv-comic" to "Movies & TV", - "music-comic" to "Music", - "mystery-comic" to "Mystery", - "mythology-comic" to "Mythology", - "personal-comic" to "Personal", - "political-comic" to "Political", - "post-apocalyptic-comic" to "Post-Apocalyptic", - "psychological-comic" to "Psychological", - "pulp-comic" to "Pulp", - "religious-comic" to "Religious", - "robots-comic" to "Robots", - "romance-comic" to "Romance", - "school-life-comic" to "School Life", - "sci-fi-comic" to "Sci-Fi", - "slice-of-life-comic" to "Slice of Life", - "sport-comic" to "Sport", - "spy-comic" to "Spy", - "superhero-comic" to "Superhero", - "supernatural-comic" to "Supernatural", - "suspense-comic" to "Suspense", - "teen-comic" to "Teen", - "thriller-comic" to "Thriller", - "vampires-comic" to "Vampires", - "video-games-comic" to "Video Games", - "war-comic" to "War", - "western-comic" to "Western", - "zombies-comic" to "Zombies", - ) + override fun genresRequest() = GET("$baseUrl/comic-list", headers) + + override val genresSelector = ".genres h2:contains(Genres) + ul.nav li a" override fun getFilterList(): FilterList { + launchIO { fetchGenres() } return FilterList( Filter.Header("Search query won't use Genre/Status filter"), - StatusFilter(getStatusList()), - GenreFilter(getGenreList()), + StatusFilter("Status", getStatusList()), + if (genreList.isEmpty()) { + Filter.Header("Tap 'Reset' to load genres") + } else { + GenreFilter("Genre", genreList) + }, ) } } diff --git a/src/ja/jmanga/src/eu/kanade/tachiyomi/ja/jmanga/JManga.kt b/src/ja/jmanga/src/eu/kanade/tachiyomi/extension/ja/jmanga/JManga.kt similarity index 75% rename from src/ja/jmanga/src/eu/kanade/tachiyomi/ja/jmanga/JManga.kt rename to src/ja/jmanga/src/eu/kanade/tachiyomi/extension/ja/jmanga/JManga.kt index 592f7012e..31eeb15d7 100644 --- a/src/ja/jmanga/src/eu/kanade/tachiyomi/ja/jmanga/JManga.kt +++ b/src/ja/jmanga/src/eu/kanade/tachiyomi/extension/ja/jmanga/JManga.kt @@ -13,27 +13,36 @@ import java.text.SimpleDateFormat import java.util.Calendar import java.util.Locale -class JManga : WPComics("JManga", "https://jmanga.vip", "ja", SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'", Locale.JAPANESE), null) { +class JManga : WPComics( + "JManga", + "https://jmanga.vip", + "ja", + dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'", Locale.JAPANESE), + gmtOffset = null, +) { override fun popularMangaSelector() = "div.items article.item" - override fun popularMangaNextPageSelector() = "li:nth-last-child(2) a.page-link" + + override fun popularMangaNextPageSelector() = "li.active + li.page-item a.page-link" + 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 = when { - info.select("li.status p.col-xs-8").text().contains("連載中", true) -> SManga.ONGOING - info.select("li.status p.col-xs-8").text().contains("完結済み", true) -> SManga.COMPLETED - else -> SManga.UNKNOWN - } + 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").text() + val otherName = info.select("h2.other-name").text() + description = info.select("div.detail-content").text() + + if (otherName.isNotBlank()) "\n\n ${intl["OTHER_NAME"]}: $otherName" else "" thumbnail_url = imageOrNull(info[0].selectFirst("div.col-image img")!!) } } } + + override val searchPath = "search/manga" + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { val filterList = filters.let { if (it.isEmpty()) getFilterList() else it } - val url = "$baseUrl/search/manga".toHttpUrl().newBuilder() + val url = "$baseUrl/$searchPath".toHttpUrl().newBuilder() filterList.forEach { filter -> when (filter) { @@ -51,6 +60,7 @@ class JManga : WPComics("JManga", "https://jmanga.vip", "ja", SimpleDateFormat(" return GET(url.build(), headers) } + override fun chapterFromElement(element: Element): SChapter { val minuteWords = listOf("minute", "分") val hourWords = listOf("hour", "時間") @@ -111,28 +121,15 @@ class JManga : WPComics("JManga", "https://jmanga.vip", "ja", SimpleDateFormat(" } } } - override fun getStatusList(): Array> { - return arrayOf( + + override fun getStatusList(): List> = + listOf( Pair("-1", "全て"), Pair("0", "完結済み"), Pair("1", "連載中"), ) - } - override fun getGenreList(): Array> { - return arrayOf( - null to "全てのジャンル", - "TL" to "TL", - "BL" to "BL", - " ファンタジー " to " ファンタジー ", - "恋愛" to "恋愛", - "ドラマ" to "ドラマ", - "アクション" to "アクション", - "ホラー・ミステリー" to "ホラー・ミステリー", - "裏社会・アングラ" to "裏社会・アングラ", - "スポーツ" to "スポーツ", - "グルメ" to "グルメ", - "日常" to "日常", - "SF" to "SF", - ) - } + + override val genresSelector = ".genres ul.nav li:not(.active) a" + + override val genresUrlDelimiter = "=" } diff --git a/src/vi/nettruyen/src/eu/kanade/tachiyomi/extension/vi/nettruyen/NetTruyen.kt b/src/vi/nettruyen/src/eu/kanade/tachiyomi/extension/vi/nettruyen/NetTruyen.kt index 0aaa4cbee..90473e491 100644 --- a/src/vi/nettruyen/src/eu/kanade/tachiyomi/extension/vi/nettruyen/NetTruyen.kt +++ b/src/vi/nettruyen/src/eu/kanade/tachiyomi/extension/vi/nettruyen/NetTruyen.kt @@ -6,9 +6,13 @@ import okhttp3.Response import java.text.SimpleDateFormat import java.util.Locale -class NetTruyen : WPComics("NetTruyen", "https://www.nettruyenff.com", "vi", SimpleDateFormat("dd/MM/yy", Locale.getDefault()), null) { - override fun String.replaceSearchPath() = replace("/$searchPath?status=2&", "/truyen-full?") - +class NetTruyen : WPComics( + "NetTruyen", + "https://www.nettruyenff.com", + "vi", + dateFormat = SimpleDateFormat("dd/MM/yy", Locale.getDefault()), + gmtOffset = null, +) { /** * NetTruyen/NhatTruyen redirect back to catalog page if searching query is not found. * That makes both sites always return un-relevant results when searching should return empty. @@ -19,62 +23,4 @@ class NetTruyen : WPComics("NetTruyen", "https://www.nettruyenff.com", "vi", Sim } return super.searchMangaParse(response) } - - override fun getGenreList(): Array> = arrayOf( - null to "Tất cả", - "action-95" to "Action", - "truong-thanh" to "Adult", - "adventure" to "Adventure", - "anime" to "Anime", - "chuyen-sinh-2131" to "Chuyển Sinh", - "comedy-99" to "Comedy", - "comic" to "Comic", - "cooking-101" to "Cooking", - "co-dai-207" to "Cổ Đại", - "doujinshi" to "Doujinshi", - "drama-103" to "Drama", - "dam-my" to "Đam Mỹ", - "ecchi-104" to "Ecchi", - "fantasy-1050" to "Fantasy", - "gender-bender-106" to "Gender Bender", - "harem-107" to "Harem", - "historical" to "Historical", - "horror" to "Horror", - "josei" to "Josei", - "live-action" to "Live action", - "manga-112" to "Manga", - "manhua" to "Manhua", - "manhwa-11400" to "Manhwa", - "martial-arts" to "Martial Arts", - "mature" to "Mature", - "mecha-117" to "Mecha", - "mystery" to "Mystery", - "ngon-tinh" to "Ngôn Tình", - "one-shot" to "One shot", - "psychological" to "Psychological", - "romance-121" to "Romance", - "school-life" to "School Life", - "sci-fi" to "Sci-fi", - "seinen" to "Seinen", - "shoujo" to "Shoujo", - "shoujo-ai-126" to "Shoujo Ai", - "shounen-127" 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", - "tap-chi-truyen-tranh" to "Tạp chí truyện tranh", - "thieu-nhi" to "Thiếu Nhi", - "tragedy-136" to "Tragedy", - "trinh-tham" to "Trinh Thám", - "truyen-scan" to "Truyện scan", - "truyen-mau-214" to "Truyện Màu", - "viet-nam" to "Việt Nam", - "webtoon-140" to "Webtoon", - "xuyen-khong-205" to "Xuyên Không", - "16" to "16+", - ) } diff --git a/src/vi/nettruyenco/build.gradle b/src/vi/nettruyenco/build.gradle new file mode 100644 index 000000000..a1407d941 --- /dev/null +++ b/src/vi/nettruyenco/build.gradle @@ -0,0 +1,9 @@ +ext { + extName = 'NetTruyenCO (unoriginal)' + extClass = '.NetTruyenCO' + themePkg = 'wpcomics' + baseUrl = 'https://nettruyenco.vn' + overrideVersionCode = 0 +} + +apply from: "$rootDir/common.gradle" diff --git a/src/vi/nettruyenco/res/mipmap-hdpi/ic_launcher.png b/src/vi/nettruyenco/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..c4921b862 Binary files /dev/null and b/src/vi/nettruyenco/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/vi/nettruyenco/res/mipmap-mdpi/ic_launcher.png b/src/vi/nettruyenco/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..eabad57a1 Binary files /dev/null and b/src/vi/nettruyenco/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/vi/nettruyenco/res/mipmap-xhdpi/ic_launcher.png b/src/vi/nettruyenco/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..cd103ae21 Binary files /dev/null and b/src/vi/nettruyenco/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/vi/nettruyenco/res/mipmap-xxhdpi/ic_launcher.png b/src/vi/nettruyenco/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..cba7110f5 Binary files /dev/null and b/src/vi/nettruyenco/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/vi/nettruyenco/res/mipmap-xxxhdpi/ic_launcher.png b/src/vi/nettruyenco/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..7bbc8104d Binary files /dev/null and b/src/vi/nettruyenco/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/vi/nettruyenco/src/eu/kanade/tachiyomi/extension/vi/nettruyenco/NetTruyenCO.kt b/src/vi/nettruyenco/src/eu/kanade/tachiyomi/extension/vi/nettruyenco/NetTruyenCO.kt new file mode 100644 index 000000000..57d396724 --- /dev/null +++ b/src/vi/nettruyenco/src/eu/kanade/tachiyomi/extension/vi/nettruyenco/NetTruyenCO.kt @@ -0,0 +1,32 @@ +package eu.kanade.tachiyomi.extension.vi.nettruyenco + +import eu.kanade.tachiyomi.multisrc.wpcomics.WPComics +import eu.kanade.tachiyomi.source.model.SManga +import org.jsoup.nodes.Document +import java.text.SimpleDateFormat +import java.util.Locale + +class NetTruyenCO : WPComics( + "NetTruyenCO (unoriginal)", + "https://nettruyenco.vn", + "vi", + dateFormat = SimpleDateFormat("dd/MM/yy", Locale.getDefault()), + gmtOffset = null, +) { + override val popularPath = "truyen-tranh-hot" + + // 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() } + val otherName = info.select("h2.other-name").text() + description = info.select("div.detail-content div div.nth-child(3)").text() + + if (otherName.isNotBlank()) "\n\n ${intl["OTHER_NAME"]}: $otherName" else "" + thumbnail_url = imageOrNull(info.select("div.col-image img").first()!!) + } + } + } +} diff --git a/src/vi/nettruyenx/build.gradle b/src/vi/nettruyenx/build.gradle new file mode 100644 index 000000000..da410c7b1 --- /dev/null +++ b/src/vi/nettruyenx/build.gradle @@ -0,0 +1,9 @@ +ext { + extName = 'NetTruyenX (unoriginal)' + extClass = '.NetTruyenX' + themePkg = 'wpcomics' + baseUrl = 'https://nettruyenx.com' + overrideVersionCode = 0 +} + +apply from: "$rootDir/common.gradle" diff --git a/src/vi/nettruyenx/res/mipmap-hdpi/ic_launcher.png b/src/vi/nettruyenx/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..c4921b862 Binary files /dev/null and b/src/vi/nettruyenx/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/vi/nettruyenx/res/mipmap-mdpi/ic_launcher.png b/src/vi/nettruyenx/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..eabad57a1 Binary files /dev/null and b/src/vi/nettruyenx/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/vi/nettruyenx/res/mipmap-xhdpi/ic_launcher.png b/src/vi/nettruyenx/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..cd103ae21 Binary files /dev/null and b/src/vi/nettruyenx/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/vi/nettruyenx/res/mipmap-xxhdpi/ic_launcher.png b/src/vi/nettruyenx/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..cba7110f5 Binary files /dev/null and b/src/vi/nettruyenx/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/vi/nettruyenx/res/mipmap-xxxhdpi/ic_launcher.png b/src/vi/nettruyenx/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..7bbc8104d Binary files /dev/null and b/src/vi/nettruyenx/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/vi/nettruyenx/src/eu/kanade/tachiyomi/extension/vi/nettruyenx/NetTruyenX.kt b/src/vi/nettruyenx/src/eu/kanade/tachiyomi/extension/vi/nettruyenx/NetTruyenX.kt new file mode 100644 index 000000000..477c7f9f4 --- /dev/null +++ b/src/vi/nettruyenx/src/eu/kanade/tachiyomi/extension/vi/nettruyenx/NetTruyenX.kt @@ -0,0 +1,32 @@ +package eu.kanade.tachiyomi.extension.vi.nettruyenx + +import eu.kanade.tachiyomi.multisrc.wpcomics.WPComics +import eu.kanade.tachiyomi.source.model.SManga +import org.jsoup.nodes.Document +import java.text.SimpleDateFormat +import java.util.Locale + +class NetTruyenX : WPComics( + "NetTruyenX (unoriginal)", + "https://nettruyenx.com", + "vi", + dateFormat = SimpleDateFormat("dd/MM/yy", Locale.getDefault()), + gmtOffset = null, +) { + override val popularPath = "truyen-tranh-hot" + + // 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() } + val otherName = info.select("h2.other-name").text() + description = info.select("div.detail-content div div:nth-child(4)").text() + + if (otherName.isNotBlank()) "\n\n ${intl["OTHER_NAME"]}: $otherName" else "" + thumbnail_url = imageOrNull(info.select("div.col-image img").first()!!) + } + } + } +} diff --git a/src/vi/nhattruyen/src/eu/kanade/tachiyomi/extension/vi/nhattruyen/NhatTruyen.kt b/src/vi/nhattruyen/src/eu/kanade/tachiyomi/extension/vi/nhattruyen/NhatTruyen.kt index 48499bc5b..089b995c7 100644 --- a/src/vi/nhattruyen/src/eu/kanade/tachiyomi/extension/vi/nhattruyen/NhatTruyen.kt +++ b/src/vi/nhattruyen/src/eu/kanade/tachiyomi/extension/vi/nhattruyen/NhatTruyen.kt @@ -6,7 +6,13 @@ import okhttp3.Response import java.text.SimpleDateFormat import java.util.Locale -class NhatTruyen : WPComics("NhatTruyen", "https://nhattruyenup.com", "vi", SimpleDateFormat("dd/MM/yy", Locale.getDefault()), null) { +class NhatTruyen : WPComics( + "NhatTruyen", + "https://nhattruyenup.com", + "vi", + dateFormat = SimpleDateFormat("dd/MM/yy", Locale.getDefault()), + gmtOffset = null, +) { override val searchPath = "the-loai" /** @@ -19,62 +25,4 @@ class NhatTruyen : WPComics("NhatTruyen", "https://nhattruyenup.com", "vi", Simp } return super.searchMangaParse(response) } - - override fun getGenreList(): Array> = 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-241" to "Manga", - "manhua" to "Manhua", - "manhwa-2431" 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", - "tap-chi-truyen-tranh" to "Tạp chí truyện tranh", - "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", - "viet-nam" to "Việt Nam", - "webtoon" to "Webtoon", - "xuyen-khong" to "Xuyên Không", - "16" to "16+", - ) } diff --git a/src/vi/nhattruyens/build.gradle b/src/vi/nhattruyens/build.gradle new file mode 100644 index 000000000..83a66de5f --- /dev/null +++ b/src/vi/nhattruyens/build.gradle @@ -0,0 +1,9 @@ +ext { + extName = 'NhatTruyenS (unoriginal)' + extClass = '.NhatTruyenS' + themePkg = 'wpcomics' + baseUrl = 'https://nhattruyens.com' + overrideVersionCode = 0 +} + +apply from: "$rootDir/common.gradle" diff --git a/src/vi/nhattruyens/res/mipmap-hdpi/ic_launcher.png b/src/vi/nhattruyens/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..c4921b862 Binary files /dev/null and b/src/vi/nhattruyens/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/vi/nhattruyens/res/mipmap-mdpi/ic_launcher.png b/src/vi/nhattruyens/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..eabad57a1 Binary files /dev/null and b/src/vi/nhattruyens/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/vi/nhattruyens/res/mipmap-xhdpi/ic_launcher.png b/src/vi/nhattruyens/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..cd103ae21 Binary files /dev/null and b/src/vi/nhattruyens/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/vi/nhattruyens/res/mipmap-xxhdpi/ic_launcher.png b/src/vi/nhattruyens/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..cba7110f5 Binary files /dev/null and b/src/vi/nhattruyens/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/vi/nhattruyens/res/mipmap-xxxhdpi/ic_launcher.png b/src/vi/nhattruyens/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..7bbc8104d Binary files /dev/null and b/src/vi/nhattruyens/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/vi/nhattruyens/src/eu/kanade/tachiyomi/extension/vi/nhattruyens/NhatTruyenS.kt b/src/vi/nhattruyens/src/eu/kanade/tachiyomi/extension/vi/nhattruyens/NhatTruyenS.kt new file mode 100644 index 000000000..744c1b668 --- /dev/null +++ b/src/vi/nhattruyens/src/eu/kanade/tachiyomi/extension/vi/nhattruyens/NhatTruyenS.kt @@ -0,0 +1,54 @@ +package eu.kanade.tachiyomi.extension.vi.nhattruyens + +import eu.kanade.tachiyomi.multisrc.wpcomics.WPComics +import eu.kanade.tachiyomi.source.model.MangasPage +import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.util.asJsoup +import okhttp3.Response +import org.jsoup.nodes.Document +import java.text.SimpleDateFormat +import java.util.Locale + +class NhatTruyenS : WPComics( + "NhatTruyenS (unoriginal)", + "https://nhattruyens.com", + "vi", + dateFormat = SimpleDateFormat("dd/MM/yy", Locale.getDefault()), + gmtOffset = null, +) { + override val popularPath = "truyen-hot" + + /** + * Remove fake-manga ads + */ + override fun searchMangaParse(response: Response): MangasPage { + val document = response.asJsoup() + + val mangas = document.select(searchMangaSelector()) + .filter { element -> element.select("figure > div > a[rel='nofollow']").isNullOrEmpty() } + .map { element -> + searchMangaFromElement(element) + } + + val hasNextPage = searchMangaNextPageSelector().let { selector -> + document.select(selector).first() + } != null + + return MangasPage(mangas, hasNextPage) + } + + // 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() } + val otherName = info.select("h2.other-name").text() + description = info.select("div.detail-content div.about:nth-child(3)").text() + + if (otherName.isNotBlank()) "\n\n ${intl["OTHER_NAME"]}: $otherName" else "" + thumbnail_url = imageOrNull(info.select("div.col-image img").first()!!) + } + } + } +}