mirror of
https://github.com/recloudstream/cloudstream.git
synced 2026-06-19 20:05:41 +00:00
Compare commits
2 commits
master
...
OnlyPlayer
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cfabda0e1c |
||
|
|
accbf4d806 |
13 changed files with 33 additions and 78 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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<KClass<*>> {
|
||||
val context = InstrumentationRegistry
|
||||
|
|
@ -109,6 +109,7 @@ class SerializationClassTester {
|
|||
.targetContext
|
||||
|
||||
val dexFile = DexFile(context.packageCodePath)
|
||||
|
||||
return dexFile.entries()
|
||||
.toList()
|
||||
.filter { it.startsWith(packageName) }
|
||||
|
|
|
|||
|
|
@ -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<SyncAPI.SyncSearchResult>? {
|
||||
val data = searchShows(query) ?: return null
|
||||
override suspend fun search(auth : AuthData?, query: String): List<SyncAPI.SyncSearchResult>? {
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -98,9 +98,9 @@ class MALApi : SyncAPI() {
|
|||
)
|
||||
}
|
||||
|
||||
override suspend fun search(auth: AuthData?, query: String): List<SyncAPI.SyncSearchResult>? {
|
||||
override suspend fun search(auth : AuthData?, query: String): List<SyncAPI.SyncSearchResult>? {
|
||||
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<Data>? {
|
||||
private suspend fun getMalAnimeListSmart(auth : AuthData): Array<Data>? {
|
||||
return if (requireLibraryRefresh) {
|
||||
val list = getMalAnimeList(auth.token)
|
||||
setKey(MAL_CACHED_LIST, auth.user.id.toString(), list)
|
||||
|
|
|
|||
|
|
@ -911,7 +911,7 @@ class SimklApi : SyncAPI() {
|
|||
|
||||
override suspend fun search(auth: AuthData?, query: String): List<SyncAPI.SyncSearchResult>? {
|
||||
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<Array<MediaObject>>()?.mapNotNull { it.toSyncSearchResult() }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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" }
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<StreamInfo>()
|
||||
|
||||
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
|
||||
)
|
||||
}
|
||||
|
|
@ -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<ExtractorApi> = atomicListOf(
|
|||
GUpload(),
|
||||
HlsWish(),
|
||||
ByseQekaho(),
|
||||
Flyfile()
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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<String, String>
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue