cloudstream/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt

1431 lines
44 KiB
Kotlin
Raw Normal View History

2021-04-30 17:20:15 +00:00
package com.lagradost.cloudstream3
import android.annotation.SuppressLint
import android.content.Context
2022-02-19 15:15:48 +00:00
import android.net.Uri
import android.util.Base64.encodeToString
2022-02-11 09:17:04 +00:00
import androidx.annotation.WorkerThread
2021-05-15 23:37:42 +00:00
import androidx.preference.PreferenceManager
2022-03-16 15:29:11 +00:00
import com.fasterxml.jackson.annotation.JsonProperty
2021-04-30 17:20:15 +00:00
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.json.JsonMapper
import com.fasterxml.jackson.module.kotlin.KotlinModule
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.aniListApi
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.malApi
2022-01-07 19:27:25 +00:00
import com.lagradost.cloudstream3.ui.player.SubtitleData
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
import com.lagradost.cloudstream3.utils.AppUtils.toJson
2021-05-20 15:22:28 +00:00
import com.lagradost.cloudstream3.utils.ExtractorLink
2022-08-06 15:16:04 +00:00
import com.lagradost.cloudstream3.utils.Qualities
2022-06-17 17:25:41 +00:00
import com.lagradost.cloudstream3.utils.loadExtractor
2022-04-02 17:50:16 +00:00
import okhttp3.Interceptor
import java.text.SimpleDateFormat
2021-04-30 17:20:15 +00:00
import java.util.*
import kotlin.math.absoluteValue
2022-08-04 10:13:50 +00:00
import kotlin.collections.MutableList
2021-04-30 17:20:15 +00:00
2021-08-14 17:31:27 +00:00
const val USER_AGENT =
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
//val baseHeader = mapOf("User-Agent" to USER_AGENT)
2021-04-30 17:20:15 +00:00
val mapper = JsonMapper.builder().addModule(KotlinModule())
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).build()!!
2021-05-12 21:51:02 +00:00
object APIHolder {
2021-06-17 16:20:05 +00:00
val unixTime: Long
get() = System.currentTimeMillis() / 1000L
val unixTimeMS: Long
get() = System.currentTimeMillis()
2021-06-17 16:20:05 +00:00
2021-05-15 23:37:42 +00:00
private const val defProvider = 0
2022-08-04 10:13:50 +00:00
val allProviders: MutableList<MainAPI> = arrayListOf()
2022-06-16 22:07:07 +00:00
2022-06-02 22:51:41 +00:00
fun initAll() {
for (api in allProviders) {
api.init()
}
2022-06-02 23:06:18 +00:00
apiMap = null
2022-06-02 22:51:41 +00:00
}
fun String.capitalize(): String {
return this.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }
}
2022-03-20 23:59:17 +00:00
var apis: List<MainAPI> = arrayListOf()
2022-06-16 22:07:07 +00:00
var apiMap: Map<String, Int>? = null
2022-04-13 17:29:30 +00:00
fun addPluginMapping(plugin: MainAPI) {
apis = apis + plugin
initMap(true)
}
fun removePluginMapping(plugin: MainAPI) {
apis = apis.filter { it != plugin }
initMap(true)
}
private fun initMap(forcedUpdate: Boolean = false) {
if (apiMap == null || forcedUpdate)
2022-04-13 17:29:30 +00:00
apiMap = apis.mapIndexed { index, api -> api.name to index }.toMap()
}
2021-07-29 00:19:42 +00:00
fun getApiFromNameNull(apiName: String?): MainAPI? {
if (apiName == null) return null
2022-04-13 17:29:30 +00:00
initMap()
return apiMap?.get(apiName)?.let { apis.getOrNull(it) }
?: allProviders.firstOrNull { it.name == apiName }
2021-07-29 00:19:42 +00:00
}
2021-07-25 16:08:34 +00:00
2022-04-03 21:41:28 +00:00
fun getApiFromUrlNull(url: String?): MainAPI? {
2022-03-20 18:02:52 +00:00
if (url == null) return null
for (api in allProviders) {
2022-04-03 21:41:28 +00:00
if (url.startsWith(api.mainUrl))
2022-03-20 18:02:52 +00:00
return api
}
return null
}
2022-08-04 01:19:59 +00:00
private fun getLoadResponseIdFromUrl(url: String, apiName: String): Int {
2022-08-20 17:39:04 +00:00
return url.replace(getApiFromNameNull(apiName)?.mainUrl ?: "", "").replace("/", "")
.hashCode()
2021-07-25 16:08:34 +00:00
}
2022-05-02 21:32:28 +00:00
fun LoadResponse.getId(): Int {
2022-06-02 22:51:41 +00:00
return getLoadResponseIdFromUrl(url, apiName)
2022-05-02 21:32:28 +00:00
}
2022-02-19 15:15:48 +00:00
/**
* Gets the website captcha token
* discovered originally by https://github.com/ahmedgamal17
* optimized by https://github.com/justfoolingaround
*
* @param url the main url, likely the same website you found the key from.
* @param key used to fill https://www.google.com/recaptcha/api.js?render=....
*
* @param referer the referer for the google.com/recaptcha/api.js... request, optional.
* */
// Try document.select("script[src*=https://www.google.com/recaptcha/api.js?render=]").attr("src").substringAfter("render=")
// To get the key
suspend fun getCaptchaToken(url: String, key: String, referer: String? = null): String? {
2022-08-20 17:39:04 +00:00
try {
val uri = Uri.parse(url)
val domain = encodeToString(
(uri.scheme + "://" + uri.host + ":443").encodeToByteArray(),
0
).replace("\n", "").replace("=", ".")
val vToken =
app.get(
"https://www.google.com/recaptcha/api.js?render=$key",
referer = referer,
cacheTime = 0
)
.text
.substringAfter("releases/")
.substringBefore("/")
val recapToken =
app.get("https://www.google.com/recaptcha/api2/anchor?ar=1&hl=en&size=invisible&cb=cs3&k=$key&co=$domain&v=$vToken")
.document
.selectFirst("#recaptcha-token")?.attr("value")
if (recapToken != null) {
return app.post(
"https://www.google.com/recaptcha/api2/reload?k=$key",
data = mapOf(
"v" to vToken,
"k" to key,
"c" to recapToken,
"co" to domain,
"sa" to "",
"reason" to "q"
), cacheTime = 0
).text
.substringAfter("rresp\",\"")
.substringBefore("\"")
}
} catch (e: Exception) {
logError(e)
2022-02-19 15:15:48 +00:00
}
return null
}
fun Context.getApiSettings(): HashSet<String> {
2022-01-30 01:50:49 +00:00
//val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
2021-05-15 23:37:42 +00:00
2021-08-19 20:05:18 +00:00
val hashSet = HashSet<String>()
2021-10-02 17:10:50 +00:00
val activeLangs = getApiProviderLangSettings()
hashSet.addAll(apis.filter { activeLangs.contains(it.lang) }.map { it.name })
2021-08-19 20:05:18 +00:00
2022-01-30 01:50:49 +00:00
/*val set = settingsManager.getStringSet(
2021-07-02 19:43:15 +00:00
this.getString(R.string.search_providers_list_key),
2021-08-19 20:05:18 +00:00
hashSet
)?.toHashSet() ?: hashSet
val list = HashSet<String>()
for (name in set) {
val api = getApiFromNameNull(name) ?: continue
if (activeLangs.contains(api.lang)) {
list.add(name)
}
2022-01-30 01:50:49 +00:00
}*/
//if (list.isEmpty()) return hashSet
//return list
return hashSet
2021-05-15 23:37:42 +00:00
}
2021-08-14 19:35:26 +00:00
fun Context.getApiDubstatusSettings(): HashSet<DubStatus> {
2021-09-12 14:10:22 +00:00
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
val hashSet = HashSet<DubStatus>()
hashSet.addAll(DubStatus.values())
val list = settingsManager.getStringSet(
this.getString(R.string.display_sub_key),
hashSet.map { it.name }.toMutableSet()
) ?: return hashSet
val names = DubStatus.values().map { it.name }.toHashSet()
//if(realSet.isEmpty()) return hashSet
return list.filter { names.contains(it) }.map { DubStatus.valueOf(it) }.toHashSet()
}
fun Context.getApiProviderLangSettings(): HashSet<String> {
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
val hashSet = HashSet<String>()
hashSet.add("en") // def is only en
val list = settingsManager.getStringSet(
this.getString(R.string.provider_lang_key),
hashSet.toMutableSet()
)
if (list.isNullOrEmpty()) return hashSet
return list.toHashSet()
}
fun Context.getApiTypeSettings(): HashSet<TvType> {
2021-08-14 19:35:26 +00:00
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
2021-08-19 20:05:18 +00:00
val hashSet = HashSet<TvType>()
hashSet.addAll(TvType.values())
2021-08-14 19:35:26 +00:00
val list = settingsManager.getStringSet(
this.getString(R.string.search_types_list_key),
2021-08-19 20:05:18 +00:00
hashSet.map { it.name }.toMutableSet()
2021-08-14 19:35:26 +00:00
)
2021-08-19 20:05:18 +00:00
if (list.isNullOrEmpty()) return hashSet
2021-08-14 19:35:26 +00:00
val names = TvType.values().map { it.name }.toHashSet()
val realSet = list.filter { names.contains(it) }.map { TvType.valueOf(it) }.toHashSet()
if (realSet.isEmpty()) return hashSet
2021-08-14 19:35:26 +00:00
return realSet
}
fun Context.updateHasTrailers() {
LoadResponse.isTrailersEnabled = getHasTrailers()
}
private fun Context.getHasTrailers(): Boolean {
2022-08-28 23:52:15 +00:00
if (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> {
val default = enumValues<TvType>().sorted().filter { it != TvType.NSFW }.map { it.ordinal }
val defaultSet = default.map { it.toString() }.toSet()
2022-08-20 12:16:27 +00:00
val currentPrefMedia = try {
PreferenceManager.getDefaultSharedPreferences(this)
2022-08-25 01:59:20 +00:00
.getStringSet(this.getString(R.string.prefer_media_type_key), defaultSet)
?.mapNotNull { it.toIntOrNull() ?: return@mapNotNull null }
2022-08-20 12:16:27 +00:00
} catch (e: Throwable) {
null
} ?: default
val langs = this.getApiProviderLangSettings()
val allApis = apis.filter { langs.contains(it.lang) }
.filter { api -> api.hasMainPage || !hasHomePageIsRequired }
return if (currentPrefMedia.isEmpty()) {
allApis
} else {
// Filter API depending on preferred media type
allApis.filter { api -> api.supportedTypes.any { currentPrefMedia.contains(it.ordinal) } }
}
}
fun Context.filterSearchResultByFilmQuality(data: List<SearchResponse>): List<SearchResponse> {
// Filter results omitting entries with certain quality
if (data.isNotEmpty()) {
val filteredSearchQuality = PreferenceManager.getDefaultSharedPreferences(this)
?.getStringSet(getString(R.string.pref_filter_search_quality_key), setOf())
?.mapNotNull { entry ->
entry.toIntOrNull() ?: return@mapNotNull null
} ?: listOf()
if (filteredSearchQuality.isNotEmpty()) {
return data.filter { item ->
val searchQualVal = item.quality?.ordinal ?: -1
//Log.i("filterSearch", "QuickSearch item => ${item.toJson()}")
!filteredSearchQuality.contains(searchQualVal)
}
}
}
return data
}
fun Context.filterHomePageListByFilmQuality(data: HomePageList): HomePageList {
// Filter results omitting entries with certain quality
if (data.list.isNotEmpty()) {
val filteredSearchQuality = PreferenceManager.getDefaultSharedPreferences(this)
?.getStringSet(getString(R.string.pref_filter_search_quality_key), setOf())
?.mapNotNull { entry ->
entry.toIntOrNull() ?: return@mapNotNull null
} ?: listOf()
if (filteredSearchQuality.isNotEmpty()) {
return HomePageList(
name = data.name,
isHorizontalImages = data.isHorizontalImages,
list = data.list.filter { item ->
val searchQualVal = item.quality?.ordinal ?: -1
//Log.i("filterSearch", "QuickSearch item => ${item.toJson()}")
!filteredSearchQuality.contains(searchQualVal)
}
)
}
}
return data
}
2021-05-12 21:51:02 +00:00
}
2022-03-16 15:29:11 +00:00
/*
0 = Site not good
1 = All good
2 = Slow, heavy traffic
3 = restricted, must donate 30 benenes to use
*/
const val PROVIDER_STATUS_KEY = "PROVIDER_STATUS_KEY"
const val PROVIDER_STATUS_BETA_ONLY = 3
const val PROVIDER_STATUS_SLOW = 2
const val PROVIDER_STATUS_OK = 1
const val PROVIDER_STATUS_DOWN = 0
data class ProvidersInfoJson(
@JsonProperty("name") var name: String,
@JsonProperty("url") var url: String,
@JsonProperty("credentials") var credentials: String? = null,
2022-03-16 15:29:11 +00:00
@JsonProperty("status") var status: Int,
)
data class SettingsJson(
@JsonProperty("enableAdult") var enableAdult: Boolean = false,
)
data class MainPageData(
val name: String,
val data: String,
)
2022-07-31 15:02:22 +00:00
data class MainPageRequest(
val name: String,
val data: String,
)
/** return list of MainPageData with url to name, make for more readable code */
fun mainPageOf(vararg elements: Pair<String, String>): List<MainPageData> {
return elements.map { (url, name) -> MainPageData(name = name, data = url) }
}
fun newHomePageResponse(
name: String,
list: List<SearchResponse>,
hasNext: Boolean? = null
): HomePageResponse {
return HomePageResponse(
listOf(HomePageList(name, list)),
hasNext = hasNext ?: list.isNotEmpty()
)
}
fun newHomePageResponse(list: HomePageList, hasNext: Boolean? = null): HomePageResponse {
return HomePageResponse(listOf(list), hasNext = hasNext ?: list.list.isNotEmpty())
}
fun newHomePageResponse(list: List<HomePageList>, hasNext: Boolean? = null): HomePageResponse {
return HomePageResponse(list, hasNext = hasNext ?: list.any { it.list.isNotEmpty() })
}
2021-07-23 23:44:54 +00:00
/**Every provider will **not** have try catch built in, so handle exceptions when calling these functions*/
2021-05-12 21:51:02 +00:00
abstract class MainAPI {
2022-03-16 15:29:11 +00:00
companion object {
2022-03-20 23:59:17 +00:00
var overrideData: HashMap<String, ProvidersInfoJson>? = null
var settingsForProvider: SettingsJson = SettingsJson()
2022-03-16 15:29:11 +00:00
}
2022-06-02 22:51:41 +00:00
fun init() {
2022-03-16 15:29:11 +00:00
overrideData?.get(this.javaClass.simpleName)?.let { data ->
overrideWithNewData(data)
}
}
2022-06-02 22:51:41 +00:00
fun overrideWithNewData(data: ProvidersInfoJson) {
2022-06-16 22:07:07 +00:00
if (!canBeOverridden) return
2022-06-02 22:51:41 +00:00
this.name = data.name
if (data.url.isNotBlank() && data.url != "NONE")
this.mainUrl = data.url
2022-06-02 22:51:41 +00:00
this.storedCredentials = data.credentials
}
2022-03-16 15:29:11 +00:00
open var name = "NONE"
open var mainUrl = "NONE"
open var storedCredentials: String? = null
2022-06-16 22:07:07 +00:00
open var canBeOverridden: Boolean = true
2021-07-23 23:44:54 +00:00
2022-03-06 14:44:00 +00:00
//open val uniqueId : Int by lazy { this.name.hashCode() } // in case of duplicate providers you can have a shared id
2022-06-16 22:07:07 +00:00
open var lang = "en" // ISO_639_1 check SubtitleHelper
2021-07-23 23:44:54 +00:00
/**If link is stored in the "data" string, so links can be instantly loaded*/
open val instantLinkLoading = false
/**Set false if links require referer or for some reason cant be played on a chromecast*/
open val hasChromecastSupport = true
/**If all links are encrypted then set this to false*/
2021-07-23 23:44:54 +00:00
open val hasDownloadSupport = true
/**Used for testing and can be used to disable the providers if WebView is not available*/
open val usesWebView = false
2022-08-09 08:33:16 +00:00
/** Determines which plugin a given provider is from */
var sourcePlugin: String? = null
2021-07-29 00:19:42 +00:00
open val hasMainPage = false
open val hasQuickSearch = false
2021-08-14 17:31:27 +00:00
open val supportedTypes = setOf(
TvType.Movie,
TvType.TvSeries,
TvType.Cartoon,
TvType.Anime,
2022-01-31 20:47:59 +00:00
TvType.OVA,
2021-08-14 17:31:27 +00:00
)
open val vpnStatus = VPNStatus.None
open val providerType = ProviderType.DirectProvider
open val mainPage = listOf(MainPageData("", ""))
2022-02-11 09:17:04 +00:00
@WorkerThread
open suspend fun getMainPage(
page: Int,
2022-07-31 15:02:22 +00:00
request: MainPageRequest,
): HomePageResponse? {
2021-08-21 21:06:24 +00:00
throw NotImplementedError()
2021-07-29 00:19:42 +00:00
}
2022-02-19 15:15:48 +00:00
2022-02-11 09:17:04 +00:00
@WorkerThread
2022-03-16 15:29:11 +00:00
open suspend fun search(query: String): List<SearchResponse>? {
2021-08-21 21:06:24 +00:00
throw NotImplementedError()
2021-04-30 17:20:15 +00:00
}
2022-02-19 15:15:48 +00:00
2022-02-11 09:17:04 +00:00
@WorkerThread
2022-03-16 15:29:11 +00:00
open suspend fun quickSearch(query: String): List<SearchResponse>? {
2021-08-21 21:06:24 +00:00
throw NotImplementedError()
2021-06-17 16:20:05 +00:00
}
2022-02-19 15:15:48 +00:00
2022-02-11 09:17:04 +00:00
@WorkerThread
/**
* Based on data from search() or getMainPage() it generates a LoadResponse,
* basically opening the info page from a link.
* */
2022-03-16 15:29:11 +00:00
open suspend fun load(url: String): LoadResponse? {
2021-08-21 21:06:24 +00:00
throw NotImplementedError()
2021-04-30 17:20:15 +00:00
}
2022-02-11 09:17:04 +00:00
/**
* Largely redundant feature for most providers.
*
* This job runs in the background when a link is playing in exoplayer.
* First implemented to do polling for sflix to keep the link from getting expired.
*
* This function might be updated to include exoplayer timestamps etc in the future
* if the need arises.
* */
@WorkerThread
2022-03-16 15:29:11 +00:00
open suspend fun extractorVerifierJob(extractorData: String?) {
2022-02-11 09:17:04 +00:00
throw NotImplementedError()
}
2021-07-23 23:44:54 +00:00
/**Callback is fired once a link is found, will return true if method is executed successfully*/
2022-02-11 09:17:04 +00:00
@WorkerThread
2022-03-16 15:29:11 +00:00
open suspend fun loadLinks(
2021-07-02 19:43:15 +00:00
data: String,
isCasting: Boolean,
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit
): Boolean {
2021-08-21 21:06:24 +00:00
throw NotImplementedError()
2021-04-30 17:20:15 +00:00
}
2022-04-02 17:50:16 +00:00
/** An okhttp interceptor for used in OkHttpDataSource */
2022-04-03 21:41:28 +00:00
open fun getVideoInterceptor(extractorLink: ExtractorLink): Interceptor? {
2022-04-02 17:50:16 +00:00
return null
}
2021-04-30 17:20:15 +00:00
}
/** Might need a different implementation for desktop*/
@SuppressLint("NewApi")
fun base64Decode(string: String): String {
2022-01-29 18:37:23 +00:00
return String(base64DecodeArray(string), Charsets.ISO_8859_1)
}
@SuppressLint("NewApi")
fun base64DecodeArray(string: String): ByteArray {
return try {
android.util.Base64.decode(string, android.util.Base64.DEFAULT)
} catch (e: Exception) {
Base64.getDecoder().decode(string)
}
}
@SuppressLint("NewApi")
fun base64Encode(array: ByteArray): String {
return try {
2022-01-29 18:37:23 +00:00
String(android.util.Base64.encode(array, android.util.Base64.NO_WRAP), Charsets.ISO_8859_1)
} catch (e: Exception) {
2022-01-29 18:37:23 +00:00
String(Base64.getEncoder().encode(array))
}
}
2021-08-04 01:50:24 +00:00
class ErrorLoadingException(message: String? = null) : Exception(message)
2022-01-13 21:09:05 +00:00
fun MainAPI.fixUrlNull(url: String?): String? {
if (url.isNullOrEmpty()) {
return null
}
return fixUrl(url)
2021-12-26 00:05:10 +00:00
}
2021-05-15 23:37:42 +00:00
fun MainAPI.fixUrl(url: String): String {
if (url.startsWith("http") ||
// Do not fix JSON objects when passed as urls.
url.startsWith("{\"")
) {
2021-06-23 18:33:47 +00:00
return url
}
if (url.isEmpty()) {
return ""
}
2021-06-23 18:33:47 +00:00
val startsWithNoHttp = url.startsWith("//")
2021-06-26 14:44:53 +00:00
if (startsWithNoHttp) {
2021-06-23 18:33:47 +00:00
return "https:$url"
2021-06-26 14:44:53 +00:00
} else {
if (url.startsWith('/')) {
2021-06-23 18:33:47 +00:00
return mainUrl + url
}
2021-05-15 23:37:42 +00:00
return "$mainUrl/$url"
}
}
2022-01-07 19:27:25 +00:00
fun sortUrls(urls: Set<ExtractorLink>): List<ExtractorLink> {
2021-06-10 19:43:05 +00:00
return urls.sortedBy { t -> -t.quality }
}
2021-05-15 23:37:42 +00:00
2022-01-13 21:09:05 +00:00
fun sortSubs(subs: Set<SubtitleData>): List<SubtitleData> {
2022-01-07 19:27:25 +00:00
return subs.sortedBy { it.name }
2021-07-02 19:43:15 +00:00
}
fun capitalizeString(str: String): String {
return capitalizeStringNullable(str) ?: str
}
fun capitalizeStringNullable(str: String?): String? {
if (str == null)
return null
return try {
str.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }
} catch (e: Exception) {
str
}
}
fun fixTitle(str: String): String {
return str.split(" ").joinToString(" ") {
it.lowercase()
.replaceFirstChar { char -> if (char.isLowerCase()) char.titlecase(Locale.getDefault()) else it }
}
}
2021-08-04 01:50:24 +00:00
/** https://www.imdb.com/title/tt2861424/ -> tt2861424 */
2021-10-30 18:14:12 +00:00
fun imdbUrlToId(url: String): String? {
2021-11-02 15:09:29 +00:00
return Regex("/title/(tt[0-9]*)").find(url)?.groupValues?.get(1)
?: Regex("tt[0-9]{5,}").find(url)?.groupValues?.get(0)
2021-08-04 01:50:24 +00:00
}
fun imdbUrlToIdNullable(url: String?): String? {
2021-08-11 17:04:31 +00:00
if (url == null) return null
2021-08-04 01:50:24 +00:00
return imdbUrlToId(url)
}
enum class ProviderType {
// When data is fetched from a 3rd party site like imdb
MetaProvider,
// When all data is from the site
DirectProvider,
}
enum class VPNStatus {
None,
MightBeNeeded,
Torrent,
}
2021-04-30 17:20:15 +00:00
enum class ShowStatus {
Completed,
Ongoing,
}
enum class DubStatus(val id: Int) {
2022-08-01 01:00:48 +00:00
None(-1),
Dubbed(1),
Subbed(0),
2021-04-30 17:20:15 +00:00
}
enum class TvType(value: Int?) {
Movie(1),
AnimeMovie(2),
TvSeries(3),
Cartoon(4),
Anime(5),
OVA(6),
Torrent(7),
Documentary(8),
AsianDrama(9),
Live(10),
NSFW(11),
Others(12)
2021-04-30 17:20:15 +00:00
}
2021-07-15 16:45:25 +00:00
// IN CASE OF FUTURE ANIME MOVIE OR SMTH
2021-07-25 16:08:34 +00:00
fun TvType.isMovieType(): Boolean {
return this == TvType.Movie || this == TvType.AnimeMovie || this == TvType.Torrent || this == TvType.Live
}
fun TvType.isLiveStream(): Boolean {
return this == TvType.Live
2021-07-15 16:45:25 +00:00
}
2022-01-07 19:27:25 +00:00
// returns if the type has an anime opening
fun TvType.isAnimeOp(): Boolean {
2022-01-31 20:47:59 +00:00
return this == TvType.Anime || this == TvType.OVA
2022-01-07 19:27:25 +00:00
}
2021-07-02 19:43:15 +00:00
data class SubtitleFile(val lang: String, val url: String)
2021-07-01 20:11:33 +00:00
data class HomePageResponse(
val items: List<HomePageList>,
val hasNext: Boolean = false
2021-07-29 00:19:42 +00:00
)
data class HomePageList(
2021-07-29 00:19:42 +00:00
val name: String,
var list: List<SearchResponse>,
val isHorizontalImages: Boolean = false
2021-07-29 00:19:42 +00:00
)
enum class SearchQuality(value: Int?) {
2022-03-20 23:59:17 +00:00
//https://en.wikipedia.org/wiki/Pirated_movie_release_types
Cam(1),
CamRip(2),
HdCam(3),
Telesync(4), // TS
WorkPrint(5),
Telecine(6), // TC
HQ(7),
HD(8),
HDR(9), // high dynamic range
BlueRay(10),
DVD(11),
SD(12),
FourK(13),
UHD(14),
SDR(15), // standard dynamic range
WebRip(16)
2022-03-30 13:41:45 +00:00
}
/**Add anything to here if you find a site that uses some specific naming convention*/
2022-04-03 21:41:28 +00:00
fun getQualityFromString(string: String?): SearchQuality? {
val check = (string ?: return null).trim().lowercase().replace(" ", "")
2022-03-30 13:41:45 +00:00
2022-04-03 21:41:28 +00:00
return when (check) {
2022-03-30 13:41:45 +00:00
"cam" -> SearchQuality.Cam
"camrip" -> SearchQuality.CamRip
2022-03-30 13:41:45 +00:00
"hdcam" -> SearchQuality.HdCam
"hdtc" -> SearchQuality.HdCam
"hdts" -> SearchQuality.HdCam
"highquality" -> SearchQuality.HQ
2022-03-30 13:41:45 +00:00
"hq" -> SearchQuality.HQ
"highdefinition" -> SearchQuality.HD
2022-03-30 13:41:45 +00:00
"hdrip" -> SearchQuality.HD
"hd" -> SearchQuality.HD
"hdtv" -> SearchQuality.HD
2022-03-30 13:41:45 +00:00
"rip" -> SearchQuality.CamRip
"telecine" -> SearchQuality.Telecine
2022-03-30 13:41:45 +00:00
"tc" -> SearchQuality.Telecine
"telesync" -> SearchQuality.Telesync
2022-03-30 13:41:45 +00:00
"ts" -> SearchQuality.Telesync
"dvd" -> SearchQuality.DVD
"dvdrip" -> SearchQuality.DVD
"dvdscr" -> SearchQuality.DVD
2022-04-03 21:41:28 +00:00
"blueray" -> SearchQuality.BlueRay
"bluray" -> SearchQuality.BlueRay
"blu" -> SearchQuality.BlueRay
"fhd" -> SearchQuality.HD
"br" -> SearchQuality.BlueRay
"standard" -> SearchQuality.SD
2022-03-30 13:41:45 +00:00
"sd" -> SearchQuality.SD
"4k" -> SearchQuality.FourK
"uhd" -> SearchQuality.UHD // may also be 4k or 8k
"blue" -> SearchQuality.BlueRay
"wp" -> SearchQuality.WorkPrint
"workprint" -> SearchQuality.WorkPrint
"webrip" -> SearchQuality.WebRip
"webdl" -> SearchQuality.WebRip
"web" -> SearchQuality.WebRip
"hdr" -> SearchQuality.HDR
"sdr" -> SearchQuality.SDR
2022-03-30 13:41:45 +00:00
else -> null
}
2022-03-20 23:59:17 +00:00
}
2021-04-30 17:20:15 +00:00
interface SearchResponse {
val name: String
2021-08-25 15:28:25 +00:00
val url: String
2021-04-30 17:20:15 +00:00
val apiName: String
2022-03-20 23:59:17 +00:00
var type: TvType?
var posterUrl: String?
2022-04-13 17:29:30 +00:00
var posterHeaders: Map<String, String>?
2022-03-20 23:59:17 +00:00
var id: Int?
2022-04-03 21:41:28 +00:00
var quality: SearchQuality?
2021-04-30 17:20:15 +00:00
}
2022-04-13 17:29:30 +00:00
fun MainAPI.newMovieSearchResponse(
name: String,
url: String,
type: TvType = TvType.Movie,
fix: Boolean = true,
initializer: MovieSearchResponse.() -> Unit = { },
): MovieSearchResponse {
val builder = MovieSearchResponse(name, if (fix) fixUrl(url) else url, this.name, type)
builder.initializer()
return builder
}
fun MainAPI.newTvSeriesSearchResponse(
name: String,
url: String,
type: TvType = TvType.TvSeries,
fix: Boolean = true,
initializer: TvSeriesSearchResponse.() -> Unit = { },
): TvSeriesSearchResponse {
val builder = TvSeriesSearchResponse(name, if (fix) fixUrl(url) else url, this.name, type)
builder.initializer()
return builder
}
fun MainAPI.newAnimeSearchResponse(
name: String,
url: String,
type: TvType = TvType.Anime,
fix: Boolean = true,
initializer: AnimeSearchResponse.() -> Unit = { },
): AnimeSearchResponse {
val builder = AnimeSearchResponse(name, if (fix) fixUrl(url) else url, this.name, type)
builder.initializer()
return builder
}
fun SearchResponse.addQuality(quality: String) {
this.quality = getQualityFromString(quality)
}
fun SearchResponse.addPoster(url: String?, headers: Map<String, String>? = null) {
this.posterUrl = url
this.posterHeaders = headers
}
fun LoadResponse.addPoster(url: String?, headers: Map<String, String>? = null) {
this.posterUrl = url
this.posterHeaders = headers
}
enum class ActorRole {
Main,
Supporting,
2022-02-05 23:58:56 +00:00
Background,
}
data class Actor(
val name: String,
val image: String? = null,
)
data class ActorData(
val actor: Actor,
val role: ActorRole? = null,
2022-02-19 15:15:48 +00:00
val roleString: String? = null,
val voiceActor: Actor? = null,
)
2021-04-30 17:20:15 +00:00
data class AnimeSearchResponse(
override val name: String,
override val url: String,
override val apiName: String,
2022-04-13 17:29:30 +00:00
override var type: TvType? = null,
2021-04-30 17:20:15 +00:00
2022-04-13 17:29:30 +00:00
override var posterUrl: String? = null,
var year: Int? = null,
var dubStatus: EnumSet<DubStatus>? = null,
var otherName: String? = null,
var episodes: MutableMap<DubStatus, Int> = mutableMapOf(),
2021-10-10 02:26:18 +00:00
2022-03-20 23:59:17 +00:00
override var id: Int? = null,
override var quality: SearchQuality? = null,
2022-04-13 17:29:30 +00:00
override var posterHeaders: Map<String, String>? = null,
2021-04-30 17:20:15 +00:00
) : SearchResponse
2022-04-13 17:29:30 +00:00
fun AnimeSearchResponse.addDubStatus(status: DubStatus, episodes: Int? = null) {
this.dubStatus = dubStatus?.also { it.add(status) } ?: EnumSet.of(status)
if (this.type?.isMovieType() != true)
if (episodes != null && episodes > 0)
this.episodes[status] = episodes
}
fun AnimeSearchResponse.addDubStatus(isDub: Boolean, episodes: Int? = null) {
addDubStatus(if (isDub) DubStatus.Dubbed else DubStatus.Subbed, episodes)
}
fun AnimeSearchResponse.addDub(episodes: Int?) {
2022-06-02 22:51:41 +00:00
if (episodes == null || episodes <= 0) return
2022-04-13 17:29:30 +00:00
addDubStatus(DubStatus.Dubbed, episodes)
}
fun AnimeSearchResponse.addSub(episodes: Int?) {
2022-06-02 22:51:41 +00:00
if (episodes == null || episodes <= 0) return
2022-04-13 17:29:30 +00:00
addDubStatus(DubStatus.Subbed, episodes)
}
fun AnimeSearchResponse.addDubStatus(
dubExist: Boolean,
subExist: Boolean,
dubEpisodes: Int? = null,
subEpisodes: Int? = null
) {
if (dubExist)
addDubStatus(DubStatus.Dubbed, dubEpisodes)
if (subExist)
addDubStatus(DubStatus.Subbed, subEpisodes)
}
fun AnimeSearchResponse.addDubStatus(status: String, episodes: Int? = null) {
if (status.contains("(dub)", ignoreCase = true)) {
2022-07-25 21:46:17 +00:00
addDubStatus(DubStatus.Dubbed, episodes)
2022-04-13 17:29:30 +00:00
} else if (status.contains("(sub)", ignoreCase = true)) {
2022-07-25 21:46:17 +00:00
addDubStatus(DubStatus.Subbed, episodes)
2022-04-13 17:29:30 +00:00
}
}
2021-08-30 17:11:04 +00:00
data class TorrentSearchResponse(
override val name: String,
override val url: String,
override val apiName: String,
2022-03-20 23:59:17 +00:00
override var type: TvType?,
2021-08-30 17:11:04 +00:00
2022-03-20 23:59:17 +00:00
override var posterUrl: String?,
override var id: Int? = null,
override var quality: SearchQuality? = null,
2022-04-13 17:29:30 +00:00
override var posterHeaders: Map<String, String>? = null,
2021-08-30 17:11:04 +00:00
) : SearchResponse
2021-04-30 17:20:15 +00:00
data class MovieSearchResponse(
override val name: String,
override val url: String,
override val apiName: String,
2022-04-13 17:29:30 +00:00
override var type: TvType? = null,
2021-04-30 17:20:15 +00:00
2022-04-13 17:29:30 +00:00
override var posterUrl: String? = null,
var year: Int? = null,
2022-03-20 23:59:17 +00:00
override var id: Int? = null,
override var quality: SearchQuality? = null,
2022-04-13 17:29:30 +00:00
override var posterHeaders: Map<String, String>? = null,
2021-04-30 17:20:15 +00:00
) : SearchResponse
data class LiveSearchResponse(
override val name: String,
override val url: String,
override val apiName: String,
override var type: TvType? = null,
override var posterUrl: String? = null,
override var id: Int? = null,
override var quality: SearchQuality? = null,
override var posterHeaders: Map<String, String>? = null,
val lang: String? = null,
) : SearchResponse
2021-04-30 17:20:15 +00:00
data class TvSeriesSearchResponse(
override val name: String,
override val url: String,
override val apiName: String,
2022-04-13 17:29:30 +00:00
override var type: TvType? = null,
2021-04-30 17:20:15 +00:00
2022-04-13 17:29:30 +00:00
override var posterUrl: String? = null,
val year: Int? = null,
val episodes: Int? = null,
2022-03-20 23:59:17 +00:00
override var id: Int? = null,
override var quality: SearchQuality? = null,
2022-04-13 17:29:30 +00:00
override var posterHeaders: Map<String, String>? = null,
2021-04-30 17:20:15 +00:00
) : SearchResponse
2022-07-27 17:36:31 +00:00
data class TrailerData(
2022-08-25 01:59:20 +00:00
val extractorUrl: String,
val referer: String?,
val raw: Boolean,
//var mirros: List<ExtractorLink>,
//var subtitles: List<SubtitleFile> = emptyList(),
2022-07-27 17:36:31 +00:00
)
2021-04-30 17:20:15 +00:00
interface LoadResponse {
2022-04-10 00:14:36 +00:00
var name: String
var url: String
var apiName: String
var type: TvType
2022-04-03 01:13:02 +00:00
var posterUrl: String?
2022-04-10 00:14:36 +00:00
var year: Int?
2022-04-03 01:13:02 +00:00
var plot: String?
2022-06-08 14:54:58 +00:00
var rating: Int? // 0-10000
2022-04-03 01:13:02 +00:00
var tags: List<String>?
2021-12-13 18:41:33 +00:00
var duration: Int? // in minutes
2022-07-27 17:36:31 +00:00
var trailers: MutableList<TrailerData>
2022-03-06 16:02:54 +00:00
var recommendations: List<SearchResponse>?
var actors: List<ActorData>?
2022-03-20 23:59:17 +00:00
var comingSoon: Boolean
2022-04-03 21:41:28 +00:00
var syncData: MutableMap<String, String>
2022-04-13 17:29:30 +00:00
var posterHeaders: Map<String, String>?
2022-07-31 15:02:22 +00:00
var backgroundPosterUrl: String?
companion object {
2022-04-10 00:14:36 +00:00
private val malIdPrefix = malApi.idPrefix
private val aniListIdPrefix = aniListApi.idPrefix
var isTrailersEnabled = true
2022-04-03 21:41:28 +00:00
2022-08-06 15:16:04 +00:00
fun LoadResponse.isMovie(): Boolean {
2022-08-02 00:43:42 +00:00
return this.type.isMovieType()
}
2022-02-05 23:58:56 +00:00
@JvmName("addActorNames")
fun LoadResponse.addActors(actors: List<String>?) {
this.actors = actors?.map { ActorData(Actor(it)) }
}
2022-02-05 23:58:56 +00:00
@JvmName("addActors")
fun LoadResponse.addActors(actors: List<Pair<Actor, String?>>?) {
this.actors = actors?.map { (actor, role) -> ActorData(actor, roleString = role) }
}
2022-02-05 23:58:56 +00:00
@JvmName("addActorsRole")
fun LoadResponse.addActors(actors: List<Pair<Actor, ActorRole?>>?) {
this.actors = actors?.map { (actor, role) -> ActorData(actor, role = role) }
}
2022-02-06 22:38:53 +00:00
@JvmName("addActorsOnly")
fun LoadResponse.addActors(actors: List<Actor>?) {
this.actors = actors?.map { actor -> ActorData(actor) }
}
2022-06-20 13:17:34 +00:00
fun LoadResponse.getMalId(): String? {
2022-06-18 00:30:39 +00:00
return this.syncData[malIdPrefix]
}
2022-06-20 13:17:34 +00:00
fun LoadResponse.getAniListId(): String? {
2022-06-18 00:30:39 +00:00
return this.syncData[aniListIdPrefix]
}
2022-04-03 21:41:28 +00:00
fun LoadResponse.addMalId(id: Int?) {
this.syncData[malIdPrefix] = (id ?: return).toString()
}
fun LoadResponse.addAniListId(id: Int?) {
this.syncData[aniListIdPrefix] = (id ?: return).toString()
}
2022-04-07 13:54:55 +00:00
fun LoadResponse.addImdbUrl(url: String?) {
2022-04-03 21:41:28 +00:00
addImdbId(imdbUrlToIdNullable(url))
}
2022-06-17 17:25:41 +00:00
/**better to call addTrailer with mutible trailers directly instead of calling this multiple times*/
2022-08-06 15:16:04 +00:00
suspend fun LoadResponse.addTrailer(
trailerUrl: String?,
referer: String? = null,
addRaw: Boolean = false
) {
if (!isTrailersEnabled || trailerUrl.isNullOrBlank()) return
2022-08-25 01:59:20 +00:00
this.trailers.add(TrailerData(trailerUrl, referer, addRaw))
/*val links = arrayListOf<ExtractorLink>()
2022-07-27 17:36:31 +00:00
val subs = arrayListOf<SubtitleFile>()
2022-08-06 15:16:04 +00:00
if (!loadExtractor(
trailerUrl,
referer,
{ subs.add(it) },
{ links.add(it) }) && addRaw
) {
this.trailers.add(
TrailerData(
listOf(
ExtractorLink(
"",
"Trailer",
trailerUrl,
referer ?: "",
Qualities.Unknown.value,
trailerUrl.contains(".m3u8")
)
), listOf()
)
)
} else {
this.trailers.add(TrailerData(links, subs))
2022-08-25 01:59:20 +00:00
}*/
2022-06-17 17:25:41 +00:00
}
2022-08-25 01:59:20 +00:00
/*
2022-06-17 17:25:41 +00:00
fun LoadResponse.addTrailer(newTrailers: List<ExtractorLink>) {
2022-07-27 17:36:31 +00:00
trailers.addAll(newTrailers.map { TrailerData(listOf(it)) })
2022-08-25 01:59:20 +00:00
}*/
2022-04-10 00:14:36 +00:00
2022-08-06 15:16:04 +00:00
suspend fun LoadResponse.addTrailer(
trailerUrls: List<String>?,
referer: String? = null,
addRaw: Boolean = false
) {
if (!isTrailersEnabled || trailerUrls == null) return
2022-08-25 01:59:20 +00:00
trailers.addAll(trailerUrls.map { TrailerData(it, referer, addRaw) })
/*val trailers = trailerUrls.filter { it.isNotBlank() }.apmap { trailerUrl ->
2022-07-27 17:36:31 +00:00
val links = arrayListOf<ExtractorLink>()
val subs = arrayListOf<SubtitleFile>()
2022-08-06 15:16:04 +00:00
if (!loadExtractor(
trailerUrl,
referer,
{ subs.add(it) },
{ links.add(it) }) && addRaw
) {
arrayListOf(
ExtractorLink(
"",
"Trailer",
trailerUrl,
referer ?: "",
Qualities.Unknown.value,
trailerUrl.contains(".m3u8")
)
) to arrayListOf()
} else {
links to subs
}
2022-07-27 17:36:31 +00:00
}.map { (links, subs) -> TrailerData(links, subs) }
2022-08-25 01:59:20 +00:00
this.trailers.addAll(trailers)*/
2022-06-16 01:04:24 +00:00
}
2022-04-03 21:41:28 +00:00
fun LoadResponse.addImdbId(id: String?) {
// TODO add imdb sync
}
fun LoadResponse.addTrackId(id: String?) {
// TODO add trackt sync
}
fun LoadResponse.addkitsuId(id: String?) {
// TODO add kitsu sync
}
2022-04-08 19:38:19 +00:00
fun LoadResponse.addTMDbId(id: String?) {
2022-04-10 00:14:36 +00:00
// TODO add TMDb sync
}
2022-04-08 19:38:19 +00:00
2022-04-10 00:14:36 +00:00
fun LoadResponse.addRating(text: String?) {
addRating(text.toRatingInt())
2022-04-08 19:38:19 +00:00
}
2022-04-10 00:14:36 +00:00
fun LoadResponse.addRating(value: Int?) {
2022-06-08 14:54:58 +00:00
if ((value ?: return) < 0 || value > 10000) {
2022-04-10 00:14:36 +00:00
return
}
this.rating = value
}
fun LoadResponse.addDuration(input: String?) {
2022-07-25 21:46:17 +00:00
this.duration = getDurationFromString(input) ?: this.duration
}
}
}
fun getDurationFromString(input: String?): Int? {
val cleanInput = input?.trim()?.replace(" ", "") ?: return null
Regex("([0-9]*)h.*?([0-9]*)m").find(cleanInput)?.groupValues?.let { values ->
if (values.size == 3) {
val hours = values[1].toIntOrNull()
val minutes = values[2].toIntOrNull()
return if (minutes != null && hours != null) {
hours * 60 + minutes
} else null
}
}
Regex("([0-9]*)m").find(cleanInput)?.groupValues?.let { values ->
if (values.size == 2) {
return values[1].toIntOrNull()
}
}
2022-07-25 21:46:17 +00:00
return null
2021-04-30 17:20:15 +00:00
}
2021-06-17 15:39:01 +00:00
fun LoadResponse?.isEpisodeBased(): Boolean {
if (this == null) return false
2022-06-20 13:17:34 +00:00
return this is EpisodeResponse && this.type.isEpisodeBased()
2021-06-17 15:39:01 +00:00
}
fun LoadResponse?.isAnimeBased(): Boolean {
if (this == null) return false
2022-01-31 20:47:59 +00:00
return (this.type == TvType.Anime || this.type == TvType.OVA) // && (this is AnimeLoadResponse)
2021-05-28 13:38:06 +00:00
}
2022-01-13 21:09:05 +00:00
fun TvType?.isEpisodeBased(): Boolean {
2022-01-07 19:27:25 +00:00
if (this == null) return false
return (this == TvType.TvSeries || this == TvType.Anime)
}
2022-06-20 13:17:34 +00:00
data class NextAiring(
val episode: Int,
val unixTime: Long,
)
2022-09-12 14:00:27 +00:00
/**
* @param season To be mapped with episode season, not shown in UI if displaySeason is defined
* @param name To be shown next to the season like "Season $displaySeason $name" but if displaySeason is null then "$name"
* @param displaySeason What to be displayed next to the season name, if null then the name is the only thing shown.
* */
2022-07-31 15:02:22 +00:00
data class SeasonData(
val season: Int,
val name: String? = null,
2022-08-06 15:16:04 +00:00
val displaySeason: Int? = null, // will use season if null
2022-07-31 15:02:22 +00:00
)
2022-06-20 13:17:34 +00:00
interface EpisodeResponse {
var showStatus: ShowStatus?
var nextAiring: NextAiring?
2022-07-31 15:02:22 +00:00
var seasonNames: List<SeasonData>?
}
@JvmName("addSeasonNamesString")
fun EpisodeResponse.addSeasonNames(names: List<String>) {
this.seasonNames = if (names.isEmpty()) null else names.mapIndexed { index, s ->
SeasonData(
season = index + 1,
s
)
}
}
@JvmName("addSeasonNamesSeasonData")
fun EpisodeResponse.addSeasonNames(names: List<SeasonData>) {
this.seasonNames = names.ifEmpty { null }
2022-06-20 13:17:34 +00:00
}
2021-08-30 17:11:04 +00:00
data class TorrentLoadResponse(
2021-12-13 18:41:33 +00:00
override var name: String,
override var url: String,
override var apiName: String,
var magnet: String?,
var torrent: String?,
override var plot: String?,
override var type: TvType = TvType.Torrent,
override var posterUrl: String? = null,
override var year: Int? = null,
override var rating: Int? = null,
override var tags: List<String>? = null,
override var duration: Int? = null,
2022-07-27 17:36:31 +00:00
override var trailers: MutableList<TrailerData> = mutableListOf(),
2021-12-13 18:41:33 +00:00
override var recommendations: List<SearchResponse>? = null,
override var actors: List<ActorData>? = null,
2022-03-20 23:59:17 +00:00
override var comingSoon: Boolean = false,
2022-04-03 21:41:28 +00:00
override var syncData: MutableMap<String, String> = mutableMapOf(),
2022-04-13 17:29:30 +00:00
override var posterHeaders: Map<String, String>? = null,
2022-07-31 15:02:22 +00:00
override var backgroundPosterUrl: String? = null,
2021-08-30 17:11:04 +00:00
) : LoadResponse
2021-04-30 17:20:15 +00:00
data class AnimeLoadResponse(
2021-11-02 15:09:29 +00:00
var engName: String? = null,
var japName: String? = null,
2021-12-13 18:41:33 +00:00
override var name: String,
override var url: String,
override var apiName: String,
override var type: TvType,
2021-04-30 17:20:15 +00:00
2021-11-02 15:09:29 +00:00
override var posterUrl: String? = null,
override var year: Int? = null,
2021-04-30 17:20:15 +00:00
var episodes: MutableMap<DubStatus, List<Episode>> = mutableMapOf(),
2022-06-20 13:17:34 +00:00
override var showStatus: ShowStatus? = null,
2021-04-30 17:20:15 +00:00
2021-11-02 15:09:29 +00:00
override var plot: String? = null,
override var tags: List<String>? = null,
var synonyms: List<String>? = null,
2021-04-30 17:20:15 +00:00
2021-11-02 15:09:29 +00:00
override var rating: Int? = null,
2021-12-13 18:41:33 +00:00
override var duration: Int? = null,
2022-07-27 17:36:31 +00:00
override var trailers: MutableList<TrailerData> = mutableListOf(),
2021-11-02 15:09:29 +00:00
override var recommendations: List<SearchResponse>? = null,
override var actors: List<ActorData>? = null,
2022-03-20 23:59:17 +00:00
override var comingSoon: Boolean = false,
2022-04-03 21:41:28 +00:00
override var syncData: MutableMap<String, String> = mutableMapOf(),
2022-04-13 17:29:30 +00:00
override var posterHeaders: Map<String, String>? = null,
2022-06-20 13:17:34 +00:00
override var nextAiring: NextAiring? = null,
2022-07-31 15:02:22 +00:00
override var seasonNames: List<SeasonData>? = null,
override var backgroundPosterUrl: String? = null,
2022-06-20 13:17:34 +00:00
) : LoadResponse, EpisodeResponse
2021-04-30 17:20:15 +00:00
fun AnimeLoadResponse.addEpisodes(status: DubStatus, episodes: List<Episode>?) {
2022-07-25 21:46:17 +00:00
if (episodes.isNullOrEmpty()) return
2021-11-02 15:09:29 +00:00
this.episodes[status] = episodes
}
2022-06-17 17:25:41 +00:00
suspend fun MainAPI.newAnimeLoadResponse(
2021-11-02 15:09:29 +00:00
name: String,
url: String,
type: TvType,
2022-04-13 17:29:30 +00:00
comingSoonIfNone: Boolean = true,
2022-06-17 17:25:41 +00:00
initializer: suspend AnimeLoadResponse.() -> Unit = { },
2021-11-02 15:09:29 +00:00
): AnimeLoadResponse {
val builder = AnimeLoadResponse(name = name, url = url, apiName = this.name, type = type)
builder.initializer()
2022-04-03 21:41:28 +00:00
if (comingSoonIfNone) {
2022-03-20 23:59:17 +00:00
builder.comingSoon = true
for (key in builder.episodes.keys)
2022-04-03 21:41:28 +00:00
if (!builder.episodes[key].isNullOrEmpty()) {
2022-03-20 23:59:17 +00:00
builder.comingSoon = false
break
}
}
2021-11-02 15:09:29 +00:00
return builder
}
data class LiveStreamLoadResponse(
override var name: String,
override var url: String,
override var apiName: String,
var dataUrl: String,
override var posterUrl: String? = null,
override var year: Int? = null,
override var plot: String? = null,
override var type: TvType = TvType.Live,
override var rating: Int? = null,
override var tags: List<String>? = null,
override var duration: Int? = null,
2022-07-27 17:36:31 +00:00
override var trailers: MutableList<TrailerData> = mutableListOf(),
override var recommendations: List<SearchResponse>? = null,
override var actors: List<ActorData>? = null,
override var comingSoon: Boolean = false,
override var syncData: MutableMap<String, String> = mutableMapOf(),
override var posterHeaders: Map<String, String>? = null,
2022-07-31 15:02:22 +00:00
override var backgroundPosterUrl: String? = null,
) : LoadResponse
2021-04-30 17:20:15 +00:00
data class MovieLoadResponse(
2021-12-13 18:41:33 +00:00
override var name: String,
override var url: String,
override var apiName: String,
override var type: TvType,
var dataUrl: String,
override var posterUrl: String? = null,
override var year: Int? = null,
override var plot: String? = null,
override var rating: Int? = null,
override var tags: List<String>? = null,
override var duration: Int? = null,
2022-07-27 17:36:31 +00:00
override var trailers: MutableList<TrailerData> = mutableListOf(),
2021-12-13 18:41:33 +00:00
override var recommendations: List<SearchResponse>? = null,
override var actors: List<ActorData>? = null,
2022-03-20 23:59:17 +00:00
override var comingSoon: Boolean = false,
2022-04-03 21:41:28 +00:00
override var syncData: MutableMap<String, String> = mutableMapOf(),
2022-04-13 17:29:30 +00:00
override var posterHeaders: Map<String, String>? = null,
2022-07-31 15:02:22 +00:00
override var backgroundPosterUrl: String? = null,
2021-04-30 17:20:15 +00:00
) : LoadResponse
2022-06-17 17:25:41 +00:00
suspend fun <T> MainAPI.newMovieLoadResponse(
name: String,
url: String,
type: TvType,
data: T?,
2022-06-17 17:25:41 +00:00
initializer: suspend MovieLoadResponse.() -> Unit = { }
): MovieLoadResponse {
2022-04-10 00:14:36 +00:00
// just in case
if (data is String) return newMovieLoadResponse(
name,
url,
type,
dataUrl = data,
initializer = initializer
)
val dataUrl = data?.toJson() ?: ""
val builder = MovieLoadResponse(
name = name,
url = url,
apiName = this.name,
type = type,
dataUrl = dataUrl,
comingSoon = dataUrl.isBlank()
)
builder.initializer()
return builder
}
2022-06-17 17:25:41 +00:00
suspend fun MainAPI.newMovieLoadResponse(
2021-12-13 18:41:33 +00:00
name: String,
url: String,
type: TvType,
dataUrl: String,
2022-06-17 17:25:41 +00:00
initializer: suspend MovieLoadResponse.() -> Unit = { }
2021-12-13 18:41:33 +00:00
): MovieLoadResponse {
2022-01-13 21:09:05 +00:00
val builder = MovieLoadResponse(
name = name,
url = url,
apiName = this.name,
type = type,
2022-03-20 23:59:17 +00:00
dataUrl = dataUrl,
comingSoon = dataUrl.isBlank()
2022-01-13 21:09:05 +00:00
)
2021-12-13 18:41:33 +00:00
builder.initializer()
return builder
}
data class Episode(
var data: String,
var name: String? = null,
var season: Int? = null,
var episode: Int? = null,
var posterUrl: String? = null,
var rating: Int? = null,
var description: String? = null,
var date: Long? = null,
2021-06-26 14:44:53 +00:00
)
2022-04-10 00:14:36 +00:00
fun Episode.addDate(date: String?, format: String = "yyyy-MM-dd") {
try {
2022-04-10 00:14:36 +00:00
this.date = SimpleDateFormat(format)?.parse(date ?: return)?.time
} catch (e: Exception) {
logError(e)
}
}
fun Episode.addDate(date: Date?) {
this.date = date?.time
}
2022-04-10 00:14:36 +00:00
fun MainAPI.newEpisode(
url: String,
initializer: Episode.() -> Unit = { },
fix: Boolean = true,
): Episode {
val builder = Episode(
data = if (fix) fixUrl(url) else url
)
builder.initializer()
return builder
}
fun <T> MainAPI.newEpisode(
data: T,
initializer: Episode.() -> Unit = { }
): Episode {
2022-04-10 00:14:36 +00:00
if (data is String) return newEpisode(
url = data,
initializer = initializer
) // just in case java is wack
val builder = Episode(
data = data?.toJson() ?: throw ErrorLoadingException("invalid newEpisode")
)
builder.initializer()
return builder
}
2021-04-30 17:20:15 +00:00
data class TvSeriesLoadResponse(
2021-12-13 18:41:33 +00:00
override var name: String,
override var url: String,
override var apiName: String,
override var type: TvType,
var episodes: List<Episode>,
2021-12-13 18:41:33 +00:00
override var posterUrl: String? = null,
override var year: Int? = null,
override var plot: String? = null,
2022-06-20 13:17:34 +00:00
override var showStatus: ShowStatus? = null,
2021-12-13 18:41:33 +00:00
override var rating: Int? = null,
override var tags: List<String>? = null,
override var duration: Int? = null,
2022-07-27 17:36:31 +00:00
override var trailers: MutableList<TrailerData> = mutableListOf(),
2021-12-13 18:41:33 +00:00
override var recommendations: List<SearchResponse>? = null,
override var actors: List<ActorData>? = null,
2022-03-20 23:59:17 +00:00
override var comingSoon: Boolean = false,
2022-04-03 21:41:28 +00:00
override var syncData: MutableMap<String, String> = mutableMapOf(),
2022-04-13 17:29:30 +00:00
override var posterHeaders: Map<String, String>? = null,
2022-06-20 13:17:34 +00:00
override var nextAiring: NextAiring? = null,
2022-07-31 15:02:22 +00:00
override var seasonNames: List<SeasonData>? = null,
override var backgroundPosterUrl: String? = null,
2022-06-20 13:17:34 +00:00
) : LoadResponse, EpisodeResponse
2021-12-13 18:41:33 +00:00
2022-06-17 17:25:41 +00:00
suspend fun MainAPI.newTvSeriesLoadResponse(
2021-12-13 18:41:33 +00:00
name: String,
url: String,
type: TvType,
episodes: List<Episode>,
2022-06-17 17:25:41 +00:00
initializer: suspend TvSeriesLoadResponse.() -> Unit = { }
2021-12-13 18:41:33 +00:00
): TvSeriesLoadResponse {
2022-01-13 21:09:05 +00:00
val builder = TvSeriesLoadResponse(
name = name,
url = url,
apiName = this.name,
type = type,
2022-03-20 23:59:17 +00:00
episodes = episodes,
comingSoon = episodes.isEmpty(),
2022-01-13 21:09:05 +00:00
)
2021-12-13 18:41:33 +00:00
builder.initializer()
return builder
}
fun fetchUrls(text: String?): List<String> {
if (text.isNullOrEmpty()) {
return listOf()
}
val linkRegex =
2022-04-08 19:38:19 +00:00
Regex("""(https?://(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&/=]*))""")
return linkRegex.findAll(text).map { it.value.trim().removeSurrounding("\"") }.toList()
}
2022-04-03 21:41:28 +00:00
fun String?.toRatingInt(): Int? =
2022-04-10 00:14:36 +00:00
this?.replace(" ", "")?.trim()?.toDoubleOrNull()?.absoluteValue?.times(1000f)?.toInt()