diff --git a/Samehadaku/build.gradle.kts b/Samehadaku/build.gradle.kts index 25a86ea3..0ef8f5ed 100644 --- a/Samehadaku/build.gradle.kts +++ b/Samehadaku/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 18 +version = 19 cloudstream { diff --git a/Samehadaku/src/main/kotlin/com/hexated/Samehadaku.kt b/Samehadaku/src/main/kotlin/com/hexated/Samehadaku.kt index 69bdf841..b31f4c7e 100644 --- a/Samehadaku/src/main/kotlin/com/hexated/Samehadaku.kt +++ b/Samehadaku/src/main/kotlin/com/hexated/Samehadaku.kt @@ -217,9 +217,10 @@ class Samehadaku : MainAPI() { } private fun String.fixQuality() : Int { - return when(this) { - "MP4HD" -> Qualities.P720.value + return when(this.uppercase()) { + "4K" -> Qualities.P2160.value "FULLHD" -> Qualities.P1080.value + "MP4HD" -> Qualities.P720.value else -> this.filter { it.isDigit() }.toIntOrNull() ?: Qualities.Unknown.value } } diff --git a/SoraStream/build.gradle.kts b/SoraStream/build.gradle.kts index f8d93318..8c2f717a 100644 --- a/SoraStream/build.gradle.kts +++ b/SoraStream/build.gradle.kts @@ -1,7 +1,7 @@ import org.jetbrains.kotlin.konan.properties.Properties // use an integer for version numbers -version = 210 +version = 211 android { defaultConfig { diff --git a/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt b/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt index 3ee5c51a..677c7bc2 100644 --- a/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt +++ b/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt @@ -1505,7 +1505,8 @@ object SoraExtractor : SoraStream() { "en-US", "zh-CN", ) - val headers = getCrunchyrollToken() + val token = getCrunchyrollToken() + val headers = mapOf("Authorization" to "${token.tokenType} ${token.accessToken}") val seasonIdData = app.get( "$crunchyrollAPI/content/v2/cms/series/${id ?: return}/seasons", headers = headers ).parsedSafe()?.data?.let { s -> @@ -1532,17 +1533,18 @@ object SoraExtractor : SoraStream() { 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() + }?.streams_link?.substringAfter("/videos/")?.substringBefore("/streams") ?: return@apmap + val sources = app.get( + "$crunchyrollAPI/cms/v2${token.bucket}/videos/$streamsLink/streams?Policy=${token.policy}&Signature=${token.signature}&Key-Pair-Id=${token.key_pair_id}", + 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 { + val source = sources?.streams?.let { if (hls == "adaptive_hls") it.adaptive_hls else it.vo_adaptive_hls } M3u8Helper.generateM3u8( @@ -1552,7 +1554,7 @@ object SoraExtractor : SoraStream() { ).forEach(callback) } - sources?.meta?.subtitles?.map { sub -> + sources?.subtitles?.map { sub -> subtitleCallback.invoke( SubtitleFile( "${fixCrunchyrollLang(sub.key) ?: sub.key} [ass]", @@ -1560,8 +1562,6 @@ object SoraExtractor : SoraStream() { ) ) } - - } } @@ -2135,7 +2135,7 @@ object SoraExtractor : SoraStream() { media.third.substringAfterLast("/") to iframe.substringAfterLast("/") .substringBefore("-") } - val res = app.get(fixUrl(iframe, api)) + val res = app.get(fixUrl(iframe, api), verify = false) delay(2000) val serverUrl = res.document.selectFirst("script:containsData(pushState)")?.data()?.let { """,\s*'([^']+)""".toRegex().find(it)?.groupValues?.get(1) diff --git a/SoraStream/src/main/kotlin/com/hexated/SoraParser.kt b/SoraStream/src/main/kotlin/com/hexated/SoraParser.kt index 21eb55a6..03eafa4c 100644 --- a/SoraStream/src/main/kotlin/com/hexated/SoraParser.kt +++ b/SoraStream/src/main/kotlin/com/hexated/SoraParser.kt @@ -2,6 +2,15 @@ package com.hexated import com.fasterxml.jackson.annotation.JsonProperty +data class CrunchyrollAccessToken( + val accessToken: String? = null, + val tokenType: String? = null, + val bucket: String? = null, + val policy: String? = null, + val signature: String? = null, + val key_pair_id: String? = null, +) + data class ShowboxMedia( val url: String, val title: String, @@ -207,12 +216,17 @@ data class AnilistResponses( ) 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 -) + @JsonProperty("access_token") val accessToken: String? = null, + @JsonProperty("token_type") val tokenType: String? = null, + @JsonProperty("cms") val cms: Cms? = null, +) { + data class Cms( + @JsonProperty("bucket") var bucket: String? = null, + @JsonProperty("policy") var policy: String? = null, + @JsonProperty("signature") var signature: String? = null, + @JsonProperty("key_pair_id") var key_pair_id: String? = null, + ) +} data class CrunchyrollVersions( @JsonProperty("audio_locale") val audio_locale: String? = null, @@ -227,22 +241,21 @@ data class CrunchyrollData( @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, -) + @JsonProperty("streams") val streams: Streams? = Streams(), + @JsonProperty("subtitles") val subtitles: HashMap>? = hashMapOf(), +) { + data class Streams( + @JsonProperty("adaptive_hls") val adaptive_hls: HashMap>? = hashMapOf(), + @JsonProperty("vo_adaptive_hls") val vo_adaptive_hls: HashMap>? = hashMapOf(), + ) +} data class MALSyncSites( @JsonProperty("Zoro") val zoro: HashMap>? = hashMapOf(), diff --git a/SoraStream/src/main/kotlin/com/hexated/SoraUtils.kt b/SoraStream/src/main/kotlin/com/hexated/SoraUtils.kt index 89593955..22c68cac 100644 --- a/SoraStream/src/main/kotlin/com/hexated/SoraUtils.kt +++ b/SoraStream/src/main/kotlin/com/hexated/SoraUtils.kt @@ -734,7 +734,7 @@ fun Document.findTvMoviesIframe(): String? { } //modified code from https://github.com/jmir1/aniyomi-extensions/blob/master/src/all/kamyroll/src/eu/kanade/tachiyomi/animeextension/all/kamyroll/AccessTokenInterceptor.kt -suspend fun getCrunchyrollToken(): Map { +suspend fun getCrunchyrollToken(): CrunchyrollAccessToken { val client = app.baseClient.newBuilder() .proxy(Proxy(Proxy.Type.SOCKS, InetSocketAddress("cr-unblocker.us.to", 1080))) .build() @@ -760,9 +760,17 @@ suspend fun getCrunchyrollToken(): Map { ) ) - val response = tryParseJson(client.newCall(request).execute().body.string()) - return mapOf("Authorization" to "${response?.tokenType} ${response?.accessToken}") - + val token = tryParseJson(client.newCall(request).execute().body.string()) + val headers = mapOf("Authorization" to "${token?.tokenType} ${token?.accessToken}") + val cms = app.get("$crunchyrollAPI/index/v2", headers = headers).parsedSafe()?.cms + return CrunchyrollAccessToken( + token?.accessToken, + token?.tokenType, + cms?.bucket, + cms?.policy, + cms?.signature, + cms?.key_pair_id, + ) } suspend fun getCrunchyrollId(aniId: String?): String? { @@ -796,7 +804,7 @@ suspend fun getCrunchyrollId(aniId: String?): String? { return (externalLinks?.find { it.site == "VRV" } ?: externalLinks?.find { it.site == "Crunchyroll" })?.url?.let { - Regex("series/(\\w+)/?").find(it)?.groupValues?.get(1) + app.get(it).url.substringAfter("/series/").substringBefore("/") } }