diff --git a/src/pt/animeshouse/AndroidManifest.xml b/src/pt/animeshouse/AndroidManifest.xml
new file mode 100644
index 000000000..a71166334
--- /dev/null
+++ b/src/pt/animeshouse/AndroidManifest.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/pt/animeshouse/build.gradle b/src/pt/animeshouse/build.gradle
new file mode 100644
index 000000000..62206a521
--- /dev/null
+++ b/src/pt/animeshouse/build.gradle
@@ -0,0 +1,12 @@
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+
+ext {
+ extName = 'Animes House'
+ pkgNameSuffix = 'pt.animeshouse'
+ extClass = '.AnimesHouse'
+ extVersionCode = 1
+ libVersion = '13'
+}
+
+apply from: "$rootDir/common.gradle"
diff --git a/src/pt/animeshouse/res/mipmap-hdpi/ic_launcher.png b/src/pt/animeshouse/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 000000000..9e65041b8
Binary files /dev/null and b/src/pt/animeshouse/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/src/pt/animeshouse/res/mipmap-mdpi/ic_launcher.png b/src/pt/animeshouse/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 000000000..d7fe121ea
Binary files /dev/null and b/src/pt/animeshouse/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/src/pt/animeshouse/res/mipmap-xhdpi/ic_launcher.png b/src/pt/animeshouse/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 000000000..cb454dedf
Binary files /dev/null and b/src/pt/animeshouse/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/src/pt/animeshouse/res/mipmap-xxhdpi/ic_launcher.png b/src/pt/animeshouse/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 000000000..27e20c734
Binary files /dev/null and b/src/pt/animeshouse/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/src/pt/animeshouse/res/mipmap-xxxhdpi/ic_launcher.png b/src/pt/animeshouse/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 000000000..214c5f342
Binary files /dev/null and b/src/pt/animeshouse/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/src/pt/animeshouse/src/eu/kanade/tachiyomi/animeextension/pt/animeshouse/AHConstants.kt b/src/pt/animeshouse/src/eu/kanade/tachiyomi/animeextension/pt/animeshouse/AHConstants.kt
new file mode 100644
index 000000000..2f5878233
--- /dev/null
+++ b/src/pt/animeshouse/src/eu/kanade/tachiyomi/animeextension/pt/animeshouse/AHConstants.kt
@@ -0,0 +1,11 @@
+package eu.kanade.tachiyomi.animeextension.pt.animeshouse
+
+object AHConstants {
+ const val ACCEPT_LANGUAGE = "pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7"
+ const val USER_AGENT = "Mozilla/5.0 (Linux; Android 10; SM-A307GT Build/QP1A.190711.020;) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/103.0.5060.71 Mobile Safari/537.36"
+ const val MSG_ERR_BODY = "Erro ao obter dados do episódio."
+ const val PREFERRED_QUALITY = "preferred_quality"
+ const val DEFAULT_QUALITY = "720p"
+ const val PREFIX_SEARCH = "slug:"
+ val QUALITY_LIST = arrayOf("240p", "360p", "480p", "720p", "1080p")
+}
diff --git a/src/pt/animeshouse/src/eu/kanade/tachiyomi/animeextension/pt/animeshouse/AHFilters.kt b/src/pt/animeshouse/src/eu/kanade/tachiyomi/animeextension/pt/animeshouse/AHFilters.kt
new file mode 100644
index 000000000..163e07bd2
--- /dev/null
+++ b/src/pt/animeshouse/src/eu/kanade/tachiyomi/animeextension/pt/animeshouse/AHFilters.kt
@@ -0,0 +1,65 @@
+package eu.kanade.tachiyomi.animeextension.pt.animeshouse
+
+import eu.kanade.tachiyomi.animesource.model.AnimeFilter
+import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
+
+object AHFilters {
+
+ open class QueryPartFilter(
+ displayName: String,
+ val vals: Array>
+ ) : AnimeFilter.Select(
+ displayName,
+ vals.map { it.first }.toTypedArray()
+ ) {
+ fun toQueryPart() = vals[state].second
+ }
+
+ private inline fun AnimeFilterList.asQueryPart(): String {
+ return this.filterIsInstance().joinToString("") {
+ (it as QueryPartFilter).toQueryPart()
+ }
+ }
+
+ class GenreFilter : QueryPartFilter("Gênero", AHFiltersData.genres)
+
+ val filterList = AnimeFilterList(
+ AnimeFilter.Header(AHFiltersData.IGNORE_SEARCH_MSG),
+ GenreFilter()
+ )
+
+ data class FilterSearchParams(
+ val genre: String = ""
+ )
+
+ internal fun getSearchParameters(filters: AnimeFilterList): FilterSearchParams {
+ return FilterSearchParams(filters.asQueryPart())
+ }
+
+ private object AHFiltersData {
+
+ const val IGNORE_SEARCH_MSG = "NOTA: O filtro por gênero será IGNORADO ao usar a pesquisa por nome."
+
+ val genres = arrayOf(
+ Pair("Ação", "acao"),
+ Pair("Aventura", "aventura"),
+ Pair("Artes Marciais", "artes-marciais"),
+ Pair("Drama", "drama"),
+ Pair("Ecchi", "ecchi"),
+ Pair("Escolar", "escolar"),
+ Pair("Esporte", "esporte"),
+ Pair("Fantasia", "fantasia"),
+ Pair("Ficção Científica", "ficcao-cientifica"),
+ Pair("Harém", "harem"),
+ Pair("Mecha", "mecha"),
+ Pair("Mistério", "misterio"),
+ Pair("Psicológico", "psicologico"),
+ Pair("Romance", "romance"),
+ Pair("Seinen", "seinen"),
+ Pair("Shounen", "shounen"),
+ Pair("Slice Of Life", "slice-of-life"),
+ Pair("Sobrenatural", "sobrenatural"),
+ Pair("Superpoderes", "superpoderes")
+ )
+ }
+}
diff --git a/src/pt/animeshouse/src/eu/kanade/tachiyomi/animeextension/pt/animeshouse/AHUrlActivity.kt b/src/pt/animeshouse/src/eu/kanade/tachiyomi/animeextension/pt/animeshouse/AHUrlActivity.kt
new file mode 100644
index 000000000..cd0451551
--- /dev/null
+++ b/src/pt/animeshouse/src/eu/kanade/tachiyomi/animeextension/pt/animeshouse/AHUrlActivity.kt
@@ -0,0 +1,42 @@
+package eu.kanade.tachiyomi.animeextension.pt.animeshouse
+
+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://animeshouse.net/anime/ intents
+ * and redirects them to the main Aniyomi process.
+ */
+class AHUrlActivity : Activity() {
+
+ private val TAG = "AHUrlActivity"
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ val pathSegments = intent?.data?.pathSegments
+ if (pathSegments != null && pathSegments.size > 1) {
+ val slug = pathSegments[1]
+ val searchQuery = AHConstants.PREFIX_SEARCH + slug
+ val mainIntent = Intent().apply {
+ action = "eu.kanade.tachiyomi.ANIMESEARCH"
+ putExtra("query", searchQuery)
+ putExtra("filter", packageName)
+ }
+
+ try {
+ startActivity(mainIntent)
+ } catch (e: ActivityNotFoundException) {
+ Log.e(TAG, e.toString())
+ }
+ } else {
+ Log.e(TAG, "could not parse uri from intent $intent")
+ }
+
+ finish()
+ exitProcess(0)
+ }
+}
diff --git a/src/pt/animeshouse/src/eu/kanade/tachiyomi/animeextension/pt/animeshouse/AnimesHouse.kt b/src/pt/animeshouse/src/eu/kanade/tachiyomi/animeextension/pt/animeshouse/AnimesHouse.kt
new file mode 100644
index 000000000..c3172475a
--- /dev/null
+++ b/src/pt/animeshouse/src/eu/kanade/tachiyomi/animeextension/pt/animeshouse/AnimesHouse.kt
@@ -0,0 +1,317 @@
+package eu.kanade.tachiyomi.animeextension.pt.animeshouse
+
+import android.app.Application
+import android.content.SharedPreferences
+import androidx.preference.ListPreference
+import androidx.preference.PreferenceScreen
+import eu.kanade.tachiyomi.animeextension.pt.animeshouse.extractors.EdifierExtractor
+import eu.kanade.tachiyomi.animeextension.pt.animeshouse.extractors.EmbedExtractor
+import eu.kanade.tachiyomi.animeextension.pt.animeshouse.extractors.GenericExtractor
+import eu.kanade.tachiyomi.animeextension.pt.animeshouse.extractors.JsUnpacker
+import eu.kanade.tachiyomi.animeextension.pt.animeshouse.extractors.McpExtractor
+import eu.kanade.tachiyomi.animeextension.pt.animeshouse.extractors.MpFourDooExtractor
+import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
+import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
+import eu.kanade.tachiyomi.animesource.model.AnimesPage
+import eu.kanade.tachiyomi.animesource.model.SAnime
+import eu.kanade.tachiyomi.animesource.model.SEpisode
+import eu.kanade.tachiyomi.animesource.model.Video
+import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
+import eu.kanade.tachiyomi.network.GET
+import eu.kanade.tachiyomi.network.POST
+import eu.kanade.tachiyomi.network.asObservableSuccess
+import eu.kanade.tachiyomi.util.asJsoup
+import okhttp3.FormBody
+import okhttp3.Headers
+import okhttp3.OkHttpClient
+import okhttp3.Request
+import okhttp3.Response
+import org.jsoup.nodes.Document
+import org.jsoup.nodes.Element
+import rx.Observable
+import uy.kohesive.injekt.Injekt
+import uy.kohesive.injekt.api.get
+import java.lang.Exception
+
+class AnimesHouse : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
+
+ override val name = "Animes House"
+
+ override val baseUrl = "https://animeshouse.net"
+
+ override val lang = "pt-BR"
+
+ override val supportsLatest = true
+
+ override val client: OkHttpClient = network.client
+
+ override fun headersBuilder(): Headers.Builder = Headers.Builder()
+ .add("Referer", baseUrl)
+ .add("Accept-Language", AHConstants.ACCEPT_LANGUAGE)
+ .add("User-Agent", AHConstants.USER_AGENT)
+
+ private val preferences: SharedPreferences by lazy {
+ Injekt.get().getSharedPreferences("source_$id", 0x0000)
+ }
+
+ // ============================== Popular ===============================
+ override fun popularAnimeSelector(): String = "div#featured-titles div.poster"
+
+ override fun popularAnimeRequest(page: Int): Request = GET(baseUrl, headers)
+
+ override fun popularAnimeFromElement(element: Element): SAnime {
+ val anime = SAnime.create()
+ val img = element.selectFirst("img")
+ anime.setUrlWithoutDomain(element.selectFirst("a").attr("href"))
+ anime.title = img.attr("alt")
+ anime.thumbnail_url = img.attr("src")
+ return anime
+ }
+
+ override fun popularAnimeNextPageSelector() = throw Exception("not used")
+
+ override fun popularAnimeParse(response: Response): AnimesPage {
+ val document = response.asJsoup()
+ val animes = document.select(popularAnimeSelector()).map { element ->
+ popularAnimeFromElement(element)
+ }
+ return AnimesPage(animes, false)
+ }
+
+ // ============================== Episodes ==============================
+ override fun episodeListSelector(): String = "ul.episodios > li"
+
+ override fun episodeListParse(response: Response): List {
+ val doc = getRealDoc(response.asJsoup())
+ val epList = doc.select(episodeListSelector())
+ if (epList.size < 1) {
+ val episode = SEpisode.create()
+ episode.setUrlWithoutDomain(response.request.url.toString())
+ episode.episode_number = 1F
+ episode.name = "Filme"
+ return listOf(episode)
+ }
+ return epList.reversed().map { episodeFromElement(it) }
+ }
+
+ override fun episodeFromElement(element: Element): SEpisode {
+ val episode = SEpisode.create()
+ val origName = element.selectFirst("div.numerando").text()
+
+ episode.episode_number = origName.substring(origName.indexOf("-") + 1)
+ .toFloat() + if ("Dub" in origName) 0.5F else 0F
+ episode.name = "Temp " + origName.replace(" - ", ": Ep ")
+ episode.setUrlWithoutDomain(element.selectFirst("a").attr("href"))
+ return episode
+ }
+
+ // ============================ Video Links =============================
+ private fun getPlayerUrl(player: Element): String {
+ val body = FormBody.Builder()
+ .add("action", "doo_player_ajax")
+ .add("post", player.attr("data-post"))
+ .add("nume", player.attr("data-nume"))
+ .add("type", player.attr("data-type"))
+ .build()
+ val doc = client.newCall(
+ POST("$baseUrl/wp-admin/admin-ajax.php", headers, body)
+ )
+ .execute()
+ .asJsoup()
+ val iframe = doc.selectFirst("iframe")
+ return iframe.attr("src")
+ }
+
+ override fun videoListParse(response: Response): List