diff --git a/src/pt/brasilhentai/AndroidManifest.xml b/src/pt/brasilhentai/AndroidManifest.xml
new file mode 100644
index 000000000..68428d319
--- /dev/null
+++ b/src/pt/brasilhentai/AndroidManifest.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/pt/brasilhentai/build.gradle b/src/pt/brasilhentai/build.gradle
new file mode 100644
index 000000000..ca5ccc65c
--- /dev/null
+++ b/src/pt/brasilhentai/build.gradle
@@ -0,0 +1,8 @@
+ext {
+ extName = 'Brasil Hentai'
+ extClass = '.BrasilHentai'
+ extVersionCode = 1
+ isNsfw = true
+}
+
+apply from: "$rootDir/common.gradle"
diff --git a/src/pt/brasilhentai/res/mipmap-hdpi/ic_launcher.png b/src/pt/brasilhentai/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 000000000..919fab649
Binary files /dev/null and b/src/pt/brasilhentai/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/src/pt/brasilhentai/res/mipmap-mdpi/ic_launcher.png b/src/pt/brasilhentai/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 000000000..7db93692a
Binary files /dev/null and b/src/pt/brasilhentai/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/src/pt/brasilhentai/res/mipmap-xhdpi/ic_launcher.png b/src/pt/brasilhentai/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 000000000..8f46fd033
Binary files /dev/null and b/src/pt/brasilhentai/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/src/pt/brasilhentai/res/mipmap-xxhdpi/ic_launcher.png b/src/pt/brasilhentai/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 000000000..53074d98b
Binary files /dev/null and b/src/pt/brasilhentai/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/src/pt/brasilhentai/res/mipmap-xxxhdpi/ic_launcher.png b/src/pt/brasilhentai/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 000000000..2821b9ff9
Binary files /dev/null and b/src/pt/brasilhentai/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/src/pt/brasilhentai/src/eu/kanade/tachiyomi/extension/pt/brasilhentai/BrasilHentai.kt b/src/pt/brasilhentai/src/eu/kanade/tachiyomi/extension/pt/brasilhentai/BrasilHentai.kt
new file mode 100644
index 000000000..4035b792c
--- /dev/null
+++ b/src/pt/brasilhentai/src/eu/kanade/tachiyomi/extension/pt/brasilhentai/BrasilHentai.kt
@@ -0,0 +1,173 @@
+package eu.kanade.tachiyomi.extension.pt.brasilhentai
+
+import eu.kanade.tachiyomi.network.GET
+import eu.kanade.tachiyomi.network.asObservableSuccess
+import eu.kanade.tachiyomi.network.interceptor.rateLimit
+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.HttpUrl.Companion.toHttpUrl
+import okhttp3.OkHttpClient
+import okhttp3.Request
+import okhttp3.Response
+import org.jsoup.nodes.Document
+import org.jsoup.nodes.Element
+import rx.Observable
+
+class BrasilHentai : ParsedHttpSource() {
+
+ override val name = "Brasil Hentai"
+
+ override val baseUrl = "https://brasilhentai.com"
+
+ override val lang = "pt-BR"
+
+ override val supportsLatest: Boolean = false
+
+ override val client: OkHttpClient = network.cloudflareClient.newBuilder()
+ .rateLimit(4)
+ .build()
+
+ private var categoryFilterOptions: Array> = emptyArray()
+
+ override fun chapterFromElement(element: Element) =
+ throw UnsupportedOperationException()
+
+ override fun chapterListSelector() =
+ throw UnsupportedOperationException()
+
+ override fun fetchChapterList(manga: SManga): Observable> {
+ return Observable.just(
+ listOf(
+ SChapter.create().apply {
+ name = "CapĂtulo Ășnico"
+ url = manga.url
+ },
+ ),
+ )
+ }
+
+ override fun imageUrlParse(document: Document) = ""
+
+ override fun latestUpdatesFromElement(element: Element) =
+ throw UnsupportedOperationException()
+
+ override fun latestUpdatesNextPageSelector() =
+ throw UnsupportedOperationException()
+
+ override fun latestUpdatesRequest(page: Int) =
+ throw UnsupportedOperationException()
+
+ override fun latestUpdatesSelector() =
+ throw UnsupportedOperationException()
+
+ override fun mangaDetailsParse(document: Document) = SManga.create().apply {
+ title = document.selectFirst("h1")!!.ownText()
+ thumbnail_url = document.selectFirst(".entry-content p a img")?.absUrl("src")
+ }
+
+ override fun pageListParse(document: Document): List {
+ return document.select(".entry-content p > img").mapIndexed { index, element ->
+ Page(index, imageUrl = element.absUrl("src"))
+ }
+ }
+
+ override fun popularMangaFromElement(element: Element) = SManga.create().apply {
+ val anchor = element.selectFirst("a[title]")
+ title = anchor!!.attr("title")
+ thumbnail_url = element.selectFirst("img")?.absUrl("src")
+ initialized = true
+ setUrlWithoutDomain(anchor.absUrl("href"))
+ }
+
+ override fun popularMangaNextPageSelector() = ".next.page-numbers"
+
+ override fun popularMangaRequest(page: Int) = GET("$baseUrl/page/$page", headers)
+
+ override fun popularMangaSelector() = ".content-area article"
+
+ override fun popularMangaParse(response: Response): MangasPage {
+ val document = response.asJsoup()
+ if (categoryFilterOptions.isEmpty()) {
+ categoryFilterOptions = parseCategories(document)
+ }
+ return MangasPage(
+ mangas = document.select(popularMangaSelector()).map(::popularMangaFromElement),
+ hasNextPage = document.selectFirst(popularMangaNextPageSelector()) != null,
+ )
+ }
+
+ override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element)
+
+ override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
+
+ override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
+ val category = filters.filterIsInstance().first()
+ .selectedValue()
+
+ val url = if (category.isEmpty()) {
+ "$baseUrl/page/$page".toHttpUrl().newBuilder()
+ .addQueryParameter("s", query)
+ .build()
+ } else {
+ "$baseUrl/category/$category/page/$page/".toHttpUrl()
+ }
+
+ return GET(url, headers)
+ }
+
+ override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable {
+ return if (query.startsWith(PREFIX_SEARCH)) {
+ client.newCall(GET("$baseUrl/${query.substringAfter(PREFIX_SEARCH)}", headers))
+ .asObservableSuccess()
+ .map { MangasPage(listOf(searchMangaFromElement(it.asJsoup())), false) }
+ } else {
+ super.fetchSearchManga(page, query, filters)
+ }
+ }
+
+ override fun searchMangaSelector() = popularMangaSelector()
+
+ override fun getFilterList(): FilterList {
+ val filters = mutableListOf>()
+
+ filters += if (categoryFilterOptions.isNotEmpty()) {
+ CategoryFilter("Categoria", categoryFilterOptions)
+ } else {
+ Filter.Header("Aperte 'Redefinir' para tentar mostrar as categorias")
+ }
+
+ return FilterList(filters)
+ }
+
+ private fun parseCategories(document: Document): Array> {
+ return document.select("#categories-2 li a")
+ .map { element ->
+ val url = element.absUrl("href")
+ val category = url.split("/").filter { it.isNotBlank() }.last()
+ Pair(element.ownText(), category)
+ }.toTypedArray()
+ }
+
+ class CategoryFilter(
+ displayName: String,
+ private val vals: Array>,
+ ) : Filter.Select(
+ displayName,
+ vals.map { it.first }.toTypedArray(),
+ vals.indexOfFirst { it.second.isEmpty() }.takeIf { it != -1 } ?: 0,
+ ) {
+ fun selectedValue() = vals[state].second
+ }
+
+ private fun categoriesRequest(): Request = GET(baseUrl, headers)
+
+ companion object {
+ val PREFIX_SEARCH: String = "slug:"
+ }
+}
diff --git a/src/pt/brasilhentai/src/eu/kanade/tachiyomi/extension/pt/brasilhentai/BrasilHentaiUrlActivity.kt b/src/pt/brasilhentai/src/eu/kanade/tachiyomi/extension/pt/brasilhentai/BrasilHentaiUrlActivity.kt
new file mode 100644
index 000000000..9efca6cda
--- /dev/null
+++ b/src/pt/brasilhentai/src/eu/kanade/tachiyomi/extension/pt/brasilhentai/BrasilHentaiUrlActivity.kt
@@ -0,0 +1,34 @@
+package eu.kanade.tachiyomi.extension.pt.brasilhentai
+
+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 BrasilHentaiUrlActivity : Activity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ val segment = intent?.data?.lastPathSegment
+ if (segment != null) {
+ val mainIntent = Intent().apply {
+ action = "eu.kanade.tachiyomi.SEARCH"
+ putExtra("query", "${BrasilHentai.PREFIX_SEARCH}$segment")
+ putExtra("filter", packageName)
+ }
+
+ try {
+ startActivity(mainIntent)
+ } catch (e: ActivityNotFoundException) {
+ Log.e("BrasilHentaiUrlActivity", e.toString())
+ }
+ } else {
+ Log.e("BrasilHentaiUrlActivity", "could not parse uri from intent $intent")
+ }
+
+ finish()
+ exitProcess(0)
+ }
+}