Merge branch 'recloudstream:master' into initial-multi-delete

This commit is contained in:
Luna712 2024-07-16 12:54:50 -06:00 committed by GitHub
commit b601704764
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 484 additions and 316 deletions

View file

@ -157,16 +157,16 @@ dependencies {
testImplementation("junit:junit:4.13.2") testImplementation("junit:junit:4.13.2")
testImplementation("org.json:json:20240303") testImplementation("org.json:json:20240303")
androidTestImplementation("androidx.test:core") androidTestImplementation("androidx.test:core")
implementation("androidx.test.ext:junit-ktx:1.1.5") implementation("androidx.test.ext:junit-ktx:1.2.1")
androidTestImplementation("androidx.test.ext:junit:1.1.5") androidTestImplementation("androidx.test.ext:junit:1.2.1")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1")
// Android Core & Lifecycle // Android Core & Lifecycle
implementation("androidx.core:core-ktx:1.13.1") implementation("androidx.core:core-ktx:1.13.1")
implementation("androidx.appcompat:appcompat:1.7.0") implementation("androidx.appcompat:appcompat:1.7.0")
implementation("androidx.navigation:navigation-ui-ktx:2.7.7") implementation("androidx.navigation:navigation-ui-ktx:2.7.7")
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.8.2") implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.8.3")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.2") implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.3")
implementation("androidx.navigation:navigation-fragment-ktx:2.7.7") implementation("androidx.navigation:navigation-fragment-ktx:2.7.7")
// Design & UI // Design & UI
@ -182,9 +182,9 @@ dependencies {
implementation("com.github.bumptech.glide:okhttp3-integration:4.16.0") implementation("com.github.bumptech.glide:okhttp3-integration:4.16.0")
// For KSP -> Official Annotation Processors are Not Yet Supported for KSP // For KSP -> Official Annotation Processors are Not Yet Supported for KSP
ksp("dev.zacsweers.autoservice:auto-service-ksp:1.1.0") ksp("dev.zacsweers.autoservice:auto-service-ksp:1.2.0")
implementation("com.google.guava:guava:33.2.0-android") implementation("com.google.guava:guava:33.2.1-android")
implementation("dev.zacsweers.autoservice:auto-service-ksp:1.1.0") implementation("dev.zacsweers.autoservice:auto-service-ksp:1.2.0")
// Media 3 (ExoPlayer) // Media 3 (ExoPlayer)
implementation("androidx.media3:media3-ui:1.1.1") implementation("androidx.media3:media3-ui:1.1.1")
@ -200,9 +200,9 @@ dependencies {
// PlayBack // PlayBack
implementation("com.jaredrummler:colorpicker:1.1.0") // Subtitle Color Picker implementation("com.jaredrummler:colorpicker:1.1.0") // Subtitle Color Picker
implementation("com.github.recloudstream:media-ffmpeg:1.1.0") // Custom FF-MPEG Lib for Audio Codecs implementation("com.github.recloudstream:media-ffmpeg:1.1.0") // Custom FF-MPEG Lib for Audio Codecs
implementation("com.github.teamnewpipe:NewPipeExtractor:fafd471") /* For Trailers implementation("com.github.teamnewpipe:NewPipeExtractor:592f159") /* For Trailers
^ Update to Latest Commits if Trailers Misbehave, github.com/TeamNewPipe/NewPipeExtractor/commits/dev */ ^ Update to Latest Commits if Trailers Misbehave, github.com/TeamNewPipe/NewPipeExtractor/commits/dev */
implementation("com.github.albfernandez:juniversalchardet:2.4.0") // Subtitle Decoding implementation("com.github.albfernandez:juniversalchardet:2.5.0") // Subtitle Decoding
// Crash Reports (AcraApplication.kt) // Crash Reports (AcraApplication.kt)
implementation("ch.acra:acra-core:5.11.3") implementation("ch.acra:acra-core:5.11.3")
@ -215,14 +215,14 @@ dependencies {
implementation("com.github.discord:OverlappingPanels:0.1.5") // Gestures implementation("com.github.discord:OverlappingPanels:0.1.5") // Gestures
implementation ("androidx.biometric:biometric:1.2.0-alpha05") // Fingerprint Authentication implementation ("androidx.biometric:biometric:1.2.0-alpha05") // Fingerprint Authentication
implementation("com.github.rubensousa:previewseekbar-media3:1.1.1.0") // SeekBar Preview implementation("com.github.rubensousa:previewseekbar-media3:1.1.1.0") // SeekBar Preview
implementation("io.github.g0dkar:qrcode-kotlin:4.1.1") // QR code for PIN Auth on TV implementation("io.github.g0dkar:qrcode-kotlin:4.2.0") // QR code for PIN Auth on TV
// Extensions & Other Libs // Extensions & Other Libs
implementation("org.mozilla:rhino:1.7.15") // run JavaScript implementation("org.mozilla:rhino:1.7.15") // run JavaScript
implementation("me.xdrop:fuzzywuzzy:1.4.0") // Library/Ext Searching with Levenshtein Distance implementation("me.xdrop:fuzzywuzzy:1.4.0") // Library/Ext Searching with Levenshtein Distance
implementation("com.github.LagradOst:SafeFile:0.0.6") // To Prevent the URI File Fu*kery implementation("com.github.LagradOst:SafeFile:0.0.6") // To Prevent the URI File Fu*kery
implementation("org.conscrypt:conscrypt-android:2.5.2") // To Fix SSL Fu*kery on Android 9 implementation("org.conscrypt:conscrypt-android:2.5.2") // To Fix SSL Fu*kery on Android 9
implementation("com.uwetrottmann.tmdb2:tmdb-java:2.10.0") // TMDB API v3 Wrapper Made with RetroFit implementation("com.uwetrottmann.tmdb2:tmdb-java:2.11.0") // TMDB API v3 Wrapper Made with RetroFit
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.4") coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.4")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.13.1") /* JSON Parser implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.13.1") /* JSON Parser
^ Don't Bump Jackson above 2.13.1 , Crashes on Android TV's and FireSticks that have Min API ^ Don't Bump Jackson above 2.13.1 , Crashes on Android TV's and FireSticks that have Min API

View file

@ -1115,23 +1115,6 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
MainAPI.settingsForProvider = settingsForProvider MainAPI.settingsForProvider = settingsForProvider
// Change library icon with logo of current api in sync
libraryViewModel = ViewModelProvider(this)[LibraryViewModel::class.java]
libraryViewModel?.currentApiName?.observe(this) {
val syncAPI = libraryViewModel?.currentSyncApi
Log.i("SYNC_API", "${syncAPI?.name}, ${syncAPI?.idPrefix}")
val icon = if (syncAPI?.idPrefix == localListApi.idPrefix) {
R.drawable.library_icon
} else {
syncAPI?.icon ?: R.drawable.library_icon
}
binding?.apply {
navRailView.menu.findItem(R.id.navigation_library)?.setIcon(icon)
navView.menu.findItem(R.id.navigation_library)?.setIcon(icon)
}
}
loadThemes(this) loadThemes(this)
updateLocale() updateLocale()
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -1537,6 +1520,26 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
logError(e) logError(e)
} }
} }
// we need to run this after we init all apis, otherwise currentSyncApi will fuck itself
this@MainActivity.runOnUiThread {
// Change library icon with logo of current api in sync
libraryViewModel = ViewModelProvider(this@MainActivity)[LibraryViewModel::class.java]
libraryViewModel?.currentApiName?.observe(this@MainActivity) {
val syncAPI = libraryViewModel?.currentSyncApi
Log.i("SYNC_API", "${syncAPI?.name}, ${syncAPI?.idPrefix}")
val icon = if (syncAPI?.idPrefix == localListApi.idPrefix) {
R.drawable.library_icon
} else {
syncAPI?.icon ?: R.drawable.library_icon
}
binding?.apply {
navRailView.menu.findItem(R.id.navigation_library)?.setIcon(icon)
navView.menu.findItem(R.id.navigation_library)?.setIcon(icon)
}
}
}
} }
SearchResultBuilder.updateCache(this) SearchResultBuilder.updateCache(this)

View file

@ -238,7 +238,7 @@ open class TraktProvider : MainAPI() {
description = episode.overview, description = episode.overview,
).apply { ).apply {
this.addDate(episode.firstAired, "yyyy-MM-dd'T'HH:mm:ss.SSSXXX") this.addDate(episode.firstAired, "yyyy-MM-dd'T'HH:mm:ss.SSSXXX")
if (nextAir == null && this.date != null && this.date!! > unixTimeMS) { if (nextAir == null && this.date != null && this.date!! > unixTimeMS && this.season != 0) {
nextAir = NextAiring( nextAir = NextAiring(
episode = this.episode!!, episode = this.episode!!,
unixTime = this.date!!.div(1000L), unixTime = this.date!!.div(1000L),

View file

@ -22,6 +22,7 @@ abstract class AccountManager(private val defIndex: Int) : AuthAPI {
val addic7ed = Addic7ed() val addic7ed = Addic7ed()
val subDlApi = SubDlApi(0) val subDlApi = SubDlApi(0)
val localListApi = LocalList() val localListApi = LocalList()
val subSourceApi = SubSourceApi()
// used to login via app intent // used to login via app intent
val OAuth2Apis val OAuth2Apis
@ -51,7 +52,8 @@ abstract class AccountManager(private val defIndex: Int) : AuthAPI {
get() = listOf( get() = listOf(
openSubtitlesApi, openSubtitlesApi,
addic7ed, addic7ed,
subDlApi subDlApi,
subSourceApi
) )
const val appString = "cloudstreamapp" const val appString = "cloudstreamapp"

View file

@ -0,0 +1,158 @@
package com.lagradost.cloudstream3.syncproviders.providers
import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.TvType
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.subtitles.AbstractSubProvider
import com.lagradost.cloudstream3.subtitles.AbstractSubtitleEntities
import com.lagradost.cloudstream3.subtitles.SubtitleResource
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
import com.lagradost.cloudstream3.utils.AppUtils.toJson
import com.lagradost.cloudstream3.utils.SubtitleHelper
class SubSourceApi : AbstractSubProvider {
override val idPrefix = "subsource"
val name = "SubSource"
companion object {
const val APIURL = "https://api.subsource.net/api"
const val DOWNLOADENDPOINT = "https://api.subsource.net/api/downloadSub"
}
override suspend fun search(query: AbstractSubtitleEntities.SubtitleSearch): List<AbstractSubtitleEntities.SubtitleEntity>? {
//Only supports Imdb Id search for now
if (query.imdbId == null) return null
val queryLang = SubtitleHelper.fromTwoLettersToLanguage(query.lang!!)
val type = if ((query.seasonNumber ?: 0) > 0) TvType.TvSeries else TvType.Movie
val searchRes = app.post(
url = "$APIURL/searchMovie",
data = mapOf(
"query" to query.imdbId!!
)
).parsedSafe<ApiSearch>() ?: return null
val postData = if (type == TvType.TvSeries) {
mapOf(
"langs" to "[]",
"movieName" to searchRes.found.first().linkName,
"season" to "season-${query.seasonNumber}"
)
} else {
mapOf(
"langs" to "[]",
"movieName" to searchRes.found.first().linkName,
)
}
val getMovieRes = app.post(
url = "$APIURL/getMovie",
data = postData
).parsedSafe<ApiResponse>().let {
// api doesn't has episode number or lang filtering
if (type == TvType.Movie) {
it?.subs?.filter { sub ->
sub.lang == queryLang
}
} else {
it?.subs?.filter { sub ->
sub.releaseName!!.contains(
String.format(
"E%02d",
query.epNumber
)
) && sub.lang == queryLang
}
}
} ?: return null
return getMovieRes.map { subtitle ->
AbstractSubtitleEntities.SubtitleEntity(
idPrefix = this.idPrefix,
name = subtitle.releaseName!!,
lang = subtitle.lang!!,
data = SubData(
movie = subtitle.linkName!!,
lang = subtitle.lang,
id = subtitle.subId.toString(),
).toJson(),
type = type,
source = this.name,
epNumber = query.epNumber,
seasonNumber = query.seasonNumber,
isHearingImpaired = subtitle.hi == 1,
)
}
}
override suspend fun SubtitleResource.getResources(data: AbstractSubtitleEntities.SubtitleEntity) {
val parsedSub = parseJson<SubData>(data.data)
val subRes = app.post(
url = "$APIURL/getSub",
data = mapOf(
"movie" to parsedSub.movie,
"lang" to data.lang,
"id" to parsedSub.id
)
).parsedSafe<SubTitleLink>() ?: return
this.addZipUrl(
"$DOWNLOADENDPOINT/${subRes.sub.downloadToken}"
) { name, _ ->
name
}
}
data class ApiSearch(
@JsonProperty("success") val success: Boolean,
@JsonProperty("found") val found: List<Found>,
)
data class Found(
@JsonProperty("id") val id: Long,
@JsonProperty("title") val title: String,
@JsonProperty("seasons") val seasons: Long,
@JsonProperty("type") val type: String,
@JsonProperty("releaseYear") val releaseYear: Long,
@JsonProperty("linkName") val linkName: String,
)
data class ApiResponse(
@JsonProperty("success") val success: Boolean,
@JsonProperty("movie") val movie: Movie,
@JsonProperty("subs") val subs: List<Sub>,
)
data class Movie(
@JsonProperty("id") val id: Long? = null,
@JsonProperty("type") val type: String? = null,
@JsonProperty("year") val year: Long? = null,
@JsonProperty("fullName") val fullName: String? = null,
)
data class Sub(
@JsonProperty("hi") val hi: Int? = null,
@JsonProperty("fullLink") val fullLink: String? = null,
@JsonProperty("linkName") val linkName: String? = null,
@JsonProperty("lang") val lang: String? = null,
@JsonProperty("releaseName") val releaseName: String? = null,
@JsonProperty("subId") val subId: Long? = null,
)
data class SubData(
@JsonProperty("movie") val movie: String,
@JsonProperty("lang") val lang: String,
@JsonProperty("id") val id: String,
)
data class SubTitleLink(
@JsonProperty("sub") val sub: SubToken,
)
data class SubToken(
@JsonProperty("downloadToken") val downloadToken: String,
)
}

View file

@ -57,8 +57,7 @@ const val ACTION_PLAY_EPISODE_IN_MPV = 17
const val ACTION_MARK_AS_WATCHED = 18 const val ACTION_MARK_AS_WATCHED = 18
const val ACTION_FCAST = 19 const val ACTION_FCAST = 19
const val TV_EP_SIZE_LARGE = 400 const val TV_EP_SIZE = 400
const val TV_EP_SIZE_SMALL = 300
data class EpisodeClickEvent(val action: Int, val data: ResultEpisode) data class EpisodeClickEvent(val action: Int, val data: ResultEpisode)
class EpisodeAdapter( class EpisodeAdapter(
@ -181,7 +180,7 @@ class EpisodeAdapter(
fun bind(card: ResultEpisode) { fun bind(card: ResultEpisode) {
localCard = card localCard = card
val setWidth = val setWidth =
if (isLayout(TV or EMULATOR)) TV_EP_SIZE_LARGE.toPx else ViewGroup.LayoutParams.MATCH_PARENT if (isLayout(TV or EMULATOR)) TV_EP_SIZE.toPx else ViewGroup.LayoutParams.MATCH_PARENT
binding.episodeLinHolder.layoutParams.width = setWidth binding.episodeLinHolder.layoutParams.width = setWidth
binding.episodeHolderLarge.layoutParams.width = setWidth binding.episodeHolderLarge.layoutParams.width = setWidth
@ -336,7 +335,7 @@ class EpisodeAdapter(
fun bind(card: ResultEpisode) { fun bind(card: ResultEpisode) {
binding.episodeHolder.layoutParams.apply { binding.episodeHolder.layoutParams.apply {
width = width =
if (isLayout(TV or EMULATOR)) TV_EP_SIZE_SMALL.toPx else ViewGroup.LayoutParams.MATCH_PARENT if (isLayout(TV or EMULATOR)) TV_EP_SIZE.toPx else ViewGroup.LayoutParams.MATCH_PARENT
} }
binding.apply { binding.apply {

View file

@ -7,6 +7,7 @@
<TextView <TextView
android:id="@+id/add_site" android:id="@+id/add_site"
android:text="@string/add_site_pref" android:text="@string/add_site_pref"
android:focusable="true"
style="@style/SettingsItem"> style="@style/SettingsItem">
<requestFocus /> <requestFocus />
@ -15,5 +16,6 @@
<TextView <TextView
android:id="@+id/remove_site" android:id="@+id/remove_site"
android:text="@string/remove_site_pref" android:text="@string/remove_site_pref"
android:focusable="true"
style="@style/SettingsItem" /> style="@style/SettingsItem" />
</LinearLayout> </LinearLayout>

View file

@ -90,14 +90,15 @@
android:textColor="?attr/textColor" android:textColor="?attr/textColor"
tools:text="Episode 1" /> tools:text="Episode 1" />
</LinearLayout>
<com.lagradost.cloudstream3.ui.download.button.PieFetchButton <com.lagradost.cloudstream3.ui.download.button.PieFetchButton
android:id="@+id/download_button" android:id="@+id/download_button"
android:layout_width="@dimen/download_size" android:layout_width="@dimen/download_size"
android:layout_height="@dimen/download_size" android:layout_height="@dimen/download_size"
android:layout_gravity="center_vertical|end" android:layout_gravity="center_vertical|end"
android:layout_marginStart="-50dp" android:layout_marginStart="-60dp"
android:background="?selectableItemBackgroundBorderless" android:background="?selectableItemBackgroundBorderless"
android:padding="10dp" /> android:padding="10dp" />
</LinearLayout>
</androidx.cardview.widget.CardView> </androidx.cardview.widget.CardView>

View file

@ -8,221 +8,135 @@
<ScrollView <ScrollView
android:id="@+id/result_sync_holder" android:id="@+id/result_sync_holder"
tools:visibility="visible" android:layout_width="match_parent"
android:visibility="gone" android:layout_height="wrap_content"
android:padding="16dp" android:padding="16dp"
android:layout_width="match_parent" android:visibility="gone"
android:layout_height="wrap_content"> tools:visibility="visible">
<LinearLayout <LinearLayout
android:orientation="vertical"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content"
android:orientation="vertical">
<TextView <TextView
android:id="@+id/result_sync_names" android:id="@+id/result_sync_names"
android:textStyle="bold" android:layout_width="wrap_content"
android:textSize="16sp" android:layout_height="wrap_content"
android:layout_marginBottom="10dp" android:layout_marginBottom="10dp"
android:text="MyAnimeList, AniList" android:text="MyAnimeList, AniList"
android:layout_width="wrap_content" android:textSize="16sp"
android:layout_height="wrap_content" /> android:textStyle="bold" />
<LinearLayout <LinearLayout
android:visibility="visible"
android:orientation="horizontal"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content"
android:orientation="horizontal"
android:visibility="visible">
<ImageView <ImageView
android:id="@+id/result_sync_sub_episode" android:id="@+id/result_sync_sub_episode"
android:padding="10dp"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:src="@drawable/baseline_remove_24"
android:layout_gravity="end|center_vertical" android:layout_gravity="end|center_vertical"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:padding="10dp"
android:src="@drawable/baseline_remove_24"
app:tint="?attr/textColor" /> app:tint="?attr/textColor" />
<EditText <EditText
android:textColorHint="?attr/grayTextColor"
android:id="@+id/result_sync_current_episodes" android:id="@+id/result_sync_current_episodes"
style="@style/AppEditStyle" style="@style/AppEditStyle"
tools:hint="20"
android:textSize="20sp"
android:inputType="number"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:inputType="number"
android:textColorHint="?attr/grayTextColor"
android:textSize="20sp"
tools:hint="20"
tools:ignore="LabelFor" /> tools:ignore="LabelFor" />
<TextView <TextView
android:id="@+id/result_sync_max_episodes" android:id="@+id/result_sync_max_episodes"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
android:paddingBottom="1dp" android:paddingBottom="1dp"
android:textSize="20sp"
android:textColor="?attr/textColor" android:textColor="?attr/textColor"
tools:text="30" android:textSize="20sp"
android:layout_width="wrap_content" tools:text="30" />
android:layout_height="wrap_content" />
<ImageView <ImageView
android:id="@+id/result_sync_add_episode" android:id="@+id/result_sync_add_episode"
android:padding="10dp"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_baseline_add_24"
android:layout_gravity="end|center_vertical" android:layout_gravity="end|center_vertical"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:padding="10dp"
android:src="@drawable/ic_baseline_add_24"
app:tint="?attr/textColor" /> app:tint="?attr/textColor" />
</LinearLayout> </LinearLayout>
<androidx.core.widget.ContentLoadingProgressBar <androidx.core.widget.ContentLoadingProgressBar
android:id="@+id/result_sync_episodes" android:id="@+id/result_sync_episodes"
android:padding="10dp" style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="20dp" android:layout_height="20dp"
android:progress="0"
android:indeterminate="false"
android:progressBackgroundTint="?attr/colorPrimary"
style="?android:attr/progressBarStyleHorizontal"
android:max="100"
android:layout_gravity="end|center_vertical" android:layout_gravity="end|center_vertical"
android:indeterminate="false"
android:max="100"
android:padding="10dp"
android:progress="0"
android:progressBackgroundTint="?attr/colorPrimary"
tools:visibility="visible" /> tools:visibility="visible" />
<!--
<LinearLayout <LinearLayout
android:layout_marginBottom="10dp"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
android:padding="10dp" android:padding="10dp"
android:textSize="17sp"
android:textColor="?attr/textColor"
android:text="Status:"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<com.google.android.material.button.MaterialButton
android:layout_height="30dp"
android:text="Watching"
android:minWidth="0dp"
android:layout_width="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="0dp"
style="@style/BlackButton" />
</LinearLayout>
<GridLayout
android:orientation="horizontal"
android:columnCount="2"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.button.MaterialButton
android:id="@+id/sync_completed"
android:nextFocusRight="@id/sync_on_hold"
android:nextFocusDown="@id/sync_plan_to_watch"
android:layout_row="0"
android:layout_column="0"
android:text="@string/type_completed"
style="@style/SyncButton" />
<com.google.android.material.button.MaterialButton
android:id="@+id/sync_on_hold"
android:nextFocusDown="@id/sync_watching"
android:nextFocusLeft="@id/sync_completed"
android:layout_row="0"
android:layout_column="1"
style="@style/SyncButton"
android:text="@string/type_on_hold" />
<com.google.android.material.button.MaterialButton
android:id="@+id/sync_plan_to_watch"
android:nextFocusRight="@id/sync_plan_to_watch"
android:nextFocusDown="@id/sync_dropped"
android:nextFocusUp="@id/sync_completed"
android:layout_row="1"
android:layout_column="0"
android:text="@string/type_plan_to_watch"
style="@style/SyncButton"
/>
<com.google.android.material.button.MaterialButton
android:id="@+id/sync_watching"
android:nextFocusLeft="@id/sync_plan_to_watch"
android:nextFocusDown="@id/sync_dropped"
android:nextFocusUp="@id/sync_on_hold"
android:layout_row="1"
android:layout_column="1"
style="@style/SyncButton"
android:text="@string/type_watching" />
</GridLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/sync_dropped"
android:nextFocusUp="@id/sync_plan_to_watch"
android:layout_width="match_parent"
style="@style/SyncButton"
android:text="@string/type_dropped" />-->
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_gravity="center_vertical"
android:padding="10dp"
android:textSize="17sp"
android:textColor="?attr/textColor"
android:text="@string/sync_score" android:text="@string/sync_score"
android:layout_width="wrap_content" android:textColor="?attr/textColor"
android:layout_height="wrap_content" /> android:textSize="17sp" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/result_sync_score_text" android:id="@+id/result_sync_score_text"
android:layout_height="30dp" style="@style/BlackButton"
android:text="7/10"
android:minWidth="0dp"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="30dp"
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
android:layout_marginStart="0dp" android:layout_marginStart="0dp"
style="@style/BlackButton" /> android:minWidth="0dp"
android:text="7/10" />
</LinearLayout> </LinearLayout>
<com.google.android.material.slider.Slider <com.google.android.material.slider.Slider
android:id="@+id/result_sync_rating" android:id="@+id/result_sync_rating"
android:valueFrom="0" android:layout_width="wrap_content"
android:valueTo="10" android:layout_height="wrap_content"
android:value="4"
android:stepSize="1"
app:tickVisible="false"
android:layout_marginStart="-5dp" android:layout_marginStart="-5dp"
android:layout_marginEnd="-5dp" android:layout_marginEnd="-5dp"
app:thumbRadius="10dp" app:thumbHeight="20dp"
android:stepSize="1"
android:value="4"
android:valueFrom="0"
android:valueTo="10"
app:labelStyle="@style/BlackLabel" app:labelStyle="@style/BlackLabel"
android:layout_width="wrap_content" app:thumbRadius="10dp"
android:layout_height="wrap_content" /> app:tickVisible="false" />
<FrameLayout <FrameLayout
android:visibility="gone" android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingTop="12dp" android:paddingTop="12dp"
android:paddingBottom="12dp" android:paddingBottom="12dp"
android:orientation="horizontal" android:visibility="gone">
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView <TextView
android:id="@+id/home_parent_item_title" android:id="@+id/home_parent_item_title"
@ -230,52 +144,53 @@
tools:text="Recommended" /> tools:text="Recommended" />
<ImageView <ImageView
app:tint="?attr/textColor"
android:layout_marginEnd="5dp"
android:layout_gravity="end|center_vertical"
android:src="@drawable/ic_baseline_arrow_forward_24"
android:layout_width="30dp" android:layout_width="30dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:contentDescription="@string/home_more_info" /> android:layout_gravity="end|center_vertical"
android:layout_marginEnd="5dp"
android:contentDescription="@string/home_more_info"
android:src="@drawable/ic_baseline_arrow_forward_24"
app:tint="?attr/textColor" />
</FrameLayout> </FrameLayout>
<ListView <ListView
android:id="@+id/result_sync_check" android:id="@+id/result_sync_check"
tools:listitem="@layout/sort_bottom_single_choice"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_rowWeight="1" /> android:layout_rowWeight="1"
tools:listitem="@layout/sort_bottom_single_choice" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:visibility="gone"
android:layout_marginTop="10dp"
android:layout_width="match_parent"
style="@style/WhiteButton" style="@style/WhiteButton"
android:text="@string/type_watching" /> android:layout_width="match_parent"
android:layout_marginTop="10dp"
android:text="@string/type_watching"
android:visibility="gone" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/result_sync_set_score" android:id="@+id/result_sync_set_score"
android:layout_marginTop="10dp"
android:layout_width="match_parent"
style="@style/BlackButton" style="@style/BlackButton"
app:icon="@drawable/baseline_sync_24" android:layout_width="match_parent"
android:text="@string/upload_sync" /> android:layout_marginTop="10dp"
android:text="@string/upload_sync"
app:icon="@drawable/baseline_sync_24" />
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>
<com.facebook.shimmer.ShimmerFrameLayout <com.facebook.shimmer.ShimmerFrameLayout
tools:visibility="gone"
android:id="@+id/result_sync_loading_shimmer" android:id="@+id/result_sync_loading_shimmer"
app:shimmer_base_alpha="0.2"
app:shimmer_highlight_alpha="0.3"
app:shimmer_duration="@integer/loading_time"
app:shimmer_auto_start="true"
android:padding="15dp"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="center" android:layout_gravity="center"
android:orientation="vertical"> android:orientation="vertical"
android:padding="15dp"
app:shimmer_auto_start="true"
app:shimmer_base_alpha="0.2"
app:shimmer_duration="@integer/loading_time"
app:shimmer_highlight_alpha="0.3"
tools:visibility="gone"
tools:ignore="MissingClass">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"

View file

@ -6,6 +6,7 @@ import com.lagradost.cloudstream3.extractors.helper.AesHelper.cryptoAESHandler
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 kotlin.run
class Moviesapi : Chillx() { class Moviesapi : Chillx() {
override val name = "Moviesapi" override val name = "Moviesapi"
@ -28,17 +29,22 @@ open class Chillx : ExtractorApi() {
override val requiresReferer = true override val requiresReferer = true
companion object { companion object {
private val keySource = "https://rowdy-avocado.github.io/multi-keys/"
private var key: String? = null private var key: String? = null
suspend fun fetchKey(): String { private suspend fun fetchKey(): String {
return if (key != null) { return key
key!! ?: run {
} else { val res =
val fetch = app.get("https://raw.githubusercontent.com/rushi-chavan/multi-keys/keys/keys.json").parsedSafe<Keys>()?.key?.get(0) ?: throw ErrorLoadingException("Unable to get key") app.get(keySource).parsedSafe<KeysData>()
key = fetch ?: throw ErrorLoadingException("Unable to get keys")
key!! key = res.keys.get(0)
res.keys.get(0)
} }
} }
private data class KeysData(@JsonProperty("chillx") val keys: List<String>)
} }
@Suppress("NAME_SHADOWING") @Suppress("NAME_SHADOWING")
@ -97,11 +103,4 @@ open class Chillx : ExtractorApi() {
it.groupValues[1].toInt(16).toChar().toString() it.groupValues[1].toInt(16).toChar().toString()
} }
} }
data class Keys(
@JsonProperty("chillx") val key: List<String>
)
} }

View file

@ -22,6 +22,11 @@ class FourPlayRu : ContentX() {
override var mainUrl = "https://four.playru.net" override var mainUrl = "https://four.playru.net"
} }
class Pichive : ContentX() {
override var name = "Pichive"
override var mainUrl = "https://pichive.online"
}
class FourPichive : ContentX() { class FourPichive : ContentX() {
override var name = "FourPichive" override var name = "FourPichive"
override var mainUrl = "https://four.pichive.online" override var mainUrl = "https://four.pichive.online"

View file

@ -0,0 +1,53 @@
// ! Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
package com.lagradost.cloudstream3.extractors
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.utils.*
import com.fasterxml.jackson.annotation.JsonProperty
open class Sobreatsesuyp : ExtractorApi() {
override val name = "Sobreatsesuyp"
override val mainUrl = "https://sobreatsesuyp.com"
override val requiresReferer = true
override suspend fun getUrl(url: String, referer: String?, subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit) {
val extRef = referer ?: ""
val videoReq = app.get(url, referer = extRef).text
val file = Regex("""file\":\"([^\"]+)""").find(videoReq)?.groupValues?.get(1) ?: throw ErrorLoadingException("File not found")
val postLink = "${mainUrl}/" + file.replace("\\", "")
val rawList = app.post(postLink, referer = extRef).parsedSafe<List<Any>>() ?: throw ErrorLoadingException("Post link not found")
val postJson: List<SobreatsesuypVideoData> = rawList.drop(1).map { item ->
val mapItem = item as Map<*, *>
SobreatsesuypVideoData(
title = mapItem["title"] as? String,
file = mapItem["file"] as? String
)
}
for (item in postJson) {
if (item.file == null || item.title == null) continue
val videoData = app.post("${mainUrl}/playlist/${item.file.substring(1)}.txt", referer = extRef).text
callback.invoke(
ExtractorLink(
source = this.name,
name = "${this.name} - ${item.title}",
url = videoData,
referer = extRef,
quality = Qualities.Unknown.value,
type = INFER_TYPE
)
)
}
}
data class SobreatsesuypVideoData(
@JsonProperty("title") val title: String? = null,
@JsonProperty("file") val file: String? = null
)
}

View file

@ -26,7 +26,13 @@ class VidSrcTo : ExtractorApi() {
callback: (ExtractorLink) -> Unit callback: (ExtractorLink) -> Unit
) { ) {
val mediaId = app.get(url).document.selectFirst("ul.episodes li a")?.attr("data-id") ?: return val mediaId = app.get(url).document.selectFirst("ul.episodes li a")?.attr("data-id") ?: return
val res = app.get("$mainUrl/ajax/embed/episode/$mediaId/sources").parsedSafe<VidsrctoEpisodeSources>() ?: return val subtitlesLink = "$mainUrl/ajax/embed/episode/$mediaId/subtitles"
val subRes = app.get(subtitlesLink).parsedSafe<Array<VidsrctoSubtitles>>()
subRes?.forEach {
if (it.kind.equals("captions")) subtitleCallback.invoke(SubtitleFile(it.label, it.file))
}
val sourcesLink = "$mainUrl/ajax/embed/episode/$mediaId/sources"
val res = app.get(sourcesLink).parsedSafe<VidsrctoEpisodeSources>() ?: return
if (res.status != 200) return if (res.status != 200) return
res.result?.amap { source -> res.result?.amap { source ->
try { try {
@ -68,5 +74,11 @@ class VidSrcTo : ExtractorApi() {
@JsonProperty("result") val result: VidsrctoUrl @JsonProperty("result") val result: VidsrctoUrl
) )
data class VidsrctoSubtitles(
@JsonProperty("file") val file: String,
@JsonProperty("label") val label: String,
@JsonProperty("kind") val kind: String
)
data class VidsrctoUrl(@JsonProperty("url") val encUrl: String) data class VidsrctoUrl(@JsonProperty("url") val encUrl: String)
} }

View file

@ -1,6 +1,7 @@
package com.lagradost.cloudstream3.extractors package com.lagradost.cloudstream3.extractors
import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.annotation.JsonProperty
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.base64Encode import com.lagradost.cloudstream3.base64Encode
@ -9,6 +10,7 @@ import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.M3u8Helper import com.lagradost.cloudstream3.utils.M3u8Helper
import javax.crypto.Cipher import javax.crypto.Cipher
import javax.crypto.spec.SecretKeySpec import javax.crypto.spec.SecretKeySpec
import kotlin.run
// Code found in https://github.com/KillerDogeEmpire/vidplay-keys // Code found in https://github.com/KillerDogeEmpire/vidplay-keys
// special credits to @KillerDogeEmpire for providing key // special credits to @KillerDogeEmpire for providing key
@ -35,8 +37,25 @@ open class Vidplay : ExtractorApi() {
override val name = "Vidplay" override val name = "Vidplay"
override val mainUrl = "https://vidplay.site" override val mainUrl = "https://vidplay.site"
override val requiresReferer = true override val requiresReferer = true
open val key =
"https://raw.githubusercontent.com/KillerDogeEmpire/vidplay-keys/keys/keys.json" companion object {
private val keySource = "https://rowdy-avocado.github.io/multi-keys/"
private var keys: List<String>? = null
private suspend fun getKeys(): List<String> {
return keys
?: run {
val res =
app.get(keySource).parsedSafe<KeysData>()
?: throw ErrorLoadingException("Unable to get keys")
keys = res.keys
res.keys
}
}
private data class KeysData(@JsonProperty("vidplay") val keys: List<String>)
}
override suspend fun getUrl( override suspend fun getUrl(
url: String, url: String,
@ -70,10 +89,6 @@ open class Vidplay : ExtractorApi() {
} }
private suspend fun getKeys(): List<String> {
return app.get(key).parsed()
}
private suspend fun callFutoken(id: String, url: String): String? { private suspend fun callFutoken(id: String, url: String): String? {
val script = app.get("$mainUrl/futoken", referer = url).text val script = app.get("$mainUrl/futoken", referer = url).text
val k = "k='(\\S+)'".toRegex().find(script)?.groupValues?.get(1) ?: return null val k = "k='(\\S+)'".toRegex().find(script)?.groupValues?.get(1) ?: return null

View file

@ -115,6 +115,7 @@ 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.PlayRu
import com.lagradost.cloudstream3.extractors.FourPlayRu import com.lagradost.cloudstream3.extractors.FourPlayRu
import com.lagradost.cloudstream3.extractors.Pichive
import com.lagradost.cloudstream3.extractors.FourPichive import com.lagradost.cloudstream3.extractors.FourPichive
import com.lagradost.cloudstream3.extractors.HDMomPlayer import com.lagradost.cloudstream3.extractors.HDMomPlayer
import com.lagradost.cloudstream3.extractors.HDPlayerSystem import com.lagradost.cloudstream3.extractors.HDPlayerSystem
@ -124,6 +125,7 @@ import com.lagradost.cloudstream3.extractors.HDStreamAble
import com.lagradost.cloudstream3.extractors.RapidVid import com.lagradost.cloudstream3.extractors.RapidVid
import com.lagradost.cloudstream3.extractors.TRsTX import com.lagradost.cloudstream3.extractors.TRsTX
import com.lagradost.cloudstream3.extractors.VidMoxy import com.lagradost.cloudstream3.extractors.VidMoxy
import com.lagradost.cloudstream3.extractors.Sobreatsesuyp
import com.lagradost.cloudstream3.extractors.PixelDrain import com.lagradost.cloudstream3.extractors.PixelDrain
import com.lagradost.cloudstream3.extractors.MailRu import com.lagradost.cloudstream3.extractors.MailRu
import com.lagradost.cloudstream3.extractors.Mediafire import com.lagradost.cloudstream3.extractors.Mediafire
@ -734,6 +736,7 @@ val extractorApis: MutableList<ExtractorApi> = arrayListOf(
FourCX(), FourCX(),
PlayRu(), PlayRu(),
FourPlayRu(), FourPlayRu(),
Pichive(),
FourPichive(), FourPichive(),
HDMomPlayer(), HDMomPlayer(),
HDPlayerSystem(), HDPlayerSystem(),
@ -743,6 +746,7 @@ val extractorApis: MutableList<ExtractorApi> = arrayListOf(
RapidVid(), RapidVid(),
TRsTX(), TRsTX(),
VidMoxy(), VidMoxy(),
Sobreatsesuyp(),
PixelDrain(), PixelDrain(),
MailRu(), MailRu(),