diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/SubSource.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/SubSource.kt index df635c13c0..ad8b277a46 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/SubSource.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/SubSource.kt @@ -1,15 +1,15 @@ package com.lagradost.cloudstream3.syncproviders.providers import com.fasterxml.jackson.annotation.JsonProperty -import com.lagradost.cloudstream3.TvType import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.subtitles.AbstractSubtitleEntities import com.lagradost.cloudstream3.subtitles.SubtitleResource import com.lagradost.cloudstream3.syncproviders.AuthData import com.lagradost.cloudstream3.syncproviders.SubtitleAPI +import com.lagradost.cloudstream3.TvType 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.fromLanguageToTagIETF class SubSourceApi : SubtitleAPI() { override val name = "SubSource" @@ -18,8 +18,8 @@ class SubSourceApi : SubtitleAPI() { override val requiresLogin = false companion object { - const val APIURL = "https://api.subsource.net/api" - const val DOWNLOADENDPOINT = "https://api.subsource.net/api/downloadSub" + const val APIURL = "https://api.subsource.net/v1" + const val DOWNLOADENDPOINT = "https://api.subsource.net/v1/subtitle/download" } override suspend fun search( @@ -27,49 +27,44 @@ class SubSourceApi : SubtitleAPI() { query: AbstractSubtitleEntities.SubtitleSearch ): List? { - //Only supports Imdb Id search for now - if (query.imdbId == null) return null - val queryLang = SubtitleHelper.fromTwoLettersToLanguage(query.lang!!) + if (query.query.isNullOrEmpty() && query.imdbId.isNullOrEmpty()) return null + val queryLangSubSource = langTagIETF2SubSource[query.lang.toString()] ?: "" val type = if ((query.seasonNumber ?: 0) > 0) TvType.TvSeries else TvType.Movie + // 1st search for the movie/series val searchRes = app.post( - url = "$APIURL/searchMovie", + url = "$APIURL/movie/search", data = mapOf( - "query" to query.imdbId!! + "query" to {query.imdbId ?: query.query}.toString(), + "includeSeasons" to "false", + "limit" to "15", + "signal" to "{}", ) ).parsedSafe() ?: return null - val postData = if (type == TvType.TvSeries) { - mapOf( - "langs" to "[]", - "movieName" to searchRes.found.first().linkName, - "season" to "season-${query.seasonNumber}" - ) - } else { - mapOf( - "langs" to "[]", - "movieName" to searchRes.found.first().linkName, - ) - } - val getMovieRes = app.post( - url = "$APIURL/getMovie", - data = postData - ).parsedSafe().let { + val urlPath = searchRes.results.first().link + + if (type != TvType.TvSeries) "" + else "/season-${query.seasonNumber ?: 1}" + + // 2nd get subtitles links for that movie/series + val getMovieRes = app.get( + url = "$APIURL/$urlPath?language=$queryLangSubSource&sort_by_date=false" + ).parsedSafe().let { // api doesn't has episode number or lang filtering if (type == TvType.Movie) { - it?.subs?.filter { sub -> - sub.lang == queryLang + it?.subtitles?.filter { subtitle -> + subtitle.language == queryLangSubSource } } else { - it?.subs?.filter { sub -> - sub.releaseName!!.contains( + it?.subtitles?.filter { subtitle -> + subtitle.releaseInfo!!.contains( String.format( null, "E%02d", query.epNumber ) - ) && sub.lang == queryLang + ) && subtitle.language == queryLangSubSource } } } ?: return null @@ -77,18 +72,16 @@ class SubSourceApi : SubtitleAPI() { return getMovieRes.map { subtitle -> AbstractSubtitleEntities.SubtitleEntity( idPrefix = this.idPrefix, - name = subtitle.releaseName!!, - lang = subtitle.lang!!, - data = SubData( - movie = subtitle.linkName!!, - lang = subtitle.lang, - id = subtitle.subId.toString(), - ).toJson(), + name = searchRes.results.first().title, + lang = langTagIETF2SubSource.entries.find { it.value == subtitle.language }?.key ?: + fromLanguageToTagIETF(subtitle.language?.replace("_", " ")) ?: + subtitle.language!!, + data = subtitle.link ?: "", type = type, source = this.name, epNumber = query.epNumber, seasonNumber = query.seasonNumber, - isHearingImpaired = subtitle.hi == 1, + isHearingImpaired = subtitle.hearingImpaired == 1, ) } } @@ -97,19 +90,15 @@ class SubSourceApi : SubtitleAPI() { auth: AuthData?, subtitle: AbstractSubtitleEntities.SubtitleEntity ) { - val parsedSub = parseJson(subtitle.data) + val movieUrlPath = subtitle.data - val subRes = app.post( - url = "$APIURL/getSub", - data = mapOf( - "movie" to parsedSub.movie, - "lang" to subtitle.lang, - "id" to parsedSub.id - ) - ).parsedSafe() ?: return + // 3rd get the subtitle downloadToken + val subRes = app.get( + url = "$APIURL/$movieUrlPath" + ).parsedSafe() ?: return this.addZipUrl( - "$DOWNLOADENDPOINT/${subRes.sub.downloadToken}" + "$DOWNLOADENDPOINT/${subRes.subtitle.downloadToken}" ) { name, _ -> name } @@ -117,51 +106,146 @@ class SubSourceApi : SubtitleAPI() { data class ApiSearch( @JsonProperty("success") val success: Boolean, - @JsonProperty("found") val found: List, + @JsonProperty("results") val results: List, ) - data class Found( + data class results( @JsonProperty("id") val id: Long, @JsonProperty("title") val title: String, - @JsonProperty("seasons") val seasons: Long, - @JsonProperty("type") val type: String, - @JsonProperty("releaseYear") val releaseYear: Long, - @JsonProperty("linkName") val linkName: String, + @JsonProperty("type") val type: String, // "movie" or "tvseries" + @JsonProperty("link") val link: String, + @JsonProperty("releaseYear") val releaseYear: Int, + @JsonProperty("poster") val poster: String, + @JsonProperty("subtitleCount") val subtitleCount: Long?, + @JsonProperty("rating") val rating: Float?, + @JsonProperty("cast") val cast: List?, + @JsonProperty("genres") val genres: List?, + @JsonProperty("score") val score: Float?, ) - data class ApiResponse( - @JsonProperty("success") val success: Boolean, + data class ApiSubtitlesLinks( + @JsonProperty("media_type") val mediaType: String, // "movie" or "seasons" or "tvseries" @JsonProperty("movie") val movie: Movie, - @JsonProperty("subs") val subs: List, + @JsonProperty("subtitles") val subtitles: List?, + @JsonProperty("seasons") val seasons: List?, + ) + + data class Subtitle( + @JsonProperty("id") val id: Long?, + @JsonProperty("uploaded_at") val uploadedAt: String?, + @JsonProperty("language") val language: String?, // language name with underscore + @JsonProperty("release_type") val releaseType: String?, + @JsonProperty("release_info") val releaseInfo: String?, // description could include S01.E01 + @JsonProperty("upload_date") val upload_date: String?, + @JsonProperty("hearing_impaired") val hearingImpaired: Int?, + @JsonProperty("caption") val caption: String?, + @JsonProperty("uploader_id") val uploaderId: Long?, + @JsonProperty("uploaded_by") val uploadedBy: Long?, + @JsonProperty("uploader_displayname") val uploaderDisplayname: String?, + @JsonProperty("uploader_badges") val uploaderBadges: List?, + @JsonProperty("link") val link: String?, + @JsonProperty("production_type") val productionType: String?, + @JsonProperty("last_subtitle") val lastSubtitle: Boolean?, + @JsonProperty("commentary") val commentary: String?, + @JsonProperty("files") val files: String?, + @JsonProperty("size") val size: Long?, + @JsonProperty("downloads") val downloads: Long?, + @JsonProperty("foreign_parts") val foreignParts: String?, + @JsonProperty("framerate") val framerate: String?, + @JsonProperty("preview") val preview: String?, + @JsonProperty("user_uploaded") val userUploaded: Boolean?, + @JsonProperty("rating") val rating: String?, + @JsonProperty("rates") val rates: Map?, + @JsonProperty("contribs") val contribs: List>?, + @JsonProperty("download_token") val downloadToken: String?, ) data class Movie( @JsonProperty("id") val id: Long? = null, - @JsonProperty("type") val type: String? = null, - @JsonProperty("year") val year: Long? = null, - @JsonProperty("fullName") val fullName: String? = null, + @JsonProperty("title") val title: String? = null, + @JsonProperty("release_year") val releaseYear: Int? = null, + @JsonProperty("source") val source: String? = null, + @JsonProperty("source_data") val sourceData: SourceData? = null, + @JsonProperty("type") val type: String? = null, // "movie" or "tvseries" + @JsonProperty("cast") val cast: List? = null, + @JsonProperty("poster") val posterUrl: String? = null, + @JsonProperty("season") val season: String? = null, + @JsonProperty("full_link_name") val fullLinkName: String? = null, + @JsonProperty("link_name") val linkName: String? = null, ) - data class Sub( - @JsonProperty("hi") val hi: Int? = null, - @JsonProperty("fullLink") val fullLink: String? = null, - @JsonProperty("linkName") val linkName: String? = null, - @JsonProperty("lang") val lang: String? = null, - @JsonProperty("releaseName") val releaseName: String? = null, - @JsonProperty("subId") val subId: Long? = null, + data class SourceData( + @JsonProperty("endYear") val endYear: String? = null, + @JsonProperty("genres") val genres: List? = null, + @JsonProperty("link") val link: String? = null, + @JsonProperty("rating") val rating: Float? = null, + @JsonProperty("source") val source: String? = null, + @JsonProperty("votes") val votes: Long? = null, ) - data class SubData( - @JsonProperty("movie") val movie: String, - @JsonProperty("lang") val lang: String, - @JsonProperty("id") val id: String, + data class Seasson( + @JsonProperty("season") val season: Int?, + @JsonProperty("subtitlesCount") val subtitlesCount: Int?, + @JsonProperty("year") val year: Int?, + @JsonProperty("poster") val posterUrl: String?, ) - data class SubTitleLink( - @JsonProperty("sub") val sub: SubToken, + data class ApiSubDownloadToken( + @JsonProperty("isDownloaded") val isDownloaded: Boolean?, + @JsonProperty("movie") val movie: String, + @JsonProperty("subtitle") val subtitle: Subtitle, + @JsonProperty("user_rated") val userRated: String, ) - data class SubToken( - @JsonProperty("downloadToken") val downloadToken: String, + // find `^(?!.*"language").*$\n` and replace by "" + // find `.*"language":` and replace by "" + // find `^(.*)(\r?\n\1)+$` and replace by "$1" + private val langTagIETF2SubSource = mapOf( + "ar" to "arabic", + "bg" to "bulgarian", + "bn" to "bengali", + "cs" to "czech", + "da" to "danish", + "de" to "german", + "el" to "greek", + "en" to "english", + "es" to "spanish", + "et" to "estonian", + "fa" to "farsi_persian", + "fi" to "finnish", + "fi" to "french", + "he" to "hebrew", + "hr" to "croatian", + "hu" to "hungarian", + "id" to "indonesian", + "is" to "icelandic", + "it" to "italian", + "ja" to "japanese", + "kl" to "greenlandic", + "ko" to "korean", + "ku" to "kurdish", + "mk" to "macedonian", + "ml" to "malayalam", + "ms" to "malay", + "nl" to "dutch", + "no" to "norwegian", + "pl" to "polish", + "pt-br" to "brazilian_portuguese", + "pt" to "portuguese", + "ro" to "romanian", + "ru" to "russian", + "si" to "sinhala", + "sk" to "slovak", + "sl" to "slovenian", + "sq" to "albanian", + "sr" to "serbian", + "su" to "sundanese", + "sv" to "swedish", + "th" to "thai", + "tr" to "turkish", + "ur" to "urdu", + "vi" to "vietnamese", + "zh-hant" to "big_5_code", + "zh" to "chinese_bg_code", ) } \ No newline at end of file