new multisrc theme: MyMangaCMS (#12702)

* Initial MyMangaCMS commit

* Finish up the new template

* Add URL intent filter

* more lenient rate limit since the website loads like a ton of shit

* Delete scaffold.py
This commit is contained in:
beerpsi 2022-07-24 20:36:59 +07:00 committed by GitHub
parent 08a5a14915
commit 5e6acd100c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 683 additions and 139 deletions

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="eu.kanade.tachiyomi.extension">
<application>
<activity
android:name="eu.kanade.tachiyomi.multisrc.mymangacms.MyMangaCMSUrlActivity"
android:excludeFromRecents="true"
android:exported="true"
android:theme="@android:style/Theme.NoDisplay">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="${SOURCEHOST}"
android:pathPattern="/..*"
android:scheme="${SOURCESCHEME}" />
</intent-filter>
</activity>
</application>
</manifest>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

View File

@ -0,0 +1,121 @@
package eu.kanade.tachiyomi.extension.vi.lkdtt
import eu.kanade.tachiyomi.multisrc.mymangacms.MyMangaCMS
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.TimeZone
class LKDTT : MyMangaCMS("LKDTT", "https://lkdtt.com", "vi") {
override val dateFormatter = SimpleDateFormat("dd/MM/yy", Locale.US).apply {
timeZone = TimeZone.getTimeZone(super.timeZone)
}
override fun dateUpdatedParser(date: String): Long =
runCatching { super.dateUpdatedParser(date.split(" - ")[1]) }.getOrNull() ?: 0L
override fun getGenreList() = listOf(
Genre("Học đường", 1),
Genre("Hài hước", 2),
Genre("Cổ Đại", 3),
Genre("Hiện đại", 4),
Genre("Kinh dị", 5),
Genre("Tổng tài", 6),
Genre("Xuyên không", 7),
Genre("Manhua", 8),
Genre("Manhwa", 9),
Genre("Mystery", 10),
Genre("One shot", 11),
Genre("Smut", 12),
Genre("Webtoon", 13),
Genre("Yaoi", 14),
Genre("Yuri", 15),
Genre("Trinh Thám", 16),
Genre("Tình Cảm", 17),
Genre("Drama", 18),
Genre("Comedy", 19),
Genre("Fantasy", 20),
Genre("Novel", 21),
Genre("Action", 22),
Genre("Manga", 23),
Genre("Đam Mỹ", 24),
Genre("Trọng Sinh", 25),
Genre("Ngôn Tình", 26),
Genre("Phiêu Lưu", 27),
Genre("Boy Love", 28),
Genre("giới giải trí", 29),
Genre("đô thị", 30),
Genre("Romance", 31),
Genre("Đô Thị", 32),
Genre("Shoujo", 33),
Genre("Historical", 34),
Genre("Slice of life", 35),
Genre("Mature", 36),
Genre("GL", 37),
Genre("Adult", 38),
Genre("Huyền huyễn", 39),
Genre("Baby", 40),
Genre("Tragedy", 41),
Genre("Truyện Màu", 42),
Genre("School Life", 43),
Genre("Josei", 44),
Genre("Oneshot", 45),
Genre("Gender Bender", 46),
Genre("Nữ cường", 47),
Genre("Harem", 48),
Genre("Reverse Harem", 49),
Genre("Isekai", 50),
Genre("Adventure", 51),
Genre("Chuyển Sinh", 52),
Genre("Đại Nữ Chủ", 53),
Genre("Shounen", 54),
Genre("Sports", 55),
Genre("Sủng Ngọt", 56),
Genre("Truyện 18+", 57),
Genre("Trung Cổ", 58),
Genre("Ma Thuật", 59),
Genre("Webtoons", 60),
Genre("Xuyên", 61),
Genre("Ngôn", 62),
Genre("Tiểu Bạch Thỏ", 63),
Genre("Sủng", 65),
Genre("Trùng Sinh", 66),
Genre("Ma Cà Rồng", 67),
Genre("Tái Sinh", 68),
Genre("Quân Nhân", 69),
Genre("Showbiz", 70),
Genre("Comic", 71),
Genre("Phép Thuật", 72),
Genre("Psychological", 73),
Genre("Supernatural", 74),
Genre("Lãng Mạn", 75),
Genre("Gender", 76),
Genre("Bender", 77),
Genre("Vườn Trường", 78),
Genre("Magic", 79),
Genre("Nhân Thú", 80),
Genre("Soft Yaoi", 81),
Genre("Hôn Nhân Hợp Đồng", 82),
Genre("Cưới Trước Yêu Sau", 83),
Genre("Bi Kịch", 84),
Genre("Horror", 85),
Genre("Reincarnation", 86),
Genre("Hồi Sinh", 87),
Genre("Hoàng Gia", 88),
Genre("Giả Tưởng", 89),
Genre("Xuyên Sách", 90),
Genre("Hài", 91),
Genre("Ngọt", 92),
Genre("Nam Cường", 93),
Genre("Chủ Nam", 94),
Genre("Minh Tinh", 95),
Genre("Cổ Trang", 96),
Genre("Xuyên Game", 97),
Genre("Villainess", 98),
Genre("Cung Đấu", 99),
Genre("Hành Động", 100),
Genre("Truyện Tranh", 101),
Genre("Adaptation", 102),
Genre("Magi", 103),
Genre("Âu Cổ", 104),
)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 KiB

View File

@ -0,0 +1,68 @@
package eu.kanade.tachiyomi.extension.vi.phemanga
import eu.kanade.tachiyomi.multisrc.mymangacms.MyMangaCMS
class PheManga : MyMangaCMS("Phê Manga", "https://phemanga.net", "vi") {
override fun dateUpdatedParser(date: String): Long =
runCatching { super.dateUpdatedParser(date.split(" - ")[1]) }.getOrNull() ?: 0L
override fun getGenreList() = listOf(
Genre("16+", 1),
Genre("18+", 2),
Genre("Action", 3),
Genre("Adult", 4),
Genre("Adventure", 5),
Genre("Anime", 6),
Genre("Comedy", 7),
Genre("Comic", 8),
Genre("Doujinshi", 9),
Genre("Drama", 10),
Genre("Ecchi", 11),
Genre("Fantasy", 13),
Genre("Full màu", 14),
Genre("Game", 15),
Genre("Gender Bender", 16),
Genre("Harem", 17),
Genre("Historical", 18),
Genre("Horror", 19),
Genre("Isekai/Dị giới/Trọng sinh", 20),
Genre("Josei", 21),
Genre("Live action", 22),
Genre("Magic", 23),
Genre("Manga", 24),
Genre("Manhua", 25),
Genre("Manhwa", 26),
Genre("Martial Arts", 27),
Genre("Mature", 28),
Genre("Mecha", 29),
Genre("Mystery", 30),
Genre("Nấu Ăn", 31),
Genre("Ngôn Tình", 32),
Genre("NTR", 33),
Genre("One shot", 34),
Genre("Psychological", 35),
Genre("Romance", 36),
Genre("School Life", 37),
Genre("Sci-fi", 38),
Genre("Seinen", 39),
Genre("Shoujo", 40),
Genre("Shoujo Ai", 41),
Genre("Shounen", 42),
Genre("Shounen Ai", 43),
Genre("Slice of life", 44),
Genre("Smut", 45),
Genre("Soft Yaoi", 46),
Genre("Soft Yuri", 47),
Genre("Sports", 48),
Genre("Supernatural", 49),
Genre("Tạp chí truyện tranh", 50),
Genre("Tragedy", 51),
Genre("Trap (Crossdressing)", 52),
Genre("Trinh Thám", 53),
Genre("Truyện scan", 54),
Genre("Tu chân - tu tiên", 55),
Genre("VnComic", 56),
Genre("Webtoon", 57),
Genre("Yuri", 58),
)
}

View File

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

View File

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

@ -0,0 +1,68 @@
package eu.kanade.tachiyomi.extension.vi.truyentranhlh
import eu.kanade.tachiyomi.multisrc.mymangacms.MyMangaCMS
class TruyenTranhLH : MyMangaCMS("TruyenTranhLH", "https://truyentranhlh.net", "vi") {
override val id: Long = 7969606392351831672
override fun getGenreList() = listOf(
Genre("Action", 1),
Genre("Adult", 2),
Genre("Adventure", 3),
Genre("Anime", 4),
Genre("Chuyển Sinh", 5),
Genre("Cổ Đại", 6),
Genre("Comedy", 7),
Genre("Comic", 8),
Genre("Demons", 9),
Genre("Detective", 10),
Genre("Doujinshi", 11),
Genre("Drama", 12),
Genre("Đam Mỹ", 13),
Genre("Ecchi", 14),
Genre("Fantasy", 15),
Genre("Gender Bender", 16),
Genre("Harem", 17),
Genre("Historical", 18),
Genre("Horror", 19),
Genre("Huyền Huyễn", 20),
Genre("Isekai", 21),
Genre("Josei", 22),
Genre("Mafia", 23),
Genre("Magic", 24),
Genre("Manhua", 25),
Genre("Manhwa", 26),
Genre("Martial Arts", 27),
Genre("Mature", 28),
Genre("Military", 29),
Genre("Mystery", 30),
Genre("Ngôn Tình", 31),
Genre("One shot", 32),
Genre("Psychological", 33),
Genre("Romance", 34),
Genre("School Life", 35),
Genre("Sci-fi", 36),
Genre("Seinen", 37),
Genre("Shoujo", 38),
Genre("Shoujo Ai", 39),
Genre("Shounen", 40),
Genre("Shounen Ai", 41),
Genre("Slice of life", 42),
Genre("Smut", 43),
Genre("Sports", 44),
Genre("Supernatural", 45),
Genre("Tragedy", 46),
Genre("Trọng Sinh", 47),
Genre("Truyện Màu", 48),
Genre("Webtoon", 49),
Genre("Xuyên Không", 50),
Genre("Yaoi", 51),
Genre("Yuri", 52),
Genre("Mecha", 53),
Genre("Cooking", 54),
Genre("Trùng Sinh", 55),
Genre("Gourmet", 56),
Genre("Dark Fantasy", 57),
)
}

View File

@ -0,0 +1,334 @@
package eu.kanade.tachiyomi.multisrc.mymangacms
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import eu.kanade.tachiyomi.source.model.Filter
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 eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import org.jsoup.select.Evaluator
import rx.Observable
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.TimeZone
import java.util.concurrent.TimeUnit
abstract class MyMangaCMS(
override val name: String,
override val baseUrl: String,
override val lang: String
) : ParsedHttpSource() {
override val supportsLatest = true
override val client = network.cloudflareClient.newBuilder().apply {
rateLimit(3, 1)
connectTimeout(1, TimeUnit.MINUTES)
readTimeout(1, TimeUnit.MINUTES)
writeTimeout(1, TimeUnit.MINUTES)
}.build()
override fun headersBuilder(): Headers.Builder = Headers.Builder().apply {
add("Referer", "$baseUrl/")
add(
"User-Agent",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:101.0) Gecko/20100101 Firefox/101.0"
)
}
//region Source settings
open val timeZone = "Asia/Ho_Chi_Minh"
open val dateFormatter = SimpleDateFormat("dd/MM/yyyy", Locale.US).apply {
timeZone = TimeZone.getTimeZone(this@MyMangaCMS.timeZone)
}
open fun dateUpdatedParser(date: String): Long =
runCatching { dateFormatter.parse(date)?.time }.getOrNull() ?: 0L
private val floatingNumberRegex = Regex("""([+-]?(?:[0-9]*[.])?[0-9]+)""")
/**
* Regex for extracting URL from CSS `background-image: url()` property.
*
* - `url\(` matches the opening `url(`
* - `['"]?` checks for the existence (or lack thereof) of single/double quotes
* - `(.*?)` captures everything up to but not including the next quote
* - `\)` to match the closing bracket.
*/
private val backgroundImageRegex = Regex("""url\(['"]?(.*?)['"]?\)""")
//endregion
//region Popular
override fun popularMangaRequest(page: Int): Request = GET(
baseUrl.toHttpUrl().newBuilder().apply {
addPathSegment("tim-kiem")
addQueryParameter("sort", "top")
addQueryParameter("page", page.toString())
}.build().toString()
)
override fun popularMangaSelector(): String = "div.thumb-item-flow.col-6.col-md-2"
override fun popularMangaNextPageSelector(): String? =
"div.pagination_wrap a.paging_item:last-of-type:not(.disabled)"
override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply {
setUrlWithoutDomain(element.select("a").first().attr("abs:href"))
title = element.select("div.thumb_attr.series-title a[title]").first().text()
thumbnail_url = element.select("div[data-bg]").first().attr("data-bg")
}
//endregion
//region Latest
override fun latestUpdatesRequest(page: Int): Request = GET(
baseUrl.toHttpUrl().newBuilder().apply {
addPathSegment("tim-kiem")
addQueryParameter("sort", "update")
addQueryParameter("page", page.toString())
}.build().toString()
)
override fun latestUpdatesSelector(): String = popularMangaSelector()
override fun latestUpdatesNextPageSelector(): String? = popularMangaNextPageSelector()
override fun latestUpdatesFromElement(element: Element): SManga =
popularMangaFromElement(element)
//endregion
//region Search
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
return when {
query.startsWith(PREFIX_URL_SEARCH) -> {
fetchMangaDetails(SManga.create().apply {
url = query.removePrefix(PREFIX_URL_SEARCH).trim().replace(baseUrl, "")
})
.map { MangasPage(listOf(it), false) }
}
else -> super.fetchSearchManga(page, query, filters)
}
}
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request =
GET(
baseUrl.toHttpUrl().newBuilder().apply {
val genres = mutableListOf<Int>()
val genresEx = mutableListOf<Int>()
addPathSegment("tim-kiem")
addQueryParameter("page", page.toString())
(if (filters.isEmpty()) getFilterList() else filters).forEach {
when (it) {
is GenreList -> it.state.forEach { genre ->
when (genre.state) {
Filter.TriState.STATE_INCLUDE -> genres.add(genre.id)
Filter.TriState.STATE_EXCLUDE -> genresEx.add(genre.id)
else -> {}
}
}
is Author -> if (it.state.isNotEmpty()) {
addQueryParameter("artist", it.state)
}
is Sort -> addQueryParameter("sort", it.toUriPart())
is Status -> if (it.state != 0) {
addQueryParameter("status", it.state.toString())
}
else -> {}
}
}
if (genresEx.isNotEmpty()) {
addQueryParameter("reject_genres", genresEx.joinToString(","))
}
if (genres.isNotEmpty()) {
addQueryParameter("accept_genres", genres.joinToString(","))
}
if (query.isNotEmpty()) {
addQueryParameter("q", query)
}
}.build().toString()
)
override fun searchMangaSelector(): String = popularMangaSelector()
override fun searchMangaNextPageSelector(): String? = popularMangaNextPageSelector()
override fun searchMangaFromElement(element: Element): SManga =
popularMangaFromElement(element)
//endregion
//region Manga details
override fun mangaDetailsRequest(manga: SManga): Request = GET("$baseUrl${manga.url}")
override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply {
setUrlWithoutDomain(
document.select(".series-name-group a")
.first()
.attr("abs:href")
)
title = document.select(".series-name").first().text().trim()
var alternativeNames: String? = null
document.select(".info-item").forEach {
val value = it.select(".info-value")
when (it.select(".info-name").text().trim()) {
"Tên khác:" -> alternativeNames = value.joinToString(", ") { name ->
name.text().trim()
}
"Tác giả:" -> author = value.joinToString(", ") { auth ->
auth.text().trim()
}
"Tình trạng:" -> status = when (value.first().text().lowercase().trim()) {
"đang tiến hành" -> SManga.ONGOING
"tạm ngưng" -> SManga.ON_HIATUS
"đã hoàn thành" -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
}
}
val descElem = document.select(".summary-content")
description = if (descElem.select("p").any()) {
descElem.select("p").joinToString("\n") {
it.run {
select(Evaluator.Tag("br")).prepend("\\n")
this.text()
.replace("\\n", "\n")
.replace("\n ", "\n")
}
}.trim()
} else {
descElem.text().trim()
}
if (!alternativeNames.isNullOrEmpty()) {
description = "Tên khác: ${alternativeNames}\n\n" + description
}
genre = document.select("a[href*=the-loai] span.badge")
.joinToString(", ") { it.text().trim() }
thumbnail_url = document
.select("div.content.img-in-ratio")
.first()
.attr("style")
.let { backgroundImageRegex.find(it)?.groups?.get(1)?.value }
}
//endregion
//region Chapter list
override fun chapterListRequest(manga: SManga): Request = mangaDetailsRequest(manga)
override fun chapterListSelector(): String = "ul.list-chapters > a"
override fun chapterFromElement(element: Element): SChapter = throw Exception("Not used")
private fun chapterFromElement(element: Element, scanlator: String?): SChapter =
SChapter.create().apply {
setUrlWithoutDomain(element.attr("abs:href"))
name = element.select("div.chapter-name").first().text()
date_upload = dateUpdatedParser(
element.select("div.chapter-time").first().text()
)
val match = floatingNumberRegex.find(name)
chapter_number = if (name.lowercase().startsWith("vol")) {
match?.groups?.get(2)
} else {
match?.groups?.get(1)
}?.value?.toFloat() ?: -1f
this.scanlator = scanlator
}
override fun chapterListParse(response: Response): List<SChapter> {
val document = response.asJsoup()
val originalScanlator = document.select("div.fantrans-value a")
val scanlator: String? = if (originalScanlator.isEmpty() ||
originalScanlator.first().text().trim().lowercase() == "đang cập nhật") {
null
} else {
originalScanlator.first().text().trim()
}
return document.select(chapterListSelector()).map { chapterFromElement(it, scanlator) }
}
//endregion
//region Pages
override fun pageListRequest(chapter: SChapter): Request = GET("$baseUrl${chapter.url}")
override fun pageListParse(document: Document): List<Page> =
document
.select("div#chapter-content img")
.filterNot { it.attr("abs:data-src").isNullOrEmpty() }
.mapIndexed { index, elem -> Page(index, "", elem.attr("abs:data-src")) }
override fun imageUrlParse(document: Document): String = throw Exception("Not used")
//endregion
//region Filters
open class UriPartFilter(
displayName: String,
private val vals: Array<Pair<String, String>>,
state: Int = 0,
) : Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray(), state) {
fun toUriPart() = vals[state].second
}
private class Status : Filter.Select<String>(
"Tình trạng",
arrayOf(
"Tất cả",
"Đang tiến hành",
"Tạm ngưng",
"Hoàn thành"
)
)
private class Sort : UriPartFilter(
"Sắp xếp",
arrayOf(
Pair("A-Z", "az"),
Pair("Z-A", "za"),
Pair("Mới cập nhật", "update"),
Pair("Truyện mới", "new"),
Pair("Xem nhiều", "top"),
Pair("Được thích nhiều", "like"),
),
4
)
open class Genre(name: String, val id: Int) : Filter.TriState(name)
private class Author : Filter.Text("Tác giả")
private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Thể loại", genres)
override fun getFilterList(): FilterList = FilterList(
Author(),
Status(),
Sort(),
GenreList(getGenreList()),
)
// To populate this list:
// console.log([...document.querySelectorAll("div.search-gerne_item")].map(elem => `Genre("${elem.textContent.trim()}", ${elem.querySelector("label").getAttribute("data-genre-id")}),`).join("\n"))
abstract fun getGenreList(): List<Genre>
//endregion
companion object {
const val PREFIX_URL_SEARCH = "url:"
}
}

