forked from recloudstream/cloudstream
updated syncapi and possible bug fixed
This commit is contained in:
parent
1628ec56c2
commit
a16cd3ef9a
17 changed files with 710 additions and 398 deletions
|
@ -1,4 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#6A32D3</color>
|
||||
<!--#6A32D3-->
|
||||
<color name="ic_launcher_background">@color/colorPrimaryPurple</color>
|
||||
</resources>
|
|
@ -54,7 +54,7 @@ object APIHolder {
|
|||
)
|
||||
|
||||
val restrictedApis = arrayListOf(
|
||||
TrailersToProvider(), // be aware that this is fuckery
|
||||
// TrailersToProvider(), // be aware that this is fuckery
|
||||
// NyaaProvider(), // torrents in cs3 is wack
|
||||
// ThenosProvider(), // ddos protection and wacked links
|
||||
AsiaFlixProvider(),
|
||||
|
@ -314,7 +314,7 @@ interface SearchResponse {
|
|||
val name: String
|
||||
val url: String
|
||||
val apiName: String
|
||||
val type: TvType
|
||||
val type: TvType?
|
||||
val posterUrl: String?
|
||||
val id: Int?
|
||||
}
|
||||
|
|
|
@ -28,9 +28,9 @@ import com.lagradost.cloudstream3.APIHolder.restrictedApis
|
|||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import com.lagradost.cloudstream3.network.initRequestClient
|
||||
import com.lagradost.cloudstream3.receivers.VideoDownloadRestartReceiver
|
||||
import com.lagradost.cloudstream3.syncproviders.OAuth2Interface.Companion.OAuth2Apis
|
||||
import com.lagradost.cloudstream3.syncproviders.OAuth2Interface.Companion.OAuth2accountApis
|
||||
import com.lagradost.cloudstream3.syncproviders.OAuth2Interface.Companion.appString
|
||||
import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.OAuth2Apis
|
||||
import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.OAuth2accountApis
|
||||
import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.appString
|
||||
import com.lagradost.cloudstream3.ui.APIRepository
|
||||
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_NAVIGATE_TO
|
||||
import com.lagradost.cloudstream3.ui.player.PlayerEventType
|
||||
|
@ -45,6 +45,7 @@ import com.lagradost.cloudstream3.utils.InAppUpdater.Companion.runAutoUpdate
|
|||
import com.lagradost.cloudstream3.utils.UIHelper.checkWrite
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.getResourceColor
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.hasPIPPermission
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.navigate
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.requestRW
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.shouldShowPIPMode
|
||||
|
@ -447,6 +448,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
|||
nav_view.setupWithNavController(navController)
|
||||
|
||||
navController.addOnDestinationChangedListener { _, destination, _ ->
|
||||
this.hideKeyboard()
|
||||
// nav_view.hideKeyboard()
|
||||
/*if (destination.id != R.id.navigation_player) {
|
||||
requestedOrientation = if (settingsManager?.getBoolean("force_landscape", false) == true) {
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
package com.lagradost.cloudstream3.syncproviders
|
||||
|
||||
import android.content.Context
|
||||
import com.lagradost.cloudstream3.utils.DataStore.getKey
|
||||
import com.lagradost.cloudstream3.utils.DataStore.removeKeys
|
||||
import com.lagradost.cloudstream3.utils.DataStore.setKey
|
||||
|
||||
abstract class AccountManager(private val defIndex: Int) : OAuth2API {
|
||||
// don't change this as all keys depend on it
|
||||
open val idPrefix: String
|
||||
get() {
|
||||
throw(NotImplementedError())
|
||||
}
|
||||
|
||||
var accountIndex = defIndex
|
||||
protected val accountId get() = "${idPrefix}_account_$accountIndex"
|
||||
private val accountActiveKey get() = "${idPrefix}_active"
|
||||
|
||||
// int array of all accounts indexes
|
||||
private val accountsKey get() = "${idPrefix}_accounts"
|
||||
|
||||
protected fun Context.removeAccountKeys() {
|
||||
this.removeKeys(accountId)
|
||||
val accounts = getAccounts(this).toMutableList()
|
||||
accounts.remove(accountIndex)
|
||||
this.setKey(accountsKey, accounts.toIntArray())
|
||||
|
||||
init(this)
|
||||
}
|
||||
|
||||
fun getAccounts(context: Context): IntArray {
|
||||
return context.getKey(accountsKey, intArrayOf())!!
|
||||
}
|
||||
|
||||
fun init(context: Context) {
|
||||
accountIndex = context.getKey(accountActiveKey, defIndex)!!
|
||||
val accounts = getAccounts(context)
|
||||
if (accounts.isNotEmpty() && this.loginInfo(context) == null) {
|
||||
accountIndex = accounts.first()
|
||||
}
|
||||
}
|
||||
|
||||
protected fun Context.switchToNewAccount() {
|
||||
val accounts = getAccounts(this)
|
||||
accountIndex = (accounts.maxOrNull() ?: 0) + 1
|
||||
}
|
||||
|
||||
protected fun Context.registerAccount() {
|
||||
this.setKey(accountActiveKey, accountIndex)
|
||||
val accounts = getAccounts(this).toMutableList()
|
||||
if (!accounts.contains(accountIndex)) {
|
||||
accounts.add(accountIndex)
|
||||
}
|
||||
|
||||
this.setKey(accountsKey, accounts.toIntArray())
|
||||
}
|
||||
|
||||
fun changeAccount(context: Context, index: Int) {
|
||||
accountIndex = index
|
||||
context.setKey(accountActiveKey, index)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
package com.lagradost.cloudstream3.syncproviders
|
||||
|
||||
import android.content.Context
|
||||
import com.lagradost.cloudstream3.syncproviders.providers.AniListApi
|
||||
import com.lagradost.cloudstream3.syncproviders.providers.MALApi
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
interface OAuth2API {
|
||||
val key: String
|
||||
val name: String
|
||||
val redirectUrl: String
|
||||
|
||||
fun handleRedirect(context: Context, url: String)
|
||||
fun authenticate(context: Context)
|
||||
|
||||
fun loginInfo(context: Context): LoginInfo?
|
||||
fun logOut(context: Context)
|
||||
|
||||
class LoginInfo(
|
||||
val profilePicture: String?,
|
||||
val name: String?,
|
||||
|
||||
val accountIndex: Int,
|
||||
)
|
||||
|
||||
companion object {
|
||||
val malApi = MALApi(0)
|
||||
val aniListApi = AniListApi(0)
|
||||
|
||||
// used to login via app intent
|
||||
val OAuth2Apis
|
||||
get() = listOf<OAuth2API>(
|
||||
malApi, aniListApi
|
||||
)
|
||||
|
||||
// this needs init with context and can be accessed in settings
|
||||
val OAuth2accountApis
|
||||
get() = listOf<AccountManager>(
|
||||
malApi, aniListApi
|
||||
)
|
||||
|
||||
// used for active syncing
|
||||
val SyncApis
|
||||
get() = listOf<SyncAPI>(
|
||||
malApi, aniListApi
|
||||
)
|
||||
|
||||
const val appString = "cloudstreamapp"
|
||||
|
||||
val unixTime: Long
|
||||
get() = System.currentTimeMillis() / 1000L
|
||||
|
||||
const val maxStale = 60 * 10
|
||||
|
||||
fun secondsToReadable(seconds: Int, completedValue: String): String {
|
||||
var secondsLong = seconds.toLong()
|
||||
val days = TimeUnit.SECONDS
|
||||
.toDays(secondsLong)
|
||||
secondsLong -= TimeUnit.DAYS.toSeconds(days)
|
||||
|
||||
val hours = TimeUnit.SECONDS
|
||||
.toHours(secondsLong)
|
||||
secondsLong -= TimeUnit.HOURS.toSeconds(hours)
|
||||
|
||||
val minutes = TimeUnit.SECONDS
|
||||
.toMinutes(secondsLong)
|
||||
secondsLong -= TimeUnit.MINUTES.toSeconds(minutes)
|
||||
if (minutes < 0) {
|
||||
return completedValue
|
||||
}
|
||||
//println("$days $hours $minutes")
|
||||
return "${if (days != 0L) "$days" + "d " else ""}${if (hours != 0L) "$hours" + "h " else ""}${minutes}m"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,127 +0,0 @@
|
|||
package com.lagradost.cloudstream3.syncproviders
|
||||
|
||||
import android.content.Context
|
||||
import com.lagradost.cloudstream3.utils.DataStore.getKey
|
||||
import com.lagradost.cloudstream3.utils.DataStore.removeKeys
|
||||
import com.lagradost.cloudstream3.utils.DataStore.setKey
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
interface OAuth2Interface {
|
||||
val key: String
|
||||
val name: String
|
||||
val redirectUrl: String
|
||||
|
||||
fun handleRedirect(context: Context, url: String)
|
||||
fun authenticate(context: Context)
|
||||
|
||||
fun loginInfo(context: Context): LoginInfo?
|
||||
fun logOut(context: Context)
|
||||
|
||||
class LoginInfo(
|
||||
val profilePicture: String?,
|
||||
val name: String?,
|
||||
|
||||
val accountIndex: Int,
|
||||
)
|
||||
|
||||
abstract class AccountManager(private val defIndex: Int) : OAuth2Interface {
|
||||
// don't change this as all keys depend on it
|
||||
open val idPrefix: String
|
||||
get() {
|
||||
throw(NotImplementedError())
|
||||
}
|
||||
|
||||
var accountIndex = defIndex
|
||||
protected val accountId get() = "${idPrefix}_account_$accountIndex"
|
||||
private val accountActiveKey get() = "${idPrefix}_active"
|
||||
|
||||
// int array of all accounts indexes
|
||||
private val accountsKey get() = "${idPrefix}_accounts"
|
||||
|
||||
protected fun Context.removeAccountKeys() {
|
||||
this.removeKeys(accountId)
|
||||
val accounts = getAccounts(this).toMutableList()
|
||||
accounts.remove(accountIndex)
|
||||
this.setKey(accountsKey, accounts.toIntArray())
|
||||
|
||||
init(this)
|
||||
}
|
||||
|
||||
fun getAccounts(context: Context): IntArray {
|
||||
return context.getKey(accountsKey, intArrayOf())!!
|
||||
}
|
||||
|
||||
fun init(context: Context) {
|
||||
accountIndex = context.getKey(accountActiveKey, defIndex)!!
|
||||
val accounts = getAccounts(context)
|
||||
if (accounts.isNotEmpty() && this.loginInfo(context) == null) {
|
||||
accountIndex = accounts.first()
|
||||
}
|
||||
}
|
||||
|
||||
protected fun Context.switchToNewAccount() {
|
||||
val accounts = getAccounts(this)
|
||||
accountIndex = (accounts.maxOrNull() ?: 0) + 1
|
||||
}
|
||||
|
||||
protected fun Context.registerAccount() {
|
||||
this.setKey(accountActiveKey, accountIndex)
|
||||
val accounts = getAccounts(this).toMutableList()
|
||||
if (!accounts.contains(accountIndex)) {
|
||||
accounts.add(accountIndex)
|
||||
}
|
||||
|
||||
this.setKey(accountsKey, accounts.toIntArray())
|
||||
}
|
||||
|
||||
fun changeAccount(context: Context, index: Int) {
|
||||
accountIndex = index
|
||||
context.setKey(accountActiveKey, index)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val malApi = MALApi(0)
|
||||
val aniListApi = AniListApi(0)
|
||||
|
||||
val OAuth2Apis
|
||||
get() = listOf<OAuth2Interface>(
|
||||
malApi, aniListApi
|
||||
)
|
||||
|
||||
// this needs init with context
|
||||
val OAuth2accountApis
|
||||
get() = listOf<AccountManager>(
|
||||
malApi, aniListApi
|
||||
)
|
||||
|
||||
const val appString = "cloudstreamapp"
|
||||
|
||||
val unixTime: Long
|
||||
get() = System.currentTimeMillis() / 1000L
|
||||
val unixTimeMS: Long
|
||||
get() = System.currentTimeMillis()
|
||||
|
||||
const val maxStale = 60 * 10
|
||||
|
||||
fun secondsToReadable(seconds: Int, completedValue: String): String {
|
||||
var secondsLong = seconds.toLong()
|
||||
val days = TimeUnit.SECONDS
|
||||
.toDays(secondsLong)
|
||||
secondsLong -= TimeUnit.DAYS.toSeconds(days)
|
||||
|
||||
val hours = TimeUnit.SECONDS
|
||||
.toHours(secondsLong)
|
||||
secondsLong -= TimeUnit.HOURS.toSeconds(hours)
|
||||
|
||||
val minutes = TimeUnit.SECONDS
|
||||
.toMinutes(secondsLong)
|
||||
secondsLong -= TimeUnit.MINUTES.toSeconds(minutes)
|
||||
if (minutes < 0) {
|
||||
return completedValue
|
||||
}
|
||||
//println("$days $hours $minutes")
|
||||
return "${if (days != 0L) "$days" + "d " else ""}${if (hours != 0L) "$hours" + "h " else ""}${minutes}m"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
package com.lagradost.cloudstream3.syncproviders
|
||||
|
||||
import android.content.Context
|
||||
import com.lagradost.cloudstream3.ShowStatus
|
||||
|
||||
interface SyncAPI {
|
||||
data class SyncSearchResult(
|
||||
val name: String,
|
||||
val syncApiName: String,
|
||||
val id: String,
|
||||
val url: String?,
|
||||
val posterUrl: String?,
|
||||
)
|
||||
|
||||
data class SyncNextAiring(
|
||||
val episode: Int,
|
||||
val unixTime: Long,
|
||||
)
|
||||
|
||||
data class SyncActor(
|
||||
val name: String,
|
||||
val posterUrl: String?,
|
||||
)
|
||||
|
||||
data class SyncCharacter(
|
||||
val name: String,
|
||||
val posterUrl: String?,
|
||||
)
|
||||
|
||||
data class SyncStatus(
|
||||
val status: Int,
|
||||
/** 1-10 */
|
||||
val score: Int?,
|
||||
val watchedEpisodes: Int?,
|
||||
var isFavorite: Boolean? = null,
|
||||
)
|
||||
|
||||
data class SyncResult(
|
||||
/**Used to verify*/
|
||||
var id: String,
|
||||
|
||||
var totalEpisodes: Int? = null,
|
||||
|
||||
var title: String? = null,
|
||||
/**1-1000*/
|
||||
var publicScore: Int? = null,
|
||||
/**In minutes*/
|
||||
var duration: Int? = null,
|
||||
var synopsis: String? = null,
|
||||
var airStatus: ShowStatus? = null,
|
||||
var nextAiring: SyncNextAiring? = null,
|
||||
var studio: String? = null,
|
||||
var genres: List<String>? = null,
|
||||
var trailerUrl: String? = null,
|
||||
|
||||
/** In unixtime */
|
||||
var startDate: Long? = null,
|
||||
/** In unixtime */
|
||||
var endDate: Long? = null,
|
||||
var recommendations: List<SyncSearchResult>? = null,
|
||||
var nextSeason: SyncSearchResult? = null,
|
||||
var prevSeason: SyncSearchResult? = null,
|
||||
var actors: List<SyncActor>? = null,
|
||||
var characters: List<SyncCharacter>? = null,
|
||||
)
|
||||
|
||||
val icon : Int
|
||||
|
||||
val mainUrl: String
|
||||
fun search(context: Context, name: String): List<SyncSearchResult>?
|
||||
|
||||
/**
|
||||
-1 -> None
|
||||
0 -> Watching
|
||||
1 -> Completed
|
||||
2 -> OnHold
|
||||
3 -> Dropped
|
||||
4 -> PlanToWatch
|
||||
5 -> ReWatching
|
||||
*/
|
||||
fun score(context: Context, id: String, status : SyncStatus): Boolean
|
||||
|
||||
fun getStatus(context: Context, id : String) : SyncStatus?
|
||||
|
||||
fun getResult(context: Context, id: String): SyncResult?
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package com.lagradost.cloudstream3.syncproviders
|
||||
package com.lagradost.cloudstream3.syncproviders.providers
|
||||
|
||||
import android.content.Context
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
|
@ -6,12 +6,16 @@ import com.fasterxml.jackson.databind.DeserializationFeature
|
|||
import com.fasterxml.jackson.databind.json.JsonMapper
|
||||
import com.fasterxml.jackson.module.kotlin.KotlinModule
|
||||
import com.fasterxml.jackson.module.kotlin.readValue
|
||||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import com.lagradost.cloudstream3.network.post
|
||||
import com.lagradost.cloudstream3.network.text
|
||||
import com.lagradost.cloudstream3.syncproviders.OAuth2Interface.Companion.appString
|
||||
import com.lagradost.cloudstream3.syncproviders.OAuth2Interface.Companion.maxStale
|
||||
import com.lagradost.cloudstream3.syncproviders.OAuth2Interface.Companion.unixTime
|
||||
import com.lagradost.cloudstream3.syncproviders.AccountManager
|
||||
import com.lagradost.cloudstream3.syncproviders.OAuth2API
|
||||
import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.appString
|
||||
import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.maxStale
|
||||
import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.unixTime
|
||||
import com.lagradost.cloudstream3.syncproviders.SyncAPI
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.openBrowser
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.splitQuery
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||
|
@ -21,9 +25,8 @@ import com.lagradost.cloudstream3.utils.DataStore.setKey
|
|||
import com.lagradost.cloudstream3.utils.DataStore.toKotlinObject
|
||||
import java.net.URL
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class AniListApi(index : Int) : OAuth2Interface.AccountManager(index) {
|
||||
class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
||||
override val name: String
|
||||
get() = "AniList"
|
||||
override val key: String
|
||||
|
@ -32,11 +35,19 @@ class AniListApi(index : Int) : OAuth2Interface.AccountManager(index) {
|
|||
get() = "anilistlogin"
|
||||
override val idPrefix: String
|
||||
get() = "anilist"
|
||||
override val mainUrl: String
|
||||
get() = "https://anilist.co"
|
||||
override val icon: Int
|
||||
get() = R.drawable.ic_anilist_icon
|
||||
|
||||
override fun loginInfo(context: Context): OAuth2Interface.LoginInfo? {
|
||||
override fun loginInfo(context: Context): OAuth2API.LoginInfo? {
|
||||
// context.getUser(true)?.
|
||||
context.getKey<AniListUser>(accountId, ANILIST_USER_KEY)?.let { user ->
|
||||
return OAuth2Interface.LoginInfo(profilePicture = user.picture, name = user.name, accountIndex = accountIndex)
|
||||
return OAuth2API.LoginInfo(
|
||||
profilePicture = user.picture,
|
||||
name = user.name,
|
||||
accountIndex = accountIndex
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
@ -71,7 +82,60 @@ class AniListApi(index : Int) : OAuth2Interface.AccountManager(index) {
|
|||
}
|
||||
}
|
||||
|
||||
override fun search(context: Context, name: String): List<SyncAPI.SyncSearchResult>? {
|
||||
val data = searchShows(name) ?: return null
|
||||
return data.data.Page.media.map {
|
||||
SyncAPI.SyncSearchResult(
|
||||
it.title.romaji,
|
||||
this.name,
|
||||
it.id.toString(),
|
||||
"$mainUrl/anime/${it.id}",
|
||||
it.bannerImage
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getResult(context: Context, id: String): SyncAPI.SyncResult? {
|
||||
val internalId = id.toIntOrNull() ?: return null
|
||||
val season = getSeason(internalId)?.data?.Media ?: return null
|
||||
|
||||
return SyncAPI.SyncResult(
|
||||
season.id.toString(),
|
||||
nextAiring = season.nextAiringEpisode?.let {
|
||||
SyncAPI.SyncNextAiring(
|
||||
it.episode,
|
||||
it.timeUntilAiring + unixTime
|
||||
)
|
||||
},
|
||||
//TODO REST
|
||||
)
|
||||
}
|
||||
|
||||
override fun getStatus(context: Context, id: String): SyncAPI.SyncStatus? {
|
||||
val internalId = id.toIntOrNull() ?: return null
|
||||
val data = context.getDataAboutId(internalId) ?: return null
|
||||
|
||||
return SyncAPI.SyncStatus(
|
||||
score = data.score,
|
||||
watchedEpisodes = data.episodes,
|
||||
status = data.type.value,
|
||||
isFavorite = data.isFavourite,
|
||||
)
|
||||
}
|
||||
|
||||
override fun score(context: Context, id: String, status: SyncAPI.SyncStatus): Boolean {
|
||||
return context.postDataAboutId(
|
||||
id.toIntOrNull() ?: return false,
|
||||
fromIntToAnimeStatus(status.status),
|
||||
status.score,
|
||||
status.watchedEpisodes
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val mapper = JsonMapper.builder().addModule(KotlinModule())
|
||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).build()!!
|
||||
|
||||
private val aniListStatusString = arrayOf("CURRENT", "COMPLETED", "PAUSED", "DROPPED", "PLANNING", "REPEATING")
|
||||
|
||||
const val ANILIST_UNIXTIME_KEY: String = "anilist_unixtime" // When token expires
|
||||
|
@ -79,89 +143,14 @@ class AniListApi(index : Int) : OAuth2Interface.AccountManager(index) {
|
|||
const val ANILIST_USER_KEY: String = "anilist_user" // user data like profile
|
||||
const val ANILIST_CACHED_LIST: String = "anilist_cached_list"
|
||||
const val ANILIST_SHOULD_UPDATE_LIST: String = "anilist_should_update_list"
|
||||
}
|
||||
|
||||
|
||||
private val mapper = JsonMapper.builder().addModule(KotlinModule())
|
||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).build()!!
|
||||
|
||||
|
||||
// Changing names of these will show up in UI
|
||||
enum class AniListStatusType(var value: Int) {
|
||||
Watching(0),
|
||||
Completed(1),
|
||||
Paused(2),
|
||||
Dropped(3),
|
||||
Planning(4),
|
||||
Rewatching(5),
|
||||
None(-1)
|
||||
}
|
||||
|
||||
fun fromIntToAnimeStatus(inp: Int): AniListStatusType {//= AniListStatusType.values().first { it.value == inp }
|
||||
return when (inp) {
|
||||
-1 -> AniListStatusType.None
|
||||
0 -> AniListStatusType.Watching
|
||||
1 -> AniListStatusType.Completed
|
||||
2 -> AniListStatusType.Paused
|
||||
3 -> AniListStatusType.Dropped
|
||||
4 -> AniListStatusType.Planning
|
||||
5 -> AniListStatusType.Rewatching
|
||||
else -> AniListStatusType.None
|
||||
private fun fixName(name: String): String {
|
||||
return name.toLowerCase(Locale.ROOT).replace(" ", "").replace("[^a-zA-Z0-9]".toRegex(), "")
|
||||
}
|
||||
}
|
||||
|
||||
fun convertAnilistStringToStatus(string: String): AniListStatusType {
|
||||
return fromIntToAnimeStatus(aniListStatusString.indexOf(string))
|
||||
}
|
||||
|
||||
fun Context.initGetUser() {
|
||||
if (getKey<String>(accountId, ANILIST_TOKEN_KEY, null) == null) return
|
||||
ioSafe {
|
||||
getUser()
|
||||
}
|
||||
}
|
||||
|
||||
private fun Context.checkToken(): Boolean {
|
||||
if (unixTime > getKey(accountId,
|
||||
ANILIST_UNIXTIME_KEY, 0L
|
||||
)!!
|
||||
) {
|
||||
/*getCurrentActivity()?.runOnUiThread {
|
||||
val alertDialog: AlertDialog? = activity?.let {
|
||||
val builder = AlertDialog.Builder(it, R.style.AlertDialogCustom)
|
||||
builder.apply {
|
||||
setPositiveButton(
|
||||
"Login"
|
||||
) { dialog, id ->
|
||||
authenticateAniList()
|
||||
}
|
||||
setNegativeButton(
|
||||
"Cancel"
|
||||
) { dialog, id ->
|
||||
// User cancelled the dialog
|
||||
}
|
||||
}
|
||||
// Set other dialog properties
|
||||
builder.setTitle("AniList token has expired")
|
||||
|
||||
// Create the AlertDialog
|
||||
builder.create()
|
||||
}
|
||||
alertDialog?.show()
|
||||
}*/
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private fun fixName(name: String): String {
|
||||
return name.toLowerCase(Locale.ROOT).replace(" ", "").replace("[^a-zA-Z0-9]".toRegex(), "")
|
||||
}
|
||||
|
||||
private fun searchShows(name: String): GetSearchRoot? {
|
||||
try {
|
||||
val query = """
|
||||
private fun searchShows(name: String): GetSearchRoot? {
|
||||
try {
|
||||
val query = """
|
||||
query (${"$"}id: Int, ${"$"}page: Int, ${"$"}search: String, ${"$"}type: MediaType) {
|
||||
Page (page: ${"$"}page, perPage: 10) {
|
||||
media (id: ${"$"}id, search: ${"$"}search, type: ${"$"}type) {
|
||||
|
@ -216,91 +205,180 @@ class AniListApi(index : Int) : OAuth2Interface.AccountManager(index) {
|
|||
}
|
||||
}
|
||||
"""
|
||||
val data =
|
||||
mapOf("query" to query, "variables" to mapper.writeValueAsString(mapOf("search" to name, "page" to 1, "type" to "ANIME")) )
|
||||
|
||||
val res = post(
|
||||
"https://graphql.anilist.co/",
|
||||
//headers = mapOf(),
|
||||
data = data,//(if (vars == null) mapOf("query" to q) else mapOf("query" to q, "variables" to vars))
|
||||
timeout = 5000 // REASONABLE TIMEOUT
|
||||
).text.replace("\\", "")
|
||||
return res.toKotlinObject()
|
||||
} catch (e: Exception) {
|
||||
logError(e)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// Should use https://gist.github.com/purplepinapples/5dc60f15f2837bf1cea71b089cfeaa0a
|
||||
fun getShowId(malId: String?, name: String, year: Int?): GetSearchMedia? {
|
||||
// Strips these from the name
|
||||
val blackList = listOf(
|
||||
"TV Dubbed",
|
||||
"(Dub)",
|
||||
"Subbed",
|
||||
"(TV)",
|
||||
"(Uncensored)",
|
||||
"(Censored)",
|
||||
"(\\d+)" // year
|
||||
)
|
||||
val blackListRegex =
|
||||
Regex(""" (${blackList.joinToString(separator = "|").replace("(", "\\(").replace(")", "\\)")})""")
|
||||
//println("NAME $name NEW NAME ${name.replace(blackListRegex, "")}")
|
||||
val shows = searchShows(name.replace(blackListRegex, ""))
|
||||
|
||||
shows?.data?.Page?.media?.find {
|
||||
malId ?: "NONE" == it.idMal.toString()
|
||||
}?.let { return it }
|
||||
|
||||
val filtered =
|
||||
shows?.data?.Page?.media?.filter {
|
||||
(
|
||||
it.startDate.year ?: year.toString() == year.toString()
|
||||
|| year == null
|
||||
val data =
|
||||
mapOf(
|
||||
"query" to query,
|
||||
"variables" to mapper.writeValueAsString(
|
||||
mapOf(
|
||||
"search" to name,
|
||||
"page" to 1,
|
||||
"type" to "ANIME"
|
||||
)
|
||||
)
|
||||
}
|
||||
filtered?.forEach {
|
||||
if (fixName(it.title.romaji) == fixName(name)) return it
|
||||
}
|
||||
)
|
||||
|
||||
return filtered?.firstOrNull()
|
||||
}
|
||||
|
||||
private fun Context.postApi(url: String, q: String, cache: Boolean = false): String {
|
||||
return try {
|
||||
if (!checkToken()) {
|
||||
// println("VARS_ " + vars)
|
||||
post(
|
||||
val res = post(
|
||||
"https://graphql.anilist.co/",
|
||||
headers = mapOf(
|
||||
"Authorization" to "Bearer " + getKey(
|
||||
accountId,
|
||||
ANILIST_TOKEN_KEY,
|
||||
""
|
||||
)!!,
|
||||
if (cache) "Cache-Control" to "max-stale=$maxStale" else "Cache-Control" to "no-cache"
|
||||
),
|
||||
cacheTime = 0,
|
||||
data = mapOf("query" to q),//(if (vars == null) mapOf("query" to q) else mapOf("query" to q, "variables" to vars))
|
||||
timeout = 5 // REASONABLE TIMEOUT
|
||||
).text.replace("\\/", "/")
|
||||
} else {
|
||||
""
|
||||
//headers = mapOf(),
|
||||
data = data,//(if (vars == null) mapOf("query" to q) else mapOf("query" to q, "variables" to vars))
|
||||
timeout = 5000 // REASONABLE TIMEOUT
|
||||
).text.replace("\\", "")
|
||||
return res.toKotlinObject()
|
||||
} catch (e: Exception) {
|
||||
logError(e)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// Should use https://gist.github.com/purplepinapples/5dc60f15f2837bf1cea71b089cfeaa0a
|
||||
fun getShowId(malId: String?, name: String, year: Int?): GetSearchMedia? {
|
||||
// Strips these from the name
|
||||
val blackList = listOf(
|
||||
"TV Dubbed",
|
||||
"(Dub)",
|
||||
"Subbed",
|
||||
"(TV)",
|
||||
"(Uncensored)",
|
||||
"(Censored)",
|
||||
"(\\d+)" // year
|
||||
)
|
||||
val blackListRegex =
|
||||
Regex(""" (${blackList.joinToString(separator = "|").replace("(", "\\(").replace(")", "\\)")})""")
|
||||
//println("NAME $name NEW NAME ${name.replace(blackListRegex, "")}")
|
||||
val shows = searchShows(name.replace(blackListRegex, ""))
|
||||
|
||||
shows?.data?.Page?.media?.find {
|
||||
malId ?: "NONE" == it.idMal.toString()
|
||||
}?.let { return it }
|
||||
|
||||
val filtered =
|
||||
shows?.data?.Page?.media?.filter {
|
||||
(
|
||||
it.startDate.year ?: year.toString() == year.toString()
|
||||
|| year == null
|
||||
)
|
||||
}
|
||||
filtered?.forEach {
|
||||
if (fixName(it.title.romaji) == fixName(name)) return it
|
||||
}
|
||||
|
||||
return filtered?.firstOrNull()
|
||||
}
|
||||
|
||||
// Changing names of these will show up in UI
|
||||
enum class AniListStatusType(var value: Int) {
|
||||
Watching(0),
|
||||
Completed(1),
|
||||
Paused(2),
|
||||
Dropped(3),
|
||||
Planning(4),
|
||||
Rewatching(5),
|
||||
None(-1)
|
||||
}
|
||||
|
||||
fun fromIntToAnimeStatus(inp: Int): AniListStatusType {//= AniListStatusType.values().first { it.value == inp }
|
||||
return when (inp) {
|
||||
-1 -> AniListStatusType.None
|
||||
0 -> AniListStatusType.Watching
|
||||
1 -> AniListStatusType.Completed
|
||||
2 -> AniListStatusType.Paused
|
||||
3 -> AniListStatusType.Dropped
|
||||
4 -> AniListStatusType.Planning
|
||||
5 -> AniListStatusType.Rewatching
|
||||
else -> AniListStatusType.None
|
||||
}
|
||||
}
|
||||
|
||||
fun convertAnilistStringToStatus(string: String): AniListStatusType {
|
||||
return fromIntToAnimeStatus(aniListStatusString.indexOf(string))
|
||||
}
|
||||
|
||||
|
||||
private fun getSeason(id: Int): SeasonResponse? {
|
||||
val q: String = """
|
||||
query (${'$'}id: Int = $id) {
|
||||
Media (id: ${'$'}id, type: ANIME) {
|
||||
id
|
||||
idMal
|
||||
relations {
|
||||
edges {
|
||||
id
|
||||
relationType(version: 2)
|
||||
node {
|
||||
id
|
||||
format
|
||||
nextAiringEpisode {
|
||||
timeUntilAiring
|
||||
episode
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
nextAiringEpisode {
|
||||
timeUntilAiring
|
||||
episode
|
||||
}
|
||||
format
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
val data = post(
|
||||
"https://graphql.anilist.co",
|
||||
data = mapOf("query" to q),
|
||||
cacheTime = 0,
|
||||
).text
|
||||
if (data == "") return null
|
||||
return try {
|
||||
mapper.readValue(data)
|
||||
} catch (e: Exception) {
|
||||
logError(e)
|
||||
null
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logError(e)
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
data class MediaRecommendation(
|
||||
@JsonProperty("id") val id: Int,
|
||||
@JsonProperty("title") val title: Title,
|
||||
@JsonProperty("idMal") val idMal: Int?,
|
||||
@JsonProperty("coverImage") val coverImage: CoverImage,
|
||||
@JsonProperty("averageScore") val averageScore: Int?
|
||||
)
|
||||
fun Context.initGetUser() {
|
||||
if (getKey<String>(accountId, ANILIST_TOKEN_KEY, null) == null) return
|
||||
ioSafe {
|
||||
getUser()
|
||||
}
|
||||
}
|
||||
|
||||
private fun Context.checkToken(): Boolean {
|
||||
if (unixTime > getKey(
|
||||
accountId,
|
||||
ANILIST_UNIXTIME_KEY, 0L
|
||||
)!!
|
||||
) {
|
||||
/*getCurrentActivity()?.runOnUiThread {
|
||||
val alertDialog: AlertDialog? = activity?.let {
|
||||
val builder = AlertDialog.Builder(it, R.style.AlertDialogCustom)
|
||||
builder.apply {
|
||||
setPositiveButton(
|
||||
"Login"
|
||||
) { dialog, id ->
|
||||
authenticateAniList()
|
||||
}
|
||||
setNegativeButton(
|
||||
"Cancel"
|
||||
) { dialog, id ->
|
||||
// User cancelled the dialog
|
||||
}
|
||||
}
|
||||
// Set other dialog properties
|
||||
builder.setTitle("AniList token has expired")
|
||||
|
||||
// Create the AlertDialog
|
||||
builder.create()
|
||||
}
|
||||
alertDialog?.show()
|
||||
}*/
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.getDataAboutId(id: Int): AniListTitleHolder? {
|
||||
val q =
|
||||
|
@ -361,6 +439,41 @@ class AniListApi(index : Int) : OAuth2Interface.AccountManager(index) {
|
|||
}
|
||||
}
|
||||
|
||||
private fun Context.postApi(url: String, q: String, cache: Boolean = false): String {
|
||||
return try {
|
||||
if (!checkToken()) {
|
||||
// println("VARS_ " + vars)
|
||||
post(
|
||||
"https://graphql.anilist.co/",
|
||||
headers = mapOf(
|
||||
"Authorization" to "Bearer " + getKey(
|
||||
accountId,
|
||||
ANILIST_TOKEN_KEY,
|
||||
""
|
||||
)!!,
|
||||
if (cache) "Cache-Control" to "max-stale=$maxStale" else "Cache-Control" to "no-cache"
|
||||
),
|
||||
cacheTime = 0,
|
||||
data = mapOf("query" to q),//(if (vars == null) mapOf("query" to q) else mapOf("query" to q, "variables" to vars))
|
||||
timeout = 5 // REASONABLE TIMEOUT
|
||||
).text.replace("\\/", "/")
|
||||
} else {
|
||||
""
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logError(e)
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
data class MediaRecommendation(
|
||||
@JsonProperty("id") val id: Int,
|
||||
@JsonProperty("title") val title: Title,
|
||||
@JsonProperty("idMal") val idMal: Int?,
|
||||
@JsonProperty("coverImage") val coverImage: CoverImage,
|
||||
@JsonProperty("averageScore") val averageScore: Int?
|
||||
)
|
||||
|
||||
data class FullAnilistList(
|
||||
@JsonProperty("data") val data: Data
|
||||
)
|
||||
|
@ -530,7 +643,7 @@ class AniListApi(index : Int) : OAuth2Interface.AccountManager(index) {
|
|||
return data != ""
|
||||
}
|
||||
|
||||
fun Context.postDataAboutId(id: Int, type: AniListStatusType, score: Int, progress: Int): Boolean {
|
||||
private fun Context.postDataAboutId(id: Int, type: AniListStatusType, score: Int?, progress: Int?): Boolean {
|
||||
try {
|
||||
val q =
|
||||
"""mutation (${'$'}id: Int = $id, ${'$'}status: MediaListStatus = ${
|
||||
|
@ -538,7 +651,7 @@ class AniListApi(index : Int) : OAuth2Interface.AccountManager(index) {
|
|||
0,
|
||||
type.value
|
||||
)]
|
||||
}, ${'$'}scoreRaw: Int = ${score * 10}, ${'$'}progress: Int = $progress) {
|
||||
}, ${if (score != null) "${'$'}scoreRaw: Int = ${score * 10}" else ""} , ${if (progress != null) "${'$'}progress: Int = $progress" else ""}) {
|
||||
SaveMediaListEntry (mediaId: ${'$'}id, status: ${'$'}status, scoreRaw: ${'$'}scoreRaw, progress: ${'$'}progress) {
|
||||
id
|
||||
status
|
||||
|
@ -597,49 +710,6 @@ class AniListApi(index : Int) : OAuth2Interface.AccountManager(index) {
|
|||
}
|
||||
}
|
||||
|
||||
private fun getSeason(id: Int): SeasonResponse? {
|
||||
val q: String = """
|
||||
query (${'$'}id: Int = $id) {
|
||||
Media (id: ${'$'}id, type: ANIME) {
|
||||
id
|
||||
idMal
|
||||
relations {
|
||||
edges {
|
||||
id
|
||||
relationType(version: 2)
|
||||
node {
|
||||
id
|
||||
format
|
||||
nextAiringEpisode {
|
||||
timeUntilAiring
|
||||
episode
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
nextAiringEpisode {
|
||||
timeUntilAiring
|
||||
episode
|
||||
}
|
||||
format
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
val data = post(
|
||||
"https://graphql.anilist.co",
|
||||
data = mapOf("query" to q),
|
||||
cacheTime = 0,
|
||||
).text
|
||||
if (data == "") return null
|
||||
return try {
|
||||
mapper.readValue(data)
|
||||
} catch (e: Exception) {
|
||||
logError(e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun getAllSeasons(id: Int): List<SeasonResponse?> {
|
||||
val seasons = mutableListOf<SeasonResponse?>()
|
||||
fun getSeasonRecursive(id: Int) {
|
||||
|
@ -662,27 +732,6 @@ class AniListApi(index : Int) : OAuth2Interface.AccountManager(index) {
|
|||
return seasons.toList()
|
||||
}
|
||||
|
||||
fun secondsToReadable(seconds: Int, completedValue: String): String {
|
||||
var secondsLong = seconds.toLong()
|
||||
val days = TimeUnit.SECONDS
|
||||
.toDays(secondsLong)
|
||||
secondsLong -= TimeUnit.DAYS.toSeconds(days)
|
||||
|
||||
val hours = TimeUnit.SECONDS
|
||||
.toHours(secondsLong)
|
||||
secondsLong -= TimeUnit.HOURS.toSeconds(hours)
|
||||
|
||||
val minutes = TimeUnit.SECONDS
|
||||
.toMinutes(secondsLong)
|
||||
secondsLong -= TimeUnit.MINUTES.toSeconds(minutes)
|
||||
if (minutes < 0) {
|
||||
return completedValue
|
||||
}
|
||||
//println("$days $hours $minutes")
|
||||
return "${if (days != 0L) "$days" + "d " else ""}${if (hours != 0L) "$hours" + "h " else ""}${minutes}m"
|
||||
}
|
||||
|
||||
|
||||
data class SeasonResponse(
|
||||
@JsonProperty("data") val data: SeasonData,
|
||||
)
|
||||
|
@ -725,9 +774,9 @@ class AniListApi(index : Int) : OAuth2Interface.AccountManager(index) {
|
|||
data class SeasonNode(
|
||||
@JsonProperty("id") val id: Int,
|
||||
@JsonProperty("format") val format: String?,
|
||||
@JsonProperty("title") val title: AniListApi.Title,
|
||||
@JsonProperty("title") val title: Title,
|
||||
@JsonProperty("idMal") val idMal: Int?,
|
||||
@JsonProperty("coverImage") val coverImage: AniListApi.CoverImage,
|
||||
@JsonProperty("coverImage") val coverImage: CoverImage,
|
||||
@JsonProperty("averageScore") val averageScore: Int?
|
||||
// @JsonProperty("nextAiringEpisode") val nextAiringEpisode: SeasonNextAiringEpisode?,
|
||||
)
|
|
@ -1,9 +1,10 @@
|
|||
package com.lagradost.cloudstream3.syncproviders
|
||||
package com.lagradost.cloudstream3.syncproviders.providers
|
||||
|
||||
import android.content.Context
|
||||
import com.lagradost.cloudstream3.syncproviders.OAuth2API
|
||||
|
||||
//TODO dropbox sync
|
||||
class Dropbox : OAuth2Interface {
|
||||
class Dropbox : OAuth2API {
|
||||
override val name: String
|
||||
get() = "Dropbox"
|
||||
override val key: String
|
||||
|
@ -23,7 +24,7 @@ class Dropbox : OAuth2Interface {
|
|||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun loginInfo(context: Context): OAuth2Interface.LoginInfo? {
|
||||
override fun loginInfo(context: Context): OAuth2API.LoginInfo? {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package com.lagradost.cloudstream3.syncproviders
|
||||
package com.lagradost.cloudstream3.syncproviders.providers
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Base64
|
||||
|
@ -7,14 +7,18 @@ import com.fasterxml.jackson.databind.DeserializationFeature
|
|||
import com.fasterxml.jackson.databind.json.JsonMapper
|
||||
import com.fasterxml.jackson.module.kotlin.KotlinModule
|
||||
import com.fasterxml.jackson.module.kotlin.readValue
|
||||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import com.lagradost.cloudstream3.network.get
|
||||
import com.lagradost.cloudstream3.network.post
|
||||
import com.lagradost.cloudstream3.network.put
|
||||
import com.lagradost.cloudstream3.network.text
|
||||
import com.lagradost.cloudstream3.syncproviders.OAuth2Interface.Companion.appString
|
||||
import com.lagradost.cloudstream3.syncproviders.OAuth2Interface.Companion.secondsToReadable
|
||||
import com.lagradost.cloudstream3.syncproviders.OAuth2Interface.Companion.unixTime
|
||||
import com.lagradost.cloudstream3.syncproviders.AccountManager
|
||||
import com.lagradost.cloudstream3.syncproviders.OAuth2API
|
||||
import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.appString
|
||||
import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.secondsToReadable
|
||||
import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.unixTime
|
||||
import com.lagradost.cloudstream3.syncproviders.SyncAPI
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.openBrowser
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.splitQuery
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||
|
@ -27,7 +31,10 @@ import java.text.ParseException
|
|||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
class MALApi(index : Int) : OAuth2Interface.AccountManager(index) {
|
||||
/** max 100 via https://myanimelist.net/apiconfig/references/api/v2#tag/anime */
|
||||
const val MAL_MAX_SEARCH_LIMIT = 25
|
||||
|
||||
class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
||||
override val name: String
|
||||
get() = "MAL"
|
||||
override val key: String
|
||||
|
@ -36,19 +43,70 @@ class MALApi(index : Int) : OAuth2Interface.AccountManager(index) {
|
|||
get() = "mallogin"
|
||||
override val idPrefix: String
|
||||
get() = "mal"
|
||||
override val mainUrl: String
|
||||
get() = "https://myanimelist.net"
|
||||
override val icon: Int
|
||||
get() = R.drawable.mal_logo
|
||||
|
||||
override fun logOut(context: Context) {
|
||||
context.removeAccountKeys()
|
||||
}
|
||||
|
||||
override fun loginInfo(context: Context): OAuth2Interface.LoginInfo? {
|
||||
override fun loginInfo(context: Context): OAuth2API.LoginInfo? {
|
||||
//context.getMalUser(true)?
|
||||
context.getKey<MalUser>(accountId, MAL_USER_KEY)?.let { user ->
|
||||
return OAuth2Interface.LoginInfo(profilePicture = user.picture, name = user.name, accountIndex = accountIndex)
|
||||
return OAuth2API.LoginInfo(profilePicture = user.picture, name = user.name, accountIndex = accountIndex)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun search(context: Context, name: String): List<SyncAPI.SyncSearchResult> {
|
||||
val url = "https://api.myanimelist.net/v2/anime?q=$name&limit=$MAL_MAX_SEARCH_LIMIT"
|
||||
val res = get(
|
||||
url, headers = mapOf(
|
||||
"Authorization" to "Bearer " + context.getKey<String>(
|
||||
accountId,
|
||||
MAL_TOKEN_KEY
|
||||
)!!,
|
||||
), cacheTime = 0
|
||||
).text
|
||||
return mapper.readValue<MalSearch>(res).data.map {
|
||||
SyncAPI.SyncSearchResult(
|
||||
it.title,
|
||||
this.name,
|
||||
it.id.toString(),
|
||||
"$mainUrl/anime/${it.id}/",
|
||||
it.main_picture?.large ?: it.main_picture?.medium
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun score(context: Context, id: String, status : SyncAPI.SyncStatus): Boolean {
|
||||
return context.setScoreRequest(
|
||||
id.toIntOrNull() ?: return false,
|
||||
fromIntToAnimeStatus(status.status),
|
||||
status.score,
|
||||
status.watchedEpisodes
|
||||
)
|
||||
}
|
||||
|
||||
override fun getResult(context: Context, id: String): SyncAPI.SyncResult? {
|
||||
val internalId = id.toIntOrNull() ?: return null
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun getStatus(context: Context, id: String): SyncAPI.SyncStatus? {
|
||||
val internalId = id.toIntOrNull() ?: return null
|
||||
|
||||
val data = context.getDataAboutMalId(internalId)?.my_list_status ?: return null
|
||||
return SyncAPI.SyncStatus(
|
||||
score = data.score,
|
||||
status = malStatusAsString.indexOf(data.status),
|
||||
isFavorite = null,
|
||||
watchedEpisodes = data.num_episodes_watched,
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val malStatusAsString = arrayOf("watching", "completed", "on_hold", "dropped", "plan_to_watch")
|
||||
|
||||
|
@ -60,7 +118,7 @@ class MALApi(index : Int) : OAuth2Interface.AccountManager(index) {
|
|||
const val MAL_TOKEN_KEY: String = "mal_token" // anilist token for api
|
||||
}
|
||||
|
||||
override fun handleRedirect(context: Context,url: String) {
|
||||
override fun handleRedirect(context: Context, url: String) {
|
||||
try {
|
||||
val sanitizer =
|
||||
splitQuery(URL(url.replace(appString, "https").replace("/#", "?"))) // FIX ERROR
|
||||
|
@ -293,7 +351,7 @@ class MALApi(index : Int) : OAuth2Interface.AccountManager(index) {
|
|||
}
|
||||
}
|
||||
|
||||
fun Context.getDataAboutMalId(id: Int): MalAnime? {
|
||||
private fun Context.getDataAboutMalId(id: Int): MalAnime? {
|
||||
return try {
|
||||
// https://myanimelist.net/apiconfig/references/api/v2#operation/anime_anime_id_get
|
||||
val url = "https://api.myanimelist.net/v2/anime/$id?fields=id,title,num_episodes,my_list_status"
|
||||
|
@ -419,7 +477,7 @@ class MALApi(index : Int) : OAuth2Interface.AccountManager(index) {
|
|||
None(-1)
|
||||
}
|
||||
|
||||
fun fromIntToAnimeStatus(inp: Int): MalStatusType {//= AniListStatusType.values().first { it.value == inp }
|
||||
private fun fromIntToAnimeStatus(inp: Int): MalStatusType {//= AniListStatusType.values().first { it.value == inp }
|
||||
return when (inp) {
|
||||
-1 -> MalStatusType.None
|
||||
0 -> MalStatusType.Watching
|
||||
|
@ -534,12 +592,23 @@ class MALApi(index : Int) : OAuth2Interface.AccountManager(index) {
|
|||
@JsonProperty("picture") val picture: String,
|
||||
)
|
||||
|
||||
data class MalMainPicture(
|
||||
@JsonProperty("large") val large: String?,
|
||||
@JsonProperty("medium") val medium: String?,
|
||||
)
|
||||
|
||||
// Used for getDataAboutId()
|
||||
data class MalAnime(
|
||||
@JsonProperty("id") val id: Int,
|
||||
@JsonProperty("title") val title: String,
|
||||
@JsonProperty("num_episodes") val num_episodes: Int,
|
||||
@JsonProperty("my_list_status") val my_list_status: MalStatus?
|
||||
@JsonProperty("my_list_status") val my_list_status: MalStatus?,
|
||||
@JsonProperty("main_picture") val main_picture: MalMainPicture?,
|
||||
)
|
||||
|
||||
data class MalSearch(
|
||||
@JsonProperty("data") val data: List<MalAnime>,
|
||||
//paging
|
||||
)
|
||||
|
||||
data class MalTitleHolder(
|
|
@ -26,7 +26,7 @@ import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
|
|||
import com.lagradost.cloudstream3.mvvm.Resource
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import com.lagradost.cloudstream3.mvvm.observe
|
||||
import com.lagradost.cloudstream3.syncproviders.OAuth2Interface
|
||||
import com.lagradost.cloudstream3.syncproviders.OAuth2API
|
||||
import com.lagradost.cloudstream3.ui.APIRepository.Companion.noneApi
|
||||
import com.lagradost.cloudstream3.ui.APIRepository.Companion.randomApi
|
||||
import com.lagradost.cloudstream3.ui.AutofitRecyclerView
|
||||
|
@ -430,7 +430,7 @@ class HomeFragment : Fragment() {
|
|||
// nice profile pic on homepage
|
||||
home_profile_picture_holder?.isVisible = false
|
||||
context?.let { ctx ->
|
||||
for (syncApi in OAuth2Interface.OAuth2Apis) {
|
||||
for (syncApi in OAuth2API.OAuth2Apis) {
|
||||
val login = syncApi.loginInfo(ctx)
|
||||
val pic = login?.profilePicture
|
||||
if (pic != null) {
|
||||
|
|
|
@ -8,13 +8,13 @@ import android.widget.TextView
|
|||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.syncproviders.OAuth2Interface
|
||||
import com.lagradost.cloudstream3.syncproviders.OAuth2API
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
||||
|
||||
class AccountClickCallback(val action: Int, val view : View, val card: OAuth2Interface.LoginInfo)
|
||||
class AccountClickCallback(val action: Int, val view : View, val card: OAuth2API.LoginInfo)
|
||||
|
||||
class AccountAdapter(
|
||||
val cardList: List<OAuth2Interface.LoginInfo>,
|
||||
val cardList: List<OAuth2API.LoginInfo>,
|
||||
val layout: Int = R.layout.account_single,
|
||||
private val clickCallback: (AccountClickCallback) -> Unit
|
||||
) :
|
||||
|
@ -48,7 +48,7 @@ class AccountAdapter(
|
|||
private val pfp: ImageView = itemView.findViewById(R.id.account_profile_picture)!!
|
||||
private val accountName: TextView = itemView.findViewById(R.id.account_name)!!
|
||||
|
||||
fun bind(card: OAuth2Interface.LoginInfo) {
|
||||
fun bind(card: OAuth2API.LoginInfo) {
|
||||
// just in case name is null account index will show, should never happened
|
||||
accountName.text = card.name ?: "%s %d".format(accountName.context.getString(R.string.account), card.accountIndex)
|
||||
if(card.profilePicture.isNullOrEmpty()) {
|
||||
|
|
|
@ -30,9 +30,10 @@ import com.lagradost.cloudstream3.MainActivity.Companion.showToast
|
|||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import com.lagradost.cloudstream3.network.initRequestClient
|
||||
import com.lagradost.cloudstream3.syncproviders.OAuth2Interface
|
||||
import com.lagradost.cloudstream3.syncproviders.OAuth2Interface.Companion.aniListApi
|
||||
import com.lagradost.cloudstream3.syncproviders.OAuth2Interface.Companion.malApi
|
||||
import com.lagradost.cloudstream3.syncproviders.AccountManager
|
||||
import com.lagradost.cloudstream3.syncproviders.OAuth2API
|
||||
import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.aniListApi
|
||||
import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.malApi
|
||||
import com.lagradost.cloudstream3.ui.APIRepository
|
||||
import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment
|
||||
import com.lagradost.cloudstream3.utils.AppUtils
|
||||
|
@ -120,7 +121,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
|||
Triple("🇹🇷", "Turkish", "tr")
|
||||
).sortedBy { it.second } //ye, we go alphabetical, so ppl don't put their lang on top
|
||||
|
||||
private fun showAccountSwitch(context: Context, api: OAuth2Interface.AccountManager) {
|
||||
private fun showAccountSwitch(context: Context, api: AccountManager) {
|
||||
val builder =
|
||||
AlertDialog.Builder(context, R.style.AlertDialogCustom).setView(R.layout.account_switch)
|
||||
val dialog = builder.show()
|
||||
|
@ -132,7 +133,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
|||
|
||||
val ogIndex = api.accountIndex
|
||||
|
||||
val items = ArrayList<OAuth2Interface.LoginInfo>()
|
||||
val items = ArrayList<OAuth2API.LoginInfo>()
|
||||
|
||||
for (index in accounts) {
|
||||
api.accountIndex = index
|
||||
|
@ -150,7 +151,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
|||
list?.adapter = adapter
|
||||
}
|
||||
|
||||
private fun showLoginInfo(context: Context, api: OAuth2Interface.AccountManager, info: OAuth2Interface.LoginInfo) {
|
||||
private fun showLoginInfo(context: Context, api: AccountManager, info: OAuth2API.LoginInfo) {
|
||||
val builder =
|
||||
AlertDialog.Builder(context, R.style.AlertDialogCustom).setView(R.layout.account_managment)
|
||||
val dialog = builder.show()
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
package com.lagradost.cloudstream3.utils
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.fasterxml.jackson.module.kotlin.readValue
|
||||
import com.lagradost.cloudstream3.mapper
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import com.lagradost.cloudstream3.network.get
|
||||
import com.lagradost.cloudstream3.network.text
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
object SyncUtil {
|
||||
/** first. Mal, second. Anilist,
|
||||
* valid sites are: Gogoanime, Twistmoe and 9anime*/
|
||||
fun getIdsFromSlug(slug: String, site : String = "Gogoanime"): Pair<String?, String?>? {
|
||||
try {
|
||||
//Gogoanime, Twistmoe and 9anime
|
||||
val url =
|
||||
"https://raw.githubusercontent.com/MALSync/MAL-Sync-Backup/master/data/pages/$site/$slug.json"
|
||||
val response = get(url, cacheTime = 1, cacheUnit = TimeUnit.DAYS).text
|
||||
val mapped = mapper.readValue<MalSyncPage?>(response)
|
||||
|
||||
val overrideMal = mapped?.malId ?: mapped?.Mal?.id ?: mapped?.Anilist?.malId
|
||||
val overrideAnilist = mapped?.aniId ?: mapped?.Anilist?.id
|
||||
|
||||
if (overrideMal != null) {
|
||||
return overrideMal.toString() to overrideAnilist?.toString()
|
||||
}
|
||||
return null
|
||||
} catch (e: Exception) {
|
||||
logError(e)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
data class MalSyncPage(
|
||||
@JsonProperty("identifier") val identifier: String?,
|
||||
@JsonProperty("type") val type: String?,
|
||||
@JsonProperty("page") val page: String?,
|
||||
@JsonProperty("title") val title: String?,
|
||||
@JsonProperty("url") val url: String?,
|
||||
@JsonProperty("image") val image: String?,
|
||||
@JsonProperty("hentai") val hentai: Boolean?,
|
||||
@JsonProperty("sticky") val sticky: Boolean?,
|
||||
@JsonProperty("active") val active: Boolean?,
|
||||
@JsonProperty("actor") val actor: String?,
|
||||
@JsonProperty("malId") val malId: Int?,
|
||||
@JsonProperty("aniId") val aniId: Int?,
|
||||
@JsonProperty("createdAt") val createdAt: String?,
|
||||
@JsonProperty("updatedAt") val updatedAt: String?,
|
||||
@JsonProperty("deletedAt") val deletedAt: String?,
|
||||
@JsonProperty("Mal") val Mal: Mal?,
|
||||
@JsonProperty("Anilist") val Anilist: Anilist?,
|
||||
@JsonProperty("malUrl") val malUrl: String?
|
||||
)
|
||||
|
||||
data class Anilist(
|
||||
// @JsonProperty("altTitle") val altTitle: List<String>?,
|
||||
// @JsonProperty("externalLinks") val externalLinks: List<String>?,
|
||||
@JsonProperty("id") val id: Int?,
|
||||
@JsonProperty("malId") val malId: Int?,
|
||||
@JsonProperty("type") val type: String?,
|
||||
@JsonProperty("title") val title: String?,
|
||||
@JsonProperty("url") val url: String?,
|
||||
@JsonProperty("image") val image: String?,
|
||||
@JsonProperty("category") val category: String?,
|
||||
@JsonProperty("hentai") val hentai: Boolean?,
|
||||
@JsonProperty("createdAt") val createdAt: String?,
|
||||
@JsonProperty("updatedAt") val updatedAt: String?,
|
||||
@JsonProperty("deletedAt") val deletedAt: String?
|
||||
)
|
||||
|
||||
data class Mal(
|
||||
// @JsonProperty("altTitle") val altTitle: List<String>?,
|
||||
@JsonProperty("id") val id: Int?,
|
||||
@JsonProperty("type") val type: String?,
|
||||
@JsonProperty("title") val title: String?,
|
||||
@JsonProperty("url") val url: String?,
|
||||
@JsonProperty("image") val image: String?,
|
||||
@JsonProperty("category") val category: String?,
|
||||
@JsonProperty("hentai") val hentai: Boolean?,
|
||||
@JsonProperty("createdAt") val createdAt: String?,
|
||||
@JsonProperty("updatedAt") val updatedAt: String?,
|
||||
@JsonProperty("deletedAt") val deletedAt: String?
|
||||
)
|
||||
|
||||
}
|
|
@ -67,10 +67,15 @@ object UIHelper {
|
|||
|
||||
fun Fragment.hideKeyboard() {
|
||||
activity?.window?.decorView?.clearFocus()
|
||||
view.let {
|
||||
if (it != null) {
|
||||
hideKeyboard(it)
|
||||
}
|
||||
view?.let {
|
||||
hideKeyboard(it)
|
||||
}
|
||||
}
|
||||
|
||||
fun Activity.hideKeyboard() {
|
||||
window?.decorView?.clearFocus()
|
||||
this.findViewById<View>(android.R.id.content)?.rootView?.let {
|
||||
hideKeyboard(it)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -84,6 +84,7 @@
|
|||
<string name="type_dropped">Dropped</string>
|
||||
<string name="type_plan_to_watch">Plan to Watch</string>
|
||||
<string name="type_none">None</string>
|
||||
<string name="type_re_watching">ReWatching</string>
|
||||
|
||||
<string name="play_movie_button">Play Movie</string>
|
||||
<string name="play_torrent_button">Stream Torrent</string>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#3DDC84</color>
|
||||
<!--#3DDC84-->
|
||||
<color name="ic_launcher_background">@color/colorPrimaryDark</color>
|
||||
</resources>
|
Loading…
Reference in a new issue