diff --git a/src/all/animeonsen/AndroidManifest.xml b/src/all/animeonsen/AndroidManifest.xml
new file mode 100644
index 000000000..7b3ef3e7c
--- /dev/null
+++ b/src/all/animeonsen/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/src/all/animeonsen/build.gradle b/src/all/animeonsen/build.gradle
new file mode 100644
index 000000000..56e228e8e
--- /dev/null
+++ b/src/all/animeonsen/build.gradle
@@ -0,0 +1,13 @@
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlinx-serialization'
+
+ext {
+ extName = 'AnimeOnsen'
+ pkgNameSuffix = 'all.animeonsen'
+ extClass = '.AnimeOnsen'
+ extVersionCode = 1
+ libVersion = '13'
+}
+
+apply from: "$rootDir/common.gradle"
diff --git a/src/all/animeonsen/res/mipmap-hdpi/ic_launcher.png b/src/all/animeonsen/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 000000000..61a1f08f0
Binary files /dev/null and b/src/all/animeonsen/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/src/all/animeonsen/res/mipmap-mdpi/ic_launcher.png b/src/all/animeonsen/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 000000000..4a26d43bc
Binary files /dev/null and b/src/all/animeonsen/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/src/all/animeonsen/res/mipmap-xhdpi/ic_launcher.png b/src/all/animeonsen/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 000000000..ec6eaa8ac
Binary files /dev/null and b/src/all/animeonsen/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/src/all/animeonsen/res/mipmap-xxhdpi/ic_launcher.png b/src/all/animeonsen/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 000000000..26387f979
Binary files /dev/null and b/src/all/animeonsen/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/src/all/animeonsen/res/mipmap-xxxhdpi/ic_launcher.png b/src/all/animeonsen/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 000000000..b575c034e
Binary files /dev/null and b/src/all/animeonsen/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/src/all/animeonsen/res/web_hi_res_512.png b/src/all/animeonsen/res/web_hi_res_512.png
new file mode 100644
index 000000000..2ca8cc988
Binary files /dev/null and b/src/all/animeonsen/res/web_hi_res_512.png differ
diff --git a/src/all/animeonsen/src/eu/kanade/tachiyomi/animeextension/all/animeonsen/AOAPIInterceptor.kt b/src/all/animeonsen/src/eu/kanade/tachiyomi/animeextension/all/animeonsen/AOAPIInterceptor.kt
new file mode 100644
index 000000000..8d6f3cc44
--- /dev/null
+++ b/src/all/animeonsen/src/eu/kanade/tachiyomi/animeextension/all/animeonsen/AOAPIInterceptor.kt
@@ -0,0 +1,40 @@
+package eu.kanade.tachiyomi.animeextension.all.animeonsen
+
+import android.util.Base64
+import android.util.Log
+import eu.kanade.tachiyomi.network.GET
+import okhttp3.HttpUrl.Companion.toHttpUrl
+import okhttp3.Interceptor
+import okhttp3.OkHttpClient
+import okhttp3.Response
+
+class AOAPIInterceptor(client: OkHttpClient) : Interceptor {
+
+ private val token: String
+
+ init {
+ val cookie = client.cookieJar
+ .loadForRequest("https://animeonsen.xyz".toHttpUrl())
+ .find { it.name == "ao.session" }?.value
+ ?: client.newCall(GET("https://animeonsen.xyz")).execute().header("set-cookie")
+
+ token = String(
+ Base64.decode(
+ java.net.URLDecoder.decode(cookie, "utf-8"),
+ Base64.DEFAULT
+ )
+ )
+
+ Log.i("bruh", token)
+ }
+
+ override fun intercept(chain: Interceptor.Chain): Response {
+ val originalRequest = chain.request()
+
+ val newRequest = originalRequest.newBuilder()
+ .addHeader("Authorization", "Bearer $token")
+ .build()
+
+ return chain.proceed(newRequest)
+ }
+}
diff --git a/src/all/animeonsen/src/eu/kanade/tachiyomi/animeextension/all/animeonsen/AnimeOnsen.kt b/src/all/animeonsen/src/eu/kanade/tachiyomi/animeextension/all/animeonsen/AnimeOnsen.kt
new file mode 100644
index 000000000..eb49b909e
--- /dev/null
+++ b/src/all/animeonsen/src/eu/kanade/tachiyomi/animeextension/all/animeonsen/AnimeOnsen.kt
@@ -0,0 +1,168 @@
+package eu.kanade.tachiyomi.animeextension.all.animeonsen
+
+import eu.kanade.tachiyomi.animeextension.all.animeonsen.dto.AnimeDetails
+import eu.kanade.tachiyomi.animeextension.all.animeonsen.dto.AnimeListItem
+import eu.kanade.tachiyomi.animeextension.all.animeonsen.dto.AnimeListResponse
+import eu.kanade.tachiyomi.animeextension.all.animeonsen.dto.SearchResponse
+import eu.kanade.tachiyomi.animeextension.all.animeonsen.dto.VideoData
+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.Track
+import eu.kanade.tachiyomi.animesource.model.Video
+import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
+import eu.kanade.tachiyomi.network.GET
+import eu.kanade.tachiyomi.network.asObservableSuccess
+import eu.kanade.tachiyomi.util.asJsoup
+import kotlinx.serialization.decodeFromString
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.json.boolean
+import kotlinx.serialization.json.jsonPrimitive
+import okhttp3.Headers
+import okhttp3.OkHttpClient
+import okhttp3.Request
+import okhttp3.Response
+import rx.Observable
+import kotlin.Exception
+
+class AnimeOnsen : AnimeHttpSource() {
+
+ override val name = "AnimeOnsen"
+
+ override val baseUrl = "https://animeonsen.xyz"
+
+ private val apiUrl = "https://api.animeonsen.xyz/v4"
+
+ override val lang = "all"
+
+ override val supportsLatest = false
+
+ private val cfClient = network.cloudflareClient
+
+ override val client: OkHttpClient = network.client.newBuilder()
+ .addInterceptor(AOAPIInterceptor(cfClient))
+ .build()
+
+ private val json = Json {
+ ignoreUnknownKeys = true
+ }
+
+ override fun headersBuilder(): Headers.Builder = Headers.Builder()
+ .add("Referer", baseUrl)
+
+ // ============================== Popular ===============================
+ // The site doesn't have a popular anime tab, so we use the home page instead (latest anime).
+ override fun popularAnimeRequest(page: Int): Request =
+ GET("$apiUrl/content/index?start=${(page - 1) * 20}&limit=20")
+
+ override fun popularAnimeParse(response: Response): AnimesPage {
+ val responseJson = json.decodeFromString(response.body!!.string())
+ val animes = responseJson.content.map {
+ it.toSAnime()
+ }
+ val hasNextPage = responseJson.cursor.next.firstOrNull()?.jsonPrimitive?.boolean == true
+ return AnimesPage(animes, hasNextPage)
+ }
+
+ // ============================== Episodes ==============================
+ override fun episodeListParse(response: Response): List {
+ val episodes = response.asJsoup().select("div.episode-list > a")
+ return episodes.map {
+ val num = it.attr("data-episode")
+ val episodeSpan = it.select("div.episode > span.general")
+ val titleSpan = it.select("div.episode > span.title")
+ SEpisode.create().apply {
+ url = it.attr("href")
+ .substringAfter("/watch/")
+ .replace("?episode=", "/video/")
+ episode_number = num.toFloat()
+ name = episodeSpan.text() + ": " + titleSpan.text()
+ }
+ }.reversed()
+ }
+
+ override fun episodeListRequest(anime: SAnime): Request {
+ return GET("$baseUrl/details/${anime.url}")
+ }
+
+ // ============================ Video Links =============================
+ override fun videoListParse(response: Response): List