From 93c948bb8e8f14630eb0a66d38ffbfabc75d81b0 Mon Sep 17 00:00:00 2001 From: hexated Date: Sun, 21 May 2023 20:20:19 +0700 Subject: [PATCH] sora: fixed Crunchyroll and Zoro --- .github/workflows/build.yml | 4 + SoraStream/build.gradle.kts | 2 + .../main/kotlin/com/hexated/SoraExtractor.kt | 520 ++++++------------ .../src/main/kotlin/com/hexated/SoraStream.kt | 18 +- .../main/kotlin/com/hexated/SoraStreamLite.kt | 2 +- .../src/main/kotlin/com/hexated/SoraUtils.kt | 213 +++---- 6 files changed, 298 insertions(+), 461 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e558bd18..0f3a88fd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -46,12 +46,16 @@ jobs: SORAHE: ${{ secrets.SORAHE }} SORAXA: ${{ secrets.SORAXA }} SORATED: ${{ secrets.SORATED }} + CRUNCHYROLL_BASIC_TOKEN: ${{ secrets.CRUNCHYROLL_BASIC_TOKEN }} + CRUNCHYROLL_REFRESH_TOKEN: ${{ secrets.CRUNCHYROLL_REFRESH_TOKEN }} run: | cd $GITHUB_WORKSPACE/src echo SORA_API=$SORA_API >> local.properties echo SORAHE=$SORAHE >> local.properties echo SORAXA=$SORAXA >> local.properties echo SORATED=$SORATED >> local.properties + echo CRUNCHYROLL_BASIC_TOKEN=$CRUNCHYROLL_BASIC_TOKEN >> local.properties + echo CRUNCHYROLL_REFRESH_TOKEN=$CRUNCHYROLL_REFRESH_TOKEN >> local.properties - name: Build Plugins run: | diff --git a/SoraStream/build.gradle.kts b/SoraStream/build.gradle.kts index 9cc8a87c..d5bebe72 100644 --- a/SoraStream/build.gradle.kts +++ b/SoraStream/build.gradle.kts @@ -12,6 +12,8 @@ android { buildConfigField("String", "SORAHE", "\"${properties.getProperty("SORAHE")}\"") buildConfigField("String", "SORAXA", "\"${properties.getProperty("SORAXA")}\"") buildConfigField("String", "SORATED", "\"${properties.getProperty("SORATED")}\"") + buildConfigField("String", "CRUNCHYROLL_BASIC_TOKEN", "\"${properties.getProperty("CRUNCHYROLL_BASIC_TOKEN")}\"") + buildConfigField("String", "CRUNCHYROLL_REFRESH_TOKEN", "\"${properties.getProperty("CRUNCHYROLL_REFRESH_TOKEN")}\"") } diff --git a/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt b/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt index d3aa8a53..db166185 100644 --- a/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt +++ b/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt @@ -45,7 +45,9 @@ object SoraExtractor : SoraStream() { val link = source.link ?: return@let if (link.contains("rabbitstream")) { extractRabbitStream( + "Vidcloud", link, + "$twoEmbedAPI/", subtitleCallback, callback, false, @@ -188,21 +190,6 @@ object SoraExtractor : SoraStream() { } } - suspend fun invokeDatabaseGdrive( - imdbId: String? = null, - season: Int? = null, - episode: Int? = null, - subtitleCallback: (SubtitleFile) -> Unit, - callback: (ExtractorLink) -> Unit - ) { - val url = if (season == null) { - "$databaseGdriveAPI/player.php?imdb=$imdbId" - } else { - "$databaseGdriveAPI/player.php?type=series&imdb=$imdbId&season=$season&episode=$episode" - } - loadExtractor(url, databaseGdriveAPI, subtitleCallback, callback) - } - suspend fun invokeHDMovieBox( title: String? = null, season: Int? = null, @@ -327,14 +314,13 @@ object SoraExtractor : SoraStream() { }, { val iv = "9225679083961858" val secretKey = "25742532592138496744665879883281" - val secretDecryptKey = secretKey GogoHelper.extractVidstream( iframe.url, "Vidstream", callback, iv, secretKey, - secretDecryptKey, + secretKey, isUsingAdaptiveKeys = false, isUsingAdaptiveData = true, iframeDocument = iframeDoc @@ -496,13 +482,12 @@ object SoraExtractor : SoraStream() { } else { "${filmxyAPI}/tv/$imdbId" } - val filmxyCookies = - getFilmxyCookies(imdbId, season) ?: throw ErrorLoadingException("No Cookies Found") + val filmxyCookies = getFilmxyCookies(imdbId, season) val cookiesDoc = mapOf( "G_ENABLED_IDPS" to "google", - "wordpress_logged_in_8bf9d5433ac88cc9a3a396d6b154cd01" to "${filmxyCookies.wLog}", - "PHPSESSID" to "${filmxyCookies.phpsessid}" + "wordpress_logged_in_8bf9d5433ac88cc9a3a396d6b154cd01" to (filmxyCookies.wLog ?: return), + "PHPSESSID" to (filmxyCookies.phpsessid ?: return) ) val doc = session.get(url, cookies = cookiesDoc).document @@ -868,85 +853,10 @@ object SoraExtractor : SoraStream() { { invokeBiliBili(aniId, episode, subtitleCallback, callback) }, -// { -// if (season != null) invokeAllanime(aniId, title, jpTitle, episode, callback) -// } - ) - } - - private suspend fun invokeAllanime( - aniId: String? = null, - title: String? = null, - jpTitle: String? = null, - episode: Int? = null, - callback: (ExtractorLink) -> Unit - ) { - val aniDetail = - app.get("$consumetAnilistAPI/info/$aniId").parsedSafe() ?: return - val edges = app.get( - allanimeQueries( - """{"search":{"query":"$title","allowAdult":false,"allowUnknown":false},"limit":26,"page":1,"translationType":"sub","countryOrigin":"ALL"}""", - allanimeSearchQuery - ) - ) - .parsedSafe()?.data?.shows?.edges - val id = edges?.let { edge -> - if (edges.size == 1) { - edge.firstOrNull() - } else { - edge.find { - (it.thumbnail == aniDetail.cover || it.thumbnail == aniDetail.image) || ( - (it.name.equals( - aniDetail.title?.romaji, - true - ) || it.name.equals( - jpTitle, - true - ) || it.englishName.equals(aniDetail.title?.english, true)) - && it.airedStart?.year == aniDetail.releaseDate) - } ?: edge.find { - it.name.equals( - aniDetail.title?.romaji, - true - ) || it.name.equals( - jpTitle, - true - ) || it.englishName.equals( - aniDetail.title?.english, - true - ) || it.englishName.equals(title, true) - } + { + if (season != null) invokeCrunchyroll(aniId, epsTitle, season, episode, subtitleCallback, callback) } - }?._id ?: return - - listOf( - "sub", - "dub" - ).apmap { tl -> - val server = app.get( - allanimeQueries( - """{"showId":"$id","translationType":"$tl","episodeString":"$episode"}""", - allanimeServerQuery - ) - ) - .parsedSafe()?.data?.episode?.sourceUrls?.find { it.sourceName == "Ac" } - val serverUrl = fixUrl( - server?.sourceUrl?.replace("/clock", "/clock.json") ?: return@apmap, - "https://blog.allanime.pro" - ) - app.get(serverUrl) - .parsedSafe()?.links?.filter { it.resolutionStr == "RAW" && it.hls == true } - ?.forEach { source -> - val translation = if (tl == "sub") "Raw" else "English Dub" - M3u8Helper.generateM3u8( - "Vrv [$translation]", - source.link ?: return@apmap, - "https://static.crunchyroll.com/", - ).forEach(callback) - } - - } - + ) } private suspend fun invokeBiliBili( @@ -999,41 +909,47 @@ object SoraExtractor : SoraStream() { subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit ) { - val episodeId = app.get("$consumetAnilistAPI/info/$aniId?provider=zoro") - .parsedSafe()?.episodes?.find { - it.number == (episode ?: 1) - }?.id?.substringBeforeLast("$") ?: return + val animeId = + app.get("https://raw.githubusercontent.com/MALSync/MAL-Sync-Backup/master/data/anilist/anime/$aniId.json") + .parsedSafe()?.pages?.zoro?.keys?.firstOrNull() - listOf( - "$episodeId\$sub" to "Raw", - "$episodeId\$dub" to "English Dub", - ).apmap { (id, type) -> - val sources = app.get("$consumetZoroAPI/watch?episodeId=$id") - .parsedSafe() ?: return@apmap null + val episodeId = app.get("$zoroAPI/ajax/v2/episode/list/${animeId ?: return}") + .parsedSafe()?.html?.let { + Jsoup.parse(it) + }?.select("div.ss-list a")?.find { it.attr("data-number") == "$episode" } + ?.attr("data-id") - sources.sources?.map sources@{ - callback.invoke( - ExtractorLink( - "Zoro [$type]", - "Zoro [$type]", - it.url ?: return@sources null, - "", - getQualityFromName(it.quality), - it.isM3U8 ?: true - ) + val servers = app.get("$zoroAPI/ajax/v2/episode/servers?episodeId=${episodeId ?: return}") + .parsedSafe()?.html?.let { Jsoup.parse(it) } + ?.select("div.item.server-item")?.map { + Triple( + it.text(), + it.attr("data-id"), + it.attr("data-type"), ) } - sources.subtitles?.map subtitles@{ - subtitleCallback.invoke( - SubtitleFile( - it.lang ?: "", - it.url ?: return@subtitles null - ) - ) + servers?.apmap { server -> + val iframe = app.get("$zoroAPI/ajax/v2/episode/sources?id=${server.second ?: return@apmap}") + .parsedSafe()?.link ?: return@apmap + val audio = if(server.third == "sub") "Raw" else "English Dub" + if(server.first == "Vidstreaming" || server.first == "Vidcloud") { + extractRabbitStream( + "${server.first} [$audio]", + iframe, + "$zoroAPI/", + subtitleCallback, + callback, + false, + decryptKey = RabbitStream.getZoroKey() + ) { it } + } else { + loadExtractor(iframe,"$zoroAPI/", subtitleCallback, callback) } + } + } private suspend fun invokeAnimeKaizoku( @@ -1551,52 +1467,78 @@ object SoraExtractor : SoraStream() { } suspend fun invokeCrunchyroll( - title: String? = null, + aniId: String? = null, epsTitle: String? = null, season: Int? = null, episode: Int? = null, subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit ) { - val id = searchCrunchyrollAnimeId(title ?: return) ?: return - val detail = - app.get("$consumetCrunchyrollAPI/info/$id?fetchAllSeasons=true", timeout = 600L).text - val epsId = tryParseJson(detail)?.findCrunchyrollId( - season, - episode, - epsTitle + val id = getCrunchyrollId(aniId) ?: return + val audioLocal = listOf( + "ja-JP", + "en-US", + "zh-CN", ) + val headers = getCrunchyrollToken() + val seasonIdData = app.get("$crunchyrollAPI/content/v2/cms/series/$id/seasons", headers = headers) + .parsedSafe()?.data?.let { s -> + if (s.size == 1) { + s.firstOrNull() + } else { + s.find { + when (epsTitle) { + "One Piece" -> it.season_number == 13 + "Hunter x Hunter" -> it.season_number == 5 + else -> it.season_number == season + } + } + } + } + val seasonId = seasonIdData?.versions?.filter { it.audio_locale in audioLocal } + ?.map { it.guid to it.audio_locale } - epsId?.apmap { - delay(2000) - val json = + seasonId?.apmap { (sId, audioL) -> + val streamsLink = app.get( - "$consumetCrunchyrollAPI/watch/${it?.first?.id ?: return@apmap null}", - timeout = 600L - ) - .parsedSafe() - - json?.sources?.map source@{ source -> - callback.invoke( - ExtractorLink( - "Crunchyroll", - "Crunchyroll [${it.second ?: ""}]", - source.url ?: return@source null, - "https://static.crunchyroll.com/", - source.quality?.removeSuffix("p")?.toIntOrNull() ?: return@source null, + "$crunchyrollAPI/content/v2/cms/seasons/${sId ?: return@apmap}/episodes", + headers = headers + ).parsedSafe()?.data?.find { + it.title.equals(epsTitle, true) || it.slug_title.equals( + epsTitle.createSlug(), true - ) - ) + ) || it.episode_number == episode + }?.streams_link + val sources = + app.get(fixUrl(streamsLink ?: return@apmap, crunchyrollAPI), headers = headers) + .parsedSafe() + + listOf( + "adaptive_hls", + "vo_adaptive_hls" + ).map { hls -> + val name = if (hls == "adaptive_hls") "Crunchyroll" else "Vrv" + val audio = if (audioL == "en-US") "English Dub" else "Raw" + val source = sources?.data?.firstOrNull()?.let { + if (hls == "adaptive_hls") it.adaptive_hls else it.vo_adaptive_hls + } + M3u8Helper.generateM3u8( + "$name [$audio]", + source?.get("")?.get("url") ?: return@map, + "https://static.crunchyroll.com/" + ).forEach(callback) } - json?.subtitles?.map subtitle@{ sub -> + sources?.meta?.subtitles?.map { sub -> subtitleCallback.invoke( SubtitleFile( - "${fixCrunchyrollLang(sub.lang ?: return@subtitle null) ?: sub.lang} [ass]", - sub.url ?: return@subtitle null + "${fixCrunchyrollLang(sub.key) ?: sub.key} [ass]", + sub.value["url"] ?: return@map null ) ) } + + } } @@ -1900,6 +1842,7 @@ object SoraExtractor : SoraStream() { imdbId: String? = null, season: Int? = null, episode: Int? = null, + subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit, ) { val url = if (season == null) { @@ -1927,6 +1870,9 @@ object SoraExtractor : SoraStream() { it.first.contains("/nflim") -> { invokeSmashyNflim(it.second, it.first, callback) } + it.first.contains("/rip") -> { + invokeSmashyRip(it.second, it.first, subtitleCallback, callback) + } else -> return@apmap } } @@ -2121,46 +2067,6 @@ object SoraExtractor : SoraStream() { ) } - suspend fun invokePapaonMovies1( - apiUrl: String, - api: String, - title: String? = null, - year: Int? = null, - season: Int? = null, - episode: Int? = null, - callback: (ExtractorLink) -> Unit, - ) { - invokeIndex( - apiUrl, - api, - title, - year, - season, - episode, - callback, - ) - } - - suspend fun invokePapaonMovies2( - apiUrl: String, - api: String, - title: String? = null, - year: Int? = null, - season: Int? = null, - episode: Int? = null, - callback: (ExtractorLink) -> Unit, - ) { - invokeIndex( - apiUrl, - api, - title, - year, - season, - episode, - callback, - ) - } - suspend fun invokeJmdkhMovies( apiUrl: String, api: String, @@ -2700,7 +2606,7 @@ object SoraExtractor : SoraStream() { season: Int? = null, callback: (ExtractorLink) -> Unit, ) { - val monsterMainUrl = "https://freedoze.monster" + val monsterMainUrl = "https://mobirs.monster" val playSlug = if (season == null) { "movies/play/$urlSlug" } else { @@ -2713,7 +2619,7 @@ object SoraExtractor : SoraStream() { val res = app.get(url).document val script = res.selectFirst("script:containsData(window['show_storage'])")?.data() val hash = Regex("hash:\\s*['\"](\\S+)['\"],").find(script ?: return)?.groupValues?.get(1) - val expires = Regex("expires:\\s*(\\d+),").find(script ?: return)?.groupValues?.get(1) + val expires = Regex("expires:\\s*(\\d+),").find(script)?.groupValues?.get(1) val videoUrl = if (season == null) { "$monsterMainUrl/api/v1/security/movie-access?id_movie=$episodeId&hash=$hash&expires=$expires" @@ -3051,71 +2957,6 @@ data class Load( @JsonProperty("data") val data: MediaDetail? = null, ) -data class ConsumetHeaders( - @JsonProperty("Referer") val referer: String? = null, -) - -data class ConsumetSubtitles( - @JsonProperty("url") val url: String? = null, - @JsonProperty("lang") val lang: String? = null, -) - -data class ConsumetSources( - @JsonProperty("url") val url: String? = null, - @JsonProperty("quality") val quality: String? = null, - @JsonProperty("isM3U8") val isM3U8: Boolean? = null, -) - -data class ConsumetSourcesResponse( - @JsonProperty("headers") val headers: ConsumetHeaders? = null, - @JsonProperty("sources") val sources: ArrayList? = arrayListOf(), - @JsonProperty("subtitles") val subtitles: ArrayList? = arrayListOf(), -) - -data class ConsumetEpisodes( - @JsonProperty("id") val id: String? = null, - @JsonProperty("type") val type: String? = null, - @JsonProperty("title") val title: String? = null, - @JsonProperty("number") val number: Int? = null, - @JsonProperty("season") val season: Int? = null, -) - -data class ConsumetTitle( - @JsonProperty("romaji") val romaji: String? = null, - @JsonProperty("english") val english: String? = null -) - -data class ConsumetDetails( - @JsonProperty("episodes") val episodes: ArrayList? = arrayListOf(), - @JsonProperty("image") val image: String? = null, - @JsonProperty("cover") val cover: String? = null, - @JsonProperty("title") val title: ConsumetTitle? = null, - @JsonProperty("releaseDate") val releaseDate: Int? = null -) - -data class CrunchyrollEpisodes( - @JsonProperty("id") val id: String? = null, - @JsonProperty("title") val title: String? = null, - @JsonProperty("episode_number") val episode_number: Int? = null, - @JsonProperty("season_number") val season_number: Int? = null, -) - -data class CrunchyrollDetails( - @JsonProperty("episodes") val episodes: HashMap>? = hashMapOf(), -) - -data class ConsumetResults( - @JsonProperty("id") val id: String? = null, - @JsonProperty("title") val title: String? = null, - @JsonProperty("seasons") val seasons: Int? = null, - @JsonProperty("releaseDate") val releaseDate: String? = null, - @JsonProperty("type") val type: String? = null, -) - -data class ConsumetSearchResponse( - @JsonProperty("results") val results: ArrayList? = arrayListOf(), -) - data class KisskhSources( @JsonProperty("Video") val video: String?, @JsonProperty("ThirdParty") val thirdParty: String?, @@ -3246,23 +3087,6 @@ data class SorastreamVideos( @JsonProperty("currentDefinition") val currentDefinition: String? = null, ) -data class SapphireSubtitles( - @JsonProperty("language") val language: String? = null, - @JsonProperty("url") val url: String? = null, -) - -data class SapphireStreams( - @JsonProperty("format") val format: String? = null, - @JsonProperty("audio_lang") val audio_lang: String? = null, - @JsonProperty("hardsub_lang") val hardsub_lang: String? = null, - @JsonProperty("url") val url: String? = null, -) - -data class SapphireSources( - @JsonProperty("streams") val streams: ArrayList? = arrayListOf(), - @JsonProperty("subtitles") val subtitles: ArrayList? = arrayListOf(), -) - data class BiliBiliEpisodes( @JsonProperty("id") val id: Int? = null, @JsonProperty("sourceId") val sourceId: String? = null, @@ -3325,74 +3149,6 @@ data class PutlockerResponses( @JsonProperty("backupLink") val backupLink: String? = null, ) -data class AllanimeStreams( - @JsonProperty("format") val format: String? = null, - @JsonProperty("url") val url: String? = null, - @JsonProperty("audio_lang") val audio_lang: String? = null, - @JsonProperty("hardsub_lang") val hardsub_lang: String? = null, -) - -data class AllanimePortData( - @JsonProperty("streams") val streams: ArrayList? = arrayListOf(), -) - -data class AllanimeLink( - @JsonProperty("portData") val portData: AllanimePortData? = null, - @JsonProperty("resolutionStr") val resolutionStr: String? = null, - @JsonProperty("src") val src: String? = null, - @JsonProperty("link") val link: String? = null, - @JsonProperty("hls") val hls: Boolean? = null, -) - -data class AllanimeLinks( - @JsonProperty("links") val links: ArrayList? = arrayListOf(), -) - -data class AllanimeSourceUrls( - @JsonProperty("sourceUrl") val sourceUrl: String? = null, - @JsonProperty("sourceName") val sourceName: String? = null, -) - -data class AllanimeEpisode( - @JsonProperty("sourceUrls") val sourceUrls: ArrayList? = arrayListOf(), -) - -data class AllanimeAvailableEpisodesDetail( - @JsonProperty("sub") val sub: ArrayList? = arrayListOf(), - @JsonProperty("dub") val dub: ArrayList? = arrayListOf(), -) - -data class AllanimeDetailShow( - @JsonProperty("availableEpisodesDetail") val availableEpisodesDetail: AllanimeAvailableEpisodesDetail? = null, -) - -data class AllanimeAiredStart( - @JsonProperty("year") val year: Int? = null, -) - -data class AllanimeEdges( - @JsonProperty("_id") val _id: String? = null, - @JsonProperty("name") val name: String? = null, - @JsonProperty("englishName") val englishName: String? = null, - @JsonProperty("thumbnail") val thumbnail: String? = null, - @JsonProperty("type") val type: String? = null, - @JsonProperty("airedStart") val airedStart: AllanimeAiredStart? = null, -) - -data class AllanimeShows( - @JsonProperty("edges") val edges: ArrayList? = arrayListOf(), -) - -data class AllanimeData( - @JsonProperty("shows") val shows: AllanimeShows? = null, - @JsonProperty("show") val show: AllanimeDetailShow? = null, - @JsonProperty("episode") val episode: AllanimeEpisode? = null, -) - -data class AllanimeResponses( - @JsonProperty("data") val data: AllanimeData? = null, -) - data class ShivamhwSources( @JsonProperty("id") val id: String? = null, @JsonProperty("stream_link") val stream_link: String? = null, @@ -3448,4 +3204,74 @@ data class VizcloudData( data class VizcloudResponses( @JsonProperty("data") val data: VizcloudData? = null, +) + +data class AnilistExternalLinks( + @JsonProperty("id") var id: Int? = null, + @JsonProperty("site") var site: String? = null, + @JsonProperty("url") var url: String? = null, + @JsonProperty("type") var type: String? = null, +) + +data class AnilistMedia( + @JsonProperty("externalLinks") var externalLinks: ArrayList = arrayListOf() +) + +data class AnilistData( + @JsonProperty("Media") var Media: AnilistMedia? = AnilistMedia() +) + +data class AnilistResponses( + @JsonProperty("data") var data: AnilistData? = AnilistData() +) + +data class CrunchyrollToken( + @JsonProperty("access_token") val accessToken: String? = null, + @JsonProperty("expires_in") val expiresIn: Int? = null, + @JsonProperty("token_type") val tokenType: String? = null, + @JsonProperty("scope") val scope: String? = null, + @JsonProperty("country") val country: String? = null +) + +data class CrunchyrollVersions( + @JsonProperty("audio_locale") val audio_locale: String? = null, + @JsonProperty("guid") val guid: String? = null, +) + +data class CrunchyrollData( + @JsonProperty("id") val id: String? = null, + @JsonProperty("title") val title: String? = null, + @JsonProperty("slug_title") val slug_title: String? = null, + @JsonProperty("season_number") val season_number: Int? = null, + @JsonProperty("episode_number") val episode_number: Int? = null, + @JsonProperty("versions") val versions: ArrayList? = null, + @JsonProperty("streams_link") val streams_link: String? = null, + @JsonProperty("adaptive_hls") val adaptive_hls: HashMap>? = hashMapOf(), + @JsonProperty("vo_adaptive_hls") val vo_adaptive_hls: HashMap>? = hashMapOf(), +) + +data class CrunchyrollResponses( + @JsonProperty("data") val data: ArrayList? = arrayListOf(), +) + +data class CrunchyrollMeta( + @JsonProperty("subtitles") val subtitles: HashMap>? = hashMapOf(), +) + +data class CrunchyrollSourcesResponses( + @JsonProperty("data") val data: ArrayList? = arrayListOf(), + @JsonProperty("meta") val meta: CrunchyrollMeta? = null, +) + +data class MALSyncPages( + @JsonProperty("Zoro") val zoro: HashMap>? = hashMapOf(), +) + +data class MALSyncResponses( + @JsonProperty("Pages") val pages: MALSyncPages? = null, +) + +data class ZoroResponses( + @JsonProperty("html") val html: String? = null, + @JsonProperty("link") val link: String? = null, ) \ No newline at end of file diff --git a/SoraStream/src/main/kotlin/com/hexated/SoraStream.kt b/SoraStream/src/main/kotlin/com/hexated/SoraStream.kt index c4a7620a..26b874c4 100644 --- a/SoraStream/src/main/kotlin/com/hexated/SoraStream.kt +++ b/SoraStream/src/main/kotlin/com/hexated/SoraStream.kt @@ -6,7 +6,6 @@ import com.hexated.SoraExtractor.invokeAsk4Movies import com.hexated.SoraExtractor.invokeBlackmovies import com.hexated.SoraExtractor.invokeBollyMaza import com.hexated.SoraExtractor.invokeCodexmovies -import com.hexated.SoraExtractor.invokeCrunchyroll import com.hexated.SoraExtractor.invokeCryMovies import com.hexated.SoraExtractor.invokeDbgo import com.hexated.SoraExtractor.invokeFilmxy @@ -17,7 +16,6 @@ import com.hexated.SoraExtractor.invokeMovieHab import com.hexated.SoraExtractor.invokeNoverse import com.hexated.SoraExtractor.invokeSeries9 import com.hexated.SoraExtractor.invokeTwoEmbed -import com.hexated.SoraExtractor.invokeUniqueStream import com.hexated.SoraExtractor.invokeVidSrc import com.hexated.SoraExtractor.invokeXmovies import com.lagradost.cloudstream3.* @@ -41,8 +39,6 @@ import com.hexated.SoraExtractor.invokeMoviesbay import com.hexated.SoraExtractor.invokeMoviezAdd import com.hexated.SoraExtractor.invokeNinetv import com.hexated.SoraExtractor.invokeNowTv -import com.hexated.SoraExtractor.invokePapaonMovies1 -import com.hexated.SoraExtractor.invokePapaonMovies2 import com.hexated.SoraExtractor.invokePutlocker import com.hexated.SoraExtractor.invokeRStream import com.hexated.SoraExtractor.invokeRinzrymovies @@ -51,7 +47,6 @@ import com.hexated.SoraExtractor.invokeShinobiMovies import com.hexated.SoraExtractor.invokeShivamhw import com.hexated.SoraExtractor.invokeSmashyStream import com.hexated.SoraExtractor.invokeSoraStream -import com.hexated.SoraExtractor.invokeTgarMovies import com.hexated.SoraExtractor.invokeTvMovies import com.hexated.SoraExtractor.invokeUhdmovies import com.hexated.SoraExtractor.invokeVitoenMovies @@ -81,17 +76,14 @@ open class SoraStream : TmdbProvider() { private const val tmdbAPI = "https://api.themoviedb.org/3" const val tmdb2anilist = "https://tmdb2anilist.slidemovies.org" const val gdbot = "https://gdtot.pro" - const val consumetAnilistAPI = "https://api.consumet.org/meta/anilist" - private val apiKey = - base64DecodeAPI("ZTM=NTg=MjM=MjM=ODc=MzI=OGQ=MmE=Nzk=Nzk=ZjI=NTA=NDY=NDA=MzA=YjA=") // PLEASE DON'T STEAL + private val apiKey = base64DecodeAPI("ZTM=NTg=MjM=MjM=ODc=MzI=OGQ=MmE=Nzk=Nzk=ZjI=NTA=NDY=NDA=MzA=YjA=") // PLEASE DON'T STEAL /** ALL SOURCES */ const val twoEmbedAPI = "https://www.2embed.to" const val vidSrcAPI = "https://v2.vidsrc.me" const val dbgoAPI = "https://dbgo.fun" const val movieHabAPI = "https://moviehab.com" - const val databaseGdriveAPI = "https://databasegdriveplayer.co" const val hdMovieBoxAPI = "https://hdmoviebox.net" const val series9API = "https://series9.sh" const val idlixAPI = "https://idlixian.com" @@ -100,11 +92,11 @@ open class SoraStream : TmdbProvider() { const val filmxyAPI = "https://www.filmxy.vip" const val kimcartoonAPI = "https://kimcartoon.li" const val xMovieAPI = "https://xemovies.to" - const val consumetZoroAPI = "https://api.consumet.org/anime/zoro" - const val allanimeAPI = "https://api.allanime.to" + const val zoroAPI = "https://sanji.to" + const val crunchyrollAPI = "https://beta-api.crunchyroll.com" const val kissKhAPI = "https://kisskh.co" const val lingAPI = "https://ling-online.net" - const val uhdmoviesAPI = "https://uhdmovies.one" + const val uhdmoviesAPI = "https://uhdmovies.bio" const val fwatayakoAPI = "https://5100.svetacdn.in" const val gMoviesAPI = "https://gdrivemovies.xyz" const val fdMoviesAPI = "https://freedrivemovie.lol" @@ -600,7 +592,7 @@ open class SoraStream : TmdbProvider() { invokeMovie123Net(res.title, res.season, res.episode, subtitleCallback, callback) }, { - invokeSmashyStream(res.imdbId, res.season, res.episode, callback) + invokeSmashyStream(res.imdbId, res.season, res.episode, subtitleCallback, callback) }, { invokeWatchsomuch( diff --git a/SoraStream/src/main/kotlin/com/hexated/SoraStreamLite.kt b/SoraStream/src/main/kotlin/com/hexated/SoraStreamLite.kt index dfcc9968..a921c45b 100644 --- a/SoraStream/src/main/kotlin/com/hexated/SoraStreamLite.kt +++ b/SoraStream/src/main/kotlin/com/hexated/SoraStreamLite.kt @@ -167,7 +167,7 @@ class SoraStreamLite : SoraStream() { invokeKimcartoon(res.title, res.season, res.episode, subtitleCallback, callback) }, { - invokeSmashyStream(res.imdbId, res.season, res.episode, callback) + invokeSmashyStream(res.imdbId, res.season, res.episode, subtitleCallback, callback) }, { invokeXmovies( diff --git a/SoraStream/src/main/kotlin/com/hexated/SoraUtils.kt b/SoraStream/src/main/kotlin/com/hexated/SoraUtils.kt index 07bb476d..960d3dfe 100644 --- a/SoraStream/src/main/kotlin/com/hexated/SoraUtils.kt +++ b/SoraStream/src/main/kotlin/com/hexated/SoraUtils.kt @@ -2,10 +2,9 @@ package com.hexated import android.util.Base64 import com.fasterxml.jackson.annotation.JsonProperty -import com.hexated.SoraStream.Companion.allanimeAPI import com.hexated.SoraStream.Companion.base64DecodeAPI import com.hexated.SoraStream.Companion.baymoviesAPI -import com.hexated.SoraStream.Companion.consumetCrunchyrollAPI +import com.hexated.SoraStream.Companion.crunchyrollAPI import com.hexated.SoraStream.Companion.filmxyAPI import com.hexated.SoraStream.Companion.gdbot import com.hexated.SoraStream.Companion.putlockerAPI @@ -17,10 +16,12 @@ import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.APIHolder.getCaptchaToken import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall import com.lagradost.cloudstream3.utils.* +import com.lagradost.cloudstream3.utils.AppUtils.toJson import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.nicehttp.NiceResponse import com.lagradost.nicehttp.RequestBodyTypes +import com.lagradost.nicehttp.requestCreator import kotlinx.coroutines.delay import okhttp3.FormBody import okhttp3.Headers @@ -28,10 +29,7 @@ import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.RequestBody.Companion.toRequestBody import org.jsoup.nodes.Document -import java.net.URI -import java.net.URL -import java.net.URLDecoder -import java.net.URLEncoder +import java.net.* import java.nio.charset.StandardCharsets import java.security.MessageDigest import java.security.SecureRandom @@ -45,56 +43,13 @@ import kotlin.math.min val soraAPI = base64DecodeAPI("cA==YXA=cy8=Y20=di8=LnQ=b2s=a2w=bG8=aS4=YXA=ZS0=aWw=b2I=LW0=Z2E=Ly8=czo=dHA=aHQ=") val bflixChipperKey = base64DecodeAPI("Yjc=ejM=TzA=YTk=WHE=WnU=bXU=RFo=") val bflixKey = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" -val kaguyaBaseUrl = "https://kaguya.app/" +const val kaguyaBaseUrl = "https://kaguya.app/" val soraHeaders = mapOf( "lang" to "en", "versioncode" to "33", "clienttype" to "android_Official", "deviceid" to getDeviceId(), ) -val allanimeSearchQuery = """ - query( - ${'$'}search: SearchInput - ${'$'}limit: Int - ${'$'}page: Int - ${'$'}translationType: VaildTranslationTypeEnumType - ${'$'}countryOrigin: VaildCountryOriginEnumType - ) { - shows( - search: ${'$'}search - limit: ${'$'}limit - page: ${'$'}page - translationType: ${'$'}translationType - countryOrigin: ${'$'}countryOrigin - ) { - pageInfo { - total - } - edges { - _id - name - thumbnail - englishName - nativeName - } - } - } - """.trimIndent().trim() -val allanimeServerQuery = """ - query( - ${'$'}showId: String!, - ${'$'}translationType: VaildTranslationTypeEnumType!, - ${'$'}episodeString: String! - ) { - episode( - showId: ${'$'}showId - translationType: ${'$'}translationType - episodeString: ${'$'}episodeString - ) { - sourceUrls - } - } - """.trimIndent().trim() val encodedIndex = arrayOf( "GamMovies", "JSMovies", @@ -574,6 +529,45 @@ suspend fun invokeSmashyNflim( } +suspend fun invokeSmashyRip( + name: String, + url: String, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit, +) { + val script = app.get(url).document.selectFirst("script:containsData(player =)")?.data() ?: return + + val source = Regex("file:\\s*\"([^\"]+)").find(script)?.groupValues?.get(1) + val subtitle = Regex("subtitle:\\s*\"([^\"]+)").find(script)?.groupValues?.get(1) + + source?.split(",")?.map { links -> + val quality = Regex("\\[(\\d+)]").find(links)?.groupValues?.getOrNull(1)?.trim() + val link = links.removePrefix("[$quality]").substringAfter("dev/").trim() + callback.invoke( + ExtractorLink( + "Smashy [$name]", + "Smashy [$name]", + link, + "", + quality?.toIntOrNull() ?: return@map, + isM3u8 = true, + ) + ) + } + + subtitle?.replace("
", "")?.split(",")?.map { sub -> + val lang = Regex("\\[(.*?)]").find(sub)?.groupValues?.getOrNull(1)?.trim() + val link = sub.removePrefix("[$lang]") + subtitleCallback.invoke( + SubtitleFile( + lang.orEmpty().ifEmpty { return@map }, + link + ) + ) + } + +} + suspend fun getSoraIdAndType(title: String?, year: Int?, season: Int?): Pair? { val doc = app.get("${base64DecodeAPI("b20=LmM=b2s=a2w=bG8=Ly8=czo=dHA=aHQ=")}/search?keyword=$title").document @@ -825,7 +819,7 @@ suspend fun getTvMoviesServer(url: String, season: Int?, episode: Int?): Pair()?.results - return (if (res?.size == 1) { - res.firstOrNull() - } else { - res?.find { - (it.title?.contains( - title, - true - ) == true || it.title.createSlug() - ?.contains("${title.createSlug()}", true) == true) && it.type.equals("series") +//modified code from https://github.com/jmir1/aniyomi-extensions/blob/master/src/all/kamyroll/src/eu/kanade/tachiyomi/animeextension/all/kamyroll/AccessTokenInterceptor.kt +fun getCrunchyrollToken(): Map { + val client = app.baseClient.newBuilder() + .proxy(Proxy(Proxy.Type.SOCKS, InetSocketAddress("cr-unblocker.us.to", 1080))) + .build() + + Authenticator.setDefault(object : Authenticator() { + override fun getPasswordAuthentication(): PasswordAuthentication { + return PasswordAuthentication("crunblocker", "crunblocker".toCharArray()) } - })?.id + }) + + val request = requestCreator( + method = "POST", + url = "$crunchyrollAPI/auth/v1/token", + headers = mapOf( + "User-Agent" to "Crunchyroll/3.26.1 Android/11 okhttp/4.9.2", + "Content-Type" to "application/x-www-form-urlencoded", + "Authorization" to "Basic ${BuildConfig.CRUNCHYROLL_BASIC_TOKEN}" + ), + data = mapOf( + "refresh_token" to BuildConfig.CRUNCHYROLL_REFRESH_TOKEN, + "grant_type" to "refresh_token", + "scope" to "offline_access" + ) + ) + + val response = tryParseJson(client.newCall(request).execute().body.string()) + return mapOf("Authorization" to "${response?.tokenType} ${response?.accessToken}") + } -fun CrunchyrollDetails.findCrunchyrollId( - season: Int?, - episode: Int?, - epsTitle: String? -): List?> { - val sub = this.episodes?.filterKeys { it.contains("subbed") } - .matchingEpisode(epsTitle, season, episode) to "Raw" - val dub = this.episodes?.filterKeys { it.contains("English Dub") } - .matchingEpisode(epsTitle, season, episode) to "English Dub" - return listOf(sub, dub) -} +suspend fun getCrunchyrollId(aniId: String?): String? { + val query = """ + query media(${'$'}id: Int, ${'$'}type: MediaType, ${'$'}isAdult: Boolean) { + Media(id: ${'$'}id, type: ${'$'}type, isAdult: ${'$'}isAdult) { + id + externalLinks { + id + site + url + type + } + } + } + """.trimIndent().trim() -fun Map>?.matchingEpisode( - epsTitle: String?, - season: Int?, - episode: Int? -): CrunchyrollEpisodes? { - return this?.mapNotNull { eps -> - eps.value.find { - (it.episode_number == episode && it.season_number == season) || it.title.equals( - epsTitle, - true - ) - } ?: eps.value.find { it.episode_number == episode } - }?.firstOrNull() + val variables = mapOf( + "id" to aniId, + "isAdult" to false, + "type" to "ANIME", + ) + + val data = mapOf( + "query" to query, + "variables" to variables + ).toJson().toRequestBody(RequestBodyTypes.JSON.toMediaTypeOrNull()) + + return app.post("https://graphql.anilist.co", requestBody = data) + .parsedSafe()?.data?.Media?.externalLinks?.find { it.site == "VRV" }?.url?.let { + Regex("series/(\\w+)/?").find(it)?.groupValues?.get(1) + } } suspend fun extractPutlockerSources(url: String?): NiceResponse? { @@ -1120,10 +1135,6 @@ fun String.decryptGomoviesJson(key: String = "123"): String { return sb.toString() } -fun allanimeQueries(variables: String, query: String) : String { - return "${allanimeAPI}/allanimeapi?variables=$variables&query=$query" -} - fun Headers.getGomoviesCookies(cookieKey: String = "set-cookie"): Map { val cookieList = this.filter { it.first.equals(cookieKey, ignoreCase = true) }.mapNotNull { @@ -1292,7 +1303,7 @@ private fun decryptVrf(input: String, key: String): String { val t = if (input.replace("""[\t\n\f\r]""".toRegex(), "").length % 4 == 0) { input.replace("""==?$""".toRegex(), "") } else input - if (t.length % 4 == 1 || t.contains("""[^+/0-9A-Za-z]""".toRegex())) throw Exception("bad input") + if (t.length % 4 == 1 || t.contains("""[^+/\dA-Za-z]""".toRegex())) throw Exception("bad input") var i: Int var r = "" var e = 0 @@ -1359,10 +1370,6 @@ fun getBaseUrl(url: String): String { } } -fun String.decodeBase64(): String { - return Base64.decode(this, Base64.DEFAULT).toString(Charsets.UTF_8) -} - fun decode(input: String): String = URLDecoder.decode(input, "utf-8") fun encode(input: String): String = URLEncoder.encode(input, "utf-8").replace("+", "%20") @@ -1599,7 +1606,9 @@ object CryptoAES { object RabbitStream { suspend fun MainAPI.extractRabbitStream( + server: String, url: String, + ref: String, subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit, useSidAuthentication: Boolean, @@ -1681,8 +1690,8 @@ object RabbitStream { list.forEach { subList -> subList.first?.forEach { source -> source?.toExtractorLink( - "Vidcloud", - "$twoEmbedAPI/", + server, + ref, extractorData, ) ?.forEach { @@ -1792,11 +1801,15 @@ object RabbitStream { return code.reversed() } - suspend fun getKey(): String? { + suspend fun getKey(): String { return app.get("https://raw.githubusercontent.com/enimax-anime/key/e4/key.txt") .text } + suspend fun getZoroKey(): String { + return app.get("https://raw.githubusercontent.com/enimax-anime/key/e6/key.txt").text + } + private inline fun decryptMapped(input: String, key: String): T? { return tryParseJson(decrypt(input, key)) }