From 634466831995699dc63d705c75275d3aace8644d Mon Sep 17 00:00:00 2001 From: seew3l <90949336+seew3l@users.noreply.github.com> Date: Tue, 28 Feb 2023 14:44:11 -0500 Subject: [PATCH] MhwLatino: Move to Madara (#15520) * Fix * Use body.string() Co-authored-by: Alessandro Jean <14254807+alessandrojean@users.noreply.github.com> * Use data() instead html() Co-authored-by: Alessandro Jean <14254807+alessandrojean@users.noreply.github.com> * Use selectFirst() * Migrate MhwLatino to Madara * Add ID * Remove ID xd --------- Co-authored-by: Alessandro Jean <14254807+alessandrojean@users.noreply.github.com> --- .../madara/manhwalatino/additional.gradle | 3 + .../res/mipmap-hdpi/ic_launcher.png | Bin .../res/mipmap-mdpi/ic_launcher.png | Bin .../res/mipmap-xhdpi/ic_launcher.png | Bin .../res/mipmap-xxhdpi/ic_launcher.png | Bin .../res/mipmap-xxxhdpi/ic_launcher.png | Bin .../manhwalatino}/res/web_hi_res_512.png | Bin .../madara/manhwalatino/src/ManhwaLatino.kt | 71 ++++ .../multisrc/madara/MadaraGenerator.kt | 1 + src/es/mahnwalatino/AndroidManifest.xml | 24 -- src/es/mahnwalatino/build.gradle | 16 - .../extension/es/manhwalatino/MLConstants.kt | 40 -- .../extension/es/manhwalatino/ManhwaLatino.kt | 278 ------------- .../es/manhwalatino/ManhwaLatinoSiteParser.kt | 372 ------------------ .../manhwalatino/ManhwaLatinoUrlActivity.kt | 37 -- .../es/manhwalatino/filters/GenreTagFilter.kt | 48 --- .../es/manhwalatino/filters/UriFilter.kt | 10 - .../es/manhwalatino/filters/UriPartFilter.kt | 27 -- 18 files changed, 75 insertions(+), 852 deletions(-) create mode 100644 multisrc/overrides/madara/manhwalatino/additional.gradle rename {src/es/mahnwalatino => multisrc/overrides/madara/manhwalatino}/res/mipmap-hdpi/ic_launcher.png (100%) rename {src/es/mahnwalatino => multisrc/overrides/madara/manhwalatino}/res/mipmap-mdpi/ic_launcher.png (100%) rename {src/es/mahnwalatino => multisrc/overrides/madara/manhwalatino}/res/mipmap-xhdpi/ic_launcher.png (100%) rename {src/es/mahnwalatino => multisrc/overrides/madara/manhwalatino}/res/mipmap-xxhdpi/ic_launcher.png (100%) rename {src/es/mahnwalatino => multisrc/overrides/madara/manhwalatino}/res/mipmap-xxxhdpi/ic_launcher.png (100%) rename {src/es/mahnwalatino => multisrc/overrides/madara/manhwalatino}/res/web_hi_res_512.png (100%) create mode 100644 multisrc/overrides/madara/manhwalatino/src/ManhwaLatino.kt delete mode 100644 src/es/mahnwalatino/AndroidManifest.xml delete mode 100644 src/es/mahnwalatino/build.gradle delete mode 100644 src/es/mahnwalatino/src/eu/kanade/tachiyomi/extension/es/manhwalatino/MLConstants.kt delete mode 100644 src/es/mahnwalatino/src/eu/kanade/tachiyomi/extension/es/manhwalatino/ManhwaLatino.kt delete mode 100644 src/es/mahnwalatino/src/eu/kanade/tachiyomi/extension/es/manhwalatino/ManhwaLatinoSiteParser.kt delete mode 100644 src/es/mahnwalatino/src/eu/kanade/tachiyomi/extension/es/manhwalatino/ManhwaLatinoUrlActivity.kt delete mode 100644 src/es/mahnwalatino/src/eu/kanade/tachiyomi/extension/es/manhwalatino/filters/GenreTagFilter.kt delete mode 100644 src/es/mahnwalatino/src/eu/kanade/tachiyomi/extension/es/manhwalatino/filters/UriFilter.kt delete mode 100644 src/es/mahnwalatino/src/eu/kanade/tachiyomi/extension/es/manhwalatino/filters/UriPartFilter.kt diff --git a/multisrc/overrides/madara/manhwalatino/additional.gradle b/multisrc/overrides/madara/manhwalatino/additional.gradle new file mode 100644 index 000000000..f9e6f25e0 --- /dev/null +++ b/multisrc/overrides/madara/manhwalatino/additional.gradle @@ -0,0 +1,3 @@ +dependencies { + implementation(project(':lib-cryptoaes')) +} diff --git a/src/es/mahnwalatino/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/manhwalatino/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from src/es/mahnwalatino/res/mipmap-hdpi/ic_launcher.png rename to multisrc/overrides/madara/manhwalatino/res/mipmap-hdpi/ic_launcher.png diff --git a/src/es/mahnwalatino/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/manhwalatino/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from src/es/mahnwalatino/res/mipmap-mdpi/ic_launcher.png rename to multisrc/overrides/madara/manhwalatino/res/mipmap-mdpi/ic_launcher.png diff --git a/src/es/mahnwalatino/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/manhwalatino/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from src/es/mahnwalatino/res/mipmap-xhdpi/ic_launcher.png rename to multisrc/overrides/madara/manhwalatino/res/mipmap-xhdpi/ic_launcher.png diff --git a/src/es/mahnwalatino/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/manhwalatino/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from src/es/mahnwalatino/res/mipmap-xxhdpi/ic_launcher.png rename to multisrc/overrides/madara/manhwalatino/res/mipmap-xxhdpi/ic_launcher.png diff --git a/src/es/mahnwalatino/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/manhwalatino/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from src/es/mahnwalatino/res/mipmap-xxxhdpi/ic_launcher.png rename to multisrc/overrides/madara/manhwalatino/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/src/es/mahnwalatino/res/web_hi_res_512.png b/multisrc/overrides/madara/manhwalatino/res/web_hi_res_512.png similarity index 100% rename from src/es/mahnwalatino/res/web_hi_res_512.png rename to multisrc/overrides/madara/manhwalatino/res/web_hi_res_512.png diff --git a/multisrc/overrides/madara/manhwalatino/src/ManhwaLatino.kt b/multisrc/overrides/madara/manhwalatino/src/ManhwaLatino.kt new file mode 100644 index 000000000..6bd7ab529 --- /dev/null +++ b/multisrc/overrides/madara/manhwalatino/src/ManhwaLatino.kt @@ -0,0 +1,71 @@ +package eu.kanade.tachiyomi.extension.es.manhwalatino + +import android.util.Base64 +import eu.kanade.tachiyomi.lib.cryptoaes.CryptoAES +import eu.kanade.tachiyomi.multisrc.madara.Madara +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.source.model.Page +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive +import org.jsoup.nodes.Document +import java.text.SimpleDateFormat +import java.util.Locale + +class ManhwaLatino : Madara( + "Manhwa-Latino", + "https://manhwa-latino.com", + "es", + SimpleDateFormat("dd/MM/yyyy", Locale("es")), +) { + + override val useNewChapterEndpoint = true + + override val chapterUrlSelector = "a:eq(1)" + + override fun pageListParse(document: Document): List { + val script = document.selectFirst("div.read-container script") + + val scriptData: String = if (script!!.hasAttr("src")) { + client.newCall(GET(script.attr("src"), headers)).execute().body.string() + } else { + script.data() + } + + val password = scriptData + .substringAfter("wpmangaprotectornonce='") + .substringBefore("';") + + val chapterData = json.parseToJsonElement( + scriptData + .substringAfter("chapter_data='") + .substringBefore("';") + .replace("\\/", "/"), + ).jsonObject + + val unsaltedCiphertext = Base64.decode(chapterData["ct"]!!.jsonPrimitive.content, Base64.DEFAULT) + val salt = chapterData["s"]!!.jsonPrimitive.content.decodeHex() + val ciphertext = SALTED + salt + unsaltedCiphertext + + val rawImgArray = CryptoAES.decrypt(Base64.encodeToString(ciphertext, Base64.DEFAULT), password) + val imgArrayString = json.parseToJsonElement(rawImgArray).jsonPrimitive.content + val imgArray = json.parseToJsonElement(imgArrayString).jsonArray + + return imgArray.mapIndexed { idx, it -> + Page(idx, document.location(), it.jsonPrimitive.content) + } + } + + // https://stackoverflow.com/a/66614516 + private fun String.decodeHex(): ByteArray { + check(length % 2 == 0) { "Must have an even length" } + + return chunked(2) + .map { it.toInt(16).toByte() } + .toByteArray() + } + + companion object { + val SALTED = "Salted__".toByteArray(Charsets.UTF_8) + } +} diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/madara/MadaraGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/madara/MadaraGenerator.kt index 7e32d5e84..797dabf31 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/madara/MadaraGenerator.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/madara/MadaraGenerator.kt @@ -279,6 +279,7 @@ class MadaraGenerator : ThemeSourceGenerator { SingleLang("ManhuaUS", "https://manhuaus.com", "en", overrideVersionCode = 5), SingleLang("ManhuaZone", "https://manhuazone.com", "en"), SingleLang("Manhwa Raw", "https://manhwaraw.com", "ko", isNsfw = true, overrideVersionCode = 1), + SingleLang("Manhwa-Latino", "https://manhwa-latino.com", "es", isNsfw = true, className = "ManhwaLatino"), SingleLang("Manhwa-raw", "https://manhwa-raw.com", "all", isNsfw = true, className = "ManhwaDashRaw"), SingleLang("Manhwa18.app", "https://manhwa18.app", "en", isNsfw = true, className = "Manhwa18app"), SingleLang("Manhwa18.org", "https://manhwa18.org", "en", isNsfw = true, className = "Manhwa18Org", overrideVersionCode = 2), diff --git a/src/es/mahnwalatino/AndroidManifest.xml b/src/es/mahnwalatino/AndroidManifest.xml deleted file mode 100644 index a65d7d653..000000000 --- a/src/es/mahnwalatino/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/es/mahnwalatino/build.gradle b/src/es/mahnwalatino/build.gradle deleted file mode 100644 index 25aef2010..000000000 --- a/src/es/mahnwalatino/build.gradle +++ /dev/null @@ -1,16 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Manhwa-Latino' - pkgNameSuffix = 'es.manhwalatino' - extClass = '.ManhwaLatino' - extVersionCode = 23 - isNsfw = true -} - -apply from: "$rootDir/common.gradle" - -dependencies { - implementation(project(':lib-cryptoaes')) -} diff --git a/src/es/mahnwalatino/src/eu/kanade/tachiyomi/extension/es/manhwalatino/MLConstants.kt b/src/es/mahnwalatino/src/eu/kanade/tachiyomi/extension/es/manhwalatino/MLConstants.kt deleted file mode 100644 index 82178915e..000000000 --- a/src/es/mahnwalatino/src/eu/kanade/tachiyomi/extension/es/manhwalatino/MLConstants.kt +++ /dev/null @@ -1,40 +0,0 @@ -package eu.kanade.tachiyomi.extension.es.manhwalatino - -object MLConstants { - - const val PREFIX_MANGA_ID_SEARCH = "id:" - - const val searchMangaNextPageSelector = "link[rel=next]" - const val latestUpdatesSelector = "div.slider__item" - const val searchMangaSelector = "div.page-item-detail.manga" - const val popularMangaNextPageSelector = "a.nextpostslink" - const val latestUpdatesNextPageSelector = "div[role=navigation] a.last" - - const val popularMangaSelector = "div.page-item-detail.manga" - const val popularGenreTitleHTMLSelector: String = "div.item-summary div.post-title h3" - const val popularGenreUrlHTMLSelector: String = "div.item-summary div.post-title h3 a" - const val popularGenreThumbnailUrlMangaHTMLSelector: String = "div.item-thumb.c-image-hover img" - - const val searchPageTitleHTMLSelector: String = "div.tab-summary div.post-title h3" - const val searchPageUrlHTMLSelector: String = "div.tab-summary div.post-title h3 a" - const val searchPageThumbnailUrlMangaHTMLSelector: String = "div.tab-thumb.c-image-hover img" - - const val mangaDetailsThumbnailUrlHTMLSelector: String = "div.summary_image img" - const val mangaDetailsAuthorHTMLSelector: String = "div.author-content" - const val mangaDetailsArtistHTMLSelector: String = "div.artist-content" - const val mangaDetailsDescriptionHTMLSelector: String = "div.post-content_item > div > p" - const val mangaDetailsGenreHTMLSelector: String = "div.genres-content a" - const val mangaDetailsTagsHTMLSelector: String = "div.tags-content a" - const val mangaDetailsAttributes: String = "div.summary_content div.post-content_item" - const val searchSiteMangasHTMLSelector = "div.c-tabs-item__content" - const val genreSiteMangasHTMLSelector = "div.page-item-detail.manga" - const val latestUpdatesSelectorThumbnailUrl = "div.slider__thumb_item > a > img" - const val latestUpdatesSelectorTitle = "div.slider__content h4" - const val latestUpdatesSelectorUrl = "div.slider__content h4 a" - const val chapterListParseSelector = "div.listing-chapters_wrap > ul > li" - const val chapterLinkParser = "a" - const val chapterReleaseDateLinkParser = "span.chapter-release-date a" - const val chapterReleaseDateIParser = "span.chapter-release-date i" - const val pageListParseSelector = "div.read-container script" - const val imageAttribute = "abs:data-src" -} diff --git a/src/es/mahnwalatino/src/eu/kanade/tachiyomi/extension/es/manhwalatino/ManhwaLatino.kt b/src/es/mahnwalatino/src/eu/kanade/tachiyomi/extension/es/manhwalatino/ManhwaLatino.kt deleted file mode 100644 index 15f546e06..000000000 --- a/src/es/mahnwalatino/src/eu/kanade/tachiyomi/extension/es/manhwalatino/ManhwaLatino.kt +++ /dev/null @@ -1,278 +0,0 @@ -package eu.kanade.tachiyomi.extension.es.manhwalatino - -import eu.kanade.tachiyomi.extension.es.manhwalatino.filters.GenreTagFilter -import eu.kanade.tachiyomi.network.GET -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.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable - -class ManhwaLatino : ParsedHttpSource() { - - /** - * Name of the source. - */ - override val name = "Manhwa-Latino" - - /** - * Base url of the website without the trailing slash, like: http://mysite.com - * alternative site "https://manhwa-es.com" - */ - override val baseUrl = "https://manhwa-latino.com" - - /** - * Header for Request - */ - override fun headersBuilder() = Headers.Builder().add("Referer", "$baseUrl") - - /** - * Parser for The WebSite - */ - private val manhwaLatinoSiteParser = ManhwaLatinoSiteParser(baseUrl, client, headers) - - /** - * An ISO 639-1 compliant language code (two letters in lower case). - */ - override val lang = "es" - - /** - * Whether the source has support for latest updates. - */ - override val supportsLatest = true - - /** - * Returns the Jsoup selector that returns a list of [Element] corresponding to each manga. - */ - override fun popularMangaSelector(): String { - return MLConstants.popularMangaSelector - } - - /** - * Returns the Jsoup selector that returns a list of [Element] corresponding to each manga. - */ - override fun latestUpdatesSelector(): String { - return MLConstants.latestUpdatesSelector - } - - /** - * Returns the Jsoup selector that returns a list of [Element] corresponding to each manga. - */ - override fun searchMangaSelector(): String { - return MLConstants.searchMangaSelector - } - - /** - * Returns the Jsoup selector that returns a list of [Element] corresponding to each chapter. - */ - override fun chapterListSelector() = - throw Exception("Not Used") - - /** - * Returns the Jsoup selector that returns the tag linking to the next page, or null if - * there's no next page. - */ - override fun popularMangaNextPageSelector(): String { - return MLConstants.popularMangaNextPageSelector - } - - /** - * Returns the Jsoup selector that returns the tag linking to the next page, or null if - * there's no next page. - */ - override fun latestUpdatesNextPageSelector(): String { - return MLConstants.latestUpdatesNextPageSelector - } - - /** - * Returns the Jsoup selector that returns the tag linking to the next page, or null if - * there's no next page. - */ - override fun searchMangaNextPageSelector(): String { - return MLConstants.searchMangaNextPageSelector - } - - /** - * Returns the request for the popular manga given the page. - * - * @param page the page number to retrieve. - */ - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/page/$page/", headers) - } - - /** - * Returns the request for latest manga given the page. - * - * @param page the page number to retrieve. - */ - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/page/$page/", headers) - } - - /** - * Parses the response from the site and returns a [MangasPage] object. - * - * @param response the response from the site. - */ - override fun latestUpdatesParse(response: Response): MangasPage { - val document = response.asJsoup() - val mangas = document.select(latestUpdatesSelector()).map { latestUpdatesFromElement(it) } - return MangasPage(mangas, manhwaLatinoSiteParser.latestUpdatesHasNextPages()) - } - - /** - * Returns a manga from the given [element]. Most sites only show the title and the url, it's - * totally fine to fill only those two values. - * - * @param element an element obtained from [latestUpdatesSelector]. - */ - override fun latestUpdatesFromElement(element: Element): SManga { - return manhwaLatinoSiteParser.getMangaFromLastTranslatedSlide(element) - } - - /** - * Returns the request for the search manga given the page. - * - * @param page the page number to retrieve. - * @param query the search query. - * @param filters the list of filters to apply. - */ - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val uri = manhwaLatinoSiteParser.searchMangaRequest(page, query, filters) - return GET(uri.toString(), headers) - } - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { - return manhwaLatinoSiteParser.fetchSearchManga(page, query, filters) - } - - /** - * Parses the response from the site and returns a [MangasPage] object. - * - * @param response the response from the site. - */ - override fun searchMangaParse(response: Response): MangasPage { - return manhwaLatinoSiteParser.searchMangaParse(response) - } - - /** - * Returns the request for the details of a manga. Override only if it's needed to change the - * url, send different headers or request method like POST. - * - * @param manga the manga to be updated. - */ - override fun mangaDetailsRequest(manga: SManga) = GET(baseUrl + manga.url, headers) - - /** - * Returns the request for updating the chapter list. Override only if it's needed to override - * the url, send different headers or request method like POST. - * - * @param manga the manga to look for chapters. - */ - override fun chapterListRequest(manga: SManga): Request { - return GET(baseUrl + manga.url, headers) - } - - /** - * Returns a manga from the given [element]. Most sites only show the title and the url, it's - * totally fine to fill only those two values. - * - * @param element an element obtained from [popularMangaSelector]. - */ - override fun popularMangaFromElement(element: Element) = mangaFromElement(element) - - /** - * Returns a manga from the given [element]. Most sites only show the title and the url, it's - * totally fine to fill only those two values. - * - * @param element an element obtained from [searchMangaSelector]. - */ - override fun searchMangaFromElement(element: Element) = mangaFromElement(element) - - /** - * Returns a manga from the given [element]. Most sites only show the title and the url, it's - * totally fine to fill only those two values. - * - * @param element an element obtained from [searchMangaSelector]. - */ - private fun mangaFromElement(element: Element): SManga { - return manhwaLatinoSiteParser.getMangaFromList(element) - } - - /** - * Parses the response from the site and returns a list of chapters. - * - * @param response the response from the site. - */ - override fun chapterListParse(response: Response): List { - return manhwaLatinoSiteParser.getChapterListParse(response) - } - - /** - * Returns a chapter from the given element. - * - * @param element an element obtained from [chapterListSelector]. - */ - override fun chapterFromElement(element: Element) = throw Exception("Not used") - - /** - * Returns the details of the manga from the given [document]. - * - * @param document the parsed document. - */ - override fun mangaDetailsParse(document: Document): SManga { - return manhwaLatinoSiteParser.getMangaDetails(document) - } - - /** - * Returns the request for getting the page list. Override only if it's needed to override the - * url, send different headers or request method like POST. - * (Request to Webseite with comic) - * - * @param chapter the chapter whose page list has to be fetched. - */ - override fun pageListRequest(chapter: SChapter): Request { - return GET(baseUrl + chapter.url, headers) - } - - /** - * Parses the response from the site and returns the page list. - * (Parse the comic pages from the website with the chapter) - * - * @param response the response from the site. - */ - override fun pageListParse(response: Response): List { - return manhwaLatinoSiteParser.getPageListParse(response) - } - - /** - * Returns a page list from the given document. - * - * @param document the parsed document. - */ - override fun pageListParse(document: Document) = throw Exception("Not Used") - - override fun imageUrlParse(document: Document) = throw Exception("Not Used") - - /** - * Returns the list of filters for the source. - */ - override fun getFilterList() = FilterList( - Filter.Header("NOTA: ¡La búsqueda de títulos no funciona!"), // "Title search not working" - Filter.Separator(), - GenreTagFilter(), -// LetterFilter(), -// StatusFilter(), -// SortFilter() - ) -} diff --git a/src/es/mahnwalatino/src/eu/kanade/tachiyomi/extension/es/manhwalatino/ManhwaLatinoSiteParser.kt b/src/es/mahnwalatino/src/eu/kanade/tachiyomi/extension/es/manhwalatino/ManhwaLatinoSiteParser.kt deleted file mode 100644 index 41157b998..000000000 --- a/src/es/mahnwalatino/src/eu/kanade/tachiyomi/extension/es/manhwalatino/ManhwaLatinoSiteParser.kt +++ /dev/null @@ -1,372 +0,0 @@ -package eu.kanade.tachiyomi.extension.es.manhwalatino - -import android.net.Uri -import android.util.Base64 -import eu.kanade.tachiyomi.extension.es.manhwalatino.filters.UriFilter -import eu.kanade.tachiyomi.lib.cryptoaes.CryptoAES -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservableSuccess -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.util.asJsoup -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.jsonArray -import kotlinx.serialization.json.jsonObject -import kotlinx.serialization.json.jsonPrimitive -import okhttp3.Headers -import okhttp3.OkHttpClient -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import org.jsoup.select.Elements -import rx.Observable -import uy.kohesive.injekt.injectLazy -import java.text.SimpleDateFormat -import java.util.Calendar -import java.util.Locale - -class ManhwaLatinoSiteParser( - private val baseUrl: String, - private val client: OkHttpClient, - private val headers: Headers, -) { - - /** - * Search Type - */ - enum class SearchType { - SEARCH_FREE, SEARCH_FILTER - } - - /** - * Type of search ( FREE, FILTER) - */ - private var searchType = SearchType.SEARCH_FREE - - private val json by injectLazy() - - /** - * The Latest Updates are in a Slider, this Methods get a Manga from the slide - */ - fun getMangaFromLastTranslatedSlide(element: Element): SManga { - val manga = SManga.create() - manga.url = - getUrlWithoutDomain( - element.select(MLConstants.latestUpdatesSelectorUrl).first()!!.attr("abs:href"), - ) - manga.title = element.select(MLConstants.latestUpdatesSelectorTitle).text().trim() - manga.thumbnail_url = getImage(element.select(MLConstants.latestUpdatesSelectorThumbnailUrl)).replace("//", "/") - return manga - } - - /** - * The Latest Updates has only one site - */ - fun latestUpdatesHasNextPages() = false - - /** - * Search the information of a Manga with the URL-Address - */ - fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { - return if (query.startsWith(MLConstants.PREFIX_MANGA_ID_SEARCH)) { - val realQuery = query.removePrefix(MLConstants.PREFIX_MANGA_ID_SEARCH) - client.newCall(GET("$baseUrl/$realQuery", headers)) - .asObservableSuccess().map { response -> - val details = getMangaDetails(response.asJsoup()) - details.url = "/$realQuery" - MangasPage(listOf(details), false) - } - } else { - val request = GET(searchMangaRequest(page, query, filters).toString(), headers) - client.newCall(request) - .asObservableSuccess().map { response -> - searchMangaParse(response) - } - } - } - - /** - * Get eine Liste mit Mangas from Search Site - */ - private fun getMangasFromSearchSite(document: Document): List { - return document.select(MLConstants.searchSiteMangasHTMLSelector).map { - val manga = SManga.create() - manga.url = getUrlWithoutDomain( - it.select(MLConstants.searchPageUrlHTMLSelector).attr("abs:href"), - ) - manga.title = it.select(MLConstants.searchPageTitleHTMLSelector).text().trim() - manga.thumbnail_url = getImage(it.select(MLConstants.searchPageThumbnailUrlMangaHTMLSelector)) - manga - } - } - - /** - * Get eine Liste mit Mangas from Genre Site - */ - private fun getMangasFromGenreSite(document: Document): List { - return document.select(MLConstants.genreSiteMangasHTMLSelector).map { getMangaFromList(it) } - } - - /** - * Parse The Information from Mangas From Popular or Genre Site - * Title, Address and thumbnail_url - */ - fun getMangaFromList(element: Element): SManga { - val manga = SManga.create() - manga.url = getUrlWithoutDomain( - element.select(MLConstants.popularGenreUrlHTMLSelector).attr("abs:href"), - ) - manga.title = element.select(MLConstants.popularGenreTitleHTMLSelector).text().trim() - manga.thumbnail_url = getImage(element.select(MLConstants.popularGenreThumbnailUrlMangaHTMLSelector)) - return manga - } - - /** - * Get The Details of a Manga Main Website - * Description, genre, tags, picture (thumbnail_url) - * status... - */ - fun getMangaDetails(document: Document): SManga { - val manga = SManga.create() - - val titleElements = document.select("#manga-title h1") - val descriptionList = - document.select(MLConstants.mangaDetailsDescriptionHTMLSelector).map { it.text() } - val author = document.select(MLConstants.mangaDetailsAuthorHTMLSelector).text() - val artist = document.select(MLConstants.mangaDetailsArtistHTMLSelector).text() - - val genrelist = document.select(MLConstants.mangaDetailsGenreHTMLSelector).map { it.text() } - val tagList = document.select(MLConstants.mangaDetailsTagsHTMLSelector).map { it.text() } - val genreTagList = genrelist + tagList - - manga.title = titleElements.last()!!.ownText().trim() - manga.thumbnail_url = getImage(document.select(MLConstants.mangaDetailsThumbnailUrlHTMLSelector)) - manga.description = descriptionList.joinToString("\n") - manga.author = author.ifBlank { "Autor Desconocido" } - manga.artist = artist - manga.genre = genreTagList.joinToString(", ") - manga.status = findMangaStatus(tagList, document.select(MLConstants.mangaDetailsAttributes)) - return manga - } - - private fun findMangaStatus(tagList: List, elements: Elements): Int { - if (tagList.contains("Fin")) { - return SManga.COMPLETED - } - elements.forEach { element -> - val key = element.select("div.summary-heading h5").text().trim() - val value = element.select("div.summary-content").text().trim() - - if (key == "Estado del comic") { - return when (value) { - "Publicandose" -> SManga.ONGOING - else -> SManga.UNKNOWN - } - } - } - return SManga.UNKNOWN - } - - /** - * Parses the response from the site and returns a list of chapters. - * - * @param response the response from the site. - */ - fun getChapterListParse(response: Response): List { - return response.asJsoup().select(MLConstants.chapterListParseSelector).map { element -> - // Link to the Chapter with the info (address and chapter title) - val chapterInfo = element.select(MLConstants.chapterLinkParser) - // Chaptername - val chapterName = chapterInfo.text().trim() - // release date came as text with format dd/mm/yyyy from a link or dd/mm/yyyy - val chapterReleaseDate = getChapterReleaseDate(element) - SChapter.create().apply { - name = chapterName - chapter_number = getChapterNumber(chapterName) - url = getUrlWithoutDomain(chapterInfo.attr("abs:href")) - date_upload = parseChapterReleaseDate(chapterReleaseDate) - } - } - } - - /** - * Get the number of Chapter from Chaptername - */ - private fun getChapterNumber(chapterName: String): Float = - Regex("""\d+""").find(chapterName)?.value.toString().trim().toFloat() - - /** - * Get The String with the information about the Release date of the Chapter - */ - private fun getChapterReleaseDate(element: Element): String { - val chapterReleaseDateLink = - element.select(MLConstants.chapterReleaseDateLinkParser).attr("title") - val chapterReleaseDateI = element.select(MLConstants.chapterReleaseDateIParser).text() - return when { - chapterReleaseDateLink.isNotEmpty() -> chapterReleaseDateLink - chapterReleaseDateI.isNotEmpty() -> chapterReleaseDateI - else -> "" - } - } - - /** - * Transform String with the Date of Release into Long format - */ - private fun parseChapterReleaseDate(releaseDateStr: String): Long { - val regExSecs = Regex("""hace\s+(\d+)\s+segundos?""") - val regExMins = Regex("""hace\s+(\d+)\s+mins?""") - val regExHours = Regex("""hace\s+(\d+)\s+horas?""") - val regExDays = Regex("""hace\s+(\d+)\s+días?""") - val regExDate = Regex("""\d+/\d+/\d+""") - - return when { - regExSecs.containsMatchIn(releaseDateStr) -> - getReleaseTime(releaseDateStr, Calendar.SECOND) - - regExMins.containsMatchIn(releaseDateStr) -> - getReleaseTime(releaseDateStr, Calendar.MINUTE) - - regExHours.containsMatchIn(releaseDateStr) -> - getReleaseTime(releaseDateStr, Calendar.HOUR) - - regExDays.containsMatchIn(releaseDateStr) -> - getReleaseTime(releaseDateStr, Calendar.DAY_OF_YEAR) - - regExDate.containsMatchIn(releaseDateStr) -> - SimpleDateFormat("dd/MM/yyyy", Locale.getDefault()).parse(releaseDateStr)!!.time - - else -> 0 - } - } - - /** - * Extract the Release time from a Text String - * Format of the String "hace\s+\d+\s(segundo|minuto|hora|dia)s?" - */ - private fun getReleaseTime(releaseDateStr: String, timeType: Int): Long { - val releaseTimeAgo = Regex("""\d+""").find(releaseDateStr)?.value.toString().toInt() - val calendar = Calendar.getInstance() - calendar.add(timeType, -releaseTimeAgo) - return calendar.timeInMillis - } - - /** - * Parses the response from the site and returns the page list. - * (Parse the comic pages from the website with the chapter) - * - * @param response the response from the site. - */ - fun getPageListParse(response: Response): List { - val document = response.asJsoup() - val scripUrl = document.select(MLConstants.pageListParseSelector).attr("src") - val script = client.newCall(GET(scripUrl, headers)).execute().asJsoup().text() - - val password = script - .substringAfter("wpmangaprotectornonce='") - .substringBefore("';") - - val chapterData = json.parseToJsonElement( - script - .substringAfter("chapter_data='") - .substringBefore("';") - .replace("\\/", "/"), - ).jsonObject - - val unsaltedCiphertext = Base64.decode(chapterData["ct"]!!.jsonPrimitive.content, Base64.DEFAULT) - val salt = chapterData["s"]!!.jsonPrimitive.content.decodeHex() - val ciphertext = SALTED + salt + unsaltedCiphertext - - val rawImgArray = CryptoAES.decrypt(Base64.encodeToString(ciphertext, Base64.DEFAULT), password) - val imgArrayString = json.parseToJsonElement(rawImgArray).jsonPrimitive.content - val imgArray = json.parseToJsonElement(imgArrayString).jsonArray - - return imgArray.mapIndexed { idx, it -> - Page(idx, document.location(), it.jsonPrimitive.content) - } - } - - /** - * Returns the request for the search manga given the page. - * - * @param page the page number to retrieve. - * @param query the search query. - * @param filters the list of filters to apply. - */ - fun searchMangaRequest(page: Int, query: String, filters: FilterList): Uri.Builder { - val uri = Uri.parse(baseUrl).buildUpon() - if (query.isNotBlank()) { - searchType = SearchType.SEARCH_FREE - uri.appendQueryParameter("s", query) - .appendQueryParameter("post_type", "wp-manga") - } else { - searchType = SearchType.SEARCH_FILTER - // Append uri filters - filters.forEach { - if (it is UriFilter) { - it.addToUri(uri) - } - } - uri.appendPath("page").appendPath(page.toString()) - } - return uri - } - - /** - * Parses the response from the site and returns a [MangasPage] object. - * - * @param response the response from the site. - */ - fun searchMangaParse(response: Response): MangasPage { - val document = response.asJsoup() - val hasNextPages = hasNextPages(document) - - val mangas: List = when (searchType) { - SearchType.SEARCH_FREE -> - getMangasFromSearchSite(document) - SearchType.SEARCH_FILTER -> - getMangasFromGenreSite(document) - } - - return MangasPage(mangas, hasNextPages) - } - - /** - * Check if there ir another page to show - */ - private fun hasNextPages(document: Document): Boolean { - return !document.select(MLConstants.searchMangaNextPageSelector).isEmpty() - } - - /** - * Create a Address url without the base url. - */ - private fun getUrlWithoutDomain(url: String) = url.substringAfter(baseUrl) - - /** - * Extract the Image from the Html Element - * The website changes often the attr of the images - * data-src or src - */ - private fun getImage(elements: Elements): String { - var imageUrl: String = elements.attr(MLConstants.imageAttribute) - if (imageUrl.isEmpty()) { - imageUrl = elements.attr("abs:src") - } - return imageUrl - } - - private fun String.decodeHex(): ByteArray { - check(length % 2 == 0) { "Must have an even length" } - - return chunked(2) - .map { it.toInt(16).toByte() } - .toByteArray() - } - - companion object { - val SALTED = "Salted__".toByteArray(Charsets.UTF_8) - } -} diff --git a/src/es/mahnwalatino/src/eu/kanade/tachiyomi/extension/es/manhwalatino/ManhwaLatinoUrlActivity.kt b/src/es/mahnwalatino/src/eu/kanade/tachiyomi/extension/es/manhwalatino/ManhwaLatinoUrlActivity.kt deleted file mode 100644 index ffb1e0a4b..000000000 --- a/src/es/mahnwalatino/src/eu/kanade/tachiyomi/extension/es/manhwalatino/ManhwaLatinoUrlActivity.kt +++ /dev/null @@ -1,37 +0,0 @@ -package eu.kanade.tachiyomi.extension.es.manhwalatino - -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 ManhwaLatinoUrlActivity : Activity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - val pathSegments = intent?.data?.pathSegments - - if (pathSegments != null && pathSegments.size > 1) { - val type = pathSegments[0] - val mangaName = pathSegments[1] - - val mainIntent = Intent().apply { - action = "eu.kanade.tachiyomi.SEARCH" - putExtra("query", "${MLConstants.PREFIX_MANGA_ID_SEARCH}$type/$mangaName") - putExtra("filter", packageName) - } - - try { - startActivity(mainIntent) - } catch (e: ActivityNotFoundException) { - Log.e("MLUrlActivity", e.toString()) - } - } else { - Log.e("TMOUrlActivity", "could not parse uri from intent $intent") - } - - finish() - exitProcess(0) - } -} diff --git a/src/es/mahnwalatino/src/eu/kanade/tachiyomi/extension/es/manhwalatino/filters/GenreTagFilter.kt b/src/es/mahnwalatino/src/eu/kanade/tachiyomi/extension/es/manhwalatino/filters/GenreTagFilter.kt deleted file mode 100644 index eba78742e..000000000 --- a/src/es/mahnwalatino/src/eu/kanade/tachiyomi/extension/es/manhwalatino/filters/GenreTagFilter.kt +++ /dev/null @@ -1,48 +0,0 @@ -package eu.kanade.tachiyomi.extension.es.manhwalatino.filters - -class GenreTagFilter : UriPartFilter( - "Género o Tag", - "filtro", - arrayOf( - Pair("manga-genre/manga", "Todas"), - Pair("manga-genre/accion", "Acción"), - Pair("manga-genre/adulto", "Adulto"), - Pair("manga-genre/aventura", "Aventura"), - Pair("manga-genre/bondage", "Bondage"), - Pair("manga-genre/cambio-de-pareja", "Cambio de pareja"), - Pair("manga-genre/chantaje", "Chantaje"), - Pair("manga-genre/ciencia-ficcion", "Ciencia Ficción"), - Pair("manga-genre/comedia", "Comedia"), - Pair("manga-genre/doujinshi", "Doujinshi"), - Pair("manga-genre/drama", "Drama"), - Pair("manga-tag/lunes", "Dia Publicación Lunes"), - Pair("manga-tag/martes", "Dia Publicación Martes"), - Pair("manga-tag/miercoles", "Dia Publicación Miércoles"), - Pair("manga-tag/jueves", "Dia Publicación Jueves"), - Pair("manga-tag/viernes", "Dia Publicación Viernes"), - Pair("manga-tag/sabado", "Dia Publicación Sábado"), - Pair("manga-tag/domingo", "Dia Publicación Domingo"), - Pair("manga-genre/ecchi", "Ecchi"), - Pair("manga-tag/espanol", "Español"), - Pair("manga-genre/exhibicion", "Exhibición"), - Pair("manga-genre/familia", "Familia"), - Pair("manga-genre/fantasia", "Fantasia"), - Pair("manga-tag/fin", "Finalizado"), - Pair("manga-genre/harem", "Harem"), - Pair("manga-genre/manga", "Manga"), - Pair("manga-genre/manhua", "Manhua"), - Pair("manga-genre/manhwa", "Manhwa"), - Pair("manga-genre/misterio", "Misterio"), - Pair("manga-genre/ntr", "Ntr"), - Pair("manga-genre/obsenidad", "Obsenidad"), - Pair("manga-genre/relato vida", "Relato vida"), - Pair("manga-genre/romance", "Romance"), - Pair("manga-genre/sangre", "Sangre"), - Pair("manga-genre/sexo-forzado", "Sexo forzado"), - Pair("manga-genre/sometimiento", "Sometimiento"), - Pair("manga-genre/tragedia", "Tragedia"), - Pair("manga-genre/venganza", "Venganza"), - Pair("manga-genre/vida-escolar", "Vida Escolar"), - Pair("manga-genre/webtoon", "Webtoon"), - ), -) diff --git a/src/es/mahnwalatino/src/eu/kanade/tachiyomi/extension/es/manhwalatino/filters/UriFilter.kt b/src/es/mahnwalatino/src/eu/kanade/tachiyomi/extension/es/manhwalatino/filters/UriFilter.kt deleted file mode 100644 index 0d47ed205..000000000 --- a/src/es/mahnwalatino/src/eu/kanade/tachiyomi/extension/es/manhwalatino/filters/UriFilter.kt +++ /dev/null @@ -1,10 +0,0 @@ -package eu.kanade.tachiyomi.extension.es.manhwalatino.filters - -import android.net.Uri - -/** - * Represents a filter that is able to modify a URI. - */ -interface UriFilter { - fun addToUri(uri: Uri.Builder) -} diff --git a/src/es/mahnwalatino/src/eu/kanade/tachiyomi/extension/es/manhwalatino/filters/UriPartFilter.kt b/src/es/mahnwalatino/src/eu/kanade/tachiyomi/extension/es/manhwalatino/filters/UriPartFilter.kt deleted file mode 100644 index 7d31c95f2..000000000 --- a/src/es/mahnwalatino/src/eu/kanade/tachiyomi/extension/es/manhwalatino/filters/UriPartFilter.kt +++ /dev/null @@ -1,27 +0,0 @@ -package eu.kanade.tachiyomi.extension.es.manhwalatino.filters - -import android.net.Uri -import eu.kanade.tachiyomi.source.model.Filter - -/** - * Class that creates a select filter. Each entry in the dropdown has a name and a display name. - * If an entry is selected it is appended as a query parameter onto the end of the URI. - * If `firstIsUnspecified` is set to true, if the first entry is selected, nothing will be appended on the the URI. - */ -// vals: -open class UriPartFilter( - displayName: String, - private val uriParam: String, - private val vals: Array>, - private val firstIsUnspecified: Boolean = true, - defaultValue: Int = 0, -) : - Filter.Select(displayName, vals.map { it.second }.toTypedArray(), defaultValue), - UriFilter { - override fun addToUri(uri: Uri.Builder) { - if (state != 0 || !firstIsUnspecified) { - val filter = vals[state].first - uri.appendPath(filter) - } - } -}