diff --git a/src/en/animeowl/AndroidManifest.xml b/src/en/animeowl/AndroidManifest.xml
new file mode 100644
index 000000000..acb4de356
--- /dev/null
+++ b/src/en/animeowl/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/src/en/animeowl/build.gradle b/src/en/animeowl/build.gradle
new file mode 100644
index 000000000..7a5950f42
--- /dev/null
+++ b/src/en/animeowl/build.gradle
@@ -0,0 +1,17 @@
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+
+ext {
+ extName = 'AnimeOwl'
+ pkgNameSuffix = 'en.animeowl'
+ extClass = '.AnimeOwl'
+ extVersionCode = 1
+ libVersion = '13'
+}
+
+dependencies {
+ implementation(project(':lib-dood-extractor'))
+ implementation(project(':lib-streamsb-extractor'))
+}
+
+apply from: "$rootDir/common.gradle"
diff --git a/src/en/animeowl/res/mipmap-anydpi-v26/ic_launcher.xml b/src/en/animeowl/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 000000000..90f958096
--- /dev/null
+++ b/src/en/animeowl/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/en/animeowl/res/mipmap-hdpi/ic_launcher.png b/src/en/animeowl/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 000000000..57c450c2a
Binary files /dev/null and b/src/en/animeowl/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/src/en/animeowl/res/mipmap-hdpi/ic_launcher_adaptive_back.png b/src/en/animeowl/res/mipmap-hdpi/ic_launcher_adaptive_back.png
new file mode 100644
index 000000000..4ea92bd5e
Binary files /dev/null and b/src/en/animeowl/res/mipmap-hdpi/ic_launcher_adaptive_back.png differ
diff --git a/src/en/animeowl/res/mipmap-hdpi/ic_launcher_adaptive_fore.png b/src/en/animeowl/res/mipmap-hdpi/ic_launcher_adaptive_fore.png
new file mode 100644
index 000000000..8014e4da6
Binary files /dev/null and b/src/en/animeowl/res/mipmap-hdpi/ic_launcher_adaptive_fore.png differ
diff --git a/src/en/animeowl/res/mipmap-mdpi/ic_launcher.png b/src/en/animeowl/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 000000000..41bcb73ac
Binary files /dev/null and b/src/en/animeowl/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/src/en/animeowl/res/mipmap-mdpi/ic_launcher_adaptive_back.png b/src/en/animeowl/res/mipmap-mdpi/ic_launcher_adaptive_back.png
new file mode 100644
index 000000000..8d66ef758
Binary files /dev/null and b/src/en/animeowl/res/mipmap-mdpi/ic_launcher_adaptive_back.png differ
diff --git a/src/en/animeowl/res/mipmap-mdpi/ic_launcher_adaptive_fore.png b/src/en/animeowl/res/mipmap-mdpi/ic_launcher_adaptive_fore.png
new file mode 100644
index 000000000..108d5a9ce
Binary files /dev/null and b/src/en/animeowl/res/mipmap-mdpi/ic_launcher_adaptive_fore.png differ
diff --git a/src/en/animeowl/res/mipmap-xhdpi/ic_launcher.png b/src/en/animeowl/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 000000000..ce921ca78
Binary files /dev/null and b/src/en/animeowl/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/src/en/animeowl/res/mipmap-xhdpi/ic_launcher_adaptive_back.png b/src/en/animeowl/res/mipmap-xhdpi/ic_launcher_adaptive_back.png
new file mode 100644
index 000000000..156f64239
Binary files /dev/null and b/src/en/animeowl/res/mipmap-xhdpi/ic_launcher_adaptive_back.png differ
diff --git a/src/en/animeowl/res/mipmap-xhdpi/ic_launcher_adaptive_fore.png b/src/en/animeowl/res/mipmap-xhdpi/ic_launcher_adaptive_fore.png
new file mode 100644
index 000000000..dfaadd63e
Binary files /dev/null and b/src/en/animeowl/res/mipmap-xhdpi/ic_launcher_adaptive_fore.png differ
diff --git a/src/en/animeowl/res/mipmap-xxhdpi/ic_launcher.png b/src/en/animeowl/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 000000000..b4c5b268e
Binary files /dev/null and b/src/en/animeowl/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/src/en/animeowl/res/mipmap-xxhdpi/ic_launcher_adaptive_back.png b/src/en/animeowl/res/mipmap-xxhdpi/ic_launcher_adaptive_back.png
new file mode 100644
index 000000000..0425f0ad0
Binary files /dev/null and b/src/en/animeowl/res/mipmap-xxhdpi/ic_launcher_adaptive_back.png differ
diff --git a/src/en/animeowl/res/mipmap-xxhdpi/ic_launcher_adaptive_fore.png b/src/en/animeowl/res/mipmap-xxhdpi/ic_launcher_adaptive_fore.png
new file mode 100644
index 000000000..dfdaf4301
Binary files /dev/null and b/src/en/animeowl/res/mipmap-xxhdpi/ic_launcher_adaptive_fore.png differ
diff --git a/src/en/animeowl/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/animeowl/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 000000000..d739f97cd
Binary files /dev/null and b/src/en/animeowl/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/src/en/animeowl/res/mipmap-xxxhdpi/ic_launcher_adaptive_back.png b/src/en/animeowl/res/mipmap-xxxhdpi/ic_launcher_adaptive_back.png
new file mode 100644
index 000000000..8ebe16407
Binary files /dev/null and b/src/en/animeowl/res/mipmap-xxxhdpi/ic_launcher_adaptive_back.png differ
diff --git a/src/en/animeowl/res/mipmap-xxxhdpi/ic_launcher_adaptive_fore.png b/src/en/animeowl/res/mipmap-xxxhdpi/ic_launcher_adaptive_fore.png
new file mode 100644
index 000000000..ffae00518
Binary files /dev/null and b/src/en/animeowl/res/mipmap-xxxhdpi/ic_launcher_adaptive_fore.png differ
diff --git a/src/en/animeowl/res/play_store_512.png b/src/en/animeowl/res/play_store_512.png
new file mode 100644
index 000000000..8305be176
Binary files /dev/null and b/src/en/animeowl/res/play_store_512.png differ
diff --git a/src/en/animeowl/src/eu/kanade/tachiyomi/animeextension/en/animeowl/AnimeOwl.kt b/src/en/animeowl/src/eu/kanade/tachiyomi/animeextension/en/animeowl/AnimeOwl.kt
new file mode 100644
index 000000000..5dd3e3502
--- /dev/null
+++ b/src/en/animeowl/src/eu/kanade/tachiyomi/animeextension/en/animeowl/AnimeOwl.kt
@@ -0,0 +1,396 @@
+package eu.kanade.tachiyomi.animeextension.en.animeowl
+
+import android.app.Application
+import android.content.SharedPreferences
+import androidx.preference.ListPreference
+import androidx.preference.PreferenceScreen
+import eu.kanade.tachiyomi.animeextension.en.animeowl.extractors.GogoCdnExtractor
+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.lib.doodextractor.DoodExtractor
+import eu.kanade.tachiyomi.lib.streamsbextractor.StreamSBExtractor
+import eu.kanade.tachiyomi.network.GET
+import eu.kanade.tachiyomi.network.POST
+import eu.kanade.tachiyomi.util.asJsoup
+import kotlinx.serialization.ExperimentalSerializationApi
+import kotlinx.serialization.decodeFromString
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.json.JsonObject
+import kotlinx.serialization.json.float
+import kotlinx.serialization.json.int
+import kotlinx.serialization.json.jsonArray
+import kotlinx.serialization.json.jsonObject
+import kotlinx.serialization.json.jsonPrimitive
+import okhttp3.Headers
+import okhttp3.HttpUrl.Companion.toHttpUrl
+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 uy.kohesive.injekt.injectLazy
+import kotlin.math.ceil
+
+@ExperimentalSerializationApi
+class AnimeOwl : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
+
+ override val name = "AnimeOwl"
+
+ override val baseUrl by lazy { preferences.getString("preferred_domain", "https://animeowl.net")!! }
+
+ override val lang = "en"
+
+ override val supportsLatest = true
+
+ override val client: OkHttpClient = network.cloudflareClient
+
+ private val json: Json by injectLazy()
+
+ private val preferences: SharedPreferences by lazy {
+ Injekt.get().getSharedPreferences("source_$id", 0x0000)
+ }
+
+ // ============================== Popular ===============================
+ override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/trending?page=$page")
+
+ override fun popularAnimeSelector(): String = "div#anime-list > div.recent-anime"
+
+ override fun popularAnimeNextPageSelector(): String = "ul.pagination > li.page-item > a[rel=next]"
+
+ override fun popularAnimeFromElement(element: Element): SAnime {
+ return SAnime.create().apply {
+ setUrlWithoutDomain(element.select("div > a").attr("href"))
+ thumbnail_url = element.select("div.img-container > a > img").attr("src")
+ title = element.select("a.title-link").text()
+ }
+ }
+
+ // =============================== Latest ===============================
+ override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/recent-episode/all")
+
+ override fun latestUpdatesSelector(): String = popularAnimeSelector()
+
+ override fun latestUpdatesNextPageSelector(): String = popularAnimeNextPageSelector()
+
+ override fun latestUpdatesFromElement(element: Element): SAnime = popularAnimeFromElement(element)
+
+ // =============================== Search ===============================
+ override fun fetchSearchAnime(
+ page: Int,
+ query: String,
+ filters: AnimeFilterList
+ ): Observable {
+ val limit = 30
+ val mediaType = "application/json; charset=utf-8".toMediaType()
+ val body = """{"limit":$limit,"page":${page - 1},"pageCount":0,"value":"$query","sort":4,"selected":{"type":[],"genre":[],"year":[],"country":[],"season":[],"status":[],"sort":[],"language":[]}}""".toRequestBody(mediaType)
+
+ val response = client.newCall(POST("$baseUrl/api/advance-search", body = body, headers = headers)).execute()
+ val result = json.decodeFromString(response.body!!.string())
+
+ val total = result["total"]!!.jsonPrimitive.int
+ val nextPage = ceil(total.toFloat() / limit).toInt() > page
+ val data = result["results"]!!.jsonArray
+ val animes = data.map { item ->
+ SAnime.create().apply {
+ setUrlWithoutDomain("/anime/${item.jsonObject["anime_slug"]!!.jsonPrimitive.content}/")
+ thumbnail_url = "$baseUrl${item.jsonObject["image"]!!.jsonPrimitive.content}"
+ title = item.jsonObject["anime_name"]!!.jsonPrimitive.content
+ }
+ }
+
+ return Observable.just(AnimesPage(animes, nextPage))
+ }
+
+ override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request =
+ throw Exception("Not Used")
+
+ override fun searchAnimeSelector(): String = throw Exception("Not Used")
+
+ override fun searchAnimeNextPageSelector(): String = throw Exception("Not Used")
+
+ override fun searchAnimeFromElement(element: Element): SAnime = throw Exception("Not Used")
+
+ // =========================== Anime Details ============================
+ override fun animeDetailsParse(document: Document): SAnime {
+ val anime = SAnime.create()
+ anime.title = document.select("h3.anime-name").text()
+ anime.genre = document.select("div.genre > a").joinToString { it.text() }
+ anime.description = document.select("div.anime-desc.desc-content").text()
+ // No author info so use type of anime
+ anime.author = document.select("div.type > a").text()
+ anime.status = parseStatus(document.select("div.status > span").text())
+
+ // add alternative name to anime description
+ val altName = "Other name(s): "
+ document.select("h4.anime-alternatives").text()?.let {
+ if (it.isBlank().not()) {
+ anime.description = when {
+ anime.description.isNullOrBlank() -> altName + it
+ else -> anime.description + "\n\n$altName" + it
+ }
+ }
+ }
+ return anime
+ }
+
+ // ============================== Episodes ==============================
+ override fun episodeListParse(response: Response): List {
+ val animeId = response.asJsoup().select("div#unq-anime-id").attr("animeId")
+ val episodesJson = client.newCall(GET("$baseUrl/api/anime/$animeId/episodes")).execute().body!!.string()
+ val episodes = json.decodeFromString(episodesJson)
+ val subList = episodes["sub"]!!.jsonArray
+ val dubList = episodes["dub"]!!.jsonArray
+ val subSlug = episodes["sub_slug"]!!.jsonPrimitive.content
+ val dubSlug = episodes["dub_slug"]!!.jsonPrimitive.content
+ return subList.map { item ->
+ val dub = dubList.find {
+ it.jsonObject["name"]!!.jsonPrimitive.content == item.jsonObject["name"]!!.jsonPrimitive.content
+ }
+ SEpisode.create().apply {
+ url = "{\"Sub\": \"https://portablegaming.co/watch/$subSlug/${item.jsonObject["episode_index"]!!.jsonPrimitive.content}\"," +
+ if (dub != null) {
+ "\"Dub\": \"https://portablegaming.co/watch/$dubSlug/${dub.jsonObject["episode_index"]!!.jsonPrimitive.content}\"}"
+ } else { "\"Dub\": \"\"}" }
+ episode_number = item.jsonObject["name"]!!.jsonPrimitive.float
+ name = "Episode " + item.jsonObject["name"]!!.jsonPrimitive.content
+ }
+ }.reversed()
+ }
+
+ override fun episodeListSelector(): String = throw Exception("Not Used")
+
+ override fun episodeFromElement(element: Element): SEpisode = throw Exception("Not Used")
+
+ // ============================ Video Links =============================
+ override fun fetchVideoList(episode: SEpisode): Observable> {
+ val urlJson = json.decodeFromString(episode.url)
+ val videoList = mutableListOf