forked from recloudstream/cloudstream
another provider and a bunch of bug fixes
This commit is contained in:
parent
9550154a49
commit
f1c563e3bc
12 changed files with 250 additions and 40 deletions
|
@ -12,9 +12,10 @@
|
|||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:usesCleartextTraffic="true"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme" android:fullBackupContent="@xml/backup_descriptor">
|
||||
android:theme="@style/AppTheme" android:fullBackupContent="@xml/backup_descriptor" tools:targetApi="m">
|
||||
<meta-data
|
||||
android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
|
||||
android:value="com.lagradost.cloudstream3.utils.CastOptionsProvider"/>
|
||||
|
|
|
@ -6,6 +6,7 @@ import com.fasterxml.jackson.databind.DeserializationFeature
|
|||
import com.fasterxml.jackson.databind.json.JsonMapper
|
||||
import com.fasterxml.jackson.module.kotlin.KotlinModule
|
||||
import com.lagradost.cloudstream3.animeproviders.ShiroProvider
|
||||
import com.lagradost.cloudstream3.movieproviders.MeloMovieProvider
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
|
@ -20,8 +21,9 @@ object APIHolder {
|
|||
|
||||
private const val defProvider = 0
|
||||
|
||||
val apis = arrayListOf<MainAPI>(
|
||||
ShiroProvider()
|
||||
val apis = arrayListOf(
|
||||
ShiroProvider(),
|
||||
MeloMovieProvider(),
|
||||
)
|
||||
|
||||
fun getApiFromName(apiName: String?): MainAPI {
|
||||
|
@ -44,6 +46,7 @@ object APIHolder {
|
|||
abstract class MainAPI {
|
||||
open val name = "NONE"
|
||||
open val mainUrl = "NONE"
|
||||
open val instantLinkLoading = false // THIS IS IF THE LINK IS STORED IN THE "DATA"
|
||||
open fun search(query: String): ArrayList<Any>? { // SearchResponse
|
||||
return null
|
||||
}
|
||||
|
@ -195,12 +198,14 @@ data class MovieLoadResponse(
|
|||
val imdbId: Int?,
|
||||
) : LoadResponse
|
||||
|
||||
data class TvSeriesEpisode(val name: String?, val season : Int?, val episode: Int?, val data : String)
|
||||
|
||||
data class TvSeriesLoadResponse(
|
||||
override val name: String,
|
||||
override val url: String,
|
||||
override val apiName: String,
|
||||
override val type: TvType,
|
||||
val episodes: ArrayList<String>,
|
||||
val episodes: ArrayList<TvSeriesEpisode>,
|
||||
|
||||
override val posterUrl: String?,
|
||||
override val year: Int?,
|
||||
|
|
|
@ -0,0 +1,155 @@
|
|||
package com.lagradost.cloudstream3.movieproviders
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.fasterxml.jackson.module.kotlin.readValue
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.getQualityFromName
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Element
|
||||
|
||||
class MeloMovieProvider : MainAPI() {
|
||||
override val name: String
|
||||
get() = "MeloMovie"
|
||||
override val mainUrl: String
|
||||
get() = "https://melomovie.com"
|
||||
override val instantLinkLoading: Boolean
|
||||
get() = true
|
||||
|
||||
data class MeloMovieSearchResult(
|
||||
@JsonProperty("id") val id: Int,
|
||||
@JsonProperty("imdb_code") val imdbId: String,
|
||||
@JsonProperty("title") val title: String,
|
||||
@JsonProperty("type") val type: Int, // 1 = MOVIE, 2 = TV-SERIES
|
||||
@JsonProperty("year") val year: Int?, // 1 = MOVIE, 2 = TV-SERIES
|
||||
//"mppa" for tags
|
||||
)
|
||||
|
||||
data class MeloMovieLink(val name: String, val link: String)
|
||||
|
||||
override fun search(query: String): ArrayList<Any>? {
|
||||
val url = "$mainUrl/movie/search/?name=$query"
|
||||
val returnValue: ArrayList<Any> = ArrayList()
|
||||
val response = khttp.get(url)
|
||||
val mapped = response.let { mapper.readValue<List<MeloMovieSearchResult>>(it.text) }
|
||||
if (mapped.isEmpty()) return returnValue
|
||||
|
||||
for (i in mapped) {
|
||||
val currentUrl = "$mainUrl/movie/${i.id}"
|
||||
val currentPoster = "$mainUrl/assets/images/poster/${i.imdbId}.jpg"
|
||||
if (i.type == 2) { // TV-SERIES
|
||||
returnValue.add(TvSeriesSearchResponse(i.title,
|
||||
currentUrl,
|
||||
currentUrl,
|
||||
this.name,
|
||||
TvType.TvSeries,
|
||||
currentPoster,
|
||||
i.year,
|
||||
null))
|
||||
} else if (i.type == 1) { // MOVIE
|
||||
returnValue.add(MovieSearchResponse(i.title,
|
||||
currentUrl,
|
||||
currentUrl,
|
||||
this.name,
|
||||
TvType.Movie,
|
||||
currentUrl,
|
||||
i.year))
|
||||
}
|
||||
}
|
||||
return returnValue
|
||||
}
|
||||
|
||||
// http not https, the links are not https!
|
||||
private fun fixUrl(url: String): String {
|
||||
if(url.isEmpty()) return ""
|
||||
|
||||
if (url.startsWith("//")) {
|
||||
return "http:$url"
|
||||
}
|
||||
if (!url.startsWith("http")) {
|
||||
return "http://$url"
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
private fun serializeData(element: Element): String {
|
||||
val eps = element.select("> tbody > tr")
|
||||
val parsed = eps.map {
|
||||
try {
|
||||
val tds = it.select("> td")
|
||||
val name = tds[if (tds.size == 5) 1 else 0].text()
|
||||
val url = fixUrl(tds.last().selectFirst("> a").attr("data-lnk").replace(" ", "%20"))
|
||||
MeloMovieLink(name, url)
|
||||
} catch (e: Exception) {
|
||||
MeloMovieLink("", "")
|
||||
}
|
||||
}.filter { it.link != "" && it.name != "" }
|
||||
return mapper.writeValueAsString(parsed)
|
||||
}
|
||||
|
||||
override fun loadLinks(data: String, isCasting: Boolean, callback: (ExtractorLink) -> Unit): Boolean {
|
||||
val links = mapper.readValue<List<MeloMovieLink>>(data)
|
||||
for (link in links) {
|
||||
callback.invoke(ExtractorLink(this.name, link.name, link.link, "", getQualityFromName(link.name), false))
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun load(slug: String): Any? {
|
||||
val response = khttp.get(slug).text
|
||||
|
||||
//backdrop = imgurl
|
||||
fun findUsingRegex(src: String): String? {
|
||||
return src.toRegex().find(response)?.groups?.get(1)?.value ?: return null
|
||||
}
|
||||
|
||||
val imdbId = findUsingRegex("var imdb = \"(tt[0-9]*)\"")?.toIntOrNull()
|
||||
val document = Jsoup.parse(response)
|
||||
val poster = document.selectFirst("img.img-fluid").attr("src")
|
||||
val type = findUsingRegex("var posttype = ([0-9]*)")?.toInt() ?: return null
|
||||
val titleInfo = document.selectFirst("div.movie_detail_title > div > div > h1")
|
||||
val title = titleInfo.ownText()
|
||||
val year = titleInfo.selectFirst("> a").text().replace("(", "").replace(")", "").toIntOrNull()
|
||||
val plot = document.selectFirst("div.col-lg-12 > p").text()
|
||||
|
||||
if (type == 1) { // MOVIE
|
||||
val serialize = document.selectFirst("table.accordion__list")
|
||||
return MovieLoadResponse(title,
|
||||
slug,
|
||||
this.name,
|
||||
TvType.Movie,
|
||||
serializeData(serialize),
|
||||
poster,
|
||||
year,
|
||||
plot,
|
||||
imdbId)
|
||||
} else if (type == 2) {
|
||||
val episodes = ArrayList<TvSeriesEpisode>()
|
||||
val seasons = document.select("div.accordion__card")
|
||||
for (s in seasons) {
|
||||
val season =
|
||||
s.selectFirst("> div.card-header > button > span").text().replace("Season: ", "").toIntOrNull()
|
||||
val localEpisodes = s.select("> div.collapse > div > div > div.accordion__card")
|
||||
for (e in localEpisodes) {
|
||||
val episode =
|
||||
e.selectFirst("> div.card-header > button > span").text().replace("Episode: ", "").toIntOrNull()
|
||||
val links = e.selectFirst("> div.collapse > div > table.accordion__list")
|
||||
val data = serializeData(links)
|
||||
episodes.add(TvSeriesEpisode(null, season, episode, data))
|
||||
}
|
||||
}
|
||||
episodes.reverse()
|
||||
return TvSeriesLoadResponse(title,
|
||||
slug,
|
||||
this.name,
|
||||
TvType.TvSeries,
|
||||
episodes,
|
||||
poster,
|
||||
year,
|
||||
plot,
|
||||
null,
|
||||
imdbId)
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
|
@ -77,6 +77,7 @@ class SkipNextEpisodeController(val view: ImageView) : UIController() {
|
|||
|
||||
data class MetadataHolder(
|
||||
val apiName: String,
|
||||
val isMovie: Boolean,
|
||||
val title: String?,
|
||||
val poster: String?,
|
||||
val currentEpisodeIndex: Int,
|
||||
|
|
|
@ -386,7 +386,8 @@ class PlayerFragment : Fragment() {
|
|||
if (useSystemBrightness) {
|
||||
// https://developer.android.com/reference/android/view/WindowManager.LayoutParams#screenBrightness
|
||||
val lp = activity?.window?.attributes
|
||||
val currentBrightness = if (lp?.screenBrightness ?: -1.0f <= 0f) (android.provider.Settings.System.getInt(
|
||||
val currentBrightness =
|
||||
if (lp?.screenBrightness ?: -1.0f <= 0f) (android.provider.Settings.System.getInt(
|
||||
context?.contentResolver,
|
||||
android.provider.Settings.System.SCREEN_BRIGHTNESS
|
||||
) * (1 / 255).toFloat())
|
||||
|
@ -403,8 +404,7 @@ class PlayerFragment : Fragment() {
|
|||
|
||||
progressBarRight?.max = 100 * 100
|
||||
progressBarRight?.progress = (alpha * 100 * 100).toInt()
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
val alpha = minOf(0.95f,
|
||||
brightness_overlay.alpha + diffY.toFloat() * 0.5f) // 0.05f *if (diffY > 0) 1 else -1
|
||||
brightness_overlay?.alpha = alpha
|
||||
|
@ -638,6 +638,7 @@ class PlayerFragment : Fragment() {
|
|||
private var episodes: List<ResultEpisode> = ArrayList()
|
||||
var currentPoster: String? = null
|
||||
var currentHeaderName: String? = null
|
||||
var currentIsMovie: Boolean? = null
|
||||
|
||||
//region PIP MODE
|
||||
private fun getPen(code: PlayerEventType): PendingIntent {
|
||||
|
@ -763,6 +764,7 @@ class PlayerFragment : Fragment() {
|
|||
val index = links.indexOf(getCurrentUrl())
|
||||
context?.startCast(
|
||||
apiName,
|
||||
currentIsMovie ?: return@addCastStateListener,
|
||||
currentHeaderName,
|
||||
currentPoster,
|
||||
epData.index,
|
||||
|
@ -893,6 +895,7 @@ class PlayerFragment : Fragment() {
|
|||
localData = d
|
||||
currentPoster = d.posterUrl
|
||||
currentHeaderName = d.name
|
||||
currentIsMovie = !d.isEpisodeBased()
|
||||
}
|
||||
}
|
||||
is Resource.Failure -> {
|
||||
|
@ -1474,6 +1477,7 @@ class PlayerFragment : Fragment() {
|
|||
}
|
||||
|
||||
override fun onPlayerError(error: ExoPlaybackException) {
|
||||
println("CURRENT URL: " + currentUrl.url)
|
||||
// Lets pray this doesn't spam Toasts :)
|
||||
when (error.type) {
|
||||
ExoPlaybackException.TYPE_SOURCE -> {
|
||||
|
|
|
@ -29,6 +29,7 @@ import com.google.android.gms.cast.framework.CastContext
|
|||
import com.google.android.gms.cast.framework.CastState
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.APIHolder.getApiFromName
|
||||
import com.lagradost.cloudstream3.UIHelper.colorFromAttribute
|
||||
import com.lagradost.cloudstream3.UIHelper.fixPaddingStatusbar
|
||||
import com.lagradost.cloudstream3.UIHelper.getStatusBarHeight
|
||||
|
@ -176,6 +177,7 @@ class ResultFragment : Fragment() {
|
|||
}
|
||||
|
||||
private var currentPoster: String? = null
|
||||
private var currentIsMovie: Boolean? = null
|
||||
|
||||
var url: String? = null
|
||||
|
||||
|
@ -239,22 +241,33 @@ class ResultFragment : Fragment() {
|
|||
currentLoadingCount++
|
||||
when (episodeClick.action) {
|
||||
ACTION_CHROME_CAST_EPISODE -> {
|
||||
|
||||
val skipLoading = if (apiName != null) {
|
||||
getApiFromName(apiName).instantLinkLoading
|
||||
} else false
|
||||
|
||||
var dialog : AlertDialog? = null
|
||||
val currentLoad = currentLoadingCount
|
||||
|
||||
if(!skipLoading) {
|
||||
val builder = AlertDialog.Builder(requireContext(), R.style.AlertDialogCustomTransparent)
|
||||
val customLayout = layoutInflater.inflate(R.layout.dialog_loading, null)
|
||||
builder.setView(customLayout)
|
||||
|
||||
val dialog = builder.create()
|
||||
dialog = builder.create()
|
||||
|
||||
dialog.show()
|
||||
dialog.setOnDismissListener {
|
||||
currentLoadingCount++
|
||||
}
|
||||
}
|
||||
|
||||
// Toast.makeText(activity, "Loading links", Toast.LENGTH_SHORT).show()
|
||||
|
||||
viewModel.loadEpisode(episodeClick.data, true) { data ->
|
||||
if (currentLoadingCount != currentLoad) return@loadEpisode
|
||||
dialog.dismiss()
|
||||
dialog?.dismiss()
|
||||
|
||||
when (data) {
|
||||
is Resource.Failure -> {
|
||||
Toast.makeText(activity, "Failed to load links", Toast.LENGTH_SHORT).show()
|
||||
|
@ -263,6 +276,7 @@ class ResultFragment : Fragment() {
|
|||
val eps = currentEpisodes ?: return@loadEpisode
|
||||
context?.startCast(
|
||||
apiName ?: return@loadEpisode,
|
||||
currentIsMovie ?: return@loadEpisode,
|
||||
currentHeaderName,
|
||||
currentPoster,
|
||||
episodeClick.data.index,
|
||||
|
@ -354,6 +368,7 @@ class ResultFragment : Fragment() {
|
|||
currentHeaderName = d.name
|
||||
|
||||
currentPoster = d.posterUrl
|
||||
currentIsMovie = !d.isEpisodeBased()
|
||||
|
||||
result_openinbrower.setOnClickListener {
|
||||
val i = Intent(Intent.ACTION_VIEW)
|
||||
|
|
|
@ -104,11 +104,12 @@ class ResultViewModel : ViewModel() {
|
|||
val episodes = ArrayList<ResultEpisode>()
|
||||
for ((index, i) in d.episodes.withIndex()) {
|
||||
episodes.add(context.buildResultEpisode(
|
||||
null, // TODO ADD NAMES
|
||||
(i.name
|
||||
?: (if (i.season != null && i.episode != null) "S${i.season}:E${i.episode}" else null)), // TODO ADD NAMES
|
||||
null,
|
||||
index + 1, //TODO MAKE ABLE TO NOT HAVE SOME EPISODE
|
||||
null, // TODO FIX SEASON
|
||||
i,
|
||||
i.episode ?: (index + 1),
|
||||
i.season,
|
||||
i.data,
|
||||
apiName,
|
||||
(mainId + index + 1).hashCode(),
|
||||
index,
|
||||
|
|
|
@ -144,7 +144,9 @@ class SearchFragment : Fragment() {
|
|||
}
|
||||
}
|
||||
}
|
||||
searchViewModel.search("overlord")
|
||||
allApi.providersActive = requireActivity().getApiSettings()
|
||||
|
||||
searchViewModel.search("iron man")
|
||||
// (activity as AppCompatActivity).loadResult("https://shiro.is/overlord-dubbed", "overlord-dubbed", "Shiro")
|
||||
/*
|
||||
(requireActivity() as AppCompatActivity).supportFragmentManager.beginTransaction()
|
||||
|
|
|
@ -4,6 +4,7 @@ import androidx.lifecycle.LiveData
|
|||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.lagradost.cloudstream3.APIHolder.allApi
|
||||
import com.lagradost.cloudstream3.APIHolder.apis
|
||||
import com.lagradost.cloudstream3.MainAPI
|
||||
import com.lagradost.cloudstream3.mvvm.Resource
|
||||
|
@ -11,15 +12,13 @@ import com.lagradost.cloudstream3.mvvm.safeApiCall
|
|||
import kotlinx.coroutines.launch
|
||||
|
||||
class SearchViewModel : ViewModel() {
|
||||
val api: MainAPI = apis[0] //TODO MULTI API
|
||||
|
||||
private val _searchResponse: MutableLiveData<Resource<ArrayList<Any>>> = MutableLiveData()
|
||||
val searchResponse: LiveData<Resource<ArrayList<Any>>> get() = _searchResponse
|
||||
|
||||
fun search(query: String) = viewModelScope.launch {
|
||||
_searchResponse.postValue(Resource.Loading())
|
||||
val data = safeApiCall {
|
||||
api.search(query)
|
||||
allApi.search(query)
|
||||
}
|
||||
|
||||
_searchResponse.postValue(data as Resource<ArrayList<Any>>?)
|
||||
|
|
|
@ -31,6 +31,9 @@ object CastHelper {
|
|||
val link = holder.currentLinks[index]
|
||||
val movieMetadata = MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE)
|
||||
movieMetadata.putString(MediaMetadata.KEY_SUBTITLE,
|
||||
if (holder.isMovie)
|
||||
link.name
|
||||
else
|
||||
(epData.name ?: "Episode ${epData.episode}") + " - ${link.name}")
|
||||
|
||||
movieMetadata.putString(MediaMetadata.KEY_TITLE, holder.title)
|
||||
|
@ -58,7 +61,7 @@ object CastHelper {
|
|||
println("FAILED AND LOAD NEXT")
|
||||
}
|
||||
else -> {
|
||||
println("FAILED::: " + res.status)
|
||||
//IDK DO SMTH HERE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -67,6 +70,7 @@ object CastHelper {
|
|||
|
||||
fun Context.startCast(
|
||||
apiName: String,
|
||||
isMovie: Boolean,
|
||||
title: String?,
|
||||
poster: String?,
|
||||
currentEpisodeIndex: Int,
|
||||
|
@ -81,7 +85,7 @@ object CastHelper {
|
|||
|
||||
val epData = episodes[currentEpisodeIndex]
|
||||
|
||||
val holder = MetadataHolder(apiName, title, poster, currentEpisodeIndex, episodes, currentLinks)
|
||||
val holder = MetadataHolder(apiName, isMovie, title, poster, currentEpisodeIndex, episodes, currentLinks)
|
||||
|
||||
val index = startIndex ?: 0
|
||||
val mediaItem =
|
||||
|
@ -96,7 +100,15 @@ object CastHelper {
|
|||
startTime ?: 0,
|
||||
)) {
|
||||
if (currentLinks.size > index + 1)
|
||||
startCast(apiName, title, poster, currentEpisodeIndex, episodes, currentLinks, index + 1, startTime)
|
||||
startCast(apiName,
|
||||
isMovie,
|
||||
title,
|
||||
poster,
|
||||
currentEpisodeIndex,
|
||||
episodes,
|
||||
currentLinks,
|
||||
index + 1,
|
||||
startTime)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -27,6 +27,20 @@ enum class Qualities(var value: Int) {
|
|||
UHD(3) // 4k
|
||||
}
|
||||
|
||||
fun getQualityFromName(qualityName: String): Int {
|
||||
return when (qualityName.replace("p", "").replace("P", "")) {
|
||||
"360" -> Qualities.SD
|
||||
"480" -> Qualities.SD
|
||||
"720" -> Qualities.HD
|
||||
"1080" -> Qualities.FullHd
|
||||
"1440" -> Qualities.UHD // I KNOW KINDA MISLEADING
|
||||
"2160" -> Qualities.UHD
|
||||
"4k" -> Qualities.UHD
|
||||
"4K" -> Qualities.UHD
|
||||
else -> Qualities.Unknown
|
||||
}.value
|
||||
}
|
||||
|
||||
private val packedRegex = Regex("""eval\(function\(p,a,c,k,e,.*\)\)""")
|
||||
fun getPacked(string: String): String? {
|
||||
return packedRegex.find(string)?.value
|
||||
|
|
|
@ -248,6 +248,7 @@
|
|||
|
||||
<TextView
|
||||
android:id="@+id/result_descript"
|
||||
android:textSize="15sp"
|
||||
tools:text="Ryan Quicksave Romano is an eccentric adventurer with a strange power: he can create a save-point in time and redo his life whenever he dies. Arriving in New Rome, the glitzy capital of sin of a rebuilding Europe, he finds the city torn between mega-corporations, sponsored heroes, superpowered criminals, and true monsters. It's a time of chaos, where potions can grant the power to rule the world and dangers lurk everywhere. "
|
||||
android:layout_width="match_parent" android:layout_height="wrap_content">
|
||||
</TextView>
|
||||
|
|
Loading…
Reference in a new issue