Remove some sources that are in a cat and mouse game. (#7065)
@ -1,2 +0,0 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<manifest package="eu.kanade.tachiyomi.extension" />
 | 
			
		||||
@ -1,16 +0,0 @@
 | 
			
		||||
apply plugin: 'com.android.application'
 | 
			
		||||
apply plugin: 'kotlin-android'
 | 
			
		||||
 | 
			
		||||
ext {
 | 
			
		||||
    extName = 'HQ Dragon'
 | 
			
		||||
    pkgNameSuffix = 'pt.hqdragon'
 | 
			
		||||
    extClass = '.HQDragon'
 | 
			
		||||
    extVersionCode = 4
 | 
			
		||||
    libVersion = '1.2'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
    implementation project(':lib-ratelimit')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
apply from: "$rootDir/common.gradle"
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 4.0 KiB  | 
| 
		 Before Width: | Height: | Size: 2.0 KiB  | 
| 
		 Before Width: | Height: | Size: 5.8 KiB  | 
| 
		 Before Width: | Height: | Size: 11 KiB  | 
| 
		 Before Width: | Height: | Size: 16 KiB  | 
| 
		 Before Width: | Height: | Size: 98 KiB  | 
@ -1,206 +0,0 @@
 | 
			
		||||
package eu.kanade.tachiyomi.extension.pt.hqdragon
 | 
			
		||||
 | 
			
		||||
import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor
 | 
			
		||||
import eu.kanade.tachiyomi.network.GET
 | 
			
		||||
import eu.kanade.tachiyomi.network.POST
 | 
			
		||||
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 okhttp3.FormBody
 | 
			
		||||
import okhttp3.Headers
 | 
			
		||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
 | 
			
		||||
import okhttp3.OkHttpClient
 | 
			
		||||
import okhttp3.Request
 | 
			
		||||
import okhttp3.Response
 | 
			
		||||
import org.jsoup.nodes.Document
 | 
			
		||||
import org.jsoup.nodes.Element
 | 
			
		||||
import rx.Observable
 | 
			
		||||
import java.util.concurrent.TimeUnit
 | 
			
		||||
 | 
			
		||||
class HQDragon : ParsedHttpSource() {
 | 
			
		||||
 | 
			
		||||
    override val name = "HQ Dragon"
 | 
			
		||||
 | 
			
		||||
    override val baseUrl = "https://hqdragon.com"
 | 
			
		||||
 | 
			
		||||
    override val lang = "pt-BR"
 | 
			
		||||
 | 
			
		||||
    override val supportsLatest = true
 | 
			
		||||
 | 
			
		||||
    override val client: OkHttpClient = network.cloudflareClient.newBuilder()
 | 
			
		||||
        .addInterceptor(RateLimitInterceptor(1, 2, TimeUnit.SECONDS))
 | 
			
		||||
        .build()
 | 
			
		||||
 | 
			
		||||
    override fun headersBuilder(): Headers.Builder = Headers.Builder()
 | 
			
		||||
        .add("Accept", ACCEPT)
 | 
			
		||||
        .add("Accept-Language", ACCEPT_LANGUAGE)
 | 
			
		||||
        .add("Referer", "$baseUrl/")
 | 
			
		||||
 | 
			
		||||
    // Popular
 | 
			
		||||
 | 
			
		||||
    // Top 10
 | 
			
		||||
    override fun popularMangaRequest(page: Int): Request {
 | 
			
		||||
        return GET(baseUrl, headers)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun popularMangaParse(response: Response): MangasPage {
 | 
			
		||||
        val results = super.popularMangaParse(response)
 | 
			
		||||
 | 
			
		||||
        if (results.mangas.isEmpty()) {
 | 
			
		||||
            throw Exception(BLOCK_MESSAGE)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return results
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun popularMangaSelector() = "h4:contains(Top 10) + ol.mb-0 li a"
 | 
			
		||||
 | 
			
		||||
    override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply {
 | 
			
		||||
        title = element.text()
 | 
			
		||||
        setUrlWithoutDomain(element.attr("href"))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun popularMangaNextPageSelector(): String? = null
 | 
			
		||||
 | 
			
		||||
    // Latest
 | 
			
		||||
 | 
			
		||||
    override fun fetchLatestUpdates(page: Int): Observable<MangasPage> {
 | 
			
		||||
        return super.fetchLatestUpdates(page)
 | 
			
		||||
            .map { results -> results.copy(hasNextPage = page < 5) }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun latestUpdatesRequest(page: Int): Request {
 | 
			
		||||
        val formBody = FormBody.Builder()
 | 
			
		||||
            .add("pagina", page.toString())
 | 
			
		||||
            .build()
 | 
			
		||||
 | 
			
		||||
        val headers = headersBuilder()
 | 
			
		||||
            .add("Content-Length", formBody.contentLength().toString())
 | 
			
		||||
            .add("Content-Type", formBody.contentType().toString())
 | 
			
		||||
            .add("Origin", baseUrl)
 | 
			
		||||
            .add("X-Requested-With", "XMLHttpRequest")
 | 
			
		||||
            .set("Accept", "*/*")
 | 
			
		||||
            .build()
 | 
			
		||||
 | 
			
		||||
        return POST("$baseUrl/assets/php/index_paginar.php", headers, formBody)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun latestUpdatesParse(response: Response): MangasPage {
 | 
			
		||||
        val results = super.latestUpdatesParse(response)
 | 
			
		||||
 | 
			
		||||
        if (results.mangas.isEmpty()) {
 | 
			
		||||
            throw Exception(BLOCK_MESSAGE)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return results
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun latestUpdatesSelector() = "a:has(img)"
 | 
			
		||||
 | 
			
		||||
    override fun latestUpdatesFromElement(element: Element): SManga = SManga.create().apply {
 | 
			
		||||
        val image = element.select("img").first()
 | 
			
		||||
 | 
			
		||||
        title = image.attr("alt")
 | 
			
		||||
        thumbnail_url = image.attr("abs:src")
 | 
			
		||||
        setUrlWithoutDomain(element.attr("href"))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun latestUpdatesNextPageSelector(): String? = null
 | 
			
		||||
 | 
			
		||||
    // Search
 | 
			
		||||
 | 
			
		||||
    override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
 | 
			
		||||
        val url = "$baseUrl/pesquisa".toHttpUrlOrNull()!!.newBuilder()
 | 
			
		||||
            .addQueryParameter("titulo", query)
 | 
			
		||||
 | 
			
		||||
        return GET(url.toString(), headers)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun searchMangaParse(response: Response): MangasPage {
 | 
			
		||||
        val results = super.searchMangaParse(response)
 | 
			
		||||
 | 
			
		||||
        if (results.mangas.isEmpty()) {
 | 
			
		||||
            throw Exception(BLOCK_MESSAGE)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return results
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun searchMangaSelector() = "div.col-sm-6.col-md-3:has(img.img-thumbnail)"
 | 
			
		||||
 | 
			
		||||
    override fun searchMangaFromElement(element: Element): SManga = SManga.create().apply {
 | 
			
		||||
        val link = element.select("a + a").first()
 | 
			
		||||
 | 
			
		||||
        title = link.text()
 | 
			
		||||
        thumbnail_url = element.select("img").first().attr("abs:src")
 | 
			
		||||
        setUrlWithoutDomain(link.attr("href"))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun searchMangaNextPageSelector(): String? = null
 | 
			
		||||
 | 
			
		||||
    // Manga summary page
 | 
			
		||||
 | 
			
		||||
    override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply {
 | 
			
		||||
        val infoElement = document.select("div.blog-post div.row").firstOrNull()
 | 
			
		||||
            ?: throw Exception(BLOCK_MESSAGE)
 | 
			
		||||
 | 
			
		||||
        title = infoElement.select("h3").first().text()
 | 
			
		||||
        author = infoElement.select("p:contains(Editora:)").first().textWithoutLabel()
 | 
			
		||||
        status = infoElement.select("p:contains(Status:) span").first().text().toStatus()
 | 
			
		||||
        description = infoElement.select("p:contains(Sinopse:)").first().ownText()
 | 
			
		||||
        thumbnail_url = infoElement.select("div.col-md-4 .img-fluid").first().attr("src")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Chapters
 | 
			
		||||
 | 
			
		||||
    override fun chapterListSelector() = "table.table tr a"
 | 
			
		||||
 | 
			
		||||
    override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply {
 | 
			
		||||
        name = element.text().replace("Ler ", "")
 | 
			
		||||
        setUrlWithoutDomain(element.attr("href"))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Pages
 | 
			
		||||
 | 
			
		||||
    override fun pageListParse(document: Document): List<Page> {
 | 
			
		||||
        return document.select("img.img-responsive.img-manga")
 | 
			
		||||
            .filter { it.attr("src").contains("/leitor/") }
 | 
			
		||||
            .mapIndexed { i, element ->
 | 
			
		||||
                Page(i, document.location(), element.absUrl("src"))
 | 
			
		||||
            }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun imageUrlParse(document: Document) = ""
 | 
			
		||||
 | 
			
		||||
    override fun imageRequest(page: Page): Request {
 | 
			
		||||
        val newHeaders = headersBuilder()
 | 
			
		||||
            .set("Accept", ACCEPT_IMAGE)
 | 
			
		||||
            .set("Referer", page.url)
 | 
			
		||||
            .build()
 | 
			
		||||
 | 
			
		||||
        return GET(page.imageUrl!!, newHeaders)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun Element.textWithoutLabel(): String = text()!!.substringAfter(":").trim()
 | 
			
		||||
 | 
			
		||||
    private fun String.toStatus(): Int = when {
 | 
			
		||||
        contains("Ativo") -> SManga.ONGOING
 | 
			
		||||
        contains("Completo") -> SManga.COMPLETED
 | 
			
		||||
        else -> SManga.UNKNOWN
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        private const val ACCEPT = "text/html,application/xhtml+xml,application/xml;q=0.9," +
 | 
			
		||||
            "image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"
 | 
			
		||||
        private const val ACCEPT_IMAGE = "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8"
 | 
			
		||||
        private const val ACCEPT_LANGUAGE = "pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7,es;q=0.6,gl;q=0.5"
 | 
			
		||||
        private const val USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " +
 | 
			
		||||
            "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36"
 | 
			
		||||
 | 
			
		||||
        private const val BLOCK_MESSAGE = "O site está bloqueando o Tachiyomi. " +
 | 
			
		||||
            "Migre para outra fonte caso o problema persistir."
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,2 +0,0 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<manifest package="eu.kanade.tachiyomi.extension" />
 | 
			
		||||
@ -1,16 +0,0 @@
 | 
			
		||||
apply plugin: 'com.android.application'
 | 
			
		||||
apply plugin: 'kotlin-android'
 | 
			
		||||
 | 
			
		||||
ext {
 | 
			
		||||
    extName = 'Mangá Host'
 | 
			
		||||
    pkgNameSuffix = 'pt.mangahost'
 | 
			
		||||
    extClass = '.MangaHost'
 | 
			
		||||
    extVersionCode = 24
 | 
			
		||||
    libVersion = '1.2'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
    implementation project(':lib-ratelimit')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
apply from: "$rootDir/common.gradle"
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 2.8 KiB  | 
| 
		 Before Width: | Height: | Size: 1.6 KiB  | 
| 
		 Before Width: | Height: | Size: 3.9 KiB  | 
| 
		 Before Width: | Height: | Size: 7.3 KiB  | 
| 
		 Before Width: | Height: | Size: 10 KiB  | 
| 
		 Before Width: | Height: | Size: 49 KiB  | 
@ -1,288 +0,0 @@
 | 
			
		||||
package eu.kanade.tachiyomi.extension.pt.mangahost
 | 
			
		||||
 | 
			
		||||
import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor
 | 
			
		||||
import eu.kanade.tachiyomi.network.GET
 | 
			
		||||
import eu.kanade.tachiyomi.network.asObservable
 | 
			
		||||
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 okhttp3.Call
 | 
			
		||||
import okhttp3.Headers
 | 
			
		||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
 | 
			
		||||
import okhttp3.Interceptor
 | 
			
		||||
import okhttp3.OkHttpClient
 | 
			
		||||
import okhttp3.Request
 | 
			
		||||
import okhttp3.Response
 | 
			
		||||
import org.jsoup.nodes.Document
 | 
			
		||||
import org.jsoup.nodes.Element
 | 
			
		||||
import org.jsoup.select.Elements
 | 
			
		||||
import rx.Observable
 | 
			
		||||
import java.text.ParseException
 | 
			
		||||
import java.text.SimpleDateFormat
 | 
			
		||||
import java.util.Locale
 | 
			
		||||
import java.util.concurrent.TimeUnit
 | 
			
		||||
 | 
			
		||||
class MangaHost : ParsedHttpSource() {
 | 
			
		||||
 | 
			
		||||
    // Hardcode the id because the name was wrong and the language wasn't specific.
 | 
			
		||||
    override val id: Long = 3926812845500643354
 | 
			
		||||
 | 
			
		||||
    override val name = "Mangá Host"
 | 
			
		||||
 | 
			
		||||
    override val baseUrl = "https://mangahostz.com"
 | 
			
		||||
 | 
			
		||||
    override val lang = "pt-BR"
 | 
			
		||||
 | 
			
		||||
    override val supportsLatest = true
 | 
			
		||||
 | 
			
		||||
    override val client: OkHttpClient = network.cloudflareClient.newBuilder()
 | 
			
		||||
        .addInterceptor(RateLimitInterceptor(1, 3, TimeUnit.SECONDS))
 | 
			
		||||
        .addInterceptor(::blockMessageIntercept)
 | 
			
		||||
        .build()
 | 
			
		||||
 | 
			
		||||
    override fun headersBuilder(): Headers.Builder = Headers.Builder()
 | 
			
		||||
        .add("Accept", ACCEPT)
 | 
			
		||||
        .add("Accept-Language", ACCEPT_LANGUAGE)
 | 
			
		||||
        .add("Referer", baseUrl)
 | 
			
		||||
        .add("User-Agent", USER_AGENT)
 | 
			
		||||
 | 
			
		||||
    private fun genericMangaFromElement(element: Element): SManga = SManga.create().apply {
 | 
			
		||||
        val thumbnailEl = element.select("img")
 | 
			
		||||
        val thumbnailAttr = if (thumbnailEl.hasAttr("data-path")) "data-path" else "src"
 | 
			
		||||
 | 
			
		||||
        title = element.attr("title").withoutLanguage()
 | 
			
		||||
        thumbnail_url = thumbnailEl.attr(thumbnailAttr).toLargeUrl()
 | 
			
		||||
        setUrlWithoutDomain(element.attr("href"))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun popularMangaRequest(page: Int): Request {
 | 
			
		||||
        val listPath = if (page == 1) "" else "/mais-visualizados/page/${page - 1}"
 | 
			
		||||
        val newHeaders = headersBuilder()
 | 
			
		||||
            .set("Referer", "$baseUrl/mangas$listPath")
 | 
			
		||||
            .build()
 | 
			
		||||
 | 
			
		||||
        val pageStr = if (page != 1) "/page/$page" else ""
 | 
			
		||||
        return GET("$baseUrl/mangas/mais-visualizados$pageStr", newHeaders)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun popularMangaParse(response: Response): MangasPage {
 | 
			
		||||
        val results = super.popularMangaParse(response)
 | 
			
		||||
 | 
			
		||||
        if (results.mangas.isEmpty()) {
 | 
			
		||||
            throw Exception(BLOCK_MESSAGE)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return results
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun popularMangaSelector(): String = "div#dados div.manga-block div.manga-block-left a"
 | 
			
		||||
 | 
			
		||||
    override fun popularMangaFromElement(element: Element): SManga = genericMangaFromElement(element)
 | 
			
		||||
 | 
			
		||||
    override fun popularMangaNextPageSelector() = "div.wp-pagenavi:has(a.nextpostslink)"
 | 
			
		||||
 | 
			
		||||
    override fun latestUpdatesRequest(page: Int): Request {
 | 
			
		||||
        val listPath = if (page == 1) "" else "/lancamentos/page/${page - 1}"
 | 
			
		||||
        val newHeaders = headersBuilder()
 | 
			
		||||
            .set("Referer", baseUrl + listPath)
 | 
			
		||||
            .build()
 | 
			
		||||
 | 
			
		||||
        val pageStr = if (page != 1) "/page/$page" else ""
 | 
			
		||||
        return GET("$baseUrl/lancamentos$pageStr", newHeaders)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun latestUpdatesParse(response: Response): MangasPage {
 | 
			
		||||
        val results = super.latestUpdatesParse(response)
 | 
			
		||||
 | 
			
		||||
        if (results.mangas.isEmpty()) {
 | 
			
		||||
            throw Exception(BLOCK_MESSAGE)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return results
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun latestUpdatesSelector() = "div#dados div.w-row div.column-img a"
 | 
			
		||||
 | 
			
		||||
    override fun latestUpdatesFromElement(element: Element): SManga = genericMangaFromElement(element)
 | 
			
		||||
 | 
			
		||||
    override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector()
 | 
			
		||||
 | 
			
		||||
    override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
 | 
			
		||||
        val url = "$baseUrl/find".toHttpUrlOrNull()!!.newBuilder()
 | 
			
		||||
            .addQueryParameter("this", query)
 | 
			
		||||
 | 
			
		||||
        return GET(url.toString(), headers)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun searchMangaSelector() = "table.table-search > tbody > tr > td:eq(0) > a"
 | 
			
		||||
 | 
			
		||||
    override fun searchMangaFromElement(element: Element): SManga = genericMangaFromElement(element)
 | 
			
		||||
 | 
			
		||||
    override fun searchMangaNextPageSelector(): String? = null
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The site wrongly return 404 for some titles, even if they are present.
 | 
			
		||||
     * In those cases, the extension will parse the response normally.
 | 
			
		||||
     */
 | 
			
		||||
    override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
 | 
			
		||||
        return client.newCall(mangaDetailsRequest(manga))
 | 
			
		||||
            .asObservableIgnoreCode(404)
 | 
			
		||||
            .map { response ->
 | 
			
		||||
                mangaDetailsParse(response).apply { initialized = true }
 | 
			
		||||
            }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply {
 | 
			
		||||
        val infoElement = document.select("div.box-content div.w-row div.w-col:eq(1) article")
 | 
			
		||||
            .firstOrNull() ?: throw Exception(BLOCK_MESSAGE)
 | 
			
		||||
 | 
			
		||||
        author = infoElement.select("div.text li div:contains(Autor:)").textWithoutLabel()
 | 
			
		||||
        artist = infoElement.select("div.text li div:contains(Arte:)").textWithoutLabel()
 | 
			
		||||
        genre = infoElement.select("h3.subtitle + div.tags a").joinToString { it.text() }
 | 
			
		||||
        description = infoElement.select("div.text div.paragraph").first()?.text()
 | 
			
		||||
            ?.substringBefore("Relacionados:")
 | 
			
		||||
        status = infoElement.select("div.text li div:contains(Status:)").text().toStatus()
 | 
			
		||||
        thumbnail_url = document.select("div.box-content div.w-row div.w-col:eq(0) div.widget img")
 | 
			
		||||
            .attr("src")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The site wrongly return 404 for some titles, even if they are present.
 | 
			
		||||
     * In those cases, the extension will parse the response normally.
 | 
			
		||||
     */
 | 
			
		||||
    override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
 | 
			
		||||
        return if (manga.status != SManga.LICENSED) {
 | 
			
		||||
            client.newCall(chapterListRequest(manga))
 | 
			
		||||
                .asObservableIgnoreCode(404)
 | 
			
		||||
                .map(::chapterListParse)
 | 
			
		||||
        } else {
 | 
			
		||||
            Observable.error(Exception("Licensed - No chapters to show"))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun chapterListParse(response: Response): List<SChapter> {
 | 
			
		||||
        val chapters = super.chapterListParse(response)
 | 
			
		||||
 | 
			
		||||
        if (chapters.isEmpty()) {
 | 
			
		||||
            throw Exception(BLOCK_MESSAGE)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return chapters
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun chapterListSelector(): String =
 | 
			
		||||
        "article section.clearfix div.chapters div.cap div.card.pop"
 | 
			
		||||
 | 
			
		||||
    override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply {
 | 
			
		||||
        name = element.select("div.pop-title").text().withoutLanguage()
 | 
			
		||||
        scanlator = element.select("div.pop-content small strong").text()
 | 
			
		||||
        date_upload = element.select("small.clearfix").text()
 | 
			
		||||
            .substringAfter("Adicionado em ")
 | 
			
		||||
            .toDate()
 | 
			
		||||
        chapter_number = element.select("div.pop-title span.btn-caps").text()
 | 
			
		||||
            .toFloatOrNull() ?: 1f
 | 
			
		||||
        setUrlWithoutDomain(element.select("div.tags a").attr("href"))
 | 
			
		||||
 | 
			
		||||
        if (scanlator!!.split("/").count() >= 5) {
 | 
			
		||||
            val scanlators = scanlator!!.split("/")
 | 
			
		||||
            scanlator = scanlators[0] + " e mais " + (scanlators.count() - 1)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The site wrongly return 404 for some chapters, even if they are present.
 | 
			
		||||
     * In those cases, the extension will parse the response normally.
 | 
			
		||||
     */
 | 
			
		||||
    override fun fetchPageList(chapter: SChapter): Observable<List<Page>> {
 | 
			
		||||
        return client.newCall(pageListRequest(chapter))
 | 
			
		||||
            .asObservableIgnoreCode(404)
 | 
			
		||||
            .map(::pageListParse)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun pageListRequest(chapter: SChapter): Request {
 | 
			
		||||
        // Just to prevent the detection of the crawler.
 | 
			
		||||
        val newHeader = headersBuilder()
 | 
			
		||||
            .set("Referer", "$baseUrl${chapter.url}".substringBeforeLast("/"))
 | 
			
		||||
            .build()
 | 
			
		||||
 | 
			
		||||
        return GET(baseUrl + chapter.url, newHeader)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun pageListParse(document: Document): List<Page> {
 | 
			
		||||
        return document.select("div#slider a img")
 | 
			
		||||
            .mapIndexed { i, el -> Page(i, document.location(), el.attr("src")) }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun imageUrlParse(document: Document) = ""
 | 
			
		||||
 | 
			
		||||
    override fun imageRequest(page: Page): Request {
 | 
			
		||||
        val newHeaders = headersBuilder()
 | 
			
		||||
            .set("Accept", ACCEPT_IMAGE)
 | 
			
		||||
            .set("Referer", "$baseUrl/")
 | 
			
		||||
            .build()
 | 
			
		||||
 | 
			
		||||
        return GET(page.imageUrl!!, newHeaders)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun blockMessageIntercept(chain: Interceptor.Chain): Response {
 | 
			
		||||
        val response = chain.proceed(chain.request())
 | 
			
		||||
 | 
			
		||||
        if (response.code == 403 || response.code == 1020) {
 | 
			
		||||
            response.close()
 | 
			
		||||
            throw Exception(BLOCK_MESSAGE)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return response
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun Call.asObservableIgnoreCode(code: Int): Observable<Response> {
 | 
			
		||||
        return asObservable().doOnNext { response ->
 | 
			
		||||
            if (!response.isSuccessful && response.code != code) {
 | 
			
		||||
                response.close()
 | 
			
		||||
                throw Exception("HTTP error ${response.code}")
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun String.toDate(): Long {
 | 
			
		||||
        return try {
 | 
			
		||||
            DATE_FORMAT.parse(this)?.time ?: 0L
 | 
			
		||||
        } catch (e: ParseException) {
 | 
			
		||||
            0L
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun String.toStatus() = when (this) {
 | 
			
		||||
        "Ativo" -> SManga.ONGOING
 | 
			
		||||
        "Completo" -> SManga.COMPLETED
 | 
			
		||||
        else -> SManga.UNKNOWN
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun String.withoutLanguage(): String = replace(LANG_REGEX, "")
 | 
			
		||||
 | 
			
		||||
    private fun String.toLargeUrl(): String = replace(IMAGE_REGEX, "_full.")
 | 
			
		||||
 | 
			
		||||
    private fun Elements.textWithoutLabel(): String = text()!!.substringAfter(":").trim()
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        private const val ACCEPT = "text/html,application/xhtml+xml,application/xml;q=0.9," +
 | 
			
		||||
            "image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"
 | 
			
		||||
        private const val ACCEPT_IMAGE = "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8"
 | 
			
		||||
        private const val ACCEPT_LANGUAGE = "pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7,es;q=0.6,gl;q=0.5"
 | 
			
		||||
        private const val USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " +
 | 
			
		||||
            "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36"
 | 
			
		||||
 | 
			
		||||
        private val LANG_REGEX = "( )?\\((PT-)?BR\\)".toRegex()
 | 
			
		||||
        private val IMAGE_REGEX = "_(small|medium|xmedium)\\.".toRegex()
 | 
			
		||||
 | 
			
		||||
        private const val BLOCK_MESSAGE = "O site está bloqueando o Tachiyomi. " +
 | 
			
		||||
            "Migre para outra fonte caso o problema persistir."
 | 
			
		||||
 | 
			
		||||
        private val DATE_FORMAT by lazy {
 | 
			
		||||
            SimpleDateFormat("MMM dd, yyyy", Locale.ENGLISH)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,43 +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=".pt.unionmangas.UnionMangasUrlActivity"
 | 
			
		||||
            android:excludeFromRecents="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="unionleitor.top"
 | 
			
		||||
                    android:pathPattern="/perfil-manga/..*"
 | 
			
		||||
                    android:scheme="https" />
 | 
			
		||||
                <data
 | 
			
		||||
                    android:host="unionleitor.top"
 | 
			
		||||
                    android:pathPattern="/pagina-manga/..*"
 | 
			
		||||
                    android:scheme="https" />
 | 
			
		||||
                <data
 | 
			
		||||
                    android:host="unionmangas.top"
 | 
			
		||||
                    android:pathPattern="/perfil-manga/..*"
 | 
			
		||||
                    android:scheme="http" />
 | 
			
		||||
                <data
 | 
			
		||||
                    android:host="unionmangas.top"
 | 
			
		||||
                    android:pathPattern="/pagina-manga/..*"
 | 
			
		||||
                    android:scheme="http" />
 | 
			
		||||
                <data
 | 
			
		||||
                    android:host="unionmangas.top"
 | 
			
		||||
                    android:pathPattern="/perfil-manga/..*"
 | 
			
		||||
                    android:scheme="https" />
 | 
			
		||||
                <data
 | 
			
		||||
                    android:host="unionmangas.top"
 | 
			
		||||
                    android:pathPattern="/pagina-manga/..*"
 | 
			
		||||
                    android:scheme="https" />
 | 
			
		||||
            </intent-filter>
 | 
			
		||||
        </activity>
 | 
			
		||||
    </application>
 | 
			
		||||
</manifest>
 | 
			
		||||
@ -1,16 +0,0 @@
 | 
			
		||||
apply plugin: 'com.android.application'
 | 
			
		||||
apply plugin: 'kotlin-android'
 | 
			
		||||
 | 
			
		||||
ext {
 | 
			
		||||
    extName = 'Union Mangás'
 | 
			
		||||
    pkgNameSuffix = 'pt.unionmangas'
 | 
			
		||||
    extClass = '.UnionMangas'
 | 
			
		||||
    extVersionCode = 22
 | 
			
		||||
    libVersion = '1.2'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
    implementation project(':lib-ratelimit')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
apply from: "$rootDir/common.gradle"
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 3.0 KiB  | 
| 
		 Before Width: | Height: | Size: 1.7 KiB  | 
| 
		 Before Width: | Height: | Size: 3.8 KiB  | 
| 
		 Before Width: | Height: | Size: 6.8 KiB  | 
| 
		 Before Width: | Height: | Size: 10 KiB  | 
| 
		 Before Width: | Height: | Size: 48 KiB  | 
@ -1,278 +0,0 @@
 | 
			
		||||
package eu.kanade.tachiyomi.extension.pt.unionmangas
 | 
			
		||||
 | 
			
		||||
import com.github.salomonbrys.kotson.array
 | 
			
		||||
import com.github.salomonbrys.kotson.nullObj
 | 
			
		||||
import com.github.salomonbrys.kotson.obj
 | 
			
		||||
import com.github.salomonbrys.kotson.string
 | 
			
		||||
import com.google.gson.JsonElement
 | 
			
		||||
import com.google.gson.JsonObject
 | 
			
		||||
import com.google.gson.JsonParser
 | 
			
		||||
import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor
 | 
			
		||||
import eu.kanade.tachiyomi.network.GET
 | 
			
		||||
import eu.kanade.tachiyomi.network.POST
 | 
			
		||||
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 okhttp3.FormBody
 | 
			
		||||
import okhttp3.Headers
 | 
			
		||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
 | 
			
		||||
import okhttp3.OkHttpClient
 | 
			
		||||
import okhttp3.Request
 | 
			
		||||
import okhttp3.Response
 | 
			
		||||
import org.jsoup.nodes.Document
 | 
			
		||||
import org.jsoup.nodes.Element
 | 
			
		||||
import java.text.ParseException
 | 
			
		||||
import java.text.SimpleDateFormat
 | 
			
		||||
import java.util.Locale
 | 
			
		||||
import java.util.concurrent.TimeUnit
 | 
			
		||||
 | 
			
		||||
class UnionMangas : ParsedHttpSource() {
 | 
			
		||||
 | 
			
		||||
    // Hardcode the id because the language wasn't specific.
 | 
			
		||||
    override val id: Long = 6931383302802153355
 | 
			
		||||
 | 
			
		||||
    override val name = "Union Mangás"
 | 
			
		||||
 | 
			
		||||
    override val baseUrl = "https://unionmangas.top"
 | 
			
		||||
 | 
			
		||||
    override val lang = "pt-BR"
 | 
			
		||||
 | 
			
		||||
    override val supportsLatest = true
 | 
			
		||||
 | 
			
		||||
    override val client: OkHttpClient = network.cloudflareClient.newBuilder()
 | 
			
		||||
        .connectTimeout(3, TimeUnit.MINUTES)
 | 
			
		||||
        .readTimeout(3, TimeUnit.MINUTES)
 | 
			
		||||
        .writeTimeout(3, TimeUnit.MINUTES)
 | 
			
		||||
        .addInterceptor(RateLimitInterceptor(1, 2, TimeUnit.SECONDS))
 | 
			
		||||
        .build()
 | 
			
		||||
 | 
			
		||||
    override fun headersBuilder(): Headers.Builder = Headers.Builder()
 | 
			
		||||
        .add("Accept", ACCEPT)
 | 
			
		||||
        .add("Accept-Language", ACCEPT_LANGUAGE)
 | 
			
		||||
        .add("Referer", "$baseUrl/home")
 | 
			
		||||
 | 
			
		||||
    override fun popularMangaRequest(page: Int): Request {
 | 
			
		||||
        val listPath = if (page == 1) "" else "/visualizacoes/${page - 1}"
 | 
			
		||||
        val newHeaders = headersBuilder()
 | 
			
		||||
            .set("Referer", "$baseUrl/lista-mangas$listPath")
 | 
			
		||||
            .build()
 | 
			
		||||
 | 
			
		||||
        val pageStr = if (page != 1) "/$page" else ""
 | 
			
		||||
        return GET("$baseUrl/lista-mangas/visualizacoes$pageStr", newHeaders)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun popularMangaParse(response: Response): MangasPage {
 | 
			
		||||
        val results = super.popularMangaParse(response)
 | 
			
		||||
 | 
			
		||||
        if (results.mangas.isEmpty()) {
 | 
			
		||||
            throw Exception(BLOCK_MESSAGE)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return results
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun popularMangaSelector(): String = "div.col-md-3.col-xs-6:has(img.img-thumbnail)"
 | 
			
		||||
 | 
			
		||||
    override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply {
 | 
			
		||||
        title = element.select("div[id^=bloco-tooltip] > b").first().text().withoutLanguage()
 | 
			
		||||
        thumbnail_url = element.select("a img").first()?.attr("src")
 | 
			
		||||
        setUrlWithoutDomain(element.select("a").last().attr("href"))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun popularMangaNextPageSelector() = ".pagination li:contains(Next)"
 | 
			
		||||
 | 
			
		||||
    override fun latestUpdatesRequest(page: Int): Request {
 | 
			
		||||
        val form = FormBody.Builder()
 | 
			
		||||
            .add("pagina", page.toString())
 | 
			
		||||
            .build()
 | 
			
		||||
 | 
			
		||||
        val newHeaders = headersBuilder()
 | 
			
		||||
            .add("Content-Type", form.contentType().toString())
 | 
			
		||||
            .add("Content-Length", form.contentLength().toString())
 | 
			
		||||
            .add("Origin", baseUrl)
 | 
			
		||||
            .add("X-Requested-With", "XMLHttpRequest")
 | 
			
		||||
            .set("Accept", "*/*")
 | 
			
		||||
            .build()
 | 
			
		||||
 | 
			
		||||
        return POST("$baseUrl/assets/noticias.php", newHeaders, form)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun latestUpdatesParse(response: Response): MangasPage {
 | 
			
		||||
        val results = super.latestUpdatesParse(response)
 | 
			
		||||
 | 
			
		||||
        if (results.mangas.isEmpty()) {
 | 
			
		||||
            throw Exception(BLOCK_MESSAGE)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return results
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun latestUpdatesSelector() = "div.row[style] div.col-md-12[style]"
 | 
			
		||||
 | 
			
		||||
    override fun latestUpdatesFromElement(element: Element): SManga = SManga.create().apply {
 | 
			
		||||
        val infoElement = element.select("a.link-titulo")
 | 
			
		||||
 | 
			
		||||
        title = infoElement.last().text().withoutLanguage()
 | 
			
		||||
        thumbnail_url = infoElement.first()?.select("img")?.attr("src")
 | 
			
		||||
        setUrlWithoutDomain(infoElement.last().attr("href"))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun latestUpdatesNextPageSelector() = "div#linha-botao-mais"
 | 
			
		||||
 | 
			
		||||
    override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
 | 
			
		||||
        if (query.startsWith(PREFIX_SLUG_SEARCH)) {
 | 
			
		||||
            val slug = query.removePrefix(PREFIX_SLUG_SEARCH)
 | 
			
		||||
            return GET("$baseUrl/pagina-manga/$slug", headers)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val newHeaders = headersBuilder()
 | 
			
		||||
            .set("Accept", ACCEPT_JSON)
 | 
			
		||||
            .add("X-Requested-With", "XMLHttpRequest")
 | 
			
		||||
            .build()
 | 
			
		||||
 | 
			
		||||
        val url = "$baseUrl/assets/busca.php".toHttpUrlOrNull()!!.newBuilder()
 | 
			
		||||
            .addQueryParameter("titulo", query)
 | 
			
		||||
 | 
			
		||||
        return GET(url.toString(), newHeaders)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun searchMangaParse(response: Response): MangasPage {
 | 
			
		||||
        val requestUrl = response.request.url.toString()
 | 
			
		||||
 | 
			
		||||
        if (requestUrl.contains("pagina-manga")) {
 | 
			
		||||
            val slug = requestUrl.substringAfter("pagina-manga/")
 | 
			
		||||
            val manga = mangaDetailsParse(response)
 | 
			
		||||
                .apply { url = "/pagina-manga/$slug" }
 | 
			
		||||
            return MangasPage(listOf(manga), false)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val result = response.asJson().nullObj
 | 
			
		||||
            ?: throw Exception(BLOCK_MESSAGE)
 | 
			
		||||
 | 
			
		||||
        val mangas = result["items"].array
 | 
			
		||||
            .map { searchMangaFromObject(it.obj) }
 | 
			
		||||
 | 
			
		||||
        return MangasPage(mangas, false)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun searchMangaFromObject(obj: JsonObject): SManga = SManga.create().apply {
 | 
			
		||||
        title = obj["titulo"].string.withoutLanguage()
 | 
			
		||||
        thumbnail_url = obj["imagem"].string
 | 
			
		||||
        setUrlWithoutDomain("$baseUrl/pagina-manga/${obj["url"].string}")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun mangaDetailsRequest(manga: SManga): Request {
 | 
			
		||||
        // Map the mangas that are already in library with the old URL to the new one.
 | 
			
		||||
        val newUrl = manga.url.replace("perfil-manga", "pagina-manga")
 | 
			
		||||
        return GET(baseUrl + newUrl, headers)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply {
 | 
			
		||||
        val infoElement = document.select("div.perfil-manga").firstOrNull()
 | 
			
		||||
            ?: throw Exception(BLOCK_MESSAGE)
 | 
			
		||||
        val rowInfo = infoElement.select("div.row:eq(2)").first()
 | 
			
		||||
 | 
			
		||||
        title = infoElement.select("h2").first().text().withoutLanguage()
 | 
			
		||||
        author = rowInfo.select("div.col-md-8:eq(4)").first().textWithoutLabel()
 | 
			
		||||
        artist = rowInfo.select("div.col-md-8:eq(5)").first().textWithoutLabel()
 | 
			
		||||
        genre = rowInfo.select("div.col-md-8:eq(3)").first().textWithoutLabel()
 | 
			
		||||
        status = rowInfo.select("div.col-md-8:eq(6)").first().text().toStatus()
 | 
			
		||||
        description = rowInfo.select("div.col-md-8:eq(8)").first().text()
 | 
			
		||||
        thumbnail_url = infoElement.select(".img-thumbnail").first().attr("src")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun chapterListParse(response: Response): List<SChapter> {
 | 
			
		||||
        val results = super.chapterListParse(response)
 | 
			
		||||
 | 
			
		||||
        if (results.isEmpty()) {
 | 
			
		||||
            throw Exception(BLOCK_MESSAGE)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return results
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun chapterListSelector() = "div.row.capitulos"
 | 
			
		||||
 | 
			
		||||
    override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply {
 | 
			
		||||
        val firstColumn = element.select("div.col-md-6:eq(0)").first()!!
 | 
			
		||||
        val secondColumn = element.select("div.col-md-6:eq(1)").firstOrNull()
 | 
			
		||||
 | 
			
		||||
        name = firstColumn.select("a").first().text()
 | 
			
		||||
        scanlator = secondColumn?.select("a")?.joinToString { it.text() }
 | 
			
		||||
        date_upload = firstColumn.select("span").last()!!.text().toDate()
 | 
			
		||||
 | 
			
		||||
        // For some reason, setUrlWithoutDomain does not work when the url have spaces.
 | 
			
		||||
        val absoluteUrlFixed = firstColumn.select("a").first()
 | 
			
		||||
            .attr("href")
 | 
			
		||||
            .replace(" ", "%20")
 | 
			
		||||
        setUrlWithoutDomain(absoluteUrlFixed)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun pageListParse(document: Document): List<Page> {
 | 
			
		||||
        return document.select("img.img-responsive.img-manga")
 | 
			
		||||
            .filter { it.attr("src").contains("/leitor/") }
 | 
			
		||||
            .mapIndexed { i, element ->
 | 
			
		||||
                Page(i, document.location(), element.absUrl("src"))
 | 
			
		||||
            }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun imageUrlParse(document: Document) = ""
 | 
			
		||||
 | 
			
		||||
    override fun imageRequest(page: Page): Request {
 | 
			
		||||
        val newHeaders = headersBuilder()
 | 
			
		||||
            .set("Accept", ACCEPT_IMAGE)
 | 
			
		||||
            .set("Referer", page.url)
 | 
			
		||||
            .build()
 | 
			
		||||
 | 
			
		||||
        return GET(page.imageUrl!!, newHeaders)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun searchMangaSelector() = throw Exception("This method should not be called!")
 | 
			
		||||
 | 
			
		||||
    override fun searchMangaFromElement(element: Element): SManga = throw Exception("This method should not be called!")
 | 
			
		||||
 | 
			
		||||
    override fun searchMangaNextPageSelector() = throw Exception("This method should not be called!")
 | 
			
		||||
 | 
			
		||||
    private fun String.toDate(): Long {
 | 
			
		||||
        return try {
 | 
			
		||||
            DATE_FORMATTER.parse(this)?.time ?: 0L
 | 
			
		||||
        } catch (e: ParseException) {
 | 
			
		||||
            0L
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun String.toStatus(): Int = when {
 | 
			
		||||
        contains("Ativo") -> SManga.ONGOING
 | 
			
		||||
        contains("Completo") -> SManga.COMPLETED
 | 
			
		||||
        else -> SManga.UNKNOWN
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun String.withoutLanguage(): String =
 | 
			
		||||
        replace("(pt-br)", "", true).trim()
 | 
			
		||||
 | 
			
		||||
    private fun Element.textWithoutLabel(): String = text()!!.substringAfter(":").trim()
 | 
			
		||||
 | 
			
		||||
    private fun Response.asJson(): JsonElement = JsonParser.parseString(body!!.string())
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        private const val ACCEPT = "text/html,application/xhtml+xml,application/xml;q=0.9," +
 | 
			
		||||
            "image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"
 | 
			
		||||
        private const val ACCEPT_IMAGE = "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8"
 | 
			
		||||
        private const val ACCEPT_JSON = "application/json, text/javascript, */*; q=0.01"
 | 
			
		||||
        private const val ACCEPT_LANGUAGE = "pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7,es;q=0.6,gl;q=0.5"
 | 
			
		||||
        private const val USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " +
 | 
			
		||||
            "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36"
 | 
			
		||||
 | 
			
		||||
        private const val BLOCK_MESSAGE = "O site está bloqueando o Tachiyomi. " +
 | 
			
		||||
            "Migre para outra fonte caso o problema persistir."
 | 
			
		||||
 | 
			
		||||
        private val DATE_FORMATTER by lazy {
 | 
			
		||||
            SimpleDateFormat("(dd/MM/yyyy)", Locale.ENGLISH)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const val PREFIX_SLUG_SEARCH = "slug:"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,38 +0,0 @@
 | 
			
		||||
package eu.kanade.tachiyomi.extension.pt.unionmangas
 | 
			
		||||
 | 
			
		||||
import android.app.Activity
 | 
			
		||||
import android.content.ActivityNotFoundException
 | 
			
		||||
import android.content.Intent
 | 
			
		||||
import android.os.Bundle
 | 
			
		||||
import android.util.Log
 | 
			
		||||
import kotlin.system.exitProcess
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Springboard that accepts https://unionleitor.top/perfil-manga/xxxxxx intents
 | 
			
		||||
 * and redirects them to the main Tachiyomi process.
 | 
			
		||||
 */
 | 
			
		||||
class UnionMangasUrlActivity : Activity() {
 | 
			
		||||
    override fun onCreate(savedInstanceState: Bundle?) {
 | 
			
		||||
        super.onCreate(savedInstanceState)
 | 
			
		||||
        val pathSegments = intent?.data?.pathSegments
 | 
			
		||||
        if (pathSegments != null && pathSegments.size > 1) {
 | 
			
		||||
            val slug = pathSegments[1]
 | 
			
		||||
            val mainIntent = Intent().apply {
 | 
			
		||||
                action = "eu.kanade.tachiyomi.SEARCH"
 | 
			
		||||
                putExtra("query", "${UnionMangas.PREFIX_SLUG_SEARCH}$slug")
 | 
			
		||||
                putExtra("filter", packageName)
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            try {
 | 
			
		||||
                startActivity(mainIntent)
 | 
			
		||||
            } catch (e: ActivityNotFoundException) {
 | 
			
		||||
                Log.e("UnionMangasUrl", e.toString())
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            Log.e("UnionMangasUrl", "could not parse uri from intent $intent")
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        finish()
 | 
			
		||||
        exitProcess(0)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,2 +0,0 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<manifest package="eu.kanade.tachiyomi.extension" />
 | 
			
		||||
@ -1,16 +0,0 @@
 | 
			
		||||
apply plugin: 'com.android.application'
 | 
			
		||||
apply plugin: 'kotlin-android'
 | 
			
		||||
 | 
			
		||||
ext {
 | 
			
		||||
    extName = 'YES Mangás'
 | 
			
		||||
    pkgNameSuffix = 'pt.yesmangas'
 | 
			
		||||
    extClass = '.YesMangas'
 | 
			
		||||
    extVersionCode = 7
 | 
			
		||||
    libVersion = '1.2'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
    implementation project(':lib-ratelimit')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
apply from: "$rootDir/common.gradle"
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 3.7 KiB  | 
| 
		 Before Width: | Height: | Size: 2.0 KiB  | 
| 
		 Before Width: | Height: | Size: 4.9 KiB  | 
| 
		 Before Width: | Height: | Size: 8.3 KiB  | 
| 
		 Before Width: | Height: | Size: 14 KiB  | 
| 
		 Before Width: | Height: | Size: 50 KiB  | 
@ -1,146 +0,0 @@
 | 
			
		||||
package eu.kanade.tachiyomi.extension.pt.yesmangas
 | 
			
		||||
 | 
			
		||||
import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor
 | 
			
		||||
import eu.kanade.tachiyomi.network.GET
 | 
			
		||||
import eu.kanade.tachiyomi.source.model.FilterList
 | 
			
		||||
import eu.kanade.tachiyomi.source.model.Page
 | 
			
		||||
import eu.kanade.tachiyomi.source.model.SChapter
 | 
			
		||||
import eu.kanade.tachiyomi.source.model.SManga
 | 
			
		||||
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
 | 
			
		||||
import okhttp3.Headers
 | 
			
		||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
 | 
			
		||||
import okhttp3.OkHttpClient
 | 
			
		||||
import okhttp3.Request
 | 
			
		||||
import org.jsoup.nodes.Document
 | 
			
		||||
import org.jsoup.nodes.Element
 | 
			
		||||
import org.jsoup.select.Elements
 | 
			
		||||
import java.util.concurrent.TimeUnit
 | 
			
		||||
 | 
			
		||||
class YesMangas : ParsedHttpSource() {
 | 
			
		||||
 | 
			
		||||
    // Hardcode the id because the language wasn't specific.
 | 
			
		||||
    override val id: Long = 7187189302580957274
 | 
			
		||||
 | 
			
		||||
    override val name = "YES Mangás"
 | 
			
		||||
 | 
			
		||||
    override val baseUrl = "https://yesmangas1.com"
 | 
			
		||||
 | 
			
		||||
    override val lang = "pt-BR"
 | 
			
		||||
 | 
			
		||||
    override val supportsLatest = true
 | 
			
		||||
 | 
			
		||||
    override val client: OkHttpClient = network.cloudflareClient.newBuilder()
 | 
			
		||||
        .addInterceptor(RateLimitInterceptor(1, 1, TimeUnit.SECONDS))
 | 
			
		||||
        .build()
 | 
			
		||||
 | 
			
		||||
    override fun headersBuilder(): Headers.Builder = Headers.Builder()
 | 
			
		||||
        .add("User-Agent", USER_AGENT)
 | 
			
		||||
        .add("Origin", baseUrl)
 | 
			
		||||
        .add("Referer", baseUrl)
 | 
			
		||||
 | 
			
		||||
    override fun popularMangaRequest(page: Int): Request = GET(baseUrl, headers)
 | 
			
		||||
 | 
			
		||||
    override fun popularMangaSelector(): String = "div#destaques div.three.columns a.img"
 | 
			
		||||
 | 
			
		||||
    override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply {
 | 
			
		||||
        title = element.attr("title").withoutLanguage()
 | 
			
		||||
        thumbnail_url = element.select("img").attr("data-path").toLargeUrl()
 | 
			
		||||
        url = element.attr("href")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun popularMangaNextPageSelector(): String? = null
 | 
			
		||||
 | 
			
		||||
    override fun latestUpdatesRequest(page: Int): Request = GET(baseUrl, headers)
 | 
			
		||||
 | 
			
		||||
    override fun latestUpdatesSelector(): String = "div#lancamentos table.u-full-width tbody tr td:eq(0) a"
 | 
			
		||||
 | 
			
		||||
    override fun latestUpdatesFromElement(element: Element): SManga = SManga.create().apply {
 | 
			
		||||
        title = element.attr("title").withoutLanguage()
 | 
			
		||||
        thumbnail_url = element.select("img").attr("data-path").toLargeUrl()
 | 
			
		||||
        setUrlWithoutDomain(element.attr("href"))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun latestUpdatesNextPageSelector(): String? = null
 | 
			
		||||
 | 
			
		||||
    override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
 | 
			
		||||
        val url = "$baseUrl/search".toHttpUrlOrNull()!!.newBuilder()
 | 
			
		||||
            .addQueryParameter("q", query)
 | 
			
		||||
 | 
			
		||||
        return GET(url.toString(), headers)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun searchMangaSelector(): String = "tbody#leituras tr td:eq(0) a"
 | 
			
		||||
 | 
			
		||||
    override fun searchMangaFromElement(element: Element): SManga = SManga.create().apply {
 | 
			
		||||
        title = element.attr("title").withoutLanguage()
 | 
			
		||||
        thumbnail_url = element.select("img").attr("data-path").toLargeUrl()
 | 
			
		||||
        setUrlWithoutDomain(element.attr("href"))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun searchMangaNextPageSelector(): String? = null
 | 
			
		||||
 | 
			
		||||
    override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply {
 | 
			
		||||
        val container = document.select("div#descricao").first()
 | 
			
		||||
 | 
			
		||||
        author = container.select("ul li:contains(Autor)").textWithoutLabel()
 | 
			
		||||
        artist = container.select("ul li:contains(Desenho)").textWithoutLabel()
 | 
			
		||||
        genre = container.select("ul li:contains(Categorias)").textWithoutLabel()
 | 
			
		||||
        status = container.select("ul li:contains(Status)").text().toStatus()
 | 
			
		||||
        description = container.select("article").text()
 | 
			
		||||
            .substringBefore("Relacionados")
 | 
			
		||||
        thumbnail_url = container.select("img").first()
 | 
			
		||||
            .attr("data-path")
 | 
			
		||||
            .toLargeUrl()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun chapterListSelector(): String = "div#capitulos a.button"
 | 
			
		||||
 | 
			
		||||
    override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply {
 | 
			
		||||
        name = element.attr("title").substringAfter(" - ")
 | 
			
		||||
        chapter_number = element.text().toFloatOrNull() ?: -1f
 | 
			
		||||
        setUrlWithoutDomain(element.attr("href"))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun pageListRequest(chapter: SChapter): Request {
 | 
			
		||||
        val newHeaders = headersBuilder()
 | 
			
		||||
            .set("Referer", baseUrl + chapter.url.substringBeforeLast("/"))
 | 
			
		||||
            .build()
 | 
			
		||||
 | 
			
		||||
        return GET(baseUrl + chapter.url, newHeaders)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun pageListParse(document: Document): List<Page> {
 | 
			
		||||
        return document.select("div.read-slideshow a img")
 | 
			
		||||
            .mapIndexed { i, el -> Page(i, document.location(), el.attr("src")) }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun imageUrlParse(document: Document): String = ""
 | 
			
		||||
 | 
			
		||||
    override fun imageRequest(page: Page): Request {
 | 
			
		||||
        val newHeaders = headersBuilder()
 | 
			
		||||
            .set("Referer", page.url)
 | 
			
		||||
            .build()
 | 
			
		||||
 | 
			
		||||
        return GET(page.imageUrl!!, newHeaders)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun String.withoutLanguage(): String = replace(LANG_REGEX, "")
 | 
			
		||||
 | 
			
		||||
    private fun String.toLargeUrl(): String = replace(IMAGE_REGEX, "_full.")
 | 
			
		||||
 | 
			
		||||
    private fun Elements.textWithoutLabel(): String = text()!!.substringAfter(":").trim()
 | 
			
		||||
 | 
			
		||||
    private fun String.toStatus() = when {
 | 
			
		||||
        contains("Completo") -> SManga.COMPLETED
 | 
			
		||||
        contains("Ativo") -> SManga.ONGOING
 | 
			
		||||
        else -> SManga.UNKNOWN
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        private const val USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " +
 | 
			
		||||
            "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36"
 | 
			
		||||
 | 
			
		||||
        private val LANG_REGEX = "( )?\\((PT-)?BR\\)".toRegex()
 | 
			
		||||
        private val IMAGE_REGEX = "_(small|medium|xmedium|xlarge)\\.".toRegex()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||