diff --git a/src/en/kayoanime/AndroidManifest.xml b/src/en/kayoanime/AndroidManifest.xml
new file mode 100644
index 000000000..acb4de356
--- /dev/null
+++ b/src/en/kayoanime/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/src/en/kayoanime/build.gradle b/src/en/kayoanime/build.gradle
new file mode 100644
index 000000000..191c40fd7
--- /dev/null
+++ b/src/en/kayoanime/build.gradle
@@ -0,0 +1,13 @@
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlinx-serialization'
+
+ext {
+ extName = 'Kayoanime'
+ pkgNameSuffix = 'en.kayoanime'
+ extClass = '.Kayoanime'
+ extVersionCode = 1
+ libVersion = '13'
+}
+
+apply from: "$rootDir/common.gradle"
diff --git a/src/en/kayoanime/res/mipmap-hdpi/ic_launcher.png b/src/en/kayoanime/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 000000000..cd2a7bc54
Binary files /dev/null and b/src/en/kayoanime/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/src/en/kayoanime/res/mipmap-mdpi/ic_launcher.png b/src/en/kayoanime/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 000000000..a0c51152e
Binary files /dev/null and b/src/en/kayoanime/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/src/en/kayoanime/res/mipmap-xhdpi/ic_launcher.png b/src/en/kayoanime/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 000000000..a90e2c5c5
Binary files /dev/null and b/src/en/kayoanime/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/src/en/kayoanime/res/mipmap-xxhdpi/ic_launcher.png b/src/en/kayoanime/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 000000000..cf96dece2
Binary files /dev/null and b/src/en/kayoanime/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/src/en/kayoanime/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/kayoanime/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 000000000..7227be5e0
Binary files /dev/null and b/src/en/kayoanime/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/src/en/kayoanime/res/web_hi_res_512.png b/src/en/kayoanime/res/web_hi_res_512.png
new file mode 100644
index 000000000..6931c2637
Binary files /dev/null and b/src/en/kayoanime/res/web_hi_res_512.png differ
diff --git a/src/en/kayoanime/src/eu/kanade/tachiyomi/animeextension/en/kayoanime/Kayoanime.kt b/src/en/kayoanime/src/eu/kanade/tachiyomi/animeextension/en/kayoanime/Kayoanime.kt
new file mode 100644
index 000000000..ba0e8c5ee
--- /dev/null
+++ b/src/en/kayoanime/src/eu/kanade/tachiyomi/animeextension/en/kayoanime/Kayoanime.kt
@@ -0,0 +1,472 @@
+package eu.kanade.tachiyomi.animeextension.en.kayoanime
+
+import android.app.Application
+import android.content.SharedPreferences
+import androidx.preference.PreferenceScreen
+import androidx.preference.SwitchPreferenceCompat
+import eu.kanade.tachiyomi.animeextension.en.kayoanime.extractors.GoogleDriveExtractor
+import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
+import eu.kanade.tachiyomi.animesource.model.AnimeFilter
+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.coroutines.Dispatchers
+import kotlinx.coroutines.async
+import kotlinx.coroutines.awaitAll
+import kotlinx.coroutines.runBlocking
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.decodeFromString
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.json.JsonElement
+import kotlinx.serialization.json.jsonArray
+import kotlinx.serialization.json.jsonPrimitive
+import okhttp3.FormBody
+import okhttp3.HttpUrl.Companion.toHttpUrl
+import okhttp3.OkHttpClient
+import okhttp3.Request
+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 java.text.CharacterIterator
+import java.text.SimpleDateFormat
+import java.text.StringCharacterIterator
+import java.util.Locale
+
+class Kayoanime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
+
+ override val name = "Kayoanime (Experimental)"
+
+ override val baseUrl = "https://kayoanime.com"
+
+ override val lang = "en"
+
+ // Used for loading anime
+ private var infoQuery = ""
+ private var max = ""
+ private var latest_post = ""
+ private var layout = ""
+ private var settings = ""
+ private var currentReferer = ""
+
+ override val supportsLatest = true
+
+ override val client: OkHttpClient = network.cloudflareClient
+
+ private val MAX_RECURSION_DEPTH = 2
+
+ private val json: Json by injectLazy()
+
+ private val preferences: SharedPreferences by lazy {
+ Injekt.get().getSharedPreferences("source_$id", 0x0000)
+ }
+
+ companion object {
+ private val DateFormatter by lazy {
+ SimpleDateFormat("d MMMM yyyy", Locale.ENGLISH)
+ }
+ }
+
+ // ============================== Popular ===============================
+
+ override fun popularAnimeRequest(page: Int): Request {
+ return if (page == 1) {
+ infoQuery = ""
+ max = ""
+ latest_post = ""
+ layout = ""
+ settings = ""
+ currentReferer = "https://kayoanime.com/ongoing-anime/"
+ GET("$baseUrl/ongoing-anime/")
+ } else {
+ val formBody = FormBody.Builder()
+ .add("action", "tie_archives_load_more")
+ .add("query", infoQuery)
+ .add("max", max)
+ .add("page", page.toString())
+ .add("latest_post", latest_post)
+ .add("layout", layout)
+ .add("settings", settings)
+ .build()
+ val formHeaders = headers.newBuilder()
+ .add("Accept", "*/*")
+ .add("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
+ .add("Host", "kayoanime.com")
+ .add("Origin", "https://kayoanime.com")
+ .add("Referer", currentReferer)
+ .add("X-Requested-With", "XMLHttpRequest")
+ .build()
+ POST("$baseUrl/wp-admin/admin-ajax.php", body = formBody, headers = formHeaders)
+ }
+ }
+
+ override fun popularAnimeParse(response: Response): AnimesPage {
+ return if (response.request.url.toString().endsWith("admin-ajax.php")) {
+ val body = response.body.string()
+ val rawParsed = json.decodeFromString(body)
+ val parsed = json.decodeFromString(rawParsed)
+ val soup = Jsoup.parse(parsed.code)
+
+ val animes = soup.select("li.post-item").map { element ->
+ popularAnimeFromElement(element)
+ }
+
+ AnimesPage(animes, !parsed.hide_next)
+ } else {
+ val document = response.asJsoup()
+
+ val animes = document.select(popularAnimeSelector()).map { element ->
+ popularAnimeFromElement(element)
+ }
+
+ val hasNextPage = popularAnimeNextPageSelector()?.let { selector ->
+ document.select(selector).first()
+ } != null
+ if (hasNextPage) {
+ val container = document.selectFirst("ul#posts-container")!!
+ val pagesNav = document.selectFirst("div.pages-nav > a")!!
+ layout = container.attr("data-layout")
+ infoQuery = pagesNav.attr("data-query")
+ max = pagesNav.attr("data-max")
+ latest_post = pagesNav.attr("data-latest")
+ settings = container.attr("data-settings")
+ }
+
+ AnimesPage(animes, hasNextPage)
+ }
+ }
+
+ override fun popularAnimeSelector(): String = "ul#posts-container > li.post-item"
+
+ override fun popularAnimeNextPageSelector(): String = "div.pages-nav > a[data-text=load more]"
+
+ override fun popularAnimeFromElement(element: Element): SAnime {
+ return SAnime.create().apply {
+ setUrlWithoutDomain(element.selectFirst("a")!!.attr("href").toHttpUrl().encodedPath)
+ thumbnail_url = element.selectFirst("img[src]")?.attr("src") ?: ""
+ title = element.selectFirst("h2.post-title")!!.text().substringBefore(" Episode")
+ }
+ }
+
+ // =============================== Latest ===============================
+
+ override fun latestUpdatesRequest(page: Int): Request = GET(baseUrl)
+
+ override fun latestUpdatesSelector(): String = "ul.tabs:has(a:contains(Recent)) + div.tab-content li.widget-single-post-item"
+
+ override fun latestUpdatesNextPageSelector(): String? = null
+
+ override fun latestUpdatesFromElement(element: Element): SAnime {
+ return SAnime.create().apply {
+ setUrlWithoutDomain(element.selectFirst("a")!!.attr("href").toHttpUrl().encodedPath)
+ thumbnail_url = element.selectFirst("img[src]")?.attr("src") ?: ""
+ title = element.selectFirst("a.post-title")!!.text().substringBefore(" Episode")
+ }
+ }
+
+ // =============================== Search ===============================
+
+ override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
+ return if (page == 1) {
+ infoQuery = ""
+ max = ""
+ latest_post = ""
+ layout = ""
+ settings = ""
+
+ val filterList = if (filters.isEmpty()) getFilterList() else filters
+ val genreFilter = filterList.find { it is GenreFilter } as GenreFilter
+ val subPageFilter = filterList.find { it is SubPageFilter } as SubPageFilter
+
+ return when {
+ query.isNotBlank() -> {
+ val cleanQuery = query.replace(" ", "+")
+ currentReferer = "$baseUrl?s=$cleanQuery"
+ GET("$baseUrl?s=$cleanQuery")
+ }
+ genreFilter.state != 0 -> {
+ val url = "$baseUrl${genreFilter.toUriPart()}"
+ currentReferer = url
+ GET(url)
+ }
+ subPageFilter.state != 0 -> {
+ val url = "$baseUrl${subPageFilter.toUriPart()}"
+ currentReferer = url
+ GET(url)
+ }
+ else -> popularAnimeRequest(page)
+ }
+ } else {
+ val formBody = FormBody.Builder()
+ .add("action", "tie_archives_load_more")
+ .add("query", infoQuery)
+ .add("max", max)
+ .add("page", page.toString())
+ .add("latest_post", latest_post)
+ .add("layout", layout)
+ .add("settings", settings)
+ .build()
+ val formHeaders = headers.newBuilder()
+ .add("Accept", "*/*")
+ .add("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
+ .add("Host", "kayoanime.com")
+ .add("Origin", "https://kayoanime.com")
+ .add("Referer", currentReferer)
+ .add("X-Requested-With", "XMLHttpRequest")
+ .build()
+ POST("$baseUrl/wp-admin/admin-ajax.php", body = formBody, headers = formHeaders)
+ }
+ }
+
+ override fun searchAnimeParse(response: Response): AnimesPage = popularAnimeParse(response)
+
+ override fun searchAnimeSelector(): String = popularAnimeSelector()
+
+ override fun searchAnimeNextPageSelector(): String = popularAnimeNextPageSelector()
+
+ override fun searchAnimeFromElement(element: Element): SAnime = popularAnimeFromElement(element)
+
+ // ============================== Filters ===============================
+
+ override fun getFilterList(): AnimeFilterList = AnimeFilterList(
+ AnimeFilter.Header("Text search ignores filters"),
+ SubPageFilter(),
+ GenreFilter(),
+ )
+
+ private class SubPageFilter : UriPartFilter(
+ "Sub-page",
+ arrayOf(
+ Pair("