Compare commits

...

2 commits

Author SHA1 Message Date
firelight
bb5d4f88aa
Add JsonProperty 2026-07-08 17:59:45 +00:00
firelight
94f3137a6f
Fix: Subsouce and cache issue 2026-07-08 17:50:44 +00:00
3 changed files with 133 additions and 104 deletions

View file

@ -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),

View file

@ -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

View file

@ -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,
) )
} }