mirror of
https://github.com/recloudstream/cloudstream.git
synced 2026-07-13 00:13:17 +00:00
Compare commits
2 commits
master
...
fix-subsou
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bb5d4f88aa |
||
|
|
94f3137a6f |
3 changed files with 133 additions and 104 deletions
|
|
@ -70,7 +70,8 @@ abstract class AccountManager {
|
||||||
SubtitleRepo(openSubtitlesApi),
|
SubtitleRepo(openSubtitlesApi),
|
||||||
SubtitleRepo(addic7ed),
|
SubtitleRepo(addic7ed),
|
||||||
SubtitleRepo(subDlApi),
|
SubtitleRepo(subDlApi),
|
||||||
PlainAuthRepo(animeSkipApi)
|
PlainAuthRepo(animeSkipApi),
|
||||||
|
SubtitleRepo(subSourceApi)
|
||||||
)
|
)
|
||||||
|
|
||||||
fun updateAccountIds() {
|
fun updateAccountIds() {
|
||||||
|
|
@ -120,7 +121,8 @@ abstract class AccountManager {
|
||||||
val subtitleProviders = arrayOf(
|
val subtitleProviders = arrayOf(
|
||||||
SubtitleRepo(openSubtitlesApi),
|
SubtitleRepo(openSubtitlesApi),
|
||||||
SubtitleRepo(addic7ed),
|
SubtitleRepo(addic7ed),
|
||||||
SubtitleRepo(subDlApi)
|
SubtitleRepo(subDlApi),
|
||||||
|
SubtitleRepo(subSourceApi)
|
||||||
)
|
)
|
||||||
val syncApis = arrayOf(
|
val syncApis = arrayOf(
|
||||||
SyncRepo(malApi),
|
SyncRepo(malApi),
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package com.lagradost.cloudstream3.syncproviders
|
||||||
|
|
||||||
import androidx.annotation.WorkerThread
|
import androidx.annotation.WorkerThread
|
||||||
import com.lagradost.cloudstream3.APIHolder.unixTime
|
import com.lagradost.cloudstream3.APIHolder.unixTime
|
||||||
import com.lagradost.cloudstream3.ErrorLoadingException
|
|
||||||
import com.lagradost.cloudstream3.subtitles.AbstractSubtitleEntities.SubtitleEntity
|
import com.lagradost.cloudstream3.subtitles.AbstractSubtitleEntities.SubtitleEntity
|
||||||
import com.lagradost.cloudstream3.subtitles.AbstractSubtitleEntities.SubtitleSearch
|
import com.lagradost.cloudstream3.subtitles.AbstractSubtitleEntities.SubtitleSearch
|
||||||
import com.lagradost.cloudstream3.subtitles.SubtitleResource
|
import com.lagradost.cloudstream3.subtitles.SubtitleResource
|
||||||
|
|
@ -14,7 +13,8 @@ class SubtitleRepo(override val api: SubtitleAPI) : AuthRepo(api) {
|
||||||
data class SavedSearchResponse(
|
data class SavedSearchResponse(
|
||||||
val unixTime: Long,
|
val unixTime: Long,
|
||||||
val response: List<SubtitleEntity>,
|
val response: List<SubtitleEntity>,
|
||||||
val query: SubtitleSearch
|
val query: SubtitleSearch,
|
||||||
|
val idPrefix: String,
|
||||||
)
|
)
|
||||||
|
|
||||||
data class SavedResourceResponse(
|
data class SavedResourceResponse(
|
||||||
|
|
@ -66,7 +66,7 @@ class SubtitleRepo(override val api: SubtitleAPI) : AuthRepo(api) {
|
||||||
var found: List<SubtitleEntity>? = null
|
var found: List<SubtitleEntity>? = null
|
||||||
for (item in searchCache) {
|
for (item in searchCache) {
|
||||||
// 120 min save
|
// 120 min save
|
||||||
if (item.query == query && (unixTime - item.unixTime) < 60 * 120) {
|
if (item.idPrefix == idPrefix && item.query == query && (unixTime - item.unixTime) < 60 * 120) {
|
||||||
found = item.response
|
found = item.response
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
@ -79,7 +79,7 @@ class SubtitleRepo(override val api: SubtitleAPI) : AuthRepo(api) {
|
||||||
|
|
||||||
// only cache valid return values
|
// only cache valid return values
|
||||||
if (returnValue.isNotEmpty()) {
|
if (returnValue.isNotEmpty()) {
|
||||||
val add = SavedSearchResponse(unixTime, returnValue, query)
|
val add = SavedSearchResponse(unixTime, returnValue, query, idPrefix)
|
||||||
searchCache.withLock {
|
searchCache.withLock {
|
||||||
if (searchCache.size > CACHE_SIZE) {
|
if (searchCache.size > CACHE_SIZE) {
|
||||||
searchCache[searchCacheIndex] = add // rolling cache
|
searchCache[searchCacheIndex] = add // rolling cache
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,10 @@ import com.lagradost.cloudstream3.subtitles.AbstractSubtitleEntities
|
||||||
import com.lagradost.cloudstream3.subtitles.SubtitleResource
|
import com.lagradost.cloudstream3.subtitles.SubtitleResource
|
||||||
import com.lagradost.cloudstream3.syncproviders.AuthData
|
import com.lagradost.cloudstream3.syncproviders.AuthData
|
||||||
import com.lagradost.cloudstream3.syncproviders.SubtitleAPI
|
import com.lagradost.cloudstream3.syncproviders.SubtitleAPI
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
|
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.toJson
|
|
||||||
import com.lagradost.cloudstream3.utils.SubtitleHelper
|
import com.lagradost.cloudstream3.utils.SubtitleHelper
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class SubSourceApi : SubtitleAPI() {
|
class SubSourceApi : SubtitleAPI() {
|
||||||
override val name = "SubSource"
|
override val name = "SubSource"
|
||||||
|
|
@ -20,77 +19,70 @@ class SubSourceApi : SubtitleAPI() {
|
||||||
override val requiresLogin = false
|
override val requiresLogin = false
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val APIURL = "https://api.subsource.net/api"
|
const val APIURL = "https://api.subsource.net/v1"
|
||||||
const val DOWNLOADENDPOINT = "https://api.subsource.net/api/downloadSub"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun search(
|
override suspend fun search(
|
||||||
auth: AuthData?,
|
auth: AuthData?,
|
||||||
query: AbstractSubtitleEntities.SubtitleSearch
|
query: AbstractSubtitleEntities.SubtitleSearch
|
||||||
): List<AbstractSubtitleEntities.SubtitleEntity>? {
|
): List<AbstractSubtitleEntities.SubtitleEntity>? {
|
||||||
|
|
||||||
//Only supports Imdb Id search for now
|
//Only supports Imdb Id search for now
|
||||||
if (query.imdbId == null) return null
|
if (query.imdbId == null) return null
|
||||||
val queryLang = SubtitleHelper.fromTagToEnglishLanguageName(query.lang)
|
val queryLang = SubtitleHelper.fromTagToEnglishLanguageName(query.lang)
|
||||||
val type = if ((query.seasonNumber ?: 0) > 0) TvType.TvSeries else TvType.Movie
|
val type = if ((query.seasonNumber ?: 0) > 0) TvType.TvSeries else TvType.Movie
|
||||||
|
|
||||||
val searchRes = app.post(
|
val searchResponse = app.post(
|
||||||
url = "$APIURL/searchMovie",
|
url = "$APIURL/movie/search",
|
||||||
data = mapOf(
|
json = mapOf(
|
||||||
"query" to query.imdbId!!
|
"includeSeasons" to false,
|
||||||
)
|
"limit" to 15,
|
||||||
).parsedSafe<ApiSearch>() ?: return null
|
"query" to query.imdbId!!,
|
||||||
|
"signal" to "{}"
|
||||||
|
),
|
||||||
|
cacheTime = 120,
|
||||||
|
cacheUnit = TimeUnit.MINUTES,
|
||||||
|
).parsedSafe<SearchRoot>() ?: return null
|
||||||
|
|
||||||
val postData = if (type == TvType.TvSeries) {
|
val firstResult = searchResponse.results.firstOrNull() ?: return null
|
||||||
mapOf(
|
|
||||||
"langs" to "[]",
|
val apiResponse = app.get(
|
||||||
"movieName" to searchRes.found.first().linkName,
|
url = "$APIURL${firstResult.link.replace("series", "subtitles")}",
|
||||||
"season" to "season-${query.seasonNumber}"
|
cacheTime = 120,
|
||||||
)
|
cacheUnit = TimeUnit.MINUTES,
|
||||||
} else {
|
).parsedSafe<ItemRoot>() ?: return null
|
||||||
mapOf(
|
|
||||||
"langs" to "[]",
|
val filteredSubtitles = apiResponse.subtitles.filter { sub ->
|
||||||
"movieName" to searchRes.found.first().linkName,
|
sub.releaseType != "trailer" &&
|
||||||
)
|
sub.language.equals(queryLang, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
val getMovieRes = app.post(
|
// api doesn't has episode number or lang filtering
|
||||||
url = "$APIURL/getMovie",
|
val subtitles = if (type == TvType.Movie) {
|
||||||
data = postData
|
filteredSubtitles
|
||||||
).parsedSafe<ApiResponse>().let {
|
} else {
|
||||||
// api doesn't has episode number or lang filtering
|
val shouldContain = String.format(
|
||||||
if (type == TvType.Movie) {
|
null,
|
||||||
it?.subs?.filter { sub ->
|
"E%02d",
|
||||||
sub.lang == queryLang
|
query.epNumber
|
||||||
}
|
)
|
||||||
} else {
|
filteredSubtitles.filter { sub ->
|
||||||
it?.subs?.filter { sub ->
|
sub.releaseInfo.contains(
|
||||||
sub.releaseName!!.contains(
|
shouldContain
|
||||||
String.format(
|
)
|
||||||
null,
|
|
||||||
"E%02d",
|
|
||||||
query.epNumber
|
|
||||||
)
|
|
||||||
) && sub.lang == queryLang
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} ?: return null
|
}
|
||||||
|
|
||||||
return getMovieRes.map { subtitle ->
|
return subtitles.map { subtitle ->
|
||||||
AbstractSubtitleEntities.SubtitleEntity(
|
AbstractSubtitleEntities.SubtitleEntity(
|
||||||
idPrefix = this.idPrefix,
|
idPrefix = this.idPrefix,
|
||||||
name = subtitle.releaseName!!,
|
name = subtitle.releaseInfo,
|
||||||
lang = subtitle.lang!!,
|
lang = subtitle.language,
|
||||||
data = SubData(
|
data = subtitle.link,
|
||||||
movie = subtitle.linkName!!,
|
|
||||||
lang = subtitle.lang,
|
|
||||||
id = subtitle.subId.toString(),
|
|
||||||
).toJson(),
|
|
||||||
type = type,
|
type = type,
|
||||||
source = this.name,
|
source = this.name,
|
||||||
epNumber = query.epNumber,
|
epNumber = query.epNumber,
|
||||||
seasonNumber = query.seasonNumber,
|
seasonNumber = query.seasonNumber,
|
||||||
isHearingImpaired = subtitle.hi == 1,
|
isHearingImpaired = subtitle.hearingImpaired == 1,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -99,79 +91,114 @@ class SubSourceApi : SubtitleAPI() {
|
||||||
auth: AuthData?,
|
auth: AuthData?,
|
||||||
subtitle: AbstractSubtitleEntities.SubtitleEntity
|
subtitle: AbstractSubtitleEntities.SubtitleEntity
|
||||||
) {
|
) {
|
||||||
val parsedSub = parseJson<SubData>(subtitle.data)
|
val data = app.get("$APIURL/subtitle/${subtitle.data}")
|
||||||
|
.parsedSafe<DownloadRoot>()
|
||||||
val subRes = app.post(
|
?: return
|
||||||
url = "$APIURL/getSub",
|
|
||||||
data = mapOf(
|
|
||||||
"movie" to parsedSub.movie,
|
|
||||||
"lang" to subtitle.lang,
|
|
||||||
"id" to parsedSub.id
|
|
||||||
)
|
|
||||||
).parsedSafe<SubTitleLink>() ?: return
|
|
||||||
|
|
||||||
this.addZipUrl(
|
this.addZipUrl(
|
||||||
"$DOWNLOADENDPOINT/${subRes.sub.downloadToken}"
|
"$APIURL/subtitle/download/${data.subtitle.downloadToken}"
|
||||||
) { name, _ ->
|
) { name, _ ->
|
||||||
name
|
name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class ApiSearch(
|
data class SearchRoot(
|
||||||
@JsonProperty("success") @SerialName("success") val success: Boolean,
|
@JsonProperty("success") @SerialName("success") var success: Boolean? = null,
|
||||||
@JsonProperty("found") @SerialName("found") val found: List<Found>,
|
@JsonProperty("results") @SerialName("results") var results: ArrayList<Results> = arrayListOf(),
|
||||||
|
@JsonProperty("users") @SerialName("users") var users: ArrayList<Users> = arrayListOf()
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Found(
|
data class Users(
|
||||||
@JsonProperty("id") @SerialName("id") val id: Long,
|
|
||||||
@JsonProperty("title") @SerialName("title") val title: String,
|
@JsonProperty("id") @SerialName("id") var id: Int? = null,
|
||||||
@JsonProperty("seasons") @SerialName("seasons") val seasons: Long,
|
@JsonProperty("displayname") @SerialName("displayname") var displayname: String? = null,
|
||||||
@JsonProperty("type") @SerialName("type") val type: String,
|
@JsonProperty("avatar") @SerialName("avatar") var avatar: String? = null,
|
||||||
@JsonProperty("releaseYear") @SerialName("releaseYear") val releaseYear: Long,
|
@JsonProperty("badges") @SerialName("badges") var badges: ArrayList<String> = arrayListOf()
|
||||||
@JsonProperty("linkName") @SerialName("linkName") val linkName: String,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class ApiResponse(
|
data class Results(
|
||||||
@JsonProperty("success") @SerialName("success") val success: Boolean,
|
@JsonProperty("id") @SerialName("id") var id: Int? = null,
|
||||||
@JsonProperty("movie") @SerialName("movie") val movie: Movie,
|
@JsonProperty("title") @SerialName("title") var title: String? = null,
|
||||||
@JsonProperty("subs") @SerialName("subs") val subs: List<Sub>,
|
@JsonProperty("type") @SerialName("type") var type: String? = null,
|
||||||
|
@JsonProperty("link") @SerialName("link") var link: String,
|
||||||
|
@JsonProperty("releaseYear") @SerialName("releaseYear") var releaseYear: Int? = null,
|
||||||
|
@JsonProperty("poster") @SerialName("poster") var poster: String? = null,
|
||||||
|
@JsonProperty("subtitleCount") @SerialName("subtitleCount") var subtitleCount: String? = null,
|
||||||
|
@JsonProperty("rating") @SerialName("rating") var rating: Double? = null,
|
||||||
|
@JsonProperty("cast") @SerialName("cast") var cast: ArrayList<String> = arrayListOf(),
|
||||||
|
@JsonProperty("genres") @SerialName("genres") var genres: ArrayList<String> = arrayListOf(),
|
||||||
|
@JsonProperty("score") @SerialName("score") var score: Double? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Movie(
|
|
||||||
@JsonProperty("id") @SerialName("id") val id: Long? = null,
|
data class ItemRoot(
|
||||||
@JsonProperty("type") @SerialName("type") val type: String? = null,
|
|
||||||
@JsonProperty("year") @SerialName("year") val year: Long? = null,
|
// @SerialName("media_type" ) var mediaType : String? = null,
|
||||||
@JsonProperty("fullName") @SerialName("fullName") val fullName: String? = null,
|
@JsonProperty("subtitles") @SerialName("subtitles") var subtitles: ArrayList<Subtitles>,
|
||||||
|
//@SerialName("movie" ) var movie : Movie? = Movie()
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Sub(
|
data class Subtitles(
|
||||||
@JsonProperty("hi") @SerialName("hi") val hi: Int? = null,
|
|
||||||
@JsonProperty("fullLink") @SerialName("fullLink") val fullLink: String? = null,
|
@JsonProperty("id") @SerialName("id") var id: Int? = null,
|
||||||
@JsonProperty("linkName") @SerialName("linkName") val linkName: String? = null,
|
@JsonProperty("language") @SerialName("language") var language: String,
|
||||||
@JsonProperty("lang") @SerialName("lang") val lang: String? = null,
|
@JsonProperty("release_type") @SerialName("release_type") var releaseType: String? = null,
|
||||||
@JsonProperty("releaseName") @SerialName("releaseName") val releaseName: String? = null,
|
@JsonProperty("release_info") @SerialName("release_info") var releaseInfo: String,
|
||||||
@JsonProperty("subId") @SerialName("subId") val subId: Long? = null,
|
@JsonProperty("upload_date") @SerialName("upload_date") var uploadDate: String? = null,
|
||||||
|
@JsonProperty("hearing_impaired") @SerialName("hearing_impaired") var hearingImpaired: Int? = null,
|
||||||
|
@JsonProperty("caption") @SerialName("caption") var caption: String? = null,
|
||||||
|
@JsonProperty("rating") @SerialName("rating") var rating: String? = null,
|
||||||
|
@JsonProperty("uploader_id") @SerialName("uploader_id") var uploaderId: Int? = null,
|
||||||
|
@JsonProperty("uploader_displayname") @SerialName("uploader_displayname") var uploaderDisplayname: String? = null,
|
||||||
|
@JsonProperty("uploader_badges") @SerialName("uploader_badges") var uploaderBadges: ArrayList<String> = arrayListOf(),
|
||||||
|
@JsonProperty("link") @SerialName("link") var link: String,
|
||||||
|
@JsonProperty("production_type") @SerialName("production_type") var productionType: String? = null,
|
||||||
|
@JsonProperty("last_subtitle") @SerialName("last_subtitle") var lastSubtitle: Boolean? = null
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class SubData(
|
data class DownloadRoot(
|
||||||
@JsonProperty("movie") @SerialName("movie") val movie: String,
|
@JsonProperty("subtitle") @SerialName("subtitle") var subtitle: Subtitle,
|
||||||
@JsonProperty("lang") @SerialName("lang") val lang: String,
|
//@SerializedName("movie" ) var movie : Movie? = Movie(),
|
||||||
@JsonProperty("id") @SerialName("id") val id: String,
|
//@SerializedName("donationLinks" ) var donationLinks : DonationLinks? = DonationLinks(),
|
||||||
|
//@SerializedName("isDownloaded" ) var isDownloaded : Boolean? = null,
|
||||||
|
//@SerializedName("user_rated" ) var userRated : String? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class SubTitleLink(
|
data class Subtitle(
|
||||||
@JsonProperty("sub") @SerialName("sub") val sub: SubToken,
|
|
||||||
)
|
@JsonProperty("id") @SerialName("id") var id: Int? = null,
|
||||||
|
@JsonProperty("uploaded_at") @SerialName("uploaded_at") var uploadedAt: String? = null,
|
||||||
|
@JsonProperty("language") @SerialName("language") var language: String? = null,
|
||||||
|
@JsonProperty("rating") @SerialName("rating") var rating: String? = null,
|
||||||
|
//SerialName("rates" ) var rates : Rates? = Rates(),
|
||||||
|
@JsonProperty("uploaded_by") @SerialName("uploaded_by") var uploadedBy: Int? = null,
|
||||||
|
//@SerialName("contribs" ) var contribs : ArrayList<Contribs> = arrayListOf(),
|
||||||
|
@JsonProperty("release_info") @SerialName("release_info") var releaseInfo: ArrayList<String> = arrayListOf(),
|
||||||
|
@JsonProperty("commentary") @SerialName("commentary") var commentary: String? = null,
|
||||||
|
@JsonProperty("files") @SerialName("files") var files: String? = null,
|
||||||
|
@JsonProperty("size") @SerialName("size") var size: String? = null,
|
||||||
|
@JsonProperty("downloads") @SerialName("downloads") var downloads: Int? = null,
|
||||||
|
@JsonProperty("comments") @SerialName("comments") var comments: Int? = null,
|
||||||
|
@JsonProperty("production_type") @SerialName("production_type") var productionType: String? = null,
|
||||||
|
@JsonProperty("release_type") @SerialName("release_type") var releaseType: String? = null,
|
||||||
|
@JsonProperty("episode") @SerialName("episode") var episode: String? = null,
|
||||||
|
@JsonProperty("hearing_impaired") @SerialName("hearing_impaired") var hearingImpaired: Int? = null,
|
||||||
|
@JsonProperty("foreign_parts") @SerialName("foreign_parts") var foreignParts: String? = null,
|
||||||
|
@JsonProperty("framerate") @SerialName("framerate") var framerate: String? = null,
|
||||||
|
@JsonProperty("preview") @SerialName("preview") var preview: String? = null,
|
||||||
|
@JsonProperty("user_uploaded") @SerialName("user_uploaded") var userUploaded: Boolean? = null,
|
||||||
|
@JsonProperty("download_token") @SerialName("download_token") var downloadToken: String
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class SubToken(
|
|
||||||
@JsonProperty("downloadToken") @SerialName("downloadToken") val downloadToken: String,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue