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("