viewmodel done I think

This commit is contained in:
reduplicated 2022-08-04 03:19:59 +02:00
parent 550b832d58
commit 0606713fd5
8 changed files with 137 additions and 363 deletions

View file

@ -191,7 +191,7 @@ object APIHolder {
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()
}

View file

@ -104,15 +104,6 @@ class EpisodeAdapter(
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 {
/*val layout = if (cardList.filter { it.poster != null }.size >= cardList.size / 2)
R.layout.result_episode_large

View file

@ -33,6 +33,7 @@ import com.google.android.gms.cast.framework.CastState
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.button.MaterialButton
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.APIHolder.getApiDubstatusSettings
import com.lagradost.cloudstream3.APIHolder.getApiFromName
import com.lagradost.cloudstream3.APIHolder.updateHasTrailers
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.Coroutines.ioWork
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.SingleSelectionHelper.showBottomDialog
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.coroutines.runBlocking
const val START_ACTION_NORMAL = 0
const val START_ACTION_RESUME_LATEST = 1
const val START_ACTION_LOAD_EP = 2
const val START_VALUE_NORMAL = 0
data class ResultEpisode(
val headerName: String,
val name: String?,
@ -177,7 +176,6 @@ class ResultFragment : ResultTrailerPlayer() {
putString(URL_BUNDLE, card.url)
putString(API_NAME_BUNDLE, card.apiName)
if (card is DataStoreHelper.ResumeWatchingResult) {
// println("CARD::::: $card")
if (card.season != null)
putInt(SEASON_BUNDLE, card.season)
if (card.episode != null)
@ -186,6 +184,8 @@ class ResultFragment : ResultTrailerPlayer() {
putInt(START_ACTION_BUNDLE, startAction)
if (startValue != null)
putInt(START_VALUE_BUNDLE, startValue)
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 currentTrailerIndex = 0
@ -465,10 +462,16 @@ class ResultFragment : ResultTrailerPlayer() {
val url = arguments?.getString(URL_BUNDLE)
val apiName = arguments?.getString(API_NAME_BUNDLE) ?: return
startAction = arguments?.getInt(START_ACTION_BUNDLE) ?: START_ACTION_NORMAL
startValue = arguments?.getInt(START_VALUE_BUNDLE)
val resumeEpisode = arguments?.getInt(EPISODE_BUNDLE)
val resumeSeason = arguments?.getInt(SEASON_BUNDLE)
val startAction = arguments?.getInt(START_ACTION_BUNDLE)
val start = startAction?.let { action ->
val startValue = arguments?.getInt(START_VALUE_BUNDLE)
val resumeEpisode = arguments?.getInt(EPISODE_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)
val api = getApiFromName(apiName)
@ -1185,8 +1188,9 @@ class ResultFragment : ResultTrailerPlayer() {
SearchHelper.handleSearchClickCallback(activity, callback)
}
context?.let { ctx ->
val dubStatus = if(ctx.getApiDubstatusSettings().contains(DubStatus.Dubbed)) DubStatus.Dubbed else DubStatus.Subbed
result_bookmark_button?.isVisible = ctx.isTvSettings()
val settingsManager = PreferenceManager.getDefaultSharedPreferences(ctx)
@ -1198,7 +1202,7 @@ class ResultFragment : ResultTrailerPlayer() {
if (url != null) {
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 {
@ -1233,7 +1237,7 @@ class ResultFragment : ResultTrailerPlayer() {
if (restart || !viewModel.hasLoaded()) {
//viewModel.clear()
viewModel.load(url, apiName, showFillers, DubStatus.Dubbed, 0, 0) //TODO FIX
viewModel.load(activity, url, apiName, showFillers, dubStatus, start) //TODO FIX
}
}
}

View file

@ -42,9 +42,16 @@ import com.lagradost.cloudstream3.utils.AppUtils.isAppInstalled
import com.lagradost.cloudstream3.utils.AppUtils.isConnectedToChromecast
import com.lagradost.cloudstream3.utils.CastHelper.startCast
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.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.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.navigate
import com.lagradost.cloudstream3.utils.UIHelper.requestRW
@ -63,6 +70,13 @@ data class EpisodeRange(
val endEpisode: Int,
)
data class AutoResume(
val season: Int?,
val episode: Int?,
val id: Int?,
val startAction: Int,
)
data class ResultData(
val url: String,
val tags: List<String>,
@ -371,8 +385,8 @@ class ResultViewModel2 : ViewModel() {
companion object {
const val TAG = "RVM2"
private const val EPISODE_RANGE_SIZE = 50
private const val EPISODE_RANGE_OVERLOAD = 60
private const val EPISODE_RANGE_SIZE = 20
private const val EPISODE_RANGE_OVERLOAD = 30
private fun filterName(name: String?): String? {
if (name == null) return null
@ -434,7 +448,6 @@ class ResultViewModel2 : ViewModel() {
val length = currentIndex - startIndex
if (length <= 0) continue
list.add(
EpisodeRange(
startIndex,
@ -443,6 +456,8 @@ class ResultViewModel2 : ViewModel() {
currentMax
)
)
currentMin = Int.MAX_VALUE
currentMax = Int.MIN_VALUE
}
/*var currentMin = Int.MAX_VALUE
@ -1091,19 +1106,21 @@ class ResultViewModel2 : ViewModel() {
false,
txt(R.string.episode_action_download_mirror)
) { (result, index) ->
startDownload(
activity,
click.data,
response.isMovie(),
response.name,
response.type,
response.posterUrl,
response.apiName,
response.getId(),
response.url,
listOf(result.links[index]),
result.subs,
)
ioSafe {
startDownload(
activity,
click.data,
response.isMovie(),
response.name,
response.type,
response.posterUrl,
response.apiName,
response.getId(),
response.url,
listOf(result.links[index]),
result.subs,
)
}
showToast(
activity,
R.string.download_started,
@ -1279,7 +1296,7 @@ class ResultViewModel2 : ViewModel() {
viewModelScope.launch {
currentMeta = meta
currentSync = syncs
val (value, updateEpisodes) = Coroutines.ioWork {
val (value, updateEpisodes) = ioWork {
currentResponse?.let { resp ->
return@ioWork applyMeta(resp, meta, syncs)
}
@ -1298,13 +1315,14 @@ class ResultViewModel2 : ViewModel() {
private suspend fun updateFillers(name: String) {
fillers =
try {
FillerEpisodeCheck.getFillerEpisodes(name)
} catch (e: Exception) {
logError(e)
null
ioWork {
try {
FillerEpisodeCheck.getFillerEpisodes(name)
} catch (e: Exception) {
logError(e)
null
}
} ?: emptyMap()
}
fun changeDubStatus(status: DubStatus) {
@ -1401,7 +1419,6 @@ class ResultViewModel2 : ViewModel() {
currentIndex = indexer
currentRange = range
_rangeSelections.postValue(ranges?.map { r ->
val text = txt(R.string.episodes_range, r.startEpisode, r.endEpisode)
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
preferStartSeason = indexer.season
preferDubStatus = indexer.dubStatus
@ -1661,7 +1683,7 @@ class ResultViewModel2 : ViewModel() {
val name =
/*loadResponse.seasonNames?.firstOrNull { it.season == seasonNumber }?.name?.let { 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
})
}
@ -1726,13 +1748,47 @@ class ResultViewModel2 : ViewModel() {
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(
activity: Activity?,
url: String,
apiName: String,
showFillers: Boolean,
dubStatus: DubStatus,
startEpisode: Int,
startSeason: Int
autostart: AutoResume?,
) =
viewModelScope.launch {
_page.postValue(Resource.Loading(url))
@ -1740,8 +1796,6 @@ class ResultViewModel2 : ViewModel() {
preferDubStatus = dubStatus
currentShowFillers = showFillers
preferStartEpisode = startEpisode
preferStartSeason = startSeason
// set api
val api = APIHolder.getApiFromNameNull(apiName) ?: APIHolder.getApiFromUrlNull(url)
@ -1782,11 +1836,17 @@ class ResultViewModel2 : ViewModel() {
_page.postValue(data)
}
is Resource.Success -> {
val loadResponse = Coroutines.ioWork {
if (!isActive) return@launch
val loadResponse = ioWork {
applyMeta(data.value, currentMeta, currentSync).first
}
if (!isActive) return@launch
val mainId = loadResponse.getId()
preferDubStatus = getDub(mainId) ?: preferDubStatus
preferStartEpisode = getResultEpisode(mainId)
preferStartSeason = getResultSeason(mainId)
AcraApplication.setKey(
DOWNLOAD_HEADER_CACHE,
mainId.toString(),
@ -1807,6 +1867,8 @@ class ResultViewModel2 : ViewModel() {
updateFillers = showFillers,
apiRepository = repo
)
if (!isActive) return@launch
handleAutoStart(activity, autostart)
}
is Resource.Loading -> {
debugException { "Invalid load result" }

View file

@ -4,7 +4,6 @@ import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.lagradost.cloudstream3.apmap
import com.lagradost.cloudstream3.mvvm.Resource
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.malApi
import com.lagradost.cloudstream3.syncproviders.SyncAPI
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
import com.lagradost.cloudstream3.utils.SyncUtil
import kotlinx.coroutines.launch
import java.util.*
@ -103,10 +102,10 @@ class SyncViewModel : ViewModel() {
var hasAddedFromUrl: HashSet<String> = hashSetOf()
fun addFromUrl(url: String?) = viewModelScope.launch {
fun addFromUrl(url: String?) = ioSafe {
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) ->
hasAddedFromUrl.add(url)
@ -170,7 +169,7 @@ class SyncViewModel : ViewModel() {
}
}
fun publishUserData() = viewModelScope.launch {
fun publishUserData() = ioSafe {
Log.i(TAG, "publishUserData")
val user = userData.value
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
private fun modifyData(update: ((SyncAPI.SyncStatus) -> (SyncAPI.SyncStatus?))) =
viewModelScope.launch {
ioSafe {
syncs.apmap { (prefix, id) ->
repos.firstOrNull { it.idPrefix == prefix }?.let { repo ->
if (repo.hasAccount()) {
@ -213,7 +212,7 @@ class SyncViewModel : ViewModel() {
}
}
fun updateUserData() = viewModelScope.launch {
fun updateUserData() = ioSafe {
Log.i(TAG, "updateUserData")
_userDataResponse.postValue(Resource.Loading())
var lastError: Resource<SyncAPI.SyncStatus> = Resource.Failure(false, null, null, "No data")
@ -223,7 +222,7 @@ class SyncViewModel : ViewModel() {
val result = repo.getStatus(id)
if (result is Resource.Success) {
_userDataResponse.postValue(result)
return@launch
return@ioSafe
} else if (result is Resource.Failure) {
Log.e(TAG, "updateUserData error ${result.errorString}")
lastError = result
@ -234,7 +233,7 @@ class SyncViewModel : ViewModel() {
_userDataResponse.postValue(lastError)
}
private fun updateMetadata() = viewModelScope.launch {
private fun updateMetadata() = ioSafe {
Log.i(TAG, "updateMetadata")
_metaResponse.postValue(Resource.Loading())
@ -257,7 +256,7 @@ class SyncViewModel : ViewModel() {
val result = repo.getResult(id)
if (result is Resource.Success) {
_metaResponse.postValue(result)
return@launch
return@ioSafe
} else if (result is Resource.Failure) {
Log.e(
TAG,

View file

@ -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_OLD = "result_resume_watching"
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_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
return getKey(
"$currentAccount/$RESULT_RESUME_WATCHING_OLD",
@ -192,8 +193,9 @@ object DataStoreHelper {
return getKey("$currentAccount/$VIDEO_POS_DUR", id.toString(), null)
}
fun getDub(id: Int): DubStatus {
return DubStatus.values()[getKey("$currentAccount/$RESULT_DUB", id.toString()) ?: 0]
fun getDub(id: Int): DubStatus? {
return DubStatus.values()
.getOrNull(getKey("$currentAccount/$RESULT_DUB", id.toString(), -1) ?: -1)
}
fun setDub(id: Int, status: DubStatus) {
@ -221,14 +223,22 @@ object DataStoreHelper {
)
}
fun getResultSeason(id: Int): Int {
return getKey("$currentAccount/$RESULT_SEASON", id.toString()) ?: -1
fun getResultSeason(id: Int): Int? {
return getKey("$currentAccount/$RESULT_SEASON", id.toString(), null)
}
fun setResultSeason(id: Int, value: Int?) {
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) {
setKey("${idPrefix}_sync", id.toString(), url)
}

View file

@ -531,7 +531,7 @@
"TrailersTwoProvider": {
"language": "en",
"name": "Trailers.to",
"status": 1,
"status": 0,
"url": "https://trailers.to"
},
"TwoEmbedProvider": {

View file

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