diff --git a/src/en/allanimechi/AndroidManifest.xml b/src/en/allanimechi/AndroidManifest.xml
new file mode 100644
index 0000000000..568741e54f
--- /dev/null
+++ b/src/en/allanimechi/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/src/en/allanimechi/build.gradle b/src/en/allanimechi/build.gradle
new file mode 100644
index 0000000000..298526f506
--- /dev/null
+++ b/src/en/allanimechi/build.gradle
@@ -0,0 +1,26 @@
+plugins {
+ alias(libs.plugins.android.application)
+ alias(libs.plugins.kotlin.android)
+ alias(libs.plugins.kotlin.serialization)
+}
+
+ext {
+ extName = 'AllAnimeChi'
+ pkgNameSuffix = 'en.allanimechi'
+ extClass = '.AllAnimeChi'
+ extVersionCode = 1
+ libVersion = '13'
+}
+
+dependencies {
+ implementation(project(':lib-mp4upload-extractor'))
+ implementation(project(':lib-okru-extractor'))
+ implementation(project(':lib-gogostream-extractor'))
+ implementation(project(':lib-filemoon-extractor'))
+ implementation(project(':lib-streamlare-extractor'))
+ implementation(project(':lib-streamwish-extractor'))
+ implementation(project(':lib-playlist-utils'))
+}
+
+
+apply from: "$rootDir/common.gradle"
diff --git a/src/en/allanimechi/res/mipmap-hdpi/ic_launcher.png b/src/en/allanimechi/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000000..4a4cf9bf7a
Binary files /dev/null and b/src/en/allanimechi/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/src/en/allanimechi/res/mipmap-mdpi/ic_launcher.png b/src/en/allanimechi/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000000..be7a1c19e4
Binary files /dev/null and b/src/en/allanimechi/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/src/en/allanimechi/res/mipmap-xhdpi/ic_launcher.png b/src/en/allanimechi/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000000..3603cefed6
Binary files /dev/null and b/src/en/allanimechi/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/src/en/allanimechi/res/mipmap-xxhdpi/ic_launcher.png b/src/en/allanimechi/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000000..531455438f
Binary files /dev/null and b/src/en/allanimechi/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/src/en/allanimechi/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/allanimechi/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000000..c9dfc3051b
Binary files /dev/null and b/src/en/allanimechi/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/src/en/allanimechi/res/web_hi_res_512.png b/src/en/allanimechi/res/web_hi_res_512.png
new file mode 100644
index 0000000000..337bc317b4
Binary files /dev/null and b/src/en/allanimechi/res/web_hi_res_512.png differ
diff --git a/src/en/allanimechi/src/eu/kanade/tachiyomi/animeextension/en/allanimechi/AllAnimeChi.kt b/src/en/allanimechi/src/eu/kanade/tachiyomi/animeextension/en/allanimechi/AllAnimeChi.kt
new file mode 100644
index 0000000000..c4ee7130e0
--- /dev/null
+++ b/src/en/allanimechi/src/eu/kanade/tachiyomi/animeextension/en/allanimechi/AllAnimeChi.kt
@@ -0,0 +1,671 @@
+package eu.kanade.tachiyomi.animeextension.en.allanimechi
+
+import android.app.Application
+import android.content.SharedPreferences
+import android.util.Base64
+import androidx.preference.ListPreference
+import androidx.preference.MultiSelectListPreference
+import androidx.preference.PreferenceScreen
+import androidx.preference.SwitchPreferenceCompat
+import eu.kanade.tachiyomi.animeextension.en.allanimechi.extractors.AllAnimeExtractor
+import eu.kanade.tachiyomi.animeextension.en.allanimechi.extractors.InternalExtractor
+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.AnimeHttpSource
+import eu.kanade.tachiyomi.lib.filemoonextractor.FilemoonExtractor
+import eu.kanade.tachiyomi.lib.gogostreamextractor.GogoStreamExtractor
+import eu.kanade.tachiyomi.lib.mp4uploadextractor.Mp4uploadExtractor
+import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor
+import eu.kanade.tachiyomi.lib.streamlareextractor.StreamlareExtractor
+import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor
+import eu.kanade.tachiyomi.network.GET
+import eu.kanade.tachiyomi.network.asObservableSuccess
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.async
+import kotlinx.coroutines.awaitAll
+import kotlinx.coroutines.runBlocking
+import kotlinx.serialization.encodeToString
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.json.JsonObject
+import kotlinx.serialization.json.add
+import kotlinx.serialization.json.buildJsonObject
+import kotlinx.serialization.json.put
+import kotlinx.serialization.json.putJsonArray
+import kotlinx.serialization.json.putJsonObject
+import okhttp3.Headers
+import okhttp3.HttpUrl.Companion.toHttpUrl
+import okhttp3.OkHttpClient
+import okhttp3.Request
+import okhttp3.Response
+import org.jsoup.Jsoup
+import rx.Observable
+import uy.kohesive.injekt.Injekt
+import uy.kohesive.injekt.api.get
+import uy.kohesive.injekt.injectLazy
+
+class AllAnimeChi : ConfigurableAnimeSource, AnimeHttpSource() {
+
+ override val name = "AllAnimeChi"
+
+ override val baseUrl = "aHR0cHM6Ly9hY2FwaS5hbGxhbmltZS5kYXk=".decodeBase64()
+ private val siteUrl = "aHR0cHM6Ly9hbGxhbmltZS50bw==".decodeBase64()
+
+ 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)
+ }
+
+ private val apiHeaders = Headers.Builder().apply {
+ add("app-version", "android_c-253")
+ add("content-type", "application/json; charset=UTF-8")
+ add("from-app", "YW5pbWVjaGlja2Vu".decodeBase64())
+ add("host", baseUrl.toHttpUrl().host)
+ add("platformstr", "android_c")
+ add("Referer", "$siteUrl/")
+ add("user-agent", "Dart/2.19 (dart:io)")
+ }.build()
+
+ // ============================== Popular ===============================
+
+ override fun popularAnimeRequest(page: Int): Request {
+ val variables = buildJsonObject {
+ put("type", "anime")
+ put("size", PAGE_SIZE)
+ put("dateRange", 7)
+ put("page", page)
+ put("allowAdult", false)
+ put("allowUnknown", false)
+ put("denyEcchi", false)
+ }.encode()
+
+ val extensions = buildJsonObject {
+ putJsonObject("persistedQuery") {
+ put("version", 1)
+ put("sha256Hash", POPULAR_HASH)
+ }
+ }.encode()
+
+ val url = baseUrl.toHttpUrl().newBuilder().apply {
+ addPathSegment("api")
+ addQueryParameter("variables", variables)
+ addQueryParameter("extensions", extensions)
+ }.build().toString().replace("%3A", ":")
+
+ return GET(url, apiHeaders)
+ }
+
+ override fun popularAnimeParse(response: Response): AnimesPage {
+ val parsed = response.parseAs()
+ val animeList = parsed.data.queryPopular.recommendations.mapNotNull {
+ it.anyCard?.toSAnime(preferences.titleStyle)
+ }
+
+ return AnimesPage(animeList, animeList.size == PAGE_SIZE)
+ }
+
+ // =============================== Latest ===============================
+
+ override fun latestUpdatesRequest(page: Int): Request {
+ val variables = buildJsonObject {
+ putJsonObject("search") {
+ put("allowAdult", false)
+ put("allowUnknown", false)
+ put("denyEcchi", false)
+ }
+ put("translationType", preferences.subPref)
+ put("limit", PAGE_SIZE)
+ put("page", page)
+ put("countryOrigin", "ALL")
+ }.encode()
+
+ val extensions = buildJsonObject {
+ putJsonObject("persistedQuery") {
+ put("version", 1)
+ put("sha256Hash", LATEST_HASH)
+ }
+ }.encode()
+
+ val url = baseUrl.toHttpUrl().newBuilder().apply {
+ addPathSegment("api")
+ addQueryParameter("variables", variables)
+ addQueryParameter("extensions", extensions)
+ }.build().toString().replace("%3A", ":")
+
+ return GET(url, apiHeaders)
+ }
+
+ override fun latestUpdatesParse(response: Response): AnimesPage {
+ val parsed = response.parseAs()
+ val animeList = parsed.data.shows.edges.map {
+ it.toSAnime(preferences.titleStyle)
+ }
+
+ return AnimesPage(animeList, animeList.size == PAGE_SIZE)
+ }
+
+ // =============================== Search ===============================
+
+ override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
+ val params = AllAnimeChiFilters.getSearchParameters(filters)
+
+ val variables = buildJsonObject {
+ putJsonObject("search") {
+ if (query.isBlank()) {
+ if (params.genres.isNotEmpty()) {
+ putJsonArray("genres") {
+ params.genres.forEach {
+ add(it)
+ }
+ }
+ }
+ if (params.type != "all") {
+ putJsonArray("types") {
+ add(params.type)
+ }
+ }
+ if (params.season != "all") {
+ put("season", params.season)
+ }
+ if (params.releaseYear != "all") {
+ put("year", params.releaseYear.toInt())
+ }
+ if (params.episodeCount != "all") {
+ val (start, end) = params.episodeCount.split("-")
+ if (start.isNotBlank()) put("epRangeStart", start.toInt())
+ if (end.isNotBlank()) put("epRangeEnd", end.toInt())
+ }
+ } else {
+ put("query", query)
+ }
+ put("sortBy", "Latest_Update")
+ put("allowAdult", false)
+ put("allowUnknown", false)
+ put("denyEcchi", false)
+ }
+ put("translationType", preferences.subPref)
+ put("limit", PAGE_SIZE)
+ put("page", page)
+ put("countryOrigin", params.origin)
+ }.encode()
+
+ val extensions = buildJsonObject {
+ putJsonObject("persistedQuery") {
+ put("version", 1)
+ put("sha256Hash", LATEST_HASH)
+ }
+ }.encode()
+
+ val url = baseUrl.toHttpUrl().newBuilder().apply {
+ addPathSegment("api")
+ addQueryParameter("variables", variables)
+ addQueryParameter("extensions", extensions)
+ }.build().toString().replace("%3A", ":")
+
+ return GET(url, apiHeaders)
+ }
+
+ override fun searchAnimeParse(response: Response): AnimesPage = latestUpdatesParse(response)
+
+ // ============================== Filters ===============================
+
+ override fun getFilterList(): AnimeFilterList = AllAnimeChiFilters.FILTER_LIST
+
+ // =========================== Anime Details ============================
+
+ override fun fetchAnimeDetails(anime: SAnime): Observable {
+ return client.newCall(animeDetailsRequestInternal(anime))
+ .asObservableSuccess()
+ .map { response ->
+ animeDetailsParse(response).apply { initialized = true }
+ }
+ }
+
+ private fun animeDetailsRequestInternal(anime: SAnime): Request {
+ val variables = buildJsonObject {
+ put("_id", anime.url)
+ }.encode()
+
+ val extensions = buildJsonObject {
+ putJsonObject("persistedQuery") {
+ put("version", 1)
+ put("sha256Hash", DETAILS_HASH)
+ }
+ }.encode()
+
+ val url = baseUrl.toHttpUrl().newBuilder().apply {
+ addPathSegment("api")
+ addQueryParameter("variables", variables)
+ addQueryParameter("extensions", extensions)
+ }.build().toString()
+
+ return GET(url, apiHeaders)
+ }
+
+ override fun animeDetailsRequest(anime: SAnime): Request {
+ return GET("data:text/plain,This%20extension%20does%20not%20exist%20as%20a%20website%21")
+ }
+
+ override fun animeDetailsParse(response: Response): SAnime {
+ val show = response.parseAs().data.show
+
+ return SAnime.create().apply {
+ genre = show.genres?.joinToString(separator = ", ") ?: ""
+ status = parseStatus(show.status)
+ author = show.studios?.firstOrNull()
+ description = buildString {
+ append(
+ Jsoup.parseBodyFragment(
+ show.description?.replace("
", "br2n") ?: "",
+ ).text().replace("br2n", "\n"),
+ )
+ append("\n\n")
+ append("Type: ${show.type ?: "Unknown"}")
+ append("\nAired: ${show.season?.quarter ?: "-"} ${show.season?.year ?: "-"}")
+ append("\nScore: ${show.score ?: "-"}★")
+ }
+ }
+ }
+
+ // ============================== Episodes ==============================
+
+ override fun episodeListRequest(anime: SAnime): Request = animeDetailsRequestInternal(anime)
+
+ override fun episodeListParse(response: Response): List {
+ val media = response.parseAs()
+ val subPref = preferences.subPref
+
+ val episodesDetail = if (subPref == "sub") {
+ media.data.show.availableEpisodesDetail.sub!!
+ } else {
+ media.data.show.availableEpisodesDetail.dub!!
+ }
+
+ return episodesDetail.map { ep ->
+ val numName = ep.toIntOrNull() ?: (ep.toFloatOrNull() ?: "1")
+
+ SEpisode.create().apply {
+ episode_number = ep.toFloatOrNull() ?: 0F
+ name = "Episode $numName ($subPref)"
+ url = buildJsonObject {
+ put("showId", media.data.show._id)
+ put("translationType", subPref)
+ put("episodeString", ep)
+ }.encode()
+ }
+ }
+ }
+
+ // ============================ Video Links =============================
+
+ private val internalExtractor by lazy { InternalExtractor(client, apiHeaders) }
+
+ override fun videoListRequest(episode: SEpisode): Request {
+ val variables = episode.url
+
+ val extensions = buildJsonObject {
+ putJsonObject("persistedQuery") {
+ put("version", 1)
+ put("sha256Hash", STREAMS_HASH)
+ }
+ }.encode()
+
+ val url = baseUrl.toHttpUrl().newBuilder().apply {
+ addPathSegment("api")
+ addQueryParameter("variables", variables)
+ addQueryParameter("extensions", extensions)
+ }.build().toString()
+
+ return GET(url, apiHeaders)
+ }
+
+ override fun videoListParse(response: Response): List