Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix google drive based extensions #3062

Merged
merged 3 commits into from
Mar 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,11 @@ package eu.kanade.tachiyomi.lib.googledriveextractor

import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Cookie
import okhttp3.CookieJar
import okhttp3.Headers
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import okhttp3.internal.commonEmptyRequestBody

class GoogleDriveExtractor(private val client: OkHttpClient, private val headers: Headers) {

Expand All @@ -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 noRedirectClient = OkHttpClient.Builder()
.followRedirects(false)
.build()

fun videosFromUrl(itemUrl: String, videoName: String = "Video"): List<Video> {
val cookieJar = GDriveCookieJar()

cookieJar.saveFromResponse("https://drive.google.com".toHttpUrl(), cookieList)

fun videosFromUrl(itemId: String, videoName: String = "Video"): List<Video> {
val url = "https://drive.usercontent.google.com/download?id=$itemId"
val docHeaders = headers.newBuilder().apply {
add("Accept", ACCEPT)
add("Connection", "keep-alive")
add("Cookie", cookieList.toStr())
add("Host", "drive.google.com")
}.build()

val docResp = noRedirectClient.newCall(
GET(itemUrl, headers = docHeaders),
val docResp = client.newCall(
GET(url, docHeaders)
).execute()

if (docResp.isRedirect) {
return videoFromRedirect(itemUrl, videoName, "", cookieJar)
if (!docResp.peekBody(15).string().equals("<!DOCTYPE html>", true)) {
return listOf(
Video(url, videoName, url, docHeaders)
)
}

val document = docResp.asJsoup()
Expand All @@ -50,101 +39,18 @@ class GoogleDriveExtractor(private val client: OkHttpClient, private val headers
?.let { " ${it.ownText().trim()} " }
?: ""

val downloadUrl = document.selectFirst("form#download-form")?.attr("action") ?: return emptyList()
val postHeaders = headers.newBuilder().apply {
add("Accept", ACCEPT)
add("Content-Type", "application/x-www-form-urlencoded")
set("Cookie", client.cookieJar.loadForRequest("https://drive.google.com".toHttpUrl()).toStr())
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()
val videoUrl = url.toHttpUrl().newBuilder().apply {
document.select("input[type=hidden]").forEach {
setQueryParameter(it.attr("name"), it.attr("value"))
}
}.build().toString()

return listOf(
Video(
videoUrl.toString(),
videoName + itemSize,
videoUrl.toString(),
headers = videoHeaders,
),
Video(videoUrl, videoName + itemSize, videoUrl, docHeaders)
)
}

private fun List<Cookie>.toStr(): String {
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() }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@ class GoogleDrive : ConfigurableAnimeSource, AnimeHttpSource() {
// ============================ Video Links =============================

override suspend fun getVideoList(episode: SEpisode): List<Video> =
GoogleDriveExtractor(client, headers).videosFromUrl(episode.url)
GoogleDriveExtractor(client, headers).videosFromUrl(episode.url.substringAfter("?id="))

// ============================= Utilities ==============================

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ class Kayoanime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
latestPost = ""
layout = ""
settings = ""
currentReferer = "https://kayoanime.com/ongoing-anime/"
GET("$baseUrl/ongoing-anime/")
currentReferer = "https://kayoanime.com/ongoing-animes/"
GET("$baseUrl/ongoing-animes/")
} else {
val formBody = FormBody.Builder()
.add("action", "tie_archives_load_more")
Expand Down Expand Up @@ -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 ===============================

Expand Down Expand Up @@ -393,7 +399,8 @@ class Kayoanime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
val url = it.selectFirst("a[href*=tinyurl.com]")!!.attr("href")
val redirected = noRedirectClient.newCall(GET(url)).execute()
redirected.headers["location"]?.let { location ->
if (location.toHttpUrl().host.contains("workers.dev")) {
val host = location.toHttpUrl().host
if (host.contains("workers.dev")) {
episodeList.addAll(
indexExtractor.getEpisodesFromIndex(
location,
Expand All @@ -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())
}
}
}
}
}
Expand All @@ -422,18 +437,16 @@ class Kayoanime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
// ============================ Video Links =============================

override suspend fun getVideoList(episode: SEpisode): List<Video> {
val host = episode.url.toHttpUrl().host
val videoList = if (host == "drive.google.com") {
GoogleDriveExtractor(client, headers).videosFromUrl(episode.url)
val httpUrl = episode.url.toHttpUrl()
val host = httpUrl.host
return if (host == "drive.google.com") {
val id = httpUrl.queryParameter("id")!!
GoogleDriveExtractor(client, headers).videosFromUrl(id)
} else if (host.contains("workers.dev")) {
getIndexVideoUrl(episode.url)
} else {
throw Exception("Unsupported url: ${episode.url}")
}

require(videoList.isNotEmpty()) { "Failed to fetch videos" }

return videoList
}

override fun videoListSelector(): String = throw UnsupportedOperationException()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class Ripcrabbyanime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {

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 {
setUrlWithoutDomain(element.selectFirst("a")!!.attr("href"))
Expand Down Expand Up @@ -323,7 +323,7 @@ class Ripcrabbyanime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
// ============================ Video Links =============================

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
}

Expand Down