another provider and a bunch of bug fixes

This commit is contained in:
LagradOst 2021-06-17 00:31:41 +02:00
parent 9550154a49
commit f1c563e3bc
12 changed files with 250 additions and 40 deletions

View file

@ -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"/>

View file

@ -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?,

View file

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

View file

@ -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,
@ -91,13 +92,13 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi
init {
view.setImageResource(R.drawable.ic_baseline_playlist_play_24)
view.setOnClickListener {
// lateinit var dialog: AlertDialog
// lateinit var dialog: AlertDialog
val holder = getCurrentMetaData()
if (holder != null) {
val items = holder.currentLinks
if (items.isNotEmpty() && remoteMediaClient?.currentItem != null) {
// val builder = AlertDialog.Builder(view.context, R.style.AlertDialogCustom)
// val builder = AlertDialog.Builder(view.context, R.style.AlertDialogCustom)
/*val builder = BottomSheetDialog(view.context, R.style.AlertDialogCustom)
builder.setTitle("Pick source")*/
val bottomSheetDialog = BottomSheetDialog(view.context)
@ -212,7 +213,7 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi
links.add(it)
}
}
if (res is Resource.Success) {
val sorted = sortUrls(links)
if (sorted.isNotEmpty()) {
@ -295,9 +296,9 @@ class ControllerActivity : ExpandedControllerActivity() {
uiMediaController.bindViewToUIController(skipBackButton, SkipTimeController(skipBackButton, false))
uiMediaController.bindViewToUIController(skipForwardButton, SkipTimeController(skipForwardButton, true))
uiMediaController.bindViewToUIController(skipOpButton, SkipNextEpisodeController(skipOpButton))
/* val progressBar: CastSeekBar? = findViewById(R.id.cast_seek_bar)
/* val progressBar: CastSeekBar? = findViewById(R.id.cast_seek_bar)
progressBar?.backgroundTintList = (UIHelper.adjustAlpha(colorFromAttribute(R.attr.colorPrimary), 0.35f))
*/
progressBar?.backgroundTintList = (UIHelper.adjustAlpha(colorFromAttribute(R.attr.colorPrimary), 0.35f))
*/
}
}

View file

@ -386,11 +386,12 @@ 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(
context?.contentResolver,
android.provider.Settings.System.SCREEN_BRIGHTNESS
) * (1 / 255).toFloat())
else lp?.screenBrightness!!
val currentBrightness =
if (lp?.screenBrightness ?: -1.0f <= 0f) (android.provider.Settings.System.getInt(
context?.contentResolver,
android.provider.Settings.System.SCREEN_BRIGHTNESS
) * (1 / 255).toFloat())
else lp?.screenBrightness!!
val alpha = minOf(
maxOf(
@ -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 -> {
@ -1209,8 +1212,8 @@ class PlayerFragment : Fragment() {
}
}
private fun handlePauseEvent(pause : Boolean) {
if(pause) {
private fun handlePauseEvent(pause: Boolean) {
if (pause) {
handlePlayerEvent(PlayerEventType.Pause)
}
}
@ -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 -> {

View file

@ -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
val builder = AlertDialog.Builder(requireContext(), R.style.AlertDialogCustomTransparent)
val customLayout = layoutInflater.inflate(R.layout.dialog_loading, null)
builder.setView(customLayout)
val dialog = builder.create()
if(!skipLoading) {
val builder = AlertDialog.Builder(requireContext(), R.style.AlertDialogCustomTransparent)
val customLayout = layoutInflater.inflate(R.layout.dialog_loading, null)
builder.setView(customLayout)
dialog.show()
dialog.setOnDismissListener {
currentLoadingCount++
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)

View file

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

View file

@ -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()

View file

@ -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>>?)

View file

@ -31,7 +31,10 @@ object CastHelper {
val link = holder.currentLinks[index]
val movieMetadata = MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE)
movieMetadata.putString(MediaMetadata.KEY_SUBTITLE,
(epData.name ?: "Episode ${epData.episode}") + " - ${link.name}")
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)
}
}
}

View file

@ -15,7 +15,7 @@ data class ExtractorLink(
val isM3u8: Boolean = false,
)
fun ExtractorLink.getId() : Int {
fun ExtractorLink.getId(): Int {
return url.hashCode()
}
@ -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

View file

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