mirror of
https://github.com/recloudstream/cloudstream.git
synced 2026-06-19 20:05:41 +00:00
Compare commits
1 commit
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8ac7e98b83 |
35 changed files with 277 additions and 422 deletions
|
|
@ -207,6 +207,7 @@ dependencies {
|
||||||
testImplementation(libs.junit)
|
testImplementation(libs.junit)
|
||||||
testImplementation(libs.json)
|
testImplementation(libs.json)
|
||||||
androidTestImplementation(libs.core)
|
androidTestImplementation(libs.core)
|
||||||
|
androidTestImplementation(libs.classgraph)
|
||||||
androidTestImplementation(libs.espresso.core)
|
androidTestImplementation(libs.espresso.core)
|
||||||
androidTestImplementation(libs.ext.junit)
|
androidTestImplementation(libs.ext.junit)
|
||||||
androidTestImplementation(libs.instancio.core)
|
androidTestImplementation(libs.instancio.core)
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,7 @@ class SerializationClassTester {
|
||||||
}
|
}
|
||||||
|
|
||||||
// DEX files are the best solution to read all our classes dynamically.
|
// 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")
|
@Suppress("DEPRECATION")
|
||||||
private fun findSerializableClasses(packageName: String): List<KClass<*>> {
|
private fun findSerializableClasses(packageName: String): List<KClass<*>> {
|
||||||
val context = InstrumentationRegistry
|
val context = InstrumentationRegistry
|
||||||
|
|
@ -109,6 +109,7 @@ class SerializationClassTester {
|
||||||
.targetContext
|
.targetContext
|
||||||
|
|
||||||
val dexFile = DexFile(context.packageCodePath)
|
val dexFile = DexFile(context.packageCodePath)
|
||||||
|
|
||||||
return dexFile.entries()
|
return dexFile.entries()
|
||||||
.toList()
|
.toList()
|
||||||
.filter { it.startsWith(packageName) }
|
.filter { it.startsWith(packageName) }
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,6 @@ import com.lagradost.cloudstream3.actions.temp.MpvPackage
|
||||||
import com.lagradost.cloudstream3.actions.temp.MpvRxPackage
|
import com.lagradost.cloudstream3.actions.temp.MpvRxPackage
|
||||||
import com.lagradost.cloudstream3.actions.temp.MpvYTDLPackage
|
import com.lagradost.cloudstream3.actions.temp.MpvYTDLPackage
|
||||||
import com.lagradost.cloudstream3.actions.temp.NextPlayerPackage
|
import com.lagradost.cloudstream3.actions.temp.NextPlayerPackage
|
||||||
import com.lagradost.cloudstream3.actions.temp.OnlyPlayer
|
|
||||||
import com.lagradost.cloudstream3.actions.temp.PlayInBrowserAction
|
import com.lagradost.cloudstream3.actions.temp.PlayInBrowserAction
|
||||||
import com.lagradost.cloudstream3.actions.temp.PlayMirrorAction
|
import com.lagradost.cloudstream3.actions.temp.PlayMirrorAction
|
||||||
import com.lagradost.cloudstream3.actions.temp.ViewM3U8Action
|
import com.lagradost.cloudstream3.actions.temp.ViewM3U8Action
|
||||||
|
|
@ -66,7 +65,6 @@ object VideoClickActionHolder {
|
||||||
MpvYTDLPackage(),
|
MpvYTDLPackage(),
|
||||||
MpvKtPackage(),
|
MpvKtPackage(),
|
||||||
MpvKtPreviewPackage(),
|
MpvKtPreviewPackage(),
|
||||||
OnlyPlayer(),
|
|
||||||
MpvRxPackage(),
|
MpvRxPackage(),
|
||||||
// Always Ask option
|
// Always Ask option
|
||||||
AlwaysAskAction(),
|
AlwaysAskAction(),
|
||||||
|
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
package com.lagradost.cloudstream3.actions.temp
|
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.core.net.toUri
|
|
||||||
import com.lagradost.cloudstream3.actions.OpenInAppAction
|
|
||||||
import com.lagradost.cloudstream3.ui.result.LinkLoadingResult
|
|
||||||
import com.lagradost.cloudstream3.ui.result.ResultEpisode
|
|
||||||
import com.lagradost.cloudstream3.utils.txt
|
|
||||||
|
|
||||||
/** https://github.com/Kindness-Kismet/only_player/tree/main
|
|
||||||
* https://github.com/Kindness-Kismet/only_player/blob/main/feature/player/src/main/java/one/only/player/feature/player/PlayerActivity.kt */
|
|
||||||
class OnlyPlayer : OpenInAppAction(
|
|
||||||
txt("Only Player"),
|
|
||||||
"one.only.player",
|
|
||||||
intentClass = "one.only.player.feature.player.PlayerActivity"
|
|
||||||
) {
|
|
||||||
override val oneSource = true
|
|
||||||
override suspend fun putExtra(
|
|
||||||
context: Context,
|
|
||||||
intent: Intent,
|
|
||||||
video: ResultEpisode,
|
|
||||||
result: LinkLoadingResult,
|
|
||||||
index: Int?
|
|
||||||
) {
|
|
||||||
/** https://github.com/Kindness-Kismet/only_player/blob/d3f55049a2913fa762d31b311146073cc2da46cb/app/src/main/java/one/only/player/navigation/CloudNavGraph.kt#L39 */
|
|
||||||
intent.apply {
|
|
||||||
val link = result.links[index!!]
|
|
||||||
setData(link.url.toUri())
|
|
||||||
|
|
||||||
putExtra("headers", Bundle().apply {
|
|
||||||
for ((key, value) in link.headers) {
|
|
||||||
putExtra(key, value)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResult(activity: Activity, intent: Intent?) {
|
|
||||||
/* onResult does not get called */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -50,8 +50,7 @@ class AniListApi : SyncAPI() {
|
||||||
override suspend fun login(redirectUrl: String, payload: String?): AuthToken? {
|
override suspend fun login(redirectUrl: String, payload: String?): AuthToken? {
|
||||||
val sanitizer = splitRedirectUrl(redirectUrl)
|
val sanitizer = splitRedirectUrl(redirectUrl)
|
||||||
val token = AuthToken(
|
val token = AuthToken(
|
||||||
accessToken = sanitizer["access_token"]
|
accessToken = sanitizer["access_token"] ?: throw ErrorLoadingException("No access token"),
|
||||||
?: throw ErrorLoadingException("No access token"),
|
|
||||||
//refreshToken = sanitizer["refresh_token"],
|
//refreshToken = sanitizer["refresh_token"],
|
||||||
accessTokenLifetime = unixTime + sanitizer["expires_in"]!!.toLong(),
|
accessTokenLifetime = unixTime + sanitizer["expires_in"]!!.toLong(),
|
||||||
)
|
)
|
||||||
|
|
@ -85,7 +84,7 @@ class AniListApi : SyncAPI() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun search(auth : AuthData?, query: String): List<SyncAPI.SyncSearchResult>? {
|
override suspend fun search(auth : AuthData?, query: String): List<SyncAPI.SyncSearchResult>? {
|
||||||
val data = searchShows(query) ?: return null
|
val data = searchShows(name) ?: return null
|
||||||
return data.data?.page?.media?.map {
|
return data.data?.page?.media?.map {
|
||||||
SyncAPI.SyncSearchResult(
|
SyncAPI.SyncSearchResult(
|
||||||
it.title.romaji ?: return null,
|
it.title.romaji ?: return null,
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@ 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 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(
|
val res = app.get(
|
||||||
url, headers = mapOf(
|
url, headers = mapOf(
|
||||||
"Authorization" to "Bearer $auth",
|
"Authorization" to "Bearer $auth",
|
||||||
|
|
|
||||||
|
|
@ -911,7 +911,7 @@ class SimklApi : SyncAPI() {
|
||||||
|
|
||||||
override suspend fun search(auth: AuthData?, query: String): List<SyncAPI.SyncSearchResult>? {
|
override suspend fun search(auth: AuthData?, query: String): List<SyncAPI.SyncSearchResult>? {
|
||||||
return app.get(
|
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() }
|
).parsedSafe<Array<MediaObject>>()?.mapNotNull { it.toSyncSearchResult() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@
|
||||||
android:id="@+id/player_metadata_scrim"
|
android:id="@+id/player_metadata_scrim"
|
||||||
android:layout_width="640dp"
|
android:layout_width="640dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_marginTop="-10dp"
|
|
||||||
android:background="@drawable/bg_player_metadata_scrim_netflix"
|
android:background="@drawable/bg_player_metadata_scrim_netflix"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@
|
||||||
android:id="@+id/player_metadata_scrim"
|
android:id="@+id/player_metadata_scrim"
|
||||||
android:layout_width="680dp"
|
android:layout_width="680dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_marginTop="-10dp"
|
|
||||||
android:background="@drawable/bg_player_metadata_scrim_netflix"
|
android:background="@drawable/bg_player_metadata_scrim_netflix"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@
|
||||||
android:id="@+id/player_metadata_scrim"
|
android:id="@+id/player_metadata_scrim"
|
||||||
android:layout_width="640dp"
|
android:layout_width="640dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_marginTop="-10dp"
|
|
||||||
android:background="@drawable/bg_player_metadata_scrim_netflix"
|
android:background="@drawable/bg_player_metadata_scrim_netflix"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
|
|
||||||
|
|
@ -252,7 +252,7 @@
|
||||||
<string name="update">Update</string>
|
<string name="update">Update</string>
|
||||||
<string name="watch_quality_pref">Bevorzugte Videoqualität (WLAN)</string>
|
<string name="watch_quality_pref">Bevorzugte Videoqualität (WLAN)</string>
|
||||||
<string name="limit_title">Videoplayertitel max. Zeichen</string>
|
<string name="limit_title">Videoplayertitel max. Zeichen</string>
|
||||||
<string name="limit_title_rez">Zeige Playerinformationen</string>
|
<string name="limit_title_rez">Playerinformationen anzeigen</string>
|
||||||
<string name="video_buffer_size_settings">Videopuffergröße</string>
|
<string name="video_buffer_size_settings">Videopuffergröße</string>
|
||||||
<string name="video_buffer_length_settings">Videopufferlänge</string>
|
<string name="video_buffer_length_settings">Videopufferlänge</string>
|
||||||
<string name="video_buffer_disk_settings">Video-Cache in Speicher</string>
|
<string name="video_buffer_disk_settings">Video-Cache in Speicher</string>
|
||||||
|
|
@ -712,8 +712,8 @@
|
||||||
<string name="extra_brightness_settings">Zusätzliche Helligkeit</string>
|
<string name="extra_brightness_settings">Zusätzliche Helligkeit</string>
|
||||||
<string name="extra_brightness_settings_des">Aktiviere Helligkeitsfilter, wenn 100% Bildschirmhelligkeit überschritten ist</string>
|
<string name="extra_brightness_settings_des">Aktiviere Helligkeitsfilter, wenn 100% Bildschirmhelligkeit überschritten ist</string>
|
||||||
<string name="extra_brightness_key">Erhöhte Helligkeit aktiviert</string>
|
<string name="extra_brightness_key">Erhöhte Helligkeit aktiviert</string>
|
||||||
<string name="show_cast_in_details">Zeige Cast-Panel</string>
|
<string name="show_cast_in_details">Cast-Panel zeigen</string>
|
||||||
<string name="video_info">Mediainfo</string>
|
<string name="video_info">Medieninfo</string>
|
||||||
<string name="source_name">Quellname</string>
|
<string name="source_name">Quellname</string>
|
||||||
<string name="download_all">Alle herunterladen</string>
|
<string name="download_all">Alle herunterladen</string>
|
||||||
<string name="download_episode_range">Möchtest du Episode %s herunter laden?</string>
|
<string name="download_episode_range">Möchtest du Episode %s herunter laden?</string>
|
||||||
|
|
@ -731,8 +731,4 @@
|
||||||
<string name="queue_empty_message">Es befinden sich keine Downloads in der Warteschlange.</string>
|
<string name="queue_empty_message">Es befinden sich keine Downloads in der Warteschlange.</string>
|
||||||
<string name="source_priority">Quellpriorität</string>
|
<string name="source_priority">Quellpriorität</string>
|
||||||
<string name="source_priority_help">Entscheide, wie Videoquellen im Player sortiert werden sollen</string>
|
<string name="source_priority_help">Entscheide, wie Videoquellen im Player sortiert werden sollen</string>
|
||||||
<string name="show_player_metadata_overlay">Zeige Player-Metadaten</string>
|
|
||||||
<string name="video_singular">Video</string>
|
|
||||||
<string name="skip_type_preview">Vorschau</string>
|
|
||||||
<string name="player_is_live">Live</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -244,7 +244,7 @@
|
||||||
<string name="quality_tc">TC</string>
|
<string name="quality_tc">TC</string>
|
||||||
<string name="subscription_new">Претплатен на %s</string>
|
<string name="subscription_new">Претплатен на %s</string>
|
||||||
<string name="pref_category_subtitles">Преводи</string>
|
<string name="pref_category_subtitles">Преводи</string>
|
||||||
<string name="download_all_plugins_from_repo">Предупредување: CloudStream не презема никаква одговорност за користење на екстензии од трети страни и не обезбедува никаква поддршка за нив!</string>
|
<string name="download_all_plugins_from_repo">Предупредување: CloudStream 3 не презема никаква одговорност за користење на екстензии од трети страни и не обезбедува никаква поддршка за нив!</string>
|
||||||
<string name="backup_failed">Недостасуваат дозволи за складирање. Обиди се повторно.</string>
|
<string name="backup_failed">Недостасуваат дозволи за складирање. Обиди се повторно.</string>
|
||||||
<string name="sort_save">Зачувај</string>
|
<string name="sort_save">Зачувај</string>
|
||||||
<string name="player_load_subtitles">Вчитај од датотека</string>
|
<string name="player_load_subtitles">Вчитај од датотека</string>
|
||||||
|
|
@ -445,7 +445,7 @@
|
||||||
<string name="backup_failed_error_format">Грешка при правење резервна копија на %s</string>
|
<string name="backup_failed_error_format">Грешка при правење резервна копија на %s</string>
|
||||||
<string name="pref_filter_search_quality">Сокриј го избраниот квалитет на видеото во резултатите од пребарувањето</string>
|
<string name="pref_filter_search_quality">Сокриј го избраниот квалитет на видеото во резултатите од пребарувањето</string>
|
||||||
<string name="apk_installer_settings_des">Некои уреди не го поддржуваат новиот инсталатор на пакети. Испробај ја легаси(старата) опција, ако ажурирањата не се инсталираат.</string>
|
<string name="apk_installer_settings_des">Некои уреди не го поддржуваат новиот инсталатор на пакети. Испробај ја легаси(старата) опција, ако ажурирањата не се инсталираат.</string>
|
||||||
<string name="limit_title_rez">Прикажи информации за плеерот</string>
|
<string name="limit_title_rez">Резолуција на видео плеер</string>
|
||||||
<string name="video_buffer_size_settings">Големина на видео баферот</string>
|
<string name="video_buffer_size_settings">Големина на видео баферот</string>
|
||||||
<string name="pref_category_player_layout">Распоред</string>
|
<string name="pref_category_player_layout">Распоред</string>
|
||||||
<string name="pref_category_defaults">Стандардно</string>
|
<string name="pref_category_defaults">Стандардно</string>
|
||||||
|
|
@ -705,37 +705,4 @@
|
||||||
<string name="top_center">Горе во центар</string>
|
<string name="top_center">Горе во центар</string>
|
||||||
<string name="top_right">Горе на десно</string>
|
<string name="top_right">Горе на десно</string>
|
||||||
<string name="play_full_series_button">Пушти ја целата серија</string>
|
<string name="play_full_series_button">Пушти ја целата серија</string>
|
||||||
<string name="download_queue">Редица за преземање</string>
|
|
||||||
<string name="queue_empty_message">Моментално нема преземања во редицата.</string>
|
|
||||||
<string name="extra_brightness_settings">Дополнителна осветленост</string>
|
|
||||||
<string name="extra_brightness_settings_des">Овозможи филтер за осветленост кога ќе се надмине 100% осветленост на екранот</string>
|
|
||||||
<string name="extra_brightness_key">овозможена_дополнителна_осветленост</string>
|
|
||||||
<string name="search_suggestions">Предлози за пребарување</string>
|
|
||||||
<string name="search_suggestions_des">Прикажувај предлози за пребарување додека пишуваш</string>
|
|
||||||
<string name="clear_suggestions">Исчисти предлози</string>
|
|
||||||
<string name="show_player_metadata_overlay">Прикажи преклоп со метаподатоци на плеерот</string>
|
|
||||||
<string name="show_cast_in_details">Прикажи панел за емитување</string>
|
|
||||||
<string name="install_prerelease">Инсталирај предиздавачка верзија</string>
|
|
||||||
<string name="prerelease_already_installed">Предиздавачката верзија е веќе инсталирана.</string>
|
|
||||||
<string name="prerelease_install_failed">Неуспешна инсталација на предиздавачката верзија.</string>
|
|
||||||
<string name="video_singular">Видео</string>
|
|
||||||
<string name="show_episode_text">Текст на епизода</string>
|
|
||||||
<string name="video_info">Информации за медиумот</string>
|
|
||||||
<string name="skip_type_preview">Преглед</string>
|
|
||||||
<string name="source_priority">Приоритет на извор</string>
|
|
||||||
<string name="source_priority_help">Одреди како ќе се подредуваат видео изворите во плеерот</string>
|
|
||||||
<string name="source_name">Име на изворот</string>
|
|
||||||
<string name="download_all">Преземи сѐ</string>
|
|
||||||
<string name="cancel_all">Откажи сѐ</string>
|
|
||||||
<string name="download_episode_range">Дали сакате да ја преземете епизодата %s?</string>
|
|
||||||
<string name="cancel_queue_message">Дали сакате да ги откажете сите преземања во редицата?</string>
|
|
||||||
<plurals name="downloads_active">
|
|
||||||
<item quantity="one">%d активно преземање</item>
|
|
||||||
<item quantity="other">%d активни преземања</item>
|
|
||||||
</plurals>
|
|
||||||
<plurals name="downloads_queued">
|
|
||||||
<item quantity="one">%d преземање во редицата</item>
|
|
||||||
<item quantity="other">%d преземања во редицата</item>
|
|
||||||
</plurals>
|
|
||||||
<string name="player_is_live">Во живо</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ annotation = "1.10.0"
|
||||||
appcompat = "1.7.1"
|
appcompat = "1.7.1"
|
||||||
biometric = "1.4.0-alpha07"
|
biometric = "1.4.0-alpha07"
|
||||||
buildkonfigGradlePlugin = "0.21.2"
|
buildkonfigGradlePlugin = "0.21.2"
|
||||||
|
classgraph = "4.8.184"
|
||||||
coil = { strictly = "3.3.0" } # Later versions require jvmTarget 11 or later
|
coil = { strictly = "3.3.0" } # Later versions require jvmTarget 11 or later
|
||||||
colorpicker = "6b46b49"
|
colorpicker = "6b46b49"
|
||||||
conscryptAndroid = { strictly = "2.5.2" } # 2.5.3 crashes everything
|
conscryptAndroid = { strictly = "2.5.2" } # 2.5.3 crashes everything
|
||||||
|
|
@ -31,12 +32,11 @@ kotlinxCollectionsImmutable = "0.4.0"
|
||||||
kotlinxCoroutinesCore = "1.11.0"
|
kotlinxCoroutinesCore = "1.11.0"
|
||||||
kotlinxDatetime = "0.8.0"
|
kotlinxDatetime = "0.8.0"
|
||||||
kotlinxSerializationJson = "1.11.0"
|
kotlinxSerializationJson = "1.11.0"
|
||||||
ktor = "3.5.0"
|
|
||||||
lifecycleKtx = "2.10.0"
|
lifecycleKtx = "2.10.0"
|
||||||
material = "1.14.0"
|
material = "1.14.0"
|
||||||
media3 = "1.9.3"
|
media3 = "1.9.3"
|
||||||
navigationKtx = "2.9.8"
|
navigationKtx = "2.9.8"
|
||||||
newpipeextractor = "v0.26.3"
|
newpipeextractor = "v0.26.2"
|
||||||
nextlibMedia3 = "1.9.3-0.12.0"
|
nextlibMedia3 = "1.9.3-0.12.0"
|
||||||
nicehttp = "0.4.18"
|
nicehttp = "0.4.18"
|
||||||
overlappingpanels = "0.1.5"
|
overlappingpanels = "0.1.5"
|
||||||
|
|
@ -69,6 +69,7 @@ anime-db = { module = "com.github.recloudstream:anime-db", version.ref = "animeD
|
||||||
annotation = { module = "androidx.annotation:annotation", version.ref = "annotation" }
|
annotation = { module = "androidx.annotation:annotation", version.ref = "annotation" }
|
||||||
appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" }
|
appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" }
|
||||||
biometric = { module = "androidx.biometric:biometric", version.ref = "biometric" }
|
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 = { module = "io.coil-kt.coil3:coil", version.ref = "coil" }
|
||||||
coil-network-okhttp = { module = "io.coil-kt.coil3:coil-network-okhttp", 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" }
|
colorpicker = { module = "com.github.recloudstream:color-picker-android", version.ref = "colorpicker" }
|
||||||
|
|
@ -94,7 +95,6 @@ kotlinx-collections-immutable = { module = "org.jetbrains.kotlinx:kotlinx-collec
|
||||||
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutinesCore" }
|
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutinesCore" }
|
||||||
kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinxDatetime" }
|
kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinxDatetime" }
|
||||||
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
|
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
|
||||||
ktor-http = { module = "io.ktor:ktor-http", version.ref = "ktor" }
|
|
||||||
lifecycle-livedata-ktx = { module = "androidx.lifecycle:lifecycle-livedata-ktx", version.ref = "lifecycleKtx" }
|
lifecycle-livedata-ktx = { module = "androidx.lifecycle:lifecycle-livedata-ktx", version.ref = "lifecycleKtx" }
|
||||||
lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycleKtx" }
|
lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycleKtx" }
|
||||||
material = { module = "com.google.android.material:material", version.ref = "material" }
|
material = { module = "com.google.android.material:material", version.ref = "material" }
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,6 @@ kotlin {
|
||||||
implementation(libs.kotlinx.coroutines.core)
|
implementation(libs.kotlinx.coroutines.core)
|
||||||
implementation(libs.kotlinx.datetime)
|
implementation(libs.kotlinx.datetime)
|
||||||
implementation(libs.kotlinx.serialization.json) // JSON Parser
|
implementation(libs.kotlinx.serialization.json) // JSON Parser
|
||||||
implementation(libs.ktor.http)
|
|
||||||
implementation(libs.jsoup) // HTML Parser
|
implementation(libs.jsoup) // HTML Parser
|
||||||
implementation(libs.rhino) // Run JavaScript
|
implementation(libs.rhino) // Run JavaScript
|
||||||
implementation(libs.tmdb.java) // TMDB API v3 Wrapper Made with RetroFit
|
implementation(libs.tmdb.java) // TMDB API v3 Wrapper Made with RetroFit
|
||||||
|
|
|
||||||
|
|
@ -15,13 +15,12 @@ import com.lagradost.cloudstream3.utils.Coroutines.main
|
||||||
import com.lagradost.cloudstream3.utils.Coroutines.mainWork
|
import com.lagradost.cloudstream3.utils.Coroutines.mainWork
|
||||||
import com.lagradost.cloudstream3.utils.Coroutines.runOnMainThread
|
import com.lagradost.cloudstream3.utils.Coroutines.runOnMainThread
|
||||||
import com.lagradost.nicehttp.requestCreator
|
import com.lagradost.nicehttp.requestCreator
|
||||||
import io.ktor.http.Url
|
|
||||||
import io.ktor.http.decodeURLPart
|
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
|
import java.net.URI
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When used as Interceptor additionalUrls cannot be returned, use WebViewResolver(...).resolveUsingWebView(...)
|
* When used as Interceptor additionalUrls cannot be returned, use WebViewResolver(...).resolveUsingWebView(...)
|
||||||
|
|
@ -212,7 +211,7 @@ actual class WebViewResolver actual constructor(
|
||||||
* */
|
* */
|
||||||
return@runBlocking try {
|
return@runBlocking try {
|
||||||
when {
|
when {
|
||||||
blacklistedFiles.any { Url(webViewUrl).encodedPath.decodeURLPart().contains(it) } || webViewUrl.endsWith(
|
blacklistedFiles.any { URI(webViewUrl).path.contains(it) } || webViewUrl.endsWith(
|
||||||
"/favicon.ico"
|
"/favicon.ico"
|
||||||
) -> WebResourceResponse(
|
) -> WebResourceResponse(
|
||||||
"image/png",
|
"image/png",
|
||||||
|
|
|
||||||
|
|
@ -22,10 +22,6 @@ import com.lagradost.cloudstream3.utils.Coroutines.mainWork
|
||||||
import com.lagradost.cloudstream3.utils.SubtitleHelper.fromCodeToLangTagIETF
|
import com.lagradost.cloudstream3.utils.SubtitleHelper.fromCodeToLangTagIETF
|
||||||
import com.lagradost.cloudstream3.utils.SubtitleHelper.fromLanguageToTagIETF
|
import com.lagradost.cloudstream3.utils.SubtitleHelper.fromLanguageToTagIETF
|
||||||
import com.lagradost.nicehttp.RequestBodyTypes
|
import com.lagradost.nicehttp.RequestBodyTypes
|
||||||
import io.ktor.http.Url
|
|
||||||
import io.ktor.http.URLBuilder
|
|
||||||
import io.ktor.http.encodedPath
|
|
||||||
import io.ktor.http.takeFrom
|
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
|
|
@ -39,8 +35,11 @@ import kotlinx.datetime.format.byUnicodePattern
|
||||||
import kotlinx.datetime.format.char
|
import kotlinx.datetime.format.char
|
||||||
import kotlinx.datetime.format.parse
|
import kotlinx.datetime.format.parse
|
||||||
import kotlinx.datetime.toInstant
|
import kotlinx.datetime.toInstant
|
||||||
|
import java.net.URI
|
||||||
|
import java.util.EnumSet
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlin.io.encoding.Base64
|
import kotlin.io.encoding.Base64
|
||||||
|
import kotlin.io.encoding.ExperimentalEncodingApi
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
import kotlin.time.Clock
|
import kotlin.time.Clock
|
||||||
|
|
@ -91,7 +90,6 @@ class ErrorLoadingException(message: String? = null) : Exception(message)
|
||||||
@Prerelease
|
@Prerelease
|
||||||
val json = Json {
|
val json = Json {
|
||||||
encodeDefaults = true
|
encodeDefaults = true
|
||||||
explicitNulls = false
|
|
||||||
ignoreUnknownKeys = true
|
ignoreUnknownKeys = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -178,9 +176,9 @@ object APIHolder {
|
||||||
// To get the key
|
// To get the key
|
||||||
suspend fun getCaptchaToken(url: String, key: String, referer: String? = null): String? {
|
suspend fun getCaptchaToken(url: String, key: String, referer: String? = null): String? {
|
||||||
try {
|
try {
|
||||||
val _url = Url(url)
|
val uri = URI.create(url)
|
||||||
val domain = base64Encode(
|
val domain = base64Encode(
|
||||||
(_url.protocol.name + "://" + _url.host + ":443").encodeToByteArray(),
|
(uri.scheme + "://" + uri.host + ":443").encodeToByteArray(),
|
||||||
).replace("\n", "").replace("=", ".")
|
).replace("\n", "").replace("=", ".")
|
||||||
|
|
||||||
val vToken =
|
val vToken =
|
||||||
|
|
@ -715,10 +713,12 @@ fun base64Decode(string: String): String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalEncodingApi::class)
|
||||||
fun base64DecodeArray(string: String): ByteArray {
|
fun base64DecodeArray(string: String): ByteArray {
|
||||||
return Base64.decode(string)
|
return Base64.decode(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalEncodingApi::class)
|
||||||
fun base64Encode(array: ByteArray): String {
|
fun base64Encode(array: ByteArray): String {
|
||||||
return Base64.encode(array)
|
return Base64.encode(array)
|
||||||
}
|
}
|
||||||
|
|
@ -1330,23 +1330,23 @@ fun getQualityFromString(string: String?): SearchQuality? {
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
fun MainAPI.updateUrl(url: String): String {
|
fun MainAPI.updateUrl(url: String): String {
|
||||||
return try {
|
try {
|
||||||
val original = Url(url)
|
val original = URI(url)
|
||||||
val updated = Url(mainUrl)
|
val updated = URI(mainUrl)
|
||||||
|
|
||||||
URLBuilder().apply {
|
// URI(String scheme, String userInfo, String host, int port, String path, String query, String fragment)
|
||||||
takeFrom(updated)
|
return URI(
|
||||||
user = original.user
|
updated.scheme,
|
||||||
password = original.password
|
original.userInfo,
|
||||||
encodedPath = original.encodedPath
|
updated.host,
|
||||||
fragment = original.fragment
|
updated.port,
|
||||||
|
original.path,
|
||||||
parameters.clear()
|
original.query,
|
||||||
parameters.appendAll(original.parameters)
|
original.fragment
|
||||||
}.buildString()
|
).toString()
|
||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
logError(t)
|
logError(t)
|
||||||
url
|
return url
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1510,7 +1510,7 @@ constructor(
|
||||||
|
|
||||||
override var posterUrl: String? = null,
|
override var posterUrl: String? = null,
|
||||||
var year: Int? = null,
|
var year: Int? = null,
|
||||||
var dubStatus: MutableSet<DubStatus>? = null,
|
var dubStatus: EnumSet<DubStatus>? = null,
|
||||||
|
|
||||||
var otherName: String? = null,
|
var otherName: String? = null,
|
||||||
var episodes: MutableMap<DubStatus, Int> = mutableMapOf(),
|
var episodes: MutableMap<DubStatus, Int> = mutableMapOf(),
|
||||||
|
|
@ -1522,7 +1522,7 @@ constructor(
|
||||||
) : SearchResponse
|
) : SearchResponse
|
||||||
|
|
||||||
fun AnimeSearchResponse.addDubStatus(status: DubStatus, episodes: Int? = null) {
|
fun AnimeSearchResponse.addDubStatus(status: DubStatus, episodes: Int? = null) {
|
||||||
this.dubStatus = dubStatus?.also { it.add(status) } ?: mutableSetOf(status)
|
this.dubStatus = dubStatus?.also { it.add(status) } ?: EnumSet.of(status)
|
||||||
if (this.type?.isMovieType() != true)
|
if (this.type?.isMovieType() != true)
|
||||||
if (episodes != null && episodes > 0)
|
if (episodes != null && episodes > 0)
|
||||||
this.episodes[status] = episodes
|
this.episodes[status] = episodes
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,7 @@ import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorApi
|
import com.lagradost.cloudstream3.utils.ExtractorApi
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
import com.lagradost.cloudstream3.utils.M3u8Helper
|
import com.lagradost.cloudstream3.utils.M3u8Helper
|
||||||
import io.ktor.http.Url
|
import java.net.URI
|
||||||
import io.ktor.http.decodeURLPart
|
|
||||||
import javax.crypto.Cipher
|
import javax.crypto.Cipher
|
||||||
import javax.crypto.spec.GCMParameterSpec
|
import javax.crypto.spec.GCMParameterSpec
|
||||||
import javax.crypto.spec.SecretKeySpec
|
import javax.crypto.spec.SecretKeySpec
|
||||||
|
|
@ -46,11 +45,11 @@ open class ByseSX : ExtractorApi() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getBaseUrl(url: String): String {
|
private fun getBaseUrl(url: String): String {
|
||||||
return Url(url).let { "${it.protocol.name}://${it.host}" }
|
return URI(url).let { "${it.scheme}://${it.host}" }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getCodeFromUrl(url: String): String {
|
private fun getCodeFromUrl(url: String): String {
|
||||||
val path = Url(url).encodedPath.decodeURLPart()
|
val path = URI(url).path ?: ""
|
||||||
return path.trimEnd('/').substringAfterLast('/')
|
return path.trimEnd('/').substringAfterLast('/')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorApi
|
import com.lagradost.cloudstream3.utils.ExtractorApi
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
import com.lagradost.cloudstream3.utils.Qualities
|
import com.lagradost.cloudstream3.utils.Qualities
|
||||||
import com.lagradost.cloudstream3.utils.StringUtils.decodeUrl
|
import com.lagradost.cloudstream3.utils.StringUtils.decodeUri
|
||||||
import com.lagradost.cloudstream3.utils.newExtractorLink
|
import com.lagradost.cloudstream3.utils.newExtractorLink
|
||||||
|
|
||||||
open class Cda : ExtractorApi() {
|
open class Cda : ExtractorApi() {
|
||||||
|
|
@ -64,7 +64,7 @@ open class Cda : ExtractorApi() {
|
||||||
.replace("_QWE", "")
|
.replace("_QWE", "")
|
||||||
.replace("_Q5", "")
|
.replace("_Q5", "")
|
||||||
.replace("_IKSDE", "")
|
.replace("_IKSDE", "")
|
||||||
a = a.decodeUrl()
|
a = a.decodeUri()
|
||||||
a = a.map { char ->
|
a = a.map { char ->
|
||||||
if (char.code in 33..126) {
|
if (char.code in 33..126) {
|
||||||
return@map (33 + (char.code + 14) % 94).toChar().toString()
|
return@map (33 + (char.code + 14) % 94).toChar().toString()
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import com.lagradost.cloudstream3.SubtitleFile
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorApi
|
import com.lagradost.cloudstream3.utils.ExtractorApi
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
import com.lagradost.cloudstream3.utils.loadExtractor
|
import com.lagradost.cloudstream3.utils.loadExtractor
|
||||||
import io.ktor.http.Url
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
|
|
||||||
// deobfuscated from https://hglink.to/main.js?v=1.1.3 using https://deobfuscate.io/
|
// deobfuscated from https://hglink.to/main.js?v=1.1.3 using https://deobfuscate.io/
|
||||||
private val mirrors = arrayOf(
|
private val mirrors = arrayOf(
|
||||||
|
|
@ -90,7 +90,7 @@ abstract class CineMMRedirect : ExtractorApi() {
|
||||||
subtitleCallback: (SubtitleFile) -> Unit,
|
subtitleCallback: (SubtitleFile) -> Unit,
|
||||||
callback: (ExtractorLink) -> Unit
|
callback: (ExtractorLink) -> Unit
|
||||||
) {
|
) {
|
||||||
val videoId = Url(url).encodedPath
|
val videoId = url.toHttpUrl().encodedPath
|
||||||
val mirror = mirrors.random()
|
val mirror = mirrors.random()
|
||||||
|
|
||||||
// re-use existing extractors by calling the ExtractorApi
|
// re-use existing extractors by calling the ExtractorApi
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,9 @@ import com.lagradost.cloudstream3.newSubtitleFile
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorApi
|
import com.lagradost.cloudstream3.utils.ExtractorApi
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
import com.lagradost.cloudstream3.utils.M3u8Helper.Companion.generateM3u8
|
import com.lagradost.cloudstream3.utils.M3u8Helper.Companion.generateM3u8
|
||||||
import io.ktor.http.Url
|
import java.net.URI
|
||||||
import io.ktor.http.decodeURLPart
|
|
||||||
|
|
||||||
|
|
||||||
class Geodailymotion : Dailymotion() {
|
class Geodailymotion : Dailymotion() {
|
||||||
override val name = "GeoDailymotion"
|
override val name = "GeoDailymotion"
|
||||||
|
|
@ -56,6 +57,7 @@ open class Dailymotion : ExtractorApi() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun getEmbedUrl(url: String): String? {
|
private fun getEmbedUrl(url: String): String? {
|
||||||
if (url.contains("/embed/") || url.contains("/video/")) return url
|
if (url.contains("/embed/") || url.contains("/video/")) return url
|
||||||
if (url.contains("geo.dailymotion.com")) {
|
if (url.contains("geo.dailymotion.com")) {
|
||||||
|
|
@ -65,8 +67,9 @@ open class Dailymotion : ExtractorApi() {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun getVideoId(url: String): String? {
|
private fun getVideoId(url: String): String? {
|
||||||
val path = Url(url).encodedPath.decodeURLPart()
|
val path = URI(url).path
|
||||||
val id = path.substringAfter("/video/")
|
val id = path.substringAfter("/video/")
|
||||||
return if (id.matches(videoIdRegex)) id else null
|
return if (id.matches(videoIdRegex)) id else null
|
||||||
}
|
}
|
||||||
|
|
@ -79,6 +82,7 @@ open class Dailymotion : ExtractorApi() {
|
||||||
return generateM3u8(name, streamLink, "").forEach(callback)
|
return generateM3u8(name, streamLink, "").forEach(callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
data class MetaData(
|
data class MetaData(
|
||||||
val qualities: Map<String, List<Quality>>?,
|
val qualities: Map<String, List<Quality>>?,
|
||||||
val subtitles: SubtitlesWrapper?
|
val subtitles: SubtitlesWrapper?
|
||||||
|
|
@ -98,4 +102,5 @@ open class Dailymotion : ExtractorApi() {
|
||||||
val label: String,
|
val label: String,
|
||||||
val urls: List<String>
|
val urls: List<String>
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -7,7 +7,7 @@ import com.lagradost.cloudstream3.utils.ExtractorApi
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
import com.lagradost.cloudstream3.utils.getQualityFromName
|
import com.lagradost.cloudstream3.utils.getQualityFromName
|
||||||
import com.lagradost.cloudstream3.utils.newExtractorLink
|
import com.lagradost.cloudstream3.utils.newExtractorLink
|
||||||
import io.ktor.http.Url
|
import java.net.URI
|
||||||
|
|
||||||
class Doodspro : DoodLaExtractor() {
|
class Doodspro : DoodLaExtractor() {
|
||||||
override var mainUrl = "https://doods.pro"
|
override var mainUrl = "https://doods.pro"
|
||||||
|
|
@ -138,6 +138,8 @@ open class DoodLaExtractor : ExtractorApi() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getBaseUrl(url: String): String {
|
private fun getBaseUrl(url: String): String {
|
||||||
return Url(url).let { "${it.protocol.name}://${it.host}" }
|
return URI(url).let {
|
||||||
|
"${it.scheme}://${it.host}"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -8,7 +8,7 @@ import com.lagradost.cloudstream3.base64Decode
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorApi
|
import com.lagradost.cloudstream3.utils.ExtractorApi
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
import com.lagradost.cloudstream3.utils.loadExtractor
|
import com.lagradost.cloudstream3.utils.loadExtractor
|
||||||
import io.ktor.http.Url
|
import java.net.URI
|
||||||
|
|
||||||
class Techinmind: GDMirrorbot() {
|
class Techinmind: GDMirrorbot() {
|
||||||
override var name = "Techinmind Cloud AIO"
|
override var name = "Techinmind Cloud AIO"
|
||||||
|
|
@ -103,7 +103,7 @@ open class GDMirrorbot : ExtractorApi() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getBaseUrl(url: String): String {
|
private fun getBaseUrl(url: String): String {
|
||||||
return Url(url).let { "${it.protocol.name}://${it.host}" }
|
return URI(url).let { "${it.scheme}://${it.host}" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
import com.lagradost.cloudstream3.utils.Qualities
|
import com.lagradost.cloudstream3.utils.Qualities
|
||||||
import com.lagradost.cloudstream3.utils.loadExtractor
|
import com.lagradost.cloudstream3.utils.loadExtractor
|
||||||
import com.lagradost.cloudstream3.utils.newExtractorLink
|
import com.lagradost.cloudstream3.utils.newExtractorLink
|
||||||
import io.ktor.http.Url
|
import java.net.URI
|
||||||
|
|
||||||
class HubCloud : ExtractorApi() {
|
class HubCloud : ExtractorApi() {
|
||||||
override val name = "Hub-Cloud"
|
override val name = "Hub-Cloud"
|
||||||
|
|
@ -24,7 +24,7 @@ class HubCloud : ExtractorApi() {
|
||||||
) {
|
) {
|
||||||
val tag = "HubCloud"
|
val tag = "HubCloud"
|
||||||
val realUrl = url.takeIf {
|
val realUrl = url.takeIf {
|
||||||
try { Url(it); true } catch (e: Exception) { Log.e(tag, "Invalid URL: ${e.message}"); false }
|
try { URI(it).toURL(); true } catch (e: Exception) { Log.e(tag, "Invalid URL: ${e.message}"); false }
|
||||||
} ?: return
|
} ?: return
|
||||||
|
|
||||||
val baseUrl=getBaseUrl(realUrl)
|
val baseUrl=getBaseUrl(realUrl)
|
||||||
|
|
@ -161,7 +161,7 @@ class HubCloud : ExtractorApi() {
|
||||||
|
|
||||||
private fun getBaseUrl(url: String): String {
|
private fun getBaseUrl(url: String): String {
|
||||||
return try {
|
return try {
|
||||||
Url(url).let { "${it.protocol.name}://${it.host}" }
|
URI(url).let { "${it.scheme}://${it.host}" }
|
||||||
} catch (_: Exception) {
|
} catch (_: Exception) {
|
||||||
""
|
""
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import com.lagradost.cloudstream3.newSubtitleFile
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorApi
|
import com.lagradost.cloudstream3.utils.ExtractorApi
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
import com.lagradost.cloudstream3.utils.Qualities
|
import com.lagradost.cloudstream3.utils.Qualities
|
||||||
import com.lagradost.cloudstream3.utils.StringUtils.decodeUrl
|
import com.lagradost.cloudstream3.utils.StringUtils.decodeUri
|
||||||
import com.lagradost.cloudstream3.utils.newExtractorLink
|
import com.lagradost.cloudstream3.utils.newExtractorLink
|
||||||
import org.jsoup.nodes.Document
|
import org.jsoup.nodes.Document
|
||||||
|
|
||||||
|
|
@ -96,7 +96,7 @@ open class InternetArchive : ExtractorApi() {
|
||||||
if (mediaUrl.isNotEmpty()) {
|
if (mediaUrl.isNotEmpty()) {
|
||||||
val name = if (mediaUrl.count() > 1) {
|
val name = if (mediaUrl.count() > 1) {
|
||||||
val fileExtension = mediaUrl.substringAfterLast(".")
|
val fileExtension = mediaUrl.substringAfterLast(".")
|
||||||
val fileNameCleaned = fileName.decodeUrl().substringBeforeLast('.')
|
val fileNameCleaned = fileName.decodeUri().substringBeforeLast('.')
|
||||||
"$fileNameCleaned ($fileExtension)"
|
"$fileNameCleaned ($fileExtension)"
|
||||||
} else this.name
|
} else this.name
|
||||||
callback(
|
callback(
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import com.lagradost.cloudstream3.SubtitleFile
|
||||||
import com.lagradost.cloudstream3.app
|
import com.lagradost.cloudstream3.app
|
||||||
import com.lagradost.cloudstream3.utils.*
|
import com.lagradost.cloudstream3.utils.*
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
|
import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
|
||||||
import io.ktor.http.Url
|
import java.net.URI
|
||||||
|
|
||||||
open class Streamplay : ExtractorApi() {
|
open class Streamplay : ExtractorApi() {
|
||||||
override val name = "Streamplay"
|
override val name = "Streamplay"
|
||||||
|
|
@ -22,7 +22,9 @@ open class Streamplay : ExtractorApi() {
|
||||||
) {
|
) {
|
||||||
val request = app.get(url, referer = referer)
|
val request = app.get(url, referer = referer)
|
||||||
val redirectUrl = request.url
|
val redirectUrl = request.url
|
||||||
val mainServer = Url(redirectUrl).let { "${it.protocol.name}://${it.host}" }
|
val mainServer = URI(redirectUrl).let {
|
||||||
|
"${it.scheme}://${it.host}"
|
||||||
|
}
|
||||||
val key = redirectUrl.substringAfter("embed-").substringBefore(".html")
|
val key = redirectUrl.substringAfter("embed-").substringBefore(".html")
|
||||||
val token =
|
val token =
|
||||||
request.document.select("script").find { it.data().contains("sitekey:") }?.data()
|
request.document.select("script").find { it.data().contains("sitekey:") }?.data()
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import com.lagradost.cloudstream3.utils.ExtractorLinkType
|
||||||
import com.lagradost.cloudstream3.utils.Qualities
|
import com.lagradost.cloudstream3.utils.Qualities
|
||||||
import com.lagradost.cloudstream3.utils.fixUrl
|
import com.lagradost.cloudstream3.utils.fixUrl
|
||||||
import com.lagradost.cloudstream3.utils.newExtractorLink
|
import com.lagradost.cloudstream3.utils.newExtractorLink
|
||||||
import io.ktor.http.Url
|
import java.net.URI
|
||||||
import javax.crypto.Cipher
|
import javax.crypto.Cipher
|
||||||
import javax.crypto.spec.IvParameterSpec
|
import javax.crypto.spec.IvParameterSpec
|
||||||
import javax.crypto.spec.SecretKeySpec
|
import javax.crypto.spec.SecretKeySpec
|
||||||
|
|
@ -84,7 +84,7 @@ open class VidStack : ExtractorApi() {
|
||||||
|
|
||||||
private fun getBaseUrl(url: String): String {
|
private fun getBaseUrl(url: String): String {
|
||||||
return try {
|
return try {
|
||||||
Url(url).let { "${it.protocol.name}://${it.host}" }
|
URI(url).let { "${it.scheme}://${it.host}" }
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("Vidstack", "getBaseUrl fallback: ${e.message}")
|
Log.e("Vidstack", "getBaseUrl fallback: ${e.message}")
|
||||||
mainUrl
|
mainUrl
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,8 @@ import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
import com.lagradost.cloudstream3.utils.M3u8Helper
|
import com.lagradost.cloudstream3.utils.M3u8Helper
|
||||||
import com.lagradost.cloudstream3.utils.getQualityFromName
|
import com.lagradost.cloudstream3.utils.getQualityFromName
|
||||||
import com.lagradost.cloudstream3.utils.newExtractorLink
|
import com.lagradost.cloudstream3.utils.newExtractorLink
|
||||||
import io.ktor.http.Url
|
|
||||||
import org.jsoup.nodes.Document
|
import org.jsoup.nodes.Document
|
||||||
|
import java.net.URI
|
||||||
import javax.crypto.Cipher
|
import javax.crypto.Cipher
|
||||||
import javax.crypto.spec.IvParameterSpec
|
import javax.crypto.spec.IvParameterSpec
|
||||||
import javax.crypto.spec.SecretKeySpec
|
import javax.crypto.spec.SecretKeySpec
|
||||||
|
|
@ -88,8 +88,8 @@ object GogoHelper {
|
||||||
val foundKey = secretKey ?: getKey(base64Decode(id) + foundIv) ?: return@safeApiCall
|
val foundKey = secretKey ?: getKey(base64Decode(id) + foundIv) ?: return@safeApiCall
|
||||||
val foundDecryptKey = secretDecryptKey ?: foundKey
|
val foundDecryptKey = secretDecryptKey ?: foundKey
|
||||||
|
|
||||||
val url = Url(iframeUrl)
|
val uri = URI(iframeUrl)
|
||||||
val mainUrl = "https://${url.host}"
|
val mainUrl = "https://" + uri.host
|
||||||
|
|
||||||
val encryptedId = cryptoHandler(id, foundIv, foundKey)
|
val encryptedId = cryptoHandler(id, foundIv, foundKey)
|
||||||
val encryptRequestData = if (isUsingAdaptiveData) {
|
val encryptRequestData = if (isUsingAdaptiveData) {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package com.lagradost.cloudstream3.extractors.helper
|
package com.lagradost.cloudstream3.extractors.helper
|
||||||
|
|
||||||
import com.lagradost.cloudstream3.utils.StringUtils.decodeUrl
|
import com.lagradost.cloudstream3.utils.StringUtils.decodeUri
|
||||||
import com.lagradost.cloudstream3.utils.StringUtils.encodeUrl
|
import com.lagradost.cloudstream3.utils.StringUtils.encodeUri
|
||||||
|
|
||||||
// Taken from https://github.com/saikou-app/saikou/blob/b35364c8c2a00364178a472fccf1ab72f09815b4/app/src/main/java/ani/saikou/parsers/anime/NineAnime.kt
|
// Taken from https://github.com/saikou-app/saikou/blob/b35364c8c2a00364178a472fccf1ab72f09815b4/app/src/main/java/ani/saikou/parsers/anime/NineAnime.kt
|
||||||
// GNU General Public License v3.0 https://github.com/saikou-app/saikou/blob/main/LICENSE.md
|
// GNU General Public License v3.0 https://github.com/saikou-app/saikou/blob/main/LICENSE.md
|
||||||
|
|
@ -108,6 +108,8 @@ object NineAnimeHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun encode(input: String): String = input.encodeUrl()
|
fun encode(input: String): String =
|
||||||
private fun decode(input: String): String = input.decodeUrl()
|
input.encodeUri().replace("+", "%20")
|
||||||
|
|
||||||
|
private fun decode(input: String): String = input.decodeUri()
|
||||||
}
|
}
|
||||||
|
|
@ -60,9 +60,8 @@ object AppUtils {
|
||||||
inline fun <reified T : Any> parseJson(value: String): T {
|
inline fun <reified T : Any> parseJson(value: String): T {
|
||||||
// @Serializable generates a serializer at compile time; contextual serializers are
|
// @Serializable generates a serializer at compile time; contextual serializers are
|
||||||
// registered manually in serializersModule, we need both to support all cases
|
// registered manually in serializersModule, we need both to support all cases
|
||||||
val serializer = runCatching { serializer<T>() }
|
val serializer = runCatching { serializer<T>() }.getOrNull()
|
||||||
.recoverCatching { json.serializersModule.getContextual(T::class) }
|
?: json.serializersModule.getContextual(T::class)
|
||||||
.getOrNull()
|
|
||||||
|
|
||||||
// Prefer Kotlin Serialization over Jackson
|
// Prefer Kotlin Serialization over Jackson
|
||||||
if (serializer != null) {
|
if (serializer != null) {
|
||||||
|
|
@ -70,8 +69,6 @@ object AppUtils {
|
||||||
return json.decodeFromString(serializer, value)
|
return json.decodeFromString(serializer, value)
|
||||||
} catch (e: SerializationException) {
|
} catch (e: SerializationException) {
|
||||||
logError(e)
|
logError(e)
|
||||||
} catch (_: Throwable) {
|
|
||||||
// Pass, the above code will trigger a NoSuchMethodError on stable due to our previously undefined json variable
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,6 @@ import com.lagradost.cloudstream3.extractors.FilemoonV2
|
||||||
import com.lagradost.cloudstream3.extractors.Filesim
|
import com.lagradost.cloudstream3.extractors.Filesim
|
||||||
import com.lagradost.cloudstream3.extractors.Multimoviesshg
|
import com.lagradost.cloudstream3.extractors.Multimoviesshg
|
||||||
import com.lagradost.cloudstream3.extractors.FlaswishCom
|
import com.lagradost.cloudstream3.extractors.FlaswishCom
|
||||||
import com.lagradost.cloudstream3.extractors.Flyfile
|
|
||||||
import com.lagradost.cloudstream3.extractors.FourCX
|
import com.lagradost.cloudstream3.extractors.FourCX
|
||||||
import com.lagradost.cloudstream3.extractors.FourPichive
|
import com.lagradost.cloudstream3.extractors.FourPichive
|
||||||
import com.lagradost.cloudstream3.extractors.FourPlayRu
|
import com.lagradost.cloudstream3.extractors.FourPlayRu
|
||||||
|
|
@ -313,12 +312,11 @@ import com.lagradost.cloudstream3.extractors.ZplayerV2
|
||||||
import com.lagradost.cloudstream3.extractors.Ztreamhub
|
import com.lagradost.cloudstream3.extractors.Ztreamhub
|
||||||
import com.lagradost.cloudstream3.mvvm.logError
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
import com.lagradost.cloudstream3.utils.Coroutines.atomicListOf
|
import com.lagradost.cloudstream3.utils.Coroutines.atomicListOf
|
||||||
import io.ktor.http.Url
|
|
||||||
import io.ktor.http.decodeURLPart
|
|
||||||
import kotlinx.coroutines.coroutineScope
|
import kotlinx.coroutines.coroutineScope
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.ensureActive
|
import kotlinx.coroutines.ensureActive
|
||||||
import org.jsoup.Jsoup
|
import org.jsoup.Jsoup
|
||||||
|
import java.net.URI
|
||||||
import kotlin.coroutines.cancellation.CancellationException
|
import kotlin.coroutines.cancellation.CancellationException
|
||||||
import kotlin.uuid.ExperimentalUuidApi
|
import kotlin.uuid.ExperimentalUuidApi
|
||||||
import kotlin.uuid.Uuid
|
import kotlin.uuid.Uuid
|
||||||
|
|
@ -422,7 +420,7 @@ enum class ExtractorLinkType {
|
||||||
|
|
||||||
private fun inferTypeFromUrl(url: String): ExtractorLinkType {
|
private fun inferTypeFromUrl(url: String): ExtractorLinkType {
|
||||||
val path = try {
|
val path = try {
|
||||||
Url(url).encodedPath.decodeURLPart()
|
URI(url).path
|
||||||
} catch (_: Throwable) {
|
} catch (_: Throwable) {
|
||||||
// don't log magnet links as errors
|
// don't log magnet links as errors
|
||||||
null
|
null
|
||||||
|
|
@ -821,7 +819,7 @@ constructor(
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes https:// and www.
|
* Removes https:// and www.
|
||||||
* To match urls regardless of schema, perhaps Url() can be used?
|
* To match urls regardless of schema, perhaps Uri() can be used?
|
||||||
*/
|
*/
|
||||||
val schemaStripRegex = Regex("""^(https:|)//(www\.|)""")
|
val schemaStripRegex = Regex("""^(https:|)//(www\.|)""")
|
||||||
|
|
||||||
|
|
@ -1299,7 +1297,6 @@ val extractorApis: AtomicMutableList<ExtractorApi> = atomicListOf(
|
||||||
GUpload(),
|
GUpload(),
|
||||||
HlsWish(),
|
HlsWish(),
|
||||||
ByseQekaho(),
|
ByseQekaho(),
|
||||||
Flyfile()
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,11 +19,12 @@
|
||||||
*/
|
*/
|
||||||
package com.lagradost.cloudstream3.utils
|
package com.lagradost.cloudstream3.utils
|
||||||
|
|
||||||
import com.lagradost.cloudstream3.base64DecodeArray
|
|
||||||
import io.ktor.http.Url
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
import java.net.URI
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
import kotlin.io.encoding.Base64
|
||||||
|
import kotlin.io.encoding.ExperimentalEncodingApi
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
object HlsPlaylistParser {
|
object HlsPlaylistParser {
|
||||||
|
|
@ -275,29 +276,29 @@ object HlsPlaylistParser {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object UrlUtil {
|
object UriUtil {
|
||||||
fun resolveToUrl(baseUrl: String?, referenceUrl: String?): Url {
|
fun resolveToUri(baseUri: String?, referenceUri: String?): URI {
|
||||||
return Url(resolve(baseUrl, referenceUrl))
|
return URI.create(resolve(baseUri, referenceUri))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/** The length of arrays returned by [.getUrlIndices]. */
|
/** The length of arrays returned by [.getUriIndices]. */
|
||||||
private
|
private
|
||||||
const val INDEX_COUNT: Int = 4
|
const val INDEX_COUNT: Int = 4
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An index into an array returned by [.getUrlIndices].
|
* An index into an array returned by [.getUriIndices].
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* The value at this position in the array is the index of the ':' after the scheme. Equals -1
|
* The value at this position in the array is the index of the ':' after the scheme. Equals -1
|
||||||
* if the URL is a relative reference (no scheme). The hier-part starts at (schemeColon + 1),
|
* if the URI is a relative reference (no scheme). The hier-part starts at (schemeColon + 1),
|
||||||
* including when the URL has no scheme.
|
* including when the URI has no scheme.
|
||||||
*/
|
*/
|
||||||
private
|
private
|
||||||
const val SCHEME_COLON: Int = 0
|
const val SCHEME_COLON: Int = 0
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An index into an array returned by [.getUrlIndices].
|
* An index into an array returned by [.getUriIndices].
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* The value at this position in the array is the index of the path part. Equals (schemeColon +
|
* The value at this position in the array is the index of the path part. Equals (schemeColon +
|
||||||
|
|
@ -309,7 +310,7 @@ object HlsPlaylistParser {
|
||||||
const val PATH: Int = 1
|
const val PATH: Int = 1
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An index into an array returned by [.getUrlIndices].
|
* An index into an array returned by [.getUriIndices].
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* The value at this position in the array is the index of the query part, including the '?'
|
* The value at this position in the array is the index of the query part, including the '?'
|
||||||
|
|
@ -320,87 +321,87 @@ object HlsPlaylistParser {
|
||||||
const val QUERY: Int = 2
|
const val QUERY: Int = 2
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An index into an array returned by [.getUrlIndices].
|
* An index into an array returned by [.getUriIndices].
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* The value at this position in the array is the index of the fragment part, including the '#'
|
* The value at this position in the array is the index of the fragment part, including the '#'
|
||||||
* before the fragment. Equal to the length of the URL if no fragment part, and (length - 1) if
|
* before the fragment. Equal to the length of the URI if no fragment part, and (length - 1) if
|
||||||
* the fragment part is a single '#' with no data.
|
* the fragment part is a single '#' with no data.
|
||||||
*/
|
*/
|
||||||
private
|
private
|
||||||
const val FRAGMENT: Int = 3
|
const val FRAGMENT: Int = 3
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs relative resolution of a `referenceUrl` with respect to a `baseUrl`.
|
* Performs relative resolution of a `referenceUri` with respect to a `baseUri`.
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* The resolution is performed as specified by RFC-3986.
|
* The resolution is performed as specified by RFC-3986.
|
||||||
*
|
*
|
||||||
* @param baseUrl The base URL.
|
* @param baseUri The base URI.
|
||||||
* @param referenceUrl The reference URL to resolve.
|
* @param referenceUri The reference URI to resolve.
|
||||||
*/
|
*/
|
||||||
private fun resolve(baseUrl: String?, referenceUrl: String?): String {
|
private fun resolve(baseUri: String?, referenceUri: String?): String {
|
||||||
var baseUrl = baseUrl
|
var baseUri = baseUri
|
||||||
var referenceUrl = referenceUrl
|
var referenceUri = referenceUri
|
||||||
val url = StringBuilder()
|
val uri = StringBuilder()
|
||||||
|
|
||||||
// Map null onto empty string, to make the following logic simpler.
|
// Map null onto empty string, to make the following logic simpler.
|
||||||
baseUrl = baseUrl ?: ""
|
baseUri = baseUri ?: ""
|
||||||
referenceUrl = referenceUrl ?: ""
|
referenceUri = referenceUri ?: ""
|
||||||
|
|
||||||
val refIndices: IntArray = getUrlIndices(referenceUrl)
|
val refIndices: IntArray = getUriIndices(referenceUri)
|
||||||
if (refIndices[SCHEME_COLON] != -1) {
|
if (refIndices[SCHEME_COLON] != -1) {
|
||||||
// The reference is absolute. The target Url is the reference.
|
// The reference is absolute. The target Uri is the reference.
|
||||||
url.append(referenceUrl)
|
uri.append(referenceUri)
|
||||||
removeDotSegments(url, refIndices[PATH], refIndices[QUERY])
|
removeDotSegments(uri, refIndices[PATH], refIndices[QUERY])
|
||||||
return url.toString()
|
return uri.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
val baseIndices: IntArray = getUrlIndices(baseUrl)
|
val baseIndices: IntArray = getUriIndices(baseUri)
|
||||||
if (refIndices[FRAGMENT] == 0) {
|
if (refIndices[FRAGMENT] == 0) {
|
||||||
// The reference is empty or contains just the fragment part, then the target Url is the
|
// The reference is empty or contains just the fragment part, then the target Uri is the
|
||||||
// concatenation of the base Url without its fragment, and the reference.
|
// concatenation of the base Uri without its fragment, and the reference.
|
||||||
return url.append(baseUrl, 0, baseIndices[FRAGMENT]).append(referenceUrl).toString()
|
return uri.append(baseUri, 0, baseIndices[FRAGMENT]).append(referenceUri).toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (refIndices[QUERY] == 0) {
|
if (refIndices[QUERY] == 0) {
|
||||||
// The reference starts with the query part. The target is the base up to (but excluding) the
|
// The reference starts with the query part. The target is the base up to (but excluding) the
|
||||||
// query, plus the reference.
|
// query, plus the reference.
|
||||||
return url.append(baseUrl, 0, baseIndices[QUERY]).append(referenceUrl).toString()
|
return uri.append(baseUri, 0, baseIndices[QUERY]).append(referenceUri).toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (refIndices[PATH] != 0) {
|
if (refIndices[PATH] != 0) {
|
||||||
// The reference has authority. The target is the base scheme plus the reference.
|
// The reference has authority. The target is the base scheme plus the reference.
|
||||||
val baseLimit = baseIndices[SCHEME_COLON] + 1
|
val baseLimit = baseIndices[SCHEME_COLON] + 1
|
||||||
url.append(baseUrl, 0, baseLimit).append(referenceUrl)
|
uri.append(baseUri, 0, baseLimit).append(referenceUri)
|
||||||
return removeDotSegments(
|
return removeDotSegments(
|
||||||
url,
|
uri,
|
||||||
baseLimit + refIndices[PATH],
|
baseLimit + refIndices[PATH],
|
||||||
baseLimit + refIndices[QUERY]
|
baseLimit + refIndices[QUERY]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (referenceUrl[refIndices[PATH]] == '/') {
|
if (referenceUri[refIndices[PATH]] == '/') {
|
||||||
// The reference path is rooted. The target is the base scheme and authority (if any), plus
|
// The reference path is rooted. The target is the base scheme and authority (if any), plus
|
||||||
// the reference.
|
// the reference.
|
||||||
url.append(baseUrl, 0, baseIndices[PATH]).append(referenceUrl)
|
uri.append(baseUri, 0, baseIndices[PATH]).append(referenceUri)
|
||||||
return removeDotSegments(
|
return removeDotSegments(
|
||||||
url,
|
uri,
|
||||||
baseIndices[PATH],
|
baseIndices[PATH],
|
||||||
baseIndices[PATH] + refIndices[QUERY]
|
baseIndices[PATH] + refIndices[QUERY]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// The target Url is the concatenation of the base Url up to (but excluding) the last segment,
|
// The target Uri is the concatenation of the base Uri up to (but excluding) the last segment,
|
||||||
// and the reference. This can be split into 2 cases:
|
// and the reference. This can be split into 2 cases:
|
||||||
if (baseIndices[SCHEME_COLON] + 2 < baseIndices[PATH]
|
if (baseIndices[SCHEME_COLON] + 2 < baseIndices[PATH]
|
||||||
&& baseIndices[PATH] == baseIndices[QUERY]
|
&& baseIndices[PATH] == baseIndices[QUERY]
|
||||||
) {
|
) {
|
||||||
// Case 1: The base hier-part is just the authority, with an empty path. An additional '/' is
|
// Case 1: The base hier-part is just the authority, with an empty path. An additional '/' is
|
||||||
// needed after the authority, before appending the reference.
|
// needed after the authority, before appending the reference.
|
||||||
url.append(baseUrl, 0, baseIndices[PATH]).append('/').append(referenceUrl)
|
uri.append(baseUri, 0, baseIndices[PATH]).append('/').append(referenceUri)
|
||||||
return removeDotSegments(
|
return removeDotSegments(
|
||||||
url,
|
uri,
|
||||||
baseIndices[PATH],
|
baseIndices[PATH],
|
||||||
baseIndices[PATH] + refIndices[QUERY] + 1
|
baseIndices[PATH] + refIndices[QUERY] + 1
|
||||||
)
|
)
|
||||||
|
|
@ -409,22 +410,22 @@ object HlsPlaylistParser {
|
||||||
// it. If base hier-part has no '/', it could only mean that it is completely empty or
|
// it. If base hier-part has no '/', it could only mean that it is completely empty or
|
||||||
// contains only one segment, in which case the whole hier-part is excluded and the reference
|
// contains only one segment, in which case the whole hier-part is excluded and the reference
|
||||||
// is appended right after the base scheme colon without an added '/'.
|
// is appended right after the base scheme colon without an added '/'.
|
||||||
val lastSlashIndex = baseUrl.lastIndexOf('/', baseIndices[QUERY] - 1)
|
val lastSlashIndex = baseUri.lastIndexOf('/', baseIndices[QUERY] - 1)
|
||||||
val baseLimit = if (lastSlashIndex == -1) baseIndices[PATH] else lastSlashIndex + 1
|
val baseLimit = if (lastSlashIndex == -1) baseIndices[PATH] else lastSlashIndex + 1
|
||||||
url.append(baseUrl, 0, baseLimit).append(referenceUrl)
|
uri.append(baseUri, 0, baseLimit).append(referenceUri)
|
||||||
return removeDotSegments(url, baseIndices[PATH], baseLimit + refIndices[QUERY])
|
return removeDotSegments(uri, baseIndices[PATH], baseLimit + refIndices[QUERY])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes dot segments from the path of a URL.
|
* Removes dot segments from the path of a URI.
|
||||||
*
|
*
|
||||||
* @param url A [StringBuilder] containing the URL.
|
* @param uri A [StringBuilder] containing the URI.
|
||||||
* @param offset The index of the start of the path in `url`.
|
* @param offset The index of the start of the path in `uri`.
|
||||||
* @param limit The limit (exclusive) of the path in `url`.
|
* @param limit The limit (exclusive) of the path in `uri`.
|
||||||
*/
|
*/
|
||||||
private fun removeDotSegments(
|
private fun removeDotSegments(
|
||||||
url: StringBuilder,
|
uri: StringBuilder,
|
||||||
offset: Int,
|
offset: Int,
|
||||||
limit: Int
|
limit: Int
|
||||||
): String {
|
): String {
|
||||||
|
|
@ -432,9 +433,9 @@ object HlsPlaylistParser {
|
||||||
var limit = limit
|
var limit = limit
|
||||||
if (offset >= limit) {
|
if (offset >= limit) {
|
||||||
// Nothing to do.
|
// Nothing to do.
|
||||||
return url.toString()
|
return uri.toString()
|
||||||
}
|
}
|
||||||
if (url[offset] == '/') {
|
if (uri[offset] == '/') {
|
||||||
// If the path starts with a /, always retain it.
|
// If the path starts with a /, always retain it.
|
||||||
offset++
|
offset++
|
||||||
}
|
}
|
||||||
|
|
@ -444,7 +445,7 @@ object HlsPlaylistParser {
|
||||||
while (i <= limit) {
|
while (i <= limit) {
|
||||||
val nextSegmentStart = if (i == limit) {
|
val nextSegmentStart = if (i == limit) {
|
||||||
i
|
i
|
||||||
} else if (url[i] == '/') {
|
} else if (uri[i] == '/') {
|
||||||
i + 1
|
i + 1
|
||||||
} else {
|
} else {
|
||||||
i++
|
i++
|
||||||
|
|
@ -452,16 +453,16 @@ object HlsPlaylistParser {
|
||||||
}
|
}
|
||||||
// We've encountered the end of a segment or the end of the path. If the final segment was
|
// We've encountered the end of a segment or the end of the path. If the final segment was
|
||||||
// "." or "..", remove the appropriate segments of the path.
|
// "." or "..", remove the appropriate segments of the path.
|
||||||
if (i == segmentStart + 1 && url[segmentStart] == '.') {
|
if (i == segmentStart + 1 && uri[segmentStart] == '.') {
|
||||||
// Given "abc/def/./ghi", remove "./" to get "abc/def/ghi".
|
// Given "abc/def/./ghi", remove "./" to get "abc/def/ghi".
|
||||||
url.delete(segmentStart, nextSegmentStart)
|
uri.delete(segmentStart, nextSegmentStart)
|
||||||
limit -= nextSegmentStart - segmentStart
|
limit -= nextSegmentStart - segmentStart
|
||||||
i = segmentStart
|
i = segmentStart
|
||||||
} else if (i == segmentStart + 2 && url[segmentStart] == '.' && url[segmentStart + 1] == '.') {
|
} else if (i == segmentStart + 2 && uri[segmentStart] == '.' && uri[segmentStart + 1] == '.') {
|
||||||
// Given "abc/def/../ghi", remove "def/../" to get "abc/ghi".
|
// Given "abc/def/../ghi", remove "def/../" to get "abc/ghi".
|
||||||
val prevSegmentStart = url.lastIndexOf("/", segmentStart - 2) + 1
|
val prevSegmentStart = uri.lastIndexOf("/", segmentStart - 2) + 1
|
||||||
val removeFrom = if (prevSegmentStart > offset) prevSegmentStart else offset
|
val removeFrom = if (prevSegmentStart > offset) prevSegmentStart else offset
|
||||||
url.delete(removeFrom, nextSegmentStart)
|
uri.delete(removeFrom, nextSegmentStart)
|
||||||
limit -= nextSegmentStart - removeFrom
|
limit -= nextSegmentStart - removeFrom
|
||||||
segmentStart = prevSegmentStart
|
segmentStart = prevSegmentStart
|
||||||
i = prevSegmentStart
|
i = prevSegmentStart
|
||||||
|
|
@ -470,41 +471,41 @@ object HlsPlaylistParser {
|
||||||
segmentStart = i
|
segmentStart = i
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return url.toString()
|
return uri.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculates indices of the constituent components of a URL.
|
* Calculates indices of the constituent components of a URI.
|
||||||
*
|
*
|
||||||
* @param urlString The URL as a string.
|
* @param uriString The URI as a string.
|
||||||
* @return The corresponding indices.
|
* @return The corresponding indices.
|
||||||
*/
|
*/
|
||||||
private fun getUrlIndices(urlString: String?): IntArray {
|
private fun getUriIndices(uriString: String?): IntArray {
|
||||||
val indices = IntArray(INDEX_COUNT)
|
val indices = IntArray(INDEX_COUNT)
|
||||||
if (urlString.isNullOrEmpty()) {
|
if (uriString.isNullOrEmpty()) {
|
||||||
indices[SCHEME_COLON] = -1
|
indices[SCHEME_COLON] = -1
|
||||||
return indices
|
return indices
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine outer structure from right to left.
|
// Determine outer structure from right to left.
|
||||||
// Url = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
|
// Uri = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
|
||||||
val length = urlString.length
|
val length = uriString.length
|
||||||
var fragmentIndex = urlString.indexOf('#')
|
var fragmentIndex = uriString.indexOf('#')
|
||||||
if (fragmentIndex == -1) {
|
if (fragmentIndex == -1) {
|
||||||
fragmentIndex = length
|
fragmentIndex = length
|
||||||
}
|
}
|
||||||
var queryIndex = urlString.indexOf('?')
|
var queryIndex = uriString.indexOf('?')
|
||||||
if (queryIndex == -1 || queryIndex > fragmentIndex) {
|
if (queryIndex == -1 || queryIndex > fragmentIndex) {
|
||||||
// '#' before '?': '?' is within the fragment.
|
// '#' before '?': '?' is within the fragment.
|
||||||
queryIndex = fragmentIndex
|
queryIndex = fragmentIndex
|
||||||
}
|
}
|
||||||
// Slashes are allowed only in hier-part so any colon after the first slash is part of the
|
// Slashes are allowed only in hier-part so any colon after the first slash is part of the
|
||||||
// hier-part, not the scheme colon separator.
|
// hier-part, not the scheme colon separator.
|
||||||
var schemeIndexLimit = urlString.indexOf('/')
|
var schemeIndexLimit = uriString.indexOf('/')
|
||||||
if (schemeIndexLimit == -1 || schemeIndexLimit > queryIndex) {
|
if (schemeIndexLimit == -1 || schemeIndexLimit > queryIndex) {
|
||||||
schemeIndexLimit = queryIndex
|
schemeIndexLimit = queryIndex
|
||||||
}
|
}
|
||||||
var schemeIndex = urlString.indexOf(':')
|
var schemeIndex = uriString.indexOf(':')
|
||||||
if (schemeIndex > schemeIndexLimit) {
|
if (schemeIndex > schemeIndexLimit) {
|
||||||
// '/' before ':'
|
// '/' before ':'
|
||||||
schemeIndex = -1
|
schemeIndex = -1
|
||||||
|
|
@ -513,10 +514,10 @@ object HlsPlaylistParser {
|
||||||
// Determine hier-part structure: hier-part = "//" authority path / path
|
// Determine hier-part structure: hier-part = "//" authority path / path
|
||||||
// This block can also cope with schemeIndex == -1.
|
// This block can also cope with schemeIndex == -1.
|
||||||
val hasAuthority =
|
val hasAuthority =
|
||||||
schemeIndex + 2 < queryIndex && urlString[schemeIndex + 1] == '/' && urlString[schemeIndex + 2] == '/'
|
schemeIndex + 2 < queryIndex && uriString[schemeIndex + 1] == '/' && uriString[schemeIndex + 2] == '/'
|
||||||
var pathIndex: Int
|
var pathIndex: Int
|
||||||
if (hasAuthority) {
|
if (hasAuthority) {
|
||||||
pathIndex = urlString.indexOf('/', schemeIndex + 3) // find first '/' after "://"
|
pathIndex = uriString.indexOf('/', schemeIndex + 3) // find first '/' after "://"
|
||||||
if (pathIndex == -1 || pathIndex > queryIndex) {
|
if (pathIndex == -1 || pathIndex > queryIndex) {
|
||||||
pathIndex = queryIndex
|
pathIndex = queryIndex
|
||||||
}
|
}
|
||||||
|
|
@ -805,7 +806,7 @@ object HlsPlaylistParser {
|
||||||
|
|
||||||
const val APPLICATION_MEDIA3_CUES: String = "$BASE_TYPE_APPLICATION/x-media3-cues"
|
const val APPLICATION_MEDIA3_CUES: String = "$BASE_TYPE_APPLICATION/x-media3-cues"
|
||||||
|
|
||||||
/** MIME type for an image URL loaded from an external image management framework. */
|
/** MIME type for an image URI loaded from an external image management framework. */
|
||||||
const val APPLICATION_EXTERNALLY_LOADED_IMAGE: String = "$BASE_TYPE_APPLICATION/x-image-uri"
|
const val APPLICATION_EXTERNALLY_LOADED_IMAGE: String = "$BASE_TYPE_APPLICATION/x-image-uri"
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1168,6 +1169,7 @@ object HlsPlaylistParser {
|
||||||
return parseOptionalStringAttr(line, pattern, null, variableDefinitions)
|
return parseOptionalStringAttr(line, pattern, null, variableDefinitions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalEncodingApi::class)
|
||||||
@Throws(ParserException::class)
|
@Throws(ParserException::class)
|
||||||
private fun parseDrmSchemeData(
|
private fun parseDrmSchemeData(
|
||||||
line: String, keyFormat: String, variableDefinitions: Map<String, String>
|
line: String, keyFormat: String, variableDefinitions: Map<String, String>
|
||||||
|
|
@ -1175,11 +1177,11 @@ object HlsPlaylistParser {
|
||||||
val keyFormatVersions =
|
val keyFormatVersions =
|
||||||
parseOptionalStringAttr(line, REGEX_KEYFORMATVERSIONS, "1", variableDefinitions)
|
parseOptionalStringAttr(line, REGEX_KEYFORMATVERSIONS, "1", variableDefinitions)
|
||||||
if (KEYFORMAT_WIDEVINE_PSSH_BINARY == keyFormat) {
|
if (KEYFORMAT_WIDEVINE_PSSH_BINARY == keyFormat) {
|
||||||
val urlString = parseStringAttr(line, REGEX_URI, variableDefinitions)
|
val uriString = parseStringAttr(line, REGEX_URI, variableDefinitions)
|
||||||
return SchemeData(
|
return SchemeData(
|
||||||
uuid = C.WIDEVINE_UUID,
|
uuid = C.WIDEVINE_UUID,
|
||||||
mimeType = MimeTypes.VIDEO_MP4,
|
mimeType = MimeTypes.VIDEO_MP4,
|
||||||
data = base64DecodeArray(urlString.substring(urlString.indexOf(',')))
|
data = Base64.Default.decode(uriString.substring(uriString.indexOf(',')))
|
||||||
)
|
)
|
||||||
} else if (KEYFORMAT_WIDEVINE_PSSH_JSON == keyFormat) {
|
} else if (KEYFORMAT_WIDEVINE_PSSH_JSON == keyFormat) {
|
||||||
return SchemeData(
|
return SchemeData(
|
||||||
|
|
@ -1188,9 +1190,9 @@ object HlsPlaylistParser {
|
||||||
data = line.encodeToByteArray()
|
data = line.encodeToByteArray()
|
||||||
)
|
)
|
||||||
} else if (KEYFORMAT_PLAYREADY == keyFormat && "1" == keyFormatVersions) {
|
} else if (KEYFORMAT_PLAYREADY == keyFormat && "1" == keyFormatVersions) {
|
||||||
val urlString = parseStringAttr(line, REGEX_URI, variableDefinitions)
|
val uriString = parseStringAttr(line, REGEX_URI, variableDefinitions)
|
||||||
val data: ByteArray =
|
val data: ByteArray =
|
||||||
base64DecodeArray(urlString.substring(urlString.indexOf(',')))
|
Base64.Default.decode(uriString.substring(uriString.indexOf(',')))
|
||||||
val psshData: ByteArray =
|
val psshData: ByteArray =
|
||||||
PsshAtomUtil.buildPsshAtom(
|
PsshAtomUtil.buildPsshAtom(
|
||||||
systemId = C.PLAYREADY_UUID,
|
systemId = C.PLAYREADY_UUID,
|
||||||
|
|
@ -1268,7 +1270,7 @@ object HlsPlaylistParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
data class Variant(
|
data class Variant(
|
||||||
val url: Url,
|
val url: URI,
|
||||||
val format: Format,
|
val format: Format,
|
||||||
val videoGroupId: String?,
|
val videoGroupId: String?,
|
||||||
val audioGroupId: String?,
|
val audioGroupId: String?,
|
||||||
|
|
@ -1321,7 +1323,7 @@ object HlsPlaylistParser {
|
||||||
|
|
||||||
data class Rendition(
|
data class Rendition(
|
||||||
/** The rendition's url, or null if the tag does not have a URI attribute. */
|
/** The rendition's url, or null if the tag does not have a URI attribute. */
|
||||||
val url: Url?,
|
val url: URI?,
|
||||||
|
|
||||||
/** Format information associated with this rendition. */
|
/** Format information associated with this rendition. */
|
||||||
val format: Format,
|
val format: Format,
|
||||||
|
|
@ -1334,14 +1336,14 @@ object HlsPlaylistParser {
|
||||||
)
|
)
|
||||||
|
|
||||||
data class HlsMultivariantPlaylist(
|
data class HlsMultivariantPlaylist(
|
||||||
/** The base url. Used to resolve relative paths. */
|
/** The base uri. Used to resolve relative paths. */
|
||||||
val baseUri: String,
|
val baseUri: String,
|
||||||
|
|
||||||
/** The list of tags in the playlist. */
|
/** The list of tags in the playlist. */
|
||||||
val tags: List<String>,
|
val tags: List<String>,
|
||||||
|
|
||||||
/** All of the media playlist URLs referenced by the playlist. */
|
/** All of the media playlist URLs referenced by the playlist. */
|
||||||
//val mediaPlaylistUrls: List<Url>,
|
//val mediaPlaylistUrls: List<URI>,
|
||||||
|
|
||||||
/** The variants declared by the playlist. */
|
/** The variants declared by the playlist. */
|
||||||
val variants: List<Variant>,
|
val variants: List<Variant>,
|
||||||
|
|
@ -1727,8 +1729,8 @@ object HlsPlaylistParser {
|
||||||
private fun parseMultivariantPlaylist(
|
private fun parseMultivariantPlaylist(
|
||||||
iterator: Iterator<String>, baseUri: String
|
iterator: Iterator<String>, baseUri: String
|
||||||
): HlsMultivariantPlaylist {
|
): HlsMultivariantPlaylist {
|
||||||
val urlToVariantInfos: HashMap<Url, ArrayList<VariantInfo>?> =
|
val urlToVariantInfos: HashMap<URI, ArrayList<VariantInfo>?> =
|
||||||
HashMap<Url, ArrayList<VariantInfo>?>()
|
HashMap<URI, ArrayList<VariantInfo>?>()
|
||||||
val variableDefinitions = HashMap<String, String>()
|
val variableDefinitions = HashMap<String, String>()
|
||||||
val variants: ArrayList<Variant> = ArrayList<Variant>()
|
val variants: ArrayList<Variant> = ArrayList<Variant>()
|
||||||
val videos: ArrayList<Rendition> = ArrayList<Rendition>()
|
val videos: ArrayList<Rendition> = ArrayList<Rendition>()
|
||||||
|
|
@ -1851,10 +1853,10 @@ object HlsPlaylistParser {
|
||||||
parseOptionalStringAttr(line, REGEX_SUBTITLES, variableDefinitions)
|
parseOptionalStringAttr(line, REGEX_SUBTITLES, variableDefinitions)
|
||||||
val closedCaptionsGroupId: String? =
|
val closedCaptionsGroupId: String? =
|
||||||
parseOptionalStringAttr(line, REGEX_CLOSED_CAPTIONS, variableDefinitions)
|
parseOptionalStringAttr(line, REGEX_CLOSED_CAPTIONS, variableDefinitions)
|
||||||
val url: Url
|
val uri: URI
|
||||||
if (isIFrameOnlyVariant) {
|
if (isIFrameOnlyVariant) {
|
||||||
url =
|
uri =
|
||||||
UrlUtil.resolveToUrl(
|
UriUtil.resolveToUri(
|
||||||
baseUri,
|
baseUri,
|
||||||
parseStringAttr(line, REGEX_URI, variableDefinitions)
|
parseStringAttr(line, REGEX_URI, variableDefinitions)
|
||||||
)
|
)
|
||||||
|
|
@ -1863,14 +1865,14 @@ object HlsPlaylistParser {
|
||||||
"#EXT-X-STREAM-INF must be followed by another line", /* cause= */null
|
"#EXT-X-STREAM-INF must be followed by another line", /* cause= */null
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
// The following line contains #EXT-X-STREAM-INF's URL.
|
// The following line contains #EXT-X-STREAM-INF's URI.
|
||||||
line = replaceVariableReferences(iterator.next(), variableDefinitions)
|
line = replaceVariableReferences(iterator.next(), variableDefinitions)
|
||||||
url = UrlUtil.resolveToUrl(baseUri, line)
|
uri = UriUtil.resolveToUri(baseUri, line)
|
||||||
}
|
}
|
||||||
|
|
||||||
val variant =
|
val variant =
|
||||||
Variant(
|
Variant(
|
||||||
url = url,
|
url = uri,
|
||||||
format = Format(
|
format = Format(
|
||||||
id = variants.size.toString(),
|
id = variants.size.toString(),
|
||||||
containerMimeType = MimeTypes.APPLICATION_M3U8,
|
containerMimeType = MimeTypes.APPLICATION_M3U8,
|
||||||
|
|
@ -1888,10 +1890,10 @@ object HlsPlaylistParser {
|
||||||
captionGroupId = closedCaptionsGroupId
|
captionGroupId = closedCaptionsGroupId
|
||||||
)
|
)
|
||||||
variants.add(variant)
|
variants.add(variant)
|
||||||
var variantInfosForUrl: ArrayList<VariantInfo>? = urlToVariantInfos[url]
|
var variantInfosForUrl: ArrayList<VariantInfo>? = urlToVariantInfos[uri]
|
||||||
if (variantInfosForUrl == null) {
|
if (variantInfosForUrl == null) {
|
||||||
variantInfosForUrl = ArrayList()
|
variantInfosForUrl = ArrayList()
|
||||||
urlToVariantInfos[url] = variantInfosForUrl
|
urlToVariantInfos[uri] = variantInfosForUrl
|
||||||
}
|
}
|
||||||
variantInfosForUrl.add(
|
variantInfosForUrl.add(
|
||||||
VariantInfo(
|
VariantInfo(
|
||||||
|
|
@ -1909,7 +1911,7 @@ object HlsPlaylistParser {
|
||||||
// TODO: Don't deduplicate variants by URL.
|
// TODO: Don't deduplicate variants by URL.
|
||||||
val deduplicatedVariants = variants.distinctBy { it.url }
|
val deduplicatedVariants = variants.distinctBy { it.url }
|
||||||
/*val deduplicatedVariants: ArrayList<Variant> = ArrayList<Variant>()
|
/*val deduplicatedVariants: ArrayList<Variant> = ArrayList<Variant>()
|
||||||
val urlsInDeduplicatedVariants = HashSet<Url>()
|
val urlsInDeduplicatedVariants = HashSet<URI>()
|
||||||
for (i in variants.indices) {
|
for (i in variants.indices) {
|
||||||
val variant: Variant = variants[i]
|
val variant: Variant = variants[i]
|
||||||
if (urlsInDeduplicatedVariants.add(variant.url)) {
|
if (urlsInDeduplicatedVariants.add(variant.url)) {
|
||||||
|
|
@ -1943,10 +1945,10 @@ object HlsPlaylistParser {
|
||||||
containerMimeType = MimeTypes.APPLICATION_M3U8,
|
containerMimeType = MimeTypes.APPLICATION_M3U8,
|
||||||
)
|
)
|
||||||
|
|
||||||
val referenceUrl: String? =
|
val referenceUri: String? =
|
||||||
parseOptionalStringAttr(line, REGEX_URI, variableDefinitions)
|
parseOptionalStringAttr(line, REGEX_URI, variableDefinitions)
|
||||||
val url: Url? =
|
val uri: URI? =
|
||||||
if (referenceUrl == null) null else UrlUtil.resolveToUrl(baseUri, referenceUrl)
|
if (referenceUri == null) null else UriUtil.resolveToUri(baseUri, referenceUri)
|
||||||
//val metadata =
|
//val metadata =
|
||||||
// Metadata(HlsTrackMetadataEntry(groupId, name, emptyList<T>()))
|
// Metadata(HlsTrackMetadataEntry(groupId, name, emptyList<T>()))
|
||||||
when (parseStringAttr(line, REGEX_TYPE, variableDefinitions)) {
|
when (parseStringAttr(line, REGEX_TYPE, variableDefinitions)) {
|
||||||
|
|
@ -1961,11 +1963,11 @@ object HlsPlaylistParser {
|
||||||
codecs = Util.getCodecsOfType(variantFormat.codecs, C.TRACK_TYPE_VIDEO)
|
codecs = Util.getCodecsOfType(variantFormat.codecs, C.TRACK_TYPE_VIDEO)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (url == null) {
|
if (uri == null) {
|
||||||
// TODO: Remove this case and add a Rendition with a null url to videos.
|
// TODO: Remove this case and add a Rendition with a null uri to videos.
|
||||||
} else {
|
} else {
|
||||||
//formatBuilder.setMetadata(metadata)
|
//formatBuilder.setMetadata(metadata)
|
||||||
videos.add(Rendition(url = url, format = formatBuilder, groupId, name))
|
videos.add(Rendition(url = uri, format = formatBuilder, groupId, name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1993,11 +1995,11 @@ object HlsPlaylistParser {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val format = formatBuilder.copy(sampleMimeType = sampleMimeType)
|
val format = formatBuilder.copy(sampleMimeType = sampleMimeType)
|
||||||
if (url != null) {
|
if (uri != null) {
|
||||||
//formatBuilder.setMetadata(metadata)
|
//formatBuilder.setMetadata(metadata)
|
||||||
audios.add(Rendition(url, format, groupId, name))
|
audios.add(Rendition(uri, format, groupId, name))
|
||||||
} else if (variant != null) {
|
} else if (variant != null) {
|
||||||
// TODO: Remove muxedAudioFormat and add a Rendition with a null url to audios.
|
// TODO: Remove muxedAudioFormat and add a Rendition with a null uri to audios.
|
||||||
muxedAudioFormat = format
|
muxedAudioFormat = format
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2016,10 +2018,10 @@ object HlsPlaylistParser {
|
||||||
if (sampleMimeType == null) {
|
if (sampleMimeType == null) {
|
||||||
sampleMimeType = MimeTypes.TEXT_VTT
|
sampleMimeType = MimeTypes.TEXT_VTT
|
||||||
}
|
}
|
||||||
if (url != null) {
|
if (uri != null) {
|
||||||
subtitles.add(
|
subtitles.add(
|
||||||
Rendition(
|
Rendition(
|
||||||
url,
|
uri,
|
||||||
formatBuilder.copy(sampleMimeType = sampleMimeType),
|
formatBuilder.copy(sampleMimeType = sampleMimeType),
|
||||||
groupId,
|
groupId,
|
||||||
name
|
name
|
||||||
|
|
|
||||||
|
|
@ -112,8 +112,8 @@ object M3u8Helper2 {
|
||||||
return c.doFinal(data)
|
return c.doFinal(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getParentLink(url: String): String {
|
private fun getParentLink(uri: String): String {
|
||||||
val split = url.split("/").toMutableList()
|
val split = uri.split("/").toMutableList()
|
||||||
split.removeAt(split.lastIndex)
|
split.removeAt(split.lastIndex)
|
||||||
return split.joinToString("/")
|
return split.joinToString("/")
|
||||||
}
|
}
|
||||||
|
|
@ -322,15 +322,15 @@ object M3u8Helper2 {
|
||||||
|
|
||||||
if (!match.isNullOrEmpty()) {
|
if (!match.isNullOrEmpty()) {
|
||||||
encryptionState = true
|
encryptionState = true
|
||||||
var encryptionUrl = match[2]
|
var encryptionUri = match[2]
|
||||||
|
|
||||||
if (isNotCompleteUrl(encryptionUrl)) {
|
if (isNotCompleteUrl(encryptionUri)) {
|
||||||
encryptionUrl = "${getParentLink(playlistStream.streamUrl)}/$encryptionUrl"
|
encryptionUri = "${getParentLink(playlistStream.streamUrl)}/$encryptionUri"
|
||||||
}
|
}
|
||||||
|
|
||||||
encryptionIv = match[3].encodeToByteArray()
|
encryptionIv = match[3].encodeToByteArray()
|
||||||
val encryptionKeyResponse =
|
val encryptionKeyResponse =
|
||||||
app.get(encryptionUrl, headers = playlistStream.headers, verify = false)
|
app.get(encryptionUri, headers = playlistStream.headers, verify = false)
|
||||||
val body = encryptionKeyResponse.body
|
val body = encryptionKeyResponse.body
|
||||||
encryptionData = body.bytes()
|
encryptionData = body.bytes()
|
||||||
body.close()
|
body.close()
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,14 @@
|
||||||
package com.lagradost.cloudstream3.utils
|
package com.lagradost.cloudstream3.utils
|
||||||
|
|
||||||
import com.lagradost.cloudstream3.Prerelease
|
import java.net.URLDecoder
|
||||||
import io.ktor.http.decodeURLQueryComponent
|
import java.net.URLEncoder
|
||||||
import io.ktor.http.encodeURLParameter
|
|
||||||
|
|
||||||
object StringUtils {
|
object StringUtils {
|
||||||
@Prerelease
|
fun String.encodeUri(): String {
|
||||||
fun String.decodeUrl(): String {
|
return URLEncoder.encode(this, "UTF-8")
|
||||||
return this.decodeURLQueryComponent()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Prerelease
|
fun String.decodeUri(): String {
|
||||||
fun String.encodeUrl(): String {
|
return URLDecoder.decode(this, "UTF-8")
|
||||||
return this.encodeURLParameter()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deprecate after next stable
|
|
||||||
|
|
||||||
/* @Deprecated(
|
|
||||||
message = "Use Ktor 'Url' naming convention instead.",
|
|
||||||
replaceWith = ReplaceWith("this.encodeUrl()")
|
|
||||||
) */
|
|
||||||
fun String.encodeUri(): String = encodeUrl()
|
|
||||||
|
|
||||||
/* @Deprecated(
|
|
||||||
message = "Use Ktor 'Url' naming convention instead.",
|
|
||||||
replaceWith = ReplaceWith("this.decodeUrl()")
|
|
||||||
) */
|
|
||||||
fun String.decodeUri(): String = decodeUrl()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@ package com.lagradost.cloudstream3.utils
|
||||||
|
|
||||||
import com.lagradost.cloudstream3.app
|
import com.lagradost.cloudstream3.app
|
||||||
import com.lagradost.cloudstream3.base64Decode
|
import com.lagradost.cloudstream3.base64Decode
|
||||||
import com.lagradost.cloudstream3.utils.StringUtils.decodeUrl
|
import com.lagradost.cloudstream3.utils.StringUtils.decodeUri
|
||||||
import com.lagradost.nicehttp.NiceResponse
|
import com.lagradost.nicehttp.NiceResponse
|
||||||
import io.ktor.http.Url
|
import java.net.URI
|
||||||
|
|
||||||
// Code heavily based on unshortenit.py form kodiondemand /addon
|
// Code heavily based on unshortenit.py form kodiondemand /addon
|
||||||
|
|
||||||
|
|
@ -48,8 +48,8 @@ object ShortLink {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun unshorten(url: String, type: String? = null): String {
|
suspend fun unshorten(uri: String, type: String? = null): String {
|
||||||
var currentUrl = url
|
var currentUrl = uri
|
||||||
|
|
||||||
val visitedUrls = mutableSetOf<String>()
|
val visitedUrls = mutableSetOf<String>()
|
||||||
var count = 10
|
var count = 10
|
||||||
|
|
@ -57,7 +57,9 @@ object ShortLink {
|
||||||
visitedUrls += currentUrl
|
visitedUrls += currentUrl
|
||||||
count -= 1
|
count -= 1
|
||||||
|
|
||||||
val domain = Url(currentUrl.trim()).host
|
val domain =
|
||||||
|
URI(currentUrl.trim()).host
|
||||||
|
?: throw IllegalArgumentException("No domain found in URI!")
|
||||||
currentUrl = shortList.firstOrNull {
|
currentUrl = shortList.firstOrNull {
|
||||||
it.regex.find(domain) != null || type == it.type
|
it.regex.find(domain) != null || type == it.type
|
||||||
}?.function?.let { it(currentUrl) } ?: break
|
}?.function?.let { it(currentUrl) } ?: break
|
||||||
|
|
@ -65,8 +67,8 @@ object ShortLink {
|
||||||
return currentUrl.trim()
|
return currentUrl.trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun unshortenAdfly(url: String): String {
|
suspend fun unshortenAdfly(uri: String): String {
|
||||||
val html = app.get(url).text
|
val html = app.get(uri).text
|
||||||
val ysmm = Regex("""var ysmm =.*;?""").find(html)!!.value
|
val ysmm = Regex("""var ysmm =.*;?""").find(html)!!.value
|
||||||
|
|
||||||
if (ysmm.isNotEmpty()) {
|
if (ysmm.isNotEmpty()) {
|
||||||
|
|
@ -79,46 +81,46 @@ object ShortLink {
|
||||||
left += c[0]
|
left += c[0]
|
||||||
right = c[1] + right
|
right = c[1] + right
|
||||||
}
|
}
|
||||||
val encodedUrl = (left + right).toMutableList()
|
val encodedUri = (left + right).toMutableList()
|
||||||
val numbers =
|
val numbers =
|
||||||
encodedUrl.mapIndexed { i, n -> Pair(i, n) }.filter { it.second.isDigit() }
|
encodedUri.mapIndexed { i, n -> Pair(i, n) }.filter { it.second.isDigit() }
|
||||||
for (el in numbers.chunked(2).dropLastWhile { it.size == 1 }) {
|
for (el in numbers.chunked(2).dropLastWhile { it.size == 1 }) {
|
||||||
val xor = (el[0].second).code.xor(el[1].second.code)
|
val xor = (el[0].second).code.xor(el[1].second.code)
|
||||||
if (xor < 10) {
|
if (xor < 10) {
|
||||||
encodedUrl[el[0].first] = xor.digitToChar()
|
encodedUri[el[0].first] = xor.digitToChar()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val encodedbytearray = encodedUrl.map { it.code.toByte() }.toByteArray()
|
val encodedbytearray = encodedUri.map { it.code.toByte() }.toByteArray()
|
||||||
var decodedUrl =
|
var decodedUri =
|
||||||
base64Decode(encodedbytearray.toString()).dropLast(16)
|
base64Decode(encodedbytearray.toString()).dropLast(16)
|
||||||
.drop(16)
|
.drop(16)
|
||||||
|
|
||||||
if (Regex("""go\.php\?u=""").find(decodedUrl) != null) {
|
if (Regex("""go\.php\?u=""").find(decodedUri) != null) {
|
||||||
decodedUrl =
|
decodedUri =
|
||||||
base64Decode(decodedUrl.replace(Regex("""(.*?)u="""), ""))
|
base64Decode(decodedUri.replace(Regex("""(.*?)u="""), ""))
|
||||||
}
|
}
|
||||||
|
|
||||||
return decodedUrl
|
return decodedUri
|
||||||
} else {
|
} else {
|
||||||
return url
|
return uri
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun unshortenLinkup(url: String): String {
|
suspend fun unshortenLinkup(uri: String): String {
|
||||||
var r: NiceResponse? = null
|
var r: NiceResponse? = null
|
||||||
var url = url
|
var uri = uri
|
||||||
when {
|
when {
|
||||||
url.contains("/tv/") -> url = url.replace("/tv/", "/tva/")
|
uri.contains("/tv/") -> uri = uri.replace("/tv/", "/tva/")
|
||||||
url.contains("delta") -> url = url.replace("/delta/", "/adelta/")
|
uri.contains("delta") -> uri = uri.replace("/delta/", "/adelta/")
|
||||||
(url.contains("/ga/") || url.contains("/ga2/")) -> url =
|
(uri.contains("/ga/") || uri.contains("/ga2/")) -> uri =
|
||||||
base64Decode(url.split('/').last()).trim()
|
base64Decode(uri.split('/').last()).trim()
|
||||||
|
|
||||||
url.contains("/speedx/") -> url =
|
uri.contains("/speedx/") -> uri =
|
||||||
url.replace("http://linkup.pro/speedx", "http://speedvideo.net")
|
uri.replace("http://linkup.pro/speedx", "http://speedvideo.net")
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
r = app.get(url, allowRedirects = true)
|
r = app.get(uri, allowRedirects = true)
|
||||||
url = r.url
|
uri = r.url
|
||||||
val link =
|
val link =
|
||||||
Regex("<iframe[^<>]*src=\\'([^'>]*)\\'[^<>]*>").find(r.text)?.value
|
Regex("<iframe[^<>]*src=\\'([^'>]*)\\'[^<>]*>").find(r.text)?.value
|
||||||
?: Regex("""action="(?:[^/]+.*?/[^/]+/([a-zA-Z0-9_]+))">""").find(r.text)?.value
|
?: Regex("""action="(?:[^/]+.*?/[^/]+/([a-zA-Z0-9_]+))">""").find(r.text)?.value
|
||||||
|
|
@ -126,40 +128,40 @@ object ShortLink {
|
||||||
.elementAtOrNull(1)?.groupValues?.get(1)
|
.elementAtOrNull(1)?.groupValues?.get(1)
|
||||||
|
|
||||||
if (link != null) {
|
if (link != null) {
|
||||||
url = link
|
uri = link
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val short = Regex("""^https?://.*?(https?://.*)""").find(url)?.value
|
val short = Regex("""^https?://.*?(https?://.*)""").find(uri)?.value
|
||||||
if (short != null) {
|
if (short != null) {
|
||||||
url = short
|
uri = short
|
||||||
}
|
}
|
||||||
if (r == null) {
|
if (r == null) {
|
||||||
r = app.get(
|
r = app.get(
|
||||||
url,
|
uri,
|
||||||
allowRedirects = false
|
allowRedirects = false
|
||||||
)
|
)
|
||||||
if (r.headers["location"] != null) {
|
if (r.headers["location"] != null) {
|
||||||
url = r.headers["location"].toString()
|
uri = r.headers["location"].toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (url.contains("snip.")) {
|
if (uri.contains("snip.")) {
|
||||||
if (url.contains("out_generator")) {
|
if (uri.contains("out_generator")) {
|
||||||
url = Regex("url=(.*)\$").find(url)!!.value
|
uri = Regex("url=(.*)\$").find(uri)!!.value
|
||||||
} else if (url.contains("/decode/")) {
|
} else if (uri.contains("/decode/")) {
|
||||||
url = app.get(url, allowRedirects = true).url
|
uri = app.get(uri, allowRedirects = true).url
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return url
|
return uri
|
||||||
}
|
}
|
||||||
|
|
||||||
fun unshortenLinksafe(url: String): String {
|
fun unshortenLinksafe(uri: String): String {
|
||||||
return base64Decode(url.split("?url=").last())
|
return base64Decode(uri.split("?url=").last())
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun unshortenNuovoIndirizzo(url: String): String {
|
suspend fun unshortenNuovoIndirizzo(uri: String): String {
|
||||||
val soup = app.get(url, allowRedirects = true)
|
val soup = app.get(uri, allowRedirects = true)
|
||||||
val header = soup.headers["refresh"]
|
val header = soup.headers["refresh"]
|
||||||
val link: String = if (header != null) {
|
val link: String = if (header != null) {
|
||||||
soup.headers["refresh"]!!.substringAfter("=")
|
soup.headers["refresh"]!!.substringAfter("=")
|
||||||
|
|
@ -169,29 +171,29 @@ object ShortLink {
|
||||||
return link
|
return link
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun unshortenNuovoLink(url: String): String {
|
suspend fun unshortenNuovoLink(uri: String): String {
|
||||||
return app.get(url, allowRedirects = true).document.selectFirst("a")!!.attr("href")
|
return app.get(uri, allowRedirects = true).document.selectFirst("a")!!.attr("href")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun unshortenUprot(url: String): String {
|
suspend fun unshortenUprot(uri: String): String {
|
||||||
val page = app.get(url).text
|
val page = app.get(uri).text
|
||||||
Regex("""<a[^>]+href="([^"]+)".*Continue""").findAll(page)
|
Regex("""<a[^>]+href="([^"]+)".*Continue""").findAll(page)
|
||||||
.map { it.value.replace("""<a href="""", "") }
|
.map { it.value.replace("""<a href="""", "") }
|
||||||
.toList().forEach { link ->
|
.toList().forEach { link ->
|
||||||
if (link.contains("https://maxstream.video") || link.contains("https://uprot.net") && link != url) {
|
if (link.contains("https://maxstream.video") || link.contains("https://uprot.net") && link != uri) {
|
||||||
return link
|
return link
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return url
|
return uri
|
||||||
}
|
}
|
||||||
|
|
||||||
fun unshortenDavisonbarker(url: String): String {
|
fun unshortenDavisonbarker(uri: String): String {
|
||||||
return url.substringAfter("dest=").decodeUrl()
|
return uri.substringAfter("dest=").decodeUri()
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun unshortenIsecure(url: String): String {
|
suspend fun unshortenIsecure(uri: String): String {
|
||||||
val doc = app.get(url).document
|
val doc = app.get(uri).document
|
||||||
return doc.selectFirst("iframe")?.attr("src")?.trim() ?: url
|
return doc.selectFirst("iframe")?.attr("src")?.trim() ?: uri
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue