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>
This commit is contained in:
seew3l 2023-02-28 14:44:11 -05:00 committed by GitHub
parent 1ff1a22fff
commit 6344668319
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 75 additions and 852 deletions

View File

@ -0,0 +1,3 @@
dependencies {
implementation(project(':lib-cryptoaes'))
}

View File

Before

Width:  |  Height:  |  Size: 8.3 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

View File

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View File

Before

Width:  |  Height:  |  Size: 202 KiB

After

Width:  |  Height:  |  Size: 202 KiB

View File

@ -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<Page> {
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)
}
}

View File

@ -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),

View File

@ -1,24 +0,0 @@
<?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=".es.manhwalatino.ManhwaLatinoUrlActivity"
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="manhwa-es.com"
android:pathPattern="/..*/..*"
android:scheme="https" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@ -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'))
}

View File

@ -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"
}

View File

@ -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 <a> 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 <a> 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 <a> 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<MangasPage> {
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<SChapter> {
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<Page> {
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()
)
}

View File

@ -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<Json>()
/**
* 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<MangasPage> {
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<SManga> {
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<SManga> {
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<String>, 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<SChapter> {
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 <i>dd/mm/yyyy</i>
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<Page> {
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<SManga> = 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)
}
}

View File

@ -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)
}
}

View File

@ -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"),
),
)

View File

@ -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)
}

View File

@ -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: <name, display>
open class UriPartFilter(
displayName: String,
private val uriParam: String,
private val vals: Array<Pair<String, String>>,
private val firstIsUnspecified: Boolean = true,
defaultValue: Int = 0,
) :
Filter.Select<String>(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)
}
}
}