mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
Merge remote-tracking branch 'origin/master' into dialog2
# Conflicts: # app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt
This commit is contained in:
commit
dd80b31fcb
24 changed files with 393 additions and 276 deletions
|
@ -29,6 +29,7 @@ import com.google.android.material.chip.ChipGroup
|
|||
import com.google.android.material.navigationrail.NavigationRailView
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
|
||||
import com.lagradost.cloudstream3.MainActivity.Companion.resumeApps
|
||||
import com.lagradost.cloudstream3.databinding.ToastBinding
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import com.lagradost.cloudstream3.ui.player.PlayerEventType
|
||||
|
|
|
@ -31,19 +31,16 @@ import java.text.SimpleDateFormat
|
|||
import java.util.*
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
const val USER_AGENT =
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36"
|
||||
|
||||
//val baseHeader = mapOf("User-Agent" to USER_AGENT)
|
||||
val mapper = JsonMapper.builder().addModule(kotlinModule())
|
||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).build()!!
|
||||
|
||||
/**
|
||||
* Defines the constant for the all languages preference, if this is set then it is
|
||||
* the equivalent of all languages being set
|
||||
**/
|
||||
const val AllLanguagesName = "universal"
|
||||
|
||||
//val baseHeader = mapOf("User-Agent" to USER_AGENT)
|
||||
val mapper = JsonMapper.builder().addModule(kotlinModule())
|
||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).build()!!
|
||||
|
||||
object APIHolder {
|
||||
val unixTime: Long
|
||||
get() = System.currentTimeMillis() / 1000L
|
||||
|
@ -121,7 +118,8 @@ object APIHolder {
|
|||
|
||||
fun LoadResponse.getId(): Int {
|
||||
// this fixes an issue with outdated api as getLoadResponseIdFromUrl might be fucked
|
||||
return (if (this is ResultViewModel2.LoadResponseFromSearch) this.id else null) ?: getLoadResponseIdFromUrl(url, apiName)
|
||||
return (if (this is ResultViewModel2.LoadResponseFromSearch) this.id else null)
|
||||
?: getLoadResponseIdFromUrl(url, apiName)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -225,7 +223,12 @@ object APIHolder {
|
|||
if (lessAccurate) matchingTitles || matchingTypes && matchingYears else matchingTitles && matchingTypes && matchingYears
|
||||
} ?: return null
|
||||
|
||||
Tracker(res.idMal, res.id.toString(), res.coverImage?.extraLarge ?: res.coverImage?.large, res.bannerImage)
|
||||
Tracker(
|
||||
res.idMal,
|
||||
res.id.toString(),
|
||||
res.coverImage?.extraLarge ?: res.coverImage?.large,
|
||||
res.bannerImage
|
||||
)
|
||||
} catch (t: Throwable) {
|
||||
logError(t)
|
||||
null
|
||||
|
@ -866,6 +869,7 @@ enum class TvType(value: Int?) {
|
|||
Others(12),
|
||||
Music(13),
|
||||
AudioBook(14),
|
||||
|
||||
/** Wont load the built in player, make your own interaction */
|
||||
CustomMedia(15),
|
||||
}
|
||||
|
@ -1253,13 +1257,15 @@ interface LoadResponse {
|
|||
|
||||
fun LoadResponse.getImdbId(): String? {
|
||||
return normalSafeApiCall {
|
||||
SimklApi.readIdFromString(this.syncData[simklIdPrefix])?.get(SimklApi.Companion.SyncServices.Imdb)
|
||||
SimklApi.readIdFromString(this.syncData[simklIdPrefix])
|
||||
?.get(SimklApi.Companion.SyncServices.Imdb)
|
||||
}
|
||||
}
|
||||
|
||||
fun LoadResponse.getTMDbId(): String? {
|
||||
return normalSafeApiCall {
|
||||
SimklApi.readIdFromString(this.syncData[simklIdPrefix])?.get(SimklApi.Companion.SyncServices.Tmdb)
|
||||
SimklApi.readIdFromString(this.syncData[simklIdPrefix])
|
||||
?.get(SimklApi.Companion.SyncServices.Tmdb)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1556,8 +1562,26 @@ data class TorrentLoadResponse(
|
|||
posterHeaders: Map<String, String>? = null,
|
||||
backgroundPosterUrl: String? = null,
|
||||
) : this(
|
||||
name, url, apiName, magnet, torrent, plot, type, posterUrl, year, rating, tags, duration, trailers,
|
||||
recommendations, actors, comingSoon, syncData, posterHeaders, backgroundPosterUrl, null
|
||||
name,
|
||||
url,
|
||||
apiName,
|
||||
magnet,
|
||||
torrent,
|
||||
plot,
|
||||
type,
|
||||
posterUrl,
|
||||
year,
|
||||
rating,
|
||||
tags,
|
||||
duration,
|
||||
trailers,
|
||||
recommendations,
|
||||
actors,
|
||||
comingSoon,
|
||||
syncData,
|
||||
posterHeaders,
|
||||
backgroundPosterUrl,
|
||||
null
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -1609,7 +1633,8 @@ data class AnimeLoadResponse(
|
|||
return this.episodes.maxOf { (_, episodes) ->
|
||||
episodes.count { episodeData ->
|
||||
// Prioritize display season as actual season may be something random to fit multiple seasons into one.
|
||||
val episodeSeason = displayMap[episodeData.season] ?: episodeData.season ?: Int.MIN_VALUE
|
||||
val episodeSeason =
|
||||
displayMap[episodeData.season] ?: episodeData.season ?: Int.MIN_VALUE
|
||||
// Count all episodes from season 1 to below the current season.
|
||||
episodeSeason in 1..<season
|
||||
}
|
||||
|
@ -1646,9 +1671,31 @@ data class AnimeLoadResponse(
|
|||
seasonNames: List<SeasonData>? = null,
|
||||
backgroundPosterUrl: String? = null,
|
||||
) : this(
|
||||
engName, japName, name, url, apiName, type, posterUrl, year, episodes, showStatus, plot, tags,
|
||||
synonyms, rating, duration, trailers, recommendations, actors, comingSoon, syncData, posterHeaders,
|
||||
nextAiring, seasonNames, backgroundPosterUrl, null
|
||||
engName,
|
||||
japName,
|
||||
name,
|
||||
url,
|
||||
apiName,
|
||||
type,
|
||||
posterUrl,
|
||||
year,
|
||||
episodes,
|
||||
showStatus,
|
||||
plot,
|
||||
tags,
|
||||
synonyms,
|
||||
rating,
|
||||
duration,
|
||||
trailers,
|
||||
recommendations,
|
||||
actors,
|
||||
comingSoon,
|
||||
syncData,
|
||||
posterHeaders,
|
||||
nextAiring,
|
||||
seasonNames,
|
||||
backgroundPosterUrl,
|
||||
null
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -1923,7 +1970,8 @@ data class TvSeriesLoadResponse(
|
|||
|
||||
return episodes.count { episodeData ->
|
||||
// Prioritize display season as actual season may be something random to fit multiple seasons into one.
|
||||
val episodeSeason = displayMap[episodeData.season] ?: episodeData.season ?: Int.MIN_VALUE
|
||||
val episodeSeason =
|
||||
displayMap[episodeData.season] ?: episodeData.season ?: Int.MIN_VALUE
|
||||
// Count all episodes from season 1 to below the current season.
|
||||
episodeSeason in 1..<season
|
||||
} + episode
|
||||
|
@ -1956,9 +2004,28 @@ data class TvSeriesLoadResponse(
|
|||
seasonNames: List<SeasonData>? = null,
|
||||
backgroundPosterUrl: String? = null,
|
||||
) : this(
|
||||
name, url, apiName, type, episodes, posterUrl, year, plot, showStatus, rating, tags, duration,
|
||||
trailers, recommendations, actors, comingSoon, syncData, posterHeaders, nextAiring, seasonNames,
|
||||
backgroundPosterUrl, null
|
||||
name,
|
||||
url,
|
||||
apiName,
|
||||
type,
|
||||
episodes,
|
||||
posterUrl,
|
||||
year,
|
||||
plot,
|
||||
showStatus,
|
||||
rating,
|
||||
tags,
|
||||
duration,
|
||||
trailers,
|
||||
recommendations,
|
||||
actors,
|
||||
comingSoon,
|
||||
syncData,
|
||||
posterHeaders,
|
||||
nextAiring,
|
||||
seasonNames,
|
||||
backgroundPosterUrl,
|
||||
null
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -2022,6 +2089,7 @@ data class AniSearch(
|
|||
@JsonProperty("extraLarge") var extraLarge: String? = null,
|
||||
@JsonProperty("large") var large: String? = null,
|
||||
)
|
||||
|
||||
data class Title(
|
||||
@JsonProperty("romaji") var romaji: String? = null,
|
||||
@JsonProperty("english") var english: String? = null,
|
||||
|
|
|
@ -174,7 +174,6 @@ import java.net.URLDecoder
|
|||
import java.nio.charset.Charset
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.absoluteValue
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
//https://github.com/videolan/vlc-android/blob/3706c4be2da6800b3d26344fc04fab03ffa4b860/application/vlc-android/src/org/videolan/vlc/gui/video/VideoPlayerActivity.kt#L1898
|
||||
|
@ -187,6 +186,8 @@ import kotlin.system.exitProcess
|
|||
|
||||
//https://github.com/jellyfin/jellyfin-android/blob/6cbf0edf84a3da82347c8d59b5d5590749da81a9/app/src/main/java/org/jellyfin/mobile/bridge/ExternalPlayer.kt#L225
|
||||
|
||||
class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricAuthCallback {
|
||||
companion object {
|
||||
const val VLC_PACKAGE = "org.videolan.vlc"
|
||||
const val MPV_PACKAGE = "is.xyz.mpv"
|
||||
const val WEB_VIDEO_CAST_PACKAGE = "com.instantbits.cast.webvideo"
|
||||
|
@ -254,11 +255,13 @@ val MPV = object : ResultResume(
|
|||
duration = "duration",
|
||||
) {
|
||||
override fun getPosition(intent: Intent?): Long {
|
||||
return intent?.getIntExtra(this.position, defaultTime.toInt())?.toLong() ?: defaultTime
|
||||
return intent?.getIntExtra(this.position, defaultTime.toInt())?.toLong()
|
||||
?: defaultTime
|
||||
}
|
||||
|
||||
override fun getDuration(intent: Intent?): Long {
|
||||
return intent?.getIntExtra(this.duration, defaultTime.toInt())?.toLong() ?: defaultTime
|
||||
return intent?.getIntExtra(this.duration, defaultTime.toInt())?.toLong()
|
||||
?: defaultTime
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -268,35 +271,7 @@ val resumeApps = arrayOf(
|
|||
VLC, MPV, WEB_VIDEO
|
||||
)
|
||||
|
||||
// Short name for requests client to make it nicer to use
|
||||
|
||||
var app = Requests(responseParser = object : ResponseParser {
|
||||
val mapper: ObjectMapper = jacksonObjectMapper().configure(
|
||||
DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,
|
||||
false
|
||||
)
|
||||
|
||||
override fun <T : Any> parse(text: String, kClass: KClass<T>): T {
|
||||
return mapper.readValue(text, kClass.java)
|
||||
}
|
||||
|
||||
override fun <T : Any> parseSafe(text: String, kClass: KClass<T>): T? {
|
||||
return try {
|
||||
mapper.readValue(text, kClass.java)
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override fun writeValueAsString(obj: Any): String {
|
||||
return mapper.writeValueAsString(obj)
|
||||
}
|
||||
}).apply {
|
||||
defaultHeaders = mapOf("user-agent" to USER_AGENT)
|
||||
}
|
||||
|
||||
class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCallback {
|
||||
companion object {
|
||||
const val TAG = "MAINACT"
|
||||
const val ANIMATED_OUTLINE: Boolean = false
|
||||
var lastError: String? = null
|
||||
|
|
|
@ -4,6 +4,7 @@ import android.net.Uri
|
|||
import com.lagradost.cloudstream3.*
|
||||
import com.fasterxml.jackson.annotation.JsonAlias
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.APIHolder.unixTimeMS
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addImdbId
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addTMDbId
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
|
||||
|
@ -166,6 +167,7 @@ open class TraktProvider : MainAPI() {
|
|||
val episodes = mutableListOf<Episode>()
|
||||
val seasons = parseJson<List<Seasons>>(resSeasons)
|
||||
val seasonsNames = mutableListOf<SeasonData>()
|
||||
var nextAir: NextAiring? = null
|
||||
|
||||
seasons.forEach { season ->
|
||||
|
||||
|
@ -215,6 +217,13 @@ open class TraktProvider : MainAPI() {
|
|||
description = episode.overview,
|
||||
).apply {
|
||||
this.addDate(episode.firstAired)
|
||||
if (nextAir == null && this.date != null && this.date!! > unixTimeMS) {
|
||||
nextAir = NextAiring(
|
||||
episode = this.episode!!,
|
||||
unixTime = this.date!!.div(1000L),
|
||||
season = if (this.season == 1) null else this.season,
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -240,6 +249,7 @@ open class TraktProvider : MainAPI() {
|
|||
this.actors = actors
|
||||
this.comingSoon = isUpcoming(mediaDetails.released)
|
||||
//posterHeaders
|
||||
this.nextAiring = nextAir
|
||||
this.seasonNames = seasonsNames
|
||||
this.backgroundPosterUrl = getOriginalWidthImageUrl(backDropUrl)
|
||||
this.contentRating = mediaDetails.certification
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package com.lagradost.cloudstream3.subtitles
|
||||
|
||||
import com.lagradost.cloudstream3.LoadResponse
|
||||
import com.lagradost.cloudstream3.TvType
|
||||
|
||||
class AbstractSubtitleEntities {
|
||||
|
@ -19,8 +20,11 @@ class AbstractSubtitleEntities {
|
|||
|
||||
data class SubtitleSearch(
|
||||
var query: String = "",
|
||||
var imdb: Long? = null,
|
||||
var lang: String? = null,
|
||||
var imdbId: String? = null,
|
||||
var tmdbId: Int? = null,
|
||||
var malId: Int? = null,
|
||||
var aniListId: Int? = null,
|
||||
var epNumber: Int? = null,
|
||||
var seasonNumber: Int? = null,
|
||||
var year: Int? = null
|
||||
|
|
|
@ -3,7 +3,6 @@ package com.lagradost.cloudstream3.syncproviders
|
|||
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKeys
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
||||
import com.lagradost.cloudstream3.syncproviders.providers.SubScene
|
||||
import com.lagradost.cloudstream3.syncproviders.providers.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
|
@ -15,7 +14,7 @@ abstract class AccountManager(private val defIndex: Int) : AuthAPI {
|
|||
val simklApi = SimklApi(0)
|
||||
val indexSubtitlesApi = IndexSubtitleApi()
|
||||
val addic7ed = Addic7ed()
|
||||
val subScene = SubScene()
|
||||
val subDl = SubDL()
|
||||
val localListApi = LocalList()
|
||||
|
||||
// used to login via app intent
|
||||
|
@ -44,7 +43,7 @@ abstract class AccountManager(private val defIndex: Int) : AuthAPI {
|
|||
openSubtitlesApi,
|
||||
indexSubtitlesApi, // they got anti scraping measures in place :(
|
||||
addic7ed,
|
||||
subScene
|
||||
subDl
|
||||
)
|
||||
|
||||
const val appString = "cloudstreamapp"
|
||||
|
|
|
@ -98,7 +98,7 @@ class IndexSubtitleApi : AbstractSubApi {
|
|||
}
|
||||
|
||||
override suspend fun search(query: AbstractSubtitleEntities.SubtitleSearch): List<AbstractSubtitleEntities.SubtitleEntity> {
|
||||
val imdbId = query.imdb ?: 0
|
||||
val imdbId = query.imdbId?.replace("tt", "")?.toLong() ?: 0
|
||||
val lang = query.lang
|
||||
val queryLang = SubtitleHelper.fromTwoLettersToLanguage(lang.toString())
|
||||
val queryText = query.query
|
||||
|
|
|
@ -185,7 +185,7 @@ class OpenSubtitlesApi(index: Int) : InAppAuthAPIManager(index), AbstractSubApi
|
|||
throwIfCantDoRequest()
|
||||
val fixedLang = fixLanguage(query.lang)
|
||||
|
||||
val imdbId = query.imdb ?: 0
|
||||
val imdbId = query.imdbId?.replace("tt", "")?.toInt() ?: 0
|
||||
val queryText = query.query
|
||||
val epNum = query.epNumber ?: 0
|
||||
val seasonNum = query.seasonNumber ?: 0
|
||||
|
|
|
@ -1,118 +0,0 @@
|
|||
package com.lagradost.cloudstream3.syncproviders.providers
|
||||
|
||||
import com.lagradost.cloudstream3.app
|
||||
import com.lagradost.cloudstream3.mvvm.debugPrint
|
||||
import com.lagradost.cloudstream3.subtitles.AbstractSubProvider
|
||||
import com.lagradost.cloudstream3.subtitles.AbstractSubtitleEntities
|
||||
import com.lagradost.cloudstream3.subtitles.SubtitleResource
|
||||
import com.lagradost.cloudstream3.syncproviders.providers.IndexSubtitleApi.Companion.getOrdinal
|
||||
import com.lagradost.cloudstream3.utils.SubtitleHelper
|
||||
|
||||
class SubScene : AbstractSubProvider {
|
||||
val mainUrl = "https://subscene.com"
|
||||
val name = "Subscene"
|
||||
override val idPrefix = "subscene"
|
||||
|
||||
override suspend fun search(query: AbstractSubtitleEntities.SubtitleSearch): List<AbstractSubtitleEntities.SubtitleEntity>? {
|
||||
val seasonName =
|
||||
query.seasonNumber?.let { number ->
|
||||
// Need to translate "7" to "Seventh Season"
|
||||
getOrdinal(number)?.let { words -> " - $words Season" }
|
||||
} ?: ""
|
||||
|
||||
val fullQuery = query.query + seasonName
|
||||
|
||||
val doc = app.post(
|
||||
"$mainUrl/subtitles/searchbytitle",
|
||||
data = mapOf("query" to fullQuery, "l" to "")
|
||||
).document
|
||||
|
||||
return doc.select("div.title a").map { element ->
|
||||
val href = "$mainUrl${element.attr("href")}"
|
||||
val title = element.text()
|
||||
|
||||
AbstractSubtitleEntities.SubtitleEntity(
|
||||
idPrefix = idPrefix,
|
||||
name = title,
|
||||
source = name,
|
||||
data = href,
|
||||
lang = query.lang ?: "en",
|
||||
epNumber = query.epNumber
|
||||
)
|
||||
}.distinctBy { it.data }
|
||||
}
|
||||
|
||||
override suspend fun SubtitleResource.getResources(data: AbstractSubtitleEntities.SubtitleEntity) {
|
||||
val resultDoc = app.get(data.data).document
|
||||
val queryLanguage = SubtitleHelper.fromTwoLettersToLanguage(data.lang) ?: "English"
|
||||
|
||||
val results = resultDoc.select("table tbody tr").mapNotNull { element ->
|
||||
val anchor = element.select("a")
|
||||
val href = anchor.attr("href") ?: return@mapNotNull null
|
||||
val fixedHref = "$mainUrl${href}"
|
||||
val spans = anchor.select("span")
|
||||
val language = spans.firstOrNull()?.text()
|
||||
val title = spans.getOrNull(1)?.text()
|
||||
val isPositive = anchor.select("span.positive-icon").isNotEmpty()
|
||||
|
||||
TableElement(title, language, fixedHref, isPositive)
|
||||
}.sortedBy {
|
||||
it.getScore(queryLanguage, data.epNumber)
|
||||
}
|
||||
|
||||
debugPrint { "$name found subtitles: ${results.takeLast(3)}" }
|
||||
// Last = highest score
|
||||
val selectedResult = results.lastOrNull() ?: return
|
||||
|
||||
val subtitleDocument = app.get(selectedResult.href).document
|
||||
val subtitleDownloadUrl =
|
||||
"$mainUrl${subtitleDocument.select("div.download a").attr("href")}"
|
||||
|
||||
this.addZipUrl(subtitleDownloadUrl) { name, _ ->
|
||||
name
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class to manage the various different subtitle results and rank them.
|
||||
*/
|
||||
data class TableElement(
|
||||
val title: String?,
|
||||
val language: String?,
|
||||
val href: String,
|
||||
val isPositive: Boolean
|
||||
) {
|
||||
private fun matchesLanguage(other: String): Boolean {
|
||||
return language != null && (language.contains(other, ignoreCase = true) ||
|
||||
other.contains(language, ignoreCase = true))
|
||||
}
|
||||
|
||||
/**
|
||||
* Scores in this order:
|
||||
* Preferred Language > Episode number > Positive rating > English Language
|
||||
*/
|
||||
fun getScore(queryLanguage: String, episodeNum: Int?): Int {
|
||||
var score = 0
|
||||
if (this.matchesLanguage(queryLanguage)) {
|
||||
score += 8
|
||||
}
|
||||
// Matches Episode 7 using "E07" with any number of leading zeroes
|
||||
if (episodeNum != null && title != null && title.contains(
|
||||
Regex(
|
||||
"""E0*${episodeNum}""",
|
||||
RegexOption.IGNORE_CASE
|
||||
)
|
||||
)
|
||||
) {
|
||||
score += 4
|
||||
}
|
||||
if (isPositive) {
|
||||
score += 2
|
||||
}
|
||||
if (this.matchesLanguage("English")) {
|
||||
score += 1
|
||||
}
|
||||
return score
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
package com.lagradost.cloudstream3.syncproviders.providers
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.TvType
|
||||
import com.lagradost.cloudstream3.app
|
||||
import com.lagradost.cloudstream3.subtitles.AbstractSubProvider
|
||||
import com.lagradost.cloudstream3.subtitles.AbstractSubtitleEntities
|
||||
import com.lagradost.cloudstream3.subtitles.SubtitleResource
|
||||
|
||||
class SubDL : AbstractSubProvider {
|
||||
//API Documentation: https://subdl.com/api-doc
|
||||
val mainUrl = "https://subdl.com/"
|
||||
val name = "SubDL"
|
||||
override val idPrefix = "subdl"
|
||||
companion object {
|
||||
const val APIKEY = "zRJl5QA-8jNA2i0pE8cxANbEukANp7IM"
|
||||
const val APIENDPOINT = "https://api.subdl.com/api/v1/subtitles"
|
||||
const val DOWNLOADENDPOINT = "https://dl.subdl.com"
|
||||
}
|
||||
|
||||
override suspend fun search(query: AbstractSubtitleEntities.SubtitleSearch): List<AbstractSubtitleEntities.SubtitleEntity>? {
|
||||
|
||||
val queryText = query.query
|
||||
val epNum = query.epNumber ?: 0
|
||||
val seasonNum = query.seasonNumber ?: 0
|
||||
val yearNum = query.year ?: 0
|
||||
|
||||
val idQuery = when {
|
||||
query.imdbId != null -> "&imdb_id=${query.imdbId}"
|
||||
query.tmdbId != null -> "&tmdb_id=${query.tmdbId}"
|
||||
else -> null
|
||||
}
|
||||
|
||||
val epQuery = if (epNum > 0) "&episode_number=$epNum" else ""
|
||||
val seasonQuery = if (seasonNum > 0) "&season_number=$seasonNum" else ""
|
||||
val yearQuery = if (yearNum > 0) "&year=$yearNum" else ""
|
||||
|
||||
val searchQueryUrl = when (idQuery) {
|
||||
//Use imdb/tmdb id to search if its valid
|
||||
null -> "$APIENDPOINT?api_key=$APIKEY&film_name=$queryText&languages=${query.lang}$epQuery$seasonQuery$yearQuery"
|
||||
else -> "$APIENDPOINT?api_key=$APIKEY$idQuery&languages=${query.lang}$epQuery$seasonQuery$yearQuery"
|
||||
}
|
||||
|
||||
val req = app.get(
|
||||
url = searchQueryUrl,
|
||||
headers = mapOf(
|
||||
"Accept" to "application/json"
|
||||
)
|
||||
)
|
||||
|
||||
return req.parsedSafe<ApiResponse>()?.subtitles?.map { subtitle ->
|
||||
val name = subtitle.releaseName
|
||||
val lang = subtitle.lang.replaceFirstChar { it.uppercase() }
|
||||
val resEpNum = subtitle.episode ?: query.epNumber
|
||||
val resSeasonNum = subtitle.season ?: query.seasonNumber
|
||||
val type = if ((resSeasonNum ?: 0) > 0) TvType.TvSeries else TvType.Movie
|
||||
|
||||
AbstractSubtitleEntities.SubtitleEntity(
|
||||
idPrefix = this.idPrefix,
|
||||
name = name,
|
||||
lang = lang,
|
||||
data = "${DOWNLOADENDPOINT}${subtitle.url}",
|
||||
type = type,
|
||||
source = this.name,
|
||||
epNumber = resEpNum,
|
||||
seasonNumber = resSeasonNum,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun SubtitleResource.getResources(data: AbstractSubtitleEntities.SubtitleEntity) {
|
||||
this.addZipUrl(data.data) { name, _ ->
|
||||
name
|
||||
}
|
||||
}
|
||||
|
||||
data class ApiResponse(
|
||||
@JsonProperty("status") val status: Boolean? = null,
|
||||
@JsonProperty("results") val results: List<Result>? = null,
|
||||
@JsonProperty("subtitles") val subtitles: List<Subtitle>? = null,
|
||||
)
|
||||
|
||||
data class Result(
|
||||
@JsonProperty("sd_id") val sdId: Int? = null,
|
||||
@JsonProperty("type") val type: String? = null,
|
||||
@JsonProperty("name") val name: String? = null,
|
||||
@JsonProperty("imdb_id") val imdbId: String? = null,
|
||||
@JsonProperty("tmdb_id") val tmdbId: Long? = null,
|
||||
@JsonProperty("first_air_date") val firstAirDate: String? = null,
|
||||
@JsonProperty("year") val year: Int? = null,
|
||||
)
|
||||
|
||||
data class Subtitle(
|
||||
@JsonProperty("release_name") val releaseName: String,
|
||||
@JsonProperty("name") val name: String,
|
||||
@JsonProperty("lang") val lang: String,
|
||||
@JsonProperty("author") val author: String? = null,
|
||||
@JsonProperty("url") val url: String? = null,
|
||||
@JsonProperty("season") val season: Int? = null,
|
||||
@JsonProperty("episode") val episode: Int? = null,
|
||||
)
|
||||
}
|
|
@ -32,6 +32,7 @@ import com.lagradost.cloudstream3.CommonActivity.keyEventListener
|
|||
import com.lagradost.cloudstream3.CommonActivity.playerEventListener
|
||||
import com.lagradost.cloudstream3.CommonActivity.screenHeight
|
||||
import com.lagradost.cloudstream3.CommonActivity.screenWidth
|
||||
import com.lagradost.cloudstream3.LoadResponse
|
||||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.databinding.PlayerCustomLayoutBinding
|
||||
import com.lagradost.cloudstream3.databinding.SubtitleOffsetBinding
|
||||
|
@ -177,7 +178,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
|
|||
|
||||
open fun openOnlineSubPicker(
|
||||
context: Context,
|
||||
imdbId: Long?,
|
||||
loadResponse: LoadResponse?,
|
||||
dismissCallback: (() -> Unit)
|
||||
) {
|
||||
throw NotImplementedError()
|
||||
|
|
|
@ -25,6 +25,10 @@ import com.lagradost.cloudstream3.*
|
|||
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
||||
import com.lagradost.cloudstream3.CommonActivity.showToast
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.getAniListId
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.getImdbId
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.getMalId
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.getTMDbId
|
||||
import com.lagradost.cloudstream3.databinding.DialogOnlineSubtitlesBinding
|
||||
import com.lagradost.cloudstream3.databinding.FragmentPlayerBinding
|
||||
import com.lagradost.cloudstream3.databinding.PlayerSelectSourceAndSubsBinding
|
||||
|
@ -39,7 +43,6 @@ import com.lagradost.cloudstream3.ui.player.PlayerSubtitleHelper.Companion.toSub
|
|||
import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper
|
||||
import com.lagradost.cloudstream3.ui.player.source_priority.QualityProfileDialog
|
||||
import com.lagradost.cloudstream3.ui.result.*
|
||||
import com.lagradost.cloudstream3.ui.settings.Globals
|
||||
import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
|
||||
import com.lagradost.cloudstream3.ui.settings.Globals.TV
|
||||
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
|
||||
|
@ -258,6 +261,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
var episode: Int? = null,
|
||||
var season: Int? = null,
|
||||
var name: String? = null,
|
||||
var imdbId: String? = null,
|
||||
)
|
||||
|
||||
private fun getMetaData(): TempMetaData {
|
||||
|
@ -284,7 +288,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
}
|
||||
|
||||
override fun openOnlineSubPicker(
|
||||
context: Context, imdbId: Long?, dismissCallback: (() -> Unit)
|
||||
context: Context, loadResponse: LoadResponse?, dismissCallback: (() -> Unit)
|
||||
) {
|
||||
val providers = subsProviders
|
||||
val isSingleProvider = subsProviders.size == 1
|
||||
|
@ -377,6 +381,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
}
|
||||
|
||||
val currentTempMeta = getMetaData()
|
||||
|
||||
// bruh idk why it is not correct
|
||||
val color = ColorStateList.valueOf(context.colorFromAttribute(R.attr.colorAccent))
|
||||
binding.searchLoadingBar.progressTintList = color
|
||||
|
@ -424,7 +429,10 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
val search =
|
||||
AbstractSubtitleEntities.SubtitleSearch(
|
||||
query = query ?: return@ioSafe,
|
||||
imdb = imdbId,
|
||||
imdbId = loadResponse?.getImdbId(),
|
||||
tmdbId = loadResponse?.getTMDbId()?.toInt(),
|
||||
malId = loadResponse?.getMalId()?.toInt(),
|
||||
aniListId = loadResponse?.getAniListId()?.toInt(),
|
||||
epNumber = currentTempMeta.episode,
|
||||
seasonNumber = currentTempMeta.season,
|
||||
lang = currentLanguageTwoLetters.ifBlank { null },
|
||||
|
@ -633,6 +641,8 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
}
|
||||
|
||||
if (subsProvidersIsActive) {
|
||||
val currentLoadResponse = viewModel.getLoadResponse()
|
||||
|
||||
val loadFromOpenSubsFooter: TextView = layoutInflater.inflate(
|
||||
R.layout.sort_bottom_footer_add_choice, null
|
||||
) as TextView
|
||||
|
@ -643,7 +653,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
loadFromOpenSubsFooter.setOnClickListener {
|
||||
shouldDismiss = false
|
||||
sourceDialog.dismissSafe(activity)
|
||||
openOnlineSubPicker(it.context, null) {
|
||||
openOnlineSubPicker(it.context, currentLoadResponse) {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import androidx.lifecycle.LiveData
|
|||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.lagradost.cloudstream3.LoadResponse
|
||||
import com.lagradost.cloudstream3.mvvm.Resource
|
||||
import com.lagradost.cloudstream3.mvvm.launchSafe
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
|
@ -111,6 +112,9 @@ class PlayerGeneratorViewModel : ViewModel() {
|
|||
}
|
||||
}
|
||||
}
|
||||
fun getLoadResponse(): LoadResponse? {
|
||||
return normalSafeApiCall { (generator as? RepoLinkGenerator?)?.page }
|
||||
}
|
||||
|
||||
fun getMeta(): Any? {
|
||||
return normalSafeApiCall { generator?.getCurrent() }
|
||||
|
|
|
@ -12,6 +12,7 @@ import androidx.core.view.isGone
|
|||
import androidx.core.view.isVisible
|
||||
import com.lagradost.cloudstream3.CommonActivity.screenHeight
|
||||
import com.lagradost.cloudstream3.CommonActivity.screenWidth
|
||||
import com.lagradost.cloudstream3.LoadResponse
|
||||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.ui.player.CSPlayerEvent
|
||||
import com.lagradost.cloudstream3.ui.player.PlayerEventSource
|
||||
|
@ -110,7 +111,7 @@ open class ResultTrailerPlayer : ResultFragmentPhone() {
|
|||
|
||||
override fun openOnlineSubPicker(
|
||||
context: Context,
|
||||
imdbId: Long?,
|
||||
loadResponse: LoadResponse?,
|
||||
dismissCallback: () -> Unit
|
||||
) {
|
||||
}
|
||||
|
|
|
@ -27,8 +27,17 @@ import com.lagradost.cloudstream3.CommonActivity.getCastSession
|
|||
import com.lagradost.cloudstream3.CommonActivity.showToast
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.getAniListId
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.getImdbId
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.getMalId
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.isMovie
|
||||
import com.lagradost.cloudstream3.MainActivity.Companion.MPV
|
||||
import com.lagradost.cloudstream3.MainActivity.Companion.MPV_COMPONENT
|
||||
import com.lagradost.cloudstream3.MainActivity.Companion.MPV_PACKAGE
|
||||
import com.lagradost.cloudstream3.MainActivity.Companion.VLC
|
||||
import com.lagradost.cloudstream3.MainActivity.Companion.VLC_COMPONENT
|
||||
import com.lagradost.cloudstream3.MainActivity.Companion.VLC_PACKAGE
|
||||
import com.lagradost.cloudstream3.MainActivity.Companion.WEB_VIDEO
|
||||
import com.lagradost.cloudstream3.MainActivity.Companion.WEB_VIDEO_CAST_PACKAGE
|
||||
import com.lagradost.cloudstream3.metaproviders.SyncRedirector
|
||||
import com.lagradost.cloudstream3.mvvm.*
|
||||
import com.lagradost.cloudstream3.syncproviders.AccountManager
|
||||
|
@ -1354,7 +1363,7 @@ class ResultViewModel2 : ViewModel() {
|
|||
|
||||
private fun launchActivity(
|
||||
activity: Activity?,
|
||||
resumeApp: ResultResume,
|
||||
resumeApp: MainActivity.Companion.ResultResume,
|
||||
id: Int? = null,
|
||||
work: suspend (Intent.(Activity) -> Unit)
|
||||
): Job? {
|
||||
|
@ -2409,7 +2418,7 @@ class ResultViewModel2 : ViewModel() {
|
|||
null,
|
||||
loadResponse.type,
|
||||
mainId,
|
||||
null
|
||||
null,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="30dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginEnd="30dp">
|
||||
android:layout_marginEnd="40dp">
|
||||
|
||||
<androidx.appcompat.widget.SearchView
|
||||
android:id="@+id/subtitles_search"
|
||||
|
@ -91,7 +91,8 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center_vertical|end"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:nextFocusLeft="@id/subtitles_search"
|
||||
android:nextFocusRight="@id/search_filter"
|
||||
android:text="@string/none"
|
||||
/>
|
||||
</LinearLayout>
|
||||
|
@ -106,7 +107,7 @@
|
|||
android:layout_margin="10dp"
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/change_providers_img_des"
|
||||
android:nextFocusLeft="@id/main_search"
|
||||
android:nextFocusLeft="@id/year_btt"
|
||||
android:nextFocusRight="@id/main_search"
|
||||
android:nextFocusUp="@id/nav_rail_view"
|
||||
android:nextFocusDown="@id/search_autofit_results"
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
package com.lagradost.cloudstream3.utils
|
||||
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
|
||||
actual fun runOnMainThreadNative(work: () -> Unit) {
|
||||
val mainHandler = Handler(Looper.getMainLooper())
|
||||
mainHandler.post {
|
||||
work()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package com.lagradost.cloudstream3
|
||||
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
import com.lagradost.nicehttp.Requests
|
||||
import com.lagradost.nicehttp.ResponseParser
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
// Short name for requests client to make it nicer to use
|
||||
|
||||
var app = Requests(responseParser = object : ResponseParser {
|
||||
val mapper: ObjectMapper = jacksonObjectMapper().configure(
|
||||
DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,
|
||||
false
|
||||
)
|
||||
|
||||
override fun <T : Any> parse(text: String, kClass: KClass<T>): T {
|
||||
return mapper.readValue(text, kClass.java)
|
||||
}
|
||||
|
||||
override fun <T : Any> parseSafe(text: String, kClass: KClass<T>): T? {
|
||||
return try {
|
||||
mapper.readValue(text, kClass.java)
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override fun writeValueAsString(obj: Any): String {
|
||||
return mapper.writeValueAsString(obj)
|
||||
}
|
||||
}).apply {
|
||||
defaultHeaders = mapOf("user-agent" to USER_AGENT)
|
||||
}
|
|
@ -1,3 +1,6 @@
|
|||
package com.lagradost.cloudstream3
|
||||
|
||||
const val USER_AGENT =
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36"
|
||||
|
||||
class ErrorLoadingException(message: String? = null) : Exception(message)
|
|
@ -1,12 +1,11 @@
|
|||
package com.lagradost.cloudstream3.utils
|
||||
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import com.lagradost.cloudstream3.mvvm.launchSafe
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import kotlinx.coroutines.*
|
||||
import java.util.Collections.synchronizedList
|
||||
|
||||
expect fun runOnMainThreadNative(work: (() -> Unit))
|
||||
object Coroutines {
|
||||
fun <T> T.main(work: suspend ((T) -> Unit)): Job {
|
||||
val value = this
|
||||
|
@ -50,10 +49,7 @@ object Coroutines {
|
|||
}
|
||||
|
||||
fun runOnMainThread(work: (() -> Unit)) {
|
||||
val mainHandler = Handler(Looper.getMainLooper())
|
||||
mainHandler.post {
|
||||
work()
|
||||
}
|
||||
runOnMainThreadNative(work)
|
||||
}
|
||||
|
||||
/**
|
|
@ -0,0 +1,5 @@
|
|||
package com.lagradost.cloudstream3.utils
|
||||
|
||||
actual fun runOnMainThreadNative(work: () -> Unit) {
|
||||
work.invoke()
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue