mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
fillers
This commit is contained in:
parent
bc1653e8c1
commit
989d7666be
8 changed files with 178 additions and 33 deletions
|
@ -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(
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<List<ResultEpisode>> = MutableLiveData()
|
||||
private val episodeById: MutableLiveData<HashMap<Int, Int>> = MutableLiveData() // lookup by ID to get Index
|
||||
|
||||
private val _publicEpisodes: MutableLiveData<List<ResultEpisode>> = MutableLiveData()
|
||||
private val _publicEpisodes: MutableLiveData<Resource<List<ResultEpisode>>> = MutableLiveData()
|
||||
private val _publicEpisodesCount: MutableLiveData<Int> = MutableLiveData() // before the sorting
|
||||
private val _rangeOptions: MutableLiveData<List<String>> = MutableLiveData()
|
||||
val selectedRange: MutableLiveData<String> = MutableLiveData()
|
||||
|
@ -48,7 +49,7 @@ class ResultViewModel : ViewModel() {
|
|||
|
||||
val resultResponse: LiveData<Resource<Any?>> get() = _resultResponse
|
||||
val episodes: LiveData<List<ResultEpisode>> get() = _episodes
|
||||
val publicEpisodes: LiveData<List<ResultEpisode>> get() = _publicEpisodes
|
||||
val publicEpisodes: LiveData<Resource<List<ResultEpisode>>> get() = _publicEpisodes
|
||||
val publicEpisodesCount: LiveData<Int> get() = _publicEpisodesCount
|
||||
|
||||
private val dubStatus: MutableLiveData<DubStatus> = 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<ResultEpisode>()
|
||||
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
|
||||
)
|
||||
|
|
|
@ -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<String, String>? = 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<String, String>()
|
||||
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<Int, Boolean>? {
|
||||
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<Int, Boolean>()
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -435,6 +435,13 @@
|
|||
/>
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.core.widget.ContentLoadingProgressBar
|
||||
style="@style/Widget.AppCompat.ProgressBar"
|
||||
android:id="@+id/result_episode_loading"
|
||||
android:layout_gravity="center"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp">
|
||||
</androidx.core.widget.ContentLoadingProgressBar>
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:layout_marginTop="0dp"
|
||||
android:paddingBottom="100dp"
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
<string name="double_tap_enabled_key" translatable="false">double_tap_enabled_key</string>
|
||||
<string name="swipe_vertical_enabled_key" translatable="false">swipe_vertical_enabled_key</string>
|
||||
<string name="display_sub_key" translatable="false">display_sub_key</string>
|
||||
<string name="show_fillers_key" translatable="false">show_fillers_key</string>
|
||||
|
||||
<!-- FORMAT MIGHT TRANSLATE, WILL CAUSE CRASH IF APPLIED WRONG -->
|
||||
<string name="extra_info_format" translatable="false" formatted="true">%d %s | %sMB</string>
|
||||
|
@ -48,6 +49,7 @@
|
|||
<string name="player_speed_text_format" formatted="true">Speed (%.2fx)</string>
|
||||
<string name="rated_format" formatted="true">Rated: %.1f</string>
|
||||
<string name="new_update_format" formatted="true">New update found!\n%s -> %s</string>
|
||||
<string name="filler_format" formatted="true">(Filler) %s</string>
|
||||
|
||||
<string name="app_name">CloudStream</string>
|
||||
<string name="title_home">Home</string>
|
||||
|
@ -169,6 +171,7 @@
|
|||
<string name="advanced_search_des">Gives you the search results separated by provider</string>
|
||||
<string name="bug_report_settings_off">Only sends data on crashes</string>
|
||||
<string name="bug_report_settings_on">Sends no data</string>
|
||||
<string name="show_fillers_settings">Show filler episode for anime</string>
|
||||
<string name="updates_settings">Show app updates</string>
|
||||
<string name="updates_settings_des">Automatically search for new updates on start</string>
|
||||
<string name="uprereleases_settings">Update to prereleases</string>
|
||||
|
|
|
@ -103,6 +103,11 @@
|
|||
android:title="@string/app_language"
|
||||
android:icon="@drawable/ic_baseline_language_24">
|
||||
</Preference>
|
||||
<SwitchPreference
|
||||
android:key="@string/show_fillers_key"
|
||||
android:icon="@drawable/ic_baseline_skip_next_24"
|
||||
android:title="@string/show_fillers_settings"
|
||||
android:defaultValue="true"/>
|
||||
<SwitchPreference
|
||||
android:key="acra.disable"
|
||||
android:icon="@drawable/ic_baseline_bug_report_24"
|
||||
|
|
Loading…
Reference in a new issue