From 989d7666be29180a6d508acf04be3c060f53eab4 Mon Sep 17 00:00:00 2001 From: LagradOst Date: Mon, 20 Sep 2021 00:36:32 +0200 Subject: [PATCH] fillers --- .../com/lagradost/cloudstream3/MainAPI.kt | 1 + .../cloudstream3/ui/result/EpisodeAdapter.kt | 2 +- .../cloudstream3/ui/result/ResultFragment.kt | 71 ++++++++++----- .../cloudstream3/ui/result/ResultViewModel.kt | 32 ++++--- .../cloudstream3/utils/FillerEpisodeCheck.kt | 90 +++++++++++++++++++ app/src/main/res/layout/fragment_result.xml | 7 ++ app/src/main/res/values/strings.xml | 3 + app/src/main/res/xml/settings.xml | 5 ++ 8 files changed, 178 insertions(+), 33 deletions(-) create mode 100644 app/src/main/java/com/lagradost/cloudstream3/utils/FillerEpisodeCheck.kt diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt index d286941c..49a429c3 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt @@ -351,6 +351,7 @@ data class AnimeEpisode( val date: String? = null, val rating: Int? = null, val descript: String? = null, + val episode : Int? = null, ) data class TorrentLoadResponse( 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 4cd5c932..78d6169f 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 @@ -141,7 +141,7 @@ class EpisodeAdapter( localCard = card val name = if (card.name == null) "${episodeText.context.getString(R.string.episode)} ${card.episode}" else "${card.episode}. ${card.name}" - episodeText.text = name + episodeText.text = if(card.isFiller == true) episodeText.context.getString(R.string.filler_format).format(name) else name episodeText.isSelected = true // is needed for text repeating val displayPos = card.getDisplayPosition() 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 4ec0d152..58465521 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 @@ -22,9 +22,11 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.content.ContextCompat import androidx.core.content.FileProvider import androidx.core.text.color +import androidx.core.view.isVisible import androidx.core.widget.NestedScrollView import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider +import androidx.preference.PreferenceManager import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView import com.google.android.gms.cast.framework.CastButtonFactory @@ -35,6 +37,7 @@ import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.APIHolder.getApiFromName import com.lagradost.cloudstream3.APIHolder.getId import com.lagradost.cloudstream3.MainActivity.Companion.showToast +import com.lagradost.cloudstream3.MainActivity.Companion.updateLocale import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.observe import com.lagradost.cloudstream3.ui.WatchType @@ -92,6 +95,7 @@ data class ResultEpisode( val duration: Long, // duration in MS val rating: Int?, val descript: String?, + val isFiller: Boolean?, ) fun ResultEpisode.getRealPosition(): Long { @@ -121,6 +125,7 @@ fun Context.buildResultEpisode( index: Int, rating: Int?, descript: String?, + isFiller: Boolean?, ): ResultEpisode { val posDur = getViewPos(id) return ResultEpisode( @@ -136,6 +141,7 @@ fun Context.buildResultEpisode( posDur?.duration ?: 0, rating, descript, + isFiller ) } @@ -442,9 +448,11 @@ class ResultFragment : Fragment() { if (isMovie) null else episodeClick.data.episode ) + val folder = when (currentType) { TvType.Anime -> "Anime/$titleName" TvType.Movie -> "Movies" + TvType.AnimeMovie -> "Movies" TvType.TvSeries -> "TVSeries/$titleName" TvType.ONA -> "ONA" TvType.Cartoon -> "Cartoons/$titleName" @@ -817,11 +825,22 @@ class ResultFragment : Fragment() { } observe(viewModel.publicEpisodes) { episodes -> - if (result_episodes == null || result_episodes.adapter == null) return@observe - currentEpisodes = episodes - (result_episodes?.adapter as EpisodeAdapter?)?.cardList = episodes - (result_episodes?.adapter as EpisodeAdapter?)?.updateLayout() - (result_episodes?.adapter as EpisodeAdapter?)?.notifyDataSetChanged() + when (episodes) { + is Resource.Failure -> { + result_episode_loading.isVisible = false + } + is Resource.Loading -> { + result_episode_loading.isVisible = true + } + is Resource.Success -> { + result_episode_loading.isVisible = false + if (result_episodes == null || result_episodes.adapter == null) return@observe + currentEpisodes = episodes.value + (result_episodes?.adapter as EpisodeAdapter?)?.cardList = episodes.value + (result_episodes?.adapter as EpisodeAdapter?)?.updateLayout() + (result_episodes?.adapter as EpisodeAdapter?)?.notifyDataSetChanged() + } + } } observe(viewModel.selectedRange) { range -> @@ -856,6 +875,10 @@ class ResultFragment : Fragment() { is Resource.Success -> { val d = data.value if (d is LoadResponse) { + if (d !is AnimeLoadResponse && result_episode_loading.isVisible) { // no episode loading when not anime + result_episode_loading.isVisible = false + } + updateVisStatus(2) result_vpn?.text = when (api.vpnStatus) { @@ -1034,7 +1057,8 @@ class ResultFragment : Fragment() { 0L, 0L, null, - null + null, + null, ) ) ) @@ -1073,25 +1097,28 @@ class ResultFragment : Fragment() { } } - val tempUrl = url - if (tempUrl != null) { - result_reload_connectionerror.setOnClickListener { - viewModel.load(it.context, tempUrl, apiName) - } + context?.let { ctx -> + val settingsManager = PreferenceManager.getDefaultSharedPreferences(ctx) + val showFillers = settingsManager.getBoolean(ctx.getString(R.string.show_fillers_key), true) - result_reload_connection_open_in_browser.setOnClickListener { - val i = Intent(ACTION_VIEW) - i.data = Uri.parse(tempUrl) - try { - startActivity(i) - } catch (e: Exception) { - e.printStackTrace() + val tempUrl = url + if (tempUrl != null) { + result_reload_connectionerror.setOnClickListener { + viewModel.load(it.context, tempUrl, apiName, showFillers) } - } - if (viewModel.resultResponse.value == null) { - context?.let { ctx -> - viewModel.load(ctx, tempUrl, apiName) + result_reload_connection_open_in_browser.setOnClickListener { + val i = Intent(ACTION_VIEW) + i.data = Uri.parse(tempUrl) + try { + startActivity(i) + } catch (e: Exception) { + e.printStackTrace() + } + } + + if (viewModel.resultResponse.value == null) { + viewModel.load(ctx, tempUrl, apiName, showFillers) } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel.kt index 3f212c64..aa312166 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel.kt @@ -23,6 +23,7 @@ import com.lagradost.cloudstream3.utils.DataStoreHelper.setResultSeason import com.lagradost.cloudstream3.utils.DataStoreHelper.setResultWatchState import com.lagradost.cloudstream3.utils.DataStoreHelper.setViewPos import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.FillerEpisodeCheck.getFillerEpisodes import com.lagradost.cloudstream3.utils.VideoDownloadHelper import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -39,7 +40,7 @@ class ResultViewModel : ViewModel() { private val _episodes: MutableLiveData> = MutableLiveData() private val episodeById: MutableLiveData> = MutableLiveData() // lookup by ID to get Index - private val _publicEpisodes: MutableLiveData> = MutableLiveData() + private val _publicEpisodes: MutableLiveData>> = MutableLiveData() private val _publicEpisodesCount: MutableLiveData = MutableLiveData() // before the sorting private val _rangeOptions: MutableLiveData> = MutableLiveData() val selectedRange: MutableLiveData = MutableLiveData() @@ -48,7 +49,7 @@ class ResultViewModel : ViewModel() { val resultResponse: LiveData> get() = _resultResponse val episodes: LiveData> get() = _episodes - val publicEpisodes: LiveData> get() = _publicEpisodes + val publicEpisodes: LiveData>> get() = _publicEpisodes val publicEpisodesCount: LiveData get() = _publicEpisodesCount private val dubStatus: MutableLiveData = MutableLiveData() @@ -106,7 +107,7 @@ class ResultViewModel : ViewModel() { val seasons = seasonTypes.toList().map { it.first }.sortedBy { it } seasonSelections.postValue(seasons) if (seasons.isEmpty()) { // WHAT THE FUCK DID YOU DO????? HOW DID YOU DO THIS - _publicEpisodes.postValue(ArrayList()) + _publicEpisodes.postValue(Resource.Success( ArrayList())) return } @@ -156,7 +157,7 @@ class ResultViewModel : ViewModel() { selectedRange.postValue(allRange) } - _publicEpisodes.postValue(currentList) + _publicEpisodes.postValue(Resource.Success( currentList)) } fun changeSeason(context: Context, selection: Int?) { @@ -224,17 +225,18 @@ class ResultViewModel : ViewModel() { } } - private fun filterName(name : String?) : String? { - if(name == null) return null + private fun filterName(name: String?): String? { + if (name == null) return null Regex("[eE]pisode [0-9]*(.*)").find(name)?.groupValues?.get(1)?.let { - if(it.isEmpty()) + if (it.isEmpty()) return null } return name } - fun load(context: Context, url: String, apiName: String) = viewModelScope.launch { + fun load(context: Context, url: String, apiName: String, showFillers : Boolean) = viewModelScope.launch { _resultResponse.postValue(Resource.Loading(url)) + _publicEpisodes.postValue(Resource.Loading()) _apiName.postValue(apiName) val api = getApiFromName(apiName) @@ -273,14 +275,17 @@ class ResultViewModel : ViewModel() { val dataList = (if (isDub) d.dubEpisodes else d.subEpisodes) + val fillerEpisodes = if(showFillers) safeApiCall { getFillerEpisodes(d.name) } else null + if (dataList != null) { // TODO dub and sub at the same time val episodes = ArrayList() for ((index, i) in dataList.withIndex()) { + val episode = i.episode ?: (index + 1); episodes.add( context.buildResultEpisode( filterName(i.name), i.posterUrl, - index + 1, //TODO MAKE ABLE TO NOT HAVE SOME EPISODE + episode, null, // TODO FIX SEASON i.url, apiName, @@ -288,6 +293,10 @@ class ResultViewModel : ViewModel() { index, i.rating, i.descript, + if (fillerEpisodes is Resource.Success) fillerEpisodes.value?.let { + it.contains(episode) && it[episode] == true + } + ?: false else false, ) ) } @@ -309,7 +318,8 @@ class ResultViewModel : ViewModel() { (mainId + index + 1).hashCode(), index, i.rating, - i.descript + i.descript, + null, ) ) } @@ -329,6 +339,7 @@ class ResultViewModel : ViewModel() { 0, null, null, + null, ) ), -1 ) @@ -347,6 +358,7 @@ class ResultViewModel : ViewModel() { 0, null, null, + null, ) ), -1 ) diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/FillerEpisodeCheck.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/FillerEpisodeCheck.kt new file mode 100644 index 00000000..880383e9 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/FillerEpisodeCheck.kt @@ -0,0 +1,90 @@ +package com.lagradost.cloudstream3.utils + +import org.jsoup.Jsoup +import java.util.* +import kotlin.collections.HashMap + +object FillerEpisodeCheck { + private const val MAIN_URL = "https://www.animefillerlist.com" + + var list: HashMap? = null + + private fun fixName(name: String): String { + return name.toLowerCase(Locale.ROOT)/*.replace(" ", "")*/.replace("-", " ").replace("[^a-zA-Z0-9 ]".toRegex(), "") + } + + private fun getFillerList(): Boolean { + if (list != null) return true + try { + val result = khttp.get("$MAIN_URL/shows") + val documented = Jsoup.parse(result.text) + val localHTMLList = documented.select("div#ShowList > div.Group > ul > li > a") + val localList = HashMap() + for (i in localHTMLList) { + val name = i.text() + + if (name.toLowerCase(Locale.ROOT).contains("manga only")) continue + + val href = i.attr("href") + if (name.isNullOrEmpty() || href.isNullOrEmpty()) { + continue + } + + val values = "(.*) \\((.*)\\)".toRegex().matchEntire(name)?.groups + if (values != null) { + for (index in 1 until values.size) { + val localName = values[index]?.value ?: continue + localList[fixName(localName)] = href + } + } else { + localList[fixName(name)] = href + } + } + if (localList.size > 0) { + list = localList + return true + } + } catch (e: Exception) { + e.printStackTrace() + } + return false + } + + fun getFillerEpisodes(query: String): HashMap? { + try { + if (!getFillerList()) return null + val localList = list ?: return null + + // Strips these from the name + val blackList = listOf( + "TV Dubbed", + "(Dub)", + "Subbed", + "(TV)", + "(Uncensored)", + "(Censored)", + "(\\d+)" // year + ) + val blackListRegex = + Regex(""" (${blackList.joinToString(separator = "|").replace("(", "\\(").replace(")", "\\)")})""") + + val realQuery = fixName(query.replace(blackListRegex, "")).replace("shippuuden", "shippuden") + if (!localList.containsKey(realQuery)) return null + val href = localList[realQuery]?.replace(MAIN_URL, "") ?: return null // JUST IN CASE + val result = khttp.get("$MAIN_URL$href") + val documented = Jsoup.parse(result.text) ?: return null + val hashMap = HashMap() + documented.select("table.EpisodeList > tbody > tr").forEach { + val type = it.selectFirst("td.Type > span").text() == "Filler" + val episodeNumber = it.selectFirst("td.Number").text().toIntOrNull() + if (episodeNumber != null) { + hashMap[episodeNumber] = type + } + } + return hashMap + } catch (e: Exception) { + e.printStackTrace() + return null + } + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_result.xml b/app/src/main/res/layout/fragment_result.xml index 9cb0eaae..d8102686 100644 --- a/app/src/main/res/layout/fragment_result.xml +++ b/app/src/main/res/layout/fragment_result.xml @@ -435,6 +435,13 @@ /> + + double_tap_enabled_key swipe_vertical_enabled_key display_sub_key + show_fillers_key %d %s | %sMB @@ -48,6 +49,7 @@ Speed (%.2fx) Rated: %.1f New update found!\n%s -> %s + (Filler) %s CloudStream Home @@ -169,6 +171,7 @@ Gives you the search results separated by provider Only sends data on crashes Sends no data + Show filler episode for anime Show app updates Automatically search for new updates on start Update to prereleases diff --git a/app/src/main/res/xml/settings.xml b/app/src/main/res/xml/settings.xml index 4d559c8c..9976e4a4 100644 --- a/app/src/main/res/xml/settings.xml +++ b/app/src/main/res/xml/settings.xml @@ -103,6 +103,11 @@ android:title="@string/app_language" android:icon="@drawable/ic_baseline_language_24"> +