diff --git a/src/pt/mundohentai/AndroidManifest.xml b/src/pt/mundohentai/AndroidManifest.xml new file mode 100644 index 000000000..389c51256 --- /dev/null +++ b/src/pt/mundohentai/AndroidManifest.xml @@ -0,0 +1,3 @@ + + + diff --git a/src/pt/mundohentai/build.gradle b/src/pt/mundohentai/build.gradle new file mode 100644 index 000000000..d946e94f5 --- /dev/null +++ b/src/pt/mundohentai/build.gradle @@ -0,0 +1,17 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' + +ext { + extName = 'Mundo Hentai' + pkgNameSuffix = 'pt.mundohentai' + extClass = '.MundoHentai' + extVersionCode = 3 + isNsfw = true +} + +dependencies { + implementation project(':lib-ratelimit') +} + +apply from: "$rootDir/common.gradle" + diff --git a/src/pt/mundohentai/res/mipmap-hdpi/ic_launcher.png b/src/pt/mundohentai/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..071cff073 Binary files /dev/null and b/src/pt/mundohentai/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/pt/mundohentai/res/mipmap-mdpi/ic_launcher.png b/src/pt/mundohentai/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..82e950931 Binary files /dev/null and b/src/pt/mundohentai/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/pt/mundohentai/res/mipmap-xhdpi/ic_launcher.png b/src/pt/mundohentai/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..f4320c59b Binary files /dev/null and b/src/pt/mundohentai/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/pt/mundohentai/res/mipmap-xxhdpi/ic_launcher.png b/src/pt/mundohentai/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..01ebdefba Binary files /dev/null and b/src/pt/mundohentai/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/pt/mundohentai/res/mipmap-xxxhdpi/ic_launcher.png b/src/pt/mundohentai/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..650b323ec Binary files /dev/null and b/src/pt/mundohentai/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/pt/mundohentai/res/web_hi_res_512.png b/src/pt/mundohentai/res/web_hi_res_512.png new file mode 100644 index 000000000..cf69095de Binary files /dev/null and b/src/pt/mundohentai/res/web_hi_res_512.png differ diff --git a/src/pt/mundohentai/src/eu/kanade/tachiyomi/extension/pt/mundohentai/MundoHentai.kt b/src/pt/mundohentai/src/eu/kanade/tachiyomi/extension/pt/mundohentai/MundoHentai.kt new file mode 100644 index 000000000..f8189ba7f --- /dev/null +++ b/src/pt/mundohentai/src/eu/kanade/tachiyomi/extension/pt/mundohentai/MundoHentai.kt @@ -0,0 +1,189 @@ +package eu.kanade.tachiyomi.extension.pt.mundohentai + +import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor +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.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 java.util.concurrent.TimeUnit + +class MundoHentai : ParsedHttpSource() { + + override val name = "Mundo Hentai" + + override val baseUrl = "https://mundohentaioficial.com" + + override val lang = "pt-BR" + + override val supportsLatest = false + + override val client: OkHttpClient = network.cloudflareClient.newBuilder() + .addInterceptor(RateLimitInterceptor(1, 2, TimeUnit.SECONDS)) + .build() + + override fun headersBuilder(): Headers.Builder = Headers.Builder() + .add("Referer", baseUrl) + + private fun genericMangaFromElement(element: Element): SManga = + SManga.create().apply { + title = element.select("div.menu a.title").text() + thumbnail_url = element.attr("style") + .substringAfter("url(\"") + .substringBefore("\")") + url = element.select("a.absolute").attr("href") + } + + // The source does not have a popular list page, so we use the Doujin list instead. + override fun popularMangaRequest(page: Int): Request { + val newHeaders = headersBuilder() + .set("Referer", if (page == 1) baseUrl else "$baseUrl/tipo/doujin/${page - 1}") + .build() + + val pageStr = if (page != 1) "/$page" else "" + return GET("$baseUrl/tipo/doujin$pageStr", newHeaders) + } + + override fun popularMangaSelector(): String = "ul.post-list li div.card:has(a.absolute[href^=/])" + + override fun popularMangaFromElement(element: Element): SManga = genericMangaFromElement(element) + + override fun popularMangaNextPageSelector() = "div.buttons:not(:has(a.selected + a.material-icons))" + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + if (query.isNotEmpty()) { + val url = "$baseUrl/search".toHttpUrlOrNull()!!.newBuilder() + .addQueryParameter("q", query) + .toString() + + return GET(url, headers) + } + + val tagFilter = filters[1] as TagFilter + val tagSlug = tagFilter.values[tagFilter.state].slug + + if (tagSlug.isEmpty()) { + return popularMangaRequest(page) + } + + val newHeaders = headersBuilder() + .set("Referer", if (page == 1) "$baseUrl/categories" else "$baseUrl/tags/$tagSlug/${page - 1}") + .build() + + val pageStr = if (page != 1) "/$page" else "" + return GET("$baseUrl/tags/$tagSlug$pageStr", newHeaders) + } + + override fun searchMangaSelector() = popularMangaSelector() + ":not(:has(div.right-tape))" + + override fun searchMangaFromElement(element: Element): SManga = genericMangaFromElement(element) + + override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() + + override fun mangaDetailsParse(document: Document): SManga { + val post = document.select("div.post") + + return SManga.create().apply { + author = post.select("div.tags div.tag:contains(Artista:) a.value").text() + genre = post.select("div.tags div.tag:contains(Tags:) a.value").joinToString { it.text() } + description = post.select("div.tags div.tag:contains(Tipo:)").text() + .plus("\n" + post.select("div.tags div.tag:contains(Cor:)").text()) + status = SManga.COMPLETED + thumbnail_url = post.select("div.cover img").attr("src") + } + } + + override fun chapterListSelector(): String = "div.post header.data div.float-buttons a.read" + + override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { + name = "Capítulo" + scanlator = element.parent().parent() + .select("div.tags div.tag:contains(Tradutor:) a.value") + .text() + chapter_number = 1f + url = element.attr("href") + } + + override fun pageListRequest(chapter: SChapter): Request { + val newHeader = headersBuilder() + .set("Referer", "$baseUrl${chapter.url}".substringBeforeLast("/")) + .build() + + return GET(baseUrl + chapter.url, newHeader) + } + + override fun pageListParse(document: Document): List { + return document.select("div.gallery > 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("Referer", page.url) + .build() + + return GET(page.imageUrl!!, newHeaders) + } + + override fun getFilterList(): FilterList = FilterList( + Filter.Header("Os filtros são ignorados na busca!"), + TagFilter(getTags()) + ) + + data class Tag(val name: String, val slug: String) { + override fun toString(): String = name + } + + private class TagFilter(tags: Array) : Filter.Select("Tag", tags) + + private fun getTags(): Array = arrayOf( + Tag("-- Selecione --", ""), + Tag("Ahegao", "ahegao"), + Tag("Anal", "anal"), + Tag("Biquíni", "biquini"), + Tag("Chubby", "chubby"), + Tag("Colegial", "colegial"), + Tag("Creampie", "creampie"), + Tag("Dark Skin", "dark-skin"), + Tag("Dupla Penetração", "dupla-penetracao"), + Tag("Espanhola", "espanhola"), + Tag("Exibicionismo", "exibicionismo"), + Tag("Footjob", "footjob"), + Tag("Furry", "furry"), + Tag("Futanari", "futanari"), + Tag("Grupal", "grupal"), + Tag("Incesto", "incesto"), + Tag("Lingerie", "lingerie"), + Tag("MILF", "milf"), + Tag("Maiô", "maio"), + Tag("Masturbação", "masturbacao"), + Tag("Netorare", "netorare"), + Tag("Oral", "oral"), + Tag("Peitinhos", "peitinhos"), + Tag("Preservativo", "preservativo"), + Tag("Professora", "professora"), + Tag("Sex Toys", "sex-toys"), + Tag("Tentáculos", "tentaculos"), + Tag("Yaoi", "yaoi") + ) + + override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException("Not used") + + override fun latestUpdatesSelector(): String = throw UnsupportedOperationException("Not used") + + override fun latestUpdatesFromElement(element: Element): SManga = throw UnsupportedOperationException("Not used") + + override fun latestUpdatesNextPageSelector(): String = throw UnsupportedOperationException("Not used") +}