mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
viewmodel done I think
This commit is contained in:
parent
550b832d58
commit
0606713fd5
8 changed files with 137 additions and 363 deletions
|
@ -191,7 +191,7 @@ object APIHolder {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getLoadResponseIdFromUrl(url: String, apiName: String): Int {
|
private fun getLoadResponseIdFromUrl(url: String, apiName: String): Int {
|
||||||
return url.replace(getApiFromName(apiName).mainUrl, "").replace("/", "").hashCode()
|
return url.replace(getApiFromName(apiName).mainUrl, "").replace("/", "").hashCode()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -104,15 +104,6 @@ class EpisodeAdapter(
|
||||||
diffResult.dispatchUpdatesTo(this)
|
diffResult.dispatchUpdatesTo(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
@LayoutRes
|
|
||||||
private var layout: Int = 0
|
|
||||||
fun updateLayout() {
|
|
||||||
// layout =
|
|
||||||
// if (cardList.filter { it.poster != null }.size >= cardList.size / 2f) // If over half has posters then use the large layout
|
|
||||||
// R.layout.result_episode_large
|
|
||||||
// else R.layout.result_episode
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
/*val layout = if (cardList.filter { it.poster != null }.size >= cardList.size / 2)
|
/*val layout = if (cardList.filter { it.poster != null }.size >= cardList.size / 2)
|
||||||
R.layout.result_episode_large
|
R.layout.result_episode_large
|
||||||
|
|
|
@ -33,6 +33,7 @@ import com.google.android.gms.cast.framework.CastState
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||||
import com.google.android.material.button.MaterialButton
|
import com.google.android.material.button.MaterialButton
|
||||||
import com.lagradost.cloudstream3.*
|
import com.lagradost.cloudstream3.*
|
||||||
|
import com.lagradost.cloudstream3.APIHolder.getApiDubstatusSettings
|
||||||
import com.lagradost.cloudstream3.APIHolder.getApiFromName
|
import com.lagradost.cloudstream3.APIHolder.getApiFromName
|
||||||
import com.lagradost.cloudstream3.APIHolder.updateHasTrailers
|
import com.lagradost.cloudstream3.APIHolder.updateHasTrailers
|
||||||
import com.lagradost.cloudstream3.CommonActivity.showToast
|
import com.lagradost.cloudstream3.CommonActivity.showToast
|
||||||
|
@ -56,6 +57,7 @@ import com.lagradost.cloudstream3.utils.AppUtils.loadCache
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.openBrowser
|
import com.lagradost.cloudstream3.utils.AppUtils.openBrowser
|
||||||
import com.lagradost.cloudstream3.utils.Coroutines.ioWork
|
import com.lagradost.cloudstream3.utils.Coroutines.ioWork
|
||||||
import com.lagradost.cloudstream3.utils.Coroutines.main
|
import com.lagradost.cloudstream3.utils.Coroutines.main
|
||||||
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.getDub
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos
|
||||||
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
|
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
|
||||||
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialogInstant
|
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialogInstant
|
||||||
|
@ -75,12 +77,9 @@ import kotlinx.android.synthetic.main.result_sync.*
|
||||||
import kotlinx.android.synthetic.main.trailer_custom_layout.*
|
import kotlinx.android.synthetic.main.trailer_custom_layout.*
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
|
||||||
const val START_ACTION_NORMAL = 0
|
|
||||||
const val START_ACTION_RESUME_LATEST = 1
|
const val START_ACTION_RESUME_LATEST = 1
|
||||||
const val START_ACTION_LOAD_EP = 2
|
const val START_ACTION_LOAD_EP = 2
|
||||||
|
|
||||||
const val START_VALUE_NORMAL = 0
|
|
||||||
|
|
||||||
data class ResultEpisode(
|
data class ResultEpisode(
|
||||||
val headerName: String,
|
val headerName: String,
|
||||||
val name: String?,
|
val name: String?,
|
||||||
|
@ -177,7 +176,6 @@ class ResultFragment : ResultTrailerPlayer() {
|
||||||
putString(URL_BUNDLE, card.url)
|
putString(URL_BUNDLE, card.url)
|
||||||
putString(API_NAME_BUNDLE, card.apiName)
|
putString(API_NAME_BUNDLE, card.apiName)
|
||||||
if (card is DataStoreHelper.ResumeWatchingResult) {
|
if (card is DataStoreHelper.ResumeWatchingResult) {
|
||||||
// println("CARD::::: $card")
|
|
||||||
if (card.season != null)
|
if (card.season != null)
|
||||||
putInt(SEASON_BUNDLE, card.season)
|
putInt(SEASON_BUNDLE, card.season)
|
||||||
if (card.episode != null)
|
if (card.episode != null)
|
||||||
|
@ -186,6 +184,8 @@ class ResultFragment : ResultTrailerPlayer() {
|
||||||
putInt(START_ACTION_BUNDLE, startAction)
|
putInt(START_ACTION_BUNDLE, startAction)
|
||||||
if (startValue != null)
|
if (startValue != null)
|
||||||
putInt(START_VALUE_BUNDLE, startValue)
|
putInt(START_VALUE_BUNDLE, startValue)
|
||||||
|
|
||||||
|
|
||||||
putBoolean(RESTART_BUNDLE, true)
|
putBoolean(RESTART_BUNDLE, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -290,9 +290,6 @@ class ResultFragment : ResultTrailerPlayer() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var startAction: Int? = null
|
|
||||||
private var startValue: Int? = null
|
|
||||||
|
|
||||||
var currentTrailers: List<ExtractorLink> = emptyList()
|
var currentTrailers: List<ExtractorLink> = emptyList()
|
||||||
var currentTrailerIndex = 0
|
var currentTrailerIndex = 0
|
||||||
|
|
||||||
|
@ -465,10 +462,16 @@ class ResultFragment : ResultTrailerPlayer() {
|
||||||
|
|
||||||
val url = arguments?.getString(URL_BUNDLE)
|
val url = arguments?.getString(URL_BUNDLE)
|
||||||
val apiName = arguments?.getString(API_NAME_BUNDLE) ?: return
|
val apiName = arguments?.getString(API_NAME_BUNDLE) ?: return
|
||||||
startAction = arguments?.getInt(START_ACTION_BUNDLE) ?: START_ACTION_NORMAL
|
val startAction = arguments?.getInt(START_ACTION_BUNDLE)
|
||||||
startValue = arguments?.getInt(START_VALUE_BUNDLE)
|
val start = startAction?.let { action ->
|
||||||
|
val startValue = arguments?.getInt(START_VALUE_BUNDLE)
|
||||||
val resumeEpisode = arguments?.getInt(EPISODE_BUNDLE)
|
val resumeEpisode = arguments?.getInt(EPISODE_BUNDLE)
|
||||||
val resumeSeason = arguments?.getInt(SEASON_BUNDLE)
|
val resumeSeason = arguments?.getInt(SEASON_BUNDLE)
|
||||||
|
|
||||||
|
arguments?.remove(START_VALUE_BUNDLE)
|
||||||
|
arguments?.remove(START_ACTION_BUNDLE)
|
||||||
|
AutoResume(startAction = action, id = startValue, episode = resumeEpisode, season = resumeSeason)
|
||||||
|
}
|
||||||
syncModel.addFromUrl(url)
|
syncModel.addFromUrl(url)
|
||||||
|
|
||||||
val api = getApiFromName(apiName)
|
val api = getApiFromName(apiName)
|
||||||
|
@ -1185,8 +1188,9 @@ class ResultFragment : ResultTrailerPlayer() {
|
||||||
SearchHelper.handleSearchClickCallback(activity, callback)
|
SearchHelper.handleSearchClickCallback(activity, callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
context?.let { ctx ->
|
context?.let { ctx ->
|
||||||
|
val dubStatus = if(ctx.getApiDubstatusSettings().contains(DubStatus.Dubbed)) DubStatus.Dubbed else DubStatus.Subbed
|
||||||
|
|
||||||
result_bookmark_button?.isVisible = ctx.isTvSettings()
|
result_bookmark_button?.isVisible = ctx.isTvSettings()
|
||||||
|
|
||||||
val settingsManager = PreferenceManager.getDefaultSharedPreferences(ctx)
|
val settingsManager = PreferenceManager.getDefaultSharedPreferences(ctx)
|
||||||
|
@ -1198,7 +1202,7 @@ class ResultFragment : ResultTrailerPlayer() {
|
||||||
|
|
||||||
if (url != null) {
|
if (url != null) {
|
||||||
result_reload_connectionerror.setOnClickListener {
|
result_reload_connectionerror.setOnClickListener {
|
||||||
viewModel.load(url, apiName, showFillers, DubStatus.Dubbed, 0, 0) //TODO FIX
|
viewModel.load(activity, url, apiName, showFillers, dubStatus, start) //TODO FIX
|
||||||
}
|
}
|
||||||
|
|
||||||
result_reload_connection_open_in_browser?.setOnClickListener {
|
result_reload_connection_open_in_browser?.setOnClickListener {
|
||||||
|
@ -1233,7 +1237,7 @@ class ResultFragment : ResultTrailerPlayer() {
|
||||||
|
|
||||||
if (restart || !viewModel.hasLoaded()) {
|
if (restart || !viewModel.hasLoaded()) {
|
||||||
//viewModel.clear()
|
//viewModel.clear()
|
||||||
viewModel.load(url, apiName, showFillers, DubStatus.Dubbed, 0, 0) //TODO FIX
|
viewModel.load(activity, url, apiName, showFillers, dubStatus, start) //TODO FIX
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,9 +42,16 @@ import com.lagradost.cloudstream3.utils.AppUtils.isAppInstalled
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.isConnectedToChromecast
|
import com.lagradost.cloudstream3.utils.AppUtils.isConnectedToChromecast
|
||||||
import com.lagradost.cloudstream3.utils.CastHelper.startCast
|
import com.lagradost.cloudstream3.utils.CastHelper.startCast
|
||||||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||||
|
import com.lagradost.cloudstream3.utils.Coroutines.ioWork
|
||||||
import com.lagradost.cloudstream3.utils.DataStore.setKey
|
import com.lagradost.cloudstream3.utils.DataStore.setKey
|
||||||
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.getDub
|
||||||
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultEpisode
|
||||||
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultSeason
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultWatchState
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultWatchState
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos
|
||||||
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.setDub
|
||||||
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.setResultEpisode
|
||||||
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.setResultSeason
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.checkWrite
|
import com.lagradost.cloudstream3.utils.UIHelper.checkWrite
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.navigate
|
import com.lagradost.cloudstream3.utils.UIHelper.navigate
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.requestRW
|
import com.lagradost.cloudstream3.utils.UIHelper.requestRW
|
||||||
|
@ -63,6 +70,13 @@ data class EpisodeRange(
|
||||||
val endEpisode: Int,
|
val endEpisode: Int,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
data class AutoResume(
|
||||||
|
val season: Int?,
|
||||||
|
val episode: Int?,
|
||||||
|
val id: Int?,
|
||||||
|
val startAction: Int,
|
||||||
|
)
|
||||||
|
|
||||||
data class ResultData(
|
data class ResultData(
|
||||||
val url: String,
|
val url: String,
|
||||||
val tags: List<String>,
|
val tags: List<String>,
|
||||||
|
@ -371,8 +385,8 @@ class ResultViewModel2 : ViewModel() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val TAG = "RVM2"
|
const val TAG = "RVM2"
|
||||||
private const val EPISODE_RANGE_SIZE = 50
|
private const val EPISODE_RANGE_SIZE = 20
|
||||||
private const val EPISODE_RANGE_OVERLOAD = 60
|
private const val EPISODE_RANGE_OVERLOAD = 30
|
||||||
|
|
||||||
private fun filterName(name: String?): String? {
|
private fun filterName(name: String?): String? {
|
||||||
if (name == null) return null
|
if (name == null) return null
|
||||||
|
@ -434,7 +448,6 @@ class ResultViewModel2 : ViewModel() {
|
||||||
|
|
||||||
val length = currentIndex - startIndex
|
val length = currentIndex - startIndex
|
||||||
if (length <= 0) continue
|
if (length <= 0) continue
|
||||||
|
|
||||||
list.add(
|
list.add(
|
||||||
EpisodeRange(
|
EpisodeRange(
|
||||||
startIndex,
|
startIndex,
|
||||||
|
@ -443,6 +456,8 @@ class ResultViewModel2 : ViewModel() {
|
||||||
currentMax
|
currentMax
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
currentMin = Int.MAX_VALUE
|
||||||
|
currentMax = Int.MIN_VALUE
|
||||||
}
|
}
|
||||||
|
|
||||||
/*var currentMin = Int.MAX_VALUE
|
/*var currentMin = Int.MAX_VALUE
|
||||||
|
@ -1091,6 +1106,7 @@ class ResultViewModel2 : ViewModel() {
|
||||||
false,
|
false,
|
||||||
txt(R.string.episode_action_download_mirror)
|
txt(R.string.episode_action_download_mirror)
|
||||||
) { (result, index) ->
|
) { (result, index) ->
|
||||||
|
ioSafe {
|
||||||
startDownload(
|
startDownload(
|
||||||
activity,
|
activity,
|
||||||
click.data,
|
click.data,
|
||||||
|
@ -1104,6 +1120,7 @@ class ResultViewModel2 : ViewModel() {
|
||||||
listOf(result.links[index]),
|
listOf(result.links[index]),
|
||||||
result.subs,
|
result.subs,
|
||||||
)
|
)
|
||||||
|
}
|
||||||
showToast(
|
showToast(
|
||||||
activity,
|
activity,
|
||||||
R.string.download_started,
|
R.string.download_started,
|
||||||
|
@ -1279,7 +1296,7 @@ class ResultViewModel2 : ViewModel() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
currentMeta = meta
|
currentMeta = meta
|
||||||
currentSync = syncs
|
currentSync = syncs
|
||||||
val (value, updateEpisodes) = Coroutines.ioWork {
|
val (value, updateEpisodes) = ioWork {
|
||||||
currentResponse?.let { resp ->
|
currentResponse?.let { resp ->
|
||||||
return@ioWork applyMeta(resp, meta, syncs)
|
return@ioWork applyMeta(resp, meta, syncs)
|
||||||
}
|
}
|
||||||
|
@ -1298,13 +1315,14 @@ class ResultViewModel2 : ViewModel() {
|
||||||
|
|
||||||
private suspend fun updateFillers(name: String) {
|
private suspend fun updateFillers(name: String) {
|
||||||
fillers =
|
fillers =
|
||||||
|
ioWork {
|
||||||
try {
|
try {
|
||||||
FillerEpisodeCheck.getFillerEpisodes(name)
|
FillerEpisodeCheck.getFillerEpisodes(name)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logError(e)
|
logError(e)
|
||||||
null
|
null
|
||||||
|
}
|
||||||
} ?: emptyMap()
|
} ?: emptyMap()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun changeDubStatus(status: DubStatus) {
|
fun changeDubStatus(status: DubStatus) {
|
||||||
|
@ -1401,7 +1419,6 @@ class ResultViewModel2 : ViewModel() {
|
||||||
currentIndex = indexer
|
currentIndex = indexer
|
||||||
currentRange = range
|
currentRange = range
|
||||||
|
|
||||||
|
|
||||||
_rangeSelections.postValue(ranges?.map { r ->
|
_rangeSelections.postValue(ranges?.map { r ->
|
||||||
val text = txt(R.string.episodes_range, r.startEpisode, r.endEpisode)
|
val text = txt(R.string.episodes_range, r.startEpisode, r.endEpisode)
|
||||||
text to r
|
text to r
|
||||||
|
@ -1448,7 +1465,12 @@ class ResultViewModel2 : ViewModel() {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
//TODO SET KEYS
|
currentId?.let { id ->
|
||||||
|
setDub(id, indexer.dubStatus)
|
||||||
|
setResultSeason(id, indexer.season)
|
||||||
|
setResultEpisode(id, range.startEpisode)
|
||||||
|
}
|
||||||
|
|
||||||
preferStartEpisode = range.startEpisode
|
preferStartEpisode = range.startEpisode
|
||||||
preferStartSeason = indexer.season
|
preferStartSeason = indexer.season
|
||||||
preferDubStatus = indexer.dubStatus
|
preferDubStatus = indexer.dubStatus
|
||||||
|
@ -1661,7 +1683,7 @@ class ResultViewModel2 : ViewModel() {
|
||||||
val name =
|
val name =
|
||||||
/*loadResponse.seasonNames?.firstOrNull { it.season == seasonNumber }?.name?.let { seasonData ->
|
/*loadResponse.seasonNames?.firstOrNull { it.season == seasonNumber }?.name?.let { seasonData ->
|
||||||
txt(seasonData)
|
txt(seasonData)
|
||||||
} ?:*/ txt(R.string.season_format, txt(R.string.season), seasonNumber) //TODO FIX
|
} ?:*/txt(R.string.season_format, txt(R.string.season), seasonNumber) //TODO FIX
|
||||||
name to seasonNumber
|
name to seasonNumber
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1726,13 +1748,47 @@ class ResultViewModel2 : ViewModel() {
|
||||||
|
|
||||||
fun hasLoaded() = currentResponse != null
|
fun hasLoaded() = currentResponse != null
|
||||||
|
|
||||||
|
private fun handleAutoStart(activity: Activity?, autostart: AutoResume?) =
|
||||||
|
viewModelScope.launch {
|
||||||
|
if (autostart == null || activity == null) return@launch
|
||||||
|
|
||||||
|
when (autostart.startAction) {
|
||||||
|
START_ACTION_RESUME_LATEST -> {
|
||||||
|
currentEpisodes[currentIndex]?.let { currentRange ->
|
||||||
|
for (ep in currentRange) {
|
||||||
|
if (ep.getWatchProgress() > 0.9) continue
|
||||||
|
handleAction(
|
||||||
|
activity,
|
||||||
|
EpisodeClickEvent(ACTION_PLAY_EPISODE_IN_PLAYER, ep)
|
||||||
|
)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
START_ACTION_LOAD_EP -> {
|
||||||
|
val all = currentEpisodes.values.flatten()
|
||||||
|
val episode =
|
||||||
|
autostart.id?.let { id -> all.firstOrNull { it.id == id } }
|
||||||
|
?: autostart.episode?.let { ep ->
|
||||||
|
currentEpisodes[currentIndex]?.firstOrNull { it.episode == ep && it.season == autostart.episode }
|
||||||
|
?: all.firstOrNull { it.episode == ep && it.season == autostart.episode }
|
||||||
|
}
|
||||||
|
?: return@launch
|
||||||
|
handleAction(
|
||||||
|
activity,
|
||||||
|
EpisodeClickEvent(ACTION_PLAY_EPISODE_IN_PLAYER, episode)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun load(
|
fun load(
|
||||||
|
activity: Activity?,
|
||||||
url: String,
|
url: String,
|
||||||
apiName: String,
|
apiName: String,
|
||||||
showFillers: Boolean,
|
showFillers: Boolean,
|
||||||
dubStatus: DubStatus,
|
dubStatus: DubStatus,
|
||||||
startEpisode: Int,
|
autostart: AutoResume?,
|
||||||
startSeason: Int
|
|
||||||
) =
|
) =
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
_page.postValue(Resource.Loading(url))
|
_page.postValue(Resource.Loading(url))
|
||||||
|
@ -1740,8 +1796,6 @@ class ResultViewModel2 : ViewModel() {
|
||||||
|
|
||||||
preferDubStatus = dubStatus
|
preferDubStatus = dubStatus
|
||||||
currentShowFillers = showFillers
|
currentShowFillers = showFillers
|
||||||
preferStartEpisode = startEpisode
|
|
||||||
preferStartSeason = startSeason
|
|
||||||
|
|
||||||
// set api
|
// set api
|
||||||
val api = APIHolder.getApiFromNameNull(apiName) ?: APIHolder.getApiFromUrlNull(url)
|
val api = APIHolder.getApiFromNameNull(apiName) ?: APIHolder.getApiFromUrlNull(url)
|
||||||
|
@ -1782,11 +1836,17 @@ class ResultViewModel2 : ViewModel() {
|
||||||
_page.postValue(data)
|
_page.postValue(data)
|
||||||
}
|
}
|
||||||
is Resource.Success -> {
|
is Resource.Success -> {
|
||||||
val loadResponse = Coroutines.ioWork {
|
if (!isActive) return@launch
|
||||||
|
val loadResponse = ioWork {
|
||||||
applyMeta(data.value, currentMeta, currentSync).first
|
applyMeta(data.value, currentMeta, currentSync).first
|
||||||
}
|
}
|
||||||
|
if (!isActive) return@launch
|
||||||
val mainId = loadResponse.getId()
|
val mainId = loadResponse.getId()
|
||||||
|
|
||||||
|
preferDubStatus = getDub(mainId) ?: preferDubStatus
|
||||||
|
preferStartEpisode = getResultEpisode(mainId)
|
||||||
|
preferStartSeason = getResultSeason(mainId)
|
||||||
|
|
||||||
AcraApplication.setKey(
|
AcraApplication.setKey(
|
||||||
DOWNLOAD_HEADER_CACHE,
|
DOWNLOAD_HEADER_CACHE,
|
||||||
mainId.toString(),
|
mainId.toString(),
|
||||||
|
@ -1807,6 +1867,8 @@ class ResultViewModel2 : ViewModel() {
|
||||||
updateFillers = showFillers,
|
updateFillers = showFillers,
|
||||||
apiRepository = repo
|
apiRepository = repo
|
||||||
)
|
)
|
||||||
|
if (!isActive) return@launch
|
||||||
|
handleAutoStart(activity, autostart)
|
||||||
}
|
}
|
||||||
is Resource.Loading -> {
|
is Resource.Loading -> {
|
||||||
debugException { "Invalid load result" }
|
debugException { "Invalid load result" }
|
||||||
|
|
|
@ -4,7 +4,6 @@ import android.util.Log
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
|
||||||
import com.lagradost.cloudstream3.apmap
|
import com.lagradost.cloudstream3.apmap
|
||||||
import com.lagradost.cloudstream3.mvvm.Resource
|
import com.lagradost.cloudstream3.mvvm.Resource
|
||||||
import com.lagradost.cloudstream3.mvvm.logError
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
|
@ -12,8 +11,8 @@ import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.SyncApi
|
||||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.aniListApi
|
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.aniListApi
|
||||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.malApi
|
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.malApi
|
||||||
import com.lagradost.cloudstream3.syncproviders.SyncAPI
|
import com.lagradost.cloudstream3.syncproviders.SyncAPI
|
||||||
|
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||||
import com.lagradost.cloudstream3.utils.SyncUtil
|
import com.lagradost.cloudstream3.utils.SyncUtil
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
|
@ -103,10 +102,10 @@ class SyncViewModel : ViewModel() {
|
||||||
|
|
||||||
var hasAddedFromUrl: HashSet<String> = hashSetOf()
|
var hasAddedFromUrl: HashSet<String> = hashSetOf()
|
||||||
|
|
||||||
fun addFromUrl(url: String?) = viewModelScope.launch {
|
fun addFromUrl(url: String?) = ioSafe {
|
||||||
Log.i(TAG, "addFromUrl = $url")
|
Log.i(TAG, "addFromUrl = $url")
|
||||||
|
|
||||||
if (url == null || hasAddedFromUrl.contains(url)) return@launch
|
if (url == null || hasAddedFromUrl.contains(url)) return@ioSafe
|
||||||
SyncUtil.getIdsFromUrl(url)?.let { (malId, aniListId) ->
|
SyncUtil.getIdsFromUrl(url)?.let { (malId, aniListId) ->
|
||||||
hasAddedFromUrl.add(url)
|
hasAddedFromUrl.add(url)
|
||||||
|
|
||||||
|
@ -170,7 +169,7 @@ class SyncViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun publishUserData() = viewModelScope.launch {
|
fun publishUserData() = ioSafe {
|
||||||
Log.i(TAG, "publishUserData")
|
Log.i(TAG, "publishUserData")
|
||||||
val user = userData.value
|
val user = userData.value
|
||||||
if (user is Resource.Success) {
|
if (user is Resource.Success) {
|
||||||
|
@ -195,7 +194,7 @@ class SyncViewModel : ViewModel() {
|
||||||
|
|
||||||
/// modifies the current sync data, return null if you don't want to change it
|
/// modifies the current sync data, return null if you don't want to change it
|
||||||
private fun modifyData(update: ((SyncAPI.SyncStatus) -> (SyncAPI.SyncStatus?))) =
|
private fun modifyData(update: ((SyncAPI.SyncStatus) -> (SyncAPI.SyncStatus?))) =
|
||||||
viewModelScope.launch {
|
ioSafe {
|
||||||
syncs.apmap { (prefix, id) ->
|
syncs.apmap { (prefix, id) ->
|
||||||
repos.firstOrNull { it.idPrefix == prefix }?.let { repo ->
|
repos.firstOrNull { it.idPrefix == prefix }?.let { repo ->
|
||||||
if (repo.hasAccount()) {
|
if (repo.hasAccount()) {
|
||||||
|
@ -213,7 +212,7 @@ class SyncViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateUserData() = viewModelScope.launch {
|
fun updateUserData() = ioSafe {
|
||||||
Log.i(TAG, "updateUserData")
|
Log.i(TAG, "updateUserData")
|
||||||
_userDataResponse.postValue(Resource.Loading())
|
_userDataResponse.postValue(Resource.Loading())
|
||||||
var lastError: Resource<SyncAPI.SyncStatus> = Resource.Failure(false, null, null, "No data")
|
var lastError: Resource<SyncAPI.SyncStatus> = Resource.Failure(false, null, null, "No data")
|
||||||
|
@ -223,7 +222,7 @@ class SyncViewModel : ViewModel() {
|
||||||
val result = repo.getStatus(id)
|
val result = repo.getStatus(id)
|
||||||
if (result is Resource.Success) {
|
if (result is Resource.Success) {
|
||||||
_userDataResponse.postValue(result)
|
_userDataResponse.postValue(result)
|
||||||
return@launch
|
return@ioSafe
|
||||||
} else if (result is Resource.Failure) {
|
} else if (result is Resource.Failure) {
|
||||||
Log.e(TAG, "updateUserData error ${result.errorString}")
|
Log.e(TAG, "updateUserData error ${result.errorString}")
|
||||||
lastError = result
|
lastError = result
|
||||||
|
@ -234,7 +233,7 @@ class SyncViewModel : ViewModel() {
|
||||||
_userDataResponse.postValue(lastError)
|
_userDataResponse.postValue(lastError)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateMetadata() = viewModelScope.launch {
|
private fun updateMetadata() = ioSafe {
|
||||||
Log.i(TAG, "updateMetadata")
|
Log.i(TAG, "updateMetadata")
|
||||||
|
|
||||||
_metaResponse.postValue(Resource.Loading())
|
_metaResponse.postValue(Resource.Loading())
|
||||||
|
@ -257,7 +256,7 @@ class SyncViewModel : ViewModel() {
|
||||||
val result = repo.getResult(id)
|
val result = repo.getResult(id)
|
||||||
if (result is Resource.Success) {
|
if (result is Resource.Success) {
|
||||||
_metaResponse.postValue(result)
|
_metaResponse.postValue(result)
|
||||||
return@launch
|
return@ioSafe
|
||||||
} else if (result is Resource.Failure) {
|
} else if (result is Resource.Failure) {
|
||||||
Log.e(
|
Log.e(
|
||||||
TAG,
|
TAG,
|
||||||
|
|
|
@ -18,6 +18,7 @@ const val RESULT_WATCH_STATE_DATA = "result_watch_state_data"
|
||||||
const val RESULT_RESUME_WATCHING = "result_resume_watching_2" // changed due to id changes
|
const val RESULT_RESUME_WATCHING = "result_resume_watching_2" // changed due to id changes
|
||||||
const val RESULT_RESUME_WATCHING_OLD = "result_resume_watching"
|
const val RESULT_RESUME_WATCHING_OLD = "result_resume_watching"
|
||||||
const val RESULT_RESUME_WATCHING_HAS_MIGRATED = "result_resume_watching_migrated"
|
const val RESULT_RESUME_WATCHING_HAS_MIGRATED = "result_resume_watching_migrated"
|
||||||
|
const val RESULT_EPISODE = "result_episode"
|
||||||
const val RESULT_SEASON = "result_season"
|
const val RESULT_SEASON = "result_season"
|
||||||
const val RESULT_DUB = "result_dub"
|
const val RESULT_DUB = "result_dub"
|
||||||
|
|
||||||
|
@ -163,7 +164,7 @@ object DataStoreHelper {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getLastWatchedOld(id: Int?): VideoDownloadHelper.ResumeWatching? {
|
private fun getLastWatchedOld(id: Int?): VideoDownloadHelper.ResumeWatching? {
|
||||||
if (id == null) return null
|
if (id == null) return null
|
||||||
return getKey(
|
return getKey(
|
||||||
"$currentAccount/$RESULT_RESUME_WATCHING_OLD",
|
"$currentAccount/$RESULT_RESUME_WATCHING_OLD",
|
||||||
|
@ -192,8 +193,9 @@ object DataStoreHelper {
|
||||||
return getKey("$currentAccount/$VIDEO_POS_DUR", id.toString(), null)
|
return getKey("$currentAccount/$VIDEO_POS_DUR", id.toString(), null)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getDub(id: Int): DubStatus {
|
fun getDub(id: Int): DubStatus? {
|
||||||
return DubStatus.values()[getKey("$currentAccount/$RESULT_DUB", id.toString()) ?: 0]
|
return DubStatus.values()
|
||||||
|
.getOrNull(getKey("$currentAccount/$RESULT_DUB", id.toString(), -1) ?: -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setDub(id: Int, status: DubStatus) {
|
fun setDub(id: Int, status: DubStatus) {
|
||||||
|
@ -221,14 +223,22 @@ object DataStoreHelper {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getResultSeason(id: Int): Int {
|
fun getResultSeason(id: Int): Int? {
|
||||||
return getKey("$currentAccount/$RESULT_SEASON", id.toString()) ?: -1
|
return getKey("$currentAccount/$RESULT_SEASON", id.toString(), null)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setResultSeason(id: Int, value: Int?) {
|
fun setResultSeason(id: Int, value: Int?) {
|
||||||
setKey("$currentAccount/$RESULT_SEASON", id.toString(), value)
|
setKey("$currentAccount/$RESULT_SEASON", id.toString(), value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getResultEpisode(id: Int): Int? {
|
||||||
|
return getKey("$currentAccount/$RESULT_EPISODE", id.toString(), null)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setResultEpisode(id: Int, value: Int?) {
|
||||||
|
setKey("$currentAccount/$RESULT_EPISODE", id.toString(), value)
|
||||||
|
}
|
||||||
|
|
||||||
fun addSync(id: Int, idPrefix: String, url: String) {
|
fun addSync(id: Int, idPrefix: String, url: String) {
|
||||||
setKey("${idPrefix}_sync", id.toString(), url)
|
setKey("${idPrefix}_sync", id.toString(), url)
|
||||||
}
|
}
|
||||||
|
|
|
@ -531,7 +531,7 @@
|
||||||
"TrailersTwoProvider": {
|
"TrailersTwoProvider": {
|
||||||
"language": "en",
|
"language": "en",
|
||||||
"name": "Trailers.to",
|
"name": "Trailers.to",
|
||||||
"status": 1,
|
"status": 0,
|
||||||
"url": "https://trailers.to"
|
"url": "https://trailers.to"
|
||||||
},
|
},
|
||||||
"TwoEmbedProvider": {
|
"TwoEmbedProvider": {
|
||||||
|
|
292
providers.json
292
providers.json
|
@ -1,292 +0,0 @@
|
||||||
{
|
|
||||||
"AkwamProvider": {
|
|
||||||
"name": "Akwam",
|
|
||||||
"url": "https://akwam.to",
|
|
||||||
"status": 1
|
|
||||||
},
|
|
||||||
"AllAnimeProvider": {
|
|
||||||
"name": "AllAnime",
|
|
||||||
"url": "https://allanime.site",
|
|
||||||
"status": 1
|
|
||||||
},
|
|
||||||
"AllMoviesForYouProvider": {
|
|
||||||
"name": "AllMoviesForYou",
|
|
||||||
"url": "https://allmoviesforyou.net",
|
|
||||||
"status": 1
|
|
||||||
},
|
|
||||||
"AnimeFlickProvider": {
|
|
||||||
"name": "AnimeFlick",
|
|
||||||
"url": "https://animeflick.net",
|
|
||||||
"status": 1
|
|
||||||
},
|
|
||||||
"AnimePaheProvider": {
|
|
||||||
"name": "AnimePahe",
|
|
||||||
"url": "https://animepahe.com",
|
|
||||||
"status": 0
|
|
||||||
},
|
|
||||||
"AnimeWorldProvider": {
|
|
||||||
"name": "AnimeWorld",
|
|
||||||
"url": "https://www.animeworld.tv",
|
|
||||||
"status": 1
|
|
||||||
},
|
|
||||||
"AnimeflvnetProvider": {
|
|
||||||
"name": "Animeflv.net",
|
|
||||||
"url": "https://www3.animeflv.net",
|
|
||||||
"status": 1
|
|
||||||
},
|
|
||||||
"AnimekisaProvider": {
|
|
||||||
"name": "Animekisa",
|
|
||||||
"url": "https://animekisa.in",
|
|
||||||
"status": 1
|
|
||||||
},
|
|
||||||
"AsianLoadProvider": {
|
|
||||||
"name": "AsianLoad",
|
|
||||||
"url": "https://asianembed.io",
|
|
||||||
"status": 1
|
|
||||||
},
|
|
||||||
"AsiaFlixProvider": {
|
|
||||||
"name": "AsiaFlix",
|
|
||||||
"url": "https://asiaflix.app",
|
|
||||||
"status": 0
|
|
||||||
},
|
|
||||||
"BflixProvider": {
|
|
||||||
"name": "Bflix",
|
|
||||||
"url": "https://bflix.ru",
|
|
||||||
"status": 0
|
|
||||||
},
|
|
||||||
"FmoviesToProvider": {
|
|
||||||
"name": "Fmovies.to",
|
|
||||||
"url": "https://fmovies.to",
|
|
||||||
"status": 0
|
|
||||||
},
|
|
||||||
"SflixProProvider": {
|
|
||||||
"name": "Sflix.pro",
|
|
||||||
"url": "https://sflix.pro",
|
|
||||||
"status": 0
|
|
||||||
},
|
|
||||||
"CinecalidadProvider": {
|
|
||||||
"name": "Cinecalidad",
|
|
||||||
"url": "https://cinecalidad.lol",
|
|
||||||
"status": 1
|
|
||||||
},
|
|
||||||
"CrossTmdbProvider": {
|
|
||||||
"name": "MultiMovie",
|
|
||||||
"url": "NONE",
|
|
||||||
"status": 1
|
|
||||||
},
|
|
||||||
"CuevanaProvider": {
|
|
||||||
"name": "Cuevana",
|
|
||||||
"url": "https://cuevana3.me",
|
|
||||||
"status": 1
|
|
||||||
},
|
|
||||||
"DoramasYTProvider": {
|
|
||||||
"name": "DoramasYT",
|
|
||||||
"url": "https://doramasyt.com",
|
|
||||||
"status": 1
|
|
||||||
},
|
|
||||||
"DramaSeeProvider": {
|
|
||||||
"name": "DramaSee",
|
|
||||||
"url": "https://dramasee.net",
|
|
||||||
"status": 1
|
|
||||||
},
|
|
||||||
"DubbedAnimeProvider": {
|
|
||||||
"name": "DubbedAnime",
|
|
||||||
"url": "https://bestdubbedanime.com",
|
|
||||||
"status": 1
|
|
||||||
},
|
|
||||||
"EgyBestProvider": {
|
|
||||||
"name": "EgyBest",
|
|
||||||
"url": "https://egy.best",
|
|
||||||
"status": 0
|
|
||||||
},
|
|
||||||
"EntrepeliculasyseriesProvider": {
|
|
||||||
"name": "EntrePeliculasySeries",
|
|
||||||
"url": "https://entrepeliculasyseries.nu",
|
|
||||||
"status": 1
|
|
||||||
},
|
|
||||||
"FilmanProvider": {
|
|
||||||
"name": "filman.cc",
|
|
||||||
"url": "https://filman.cc",
|
|
||||||
"status": 1
|
|
||||||
},
|
|
||||||
"FrenchStreamProvider": {
|
|
||||||
"name": "French Stream",
|
|
||||||
"url": "https://french-stream.re",
|
|
||||||
"status": 1
|
|
||||||
},
|
|
||||||
"GogoanimeProvider": {
|
|
||||||
"name": "GogoAnime",
|
|
||||||
"url": "https://gogoanime.sk",
|
|
||||||
"status": 1
|
|
||||||
},
|
|
||||||
"KawaiifuProvider": {
|
|
||||||
"name": "Kawaiifu",
|
|
||||||
"url": "https://kawaiifu.com",
|
|
||||||
"status": 1
|
|
||||||
},
|
|
||||||
"HDMProvider": {
|
|
||||||
"name": "HD Movies",
|
|
||||||
"url": "https://hdm.to",
|
|
||||||
"status": 0
|
|
||||||
},
|
|
||||||
"IHaveNoTvProvider": {
|
|
||||||
"name": "I Have No TV",
|
|
||||||
"url": "https://ihavenotv.com",
|
|
||||||
"status": 1
|
|
||||||
},
|
|
||||||
"KdramaHoodProvider": {
|
|
||||||
"name": "KDramaHood",
|
|
||||||
"url": "https://kdramahood.com",
|
|
||||||
"status": 1
|
|
||||||
},
|
|
||||||
"LookMovieProvider": {
|
|
||||||
"name": "LookMovie",
|
|
||||||
"url": "https://lookmovie.io",
|
|
||||||
"status": 0
|
|
||||||
},
|
|
||||||
"MeloMovieProvider": {
|
|
||||||
"name": "MeloMovie",
|
|
||||||
"url": "https://melomovie.com",
|
|
||||||
"status": 0
|
|
||||||
},
|
|
||||||
"MonoschinosProvider": {
|
|
||||||
"name": "Monoschinos",
|
|
||||||
"url": "https://monoschinos2.com",
|
|
||||||
"status": 1
|
|
||||||
},
|
|
||||||
"MyCimaProvider": {
|
|
||||||
"name": "MyCima",
|
|
||||||
"url": "https://mycima.tv",
|
|
||||||
"status": 1
|
|
||||||
},
|
|
||||||
"NineAnimeProvider": {
|
|
||||||
"name": "9Anime",
|
|
||||||
"url": "https://9anime.id",
|
|
||||||
"status": 0
|
|
||||||
},
|
|
||||||
"PeliSmartProvider": {
|
|
||||||
"name": "PeliSmart",
|
|
||||||
"url": "https://pelismart.com",
|
|
||||||
"status": 1
|
|
||||||
},
|
|
||||||
"PelisflixProvider": {
|
|
||||||
"name": "Pelisflix",
|
|
||||||
"url": "https://pelisflix.li",
|
|
||||||
"status": 1
|
|
||||||
},
|
|
||||||
"PelisplusHDProvider": {
|
|
||||||
"name": "PelisplusHD",
|
|
||||||
"url": "https://pelisplushd.net",
|
|
||||||
"status": 1
|
|
||||||
},
|
|
||||||
"PelisplusProvider": {
|
|
||||||
"name": "Pelisplus",
|
|
||||||
"url": "https://pelisplus.icu",
|
|
||||||
"status": 1
|
|
||||||
},
|
|
||||||
"PinoyHDXyzProvider": {
|
|
||||||
"name": "Pinoy-HD",
|
|
||||||
"url": "https://www.pinoy-hd.xyz",
|
|
||||||
"status": 1
|
|
||||||
},
|
|
||||||
"PinoyMoviePediaProvider": {
|
|
||||||
"name": "Pinoy Moviepedia",
|
|
||||||
"url": "https://pinoymoviepedia.ru",
|
|
||||||
"status": 1
|
|
||||||
},
|
|
||||||
"PinoyMoviesEsProvider": {
|
|
||||||
"name": "Pinoy Movies",
|
|
||||||
"url": "https://pinoymovies.es",
|
|
||||||
"status": 1
|
|
||||||
},
|
|
||||||
"SflixProvider": {
|
|
||||||
"name": "Sflix.to",
|
|
||||||
"url": "https://sflix.to",
|
|
||||||
"status": 1
|
|
||||||
},
|
|
||||||
"DopeboxProvider": {
|
|
||||||
"name": "Dopebox",
|
|
||||||
"url": "https://dopebox.to",
|
|
||||||
"status": 1
|
|
||||||
},
|
|
||||||
"SolarmovieProvider": {
|
|
||||||
"name": "Solarmovie",
|
|
||||||
"url": "https://solarmovie.pe",
|
|
||||||
"status": 1
|
|
||||||
},
|
|
||||||
"SeriesflixProvider": {
|
|
||||||
"name": "Seriesflix",
|
|
||||||
"url": "https://seriesflix.video",
|
|
||||||
"status": 1
|
|
||||||
},
|
|
||||||
"SoaptwoDayProvider": {
|
|
||||||
"name": "Soap2Day",
|
|
||||||
"url": "https://secretlink.xyz",
|
|
||||||
"status": 0
|
|
||||||
},
|
|
||||||
"TenshiProvider": {
|
|
||||||
"name": "Tenshi.moe",
|
|
||||||
"url": "https://tenshi.moe",
|
|
||||||
"status": 1
|
|
||||||
},
|
|
||||||
"TrailersTwoProvider": {
|
|
||||||
"name": "Trailers.to",
|
|
||||||
"url": "https://trailers.to",
|
|
||||||
"status": 1
|
|
||||||
},
|
|
||||||
"TheFlixToProvider": {
|
|
||||||
"name": "TheFlix.to",
|
|
||||||
"url": "https://theflix.to",
|
|
||||||
"status": 0
|
|
||||||
},
|
|
||||||
"TwoEmbedProvider": {
|
|
||||||
"name": "2Embed",
|
|
||||||
"url": "https://www.2embed.to",
|
|
||||||
"status": 1
|
|
||||||
},
|
|
||||||
"VMoveeProvider": {
|
|
||||||
"name": "VMovee",
|
|
||||||
"url": "https://www.vmovee.watch",
|
|
||||||
"status": 1
|
|
||||||
},
|
|
||||||
"VfFilmProvider": {
|
|
||||||
"name": "vf-film.me",
|
|
||||||
"url": "https://vf-film.me",
|
|
||||||
"status": 1
|
|
||||||
},
|
|
||||||
"VfSerieProvider": {
|
|
||||||
"name": "vf-serie.org",
|
|
||||||
"url": "https://vf-serie.org",
|
|
||||||
"status": 1
|
|
||||||
},
|
|
||||||
"VidEmbedProvider": {
|
|
||||||
"name": "VidEmbed",
|
|
||||||
"url": "https://vidembed.cc",
|
|
||||||
"status": 1
|
|
||||||
},
|
|
||||||
"YomoviesProvider": {
|
|
||||||
"name": "Yomovies",
|
|
||||||
"url": "https://yomovies.vip",
|
|
||||||
"status": 1
|
|
||||||
},
|
|
||||||
"WatchAsianProvider": {
|
|
||||||
"name": "WatchAsian",
|
|
||||||
"url": "https://watchasian.cx",
|
|
||||||
"status": 1
|
|
||||||
},
|
|
||||||
"WatchCartoonOnlineProvider": {
|
|
||||||
"name": "WatchCartoonOnline",
|
|
||||||
"url": "https://www.wcostream.com",
|
|
||||||
"status": 1
|
|
||||||
},
|
|
||||||
"WcoProvider": {
|
|
||||||
"name": "WCO Stream",
|
|
||||||
"url": "https://wcostream.cc",
|
|
||||||
"status": 1
|
|
||||||
},
|
|
||||||
"ZoroProvider": {
|
|
||||||
"name": "Zoro",
|
|
||||||
"url": "https://zoro.to",
|
|
||||||
"status": 0
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue