From 9f7b5cfda685f12deb3500e87ca44b92cff84ca7 Mon Sep 17 00:00:00 2001 From: Secozzi <49240133+Secozzi@users.noreply.github.com> Date: Fri, 29 Mar 2024 16:09:40 +0000 Subject: [PATCH] feat(src/en): New source: Animesakura (#3110) Signed-off-by: Secozzi --- lib/googledrive-episodes/build.gradle.kts | 3 + .../GoogleDriveEpisodes.kt | 176 +++++++ src/en/animesakura/README.md | 3 + src/en/animesakura/build.gradle | 12 + .../res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 4297 bytes .../res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2117 bytes .../res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 6045 bytes .../res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 11402 bytes .../res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 17250 bytes .../en/animesakura/AnimeSakura.kt | 477 ++++++++++++++++++ .../en/animesakura/DriveIndexExtractor.kt | 162 ++++++ 11 files changed, 833 insertions(+) create mode 100644 lib/googledrive-episodes/build.gradle.kts create mode 100644 lib/googledrive-episodes/src/main/java/eu/kanade/tachiyomi/lib/googledriveepisodes/GoogleDriveEpisodes.kt create mode 100644 src/en/animesakura/README.md create mode 100644 src/en/animesakura/build.gradle create mode 100644 src/en/animesakura/res/mipmap-hdpi/ic_launcher.png create mode 100644 src/en/animesakura/res/mipmap-mdpi/ic_launcher.png create mode 100644 src/en/animesakura/res/mipmap-xhdpi/ic_launcher.png create mode 100644 src/en/animesakura/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 src/en/animesakura/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 src/en/animesakura/src/eu/kanade/tachiyomi/animeextension/en/animesakura/AnimeSakura.kt create mode 100644 src/en/animesakura/src/eu/kanade/tachiyomi/animeextension/en/animesakura/DriveIndexExtractor.kt diff --git a/lib/googledrive-episodes/build.gradle.kts b/lib/googledrive-episodes/build.gradle.kts new file mode 100644 index 0000000000..c26cbc8a82 --- /dev/null +++ b/lib/googledrive-episodes/build.gradle.kts @@ -0,0 +1,3 @@ +plugins { + id("lib-android") +} diff --git a/lib/googledrive-episodes/src/main/java/eu/kanade/tachiyomi/lib/googledriveepisodes/GoogleDriveEpisodes.kt b/lib/googledrive-episodes/src/main/java/eu/kanade/tachiyomi/lib/googledriveepisodes/GoogleDriveEpisodes.kt new file mode 100644 index 0000000000..229ff7747d --- /dev/null +++ b/lib/googledrive-episodes/src/main/java/eu/kanade/tachiyomi/lib/googledriveepisodes/GoogleDriveEpisodes.kt @@ -0,0 +1,176 @@ +package eu.kanade.tachiyomi.lib.googledriveepisodes + +import eu.kanade.tachiyomi.animesource.model.SEpisode +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.POST +import eu.kanade.tachiyomi.util.asJsoup +import eu.kanade.tachiyomi.util.parseAs +import kotlinx.serialization.Serializable +import okhttp3.Headers +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.OkHttpClient +import okhttp3.RequestBody.Companion.toRequestBody +import java.security.MessageDigest + +class GoogleDriveEpisodes(private val client: OkHttpClient, private val headers: Headers) { + // Lots of code borrowed from https://github.com/yt-dlp/yt-dlp/blob/master/yt_dlp/extractor/googledrive.py under the `GoogleDriveFolderIE` class + fun getEpisodesFromFolder(folderId: String, path: String, maxRecDepth: Int, trimNames: Boolean): List { + val episodeList = mutableListOf() + + fun traverseFolder(folderId: String, path: String, recursionDepth: Int = 0) { + if (recursionDepth == maxRecDepth) return + + val driveHeaders = headers.newBuilder() + .add("Accept", "*/*") + .add("Connection", "keep-alive") + .add("Cookie", getCookie("https://drive.google.com")) + .add("Host", "drive.google.com") + .build() + + val driveDocument = client.newCall( + GET("https://drive.google.com/drive/folders/$folderId", headers = driveHeaders), + ).execute().asJsoup() + if (driveDocument.selectFirst("title:contains(Error 404 \\(Not found\\))") != null) return + + val keyScript = driveDocument.select("script").first { script -> + KEY_REGEX.find(script.data()) != null + }.data() + val key = KEY_REGEX.find(keyScript)?.groupValues?.get(1) ?: "" + + val versionScript = driveDocument.select("script").first { script -> + KEY_REGEX.find(script.data()) != null + }.data() + val driveVersion = VERSION_REGEX.find(versionScript)?.groupValues?.get(1) ?: "" + val sapisid = client.cookieJar.loadForRequest("https://drive.google.com".toHttpUrl()).firstOrNull { + it.name == "SAPISID" || it.name == "__Secure-3PAPISID" + }?.value ?: "" + + var pageToken: String? = "" + while (pageToken != null) { + val requestUrl = "/drive/v2internal/files?openDrive=false&reason=102&syncType=0&errorRecovery=false&q=trashed%20%3D%20false%20and%20'$folderId'%20in%20parents&fields=kind%2CnextPageToken%2Citems(kind%2CmodifiedDate%2ChasVisitorPermissions%2CcontainsUnsubscribedChildren%2CmodifiedByMeDate%2ClastViewedByMeDate%2CalternateLink%2CfileSize%2Cowners(kind%2CpermissionId%2CemailAddressFromAccount%2Cdomain%2Cid)%2ClastModifyingUser(kind%2CpermissionId%2CemailAddressFromAccount%2Cid)%2CcustomerId%2CancestorHasAugmentedPermissions%2ChasThumbnail%2CthumbnailVersion%2Ctitle%2Cid%2CresourceKey%2CabuseIsAppealable%2CabuseNoticeReason%2Cshared%2CaccessRequestsCount%2CsharedWithMeDate%2CuserPermission(role)%2CexplicitlyTrashed%2CmimeType%2CquotaBytesUsed%2Ccopyable%2Csubscribed%2CfolderColor%2ChasChildFolders%2CfileExtension%2CprimarySyncParentId%2CsharingUser(kind%2CpermissionId%2CemailAddressFromAccount%2Cid)%2CflaggedForAbuse%2CfolderFeatures%2Cspaces%2CsourceAppId%2Crecency%2CrecencyReason%2Cversion%2CactionItems%2CteamDriveId%2ChasAugmentedPermissions%2CcreatedDate%2CprimaryDomainName%2CorganizationDisplayName%2CpassivelySubscribed%2CtrashingUser(kind%2CpermissionId%2CemailAddressFromAccount%2Cid)%2CtrashedDate%2Cparents(id)%2Ccapabilities(canMoveItemIntoTeamDrive%2CcanUntrash%2CcanMoveItemWithinTeamDrive%2CcanMoveItemOutOfTeamDrive%2CcanDeleteChildren%2CcanTrashChildren%2CcanRequestApproval%2CcanReadCategoryMetadata%2CcanEditCategoryMetadata%2CcanAddMyDriveParent%2CcanRemoveMyDriveParent%2CcanShareChildFiles%2CcanShareChildFolders%2CcanRead%2CcanMoveItemWithinDrive%2CcanMoveChildrenWithinDrive%2CcanAddFolderFromAnotherDrive%2CcanChangeSecurityUpdateEnabled%2CcanBlockOwner%2CcanReportSpamOrAbuse%2CcanCopy%2CcanDownload%2CcanEdit%2CcanAddChildren%2CcanDelete%2CcanRemoveChildren%2CcanShare%2CcanTrash%2CcanRename%2CcanReadTeamDrive%2CcanMoveTeamDriveItem)%2CcontentRestrictions(readOnly)%2CapprovalMetadata(approvalVersion%2CapprovalSummaries%2ChasIncomingApproval)%2CshortcutDetails(targetId%2CtargetMimeType%2CtargetLookupStatus%2CtargetFile%2CcanRequestAccessToTarget)%2CspamMetadata(markedAsSpamDate%2CinSpamView)%2Clabels(starred%2Ctrashed%2Crestricted%2Cviewed))%2CincompleteSearch&appDataFilter=NO_APP_DATA&spaces=drive&pageToken=$pageToken&maxResults=100&supportsTeamDrives=true&includeItemsFromAllDrives=true&corpora=default&orderBy=folder%2Ctitle_natural%20asc&retryCount=0&key=$key HTTP/1.1" + val body = """--$BOUNDARY + |content-type: application/http + |content-transfer-encoding: binary + | + |GET $requestUrl + |X-Goog-Drive-Client-Version: $driveVersion + |authorization: ${generateSapisidhashHeader(sapisid)} + |x-goog-authuser: 0 + | + |--$BOUNDARY--""".trimMargin("|").toRequestBody("multipart/mixed; boundary=\"$BOUNDARY\"".toMediaType()) + + val postUrl = buildString { + append("https://clients6.google.com/batch/drive/v2internal") + append("?${'$'}ct=multipart/mixed; boundary=\"$BOUNDARY\"") + append("&key=$key") + } + + val postHeaders = headers.newBuilder() + .add("Content-Type", "text/plain; charset=UTF-8") + .add("Origin", "https://drive.google.com") + .add("Cookie", getCookie("https://drive.google.com")) + .build() + + val response = client.newCall( + POST(postUrl, body = body, headers = postHeaders), + ).execute() + + val parsed = response.parseAs { + JSON_REGEX.find(it)!!.groupValues[1] + } + + if (parsed.items == null) throw Exception("Failed to load items, please log in to google drive through webview") + parsed.items.forEachIndexed { index, it -> + if (it.mimeType.startsWith("video")) { + val size = it.fileSize?.toLongOrNull()?.let { formatBytes(it) } + val pathName = path.trimInfo() + + episodeList.add( + SEpisode.create().apply { + name = if (trimNames) it.title.trimInfo() else it.title + this.url = "https://drive.google.com/uc?id=${it.id}" + episode_number = ITEM_NUMBER_REGEX.find(it.title.trimInfo())?.groupValues?.get(1)?.toFloatOrNull() ?: index.toFloat() + date_upload = -1L + scanlator = "$size • /$pathName" + }, + ) + } + if (it.mimeType.endsWith(".folder")) { + traverseFolder(it.id, "$path/${it.title}", recursionDepth + 1) + } + } + + pageToken = parsed.nextPageToken + } + } + + traverseFolder(folderId, path) + + return episodeList + } + + // https://github.com/yt-dlp/yt-dlp/blob/8f0be90ecb3b8d862397177bb226f17b245ef933/yt_dlp/extractor/youtube.py#L573 + private fun generateSapisidhashHeader(SAPISID: String, origin: String = "https://drive.google.com"): String { + val timeNow = System.currentTimeMillis() / 1000 + // SAPISIDHASH algorithm from https://stackoverflow.com/a/32065323 + val sapisidhash = MessageDigest + .getInstance("SHA-1") + .digest("$timeNow $SAPISID $origin".toByteArray()) + .joinToString("") { "%02x".format(it) } + return "SAPISIDHASH ${timeNow}_$sapisidhash" + } + + @Serializable + data class GDrivePostResponse( + val nextPageToken: String? = null, + val items: List? = null, + ) { + @Serializable + data class ResponseItem( + val id: String, + val title: String, + val mimeType: String, + val fileSize: String? = null, + ) + } + + private fun String.trimInfo(): String { + var newString = this.replaceFirst("""^\[\w+\] ?""".toRegex(), "") + val regex = """( ?\[[\s\w-]+\]| ?\([\s\w-]+\))(\.mkv|\.mp4|\.avi)?${'$'}""".toRegex() + + while (regex.containsMatchIn(newString)) { + newString = regex.replace(newString) { matchResult -> + matchResult.groups[2]?.value ?: "" + } + } + + return newString.trim() + } + + private fun formatBytes(bytes: Long): String = when { + bytes >= 1_000_000_000 -> "%.2f GB".format(bytes / 1_000_000_000.0) + bytes >= 1_000_000 -> "%.2f MB".format(bytes / 1_000_000.0) + bytes >= 1_000 -> "%.2f KB".format(bytes / 1_000.0) + bytes > 1 -> "$bytes bytes" + bytes == 1L -> "$bytes byte" + else -> "" + } + + private fun getCookie(url: String): String { + val cookieList = client.cookieJar.loadForRequest(url.toHttpUrl()) + return if (cookieList.isNotEmpty()) { + cookieList.joinToString("; ") { "${it.name}=${it.value}" } + } else { + "" + } + } + + companion object { + private val ITEM_NUMBER_REGEX = """ - (?:S\d+E)?(\d+)""".toRegex() + private val KEY_REGEX = """"(\w{39})"""".toRegex() + private val VERSION_REGEX = """"([^"]+web-frontend[^"]+)"""".toRegex() + private val JSON_REGEX = """(?:)\s*(\{(.+)\})\s*(?:)""".toRegex(RegexOption.DOT_MATCHES_ALL) + private const val BOUNDARY = "=====vc17a3rwnndj=====" + } +} diff --git a/src/en/animesakura/README.md b/src/en/animesakura/README.md new file mode 100644 index 0000000000..4e7ea87d97 --- /dev/null +++ b/src/en/animesakura/README.md @@ -0,0 +1,3 @@ +# DISCLAIMER + +This extension requires you to log in through Google and relies heavily on scraping the website of Google Drive, which may be against their terms of service. Use at your own risk. diff --git a/src/en/animesakura/build.gradle b/src/en/animesakura/build.gradle new file mode 100644 index 0000000000..da638d6a5e --- /dev/null +++ b/src/en/animesakura/build.gradle @@ -0,0 +1,12 @@ +ext { + extName = 'Anime Sakura' + extClass = '.AnimeSakura' + extVersionCode = 1 +} + +apply from: "$rootDir/common.gradle" + +dependencies { + implementation(project(':lib:googledrive-extractor')) + implementation(project(':lib:googledrive-episodes')) +} \ No newline at end of file diff --git a/src/en/animesakura/res/mipmap-hdpi/ic_launcher.png b/src/en/animesakura/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..9e53b7a9907c4ce1ad724a53ffaa541ea764137c GIT binary patch literal 4297 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 literal 0 HcmV?d00001 diff --git a/src/en/animesakura/res/mipmap-xhdpi/ic_launcher.png b/src/en/animesakura/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..5d3bad6082754f8638ef2cd61411c4f427915cd7 GIT binary patch literal 6045 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 X_hcXU7+;L400000NkvXXu0mjfi2ai{ literal 0 HcmV?d00001 diff --git a/src/en/animesakura/res/mipmap-xxhdpi/ic_launcher.png b/src/en/animesakura/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..95caa92766228ab63540fe4d1a46117dcefa54df GIT binary patch literal 11402 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 literal 0 HcmV?d00001 diff --git a/src/en/animesakura/src/eu/kanade/tachiyomi/animeextension/en/animesakura/AnimeSakura.kt b/src/en/animesakura/src/eu/kanade/tachiyomi/animeextension/en/animesakura/AnimeSakura.kt new file mode 100644 index 0000000000..552532bcda --- /dev/null +++ b/src/en/animesakura/src/eu/kanade/tachiyomi/animeextension/en/animesakura/AnimeSakura.kt @@ -0,0 +1,477 @@ +package eu.kanade.tachiyomi.animeextension.en.animesakura + +import android.app.Application +import android.content.SharedPreferences +import androidx.preference.EditTextPreference +import androidx.preference.PreferenceScreen +import androidx.preference.SwitchPreferenceCompat +import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource +import eu.kanade.tachiyomi.animesource.model.AnimeFilter +import eu.kanade.tachiyomi.animesource.model.AnimeFilterList +import eu.kanade.tachiyomi.animesource.model.AnimesPage +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.AnimeHttpSource +import eu.kanade.tachiyomi.lib.googledriveepisodes.GoogleDriveEpisodes +import eu.kanade.tachiyomi.lib.googledriveextractor.GoogleDriveExtractor +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.POST +import eu.kanade.tachiyomi.util.asJsoup +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json +import okhttp3.FormBody +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.Request +import okhttp3.Response +import org.jsoup.Jsoup +import org.jsoup.nodes.Element +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get +import uy.kohesive.injekt.injectLazy + +class AnimeSakura : ConfigurableAnimeSource, AnimeHttpSource() { + + override val name = "Anime Sakura" + + override val baseUrl = "https://animesakura.co" + + override val lang = "en" + + // Used for loading anime + private var infoQuery = "" + private var max = "" + private var latestPost = "" + private var layout = "" + private var settings = "" + private var currentReferer = "" + + override val supportsLatest = true + + private val json: Json by injectLazy() + + private val preferences: SharedPreferences by lazy { + Injekt.get().getSharedPreferences("source_$id", 0x0000) + } + + // ============================== Popular =============================== + + override fun popularAnimeRequest(page: Int): Request { + val formBody = FormBody.Builder().apply { + add("action", "tie_blocks_load_more") + add("block[order]", "views") + add("block[asc_or_desc]", "DESC") + add("block[id][]", "3") + add("block[number]", "24") + addExtra(page) + }.build() + + val formHeaders = headersBuilder().apply { + add("Accept", "*/*") + add("Host", baseUrl.toHttpUrl().host) + add("Origin", baseUrl) + add("Referer", "$baseUrl/anime-series/") + add("X-Requested-With", "XMLHttpRequest") + }.build() + + return POST("$baseUrl/wp-admin/admin-ajax.php", formHeaders, formBody) + } + + override fun popularAnimeParse(response: Response): AnimesPage { + val body = response.body.string() + val rawParsed = json.decodeFromString(body) + val parsed = json.decodeFromString(rawParsed) + val document = Jsoup.parseBodyFragment(parsed.code) + + val animeList = document.select("li.post-item") + .map(::popularAnimeFromElement) + + return AnimesPage(animeList, !parsed.hide_next) + } + + private fun popularAnimeFromElement(element: Element): SAnime = SAnime.create().apply { + setUrlWithoutDomain(element.selectFirst("a")!!.attr("href")) + thumbnail_url = element.selectFirst("img[src]")?.attr("src") ?: "" + title = element.selectFirst("h2.post-title")!!.text().substringBefore(" Episode") + } + + // =============================== Latest =============================== + + override fun latestUpdatesRequest(page: Int): Request { + val formBody = FormBody.Builder().apply { + add("action", "tie_blocks_load_more") + add("block[asc_or_desc]", "DESC") + add("block[id][]", "14") + add("block[number]", "10") + addExtra(page) + }.build() + + val formHeaders = headersBuilder().apply { + add("Accept", "*/*") + add("Host", baseUrl.toHttpUrl().host) + add("Origin", baseUrl) + add("Referer", "$baseUrl/ongoing-anime/") + add("X-Requested-With", "XMLHttpRequest") + }.build() + + return POST("$baseUrl/wp-admin/admin-ajax.php", formHeaders, formBody) + } + + override fun latestUpdatesParse(response: Response): AnimesPage = popularAnimeParse(response) + + // =============================== Search =============================== + + override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request { + val subPage = filters.filterIsInstance().first().toUriPart() + val genreFilter = filters.filterIsInstance().first().toUriPart() + + if (query.isEmpty() && subPage.isNotEmpty()) { + val formBody = FormBody.Builder().apply { + add("action", "tie_blocks_load_more") + add("block[asc_or_desc]", "DESC") + add("block[id][]", "35") + add("block[number]", "15") + addExtra(page) + }.build() + val formHeaders = headersBuilder().apply { + add("Accept", "*/*") + add("Host", baseUrl.toHttpUrl().host) + add("Origin", baseUrl) + add("Referer", "$baseUrl/anime-movies/") + add("X-Requested-With", "XMLHttpRequest") + }.build() + return POST("$baseUrl/wp-admin/admin-ajax.php", formHeaders, formBody) + } + + return if (page == 1) { + infoQuery = "" + max = "" + latestPost = "" + layout = "" + settings = "" + currentReferer = "" + + val docHeaders = headersBuilder().apply { + add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8") + }.build() + + if (query.isNotEmpty()) { + val url = baseUrl.toHttpUrl().newBuilder().apply { + addPathSegment("") + addQueryParameter("s", query) + }.build() + currentReferer = url.toString() + GET(url, docHeaders) + } else if (genreFilter.isNotEmpty()) { + currentReferer = "$baseUrl/category/$genreFilter" + GET("$baseUrl/category/$genreFilter", docHeaders) + } else { + currentReferer = "$baseUrl/?s=" + GET("$baseUrl/?s=", docHeaders) + } + } else { + val formBody = FormBody.Builder().apply { + add("action", "tie_archives_load_more") + add("query", infoQuery) + add("max", max) + add("page", page.toString()) + add("latest_post", latestPost) + add("layout", layout) + add("settings", settings) + }.build() + val formHeaders = headersBuilder().apply { + add("Accept", "*/*") + add("Host", baseUrl.toHttpUrl().host) + add("Origin", baseUrl) + add("Referer", currentReferer) + add("X-Requested-With", "XMLHttpRequest") + }.build() + POST("$baseUrl/wp-admin/admin-ajax.php", formHeaders, formBody) + } + } + + override fun searchAnimeParse(response: Response): AnimesPage { + return if (response.request.url.toString().contains("admin-ajax")) { + popularAnimeParse(response) + } else { + val document = response.asJsoup() + val animeList = document.select("ul#posts-container > li.post-item").map { element -> + SAnime.create().apply { + setUrlWithoutDomain(element.selectFirst("a")!!.attr("abs:href")) + thumbnail_url = element.selectFirst("img")!!.imgAttr() + title = element.selectFirst("h2.post-title")!!.text().substringBefore(" Episode") + } + } + val hasNextPage = document.selectFirst("div.pages-nav > a[data-text=load more]") != null + if (hasNextPage) { + val container = document.selectFirst("ul#posts-container")!! + val pagesNav = document.selectFirst("div.pages-nav > a")!! + layout = container.attr("data-layout") + infoQuery = pagesNav.attr("data-query") + max = pagesNav.attr("data-max") + latestPost = pagesNav.attr("data-latest") + settings = container.attr("data-settings") + } + + AnimesPage(animeList, hasNextPage) + } + } + + // ============================== Filters =============================== + + override fun getFilterList(): AnimeFilterList = AnimeFilterList( + AnimeFilter.Header("Text search ignores filters"), + SubPageFilter(), + GenreFilter(), + ) + + private class SubPageFilter : UriPartFilter( + "Sub-page", + arrayOf( + Pair("", ""), + Pair("Action", "action"), + Pair("Adventure", "adventure"), + Pair("Romance", "romance"), + Pair("Ecchi", "ecchi"), + Pair("School", "school"), + Pair("Harem", "harem"), + Pair("Sci-fi", "sci-fi"), + Pair("Comedy", "comedy"), + Pair("Drama", "drama"), + Pair("Mystery", "mystery"), + Pair("Military", "military"), + Pair("Fantasy", "fantasy"), + Pair("Isekai", "isekai"), + Pair("Psychological", "psychological"), + Pair("Shoujo", "shoujo"), + Pair("Slice of Life", "slice-of-life"), + Pair("Shounen", "shounen"), + Pair("Sports", "sports"), + Pair("Supernatural", "supernatural-2"), + ), + ) + + private open class UriPartFilter(displayName: String, val vals: Array>) : + AnimeFilter.Select(displayName, vals.map { it.first }.toTypedArray()) { + fun toUriPart() = vals[state].second + } + + // =========================== Anime Details ============================ + + override fun animeDetailsParse(response: Response): SAnime { + val document = response.asJsoup() + val moreInfo = Jsoup.parseBodyFragment( + document.selectFirst("div.toggle-content li p")?.html()?.replace("
", "br2n") ?: "", + ).text().replace("br2n", "\n") + val realDesc = document.select("div.stream-item ~ p").joinToString("\n\n") { it.text() } + + return SAnime.create().apply { + status = document.selectFirst("div.toggle-content > ul > li:contains(Status)")?.let { + parseStatus(it.text()) + } ?: SAnime.UNKNOWN + description = realDesc + "\n\n$moreInfo" + genre = document.selectFirst("div.toggle-content > ul > li:contains(Genres)")?.let { + it.text().substringAfter("Genres").substringAfter("⋩ ").substringBefore(" ❀") + } + author = document.selectFirst("div.toggle-content > ul > li:contains(Studios)")?.let { + it.text().substringAfter("Studios").substringAfter("⋩ ").substringBefore("⁃") + } + } + } + + // ============================== Episodes ============================== + + private val googleDriveEpisodes by lazy { GoogleDriveEpisodes(client, headers) } + private val indexExtractor by lazy { DriveIndexExtractor(client, headers) } + + override fun episodeListParse(response: Response): List { + val document = response.asJsoup() + val episodeList = mutableListOf() + val trimNames = preferences.trimEpisodeName + val blackListed = preferences.blKeywords + + document.select("div.toggle:has(> div.toggle-content > a[href*=drive.google.com]),div.toggle:has(a.shortc-button[href*=drive.google.com])").distinctBy { t -> + getVideoPathsFromElement(t) + }.forEach { season -> + season.select("a[href*=drive.google.com]").distinctBy { it.text() }.forEach season@{ + if (blackListed.any { t -> it.text().contains(t, true) }) return@season + val folderId = it.selectFirst("a[href*=drive.google.com]")!!.attr("abs:href").toHttpUrl().pathSegments[2] + episodeList.addAll( + googleDriveEpisodes.getEpisodesFromFolder(folderId, "${getVideoPathsFromElement(season)} ${it.text()}", 2, trimNames), + ) + } + } + + document.select("div.wp-block-buttons > div.wp-block-button a[href*=drive.google.com]").distinctBy { + it.text() + }.forEach { + if (blackListed.any { t -> it.text().contains(t, true) }) return@forEach + val folderId = it.attr("abs:href").toHttpUrl().pathSegments[2] + episodeList.addAll( + googleDriveEpisodes.getEpisodesFromFolder(folderId, it.text(), 2, trimNames), + ) + } + + document.select("div.toggle:has(> div.toggle-content > a[href*=workers.dev]),div.toggle:has(a.shortc-button[href*=workers.dev])").distinctBy { t -> + getVideoPathsFromElement(t) + }.forEach { season -> + season.select("a[href*=workers.dev]").distinctBy { it.text() }.forEach season@{ + if (blackListed.any { t -> it.text().contains(t, true) }) return@season + runCatching { + episodeList.addAll( + indexExtractor.getEpisodesFromIndex(it.attr("abs:href"), "${getVideoPathsFromElement(season)} ${it.text()}", trimNames), + ) + } + } + } + + return episodeList.reversed() + } + + private fun getVideoPathsFromElement(element: Element): String { + return element.selectFirst("h3")!!.text() + .substringBefore("480p").substringBefore("720p").substringBefore("1080p") + .replace("Download - ", "", true) + .replace("Download The Anime From Worker ?", "", true) + .replace("Download The Anime From Drive ", "", true) + .trim() + } + + // ============================ Video Links ============================= + + private val googleDriveExtractor by lazy { GoogleDriveExtractor(client, headers) } + + override suspend fun getVideoList(episode: SEpisode): List