From 0606713fd56e47310ec611b3ef4816270512c782 Mon Sep 17 00:00:00 2001 From: reduplicated <110570621+reduplicated@users.noreply.github.com> Date: Thu, 4 Aug 2022 03:19:59 +0200 Subject: [PATCH] viewmodel done I think --- .../com/lagradost/cloudstream3/MainAPI.kt | 2 +- .../cloudstream3/ui/result/EpisodeAdapter.kt | 9 - .../cloudstream3/ui/result/ResultFragment.kt | 32 +- .../ui/result/ResultViewModel2.kt | 124 ++++++-- .../cloudstream3/ui/result/SyncViewModel.kt | 19 +- .../cloudstream3/utils/DataStoreHelper.kt | 20 +- docs/providers.json | 2 +- providers.json | 292 ------------------ 8 files changed, 137 insertions(+), 363 deletions(-) delete mode 100644 providers.json diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt index b34fc6d7..44bbebed 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt @@ -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() } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt index e8ddc7f4..6d6586c0 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt @@ -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 diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt index 10d5fbd4..7e840ef9 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt @@ -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 = 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 } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt index 03b31936..5c43c1ff 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt @@ -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, @@ -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" } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/SyncViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/SyncViewModel.kt index 40ad3913..b99d92f7 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/SyncViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/SyncViewModel.kt @@ -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 = 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 = 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, diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt index 9367189b..46c29e3f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt @@ -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) } diff --git a/docs/providers.json b/docs/providers.json index 32a48763..a562b79e 100644 --- a/docs/providers.json +++ b/docs/providers.json @@ -531,7 +531,7 @@ "TrailersTwoProvider": { "language": "en", "name": "Trailers.to", - "status": 1, + "status": 0, "url": "https://trailers.to" }, "TwoEmbedProvider": { diff --git a/providers.json b/providers.json deleted file mode 100644 index 0e95ddfe..00000000 --- a/providers.json +++ /dev/null @@ -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 - } -}