mirror of
				https://github.com/recloudstream/cloudstream.git
				synced 2024-08-15 01:53:11 +00:00 
			
		
		
		
	Merge branch 'recloudstream:master' into master
This commit is contained in:
		
						commit
						ef7b66989c
					
				
					 14 changed files with 267 additions and 20 deletions
				
			
		|  | @ -0,0 +1,101 @@ | ||||||
|  | package com.lagradost.cloudstream3.extractors | ||||||
|  | 
 | ||||||
|  | import android.util.Log | ||||||
|  | import com.lagradost.cloudstream3.SubtitleFile | ||||||
|  | import com.lagradost.cloudstream3.app | ||||||
|  | import com.lagradost.cloudstream3.utils.AppUtils | ||||||
|  | import com.lagradost.cloudstream3.utils.ExtractorApi | ||||||
|  | import com.lagradost.cloudstream3.utils.ExtractorLink | ||||||
|  | import com.lagradost.cloudstream3.utils.INFER_TYPE | ||||||
|  | import com.lagradost.cloudstream3.utils.Qualities | ||||||
|  | import org.mozilla.javascript.Context | ||||||
|  | import org.mozilla.javascript.NativeJSON | ||||||
|  | import org.mozilla.javascript.NativeObject | ||||||
|  | import org.mozilla.javascript.Scriptable | ||||||
|  | import java.util.Base64 | ||||||
|  | 
 | ||||||
