diff --git a/src/pt/sukianimes/AndroidManifest.xml b/src/pt/sukianimes/AndroidManifest.xml
new file mode 100644
index 000000000..6a0cda01c
--- /dev/null
+++ b/src/pt/sukianimes/AndroidManifest.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/pt/sukianimes/build.gradle b/src/pt/sukianimes/build.gradle
new file mode 100644
index 000000000..c15a13c50
--- /dev/null
+++ b/src/pt/sukianimes/build.gradle
@@ -0,0 +1,14 @@
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlinx-serialization'
+
+ext {
+ extName = 'SukiAnimes'
+ pkgNameSuffix = 'pt.sukianimes'
+ extClass = '.SukiAnimes'
+ extVersionCode = 1
+ libVersion = '13'
+}
+
+
+apply from: "$rootDir/common.gradle"
diff --git a/src/pt/sukianimes/res/mipmap-hdpi/ic_launcher.png b/src/pt/sukianimes/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 000000000..726f2db9c
Binary files /dev/null and b/src/pt/sukianimes/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/src/pt/sukianimes/res/mipmap-mdpi/ic_launcher.png b/src/pt/sukianimes/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 000000000..8d77007e0
Binary files /dev/null and b/src/pt/sukianimes/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/src/pt/sukianimes/res/mipmap-xhdpi/ic_launcher.png b/src/pt/sukianimes/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 000000000..b5f961251
Binary files /dev/null and b/src/pt/sukianimes/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/src/pt/sukianimes/res/mipmap-xxhdpi/ic_launcher.png b/src/pt/sukianimes/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 000000000..c0caa463d
Binary files /dev/null and b/src/pt/sukianimes/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/src/pt/sukianimes/res/mipmap-xxxhdpi/ic_launcher.png b/src/pt/sukianimes/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 000000000..27529bd4b
Binary files /dev/null and b/src/pt/sukianimes/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/src/pt/sukianimes/src/eu/kanade/tachiyomi/animeextension/pt/sukianimes/SKFilters.kt b/src/pt/sukianimes/src/eu/kanade/tachiyomi/animeextension/pt/sukianimes/SKFilters.kt
new file mode 100644
index 000000000..818374a09
--- /dev/null
+++ b/src/pt/sukianimes/src/eu/kanade/tachiyomi/animeextension/pt/sukianimes/SKFilters.kt
@@ -0,0 +1,170 @@
+package eu.kanade.tachiyomi.animeextension.pt.sukianimes
+
+import eu.kanade.tachiyomi.animesource.model.AnimeFilter
+import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
+
+object SKFilters {
+
+ open class QueryPartFilter(
+ displayName: String,
+ val vals: Array>
+ ) : AnimeFilter.Select(
+ displayName,
+ vals.map { it.first }.toTypedArray()
+ ) {
+
+ fun toQueryPart() = vals[state].second
+ }
+
+ private class CheckBoxVal(name: String, state: Boolean = false) : AnimeFilter.CheckBox(name, state)
+ open class CheckBoxFilterList(name: String, values: List) : AnimeFilter.Group(name, values)
+
+ 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 AdultFilter : AnimeFilter.CheckBox("Exibir animes adultos", true)
+
+ class FormatFilter : QueryPartFilter("Formato", SKFiltersData.formats)
+ class StatusFilter : QueryPartFilter("Status do anime", SKFiltersData.status)
+ class TypeFilter : QueryPartFilter("Tipo de vídeo", SKFiltersData.types)
+
+ class GenresFilter : CheckBoxFilterList(
+ "Gêneros",
+ SKFiltersData.genres.map { CheckBoxVal(it.first, false) }
+ )
+
+ // Mimicking the order of filters on the source
+ val filterList = AnimeFilterList(
+ TypeFilter(),
+ StatusFilter(),
+ AdultFilter(),
+ FormatFilter(),
+ GenresFilter()
+ )
+
+ data class FilterSearchParams(
+ val adult: Boolean = true,
+ val format: String = "",
+ val genres: List = emptyList(),
+ val status: String = "",
+ val type: String = "",
+ )
+
+ internal fun getSearchParameters(filters: AnimeFilterList): FilterSearchParams {
+ val genres = filters.getFirst().state
+ .mapNotNull { genre ->
+ if (genre.state) {
+ SKFiltersData.genres.find { it.first == genre.name }!!.second
+ } else { null }
+ }.toList()
+
+ return FilterSearchParams(
+ filters.getFirst().state,
+ filters.asQueryPart(),
+ genres,
+ filters.asQueryPart(),
+ filters.asQueryPart()
+ )
+ }
+
+ private object SKFiltersData {
+ val every = Pair("Qualquer um", "")
+
+ val types = arrayOf(
+ every,
+ Pair("Legendado", "1"),
+ Pair("Dublado", "2")
+ )
+
+ val status = arrayOf(
+ every,
+ Pair("Completo", "Completo"),
+ Pair("Em lançamento", "Lançamento")
+ )
+
+ val formats = arrayOf(
+ every,
+ Pair("Anime", "Anime"),
+ Pair("Filme", "Filme")
+ )
+
+ val genres = arrayOf(
+ Pair("Artes Marciais", "12"),
+ Pair("Aventura", "13"),
+ Pair("Ação", "5"),
+ Pair("Boys Love", "1125"),
+ Pair("Carros", "945"),
+ Pair("Chinês", "1032"),
+ Pair("Comédia Romântica", "15"),
+ Pair("Comédia", "14"),
+ Pair("Corrida", "1690"),
+ Pair("Culinária", "576"),
+ Pair("Dementia", "164"),
+ Pair("Demônios", "35"),
+ Pair("Drama", "9"),
+ Pair("Ecchi", "16"),
+ Pair("Erótico", "1203"),
+ Pair("Escolar", "812"),
+ Pair("Espaço", "429"),
+ Pair("Esporte", "17"),
+ Pair("Fantasia", "10"),
+ Pair("Ficção Científica", "18"),
+ Pair("Game", "156"),
+ Pair("Girls Love", "1228"),
+ Pair("Gore", "1708"),
+ Pair("Harém", "69"),
+ Pair("Histórico", "88"),
+ Pair("Horror", "165"),
+ Pair("Idols", "1702"),
+ Pair("Insanidade", "891"),
+ Pair("Isekai", "1138"),
+ Pair("Jogos", "19"),
+ Pair("Josei", "1345"),
+ Pair("Kids", "847"),
+ Pair("Magia", "20"),
+ Pair("Maid", "1677"),
+ Pair("Mecha", "21"),
+ Pair("Militar", "6"),
+ Pair("Mistério", "7"),
+ Pair("Munyuu", "1074"),
+ Pair("Musical", "22"),
+ Pair("Novel", "252"),
+ Pair("Parody", "1197"),
+ Pair("Paródia", "207"),
+ Pair("Performing Arts", "1564"),
+ Pair("Piratas", "172"),
+ Pair("Polícia", "229"),
+ Pair("Psicológico", "50"),
+ Pair("RPG", "94"),
+ Pair("Romance", "23"),
+ Pair("Samurai", "439"),
+ Pair("School", "1065"),
+ Pair("Sci-Fi", "42"),
+ Pair("Seinen", "24"),
+ Pair("Shoujo", "210"),
+ Pair("Shoujo-ai", "25"),
+ Pair("Shounen", "11"),
+ Pair("Shounen-AI", "322"),
+ Pair("Slice of Life", "26"),
+ Pair("Sobrenatural", "27"),
+ Pair("Super Poder", "8"),
+ Pair("Suspense", "230"),
+ Pair("Terror", "28"),
+ Pair("Thriller", "51"),
+ Pair("Tragedia", "269"),
+ Pair("Vampiro", "134"),
+ Pair("Vida Diaria", "253"),
+ Pair("Vida Escolar", "29"),
+ Pair("Violência", "440"),
+ Pair("Yaoi", "612"),
+ Pair("Yuri", "1497")
+ )
+ }
+}
diff --git a/src/pt/sukianimes/src/eu/kanade/tachiyomi/animeextension/pt/sukianimes/SKUrlActivity.kt b/src/pt/sukianimes/src/eu/kanade/tachiyomi/animeextension/pt/sukianimes/SKUrlActivity.kt
new file mode 100644
index 000000000..5829ab1b9
--- /dev/null
+++ b/src/pt/sukianimes/src/eu/kanade/tachiyomi/animeextension/pt/sukianimes/SKUrlActivity.kt
@@ -0,0 +1,41 @@
+package eu.kanade.tachiyomi.animeextension.pt.sukianimes
+
+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://sukianimes.com/anime/- intents
+ * and redirects them to the main Aniyomi process.
+ */
+class SKUrlActivity : Activity() {
+
+ private val TAG = "SKUrlActivity"
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ val pathSegments = intent?.data?.pathSegments
+ if (pathSegments != null && pathSegments.size > 1) {
+ val item = pathSegments[1]
+ val mainIntent = Intent().apply {
+ action = "eu.kanade.tachiyomi.ANIMESEARCH"
+ putExtra("query", "${SukiAnimes.PREFIX_SEARCH}$item")
+ 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/sukianimes/src/eu/kanade/tachiyomi/animeextension/pt/sukianimes/SukiAnimes.kt b/src/pt/sukianimes/src/eu/kanade/tachiyomi/animeextension/pt/sukianimes/SukiAnimes.kt
new file mode 100644
index 000000000..1a02a793c
--- /dev/null
+++ b/src/pt/sukianimes/src/eu/kanade/tachiyomi/animeextension/pt/sukianimes/SukiAnimes.kt
@@ -0,0 +1,274 @@
+package eu.kanade.tachiyomi.animeextension.pt.sukianimes
+
+import eu.kanade.tachiyomi.animeextension.pt.sukianimes.dto.AnimeDto
+import eu.kanade.tachiyomi.animeextension.pt.sukianimes.dto.SearchResultDto
+import eu.kanade.tachiyomi.animeextension.pt.sukianimes.extractors.BloggerExtractor
+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.json.Json
+import okhttp3.FormBody
+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.api.get
+import kotlin.Exception
+
+class SukiAnimes : ParsedAnimeHttpSource() {
+
+ override val name = "SukiAnimes"
+
+ override val baseUrl = "https://sukianimes.com"
+ private val API_URL = "$baseUrl/wp-admin/admin-ajax.php"
+ private val NONCE_URL = "$baseUrl/?js_global=1&ver=6.1.1"
+
+ override val lang = "pt-BR"
+
+ override val supportsLatest = true
+
+ override val client: OkHttpClient = network.client
+
+ private val json = Json {
+ ignoreUnknownKeys = true
+ }
+
+ // ============================== Popular ===============================
+ // This source doesn't have a popular anime page, so we'll grab
+ // the latest anime additions instead.
+ override fun popularAnimeSelector() = "section.animeslancamentos div.aniItem > a"
+ override fun popularAnimeRequest(page: Int) = GET(baseUrl)
+ override fun popularAnimeNextPageSelector() = null // disable it
+ override fun popularAnimeFromElement(element: Element): SAnime {
+ return SAnime.create().apply {
+ setUrlWithoutDomain(element.attr("href"))
+ title = element.attr("title")
+ thumbnail_url = element.selectFirst("img").attr("src")
+ }
+ }
+
+ // ============================== Episodes ==============================
+ override fun episodeListSelector() = "div.ultEpsContainerItem > a"
+ private fun episodeListNextPageSelector() = latestUpdatesNextPageSelector()
+
+ override fun fetchEpisodeList(anime: SAnime): Observable
> {
+ return client.newCall(episodeListRequest(anime))
+ .asObservableSuccess()
+ .map { response ->
+ val realDoc = getRealDoc(response.asJsoup())
+ episodeListParse(realDoc).reversed()
+ }
+ }
+
+ override fun episodeListParse(response: Response): List {
+ return episodeListParse(response.asJsoup())
+ }
+
+ private fun episodeListParse(doc: Document): List {
+ val episodeList = mutableListOf()
+ val eps = doc.select(episodeListSelector()).map(::episodeFromElement)
+ episodeList.addAll(eps)
+ val nextPageElement = doc.selectFirst(episodeListNextPageSelector())
+ if (nextPageElement != null) {
+ val nextUrl = nextPageElement.attr("href")
+ val res = client.newCall(GET(nextUrl)).execute()
+ episodeList.addAll(episodeListParse(res))
+ }
+ return episodeList
+ }
+
+ override fun episodeFromElement(element: Element): SEpisode {
+ return SEpisode.create().apply {
+ setUrlWithoutDomain(element.attr("href"))
+ val title = element.attr("title")
+ name = title
+ episode_number = runCatching {
+ title.trim().substringAfterLast(" ").toFloat()
+ }.getOrDefault(0F)
+ }
+ }
+
+ // ============================ Video Links =============================
+ override fun videoListParse(response: Response): List