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
// use an integer for version numbers
version = 217
version = 218
android {
defaultConfig {

View file

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

View file

@ -68,7 +68,7 @@ open class SoraStream : TmdbProvider() {
TvType.Anime,
)
val wpredisInterceptor by lazy { CloudflareKiller() }
val wpRedisInterceptor by lazy { CloudflareKiller() }
val multiInterceptor by lazy { CloudflareKiller() }
/** AUTHOR : Hexated & Sora */
@ -250,8 +250,7 @@ open class SoraStream : TmdbProvider() {
val recommendations =
res.recommendations?.results?.mapNotNull { media -> media.toSearchResponse() }
val trailer =
res.videos?.results?.map { "https://www.youtube.com/watch?v=${it.key}" }?.randomOrNull()
val trailer = res.videos?.results?.map { "https://www.youtube.com/watch?v=${it.key}" }
return if (type == TvType.TvSeries) {
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 okhttp3.FormBody
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Interceptor
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.OkHttpClient
import okhttp3.Request
@ -1285,23 +1286,17 @@ private enum class Symbol(val decimalValue: Int) {
}
}
suspend fun request(
url: String,
allowRedirects: Boolean = true,
timeout: Long = 60L
): Response {
val client = OkHttpClient().newBuilder()
.connectTimeout(timeout, TimeUnit.SECONDS)
.readTimeout(timeout, TimeUnit.SECONDS)
.writeTimeout(timeout, TimeUnit.SECONDS)
.followRedirects(allowRedirects)
.followSslRedirects(allowRedirects)
class TimeOutInterceptor : Interceptor {
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()
val request: Request = Request.Builder()
.url(url)
.build()
return client.newCall(request).await()
return chain.proceed(call)
}
}
// 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
version = 12
import org.jetbrains.kotlin.konan.properties.Properties
// 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 {
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.AppUtils.parseJson
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() {
override var mainUrl = "https://stremio.github.io/stremio-static-addon-example"
override var name = "StremioC"
override val supportedTypes = setOf(TvType.Others)
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()
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>()
res.catalogs.apmap { catalog ->
res?.catalogs?.apmap { catalog ->
catalog.toHomePageList(this).let {
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()
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>()
res.catalogs.apmap { catalog ->
res?.catalogs?.apmap { catalog ->
list.addAll(catalog.search(query, this))
}
return list.distinct()
@ -64,10 +63,13 @@ class StremioC : MainAPI() {
callback: (ExtractorLink) -> Unit
): Boolean {
val loadData = parseJson<LoadData>(data)
val request = request("${mainUrl}/stream/${loadData.type}/${loadData.id}.json")
if (request.code.isSuccessful()) {
val res = tryParseJson<StreamsResponse>(request.body.string()) ?: return false
res.streams.forEach { stream ->
val request = app.get(
"${mainUrl}/stream/${loadData.type}/${loadData.id}.json",
interceptor = interceptor
)
if (request.isSuccessful) {
val res = request.parsedSafe<StreamsResponse>()
res?.streams?.forEach { stream ->
stream.runCallback(subtitleCallback, callback)
}
} else {
@ -103,15 +105,14 @@ class StremioC : MainAPI() {
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit
) {
val sites =
AcraApplication.getKey<Array<CustomSite>>(USER_PROVIDER_API)?.toMutableList()
val sites = AcraApplication.getKey<Array<CustomSite>>(USER_PROVIDER_API)?.toMutableList()
?: mutableListOf()
sites.filter { it.parentJavaClass == "StremioX" }.apmap { site ->
val request = request("${site.url.fixSourceUrl()}/stream/${type}/${id}.json").body.string()
val res =
tryParseJson<StreamsResponse>(request)
?: return@apmap
res.streams.forEach { stream ->
val res = app.get(
"${site.url.fixSourceUrl()}/stream/${type}/${id}.json",
interceptor = interceptor
).parsedSafe<StreamsResponse>()
res?.streams?.forEach { stream ->
stream.runCallback(subtitleCallback, callback)
}
}
@ -151,11 +152,11 @@ class StremioC : MainAPI() {
suspend fun search(query: String, provider: StremioC): List<SearchResponse> {
val entries = mutableListOf<SearchResponse>()
types.forEach { type ->
val json = request("${provider.mainUrl}/catalog/${type}/${id}/search=${query}.json").body.string()
val res =
tryParseJson<CatalogResponse>(json)
?: return@forEach
res.metas?.forEach { entry ->
val res = app.get(
"${provider.mainUrl}/catalog/${type}/${id}/search=${query}.json",
interceptor = interceptor
).parsedSafe<CatalogResponse>()
res?.metas?.forEach { entry ->
entries.add(entry.toSearchResponse(provider))
}
}
@ -165,11 +166,11 @@ class StremioC : MainAPI() {
suspend fun toHomePageList(provider: StremioC): HomePageList {
val entries = mutableListOf<SearchResponse>()
types.forEach { type ->
val json = request("${provider.mainUrl}/catalog/${type}/${id}.json").body.string()
val res =
tryParseJson<CatalogResponse>(json)
?: return@forEach
res.metas?.forEach { entry ->
val res = app.get(
"${provider.mainUrl}/catalog/${type}/${id}.json",
interceptor = interceptor
).parsedSafe<CatalogResponse>()
res?.metas?.forEach { entry ->
entries.add(entry.toSearchResponse(provider))
}
}
@ -186,6 +187,7 @@ class StremioC : MainAPI() {
val source: String?,
val type: String?
)
private data class CatalogEntry(
@JsonProperty("name") val name: String,
@JsonProperty("id") val id: String,
@ -226,7 +228,7 @@ class StremioC : MainAPI() {
year = yearNum?.toIntOrNull()
tags = genre ?: genres
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)
}
} else {
@ -245,7 +247,8 @@ class StremioC : MainAPI() {
year = yearNum?.toIntOrNull()
tags = genre ?: genres
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)
}
}
@ -285,13 +288,14 @@ class StremioC : MainAPI() {
)
private data class ProxyHeaders(
val request: Map<String,String>?,
val request: Map<String, String>?,
)
private data class BehaviorHints(
val proxyHeaders: ProxyHeaders?,
val headers: Map<String,String>?,
val headers: Map<String, String>?,
)
private data class Stream(
val name: String?,
val title: String?,
@ -312,12 +316,13 @@ class StremioC : MainAPI() {
callback.invoke(
ExtractorLink(
name ?: "",
fixRDSourceName(name, title),
fixSourceName(name, title),
url,
"",
getQualityFromName(description),
headers = behaviorHints?.proxyHeaders?.request ?: behaviorHints?.headers ?: mapOf(),
isM3u8 = URI(url).path.endsWith(".m3u8")
getQuality(listOf(description,title,name)),
headers = behaviorHints?.proxyHeaders?.request ?: behaviorHints?.headers
?: mapOf(),
type = INFER_TYPE
)
)
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.AppUtils.parseJson
import com.lagradost.cloudstream3.utils.AppUtils.toJson
import java.net.URI
import java.util.ArrayList
import kotlin.math.roundToInt
import com.lagradost.cloudstream3.metaproviders.TmdbProvider
open class StremioX : TmdbProvider() {
class StremioX : TmdbProvider() {
override var mainUrl = "https://torrentio.strem.fun"
override var name = "StremioX"
override val hasMainPage = true
override val hasQuickSearch = true
override val supportedTypes = setOf(
TvType.Others,
)
override val supportedTypes = setOf(TvType.Others)
companion object {
const val TRACKER_LIST_URL =
"https://raw.githubusercontent.com/ngosang/trackerslist/master/trackers_best.txt"
const val TRACKER_LIST_URL = "https://raw.githubusercontent.com/ngosang/trackerslist/master/trackers_best.txt"
private const val tmdbAPI = "https://api.themoviedb.org/3"
private val apiKey =
base64DecodeAPI("ZTM=NTg=MjM=MjM=ODc=MzI=OGQ=MmE=Nzk=Nzk=ZjI=NTA=NDY=NDA=MzA=YjA=") // PLEASE DON'T STEAL
private const val apiKey = BuildConfig.TMDB_API
fun getType(t: String?): TvType {
return when (t) {
@ -44,11 +39,6 @@ open class StremioX : TmdbProvider() {
else -> ShowStatus.Completed
}
}
private fun base64DecodeAPI(api: String): String {
return api.chunked(4).map { base64Decode(it) }.reversed().joinToString("")
}
}
override val mainPage = mainPageOf(
@ -159,7 +149,7 @@ open class StremioX : TmdbProvider() {
eps.seasonNumber,
eps.episodeNumber
).toJson(),
name = eps.name + if (isUpcoming(eps.airDate)) " - [UPCOMING]" else "",
name = eps.name + if (isUpcoming(eps.airDate)) " [UPCOMING]" else "",
season = eps.seasonNumber,
episode = eps.episodeNumber,
posterUrl = getImageUrl(eps.stillPath),
@ -177,7 +167,7 @@ open class StremioX : TmdbProvider() {
this.backgroundPosterUrl = bgPoster
this.year = year
this.plot = res.overview
this.tags = if (isAnime) keywords else genres
this.tags = keywords.takeIf { !it.isNullOrEmpty() } ?: genres
this.rating = rating
this.showStatus = getStatus(res.status)
this.recommendations = recommendations
@ -200,7 +190,7 @@ open class StremioX : TmdbProvider() {
this.year = year
this.plot = res.overview
this.duration = res.runtime
this.tags = if (isAnime) keywords else genres
this.tags = keywords.takeIf { !it.isNullOrEmpty() } ?: genres
this.rating = rating
this.recommendations = recommendations
this.actors = actors
@ -243,13 +233,13 @@ open class StremioX : TmdbProvider() {
callback: (ExtractorLink) -> Unit
) {
val fixMainUrl = mainUrl.fixSourceUrl()
val url = if(season == null) {
val url = if (season == null) {
"$fixMainUrl/stream/movie/$imdbId.json"
} else {
"$fixMainUrl/stream/series/$imdbId:$season:$episode.json"
}
val res = AppUtils.tryParseJson<StreamsResponse>(request(url).body.string()) ?: return
res.streams.forEach { stream ->
val res = app.get(url, interceptor = interceptor).parsedSafe<StreamsResponse>()
res?.streams?.forEach { stream ->
stream.runCallback(subtitleCallback, callback)
}
}
@ -262,12 +252,12 @@ open class StremioX : TmdbProvider() {
)
private data class ProxyHeaders(
val request: Map<String,String>?,
val request: Map<String, String>?,
)
private data class BehaviorHints(
val proxyHeaders: ProxyHeaders?,
val headers: Map<String,String>?,
val headers: Map<String, String>?,
)
private data class Stream(
@ -290,12 +280,13 @@ open class StremioX : TmdbProvider() {
callback.invoke(
ExtractorLink(
name ?: "",
fixRDSourceName(name, title),
fixSourceName(name, title),
url,
"",
getQualityFromName(description),
headers = behaviorHints?.proxyHeaders?.request ?: behaviorHints?.headers ?: mapOf(),
isM3u8 = URI(url).path.endsWith(".m3u8")
getQuality(listOf(description,title,name)),
headers = behaviorHints?.proxyHeaders?.request ?: behaviorHints?.headers
?: mapOf(),
type = INFER_TYPE
)
)
subtitles.map { sub ->

View file

@ -3,10 +3,9 @@ package com.hexated
import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.SubtitleFile
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.base64Encode
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"
object SubsExtractors {
@ -16,22 +15,20 @@ object SubsExtractors {
episode: Int? = null,
subtitleCallback: (SubtitleFile) -> Unit,
) {
val id = if(season == null) {
imdbId
val slug = if(season == null) {
"movie/$imdbId"
} 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}/q.json?b=$data").parsedSafe<OsResult>()?.result?.all?.map { sub ->
app.get("${openSubAPI}/subtitles/$slug.json").parsedSafe<OsResult>()?.subtitles?.map { sub ->
subtitleCallback.invoke(
SubtitleFile(
SubtitleHelper.fromThreeLettersToLanguage(sub.lang ?: "") ?: sub.lang
?: "",
?: return@map,
sub.url ?: return@map
)
)
}
}
suspend fun invokeWatchsomuch(
@ -81,12 +78,8 @@ object SubsExtractors {
@JsonProperty("lang") val lang: String? = null,
)
data class OsAll(
@JsonProperty("all") val all: ArrayList<OsSubtitles>? = arrayListOf(),
)
data class OsResult(
@JsonProperty("result") val result: OsAll? = null,
@JsonProperty("subtitles") val subtitles: ArrayList<OsSubtitles>? = arrayListOf(),
)
data class WatchsomuchTorrents(

View file

@ -1,53 +1,50 @@
package com.hexated
import com.lagradost.cloudstream3.APIHolder
import com.lagradost.cloudstream3.APIHolder.unixTimeMS
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.nicehttp.Requests.Companion.await
import okhttp3.OkHttpClient
import okhttp3.Request
import com.lagradost.cloudstream3.utils.getQualityFromName
import okhttp3.Interceptor
import okhttp3.Response
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.concurrent.TimeUnit
const val defaultTimeOut = 30L
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 interceptor = TimeOutInterceptor()
val request: Request = Request.Builder()
.url(url)
class TimeOutInterceptor : Interceptor {
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()
return client.newCall(request).await()
}
fun Int.isSuccessful() : Boolean {
return this in 200..299
return chain.proceed(call)
}
}
fun String.fixSourceUrl(): String {
return this.replace("/manifest.json", "").replace("stremio://", "https://")
}
fun fixRDSourceName(name: String?, title: String?): String {
fun fixSourceName(name: String?, title: String?): String {
return when {
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"
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(
season: Int? = null,
episode: Int? = null,