diff --git a/src/en/zoro/AndroidManifest.xml b/src/en/zoro/AndroidManifest.xml
new file mode 100644
index 000000000..acb4de356
--- /dev/null
+++ b/src/en/zoro/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/src/en/zoro/build.gradle b/src/en/zoro/build.gradle
new file mode 100644
index 000000000..ed00dc3e9
--- /dev/null
+++ b/src/en/zoro/build.gradle
@@ -0,0 +1,12 @@
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+
+ext {
+ extName = 'zoro.to (experimental)'
+ pkgNameSuffix = 'en.zoro'
+ extClass = '.Zoro'
+ extVersionCode = 1
+ libVersion = '12'
+}
+
+apply from: "$rootDir/common.gradle"
diff --git a/src/en/zoro/src/eu/kanade/tachiyomi/animeextension/en/zoro/GetSourcesInterceptor.kt b/src/en/zoro/src/eu/kanade/tachiyomi/animeextension/en/zoro/GetSourcesInterceptor.kt
new file mode 100644
index 000000000..644a7e634
--- /dev/null
+++ b/src/en/zoro/src/eu/kanade/tachiyomi/animeextension/en/zoro/GetSourcesInterceptor.kt
@@ -0,0 +1,101 @@
+package eu.kanade.tachiyomi.animeextension.en.zoro
+
+import android.annotation.SuppressLint
+import android.app.Application
+import android.os.Handler
+import android.os.Looper
+import android.util.Log
+import android.webkit.WebResourceRequest
+import android.webkit.WebResourceResponse
+import android.webkit.WebSettings
+import android.webkit.WebView
+import android.webkit.WebViewClient
+import eu.kanade.tachiyomi.network.GET
+import okhttp3.Headers.Companion.toHeaders
+import okhttp3.Interceptor
+import okhttp3.OkHttpClient
+import okhttp3.Request
+import okhttp3.Response
+import uy.kohesive.injekt.Injekt
+import uy.kohesive.injekt.api.get
+import java.io.IOException
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+
+class GetSourcesInterceptor(private val getSources: String, private val client: OkHttpClient) : Interceptor {
+ private val context = Injekt.get()
+ private val handler by lazy { Handler(Looper.getMainLooper()) }
+
+ private val initWebView by lazy {
+ WebSettings.getDefaultUserAgent(context)
+ }
+
+ @Synchronized
+ override fun intercept(chain: Interceptor.Chain): Response {
+ initWebView
+
+ val request = chain.request()
+
+ try {
+ val newRequest = resolveWithWebView(request)
+
+ return chain.proceed(newRequest ?: request)
+ } catch (e: Exception) {
+ throw IOException(e)
+ }
+ }
+
+ @SuppressLint("SetJavaScriptEnabled")
+ private fun resolveWithWebView(request: Request): Request? {
+ Log.i("bruh", "hello")
+ val latch = CountDownLatch(1)
+
+ var webView: WebView? = null
+
+ val origRequestUrl = request.url.toString()
+ val headers = request.headers.toMultimap().mapValues { it.value.getOrNull(0) ?: "" }.toMutableMap()
+ var newRequest: Request? = null
+
+ handler.post {
+ val webview = WebView(context)
+ webView = webview
+ with(webview.settings) {
+ javaScriptEnabled = true
+ domStorageEnabled = true
+ databaseEnabled = true
+ useWideViewPort = false
+ loadWithOverviewMode = false
+ userAgentString = "Mozilla/5.0 (Linux; Android) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.109 Safari/537.36 CrKey/1.54.248666"
+ }
+ webview.webViewClient = object : WebViewClient() {
+ override fun shouldInterceptRequest(
+ view: WebView,
+ request: WebResourceRequest
+ ): WebResourceResponse? {
+ val url = request.url.toString()
+ if (url.contains(getSources)) {
+ val newHeaders = request.requestHeaders.toHeaders()
+ newRequest = GET(url, newHeaders)
+ latch.countDown()
+ }
+ return super.shouldInterceptRequest(view, request)
+ }
+ }
+
+ webView?.loadUrl(origRequestUrl, headers)
+ }
+
+ latch.await(TIMEOUT_SEC, TimeUnit.SECONDS)
+
+ handler.post {
+ webView?.stopLoading()
+ webView?.destroy()
+ webView = null
+ }
+ return newRequest
+ }
+
+ companion object {
+ const val TIMEOUT_SEC: Long = 10
+ }
+}
diff --git a/src/en/zoro/src/eu/kanade/tachiyomi/animeextension/en/zoro/JSONUtil.java b/src/en/zoro/src/eu/kanade/tachiyomi/animeextension/en/zoro/JSONUtil.java
new file mode 100644
index 000000000..914995394
--- /dev/null
+++ b/src/en/zoro/src/eu/kanade/tachiyomi/animeextension/en/zoro/JSONUtil.java
@@ -0,0 +1,88 @@
+package eu.kanade.tachiyomi.animeextension.en.zoro;
+
+public class JSONUtil {
+ public static String escape(String input) {
+ StringBuilder output = new StringBuilder();
+
+ for(int i=0; i= 0x10000) {
+ assert false : "Java stores as u16, so it should never give us a character that's bigger than 2 bytes. It literally can't.";
+ } else if(chx > 127) {
+ output.append(String.format("\\u%04x", chx));
+ } else {
+ output.append(ch);
+ }
+ }
+
+ return output.toString();
+ }
+
+ public static String unescape(String input) {
+ StringBuilder builder = new StringBuilder();
+
+ int i = 0;
+ while (i < input.length()) {
+ char delimiter = input.charAt(i); i++; // consume letter or backslash
+
+ if(delimiter == '\\' && i < input.length()) {
+
+ // consume first after backslash
+ char ch = input.charAt(i); i++;
+
+ if(ch == '\\' || ch == '/' || ch == '"' || ch == '\'') {
+ builder.append(ch);
+ }
+ else if(ch == 'n') builder.append('\n');
+ else if(ch == 'r') builder.append('\r');
+ else if(ch == 't') builder.append('\t');
+ else if(ch == 'b') builder.append('\b');
+ else if(ch == 'f') builder.append('\f');
+ else if(ch == 'u') {
+
+ StringBuilder hex = new StringBuilder();
+
+ // expect 4 digits
+ if (i+4 > input.length()) {
+ throw new RuntimeException("Not enough unicode digits! ");
+ }
+ for (char x : input.substring(i, i + 4).toCharArray()) {
+ if(!Character.isLetterOrDigit(x)) {
+ throw new RuntimeException("Bad character in unicode escape.");
+ }
+ hex.append(Character.toLowerCase(x));
+ }
+ i+=4; // consume those four digits.
+
+ int code = Integer.parseInt(hex.toString(), 16);
+ builder.append((char) code);
+ } else {
+ throw new RuntimeException("Illegal escape sequence: \\"+ch);
+ }
+ } else { // it's not a backslash, or it's the last character.
+ builder.append(delimiter);
+ }
+ }
+
+ return builder.toString();
+ }
+}
diff --git a/src/en/zoro/src/eu/kanade/tachiyomi/animeextension/en/zoro/Zoro.kt b/src/en/zoro/src/eu/kanade/tachiyomi/animeextension/en/zoro/Zoro.kt
new file mode 100644
index 000000000..54731f182
--- /dev/null
+++ b/src/en/zoro/src/eu/kanade/tachiyomi/animeextension/en/zoro/Zoro.kt
@@ -0,0 +1,204 @@
+package eu.kanade.tachiyomi.animeextension.en.zoro
+
+import android.app.Application
+import android.content.SharedPreferences
+import android.util.Log
+import androidx.preference.ListPreference
+import androidx.preference.PreferenceScreen
+import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
+import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
+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 okhttp3.Headers
+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 uy.kohesive.injekt.Injekt
+import uy.kohesive.injekt.api.get
+import java.lang.Exception
+
+class Zoro : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
+
+ override val name = "zoro.to (experimental)"
+
+ override val baseUrl = "https://zoro.to"
+
+ 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)
+ }
+
+ override fun popularAnimeSelector(): String = "div.flw-item"
+
+ override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/most-popular?page=$page")
+
+ override fun popularAnimeFromElement(element: Element): SAnime {
+ val anime = SAnime.create()
+ anime.thumbnail_url = element.select("div.film-poster > img").attr("data-src")
+ anime.setUrlWithoutDomain(baseUrl + element.select("div.film-detail a").attr("href"))
+ anime.title = element.select("div.film-detail a").attr("data-jname")
+ anime.description = element.select("div.film-detail div.description").firstOrNull()?.text()
+ return anime
+ }
+
+ override fun popularAnimeNextPageSelector(): String = "li.page-item a[title=Next]"
+
+ override fun episodeListSelector() = "ul#episode_page li a"
+
+ override fun episodeListRequest(anime: SAnime): Request {
+ val id = anime.url.substringAfterLast("-")
+ val referer = Headers.headersOf("Referer", baseUrl + anime.url)
+ return GET("$baseUrl/ajax/v2/episode/list/$id", referer)
+ }
+
+ override fun episodeListParse(response: Response): List {
+ val data = response.body!!.string().substringAfter("\"html\":\"").substringBefore("