View File

@ -0,0 +1,38 @@
package eu.kanade.tachiyomi.multisrc.mymangacms
import generator.ThemeSourceData.SingleLang
import generator.ThemeSourceGenerator
class MyMangaCMSGenerator : ThemeSourceGenerator {
override val themePkg = "mymangacms"
override val themeClass = "MyMangaCMS"
override val baseVersionCode: Int = 1
override val sources = listOf(
SingleLang(
"TruyenTranhLH",
"https://truyentranhlh.net",
"vi",
overrideVersionCode = 9
),
SingleLang(
"Phê Manga",
"https://phemanga.net",
"vi",
true,
"PheManga",
"phemanga",
),
SingleLang("LKDTT", "https://lkdtt.com", "vi", true)
)
companion object {
@JvmStatic
fun main(args: Array<String>) {
MyMangaCMSGenerator().createAll()
}
}
}

View File

@ -0,0 +1,31 @@
package eu.kanade.tachiyomi.multisrc.mymangacms
import android.app.Activity
import android.content.ActivityNotFoundException
import android.content.Intent
import android.os.Bundle
import android.util.Log
import kotlin.system.exitProcess
class MyMangaCMSUrlActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val pathSegments = intent?.data?.pathSegments
if (pathSegments != null && pathSegments.size > 1) {
try {
startActivity(Intent().apply {
action = "eu.kanade.tachiyomi.SEARCH"
putExtra("query", "${MyMangaCMS.PREFIX_URL_SEARCH}${intent?.data?.path}")
putExtra("filter", packageName)
})
} catch (e: ActivityNotFoundException) {
Log.e("MyMangaCMSUrlActivity", e.toString())
}
} else {
Log.e("MyMangaCMSUrlActivity", "Could not parse URI from intent $intent")
}
finish()
exitProcess(0)
}
}

