gogo: fix video links
This commit is contained in:
@ -5,7 +5,7 @@ ext {
|
|||||||
extName = 'Gogoanime'
|
extName = 'Gogoanime'
|
||||||
pkgNameSuffix = 'en.gogoanime'
|
pkgNameSuffix = 'en.gogoanime'
|
||||||
extClass = '.GogoAnime'
|
extClass = '.GogoAnime'
|
||||||
extVersionCode = 24
|
extVersionCode = 25
|
||||||
libVersion = '12'
|
libVersion = '12'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,100 @@
|
|||||||
|
package eu.kanade.tachiyomi.animeextension.en.gogoanime
|
||||||
|
|
||||||
|
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.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() : Interceptor {
|
||||||
|
private val context = Injekt.get<Application>()
|
||||||
|
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? {
|
||||||
|
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("https://gogoplay.io/encrypt-ajax.php")) {
|
||||||
|
Log.i("bruh", "hi")
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,7 @@ import android.app.Application
|
|||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import androidx.preference.ListPreference
|
import androidx.preference.ListPreference
|
||||||
import androidx.preference.PreferenceScreen
|
import androidx.preference.PreferenceScreen
|
||||||
|
import eu.kanade.tachiyomi.animeextension.en.gogoanime.extractors.DoodExtractor
|
||||||
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
|
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
|
||||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
|
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
|
||||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
||||||
@ -13,22 +14,29 @@ import eu.kanade.tachiyomi.animesource.model.Video
|
|||||||
import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
|
import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
import eu.kanade.tachiyomi.util.asJsoup
|
import eu.kanade.tachiyomi.util.asJsoup
|
||||||
|
import kotlinx.serialization.decodeFromString
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.json.JsonObject
|
||||||
|
import kotlinx.serialization.json.jsonArray
|
||||||
|
import kotlinx.serialization.json.jsonObject
|
||||||
|
import kotlinx.serialization.json.jsonPrimitive
|
||||||
import okhttp3.Headers
|
import okhttp3.Headers
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
|
import org.jsoup.Jsoup
|
||||||
import org.jsoup.nodes.Document
|
import org.jsoup.nodes.Document
|
||||||
import org.jsoup.nodes.Element
|
import org.jsoup.nodes.Element
|
||||||
import org.jsoup.select.Elements
|
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.lang.Exception
|
import java.lang.Exception
|
||||||
|
|
||||||
class GogoAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
class GogoAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
||||||
|
|
||||||
override val name = "Gogoanime"
|
override val name = "Gogoanime"
|
||||||
|
|
||||||
override val baseUrl = "https://gogoanime.cm"
|
override val baseUrl = "https://www3.gogoanime.cm"
|
||||||
|
|
||||||
override val lang = "en"
|
override val lang = "en"
|
||||||
|
|
||||||
@ -36,6 +44,8 @@ class GogoAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||||||
|
|
||||||
override val client: OkHttpClient = network.cloudflareClient
|
override val client: OkHttpClient = network.cloudflareClient
|
||||||
|
|
||||||
|
private val json: Json by injectLazy()
|
||||||
|
|
||||||
private val preferences: SharedPreferences by lazy {
|
private val preferences: SharedPreferences by lazy {
|
||||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||||
}
|
}
|
||||||
@ -80,103 +90,52 @@ class GogoAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||||||
return episode
|
return episode
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun videoListRequest(episode: SEpisode): Request {
|
|
||||||
val document = client.newCall(GET(baseUrl + episode.url)).execute().asJsoup()
|
|
||||||
val link = document.selectFirst("li.dowloads a").attr("href")
|
|
||||||
return GET(link)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun videoListParse(response: Response): List<Video> {
|
override fun videoListParse(response: Response): List<Video> {
|
||||||
val document = response.asJsoup()
|
val responseString = response.body!!.string()
|
||||||
return document.select(videoListSelector()).ordered().map { videoFromElement(it) }
|
val doc = Jsoup.parse(
|
||||||
.filter { it.videoUrl != null }
|
responseString
|
||||||
|
.substringAfter("<!------------------ vidstream.io server type = 1 display --------------->")
|
||||||
|
.substringBefore("<!--")
|
||||||
|
)
|
||||||
|
val iframe = "https:" + doc.select("iframe").attr("src")
|
||||||
|
val baseReferer = Headers.headersOf("Referer", baseUrl)
|
||||||
|
val iframeResponse = client.newCall(GET(iframe, baseReferer)).execute().asJsoup()
|
||||||
|
|
||||||
|
val videoList = mutableListOf<Video>()
|
||||||
|
|
||||||
|
// get mirror videos
|
||||||
|
val mirrors = iframeResponse.select("div#list-server-more ul li.linkserver:not(li.active)")
|
||||||
|
val doodMirror = mirrors.first { it.attr("data-video").startsWith("https://dood.") }.attr("data-video")
|
||||||
|
val doodVideo = DoodExtractor(client).videoFromUrl(doodMirror)
|
||||||
|
|
||||||
|
// get vidstreaming videos
|
||||||
|
try {
|
||||||
|
val interceptorClient = client.newBuilder().addInterceptor(GetSourcesInterceptor()).build()
|
||||||
|
val sources = interceptorClient.newCall(GET(iframe, baseReferer)).execute().body!!.string()
|
||||||
|
val jObject = json.decodeFromString<JsonObject>(sources)
|
||||||
|
val sourcesArray = jObject["source"]!!.jsonArray
|
||||||
|
val videoHeaders = Headers.headersOf("Referer", iframe)
|
||||||
|
for (element in sourcesArray.reversed()) {
|
||||||
|
val videoUrl = element.jsonObject["file"]!!.jsonPrimitive.content
|
||||||
|
val quality = element.jsonObject["label"]!!.jsonPrimitive.content
|
||||||
|
videoList.add(Video(videoUrl, quality, videoUrl, null, videoHeaders))
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {}
|
||||||
|
|
||||||
|
|
||||||
|
if (doodVideo != null) {
|
||||||
|
videoList.add(doodVideo)
|
||||||
|
}
|
||||||
|
if (videoList.isEmpty()) throw Exception("no links found")
|
||||||
|
return videoList
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Elements.ordered(): Elements {
|
override fun videoListSelector() = throw Exception("not used")
|
||||||
val newElements = Elements()
|
|
||||||
var googleElements = 0
|
|
||||||
for (element in this) {
|
|
||||||
if (element.attr("href").contains("https://dood")) {
|
|
||||||
newElements.add(element)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
newElements.add(googleElements, element)
|
|
||||||
if (element.attr("href").contains("google")) {
|
|
||||||
googleElements++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return newElements
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun videoListSelector() = "div.mirror_link a[download], div.mirror_link a[href*=https://dood]"
|
override fun videoFromElement(element: Element) = throw Exception("not used")
|
||||||
|
|
||||||
override fun videoFromElement(element: Element): Video {
|
|
||||||
val quality = element.text().substringAfter("Download (").replace("P - mp4)", "p")
|
|
||||||
val url = element.attr("href")
|
|
||||||
val location = element.ownerDocument().location()
|
|
||||||
val videoHeaders = Headers.headersOf("Referer", location)
|
|
||||||
return when {
|
|
||||||
url.contains("https://dood") -> {
|
|
||||||
val newQuality = "Doodstream mirror"
|
|
||||||
Video(url, newQuality, doodUrlParse(url), null, videoHeaders)
|
|
||||||
}
|
|
||||||
url.contains("google") -> {
|
|
||||||
val parsedQuality = "Google server: " + when (quality) {
|
|
||||||
"FullHDp" -> "1080p"
|
|
||||||
"HDp" -> "720p"
|
|
||||||
"SDp" -> "360p"
|
|
||||||
else -> quality
|
|
||||||
}
|
|
||||||
Video(url, parsedQuality, url, null)
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
val parsedQuality = when (quality) {
|
|
||||||
"FullHDp" -> "1080p"
|
|
||||||
"HDp" -> "720p"
|
|
||||||
"SDp" -> "360p"
|
|
||||||
else -> quality
|
|
||||||
}
|
|
||||||
Video(url, parsedQuality, videoUrlParse(url, location), null, videoHeaders)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun videoUrlParse(document: Document) = throw Exception("not used")
|
override fun videoUrlParse(document: Document) = throw Exception("not used")
|
||||||
|
|
||||||
private fun videoUrlParse(url: String, referer: String): String {
|
|
||||||
val refererHeader = Headers.headersOf("Referer", referer)
|
|
||||||
val noRedirectClient = client.newBuilder().followRedirects(false).build()
|
|
||||||
val response = noRedirectClient.newCall(GET(url, refererHeader)).execute()
|
|
||||||
val videoUrl = response.header("location")
|
|
||||||
response.close()
|
|
||||||
return videoUrl ?: url
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun doodUrlParse(url: String): String? {
|
|
||||||
val response = client.newCall(GET(url.replace("/d/", "/e/"))).execute()
|
|
||||||
val content = response.body!!.string()
|
|
||||||
if (!content.contains("'/pass_md5/")) return null
|
|
||||||
val md5 = content.substringAfter("'/pass_md5/").substringBefore("',")
|
|
||||||
val token = md5.substringAfterLast("/")
|
|
||||||
val doodTld = url.substringAfter("https://dood.").substringBefore("/")
|
|
||||||
val randomString = getRandomString()
|
|
||||||
val expiry = System.currentTimeMillis()
|
|
||||||
val videoUrlStart = client.newCall(
|
|
||||||
GET(
|
|
||||||
"https://dood.$doodTld/pass_md5/$md5",
|
|
||||||
Headers.headersOf("referer", url)
|
|
||||||
)
|
|
||||||
).execute().body!!.string()
|
|
||||||
return "$videoUrlStart$randomString?token=$token&expiry=$expiry"
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getRandomString(length: Int = 10): String {
|
|
||||||
val allowedChars = ('A'..'Z') + ('a'..'z') + ('0'..'9')
|
|
||||||
return (1..length)
|
|
||||||
.map { allowedChars.random() }
|
|
||||||
.joinToString("")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun List<Video>.sort(): List<Video> {
|
override fun List<Video>.sort(): List<Video> {
|
||||||
val quality = preferences.getString("preferred_quality", null)
|
val quality = preferences.getString("preferred_quality", null)
|
||||||
if (quality != null) {
|
if (quality != null) {
|
||||||
|
@ -0,0 +1,40 @@
|
|||||||
|
package eu.kanade.tachiyomi.animeextension.en.gogoanime.extractors
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.animesource.model.Video
|
||||||
|
import eu.kanade.tachiyomi.network.GET
|
||||||
|
import okhttp3.Headers
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
|
||||||
|
class DoodExtractor(private val client: OkHttpClient) {
|
||||||
|
fun videoFromUrl(url: String): Video? {
|
||||||
|
val response = client.newCall(GET(url)).execute()
|
||||||
|
val doodTld = url.substringAfter("https://dood.").substringBefore("/")
|
||||||
|
val content = response.body!!.string()
|
||||||
|
if (!content.contains("'/pass_md5/")) return null
|
||||||
|
val md5 = content.substringAfter("'/pass_md5/").substringBefore("',")
|
||||||
|
val token = md5.substringAfterLast("/")
|
||||||
|
val randomString = getRandomString()
|
||||||
|
val expiry = System.currentTimeMillis()
|
||||||
|
val videoUrlStart = client.newCall(
|
||||||
|
GET(
|
||||||
|
"https://dood.$doodTld/pass_md5/$md5",
|
||||||
|
Headers.headersOf("referer", url)
|
||||||
|
)
|
||||||
|
).execute().body!!.string()
|
||||||
|
val videoUrl = "$videoUrlStart$randomString?token=$token&expiry=$expiry"
|
||||||
|
|
||||||
|
return Video(url, "Doodstream mirror", videoUrl, null, doodHeaders(doodTld))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getRandomString(length: Int = 10): String {
|
||||||
|
val allowedChars = ('A'..'Z') + ('a'..'z') + ('0'..'9')
|
||||||
|
return (1..length)
|
||||||
|
.map { allowedChars.random() }
|
||||||
|
.joinToString("")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun doodHeaders(tld: String) = Headers.Builder().apply {
|
||||||
|
add("User-Agent", "Aniyomi")
|
||||||
|
add("Referer", "https://dood.$tld/")
|
||||||
|
}.build()
|
||||||
|
}
|
Reference in New Issue
Block a user