This commit is contained in:
alex 2024-01-16 15:56:30 +07:00
parent 7d49cd44a0
commit 11b4998d1d
9 changed files with 137 additions and 141 deletions

View file

@ -1,7 +1,7 @@
import org.jetbrains.kotlin.konan.properties.Properties import org.jetbrains.kotlin.konan.properties.Properties
// use an integer for version numbers // use an integer for version numbers
version = 217 version = 218
android { android {
defaultConfig { defaultConfig {

View file

@ -1275,7 +1275,7 @@ object SoraExtractor : SoraStream() {
val selector = val selector =
if (season == null) "p a:contains(V-Cloud)" else "h4:matches(0?$episode) + p a:contains(V-Cloud)" if (season == null) "p a:contains(V-Cloud)" else "h4:matches(0?$episode) + p a:contains(V-Cloud)"
val server = app.get( val server = app.get(
href ?: return@apmap, interceptor = wpredisInterceptor href ?: return@apmap, interceptor = wpRedisInterceptor
).document.selectFirst("div.entry-content > $selector") ).document.selectFirst("div.entry-content > $selector")
?.attr("href") ?: return@apmap ?.attr("href") ?: return@apmap
@ -1717,13 +1717,13 @@ object SoraExtractor : SoraStream() {
"$url&apikey=whXgvN4kVyoubGwqXpw26Oy3PVryl8dm", "$url&apikey=whXgvN4kVyoubGwqXpw26Oy3PVryl8dm",
referer = "https://watcha.movie/" referer = "https://watcha.movie/"
).text ).text
val link = Regex("\"file\":\"(http.*?)\"").find(res)?.groupValues?.getOrNull(1) ?: return val link = Regex("\"file\":\"(http.*?)\"").find(res)?.groupValues?.getOrNull(1)
callback.invoke( callback.invoke(
ExtractorLink( ExtractorLink(
"RStream", "RStream",
"RStream", "RStream",
link, link ?: return,
"$rStreamAPI/", "$rStreamAPI/",
Qualities.P1080.value, Qualities.P1080.value,
INFER_TYPE INFER_TYPE
@ -2053,7 +2053,7 @@ object SoraExtractor : SoraStream() {
"$dahmerMoviesAPI/tvs/${title?.replace(":", " -")}/Season $season/" "$dahmerMoviesAPI/tvs/${title?.replace(":", " -")}/Season $season/"
} }
val request = app.get(url, timeout = 120L) val request = app.get(url, interceptor = TimeOutInterceptor())
if (!request.isSuccessful) return if (!request.isSuccessful) return
val paths = request.document.select("a").map { val paths = request.document.select("a").map {
it.text() to it.attr("href") it.text() to it.attr("href")
@ -2382,12 +2382,18 @@ object SoraExtractor : SoraStream() {
callback: (ExtractorLink) -> Unit, callback: (ExtractorLink) -> Unit,
referer: String = "https://bflix.gs/" referer: String = "https://bflix.gs/"
) { ) {
suspend fun String.isSuccess() : Boolean {
return app.get(this, referer = referer).isSuccessful
}
val slug = getEpisodeSlug(season, episode) val slug = getEpisodeSlug(season, episode)
var url = var url = if (season == null) "$nowTvAPI/$tmdbId.mp4" else "$nowTvAPI/tv/$tmdbId/s${season}e${slug.second}.mp4"
if (season == null) "$nowTvAPI/$tmdbId.mp4" else "$nowTvAPI/tv/$tmdbId/s${season}e${slug.second}.mp4" if (!url.isSuccess()) {
if (!app.get(url, referer = referer).isSuccessful) { url = if (season == null) {
url = val temp = "$nowTvAPI/$imdbId.mp4"
if (season == null) "$nowTvAPI/$imdbId.mp4" else "$nowTvAPI/tv/$imdbId/s${season}e${slug.second}.mp4" if (temp.isSuccess()) temp else "$nowTvAPI/$tmdbId-1.mp4"
} else {
"$nowTvAPI/tv/$imdbId/s${season}e${slug.second}.mp4"
}
if (!app.get(url, referer = referer).isSuccessful) return if (!app.get(url, referer = referer).isSuccessful) return
} }
callback.invoke( callback.invoke(

View file

@ -68,7 +68,7 @@ open class SoraStream : TmdbProvider() {
TvType.Anime, TvType.Anime,
) )
val wpredisInterceptor by lazy { CloudflareKiller() } val wpRedisInterceptor by lazy { CloudflareKiller() }
val multiInterceptor by lazy { CloudflareKiller() } val multiInterceptor by lazy { CloudflareKiller() }
/** AUTHOR : Hexated & Sora */ /** AUTHOR : Hexated & Sora */
@ -250,8 +250,7 @@ open class SoraStream : TmdbProvider() {
val recommendations = val recommendations =
res.recommendations?.results?.mapNotNull { media -> media.toSearchResponse() } res.recommendations?.results?.mapNotNull { media -> media.toSearchResponse() }
val trailer = val trailer = res.videos?.results?.map { "https://www.youtube.com/watch?v=${it.key}" }
res.videos?.results?.map { "https://www.youtube.com/watch?v=${it.key}" }?.randomOrNull()
return if (type == TvType.TvSeries) { return if (type == TvType.TvSeries) {
val lastSeason = res.last_episode_to_air?.season_number val lastSeason = res.last_episode_to_air?.season_number

View file

@ -23,6 +23,7 @@ import com.lagradost.nicehttp.requestCreator
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import okhttp3.FormBody import okhttp3.FormBody
import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Interceptor
import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
@ -1285,23 +1286,17 @@ private enum class Symbol(val decimalValue: Int) {
} }
} }
suspend fun request( class TimeOutInterceptor : Interceptor {
url: String, override fun intercept(chain: Interceptor.Chain): Response {
allowRedirects: Boolean = true, val call = chain
timeout: Long = 60L .withConnectTimeout(60, TimeUnit.SECONDS)
): Response { .withReadTimeout(60, TimeUnit.SECONDS)
val client = OkHttpClient().newBuilder() .withWriteTimeout(60, TimeUnit.SECONDS)
.connectTimeout(timeout, TimeUnit.SECONDS) .request()
.readTimeout(timeout, TimeUnit.SECONDS) .newBuilder()
.writeTimeout(timeout, TimeUnit.SECONDS)
.followRedirects(allowRedirects)
.followSslRedirects(allowRedirects)
.build() .build()
return chain.proceed(call)
val request: Request = Request.Builder() }
.url(url)
.build()
return client.newCall(request).await()
} }
// steal from https://github.com/aniyomiorg/aniyomi-extensions/blob/master/src/en/aniwave/src/eu/kanade/tachiyomi/animeextension/en/nineanime/AniwaveUtils.kt // steal from https://github.com/aniyomiorg/aniyomi-extensions/blob/master/src/en/aniwave/src/eu/kanade/tachiyomi/animeextension/en/nineanime/AniwaveUtils.kt

View file

@ -1,6 +1,16 @@
// use an integer for version numbers import org.jetbrains.kotlin.konan.properties.Properties
version = 12
// use an integer for version numbers
version = 13
android {
defaultConfig {
val properties = Properties()
properties.load(project.rootProject.file("local.properties").inputStream())
buildConfigField("String", "TMDB_API", "\"${properties.getProperty("TMDB_API")}\"")
}
}
cloudstream { cloudstream {
language = "en" language = "en"

View file

@ -10,24 +10,23 @@ import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.*
import com.lagradost.cloudstream3.utils.AppUtils.parseJson import com.lagradost.cloudstream3.utils.AppUtils.parseJson
import com.lagradost.cloudstream3.utils.AppUtils.toJson import com.lagradost.cloudstream3.utils.AppUtils.toJson
import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
import java.net.URI
private const val TRACKER_LIST_URL =
"https://raw.githubusercontent.com/ngosang/trackerslist/master/trackers_best.txt"
class StremioC : MainAPI() { class StremioC : MainAPI() {
override var mainUrl = "https://stremio.github.io/stremio-static-addon-example" override var mainUrl = "https://stremio.github.io/stremio-static-addon-example"
override var name = "StremioC" override var name = "StremioC"
override val supportedTypes = setOf(TvType.Others) override val supportedTypes = setOf(TvType.Others)
override val hasMainPage = true override val hasMainPage = true
private val cinemataUrl = "https://v3-cinemeta.strem.io"
override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse? { companion object {
private const val cinemataUrl = "https://v3-cinemeta.strem.io"
private const val TRACKER_LIST_URL = "https://raw.githubusercontent.com/ngosang/trackerslist/master/trackers_best.txt"
}
override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse {
mainUrl = mainUrl.fixSourceUrl() mainUrl = mainUrl.fixSourceUrl()
val res = tryParseJson<Manifest>(request("${mainUrl}/manifest.json").body.string()) ?: return null val res = app.get("${mainUrl}/manifest.json").parsedSafe<Manifest>()
val lists = mutableListOf<HomePageList>() val lists = mutableListOf<HomePageList>()
res.catalogs.apmap { catalog -> res?.catalogs?.apmap { catalog ->
catalog.toHomePageList(this).let { catalog.toHomePageList(this).let {
if (it.list.isNotEmpty()) lists.add(it) if (it.list.isNotEmpty()) lists.add(it)
} }
@ -38,11 +37,11 @@ class StremioC : MainAPI() {
) )
} }
override suspend fun search(query: String): List<SearchResponse>? { override suspend fun search(query: String): List<SearchResponse> {
mainUrl = mainUrl.fixSourceUrl() mainUrl = mainUrl.fixSourceUrl()
val res = tryParseJson<Manifest>(request("${mainUrl}/manifest.json").body.string()) ?: return null val res = app.get("${mainUrl}/manifest.json").parsedSafe<Manifest>()
val list = mutableListOf<SearchResponse>() val list = mutableListOf<SearchResponse>()
res.catalogs.apmap { catalog -> res?.catalogs?.apmap { catalog ->
list.addAll(catalog.search(query, this)) list.addAll(catalog.search(query, this))
} }
return list.distinct() return list.distinct()
@ -64,10 +63,13 @@ class StremioC : MainAPI() {
callback: (ExtractorLink) -> Unit callback: (ExtractorLink) -> Unit
): Boolean { ): Boolean {
val loadData = parseJson<LoadData>(data) val loadData = parseJson<LoadData>(data)
val request = request("${mainUrl}/stream/${loadData.type}/${loadData.id}.json") val request = app.get(
if (request.code.isSuccessful()) { "${mainUrl}/stream/${loadData.type}/${loadData.id}.json",
val res = tryParseJson<StreamsResponse>(request.body.string()) ?: return false interceptor = interceptor
res.streams.forEach { stream -> )
if (request.isSuccessful) {
val res = request.parsedSafe<StreamsResponse>()
res?.streams?.forEach { stream ->
stream.runCallback(subtitleCallback, callback) stream.runCallback(subtitleCallback, callback)
} }
} else { } else {
@ -103,15 +105,14 @@ class StremioC : MainAPI() {
subtitleCallback: (SubtitleFile) -> Unit, subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit callback: (ExtractorLink) -> Unit
) { ) {
val sites = val sites = AcraApplication.getKey<Array<CustomSite>>(USER_PROVIDER_API)?.toMutableList()
AcraApplication.getKey<Array<CustomSite>>(USER_PROVIDER_API)?.toMutableList()
?: mutableListOf() ?: mutableListOf()
sites.filter { it.parentJavaClass == "StremioX" }.apmap { site -> sites.filter { it.parentJavaClass == "StremioX" }.apmap { site ->
val request = request("${site.url.fixSourceUrl()}/stream/${type}/${id}.json").body.string() val res = app.get(
val res = "${site.url.fixSourceUrl()}/stream/${type}/${id}.json",
tryParseJson<StreamsResponse>(request) interceptor = interceptor
?: return@apmap ).parsedSafe<StreamsResponse>()
res.streams.forEach { stream -> res?.streams?.forEach { stream ->
stream.runCallback(subtitleCallback, callback) stream.runCallback(subtitleCallback, callback)
} }
} }
@ -151,11 +152,11 @@ class StremioC : MainAPI() {
suspend fun search(query: String, provider: StremioC): List<SearchResponse> { suspend fun search(query: String, provider: StremioC): List<SearchResponse> {
val entries = mutableListOf<SearchResponse>() val entries = mutableListOf<SearchResponse>()
types.forEach { type -> types.forEach { type ->
val json = request("${provider.mainUrl}/catalog/${type}/${id}/search=${query}.json").body.string() val res = app.get(
val res = "${provider.mainUrl}/catalog/${type}/${id}/search=${query}.json",
tryParseJson<CatalogResponse>(json) interceptor = interceptor
?: return@forEach ).parsedSafe<CatalogResponse>()
res.metas?.forEach { entry -> res?.metas?.forEach { entry ->
entries.add(entry.toSearchResponse(provider)) entries.add(entry.toSearchResponse(provider))
} }
} }
@ -165,11 +166,11 @@ class StremioC : MainAPI() {
suspend fun toHomePageList(provider: StremioC): HomePageList { suspend fun toHomePageList(provider: StremioC): HomePageList {
val entries = mutableListOf<SearchResponse>() val entries = mutableListOf<SearchResponse>()
types.forEach { type -> types.forEach { type ->
val json = request("${provider.mainUrl}/catalog/${type}/${id}.json").body.string() val res = app.get(
val res = "${provider.mainUrl}/catalog/${type}/${id}.json",
tryParseJson<CatalogResponse>(json) interceptor = interceptor
?: return@forEach ).parsedSafe<CatalogResponse>()
res.metas?.forEach { entry -> res?.metas?.forEach { entry ->
entries.add(entry.toSearchResponse(provider)) entries.add(entry.toSearchResponse(provider))
} }
} }
@ -186,6 +187,7 @@ class StremioC : MainAPI() {
val source: String?, val source: String?,
val type: String? val type: String?
) )
private data class CatalogEntry( private data class CatalogEntry(
@JsonProperty("name") val name: String, @JsonProperty("name") val name: String,
@JsonProperty("id") val id: String, @JsonProperty("id") val id: String,
@ -226,7 +228,7 @@ class StremioC : MainAPI() {
year = yearNum?.toIntOrNull() year = yearNum?.toIntOrNull()
tags = genre ?: genres tags = genre ?: genres
addActors(cast) addActors(cast)
addTrailer(trailersSources?.map { "https://www.youtube.com/watch?v=${it.source}" }?.randomOrNull()) addTrailer(trailersSources?.map { "https://www.youtube.com/watch?v=${it.source}" })
addImdbId(imdbId) addImdbId(imdbId)
} }
} else { } else {
@ -245,7 +247,8 @@ class StremioC : MainAPI() {
year = yearNum?.toIntOrNull() year = yearNum?.toIntOrNull()
tags = genre ?: genres tags = genre ?: genres
addActors(cast) addActors(cast)
addTrailer(trailersSources?.map { "https://www.youtube.com/watch?v=${it.source}" }?.randomOrNull()) addTrailer(trailersSources?.map { "https://www.youtube.com/watch?v=${it.source}" }
?.randomOrNull())
addImdbId(imdbId) addImdbId(imdbId)
} }
} }
@ -292,6 +295,7 @@ class StremioC : MainAPI() {
val proxyHeaders: ProxyHeaders?, val proxyHeaders: ProxyHeaders?,
val headers: Map<String, String>?, val headers: Map<String, String>?,
) )
private data class Stream( private data class Stream(
val name: String?, val name: String?,
val title: String?, val title: String?,
@ -312,12 +316,13 @@ class StremioC : MainAPI() {
callback.invoke( callback.invoke(
ExtractorLink( ExtractorLink(
name ?: "", name ?: "",
fixRDSourceName(name, title), fixSourceName(name, title),
url, url,
"", "",
getQualityFromName(description), getQuality(listOf(description,title,name)),
headers = behaviorHints?.proxyHeaders?.request ?: behaviorHints?.headers ?: mapOf(), headers = behaviorHints?.proxyHeaders?.request ?: behaviorHints?.headers
isM3u8 = URI(url).path.endsWith(".m3u8") ?: mapOf(),
type = INFER_TYPE
) )
) )
subtitles.map { sub -> subtitles.map { sub ->

View file

@ -10,26 +10,21 @@ import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.*
import com.lagradost.cloudstream3.utils.AppUtils.parseJson import com.lagradost.cloudstream3.utils.AppUtils.parseJson
import com.lagradost.cloudstream3.utils.AppUtils.toJson import com.lagradost.cloudstream3.utils.AppUtils.toJson
import java.net.URI
import java.util.ArrayList import java.util.ArrayList
import kotlin.math.roundToInt import kotlin.math.roundToInt
import com.lagradost.cloudstream3.metaproviders.TmdbProvider import com.lagradost.cloudstream3.metaproviders.TmdbProvider
open class StremioX : TmdbProvider() { class StremioX : TmdbProvider() {
override var mainUrl = "https://torrentio.strem.fun" override var mainUrl = "https://torrentio.strem.fun"
override var name = "StremioX" override var name = "StremioX"
override val hasMainPage = true override val hasMainPage = true
override val hasQuickSearch = true override val hasQuickSearch = true
override val supportedTypes = setOf( override val supportedTypes = setOf(TvType.Others)
TvType.Others,
)
companion object { companion object {
const val TRACKER_LIST_URL = const val TRACKER_LIST_URL = "https://raw.githubusercontent.com/ngosang/trackerslist/master/trackers_best.txt"
"https://raw.githubusercontent.com/ngosang/trackerslist/master/trackers_best.txt"
private const val tmdbAPI = "https://api.themoviedb.org/3" private const val tmdbAPI = "https://api.themoviedb.org/3"
private val apiKey = private const val apiKey = BuildConfig.TMDB_API
base64DecodeAPI("ZTM=NTg=MjM=MjM=ODc=MzI=OGQ=MmE=Nzk=Nzk=ZjI=NTA=NDY=NDA=MzA=YjA=") // PLEASE DON'T STEAL
fun getType(t: String?): TvType { fun getType(t: String?): TvType {
return when (t) { return when (t) {
@ -44,11 +39,6 @@ open class StremioX : TmdbProvider() {
else -> ShowStatus.Completed else -> ShowStatus.Completed
} }
} }
private fun base64DecodeAPI(api: String): String {
return api.chunked(4).map { base64Decode(it) }.reversed().joinToString("")
}
} }
override val mainPage = mainPageOf( override val mainPage = mainPageOf(
@ -159,7 +149,7 @@ open class StremioX : TmdbProvider() {
eps.seasonNumber, eps.seasonNumber,
eps.episodeNumber eps.episodeNumber
).toJson(), ).toJson(),
name = eps.name + if (isUpcoming(eps.airDate)) " - [UPCOMING]" else "", name = eps.name + if (isUpcoming(eps.airDate)) " [UPCOMING]" else "",
season = eps.seasonNumber, season = eps.seasonNumber,
episode = eps.episodeNumber, episode = eps.episodeNumber,
posterUrl = getImageUrl(eps.stillPath), posterUrl = getImageUrl(eps.stillPath),
@ -177,7 +167,7 @@ open class StremioX : TmdbProvider() {
this.backgroundPosterUrl = bgPoster this.backgroundPosterUrl = bgPoster
this.year = year this.year = year
this.plot = res.overview this.plot = res.overview
this.tags = if (isAnime) keywords else genres this.tags = keywords.takeIf { !it.isNullOrEmpty() } ?: genres
this.rating = rating this.rating = rating
this.showStatus = getStatus(res.status) this.showStatus = getStatus(res.status)
this.recommendations = recommendations this.recommendations = recommendations
@ -200,7 +190,7 @@ open class StremioX : TmdbProvider() {
this.year = year this.year = year
this.plot = res.overview this.plot = res.overview
this.duration = res.runtime this.duration = res.runtime
this.tags = if (isAnime) keywords else genres this.tags = keywords.takeIf { !it.isNullOrEmpty() } ?: genres
this.rating = rating this.rating = rating
this.recommendations = recommendations this.recommendations = recommendations
this.actors = actors this.actors = actors
@ -248,8 +238,8 @@ open class StremioX : TmdbProvider() {
} else { } else {
"$fixMainUrl/stream/series/$imdbId:$season:$episode.json" "$fixMainUrl/stream/series/$imdbId:$season:$episode.json"
} }
val res = AppUtils.tryParseJson<StreamsResponse>(request(url).body.string()) ?: return val res = app.get(url, interceptor = interceptor).parsedSafe<StreamsResponse>()
res.streams.forEach { stream -> res?.streams?.forEach { stream ->
stream.runCallback(subtitleCallback, callback) stream.runCallback(subtitleCallback, callback)
} }
} }
@ -290,12 +280,13 @@ open class StremioX : TmdbProvider() {
callback.invoke( callback.invoke(
ExtractorLink( ExtractorLink(
name ?: "", name ?: "",
fixRDSourceName(name, title), fixSourceName(name, title),
url, url,
"", "",
getQualityFromName(description), getQuality(listOf(description,title,name)),
headers = behaviorHints?.proxyHeaders?.request ?: behaviorHints?.headers ?: mapOf(), headers = behaviorHints?.proxyHeaders?.request ?: behaviorHints?.headers
isM3u8 = URI(url).path.endsWith(".m3u8") ?: mapOf(),
type = INFER_TYPE
) )
) )
subtitles.map { sub -> subtitles.map { sub ->

View file

@ -3,10 +3,9 @@ package com.hexated
import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.SubtitleFile import com.lagradost.cloudstream3.SubtitleFile
import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.base64Encode
import com.lagradost.cloudstream3.utils.SubtitleHelper import com.lagradost.cloudstream3.utils.SubtitleHelper
const val openSubAPI = "https://opensubtitles.strem.io/stremio/v1" const val openSubAPI = "https://opensubtitles-v3.strem.io"
const val watchSomuchAPI = "https://watchsomuch.tv" const val watchSomuchAPI = "https://watchsomuch.tv"
object SubsExtractors { object SubsExtractors {
@ -16,22 +15,20 @@ object SubsExtractors {
episode: Int? = null, episode: Int? = null,
subtitleCallback: (SubtitleFile) -> Unit, subtitleCallback: (SubtitleFile) -> Unit,
) { ) {
val id = if(season == null) { val slug = if(season == null) {
imdbId "movie/$imdbId"
} else { } else {
"$imdbId $season $episode" "series/$imdbId:$season:$episode"
} }
val data = base64Encode("""{"id":1,"jsonrpc":"2.0","method":"subtitles.find","params":[null,{"query":{"itemHash":"$id"}}]}""".toByteArray()) app.get("${openSubAPI}/subtitles/$slug.json").parsedSafe<OsResult>()?.subtitles?.map { sub ->
app.get("${openSubAPI}/q.json?b=$data").parsedSafe<OsResult>()?.result?.all?.map { sub ->
subtitleCallback.invoke( subtitleCallback.invoke(
SubtitleFile( SubtitleFile(
SubtitleHelper.fromThreeLettersToLanguage(sub.lang ?: "") ?: sub.lang SubtitleHelper.fromThreeLettersToLanguage(sub.lang ?: "") ?: sub.lang
?: "", ?: return@map,
sub.url ?: return@map sub.url ?: return@map
) )
) )
} }
} }
suspend fun invokeWatchsomuch( suspend fun invokeWatchsomuch(
@ -81,12 +78,8 @@ object SubsExtractors {
@JsonProperty("lang") val lang: String? = null, @JsonProperty("lang") val lang: String? = null,
) )
data class OsAll(
@JsonProperty("all") val all: ArrayList<OsSubtitles>? = arrayListOf(),
)
data class OsResult( data class OsResult(
@JsonProperty("result") val result: OsAll? = null, @JsonProperty("subtitles") val subtitles: ArrayList<OsSubtitles>? = arrayListOf(),
) )
data class WatchsomuchTorrents( data class WatchsomuchTorrents(

View file

@ -1,53 +1,50 @@
package com.hexated package com.hexated
import com.lagradost.cloudstream3.APIHolder
import com.lagradost.cloudstream3.APIHolder.unixTimeMS import com.lagradost.cloudstream3.APIHolder.unixTimeMS
import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.nicehttp.Requests.Companion.await import com.lagradost.cloudstream3.utils.getQualityFromName
import okhttp3.OkHttpClient import okhttp3.Interceptor
import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
const val defaultTimeOut = 30L val interceptor = TimeOutInterceptor()
suspend fun request(
url: String,
allowRedirects: Boolean = true,
timeout: Long = defaultTimeOut
): Response {
val client = OkHttpClient().newBuilder()
.connectTimeout(timeout, TimeUnit.SECONDS)
.readTimeout(timeout, TimeUnit.SECONDS)
.writeTimeout(timeout, TimeUnit.SECONDS)
.followRedirects(allowRedirects)
.followSslRedirects(allowRedirects)
.build()
val request: Request = Request.Builder() class TimeOutInterceptor : Interceptor {
.url(url) override fun intercept(chain: Interceptor.Chain): Response {
val call = chain
.withConnectTimeout(60, TimeUnit.SECONDS)
.withReadTimeout(60, TimeUnit.SECONDS)
.withWriteTimeout(60, TimeUnit.SECONDS)
.request()
.newBuilder()
.build() .build()
return client.newCall(request).await() return chain.proceed(call)
} }
fun Int.isSuccessful() : Boolean {
return this in 200..299
} }
fun String.fixSourceUrl(): String { fun String.fixSourceUrl(): String {
return this.replace("/manifest.json", "").replace("stremio://", "https://") return this.replace("/manifest.json", "").replace("stremio://", "https://")
} }
fun fixRDSourceName(name: String?, title: String?): String { fun fixSourceName(name: String?, title: String?): String {
return when { return when {
name?.contains("[RD+]", true) == true -> "[RD+] $title" name?.contains("[RD+]", true) == true -> "[RD+] $title"
name?.contains("[RD download]", true) == true -> "[RD] $title" name?.contains("[RD download]", true) == true -> "[RD download] $title"
!name.isNullOrEmpty() && !title.isNullOrEmpty() -> "$name $title" !name.isNullOrEmpty() && !title.isNullOrEmpty() -> "$name $title"
else -> title ?: name ?: "" else -> title ?: name ?: ""
} }
} }
fun getQuality(qualities: List<String?>): Int {
fun String.getQuality(): String? {
return Regex("(\\d{3,4}[pP])").find(this)?.groupValues?.getOrNull(1)
}
val quality = qualities.firstNotNullOfOrNull { it?.getQuality() }
return getQualityFromName(quality)
}
fun getEpisodeSlug( fun getEpisodeSlug(
season: Int? = null, season: Int? = null,
episode: Int? = null, episode: Int? = null,