diff --git a/src/vi/dualeotruyen/build.gradle b/src/vi/dualeotruyen/build.gradle new file mode 100644 index 000000000..e0dc6ed55 --- /dev/null +++ b/src/vi/dualeotruyen/build.gradle @@ -0,0 +1,8 @@ +ext { + extName = "Dua Leo Truyen" + extClass = ".DuaLeoTruyen" + extVersionCode = 1 + isNsfw = true +} + +apply from: "$rootDir/common.gradle" diff --git a/src/vi/dualeotruyen/res/mipmap-hdpi/ic_launcher.png b/src/vi/dualeotruyen/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..f9ff5ec5f Binary files /dev/null and b/src/vi/dualeotruyen/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/vi/dualeotruyen/res/mipmap-mdpi/ic_launcher.png b/src/vi/dualeotruyen/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..ff06b137a Binary files /dev/null and b/src/vi/dualeotruyen/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/vi/dualeotruyen/res/mipmap-xhdpi/ic_launcher.png b/src/vi/dualeotruyen/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..1ce40870c Binary files /dev/null and b/src/vi/dualeotruyen/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/vi/dualeotruyen/res/mipmap-xxhdpi/ic_launcher.png b/src/vi/dualeotruyen/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..3e1d4d834 Binary files /dev/null and b/src/vi/dualeotruyen/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/vi/dualeotruyen/res/mipmap-xxxhdpi/ic_launcher.png b/src/vi/dualeotruyen/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..7940f3653 Binary files /dev/null and b/src/vi/dualeotruyen/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/vi/dualeotruyen/src/eu/kanade/tachiyomi/extension/vi/dualeotruyen/DuaLeoTruyen.kt b/src/vi/dualeotruyen/src/eu/kanade/tachiyomi/extension/vi/dualeotruyen/DuaLeoTruyen.kt new file mode 100644 index 000000000..b9b7ff0df --- /dev/null +++ b/src/vi/dualeotruyen/src/eu/kanade/tachiyomi/extension/vi/dualeotruyen/DuaLeoTruyen.kt @@ -0,0 +1,194 @@ +package eu.kanade.tachiyomi.extension.vi.dualeotruyen + +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.POST +import eu.kanade.tachiyomi.network.interceptor.rateLimitHost +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.FormBody +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.Request +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import java.util.Calendar + +class DuaLeoTruyen : ParsedHttpSource() { + + override val name = "Dưa Leo Truyện" + + override val baseUrl = "https://dualeotruyenme.com" + + override val lang = "vi" + + override val supportsLatest = false + + override val client = network.cloudflareClient.newBuilder() + .rateLimitHost(baseUrl.toHttpUrl(), 2) + .build() + + override fun headersBuilder() = super.headersBuilder() + .add("Referer", "$baseUrl/") + + override fun popularMangaRequest(page: Int) = GET("$baseUrl/truyen-tranh-moi.html?page=$page", headers) + + override fun popularMangaSelector() = "div.product-grid > div" + + override fun popularMangaFromElement(element: Element) = SManga.create().apply { + setUrlWithoutDomain(element.selectFirst("a")!!.attr("href")) + title = element.selectFirst(".comics-item-title")!!.text() + thumbnail_url = element.selectFirst("img.card-img-top")?.absUrl("src") + } + + override fun popularMangaNextPageSelector() = "ul.pagination li.page-item:contains(Next):not(.disabled)" + + override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException() + + override fun latestUpdatesSelector() = throw UnsupportedOperationException() + + override fun latestUpdatesFromElement(element: Element) = throw UnsupportedOperationException() + + override fun latestUpdatesNextPageSelector() = throw UnsupportedOperationException() + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val url = baseUrl.toHttpUrl().newBuilder().apply { + if (query.isNotEmpty()) { + addPathSegment("tim-kiem") + addQueryParameter("search", query) + } else { + val genreFilter = filters.ifEmpty { getFilterList() } + .filterIsInstance<GenreFilter>() + .firstOrNull() ?: return popularMangaRequest(page) + addPathSegments(genreFilter.genre[genreFilter.state].path) + } + + if (page > 1) { + addQueryParameter("page", page.toString()) + } + }.build() + + return GET(url, headers) + } + + override fun searchMangaSelector() = popularMangaSelector() + + override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element) + + override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() + + override fun mangaDetailsParse(document: Document) = SManga.create().apply { + val statusText = document.selectFirst(".card-body dt:contains(Trạng thái) + dd")?.text() + + title = document.selectFirst(".card-title")!!.text() + description = document.selectFirst(".comics-description .inner")?.text() + genre = document.select(".cate-item").joinToString { it.text() } + status = when (statusText) { + "Đang phát hành" -> SManga.ONGOING + "Đã đủ bộ" -> SManga.COMPLETED + else -> SManga.UNKNOWN + } + thumbnail_url = document.selectFirst("img.img-fluid")?.absUrl("src") + } + + override fun chapterListSelector() = ".list-chapters > .item" + + override fun chapterFromElement(element: Element) = SChapter.create().apply { + element.selectFirst(".episode-title a")!!.let { + setUrlWithoutDomain(it.attr("href")) + name = it.text() + } + date_upload = element.selectFirst(".episode-date span")?.let { parseRelativeDate(it.text()) } ?: 0L + } + + override fun pageListParse(document: Document): List<Page> { + countView(document) + + return document.select("img.chapter-img").mapIndexed { i, it -> + Page(i, imageUrl = it.absUrl("data-original")) + } + } + + override fun imageUrlParse(document: Document) = throw UnsupportedOperationException() + + override fun getFilterList() = FilterList( + Filter.Header("Không dùng được khi tìm kiếm bằng tên truyện"), + GenreFilter(getGenreList()), + ) + + private fun countView(document: Document) { + val chapterId = document.selectFirst("input#chapter_id")!!.`val`() + val comicsId = document.selectFirst("input#comics_id")!!.`val`() + val token = document.selectFirst("meta[name=_token]")!!.attr("content") + val form = FormBody.Builder() + .add("_token", token) + .add("comics_id", comicsId) + .add("chapter_id", chapterId) + .build() + val request = POST("$baseUrl/ajax/increase-view-chapter", headers, form) + + client.newCall(request).execute().close() + } + + private fun parseRelativeDate(date: String): Long { + val dateParts = date.split(" ") + + val calendar = Calendar.getInstance().apply { + val amount = -dateParts[0].toInt() + val field = when (dateParts[1]) { + "giây" -> Calendar.SECOND + "phút" -> Calendar.MINUTE + "giờ" -> Calendar.HOUR_OF_DAY + "ngày" -> Calendar.DAY_OF_MONTH + "tuần" -> Calendar.WEEK_OF_MONTH + "tháng" -> Calendar.MONTH + "năm" -> Calendar.YEAR + else -> Calendar.SECOND + } + + add(field, amount) + } + + return calendar.timeInMillis + } + + private class Genre(val name: String, val path: String) + + private class GenreFilter(val genre: List<Genre>) : Filter.Select<String>("Thể loại", genre.map { it.name }.toTypedArray()) + + // copy([...document.querySelectorAll(".dropdown-menu .dropdown-item")].map((e) => `Genre("${e.textContent.trim()}", "${new URL(e).pathname.replace("/", "")}"),`).join("\n")) + // "Tất cả" and "Truyện full" are custom genres that are lumped in to make my life easier. + private fun getGenreList() = listOf( + Genre("Tất cả", "truyen-tranh-moi.html"), + Genre("Truyện full", "truyen-hoan-thanh.html"), + Genre("18+", "the-loai/18-.html"), + Genre("ABO", "the-loai/abo.html"), + Genre("Bách Hợp", "the-loai/bach-hop.html"), + Genre("BoyLove", "the-loai/boylove.html"), + Genre("Chuyển Sinh", "the-loai/chuyen-sinh.html"), + Genre("Cổ Đại", "the-loai/co-dai.html"), + Genre("Doujinshi", "the-loai/doujinshi.html"), + Genre("Drama", "the-loai/drama.html"), + Genre("Đam Mỹ", "the-loai/dam-my.html"), + Genre("Echi", "the-loai/echi.html"), + Genre("GirlLove", "the-loai/girllove.html"), + Genre("Hài Hước", "the-loai/hai-huoc.html"), + Genre("Hành Động", "the-loai/hanh-dong.html"), + Genre("Harem", "the-loai/harem.html"), + Genre("Hentai", "the-loai/hentai.html"), + Genre("Kịch Tính", "the-loai/kich-tinh.html"), + Genre("Lãng Mạn", "the-loai/lang-man.html"), + Genre("Manga", "the-loai/manga.html"), + Genre("Manhua", "the-loai/manhua.html"), + Genre("Manhwa", "the-loai/manhwa.html"), + Genre("Người Thú", "the-loai/nguoi-thu.html"), + Genre("Oneshot", "the-loai/oneshot.html"), + Genre("Phiêu Lưu", "the-loai/phieu-luu.html"), + Genre("Tình Cảm", "the-loai/tinh-cam.html"), + Genre("Truyện Màu", "the-loai/truyen-mau.html"), + Genre("Yaoi", "the-loai/yaoi.html"), + Genre("Yuri", "the-loai/yuri.html"), + ) +}