Merge remote-tracking branch 'origin/master' into gradle

# Conflicts:
#	app/build.gradle.kts
This commit is contained in:
IndusAryan 2024-03-02 13:11:18 +05:30
commit 69615d093e
17 changed files with 171 additions and 79 deletions

View file

@ -233,6 +233,7 @@ dependencies {
implementation("androidx.work:work-runtime:2.9.0") implementation("androidx.work:work-runtime:2.9.0")
implementation("androidx.work:work-runtime-ktx:2.9.0") implementation("androidx.work:work-runtime-ktx:2.9.0")
implementation("com.github.Blatzar:NiceHttp:0.4.11") // HTTP Lib implementation("com.github.Blatzar:NiceHttp:0.4.11") // HTTP Lib
implementation("com.github.Blatzar:NiceHttp:0.4.11") // HTTP Lib
} }
tasks.register<Exec>("retrieveCommitHash") { tasks.register<Exec>("retrieveCommitHash") {

View file

@ -2,7 +2,6 @@ package com.lagradost.cloudstream3.extractors
import com.lagradost.cloudstream3.SubtitleFile import com.lagradost.cloudstream3.SubtitleFile
import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.base64Decode
import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.*
open class Acefile : ExtractorApi() { open class Acefile : ExtractorApi() {
@ -16,22 +15,19 @@ open class Acefile : ExtractorApi() {
subtitleCallback: (SubtitleFile) -> Unit, subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit callback: (ExtractorLink) -> Unit
) { ) {
val script = getAndUnpack(app.get(url).text) val id = "/(?:d|download|player|f|file)/(\\w+)".toRegex().find(url)?.groupValues?.get(1)
val id = script.substringAfter("\"code\":\"").substringBefore("\",") val script = getAndUnpack(app.get("$mainUrl/player/${id ?: return}").text)
val doc = app.get("https://drive.google.com/uc?id=${base64Decode(id)}&export=download").document val service = """service\s*=\s*['"]([^'"]+)""".toRegex().find(script)?.groupValues?.get(1)
val form = doc.select("form#download-form").attr("action") val serverUrl = """['"](\S+check&id\S+?)['"]""".toRegex().find(script)?.groupValues?.get(1)
val uc = doc.select("input#uc-download-link").attr("value") ?.replace("\"+service+\"", service ?: return)
val video = app.post(
form, data = mapOf( val video = app.get(serverUrl ?: return, referer = "$mainUrl/").parsedSafe<Source>()?.data
"uc-download-link" to uc
)
).url
callback.invoke( callback.invoke(
ExtractorLink( ExtractorLink(
this.name, this.name,
this.name, this.name,
video, video ?: return,
"", "",
Qualities.Unknown.value, Qualities.Unknown.value,
INFER_TYPE INFER_TYPE
@ -40,4 +36,8 @@ open class Acefile : ExtractorApi() {
} }
data class Source(
val data: String? = null,
)
} }

View file

@ -49,8 +49,23 @@ open class Chillx : ExtractorApi() {
val decrypt = cryptoAESHandler(master ?: return, getKey().toByteArray(), false)?.replace("\\", "") ?: throw ErrorLoadingException("failed to decrypt") val decrypt = cryptoAESHandler(master ?: return, getKey().toByteArray(), false)?.replace("\\", "") ?: throw ErrorLoadingException("failed to decrypt")
val source = Regex(""""?file"?:\s*"([^"]+)""").find(decrypt)?.groupValues?.get(1) val source = Regex(""""?file"?:\s*"([^"]+)""").find(decrypt)?.groupValues?.get(1)
val tracks = Regex("""tracks:\s*\[(.+)]""").find(decrypt)?.groupValues?.get(1)
val subtitles = Regex("""subtitle"?:\s*"([^"]+)""").find(decrypt)?.groupValues?.get(1)
val subtitlePattern = """\[(.*?)\](https?://[^\s,]+)""".toRegex()
val matches = subtitlePattern.findAll(subtitles ?: "")
val languageUrlPairs = matches.map { matchResult ->
val (language, url) = matchResult.destructured
decodeUnicodeEscape(language) to url
}.toList()
languageUrlPairs.forEach{ (name, file) ->
subtitleCallback.invoke(
SubtitleFile(
name,
file
)
)
}
// required // required
val headers = mapOf( val headers = mapOf(
"Accept" to "*/*", "Accept" to "*/*",
@ -67,15 +82,12 @@ open class Chillx : ExtractorApi() {
"$mainUrl/", "$mainUrl/",
headers = headers headers = headers
).forEach(callback) ).forEach(callback)
}
AppUtils.tryParseJson<List<Tracks>>("[$tracks]") private fun decodeUnicodeEscape(input: String): String {
?.filter { it.kind == "captions" }?.map { track -> val regex = Regex("u([0-9a-fA-F]{4})")
subtitleCallback.invoke( return regex.replace(input) {
SubtitleFile( it.groupValues[1].toInt(16).toChar().toString()
track.label ?: "",
track.file ?: return@map null
)
)
} }
} }

View file

@ -22,9 +22,9 @@ open class Gofile : ExtractorApi() {
val id = Regex("/(?:\\?c=|d/)([\\da-zA-Z-]+)").find(url)?.groupValues?.get(1) val id = Regex("/(?:\\?c=|d/)([\\da-zA-Z-]+)").find(url)?.groupValues?.get(1)
val token = app.get("$mainApi/createAccount").parsedSafe<Account>()?.data?.get("token") val token = app.get("$mainApi/createAccount").parsedSafe<Account>()?.data?.get("token")
val websiteToken = app.get("$mainUrl/dist/js/alljs.js").text.let { val websiteToken = app.get("$mainUrl/dist/js/alljs.js").text.let {
Regex("websiteToken\\s*=\\s*\"([^\"]+)").find(it)?.groupValues?.get(1) Regex("fetchData.wt\\s*=\\s*\"([^\"]+)").find(it)?.groupValues?.get(1)
} }
app.get("$mainApi/getContent?contentId=$id&token=$token&websiteToken=$websiteToken") app.get("$mainApi/getContent?contentId=$id&token=$token&wt=$websiteToken")
.parsedSafe<Source>()?.data?.contents?.forEach { .parsedSafe<Source>()?.data?.contents?.forEach {
callback.invoke( callback.invoke(
ExtractorLink( ExtractorLink(

View file

@ -11,3 +11,13 @@ class FourCX : ContentX() {
override var name = "FourCX" override var name = "FourCX"
override var mainUrl = "https://four.contentx.me" override var mainUrl = "https://four.contentx.me"
} }
class PlayRu : ContentX() {
override var name = "PlayRu"
override var mainUrl = "https://playru.net"
}
class FourPlayRu : ContentX() {
override var name = "FourPlayRu"
override var mainUrl = "https://four.playru.net"
}

View file

@ -5,6 +5,7 @@ import com.lagradost.cloudstream3.ErrorLoadingException
import com.lagradost.cloudstream3.SubtitleFile import com.lagradost.cloudstream3.SubtitleFile
import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.base64DecodeArray import com.lagradost.cloudstream3.base64DecodeArray
import com.lagradost.cloudstream3.base64Encode
import com.lagradost.cloudstream3.utils.AppUtils import com.lagradost.cloudstream3.utils.AppUtils
import com.lagradost.cloudstream3.utils.AppUtils.parseJson import com.lagradost.cloudstream3.utils.AppUtils.parseJson
import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorApi
@ -16,13 +17,52 @@ import javax.crypto.Cipher
import javax.crypto.spec.IvParameterSpec import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec import javax.crypto.spec.SecretKeySpec
// Code found in https://github.com/theonlymo/keys
// special credits to @theonlymo for providing key
class Megacloud : Rabbitstream() { class Megacloud : Rabbitstream() {
override val name = "Megacloud" override val name = "Megacloud"
override val mainUrl = "https://megacloud.tv" override val mainUrl = "https://megacloud.tv"
override val embed = "embed-2/ajax/e-1" override val embed = "embed-2/ajax/e-1"
override val key = "https://raw.githubusercontent.com/theonlymo/keys/e1/key" private val scriptUrl = "$mainUrl/js/player/a/prod/e1-player.min.js"
override suspend fun extractRealKey(sources: String): Pair<String, String> {
val rawKeys = getKeys()
val sourcesArray = sources.toCharArray()
var extractedKey = ""
var currentIndex = 0
for (index in rawKeys) {
val start = index[0] + currentIndex
val end = start + index[1]
for (i in start until end) {
extractedKey += sourcesArray[i].toString()
sourcesArray[i] = ' '
}
currentIndex += index[1]
}
return extractedKey to sourcesArray.joinToString("").replace(" ", "")
}
private suspend fun getKeys(): List<List<Int>> {
val script = app.get(scriptUrl).text
fun matchingKey(value: String): String {
return Regex(",$value=((?:0x)?([0-9a-fA-F]+))").find(script)?.groupValues?.get(1)
?.removePrefix("0x") ?: throw ErrorLoadingException("Failed to match the key")
}
val regex = Regex("case\\s*0x[0-9a-f]+:(?![^;]*=partKey)\\s*\\w+\\s*=\\s*(\\w+)\\s*,\\s*\\w+\\s*=\\s*(\\w+);")
val indexPairs = regex.findAll(script).toList().map { match ->
val matchKey1 = matchingKey(match.groupValues[1])
val matchKey2 = matchingKey(match.groupValues[2])
try {
listOf(matchKey1.toInt(16), matchKey2.toInt(16))
} catch (e: NumberFormatException) {
emptyList()
}
}.filter { it.isNotEmpty() }
return indexPairs
}
} }
class Dokicloud : Rabbitstream() { class Dokicloud : Rabbitstream() {
@ -30,12 +70,14 @@ class Dokicloud : Rabbitstream() {
override val mainUrl = "https://dokicloud.one" override val mainUrl = "https://dokicloud.one"
} }
// Code found in https://github.com/eatmynerds/key
// special credits to @eatmynerds for providing key
open class Rabbitstream : ExtractorApi() { open class Rabbitstream : ExtractorApi() {
override val name = "Rabbitstream" override val name = "Rabbitstream"
override val mainUrl = "https://rabbitstream.net" override val mainUrl = "https://rabbitstream.net"
override val requiresReferer = false override val requiresReferer = false
open val embed = "ajax/embed-4" open val embed = "ajax/embed-4"
open val key = "https://raw.githubusercontent.com/theonlymo/keys/e4/key" open val key = "https://raw.githubusercontent.com/eatmynerds/key/e4/key.txt"
override suspend fun getUrl( override suspend fun getUrl(
url: String, url: String,
@ -56,7 +98,7 @@ open class Rabbitstream : ExtractorApi() {
val decryptedSources = if (sources == null || encryptedMap.encrypted == false) { val decryptedSources = if (sources == null || encryptedMap.encrypted == false) {
response.parsedSafe() response.parsedSafe()
} else { } else {
val (key, encData) = extractRealKey(sources, getRawKey()) val (key, encData) = extractRealKey(sources)
val decrypted = decryptMapped<List<Sources>>(encData, key) val decrypted = decryptMapped<List<Sources>>(encData, key)
SourcesResponses( SourcesResponses(
sources = decrypted, sources = decrypted,
@ -75,8 +117,8 @@ open class Rabbitstream : ExtractorApi() {
decryptedSources?.tracks?.map { track -> decryptedSources?.tracks?.map { track ->
subtitleCallback.invoke( subtitleCallback.invoke(
SubtitleFile( SubtitleFile(
track?.label ?: "", track?.label ?: return@map,
track?.file ?: return@map track.file ?: return@map
) )
) )
} }
@ -84,25 +126,10 @@ open class Rabbitstream : ExtractorApi() {
} }
private suspend fun getRawKey(): String = app.get(key).text open suspend fun extractRealKey(sources: String): Pair<String, String> {
val rawKeys = parseJson<List<Int>>(app.get(key).text)
private fun extractRealKey(sources: String, stops: String): Pair<String, String> { val extractedKey = base64Encode(rawKeys.map { it.toByte() }.toByteArray())
val decryptKey = parseJson<List<List<Int>>>(stops) return extractedKey to sources
val sourcesArray = sources.toCharArray()
var extractedKey = ""
var currentIndex = 0
for (index in decryptKey) {
val start = index[0] + currentIndex
val end = start + index[1]
for (i in start until end) {
extractedKey += sourcesArray[i].toString()
sourcesArray[i] = ' '
}
currentIndex += index[1]
}
return extractedKey to sourcesArray.joinToString("")
} }
private inline fun <reified T> decryptMapped(input: String, key: String): T? { private inline fun <reified T> decryptMapped(input: String, key: String): T? {

View file

@ -31,6 +31,7 @@ import java.net.URI
* @param useOkhttp will try to use the okhttp client as much as possible, but this might cause some requests to fail. Disable for cloudflare. * @param useOkhttp will try to use the okhttp client as much as possible, but this might cause some requests to fail. Disable for cloudflare.
* @param script pass custom js to execute * @param script pass custom js to execute
* @param scriptCallback will be called with the result from custom js * @param scriptCallback will be called with the result from custom js
* @param timeout close webview after timeout
* */ * */
class WebViewResolver( class WebViewResolver(
val interceptUrl: Regex, val interceptUrl: Regex,
@ -38,18 +39,29 @@ class WebViewResolver(
val userAgent: String? = USER_AGENT, val userAgent: String? = USER_AGENT,
val useOkhttp: Boolean = true, val useOkhttp: Boolean = true,
val script: String? = null, val script: String? = null,
val scriptCallback: ((String) -> Unit)? = null val scriptCallback: ((String) -> Unit)? = null,
val timeout: Long = DEFAULT_TIMEOUT
) : ) :
Interceptor { Interceptor {
constructor(
interceptUrl: Regex,
additionalUrls: List<Regex> = emptyList(),
userAgent: String? = USER_AGENT,
useOkhttp: Boolean = true,
script: String? = null,
scriptCallback: ((String) -> Unit)? = null,
) : this(interceptUrl, additionalUrls, userAgent, useOkhttp, script, scriptCallback, DEFAULT_TIMEOUT)
constructor( constructor(
interceptUrl: Regex, interceptUrl: Regex,
additionalUrls: List<Regex> = emptyList(), additionalUrls: List<Regex> = emptyList(),
userAgent: String? = USER_AGENT, userAgent: String? = USER_AGENT,
useOkhttp: Boolean = true useOkhttp: Boolean = true
) : this(interceptUrl, additionalUrls, userAgent, useOkhttp, null, null) ) : this(interceptUrl, additionalUrls, userAgent, useOkhttp, null, null, DEFAULT_TIMEOUT)
companion object { companion object {
private const val DEFAULT_TIMEOUT = 60_000L
var webViewUserAgent: String? = null var webViewUserAgent: String? = null
@JvmName("getWebViewUserAgent1") @JvmName("getWebViewUserAgent1")
@ -262,7 +274,7 @@ class WebViewResolver(
var loop = 0 var loop = 0
// Timeouts after this amount, 60s // Timeouts after this amount, 60s
val totalTime = 60000L val totalTime = timeout
val delayTime = 100L val delayTime = 100L

View file

@ -317,7 +317,7 @@ class HomeParentItemAdapterPreview(
homePreviewText.text = item.name homePreviewText.text = item.name
populateChips( populateChips(
homePreviewTags, homePreviewTags,
item.tags ?: emptyList(), item.tags?.take(6) ?: emptyList(),
R.style.ChipFilledSemiTransparent R.style.ChipFilledSemiTransparent
) )

View file

@ -681,6 +681,7 @@ open class ResultFragmentPhone : FullScreenPlayer() {
resultMetaYear.setText(d.yearText) resultMetaYear.setText(d.yearText)
resultMetaDuration.setText(d.durationText) resultMetaDuration.setText(d.durationText)
resultMetaRating.setText(d.ratingText) resultMetaRating.setText(d.ratingText)
resultMetaStatus.setText(d.onGoingText)
resultMetaContentRating.setText(d.contentRatingText) resultMetaContentRating.setText(d.contentRatingText)
resultCastText.setText(d.actorsText) resultCastText.setText(d.actorsText)
resultNextAiring.setText(d.nextAiringEpisode) resultNextAiring.setText(d.nextAiringEpisode)

View file

@ -1,10 +1,17 @@
package com.lagradost.cloudstream3.ui.settings.extensions package com.lagradost.cloudstream3.ui.settings.extensions
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.os.Build
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Toast
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.viewbinding.ViewBinding import androidx.viewbinding.ViewBinding
import com.lagradost.cloudstream3.CommonActivity.activity
import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.databinding.RepositoryItemBinding import com.lagradost.cloudstream3.databinding.RepositoryItemBinding
import com.lagradost.cloudstream3.databinding.RepositoryItemTvBinding import com.lagradost.cloudstream3.databinding.RepositoryItemTvBinding
@ -112,6 +119,17 @@ class RepoAdapter(
repositoryItemRoot.setOnClickListener { repositoryItemRoot.setOnClickListener {
clickCallback(repositoryData) clickCallback(repositoryData)
} }
repositoryItemRoot.setOnLongClickListener {
val clipboardManager =
activity?.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager?
clipboardManager?.setPrimaryClip(ClipData.newPlainText("RepoUrl", repositoryData.url))
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) {
showToast(R.string.copyRepoUrl, Toast.LENGTH_SHORT)
}
return@setOnLongClickListener true
}
mainText.text = repositoryData.name mainText.text = repositoryData.name
subText.text = repositoryData.url subText.text = repositoryData.url
} }

View file

@ -106,6 +106,8 @@ import com.lagradost.cloudstream3.extractors.ContentX
import com.lagradost.cloudstream3.extractors.EmturbovidExtractor import com.lagradost.cloudstream3.extractors.EmturbovidExtractor
import com.lagradost.cloudstream3.extractors.Hotlinger import com.lagradost.cloudstream3.extractors.Hotlinger
import com.lagradost.cloudstream3.extractors.FourCX import com.lagradost.cloudstream3.extractors.FourCX
import com.lagradost.cloudstream3.extractors.PlayRu
import com.lagradost.cloudstream3.extractors.FourPlayRu
import com.lagradost.cloudstream3.extractors.HDMomPlayer import com.lagradost.cloudstream3.extractors.HDMomPlayer
import com.lagradost.cloudstream3.extractors.HDPlayerSystem import com.lagradost.cloudstream3.extractors.HDPlayerSystem
import com.lagradost.cloudstream3.extractors.VideoSeyred import com.lagradost.cloudstream3.extractors.VideoSeyred
@ -704,6 +706,8 @@ val extractorApis: MutableList<ExtractorApi> = arrayListOf(
ContentX(), ContentX(),
Hotlinger(), Hotlinger(),
FourCX(), FourCX(),
PlayRu(),
FourPlayRu(),
HDMomPlayer(), HDMomPlayer(),
HDPlayerSystem(), HDPlayerSystem(),
VideoSeyred(), VideoSeyred(),

View file

@ -137,7 +137,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:ellipsize="end" android:ellipsize="end"
android:maxLines="5" android:maxLines="3"
android:paddingBottom="5dp" android:paddingBottom="5dp"
android:textSize="15sp" android:textSize="15sp"
tools:text="very nice tv series" /> tools:text="very nice tv series" />

View file

@ -83,7 +83,7 @@
android:layout_height="25dp" android:layout_height="25dp"
android:layout_margin="5dp" android:layout_margin="5dp"
android:elevation="10dp" android:elevation="10dp"
android:tooltipText="@string/subscribe_tooltip"
android:background="?android:attr/selectableItemBackgroundBorderless" android:background="?android:attr/selectableItemBackgroundBorderless"
android:src="@drawable/baseline_notifications_none_24" android:src="@drawable/baseline_notifications_none_24"
android:layout_gravity="end|center_vertical" android:layout_gravity="end|center_vertical"
@ -100,7 +100,7 @@
android:layout_height="25dp" android:layout_height="25dp"
android:layout_margin="5dp" android:layout_margin="5dp"
android:elevation="10dp" android:elevation="10dp"
android:tooltipText="@string/action_add_to_favorites"
android:background="?android:attr/selectableItemBackgroundBorderless" android:background="?android:attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_baseline_favorite_border_24" android:src="@drawable/ic_baseline_favorite_border_24"
android:layout_gravity="end|center_vertical" android:layout_gravity="end|center_vertical"
@ -117,7 +117,7 @@
android:layout_height="25dp" android:layout_height="25dp"
android:layout_margin="5dp" android:layout_margin="5dp"
android:elevation="10dp" android:elevation="10dp"
android:tooltipText="@string/result_share"
android:background="?android:attr/selectableItemBackgroundBorderless" android:background="?android:attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_outline_share_24" android:src="@drawable/ic_outline_share_24"
android:layout_gravity="end|center_vertical" android:layout_gravity="end|center_vertical"
@ -135,7 +135,7 @@
android:layout_height="25dp" android:layout_height="25dp"
android:layout_margin="5dp" android:layout_margin="5dp"
android:elevation="10dp" android:elevation="10dp"
android:tooltipText="@string/result_open_in_browser"
android:background="?android:attr/selectableItemBackgroundBorderless" android:background="?android:attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_baseline_public_24" android:src="@drawable/ic_baseline_public_24"
android:layout_gravity="end|center_vertical" android:layout_gravity="end|center_vertical"
@ -153,7 +153,7 @@
android:layout_height="30dp" android:layout_height="30dp"
android:layout_margin="5dp" android:layout_margin="5dp"
android:elevation="10dp" android:elevation="10dp"
android:tooltipText="@string/result_search_tooltip"
android:background="?android:attr/selectableItemBackgroundBorderless" android:background="?android:attr/selectableItemBackgroundBorderless"
android:src="@drawable/search_icon" android:src="@drawable/search_icon"
android:layout_gravity="end|center_vertical" android:layout_gravity="end|center_vertical"
@ -171,7 +171,7 @@
android:layout_height="25dp" android:layout_height="25dp"
android:layout_margin="5dp" android:layout_margin="5dp"
android:elevation="10dp" android:elevation="10dp"
android:tooltipText="@string/recommendations_tooltip"
android:background="?android:attr/selectableItemBackgroundBorderless" android:background="?android:attr/selectableItemBackgroundBorderless"
android:src="@drawable/baseline_list_alt_24" android:src="@drawable/baseline_list_alt_24"
android:layout_gravity="end|center_vertical" android:layout_gravity="end|center_vertical"

View file

@ -387,6 +387,7 @@ https://developer.android.com/design/ui/tv/samples/jet-fit
<com.lagradost.cloudstream3.widget.FlowLayout <com.lagradost.cloudstream3.widget.FlowLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="5dp"
app:itemSpacing="10dp"> app:itemSpacing="10dp">
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
@ -399,6 +400,7 @@ https://developer.android.com/design/ui/tv/samples/jet-fit
android:id="@+id/result_meta_content_rating" android:id="@+id/result_meta_content_rating"
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
style="@style/SmallWhiteButton" style="@style/SmallWhiteButton"
android:focusable="false"
tools:text="PG-13" /> tools:text="PG-13" />
<TextView <TextView

View file

@ -148,7 +148,7 @@
<string name="download_done">Download Done</string> <string name="download_done">Download Done</string>
<string name="download_format" translatable="false">%s - %s</string> <string name="download_format" translatable="false">%s - %s</string>
<string name="update_started">Update Started</string> <string name="update_started">Update Started</string>
<string name="stream">Stream</string> <string name="stream">Network stream</string>
<string name="error_loading_links_toast">Error Loading Links</string> <string name="error_loading_links_toast">Error Loading Links</string>
<string name="links_reloaded_toast">Links Reloaded</string> <string name="links_reloaded_toast">Links Reloaded</string>
<string name="download_storage_text">Internal Storage</string> <string name="download_storage_text">Internal Storage</string>
@ -174,6 +174,10 @@
<string name="sort_clear">Clear</string> <string name="sort_clear">Clear</string>
<string name="sort_save">Save</string> <string name="sort_save">Save</string>
<string name="copyTitle">Title copied!</string> <string name="copyTitle">Title copied!</string>
<string name="copyRepoUrl">Repo URL copied!</string>
<string name="subscribe_tooltip">New episode notification</string>
<string name="result_search_tooltip">Search in other extensions</string>
<string name="recommendations_tooltip">Show recommendations</string>
<string name="player_speed">Player Speed</string> <string name="player_speed">Player Speed</string>
<string name="subtitles_settings">Subtitle Settings</string> <string name="subtitles_settings">Subtitle Settings</string>
<string name="subs_text_color">Text Color</string> <string name="subs_text_color">Text Color</string>
@ -213,8 +217,8 @@
<string name="player_subtitles_settings_des">Player subtitles settings</string> <string name="player_subtitles_settings_des">Player subtitles settings</string>
<string name="chromecast_subtitles_settings">Chromecast Subtitles</string> <string name="chromecast_subtitles_settings">Chromecast Subtitles</string>
<string name="chromecast_subtitles_settings_des">Chromecast subtitles settings</string> <string name="chromecast_subtitles_settings_des">Chromecast subtitles settings</string>
<string name="eigengraumode_settings">Eigengravy Mode</string> <string name="eigengraumode_settings">Playback speed</string>
<string name="eigengraumode_settings_des">Adds a speed option in the player</string> <string name="speed_setting_summary">Adds a speed option in the player</string>
<string name="swipe_to_seek_settings">Swipe to seek</string> <string name="swipe_to_seek_settings">Swipe to seek</string>
<string name="swipe_to_seek_settings_des">Swipe from side to side to control your position in a video</string> <string name="swipe_to_seek_settings_des">Swipe from side to side to control your position in a video</string>
<string name="swipe_to_change_settings">Swipe to change settings</string> <string name="swipe_to_change_settings">Swipe to change settings</string>
@ -391,9 +395,9 @@
<string name="video_disk_description">Causes problems if set too high on devices with low storage space, such as Android TV.</string> <string name="video_disk_description">Causes problems if set too high on devices with low storage space, such as Android TV.</string>
<string name="dns_pref">DNS over HTTPS</string> <string name="dns_pref">DNS over HTTPS</string>
<string name="dns_pref_summary">Useful for bypassing ISP blocks</string> <string name="dns_pref_summary">Useful for bypassing ISP blocks</string>
<string name="jsdelivr_proxy">raw.githubusercontent.com Proxy</string> <string name="jsdelivr_proxy">GitHub Proxy</string>
<string name="jsdelivr_enabled">Could not reach GitHub. Turning on jsDelivr proxy…</string> <string name="jsdelivr_enabled">Could not reach GitHub. Turning on jsDelivr proxy…</string>
<string name="jsdelivr_proxy_summary">Bypasses blocking of GitHub using jsDelivr. May cause updates to be delayed by few days.</string> <string name="jsdelivr_proxy_summary">Bypass blocking of raw github URLs using jsDelivr. May cause updates to be delayed by few days.</string>
<string name="add_site_pref">Clone site</string> <string name="add_site_pref">Clone site</string>
<string name="remove_site_pref">Remove site</string> <string name="remove_site_pref">Remove site</string>
<string name="add_site_summary">Add a clone of an existing site, with a different URL</string> <string name="add_site_summary">Add a clone of an existing site, with a different URL</string>
@ -439,13 +443,15 @@
<string name="category_general">General</string> <string name="category_general">General</string>
<string name="random_button_settings">Random Button</string> <string name="random_button_settings">Random Button</string>
<string name="random_button_settings_desc">Show random button on Homepage and Library</string> <string name="random_button_settings_desc">Show random button on Homepage and Library</string>
<string name="provider_lang_settings">Provider languages</string> <string name="provider_lang_settings">Extension languages</string>
<string name="app_layout">App Layout</string> <string name="app_layout">App Layout</string>
<string name="preferred_media_settings">Preferred media</string> <string name="preferred_media_settings">Preferred media</string>
<string name="enable_nsfw_on_providers">Enable NSFW on supported providers</string> <string name="enable_nsfw_on_providers">Enable NSFW on supported Extensions</string>
<string name="subtitles_encoding">Subtitle encoding</string> <string name="subtitles_encoding">Subtitle encoding</string>
<string name="category_providers">Providers</string> <string name="category_providers">Providers</string>
<string name="category_provider_test">Provider test</string> <string name="category_provider_test">Provider test</string>
<string name="test_extensions">Test all Extensions</string>
<string name="test_extensions_summary">This Test is meant for developers only and does not verifies or denies working of any extension.</string>
<string name="category_ui">Layout</string> <string name="category_ui">Layout</string>
<string name="automatic">Auto</string> <string name="automatic">Auto</string>
<string name="tv_layout">TV layout</string> <string name="tv_layout">TV layout</string>
@ -462,11 +468,11 @@
<string name="opensubtitles_key" translatable="false">opensubtitles_key</string> <string name="opensubtitles_key" translatable="false">opensubtitles_key</string>
<string name="nginx_key" translatable="false">nginx_key</string> <string name="nginx_key" translatable="false">nginx_key</string>
<string name="example_password">password123</string> <string name="example_password">password123</string>
<string name="example_username">MyCoolUsername</string> <string name="example_username">Username</string>
<string name="example_email">hello@world.com</string> <string name="example_email">hello@world.com</string>
<string name="example_ip">127.0.0.1</string> <string name="example_ip">127.0.0.1</string>
<string name="example_site_name">MyCoolSite</string> <string name="example_site_name">NewSiteName</string>
<string name="example_site_url">example.com</string> <string name="example_site_url">https://example.com</string>
<string name="example_lang_name">Language code (en)</string> <string name="example_lang_name">Language code (en)</string>
<!-- <!--
<string name="mal_account_settings" translatable="false">MAL</string> <string name="mal_account_settings" translatable="false">MAL</string>
@ -558,8 +564,8 @@
<string name="subtitles_filter_lang">Filter by preferred media language</string> <string name="subtitles_filter_lang">Filter by preferred media language</string>
<string name="extras">Extras</string> <string name="extras">Extras</string>
<string name="trailer">Trailer</string> <string name="trailer">Trailer</string>
<string name="network_adress_example">Link to stream</string> <string name="network_adress_example">https://example.com/example.mp4</string>
<string name="referer">Referer</string> <string name="referer">Referer (optional)</string>
<string name="next">Next</string> <string name="next">Next</string>
<string name="provider_languages_tip">Watch videos in these languages</string> <string name="provider_languages_tip">Watch videos in these languages</string>
<string name="previous">Previous</string> <string name="previous">Previous</string>
@ -594,8 +600,6 @@
<string name="plugins_updated" formatted="true">Updated %d plugins</string> <string name="plugins_updated" formatted="true">Updated %d plugins</string>
<string name="blank_repo_message">CloudStream has no sites installed by default. You need to install the sites from repositories. <string name="blank_repo_message">CloudStream has no sites installed by default. You need to install the sites from repositories.
\n \n
\nBecause of a brainless DMCA takedown by Sky UK Limited 🤮 we cannot link the repository site in app.
\n
\nJoin our Discord or search online.</string> \nJoin our Discord or search online.</string>
<string name="view_public_repositories_button">View community repositories</string> <string name="view_public_repositories_button">View community repositories</string>
<string name="view_public_repositories_button_short">Public list</string> <string name="view_public_repositories_button_short">Public list</string>

View file

@ -70,7 +70,7 @@
app:key="@string/player_resize_enabled_key" /> app:key="@string/player_resize_enabled_key" />
<SwitchPreference <SwitchPreference
android:icon="@drawable/ic_baseline_speed_24" android:icon="@drawable/ic_baseline_speed_24"
android:summary="@string/eigengraumode_settings_des" android:summary="@string/speed_setting_summary"
android:title="@string/eigengraumode_settings" android:title="@string/eigengraumode_settings"
app:defaultValue="false" app:defaultValue="false"
app:key="@string/playback_speed_enabled_key" /> app:key="@string/playback_speed_enabled_key" />

View file

@ -25,6 +25,7 @@
<Preference <Preference
android:icon="@drawable/baseline_network_ping_24" android:icon="@drawable/baseline_network_ping_24"
android:key="@string/test_providers_key" android:key="@string/test_providers_key"
android:title="Test all providers" /> android:title="@string/test_extensions"
android:summary="@string/test_extensions_summary"/>
</PreferenceScreen> </PreferenceScreen>