forked from recloudstream/cloudstream
Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
b619ee9bf8
81 changed files with 1299 additions and 466 deletions
|
@ -36,7 +36,7 @@ android {
|
||||||
targetSdkVersion 30
|
targetSdkVersion 30
|
||||||
|
|
||||||
versionCode 48
|
versionCode 48
|
||||||
versionName "2.10.28"
|
versionName "2.10.29"
|
||||||
|
|
||||||
resValue "string", "app_version",
|
resValue "string", "app_version",
|
||||||
"${defaultConfig.versionName}${versionNameSuffix ?: ""}"
|
"${defaultConfig.versionName}${versionNameSuffix ?: ""}"
|
||||||
|
@ -74,6 +74,8 @@ android {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
compileOptions {
|
compileOptions {
|
||||||
|
coreLibraryDesugaringEnabled true
|
||||||
|
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
targetCompatibility JavaVersion.VERSION_1_8
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
}
|
}
|
||||||
|
@ -143,7 +145,7 @@ dependencies {
|
||||||
implementation 'com.jaredrummler:colorpicker:1.1.0'
|
implementation 'com.jaredrummler:colorpicker:1.1.0'
|
||||||
|
|
||||||
//run JS
|
//run JS
|
||||||
implementation 'org.mozilla:rhino:1.7R4'
|
implementation 'org.mozilla:rhino:1.7.14'
|
||||||
|
|
||||||
// TorrentStream
|
// TorrentStream
|
||||||
//implementation 'com.github.TorrentStream:TorrentStream-Android:2.7.0'
|
//implementation 'com.github.TorrentStream:TorrentStream-Android:2.7.0'
|
||||||
|
@ -175,6 +177,11 @@ dependencies {
|
||||||
// used for subtitle decoding https://github.com/albfernandez/juniversalchardet
|
// used for subtitle decoding https://github.com/albfernandez/juniversalchardet
|
||||||
implementation 'com.github.albfernandez:juniversalchardet:2.4.0'
|
implementation 'com.github.albfernandez:juniversalchardet:2.4.0'
|
||||||
|
|
||||||
// play yt
|
// slow af yt
|
||||||
implementation 'com.github.HaarigerHarald:android-youtubeExtractor:master-SNAPSHOT'
|
//implementation 'com.github.HaarigerHarald:android-youtubeExtractor:master-SNAPSHOT'
|
||||||
|
|
||||||
|
// newpipe yt
|
||||||
|
implementation 'com.github.TeamNewPipe:NewPipeExtractor:master-SNAPSHOT'
|
||||||
|
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
||||||
|
|
||||||
}
|
}
|
|
@ -155,11 +155,11 @@
|
||||||
<service
|
<service
|
||||||
android:name=".services.VideoDownloadService"
|
android:name=".services.VideoDownloadService"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
android:exported="false"></service>
|
android:exported="false" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:name=".ui.ControllerActivity"></activity>
|
android:name=".ui.ControllerActivity" />
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name="androidx.core.content.FileProvider"
|
android:name="androidx.core.content.FileProvider"
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
package com.lagradost.cloudstream3
|
||||||
|
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.RequestBody
|
||||||
|
import org.schabi.newpipe.extractor.downloader.Downloader
|
||||||
|
import org.schabi.newpipe.extractor.downloader.Request
|
||||||
|
import org.schabi.newpipe.extractor.downloader.Response
|
||||||
|
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
|
||||||
|
class DownloaderTestImpl private constructor(builder: OkHttpClient.Builder) : Downloader() {
|
||||||
|
private val client: OkHttpClient
|
||||||
|
override fun execute(request: Request): Response {
|
||||||
|
val httpMethod: String = request.httpMethod()
|
||||||
|
val url: String = request.url()
|
||||||
|
val headers: Map<String, List<String>> = request.headers()
|
||||||
|
val dataToSend: ByteArray? = request.dataToSend()
|
||||||
|
var requestBody: RequestBody? = null
|
||||||
|
if (dataToSend != null) {
|
||||||
|
requestBody = RequestBody.create(null, dataToSend)
|
||||||
|
}
|
||||||
|
val requestBuilder: okhttp3.Request.Builder = okhttp3.Request.Builder()
|
||||||
|
.method(httpMethod, requestBody).url(url)
|
||||||
|
.addHeader("User-Agent", USER_AGENT)
|
||||||
|
|
||||||
|
for ((headerName, headerValueList) in headers) {
|
||||||
|
if (headerValueList.size > 1) {
|
||||||
|
requestBuilder.removeHeader(headerName)
|
||||||
|
for (headerValue in headerValueList) {
|
||||||
|
requestBuilder.addHeader(headerName, headerValue)
|
||||||
|
}
|
||||||
|
} else if (headerValueList.size == 1) {
|
||||||
|
requestBuilder.header(headerName, headerValueList[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val response = client.newCall(requestBuilder.build()).execute()
|
||||||
|
if (response.code == 429) {
|
||||||
|
response.close()
|
||||||
|
throw ReCaptchaException("reCaptcha Challenge requested", url)
|
||||||
|
}
|
||||||
|
val body = response.body
|
||||||
|
var responseBodyToReturn: String? = null
|
||||||
|
if (body != null) {
|
||||||
|
responseBodyToReturn = body.string()
|
||||||
|
}
|
||||||
|
val latestUrl = response.request.url.toString()
|
||||||
|
return Response(
|
||||||
|
response.code, response.message, response.headers.toMultimap(),
|
||||||
|
responseBodyToReturn, latestUrl
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val USER_AGENT =
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; rv:91.0) Gecko/20100101 Firefox/91.0"
|
||||||
|
private var instance: DownloaderTestImpl? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* It's recommended to call exactly once in the entire lifetime of the application.
|
||||||
|
*
|
||||||
|
* @param builder if null, default builder will be used
|
||||||
|
* @return a new instance of [DownloaderTestImpl]
|
||||||
|
*/
|
||||||
|
fun init(builder: OkHttpClient.Builder?): DownloaderTestImpl? {
|
||||||
|
instance = DownloaderTestImpl(
|
||||||
|
builder ?: OkHttpClient.Builder()
|
||||||
|
)
|
||||||
|
return instance
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getInstance(): DownloaderTestImpl? {
|
||||||
|
if (instance == null) {
|
||||||
|
init(null)
|
||||||
|
}
|
||||||
|
return instance
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
client = builder.readTimeout(30, TimeUnit.SECONDS).build()
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,6 +10,7 @@ import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
import com.fasterxml.jackson.databind.DeserializationFeature
|
import com.fasterxml.jackson.databind.DeserializationFeature
|
||||||
import com.fasterxml.jackson.databind.json.JsonMapper
|
import com.fasterxml.jackson.databind.json.JsonMapper
|
||||||
import com.fasterxml.jackson.module.kotlin.KotlinModule
|
import com.fasterxml.jackson.module.kotlin.KotlinModule
|
||||||
|
import com.lagradost.cloudstream3.LoadResponse.Companion.addMalId
|
||||||
import com.lagradost.cloudstream3.animeproviders.*
|
import com.lagradost.cloudstream3.animeproviders.*
|
||||||
import com.lagradost.cloudstream3.metaproviders.CrossTmdbProvider
|
import com.lagradost.cloudstream3.metaproviders.CrossTmdbProvider
|
||||||
import com.lagradost.cloudstream3.movieproviders.*
|
import com.lagradost.cloudstream3.movieproviders.*
|
||||||
|
@ -17,8 +18,10 @@ import com.lagradost.cloudstream3.mvvm.logError
|
||||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.aniListApi
|
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.aniListApi
|
||||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.malApi
|
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.malApi
|
||||||
import com.lagradost.cloudstream3.ui.player.SubtitleData
|
import com.lagradost.cloudstream3.ui.player.SubtitleData
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.toJson
|
import com.lagradost.cloudstream3.utils.AppUtils.toJson
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
|
import com.lagradost.cloudstream3.utils.loadExtractor
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
@ -39,7 +42,7 @@ object APIHolder {
|
||||||
|
|
||||||
private const val defProvider = 0
|
private const val defProvider = 0
|
||||||
|
|
||||||
val allProviders by lazy {
|
val allProviders =
|
||||||
arrayListOf(
|
arrayListOf(
|
||||||
// Movie providers
|
// Movie providers
|
||||||
ElifilmsProvider(),
|
ElifilmsProvider(),
|
||||||
|
@ -132,7 +135,7 @@ object APIHolder {
|
||||||
NginxProvider(),
|
NginxProvider(),
|
||||||
OlgplyProvider(),
|
OlgplyProvider(),
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
fun initAll() {
|
fun initAll() {
|
||||||
for (api in allProviders) {
|
for (api in allProviders) {
|
||||||
|
@ -142,7 +145,7 @@ object APIHolder {
|
||||||
}
|
}
|
||||||
|
|
||||||
var apis: List<MainAPI> = arrayListOf()
|
var apis: List<MainAPI> = arrayListOf()
|
||||||
private var apiMap: Map<String, Int>? = null
|
var apiMap: Map<String, Int>? = null
|
||||||
|
|
||||||
private fun initMap() {
|
private fun initMap() {
|
||||||
if (apiMap == null)
|
if (apiMap == null)
|
||||||
|
@ -297,6 +300,16 @@ object APIHolder {
|
||||||
return realSet
|
return realSet
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Context.updateHasTrailers() {
|
||||||
|
LoadResponse.isTrailersEnabled = getHasTrailers()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Context.getHasTrailers(): Boolean {
|
||||||
|
if (this.isTvSettings()) return false
|
||||||
|
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
|
return settingsManager.getBoolean(this.getString(R.string.show_trailers_key), true)
|
||||||
|
}
|
||||||
|
|
||||||
fun Context.filterProviderByPreferredMedia(hasHomePageIsRequired: Boolean = true): List<MainAPI> {
|
fun Context.filterProviderByPreferredMedia(hasHomePageIsRequired: Boolean = true): List<MainAPI> {
|
||||||
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
|
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
val currentPrefMedia =
|
val currentPrefMedia =
|
||||||
|
@ -357,6 +370,7 @@ abstract class MainAPI {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun overrideWithNewData(data: ProvidersInfoJson) {
|
fun overrideWithNewData(data: ProvidersInfoJson) {
|
||||||
|
if (!canBeOverridden) return
|
||||||
this.name = data.name
|
this.name = data.name
|
||||||
if (data.url.isNotBlank() && data.url != "NONE")
|
if (data.url.isNotBlank() && data.url != "NONE")
|
||||||
this.mainUrl = data.url
|
this.mainUrl = data.url
|
||||||
|
@ -366,10 +380,11 @@ abstract class MainAPI {
|
||||||
open var name = "NONE"
|
open var name = "NONE"
|
||||||
open var mainUrl = "NONE"
|
open var mainUrl = "NONE"
|
||||||
open var storedCredentials: String? = null
|
open var storedCredentials: String? = null
|
||||||
|
open var canBeOverridden: Boolean = true
|
||||||
|
|
||||||
//open val uniqueId : Int by lazy { this.name.hashCode() } // in case of duplicate providers you can have a shared id
|
//open val uniqueId : Int by lazy { this.name.hashCode() } // in case of duplicate providers you can have a shared id
|
||||||
|
|
||||||
open val lang = "en" // ISO_639_1 check SubtitleHelper
|
open var lang = "en" // ISO_639_1 check SubtitleHelper
|
||||||
|
|
||||||
/**If link is stored in the "data" string, so links can be instantly loaded*/
|
/**If link is stored in the "data" string, so links can be instantly loaded*/
|
||||||
open val instantLinkLoading = false
|
open val instantLinkLoading = false
|
||||||
|
@ -852,7 +867,7 @@ interface LoadResponse {
|
||||||
var rating: Int? // 0-10000
|
var rating: Int? // 0-10000
|
||||||
var tags: List<String>?
|
var tags: List<String>?
|
||||||
var duration: Int? // in minutes
|
var duration: Int? // in minutes
|
||||||
var trailers: List<String>?
|
var trailers: List<ExtractorLink>?
|
||||||
var recommendations: List<SearchResponse>?
|
var recommendations: List<SearchResponse>?
|
||||||
var actors: List<ActorData>?
|
var actors: List<ActorData>?
|
||||||
var comingSoon: Boolean
|
var comingSoon: Boolean
|
||||||
|
@ -862,6 +877,7 @@ interface LoadResponse {
|
||||||
companion object {
|
companion object {
|
||||||
private val malIdPrefix = malApi.idPrefix
|
private val malIdPrefix = malApi.idPrefix
|
||||||
private val aniListIdPrefix = aniListApi.idPrefix
|
private val aniListIdPrefix = aniListApi.idPrefix
|
||||||
|
var isTrailersEnabled = true
|
||||||
|
|
||||||
@JvmName("addActorNames")
|
@JvmName("addActorNames")
|
||||||
fun LoadResponse.addActors(actors: List<String>?) {
|
fun LoadResponse.addActors(actors: List<String>?) {
|
||||||
|
@ -883,6 +899,14 @@ interface LoadResponse {
|
||||||
this.actors = actors?.map { actor -> ActorData(actor) }
|
this.actors = actors?.map { actor -> ActorData(actor) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun LoadResponse.getMalId() : String? {
|
||||||
|
return this.syncData[malIdPrefix]
|
||||||
|
}
|
||||||
|
|
||||||
|
fun LoadResponse.getAniListId() : String? {
|
||||||
|
return this.syncData[aniListIdPrefix]
|
||||||
|
}
|
||||||
|
|
||||||
fun LoadResponse.addMalId(id: Int?) {
|
fun LoadResponse.addMalId(id: Int?) {
|
||||||
this.syncData[malIdPrefix] = (id ?: return).toString()
|
this.syncData[malIdPrefix] = (id ?: return).toString()
|
||||||
}
|
}
|
||||||
|
@ -895,27 +919,38 @@ interface LoadResponse {
|
||||||
addImdbId(imdbUrlToIdNullable(url))
|
addImdbId(imdbUrlToIdNullable(url))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**better to set trailers directly instead of calling this multiple times*/
|
/**better to call addTrailer with mutible trailers directly instead of calling this multiple times*/
|
||||||
fun LoadResponse.addTrailer(trailerUrl: String?) {
|
suspend fun LoadResponse.addTrailer(trailerUrl: String?, referer: String? = null) {
|
||||||
if (trailerUrl == null) return
|
if (!isTrailersEnabled || trailerUrl == null) return
|
||||||
|
try {
|
||||||
|
val newTrailers = loadExtractor(trailerUrl, referer)
|
||||||
|
addTrailer(newTrailers)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logError(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun LoadResponse.addTrailer(newTrailers: List<ExtractorLink>) {
|
||||||
if (this.trailers == null) {
|
if (this.trailers == null) {
|
||||||
this.trailers = listOf(trailerUrl)
|
this.trailers = newTrailers
|
||||||
} else {
|
} else {
|
||||||
val update = this.trailers?.toMutableList()
|
val update = this.trailers?.toMutableList() ?: mutableListOf()
|
||||||
update?.add(trailerUrl)
|
update.addAll(newTrailers)
|
||||||
this.trailers = update
|
this.trailers = update
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun LoadResponse.addTrailer(trailerUrls: List<String>?) {
|
suspend fun LoadResponse.addTrailer(trailerUrls: List<String>?, referer: String? = null) {
|
||||||
if(trailerUrls == null) return
|
if (!isTrailersEnabled || trailerUrls == null) return
|
||||||
if (this.trailers == null) {
|
val newTrailers = trailerUrls.apmap { trailerUrl ->
|
||||||
this.trailers = trailerUrls
|
try {
|
||||||
} else {
|
loadExtractor(trailerUrl, referer)
|
||||||
val update = this.trailers?.toMutableList()
|
} catch (e: Exception) {
|
||||||
update?.addAll(trailerUrls)
|
logError(e)
|
||||||
this.trailers = update
|
emptyList()
|
||||||
}
|
}
|
||||||
|
}.flatten().distinct()
|
||||||
|
addTrailer(newTrailers)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun LoadResponse.addImdbId(id: String?) {
|
fun LoadResponse.addImdbId(id: String?) {
|
||||||
|
@ -995,7 +1030,7 @@ data class TorrentLoadResponse(
|
||||||
override var rating: Int? = null,
|
override var rating: Int? = null,
|
||||||
override var tags: List<String>? = null,
|
override var tags: List<String>? = null,
|
||||||
override var duration: Int? = null,
|
override var duration: Int? = null,
|
||||||
override var trailers: List<String>? = null,
|
override var trailers: List<ExtractorLink>? = null,
|
||||||
override var recommendations: List<SearchResponse>? = null,
|
override var recommendations: List<SearchResponse>? = null,
|
||||||
override var actors: List<ActorData>? = null,
|
override var actors: List<ActorData>? = null,
|
||||||
override var comingSoon: Boolean = false,
|
override var comingSoon: Boolean = false,
|
||||||
|
@ -1023,7 +1058,7 @@ data class AnimeLoadResponse(
|
||||||
|
|
||||||
override var rating: Int? = null,
|
override var rating: Int? = null,
|
||||||
override var duration: Int? = null,
|
override var duration: Int? = null,
|
||||||
override var trailers: List<String>? = null,
|
override var trailers: List<ExtractorLink>? = null,
|
||||||
override var recommendations: List<SearchResponse>? = null,
|
override var recommendations: List<SearchResponse>? = null,
|
||||||
override var actors: List<ActorData>? = null,
|
override var actors: List<ActorData>? = null,
|
||||||
override var comingSoon: Boolean = false,
|
override var comingSoon: Boolean = false,
|
||||||
|
@ -1036,12 +1071,12 @@ fun AnimeLoadResponse.addEpisodes(status: DubStatus, episodes: List<Episode>?) {
|
||||||
this.episodes[status] = episodes
|
this.episodes[status] = episodes
|
||||||
}
|
}
|
||||||
|
|
||||||
fun MainAPI.newAnimeLoadResponse(
|
suspend fun MainAPI.newAnimeLoadResponse(
|
||||||
name: String,
|
name: String,
|
||||||
url: String,
|
url: String,
|
||||||
type: TvType,
|
type: TvType,
|
||||||
comingSoonIfNone: Boolean = true,
|
comingSoonIfNone: Boolean = true,
|
||||||
initializer: AnimeLoadResponse.() -> Unit = { },
|
initializer: suspend AnimeLoadResponse.() -> Unit = { },
|
||||||
): AnimeLoadResponse {
|
): AnimeLoadResponse {
|
||||||
val builder = AnimeLoadResponse(name = name, url = url, apiName = this.name, type = type)
|
val builder = AnimeLoadResponse(name = name, url = url, apiName = this.name, type = type)
|
||||||
builder.initializer()
|
builder.initializer()
|
||||||
|
@ -1070,7 +1105,7 @@ data class MovieLoadResponse(
|
||||||
override var rating: Int? = null,
|
override var rating: Int? = null,
|
||||||
override var tags: List<String>? = null,
|
override var tags: List<String>? = null,
|
||||||
override var duration: Int? = null,
|
override var duration: Int? = null,
|
||||||
override var trailers: List<String>? = null,
|
override var trailers: List<ExtractorLink>? = null,
|
||||||
override var recommendations: List<SearchResponse>? = null,
|
override var recommendations: List<SearchResponse>? = null,
|
||||||
override var actors: List<ActorData>? = null,
|
override var actors: List<ActorData>? = null,
|
||||||
override var comingSoon: Boolean = false,
|
override var comingSoon: Boolean = false,
|
||||||
|
@ -1078,12 +1113,12 @@ data class MovieLoadResponse(
|
||||||
override var posterHeaders: Map<String, String>? = null,
|
override var posterHeaders: Map<String, String>? = null,
|
||||||
) : LoadResponse
|
) : LoadResponse
|
||||||
|
|
||||||
fun <T> MainAPI.newMovieLoadResponse(
|
suspend fun <T> MainAPI.newMovieLoadResponse(
|
||||||
name: String,
|
name: String,
|
||||||
url: String,
|
url: String,
|
||||||
type: TvType,
|
type: TvType,
|
||||||
data: T?,
|
data: T?,
|
||||||
initializer: MovieLoadResponse.() -> Unit = { }
|
initializer: suspend MovieLoadResponse.() -> Unit = { }
|
||||||
): MovieLoadResponse {
|
): MovieLoadResponse {
|
||||||
// just in case
|
// just in case
|
||||||
if (data is String) return newMovieLoadResponse(
|
if (data is String) return newMovieLoadResponse(
|
||||||
|
@ -1106,12 +1141,12 @@ fun <T> MainAPI.newMovieLoadResponse(
|
||||||
return builder
|
return builder
|
||||||
}
|
}
|
||||||
|
|
||||||
fun MainAPI.newMovieLoadResponse(
|
suspend fun MainAPI.newMovieLoadResponse(
|
||||||
name: String,
|
name: String,
|
||||||
url: String,
|
url: String,
|
||||||
type: TvType,
|
type: TvType,
|
||||||
dataUrl: String,
|
dataUrl: String,
|
||||||
initializer: MovieLoadResponse.() -> Unit = { }
|
initializer: suspend MovieLoadResponse.() -> Unit = { }
|
||||||
): MovieLoadResponse {
|
): MovieLoadResponse {
|
||||||
val builder = MovieLoadResponse(
|
val builder = MovieLoadResponse(
|
||||||
name = name,
|
name = name,
|
||||||
|
@ -1191,7 +1226,7 @@ data class TvSeriesLoadResponse(
|
||||||
override var rating: Int? = null,
|
override var rating: Int? = null,
|
||||||
override var tags: List<String>? = null,
|
override var tags: List<String>? = null,
|
||||||
override var duration: Int? = null,
|
override var duration: Int? = null,
|
||||||
override var trailers: List<String>? = null,
|
override var trailers: List<ExtractorLink>? = null,
|
||||||
override var recommendations: List<SearchResponse>? = null,
|
override var recommendations: List<SearchResponse>? = null,
|
||||||
override var actors: List<ActorData>? = null,
|
override var actors: List<ActorData>? = null,
|
||||||
override var comingSoon: Boolean = false,
|
override var comingSoon: Boolean = false,
|
||||||
|
@ -1199,12 +1234,12 @@ data class TvSeriesLoadResponse(
|
||||||
override var posterHeaders: Map<String, String>? = null,
|
override var posterHeaders: Map<String, String>? = null,
|
||||||
) : LoadResponse
|
) : LoadResponse
|
||||||
|
|
||||||
fun MainAPI.newTvSeriesLoadResponse(
|
suspend fun MainAPI.newTvSeriesLoadResponse(
|
||||||
name: String,
|
name: String,
|
||||||
url: String,
|
url: String,
|
||||||
type: TvType,
|
type: TvType,
|
||||||
episodes: List<Episode>,
|
episodes: List<Episode>,
|
||||||
initializer: TvSeriesLoadResponse.() -> Unit = { }
|
initializer: suspend TvSeriesLoadResponse.() -> Unit = { }
|
||||||
): TvSeriesLoadResponse {
|
): TvSeriesLoadResponse {
|
||||||
val builder = TvSeriesLoadResponse(
|
val builder = TvSeriesLoadResponse(
|
||||||
name = name,
|
name = name,
|
||||||
|
|
|
@ -28,6 +28,7 @@ import com.lagradost.cloudstream3.APIHolder.allProviders
|
||||||
import com.lagradost.cloudstream3.APIHolder.apis
|
import com.lagradost.cloudstream3.APIHolder.apis
|
||||||
import com.lagradost.cloudstream3.APIHolder.getApiDubstatusSettings
|
import com.lagradost.cloudstream3.APIHolder.getApiDubstatusSettings
|
||||||
import com.lagradost.cloudstream3.APIHolder.initAll
|
import com.lagradost.cloudstream3.APIHolder.initAll
|
||||||
|
import com.lagradost.cloudstream3.APIHolder.updateHasTrailers
|
||||||
import com.lagradost.cloudstream3.CommonActivity.loadThemes
|
import com.lagradost.cloudstream3.CommonActivity.loadThemes
|
||||||
import com.lagradost.cloudstream3.CommonActivity.onColorSelectedEvent
|
import com.lagradost.cloudstream3.CommonActivity.onColorSelectedEvent
|
||||||
import com.lagradost.cloudstream3.CommonActivity.onDialogDismissedEvent
|
import com.lagradost.cloudstream3.CommonActivity.onDialogDismissedEvent
|
||||||
|
@ -47,6 +48,7 @@ import com.lagradost.cloudstream3.ui.result.ResultFragment
|
||||||
import com.lagradost.cloudstream3.ui.search.SearchResultBuilder
|
import com.lagradost.cloudstream3.ui.search.SearchResultBuilder
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isEmulatorSettings
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isEmulatorSettings
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.SettingsGeneral
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.isCastApiAvailable
|
import com.lagradost.cloudstream3.utils.AppUtils.isCastApiAvailable
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.loadCache
|
import com.lagradost.cloudstream3.utils.AppUtils.loadCache
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.loadResult
|
import com.lagradost.cloudstream3.utils.AppUtils.loadResult
|
||||||
|
@ -68,12 +70,14 @@ import com.lagradost.cloudstream3.utils.UIHelper.getResourceColor
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
|
import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.navigate
|
import com.lagradost.cloudstream3.utils.UIHelper.navigate
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.requestRW
|
import com.lagradost.cloudstream3.utils.UIHelper.requestRW
|
||||||
|
import com.lagradost.cloudstream3.utils.USER_PROVIDER_API
|
||||||
import com.lagradost.nicehttp.Requests
|
import com.lagradost.nicehttp.Requests
|
||||||
import kotlinx.android.synthetic.main.activity_main.*
|
import kotlinx.android.synthetic.main.activity_main.*
|
||||||
import kotlinx.android.synthetic.main.fragment_result_swipe.*
|
import kotlinx.android.synthetic.main.fragment_result_swipe.*
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.schabi.newpipe.extractor.NewPipe
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import kotlin.concurrent.thread
|
import kotlin.concurrent.thread
|
||||||
|
|
||||||
|
@ -364,9 +368,23 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun test() {
|
fun test() {
|
||||||
//val youtubeLink = "https://www.youtube.com/watch?v=TxB48MEAmZw"
|
/*thread {
|
||||||
|
val youtubeLink = "https://www.youtube.com/watch?v=Zxem9rqJ5S0"
|
||||||
|
|
||||||
|
val url = YoutubeStreamLinkHandlerFactory.getInstance().fromUrl(youtubeLink)
|
||||||
|
println("ID:::: ${url.id}")
|
||||||
|
NewPipe.init(DownloaderTestImpl.getInstance())
|
||||||
|
val service = ServiceList.YouTube
|
||||||
|
val s = object : YoutubeStreamExtractor(
|
||||||
|
service,
|
||||||
|
url
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
s.fetchPage()
|
||||||
|
val streams = s.videoStreams
|
||||||
|
println("STREAMS: ${streams.map { "url = "+ it.url + " extra= " + it.height + "|" + it.isVideoOnly + "\n" }}")
|
||||||
|
}*/
|
||||||
/*
|
/*
|
||||||
runBlocking {
|
runBlocking {
|
||||||
|
|
||||||
|
@ -515,6 +533,26 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
||||||
apis = allProviders
|
apis = allProviders
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
getKey<Array<SettingsGeneral.CustomSite>>(USER_PROVIDER_API)?.let { list ->
|
||||||
|
list.forEach { custom ->
|
||||||
|
allProviders.firstOrNull { it.javaClass.simpleName == custom.parentJavaClass }
|
||||||
|
?.let {
|
||||||
|
allProviders.add(it.javaClass.newInstance().apply {
|
||||||
|
name = custom.name
|
||||||
|
lang = custom.lang
|
||||||
|
mainUrl = custom.url.trimEnd('/')
|
||||||
|
canBeOverridden = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
apis = allProviders
|
||||||
|
APIHolder.apiMap = null
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logError(e)
|
||||||
|
}
|
||||||
|
|
||||||
loadThemes(this)
|
loadThemes(this)
|
||||||
updateLocale()
|
updateLocale()
|
||||||
app.initClient(this)
|
app.initClient(this)
|
||||||
|
@ -576,6 +614,8 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
||||||
|
|
||||||
loadCache()
|
loadCache()
|
||||||
test()
|
test()
|
||||||
|
NewPipe.init(DownloaderTestImpl.getInstance())
|
||||||
|
updateHasTrailers()
|
||||||
/*nav_view.setOnNavigationItemSelectedListener { item ->
|
/*nav_view.setOnNavigationItemSelectedListener { item ->
|
||||||
when (item.itemId) {
|
when (item.itemId) {
|
||||||
R.id.navigation_home -> {
|
R.id.navigation_home -> {
|
||||||
|
|
|
@ -16,7 +16,7 @@ import org.jsoup.nodes.Element
|
||||||
class AnimeWorldProvider : MainAPI() {
|
class AnimeWorldProvider : MainAPI() {
|
||||||
override var mainUrl = "https://www.animeworld.tv"
|
override var mainUrl = "https://www.animeworld.tv"
|
||||||
override var name = "AnimeWorld"
|
override var name = "AnimeWorld"
|
||||||
override val lang = "it"
|
override var lang = "it"
|
||||||
override val hasMainPage = true
|
override val hasMainPage = true
|
||||||
|
|
||||||
override val supportedTypes = setOf(
|
override val supportedTypes = setOf(
|
||||||
|
|
|
@ -13,7 +13,7 @@ class AnimefenixProvider:MainAPI() {
|
||||||
|
|
||||||
override var mainUrl = "https://animefenix.com"
|
override var mainUrl = "https://animefenix.com"
|
||||||
override var name = "Animefenix"
|
override var name = "Animefenix"
|
||||||
override val lang = "es"
|
override var lang = "es"
|
||||||
override val hasMainPage = true
|
override val hasMainPage = true
|
||||||
override val hasChromecastSupport = true
|
override val hasChromecastSupport = true
|
||||||
override val hasDownloadSupport = true
|
override val hasDownloadSupport = true
|
||||||
|
|
|
@ -11,7 +11,7 @@ import kotlin.collections.ArrayList
|
||||||
class AnimeflvIOProvider:MainAPI() {
|
class AnimeflvIOProvider:MainAPI() {
|
||||||
override var mainUrl = "https://animeflv.io" //Also scrapes from animeid.to
|
override var mainUrl = "https://animeflv.io" //Also scrapes from animeid.to
|
||||||
override var name = "Animeflv.io"
|
override var name = "Animeflv.io"
|
||||||
override val lang = "es"
|
override var lang = "es"
|
||||||
override val hasMainPage = true
|
override val hasMainPage = true
|
||||||
override val hasChromecastSupport = true
|
override val hasChromecastSupport = true
|
||||||
override val hasDownloadSupport = true
|
override val hasDownloadSupport = true
|
||||||
|
|
|
@ -24,7 +24,7 @@ class AnimeflvnetProvider : MainAPI() {
|
||||||
|
|
||||||
override var mainUrl = "https://www3.animeflv.net"
|
override var mainUrl = "https://www3.animeflv.net"
|
||||||
override var name = "Animeflv.net"
|
override var name = "Animeflv.net"
|
||||||
override val lang = "es"
|
override var lang = "es"
|
||||||
override val hasMainPage = true
|
override val hasMainPage = true
|
||||||
override val hasChromecastSupport = true
|
override val hasChromecastSupport = true
|
||||||
override val hasDownloadSupport = true
|
override val hasDownloadSupport = true
|
||||||
|
|
|
@ -11,7 +11,7 @@ import org.jsoup.nodes.Element
|
||||||
class DreamSubProvider : MainAPI() {
|
class DreamSubProvider : MainAPI() {
|
||||||
override var mainUrl = "https://dreamsub.me"
|
override var mainUrl = "https://dreamsub.me"
|
||||||
override var name = "DreamSub"
|
override var name = "DreamSub"
|
||||||
override val lang = "it"
|
override var lang = "it"
|
||||||
override val hasMainPage = true
|
override val hasMainPage = true
|
||||||
|
|
||||||
override val supportedTypes = setOf(
|
override val supportedTypes = setOf(
|
||||||
|
|
|
@ -14,7 +14,7 @@ class GomunimeProvider : MainAPI() {
|
||||||
override var name = "Gomunime"
|
override var name = "Gomunime"
|
||||||
override val hasQuickSearch = false
|
override val hasQuickSearch = false
|
||||||
override val hasMainPage = true
|
override val hasMainPage = true
|
||||||
override val lang = "id"
|
override var lang = "id"
|
||||||
override val hasDownloadSupport = true
|
override val hasDownloadSupport = true
|
||||||
|
|
||||||
override val supportedTypes = setOf(
|
override val supportedTypes = setOf(
|
||||||
|
|
|
@ -22,7 +22,7 @@ class JKAnimeProvider : MainAPI() {
|
||||||
|
|
||||||
override var mainUrl = "https://jkanime.net"
|
override var mainUrl = "https://jkanime.net"
|
||||||
override var name = "JKAnime"
|
override var name = "JKAnime"
|
||||||
override val lang = "es"
|
override var lang = "es"
|
||||||
override val hasMainPage = true
|
override val hasMainPage = true
|
||||||
override val hasChromecastSupport = true
|
override val hasChromecastSupport = true
|
||||||
override val hasDownloadSupport = true
|
override val hasDownloadSupport = true
|
||||||
|
|
|
@ -12,7 +12,7 @@ class KuramanimeProvider : MainAPI() {
|
||||||
override var name = "Kuramanime"
|
override var name = "Kuramanime"
|
||||||
override val hasQuickSearch = false
|
override val hasQuickSearch = false
|
||||||
override val hasMainPage = true
|
override val hasMainPage = true
|
||||||
override val lang = "id"
|
override var lang = "id"
|
||||||
override val hasDownloadSupport = true
|
override val hasDownloadSupport = true
|
||||||
|
|
||||||
override val supportedTypes = setOf(
|
override val supportedTypes = setOf(
|
||||||
|
|
|
@ -3,17 +3,18 @@ package com.lagradost.cloudstream3.animeproviders
|
||||||
import com.lagradost.cloudstream3.*
|
import com.lagradost.cloudstream3.*
|
||||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
|
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
|
||||||
import com.lagradost.cloudstream3.mvvm.safeApiCall
|
import com.lagradost.cloudstream3.mvvm.safeApiCall
|
||||||
import com.lagradost.cloudstream3.utils.*
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
|
import com.lagradost.cloudstream3.utils.getQualityFromName
|
||||||
|
import com.lagradost.cloudstream3.utils.loadExtractor
|
||||||
import org.jsoup.Jsoup
|
import org.jsoup.Jsoup
|
||||||
import org.jsoup.nodes.Element
|
import org.jsoup.nodes.Element
|
||||||
import java.util.ArrayList
|
|
||||||
|
|
||||||
class KuronimeProvider : MainAPI() {
|
class KuronimeProvider : MainAPI() {
|
||||||
override var mainUrl = "https://185.231.223.254"
|
override var mainUrl = "https://185.231.223.254"
|
||||||
override var name = "Kuronime"
|
override var name = "Kuronime"
|
||||||
override val hasQuickSearch = false
|
override val hasQuickSearch = false
|
||||||
override val hasMainPage = true
|
override val hasMainPage = true
|
||||||
override val lang = "id"
|
override var lang = "id"
|
||||||
override val hasDownloadSupport = true
|
override val hasDownloadSupport = true
|
||||||
|
|
||||||
override val supportedTypes = setOf(
|
override val supportedTypes = setOf(
|
||||||
|
@ -139,7 +140,6 @@ class KuronimeProvider : MainAPI() {
|
||||||
plot = description
|
plot = description
|
||||||
addTrailer(trailer)
|
addTrailer(trailer)
|
||||||
this.tags = tags
|
this.tags = tags
|
||||||
trailers = listOf(trailer)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ class MonoschinosProvider : MainAPI() {
|
||||||
|
|
||||||
override var mainUrl = "https://monoschinos2.com"
|
override var mainUrl = "https://monoschinos2.com"
|
||||||
override var name = "Monoschinos"
|
override var name = "Monoschinos"
|
||||||
override val lang = "es"
|
override var lang = "es"
|
||||||
override val hasMainPage = true
|
override val hasMainPage = true
|
||||||
override val hasChromecastSupport = true
|
override val hasChromecastSupport = true
|
||||||
override val hasDownloadSupport = true
|
override val hasDownloadSupport = true
|
||||||
|
|
|
@ -15,7 +15,7 @@ class MundoDonghuaProvider : MainAPI() {
|
||||||
|
|
||||||
override var mainUrl = "https://www.mundodonghua.com"
|
override var mainUrl = "https://www.mundodonghua.com"
|
||||||
override var name = "MundoDonghua"
|
override var name = "MundoDonghua"
|
||||||
override val lang = "es"
|
override var lang = "es"
|
||||||
override val hasMainPage = true
|
override val hasMainPage = true
|
||||||
override val hasChromecastSupport = true
|
override val hasChromecastSupport = true
|
||||||
override val hasDownloadSupport = true
|
override val hasDownloadSupport = true
|
||||||
|
|
|
@ -11,7 +11,7 @@ class NeonimeProvider : MainAPI() {
|
||||||
override var name = "Neonime"
|
override var name = "Neonime"
|
||||||
override val hasQuickSearch = false
|
override val hasQuickSearch = false
|
||||||
override val hasMainPage = true
|
override val hasMainPage = true
|
||||||
override val lang = "id"
|
override var lang = "id"
|
||||||
override val hasDownloadSupport = true
|
override val hasDownloadSupport = true
|
||||||
|
|
||||||
override val supportedTypes = setOf(
|
override val supportedTypes = setOf(
|
||||||
|
|
|
@ -34,7 +34,7 @@ class NineAnimeProvider : MainAPI() {
|
||||||
Pair("$mainUrl/ajax/home/widget?name=updated_sub&page=1", "Recently Updated (SUB)"),
|
Pair("$mainUrl/ajax/home/widget?name=updated_sub&page=1", "Recently Updated (SUB)"),
|
||||||
Pair(
|
Pair(
|
||||||
"$mainUrl/ajax/home/widget?name=updated_dub&page=1",
|
"$mainUrl/ajax/home/widget?name=updated_dub&page=1",
|
||||||
"Recently Updated (DUB)(DUB)"
|
"Recently Updated (DUB)"
|
||||||
),
|
),
|
||||||
Pair(
|
Pair(
|
||||||
"$mainUrl/ajax/home/widget?name=updated_chinese&page=1",
|
"$mainUrl/ajax/home/widget?name=updated_chinese&page=1",
|
||||||
|
@ -64,7 +64,8 @@ class NineAnimeProvider : MainAPI() {
|
||||||
}
|
}
|
||||||
|
|
||||||
//Credits to https://github.com/jmir1
|
//Credits to https://github.com/jmir1
|
||||||
private val key = "c/aUAorINHBLxWTy3uRiPt8J+vjsOheFG1E0q2X9CYwDZlnmd4Kb5M6gSVzfk7pQ" //key credits to @Modder4869
|
private val key =
|
||||||
|
"c/aUAorINHBLxWTy3uRiPt8J+vjsOheFG1E0q2X9CYwDZlnmd4Kb5M6gSVzfk7pQ" //key credits to @Modder4869
|
||||||
|
|
||||||
private fun getVrf(id: String): String? {
|
private fun getVrf(id: String): String? {
|
||||||
val reversed = ue(encode(id) + "0000000").slice(0..5).reversed()
|
val reversed = ue(encode(id) + "0000000").slice(0..5).reversed()
|
||||||
|
@ -175,7 +176,10 @@ class NineAnimeProvider : MainAPI() {
|
||||||
return app.get(url).document.select("ul.anime-list li").mapNotNull {
|
return app.get(url).document.select("ul.anime-list li").mapNotNull {
|
||||||
val title = it.selectFirst("a.name")!!.text()
|
val title = it.selectFirst("a.name")!!.text()
|
||||||
val href =
|
val href =
|
||||||
fixUrlNull(it.selectFirst("a")!!.attr("href"))?.replace(Regex("(\\?ep=(\\d+)\$)"), "")
|
fixUrlNull(it.selectFirst("a")!!.attr("href"))?.replace(
|
||||||
|
Regex("(\\?ep=(\\d+)\$)"),
|
||||||
|
""
|
||||||
|
)
|
||||||
?: return@mapNotNull null
|
?: return@mapNotNull null
|
||||||
val image = it.selectFirst("a.poster img")!!.attr("src")
|
val image = it.selectFirst("a.poster img")!!.attr("src")
|
||||||
AnimeSearchResponse(
|
AnimeSearchResponse(
|
||||||
|
@ -199,7 +203,8 @@ class NineAnimeProvider : MainAPI() {
|
||||||
override suspend fun load(url: String): LoadResponse? {
|
override suspend fun load(url: String): LoadResponse? {
|
||||||
val validUrl = url.replace("https://9anime.to", mainUrl)
|
val validUrl = url.replace("https://9anime.to", mainUrl)
|
||||||
val doc = app.get(validUrl).document
|
val doc = app.get(validUrl).document
|
||||||
val animeid = doc.selectFirst("div.player-wrapper.watchpage")!!.attr("data-id") ?: return null
|
val animeid =
|
||||||
|
doc.selectFirst("div.player-wrapper.watchpage")!!.attr("data-id") ?: return null
|
||||||
val animeidencoded = encode(getVrf(animeid) ?: return null)
|
val animeidencoded = encode(getVrf(animeid) ?: return null)
|
||||||
val poster = doc.selectFirst("aside.main div.thumb div img")!!.attr("src")
|
val poster = doc.selectFirst("aside.main div.thumb div img")!!.attr("src")
|
||||||
val title = doc.selectFirst(".info .title")!!.text()
|
val title = doc.selectFirst(".info .title")!!.text()
|
||||||
|
|
|
@ -15,7 +15,7 @@ class NontonAnimeIDProvider : MainAPI() {
|
||||||
override var name = "NontonAnimeID"
|
override var name = "NontonAnimeID"
|
||||||
override val hasQuickSearch = false
|
override val hasQuickSearch = false
|
||||||
override val hasMainPage = true
|
override val hasMainPage = true
|
||||||
override val lang = "id"
|
override var lang = "id"
|
||||||
override val hasDownloadSupport = true
|
override val hasDownloadSupport = true
|
||||||
|
|
||||||
override val supportedTypes = setOf(
|
override val supportedTypes = setOf(
|
||||||
|
|
|
@ -13,7 +13,7 @@ class OploverzProvider : MainAPI() {
|
||||||
override var name = "Oploverz"
|
override var name = "Oploverz"
|
||||||
override val hasQuickSearch = false
|
override val hasQuickSearch = false
|
||||||
override val hasMainPage = true
|
override val hasMainPage = true
|
||||||
override val lang = "id"
|
override var lang = "id"
|
||||||
override val hasDownloadSupport = true
|
override val hasDownloadSupport = true
|
||||||
|
|
||||||
override val supportedTypes = setOf(
|
override val supportedTypes = setOf(
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
package com.lagradost.cloudstream3.extractors
|
||||||
|
|
||||||
|
import com.lagradost.cloudstream3.ErrorLoadingException
|
||||||
|
import com.lagradost.cloudstream3.mvvm.Resource
|
||||||
|
import com.lagradost.cloudstream3.mvvm.safeApiCall
|
||||||
|
import com.lagradost.cloudstream3.utils.ExtractorApi
|
||||||
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
|
import org.schabi.newpipe.extractor.ServiceList
|
||||||
|
import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeStreamExtractor
|
||||||
|
import org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeStreamLinkHandlerFactory
|
||||||
|
import org.schabi.newpipe.extractor.stream.VideoStream
|
||||||
|
|
||||||
|
class YoutubeShortLinkExtractor : YoutubeExtractor() {
|
||||||
|
override val mainUrl = "https://youtu.be"
|
||||||
|
|
||||||
|
override fun getExtractorUrl(id: String): String {
|
||||||
|
return "$mainUrl/$id"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open class YoutubeExtractor : ExtractorApi() {
|
||||||
|
override val mainUrl = "https://www.youtube.com"
|
||||||
|
override val requiresReferer = false
|
||||||
|
override val name = "YouTube"
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private var ytVideos: MutableMap<String, List<VideoStream>> = mutableMapOf()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getExtractorUrl(id: String): String {
|
||||||
|
return "$mainUrl/watch?v=$id"
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? {
|
||||||
|
val streams = safeApiCall {
|
||||||
|
val streams = ytVideos[url] ?: let {
|
||||||
|
val link =
|
||||||
|
YoutubeStreamLinkHandlerFactory.getInstance().fromUrl(url)
|
||||||
|
|
||||||
|
val s = object : YoutubeStreamExtractor(
|
||||||
|
ServiceList.YouTube,
|
||||||
|
link
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
s.fetchPage()
|
||||||
|
val streams = s.videoStreams ?: return@let emptyList()
|
||||||
|
ytVideos[url] = streams
|
||||||
|
streams
|
||||||
|
}
|
||||||
|
if (streams.isEmpty()) {
|
||||||
|
throw ErrorLoadingException("No Youtube streams")
|
||||||
|
}
|
||||||
|
|
||||||
|
streams
|
||||||
|
//streams.sortedBy { it.height }
|
||||||
|
// .firstOrNull { !it.isVideoOnly && it.height > 0 }
|
||||||
|
// ?: throw ErrorLoadingException("No valid Youtube stream")
|
||||||
|
}
|
||||||
|
if (streams is Resource.Success) {
|
||||||
|
return streams.value.mapNotNull {
|
||||||
|
if (it.isVideoOnly || it.height <= 0) return@mapNotNull null
|
||||||
|
|
||||||
|
ExtractorLink(
|
||||||
|
this.name,
|
||||||
|
this.name,
|
||||||
|
it.url ?: return@mapNotNull null,
|
||||||
|
"",
|
||||||
|
it.height
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,7 +12,7 @@ import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
class CrossTmdbProvider : TmdbProvider() {
|
class CrossTmdbProvider : TmdbProvider() {
|
||||||
override var name = "MultiMovie"
|
override var name = "MultiMovie"
|
||||||
override val apiName = "MultiMovie"
|
override val apiName = "MultiMovie"
|
||||||
override val lang = "en"
|
override var lang = "en"
|
||||||
override val useMetaLoadResponse = true
|
override val useMetaLoadResponse = true
|
||||||
override val usesWebView = true
|
override val usesWebView = true
|
||||||
override val supportedTypes = setOf(TvType.Movie)
|
override val supportedTypes = setOf(TvType.Movie)
|
||||||
|
|
|
@ -12,7 +12,7 @@ import com.lagradost.cloudstream3.utils.SyncUtil
|
||||||
// wont be implemented
|
// wont be implemented
|
||||||
class MultiAnimeProvider : MainAPI() {
|
class MultiAnimeProvider : MainAPI() {
|
||||||
override var name = "MultiAnime"
|
override var name = "MultiAnime"
|
||||||
override val lang = "en"
|
override var lang = "en"
|
||||||
override val usesWebView = true
|
override val usesWebView = true
|
||||||
override val supportedTypes = setOf(TvType.Anime)
|
override val supportedTypes = setOf(TvType.Anime)
|
||||||
private val syncApi: SyncAPI = aniListApi
|
private val syncApi: SyncAPI = aniListApi
|
||||||
|
@ -61,7 +61,7 @@ class MultiAnimeProvider : MainAPI() {
|
||||||
plot = res.synopsis
|
plot = res.synopsis
|
||||||
tags = res.genres
|
tags = res.genres
|
||||||
rating = res.publicScore
|
rating = res.publicScore
|
||||||
addTrailer(res.trailerUrl)
|
addTrailer(res.trailers)
|
||||||
addAniListId(res.id.toIntOrNull())
|
addAniListId(res.id.toIntOrNull())
|
||||||
recommendations = res.recommendations
|
recommendations = res.recommendations
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,7 +95,7 @@ open class TmdbProvider : MainAPI() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun TvShow.toLoadResponse(): TvSeriesLoadResponse {
|
private suspend fun TvShow.toLoadResponse(): TvSeriesLoadResponse {
|
||||||
val episodes = this.seasons?.filter { !disableSeasonZero || (it.season_number ?: 0) != 0 }
|
val episodes = this.seasons?.filter { !disableSeasonZero || (it.season_number ?: 0) != 0 }
|
||||||
?.mapNotNull { season ->
|
?.mapNotNull { season ->
|
||||||
season.episodes?.map { episode ->
|
season.episodes?.map { episode ->
|
||||||
|
@ -167,7 +167,7 @@ open class TmdbProvider : MainAPI() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Movie.toLoadResponse(): MovieLoadResponse {
|
private suspend fun Movie.toLoadResponse(): MovieLoadResponse {
|
||||||
return newMovieLoadResponse(
|
return newMovieLoadResponse(
|
||||||
this.title ?: this.original_title, getUrl(id, false), TvType.Movie, TmdbLink(
|
this.title ?: this.original_title, getUrl(id, false), TvType.Movie, TmdbLink(
|
||||||
this.imdb_id,
|
this.imdb_id,
|
||||||
|
|
|
@ -7,7 +7,7 @@ import com.lagradost.cloudstream3.utils.Qualities
|
||||||
import org.jsoup.nodes.Element
|
import org.jsoup.nodes.Element
|
||||||
|
|
||||||
class AkwamProvider : MainAPI() {
|
class AkwamProvider : MainAPI() {
|
||||||
override val lang = "ar"
|
override var lang = "ar"
|
||||||
override var mainUrl = "https://akwam.to"
|
override var mainUrl = "https://akwam.to"
|
||||||
override var name = "Akwam"
|
override var name = "Akwam"
|
||||||
override val usesWebView = false
|
override val usesWebView = false
|
||||||
|
|
|
@ -5,9 +5,11 @@ import com.lagradost.cloudstream3.*
|
||||||
import com.lagradost.cloudstream3.mvvm.logError
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
import com.lagradost.cloudstream3.app
|
import com.lagradost.cloudstream3.app
|
||||||
import com.lagradost.cloudstream3.utils.*
|
import com.lagradost.cloudstream3.utils.*
|
||||||
|
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
|
||||||
|
|
||||||
|
|
||||||
class AltadefinizioneProvider : MainAPI() {
|
class AltadefinizioneProvider : MainAPI() {
|
||||||
override val lang = "it"
|
override var lang = "it"
|
||||||
override var mainUrl = "https://altadefinizione.hair"
|
override var mainUrl = "https://altadefinizione.hair"
|
||||||
override var name = "Altadefinizione"
|
override var name = "Altadefinizione"
|
||||||
override val hasMainPage = true
|
override val hasMainPage = true
|
||||||
|
@ -111,6 +113,9 @@ class AltadefinizioneProvider : MainAPI() {
|
||||||
}
|
}
|
||||||
|
|
||||||
val tags: List<String> = document.select("#details > li:nth-child(1) > a").map { it.text() }
|
val tags: List<String> = document.select("#details > li:nth-child(1) > a").map { it.text() }
|
||||||
|
|
||||||
|
val trailerurl = document.selectFirst("#showtrailer > div > div > iframe")!!.attr("src")
|
||||||
|
|
||||||
return newMovieLoadResponse(
|
return newMovieLoadResponse(
|
||||||
title,
|
title,
|
||||||
url,
|
url,
|
||||||
|
@ -125,6 +130,7 @@ class AltadefinizioneProvider : MainAPI() {
|
||||||
this.duration = null
|
this.duration = null
|
||||||
this.actors = actors
|
this.actors = actors
|
||||||
this.tags = tags
|
this.tags = tags
|
||||||
|
addTrailer(trailerurl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
import com.lagradost.cloudstream3.utils.loadExtractor
|
import com.lagradost.cloudstream3.utils.loadExtractor
|
||||||
|
|
||||||
class CineblogProvider : MainAPI() {
|
class CineblogProvider : MainAPI() {
|
||||||
override val lang = "it"
|
override var lang = "it"
|
||||||
override var mainUrl = "https://cb01.rip"
|
override var mainUrl = "https://cb01.rip"
|
||||||
override var name = "CineBlog"
|
override var name = "CineBlog"
|
||||||
override val hasMainPage = true
|
override val hasMainPage = true
|
||||||
|
|
|
@ -9,7 +9,7 @@ import com.lagradost.cloudstream3.utils.loadExtractor
|
||||||
class CinecalidadProvider:MainAPI() {
|
class CinecalidadProvider:MainAPI() {
|
||||||
override var mainUrl = "https://cinecalidad.lol"
|
override var mainUrl = "https://cinecalidad.lol"
|
||||||
override var name = "Cinecalidad"
|
override var name = "Cinecalidad"
|
||||||
override val lang = "es"
|
override var lang = "es"
|
||||||
override val hasMainPage = true
|
override val hasMainPage = true
|
||||||
override val hasChromecastSupport = true
|
override val hasChromecastSupport = true
|
||||||
override val hasDownloadSupport = true
|
override val hasDownloadSupport = true
|
||||||
|
|
|
@ -10,7 +10,7 @@ import com.lagradost.cloudstream3.utils.loadExtractor
|
||||||
class CuevanaProvider : MainAPI() {
|
class CuevanaProvider : MainAPI() {
|
||||||
override var mainUrl = "https://cuevana3.me"
|
override var mainUrl = "https://cuevana3.me"
|
||||||
override var name = "Cuevana"
|
override var name = "Cuevana"
|
||||||
override val lang = "es"
|
override var lang = "es"
|
||||||
override val hasMainPage = true
|
override val hasMainPage = true
|
||||||
override val hasChromecastSupport = true
|
override val hasChromecastSupport = true
|
||||||
override val hasDownloadSupport = true
|
override val hasDownloadSupport = true
|
||||||
|
|
|
@ -23,7 +23,7 @@ class DoramasYTProvider : MainAPI() {
|
||||||
|
|
||||||
override var mainUrl = "https://doramasyt.com"
|
override var mainUrl = "https://doramasyt.com"
|
||||||
override var name = "DoramasYT"
|
override var name = "DoramasYT"
|
||||||
override val lang = "es"
|
override var lang = "es"
|
||||||
override val hasMainPage = true
|
override val hasMainPage = true
|
||||||
override val hasChromecastSupport = true
|
override val hasChromecastSupport = true
|
||||||
override val hasDownloadSupport = true
|
override val hasDownloadSupport = true
|
||||||
|
|
|
@ -15,7 +15,7 @@ class DramaidProvider : MainAPI() {
|
||||||
override var name = "DramaId"
|
override var name = "DramaId"
|
||||||
override val hasQuickSearch = false
|
override val hasQuickSearch = false
|
||||||
override val hasMainPage = true
|
override val hasMainPage = true
|
||||||
override val lang = "id"
|
override var lang = "id"
|
||||||
override val hasDownloadSupport = true
|
override val hasDownloadSupport = true
|
||||||
override val hasChromecastSupport = false
|
override val hasChromecastSupport = false
|
||||||
override val supportedTypes = setOf(TvType.AsianDrama)
|
override val supportedTypes = setOf(TvType.AsianDrama)
|
||||||
|
|
|
@ -7,7 +7,7 @@ import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
import org.jsoup.nodes.Element
|
import org.jsoup.nodes.Element
|
||||||
|
|
||||||
class EgyBestProvider : MainAPI() {
|
class EgyBestProvider : MainAPI() {
|
||||||
override val lang = "ar"
|
override var lang = "ar"
|
||||||
override var mainUrl = "https://www.egy.best"
|
override var mainUrl = "https://www.egy.best"
|
||||||
override var name = "EgyBest"
|
override var name = "EgyBest"
|
||||||
override val usesWebView = false
|
override val usesWebView = false
|
||||||
|
|
|
@ -1,19 +1,20 @@
|
||||||
package com.lagradost.cloudstream3.movieproviders
|
package com.lagradost.cloudstream3.movieproviders
|
||||||
|
|
||||||
import com.lagradost.cloudstream3.*
|
import com.lagradost.cloudstream3.*
|
||||||
import com.lagradost.cloudstream3.utils.*
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
import kotlin.collections.ArrayList
|
import com.lagradost.cloudstream3.utils.loadExtractor
|
||||||
|
|
||||||
class ElifilmsProvider : MainAPI() {
|
class ElifilmsProvider : MainAPI() {
|
||||||
override var mainUrl: String = "https://elifilms.net"
|
override var mainUrl: String = "https://elifilms.net"
|
||||||
override var name: String = "Elifilms"
|
override var name: String = "Elifilms"
|
||||||
override val lang = "es"
|
override var lang = "es"
|
||||||
override val hasMainPage = true
|
override val hasMainPage = true
|
||||||
override val hasChromecastSupport = true
|
override val hasChromecastSupport = true
|
||||||
override val hasDownloadSupport = true
|
override val hasDownloadSupport = true
|
||||||
override val supportedTypes = setOf(
|
override val supportedTypes = setOf(
|
||||||
TvType.Movie,
|
TvType.Movie,
|
||||||
)
|
)
|
||||||
|
|
||||||
override suspend fun getMainPage(): HomePageResponse {
|
override suspend fun getMainPage(): HomePageResponse {
|
||||||
val items = ArrayList<HomePageList>()
|
val items = ArrayList<HomePageList>()
|
||||||
val newest = app.get(mainUrl).document.selectFirst("a.fav_link.premiera")?.attr("href")
|
val newest = app.get(mainUrl).document.selectFirst("a.fav_link.premiera")?.attr("href")
|
||||||
|
@ -42,6 +43,7 @@ class ElifilmsProvider:MainAPI() {
|
||||||
if (items.size <= 0) throw ErrorLoadingException()
|
if (items.size <= 0) throw ErrorLoadingException()
|
||||||
return HomePageResponse(items)
|
return HomePageResponse(items)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun search(query: String): List<SearchResponse> {
|
override suspend fun search(query: String): List<SearchResponse> {
|
||||||
val url = "$mainUrl/?s=$query"
|
val url = "$mainUrl/?s=$query"
|
||||||
val doc = app.get(url).document
|
val doc = app.get(url).document
|
||||||
|
@ -52,6 +54,7 @@ class ElifilmsProvider:MainAPI() {
|
||||||
(MovieSearchResponse(name, href, this.name, TvType.Movie, poster, null))
|
(MovieSearchResponse(name, href, this.name, TvType.Movie, poster, null))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun load(url: String): LoadResponse {
|
override suspend fun load(url: String): LoadResponse {
|
||||||
val document = app.get(url, timeout = 120).document
|
val document = app.get(url, timeout = 120).document
|
||||||
val title = document.selectFirst(".post_title h1")?.text() ?: ""
|
val title = document.selectFirst(".post_title h1")?.text() ?: ""
|
||||||
|
@ -73,6 +76,7 @@ class ElifilmsProvider:MainAPI() {
|
||||||
tags
|
tags
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun loadLinks(
|
override suspend fun loadLinks(
|
||||||
data: String,
|
data: String,
|
||||||
isCasting: Boolean,
|
isCasting: Boolean,
|
||||||
|
|
|
@ -8,7 +8,7 @@ import com.lagradost.cloudstream3.utils.loadExtractor
|
||||||
class EntrepeliculasyseriesProvider:MainAPI() {
|
class EntrepeliculasyseriesProvider:MainAPI() {
|
||||||
override var mainUrl = "https://entrepeliculasyseries.nu"
|
override var mainUrl = "https://entrepeliculasyseries.nu"
|
||||||
override var name = "EntrePeliculasySeries"
|
override var name = "EntrePeliculasySeries"
|
||||||
override val lang = "es"
|
override var lang = "es"
|
||||||
override val hasMainPage = true
|
override val hasMainPage = true
|
||||||
override val hasChromecastSupport = true
|
override val hasChromecastSupport = true
|
||||||
override val hasDownloadSupport = true
|
override val hasDownloadSupport = true
|
||||||
|
|
|
@ -20,7 +20,7 @@ class EstrenosDoramasProvider : MainAPI() {
|
||||||
|
|
||||||
override var mainUrl = "https://www23.estrenosdoramas.net"
|
override var mainUrl = "https://www23.estrenosdoramas.net"
|
||||||
override var name = "EstrenosDoramas"
|
override var name = "EstrenosDoramas"
|
||||||
override val lang = "es"
|
override var lang = "es"
|
||||||
override val hasMainPage = true
|
override val hasMainPage = true
|
||||||
override val hasChromecastSupport = true
|
override val hasChromecastSupport = true
|
||||||
override val hasDownloadSupport = true
|
override val hasDownloadSupport = true
|
||||||
|
|
|
@ -5,7 +5,7 @@ import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
import org.jsoup.nodes.Element
|
import org.jsoup.nodes.Element
|
||||||
|
|
||||||
class FaselHDProvider : MainAPI() {
|
class FaselHDProvider : MainAPI() {
|
||||||
override val lang = "ar"
|
override var lang = "ar"
|
||||||
override var mainUrl = "https://faselhd.io"
|
override var mainUrl = "https://faselhd.io"
|
||||||
override var name = "FaselHD"
|
override var name = "FaselHD"
|
||||||
override val usesWebView = false
|
override val usesWebView = false
|
||||||
|
|
|
@ -11,7 +11,7 @@ import org.jsoup.select.Elements
|
||||||
class FilmanProvider : MainAPI() {
|
class FilmanProvider : MainAPI() {
|
||||||
override var mainUrl = "https://filman.cc"
|
override var mainUrl = "https://filman.cc"
|
||||||
override var name = "filman.cc"
|
override var name = "filman.cc"
|
||||||
override val lang = "pl"
|
override var lang = "pl"
|
||||||
override val hasMainPage = true
|
override val hasMainPage = true
|
||||||
override val supportedTypes = setOf(
|
override val supportedTypes = setOf(
|
||||||
TvType.Movie,
|
TvType.Movie,
|
||||||
|
|
|
@ -12,7 +12,7 @@ class FrenchStreamProvider : MainAPI() {
|
||||||
override var name = "French Stream"
|
override var name = "French Stream"
|
||||||
override val hasQuickSearch = false
|
override val hasQuickSearch = false
|
||||||
override val hasMainPage = true
|
override val hasMainPage = true
|
||||||
override val lang = "fr"
|
override var lang = "fr"
|
||||||
override val supportedTypes = setOf(TvType.AnimeMovie, TvType.TvSeries, TvType.Movie)
|
override val supportedTypes = setOf(TvType.AnimeMovie, TvType.TvSeries, TvType.Movie)
|
||||||
|
|
||||||
override suspend fun search(query: String): List<SearchResponse> {
|
override suspend fun search(query: String): List<SearchResponse> {
|
||||||
|
|
|
@ -8,9 +8,9 @@ import com.lagradost.cloudstream3.utils.loadExtractor
|
||||||
import org.jsoup.Jsoup
|
import org.jsoup.Jsoup
|
||||||
|
|
||||||
class HDMovie5 : MainAPI() {
|
class HDMovie5 : MainAPI() {
|
||||||
override var mainUrl = "https://hdmovie5.mba"
|
override var mainUrl = "https://hdmovie2.tv"
|
||||||
override var name = "HDMovie"
|
override var name = "HDMovie"
|
||||||
override val lang = "hi"
|
override var lang = "hi"
|
||||||
|
|
||||||
override val hasQuickSearch = true
|
override val hasQuickSearch = true
|
||||||
override val hasMainPage = true
|
override val hasMainPage = true
|
||||||
|
|
|
@ -11,7 +11,7 @@ class LayarKacaProvider : MainAPI() {
|
||||||
override var mainUrl = "https://149.56.24.226"
|
override var mainUrl = "https://149.56.24.226"
|
||||||
override var name = "LayarKaca"
|
override var name = "LayarKaca"
|
||||||
override val hasMainPage = true
|
override val hasMainPage = true
|
||||||
override val lang = "id"
|
override var lang = "id"
|
||||||
override val hasDownloadSupport = true
|
override val hasDownloadSupport = true
|
||||||
override val supportedTypes = setOf(
|
override val supportedTypes = setOf(
|
||||||
TvType.Movie,
|
TvType.Movie,
|
||||||
|
|
|
@ -8,7 +8,7 @@ import org.jsoup.Jsoup
|
||||||
import org.jsoup.nodes.Element
|
import org.jsoup.nodes.Element
|
||||||
|
|
||||||
class MyCimaProvider : MainAPI() {
|
class MyCimaProvider : MainAPI() {
|
||||||
override val lang = "ar"
|
override var lang = "ar"
|
||||||
override var mainUrl = "https://mycima.tv"
|
override var mainUrl = "https://mycima.tv"
|
||||||
override var name = "MyCima"
|
override var name = "MyCima"
|
||||||
override val usesWebView = false
|
override val usesWebView = false
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package com.lagradost.cloudstream3.movieproviders
|
package com.lagradost.cloudstream3.movieproviders
|
||||||
|
|
||||||
import com.lagradost.cloudstream3.*
|
import com.lagradost.cloudstream3.*
|
||||||
|
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
import com.lagradost.cloudstream3.utils.Qualities
|
import com.lagradost.cloudstream3.utils.Qualities
|
||||||
|
|
||||||
|
@ -95,7 +96,7 @@ class NginxProvider : MainAPI() {
|
||||||
this.plot = description
|
this.plot = description
|
||||||
this.rating = ratingAverage
|
this.rating = ratingAverage
|
||||||
this.tags = tagsList
|
this.tags = tagsList
|
||||||
this.trailers = trailer
|
addTrailer(trailer)
|
||||||
addPoster(poster, authHeader)
|
addPoster(poster, authHeader)
|
||||||
}
|
}
|
||||||
} else // a tv serie
|
} else // a tv serie
|
||||||
|
|
|
@ -7,7 +7,7 @@ import com.lagradost.cloudstream3.utils.loadExtractor
|
||||||
class PeliSmartProvider: MainAPI() {
|
class PeliSmartProvider: MainAPI() {
|
||||||
override var mainUrl = "https://pelismart.com"
|
override var mainUrl = "https://pelismart.com"
|
||||||
override var name = "PeliSmart"
|
override var name = "PeliSmart"
|
||||||
override val lang = "es"
|
override var lang = "es"
|
||||||
override val hasMainPage = true
|
override val hasMainPage = true
|
||||||
override val hasChromecastSupport = true
|
override val hasChromecastSupport = true
|
||||||
override val hasDownloadSupport = true
|
override val hasDownloadSupport = true
|
||||||
|
|
|
@ -9,7 +9,7 @@ import com.lagradost.cloudstream3.utils.loadExtractor
|
||||||
class PelisflixProvider : MainAPI() {
|
class PelisflixProvider : MainAPI() {
|
||||||
override var mainUrl = "https://pelisflix.li"
|
override var mainUrl = "https://pelisflix.li"
|
||||||
override var name = "Pelisflix"
|
override var name = "Pelisflix"
|
||||||
override val lang = "es"
|
override var lang = "es"
|
||||||
override val hasMainPage = true
|
override val hasMainPage = true
|
||||||
override val hasChromecastSupport = true
|
override val hasChromecastSupport = true
|
||||||
override val hasDownloadSupport = true
|
override val hasDownloadSupport = true
|
||||||
|
|
|
@ -8,7 +8,7 @@ import org.jsoup.nodes.Element
|
||||||
class PelisplusHDProvider:MainAPI() {
|
class PelisplusHDProvider:MainAPI() {
|
||||||
override var mainUrl = "https://pelisplushd.net"
|
override var mainUrl = "https://pelisplushd.net"
|
||||||
override var name = "PelisplusHD"
|
override var name = "PelisplusHD"
|
||||||
override val lang = "es"
|
override var lang = "es"
|
||||||
override val hasMainPage = true
|
override val hasMainPage = true
|
||||||
override val hasChromecastSupport = true
|
override val hasChromecastSupport = true
|
||||||
override val hasDownloadSupport = true
|
override val hasDownloadSupport = true
|
||||||
|
|
|
@ -12,7 +12,7 @@ import org.jsoup.Jsoup
|
||||||
*/
|
*/
|
||||||
|
|
||||||
open class PelisplusProviderTemplate : MainAPI() {
|
open class PelisplusProviderTemplate : MainAPI() {
|
||||||
override val lang = "es"
|
override var lang = "es"
|
||||||
open val homePageUrlList = listOf<String>()
|
open val homePageUrlList = listOf<String>()
|
||||||
|
|
||||||
// // mainUrl is good to have as a holder for the url to make future changes easier.
|
// // mainUrl is good to have as a holder for the url to make future changes easier.
|
||||||
|
|
|
@ -9,7 +9,7 @@ import com.lagradost.cloudstream3.utils.loadExtractor
|
||||||
class PinoyHDXyzProvider : MainAPI() {
|
class PinoyHDXyzProvider : MainAPI() {
|
||||||
override var name = "Pinoy-HD"
|
override var name = "Pinoy-HD"
|
||||||
override var mainUrl = "https://www.pinoy-hd.xyz"
|
override var mainUrl = "https://www.pinoy-hd.xyz"
|
||||||
override val lang = "tl"
|
override var lang = "tl"
|
||||||
override val supportedTypes = setOf(TvType.AsianDrama)
|
override val supportedTypes = setOf(TvType.AsianDrama)
|
||||||
override val hasDownloadSupport = true
|
override val hasDownloadSupport = true
|
||||||
override val hasMainPage = true
|
override val hasMainPage = true
|
||||||
|
|
|
@ -10,7 +10,7 @@ import com.lagradost.cloudstream3.utils.loadExtractor
|
||||||
class PinoyMoviePediaProvider : MainAPI() {
|
class PinoyMoviePediaProvider : MainAPI() {
|
||||||
override var name = "Pinoy Moviepedia"
|
override var name = "Pinoy Moviepedia"
|
||||||
override var mainUrl = "https://pinoymoviepedia.ru"
|
override var mainUrl = "https://pinoymoviepedia.ru"
|
||||||
override val lang = "tl"
|
override var lang = "tl"
|
||||||
override val supportedTypes = setOf(TvType.AsianDrama)
|
override val supportedTypes = setOf(TvType.AsianDrama)
|
||||||
override val hasDownloadSupport = true
|
override val hasDownloadSupport = true
|
||||||
override val hasMainPage = true
|
override val hasMainPage = true
|
||||||
|
|
|
@ -15,7 +15,7 @@ import org.jsoup.select.Elements
|
||||||
class PinoyMoviesEsProvider : MainAPI() {
|
class PinoyMoviesEsProvider : MainAPI() {
|
||||||
override var name = "Pinoy Movies"
|
override var name = "Pinoy Movies"
|
||||||
override var mainUrl = "https://pinoymovies.es"
|
override var mainUrl = "https://pinoymovies.es"
|
||||||
override val lang = "tl"
|
override var lang = "tl"
|
||||||
override val supportedTypes = setOf(TvType.AsianDrama)
|
override val supportedTypes = setOf(TvType.AsianDrama)
|
||||||
override val hasDownloadSupport = false
|
override val hasDownloadSupport = false
|
||||||
override val hasMainPage = true
|
override val hasMainPage = true
|
||||||
|
|
|
@ -15,7 +15,7 @@ class RebahinProvider : MainAPI() {
|
||||||
override var mainUrl = "http://167.88.14.149"
|
override var mainUrl = "http://167.88.14.149"
|
||||||
override var name = "Rebahin"
|
override var name = "Rebahin"
|
||||||
override val hasMainPage = true
|
override val hasMainPage = true
|
||||||
override val lang = "id"
|
override var lang = "id"
|
||||||
override val hasDownloadSupport = true
|
override val hasDownloadSupport = true
|
||||||
override val supportedTypes = setOf(
|
override val supportedTypes = setOf(
|
||||||
TvType.Movie,
|
TvType.Movie,
|
||||||
|
|
|
@ -9,7 +9,7 @@ import com.lagradost.cloudstream3.utils.loadExtractor
|
||||||
class SeriesflixProvider : MainAPI() {
|
class SeriesflixProvider : MainAPI() {
|
||||||
override var mainUrl = "https://seriesflix.video"
|
override var mainUrl = "https://seriesflix.video"
|
||||||
override var name = "Seriesflix"
|
override var name = "Seriesflix"
|
||||||
override val lang = "es"
|
override var lang = "es"
|
||||||
override val hasMainPage = true
|
override val hasMainPage = true
|
||||||
override val hasChromecastSupport = true
|
override val hasChromecastSupport = true
|
||||||
override val hasDownloadSupport = true
|
override val hasDownloadSupport = true
|
||||||
|
|
|
@ -127,7 +127,7 @@ data class TrailerElement(
|
||||||
|
|
||||||
|
|
||||||
class StreamingcommunityProvider : MainAPI() {
|
class StreamingcommunityProvider : MainAPI() {
|
||||||
override val lang = "it"
|
override var lang = "it"
|
||||||
override var mainUrl = "https://streamingcommunity.press"
|
override var mainUrl = "https://streamingcommunity.press"
|
||||||
override var name = "Streamingcommunity"
|
override var name = "Streamingcommunity"
|
||||||
override val hasMainPage = true
|
override val hasMainPage = true
|
||||||
|
@ -284,7 +284,7 @@ class StreamingcommunityProvider : MainAPI() {
|
||||||
val trailerinfojs = document.select("slider-trailer").attr("videos")
|
val trailerinfojs = document.select("slider-trailer").attr("videos")
|
||||||
val trailerinfo = parseJson<List<TrailerElement>>(trailerinfojs)
|
val trailerinfo = parseJson<List<TrailerElement>>(trailerinfojs)
|
||||||
val trailerurl: String? = if (trailerinfo.isNotEmpty()) {
|
val trailerurl: String? = if (trailerinfo.isNotEmpty()) {
|
||||||
"https://www.youtube.com/watch?v=${trailerinfo[0].id}"
|
"https://www.youtube.com/watch?v=${trailerinfo[0].url}"
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,11 @@ import com.lagradost.cloudstream3.*
|
||||||
import com.lagradost.cloudstream3.mvvm.logError
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
import com.lagradost.cloudstream3.app
|
import com.lagradost.cloudstream3.app
|
||||||
import com.lagradost.cloudstream3.utils.*
|
import com.lagradost.cloudstream3.utils.*
|
||||||
|
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
|
||||||
|
|
||||||
|
|
||||||
class TantifilmProvider : MainAPI() {
|
class TantifilmProvider : MainAPI() {
|
||||||
override val lang = "it"
|
override var lang = "it"
|
||||||
override var mainUrl = "https://www.tantifilm.rodeo"
|
override var mainUrl = "https://www.tantifilm.rodeo"
|
||||||
override var name = "Tantifilm"
|
override var name = "Tantifilm"
|
||||||
override val hasMainPage = true
|
override val hasMainPage = true
|
||||||
|
@ -69,7 +71,6 @@ class TantifilmProvider : MainAPI() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override suspend fun load(url: String): LoadResponse {
|
override suspend fun load(url: String): LoadResponse {
|
||||||
val document = app.get(url).document
|
val document = app.get(url).document
|
||||||
val type = if (document.selectFirst("div.category-film")!!.text().contains("Serie")
|
val type = if (document.selectFirst("div.category-film")!!.text().contains("Serie")
|
||||||
|
@ -107,7 +108,7 @@ class TantifilmProvider : MainAPI() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val trailerurl = document.selectFirst("#trailer_mob > iframe")!!.attr("src")
|
||||||
|
|
||||||
if (type == TvType.TvSeries) {
|
if (type == TvType.TvSeries) {
|
||||||
val list = ArrayList<Pair<Int, String>>()
|
val list = ArrayList<Pair<Int, String>>()
|
||||||
|
@ -142,22 +143,18 @@ class TantifilmProvider : MainAPI() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return TvSeriesLoadResponse(
|
return newTvSeriesLoadResponse(
|
||||||
title,
|
title,
|
||||||
url,
|
url,
|
||||||
this.name,
|
|
||||||
type,
|
type,
|
||||||
episodeList,
|
episodeList) {
|
||||||
fixUrlNull(poster),
|
this.posterUrl= fixUrlNull(poster)
|
||||||
year.toIntOrNull(),
|
this.year = year.toIntOrNull()
|
||||||
descipt[0],
|
this.plot= descipt[0]
|
||||||
null,
|
this.rating= rating
|
||||||
rating,
|
this.recommendations = recomm
|
||||||
null,
|
addTrailer(trailerurl)
|
||||||
null,
|
}
|
||||||
null,
|
|
||||||
recomm
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
val url2 = document.selectFirst("iframe")!!.attr("src")
|
val url2 = document.selectFirst("iframe")!!.attr("src")
|
||||||
val actorpagelink =
|
val actorpagelink =
|
||||||
|
@ -217,6 +214,7 @@ class TantifilmProvider : MainAPI() {
|
||||||
this.tags = tags
|
this.tags = tags
|
||||||
this.duration = duratio
|
this.duration = duratio
|
||||||
this.actors = actors
|
this.actors = actors
|
||||||
|
addTrailer(trailerurl)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ import org.jsoup.Jsoup
|
||||||
class VfFilmProvider : MainAPI() {
|
class VfFilmProvider : MainAPI() {
|
||||||
override var mainUrl = "https://vf-film.me"
|
override var mainUrl = "https://vf-film.me"
|
||||||
override var name = "vf-film.me"
|
override var name = "vf-film.me"
|
||||||
override val lang = "fr"
|
override var lang = "fr"
|
||||||
override val hasQuickSearch = false
|
override val hasQuickSearch = false
|
||||||
override val hasMainPage = false
|
override val hasMainPage = false
|
||||||
override val hasChromecastSupport = false
|
override val hasChromecastSupport = false
|
||||||
|
|
|
@ -9,7 +9,7 @@ import org.jsoup.Jsoup
|
||||||
class VfSerieProvider : MainAPI() {
|
class VfSerieProvider : MainAPI() {
|
||||||
override var mainUrl = "https://vf-serie.org"
|
override var mainUrl = "https://vf-serie.org"
|
||||||
override var name = "vf-serie.org"
|
override var name = "vf-serie.org"
|
||||||
override val lang = "fr"
|
override var lang = "fr"
|
||||||
|
|
||||||
override val hasQuickSearch = false
|
override val hasQuickSearch = false
|
||||||
override val hasMainPage = false
|
override val hasMainPage = false
|
||||||
|
|
|
@ -67,7 +67,7 @@ interface SyncAPI : OAuth2API {
|
||||||
var studio: List<String>? = null,
|
var studio: List<String>? = null,
|
||||||
var genres: List<String>? = null,
|
var genres: List<String>? = null,
|
||||||
var synonyms: List<String>? = null,
|
var synonyms: List<String>? = null,
|
||||||
var trailerUrl: String? = null,
|
var trailers: List<String>? = null,
|
||||||
var isAdult : Boolean? = null,
|
var isAdult : Boolean? = null,
|
||||||
var posterUrl: String? = null,
|
var posterUrl: String? = null,
|
||||||
var backgroundPosterUrl : String? = null,
|
var backgroundPosterUrl : String? = null,
|
||||||
|
|
|
@ -142,6 +142,10 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
||||||
getUrlFromId(recMedia.id),
|
getUrlFromId(recMedia.id),
|
||||||
recMedia.coverImage?.large ?: recMedia.coverImage?.medium
|
recMedia.coverImage?.large ?: recMedia.coverImage?.medium
|
||||||
)
|
)
|
||||||
|
},
|
||||||
|
trailers = when (season.trailer?.site?.lowercase()?.trim()) {
|
||||||
|
"youtube" -> listOf("https://www.youtube.com/watch?v=${season.trailer.id}")
|
||||||
|
else -> null
|
||||||
}
|
}
|
||||||
//TODO REST
|
//TODO REST
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,141 @@
|
||||||
|
package com.lagradost.cloudstream3.syncproviders.providers
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
import com.lagradost.cloudstream3.app
|
||||||
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
|
|
||||||
|
// modified code from from https://github.com/saikou-app/saikou/blob/main/app/src/main/java/ani/saikou/others/Kitsu.kt
|
||||||
|
// GNU General Public License v3.0 https://github.com/saikou-app/saikou/blob/main/LICENSE.md
|
||||||
|
object Kitsu {
|
||||||
|
private suspend fun getKitsuData(query: String): KitsuResponse {
|
||||||
|
val headers = mapOf(
|
||||||
|
"Content-Type" to "application/json",
|
||||||
|
"Accept" to "application/json",
|
||||||
|
"Connection" to "keep-alive",
|
||||||
|
"DNT" to "1",
|
||||||
|
"Origin" to "https://kitsu.io"
|
||||||
|
)
|
||||||
|
|
||||||
|
return app.post(
|
||||||
|
"https://kitsu.io/api/graphql",
|
||||||
|
headers = headers,
|
||||||
|
data = mapOf("query" to query)
|
||||||
|
).parsed()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val cache: MutableMap<Pair<String, String>, Map<Int, KitsuResponse.Node>> =
|
||||||
|
mutableMapOf()
|
||||||
|
|
||||||
|
suspend fun getEpisodesDetails(
|
||||||
|
malId: String?,
|
||||||
|
anilistId: String?
|
||||||
|
): Map<Int, KitsuResponse.Node>? {
|
||||||
|
if (anilistId != null) {
|
||||||
|
try {
|
||||||
|
val map = getKitsuEpisodesDetails(anilistId, "ANILIST_ANIME")
|
||||||
|
if (!map.isNullOrEmpty()) return map
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logError(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (malId != null) {
|
||||||
|
try {
|
||||||
|
val map = getKitsuEpisodesDetails(malId, "MYANIMELIST_ANIME")
|
||||||
|
if (!map.isNullOrEmpty()) return map
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logError(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws
|
||||||
|
suspend fun getKitsuEpisodesDetails(id: String, site: String): Map<Int, KitsuResponse.Node>? {
|
||||||
|
require(id.isNotBlank()) {
|
||||||
|
"Black id"
|
||||||
|
}
|
||||||
|
|
||||||
|
require(site.isNotBlank()) {
|
||||||
|
"invalid site"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cache.containsKey(id to site)) {
|
||||||
|
return cache[id to site]
|
||||||
|
}
|
||||||
|
|
||||||
|
val query =
|
||||||
|
"""
|
||||||
|
query {
|
||||||
|
lookupMapping(externalId: $id, externalSite: $site) {
|
||||||
|
__typename
|
||||||
|
... on Anime {
|
||||||
|
id
|
||||||
|
episodes(first: 2000) {
|
||||||
|
nodes {
|
||||||
|
number
|
||||||
|
titles {
|
||||||
|
canonical
|
||||||
|
}
|
||||||
|
description
|
||||||
|
thumbnail {
|
||||||
|
original {
|
||||||
|
url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}"""
|
||||||
|
val result = getKitsuData(query)
|
||||||
|
val map = (result.data?.lookupMapping?.episodes?.nodes ?: return null).mapNotNull { ep ->
|
||||||
|
val num = ep?.num ?: return@mapNotNull null
|
||||||
|
num to ep
|
||||||
|
}.toMap()
|
||||||
|
if (map.isNotEmpty()) {
|
||||||
|
cache[id to site] = map
|
||||||
|
}
|
||||||
|
return map
|
||||||
|
}
|
||||||
|
|
||||||
|
data class KitsuResponse(
|
||||||
|
val data: Data? = null
|
||||||
|
) {
|
||||||
|
data class Data(
|
||||||
|
val lookupMapping: LookupMapping? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
data class LookupMapping(
|
||||||
|
val id: String? = null,
|
||||||
|
val episodes: Episodes? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Episodes(
|
||||||
|
val nodes: List<Node?>? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Node(
|
||||||
|
@JsonProperty("number")
|
||||||
|
val num: Int? = null,
|
||||||
|
val titles: Titles? = null,
|
||||||
|
val description: Description? = null,
|
||||||
|
val thumbnail: Thumbnail? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Description(
|
||||||
|
val en: String? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Thumbnail(
|
||||||
|
val original: Original? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Original(
|
||||||
|
val url: String? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Titles(
|
||||||
|
val canonical: String? = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -33,6 +33,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
||||||
override val redirectUrl = "mallogin"
|
override val redirectUrl = "mallogin"
|
||||||
override val idPrefix = "mal"
|
override val idPrefix = "mal"
|
||||||
override var mainUrl = "https://myanimelist.net"
|
override var mainUrl = "https://myanimelist.net"
|
||||||
|
val apiUrl = "https://api.myanimelist.net"
|
||||||
override val icon = R.drawable.mal_logo
|
override val icon = R.drawable.mal_logo
|
||||||
override val requiresLogin = true
|
override val requiresLogin = true
|
||||||
|
|
||||||
|
@ -62,7 +63,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun search(name: String): List<SyncAPI.SyncSearchResult> {
|
override suspend fun search(name: String): List<SyncAPI.SyncSearchResult> {
|
||||||
val url = "https://api.myanimelist.net/v2/anime?q=$name&limit=$MAL_MAX_SEARCH_LIMIT"
|
val url = "$apiUrl/v2/anime?q=$name&limit=$MAL_MAX_SEARCH_LIMIT"
|
||||||
val auth = getAuth() ?: return emptyList()
|
val auth = getAuth() ?: return emptyList()
|
||||||
val res = app.get(
|
val res = app.get(
|
||||||
url, headers = mapOf(
|
url, headers = mapOf(
|
||||||
|
@ -179,7 +180,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
||||||
name = node?.title ?: return null,
|
name = node?.title ?: return null,
|
||||||
apiName = this.name,
|
apiName = this.name,
|
||||||
syncId = node.id.toString(),
|
syncId = node.id.toString(),
|
||||||
url = "https://myanimelist.net/anime/${node.id}",
|
url = "$mainUrl/anime/${node.id}",
|
||||||
posterUrl = node.main_picture?.large
|
posterUrl = node.main_picture?.large
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -187,7 +188,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
||||||
override suspend fun getResult(id: String): SyncAPI.SyncResult? {
|
override suspend fun getResult(id: String): SyncAPI.SyncResult? {
|
||||||
val internalId = id.toIntOrNull() ?: return null
|
val internalId = id.toIntOrNull() ?: return null
|
||||||
val url =
|
val url =
|
||||||
"https://api.myanimelist.net/v2/anime/$internalId?fields=id,title,main_picture,alternative_titles,start_date,end_date,synopsis,mean,rank,popularity,num_list_users,num_scoring_users,nsfw,created_at,updated_at,media_type,status,genres,my_list_status,num_episodes,start_season,broadcast,source,average_episode_duration,rating,pictures,background,related_anime,related_manga,recommendations,studios,statistics"
|
"$apiUrl/v2/anime/$internalId?fields=id,title,main_picture,alternative_titles,start_date,end_date,synopsis,mean,rank,popularity,num_list_users,num_scoring_users,nsfw,created_at,updated_at,media_type,status,genres,my_list_status,num_episodes,start_season,broadcast,source,average_episode_duration,rating,pictures,background,related_anime,related_manga,recommendations,studios,statistics"
|
||||||
val res = app.get(
|
val res = app.get(
|
||||||
url, headers = mapOf(
|
url, headers = mapOf(
|
||||||
"Authorization" to "Bearer " + (getAuth() ?: return null)
|
"Authorization" to "Bearer " + (getAuth() ?: return null)
|
||||||
|
@ -195,7 +196,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
||||||
).text
|
).text
|
||||||
return mapper.readValue<MalAnime>(res).let { malAnime ->
|
return mapper.readValue<MalAnime>(res).let { malAnime ->
|
||||||
SyncAPI.SyncResult(
|
SyncAPI.SyncResult(
|
||||||
id = malAnime.id?.toString()!!,
|
id = internalId.toString(),
|
||||||
totalEpisodes = malAnime.numEpisodes,
|
totalEpisodes = malAnime.numEpisodes,
|
||||||
title = malAnime.title,
|
title = malAnime.title,
|
||||||
publicScore = malAnime.mean?.toFloat()?.times(1000)?.toInt(),
|
publicScore = malAnime.mean?.toFloat()?.times(1000)?.toInt(),
|
||||||
|
@ -203,13 +204,14 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
||||||
synopsis = malAnime.synopsis,
|
synopsis = malAnime.synopsis,
|
||||||
airStatus = when (malAnime.status) {
|
airStatus = when (malAnime.status) {
|
||||||
"finished_airing" -> ShowStatus.Completed
|
"finished_airing" -> ShowStatus.Completed
|
||||||
"airing" -> ShowStatus.Ongoing
|
"currently_airing" -> ShowStatus.Ongoing
|
||||||
|
//"not_yet_aired"
|
||||||
else -> null
|
else -> null
|
||||||
},
|
},
|
||||||
nextAiring = null,
|
nextAiring = null,
|
||||||
studio = malAnime.studios?.mapNotNull { it.name },
|
studio = malAnime.studios?.mapNotNull { it.name },
|
||||||
genres = malAnime.genres?.map { it.name },
|
genres = malAnime.genres?.map { it.name },
|
||||||
trailerUrl = null,
|
trailers = null,
|
||||||
startDate = parseDate(malAnime.startDate),
|
startDate = parseDate(malAnime.startDate),
|
||||||
endDate = parseDate(malAnime.endDate),
|
endDate = parseDate(malAnime.endDate),
|
||||||
recommendations = malAnime.recommendations?.mapNotNull { rec ->
|
recommendations = malAnime.recommendations?.mapNotNull { rec ->
|
||||||
|
@ -260,7 +262,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
||||||
val currentCode = sanitizer["code"]!!
|
val currentCode = sanitizer["code"]!!
|
||||||
|
|
||||||
val res = app.post(
|
val res = app.post(
|
||||||
"https://myanimelist.net/v1/oauth2/token",
|
"$mainUrl/v1/oauth2/token",
|
||||||
data = mapOf(
|
data = mapOf(
|
||||||
"client_id" to key,
|
"client_id" to key,
|
||||||
"code" to currentCode,
|
"code" to currentCode,
|
||||||
|
@ -292,7 +294,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
||||||
.replace("/", "_").replace("\n", "")
|
.replace("/", "_").replace("\n", "")
|
||||||
val codeChallenge = codeVerifier
|
val codeChallenge = codeVerifier
|
||||||
val request =
|
val request =
|
||||||
"https://myanimelist.net/v1/oauth2/authorize?response_type=code&client_id=$key&code_challenge=$codeChallenge&state=RequestID$requestId"
|
"$mainUrl/v1/oauth2/authorize?response_type=code&client_id=$key&code_challenge=$codeChallenge&state=RequestID$requestId"
|
||||||
openBrowser(request)
|
openBrowser(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -318,7 +320,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
||||||
private suspend fun refreshToken() {
|
private suspend fun refreshToken() {
|
||||||
try {
|
try {
|
||||||
val res = app.post(
|
val res = app.post(
|
||||||
"https://myanimelist.net/v1/oauth2/token",
|
"$mainUrl/v1/oauth2/token",
|
||||||
data = mapOf(
|
data = mapOf(
|
||||||
"client_id" to key,
|
"client_id" to key,
|
||||||
"grant_type" to "refresh_token",
|
"grant_type" to "refresh_token",
|
||||||
|
@ -451,7 +453,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
||||||
// Very lackluster docs
|
// Very lackluster docs
|
||||||
// https://myanimelist.net/apiconfig/references/api/v2#operation/users_user_id_animelist_get
|
// https://myanimelist.net/apiconfig/references/api/v2#operation/users_user_id_animelist_get
|
||||||
val url =
|
val url =
|
||||||
"https://api.myanimelist.net/v2/users/$user/animelist?fields=list_status,num_episodes,media_type,status,start_date,end_date,synopsis,alternative_titles,mean,genres,rank,num_list_users,nsfw,average_episode_duration,num_favorites,popularity,num_scoring_users,start_season,favorites_info,broadcast,created_at,updated_at&nsfw=1&limit=100&offset=$offset"
|
"$apiUrl/v2/users/$user/animelist?fields=list_status,num_episodes,media_type,status,start_date,end_date,synopsis,alternative_titles,mean,genres,rank,num_list_users,nsfw,average_episode_duration,num_favorites,popularity,num_scoring_users,start_season,favorites_info,broadcast,created_at,updated_at&nsfw=1&limit=100&offset=$offset"
|
||||||
val res = app.get(
|
val res = app.get(
|
||||||
url, headers = mapOf(
|
url, headers = mapOf(
|
||||||
"Authorization" to "Bearer $auth",
|
"Authorization" to "Bearer $auth",
|
||||||
|
@ -463,7 +465,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
||||||
private suspend fun getDataAboutMalId(id: Int): SmallMalAnime? {
|
private suspend fun getDataAboutMalId(id: Int): SmallMalAnime? {
|
||||||
// https://myanimelist.net/apiconfig/references/api/v2#operation/anime_anime_id_get
|
// https://myanimelist.net/apiconfig/references/api/v2#operation/anime_anime_id_get
|
||||||
val url =
|
val url =
|
||||||
"https://api.myanimelist.net/v2/anime/$id?fields=id,title,num_episodes,my_list_status"
|
"$apiUrl/v2/anime/$id?fields=id,title,num_episodes,my_list_status"
|
||||||
val res = app.get(
|
val res = app.get(
|
||||||
url, headers = mapOf(
|
url, headers = mapOf(
|
||||||
"Authorization" to "Bearer " + (getAuth() ?: return null)
|
"Authorization" to "Bearer " + (getAuth() ?: return null)
|
||||||
|
@ -481,7 +483,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
||||||
checkMalToken()
|
checkMalToken()
|
||||||
while (!isDone) {
|
while (!isDone) {
|
||||||
val res = app.get(
|
val res = app.get(
|
||||||
"https://api.myanimelist.net/v2/users/$user/animelist?fields=list_status&limit=1000&offset=${index * 1000}",
|
"$apiUrl/v2/users/$user/animelist?fields=list_status&limit=1000&offset=${index * 1000}",
|
||||||
headers = mapOf(
|
headers = mapOf(
|
||||||
"Authorization" to "Bearer " + (getAuth() ?: return)
|
"Authorization" to "Bearer " + (getAuth() ?: return)
|
||||||
), cacheTime = 0
|
), cacheTime = 0
|
||||||
|
@ -532,10 +534,10 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun checkMalToken() {
|
private suspend fun checkMalToken() {
|
||||||
if (unixTime > getKey(
|
if (unixTime > (getKey(
|
||||||
accountId,
|
accountId,
|
||||||
MAL_UNIXTIME_KEY
|
MAL_UNIXTIME_KEY
|
||||||
) ?: 0L
|
) ?: 0L)
|
||||||
) {
|
) {
|
||||||
refreshToken()
|
refreshToken()
|
||||||
}
|
}
|
||||||
|
@ -544,7 +546,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
||||||
private suspend fun getMalUser(setSettings: Boolean = true): MalUser? {
|
private suspend fun getMalUser(setSettings: Boolean = true): MalUser? {
|
||||||
checkMalToken()
|
checkMalToken()
|
||||||
val res = app.get(
|
val res = app.get(
|
||||||
"https://api.myanimelist.net/v2/users/@me",
|
"$apiUrl/v2/users/@me",
|
||||||
headers = mapOf(
|
headers = mapOf(
|
||||||
"Authorization" to "Bearer " + (getAuth() ?: return null)
|
"Authorization" to "Bearer " + (getAuth() ?: return null)
|
||||||
), cacheTime = 0
|
), cacheTime = 0
|
||||||
|
@ -620,7 +622,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
||||||
).filter { it.value != null } as Map<String, String>
|
).filter { it.value != null } as Map<String, String>
|
||||||
|
|
||||||
return app.put(
|
return app.put(
|
||||||
"https://api.myanimelist.net/v2/anime/$id/my_list_status",
|
"$apiUrl/v2/anime/$id/my_list_status",
|
||||||
headers = mapOf(
|
headers = mapOf(
|
||||||
"Authorization" to "Bearer " + (getAuth() ?: return null)
|
"Authorization" to "Bearer " + (getAuth() ?: return null)
|
||||||
),
|
),
|
||||||
|
|
|
@ -13,12 +13,12 @@ class APIRepository(val api: MainAPI) {
|
||||||
val noneApi = object : MainAPI() {
|
val noneApi = object : MainAPI() {
|
||||||
override var name = "None"
|
override var name = "None"
|
||||||
override val supportedTypes = emptySet<TvType>()
|
override val supportedTypes = emptySet<TvType>()
|
||||||
override val lang = ""
|
override var lang = ""
|
||||||
}
|
}
|
||||||
val randomApi = object : MainAPI() {
|
val randomApi = object : MainAPI() {
|
||||||
override var name = "Random"
|
override var name = "Random"
|
||||||
override val supportedTypes = emptySet<TvType>()
|
override val supportedTypes = emptySet<TvType>()
|
||||||
override val lang = ""
|
override var lang = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isInvalidData(data: String): Boolean {
|
fun isInvalidData(data: String): Boolean {
|
||||||
|
|
|
@ -1,17 +1,11 @@
|
||||||
package com.lagradost.cloudstream3.ui.player
|
package com.lagradost.cloudstream3.ui.player
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.util.SparseArray
|
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
import androidx.core.util.forEach
|
|
||||||
import at.huber.youtubeExtractor.VideoMeta
|
|
||||||
import at.huber.youtubeExtractor.YouTubeExtractor
|
|
||||||
import at.huber.youtubeExtractor.YtFile
|
|
||||||
import com.google.android.exoplayer2.*
|
import com.google.android.exoplayer2.*
|
||||||
import com.google.android.exoplayer2.database.StandaloneDatabaseProvider
|
import com.google.android.exoplayer2.database.StandaloneDatabaseProvider
|
||||||
import com.google.android.exoplayer2.ext.okhttp.OkHttpDataSource
|
import com.google.android.exoplayer2.ext.okhttp.OkHttpDataSource
|
||||||
|
@ -38,7 +32,6 @@ import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
||||||
import com.lagradost.cloudstream3.ui.subtitles.SaveCaptionStyle
|
import com.lagradost.cloudstream3.ui.subtitles.SaveCaptionStyle
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorUri
|
import com.lagradost.cloudstream3.utils.ExtractorUri
|
||||||
import com.lagradost.cloudstream3.utils.Qualities
|
|
||||||
import com.lagradost.cloudstream3.utils.SubtitleHelper.fromTwoLettersToLanguage
|
import com.lagradost.cloudstream3.utils.SubtitleHelper.fromTwoLettersToLanguage
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import javax.net.ssl.HttpsURLConnection
|
import javax.net.ssl.HttpsURLConnection
|
||||||
|
@ -334,7 +327,6 @@ class CS3IPlayer : IPlayer {
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private var ytVideos: MutableMap<String, YtFile> = mutableMapOf()
|
|
||||||
private var simpleCache: SimpleCache? = null
|
private var simpleCache: SimpleCache? = null
|
||||||
|
|
||||||
var requestSubtitleUpdate: (() -> Unit)? = null
|
var requestSubtitleUpdate: (() -> Unit)? = null
|
||||||
|
@ -534,7 +526,6 @@ class CS3IPlayer : IPlayer {
|
||||||
)
|
)
|
||||||
setHandleAudioBecomingNoisy(true)
|
setHandleAudioBecomingNoisy(true)
|
||||||
setPlaybackSpeed(playBackSpeed)
|
setPlaybackSpeed(playBackSpeed)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -711,7 +702,7 @@ class CS3IPlayer : IPlayer {
|
||||||
if (playWhenReady) {
|
if (playWhenReady) {
|
||||||
when (playbackState) {
|
when (playbackState) {
|
||||||
Player.STATE_READY -> {
|
Player.STATE_READY -> {
|
||||||
requestAutoFocus?.invoke()
|
|
||||||
}
|
}
|
||||||
Player.STATE_ENDED -> {
|
Player.STATE_ENDED -> {
|
||||||
handleEvent(CSPlayerEvent.NextEpisode)
|
handleEvent(CSPlayerEvent.NextEpisode)
|
||||||
|
@ -740,6 +731,7 @@ class CS3IPlayer : IPlayer {
|
||||||
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
||||||
super.onIsPlayingChanged(isPlaying)
|
super.onIsPlayingChanged(isPlaying)
|
||||||
if (isPlaying) {
|
if (isPlaying) {
|
||||||
|
requestAutoFocus?.invoke()
|
||||||
onRenderFirst()
|
onRenderFirst()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -886,55 +878,9 @@ class CS3IPlayer : IPlayer {
|
||||||
return Pair(subSources, activeSubtitles)
|
return Pair(subSources, activeSubtitles)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun loadYtFile(context: Context, yt: YtFile) {
|
|
||||||
loadOnlinePlayer(
|
|
||||||
context,
|
|
||||||
ExtractorLink(
|
|
||||||
"YouTube",
|
|
||||||
"",
|
|
||||||
yt.url,
|
|
||||||
"",
|
|
||||||
yt.format?.height ?: Qualities.Unknown.value
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun loadOnlinePlayer(context: Context, link: ExtractorLink) {
|
private fun loadOnlinePlayer(context: Context, link: ExtractorLink) {
|
||||||
Log.i(TAG, "loadOnlinePlayer $link")
|
Log.i(TAG, "loadOnlinePlayer $link")
|
||||||
try {
|
try {
|
||||||
if (link.url.contains("youtube.com")) {
|
|
||||||
val ytLink = link.url.replace("/embed/", "/watch?v=")
|
|
||||||
ytVideos[ytLink]?.let {
|
|
||||||
loadYtFile(context, it)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
val ytExtractor =
|
|
||||||
@SuppressLint("StaticFieldLeak")
|
|
||||||
object : YouTubeExtractor(context) {
|
|
||||||
override fun onExtractionComplete(
|
|
||||||
ytFiles: SparseArray<YtFile>?,
|
|
||||||
videoMeta: VideoMeta?
|
|
||||||
) {
|
|
||||||
var yt: YtFile? = null
|
|
||||||
ytFiles?.forEach { _, value ->
|
|
||||||
if ((yt?.format?.height ?: 0) < (value.format?.height
|
|
||||||
?: -1) && (value.format?.audioBitrate ?: -1) > 0
|
|
||||||
) {
|
|
||||||
yt = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
yt?.let { ytf ->
|
|
||||||
ytVideos[ytLink] = ytf
|
|
||||||
loadYtFile(context, ytf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Log.i(TAG, "YouTube extraction on $ytLink")
|
|
||||||
ytExtractor.extract(ytLink)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
currentLink = link
|
currentLink = link
|
||||||
|
|
||||||
if (ignoreSSL) {
|
if (ignoreSSL) {
|
||||||
|
|
|
@ -229,6 +229,15 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val playerSourceMove = if (isShowing) 0f else -50.toPx.toFloat()
|
||||||
|
player_open_source?.let {
|
||||||
|
ObjectAnimator.ofFloat(it, "translationY", playerSourceMove).apply {
|
||||||
|
duration = 200
|
||||||
|
start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (!isLocked) {
|
if (!isLocked) {
|
||||||
player_ffwd_holder?.alpha = 1f
|
player_ffwd_holder?.alpha = 1f
|
||||||
player_rew_holder?.alpha = 1f
|
player_rew_holder?.alpha = 1f
|
||||||
|
@ -251,6 +260,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
bottom_player_bar?.startAnimation(fadeAnimation)
|
bottom_player_bar?.startAnimation(fadeAnimation)
|
||||||
|
player_open_source?.startAnimation(fadeAnimation)
|
||||||
player_top_holder?.startAnimation(fadeAnimation)
|
player_top_holder?.startAnimation(fadeAnimation)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ import android.view.ViewGroup
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.annotation.LayoutRes
|
import androidx.annotation.LayoutRes
|
||||||
|
import androidx.core.view.isGone
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.widget.ContentLoadingProgressBar
|
import androidx.core.widget.ContentLoadingProgressBar
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
@ -21,7 +22,6 @@ import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
||||||
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
|
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
|
||||||
import com.lagradost.cloudstream3.utils.VideoDownloadManager
|
import com.lagradost.cloudstream3.utils.VideoDownloadManager
|
||||||
import kotlinx.android.synthetic.main.result_episode.view.*
|
import kotlinx.android.synthetic.main.result_episode.view.*
|
||||||
import kotlinx.android.synthetic.main.result_episode.view.episode_holder
|
|
||||||
import kotlinx.android.synthetic.main.result_episode.view.episode_text
|
import kotlinx.android.synthetic.main.result_episode.view.episode_text
|
||||||
import kotlinx.android.synthetic.main.result_episode_large.view.*
|
import kotlinx.android.synthetic.main.result_episode_large.view.*
|
||||||
import kotlinx.android.synthetic.main.result_episode_large.view.episode_filler
|
import kotlinx.android.synthetic.main.result_episode_large.view.episode_filler
|
||||||
|
@ -47,6 +47,7 @@ const val ACTION_SHOW_OPTIONS = 10
|
||||||
|
|
||||||
const val ACTION_CLICK_DEFAULT = 11
|
const val ACTION_CLICK_DEFAULT = 11
|
||||||
const val ACTION_SHOW_TOAST = 12
|
const val ACTION_SHOW_TOAST = 12
|
||||||
|
const val ACTION_SHOW_DESCRIPTION = 15
|
||||||
|
|
||||||
const val ACTION_DOWNLOAD_EPISODE_SUBTITLE = 13
|
const val ACTION_DOWNLOAD_EPISODE_SUBTITLE = 13
|
||||||
const val ACTION_DOWNLOAD_EPISODE_SUBTITLE_MIRROR = 14
|
const val ACTION_DOWNLOAD_EPISODE_SUBTITLE_MIRROR = 14
|
||||||
|
@ -93,10 +94,10 @@ class EpisodeAdapter(
|
||||||
@LayoutRes
|
@LayoutRes
|
||||||
private var layout: Int = 0
|
private var layout: Int = 0
|
||||||
fun updateLayout() {
|
fun updateLayout() {
|
||||||
layout =
|
// layout =
|
||||||
if (cardList.filter { it.poster != null }.size >= cardList.size / 2f) // If over half has posters then use the large layout
|
// if (cardList.filter { it.poster != null }.size >= cardList.size / 2f) // If over half has posters then use the large layout
|
||||||
R.layout.result_episode_large
|
// R.layout.result_episode_large
|
||||||
else R.layout.result_episode
|
// else R.layout.result_episode
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
|
@ -105,7 +106,7 @@ class EpisodeAdapter(
|
||||||
else R.layout.result_episode*/
|
else R.layout.result_episode*/
|
||||||
|
|
||||||
return EpisodeCardViewHolder(
|
return EpisodeCardViewHolder(
|
||||||
LayoutInflater.from(parent.context).inflate(layout, parent, false),
|
LayoutInflater.from(parent.context).inflate(R.layout.result_episode_both, parent, false),
|
||||||
hasDownloadSupport,
|
hasDownloadSupport,
|
||||||
clickCallback,
|
clickCallback,
|
||||||
downloadClickCallback
|
downloadClickCallback
|
||||||
|
@ -134,27 +135,39 @@ class EpisodeAdapter(
|
||||||
) : RecyclerView.ViewHolder(itemView), DownloadButtonViewHolder {
|
) : RecyclerView.ViewHolder(itemView), DownloadButtonViewHolder {
|
||||||
override var downloadButton = EasyDownloadButton()
|
override var downloadButton = EasyDownloadButton()
|
||||||
|
|
||||||
private val episodeText: TextView = itemView.episode_text
|
|
||||||
private val episodeFiller: MaterialButton? = itemView.episode_filler
|
|
||||||
private val episodeRating: TextView? = itemView.episode_rating
|
|
||||||
private val episodeDescript: TextView? = itemView.episode_descript
|
|
||||||
private val episodeProgress: ContentLoadingProgressBar? = itemView.episode_progress
|
|
||||||
private val episodePoster: ImageView? = itemView.episode_poster
|
|
||||||
|
|
||||||
private val episodeDownloadBar: ContentLoadingProgressBar = itemView.result_episode_progress_downloaded
|
|
||||||
private val episodeDownloadImage: ImageView = itemView.result_episode_download
|
|
||||||
|
|
||||||
private val episodeHolder = itemView.episode_holder
|
|
||||||
|
|
||||||
|
var episodeDownloadBar: ContentLoadingProgressBar? = null
|
||||||
|
var episodeDownloadImage: ImageView? = null
|
||||||
var localCard: ResultEpisode? = null
|
var localCard: ResultEpisode? = null
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
fun bind(card: ResultEpisode) {
|
fun bind(card: ResultEpisode) {
|
||||||
localCard = card
|
localCard = card
|
||||||
|
|
||||||
val name = if (card.name == null) "${episodeText.context.getString(R.string.episode)} ${card.episode}" else "${card.episode}. ${card.name}"
|
val (parentView,otherView) = if(card.poster == null) {
|
||||||
|
itemView.episode_holder to itemView.episode_holder_large
|
||||||
|
} else {
|
||||||
|
itemView.episode_holder_large to itemView.episode_holder
|
||||||
|
}
|
||||||
|
parentView.isVisible = true
|
||||||
|
otherView.isVisible = false
|
||||||
|
|
||||||
|
val episodeText: TextView = parentView.episode_text
|
||||||
|
val episodeFiller: MaterialButton? = parentView.episode_filler
|
||||||
|
val episodeRating: TextView? = parentView.episode_rating
|
||||||
|
val episodeDescript: TextView? = parentView.episode_descript
|
||||||
|
val episodeProgress: ContentLoadingProgressBar? = parentView.episode_progress
|
||||||
|
val episodePoster: ImageView? = parentView.episode_poster
|
||||||
|
|
||||||
|
episodeDownloadBar =
|
||||||
|
parentView.result_episode_progress_downloaded
|
||||||
|
episodeDownloadImage = parentView.result_episode_download
|
||||||
|
|
||||||
|
val name =
|
||||||
|
if (card.name == null) "${episodeText.context.getString(R.string.episode)} ${card.episode}" else "${card.episode}. ${card.name}"
|
||||||
episodeFiller?.isVisible = card.isFiller == true
|
episodeFiller?.isVisible = card.isFiller == true
|
||||||
episodeText.text = name//if(card.isFiller == true) episodeText.context.getString(R.string.filler).format(name) else name
|
episodeText.text =
|
||||||
|
name//if(card.isFiller == true) episodeText.context.getString(R.string.filler).format(name) else name
|
||||||
episodeText.isSelected = true // is needed for text repeating
|
episodeText.isSelected = true // is needed for text repeating
|
||||||
|
|
||||||
val displayPos = card.getDisplayPosition()
|
val displayPos = card.getDisplayPosition()
|
||||||
|
@ -171,16 +184,20 @@ class EpisodeAdapter(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (card.rating != null) {
|
if (card.rating != null) {
|
||||||
episodeRating?.text = episodeRating?.context?.getString(R.string.rated_format)?.format(card.rating.toFloat() / 10f)
|
episodeRating?.text = episodeRating?.context?.getString(R.string.rated_format)
|
||||||
|
?.format(card.rating.toFloat() / 10f)
|
||||||
} else {
|
} else {
|
||||||
episodeRating?.text = ""
|
episodeRating?.text = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
if (card.description != null) {
|
episodeRating?.isGone = episodeRating?.text.isNullOrBlank()
|
||||||
episodeDescript?.visibility = View.VISIBLE
|
|
||||||
episodeDescript?.text = card.description
|
episodeDescript?.apply {
|
||||||
} else {
|
text = card.description ?: ""
|
||||||
episodeDescript?.visibility = View.GONE
|
isGone = text.isNullOrBlank()
|
||||||
|
setOnClickListener {
|
||||||
|
clickCallback.invoke(EpisodeClickEvent(ACTION_SHOW_DESCRIPTION, card))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
episodePoster?.setOnClickListener {
|
episodePoster?.setOnClickListener {
|
||||||
|
@ -192,34 +209,42 @@ class EpisodeAdapter(
|
||||||
return@setOnLongClickListener true
|
return@setOnLongClickListener true
|
||||||
}
|
}
|
||||||
|
|
||||||
episodeHolder.setOnClickListener {
|
parentView.setOnClickListener {
|
||||||
clickCallback.invoke(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card))
|
clickCallback.invoke(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (episodeHolder.context.isTrueTvSettings()) {
|
if (parentView.context.isTrueTvSettings()) {
|
||||||
episodeHolder.isFocusable = true
|
parentView.isFocusable = true
|
||||||
episodeHolder.isFocusableInTouchMode = true
|
parentView.isFocusableInTouchMode = true
|
||||||
episodeHolder.touchscreenBlocksFocus = false
|
parentView.touchscreenBlocksFocus = false
|
||||||
}
|
}
|
||||||
|
|
||||||
episodeHolder.setOnLongClickListener {
|
parentView.setOnLongClickListener {
|
||||||
clickCallback.invoke(EpisodeClickEvent(ACTION_SHOW_OPTIONS, card))
|
clickCallback.invoke(EpisodeClickEvent(ACTION_SHOW_OPTIONS, card))
|
||||||
|
|
||||||
return@setOnLongClickListener true
|
return@setOnLongClickListener true
|
||||||
}
|
}
|
||||||
|
|
||||||
episodeDownloadImage.isVisible = hasDownloadSupport
|
episodeDownloadImage?.isVisible = hasDownloadSupport
|
||||||
episodeDownloadBar.isVisible = hasDownloadSupport
|
episodeDownloadBar?.isVisible = hasDownloadSupport
|
||||||
|
reattachDownloadButton()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun reattachDownloadButton() {
|
override fun reattachDownloadButton() {
|
||||||
downloadButton.dispose()
|
downloadButton.dispose()
|
||||||
val card = localCard
|
val card = localCard
|
||||||
if (hasDownloadSupport && card != null) {
|
if (hasDownloadSupport && card != null) {
|
||||||
val downloadInfo = VideoDownloadManager.getDownloadFileInfoAndUpdateSettings(itemView.context, card.id)
|
val downloadInfo = VideoDownloadManager.getDownloadFileInfoAndUpdateSettings(
|
||||||
|
itemView.context,
|
||||||
|
card.id
|
||||||
|
)
|
||||||
|
|
||||||
downloadButton.setUpButton(
|
downloadButton.setUpButton(
|
||||||
downloadInfo?.fileLength, downloadInfo?.totalBytes, episodeDownloadBar, episodeDownloadImage, null,
|
downloadInfo?.fileLength,
|
||||||
|
downloadInfo?.totalBytes,
|
||||||
|
episodeDownloadBar ?: return,
|
||||||
|
episodeDownloadImage ?: return,
|
||||||
|
null,
|
||||||
VideoDownloadHelper.DownloadEpisodeCached(
|
VideoDownloadHelper.DownloadEpisodeCached(
|
||||||
card.name,
|
card.name,
|
||||||
card.poster,
|
card.poster,
|
||||||
|
|
|
@ -41,6 +41,7 @@ import com.google.android.material.button.MaterialButton
|
||||||
import com.lagradost.cloudstream3.*
|
import com.lagradost.cloudstream3.*
|
||||||
import com.lagradost.cloudstream3.APIHolder.getApiFromName
|
import com.lagradost.cloudstream3.APIHolder.getApiFromName
|
||||||
import com.lagradost.cloudstream3.APIHolder.getId
|
import com.lagradost.cloudstream3.APIHolder.getId
|
||||||
|
import com.lagradost.cloudstream3.APIHolder.updateHasTrailers
|
||||||
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
||||||
import com.lagradost.cloudstream3.CommonActivity.getCastSession
|
import com.lagradost.cloudstream3.CommonActivity.getCastSession
|
||||||
import com.lagradost.cloudstream3.CommonActivity.showToast
|
import com.lagradost.cloudstream3.CommonActivity.showToast
|
||||||
|
@ -50,6 +51,7 @@ import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_DOWNLOAD
|
||||||
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_NAVIGATE_TO
|
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_NAVIGATE_TO
|
||||||
import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick
|
import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick
|
||||||
import com.lagradost.cloudstream3.ui.download.EasyDownloadButton
|
import com.lagradost.cloudstream3.ui.download.EasyDownloadButton
|
||||||
|
import com.lagradost.cloudstream3.ui.player.CSPlayerEvent
|
||||||
import com.lagradost.cloudstream3.ui.player.GeneratorPlayer
|
import com.lagradost.cloudstream3.ui.player.GeneratorPlayer
|
||||||
import com.lagradost.cloudstream3.ui.player.RepoLinkGenerator
|
import com.lagradost.cloudstream3.ui.player.RepoLinkGenerator
|
||||||
import com.lagradost.cloudstream3.ui.player.SubtitleData
|
import com.lagradost.cloudstream3.ui.player.SubtitleData
|
||||||
|
@ -88,8 +90,10 @@ import com.lagradost.cloudstream3.utils.VideoDownloadManager.getFileName
|
||||||
import com.lagradost.cloudstream3.utils.VideoDownloadManager.sanitizeFilename
|
import com.lagradost.cloudstream3.utils.VideoDownloadManager.sanitizeFilename
|
||||||
import kotlinx.android.synthetic.main.fragment_result.*
|
import kotlinx.android.synthetic.main.fragment_result.*
|
||||||
import kotlinx.android.synthetic.main.fragment_result_swipe.*
|
import kotlinx.android.synthetic.main.fragment_result_swipe.*
|
||||||
|
import kotlinx.android.synthetic.main.fragment_trailer.*
|
||||||
import kotlinx.android.synthetic.main.result_recommendations.*
|
import kotlinx.android.synthetic.main.result_recommendations.*
|
||||||
import kotlinx.android.synthetic.main.result_sync.*
|
import kotlinx.android.synthetic.main.result_sync.*
|
||||||
|
import kotlinx.android.synthetic.main.trailer_custom_layout.*
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
@ -599,7 +603,7 @@ class ResultFragment : ResultTrailerPlayer() {
|
||||||
setFormatText(result_meta_rating, R.string.rating_format, rating?.div(1000f))
|
setFormatText(result_meta_rating, R.string.rating_format, rating?.div(1000f))
|
||||||
}
|
}
|
||||||
|
|
||||||
var currentTrailers: List<String> = emptyList()
|
var currentTrailers: List<ExtractorLink> = emptyList()
|
||||||
var currentTrailerIndex = 0
|
var currentTrailerIndex = 0
|
||||||
|
|
||||||
override fun nextMirror() {
|
override fun nextMirror() {
|
||||||
|
@ -608,49 +612,44 @@ class ResultFragment : ResultTrailerPlayer() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun playerError(exception: Exception) {
|
override fun playerError(exception: Exception) {
|
||||||
if (player.getIsPlaying()) // because we dont want random toasts in player
|
if (player.getIsPlaying()) { // because we dont want random toasts in player
|
||||||
super.playerError(exception)
|
super.playerError(exception)
|
||||||
|
} else {
|
||||||
|
nextMirror()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadTrailer(index: Int? = null) {
|
private fun loadTrailer(index: Int? = null) {
|
||||||
|
val isSuccess =
|
||||||
currentTrailers.getOrNull(index ?: currentTrailerIndex)?.let { trailer ->
|
currentTrailers.getOrNull(index ?: currentTrailerIndex)?.let { trailer ->
|
||||||
//if(trailer.contains("youtube.com")) { // wont load in exo
|
|
||||||
// nextMirror()
|
|
||||||
// return
|
|
||||||
//}
|
|
||||||
context?.let { ctx ->
|
context?.let { ctx ->
|
||||||
player.onPause()
|
player.onPause()
|
||||||
player.loadPlayer(
|
player.loadPlayer(
|
||||||
ctx,
|
ctx,
|
||||||
false,
|
false,
|
||||||
ExtractorLink(
|
|
||||||
"",
|
|
||||||
"Trailer",
|
|
||||||
trailer,
|
trailer,
|
||||||
"",
|
|
||||||
Qualities.Unknown.value
|
|
||||||
),
|
|
||||||
null,
|
null,
|
||||||
startPosition = 0L,
|
startPosition = 0L,
|
||||||
subtitles = emptySet(),
|
subtitles = emptySet(),
|
||||||
subtitle = null,
|
subtitle = null,
|
||||||
autoPlay = false
|
autoPlay = false
|
||||||
)
|
)
|
||||||
|
true
|
||||||
|
} ?: run {
|
||||||
|
false
|
||||||
}
|
}
|
||||||
|
} ?: run {
|
||||||
|
false
|
||||||
}
|
}
|
||||||
|
result_trailer_loading?.isVisible = isSuccess
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setTrailers(trailers: List<String>?) {
|
private fun setTrailers(trailers: List<ExtractorLink>?) {
|
||||||
context?.let { ctx ->
|
context?.updateHasTrailers()
|
||||||
if (ctx.isTvSettings()) return
|
if (!LoadResponse.isTrailersEnabled) return
|
||||||
val settingsManager = PreferenceManager.getDefaultSharedPreferences(ctx)
|
currentTrailers = trailers?.sortedBy { -it.quality } ?: emptyList()
|
||||||
val showTrailers =
|
|
||||||
settingsManager.getBoolean(ctx.getString(R.string.show_trailers_key), true)
|
|
||||||
if (!showTrailers) return
|
|
||||||
currentTrailers = trailers ?: emptyList()
|
|
||||||
loadTrailer()
|
loadTrailer()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun setActors(actors: List<ActorData>?) {
|
private fun setActors(actors: List<ActorData>?) {
|
||||||
if (actors.isNullOrEmpty()) {
|
if (actors.isNullOrEmpty()) {
|
||||||
|
@ -758,6 +757,12 @@ class ResultFragment : ResultTrailerPlayer() {
|
||||||
result_overlapping_panels?.setStartPanelLockState(OverlappingPanelsLayout.LockState.CLOSE)
|
result_overlapping_panels?.setStartPanelLockState(OverlappingPanelsLayout.LockState.CLOSE)
|
||||||
result_overlapping_panels?.setEndPanelLockState(OverlappingPanelsLayout.LockState.CLOSE)
|
result_overlapping_panels?.setEndPanelLockState(OverlappingPanelsLayout.LockState.CLOSE)
|
||||||
|
|
||||||
|
player_open_source?.setOnClickListener {
|
||||||
|
currentTrailers.getOrNull(currentTrailerIndex)?.let {
|
||||||
|
context?.openBrowser(it.url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
updateUIListener = ::updateUI
|
updateUIListener = ::updateUI
|
||||||
|
|
||||||
val restart = arguments?.getBoolean(RESTART_BUNDLE) ?: false
|
val restart = arguments?.getBoolean(RESTART_BUNDLE) ?: false
|
||||||
|
@ -767,6 +772,7 @@ class ResultFragment : ResultTrailerPlayer() {
|
||||||
|
|
||||||
activity?.window?.decorView?.clearFocus()
|
activity?.window?.decorView?.clearFocus()
|
||||||
hideKeyboard()
|
hideKeyboard()
|
||||||
|
context?.updateHasTrailers()
|
||||||
activity?.loadCache()
|
activity?.loadCache()
|
||||||
|
|
||||||
activity?.fixPaddingStatusbar(result_top_bar)
|
activity?.fixPaddingStatusbar(result_top_bar)
|
||||||
|
@ -828,6 +834,12 @@ class ResultFragment : ResultTrailerPlayer() {
|
||||||
} else if (dy < -5) {
|
} else if (dy < -5) {
|
||||||
result_bookmark_fab?.extend()
|
result_bookmark_fab?.extend()
|
||||||
}
|
}
|
||||||
|
if (!isFullScreenPlayer && player.getIsPlaying()) {
|
||||||
|
if (scrollY > (player_background?.height ?: scrollY)) {
|
||||||
|
player.handleEvent(CSPlayerEvent.Pause)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//result_poster_blur_holder?.translationY = -scrollY.toFloat()
|
//result_poster_blur_holder?.translationY = -scrollY.toFloat()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -975,6 +987,7 @@ class ResultFragment : ResultTrailerPlayer() {
|
||||||
}
|
}
|
||||||
ACTION_CHROME_CAST_EPISODE -> requireLinks(true)
|
ACTION_CHROME_CAST_EPISODE -> requireLinks(true)
|
||||||
ACTION_CHROME_CAST_MIRROR -> requireLinks(true)
|
ACTION_CHROME_CAST_MIRROR -> requireLinks(true)
|
||||||
|
ACTION_SHOW_DESCRIPTION -> true
|
||||||
else -> requireLinks(false)
|
else -> requireLinks(false)
|
||||||
}
|
}
|
||||||
if (!isLoaded) return@main // CANT LOAD
|
if (!isLoaded) return@main // CANT LOAD
|
||||||
|
@ -984,6 +997,14 @@ class ResultFragment : ResultTrailerPlayer() {
|
||||||
showToast(activity, R.string.play_episode_toast, Toast.LENGTH_SHORT)
|
showToast(activity, R.string.play_episode_toast, Toast.LENGTH_SHORT)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ACTION_SHOW_DESCRIPTION -> {
|
||||||
|
val builder: AlertDialog.Builder =
|
||||||
|
AlertDialog.Builder(requireContext(), R.style.AlertDialogCustom)
|
||||||
|
builder.setMessage(episodeClick.data.description ?: return@main)
|
||||||
|
.setTitle(R.string.torrent_plot)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
ACTION_CLICK_DEFAULT -> {
|
ACTION_CLICK_DEFAULT -> {
|
||||||
context?.let { ctx ->
|
context?.let { ctx ->
|
||||||
if (ctx.isConnectedToChromecast()) {
|
if (ctx.isConnectedToChromecast()) {
|
||||||
|
@ -1431,7 +1452,7 @@ class ResultFragment : ResultTrailerPlayer() {
|
||||||
val d = meta.value
|
val d = meta.value
|
||||||
result_sync_episodes?.progress = currentSyncProgress * 1000
|
result_sync_episodes?.progress = currentSyncProgress * 1000
|
||||||
setSyncMaxEpisodes(d.totalEpisodes)
|
setSyncMaxEpisodes(d.totalEpisodes)
|
||||||
viewModel.setMeta(d)
|
viewModel.setMeta(d, syncdata)
|
||||||
}
|
}
|
||||||
is Resource.Loading -> {
|
is Resource.Loading -> {
|
||||||
result_sync_max_episodes?.text =
|
result_sync_max_episodes?.text =
|
||||||
|
|
|
@ -53,6 +53,7 @@ open class ResultTrailerPlayer : com.lagradost.cloudstream3.ui.player.FullScreen
|
||||||
screenHeight
|
screenHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result_trailer_loading?.isVisible = false
|
||||||
player_background?.apply {
|
player_background?.apply {
|
||||||
isVisible = true
|
isVisible = true
|
||||||
layoutParams =
|
layoutParams =
|
||||||
|
|
|
@ -13,17 +13,22 @@ import com.lagradost.cloudstream3.APIHolder.getId
|
||||||
import com.lagradost.cloudstream3.AcraApplication.Companion.context
|
import com.lagradost.cloudstream3.AcraApplication.Companion.context
|
||||||
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
||||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
|
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
|
||||||
|
import com.lagradost.cloudstream3.LoadResponse.Companion.getAniListId
|
||||||
|
import com.lagradost.cloudstream3.LoadResponse.Companion.getMalId
|
||||||
import com.lagradost.cloudstream3.animeproviders.GogoanimeProvider
|
import com.lagradost.cloudstream3.animeproviders.GogoanimeProvider
|
||||||
import com.lagradost.cloudstream3.animeproviders.NineAnimeProvider
|
import com.lagradost.cloudstream3.animeproviders.NineAnimeProvider
|
||||||
import com.lagradost.cloudstream3.metaproviders.SyncRedirector
|
import com.lagradost.cloudstream3.metaproviders.SyncRedirector
|
||||||
import com.lagradost.cloudstream3.mvvm.Resource
|
import com.lagradost.cloudstream3.mvvm.Resource
|
||||||
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
import com.lagradost.cloudstream3.mvvm.safeApiCall
|
import com.lagradost.cloudstream3.mvvm.safeApiCall
|
||||||
import com.lagradost.cloudstream3.syncproviders.SyncAPI
|
import com.lagradost.cloudstream3.syncproviders.SyncAPI
|
||||||
|
import com.lagradost.cloudstream3.syncproviders.providers.Kitsu.getEpisodesDetails
|
||||||
import com.lagradost.cloudstream3.ui.APIRepository
|
import com.lagradost.cloudstream3.ui.APIRepository
|
||||||
import com.lagradost.cloudstream3.ui.WatchType
|
import com.lagradost.cloudstream3.ui.WatchType
|
||||||
import com.lagradost.cloudstream3.ui.player.IGenerator
|
import com.lagradost.cloudstream3.ui.player.IGenerator
|
||||||
import com.lagradost.cloudstream3.ui.player.RepoLinkGenerator
|
import com.lagradost.cloudstream3.ui.player.RepoLinkGenerator
|
||||||
import com.lagradost.cloudstream3.ui.player.SubtitleData
|
import com.lagradost.cloudstream3.ui.player.SubtitleData
|
||||||
|
import com.lagradost.cloudstream3.utils.Coroutines.ioWork
|
||||||
import com.lagradost.cloudstream3.utils.DOWNLOAD_HEADER_CACHE
|
import com.lagradost.cloudstream3.utils.DOWNLOAD_HEADER_CACHE
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper
|
import com.lagradost.cloudstream3.utils.DataStoreHelper
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getBookmarkedData
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.getBookmarkedData
|
||||||
|
@ -70,7 +75,6 @@ class ResultViewModel : ViewModel() {
|
||||||
val dubStatus: LiveData<DubStatus> get() = _dubStatus
|
val dubStatus: LiveData<DubStatus> get() = _dubStatus
|
||||||
private val _dubStatus: MutableLiveData<DubStatus> = MutableLiveData()
|
private val _dubStatus: MutableLiveData<DubStatus> = MutableLiveData()
|
||||||
|
|
||||||
private val page: MutableLiveData<LoadResponse> = MutableLiveData()
|
|
||||||
val id: MutableLiveData<Int> = MutableLiveData()
|
val id: MutableLiveData<Int> = MutableLiveData()
|
||||||
val selectedSeason: MutableLiveData<Int> = MutableLiveData(-2)
|
val selectedSeason: MutableLiveData<Int> = MutableLiveData(-2)
|
||||||
val seasonSelections: MutableLiveData<List<Int?>> = MutableLiveData()
|
val seasonSelections: MutableLiveData<List<Int?>> = MutableLiveData()
|
||||||
|
@ -88,11 +92,12 @@ class ResultViewModel : ViewModel() {
|
||||||
fun updateWatchStatus(status: WatchType) = viewModelScope.launch {
|
fun updateWatchStatus(status: WatchType) = viewModelScope.launch {
|
||||||
val currentId = id.value ?: return@launch
|
val currentId = id.value ?: return@launch
|
||||||
_watchStatus.postValue(status)
|
_watchStatus.postValue(status)
|
||||||
val resultPage = page.value
|
val resultPage = _resultResponse.value
|
||||||
|
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
setResultWatchState(currentId, status.internalId)
|
setResultWatchState(currentId, status.internalId)
|
||||||
if (resultPage != null) {
|
if (resultPage != null && resultPage is Resource.Success) {
|
||||||
|
val resultPageData = resultPage.value
|
||||||
val current = getBookmarkedData(currentId)
|
val current = getBookmarkedData(currentId)
|
||||||
val currentTime = System.currentTimeMillis()
|
val currentTime = System.currentTimeMillis()
|
||||||
setBookmarkedData(
|
setBookmarkedData(
|
||||||
|
@ -101,12 +106,12 @@ class ResultViewModel : ViewModel() {
|
||||||
currentId,
|
currentId,
|
||||||
current?.bookmarkedTime ?: currentTime,
|
current?.bookmarkedTime ?: currentTime,
|
||||||
currentTime,
|
currentTime,
|
||||||
resultPage.name,
|
resultPageData.name,
|
||||||
resultPage.url,
|
resultPageData.url,
|
||||||
resultPage.apiName,
|
resultPageData.apiName,
|
||||||
resultPage.type,
|
resultPageData.type,
|
||||||
resultPage.posterUrl,
|
resultPageData.posterUrl,
|
||||||
resultPage.year
|
resultPageData.year
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -118,20 +123,29 @@ class ResultViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
var lastMeta: SyncAPI.SyncResult? = null
|
var lastMeta: SyncAPI.SyncResult? = null
|
||||||
private fun applyMeta(resp: LoadResponse, meta: SyncAPI.SyncResult?): LoadResponse {
|
var lastSync: Map<String, String>? = null
|
||||||
if (meta == null) return resp
|
|
||||||
lastMeta = meta
|
private suspend fun applyMeta(
|
||||||
return resp.apply {
|
resp: LoadResponse,
|
||||||
|
meta: SyncAPI.SyncResult?,
|
||||||
|
syncs: Map<String, String>? = null
|
||||||
|
): Pair<LoadResponse, Boolean> {
|
||||||
|
if (meta == null) return resp to false
|
||||||
|
var updateEpisodes = false
|
||||||
|
val out = resp.apply {
|
||||||
Log.i(TAG, "applyMeta")
|
Log.i(TAG, "applyMeta")
|
||||||
|
|
||||||
duration = duration ?: meta.duration
|
duration = duration ?: meta.duration
|
||||||
rating = rating ?: meta.publicScore
|
rating = rating ?: meta.publicScore
|
||||||
tags = tags ?: meta.genres
|
tags = tags ?: meta.genres
|
||||||
plot = if (plot.isNullOrBlank()) meta.synopsis else plot
|
plot = if (plot.isNullOrBlank()) meta.synopsis else plot
|
||||||
addTrailer(meta.trailerUrl)
|
|
||||||
posterUrl = posterUrl ?: meta.posterUrl ?: meta.backgroundPosterUrl
|
posterUrl = posterUrl ?: meta.posterUrl ?: meta.backgroundPosterUrl
|
||||||
actors = actors ?: meta.actors
|
actors = actors ?: meta.actors
|
||||||
|
|
||||||
|
for ((k, v) in syncs ?: emptyMap()) {
|
||||||
|
syncData[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
val realRecommendations = ArrayList<SearchResponse>()
|
val realRecommendations = ArrayList<SearchResponse>()
|
||||||
val apiNames = listOf(GogoanimeProvider().name, NineAnimeProvider().name)
|
val apiNames = listOf(GogoanimeProvider().name, NineAnimeProvider().name)
|
||||||
meta.recommendations?.forEach { rec ->
|
meta.recommendations?.forEach { rec ->
|
||||||
|
@ -142,14 +156,54 @@ class ResultViewModel : ViewModel() {
|
||||||
|
|
||||||
recommendations = recommendations?.union(realRecommendations)?.toList()
|
recommendations = recommendations?.union(realRecommendations)?.toList()
|
||||||
?: realRecommendations
|
?: realRecommendations
|
||||||
|
|
||||||
|
argamap({
|
||||||
|
addTrailer(meta.trailers)
|
||||||
|
}, {
|
||||||
|
if (this !is AnimeLoadResponse) return@argamap
|
||||||
|
val map = getEpisodesDetails(getMalId(), getAniListId())
|
||||||
|
if (map.isNullOrEmpty()) return@argamap
|
||||||
|
updateEpisodes = DubStatus.values().map { dubStatus ->
|
||||||
|
val current =
|
||||||
|
this.episodes[dubStatus]?.sortedBy { it.episode ?: 0 }?.toMutableList()
|
||||||
|
if (current.isNullOrEmpty()) return@map false
|
||||||
|
val episodes = current.mapIndexed { index, ep -> ep.episode ?: (index + 1) }
|
||||||
|
var updateCount = 0
|
||||||
|
map.forEach { (episode, node) ->
|
||||||
|
episodes.binarySearch(episode).let { index ->
|
||||||
|
current.getOrNull(index)?.let { currentEp ->
|
||||||
|
current[index] = currentEp.apply {
|
||||||
|
updateCount++
|
||||||
|
this.description = this.description ?: node.description?.en
|
||||||
|
this.name = this.name ?: node.titles?.canonical
|
||||||
|
this.episode = this.episode ?: node.num ?: episodes[index]
|
||||||
|
this.posterUrl = this.posterUrl ?: node.thumbnail?.original?.url
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.episodes[dubStatus] = current
|
||||||
|
|
||||||
|
updateCount > 0
|
||||||
|
}.any { it }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return out to updateEpisodes
|
||||||
|
}
|
||||||
|
|
||||||
fun setMeta(meta: SyncAPI.SyncResult) {
|
fun setMeta(meta: SyncAPI.SyncResult, syncs: Map<String, String>?) =
|
||||||
|
viewModelScope.launch {
|
||||||
Log.i(TAG, "setMeta")
|
Log.i(TAG, "setMeta")
|
||||||
|
lastMeta = meta
|
||||||
|
lastSync = syncs
|
||||||
|
val (value, updateEpisodes) = ioWork {
|
||||||
(result.value as? Resource.Success<LoadResponse>?)?.value?.let { resp ->
|
(result.value as? Resource.Success<LoadResponse>?)?.value?.let { resp ->
|
||||||
_resultResponse.postValue(Resource.Success(applyMeta(resp, meta)))
|
return@ioWork applyMeta(resp, meta, syncs)
|
||||||
}
|
}
|
||||||
|
return@ioWork null to null
|
||||||
|
}
|
||||||
|
_resultResponse.postValue(Resource.Success(value ?: return@launch))
|
||||||
|
if (updateEpisodes ?: return@launch) updateEpisodes(value, lastShowFillers)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadWatchStatus(localId: Int? = null) {
|
private fun loadWatchStatus(localId: Int? = null) {
|
||||||
|
@ -310,77 +364,18 @@ class ResultViewModel : ViewModel() {
|
||||||
return name
|
return name
|
||||||
}
|
}
|
||||||
|
|
||||||
fun load(url: String, apiName: String, showFillers: Boolean) = viewModelScope.launch {
|
var lastShowFillers = false
|
||||||
_publicEpisodes.postValue(Resource.Loading())
|
private suspend fun updateEpisodes(loadResponse: LoadResponse, showFillers: Boolean) {
|
||||||
_resultResponse.postValue(Resource.Loading(url))
|
Log.i(TAG, "updateEpisodes")
|
||||||
|
try {
|
||||||
val api = getApiFromNameNull(apiName) ?: getApiFromUrlNull(url)
|
lastShowFillers = showFillers
|
||||||
if (api == null) {
|
|
||||||
_resultResponse.postValue(
|
|
||||||
Resource.Failure(
|
|
||||||
false,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
"This provider does not exist"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
|
|
||||||
val validUrlResource = safeApiCall {
|
|
||||||
SyncRedirector.redirect(
|
|
||||||
url,
|
|
||||||
api.mainUrl.replace(NineAnimeProvider().mainUrl, "9anime")
|
|
||||||
.replace(GogoanimeProvider().mainUrl, "gogoanime")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (validUrlResource !is Resource.Success) {
|
|
||||||
if (validUrlResource is Resource.Failure) {
|
|
||||||
_resultResponse.postValue(validUrlResource)
|
|
||||||
}
|
|
||||||
|
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
val validUrl = validUrlResource.value
|
|
||||||
|
|
||||||
_resultResponse.postValue(Resource.Loading(validUrl))
|
|
||||||
|
|
||||||
_apiName.postValue(apiName)
|
|
||||||
|
|
||||||
repo = APIRepository(api)
|
|
||||||
|
|
||||||
val data = repo?.load(validUrl) ?: return@launch
|
|
||||||
|
|
||||||
_resultResponse.postValue(data)
|
|
||||||
|
|
||||||
when (data) {
|
|
||||||
is Resource.Success -> {
|
|
||||||
val loadResponse = applyMeta(data.value, lastMeta)
|
|
||||||
page.postValue(loadResponse)
|
|
||||||
val mainId = loadResponse.getId()
|
val mainId = loadResponse.getId()
|
||||||
id.postValue(mainId)
|
|
||||||
loadWatchStatus(mainId)
|
|
||||||
|
|
||||||
setKey(
|
|
||||||
DOWNLOAD_HEADER_CACHE,
|
|
||||||
mainId.toString(),
|
|
||||||
VideoDownloadHelper.DownloadHeaderCached(
|
|
||||||
apiName,
|
|
||||||
validUrl,
|
|
||||||
loadResponse.type,
|
|
||||||
loadResponse.name,
|
|
||||||
loadResponse.posterUrl,
|
|
||||||
mainId,
|
|
||||||
System.currentTimeMillis(),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
when (loadResponse) {
|
when (loadResponse) {
|
||||||
is AnimeLoadResponse -> {
|
is AnimeLoadResponse -> {
|
||||||
if (loadResponse.episodes.isEmpty()) {
|
if (loadResponse.episodes.isEmpty()) {
|
||||||
_dubSubEpisodes.postValue(emptyMap())
|
_dubSubEpisodes.postValue(emptyMap())
|
||||||
return@launch
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// val status = getDub(mainId)
|
// val status = getDub(mainId)
|
||||||
|
@ -415,7 +410,7 @@ class ResultViewModel : ViewModel() {
|
||||||
episode,
|
episode,
|
||||||
i.season,
|
i.season,
|
||||||
i.data,
|
i.data,
|
||||||
apiName,
|
loadResponse.apiName,
|
||||||
id,
|
id,
|
||||||
index,
|
index,
|
||||||
i.rating,
|
i.rating,
|
||||||
|
@ -460,7 +455,7 @@ class ResultViewModel : ViewModel() {
|
||||||
episodeIndex,
|
episodeIndex,
|
||||||
episode.season,
|
episode.season,
|
||||||
episode.data,
|
episode.data,
|
||||||
apiName,
|
loadResponse.apiName,
|
||||||
id,
|
id,
|
||||||
index,
|
index,
|
||||||
episode.rating,
|
episode.rating,
|
||||||
|
@ -517,6 +512,79 @@ class ResultViewModel : ViewModel() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logError(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun load(url: String, apiName: String, showFillers: Boolean) = viewModelScope.launch {
|
||||||
|
_publicEpisodes.postValue(Resource.Loading())
|
||||||
|
_resultResponse.postValue(Resource.Loading(url))
|
||||||
|
|
||||||
|
val api = getApiFromNameNull(apiName) ?: getApiFromUrlNull(url)
|
||||||
|
if (api == null) {
|
||||||
|
_resultResponse.postValue(
|
||||||
|
Resource.Failure(
|
||||||
|
false,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
"This provider does not exist"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
|
||||||
|
val validUrlResource = safeApiCall {
|
||||||
|
SyncRedirector.redirect(
|
||||||
|
url,
|
||||||
|
api.mainUrl.replace(NineAnimeProvider().mainUrl, "9anime")
|
||||||
|
.replace(GogoanimeProvider().mainUrl, "gogoanime")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validUrlResource !is Resource.Success) {
|
||||||
|
if (validUrlResource is Resource.Failure) {
|
||||||
|
_resultResponse.postValue(validUrlResource)
|
||||||
|
}
|
||||||
|
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
val validUrl = validUrlResource.value
|
||||||
|
|
||||||
|
_resultResponse.postValue(Resource.Loading(validUrl))
|
||||||
|
|
||||||
|
_apiName.postValue(apiName)
|
||||||
|
|
||||||
|
repo = APIRepository(api)
|
||||||
|
|
||||||
|
val data = repo?.load(validUrl) ?: return@launch
|
||||||
|
|
||||||
|
_resultResponse.postValue(data)
|
||||||
|
|
||||||
|
when (data) {
|
||||||
|
is Resource.Success -> {
|
||||||
|
val loadResponse = if (lastMeta != null || lastSync != null) ioWork {
|
||||||
|
applyMeta(data.value, lastMeta, lastSync).first
|
||||||
|
} else data.value
|
||||||
|
_resultResponse.postValue(Resource.Success(loadResponse))
|
||||||
|
val mainId = loadResponse.getId()
|
||||||
|
id.postValue(mainId)
|
||||||
|
loadWatchStatus(mainId)
|
||||||
|
|
||||||
|
setKey(
|
||||||
|
DOWNLOAD_HEADER_CACHE,
|
||||||
|
mainId.toString(),
|
||||||
|
VideoDownloadHelper.DownloadHeaderCached(
|
||||||
|
apiName,
|
||||||
|
validUrl,
|
||||||
|
loadResponse.type,
|
||||||
|
loadResponse.name,
|
||||||
|
loadResponse.posterUrl,
|
||||||
|
mainId,
|
||||||
|
System.currentTimeMillis(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
updateEpisodes(loadResponse, showFillers)
|
||||||
}
|
}
|
||||||
else -> Unit
|
else -> Unit
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,12 +6,18 @@ import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.preference.PreferenceFragmentCompat
|
import androidx.preference.PreferenceFragmentCompat
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
import com.hippo.unifile.UniFile
|
import com.hippo.unifile.UniFile
|
||||||
|
import com.lagradost.cloudstream3.APIHolder.allProviders
|
||||||
import com.lagradost.cloudstream3.AcraApplication
|
import com.lagradost.cloudstream3.AcraApplication
|
||||||
|
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
|
||||||
|
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
||||||
|
import com.lagradost.cloudstream3.CommonActivity.showToast
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.app
|
import com.lagradost.cloudstream3.app
|
||||||
import com.lagradost.cloudstream3.mvvm.logError
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
|
@ -20,9 +26,15 @@ import com.lagradost.cloudstream3.network.initClient
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar
|
||||||
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
|
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
|
||||||
|
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog
|
||||||
|
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showMultiDialog
|
||||||
|
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
|
import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
|
||||||
|
import com.lagradost.cloudstream3.utils.USER_PROVIDER_API
|
||||||
import com.lagradost.cloudstream3.utils.VideoDownloadManager
|
import com.lagradost.cloudstream3.utils.VideoDownloadManager
|
||||||
import com.lagradost.cloudstream3.utils.VideoDownloadManager.getBasePath
|
import com.lagradost.cloudstream3.utils.VideoDownloadManager.getBasePath
|
||||||
|
import kotlinx.android.synthetic.main.add_remove_sites.*
|
||||||
|
import kotlinx.android.synthetic.main.add_site_input.*
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
class SettingsGeneral : PreferenceFragmentCompat() {
|
class SettingsGeneral : PreferenceFragmentCompat() {
|
||||||
|
@ -31,6 +43,17 @@ class SettingsGeneral : PreferenceFragmentCompat() {
|
||||||
setUpToolbar(R.string.category_general)
|
setUpToolbar(R.string.category_general)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class CustomSite(
|
||||||
|
@JsonProperty("parentJavaClass") // javaClass.simpleName
|
||||||
|
val parentJavaClass: String,
|
||||||
|
@JsonProperty("name")
|
||||||
|
val name: String,
|
||||||
|
@JsonProperty("url")
|
||||||
|
val url: String,
|
||||||
|
@JsonProperty("lang")
|
||||||
|
val lang: String,
|
||||||
|
)
|
||||||
|
|
||||||
// Open file picker
|
// Open file picker
|
||||||
private val pathPicker =
|
private val pathPicker =
|
||||||
registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { uri ->
|
registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { uri ->
|
||||||
|
@ -64,6 +87,94 @@ class SettingsGeneral : PreferenceFragmentCompat() {
|
||||||
setPreferencesFromResource(R.xml.settins_general, rootKey)
|
setPreferencesFromResource(R.xml.settins_general, rootKey)
|
||||||
val settingsManager = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
val settingsManager = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
|
|
||||||
|
fun getCurrent(): MutableList<CustomSite> {
|
||||||
|
return getKey<Array<CustomSite>>(USER_PROVIDER_API)?.toMutableList()
|
||||||
|
?: mutableListOf()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showAdd() {
|
||||||
|
val providers = allProviders.distinctBy { it.javaClass }.sortedBy { it.name }
|
||||||
|
activity?.showDialog(
|
||||||
|
providers.map { "${it.name} (${it.mainUrl})" },
|
||||||
|
-1,
|
||||||
|
context?.getString(R.string.add_site_pref) ?: return,
|
||||||
|
true,
|
||||||
|
{}) { selection ->
|
||||||
|
val provider = providers.getOrNull(selection) ?: return@showDialog
|
||||||
|
|
||||||
|
val builder =
|
||||||
|
AlertDialog.Builder(context ?: return@showDialog, R.style.AlertDialogCustom)
|
||||||
|
.setView(R.layout.add_site_input)
|
||||||
|
|
||||||
|
val dialog = builder.create()
|
||||||
|
dialog.show()
|
||||||
|
|
||||||
|
dialog.text2?.text = provider.name
|
||||||
|
dialog.apply_btt?.setOnClickListener {
|
||||||
|
val name = dialog.site_name_input?.text?.toString()
|
||||||
|
val url = dialog.site_url_input?.text?.toString()
|
||||||
|
val lang = dialog.site_lang_input?.text?.toString()
|
||||||
|
val realLang = if (lang.isNullOrBlank()) provider.lang else lang
|
||||||
|
if (url.isNullOrBlank() || name.isNullOrBlank() || realLang.length != 2) {
|
||||||
|
showToast(activity, R.string.error_invalid_data, Toast.LENGTH_SHORT)
|
||||||
|
return@setOnClickListener
|
||||||
|
}
|
||||||
|
|
||||||
|
val current = getCurrent()
|
||||||
|
val newSite = CustomSite(provider.javaClass.simpleName, name, url, realLang)
|
||||||
|
current.add(newSite)
|
||||||
|
setKey(USER_PROVIDER_API, current.toTypedArray())
|
||||||
|
|
||||||
|
dialog.dismissSafe(activity)
|
||||||
|
}
|
||||||
|
dialog.cancel_btt?.setOnClickListener {
|
||||||
|
dialog.dismissSafe(activity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showDelete() {
|
||||||
|
val current = getCurrent()
|
||||||
|
|
||||||
|
activity?.showMultiDialog(
|
||||||
|
current.map { it.name },
|
||||||
|
listOf(),
|
||||||
|
context?.getString(R.string.remove_site_pref) ?: return,
|
||||||
|
{}) { indexes ->
|
||||||
|
current.removeAll(indexes.map { current[it] })
|
||||||
|
setKey(USER_PROVIDER_API, current.toTypedArray())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showAddOrDelete() {
|
||||||
|
val builder =
|
||||||
|
AlertDialog.Builder(context ?: return, R.style.AlertDialogCustom)
|
||||||
|
.setView(R.layout.add_remove_sites)
|
||||||
|
|
||||||
|
val dialog = builder.create()
|
||||||
|
dialog.show()
|
||||||
|
|
||||||
|
dialog.add_site?.setOnClickListener {
|
||||||
|
showAdd()
|
||||||
|
dialog.dismissSafe(activity)
|
||||||
|
}
|
||||||
|
dialog.remove_site?.setOnClickListener {
|
||||||
|
showDelete()
|
||||||
|
dialog.dismissSafe(activity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getPref(R.string.override_site_key)?.setOnPreferenceClickListener { _ ->
|
||||||
|
|
||||||
|
if (getCurrent().isEmpty()) {
|
||||||
|
showAdd()
|
||||||
|
} else {
|
||||||
|
showAddOrDelete()
|
||||||
|
}
|
||||||
|
|
||||||
|
return@setOnPreferenceClickListener true
|
||||||
|
}
|
||||||
|
|
||||||
getPref(R.string.legal_notice_key)?.setOnPreferenceClickListener {
|
getPref(R.string.legal_notice_key)?.setOnPreferenceClickListener {
|
||||||
val builder: AlertDialog.Builder =
|
val builder: AlertDialog.Builder =
|
||||||
AlertDialog.Builder(it.context, R.style.AlertDialogCustom)
|
AlertDialog.Builder(it.context, R.style.AlertDialogCustom)
|
||||||
|
@ -177,6 +288,5 @@ class SettingsGeneral : PreferenceFragmentCompat() {
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -3,10 +3,7 @@ package com.lagradost.cloudstream3.utils
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import com.lagradost.cloudstream3.mvvm.logError
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
object Coroutines {
|
object Coroutines {
|
||||||
fun main(work: suspend (() -> Unit)): Job {
|
fun main(work: suspend (() -> Unit)): Job {
|
||||||
|
@ -25,6 +22,12 @@ object Coroutines {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun <T> ioWork(work: suspend (() -> T)): T {
|
||||||
|
return withContext(Dispatchers.IO) {
|
||||||
|
work()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun runOnMainThread(work: (() -> Unit)) {
|
fun runOnMainThread(work: (() -> Unit)) {
|
||||||
val mainHandler = Handler(Looper.getMainLooper())
|
val mainHandler = Handler(Looper.getMainLooper())
|
||||||
mainHandler.post {
|
mainHandler.post {
|
||||||
|
|
|
@ -14,6 +14,7 @@ const val DOWNLOAD_HEADER_CACHE = "download_header_cache"
|
||||||
const val DOWNLOAD_EPISODE_CACHE = "download_episode_cache"
|
const val DOWNLOAD_EPISODE_CACHE = "download_episode_cache"
|
||||||
const val VIDEO_PLAYER_BRIGHTNESS = "video_player_alpha_key"
|
const val VIDEO_PLAYER_BRIGHTNESS = "video_player_alpha_key"
|
||||||
const val HOMEPAGE_API = "home_api_used"
|
const val HOMEPAGE_API = "home_api_used"
|
||||||
|
const val USER_PROVIDER_API = "user_custom_sites"
|
||||||
|
|
||||||
const val PREFERENCES_NAME = "rebuild_preference"
|
const val PREFERENCES_NAME = "rebuild_preference"
|
||||||
|
|
||||||
|
|
|
@ -106,6 +106,19 @@ suspend fun loadExtractor(
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun loadExtractor(
|
||||||
|
url: String,
|
||||||
|
referer: String? = null,
|
||||||
|
): List<ExtractorLink> {
|
||||||
|
for (extractor in extractorApis) {
|
||||||
|
if (url.startsWith(extractor.mainUrl)) {
|
||||||
|
return extractor.getSafeUrl(url, referer) ?: emptyList()
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
val extractorApis: Array<ExtractorApi> = arrayOf(
|
val extractorApis: Array<ExtractorApi> = arrayOf(
|
||||||
//AllProvider(),
|
//AllProvider(),
|
||||||
WcoStream(),
|
WcoStream(),
|
||||||
|
@ -205,6 +218,9 @@ val extractorApis: Array<ExtractorApi> = arrayOf(
|
||||||
KotakAnimeid(),
|
KotakAnimeid(),
|
||||||
Neonime8n(),
|
Neonime8n(),
|
||||||
Neonime7n(),
|
Neonime7n(),
|
||||||
|
|
||||||
|
YoutubeExtractor(),
|
||||||
|
YoutubeShortLinkExtractor(),
|
||||||
)
|
)
|
||||||
|
|
||||||
fun getExtractorApiFromName(name: String): ExtractorApi {
|
fun getExtractorApiFromName(name: String): ExtractorApi {
|
||||||
|
|
|
@ -11,9 +11,11 @@ object FillerEpisodeCheck {
|
||||||
private const val MAIN_URL = "https://www.animefillerlist.com"
|
private const val MAIN_URL = "https://www.animefillerlist.com"
|
||||||
|
|
||||||
var list: HashMap<String, String>? = null
|
var list: HashMap<String, String>? = null
|
||||||
|
var cache: HashMap<String, HashMap<Int, Boolean>> = hashMapOf()
|
||||||
|
|
||||||
private fun fixName(name: String): String {
|
private fun fixName(name: String): String {
|
||||||
return name.lowercase(Locale.ROOT)/*.replace(" ", "")*/.replace("-", " ").replace("[^a-zA-Z0-9 ]".toRegex(), "")
|
return name.lowercase(Locale.ROOT)/*.replace(" ", "")*/.replace("-", " ")
|
||||||
|
.replace("[^a-zA-Z0-9 ]".toRegex(), "")
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun getFillerList(): Boolean {
|
private suspend fun getFillerList(): Boolean {
|
||||||
|
@ -61,6 +63,9 @@ object FillerEpisodeCheck {
|
||||||
|
|
||||||
suspend fun getFillerEpisodes(query: String): HashMap<Int, Boolean>? {
|
suspend fun getFillerEpisodes(query: String): HashMap<Int, Boolean>? {
|
||||||
try {
|
try {
|
||||||
|
cache[query]?.let {
|
||||||
|
return it
|
||||||
|
}
|
||||||
if (!getFillerList()) return null
|
if (!getFillerList()) return null
|
||||||
val localList = list ?: return null
|
val localList = list ?: return null
|
||||||
|
|
||||||
|
@ -75,9 +80,15 @@ object FillerEpisodeCheck {
|
||||||
"(\\d+)" // year
|
"(\\d+)" // year
|
||||||
)
|
)
|
||||||
val blackListRegex =
|
val blackListRegex =
|
||||||
Regex(""" (${blackList.joinToString(separator = "|").replace("(", "\\(").replace(")", "\\)")})""")
|
Regex(
|
||||||
|
""" (${
|
||||||
|
blackList.joinToString(separator = "|").replace("(", "\\(")
|
||||||
|
.replace(")", "\\)")
|
||||||
|
})"""
|
||||||
|
)
|
||||||
|
|
||||||
val realQuery = fixName(query.replace(blackListRegex, "")).replace("shippuuden", "shippuden")
|
val realQuery =
|
||||||
|
fixName(query.replace(blackListRegex, "")).replace("shippuuden", "shippuden")
|
||||||
if (!localList.containsKey(realQuery)) return null
|
if (!localList.containsKey(realQuery)) return null
|
||||||
val href = localList[realQuery]?.replace(MAIN_URL, "") ?: return null // JUST IN CASE
|
val href = localList[realQuery]?.replace(MAIN_URL, "") ?: return null // JUST IN CASE
|
||||||
val result = app.get("$MAIN_URL$href").text
|
val result = app.get("$MAIN_URL$href").text
|
||||||
|
@ -90,6 +101,7 @@ object FillerEpisodeCheck {
|
||||||
hashMap[episodeNumber] = type
|
hashMap[episodeNumber] = type
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
cache[query] = hashMap
|
||||||
return hashMap
|
return hashMap
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
|
|
|
@ -14,23 +14,33 @@ import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIconsAndNoStringRes
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
||||||
|
|
||||||
object SingleSelectionHelper {
|
object SingleSelectionHelper {
|
||||||
fun Activity.showOptionSelectStringRes(
|
fun Activity?.showOptionSelectStringRes(
|
||||||
view: View?,
|
view: View?,
|
||||||
poster: String?,
|
poster: String?,
|
||||||
options: List<Int>,
|
options: List<Int>,
|
||||||
tvOptions: List<Int> = listOf(),
|
tvOptions: List<Int> = listOf(),
|
||||||
callback: (Pair<Boolean, Int>) -> Unit
|
callback: (Pair<Boolean, Int>) -> Unit
|
||||||
) {
|
) {
|
||||||
this.showOptionSelect(view, poster, options.map { this.getString(it) },tvOptions.map { this.getString(it) }, callback)
|
if(this == null) return
|
||||||
|
|
||||||
|
this.showOptionSelect(
|
||||||
|
view,
|
||||||
|
poster,
|
||||||
|
options.map { this.getString(it) },
|
||||||
|
tvOptions.map { this.getString(it) },
|
||||||
|
callback
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Activity.showOptionSelect(
|
private fun Activity?.showOptionSelect(
|
||||||
view: View?,
|
view: View?,
|
||||||
poster: String?,
|
poster: String?,
|
||||||
options: List<String>,
|
options: List<String>,
|
||||||
tvOptions: List<String>,
|
tvOptions: List<String>,
|
||||||
callback: (Pair<Boolean, Int>) -> Unit
|
callback: (Pair<Boolean, Int>) -> Unit
|
||||||
) {
|
) {
|
||||||
|
if(this == null) return
|
||||||
|
|
||||||
if (this.isTvSettings()) {
|
if (this.isTvSettings()) {
|
||||||
val builder =
|
val builder =
|
||||||
AlertDialog.Builder(this, R.style.AlertDialogCustom)
|
AlertDialog.Builder(this, R.style.AlertDialogCustom)
|
||||||
|
@ -41,7 +51,8 @@ object SingleSelectionHelper {
|
||||||
|
|
||||||
dialog.findViewById<ListView>(R.id.listview1)?.let { listView ->
|
dialog.findViewById<ListView>(R.id.listview1)?.let { listView ->
|
||||||
listView.choiceMode = AbsListView.CHOICE_MODE_SINGLE
|
listView.choiceMode = AbsListView.CHOICE_MODE_SINGLE
|
||||||
listView.adapter = ArrayAdapter<String>(this, R.layout.sort_bottom_single_choice_color).apply {
|
listView.adapter =
|
||||||
|
ArrayAdapter<String>(this, R.layout.sort_bottom_single_choice_color).apply {
|
||||||
addAll(tvOptions)
|
addAll(tvOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,7 +78,7 @@ object SingleSelectionHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Activity.showDialog(
|
fun Activity?.showDialog(
|
||||||
dialog: Dialog,
|
dialog: Dialog,
|
||||||
items: List<String>,
|
items: List<String>,
|
||||||
selectedIndex: List<Int>,
|
selectedIndex: List<Int>,
|
||||||
|
@ -77,6 +88,8 @@ object SingleSelectionHelper {
|
||||||
callback: (List<Int>) -> Unit,
|
callback: (List<Int>) -> Unit,
|
||||||
dismissCallback: () -> Unit
|
dismissCallback: () -> Unit
|
||||||
) {
|
) {
|
||||||
|
if(this == null) return
|
||||||
|
|
||||||
val realShowApply = showApply || isMultiSelect
|
val realShowApply = showApply || isMultiSelect
|
||||||
val listView = dialog.findViewById<ListView>(R.id.listview1)!!
|
val listView = dialog.findViewById<ListView>(R.id.listview1)!!
|
||||||
val textView = dialog.findViewById<TextView>(R.id.text1)!!
|
val textView = dialog.findViewById<TextView>(R.id.text1)!!
|
||||||
|
@ -145,8 +158,7 @@ object SingleSelectionHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun Activity?.showInputDialog(
|
||||||
private fun Activity.showInputDialog(
|
|
||||||
dialog: Dialog,
|
dialog: Dialog,
|
||||||
value: String,
|
value: String,
|
||||||
name: String,
|
name: String,
|
||||||
|
@ -154,6 +166,8 @@ object SingleSelectionHelper {
|
||||||
callback: (String) -> Unit,
|
callback: (String) -> Unit,
|
||||||
dismissCallback: () -> Unit
|
dismissCallback: () -> Unit
|
||||||
) {
|
) {
|
||||||
|
if(this == null) return
|
||||||
|
|
||||||
val inputView = dialog.findViewById<EditText>(R.id.nginx_text_input)!!
|
val inputView = dialog.findViewById<EditText>(R.id.nginx_text_input)!!
|
||||||
val textView = dialog.findViewById<TextView>(R.id.text1)!!
|
val textView = dialog.findViewById<TextView>(R.id.text1)!!
|
||||||
val applyButton = dialog.findViewById<TextView>(R.id.apply_btt)!!
|
val applyButton = dialog.findViewById<TextView>(R.id.apply_btt)!!
|
||||||
|
@ -184,13 +198,15 @@ object SingleSelectionHelper {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Activity.showMultiDialog(
|
fun Activity?.showMultiDialog(
|
||||||
items: List<String>,
|
items: List<String>,
|
||||||
selectedIndex: List<Int>,
|
selectedIndex: List<Int>,
|
||||||
name: String,
|
name: String,
|
||||||
dismissCallback: () -> Unit,
|
dismissCallback: () -> Unit,
|
||||||
callback: (List<Int>) -> Unit,
|
callback: (List<Int>) -> Unit,
|
||||||
) {
|
) {
|
||||||
|
if(this == null) return
|
||||||
|
|
||||||
val builder =
|
val builder =
|
||||||
AlertDialog.Builder(this, R.style.AlertDialogCustom)
|
AlertDialog.Builder(this, R.style.AlertDialogCustom)
|
||||||
.setView(R.layout.bottom_selection_dialog)
|
.setView(R.layout.bottom_selection_dialog)
|
||||||
|
@ -200,7 +216,7 @@ object SingleSelectionHelper {
|
||||||
showDialog(dialog, items, selectedIndex, name, true, true, callback, dismissCallback)
|
showDialog(dialog, items, selectedIndex, name, true, true, callback, dismissCallback)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Activity.showDialog(
|
fun Activity?.showDialog(
|
||||||
items: List<String>,
|
items: List<String>,
|
||||||
selectedIndex: Int,
|
selectedIndex: Int,
|
||||||
name: String,
|
name: String,
|
||||||
|
@ -208,6 +224,8 @@ object SingleSelectionHelper {
|
||||||
dismissCallback: () -> Unit,
|
dismissCallback: () -> Unit,
|
||||||
callback: (Int) -> Unit,
|
callback: (Int) -> Unit,
|
||||||
) {
|
) {
|
||||||
|
if(this == null) return
|
||||||
|
|
||||||
val builder =
|
val builder =
|
||||||
AlertDialog.Builder(this, R.style.AlertDialogCustom)
|
AlertDialog.Builder(this, R.style.AlertDialogCustom)
|
||||||
.setView(R.layout.bottom_selection_dialog)
|
.setView(R.layout.bottom_selection_dialog)
|
||||||
|
@ -227,7 +245,7 @@ object SingleSelectionHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Only for a low amount of items */
|
/** Only for a low amount of items */
|
||||||
fun Activity.showBottomDialog(
|
fun Activity?.showBottomDialog(
|
||||||
items: List<String>,
|
items: List<String>,
|
||||||
selectedIndex: Int,
|
selectedIndex: Int,
|
||||||
name: String,
|
name: String,
|
||||||
|
@ -235,6 +253,7 @@ object SingleSelectionHelper {
|
||||||
dismissCallback: () -> Unit,
|
dismissCallback: () -> Unit,
|
||||||
callback: (Int) -> Unit,
|
callback: (Int) -> Unit,
|
||||||
) {
|
) {
|
||||||
|
if (this == null) return
|
||||||
val builder =
|
val builder =
|
||||||
BottomSheetDialog(this)
|
BottomSheetDialog(this)
|
||||||
builder.setContentView(R.layout.bottom_selection_dialog)
|
builder.setContentView(R.layout.bottom_selection_dialog)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
<vector android:height="24dp" android:tint="?attr/white"
|
||||||
android:viewportHeight="24" android:viewportWidth="24"
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<path android:fillColor="@android:color/white" android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
|
<path android:fillColor="@android:color/white" android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
|
||||||
|
|
19
app/src/main/res/layout/add_remove_sites.xml
Normal file
19
app/src/main/res/layout/add_remove_sites.xml
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/add_site"
|
||||||
|
android:text="@string/add_site_pref"
|
||||||
|
style="@style/SettingsItem">
|
||||||
|
|
||||||
|
<requestFocus />
|
||||||
|
</TextView>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/remove_site"
|
||||||
|
android:text="@string/remove_site_pref"
|
||||||
|
style="@style/SettingsItem" />
|
||||||
|
</LinearLayout>
|
117
app/src/main/res/layout/add_site_input.xml
Normal file
117
app/src/main/res/layout/add_site_input.xml
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_marginTop="20dp"
|
||||||
|
android:layout_marginBottom="10dp"
|
||||||
|
android:id="@+id/text1"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_rowWeight="1"
|
||||||
|
|
||||||
|
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
|
||||||
|
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
|
||||||
|
android:textColor="?attr/textColor"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:text="@string/add_site_pref" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_marginBottom="10dp"
|
||||||
|
|
||||||
|
android:id="@+id/text2"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_rowWeight="1"
|
||||||
|
|
||||||
|
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
|
||||||
|
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
|
||||||
|
android:textColor="?attr/grayTextColor"
|
||||||
|
android:textSize="15sp"
|
||||||
|
tools:text="Gogoanime" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_marginBottom="60dp"
|
||||||
|
android:layout_marginHorizontal="10dp"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:textColorHint="?attr/grayTextColor"
|
||||||
|
android:hint="@string/example_site_name"
|
||||||
|
android:autofillHints="username"
|
||||||
|
android:id="@+id/site_name_input"
|
||||||
|
android:nextFocusRight="@id/cancel_btt"
|
||||||
|
android:nextFocusLeft="@id/apply_btt"
|
||||||
|
android:nextFocusDown="@id/site_url_input"
|
||||||
|
android:requiresFadingEdge="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:inputType="text"
|
||||||
|
tools:ignore="LabelFor" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:textColorHint="?attr/grayTextColor"
|
||||||
|
android:hint="@string/example_site_url"
|
||||||
|
android:id="@+id/site_url_input"
|
||||||
|
android:nextFocusRight="@id/cancel_btt"
|
||||||
|
android:nextFocusLeft="@id/apply_btt"
|
||||||
|
android:nextFocusUp="@id/site_name_input"
|
||||||
|
android:nextFocusDown="@id/site_lang_input"
|
||||||
|
|
||||||
|
android:requiresFadingEdge="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:inputType="textUri"
|
||||||
|
tools:ignore="LabelFor" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:textColorHint="?attr/grayTextColor"
|
||||||
|
android:hint="@string/example_lang_name"
|
||||||
|
android:autofillHints="username"
|
||||||
|
android:id="@+id/site_lang_input"
|
||||||
|
android:nextFocusUp="@id/site_url_input"
|
||||||
|
android:nextFocusRight="@id/cancel_btt"
|
||||||
|
android:nextFocusLeft="@id/apply_btt"
|
||||||
|
android:nextFocusDown="@id/apply_btt"
|
||||||
|
android:requiresFadingEdge="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:inputType="text"
|
||||||
|
tools:ignore="LabelFor" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/apply_btt_holder"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_gravity="bottom"
|
||||||
|
android:gravity="bottom|end"
|
||||||
|
android:layout_marginTop="-60dp"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="60dp">
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
style="@style/WhiteButton"
|
||||||
|
android:layout_gravity="center_vertical|end"
|
||||||
|
android:text="@string/add_site_pref"
|
||||||
|
android:id="@+id/apply_btt"
|
||||||
|
android:layout_width="wrap_content" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
style="@style/BlackButton"
|
||||||
|
android:layout_gravity="center_vertical|end"
|
||||||
|
android:text="@string/sort_cancel"
|
||||||
|
android:id="@+id/cancel_btt"
|
||||||
|
android:layout_width="wrap_content" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
|
@ -138,6 +138,34 @@
|
||||||
android:background="?attr/primaryBlackBackground"
|
android:background="?attr/primaryBlackBackground"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<com.facebook.shimmer.ShimmerFrameLayout
|
||||||
|
android:visibility="gone"
|
||||||
|
android:id="@+id/result_trailer_loading"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:shimmer_auto_start="true"
|
||||||
|
app:shimmer_base_alpha="0.2"
|
||||||
|
app:shimmer_duration="@integer/loading_time"
|
||||||
|
app:shimmer_highlight_alpha="0.3"
|
||||||
|
tools:visibility="visible">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="@dimen/result_padding"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
android:background="@color/grayShimmer"
|
||||||
|
app:cardCornerRadius="@dimen/loading_radius"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="150dp"
|
||||||
|
android:foreground="@drawable/outline_drawable" />
|
||||||
|
</LinearLayout>
|
||||||
|
</com.facebook.shimmer.ShimmerFrameLayout>
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/result_smallscreen_holder"
|
android:id="@+id/result_smallscreen_holder"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
|
8
app/src/main/res/layout/result_episode_both.xml
Normal file
8
app/src/main/res/layout/result_episode_both.xml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<include android:visibility="gone" layout="@layout/result_episode" />
|
||||||
|
<include android:visibility="gone" layout="@layout/result_episode_large" />
|
||||||
|
</FrameLayout>
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
android:nextFocusLeft="@id/episode_poster"
|
android:nextFocusLeft="@id/episode_poster"
|
||||||
android:nextFocusRight="@id/result_episode_download"
|
android:nextFocusRight="@id/result_episode_download"
|
||||||
android:id="@+id/episode_holder"
|
android:id="@+id/episode_holder_large"
|
||||||
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
@ -142,11 +142,13 @@
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
android:maxLines="4"
|
||||||
|
android:ellipsize="end"
|
||||||
android:paddingTop="10dp"
|
android:paddingTop="10dp"
|
||||||
android:paddingBottom="10dp"
|
android:paddingBottom="10dp"
|
||||||
android:id="@+id/episode_descript"
|
android:id="@+id/episode_descript"
|
||||||
android:textColor="?attr/grayTextColor"
|
android:textColor="?attr/grayTextColor"
|
||||||
tools:text="Jon and Daenerys arrive in Winterfell and are met with skepticism. Sam learns about the fate of his family. Cersei gives Euron the reward he aims for. Theon follows his heart. "
|
tools:text="Jon and Daenerys arrive in Winterfell and are met with skepticism. Sam learns about the fate of his family. Cersei gives Euron the reward he aims for. Theon follows his heart. Jon and Daenerys arrive in Winterfell and are met with skepticism. Sam learns about the fate of his family. Cersei gives Euron the reward he aims for. Theon follows his heart."
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content" />
|
android:layout_height="wrap_content" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
|
@ -58,7 +58,6 @@
|
||||||
android:background="@color/black_overlay" />
|
android:background="@color/black_overlay" />
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:id="@+id/player_video_holder"
|
android:id="@+id/player_video_holder"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
@ -273,6 +272,17 @@
|
||||||
android:layout_width="0dp" />
|
android:layout_width="0dp" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||||
|
android:id="@+id/player_open_source"
|
||||||
|
app:tint="@color/white"
|
||||||
|
android:src="@drawable/ic_baseline_public_24"
|
||||||
|
android:layout_margin="20dp"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/bottom_player_bar"
|
android:id="@+id/bottom_player_bar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
@ -343,9 +353,9 @@
|
||||||
app:tint="@color/white"
|
app:tint="@color/white"
|
||||||
android:layout_gravity="center_vertical"
|
android:layout_gravity="center_vertical"
|
||||||
android:src="@drawable/baseline_fullscreen_24"
|
android:src="@drawable/baseline_fullscreen_24"
|
||||||
android:layout_marginEnd="10dp"
|
android:layout_marginEnd="20dp"
|
||||||
android:layout_width="24dp"
|
android:layout_width="30dp"
|
||||||
android:layout_height="24dp" />
|
android:layout_height="30dp" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
|
|
@ -50,6 +50,7 @@
|
||||||
<string name="bottom_title_key" translatable="false">bottom_title_key</string>
|
<string name="bottom_title_key" translatable="false">bottom_title_key</string>
|
||||||
<string name="poster_ui_key" translatable="false">poster_ui_key</string>
|
<string name="poster_ui_key" translatable="false">poster_ui_key</string>
|
||||||
<string name="subtitles_encoding_key" translatable="false">subtitles_encoding_key</string>
|
<string name="subtitles_encoding_key" translatable="false">subtitles_encoding_key</string>
|
||||||
|
<string name="override_site_key" translatable="false">override_site_key</string>
|
||||||
|
|
||||||
<!-- FORMAT MIGHT TRANSLATE, WILL CAUSE CRASH IF APPLIED WRONG -->
|
<!-- FORMAT MIGHT TRANSLATE, WILL CAUSE CRASH IF APPLIED WRONG -->
|
||||||
<string name="extra_info_format" translatable="false" formatted="true">%d %s | %sMB</string>
|
<string name="extra_info_format" translatable="false" formatted="true">%d %s | %sMB</string>
|
||||||
|
@ -382,6 +383,10 @@
|
||||||
<string name="dns_pref">DNS over HTTPS</string>
|
<string name="dns_pref">DNS over HTTPS</string>
|
||||||
<string name="dns_pref_summary">Useful for bypassing ISP blocks</string>
|
<string name="dns_pref_summary">Useful for bypassing ISP blocks</string>
|
||||||
|
|
||||||
|
<string name="add_site_pref">Clone site</string>
|
||||||
|
<string name="remove_site_pref">Remove site</string>
|
||||||
|
<string name="add_site_summary">Add a clone of an existing site, with a different url</string>
|
||||||
|
|
||||||
<string name="download_path_pref">Download path</string>
|
<string name="download_path_pref">Download path</string>
|
||||||
|
|
||||||
<string name="nginx_url_pref">Nginx server url</string>
|
<string name="nginx_url_pref">Nginx server url</string>
|
||||||
|
@ -440,6 +445,9 @@
|
||||||
<string name="example_username">MyCoolUsername</string>
|
<string name="example_username">MyCoolUsername</string>
|
||||||
<string name="example_email">hello@world.com</string>
|
<string name="example_email">hello@world.com</string>
|
||||||
<string name="example_ip">127.0.0.1</string>
|
<string name="example_ip">127.0.0.1</string>
|
||||||
|
<string name="example_site_name">MyCoolSite</string>
|
||||||
|
<string name="example_site_url">example.com</string>
|
||||||
|
<string name="example_lang_name">Language code (en)</string>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
<string name="mal_account_settings" translatable="false">MAL</string>
|
<string name="mal_account_settings" translatable="false">MAL</string>
|
||||||
|
@ -529,6 +537,8 @@
|
||||||
<string name="title">Title</string>
|
<string name="title">Title</string>
|
||||||
<string name="resolution">Resolution</string>
|
<string name="resolution">Resolution</string>
|
||||||
<string name="error_invalid_id">Invalid id</string>
|
<string name="error_invalid_id">Invalid id</string>
|
||||||
|
<string name="error_invalid_data">Invalid data</string>
|
||||||
|
<string name="error">Error</string>
|
||||||
<string name="subtitles_remove_captions">Remove closed captions from subtitles</string>
|
<string name="subtitles_remove_captions">Remove closed captions from subtitles</string>
|
||||||
<string name="subtitles_remove_bloat">Remove bloat from subtitles</string>
|
<string name="subtitles_remove_bloat">Remove bloat from subtitles</string>
|
||||||
<string name="extras">Extras</string>
|
<string name="extras">Extras</string>
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
<Preference
|
||||||
|
android:key="@string/override_site_key"
|
||||||
|
android:title="@string/add_site_pref"
|
||||||
|
android:summary="@string/add_site_summary"
|
||||||
|
android:icon="@drawable/ic_baseline_add_24" />
|
||||||
|
|
||||||
<Preference
|
<Preference
|
||||||
android:key="@string/dns_key"
|
android:key="@string/dns_key"
|
||||||
|
|
Loading…
Reference in a new issue