|  | open class Vidguardto : ExtractorApi() { | ||||||
|  |     override val name = "Vidguard" | ||||||
|  |     override val mainUrl = "https://vidguard.to" | ||||||
|  |     override val requiresReferer = false | ||||||
|  | 
 | ||||||
|  |     override suspend fun getUrl( | ||||||
|  |         url: String, | ||||||
|  |         referer: String?, | ||||||
|  |         subtitleCallback: (SubtitleFile) -> Unit, | ||||||
|  |         callback: (ExtractorLink) -> Unit | ||||||
|  |     ) { | ||||||
|  | 	val res = app.get(url) | ||||||
|  | 	val resc = res.document.select("script:containsData(eval)").firstOrNull()?.data() | ||||||
|  |         resc?.let { | ||||||
|  |             val jsonStr2 = AppUtils.parseJson<SvgObject>(runJS2(it)) | ||||||
|  |             val watchlink = sigDecode(jsonStr2.stream) | ||||||
|  | 
 | ||||||
|  |             callback.invoke( | ||||||
|  |                 ExtractorLink( | ||||||
|  |                     this.name, | ||||||
|  |                     name, | ||||||
|  |                     watchlink, | ||||||
|  |                     this.mainUrl, | ||||||
|  |                     Qualities.Unknown.value, | ||||||
|  |                     INFER_TYPE | ||||||
|  |                 ) | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private fun sigDecode(url: String): String { | ||||||
|  |         val sig = url.split("sig=")[1].split("&")[0] | ||||||
|  |         var t = "" | ||||||
|  |         for (v in sig.chunked(2)) { | ||||||
|  |             val byteValue = Integer.parseInt(v, 16) xor 2 | ||||||
|  |             t += byteValue.toChar() | ||||||
|  |         } | ||||||
|  |         val padding = when (t.length % 4) { | ||||||
|  |             2 -> "==" | ||||||
|  |             3 -> "=" | ||||||
|  |             else -> "" | ||||||
|  |         } | ||||||
|  |         val decoded = Base64.getDecoder().decode((t + padding).toByteArray(Charsets.UTF_8)) | ||||||
|  |         t = String(decoded).dropLast(5).reversed() | ||||||
|  |         val charArray = t.toCharArray() | ||||||
|  |         for (i in 0 until charArray.size - 1 step 2) { | ||||||
|  |             val temp = charArray[i] | ||||||
|  |             charArray[i] = charArray[i + 1] | ||||||
|  |             charArray[i + 1] = temp | ||||||
|  |         } | ||||||
|  |         val modifiedSig = String(charArray).dropLast(5) | ||||||
|  |         return url.replace(sig, modifiedSig) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private fun runJS2(hideMyHtmlContent: String): String { | ||||||
|  |         Log.d("runJS", "start") | ||||||
|  |         val rhino = Context.enter() | ||||||
|  |         rhino.initSafeStandardObjects() | ||||||
|  |         rhino.optimizationLevel = -1 | ||||||
|  |         val scope: Scriptable = rhino.initSafeStandardObjects() | ||||||
|  |         scope.put("window", scope, scope) | ||||||
|  |         var result = "" | ||||||
|  |         try { | ||||||
|  |             Log.d("runJS", "Executing JavaScript: $hideMyHtmlContent") | ||||||
|  |             rhino.evaluateString(scope, hideMyHtmlContent, "JavaScript", 1, null) | ||||||
|  |             val svgObject = scope.get("svg", scope) | ||||||
|  |             result = if (svgObject is NativeObject) { | ||||||
|  |                 NativeJSON.stringify(Context.getCurrentContext(), scope, svgObject, null, null).toString() | ||||||
|  |             } else { | ||||||
|  |                 Context.toString(svgObject) | ||||||
|  |             } | ||||||
|  |             Log.d("runJS", "Result: $result") | ||||||
|  |         } catch (e: Exception) { | ||||||
|  |             Log.e("runJS", "Error executing JavaScript", e) | ||||||
|  |         } finally { | ||||||
|  |             Context.exit() | ||||||
|  |         } | ||||||
|  |         return result | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     data class SvgObject( | ||||||
|  |         val stream: String, | ||||||
|  |         val hash: String | ||||||
|  |     ) | ||||||
|  | } | ||||||
|  | @ -1,13 +1,37 @@ | ||||||
| package com.lagradost.cloudstream3.extractors | package com.lagradost.cloudstream3.extractors | ||||||
| 
 | 
 | ||||||
|  | import android.util.Base64 | ||||||
|  | import com.fasterxml.jackson.annotation.JsonProperty | ||||||
| import com.lagradost.cloudstream3.SubtitleFile | import com.lagradost.cloudstream3.SubtitleFile | ||||||
| import com.lagradost.cloudstream3.app | import com.lagradost.cloudstream3.app | ||||||
|  | import com.lagradost.cloudstream3.utils.AppUtils | ||||||
| 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 | ||||||
| 
 | 
 | ||||||
| class Tubeless : Voe() { | class Tubeless : Voe() { | ||||||
|     override var mainUrl = "https://tubelessceliolymph.com" |     override val name = "Tubeless" | ||||||
|  |     override val mainUrl = "https://tubelessceliolymph.com" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | class Simpulumlamerop : Voe() { | ||||||
|  |     override val name = "Simplum" | ||||||
|  |     override var mainUrl = "https://simpulumlamerop.com" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | class Urochsunloath : Voe() { | ||||||
|  |     override val name = "Uroch" | ||||||
|  |     override var mainUrl = "https://urochsunloath.com" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | class Yipsu : Voe() { | ||||||
|  |     override val name = "Yipsu" | ||||||
|  |     override var mainUrl = "https://yip.su" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | class MetaGnathTuggers : Voe() { | ||||||
|  |     override val name = "Metagnath" | ||||||
|  |     override val mainUrl = "https://metagnathtuggers.com" | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| open class Voe : ExtractorApi() { | open class Voe : ExtractorApi() { | ||||||
|  | @ -15,6 +39,9 @@ open class Voe : ExtractorApi() { | ||||||
|     override val mainUrl = "https://voe.sx" |     override val mainUrl = "https://voe.sx" | ||||||
|     override val requiresReferer = true |     override val requiresReferer = true | ||||||
| 	 | 	 | ||||||
|  | 	private val linkRegex = "(http|https)://([\\w_-]+(?:\\.[\\w_-]+)+)([\\w.,@?^=%&:/~+#-]*[\\w@?^=%&/~+#-])".toRegex() | ||||||
|  |     private val base64Regex = Regex("'.*'") | ||||||
|  | 
 | ||||||
|     override suspend fun getUrl( |     override suspend fun getUrl( | ||||||
|         url: String, |         url: String, | ||||||
|         referer: String?, |         referer: String?, | ||||||
|  | @ -25,12 +52,33 @@ open class Voe : ExtractorApi() { | ||||||
|         val script = res.select("script").find { it.data().contains("sources =") }?.data() |         val script = res.select("script").find { it.data().contains("sources =") }?.data() | ||||||
|         val link = Regex("[\"']hls[\"']:\\s*[\"'](.*)[\"']").find(script ?: return)?.groupValues?.get(1) |         val link = Regex("[\"']hls[\"']:\\s*[\"'](.*)[\"']").find(script ?: return)?.groupValues?.get(1) | ||||||
| 
 | 
 | ||||||
|  |         val videoLinks = mutableListOf<String>() | ||||||
|  |          | ||||||
|  |         if (!link.isNullOrBlank()) { | ||||||
|  |             videoLinks.add( | ||||||
|  |                 when { | ||||||
|  |                     linkRegex.matches(link) -> link | ||||||
|  |                     else -> String(Base64.decode(link, Base64.DEFAULT)) | ||||||
|  |                 } | ||||||
|  |             ) | ||||||
|  |         } else {             | ||||||
|  |             val link2 = base64Regex.find(script)?.value ?: return | ||||||
|  |             val decoded = Base64.decode(link2, Base64.DEFAULT).toString() | ||||||
|  |             val videoLinkDTO = AppUtils.parseJson<WcoSources>(decoded) | ||||||
|  |             videoLinkDTO.let { videoLinks.add(it.toString()) } | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         videoLinks.forEach { videoLink -> | ||||||
|             M3u8Helper.generateM3u8( |             M3u8Helper.generateM3u8( | ||||||
|                 name, |                 name, | ||||||
|             link ?: return, |                 videoLink, | ||||||
|                 "$mainUrl/", |                 "$mainUrl/", | ||||||
|                 headers = mapOf("Origin" to "$mainUrl/") |                 headers = mapOf("Origin" to "$mainUrl/") | ||||||
|             ).forEach(callback) |             ).forEach(callback) | ||||||
|  |         } | ||||||
|  |     } | ||||||
| 	 | 	 | ||||||
|     } | 	data class WcoSources( | ||||||
|  |         @JsonProperty("VideoLinkDTO") val VideoLinkDTO: String, | ||||||
|  |     ) | ||||||
| } | } | ||||||
|  | @ -15,6 +15,7 @@ import com.lagradost.cloudstream3.utils.Coroutines.main | ||||||
| import com.lagradost.cloudstream3.utils.DataStore.getKey | import com.lagradost.cloudstream3.utils.DataStore.getKey | ||||||
| import com.lagradost.cloudstream3.utils.DataStore.getKeys | import com.lagradost.cloudstream3.utils.DataStore.getKeys | ||||||
| import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar | import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar | ||||||
|  | import com.lagradost.cloudstream3.utils.UIHelper.setAppBarNoScrollFlagsOnTV | ||||||
| import com.lagradost.cloudstream3.utils.VideoDownloadHelper | import com.lagradost.cloudstream3.utils.VideoDownloadHelper | ||||||
| import com.lagradost.cloudstream3.utils.VideoDownloadManager | import com.lagradost.cloudstream3.utils.VideoDownloadManager | ||||||
| import kotlinx.coroutines.Dispatchers | import kotlinx.coroutines.Dispatchers | ||||||
|  | @ -89,9 +90,9 @@ class DownloadChildFragment : Fragment() { | ||||||
|             setNavigationOnClickListener { |             setNavigationOnClickListener { | ||||||
|                 activity?.onBackPressedDispatcher?.onBackPressed() |                 activity?.onBackPressedDispatcher?.onBackPressed() | ||||||
|             } |             } | ||||||
|  |             setAppBarNoScrollFlagsOnTV() | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|         val adapter: RecyclerView.Adapter<RecyclerView.ViewHolder> = |         val adapter: RecyclerView.Adapter<RecyclerView.ViewHolder> = | ||||||
|             DownloadChildAdapter( |             DownloadChildAdapter( | ||||||
|                 ArrayList(), |                 ArrayList(), | ||||||
|  |  | ||||||
|  | @ -41,6 +41,7 @@ import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe | ||||||
| import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar | import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar | ||||||
| import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard | import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard | ||||||
| import com.lagradost.cloudstream3.utils.UIHelper.navigate | import com.lagradost.cloudstream3.utils.UIHelper.navigate | ||||||
|  | import com.lagradost.cloudstream3.utils.UIHelper.setAppBarNoScrollFlagsOnTV | ||||||
| import com.lagradost.cloudstream3.utils.VideoDownloadHelper | import com.lagradost.cloudstream3.utils.VideoDownloadHelper | ||||||
| import com.lagradost.cloudstream3.utils.VideoDownloadManager | import com.lagradost.cloudstream3.utils.VideoDownloadManager | ||||||
| import java.net.URI | import java.net.URI | ||||||
|  | @ -97,6 +98,8 @@ class DownloadFragment : Fragment() { | ||||||
|         super.onViewCreated(view, savedInstanceState) |         super.onViewCreated(view, savedInstanceState) | ||||||
|         hideKeyboard() |         hideKeyboard() | ||||||
| 
 | 
 | ||||||
|  |         binding?.downloadStorageAppbar?.setAppBarNoScrollFlagsOnTV() | ||||||
|  | 
 | ||||||
|         observe(downloadsViewModel.noDownloadsText) { |         observe(downloadsViewModel.noDownloadsText) { | ||||||
|             binding?.textNoDownloads?.text = it |             binding?.textNoDownloads?.text = it | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -9,9 +9,11 @@ import androidx.core.view.isVisible | ||||||
| import androidx.preference.PreferenceManager | import androidx.preference.PreferenceManager | ||||||
| import androidx.recyclerview.widget.DiffUtil | import androidx.recyclerview.widget.DiffUtil | ||||||
| import androidx.recyclerview.widget.RecyclerView | import androidx.recyclerview.widget.RecyclerView | ||||||
|  | import com.lagradost.cloudstream3.APIHolder.unixTimeMS | ||||||
| import com.lagradost.cloudstream3.R | import com.lagradost.cloudstream3.R | ||||||
| import com.lagradost.cloudstream3.databinding.ResultEpisodeBinding | import com.lagradost.cloudstream3.databinding.ResultEpisodeBinding | ||||||
| import com.lagradost.cloudstream3.databinding.ResultEpisodeLargeBinding | import com.lagradost.cloudstream3.databinding.ResultEpisodeLargeBinding | ||||||
|  | import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.secondsToReadable | ||||||
| import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_DOWNLOAD | import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_DOWNLOAD | ||||||
| import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_LONG_CLICK | import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_LONG_CLICK | ||||||
| import com.lagradost.cloudstream3.ui.download.DownloadClickEvent | import com.lagradost.cloudstream3.ui.download.DownloadClickEvent | ||||||
|  | @ -23,6 +25,8 @@ import com.lagradost.cloudstream3.utils.AppUtils.html | ||||||
| import com.lagradost.cloudstream3.utils.UIHelper.setImage | import com.lagradost.cloudstream3.utils.UIHelper.setImage | ||||||
| import com.lagradost.cloudstream3.utils.UIHelper.toPx | import com.lagradost.cloudstream3.utils.UIHelper.toPx | ||||||
| import com.lagradost.cloudstream3.utils.VideoDownloadHelper | import com.lagradost.cloudstream3.utils.VideoDownloadHelper | ||||||
|  | import java.text.DateFormat | ||||||
|  | import java.text.SimpleDateFormat | ||||||
| import java.util.* | import java.util.* | ||||||
| 
 | 
 | ||||||
| const val ACTION_PLAY_EPISODE_IN_PLAYER = 1 | const val ACTION_PLAY_EPISODE_IN_PLAYER = 1 | ||||||
|  | @ -104,7 +108,7 @@ class EpisodeAdapter( | ||||||
| 
 | 
 | ||||||
|     override fun getItemViewType(position: Int): Int { |     override fun getItemViewType(position: Int): Int { | ||||||
|         val item = getItem(position) |         val item = getItem(position) | ||||||
|         return if (item.poster.isNullOrBlank()) 0 else 1 |         return if (item.poster.isNullOrBlank() && item.description.isNullOrBlank()) 0 else 1 | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -260,6 +264,33 @@ class EpisodeAdapter( | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|  |                 if (card.airDate != null) { | ||||||
|  |                     val isUpcoming = unixTimeMS < card.airDate | ||||||
|  | 
 | ||||||
|  |                     if (isUpcoming) { | ||||||
|  |                         episodePlayIcon.isVisible = false | ||||||
|  |                         episodeUpcomingIcon.isVisible = !episodePoster.isVisible | ||||||
|  |                         episodeDate.setText( | ||||||
|  |                             txt( | ||||||
|  |                                 R.string.episode_upcoming_format, | ||||||
|  |                                 secondsToReadable(card.airDate.minus(unixTimeMS).div(1000).toInt(), "") | ||||||
|  |                             ) | ||||||
|  |                         ) | ||||||
|  |                     } else { | ||||||
|  |                         episodeUpcomingIcon.isVisible = false | ||||||
|  | 
 | ||||||
|  |                         val formattedAirDate = SimpleDateFormat.getDateInstance( | ||||||
|  |                             DateFormat.LONG, | ||||||
|  |                             Locale.getDefault() | ||||||
|  |                         ).apply { | ||||||
|  |                         }.format(Date(card.airDate)) | ||||||
|  | 
 | ||||||
|  |                         episodeDate.setText(txt(formattedAirDate)) | ||||||
|  |                     } | ||||||
|  |                 } else { | ||||||
|  |                     episodeDate.isVisible = false | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|                 if (isLayout(EMULATOR or PHONE)) { |                 if (isLayout(EMULATOR or PHONE)) { | ||||||
|                     episodePoster.setOnClickListener { |                     episodePoster.setOnClickListener { | ||||||
|                         clickCallback.invoke(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card)) |                         clickCallback.invoke(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card)) | ||||||
|  | @ -271,6 +302,7 @@ class EpisodeAdapter( | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  | 
 | ||||||
|             itemView.setOnClickListener { |             itemView.setOnClickListener { | ||||||
|                 clickCallback.invoke(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card)) |                 clickCallback.invoke(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card)) | ||||||
|             } |             } | ||||||
|  |  | ||||||
|  | @ -50,6 +50,7 @@ data class ResultEpisode( | ||||||
|     val videoWatchState: VideoWatchState, |     val videoWatchState: VideoWatchState, | ||||||
|     /** Sum of all previous season episode counts + episode */ |     /** Sum of all previous season episode counts + episode */ | ||||||
|     val totalEpisodeIndex: Int? = null, |     val totalEpisodeIndex: Int? = null, | ||||||
|  |     val airDate: Long? = null, | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| fun ResultEpisode.getRealPosition(): Long { | fun ResultEpisode.getRealPosition(): Long { | ||||||
|  | @ -85,6 +86,7 @@ fun buildResultEpisode( | ||||||
|     tvType: TvType, |     tvType: TvType, | ||||||
|     parentId: Int, |     parentId: Int, | ||||||
|     totalEpisodeIndex: Int? = null, |     totalEpisodeIndex: Int? = null, | ||||||
|  |     airDate: Long? = null, | ||||||
| ): ResultEpisode { | ): ResultEpisode { | ||||||
|     val posDur = getViewPos(id) |     val posDur = getViewPos(id) | ||||||
|     val videoWatchState = getVideoWatchState(id) ?: VideoWatchState.None |     val videoWatchState = getVideoWatchState(id) ?: VideoWatchState.None | ||||||
|  | @ -107,7 +109,8 @@ fun buildResultEpisode( | ||||||
|         tvType, |         tvType, | ||||||
|         parentId, |         parentId, | ||||||
|         videoWatchState, |         videoWatchState, | ||||||
|         totalEpisodeIndex |         totalEpisodeIndex, | ||||||
|  |         airDate, | ||||||
|     ) |     ) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1099,13 +1099,14 @@ class ResultViewModel2 : ViewModel() { | ||||||
| 
 | 
 | ||||||
|         val duplicateEntries = data.filter { it: DataStoreHelper.LibrarySearchResponse -> |         val duplicateEntries = data.filter { it: DataStoreHelper.LibrarySearchResponse -> | ||||||
|             val librarySyncData = it.syncData |             val librarySyncData = it.syncData | ||||||
|  |             val yearCheck = year == it.year || year == null || it.year == null | ||||||
| 
 | 
 | ||||||
|             val checks = listOf( |             val checks = listOf( | ||||||
|                 { imdbId != null && getImdbIdFromSyncData(librarySyncData) == imdbId }, |                 { imdbId != null && getImdbIdFromSyncData(librarySyncData) == imdbId }, | ||||||
|                 { tmdbId != null && getTMDbIdFromSyncData(librarySyncData) == tmdbId }, |                 { tmdbId != null && getTMDbIdFromSyncData(librarySyncData) == tmdbId }, | ||||||
|                 { malId != null && librarySyncData?.get(AccountManager.malApi.idPrefix) == malId }, |                 { malId != null && librarySyncData?.get(AccountManager.malApi.idPrefix) == malId }, | ||||||
|                 { aniListId != null && librarySyncData?.get(AccountManager.aniListApi.idPrefix) == aniListId }, |                 { aniListId != null && librarySyncData?.get(AccountManager.aniListApi.idPrefix) == aniListId }, | ||||||
|                 { normalizedName == normalizeString(it.name) && year == it.year } |                 { normalizedName == normalizeString(it.name) && yearCheck } | ||||||
|             ) |             ) | ||||||
| 
 | 
 | ||||||
|             checks.any { it() } |             checks.any { it() } | ||||||
|  | @ -2277,7 +2278,8 @@ class ResultViewModel2 : ViewModel() { | ||||||
|                                     fillers.getOrDefault(episode, false), |                                     fillers.getOrDefault(episode, false), | ||||||
|                                     loadResponse.type, |                                     loadResponse.type, | ||||||
|                                     mainId, |                                     mainId, | ||||||
|                                     totalIndex |                                     totalIndex, | ||||||
|  |                                     airDate = i.date | ||||||
|                                 ) |                                 ) | ||||||
| 
 | 
 | ||||||
|                             val season = eps.seasonIndex ?: 0 |                             val season = eps.seasonIndex ?: 0 | ||||||
|  | @ -2326,7 +2328,8 @@ class ResultViewModel2 : ViewModel() { | ||||||
|                                 null, |                                 null, | ||||||
|                                 loadResponse.type, |                                 loadResponse.type, | ||||||
|                                 mainId, |                                 mainId, | ||||||
|                                 totalIndex |                                 totalIndex, | ||||||
|  |                                 airDate = episode.date | ||||||
|                             ) |                             ) | ||||||
| 
 | 
 | ||||||
|                         val season = ep.seasonIndex ?: 0 |                         val season = ep.seasonIndex ?: 0 | ||||||
|  |  | ||||||
|  | @ -83,6 +83,7 @@ import com.lagradost.cloudstream3.extractors.Maxstream | ||||||
| import com.lagradost.cloudstream3.extractors.Mcloud | import com.lagradost.cloudstream3.extractors.Mcloud | ||||||
| import com.lagradost.cloudstream3.extractors.Megacloud | import com.lagradost.cloudstream3.extractors.Megacloud | ||||||
| import com.lagradost.cloudstream3.extractors.Meownime | import com.lagradost.cloudstream3.extractors.Meownime | ||||||
|  | import com.lagradost.cloudstream3.extractors.MetaGnathTuggers | ||||||
| import com.lagradost.cloudstream3.extractors.Minoplres | import com.lagradost.cloudstream3.extractors.Minoplres | ||||||
| import com.lagradost.cloudstream3.extractors.MixDrop | import com.lagradost.cloudstream3.extractors.MixDrop | ||||||
| import com.lagradost.cloudstream3.extractors.MixDropBz | import com.lagradost.cloudstream3.extractors.MixDropBz | ||||||
|  | @ -139,6 +140,7 @@ import com.lagradost.cloudstream3.extractors.Sbspeed | ||||||
| import com.lagradost.cloudstream3.extractors.Sbthe | import com.lagradost.cloudstream3.extractors.Sbthe | ||||||
| import com.lagradost.cloudstream3.extractors.Sendvid | import com.lagradost.cloudstream3.extractors.Sendvid | ||||||
| import com.lagradost.cloudstream3.extractors.ShaveTape | import com.lagradost.cloudstream3.extractors.ShaveTape | ||||||
|  | import com.lagradost.cloudstream3.extractors.Simpulumlamerop | ||||||
| import com.lagradost.cloudstream3.extractors.Solidfiles | import com.lagradost.cloudstream3.extractors.Solidfiles | ||||||
| import com.lagradost.cloudstream3.extractors.Ssbstream | import com.lagradost.cloudstream3.extractors.Ssbstream | ||||||
| import com.lagradost.cloudstream3.extractors.StreamM4u | import com.lagradost.cloudstream3.extractors.StreamM4u | ||||||
|  | @ -175,6 +177,7 @@ import com.lagradost.cloudstream3.extractors.UpstreamExtractor | ||||||
| import com.lagradost.cloudstream3.extractors.Uqload | import com.lagradost.cloudstream3.extractors.Uqload | ||||||
| import com.lagradost.cloudstream3.extractors.Uqload1 | import com.lagradost.cloudstream3.extractors.Uqload1 | ||||||
| import com.lagradost.cloudstream3.extractors.Uqload2 | import com.lagradost.cloudstream3.extractors.Uqload2 | ||||||
|  | import com.lagradost.cloudstream3.extractors.Urochsunloath | ||||||
| import com.lagradost.cloudstream3.extractors.Userload | import com.lagradost.cloudstream3.extractors.Userload | ||||||
| import com.lagradost.cloudstream3.extractors.Userscloud | import com.lagradost.cloudstream3.extractors.Userscloud | ||||||
| import com.lagradost.cloudstream3.extractors.Uservideo | import com.lagradost.cloudstream3.extractors.Uservideo | ||||||
|  | @ -187,6 +190,7 @@ import com.lagradost.cloudstream3.extractors.VideoVard | ||||||
| import com.lagradost.cloudstream3.extractors.VideovardSX | import com.lagradost.cloudstream3.extractors.VideovardSX | ||||||
| import com.lagradost.cloudstream3.extractors.Vidgomunime | import com.lagradost.cloudstream3.extractors.Vidgomunime | ||||||
| import com.lagradost.cloudstream3.extractors.Vidgomunimesb | import com.lagradost.cloudstream3.extractors.Vidgomunimesb | ||||||
|  | import com.lagradost.cloudstream3.extractors.Vidguardto | ||||||
| import com.lagradost.cloudstream3.extractors.VidhideExtractor | import com.lagradost.cloudstream3.extractors.VidhideExtractor | ||||||
| import com.lagradost.cloudstream3.extractors.Vidmoly | import com.lagradost.cloudstream3.extractors.Vidmoly | ||||||
| import com.lagradost.cloudstream3.extractors.Vidmolyme | import com.lagradost.cloudstream3.extractors.Vidmolyme | ||||||
|  | @ -208,6 +212,7 @@ import com.lagradost.cloudstream3.extractors.Watchx | ||||||
| import com.lagradost.cloudstream3.extractors.WcoStream | import com.lagradost.cloudstream3.extractors.WcoStream | ||||||
| import com.lagradost.cloudstream3.extractors.Wibufile | import com.lagradost.cloudstream3.extractors.Wibufile | ||||||
| import com.lagradost.cloudstream3.extractors.XStreamCdn | import com.lagradost.cloudstream3.extractors.XStreamCdn | ||||||
|  | import com.lagradost.cloudstream3.extractors.Yipsu | ||||||
| import com.lagradost.cloudstream3.extractors.YourUpload | import com.lagradost.cloudstream3.extractors.YourUpload | ||||||
| import com.lagradost.cloudstream3.extractors.YoutubeExtractor | import com.lagradost.cloudstream3.extractors.YoutubeExtractor | ||||||
| import com.lagradost.cloudstream3.extractors.YoutubeMobileExtractor | import com.lagradost.cloudstream3.extractors.YoutubeMobileExtractor | ||||||
|  | @ -890,7 +895,12 @@ val extractorApis: MutableList<ExtractorApi> = arrayListOf( | ||||||
|     StreamWishExtractor(), |     StreamWishExtractor(), | ||||||
|     EmturbovidExtractor(), |     EmturbovidExtractor(), | ||||||
|     Vtbe(), |     Vtbe(), | ||||||
|     EPlayExtractor() |     EPlayExtractor(), | ||||||
|  |     Vidguardto(), | ||||||
|  |     Simpulumlamerop(), | ||||||
|  |     Urochsunloath(), | ||||||
|  |     Yipsu(), | ||||||
|  |     MetaGnathTuggers() | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -45,6 +45,7 @@ import androidx.core.view.marginBottom | ||||||
| import androidx.core.view.marginLeft | import androidx.core.view.marginLeft | ||||||
| import androidx.core.view.marginRight | import androidx.core.view.marginRight | ||||||
| import androidx.core.view.marginTop | import androidx.core.view.marginTop | ||||||
|  | import androidx.core.view.updateLayoutParams | ||||||
| import androidx.fragment.app.Fragment | import androidx.fragment.app.Fragment | ||||||
| import androidx.fragment.app.FragmentActivity | import androidx.fragment.app.FragmentActivity | ||||||
| import androidx.navigation.fragment.NavHostFragment | import androidx.navigation.fragment.NavHostFragment | ||||||
|  | @ -58,6 +59,7 @@ import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions | ||||||
| import com.bumptech.glide.request.RequestListener | import com.bumptech.glide.request.RequestListener | ||||||
| import com.bumptech.glide.request.RequestOptions.bitmapTransform | import com.bumptech.glide.request.RequestOptions.bitmapTransform | ||||||
| import com.bumptech.glide.request.target.Target | import com.bumptech.glide.request.target.Target | ||||||
|  | import com.google.android.material.appbar.AppBarLayout | ||||||
| import com.google.android.material.chip.Chip | import com.google.android.material.chip.Chip | ||||||
| import com.google.android.material.chip.ChipDrawable | import com.google.android.material.chip.ChipDrawable | ||||||
| import com.google.android.material.chip.ChipGroup | import com.google.android.material.chip.ChipGroup | ||||||
|  | @ -208,6 +210,14 @@ object UIHelper { | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     fun View?.setAppBarNoScrollFlagsOnTV() { | ||||||
|  |         if (isLayout(Globals.TV or EMULATOR)) { | ||||||
|  |             this?.updateLayoutParams<AppBarLayout.LayoutParams> { | ||||||
|  |                 scrollFlags = AppBarLayout.LayoutParams.SCROLL_FLAG_NO_SCROLL | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     fun Activity.hideKeyboard() { |     fun Activity.hideKeyboard() { | ||||||
|         window?.decorView?.clearFocus() |         window?.decorView?.clearFocus() | ||||||
|         this.findViewById<View>(android.R.id.content)?.rootView?.let { |         this.findViewById<View>(android.R.id.content)?.rootView?.let { | ||||||
|  |  | ||||||
							
								
								
									
										9
									
								
								app/src/main/res/drawable/hourglass_24.xml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								app/src/main/res/drawable/hourglass_24.xml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,9 @@ | ||||||
|  | <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |     android:width="24dp" | ||||||
|  |     android:height="24dp" | ||||||
|  |     android:viewportWidth="960" | ||||||
|  |     android:viewportHeight="960"> | ||||||
|  |   <path | ||||||
|  |       android:fillColor="#9BA0A4" | ||||||
|  |       android:pathData="M320,800h320v-120q0,-66 -47,-113t-113,-47q-66,0 -113,47t-47,113v120ZM480,440q66,0 113,-47t47,-113v-120L320,160v120q0,66 47,113t113,47ZM160,880v-80h80v-120q0,-61 28.5,-114.5T348,480q-51,-32 -79.5,-85.5T240,280v-120h-80v-80h640v80h-80v120q0,61 -28.5,114.5T612,480q51,32 79.5,85.5T720,680v120h80v80L160,880ZM480,800ZM480,160Z"/> | ||||||
|  | </vector> | ||||||
|  | @ -9,6 +9,7 @@ | ||||||
|     android:layout_height="50dp" |     android:layout_height="50dp" | ||||||
|     android:layout_marginBottom="5dp" |     android:layout_marginBottom="5dp" | ||||||
|     android:foreground="@drawable/outline_drawable" |     android:foreground="@drawable/outline_drawable" | ||||||
|  |     android:focusable="true" | ||||||
|     android:nextFocusLeft="@id/nav_rail_view" |     android:nextFocusLeft="@id/nav_rail_view" | ||||||
|     android:nextFocusRight="@id/download_button" |     android:nextFocusRight="@id/download_button" | ||||||
|     app:cardBackgroundColor="@color/transparent" |     app:cardBackgroundColor="@color/transparent" | ||||||
|  | @ -84,7 +85,9 @@ | ||||||
|             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="-50dp" | ||||||
|             android:background="?selectableItemBackgroundBorderless" |             android:foreground="@drawable/outline_drawable" | ||||||
|  |             android:focusable="true" | ||||||
|  |             android:nextFocusLeft="@id/download_child_episode_holder" | ||||||
|             android:padding="10dp" /> |             android:padding="10dp" /> | ||||||
|     </GridLayout> |     </GridLayout> | ||||||
| </androidx.cardview.widget.CardView> | </androidx.cardview.widget.CardView> | ||||||
|  | @ -9,6 +9,8 @@ | ||||||
|     android:layout_marginTop="10dp" |     android:layout_marginTop="10dp" | ||||||
|     android:layout_marginEnd="10dp" |     android:layout_marginEnd="10dp" | ||||||
|     android:foreground="@drawable/outline_drawable" |     android:foreground="@drawable/outline_drawable" | ||||||
|  |     android:focusable="true" | ||||||
|  |     android:nextFocusRight="@id/download_button" | ||||||
|     app:cardBackgroundColor="?attr/boxItemBackground" |     app:cardBackgroundColor="?attr/boxItemBackground" | ||||||
|     app:cardCornerRadius="@dimen/rounded_image_radius"> |     app:cardCornerRadius="@dimen/rounded_image_radius"> | ||||||
| 
 | 
 | ||||||
|  | @ -71,7 +73,9 @@ | ||||||
|             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="-50dp" | ||||||
|             android:background="?selectableItemBackgroundBorderless" |             android:foreground="@drawable/outline_drawable" | ||||||
|  |             android:focusable="true" | ||||||
|  |             android:nextFocusLeft="@id/episode_holder" | ||||||
|             android:padding="10dp" /> |             android:padding="10dp" /> | ||||||
|     </LinearLayout> |     </LinearLayout> | ||||||
| </androidx.cardview.widget.CardView> | </androidx.cardview.widget.CardView> | ||||||
|  | @ -43,14 +43,26 @@ | ||||||
|                     android:foreground="?android:attr/selectableItemBackgroundBorderless" |                     android:foreground="?android:attr/selectableItemBackgroundBorderless" | ||||||
|                     android:nextFocusRight="@id/download_button" |                     android:nextFocusRight="@id/download_button" | ||||||
|                     android:scaleType="centerCrop" |                     android:scaleType="centerCrop" | ||||||
|                     tools:src="@drawable/example_poster" /> |                     tools:src="@drawable/example_poster" | ||||||
|  |                     tools:visibility="invisible"/> | ||||||
| 
 | 
 | ||||||
|                 <ImageView |                 <ImageView | ||||||
|  |                     android:id="@+id/episode_play_icon" | ||||||
|                     android:layout_width="36dp" |                     android:layout_width="36dp" | ||||||
|                     android:layout_height="36dp" |                     android:layout_height="36dp" | ||||||
|                     android:layout_gravity="center" |                     android:layout_gravity="center" | ||||||
|                     android:contentDescription="@string/play_episode" |                     android:contentDescription="@string/play_episode" | ||||||
|                     android:src="@drawable/play_button" /> |                     android:src="@drawable/play_button" | ||||||
|  |                     tools:visibility="invisible"/> | ||||||
|  | 
 | ||||||
|  |                 <ImageView | ||||||
|  |                     android:id="@+id/episode_upcoming_icon" | ||||||
|  |                     android:layout_width="36dp" | ||||||
|  |                     android:layout_height="36dp" | ||||||
|  |                     android:layout_gravity="center" | ||||||
|  |                     android:src="@drawable/hourglass_24" | ||||||
|  |                     android:visibility="gone" | ||||||
|  |                     tools:visibility="visible" /> | ||||||
| 
 | 
 | ||||||
|                 <androidx.core.widget.ContentLoadingProgressBar |                 <androidx.core.widget.ContentLoadingProgressBar | ||||||
|                     android:id="@+id/episode_progress" |                     android:id="@+id/episode_progress" | ||||||
|  | @ -100,6 +112,13 @@ | ||||||
|                     android:layout_height="wrap_content" |                     android:layout_height="wrap_content" | ||||||
|                     android:textColor="?attr/grayTextColor" |                     android:textColor="?attr/grayTextColor" | ||||||
|                     tools:text="Rated: 8.8" /> |                     tools:text="Rated: 8.8" /> | ||||||
|  | 
 | ||||||
|  |                 <TextView | ||||||
|  |                     android:id="@+id/episode_date" | ||||||
|  |                     android:layout_width="wrap_content" | ||||||
|  |                     android:layout_height="wrap_content" | ||||||
|  |                     android:textColor="?attr/grayTextColor" | ||||||
|  |                     tools:text="15 Apr 2024" /> | ||||||
|             </LinearLayout> |             </LinearLayout> | ||||||
| 
 | 
 | ||||||
|             <com.lagradost.cloudstream3.ui.download.button.PieFetchButton |             <com.lagradost.cloudstream3.ui.download.button.PieFetchButton | ||||||
|  |  | ||||||
|  | @ -292,6 +292,7 @@ | ||||||
|     <string name="episodes">Episodes</string> |     <string name="episodes">Episodes</string> | ||||||
|     <string name="episodes_range">%1$d-%2$d</string> |     <string name="episodes_range">%1$d-%2$d</string> | ||||||
|     <string name="episode_format" formatted="true">%1$d %2$s</string> |     <string name="episode_format" formatted="true">%1$d %2$s</string> | ||||||
|  |     <string name="episode_upcoming_format" formatted="true">Upcoming in %s</string> | ||||||
|     <string name="season_short">S</string> |     <string name="season_short">S</string> | ||||||
|     <string name="episode_short">E</string> |     <string name="episode_short">E</string> | ||||||
|     <string name="no_episodes_found">No Episodes found</string> |     <string name="no_episodes_found">No Episodes found</string> | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue