diff --git a/src/ja/nicomanga/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/fmreader/nicomanga/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from src/ja/nicomanga/res/mipmap-hdpi/ic_launcher.png rename to multisrc/overrides/fmreader/nicomanga/res/mipmap-hdpi/ic_launcher.png diff --git a/src/ja/nicomanga/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/fmreader/nicomanga/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from src/ja/nicomanga/res/mipmap-mdpi/ic_launcher.png rename to multisrc/overrides/fmreader/nicomanga/res/mipmap-mdpi/ic_launcher.png diff --git a/src/ja/nicomanga/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/fmreader/nicomanga/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from src/ja/nicomanga/res/mipmap-xhdpi/ic_launcher.png rename to multisrc/overrides/fmreader/nicomanga/res/mipmap-xhdpi/ic_launcher.png diff --git a/src/ja/nicomanga/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/fmreader/nicomanga/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from src/ja/nicomanga/res/mipmap-xxhdpi/ic_launcher.png rename to multisrc/overrides/fmreader/nicomanga/res/mipmap-xxhdpi/ic_launcher.png diff --git a/src/ja/nicomanga/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/fmreader/nicomanga/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from src/ja/nicomanga/res/mipmap-xxxhdpi/ic_launcher.png rename to multisrc/overrides/fmreader/nicomanga/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/multisrc/overrides/fmreader/nicomanga/src/Nicomanga.kt b/multisrc/overrides/fmreader/nicomanga/src/Nicomanga.kt new file mode 100644 index 000000000..6f17125b8 --- /dev/null +++ b/multisrc/overrides/fmreader/nicomanga/src/Nicomanga.kt @@ -0,0 +1,82 @@ +package eu.kanade.tachiyomi.extension.ja.nicomanga + +import eu.kanade.tachiyomi.multisrc.fmreader.FMReader +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.interceptor.rateLimit +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.util.asJsoup +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.Request +import okhttp3.Response +import org.jsoup.nodes.Element + +class Nicomanga : FMReader("Nicomanga", "https://nicomanga.com", "ja") { + override val client = super.client.newBuilder() + .rateLimit(2) + .build() + + override fun headersBuilder() = super.headersBuilder() + .add("Referer", "$baseUrl/") + + // =========================== Manga Details ============================ + + override val infoElementSelector = ".card-body > div.row" + override val mangaDetailsSelectorGenre = "li:has(b:contains(Genre)) a.btn-danger" + + // ============================== Chapters ============================== + + override fun chapterListRequest(manga: SManga): Request { + val slug = urlRegex.find(manga.url)?.groupValues?.get(1) ?: throw Exception("Unable to get slug") + val headers = headersBuilder().apply { + add("Accept", "*/*") + add("Host", baseUrl.toHttpUrl().host) + set("Referer", baseUrl + manga.url) + }.build() + return GET("$baseUrl/app/manga/controllers/cont.Listchapterapi.php?slug=$slug", headers) + } + + override fun chapterFromElement(element: Element, mangaTitle: String): SChapter = SChapter.create().apply { + element.select(chapterUrlSelector).first()!!.let { + setUrlWithoutDomain("$baseUrl/${it.attr("href")}") + name = it.attr("title") + } + + date_upload = element.select(chapterTimeSelector) + .let { if (it.hasText()) parseRelativeDate(it.text()) else 0 } + } + + // =============================== Pages ================================ + + override fun pageListParse(response: Response): List { + val id = chapterIdRegex.find(response.use { it.body.string() })?.groupValues?.get(1) ?: throw Exception("chapter-id not found") + val doc = client.newCall( + GET("$baseUrl/app/manga/controllers/cont.imgsList.php?cid=$id", headers), + ).execute().asJsoup() + return doc.select("img.chapter-img[data-src]").mapIndexed { i, page -> + Page(i + 1, imageUrl = page.attr("data-src")) + } + } + + // ============================= Utilities ============================== + + override fun getImgAttr(element: Element?): String? { + return when { + element?.attr("style")?.contains("background-image") == true -> { + val url = thumbnailURLRegex.find(element.attr("style"))?.groupValues?.get(1) + when { + url?.startsWith("/") == true -> baseUrl + url + else -> url + } + } + else -> super.getImgAttr(element) + } + } + + companion object { + private val thumbnailURLRegex = Regex("background-image:[^;]?url\\s*\\(\\s*'?([^')]+?)'?(\\)|\$)") + private val urlRegex = Regex("manga-([^/]+)\\.html\$") + private val chapterIdRegex = Regex("imgsListchap\\((\\d+)") + } +} diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/fmreader/FMReader.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/fmreader/FMReader.kt index a361662f0..f76f3ce3d 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/fmreader/FMReader.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/fmreader/FMReader.kt @@ -76,14 +76,10 @@ abstract class FMReader( } is TextField -> url.addQueryParameter(filter.key, filter.state) is GenreList -> { - var genre = String() - var ungenre = String() - filter.state.forEach { - if (it.isIncluded()) genre += ",${it.name}" - if (it.isExcluded()) ungenre += ",${it.name}" - } - url.addQueryParameter("genre", genre) - url.addQueryParameter("ungenre", ungenre) + val included = filter.state.filter { it.isIncluded() }.joinToString(",") { it.name } + val excluded = filter.state.filter { it.isExcluded() }.joinToString(",") { it.name } + url.addQueryParameter("genre", included) + url.addQueryParameter("ungenre", excluded) } is SortBy -> { url.addQueryParameter( @@ -161,17 +157,28 @@ abstract class FMReader( override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() + // Manga Details Selector + open val infoElementSelector = "div.row" + open val mangaDetailsSelectorAuthor = "li a.btn-info" + open val mangaDetailsSelectorGenre = "li a.btn-danger" + open val mangaDetailsSelectorStatus = "li a.btn-success" + open val mangaDetailsSelectorDescription = "div.detail .content, div.row ~ div.row:has(h3:first-child) p, .summary-content p" + open val mangaDetailsSelectorThumbnail = "img.thumbnail" + + open val altNameSelector = "li:contains(Other names)" + open val altName = "Alternative Name" // the alt name already contains ": " eg. ": alt name1, alt name2" + override fun mangaDetailsParse(document: Document): SManga { - val infoElement = document.select("div.row").first()!! + val infoElement = document.select(infoElementSelector).first()!! return SManga.create().apply { - author = infoElement.select("li a.btn-info").eachText().filter { + author = infoElement.select(mangaDetailsSelectorAuthor).eachText().filter { it.equals("Updating", true).not() }.joinToString().takeIf { it.isNotBlank() } - genre = infoElement.select("li a.btn-danger").joinToString { it.text() } - status = parseStatus(infoElement.select("li a.btn-success").first()?.text()) - description = document.select("div.detail .content, div.row ~ div.row:has(h3:first-child) p, .summary-content p").text().trim() - thumbnail_url = infoElement.select("img.thumbnail").imgAttr() + genre = infoElement.select(mangaDetailsSelectorGenre).joinToString { it.text() } + status = parseStatus(infoElement.select(mangaDetailsSelectorStatus).first()?.text()) + description = document.select(mangaDetailsSelectorDescription).text().trim() + thumbnail_url = infoElement.select(mangaDetailsSelectorThumbnail).imgAttr() // add alternative name to manga description infoElement.select(altNameSelector).firstOrNull()?.ownText()?.let { @@ -185,9 +192,6 @@ abstract class FMReader( } } - open val altNameSelector = "li:contains(Other names)" - open val altName = "Alternative Name" // the alt name already contains ": " eg. ": alt name1, alt name2" - // languages: en, vi, tr fun parseStatus(status: String?): Int { val completedWords = setOf( @@ -251,7 +255,7 @@ abstract class FMReader( // gets the unit of time (day, week hour) from "1 day ago" open val dateWordIndex = 1 - private fun parseRelativeDate(date: String): Long { + open fun parseRelativeDate(date: String): Long { val value = date.split(' ')[dateValueIndex].toInt() val dateWord = date.split(' ')[dateWordIndex].let { if (it.contains("(")) { diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/fmreader/FMReaderGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/fmreader/FMReaderGenerator.kt index 25c85fb3f..7f63dfba1 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/fmreader/FMReaderGenerator.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/fmreader/FMReaderGenerator.kt @@ -14,9 +14,10 @@ class FMReaderGenerator : ThemeSourceGenerator { override val sources = listOf( SingleLang("KissLove", "https://klz9.com", "ja", isNsfw = true, overrideVersionCode = 4), SingleLang("Manga-TR", "https://manga-tr.com", "tr", className = "MangaTR", overrideVersionCode = 3), + SingleLang("Manga1000", "https://manga1000.top", "ja"), + SingleLang("Nicomanga", "https://nicomanga.com", "ja", isNsfw = true), SingleLang("Say Truyen", "https://saytruyenvip.com", "vi", overrideVersionCode = 3), SingleLang("WeLoveManga", "https://weloma.art", "ja", pkgName = "rawlh", isNsfw = true, overrideVersionCode = 5), - SingleLang("Manga1000", "https://manga1000.top", "ja"), SingleLang("WeLoveMangaOne", "https://welovemanga.one", "ja", isNsfw = true, overrideVersionCode = 1), ) diff --git a/src/ja/nicomanga/AndroidManifest.xml b/src/ja/nicomanga/AndroidManifest.xml deleted file mode 100644 index 8072ee00d..000000000 --- a/src/ja/nicomanga/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/src/ja/nicomanga/build.gradle b/src/ja/nicomanga/build.gradle deleted file mode 100644 index 4c7c42c68..000000000 --- a/src/ja/nicomanga/build.gradle +++ /dev/null @@ -1,7 +0,0 @@ -ext { - extName = 'Nicomanga' - extClass = '.Nicomanga' - extVersionCode = 2 -} - -apply from: "$rootDir/common.gradle" diff --git a/src/ja/nicomanga/src/eu/kanade/tachiyomi/extension/ja/nicomanga/Nicomanga.kt b/src/ja/nicomanga/src/eu/kanade/tachiyomi/extension/ja/nicomanga/Nicomanga.kt deleted file mode 100644 index 239a7d233..000000000 --- a/src/ja/nicomanga/src/eu/kanade/tachiyomi/extension/ja/nicomanga/Nicomanga.kt +++ /dev/null @@ -1,146 +0,0 @@ -package eu.kanade.tachiyomi.extension.ja.nicomanga - -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.HttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.HttpUrl.Companion.toHttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response - -class Nicomanga : HttpSource() { - companion object { - private val thumbnailURLRegex: Regex = "background-image:[^;]url\\s*\\(\\s*'([^?']+)".toRegex() - - private val statusRegex: Regex = "-([^.]+)".toRegex() - - private val urlRegex: Regex = "manga-([^/]+)\\.html\$".toRegex() - - private val chapterIdRegex: Regex = "imgsListchap\\((\\d+)".toRegex() - } - - override val baseUrl: String = "https://nicomanga.com" - - override val lang: String = "ja" - - override val name: String = "Nicomanga" - - override val supportsLatest: Boolean = true - - override val client: OkHttpClient = network.cloudflareClient - - override fun headersBuilder() = super.headersBuilder() - .add("Referer", "$baseUrl/") - - private fun mangaListParse(response: Response): MangasPage { - val doc = response.asJsoup() - val hasNextPage = ( - doc.select(".pagination li:last-of-type").size > 0 && - doc.select(".pagination li:last-of-type")[0].text() == "ยป" && - doc.select(".pagination li:last-of-type a.disabled").size == 0 - ) || doc.select(".pagination li:last-of-type a.active").size == 0 - - val mangas = doc.select(".row > .thumb-item-flow").map { manga -> - SManga.create().apply { - setUrlWithoutDomain(manga.selectFirst(".series-title a")!!.absUrl("href")) - title = manga.selectFirst(".series-title")?.text()!! - thumbnail_url = thumbnailURLRegex.find(manga.selectFirst(".img-in-ratio.lazyloaded")!!.attr("style"))!!.groupValues[1] - } - } - return MangasPage(mangas, hasNextPage) - } - - override fun latestUpdatesRequest(page: Int): Request { - val url = "$baseUrl/manga-list.html".toHttpUrl().newBuilder() - .addQueryParameter("page", page.toString()) - .addQueryParameter("sort", "last_update") - .addQueryParameter("sort_type", "DESC") - .build() - return GET(url, headers) - } - - override fun latestUpdatesParse(response: Response): MangasPage = mangaListParse(response) - - override fun popularMangaRequest(page: Int): Request { - val url = "$baseUrl/manga-list.html".toHttpUrl().newBuilder() - .addQueryParameter("page", page.toString()) - .addQueryParameter("sort", "views") - .addQueryParameter("sort_type", "DESC") - .build() - return GET(url, headers) - } - - override fun popularMangaParse(response: Response): MangasPage = mangaListParse(response) - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = "$baseUrl/manga-list.html".toHttpUrl().newBuilder() - .addQueryParameter("page", page.toString()) - .addQueryParameter("artist", "") - .addQueryParameter("author", "") - .addQueryParameter("group", "") - .addQueryParameter("m_status", "") - .addQueryParameter("name", query) - .addQueryParameter("genre", "") - .addQueryParameter("ungenre", "") - .addQueryParameter("magazine", "") - .addQueryParameter("sort", "last_update") - .addQueryParameter("sort_type", "DESC") - .build() - return GET(url, headers) - } - - override fun searchMangaParse(response: Response): MangasPage = mangaListParse(response) - - override fun mangaDetailsParse(response: Response): SManga = SManga.create().apply { - val doc = response.asJsoup() - author = doc.select("ul.manga-info a[href^=\"manga-author\"]").joinToString { it.text() } - genre = doc.select("ul.manga-info a[href^=\"manga-list-genre\"]").joinToString { it.text() } - val statusText = statusRegex.find(doc.select(".manga-info li:has(i.fa-spinner) a").attr("href"))?.run { groupValues[1] } - status = when (statusText) { - "on-going" -> SManga.ONGOING - "completed" -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - } - - override fun chapterListRequest(manga: SManga): Request { - val slug = urlRegex.find(manga.url)!!.groupValues[1] - return GET("$baseUrl/app/manga/controllers/cont.Listchapterapi.php?slug=$slug") - } - - override fun chapterListParse(response: Response): List { - val doc = response.asJsoup() - val chapterList = doc.select("ul > a") - val chapterPrefix = "$baseUrl/app/manga/controllers" - - val chapters = chapterList.map { chapter -> - SChapter.create().apply { - name = chapter.attr("title").trim() - val url = chapter.absUrl("href").run { - takeIf { startsWith(chapterPrefix) } - ?.replaceFirst(chapterPrefix, baseUrl) - ?: this - } - setUrlWithoutDomain(url) - } - } - return chapters - } - - override fun pageListParse(response: Response): List { - val id = chapterIdRegex.find(response.body.string())?.groupValues?.get(1) ?: throw Exception("chapter-id not found") - val r = client.newCall(GET("$baseUrl/app/manga/controllers/cont.imgsList.php?cid=$id", headers)).execute() - val doc = r.asJsoup() - return doc.select("img.chapter-img").mapIndexed { i, page -> - Page(i + 1, imageUrl = page.attr("data-src")) - } - } - - override fun imageUrlParse(response: Response): String = - throw UnsupportedOperationException() -}