View File

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

View File

@ -1,11 +0,0 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
ext {
extName = 'TruyenTranhLH'
pkgNameSuffix = 'vi.truyentranhlh'
extClass = '.TruyenTranhLH'
extVersionCode = 9
}
apply from: "$rootDir/common.gradle"

View File

@ -1,126 +0,0 @@
package eu.kanade.tachiyomi.extension.vi.truyentranhlh
import eu.kanade.tachiyomi.network.GET
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.OkHttpClient
import okhttp3.Request
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.concurrent.TimeUnit
class TruyenTranhLH : ParsedHttpSource() {
override val name = "TruyenTranhLH"
override val baseUrl = "https://truyentranhlh.net"
override val lang = "vi"
override val supportsLatest = true
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.connectTimeout(1, TimeUnit.MINUTES)
.readTimeout(1, TimeUnit.MINUTES)
.writeTimeout(1, TimeUnit.MINUTES)
.build()
override fun headersBuilder(): Headers.Builder = Headers.Builder()
.add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:75.0) Gecko/20100101 Firefox/75.0")
// Popular
override fun popularMangaRequest(page: Int): Request {
return GET("$baseUrl/tim-kiem?sort=top&page=$page", headers)
}
override fun popularMangaSelector() = "div.thumb-item-flow"
override fun popularMangaFromElement(element: Element): SManga {
return SManga.create().apply {
element.select("div.series-title a").let {
title = it.text()
setUrlWithoutDomain(it.attr("href"))
}
thumbnail_url = element.select("div.content").attr("abs:data-bg")
}
}
override fun popularMangaNextPageSelector() = "div.pagination_wrap a.page_num.current + a:not(.disabled)"
// Latest
override fun latestUpdatesRequest(page: Int): Request {
return GET("$baseUrl/tim-kiem?sort=update&page=$page", headers)
}
override fun latestUpdatesSelector() = popularMangaSelector()
override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element)
override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector()
// Search
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
return GET("$baseUrl/tim-kiem?q=$query&sort=update&page=$page", headers)
}
override fun searchMangaSelector() = popularMangaSelector()
override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element)
override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
// Details
override fun mangaDetailsParse(document: Document): SManga {
val infoElement = document.select("div.top-part")
return SManga.create().apply {
genre = infoElement.select("span.info-name:contains(Thể loại) + span a").joinToString { it.text() }
author = infoElement.select("span.info-name:contains(Tác giả) + span").text()
status = infoElement.select("span.info-name:contains(Tình trạng) + span").text().toStatus()
thumbnail_url = infoElement.select("div.content").attr("style")
.let { Regex("""url\("(.*)"\)""").find(it)?.groups?.get(1)?.value }
description = document.select("div.summary-content").text()
}
}
private fun String?.toStatus() = when {
this == null -> SManga.UNKNOWN
this.contains("Đang tiến hành", ignoreCase = true) -> SManga.ONGOING
this.contains("Đã hoàn thành", ignoreCase = true) -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
// Chapters
override fun chapterListSelector(): String = "ul.list-chapters a"
override fun chapterFromElement(element: Element): SChapter {
return SChapter.create().apply {
setUrlWithoutDomain(element.attr("href"))
name = element.select("div.chapter-name").text()
date_upload = element.select("div.chapter-time").firstOrNull()?.text()
?.let { SimpleDateFormat("dd/MM/yyyy", Locale.getDefault()).parse(it)?.time ?: 0L } ?: 0
}
}
// Pages
override fun pageListParse(document: Document): List<Page> {
return document.select("div#chapter-content img")
.filterNot { imgEl -> imgEl.attr("abs:data-src").isNullOrEmpty() }
.mapIndexed { i, img ->
Page(i, "", img.attr("abs:data-src"))
}
}
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used")
}