mirror of
https://github.com/hexated/cloudstream-extensions-hexated.git
synced 2024-08-15 00:03:22 +00:00
stremiox: make possible stream through catalog addons
This commit is contained in:
parent
ba1fbac2a2
commit
3f97c0a253
4 changed files with 190 additions and 115 deletions
|
@ -1,5 +1,5 @@
|
||||||
// use an integer for version numbers
|
// use an integer for version numbers
|
||||||
version = 5
|
version = 6
|
||||||
|
|
||||||
|
|
||||||
cloudstream {
|
cloudstream {
|
||||||
|
|
|
@ -26,7 +26,7 @@ class StremioC : MainAPI() {
|
||||||
val lists = mutableListOf<HomePageList>()
|
val lists = mutableListOf<HomePageList>()
|
||||||
res.catalogs.forEach { catalog ->
|
res.catalogs.forEach { catalog ->
|
||||||
catalog.toHomePageList(this)?.let {
|
catalog.toHomePageList(this)?.let {
|
||||||
if(it.list.isNotEmpty()) lists.add(it)
|
if (it.list.isNotEmpty()) lists.add(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return HomePageResponse(
|
return HomePageResponse(
|
||||||
|
@ -47,10 +47,11 @@ class StremioC : MainAPI() {
|
||||||
|
|
||||||
override suspend fun load(url: String): LoadResponse? {
|
override suspend fun load(url: String): LoadResponse? {
|
||||||
val res = parseJson<CatalogEntry>(url)
|
val res = parseJson<CatalogEntry>(url)
|
||||||
mainUrl = if((res.type == "movie" || res.type == "series") && isImdborTmdb(res.id)) cinemataUrl else mainUrl
|
mainUrl =
|
||||||
|
if ((res.type == "movie" || res.type == "series") && isImdborTmdb(res.id)) cinemataUrl else mainUrl
|
||||||
val json = app.get("${mainUrl}/meta/${res.type}/${res.id}.json")
|
val json = app.get("${mainUrl}/meta/${res.type}/${res.id}.json")
|
||||||
.parsedSafe<CatalogResponse>()?.meta ?: throw RuntimeException(url)
|
.parsedSafe<CatalogResponse>()?.meta ?: throw RuntimeException(url)
|
||||||
return json.toLoadResponse(this)
|
return json.toLoadResponse(this, res.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun loadLinks(
|
override suspend fun loadLinks(
|
||||||
|
@ -59,14 +60,74 @@ class StremioC : MainAPI() {
|
||||||
subtitleCallback: (SubtitleFile) -> Unit,
|
subtitleCallback: (SubtitleFile) -> Unit,
|
||||||
callback: (ExtractorLink) -> Unit
|
callback: (ExtractorLink) -> Unit
|
||||||
): Boolean {
|
): Boolean {
|
||||||
val res = tryParseJson<StreamsResponse>(app.get(data).text) ?: return false
|
val loadData = parseJson<LoadData>(data)
|
||||||
res.streams.forEach { stream ->
|
val request = app.get("${mainUrl}/stream/${loadData.type}/${loadData.id}.json")
|
||||||
stream.runCallback(this, subtitleCallback, callback)
|
if (request.isSuccessful) {
|
||||||
|
val res = tryParseJson<StreamsResponse>(request.text) ?: return false
|
||||||
|
res.streams.forEach { stream ->
|
||||||
|
stream.runCallback(this, subtitleCallback, callback)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
argamap(
|
||||||
|
{
|
||||||
|
invokeStremioX(loadData.type, loadData.id, subtitleCallback, callback)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
SubsExtractors.invokeWatchsomuch(
|
||||||
|
loadData.imdbId,
|
||||||
|
loadData.season,
|
||||||
|
loadData.episode,
|
||||||
|
subtitleCallback
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
SubsExtractors.invokeOpenSubs(
|
||||||
|
loadData.imdbId,
|
||||||
|
loadData.season,
|
||||||
|
loadData.episode,
|
||||||
|
subtitleCallback
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun invokeStremioX(
|
||||||
|
type: String?,
|
||||||
|
id: String?,
|
||||||
|
subtitleCallback: (SubtitleFile) -> Unit,
|
||||||
|
callback: (ExtractorLink) -> Unit
|
||||||
|
) {
|
||||||
|
val sites =
|
||||||
|
AcraApplication.getKey<Array<CustomSite>>(USER_PROVIDER_API)?.toMutableList()
|
||||||
|
?: mutableListOf()
|
||||||
|
sites.filter { it.parentJavaClass == "StremioX" }.apmap { site ->
|
||||||
|
val res =
|
||||||
|
tryParseJson<StreamsResponse>(app.get("${site.url.fixSourceUrl()}/stream/${type}/${id}.json").text)
|
||||||
|
?: return@apmap
|
||||||
|
res.streams.forEach { stream ->
|
||||||
|
stream.runCallback(this, subtitleCallback, callback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class LoadData(
|
||||||
|
val type: String? = null,
|
||||||
|
val id: String? = null,
|
||||||
|
val season: Int? = null,
|
||||||
|
val episode: Int? = null,
|
||||||
|
val imdbId: String? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class CustomSite(
|
||||||
|
@JsonProperty("parentJavaClass") val parentJavaClass: String,
|
||||||
|
@JsonProperty("name") val name: String,
|
||||||
|
@JsonProperty("url") val url: String,
|
||||||
|
@JsonProperty("lang") val lang: String,
|
||||||
|
)
|
||||||
|
|
||||||
// check if id is imdb/tmdb cause stremio addons like torrentio works base on imdbId
|
// check if id is imdb/tmdb cause stremio addons like torrentio works base on imdbId
|
||||||
private fun isImdborTmdb(url: String?): Boolean {
|
private fun isImdborTmdb(url: String?): Boolean {
|
||||||
return imdbUrlToIdNullable(url) != null || url?.startsWith("tmdb:") == true
|
return imdbUrlToIdNullable(url) != null || url?.startsWith("tmdb:") == true
|
||||||
|
@ -137,13 +198,13 @@ class StremioC : MainAPI() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun toLoadResponse(provider: StremioC): LoadResponse {
|
suspend fun toLoadResponse(provider: StremioC, imdbId: String?): LoadResponse {
|
||||||
if (videos == null || videos.isEmpty()) {
|
if (videos == null || videos.isEmpty()) {
|
||||||
return provider.newMovieLoadResponse(
|
return provider.newMovieLoadResponse(
|
||||||
name,
|
name,
|
||||||
"${provider.mainUrl}/meta/${type}/${id}.json",
|
"${provider.mainUrl}/meta/${type}/${id}.json",
|
||||||
TvType.Movie,
|
TvType.Movie,
|
||||||
"${provider.mainUrl}/stream/${type}/${id}.json"
|
LoadData(type, id, imdbId = imdbId)
|
||||||
) {
|
) {
|
||||||
posterUrl = poster
|
posterUrl = poster
|
||||||
backgroundPosterUrl = background
|
backgroundPosterUrl = background
|
||||||
|
@ -156,7 +217,7 @@ class StremioC : MainAPI() {
|
||||||
"${provider.mainUrl}/meta/${type}/${id}.json",
|
"${provider.mainUrl}/meta/${type}/${id}.json",
|
||||||
TvType.TvSeries,
|
TvType.TvSeries,
|
||||||
videos.map {
|
videos.map {
|
||||||
it.toEpisode(provider, type)
|
it.toEpisode(provider, type, imdbId)
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
posterUrl = poster
|
posterUrl = poster
|
||||||
|
@ -180,9 +241,9 @@ class StremioC : MainAPI() {
|
||||||
@JsonProperty("overview") val overview: String? = null,
|
@JsonProperty("overview") val overview: String? = null,
|
||||||
@JsonProperty("description") val description: String? = null,
|
@JsonProperty("description") val description: String? = null,
|
||||||
) {
|
) {
|
||||||
fun toEpisode(provider: StremioC, type: String?): Episode {
|
fun toEpisode(provider: StremioC, type: String?, imdbId: String?): Episode {
|
||||||
return provider.newEpisode(
|
return provider.newEpisode(
|
||||||
"${provider.mainUrl}/stream/${type}/${id}.json"
|
LoadData(type, id, seasonNumber, episode ?: number, imdbId)
|
||||||
) {
|
) {
|
||||||
this.name = name ?: title
|
this.name = name ?: title
|
||||||
this.posterUrl = thumbnail
|
this.posterUrl = thumbnail
|
||||||
|
|
|
@ -2,6 +2,8 @@ package com.hexated
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
import com.hexated.SubsExtractors.invokeOpenSubs
|
||||||
|
import com.hexated.SubsExtractors.invokeWatchsomuch
|
||||||
import com.lagradost.cloudstream3.*
|
import com.lagradost.cloudstream3.*
|
||||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
|
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
|
||||||
import com.lagradost.cloudstream3.utils.*
|
import com.lagradost.cloudstream3.utils.*
|
||||||
|
@ -245,72 +247,6 @@ open class StremioX : MainAPI() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun invokeOpenSubs(
|
|
||||||
imdbId: String? = null,
|
|
||||||
season: Int? = null,
|
|
||||||
episode: Int? = null,
|
|
||||||
subtitleCallback: (SubtitleFile) -> Unit,
|
|
||||||
) {
|
|
||||||
val id = if(season == null) {
|
|
||||||
imdbId
|
|
||||||
} else {
|
|
||||||
"$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 ->
|
|
||||||
subtitleCallback.invoke(
|
|
||||||
SubtitleFile(
|
|
||||||
SubtitleHelper.fromThreeLettersToLanguage(sub.lang ?: "") ?: sub.lang
|
|
||||||
?: "",
|
|
||||||
sub.url ?: return@map
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun invokeWatchsomuch(
|
|
||||||
imdbId: String? = null,
|
|
||||||
season: Int? = null,
|
|
||||||
episode: Int? = null,
|
|
||||||
subtitleCallback: (SubtitleFile) -> Unit,
|
|
||||||
) {
|
|
||||||
val id = imdbId?.removePrefix("tt")
|
|
||||||
val epsId = app.post(
|
|
||||||
"${watchSomuchAPI}/Watch/ajMovieTorrents.aspx", data = mapOf(
|
|
||||||
"index" to "0",
|
|
||||||
"mid" to "$id",
|
|
||||||
"wsk" to "f6ea6cde-e42b-4c26-98d3-b4fe48cdd4fb",
|
|
||||||
"lid" to "",
|
|
||||||
"liu" to ""
|
|
||||||
), headers = mapOf("X-Requested-With" to "XMLHttpRequest")
|
|
||||||
).parsedSafe<WatchsomuchResponses>()?.movie?.torrents?.let { eps ->
|
|
||||||
if (season == null) {
|
|
||||||
eps.firstOrNull()?.id
|
|
||||||
} else {
|
|
||||||
eps.find { it.episode == episode && it.season == season }?.id
|
|
||||||
}
|
|
||||||
} ?: return
|
|
||||||
|
|
||||||
val (seasonSlug, episodeSlug) = getEpisodeSlug(season, episode)
|
|
||||||
|
|
||||||
val subUrl = if (season == null) {
|
|
||||||
"${watchSomuchAPI}/Watch/ajMovieSubtitles.aspx?mid=$id&tid=$epsId&part="
|
|
||||||
} else {
|
|
||||||
"${watchSomuchAPI}/Watch/ajMovieSubtitles.aspx?mid=$id&tid=$epsId&part=S${seasonSlug}E${episodeSlug}"
|
|
||||||
}
|
|
||||||
|
|
||||||
app.get(subUrl).parsedSafe<WatchsomuchSubResponses>()?.subtitles?.map { sub ->
|
|
||||||
subtitleCallback.invoke(
|
|
||||||
SubtitleFile(
|
|
||||||
sub.label ?: "", fixUrl(sub.url ?: return@map null, watchSomuchAPI)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private data class StreamsResponse(val streams: List<Stream>)
|
private data class StreamsResponse(val streams: List<Stream>)
|
||||||
private data class Subtitle(
|
private data class Subtitle(
|
||||||
val url: String?,
|
val url: String?,
|
||||||
|
@ -396,43 +332,6 @@ open class StremioX : MainAPI() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class OsSubtitles(
|
|
||||||
@JsonProperty("url") val url: String? = null,
|
|
||||||
@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,
|
|
||||||
)
|
|
||||||
|
|
||||||
data class WatchsomuchTorrents(
|
|
||||||
@JsonProperty("id") val id: Int? = null,
|
|
||||||
@JsonProperty("movieId") val movieId: Int? = null,
|
|
||||||
@JsonProperty("season") val season: Int? = null,
|
|
||||||
@JsonProperty("episode") val episode: Int? = null,
|
|
||||||
)
|
|
||||||
|
|
||||||
data class WatchsomuchMovies(
|
|
||||||
@JsonProperty("torrents") val torrents: ArrayList<WatchsomuchTorrents>? = arrayListOf(),
|
|
||||||
)
|
|
||||||
|
|
||||||
data class WatchsomuchResponses(
|
|
||||||
@JsonProperty("movie") val movie: WatchsomuchMovies? = null,
|
|
||||||
)
|
|
||||||
|
|
||||||
data class WatchsomuchSubtitles(
|
|
||||||
@JsonProperty("url") val url: String? = null,
|
|
||||||
@JsonProperty("label") val label: String? = null,
|
|
||||||
)
|
|
||||||
|
|
||||||
data class WatchsomuchSubResponses(
|
|
||||||
@JsonProperty("subtitles") val subtitles: ArrayList<WatchsomuchSubtitles>? = arrayListOf(),
|
|
||||||
)
|
|
||||||
|
|
||||||
data class LoadData(
|
data class LoadData(
|
||||||
val imdbId: String? = null,
|
val imdbId: String? = null,
|
||||||
val season: Int? = null,
|
val season: Int? = null,
|
||||||
|
|
115
StremioX/src/main/kotlin/com/hexated/SubsExtractors.kt
Normal file
115
StremioX/src/main/kotlin/com/hexated/SubsExtractors.kt
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
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 watchSomuchAPI = "https://watchsomuch.tv"
|
||||||
|
|
||||||
|
object SubsExtractors {
|
||||||
|
suspend fun invokeOpenSubs(
|
||||||
|
imdbId: String? = null,
|
||||||
|
season: Int? = null,
|
||||||
|
episode: Int? = null,
|
||||||
|
subtitleCallback: (SubtitleFile) -> Unit,
|
||||||
|
) {
|
||||||
|
val id = if(season == null) {
|
||||||
|
imdbId
|
||||||
|
} else {
|
||||||
|
"$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 ->
|
||||||
|
subtitleCallback.invoke(
|
||||||
|
SubtitleFile(
|
||||||
|
SubtitleHelper.fromThreeLettersToLanguage(sub.lang ?: "") ?: sub.lang
|
||||||
|
?: "",
|
||||||
|
sub.url ?: return@map
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun invokeWatchsomuch(
|
||||||
|
imdbId: String? = null,
|
||||||
|
season: Int? = null,
|
||||||
|
episode: Int? = null,
|
||||||
|
subtitleCallback: (SubtitleFile) -> Unit,
|
||||||
|
) {
|
||||||
|
val id = imdbId?.removePrefix("tt")
|
||||||
|
val epsId = app.post(
|
||||||
|
"${watchSomuchAPI}/Watch/ajMovieTorrents.aspx", data = mapOf(
|
||||||
|
"index" to "0",
|
||||||
|
"mid" to "$id",
|
||||||
|
"wsk" to "f6ea6cde-e42b-4c26-98d3-b4fe48cdd4fb",
|
||||||
|
"lid" to "",
|
||||||
|
"liu" to ""
|
||||||
|
), headers = mapOf("X-Requested-With" to "XMLHttpRequest")
|
||||||
|
).parsedSafe<WatchsomuchResponses>()?.movie?.torrents?.let { eps ->
|
||||||
|
if (season == null) {
|
||||||
|
eps.firstOrNull()?.id
|
||||||
|
} else {
|
||||||
|
eps.find { it.episode == episode && it.season == season }?.id
|
||||||
|
}
|
||||||
|
} ?: return
|
||||||
|
|
||||||
|
val (seasonSlug, episodeSlug) = getEpisodeSlug(season, episode)
|
||||||
|
|
||||||
|
val subUrl = if (season == null) {
|
||||||
|
"${watchSomuchAPI}/Watch/ajMovieSubtitles.aspx?mid=$id&tid=$epsId&part="
|
||||||
|
} else {
|
||||||
|
"${watchSomuchAPI}/Watch/ajMovieSubtitles.aspx?mid=$id&tid=$epsId&part=S${seasonSlug}E${episodeSlug}"
|
||||||
|
}
|
||||||
|
|
||||||
|
app.get(subUrl).parsedSafe<WatchsomuchSubResponses>()?.subtitles?.map { sub ->
|
||||||
|
subtitleCallback.invoke(
|
||||||
|
SubtitleFile(
|
||||||
|
sub.label ?: "", fixUrl(sub.url ?: return@map null, watchSomuchAPI)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
data class OsSubtitles(
|
||||||
|
@JsonProperty("url") val url: String? = null,
|
||||||
|
@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,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class WatchsomuchTorrents(
|
||||||
|
@JsonProperty("id") val id: Int? = null,
|
||||||
|
@JsonProperty("movieId") val movieId: Int? = null,
|
||||||
|
@JsonProperty("season") val season: Int? = null,
|
||||||
|
@JsonProperty("episode") val episode: Int? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class WatchsomuchMovies(
|
||||||
|
@JsonProperty("torrents") val torrents: ArrayList<WatchsomuchTorrents>? = arrayListOf(),
|
||||||
|
)
|
||||||
|
|
||||||
|
data class WatchsomuchResponses(
|
||||||
|
@JsonProperty("movie") val movie: WatchsomuchMovies? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class WatchsomuchSubtitles(
|
||||||
|
@JsonProperty("url") val url: String? = null,
|
||||||
|
@JsonProperty("label") val label: String? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class WatchsomuchSubResponses(
|
||||||
|
@JsonProperty("subtitles") val subtitles: ArrayList<WatchsomuchSubtitles>? = arrayListOf(),
|
||||||
|
)
|
||||||
|
}
|
Loading…
Reference in a new issue