backend fixes for opening from sync urls

This commit is contained in:
Blatzar 2022-12-05 20:49:45 +01:00
parent 05dc032df6
commit 67a1c447ae
11 changed files with 134 additions and 59 deletions

View file

@ -10,15 +10,14 @@ import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.json.JsonMapper
import com.fasterxml.jackson.module.kotlin.KotlinModule
import com.lagradost.cloudstream3.metaproviders.SyncIdName
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.aniListApi
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.malApi
import com.lagradost.cloudstream3.ui.player.SubtitleData
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
import com.lagradost.cloudstream3.utils.*
import com.lagradost.cloudstream3.utils.AppUtils.toJson
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.Qualities
import com.lagradost.cloudstream3.utils.loadExtractor
import okhttp3.Interceptor
import java.text.SimpleDateFormat
import java.util.*
@ -402,6 +401,20 @@ abstract class MainAPI {
open val hasMainPage = false
open val hasQuickSearch = false
/**
* A set of which ids the provider can open with getLoadUrl()
* If the set contains SyncIdName.Imdb then getLoadUrl() can be started with
* an Imdb class which inherits from SyncId.
*
* getLoadUrl() is then used to get page url based on that ID.
*
* Example:
* "tt6723592" -> getLoadUrl(ImdbSyncId("tt6723592")) -> "mainUrl/imdb/tt6723592" -> load("mainUrl/imdb/tt6723592")
*
* This is used to launch pages from personal lists or recommendations using IDs.
**/
open val supportedSyncNames = setOf<SyncIdName>()
open val supportedTypes = setOf(
TvType.Movie,
TvType.TvSeries,
@ -412,7 +425,6 @@ abstract class MainAPI {
open val vpnStatus = VPNStatus.None
open val providerType = ProviderType.DirectProvider
open val mainPage = listOf(MainPageData("", ""))
@WorkerThread
@ -471,6 +483,14 @@ abstract class MainAPI {
open fun getVideoInterceptor(extractorLink: ExtractorLink): Interceptor? {
return null
}
/**
* Get the load() url based on a sync ID like IMDb or MAL.
* Only contains SyncIds based on supportedSyncUrls.
**/
open suspend fun getLoadUrl(name: SyncIdName, id: String): String? {
return null
}
}
/** Might need a different implementation for desktop*/

View file

@ -1,30 +0,0 @@
package com.lagradost.cloudstream3.metaproviders
import com.lagradost.cloudstream3.ErrorLoadingException
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.SyncApis
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.aniListApi
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.malApi
import com.lagradost.cloudstream3.utils.SyncUtil
object SyncRedirector {
val syncApis = SyncApis
suspend fun redirect(url: String, preferredUrl: String): String {
for (api in syncApis) {
if (url.contains(api.mainUrl)) {
val otherApi = when (api.name) {
aniListApi.name -> "anilist"
malApi.name -> "myanimelist"
else -> return url
}
return SyncUtil.getUrlsFromId(api.getIdFromUrl(url), otherApi).firstOrNull { realUrl ->
realUrl.contains(preferredUrl)
} ?: run {
throw ErrorLoadingException("Page does not exist on $preferredUrl")
}
}
}
return url
}
}

View file

@ -0,0 +1,57 @@
package com.lagradost.cloudstream3.metaproviders
import com.lagradost.cloudstream3.MainAPI
import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.SyncApis
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.aniListApi
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.malApi
import com.lagradost.cloudstream3.utils.SyncUtil
enum class SyncIdName {
AniList,
MyAnimeList,
Trakt,
Imdb
}
object SyncRedirector {
val syncApis = SyncApis
private val syncIds =
listOf(
SyncIdName.MyAnimeList to Regex("""myanimelist\.net\/anime\/(\d+)"""),
SyncIdName.AniList to Regex("""anilist\.co\/anime\/(\d+)""")
)
suspend fun redirect(url: String, providerApi: MainAPI): String {
// Tries built in ID -> ProviderUrl
for (api in syncApis) {
if (url.contains(api.mainUrl)) {
val otherApi = when (api.name) {
aniListApi.name -> "anilist"
malApi.name -> "myanimelist"
else -> return url
}
SyncUtil.getUrlsFromId(api.getIdFromUrl(url), otherApi).firstOrNull { realUrl ->
realUrl.contains(providerApi.mainUrl)
}?.let {
return it
}
// ?: run {
// throw ErrorLoadingException("Page does not exist on $preferredUrl")
// }
}
}
// Tries provider solution
return syncIds.firstNotNullOfOrNull { (syncName, syncRegex) ->
if (providerApi.supportedSyncNames.contains(syncName)) {
syncRegex.find(url)?.value?.let {
suspendSafeApiCall {
providerApi.getLoadUrl(syncName, it)
}
}
} else null
} ?: url
}
}

View file

@ -602,7 +602,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
// English title first
this.media.title.english ?: this.media.title.romaji ?: this.media.synonyms.firstOrNull()
?: "",
this.media.id.toString(),
"https://anilist.co/anime/${this.media.id}/",
listName ?: return null,
this.progress,
this.media.episodes,

View file

@ -387,7 +387,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
fun toLibraryItem(): LibraryItem {
return LibraryItem(
this.node.title,
this.node.id.toString(),
"https://myanimelist.net/anime/${this.node.id}/",
this.list_status?.status?.lowercase()?.capitalize()?.replace("_", " ") ?: "NONE",
this.list_status?.num_episodes_watched,
this.node.num_episodes,

View file

@ -12,6 +12,8 @@ import com.google.android.material.tabs.TabLayoutMediator
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.mvvm.observe
import com.lagradost.cloudstream3.ui.result.txt
import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_LOAD
import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
import kotlinx.android.synthetic.main.fragment_library.*
@ -68,12 +70,17 @@ class LibraryFragment : Fragment() {
viewpager?.setPageTransformer(LibraryScrollTransformer())
viewpager?.adapter =
viewpager.adapter ?: ViewpagerAdapter(emptyList()) { isScrollingDown: Boolean ->
viewpager.adapter ?: ViewpagerAdapter(emptyList(), { isScrollingDown: Boolean ->
if (isScrollingDown) {
sort_fab?.shrink()
} else {
sort_fab?.extend()
}
}) { searchClickCallback ->
println("SEARCH CLICK $searchClickCallback")
if (searchClickCallback.action == SEARCH_ACTION_LOAD) {
activity?.loadSearchResult(searchClickCallback.card)
}
}
viewpager?.offscreenPageLimit = 2

View file

@ -6,12 +6,14 @@ import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.ui.search.SearchClickCallback
import com.lagradost.cloudstream3.ui.search.SearchResultBuilder
import com.lagradost.cloudstream3.utils.AppUtils
import kotlinx.android.synthetic.main.search_result_grid_expanded.view.*
class PageAdapter(
override val items: MutableList<LibraryItem>,
val clickCallback: (SearchClickCallback) -> Unit
) :
AppUtils.DiffAdapter<LibraryItem>(items) {
@ -33,7 +35,7 @@ class PageAdapter(
inner class LibraryItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(item: LibraryItem, position: Int) {
SearchResultBuilder.bind(
{ println("CLICKED ${it.action}") },
this@PageAdapter.clickCallback,
item,
position,
itemView,

View file

@ -12,6 +12,7 @@ import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.SearchQuality
import com.lagradost.cloudstream3.SearchResponse
import com.lagradost.cloudstream3.TvType
import com.lagradost.cloudstream3.ui.search.SearchClickCallback
import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount
import kotlinx.android.synthetic.main.library_viewpager_page.view.*
import me.xdrop.fuzzywuzzy.FuzzySearch
@ -55,7 +56,11 @@ data class LibraryItem(
) : SearchResponse
class ViewpagerAdapter(var pages: List<Page>, val scrollCallback: (isScrollingDown: Boolean) -> Unit) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
class ViewpagerAdapter(
var pages: List<Page>,
val scrollCallback: (isScrollingDown: Boolean) -> Unit,
val clickCallback: (SearchClickCallback) -> Unit
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return PageViewHolder(
LayoutInflater.from(parent.context)
@ -75,8 +80,9 @@ class ViewpagerAdapter(var pages: List<Page>, val scrollCallback: (isScrollingDo
RecyclerView.ViewHolder(itemViewTest) {
fun bind(page: Page) {
if (itemViewTest.page_recyclerview?.adapter == null) {
itemViewTest.page_recyclerview?.adapter = PageAdapter(page.items.toMutableList())
itemView.page_recyclerview?.spanCount = this@PageViewHolder.itemView.context.getSpanCount() ?: 3
itemViewTest.page_recyclerview?.adapter = PageAdapter(page.items.toMutableList(), clickCallback)
itemView.page_recyclerview?.spanCount =
this@PageViewHolder.itemView.context.getSpanCount() ?: 3
} else {
(itemViewTest.page_recyclerview?.adapter as? PageAdapter)?.updateList(page.items)
itemViewTest.page_recyclerview?.scrollToPosition(0)

View file

@ -14,6 +14,7 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.APIHolder.apis
import com.lagradost.cloudstream3.APIHolder.getId
import com.lagradost.cloudstream3.APIHolder.unixTime
import com.lagradost.cloudstream3.CommonActivity.getCastSession
@ -1264,12 +1265,18 @@ class ResultViewModel2 : ViewModel() {
val realRecommendations = ArrayList<SearchResponse>()
// TODO: fix
//val apiNames = listOf(GogoanimeProvider().name, NineAnimeProvider().name)
// meta.recommendations?.forEach { rec ->
// apiNames.forEach { name ->
// realRecommendations.add(rec.copy(apiName = name))
// }
// }
val apiNames = apis.filter {
it.name.contains("gogoanime", true) ||
it.name.contains("9anime", true)
}.map {
it.name
}
meta.recommendations?.forEach { rec ->
apiNames.forEach { name ->
realRecommendations.add(rec.copy(apiName = name))
}
}
recommendations = recommendations?.union(realRecommendations)?.toList()
?: realRecommendations
@ -1913,7 +1920,7 @@ class ResultViewModel2 : ViewModel() {
val validUrlResource = safeApiCall {
SyncRedirector.redirect(
url,
api.mainUrl
api
)
}
// TODO: fix

View file

@ -4,6 +4,7 @@ package com.lagradost.cloudstream3.utils
import android.util.Log
import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.APIHolder.apis
//import com.lagradost.cloudstream3.animeproviders.AniflixProvider
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.mvvm.logError
@ -78,17 +79,21 @@ object SyncUtil {
return null
}
suspend fun getUrlsFromId(id: String, type: String = "anilist") : List<String> {
return arrayListOf()
// val url =
// "https://raw.githubusercontent.com/MALSync/MAL-Sync-Backup/master/data/$type/anime/$id.json"
// val response = app.get(url, cacheTime = 1, cacheUnit = TimeUnit.DAYS).parsed<SyncPage>()
// val pages = response.pages ?: return emptyList()
// val current = pages.gogoanime.values.union(pages.nineanime.values).union(pages.twistmoe.values).mapNotNull { it.url }.toMutableList()
// if(type == "anilist") { // TODO MAKE BETTER
// current.add("${AniflixProvider().mainUrl}/anime/$id")
// }
// return current
suspend fun getUrlsFromId(id: String, type: String = "anilist"): List<String> {
val url =
"https://raw.githubusercontent.com/MALSync/MAL-Sync-Backup/master/data/$type/anime/$id.json"
val response = app.get(url, cacheTime = 1, cacheUnit = TimeUnit.DAYS).parsed<SyncPage>()
val pages = response.pages ?: return emptyList()
val current =
pages.gogoanime.values.union(pages.nineanime.values).union(pages.twistmoe.values)
.mapNotNull { it.url }.toMutableList()
if (type == "anilist") { // TODO MAKE BETTER
apis.filter { it.name.contains("Aniflix", ignoreCase = true) }.forEach {
current.add("${it.mainUrl}/anime/$id")
}
}
return current
}
data class SyncPage(

View file

@ -301,6 +301,7 @@
<style name="TabNoCaps" parent="TextAppearance.Design.Tab">
<item name="textAllCaps">false</item>
<item name="fontFamily">@font/google_sans</item>
</style>
<style name="AppTextViewStyle" parent="android:Widget.TextView">