mirror of
				https://github.com/recloudstream/cloudstream.git
				synced 2024-08-15 01:53:11 +00:00 
			
		
		
		
	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"?> | <?xml version="1.0" encoding="utf-8"?> | ||||||
| <resources> | <resources> | ||||||
|     <color name="ic_launcher_background">#6A32D3</color> |     <!--#6A32D3--> | ||||||
|  |     <color name="ic_launcher_background">@color/colorPrimaryPurple</color> | ||||||
| </resources> | </resources> | ||||||
|  | @ -54,7 +54,7 @@ object APIHolder { | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|     val restrictedApis = arrayListOf( |     val restrictedApis = arrayListOf( | ||||||
|         TrailersToProvider(), // be aware that this is fuckery |         // TrailersToProvider(), // be aware that this is fuckery | ||||||
|         // NyaaProvider(), // torrents in cs3 is wack |         // NyaaProvider(), // torrents in cs3 is wack | ||||||
|         // ThenosProvider(), // ddos protection and wacked links |         // ThenosProvider(), // ddos protection and wacked links | ||||||
|         AsiaFlixProvider(), |         AsiaFlixProvider(), | ||||||
|  | @ -314,7 +314,7 @@ interface SearchResponse { | ||||||
|     val name: String |     val name: String | ||||||
|     val url: String |     val url: String | ||||||
|     val apiName: String |     val apiName: String | ||||||
|     val type: TvType |     val type: TvType? | ||||||
|     val posterUrl: String? |     val posterUrl: String? | ||||||
|     val id: Int? |     val id: Int? | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -28,9 +28,9 @@ import com.lagradost.cloudstream3.APIHolder.restrictedApis | ||||||
| import com.lagradost.cloudstream3.mvvm.logError | import com.lagradost.cloudstream3.mvvm.logError | ||||||
| import com.lagradost.cloudstream3.network.initRequestClient | import com.lagradost.cloudstream3.network.initRequestClient | ||||||
| import com.lagradost.cloudstream3.receivers.VideoDownloadRestartReceiver | import com.lagradost.cloudstream3.receivers.VideoDownloadRestartReceiver | ||||||
| import com.lagradost.cloudstream3.syncproviders.OAuth2Interface.Companion.OAuth2Apis | import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.OAuth2Apis | ||||||
| import com.lagradost.cloudstream3.syncproviders.OAuth2Interface.Companion.OAuth2accountApis | import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.OAuth2accountApis | ||||||
| import com.lagradost.cloudstream3.syncproviders.OAuth2Interface.Companion.appString | import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.appString | ||||||
| import com.lagradost.cloudstream3.ui.APIRepository | import com.lagradost.cloudstream3.ui.APIRepository | ||||||
| import com.lagradost.cloudstream3.ui.download.DOWNLOAD_NAVIGATE_TO | import com.lagradost.cloudstream3.ui.download.DOWNLOAD_NAVIGATE_TO | ||||||
| import com.lagradost.cloudstream3.ui.player.PlayerEventType | 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.checkWrite | ||||||
| import com.lagradost.cloudstream3.utils.UIHelper.getResourceColor | import com.lagradost.cloudstream3.utils.UIHelper.getResourceColor | ||||||
| import com.lagradost.cloudstream3.utils.UIHelper.hasPIPPermission | 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.navigate | ||||||
| import com.lagradost.cloudstream3.utils.UIHelper.requestRW | import com.lagradost.cloudstream3.utils.UIHelper.requestRW | ||||||
| import com.lagradost.cloudstream3.utils.UIHelper.shouldShowPIPMode | import com.lagradost.cloudstream3.utils.UIHelper.shouldShowPIPMode | ||||||
|  | @ -447,6 +448,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { | ||||||
|         nav_view.setupWithNavController(navController) |         nav_view.setupWithNavController(navController) | ||||||
| 
 | 
 | ||||||
|         navController.addOnDestinationChangedListener { _, destination, _ -> |         navController.addOnDestinationChangedListener { _, destination, _ -> | ||||||
|  |             this.hideKeyboard() | ||||||
|             // nav_view.hideKeyboard() |             // nav_view.hideKeyboard() | ||||||
|             /*if (destination.id != R.id.navigation_player) { |             /*if (destination.id != R.id.navigation_player) { | ||||||
|                 requestedOrientation = if (settingsManager?.getBoolean("force_landscape", false) == true) { |                 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 android.content.Context | ||||||
| import com.fasterxml.jackson.annotation.JsonProperty | 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.databind.json.JsonMapper | ||||||
| import com.fasterxml.jackson.module.kotlin.KotlinModule | import com.fasterxml.jackson.module.kotlin.KotlinModule | ||||||
| import com.fasterxml.jackson.module.kotlin.readValue | import com.fasterxml.jackson.module.kotlin.readValue | ||||||
|  | import com.lagradost.cloudstream3.R | ||||||
| import com.lagradost.cloudstream3.mvvm.logError | import com.lagradost.cloudstream3.mvvm.logError | ||||||
| import com.lagradost.cloudstream3.network.post | import com.lagradost.cloudstream3.network.post | ||||||
| import com.lagradost.cloudstream3.network.text | import com.lagradost.cloudstream3.network.text | ||||||
| import com.lagradost.cloudstream3.syncproviders.OAuth2Interface.Companion.appString | import com.lagradost.cloudstream3.syncproviders.AccountManager | ||||||
| import com.lagradost.cloudstream3.syncproviders.OAuth2Interface.Companion.maxStale | import com.lagradost.cloudstream3.syncproviders.OAuth2API | ||||||
| import com.lagradost.cloudstream3.syncproviders.OAuth2Interface.Companion.unixTime | 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.openBrowser | ||||||
| import com.lagradost.cloudstream3.utils.AppUtils.splitQuery | import com.lagradost.cloudstream3.utils.AppUtils.splitQuery | ||||||
| import com.lagradost.cloudstream3.utils.Coroutines.ioSafe | 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 com.lagradost.cloudstream3.utils.DataStore.toKotlinObject | ||||||
| import java.net.URL | import java.net.URL | ||||||
| import java.util.* | 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 |     override val name: String | ||||||
|         get() = "AniList" |         get() = "AniList" | ||||||
|     override val key: String |     override val key: String | ||||||
|  | @ -32,11 +35,19 @@ class AniListApi(index : Int) : OAuth2Interface.AccountManager(index) { | ||||||
|         get() = "anilistlogin" |         get() = "anilistlogin" | ||||||
|     override val idPrefix: String |     override val idPrefix: String | ||||||
|         get() = "anilist" |         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.getUser(true)?. | ||||||
|         context.getKey<AniListUser>(accountId, ANILIST_USER_KEY)?.let { user -> |         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 |         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 { |     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") |         private val aniListStatusString = arrayOf("CURRENT", "COMPLETED", "PAUSED", "DROPPED", "PLANNING", "REPEATING") | ||||||
| 
 | 
 | ||||||
|         const val ANILIST_UNIXTIME_KEY: String = "anilist_unixtime" // When token expires |         const val ANILIST_UNIXTIME_KEY: String = "anilist_unixtime" // When token expires | ||||||
|  | @ -79,81 +143,6 @@ class AniListApi(index : Int) : OAuth2Interface.AccountManager(index) { | ||||||
|         const val ANILIST_USER_KEY: String = "anilist_user" // user data like profile |         const val ANILIST_USER_KEY: String = "anilist_user" // user data like profile | ||||||
|         const val ANILIST_CACHED_LIST: String = "anilist_cached_list" |         const val ANILIST_CACHED_LIST: String = "anilist_cached_list" | ||||||
|         const val ANILIST_SHOULD_UPDATE_LIST: String = "anilist_should_update_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 |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     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 { |         private fun fixName(name: String): String { | ||||||
|             return name.toLowerCase(Locale.ROOT).replace(" ", "").replace("[^a-zA-Z0-9]".toRegex(), "") |             return name.toLowerCase(Locale.ROOT).replace(" ", "").replace("[^a-zA-Z0-9]".toRegex(), "") | ||||||
|  | @ -217,7 +206,16 @@ class AniListApi(index : Int) : OAuth2Interface.AccountManager(index) { | ||||||
|                 } |                 } | ||||||
|                 """ |                 """ | ||||||
|                 val data = |                 val data = | ||||||
|                 mapOf("query" to query, "variables" to mapper.writeValueAsString(mapOf("search" to name, "page" to 1, "type" to "ANIME")) ) |                     mapOf( | ||||||
|  |                         "query" to query, | ||||||
|  |                         "variables" to mapper.writeValueAsString( | ||||||
|  |                             mapOf( | ||||||
|  |                                 "search" to name, | ||||||
|  |                                 "page" to 1, | ||||||
|  |                                 "type" to "ANIME" | ||||||
|  |                             ) | ||||||
|  |                         ) | ||||||
|  |                     ) | ||||||
| 
 | 
 | ||||||
|                 val res = post( |                 val res = post( | ||||||
|                     "https://graphql.anilist.co/", |                     "https://graphql.anilist.co/", | ||||||
|  | @ -267,40 +265,120 @@ class AniListApi(index : Int) : OAuth2Interface.AccountManager(index) { | ||||||
|             return filtered?.firstOrNull() |             return filtered?.firstOrNull() | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|     private fun Context.postApi(url: String, q: String, cache: Boolean = false): String { |         // Changing names of these will show up in UI | ||||||
|         return try { |         enum class AniListStatusType(var value: Int) { | ||||||
|             if (!checkToken()) { |             Watching(0), | ||||||
|                 // println("VARS_ " + vars) |             Completed(1), | ||||||
|                 post( |             Paused(2), | ||||||
|                     "https://graphql.anilist.co/", |             Dropped(3), | ||||||
|                     headers = mapOf( |             Planning(4), | ||||||
|                         "Authorization" to "Bearer " + getKey( |             Rewatching(5), | ||||||
|                             accountId, |             None(-1) | ||||||
|                             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) |         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 | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|     data class MediaRecommendation( |         fun convertAnilistStringToStatus(string: String): AniListStatusType { | ||||||
|         @JsonProperty("id") val id: Int, |             return fromIntToAnimeStatus(aniListStatusString.indexOf(string)) | ||||||
|         @JsonProperty("title") val title: Title, |         } | ||||||
|         @JsonProperty("idMal") val idMal: Int?, | 
 | ||||||
|         @JsonProperty("coverImage") val coverImage: CoverImage, | 
 | ||||||
|         @JsonProperty("averageScore") val averageScore: Int? |         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 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? { |     fun Context.getDataAboutId(id: Int): AniListTitleHolder? { | ||||||
|         val q = |         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( |     data class FullAnilistList( | ||||||
|         @JsonProperty("data") val data: Data |         @JsonProperty("data") val data: Data | ||||||
|     ) |     ) | ||||||
|  | @ -530,7 +643,7 @@ class AniListApi(index : Int) : OAuth2Interface.AccountManager(index) { | ||||||
|         return data != "" |         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 { |         try { | ||||||
|             val q = |             val q = | ||||||
|                 """mutation (${'$'}id: Int = $id, ${'$'}status: MediaListStatus = ${ |                 """mutation (${'$'}id: Int = $id, ${'$'}status: MediaListStatus = ${ | ||||||
|  | @ -538,7 +651,7 @@ class AniListApi(index : Int) : OAuth2Interface.AccountManager(index) { | ||||||
|                         0, |                         0, | ||||||
|                         type.value |                         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) { |                 SaveMediaListEntry (mediaId: ${'$'}id, status: ${'$'}status, scoreRaw: ${'$'}scoreRaw, progress: ${'$'}progress) { | ||||||
|                     id |                     id | ||||||
|                     status |                     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?> { |     fun getAllSeasons(id: Int): List<SeasonResponse?> { | ||||||
|         val seasons = mutableListOf<SeasonResponse?>() |         val seasons = mutableListOf<SeasonResponse?>() | ||||||
|         fun getSeasonRecursive(id: Int) { |         fun getSeasonRecursive(id: Int) { | ||||||
|  | @ -662,27 +732,6 @@ class AniListApi(index : Int) : OAuth2Interface.AccountManager(index) { | ||||||
|         return seasons.toList() |         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( |     data class SeasonResponse( | ||||||
|         @JsonProperty("data") val data: SeasonData, |         @JsonProperty("data") val data: SeasonData, | ||||||
|     ) |     ) | ||||||
|  | @ -725,9 +774,9 @@ class AniListApi(index : Int) : OAuth2Interface.AccountManager(index) { | ||||||
|     data class SeasonNode( |     data class SeasonNode( | ||||||
|         @JsonProperty("id") val id: Int, |         @JsonProperty("id") val id: Int, | ||||||
|         @JsonProperty("format") val format: String?, |         @JsonProperty("format") val format: String?, | ||||||
|         @JsonProperty("title") val title: AniListApi.Title, |         @JsonProperty("title") val title: Title, | ||||||
|         @JsonProperty("idMal") val idMal: Int?, |         @JsonProperty("idMal") val idMal: Int?, | ||||||
|         @JsonProperty("coverImage") val coverImage: AniListApi.CoverImage, |         @JsonProperty("coverImage") val coverImage: CoverImage, | ||||||
|         @JsonProperty("averageScore") val averageScore: Int? |         @JsonProperty("averageScore") val averageScore: Int? | ||||||
| //        @JsonProperty("nextAiringEpisode") val nextAiringEpisode: SeasonNextAiringEpisode?, | //        @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 android.content.Context | ||||||
|  | import com.lagradost.cloudstream3.syncproviders.OAuth2API | ||||||
| 
 | 
 | ||||||
| //TODO dropbox sync | //TODO dropbox sync | ||||||
| class Dropbox : OAuth2Interface { | class Dropbox : OAuth2API { | ||||||
|     override val name: String |     override val name: String | ||||||
|         get() = "Dropbox" |         get() = "Dropbox" | ||||||
|     override val key: String |     override val key: String | ||||||
|  | @ -23,7 +24,7 @@ class Dropbox : OAuth2Interface { | ||||||
|         TODO("Not yet implemented") |         TODO("Not yet implemented") | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override fun loginInfo(context: Context): OAuth2Interface.LoginInfo? { |     override fun loginInfo(context: Context): OAuth2API.LoginInfo? { | ||||||
|         TODO("Not yet implemented") |         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.content.Context | ||||||
| import android.util.Base64 | 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.databind.json.JsonMapper | ||||||
| import com.fasterxml.jackson.module.kotlin.KotlinModule | import com.fasterxml.jackson.module.kotlin.KotlinModule | ||||||
| import com.fasterxml.jackson.module.kotlin.readValue | import com.fasterxml.jackson.module.kotlin.readValue | ||||||
|  | import com.lagradost.cloudstream3.R | ||||||
| import com.lagradost.cloudstream3.mvvm.logError | import com.lagradost.cloudstream3.mvvm.logError | ||||||
| import com.lagradost.cloudstream3.network.get | import com.lagradost.cloudstream3.network.get | ||||||
| import com.lagradost.cloudstream3.network.post | import com.lagradost.cloudstream3.network.post | ||||||
| import com.lagradost.cloudstream3.network.put | import com.lagradost.cloudstream3.network.put | ||||||
| import com.lagradost.cloudstream3.network.text | import com.lagradost.cloudstream3.network.text | ||||||
| import com.lagradost.cloudstream3.syncproviders.OAuth2Interface.Companion.appString | import com.lagradost.cloudstream3.syncproviders.AccountManager | ||||||
| import com.lagradost.cloudstream3.syncproviders.OAuth2Interface.Companion.secondsToReadable | import com.lagradost.cloudstream3.syncproviders.OAuth2API | ||||||
| import com.lagradost.cloudstream3.syncproviders.OAuth2Interface.Companion.unixTime | 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.openBrowser | ||||||
| import com.lagradost.cloudstream3.utils.AppUtils.splitQuery | import com.lagradost.cloudstream3.utils.AppUtils.splitQuery | ||||||
| import com.lagradost.cloudstream3.utils.Coroutines.ioSafe | import com.lagradost.cloudstream3.utils.Coroutines.ioSafe | ||||||
|  | @ -27,7 +31,10 @@ import java.text.ParseException | ||||||
| import java.text.SimpleDateFormat | import java.text.SimpleDateFormat | ||||||
| import java.util.* | 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 |     override val name: String | ||||||
|         get() = "MAL" |         get() = "MAL" | ||||||
|     override val key: String |     override val key: String | ||||||
|  | @ -36,19 +43,70 @@ class MALApi(index : Int) : OAuth2Interface.AccountManager(index) { | ||||||
|         get() = "mallogin" |         get() = "mallogin" | ||||||
|     override val idPrefix: String |     override val idPrefix: String | ||||||
|         get() = "mal" |         get() = "mal" | ||||||
|  |     override val mainUrl: String | ||||||
|  |         get() = "https://myanimelist.net" | ||||||
|  |     override val icon: Int | ||||||
|  |         get() = R.drawable.mal_logo | ||||||
| 
 | 
 | ||||||
|     override fun logOut(context: Context) { |     override fun logOut(context: Context) { | ||||||
|         context.removeAccountKeys() |         context.removeAccountKeys() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override fun loginInfo(context: Context): OAuth2Interface.LoginInfo? { |     override fun loginInfo(context: Context): OAuth2API.LoginInfo? { | ||||||
|         //context.getMalUser(true)? |         //context.getMalUser(true)? | ||||||
|         context.getKey<MalUser>(accountId, MAL_USER_KEY)?.let { user -> |         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 |         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 { |     companion object { | ||||||
|         private val malStatusAsString = arrayOf("watching", "completed", "on_hold", "dropped", "plan_to_watch") |         private val malStatusAsString = arrayOf("watching", "completed", "on_hold", "dropped", "plan_to_watch") | ||||||
| 
 | 
 | ||||||
|  | @ -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 { |         return try { | ||||||
|             // https://myanimelist.net/apiconfig/references/api/v2#operation/anime_anime_id_get |             // 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" |             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) |         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) { |         return when (inp) { | ||||||
|             -1 -> MalStatusType.None |             -1 -> MalStatusType.None | ||||||
|             0 -> MalStatusType.Watching |             0 -> MalStatusType.Watching | ||||||
|  | @ -534,12 +592,23 @@ class MALApi(index : Int) : OAuth2Interface.AccountManager(index) { | ||||||
|         @JsonProperty("picture") val picture: String, |         @JsonProperty("picture") val picture: String, | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|  |     data class MalMainPicture( | ||||||
|  |         @JsonProperty("large") val large: String?, | ||||||
|  |         @JsonProperty("medium") val medium: String?, | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|     // Used for getDataAboutId() |     // Used for getDataAboutId() | ||||||
|     data class MalAnime( |     data class MalAnime( | ||||||
|         @JsonProperty("id") val id: Int, |         @JsonProperty("id") val id: Int, | ||||||
|         @JsonProperty("title") val title: String, |         @JsonProperty("title") val title: String, | ||||||
|         @JsonProperty("num_episodes") val num_episodes: Int, |         @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( |     data class MalTitleHolder( | ||||||
|  | @ -26,7 +26,7 @@ import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull | ||||||
| import com.lagradost.cloudstream3.mvvm.Resource | import com.lagradost.cloudstream3.mvvm.Resource | ||||||
| import com.lagradost.cloudstream3.mvvm.logError | import com.lagradost.cloudstream3.mvvm.logError | ||||||
| import com.lagradost.cloudstream3.mvvm.observe | 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.noneApi | ||||||
| import com.lagradost.cloudstream3.ui.APIRepository.Companion.randomApi | import com.lagradost.cloudstream3.ui.APIRepository.Companion.randomApi | ||||||
| import com.lagradost.cloudstream3.ui.AutofitRecyclerView | import com.lagradost.cloudstream3.ui.AutofitRecyclerView | ||||||
|  | @ -430,7 +430,7 @@ class HomeFragment : Fragment() { | ||||||
|         // nice profile pic on homepage |         // nice profile pic on homepage | ||||||
|         home_profile_picture_holder?.isVisible = false |         home_profile_picture_holder?.isVisible = false | ||||||
|         context?.let { ctx -> |         context?.let { ctx -> | ||||||
|             for (syncApi in OAuth2Interface.OAuth2Apis) { |             for (syncApi in OAuth2API.OAuth2Apis) { | ||||||
|                 val login = syncApi.loginInfo(ctx) |                 val login = syncApi.loginInfo(ctx) | ||||||
|                 val pic = login?.profilePicture |                 val pic = login?.profilePicture | ||||||
|                 if (pic != null) { |                 if (pic != null) { | ||||||
|  |  | ||||||
|  | @ -8,13 +8,13 @@ import android.widget.TextView | ||||||
| import androidx.core.view.isVisible | import androidx.core.view.isVisible | ||||||
| import androidx.recyclerview.widget.RecyclerView | import androidx.recyclerview.widget.RecyclerView | ||||||
| import com.lagradost.cloudstream3.R | import com.lagradost.cloudstream3.R | ||||||
| import com.lagradost.cloudstream3.syncproviders.OAuth2Interface | import com.lagradost.cloudstream3.syncproviders.OAuth2API | ||||||
| import com.lagradost.cloudstream3.utils.UIHelper.setImage | 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( | class AccountAdapter( | ||||||
|     val cardList: List<OAuth2Interface.LoginInfo>, |     val cardList: List<OAuth2API.LoginInfo>, | ||||||
|     val layout: Int = R.layout.account_single, |     val layout: Int = R.layout.account_single, | ||||||
|     private val clickCallback: (AccountClickCallback) -> Unit |     private val clickCallback: (AccountClickCallback) -> Unit | ||||||
| ) : | ) : | ||||||
|  | @ -48,7 +48,7 @@ class AccountAdapter( | ||||||
|         private val pfp: ImageView = itemView.findViewById(R.id.account_profile_picture)!! |         private val pfp: ImageView = itemView.findViewById(R.id.account_profile_picture)!! | ||||||
|         private val accountName: TextView = itemView.findViewById(R.id.account_name)!! |         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 |             // 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) |             accountName.text = card.name ?: "%s %d".format(accountName.context.getString(R.string.account), card.accountIndex) | ||||||
|             if(card.profilePicture.isNullOrEmpty()) { |             if(card.profilePicture.isNullOrEmpty()) { | ||||||
|  |  | ||||||
|  | @ -30,9 +30,10 @@ import com.lagradost.cloudstream3.MainActivity.Companion.showToast | ||||||
| import com.lagradost.cloudstream3.R | import com.lagradost.cloudstream3.R | ||||||
| import com.lagradost.cloudstream3.mvvm.logError | import com.lagradost.cloudstream3.mvvm.logError | ||||||
| import com.lagradost.cloudstream3.network.initRequestClient | import com.lagradost.cloudstream3.network.initRequestClient | ||||||
| import com.lagradost.cloudstream3.syncproviders.OAuth2Interface | import com.lagradost.cloudstream3.syncproviders.AccountManager | ||||||
| import com.lagradost.cloudstream3.syncproviders.OAuth2Interface.Companion.aniListApi | import com.lagradost.cloudstream3.syncproviders.OAuth2API | ||||||
| import com.lagradost.cloudstream3.syncproviders.OAuth2Interface.Companion.malApi | 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.APIRepository | ||||||
| import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment | import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment | ||||||
| import com.lagradost.cloudstream3.utils.AppUtils | import com.lagradost.cloudstream3.utils.AppUtils | ||||||
|  | @ -120,7 +121,7 @@ class SettingsFragment : PreferenceFragmentCompat() { | ||||||
|         Triple("🇹🇷", "Turkish", "tr") |         Triple("🇹🇷", "Turkish", "tr") | ||||||
|     ).sortedBy { it.second } //ye, we go alphabetical, so ppl don't put their lang on top |     ).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 = |         val builder = | ||||||
|             AlertDialog.Builder(context, R.style.AlertDialogCustom).setView(R.layout.account_switch) |             AlertDialog.Builder(context, R.style.AlertDialogCustom).setView(R.layout.account_switch) | ||||||
|         val dialog = builder.show() |         val dialog = builder.show() | ||||||
|  | @ -132,7 +133,7 @@ class SettingsFragment : PreferenceFragmentCompat() { | ||||||
| 
 | 
 | ||||||
|         val ogIndex = api.accountIndex |         val ogIndex = api.accountIndex | ||||||
| 
 | 
 | ||||||
|         val items = ArrayList<OAuth2Interface.LoginInfo>() |         val items = ArrayList<OAuth2API.LoginInfo>() | ||||||
| 
 | 
 | ||||||
|         for (index in accounts) { |         for (index in accounts) { | ||||||
|             api.accountIndex = index |             api.accountIndex = index | ||||||
|  | @ -150,7 +151,7 @@ class SettingsFragment : PreferenceFragmentCompat() { | ||||||
|         list?.adapter = adapter |         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 = |         val builder = | ||||||
|             AlertDialog.Builder(context, R.style.AlertDialogCustom).setView(R.layout.account_managment) |             AlertDialog.Builder(context, R.style.AlertDialogCustom).setView(R.layout.account_managment) | ||||||
|         val dialog = builder.show() |         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,11 +67,16 @@ object UIHelper { | ||||||
| 
 | 
 | ||||||
|     fun Fragment.hideKeyboard() { |     fun Fragment.hideKeyboard() { | ||||||
|         activity?.window?.decorView?.clearFocus() |         activity?.window?.decorView?.clearFocus() | ||||||
|         view.let { |         view?.let { | ||||||
|             if (it != null) { |  | ||||||
|             hideKeyboard(it) |             hideKeyboard(it) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     fun Activity.hideKeyboard() { | ||||||
|  |         window?.decorView?.clearFocus() | ||||||
|  |         this.findViewById<View>(android.R.id.content)?.rootView?.let { | ||||||
|  |             hideKeyboard(it) | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fun Activity?.navigate(@IdRes navigation: Int, arguments: Bundle? = null) { |     fun Activity?.navigate(@IdRes navigation: Int, arguments: Bundle? = null) { | ||||||
|  |  | ||||||
|  | @ -84,6 +84,7 @@ | ||||||
|     <string name="type_dropped">Dropped</string> |     <string name="type_dropped">Dropped</string> | ||||||
|     <string name="type_plan_to_watch">Plan to Watch</string> |     <string name="type_plan_to_watch">Plan to Watch</string> | ||||||
|     <string name="type_none">None</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_movie_button">Play Movie</string> | ||||||
|     <string name="play_torrent_button">Stream Torrent</string> |     <string name="play_torrent_button">Stream Torrent</string> | ||||||
|  |  | ||||||
|  | @ -1,4 +1,5 @@ | ||||||
| <?xml version="1.0" encoding="utf-8"?> | <?xml version="1.0" encoding="utf-8"?> | ||||||
| <resources> | <resources> | ||||||
|     <color name="ic_launcher_background">#3DDC84</color> |     <!--#3DDC84--> | ||||||
|  |     <color name="ic_launcher_background">@color/colorPrimaryDark</color> | ||||||
| </resources> | </resources> | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue