diff --git a/src/pt/betteranime/AndroidManifest.xml b/src/pt/betteranime/AndroidManifest.xml
new file mode 100644
index 000000000..94339ee7a
--- /dev/null
+++ b/src/pt/betteranime/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/src/pt/betteranime/build.gradle b/src/pt/betteranime/build.gradle
new file mode 100644
index 000000000..d0952c558
--- /dev/null
+++ b/src/pt/betteranime/build.gradle
@@ -0,0 +1,17 @@
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlinx-serialization'
+
+ext {
+ extName = 'Better Anime'
+ pkgNameSuffix = 'pt.betteranime'
+ extClass = '.BetterAnime'
+ extVersionCode = 1
+ libVersion = '12'
+}
+
+dependencies {
+ compileOnly 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2'
+}
+
+apply from: "$rootDir/common.gradle"
diff --git a/src/pt/betteranime/res/mipmap-hdpi/ic_launcher.png b/src/pt/betteranime/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 000000000..4f2f2aecc
Binary files /dev/null and b/src/pt/betteranime/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/src/pt/betteranime/res/mipmap-mdpi/ic_launcher.png b/src/pt/betteranime/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 000000000..9160b10a4
Binary files /dev/null and b/src/pt/betteranime/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/src/pt/betteranime/res/mipmap-xhdpi/ic_launcher.png b/src/pt/betteranime/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 000000000..78c2ef2a8
Binary files /dev/null and b/src/pt/betteranime/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/src/pt/betteranime/res/mipmap-xxhdpi/ic_launcher.png b/src/pt/betteranime/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 000000000..dfec36fad
Binary files /dev/null and b/src/pt/betteranime/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/src/pt/betteranime/res/mipmap-xxxhdpi/ic_launcher.png b/src/pt/betteranime/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 000000000..474fdb69a
Binary files /dev/null and b/src/pt/betteranime/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/src/pt/betteranime/src/eu/kanade/tachiyomi/animeextension/pt/betteranime/BAFilters.kt b/src/pt/betteranime/src/eu/kanade/tachiyomi/animeextension/pt/betteranime/BAFilters.kt
new file mode 100644
index 000000000..bb230de26
--- /dev/null
+++ b/src/pt/betteranime/src/eu/kanade/tachiyomi/animeextension/pt/betteranime/BAFilters.kt
@@ -0,0 +1,130 @@
+package eu.kanade.tachiyomi.animeextension.pt.betteranime
+
+import eu.kanade.tachiyomi.animesource.model.AnimeFilter
+import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
+
+object BAFilters {
+
+ open class QueryPartFilter(
+ displayName: String,
+ val vals: Array>
+ ) : AnimeFilter.Select(
+ displayName,
+ vals.map { it.first }.toTypedArray()
+ ) {
+
+ fun toQueryPart() = vals[state].second
+ }
+
+ open class CheckBoxFilterList(name: String, values: List) : AnimeFilter.Group(name, values)
+ private class CheckBoxVal(name: String, state: Boolean = false) : AnimeFilter.CheckBox(name, state)
+
+ private inline fun AnimeFilterList.getFirst(): R {
+ return this.filterIsInstance().first()
+ }
+
+ private inline fun AnimeFilterList.asQueryPart(): String {
+ return this.filterIsInstance().joinToString("") {
+ (it as QueryPartFilter).toQueryPart()
+ }
+ }
+
+ class LanguageFilter : QueryPartFilter("Idioma", BAFiltersData.languages)
+ class YearFilter : QueryPartFilter("Ano", BAFiltersData.years)
+
+ class GenresFilter : CheckBoxFilterList(
+ "Gêneros",
+ BAFiltersData.genres.map { CheckBoxVal(it.first, false) }
+ )
+
+ val filterList = AnimeFilterList(
+ LanguageFilter(),
+ YearFilter(),
+ GenresFilter()
+ )
+
+ data class FilterSearchParams(
+ val language: String = "",
+ val year: String = "",
+ val genres: List = emptyList()
+ )
+
+ internal fun getSearchParameters(filters: AnimeFilterList): FilterSearchParams {
+ val genres = listOf("") + filters.getFirst().state
+ .mapNotNull { genre ->
+ if (genre.state) {
+ BAFiltersData.genres.find { it.first == genre.name }!!.second
+ } else { null }
+ }.toList()
+
+ return FilterSearchParams(
+ filters.asQueryPart(),
+ filters.asQueryPart(),
+ genres
+ )
+ }
+
+ private object BAFiltersData {
+ val every = Pair("Qualquer um", "")
+
+ val languages = arrayOf(
+ every,
+ Pair("Legendado", "legendado"),
+ Pair("Dublado", "dublado")
+ )
+
+ val years = arrayOf(every) + (2022 downTo 1976).map {
+ Pair(it.toString(), it.toString())
+ }.toTypedArray()
+
+ val genres = arrayOf(
+ Pair("Ação", "acao"),
+ Pair("Artes Marciais", "artes-marciais"),
+ Pair("Aventura", "aventura"),
+ Pair("Comédia", "comedia"),
+ Pair("Cotidiano", "cotidiano"),
+ Pair("Demência", "demencia"),
+ Pair("Demônios", "demonios"),
+ Pair("Drama", "drama"),
+ Pair("Ecchi", "ecchi"),
+ Pair("Escolar", "escolar"),
+ Pair("Espacial", "espacial"),
+ Pair("Esportes", "esportes"),
+ Pair("Fantasia", "fantasia"),
+ Pair("Ficção Científica", "ficcao-cientifica"),
+ Pair("Game", "game"),
+ Pair("Harém", "harem"),
+ Pair("Histórico", "historico"),
+ Pair("Horror", "horror"),
+ Pair("Infantil", "infantil"),
+ Pair("Josei", "josei"),
+ Pair("Magia", "magia"),
+ Pair("Mecha", "mecha"),
+ Pair("Militar", "militar"),
+ Pair("Mistério", "misterio"),
+ Pair("Musical", "musical"),
+ Pair("Paródia", "parodia"),
+ Pair("Policial", "policial"),
+ Pair("Psicológico", "psicologico"),
+ Pair("Romance", "romance"),
+ Pair("Samurai", "samurai"),
+ Pair("Sci-Fi", "sci-fi"),
+ Pair("Seinen", "seinen"),
+ Pair("Shoujo-Ai", "shoujo-ai"),
+ Pair("Shoujo", "shoujo"),
+ Pair("Shounen-Ai", "shounen-ai"),
+ Pair("Shounen", "shounen"),
+ Pair("Slice of Life", "slice-of-life"),
+ Pair("Sobrenatural", "sobrenatural"),
+ Pair("Super Poderes", "super-poderes"),
+ Pair("Suspense", "suspense"),
+ Pair("Terror", "terror"),
+ Pair("Thriller", "thriller"),
+ Pair("Tragédia", "tragedia"),
+ Pair("Vampiros", "vampiros"),
+ Pair("Vida Escolar", "vida-escolar"),
+ Pair("Yaoi", "yaoi"),
+ Pair("Yuri", "yuri"),
+ )
+ }
+}
diff --git a/src/pt/betteranime/src/eu/kanade/tachiyomi/animeextension/pt/betteranime/BAUtils.kt b/src/pt/betteranime/src/eu/kanade/tachiyomi/animeextension/pt/betteranime/BAUtils.kt
new file mode 100644
index 000000000..bb0d3b200
--- /dev/null
+++ b/src/pt/betteranime/src/eu/kanade/tachiyomi/animeextension/pt/betteranime/BAUtils.kt
@@ -0,0 +1,12 @@
+package eu.kanade.tachiyomi.animeextension.pt.betteranime
+
+// Terrible way to reinvent the wheel, i just didnt wanted to use apache commons.
+fun String.unescape(): String {
+ return UNICODE_REGEX.replace(this) {
+ it.groupValues[1]
+ .toInt(16)
+ .toChar()
+ .toString()
+ }.replace("\\", "")
+}
+private val UNICODE_REGEX = "\\\\u(\\d+)".toRegex()
diff --git a/src/pt/betteranime/src/eu/kanade/tachiyomi/animeextension/pt/betteranime/BetterAnime.kt b/src/pt/betteranime/src/eu/kanade/tachiyomi/animeextension/pt/betteranime/BetterAnime.kt
new file mode 100644
index 000000000..1eb79671d
--- /dev/null
+++ b/src/pt/betteranime/src/eu/kanade/tachiyomi/animeextension/pt/betteranime/BetterAnime.kt
@@ -0,0 +1,319 @@
+package eu.kanade.tachiyomi.animeextension.pt.betteranime
+
+import android.app.Application
+import android.content.SharedPreferences
+import android.util.Log
+import androidx.preference.ListPreference
+import androidx.preference.PreferenceScreen
+import eu.kanade.tachiyomi.animeextension.pt.betteranime.dto.LivewireResponseDto
+import eu.kanade.tachiyomi.animeextension.pt.betteranime.dto.PayloadData
+import eu.kanade.tachiyomi.animeextension.pt.betteranime.dto.PayloadItem
+import eu.kanade.tachiyomi.animeextension.pt.betteranime.extractors.BetterAnimeExtractor
+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 kotlinx.serialization.decodeFromString
+import kotlinx.serialization.encodeToString
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.json.JsonPrimitive
+import okhttp3.Headers
+import okhttp3.MediaType.Companion.toMediaType
+import okhttp3.OkHttpClient
+import okhttp3.Request
+import okhttp3.RequestBody.Companion.toRequestBody
+import okhttp3.Response
+import org.jsoup.Jsoup
+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 BetterAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
+
+ override val name = "Better Anime"
+
+ override val baseUrl = "https://betteranime.net"
+
+ override val lang = "pt-BR"
+
+ override val supportsLatest = true
+
+ override val client: OkHttpClient = network.cloudflareClient
+
+ private val json = Json {
+ ignoreUnknownKeys = true
+ }
+
+ private val preferences: SharedPreferences by lazy {
+ Injekt.get().getSharedPreferences("source_$id", 0x0000)
+ }
+
+ override fun headersBuilder(): Headers.Builder = Headers.Builder()
+ .add("Referer", baseUrl)
+ .add("Accept-Language", ACCEPT_LANGUAGE)
+
+ // ============================== Popular ===============================
+ private fun nextPageSelector(): String = "ul.pagination li.page-item:contains(›)"
+ override fun popularAnimeNextPageSelector() = throw Exception("not used")
+ override fun popularAnimeSelector(): String = "div.list-animes article"
+
+ override fun popularAnimeFromElement(element: Element): SAnime {
+ val anime = SAnime.create()
+ val img = element.selectFirst("img")!!
+ val url = element.selectFirst("a")?.attr("href")!!
+ anime.setUrlWithoutDomain(url)
+ anime.title = element.selectFirst("h3")?.text()!!
+ anime.thumbnail_url = "https:" + img.attr("src")
+ return anime
+ }
+
+ // The site doesn't have a popular anime tab, so we use the latest anime page instead.
+ override fun popularAnimeRequest(page: Int): Request =
+ GET("$baseUrl/ultimosAdicionados?page=$page")
+
+ override fun popularAnimeParse(response: Response): AnimesPage {
+ val document = response.asJsoup()
+ val animes = document.select(popularAnimeSelector()).map { element ->
+ popularAnimeFromElement(element)
+ }
+ val hasNextPage = hasNextPage(document)
+ return AnimesPage(animes, hasNextPage)
+ }
+
+ // ============================== Episodes ==============================
+ override fun episodeListSelector(): String = "ul#episodesList > li.list-group-item-action > a"
+
+ override fun episodeListParse(response: Response): List {
+ val document = response.asJsoup()
+ val episodes = document.select(episodeListSelector()).map { element ->
+ episodeFromElement(element)
+ }
+ return episodes.reversed()
+ }
+
+ override fun episodeFromElement(element: Element): SEpisode {
+ val episode = SEpisode.create()
+ val episodeName = element.text()
+ episode.setUrlWithoutDomain(element.attr("href"))
+ episode.name = episodeName
+ episode.episode_number = try {
+ episodeName.substringAfterLast(" ").toFloat()
+ } catch (e: NumberFormatException) { 0F }
+ return episode
+ }
+
+ // ============================ Video Links =============================
+ override fun videoListParse(response: Response): List