diff --git a/src/all/googledriveindex/build.gradle b/src/all/googledriveindex/build.gradle index 0d766023b..1e1462c4e 100644 --- a/src/all/googledriveindex/build.gradle +++ b/src/all/googledriveindex/build.gradle @@ -6,7 +6,7 @@ ext { extName = 'GoogleDriveIndex' pkgNameSuffix = 'all.googledriveindex' extClass = '.GoogleDriveIndex' - extVersionCode = 1 + extVersionCode = 2 libVersion = '13' } diff --git a/src/all/googledriveindex/src/eu/kanade/tachiyomi/animeextension/all/googledriveindex/GoogleDriveIndex.kt b/src/all/googledriveindex/src/eu/kanade/tachiyomi/animeextension/all/googledriveindex/GoogleDriveIndex.kt index 61a7a9ce2..d7010b19d 100644 --- a/src/all/googledriveindex/src/eu/kanade/tachiyomi/animeextension/all/googledriveindex/GoogleDriveIndex.kt +++ b/src/all/googledriveindex/src/eu/kanade/tachiyomi/animeextension/all/googledriveindex/GoogleDriveIndex.kt @@ -17,10 +17,13 @@ import eu.kanade.tachiyomi.animesource.model.Video import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.POST +import eu.kanade.tachiyomi.network.asObservableSuccess import eu.kanade.tachiyomi.util.asJsoup import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json +import okhttp3.Credentials +import okhttp3.HttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient @@ -31,6 +34,7 @@ import rx.Observable import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy +import java.net.URLEncoder class GoogleDriveIndex : ConfigurableAnimeSource, AnimeHttpSource() { @@ -52,7 +56,30 @@ class GoogleDriveIndex : ConfigurableAnimeSource, AnimeHttpSource() { Injekt.get().getSharedPreferences("source_$id", 0x0000) } - override val client: OkHttpClient = network.cloudflareClient + override val client: OkHttpClient = network.cloudflareClient.newBuilder() + .addInterceptor { chain -> + var request = chain.request() + + if (request.url.username.isNotBlank() && request.url.password.isNotBlank()) { + + val credential = Credentials.basic(request.url.username, request.url.password) + request = request.newBuilder() + .header("Authorization", credential) + .build() + + val newUrl = request.url.newBuilder() + .username("") + .password("") + .build() + + request = request.newBuilder() + .url(newUrl) + .build() + } + + chain.proceed(request) + } + .build() // ============================== Popular =============================== @@ -61,13 +88,17 @@ class GoogleDriveIndex : ConfigurableAnimeSource, AnimeHttpSource() { throw Exception("Enter drive path(s) in extension settings.") } + if (baseUrl.toHttpUrl().host == "drive.google.com") { + throw Exception("This extension is only for Google Drive Index sites, not drive.google.com folders.") + } + if (page == 1) pageToken = "" val popHeaders = headers.newBuilder() .add("Accept", "*/*") .add("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8") .add("Host", baseUrl.toHttpUrl().host) .add("Origin", "https://${baseUrl.toHttpUrl().host}") - .add("Referer", baseUrl) + .add("Referer", URLEncoder.encode(baseUrl, "UTF-8")) .add("X-Requested-With", "XMLHttpRequest") .build() @@ -88,6 +119,28 @@ class GoogleDriveIndex : ConfigurableAnimeSource, AnimeHttpSource() { // =============================== Search =============================== + override fun searchAnimeParse(response: Response): AnimesPage = throw Exception("Not used") + + override fun fetchSearchAnime( + page: Int, + query: String, + filters: AnimeFilterList, + ): Observable { + val req = searchAnimeRequest(page, query, filters) + return Observable.defer { + try { + client.newCall(req).asObservableSuccess() + } catch (e: NoClassDefFoundError) { + // RxJava doesn't handle Errors, which tends to happen during global searches + // if an old extension using non-existent classes is still around + throw RuntimeException(e) + } + } + .map { response -> + searchAnimeParse(response, req.url.toString()) + } + } + override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request { if (baseUrl.isEmpty()) { throw Exception("Enter drive path(s) in extension settings.") @@ -97,6 +150,10 @@ class GoogleDriveIndex : ConfigurableAnimeSource, AnimeHttpSource() { val serverFilter = filterList.find { it is ServerFilter } as ServerFilter val serverUrl = serverFilter.toUriPart() + if (serverUrl.toHttpUrl().host == "drive.google.com") { + throw Exception("This extension is only for Google Drive Index sites, not drive.google.com folders.") + } + if (page == 1) pageToken = "" val searchHeaders = headers.newBuilder() .add("Accept", "*/*") @@ -110,11 +167,12 @@ class GoogleDriveIndex : ConfigurableAnimeSource, AnimeHttpSource() { POST( serverUrl, body = popBody, - headers = searchHeaders.add("Referer", serverUrl).build(), + headers = searchHeaders.add("Referer", URLEncoder.encode(serverUrl, "UTF-8")).build(), ) } else { val cleanQuery = query.replace(" ", "+") - val searchUrl = "https://${serverUrl.toHttpUrl().host}/${serverUrl.toHttpUrl().pathSegments[0]}search" + + val searchUrl = "https://${serverUrl.toHttpUrl().hostAndCred()}/${serverUrl.toHttpUrl().pathSegments[0]}search" val popBody = "q=$cleanQuery&page_token=$pageToken&page_index=${page - 1}".toRequestBody("application/x-www-form-urlencoded".toMediaType()) @@ -126,8 +184,8 @@ class GoogleDriveIndex : ConfigurableAnimeSource, AnimeHttpSource() { } } - override fun searchAnimeParse(response: Response): AnimesPage { - return parsePage(response, response.request.url.toString()) + private fun searchAnimeParse(response: Response, url: String): AnimesPage { + return parsePage(response, url) } // ============================== FILTERS =============================== @@ -177,7 +235,7 @@ class GoogleDriveIndex : ConfigurableAnimeSource, AnimeHttpSource() { .add("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8") .add("Host", idParsed.url.toHttpUrl().host) .add("Origin", "https://${idParsed.url.toHttpUrl().host}") - .add("Referer", idParsed.referer) + .add("Referer", URLEncoder.encode(idParsed.referer, "UTF-8")) .add("X-Requested-With", "XMLHttpRequest") .build() @@ -219,7 +277,7 @@ class GoogleDriveIndex : ConfigurableAnimeSource, AnimeHttpSource() { .add("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8") .add("Host", url.toHttpUrl().host) .add("Origin", "https://${url.toHttpUrl().host}") - .add("Referer", url) + .add("Referer", URLEncoder.encode(url, "UTF-8")) .add("X-Requested-With", "XMLHttpRequest") .build() @@ -259,11 +317,6 @@ class GoogleDriveIndex : ConfigurableAnimeSource, AnimeHttpSource() { } else { "" } - val seasonText = if (season.isBlank()) { - "" - } else { - "[${season.trimInfo()}] " - } // Get other info val extraInfo = if (paths.size > basePathCounter) { @@ -273,7 +326,7 @@ class GoogleDriveIndex : ConfigurableAnimeSource, AnimeHttpSource() { } val size = item.size?.toLongOrNull()?.let { formatFileSize(it) } - episode.name = "$seasonText${item.name.trimInfo()}${if (size == null) "" else " - $size"}" + episode.name = "${item.name.trimInfo()}${if (size == null) "" else " - $size"}" episode.url = epUrl episode.scanlator = seasonInfo + extraInfo episode.episode_number = counter.toFloat() @@ -319,6 +372,14 @@ class GoogleDriveIndex : ConfigurableAnimeSource, AnimeHttpSource() { // ============================= Utilities ============================== + private fun HttpUrl.hostAndCred(): String { + return if (this.password.isNotBlank() && this.username.isNotBlank()) { + "${this.username}:${this.password}@${this.host}" + } else { + this.host + } + } + private fun joinUrl(path1: String, path2: String): String { return path1.removeSuffix("/") + "/" + path2.removePrefix("/") } @@ -446,7 +507,9 @@ class GoogleDriveIndex : ConfigurableAnimeSource, AnimeHttpSource() { """.trimMargin() this.setDefaultValue("") dialogTitle = "Path list" - dialogMessage = "Separate paths with a comma" + dialogMessage = """Separate paths with a comma. For password protected sites, + |format as: "https://username:password@example.worker.dev/0:/" + """.trimMargin() setOnPreferenceChangeListener { _, newValue -> try {