mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
SubDL Account login support (#1101)
This commit is contained in:
parent
5502e478c4
commit
dff56026de
7 changed files with 182 additions and 15 deletions
|
@ -14,7 +14,7 @@ abstract class AccountManager(private val defIndex: Int) : AuthAPI {
|
|||
val simklApi = SimklApi(0)
|
||||
val indexSubtitlesApi = IndexSubtitleApi()
|
||||
val addic7ed = Addic7ed()
|
||||
val subDl = SubDL()
|
||||
val subDlApi = SubDlApi(0)
|
||||
val localListApi = LocalList()
|
||||
|
||||
// used to login via app intent
|
||||
|
@ -26,7 +26,7 @@ abstract class AccountManager(private val defIndex: Int) : AuthAPI {
|
|||
// this needs init with context and can be accessed in settings
|
||||
val accountManagers
|
||||
get() = listOf(
|
||||
malApi, aniListApi, openSubtitlesApi, simklApi //nginxApi
|
||||
malApi, aniListApi, openSubtitlesApi, subDlApi, simklApi //nginxApi
|
||||
)
|
||||
|
||||
// used for active syncing
|
||||
|
@ -36,14 +36,17 @@ abstract class AccountManager(private val defIndex: Int) : AuthAPI {
|
|||
)
|
||||
|
||||
val inAppAuths
|
||||
get() = listOf(openSubtitlesApi)//, nginxApi)
|
||||
get() = listOf<InAppAuthAPIManager>(
|
||||
openSubtitlesApi,
|
||||
subDlApi
|
||||
)//, nginxApi)
|
||||
|
||||
val subtitleProviders
|
||||
get() = listOf(
|
||||
openSubtitlesApi,
|
||||
indexSubtitlesApi, // they got anti scraping measures in place :(
|
||||
addic7ed,
|
||||
subDl
|
||||
subDlApi
|
||||
)
|
||||
|
||||
const val appString = "cloudstreamapp"
|
||||
|
|
|
@ -1,21 +1,80 @@
|
|||
package com.lagradost.cloudstream3.syncproviders.providers
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
||||
import com.lagradost.cloudstream3.ErrorLoadingException
|
||||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.TvType
|
||||
import com.lagradost.cloudstream3.app
|
||||
import com.lagradost.cloudstream3.subtitles.AbstractSubProvider
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import com.lagradost.cloudstream3.subtitles.AbstractSubApi
|
||||
import com.lagradost.cloudstream3.subtitles.AbstractSubtitleEntities
|
||||
import com.lagradost.cloudstream3.subtitles.SubtitleResource
|
||||
import com.lagradost.cloudstream3.syncproviders.AuthAPI.LoginInfo
|
||||
import com.lagradost.cloudstream3.syncproviders.InAppAuthAPI
|
||||
import com.lagradost.cloudstream3.syncproviders.InAppAuthAPIManager
|
||||
|
||||
class SubDL : AbstractSubProvider {
|
||||
//API Documentation: https://subdl.com/api-doc
|
||||
val mainUrl = "https://subdl.com/"
|
||||
val name = "SubDL"
|
||||
class SubDlApi(index: Int) : InAppAuthAPIManager(index), AbstractSubApi {
|
||||
override val idPrefix = "subdl"
|
||||
override val name = "SubDL"
|
||||
override val icon = R.drawable.subdl_logo_big
|
||||
override val requiresPassword = true
|
||||
override val requiresEmail = true
|
||||
override val createAccountUrl = "https://subdl.com/login"
|
||||
|
||||
companion object {
|
||||
const val APIKEY = "zRJl5QA-8jNA2i0pE8cxANbEukANp7IM"
|
||||
const val APIENDPOINT = "https://api.subdl.com/api/v1/subtitles"
|
||||
const val APIURL = "https://api.subdl.com"
|
||||
const val APIENDPOINT = "$APIURL/api/v1/subtitles"
|
||||
const val DOWNLOADENDPOINT = "https://dl.subdl.com"
|
||||
const val SUBDL_SUBTITLES_USER_KEY: String = "subdl_user"
|
||||
var currentSession: SubtitleOAuthEntity? = null
|
||||
}
|
||||
|
||||
override suspend fun initialize() {
|
||||
currentSession = getAuthKey()
|
||||
}
|
||||
|
||||
override fun logOut() {
|
||||
setAuthKey(null)
|
||||
removeAccountKeys()
|
||||
currentSession = getAuthKey()
|
||||
}
|
||||
override suspend fun login(data: InAppAuthAPI.LoginData): Boolean {
|
||||
val email = data.email ?: throw ErrorLoadingException("Requires Email")
|
||||
val password = data.password ?: throw ErrorLoadingException("Requires Password")
|
||||
switchToNewAccount()
|
||||
try {
|
||||
if (initLogin(email, password)) {
|
||||
registerAccount()
|
||||
return true
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logError(e)
|
||||
switchToOldAccount()
|
||||
}
|
||||
switchToOldAccount()
|
||||
return false
|
||||
}
|
||||
|
||||
override fun getLatestLoginData(): InAppAuthAPI.LoginData? {
|
||||
val current = getAuthKey() ?: return null
|
||||
return InAppAuthAPI.LoginData(
|
||||
email = current.userEmail,
|
||||
password = current.pass
|
||||
)
|
||||
}
|
||||
|
||||
override fun loginInfo(): LoginInfo? {
|
||||
getAuthKey()?.let { user ->
|
||||
return LoginInfo(
|
||||
profilePicture = null,
|
||||
name = user.name ?: user.userEmail,
|
||||
accountIndex = accountIndex
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override suspend fun search(query: AbstractSubtitleEntities.SubtitleSearch): List<AbstractSubtitleEntities.SubtitleEntity>? {
|
||||
|
@ -37,8 +96,8 @@ class SubDL : AbstractSubProvider {
|
|||
|
||||
val searchQueryUrl = when (idQuery) {
|
||||
//Use imdb/tmdb id to search if its valid
|
||||
null -> "$APIENDPOINT?api_key=$APIKEY&film_name=$queryText&languages=${query.lang}$epQuery$seasonQuery$yearQuery"
|
||||
else -> "$APIENDPOINT?api_key=$APIKEY$idQuery&languages=${query.lang}$epQuery$seasonQuery$yearQuery"
|
||||
null -> "$APIENDPOINT?api_key=${currentSession?.apiKey}&film_name=$queryText&languages=${query.lang}$epQuery$seasonQuery$yearQuery"
|
||||
else -> "$APIENDPOINT?api_key=${currentSession?.apiKey}$idQuery&languages=${query.lang}$epQuery$seasonQuery$yearQuery"
|
||||
}
|
||||
|
||||
val req = app.get(
|
||||
|
@ -49,7 +108,7 @@ class SubDL : AbstractSubProvider {
|
|||
)
|
||||
|
||||
return req.parsedSafe<ApiResponse>()?.subtitles?.map { subtitle ->
|
||||
val name = subtitle.releaseName
|
||||
|
||||
val lang = subtitle.lang.replaceFirstChar { it.uppercase() }
|
||||
val resEpNum = subtitle.episode ?: query.epNumber
|
||||
val resSeasonNum = subtitle.season ?: query.seasonNumber
|
||||
|
@ -57,13 +116,14 @@ class SubDL : AbstractSubProvider {
|
|||
|
||||
AbstractSubtitleEntities.SubtitleEntity(
|
||||
idPrefix = this.idPrefix,
|
||||
name = name,
|
||||
name = subtitle.releaseName,
|
||||
lang = lang,
|
||||
data = "${DOWNLOADENDPOINT}${subtitle.url}",
|
||||
type = type,
|
||||
source = this.name,
|
||||
epNumber = resEpNum,
|
||||
seasonNumber = resSeasonNum,
|
||||
isHearingImpaired = subtitle.hearingImpaired ?: false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -74,6 +134,88 @@ class SubDL : AbstractSubProvider {
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun initLogin(useremail: String, password: String): Boolean {
|
||||
|
||||
val tokenResponse = app.post(
|
||||
url = "$APIURL/login",
|
||||
data = mapOf(
|
||||
"email" to useremail,
|
||||
"password" to password
|
||||
)
|
||||
).parsedSafe<OAuthTokenResponse>()
|
||||
|
||||
if (tokenResponse?.token == null) return false
|
||||
|
||||
val apiResponse = app.get(
|
||||
url = "$APIURL/user/userApi",
|
||||
headers = mapOf(
|
||||
"Authorization" to "Bearer ${tokenResponse.token}"
|
||||
)
|
||||
).parsedSafe<ApiKeyResponse>()
|
||||
|
||||
if (apiResponse?.ok == false) return false
|
||||
|
||||
setAuthKey(
|
||||
SubtitleOAuthEntity(
|
||||
userEmail = useremail,
|
||||
pass = password,
|
||||
name = tokenResponse.userData?.username ?: tokenResponse.userData?.name,
|
||||
accessToken = tokenResponse.token,
|
||||
apiKey = apiResponse?.apiKey
|
||||
)
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
private fun getAuthKey(): SubtitleOAuthEntity? {
|
||||
return getKey(accountId, SUBDL_SUBTITLES_USER_KEY)
|
||||
}
|
||||
|
||||
private fun setAuthKey(data: SubtitleOAuthEntity?) {
|
||||
if (data == null) removeKey(
|
||||
accountId,
|
||||
SUBDL_SUBTITLES_USER_KEY
|
||||
)
|
||||
currentSession = data
|
||||
setKey(accountId, SUBDL_SUBTITLES_USER_KEY, data)
|
||||
}
|
||||
|
||||
data class SubtitleOAuthEntity(
|
||||
@JsonProperty("userEmail") var userEmail: String,
|
||||
@JsonProperty("pass") var pass: String,
|
||||
@JsonProperty("name") var name: String? = null,
|
||||
@JsonProperty("accessToken") var accessToken: String? = null,
|
||||
@JsonProperty("apiKey") var apiKey: String? = null,
|
||||
)
|
||||
|
||||
data class OAuthTokenResponse(
|
||||
@JsonProperty("token") val token: String? = null,
|
||||
@JsonProperty("userData") val userData: UserData? = null,
|
||||
@JsonProperty("status") val status: Boolean? = null,
|
||||
@JsonProperty("message") val message: String? = null,
|
||||
)
|
||||
|
||||
data class UserData(
|
||||
@JsonProperty("email") val email: String,
|
||||
@JsonProperty("name") val name: String,
|
||||
@JsonProperty("country") val country: String,
|
||||
@JsonProperty("scStepCode") val scStepCode: String,
|
||||
@JsonProperty("scVerified") val scVerified: Boolean,
|
||||
@JsonProperty("username") val username: String? = null,
|
||||
@JsonProperty("scUsername") val scUsername: String,
|
||||
)
|
||||
|
||||
data class ApiKeyResponse(
|
||||
@JsonProperty("ok") val ok: Boolean? = false,
|
||||
@JsonProperty("api_key") val apiKey: String? = null,
|
||||
@JsonProperty("usage") val usage: Usage? = null,
|
||||
)
|
||||
|
||||
data class Usage(
|
||||
@JsonProperty("total") val total: Long? = 0,
|
||||
@JsonProperty("today") val today: Long? = 0,
|
||||
)
|
||||
|
||||
data class ApiResponse(
|
||||
@JsonProperty("status") val status: Boolean? = null,
|
||||
@JsonProperty("results") val results: List<Result>? = null,
|
||||
|
@ -96,7 +238,10 @@ class SubDL : AbstractSubProvider {
|
|||
@JsonProperty("lang") val lang: String,
|
||||
@JsonProperty("author") val author: String? = null,
|
||||
@JsonProperty("url") val url: String? = null,
|
||||
@JsonProperty("subtitlePage") val subtitlePage: String? = null,
|
||||
@JsonProperty("season") val season: Int? = null,
|
||||
@JsonProperty("episode") val episode: Int? = null,
|
||||
@JsonProperty("language") val language: String? = null,
|
||||
@JsonProperty("hi") val hearingImpaired: Boolean? = null,
|
||||
)
|
||||
}
|
|
@ -27,6 +27,7 @@ import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.aniList
|
|||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.malApi
|
||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.openSubtitlesApi
|
||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.simklApi
|
||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.subDlApi
|
||||
import com.lagradost.cloudstream3.syncproviders.AuthAPI
|
||||
import com.lagradost.cloudstream3.syncproviders.InAppAuthAPI
|
||||
import com.lagradost.cloudstream3.syncproviders.OAuth2API
|
||||
|
@ -324,6 +325,7 @@ class SettingsAccount : PreferenceFragmentCompat(), BiometricAuthenticator.Biome
|
|||
R.string.anilist_key to aniListApi,
|
||||
R.string.simkl_key to simklApi,
|
||||
R.string.opensubtitles_key to openSubtitlesApi,
|
||||
R.string.subdl_key to subDlApi,
|
||||
)
|
||||
|
||||
for ((key, api) in syncApis) {
|
||||
|
|
|
@ -26,6 +26,7 @@ import com.lagradost.cloudstream3.syncproviders.providers.MALApi.Companion.MAL_T
|
|||
import com.lagradost.cloudstream3.syncproviders.providers.MALApi.Companion.MAL_UNIXTIME_KEY
|
||||
import com.lagradost.cloudstream3.syncproviders.providers.MALApi.Companion.MAL_USER_KEY
|
||||
import com.lagradost.cloudstream3.syncproviders.providers.OpenSubtitlesApi.Companion.OPEN_SUBTITLES_USER_KEY
|
||||
import com.lagradost.cloudstream3.syncproviders.providers.SubDlApi.Companion.SUBDL_SUBTITLES_USER_KEY
|
||||
import com.lagradost.cloudstream3.ui.result.txt
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.main
|
||||
|
@ -64,6 +65,7 @@ object BackupUtils {
|
|||
PLUGINS_KEY_LOCAL,
|
||||
|
||||
OPEN_SUBTITLES_USER_KEY,
|
||||
SUBDL_SUBTITLES_USER_KEY,
|
||||
|
||||
DOWNLOAD_EPISODE_CACHE,
|
||||
|
||||
|
|
10
app/src/main/res/drawable/subdl_logo_big.xml
Normal file
10
app/src/main/res/drawable/subdl_logo_big.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="20dp"
|
||||
android:viewportHeight="320"
|
||||
android:viewportWidth="320"
|
||||
android:width="20dp">
|
||||
|
||||
<path android:fillColor="@color/white"
|
||||
android:pathData="m107.87,39.3l-8.44,8.59l0,35.68l0,35.83l18.95,22.5c10.36,12.44 30.35,36.27 44.41,53l25.46,30.5l0,12.14l0,12.29l-24.43,-0l-24.43,-0l0,-11.84l0,-11.84l-19.99,-0l-19.99,-0l0,23.24l0,23.24l8.44,8.59l8.44,8.59l48.26,-0l48.26,-0l7.7,-7.85l7.7,-7.85l0,-36.86l-0.15,-37.01l-23.98,-28.28c-13.18,-15.54 -33.16,-39.23 -44.26,-52.55l-20.43,-24.13l0,-12.29l0,-12.29l24.43,-0l24.43,-0l0,12.58l0,12.58l19.99,-0l19.99,-0l0,-24.87l0,-24.87l-7.85,-7.7l-7.85,-7.7l-48.11,-0l-48.11,-0l-8.44,8.59z"/>
|
||||
|
||||
</vector>
|
|
@ -471,6 +471,7 @@
|
|||
<string name="simkl_key" translatable="false">simkl_key</string>
|
||||
<string name="mal_key" translatable="false">mal_key</string>
|
||||
<string name="opensubtitles_key" translatable="false">opensubtitles_key</string>
|
||||
<string name="subdl_key" translatable="false">subdl_key</string>
|
||||
<string name="nginx_key" translatable="false">nginx_key</string>
|
||||
<string name="example_password">password123</string>
|
||||
<string name="example_username">Username</string>
|
||||
|
|
|
@ -17,6 +17,10 @@
|
|||
android:icon="@drawable/open_subtitles_icon"
|
||||
android:key="@string/opensubtitles_key" />
|
||||
|
||||
<Preference
|
||||
android:icon="@drawable/subdl_logo_big"
|
||||
android:key="@string/subdl_key" />
|
||||
|
||||
<SwitchPreference
|
||||
android:defaultValue="false"
|
||||
android:icon="@drawable/ic_outline_account_circle_24"
|
||||
|
|
Loading…
Reference in a new issue