diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 02c1f99e8..6c784f3ef 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -207,6 +207,7 @@ dependencies { testImplementation(libs.junit) testImplementation(libs.json) androidTestImplementation(libs.core) + androidTestImplementation(libs.classgraph) androidTestImplementation(libs.espresso.core) androidTestImplementation(libs.ext.junit) androidTestImplementation(libs.instancio.core) diff --git a/app/src/androidTest/java/com/lagradost/cloudstream3/SerializationClassTester.kt b/app/src/androidTest/java/com/lagradost/cloudstream3/SerializationClassTester.kt index 80c7b49b0..d1a11e003 100644 --- a/app/src/androidTest/java/com/lagradost/cloudstream3/SerializationClassTester.kt +++ b/app/src/androidTest/java/com/lagradost/cloudstream3/SerializationClassTester.kt @@ -101,7 +101,7 @@ class SerializationClassTester { } // DEX files are the best solution to read all our classes dynamically. - // classgraph could be used instead, but it only gives results on the JVM, not Android. + // ClassGraph() can be used instead, but it only gives results on the JVM, not Android. @Suppress("DEPRECATION") private fun findSerializableClasses(packageName: String): List> { val context = InstrumentationRegistry @@ -109,6 +109,7 @@ class SerializationClassTester { .targetContext val dexFile = DexFile(context.packageCodePath) + return dexFile.entries() .toList() .filter { it.startsWith(packageName) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt index 177018e19..7a46b4113 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt @@ -50,8 +50,7 @@ class AniListApi : SyncAPI() { override suspend fun login(redirectUrl: String, payload: String?): AuthToken? { val sanitizer = splitRedirectUrl(redirectUrl) val token = AuthToken( - accessToken = sanitizer["access_token"] - ?: throw ErrorLoadingException("No access token"), + accessToken = sanitizer["access_token"] ?: throw ErrorLoadingException("No access token"), //refreshToken = sanitizer["refresh_token"], accessTokenLifetime = unixTime + sanitizer["expires_in"]!!.toLong(), ) @@ -84,8 +83,8 @@ class AniListApi : SyncAPI() { return "$mainUrl/anime/$id" } - override suspend fun search(auth: AuthData?, query: String): List? { - val data = searchShows(query) ?: return null + override suspend fun search(auth : AuthData?, query: String): List? { + val data = searchShows(name) ?: return null return data.data?.page?.media?.map { SyncAPI.SyncSearchResult( it.title.romaji ?: return null, @@ -97,7 +96,7 @@ class AniListApi : SyncAPI() { } } - override suspend fun load(auth: AuthData?, id: String): SyncAPI.SyncResult? { + override suspend fun load(auth : AuthData?, id: String): SyncAPI.SyncResult? { val internalId = (Regex("anilist\\.co/anime/(\\d*)").find(id)?.groupValues?.getOrNull(1) ?: id).toIntOrNull() ?: throw ErrorLoadingException("Invalid internalId") val season = getSeason(internalId).data.media @@ -159,7 +158,7 @@ class AniListApi : SyncAPI() { ) } - override suspend fun status(auth: AuthData?, id: String): SyncAPI.AbstractSyncStatus? { + override suspend fun status(auth : AuthData?, id: String): SyncAPI.AbstractSyncStatus? { val internalId = id.toIntOrNull() ?: return null val data = getDataAboutId(auth ?: return null, internalId) ?: return null @@ -460,7 +459,7 @@ class AniListApi : SyncAPI() { } } - private suspend fun getDataAboutId(auth: AuthData, id: Int): AniListTitleHolder? { + private suspend fun getDataAboutId(auth : AuthData, id: Int): AniListTitleHolder? { val q = """query (${'$'}id: Int = $id) { # Define which variables will be used in the query (id) Media (id: ${'$'}id, type: ANIME) { # Insert our variables into the query arguments (id) (type: ANIME is hard-coded in the query) @@ -507,7 +506,7 @@ class AniListApi : SyncAPI() { } - private suspend fun postApi(token: AuthToken, q: String, cache: Boolean = false): String? { + private suspend fun postApi(token : AuthToken, q: String, cache: Boolean = false): String? { return app.post( "https://graphql.anilist.co/", headers = mapOf( @@ -639,7 +638,7 @@ class AniListApi : SyncAPI() { } } - override suspend fun library(auth: AuthData?): SyncAPI.LibraryMetadata? { + override suspend fun library(auth : AuthData?): SyncAPI.LibraryMetadata? { val list = getAniListAnimeListSmart(auth ?: return null)?.groupBy { convertAniListStringToStatus(it.status ?: "").stringRes }?.mapValues { group -> @@ -667,7 +666,7 @@ class AniListApi : SyncAPI() { ) } - private suspend fun getFullAniListList(auth: AuthData): FullAnilistList? { + private suspend fun getFullAniListList(auth : AuthData): FullAnilistList? { val userID = auth.user.id val mediaType = "ANIME" @@ -715,7 +714,7 @@ class AniListApi : SyncAPI() { return text?.toKotlinObject() } - suspend fun toggleLike(auth: AuthData, id: Int): Boolean { + suspend fun toggleLike(auth : AuthData, id: Int): Boolean { val q = """mutation (${'$'}animeId: Int = $id) { ToggleFavourite (animeId: ${'$'}animeId) { anime { @@ -738,7 +737,7 @@ class AniListApi : SyncAPI() { data class MediaListId(@JsonProperty("id") val id: Long? = null) private suspend fun postDataAboutId( - auth: AuthData, + auth : AuthData, id: Int, type: AniListStatusType, score: Score?, @@ -787,7 +786,7 @@ class AniListApi : SyncAPI() { return data != "" } - private suspend fun getUser(token: AuthToken): AniListUser? { + private suspend fun getUser(token : AuthToken): AniListUser? { val q = """ { Viewer { diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/MALApi.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/MALApi.kt index c0a80b3c9..ba0195be6 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/MALApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/MALApi.kt @@ -98,9 +98,9 @@ class MALApi : SyncAPI() { ) } - override suspend fun search(auth: AuthData?, query: String): List? { + override suspend fun search(auth : AuthData?, query: String): List? { val auth = auth?.token?.accessToken ?: return null - val url = "$apiUrl/v2/anime?q=$query&limit=$MAL_MAX_SEARCH_LIMIT" + val url = "$apiUrl/v2/anime?q=$name&limit=$MAL_MAX_SEARCH_LIMIT" val res = app.get( url, headers = mapOf( "Authorization" to "Bearer $auth", @@ -122,7 +122,7 @@ class MALApi : SyncAPI() { Regex("""/anime/((.*)/|(.*))""").find(url)!!.groupValues.first() override suspend fun updateStatus( - auth: AuthData?, + auth : AuthData?, id: String, newStatus: SyncAPI.AbstractSyncStatus ): Boolean { @@ -225,7 +225,7 @@ class MALApi : SyncAPI() { ) } - override suspend fun load(auth: AuthData?, id: String): SyncAPI.SyncResult? { + override suspend fun load(auth : AuthData?, id: String): SyncAPI.SyncResult? { val auth = auth?.token?.accessToken ?: return null val internalId = id.toIntOrNull() ?: return null val url = @@ -271,7 +271,7 @@ class MALApi : SyncAPI() { } } - override suspend fun status(auth: AuthData?, id: String): SyncAPI.AbstractSyncStatus? { + override suspend fun status(auth : AuthData?, id: String): SyncAPI.AbstractSyncStatus? { val auth = auth?.token?.accessToken ?: return null // https://myanimelist.net/apiconfig/references/api/v2#operation/anime_anime_id_get @@ -477,7 +477,7 @@ class MALApi : SyncAPI() { @JsonProperty("start_time") val startTime: String? ) - override suspend fun library(auth: AuthData?): LibraryMetadata? { + override suspend fun library(auth : AuthData?): LibraryMetadata? { val list = getMalAnimeListSmart(auth ?: return null)?.groupBy { convertToStatus(it.listStatus?.status ?: "").stringRes }?.mapValues { group -> @@ -505,7 +505,7 @@ class MALApi : SyncAPI() { ) } - private suspend fun getMalAnimeListSmart(auth: AuthData): Array? { + private suspend fun getMalAnimeListSmart(auth : AuthData): Array? { return if (requireLibraryRefresh) { val list = getMalAnimeList(auth.token) setKey(MAL_CACHED_LIST, auth.user.id.toString(), list) diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/SimklApi.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/SimklApi.kt index 3110b23ac..84a498bb0 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/SimklApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/SimklApi.kt @@ -911,7 +911,7 @@ class SimklApi : SyncAPI() { override suspend fun search(auth: AuthData?, query: String): List? { return app.get( - "$mainUrl/search/", params = mapOf("client_id" to CLIENT_ID, "q" to query) + "$mainUrl/search/", params = mapOf("client_id" to CLIENT_ID, "q" to name) ).parsedSafe>()?.mapNotNull { it.toSyncSearchResult() } } diff --git a/app/src/main/res/layout/player_custom_layout.xml b/app/src/main/res/layout/player_custom_layout.xml index 04a2a1f1f..407de4a3f 100644 --- a/app/src/main/res/layout/player_custom_layout.xml +++ b/app/src/main/res/layout/player_custom_layout.xml @@ -11,7 +11,6 @@ android:id="@+id/player_metadata_scrim" android:layout_width="640dp" android:layout_height="match_parent" - android:layout_marginTop="-10dp" android:background="@drawable/bg_player_metadata_scrim_netflix" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" diff --git a/app/src/main/res/layout/player_custom_layout_tv.xml b/app/src/main/res/layout/player_custom_layout_tv.xml index 077929d87..3a3076943 100644 --- a/app/src/main/res/layout/player_custom_layout_tv.xml +++ b/app/src/main/res/layout/player_custom_layout_tv.xml @@ -12,7 +12,6 @@ android:id="@+id/player_metadata_scrim" android:layout_width="680dp" android:layout_height="match_parent" - android:layout_marginTop="-10dp" android:background="@drawable/bg_player_metadata_scrim_netflix" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" diff --git a/app/src/main/res/layout/trailer_custom_layout.xml b/app/src/main/res/layout/trailer_custom_layout.xml index 88a318874..76231a2d3 100644 --- a/app/src/main/res/layout/trailer_custom_layout.xml +++ b/app/src/main/res/layout/trailer_custom_layout.xml @@ -11,7 +11,6 @@ android:id="@+id/player_metadata_scrim" android:layout_width="640dp" android:layout_height="match_parent" - android:layout_marginTop="-10dp" android:background="@drawable/bg_player_metadata_scrim_netflix" android:visibility="gone" app:layout_constraintStart_toStartOf="parent" diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 80e342c2c..f28bc41ab 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,6 +8,7 @@ annotation = "1.10.0" appcompat = "1.7.1" biometric = "1.4.0-alpha07" buildkonfigGradlePlugin = "0.21.2" +classgraph = "4.8.184" coil = { strictly = "3.3.0" } # Later versions require jvmTarget 11 or later colorpicker = "6b46b49" conscryptAndroid = { strictly = "2.5.2" } # 2.5.3 crashes everything @@ -69,6 +70,7 @@ anime-db = { module = "com.github.recloudstream:anime-db", version.ref = "animeD annotation = { module = "androidx.annotation:annotation", version.ref = "annotation" } appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" } biometric = { module = "androidx.biometric:biometric", version.ref = "biometric" } +classgraph = { group = "io.github.classgraph", name = "classgraph", version.ref = "classgraph" } coil = { module = "io.coil-kt.coil3:coil", version.ref = "coil" } coil-network-okhttp = { module = "io.coil-kt.coil3:coil-network-okhttp", version.ref = "coil" } colorpicker = { module = "com.github.recloudstream:color-picker-android", version.ref = "colorpicker" } diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt index ffc0a938d..4a9e0b10a 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt @@ -41,6 +41,7 @@ import kotlinx.datetime.format.parse import kotlinx.datetime.toInstant import kotlinx.serialization.json.Json import kotlin.io.encoding.Base64 +import kotlin.io.encoding.ExperimentalEncodingApi import kotlin.math.absoluteValue import kotlin.math.roundToInt import kotlin.time.Clock @@ -715,10 +716,12 @@ fun base64Decode(string: String): String { } } +@OptIn(ExperimentalEncodingApi::class) fun base64DecodeArray(string: String): ByteArray { return Base64.decode(string) } +@OptIn(ExperimentalEncodingApi::class) fun base64Encode(array: ByteArray): String { return Base64.encode(array) } diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Flyfile.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Flyfile.kt deleted file mode 100644 index eb6d474a5..000000000 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Flyfile.kt +++ /dev/null @@ -1,48 +0,0 @@ -package com.lagradost.cloudstream3.extractors - -import com.lagradost.cloudstream3.Prerelease -import com.lagradost.cloudstream3.SubtitleFile -import com.lagradost.cloudstream3.app -import com.lagradost.cloudstream3.utils.ExtractorApi -import com.lagradost.cloudstream3.utils.ExtractorLink -import com.lagradost.cloudstream3.utils.ExtractorLinkType -import com.lagradost.cloudstream3.utils.newExtractorLink -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Prerelease -open class Flyfile : ExtractorApi() { - override val name: String = "FlyFile" - override val mainUrl: String = "https://flyfile.app" - open val apiUrl: String = "https://api.flyfile.app" - override val requiresReferer: Boolean = false - - override suspend fun getUrl( - url: String, - referer: String?, - subtitleCallback: (SubtitleFile) -> Unit, - callback: (ExtractorLink) -> Unit - ) { - val videoId = url.substringAfterLast("/") - val videoInfo = app.get("$apiUrl/api/streaming/assign/$videoId") - .parsed() - - val streamUrl = "${videoInfo.url}/hls/${videoInfo.token}/master.m3u8" - callback.invoke( - newExtractorLink( - source = name, - name = name, - url = streamUrl, - type = ExtractorLinkType.M3U8 - ) - ) - } - - @Serializable - private data class StreamInfo( - @SerialName("url") - val url: String, - @SerialName("token") - val token: String - ) -} \ No newline at end of file diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt index f9167d08c..8a71714cf 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt @@ -81,7 +81,6 @@ import com.lagradost.cloudstream3.extractors.FilemoonV2 import com.lagradost.cloudstream3.extractors.Filesim import com.lagradost.cloudstream3.extractors.Multimoviesshg import com.lagradost.cloudstream3.extractors.FlaswishCom -import com.lagradost.cloudstream3.extractors.Flyfile import com.lagradost.cloudstream3.extractors.FourCX import com.lagradost.cloudstream3.extractors.FourPichive import com.lagradost.cloudstream3.extractors.FourPlayRu @@ -1299,7 +1298,6 @@ val extractorApis: AtomicMutableList = atomicListOf( GUpload(), HlsWish(), ByseQekaho(), - Flyfile() ) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/HlsPlaylistParser.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/HlsPlaylistParser.kt index cd5d752b3..37d5f6285 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/HlsPlaylistParser.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/HlsPlaylistParser.kt @@ -19,11 +19,12 @@ */ package com.lagradost.cloudstream3.utils -import com.lagradost.cloudstream3.base64DecodeArray import io.ktor.http.Url import java.io.IOException import java.nio.ByteBuffer import java.util.UUID +import kotlin.io.encoding.Base64 +import kotlin.io.encoding.ExperimentalEncodingApi @Suppress("unused") object HlsPlaylistParser { @@ -1168,6 +1169,7 @@ object HlsPlaylistParser { return parseOptionalStringAttr(line, pattern, null, variableDefinitions) } + @OptIn(ExperimentalEncodingApi::class) @Throws(ParserException::class) private fun parseDrmSchemeData( line: String, keyFormat: String, variableDefinitions: Map @@ -1179,7 +1181,7 @@ object HlsPlaylistParser { return SchemeData( uuid = C.WIDEVINE_UUID, mimeType = MimeTypes.VIDEO_MP4, - data = base64DecodeArray(urlString.substring(urlString.indexOf(','))) + data = Base64.Default.decode(urlString.substring(urlString.indexOf(','))) ) } else if (KEYFORMAT_WIDEVINE_PSSH_JSON == keyFormat) { return SchemeData( @@ -1190,7 +1192,7 @@ object HlsPlaylistParser { } else if (KEYFORMAT_PLAYREADY == keyFormat && "1" == keyFormatVersions) { val urlString = parseStringAttr(line, REGEX_URI, variableDefinitions) val data: ByteArray = - base64DecodeArray(urlString.substring(urlString.indexOf(','))) + Base64.Default.decode(urlString.substring(urlString.indexOf(','))) val psshData: ByteArray = PsshAtomUtil.buildPsshAtom( systemId = C.PLAYREADY_UUID,