diff --git a/src/all/animexin/AndroidManifest.xml b/src/all/animexin/AndroidManifest.xml
new file mode 100644
index 000000000..acb4de356
--- /dev/null
+++ b/src/all/animexin/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/src/all/animexin/build.gradle b/src/all/animexin/build.gradle
new file mode 100644
index 000000000..9b821677d
--- /dev/null
+++ b/src/all/animexin/build.gradle
@@ -0,0 +1,19 @@
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlinx-serialization'
+
+ext {
+ extName = 'AnimeXin'
+ pkgNameSuffix = 'all.animexin'
+ extClass = '.AnimeXin'
+ extVersionCode = 1
+ libVersion = '13'
+}
+
+dependencies {
+ compileOnly libs.bundles.coroutines
+ implementation(project(':lib-okru-extractor'))
+ implementation "dev.datlag.jsunpacker:jsunpacker:1.0.1"
+}
+
+apply from: "$rootDir/common.gradle"
diff --git a/src/all/animexin/res/mipmap-hdpi/ic_launcher.png b/src/all/animexin/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 000000000..2efedb051
Binary files /dev/null and b/src/all/animexin/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/src/all/animexin/res/mipmap-mdpi/ic_launcher.png b/src/all/animexin/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 000000000..9c97ed6cb
Binary files /dev/null and b/src/all/animexin/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/src/all/animexin/res/mipmap-xhdpi/ic_launcher.png b/src/all/animexin/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 000000000..0e218a763
Binary files /dev/null and b/src/all/animexin/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/src/all/animexin/res/mipmap-xxhdpi/ic_launcher.png b/src/all/animexin/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 000000000..35e4ed4e4
Binary files /dev/null and b/src/all/animexin/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/src/all/animexin/res/mipmap-xxxhdpi/ic_launcher.png b/src/all/animexin/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 000000000..87c8ced85
Binary files /dev/null and b/src/all/animexin/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/src/all/animexin/res/web_hi_res_512.png b/src/all/animexin/res/web_hi_res_512.png
new file mode 100644
index 000000000..02d35c055
Binary files /dev/null and b/src/all/animexin/res/web_hi_res_512.png differ
diff --git a/src/all/animexin/src/eu/kanade/tachiyomi/animeextension/all/animexin/AnimeXin.kt b/src/all/animexin/src/eu/kanade/tachiyomi/animeextension/all/animexin/AnimeXin.kt
new file mode 100644
index 000000000..15c6ffb6d
--- /dev/null
+++ b/src/all/animexin/src/eu/kanade/tachiyomi/animeextension/all/animexin/AnimeXin.kt
@@ -0,0 +1,333 @@
+package eu.kanade.tachiyomi.animeextension.all.animexin
+
+import android.app.Application
+import android.content.SharedPreferences
+import android.util.Base64
+import androidx.preference.ListPreference
+import androidx.preference.PreferenceScreen
+import eu.kanade.tachiyomi.animeextension.all.animexin.extractors.DailymotionExtractor
+import eu.kanade.tachiyomi.animeextension.all.animexin.extractors.DoodExtractor
+import eu.kanade.tachiyomi.animeextension.all.animexin.extractors.FembedExtractor
+import eu.kanade.tachiyomi.animeextension.all.animexin.extractors.GdrivePlayerExtractor
+import eu.kanade.tachiyomi.animeextension.all.animexin.extractors.StreamSBExtractor
+import eu.kanade.tachiyomi.animeextension.all.animexin.extractors.YouTubeExtractor
+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.okruextractor.OkruExtractor
+import eu.kanade.tachiyomi.network.GET
+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 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 java.text.SimpleDateFormat
+import java.util.Locale
+
+class AnimeXin : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
+
+ override val name = "AnimeXin"
+
+ override val baseUrl by lazy { preferences.getString("preferred_domain", "https://animexin.vip")!! }
+
+ override val lang = "en"
+
+ override val supportsLatest = true
+
+ override val client: OkHttpClient = network.cloudflareClient
+
+ private val preferences: SharedPreferences by lazy {
+ Injekt.get().getSharedPreferences("source_$id", 0x0000)
+ }
+
+ companion object {
+ private val DateFormatter by lazy {
+ SimpleDateFormat("MMMM d, yyyy", Locale.ENGLISH)
+ }
+ }
+
+ // ============================== Popular ===============================
+
+ override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/")
+
+ override fun popularAnimeSelector(): String = "div.wpop-weekly > ul > li"
+
+ override fun popularAnimeNextPageSelector(): String? = null
+
+ override fun popularAnimeFromElement(element: Element): SAnime {
+ return SAnime.create().apply {
+ setUrlWithoutDomain(element.selectFirst("a.series").attr("href").toHttpUrl().encodedPath)
+ thumbnail_url = element.selectFirst("img").attr("src").substringBefore("?resize")
+ title = element.selectFirst("a.series:not(:has(img))").text()
+ }
+ }
+
+ // =============================== Latest ===============================
+
+ override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/anime/?page=$page&status=&type=&order=update")
+
+ override fun latestUpdatesSelector(): String = searchAnimeSelector()
+
+ override fun latestUpdatesNextPageSelector(): String = searchAnimeNextPageSelector()
+
+ override fun latestUpdatesFromElement(element: Element): SAnime = searchAnimeFromElement(element)
+
+ // =============================== Search ===============================
+
+ override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request = throw Exception("Not used")
+
+ override fun fetchSearchAnime(page: Int, query: String, filters: AnimeFilterList): Observable {
+ val params = AnimeXinFilters.getSearchParameters(filters)
+ return client.newCall(searchAnimeRequest(page, query, params))
+ .asObservableSuccess()
+ .map { response ->
+ searchAnimeParse(response)
+ }
+ }
+
+ private fun searchAnimeRequest(page: Int, query: String, filters: AnimeXinFilters.FilterSearchParams): Request {
+ return if (query.isNotEmpty()) {
+ GET("$baseUrl/page/$page/?s=$query")
+ } else {
+ val multiChoose = mutableListOf()
+ if (filters.genres.isNotEmpty()) multiChoose.add(filters.genres)
+ if (filters.seasons.isNotEmpty()) multiChoose.add(filters.seasons)
+ if (filters.studios.isNotEmpty()) multiChoose.add(filters.studios)
+ val multiString = if (multiChoose.isEmpty()) "" else multiChoose.joinToString("&") + "&"
+ GET("$baseUrl/anime/?page=$page&${multiString}status=${filters.status}&type=${filters.type}&sub=${filters.sub}&order=${filters.order}")
+ }
+ }
+
+ override fun searchAnimeSelector(): String = "div.listupd > article"
+
+ override fun searchAnimeNextPageSelector(): String = "div.hpage > a:contains(Next)"
+
+ override fun searchAnimeFromElement(element: Element): SAnime {
+ return SAnime.create().apply {
+ setUrlWithoutDomain(element.selectFirst("a").attr("href").toHttpUrl().encodedPath)
+ thumbnail_url = element.selectFirst("img").attr("src").substringBefore("?resize")
+ title = element.selectFirst("div.tt").text()
+ }
+ }
+
+ override fun getFilterList(): AnimeFilterList = AnimeXinFilters.filterList
+
+ // =========================== Anime Details ============================
+
+ override fun animeDetailsParse(document: Document): SAnime {
+ return SAnime.create().apply {
+ title = document.selectFirst("h1.entry-title").text()
+ thumbnail_url = document.selectFirst("div.thumb > img").attr("src").substringBefore("?resize")
+ status = SAnime.COMPLETED
+ description = document.select("div[itemprop=description] p")?.let {
+ it.joinToString("\n\n") { t -> t.text() } +
+ "\n\n" +
+ document.select("div.info-content > div > span").joinToString("\n") { info ->
+ info.text().replace(":", ": ")
+ }
+ } ?: ""
+ }
+ }
+
+ // ============================== Episodes ==============================
+
+ override fun episodeListParse(response: Response): List {
+ val document = response.asJsoup()
+
+ return document.select("div.eplister > ul > li").map { episodeElement ->
+ val numberText = episodeElement.selectFirst("div.epl-num").text()
+ val numberString = numberText.substringBefore(" ")
+ val episodeNumber = if (numberText.contains("part 2", true)) {
+ numberString.toFloatOrNull()?.plus(0.5F) ?: 0F
+ } else {
+ numberString.toFloatOrNull() ?: 0F
+ }
+
+ SEpisode.create().apply {
+ episode_number = episodeNumber
+ name = numberText
+ date_upload = parseDate(episodeElement.selectFirst("div.epl-date")?.text() ?: "")
+ setUrlWithoutDomain(episodeElement.selectFirst("a").attr("href").toHttpUrl().encodedPath)
+ }
+ }
+ }
+
+ override fun episodeListSelector(): String = throw Exception("Not Used")
+
+ override fun episodeFromElement(element: Element): SEpisode = throw Exception("Not Used")
+
+ // ============================ Video Links =============================
+
+ override fun videoListParse(response: Response): List