Compare commits

..

2 commits

Author SHA1 Message Date
KingLucius
004c481a5e
feat(ui): Episode Air date & Upcoming countdown (#1058) 2024-04-27 18:11:22 +02:00
b4byhuey
e2946cad6b
Added Vidguard Extractor (#1053) 2024-04-27 18:00:40 +02:00
8 changed files with 176 additions and 7 deletions

View file

@ -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
)
}

View file

@ -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))
} }

View file

@ -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,
) )
} }

View file

@ -2277,7 +2277,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 +2327,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

View file

@ -186,6 +186,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
@ -888,7 +889,8 @@ val extractorApis: MutableList<ExtractorApi> = arrayListOf(
StreamWishExtractor(), StreamWishExtractor(),
EmturbovidExtractor(), EmturbovidExtractor(),
Vtbe(), Vtbe(),
EPlayExtractor() EPlayExtractor(),
Vidguardto()
) )

View 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>

View file

@ -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

View file

@ -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>