diff --git a/src/it/mangaworld/AndroidManifest.xml b/multisrc/overrides/mangaworld/default/AndroidManifest.xml similarity index 100% rename from src/it/mangaworld/AndroidManifest.xml rename to multisrc/overrides/mangaworld/default/AndroidManifest.xml diff --git a/src/it/mangaworld/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mangaworld/default/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from src/it/mangaworld/res/mipmap-hdpi/ic_launcher.png rename to multisrc/overrides/mangaworld/default/res/mipmap-hdpi/ic_launcher.png diff --git a/src/it/mangaworld/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mangaworld/default/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from src/it/mangaworld/res/mipmap-mdpi/ic_launcher.png rename to multisrc/overrides/mangaworld/default/res/mipmap-mdpi/ic_launcher.png diff --git a/src/it/mangaworld/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/mangaworld/default/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from src/it/mangaworld/res/mipmap-xhdpi/ic_launcher.png rename to multisrc/overrides/mangaworld/default/res/mipmap-xhdpi/ic_launcher.png diff --git a/src/it/mangaworld/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/mangaworld/default/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from src/it/mangaworld/res/mipmap-xxhdpi/ic_launcher.png rename to multisrc/overrides/mangaworld/default/res/mipmap-xxhdpi/ic_launcher.png diff --git a/src/it/mangaworld/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mangaworld/default/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from src/it/mangaworld/res/mipmap-xxxhdpi/ic_launcher.png rename to multisrc/overrides/mangaworld/default/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/src/it/mangaworld/res/web_hi_res_512.png b/multisrc/overrides/mangaworld/default/res/web_hi_res_512.png similarity index 100% rename from src/it/mangaworld/res/web_hi_res_512.png rename to multisrc/overrides/mangaworld/default/res/web_hi_res_512.png diff --git a/src/it/mangaworld/src/eu/kanade/tachiyomi/extension/it/mangaworld/Mangaworld.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangaworld/MangaWorld.kt similarity index 51% rename from src/it/mangaworld/src/eu/kanade/tachiyomi/extension/it/mangaworld/Mangaworld.kt rename to multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangaworld/MangaWorld.kt index db5864917..0bfaaeaaf 100644 --- a/src/it/mangaworld/src/eu/kanade/tachiyomi/extension/it/mangaworld/Mangaworld.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangaworld/MangaWorld.kt @@ -1,4 +1,4 @@ -package eu.kanade.tachiyomi.extension.it.mangaworld +package eu.kanade.tachiyomi.multisrc.mangaworld import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.source.model.Filter @@ -16,127 +16,86 @@ import okhttp3.Request import okhttp3.Response import org.jsoup.nodes.Document import org.jsoup.nodes.Element -import java.text.ParseException +import java.lang.Exception +import java.lang.UnsupportedOperationException import java.text.SimpleDateFormat import java.util.Locale -class Mangaworld : ParsedHttpSource() { +abstract class MangaWorld( + override val name: String, + override val baseUrl: String, + override val lang: String +) : ParsedHttpSource() { - override val name = "Mangaworld" - override val baseUrl = "https://www.mangaworld.in" - override val lang = "it" override val supportsLatest = true override val client: OkHttpClient = network.cloudflareClient + companion object { + protected val CHAPTER_NUMBER_REGEX by lazy { Regex("""(?i)capitolo\s([0-9]+)""") } + + protected val DATE_FORMATTER by lazy { SimpleDateFormat("dd MMMM yyyy", Locale.ITALY) } + protected val DATE_FORMATTER_2 by lazy { SimpleDateFormat("H", Locale.ITALY) } + } + override fun popularMangaRequest(page: Int): Request { return GET("$baseUrl/archive?sort=most_read&page=$page", headers) } override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/archive?sort=newest&page=$page", headers) + return GET("$baseUrl/?page=$page", headers) } - // LIST SELECTOR - override fun popularMangaSelector() = "div.comics-grid .entry" - override fun latestUpdatesSelector() = popularMangaSelector() - override fun searchMangaSelector() = popularMangaSelector() - // ELEMENT + override fun searchMangaSelector() = "div.comics-grid .entry" + override fun popularMangaSelector() = searchMangaSelector() + override fun latestUpdatesSelector() = searchMangaSelector() + + override fun searchMangaFromElement(element: Element): SManga { + val manga = SManga.create() + manga.thumbnail_url = element.select("a.thumb img").attr("src") + element.select("a").first().let { + manga.setUrlWithoutDomain(it.attr("href").removeSuffix("/")) + manga.title = it.attr("title") + } + return manga + } override fun popularMangaFromElement(element: Element): SManga = searchMangaFromElement(element) override fun latestUpdatesFromElement(element: Element): SManga = searchMangaFromElement(element) - // NEXT SELECTOR - // Not needed - override fun popularMangaNextPageSelector(): String? = null - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - // //////////////// + override fun searchMangaNextPageSelector(): String? = null + override fun popularMangaNextPageSelector() = searchMangaNextPageSelector() + override fun latestUpdatesNextPageSelector() = searchMangaNextPageSelector() - override fun latestUpdatesParse(response: Response): MangasPage { + override fun searchMangaParse(response: Response): MangasPage { val document = response.asJsoup() - val mangas = document.select(popularMangaSelector()).map { element -> - popularMangaFromElement(element) + val mangas = document.select(latestUpdatesSelector()).map { element -> + searchMangaFromElement(element) } // nextPage is not possible because pagination is loaded after via Javascript // 16 is the default manga-per-page. If it is less than 16 then there's no next page val hasNextPage = mangas.size == 16 return MangasPage(mangas, hasNextPage) } - - override fun popularMangaParse(response: Response): MangasPage { - return latestUpdatesParse(response) - } - - override fun searchMangaFromElement(element: Element): SManga { - val manga = SManga.create() - manga.thumbnail_url = element.select("a.thumb img").attr("src") - element.select("a").first().let { - manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.attr("title") - } - return manga - } - - override fun searchMangaParse(response: Response): MangasPage { - return latestUpdatesParse(response) - } + override fun popularMangaParse(response: Response): MangasPage = searchMangaParse(response) + override fun latestUpdatesParse(response: Response): MangasPage = searchMangaParse(response) override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { val url = "$baseUrl/archive?page=$page".toHttpUrlOrNull()!!.newBuilder() - val q = query - if (query.isNotEmpty()) { - url.addQueryParameter("keyword", q) - } else { - url.addQueryParameter("keyword", "") - } + url.addQueryParameter("keyword", query) - var orderBy = "" - - (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> + filters.forEach { filter -> when (filter) { - is GenreList -> { - val genreInclude = mutableListOf<String>() - filter.state.forEach { - if (it.state == 1) { - genreInclude.add(it.id) - } + is GenreList -> + filter.state.filter { it.state }.forEach { + url.addQueryParameter("genre", it.id) } - if (genreInclude.isNotEmpty()) { - genreInclude.forEach { genre -> - url.addQueryParameter("genre", genre) - } + is StatusList -> + filter.state.filter { it.state }.forEach { + url.addQueryParameter("status", it.id) } - } - is StatusList -> { - val statuses = mutableListOf<String>() - filter.state.forEach { - if (it.state == 1) { - statuses.add(it.id) - } + is MTypeList -> + filter.state.filter { it.state }.forEach { + url.addQueryParameter("type", it.id) } - if (statuses.isNotEmpty()) { - statuses.forEach { status -> - url.addQueryParameter("status", status) - } - } - } - - is MTypeList -> { - val typeslist = mutableListOf<String>() - filter.state.forEach { - if (it.state == 1) { - typeslist.add(it.id) - } - } - if (typeslist.isNotEmpty()) { - typeslist.forEach { mtype -> - url.addQueryParameter("type", mtype) - } - } - } - - is SortBy -> { - orderBy = filter.toUriPart() - url.addQueryParameter("sort", orderBy) - } + is SortBy -> url.addQueryParameter("sort", filter.toUriPart()) is TextField -> url.addQueryParameter(filter.key, filter.state) } } @@ -145,82 +104,100 @@ class Mangaworld : ParsedHttpSource() { override fun mangaDetailsParse(document: Document): SManga { val infoElement = document.select("div.comic-info") - val metaData = document.select("div.comic-info").first() + if (infoElement.isEmpty()) + throw Exception("Page not found") val manga = SManga.create() - manga.author = infoElement.select("a[href^=$baseUrl/archive?author=]").first()?.text() - manga.artist = infoElement.select("a[href^=$baseUrl/archive?artist=]")?.text() + manga.author = infoElement.select("a[href*=/archive?author=]").first()?.text() + manga.artist = infoElement.select("a[href*=/archive?artist=]").text() + manga.thumbnail_url = infoElement.select(".thumb > img").attr("src") - val genres = mutableListOf<String>() - metaData.select("div.meta-data a.badge").forEach { element -> - val genre = element.text() - genres.add(genre) + var description = document.select("div#noidungm").text() + val otherTitle = document.select("div.meta-data > div").first()?.text() + if (!otherTitle.isNullOrBlank() && otherTitle.contains("Titoli alternativi")) + description += "\n\n$otherTitle" + manga.description = description.trim() + + manga.genre = infoElement.select("div.meta-data a.badge").joinToString(", ") { + it.text() } - manga.genre = genres.joinToString(", ") - manga.status = parseStatus(infoElement.select("a[href^=$baseUrl/archive?status=]").first().attr("href")) - manga.description = document.select("div#noidungm")?.text() - manga.thumbnail_url = document.select(".comic-info .thumb > img").attr("src") + val status = infoElement.select("a[href*=/archive?status=]").first()?.text() + manga.status = parseStatus(status) return manga } - private fun parseStatus(element: String): Int = when { - element.lowercase().contains("ongoing") -> SManga.ONGOING - element.lowercase().contains("completed") -> SManga.COMPLETED - else -> SManga.UNKNOWN + protected fun parseStatus(element: String?): Int { + if (element.isNullOrEmpty()) + return SManga.UNKNOWN + return when (element.lowercase()) { + "in corso" -> SManga.ONGOING + "finito" -> SManga.COMPLETED + "in pausa" -> SManga.ON_HIATUS + "cancellato" -> SManga.CANCELLED + else -> SManga.UNKNOWN + } } override fun chapterListSelector() = ".chapters-wrapper .chapter" override fun chapterFromElement(element: Element): SChapter { - val urlElement = element.select("a.chap").first() val chapter = SChapter.create() - chapter.setUrlWithoutDomain(getUrl(urlElement)) - chapter.name = urlElement.select("span.d-inline-block").first().text() - chapter.date_upload = element.select(".chap-date").last()?.text()?.let { - try { - SimpleDateFormat("dd MMMM yyyy", Locale.ITALY).parse(it).time - } catch (e: ParseException) { - SimpleDateFormat("H", Locale.ITALY).parse(it).time - } - } ?: 0 + + val url = element.select("a.chap").first()?.attr("href") + ?: throw throw Exception("Url not found") + chapter.setUrlWithoutDomain(fixChapterUrl(url)) + + val name = element.select("span.d-inline-block").first()?.text() ?: "" + chapter.name = name + + val date = parseChapterDate(element.select(".chap-date").last()?.text()) + chapter.date_upload = date + + val number = parseChapterNumber(name) + if (number != null) + chapter.chapter_number = number return chapter } - private fun getUrl(urlElement: Element): String { - var url = urlElement.attr("href") + protected fun fixChapterUrl(url: String?): String { + if (url.isNullOrEmpty()) + return "" + val params = url.split("?").let { if (it.size > 1) it[1] else "" } return when { - url.endsWith("?style=list") -> url - else -> "$url?style=list" + params.contains("style=list") -> url + params.contains("style=pages") -> + url.replace("style=pages", "style=list") + params.isEmpty() -> "$url?style=list" + else -> "$url&style=list" } } - override fun prepareNewChapter(chapter: SChapter, manga: SManga) { - val basic = Regex("""Capitolo\s([0-9]+)""") - when { - basic.containsMatchIn(chapter.name) -> { - basic.find(chapter.name)?.let { - chapter.chapter_number = it.groups[1]?.value!!.toFloat() - } - } + protected fun parseChapterDate(string: String?): Long { + if (string == null) + return 0L + return runCatching { DATE_FORMATTER.parse(string)?.time }.getOrNull() + ?: runCatching { DATE_FORMATTER_2.parse(string)?.time }.getOrNull() ?: 0L + } + + protected fun parseChapterNumber(string: String): Float? { + return CHAPTER_NUMBER_REGEX.find(string)?.let { + it.groups[1]?.value?.toFloat() } } override fun pageListParse(document: Document): List<Page> { val pages = mutableListOf<Page>() - var i = 0 - document.select("div#page img.page-image").forEach { element -> - val url = element.attr("src") - i++ - if (url.length != 0) { - pages.add(Page(i, "", url)) - } + document.select("div#page img.page-image").forEachIndexed { i, it -> + val url = it.attr("src") + if (url.isNotEmpty()) + pages.add(Page(i, imageUrl = url)) } return pages } - override fun imageUrlParse(document: Document) = "" + override fun imageUrlParse(document: Document) = throw UnsupportedOperationException("Not used.") override fun imageRequest(page: Page): Request { val imgHeader = Headers.Builder().apply { @@ -229,52 +206,40 @@ class Mangaworld : ParsedHttpSource() { }.build() return GET(page.imageUrl!!, imgHeader) } - // private class Status : Filter.TriState("Completed") - private class TextField(name: String, val key: String) : Filter.Text(name) - private class SortBy : UriPartFilter( - "Ordina per", - arrayOf( - Pair("Rilevanza", ""), - Pair("Più recenti", "newest"), - Pair("Meno recenti", "oldest"), - Pair("A-Z", "a-z"), - Pair("Z-A", "z-a"), - Pair("Più letti", "most_read"), - Pair("Meno letti", "less_read") - ) - ) - private class Genre(name: String, val id: String = name) : Filter.TriState(name) - private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Generi", genres) - private class MType(name: String, val id: String = name) : Filter.TriState(name) - private class MTypeList(types: List<MType>) : Filter.Group<MType>("Tipologia", types) - private class Status(name: String, val id: String = name) : Filter.TriState(name) - private class StatusList(statuses: List<Status>) : Filter.Group<Status>("Stato", statuses) override fun getFilterList() = FilterList( - TextField("Anno di rilascio", "year"), + TextField("Anno di uscita", "year"), SortBy(), StatusList(getStatusList()), GenreList(getGenreList()), MTypeList(getTypesList()) ) - private fun getStatusList() = listOf( - Status("In corso", "ongoing"), - Status("Finito", "completed"), - Status("Droppato", "dropped"), - Status("In pausa", "paused"), - Status("Cancellato", "canceled") + + private class SortBy : UriPartFilter( + "Ordina per", + arrayOf( + Pair("Rilevanza", ""), + Pair("Più letti", "most_read"), + Pair("Meno letti", "less_read"), + Pair("Più recenti", "newest"), + Pair("Meno recenti", "oldest"), + Pair("A-Z", "a-z"), + Pair("Z-A", "z-a") + ) ) - private fun getTypesList() = listOf( - MType("Manga", "manga"), - MType("Manhua", "manhua"), - MType("Manhwa", "manhwa"), - MType("Oneshot", "oneshot"), - MType("Thai", "thai"), - MType("Vietnamita", "vietnamese") - ) + private class TextField(name: String, val key: String) : Filter.Text(name) - private fun getGenreList() = listOf( + class Genre(name: String, val id: String = name) : Filter.CheckBox(name) + private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Generi", genres) + + class MType(name: String, val id: String = name) : Filter.CheckBox(name) + private class MTypeList(types: List<MType>) : Filter.Group<MType>("Tipologia", types) + + class Status(name: String, val id: String = name) : Filter.CheckBox(name) + private class StatusList(statuses: List<Status>) : Filter.Group<Status>("Stato", statuses) + + protected fun getGenreList() = listOf( Genre("Adulti", "adulti"), Genre("Arti Marziali", "arti-marziali"), Genre("Avventura", "avventura"), @@ -312,6 +277,22 @@ class Mangaworld : ParsedHttpSource() { Genre("Yaoi", "yaoi"), Genre("Yuri", "yuri") ) + protected fun getTypesList() = listOf( + MType("Manga", "manga"), + MType("Manhua", "manhua"), + MType("Manhwa", "manhwa"), + MType("Oneshot", "oneshot"), + MType("Thai", "thai"), + MType("Vietnamita", "vietnamese") + ) + protected fun getStatusList() = listOf( + Status("In corso", "ongoing"), + Status("Finito", "completed"), + Status("Droppato", "dropped"), + Status("In pausa", "paused"), + Status("Cancellato", "canceled") + ) + private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) : Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) { fun toUriPart() = vals[state].second diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangaworld/MangaWorldGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangaworld/MangaWorldGenerator.kt new file mode 100644 index 000000000..4e114c244 --- /dev/null +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangaworld/MangaWorldGenerator.kt @@ -0,0 +1,23 @@ +package eu.kanade.tachiyomi.multisrc.mangaworld + +import generator.ThemeSourceData.SingleLang +import generator.ThemeSourceGenerator + +class MangaWorldGenerator : ThemeSourceGenerator { + + override val themePkg = "mangaworld" + override val themeClass = "MangaWorld" + override val baseVersionCode: Int = 1 + + override val sources = listOf( + SingleLang("Mangaworld", "https://www.mangaworld.in", "it", isNsfw = false, pkgName = "mangaworld", overrideVersionCode = 5), + SingleLang("MangaworldAdult", "https://www.mangaworldadult.com", "it", isNsfw = true), + ) + + companion object { + @JvmStatic + fun main(args: Array<String>) { + MangaWorldGenerator().createAll() + } + } +} diff --git a/src/it/mangaworld/build.gradle b/src/it/mangaworld/build.gradle deleted file mode 100644 index ad2ef9b7c..000000000 --- a/src/it/mangaworld/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Mangaworld' - pkgNameSuffix = 'it.mangaworld' - extClass = '.Mangaworld' - extVersionCode = 5 - isNsfw = true -} - -apply from: "$rootDir/common.gradle"