fix(src/all): Fix google drive based extensions (#3062)

Signed-off-by: Secozzi <folke.steen85@gmail.com>
This commit is contained in:
Secozzi 2024-03-21 07:39:27 +00:00 committed by GitHub
parent ed2f4d6d13
commit b2523ab859
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 43 additions and 124 deletions

View File

@ -2,15 +2,11 @@ package eu.kanade.tachiyomi.lib.googledriveextractor
import eu.kanade.tachiyomi.animesource.model.Video import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Cookie import okhttp3.Cookie
import okhttp3.CookieJar
import okhttp3.Headers import okhttp3.Headers
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.internal.commonEmptyRequestBody
class GoogleDriveExtractor(private val client: OkHttpClient, private val headers: Headers) { class GoogleDriveExtractor(private val client: OkHttpClient, private val headers: Headers) {
@ -20,28 +16,21 @@ class GoogleDriveExtractor(private val client: OkHttpClient, private val headers
private val cookieList = client.cookieJar.loadForRequest("https://drive.google.com".toHttpUrl()) private val cookieList = client.cookieJar.loadForRequest("https://drive.google.com".toHttpUrl())
private val noRedirectClient = OkHttpClient.Builder() fun videosFromUrl(itemId: String, videoName: String = "Video"): List<Video> {
.followRedirects(false) val url = "https://drive.usercontent.google.com/download?id=$itemId"
.build()
fun videosFromUrl(itemUrl: String, videoName: String = "Video"): List<Video> {
val cookieJar = GDriveCookieJar()
cookieJar.saveFromResponse("https://drive.google.com".toHttpUrl(), cookieList)
val docHeaders = headers.newBuilder().apply { val docHeaders = headers.newBuilder().apply {
add("Accept", ACCEPT) add("Accept", ACCEPT)
add("Connection", "keep-alive")
add("Cookie", cookieList.toStr()) add("Cookie", cookieList.toStr())
add("Host", "drive.google.com")
}.build() }.build()
val docResp = noRedirectClient.newCall( val docResp = client.newCall(
GET(itemUrl, headers = docHeaders), GET(url, docHeaders)
).execute() ).execute()
if (docResp.isRedirect) { if (!docResp.peekBody(15).string().equals("<!DOCTYPE html>", true)) {
return videoFromRedirect(itemUrl, videoName, "", cookieJar) return listOf(
Video(url, videoName, url, docHeaders)
)
} }
val document = docResp.asJsoup() val document = docResp.asJsoup()
@ -50,78 +39,14 @@ class GoogleDriveExtractor(private val client: OkHttpClient, private val headers
?.let { " ${it.ownText().trim()} " } ?.let { " ${it.ownText().trim()} " }
?: "" ?: ""
val downloadUrl = document.selectFirst("form#download-form")?.attr("action") ?: return emptyList() val videoUrl = url.toHttpUrl().newBuilder().apply {
val postHeaders = headers.newBuilder().apply { document.select("input[type=hidden]").forEach {
add("Accept", ACCEPT) setQueryParameter(it.attr("name"), it.attr("value"))
add("Content-Type", "application/x-www-form-urlencoded") }
set("Cookie", client.cookieJar.loadForRequest("https://drive.google.com".toHttpUrl()).toStr()) }.build().toString()
add("Host", "drive.google.com")
add("Referer", "https://drive.google.com/")
}.build()
val newUrl = noRedirectClient.newCall(
POST(downloadUrl, headers = postHeaders, body = commonEmptyRequestBody),
).execute().use { it.headers["location"] ?: downloadUrl }
return videoFromRedirect(newUrl, videoName, itemSize, cookieJar)
}
private fun videoFromRedirect(
downloadUrl: String,
videoName: String,
itemSize: String,
cookieJar: GDriveCookieJar,
): List<Video> {
var newUrl = downloadUrl
val newHeaders = headers.newBuilder().apply {
add("Accept", ACCEPT)
set("Cookie", cookieJar.loadForRequest(newUrl.toHttpUrl()).toStr())
set("Host", newUrl.toHttpUrl().host)
add("Referer", "https://drive.google.com/")
}.build()
var newResp = noRedirectClient.newCall(
GET(newUrl, headers = newHeaders),
).execute()
var redirectCounter = 1
while (newResp.isRedirect && redirectCounter < 15) {
val setCookies = newResp.headers("Set-Cookie").mapNotNull { Cookie.parse(newResp.request.url, it) }
cookieJar.saveFromResponse(newResp.request.url, setCookies)
newUrl = newResp.headers["location"]!!
newResp.close()
val newHeaders = headers.newBuilder().apply {
add("Accept", ACCEPT)
set("Cookie", cookieJar.loadForRequest(newUrl.toHttpUrl()).toStr())
set("Host", newUrl.toHttpUrl().host)
add("Referer", "https://drive.google.com/")
}.build()
newResp = noRedirectClient.newCall(
GET(newUrl, headers = newHeaders),
).execute()
redirectCounter += 1
}
val videoUrl = newResp.use { it.request.url }
val videoHeaders = headers.newBuilder().apply {
add("Accept", ACCEPT)
set("Cookie", cookieJar.loadForRequest(videoUrl).toStr())
set("Host", videoUrl.host)
add("Referer", "https://drive.google.com/")
}.build()
return listOf( return listOf(
Video( Video(videoUrl, videoName + itemSize, videoUrl, docHeaders)
videoUrl.toString(),
videoName + itemSize,
videoUrl.toString(),
headers = videoHeaders,
),
) )
} }
@ -129,22 +54,3 @@ class GoogleDriveExtractor(private val client: OkHttpClient, private val headers
return this.joinToString("; ") { "${it.name}=${it.value}" } return this.joinToString("; ") { "${it.name}=${it.value}" }
} }
} }
class GDriveCookieJar : CookieJar {
private val cookieStore = mutableMapOf<String, MutableList<Cookie>>()
// Append rather than overwrite, what could go wrong?
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {
val oldCookies = (cookieStore[url.host] ?: emptyList()).filter { c ->
!cookies.any { t -> c.name == t.name }
}
cookieStore[url.host] = (oldCookies + cookies).toMutableList()
}
override fun loadForRequest(url: HttpUrl): List<Cookie> {
val cookies = cookieStore[url.host] ?: emptyList()
return cookies.filter { it.expiresAt >= System.currentTimeMillis() }
}
}

View File

@ -357,7 +357,7 @@ class GoogleDrive : ConfigurableAnimeSource, AnimeHttpSource() {
// ============================ Video Links ============================= // ============================ Video Links =============================
override suspend fun getVideoList(episode: SEpisode): List<Video> = override suspend fun getVideoList(episode: SEpisode): List<Video> =
GoogleDriveExtractor(client, headers).videosFromUrl(episode.url) GoogleDriveExtractor(client, headers).videosFromUrl(episode.url.substringAfter("?id="))
// ============================= Utilities ============================== // ============================= Utilities ==============================

View File

@ -68,8 +68,8 @@ class Kayoanime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
latestPost = "" latestPost = ""
layout = "" layout = ""
settings = "" settings = ""
currentReferer = "https://kayoanime.com/ongoing-anime/" currentReferer = "https://kayoanime.com/ongoing-animes/"
GET("$baseUrl/ongoing-anime/") GET("$baseUrl/ongoing-animes/")
} else { } else {
val formBody = FormBody.Builder() val formBody = FormBody.Builder()
.add("action", "tie_archives_load_more") .add("action", "tie_archives_load_more")
@ -206,11 +206,17 @@ class Kayoanime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
} }
} }
override fun searchAnimeSelector(): String = popularAnimeSelector() override fun searchAnimeParse(response: Response): AnimesPage =
popularAnimeParse(response)
override fun searchAnimeFromElement(element: Element): SAnime = popularAnimeFromElement(element) override fun searchAnimeSelector(): String =
throw UnsupportedOperationException()
override fun searchAnimeNextPageSelector(): String = popularAnimeNextPageSelector() override fun searchAnimeFromElement(element: Element): SAnime =
throw UnsupportedOperationException()
override fun searchAnimeNextPageSelector(): String =
throw UnsupportedOperationException()
// ============================== Filters =============================== // ============================== Filters ===============================
@ -393,7 +399,8 @@ class Kayoanime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
val url = it.selectFirst("a[href*=tinyurl.com]")!!.attr("href") val url = it.selectFirst("a[href*=tinyurl.com]")!!.attr("href")
val redirected = noRedirectClient.newCall(GET(url)).execute() val redirected = noRedirectClient.newCall(GET(url)).execute()
redirected.headers["location"]?.let { location -> redirected.headers["location"]?.let { location ->
if (location.toHttpUrl().host.contains("workers.dev")) { val host = location.toHttpUrl().host
if (host.contains("workers.dev")) {
episodeList.addAll( episodeList.addAll(
indexExtractor.getEpisodesFromIndex( indexExtractor.getEpisodesFromIndex(
location, location,
@ -402,6 +409,14 @@ class Kayoanime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
), ),
) )
} }
if (host.contains("slogoanime")) {
val document = client.newCall(GET(location)).execute().asJsoup()
document.select("a[href*=drive.google.com]").distinctBy { it.text() }.forEach {
val url = it.selectFirst("a[href*=drive.google.com]")!!.attr("href").substringBeforeLast("?usp=shar")
traverseFolder(url, getVideoPathsFromElement(season) + " " + it.text())
}
}
} }
} }
} }
@ -422,18 +437,16 @@ class Kayoanime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
// ============================ Video Links ============================= // ============================ Video Links =============================
override suspend fun getVideoList(episode: SEpisode): List<Video> { override suspend fun getVideoList(episode: SEpisode): List<Video> {
val host = episode.url.toHttpUrl().host val httpUrl = episode.url.toHttpUrl()
val videoList = if (host == "drive.google.com") { val host = httpUrl.host
GoogleDriveExtractor(client, headers).videosFromUrl(episode.url) return if (host == "drive.google.com") {
val id = httpUrl.queryParameter("id")!!
GoogleDriveExtractor(client, headers).videosFromUrl(id)
} else if (host.contains("workers.dev")) { } else if (host.contains("workers.dev")) {
getIndexVideoUrl(episode.url) getIndexVideoUrl(episode.url)
} else { } else {
throw Exception("Unsupported url: ${episode.url}") throw Exception("Unsupported url: ${episode.url}")
} }
require(videoList.isNotEmpty()) { "Failed to fetch videos" }
return videoList
} }
override fun videoListSelector(): String = throw UnsupportedOperationException() override fun videoListSelector(): String = throw UnsupportedOperationException()

View File

@ -55,7 +55,7 @@ class Ripcrabbyanime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/ongoing-series/") override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/ongoing-series/")
override fun popularAnimeSelector(): String = "section#movies-list > div.movies-box" override fun popularAnimeSelector(): String = "section#movies-list div.movies-box"
override fun popularAnimeFromElement(element: Element): SAnime = SAnime.create().apply { override fun popularAnimeFromElement(element: Element): SAnime = SAnime.create().apply {
setUrlWithoutDomain(element.selectFirst("a")!!.attr("href")) setUrlWithoutDomain(element.selectFirst("a")!!.attr("href"))
@ -323,7 +323,7 @@ class Ripcrabbyanime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
// ============================ Video Links ============================= // ============================ Video Links =============================
override suspend fun getVideoList(episode: SEpisode): List<Video> { override suspend fun getVideoList(episode: SEpisode): List<Video> {
val videoList = GoogleDriveExtractor(client, headers).videosFromUrl(episode.url) val videoList = GoogleDriveExtractor(client, headers).videosFromUrl(episode.url.substringAfter("?id="))
return videoList return videoList
} }