From f7b23d646727145568609b3a5cbb154e7a8c8dd1 Mon Sep 17 00:00:00 2001 From: LagradOst <11805592+LagradOst@users.noreply.github.com> Date: Wed, 29 Jun 2022 03:20:23 +0200 Subject: [PATCH] added https://aniflix.pro --- .../com/lagradost/cloudstream3/MainAPI.kt | 1 + .../animeproviders/AniflixProvider.kt | 246 ++++++++++++++++++ .../ui/download/DownloadFragment.kt | 19 +- .../cloudstream3/ui/result/EpisodeAdapter.kt | 16 +- .../cloudstream3/ui/result/ResultFragment.kt | 20 +- .../lagradost/cloudstream3/utils/AppUtils.kt | 21 +- .../lagradost/cloudstream3/utils/SyncUtil.kt | 7 +- .../cloudstream3/widget/FlowLayout.kt | 5 +- .../res/layout/download_header_episode.xml | 4 +- .../main/res/layout/fragment_downloads.xml | 12 +- app/src/main/res/layout/fragment_result.xml | 4 + app/src/main/res/layout/stream_input.xml | 41 +++ app/src/main/res/values/strings.xml | 1 + 13 files changed, 370 insertions(+), 27 deletions(-) create mode 100644 app/src/main/java/com/lagradost/cloudstream3/animeproviders/AniflixProvider.kt create mode 100644 app/src/main/res/layout/stream_input.xml diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt index 89f6cf75..630e03f6 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt @@ -134,6 +134,7 @@ object APIHolder { //MultiAnimeProvider(), NginxProvider(), OlgplyProvider(), + AniflixProvider(), ) diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/AniflixProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/AniflixProvider.kt new file mode 100644 index 00000000..8b2e408e --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/AniflixProvider.kt @@ -0,0 +1,246 @@ +package com.lagradost.cloudstream3.animeproviders + +import com.fasterxml.jackson.annotation.JsonProperty +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.LoadResponse.Companion.addAniListId +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.getQualityFromName + +class AniflixProvider : MainAPI() { + override var mainUrl = "https://aniflix.pro" + override var name = "Aniflix" + override val hasMainPage = false + + override val supportedTypes = setOf( + TvType.AnimeMovie, + TvType.OVA, + TvType.Anime, + ) + + companion object { + var token: String? = null + } + + private suspend fun getToken(): String { + return token ?: run { + Regex("([^/]*)/_buildManifest\\.js").find(app.get(mainUrl).text)?.groupValues?.getOrNull( + 1 + ) + ?.also { + token = it + } + ?: throw ErrorLoadingException("No token found") + } + } + + private fun Anime.toSearchResponse(): SearchResponse? { + return newAnimeSearchResponse( + title?.english ?: title?.romaji ?: return null, + "$mainUrl/anime/${id ?: return null}" + ) { + posterUrl = coverImage?.large ?: coverImage?.medium + } + } + + override suspend fun search(query: String): List? { + val token = getToken() + val url = "$mainUrl/_next/data/$token/search.json?keyword=$query" + val response = app.get(url) + println("resp: $url ===> ${response.text}") + val searchResponse = + response.parsedSafe() + ?: throw ErrorLoadingException("No Media") + return searchResponse.pageProps?.searchResults?.Page?.media?.mapNotNull { media -> + media.toSearchResponse() + } + } + + override suspend fun load(url: String): LoadResponse { + val token = getToken() + val id = Regex("$mainUrl/anime/([0-9]*)").find(url)?.groupValues?.getOrNull(1) + ?: throw ErrorLoadingException("Error parsing link for id") + + val res = app.get("https://aniflix.pro/_next/data/$token/anime/$id.json?id=$id") + .parsedSafe()?.pageProps + ?: throw ErrorLoadingException("Invalid Json reponse") + val isMovie = res.anime.format == "MOVIE" + return newAnimeLoadResponse( + res.anime.title?.english ?: res.anime.title?.romaji + ?: throw ErrorLoadingException("Invalid title reponse"), + url, if (isMovie) TvType.AnimeMovie else TvType.Anime + ) { + recommendations = res.recommended.mapNotNull { it.toSearchResponse() } + tags = res.anime.genres + posterUrl = res.anime.coverImage?.large ?: res.anime.coverImage?.medium + plot = res.anime.description + showStatus = when (res.anime.status) { + "FINISHED" -> ShowStatus.Completed + "RELEASING" -> ShowStatus.Ongoing + else -> null + } + addAniListId(id.toIntOrNull()) + + // subbed because they are both subbed and dubbed + if (isMovie) + addEpisodes( + DubStatus.Subbed, + listOf(newEpisode("$mainUrl/api/anime/?id=$id&episode=1")) + ) + else + addEpisodes(DubStatus.Subbed, res.episodes.episodes?.nodes?.mapNotNull { node -> + val ep = node?.number ?: return@mapNotNull null + //"$mainUrl/_next/data/$token/watch/$id.json?episode=${node.number ?: return@mapNotNull null}&id=$id" + newEpisode("$mainUrl/api/anime?id=$id&episode=${ep}") { + episode = ep + posterUrl = node.thumbnail?.original?.url + name = node.titles?.canonical + } + }) + } + } + + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + return app.get(data).parsed().let { res -> + val dubReferer = res.dub?.Referer ?: "" + res.dub?.sources?.forEach { source -> + callback( + ExtractorLink( + name, + "${source.label ?: name} (DUB)", + source.file ?: return@forEach, + dubReferer, + getQualityFromName(source.label), + source.type == "hls" + ) + ) + } + + val subReferer = res.dub?.Referer ?: "" + res.sub?.sources?.forEach { source -> + callback( + ExtractorLink( + name, + "${source.label ?: name} (SUB)", + source.file ?: return@forEach, + subReferer, + getQualityFromName(source.label), + source.type == "hls" + ) + ) + } + + !res.dub?.sources.isNullOrEmpty() && !res.sub?.sources.isNullOrEmpty() + } + } + + data class AniLoadResponse( + @JsonProperty("sub") val sub: DubSubSource?, + @JsonProperty("dub") val dub: DubSubSource?, + @JsonProperty("episodes") val episodes: Int? + ) + + data class Sources( + @JsonProperty("file") val file: String?, + @JsonProperty("label") val label: String?, + @JsonProperty("type") val type: String? + ) + + data class DubSubSource( + @JsonProperty("Referer") var Referer: String?, + @JsonProperty("sources") var sources: ArrayList = arrayListOf() + ) + + data class PageProps( + @JsonProperty("searchResults") val searchResults: SearchResults? + ) + + data class SearchResults( + @JsonProperty("Page") val Page: Page? + ) + + data class Page( + @JsonProperty("media") val media: ArrayList = arrayListOf() + ) + + data class CoverImage( + @JsonProperty("color") val color: String?, + @JsonProperty("medium") val medium: String?, + @JsonProperty("large") val large: String?, + ) + + data class Title( + @JsonProperty("english") val english: String?, + @JsonProperty("romaji") val romaji: String?, + ) + + data class Search( + @JsonProperty("pageProps") val pageProps: PageProps?, + @JsonProperty("__N_SSP") val _NSSP: Boolean? + ) + + data class Anime( + @JsonProperty("status") val status: String?, + @JsonProperty("id") val id: Int?, + @JsonProperty("title") val title: Title?, + @JsonProperty("coverImage") val coverImage: CoverImage?, + @JsonProperty("format") val format: String?, + @JsonProperty("duration") val duration: Int?, + @JsonProperty("meanScore") val meanScore: Int?, + @JsonProperty("nextAiringEpisode") val nextAiringEpisode: String?, + @JsonProperty("bannerImage") val bannerImage: String?, + @JsonProperty("description") val description: String?, + @JsonProperty("genres") val genres: ArrayList? = null, + @JsonProperty("season") val season: String?, + @JsonProperty("startDate") val startDate: StartDate?, + ) + + data class StartDate( + @JsonProperty("year") val year: Int? + ) + + data class AnimeResponsePage( + @JsonProperty("pageProps") val pageProps: AnimeResponse?, + ) + + data class AnimeResponse( + @JsonProperty("anime") val anime: Anime, + @JsonProperty("recommended") val recommended: ArrayList, + @JsonProperty("episodes") val episodes: EpisodesParent, + ) + + data class EpisodesParent( + @JsonProperty("id") val id: String?, + @JsonProperty("season") val season: String?, + @JsonProperty("startDate") val startDate: String?, + @JsonProperty("episodeCount") val episodeCount: Int?, + @JsonProperty("episodes") val episodes: Episodes?, + ) + + data class Episodes( + @JsonProperty("nodes") val nodes: ArrayList = arrayListOf() + ) + + data class Nodes( + @JsonProperty("number") val number: Int? = null, + @JsonProperty("titles") val titles: Titles?, + @JsonProperty("thumbnail") val thumbnail: Thumbnail?, + ) + + data class Titles( + @JsonProperty("canonical") val canonical: String?, + ) + + data class Original( + @JsonProperty("url") val url: String?, + ) + + data class Thumbnail( + @JsonProperty("original") val original: Original?, + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt index c6cdb777..d6996b92 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt @@ -26,6 +26,7 @@ import com.lagradost.cloudstream3.utils.VideoDownloadHelper import com.lagradost.cloudstream3.utils.VideoDownloadManager import kotlinx.android.synthetic.main.fragment_downloads.* + const val DOWNLOAD_NAVIGATE_TO = "downloadpage" class DownloadFragment : Fragment() { @@ -165,8 +166,22 @@ class DownloadFragment : Fragment() { downloadDeleteEventListener?.let { VideoDownloadManager.downloadDeleteEvent += it } - download_list.adapter = adapter - download_list.layoutManager = GridLayoutManager(context, 1) + download_list?.adapter = adapter + download_list?.layoutManager = GridLayoutManager(context, 1) + /*download_stream_button?.isGone = context?.isTvSettings() == true + download_stream_button?.setOnClickListener { + + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + download_list?.setOnScrollChangeListener { _, _, scrollY, _, oldScrollY -> + val dy = scrollY - oldScrollY + if (dy > 0) { //check for scroll down + download_stream_button?.shrink() // hide + } else if (dy < -5) { + download_stream_button?.extend() // show + } + } + }*/ downloadsViewModel.updateList(requireContext()) context?.fixPaddingStatusbar(download_root) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt index 99924f22..6a62a48b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt @@ -18,6 +18,7 @@ import com.lagradost.cloudstream3.ui.download.DownloadButtonViewHolder import com.lagradost.cloudstream3.ui.download.DownloadClickEvent import com.lagradost.cloudstream3.ui.download.EasyDownloadButton import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings +import com.lagradost.cloudstream3.utils.AppUtils.html import com.lagradost.cloudstream3.utils.UIHelper.setImage import com.lagradost.cloudstream3.utils.VideoDownloadHelper import com.lagradost.cloudstream3.utils.VideoDownloadManager @@ -94,10 +95,10 @@ class EpisodeAdapter( @LayoutRes private var layout: Int = 0 fun updateLayout() { - // layout = - // if (cardList.filter { it.poster != null }.size >= cardList.size / 2f) // If over half has posters then use the large layout - // R.layout.result_episode_large - // else R.layout.result_episode + // layout = + // if (cardList.filter { it.poster != null }.size >= cardList.size / 2f) // If over half has posters then use the large layout + // R.layout.result_episode_large + // else R.layout.result_episode } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { @@ -106,7 +107,8 @@ class EpisodeAdapter( else R.layout.result_episode*/ return EpisodeCardViewHolder( - LayoutInflater.from(parent.context).inflate(R.layout.result_episode_both, parent, false), + LayoutInflater.from(parent.context) + .inflate(R.layout.result_episode_both, parent, false), hasDownloadSupport, clickCallback, downloadClickCallback @@ -144,7 +146,7 @@ class EpisodeAdapter( fun bind(card: ResultEpisode) { localCard = card - val (parentView,otherView) = if(card.poster == null) { + val (parentView, otherView) = if (card.poster == null) { itemView.episode_holder to itemView.episode_holder_large } else { itemView.episode_holder_large to itemView.episode_holder @@ -193,7 +195,7 @@ class EpisodeAdapter( episodeRating?.isGone = episodeRating?.text.isNullOrBlank() episodeDescript?.apply { - text = card.description ?: "" + text = card.description.html() isGone = text.isNullOrBlank() setOnClickListener { clickCallback.invoke(EpisodeClickEvent(ACTION_SHOW_DESCRIPTION, card)) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt index 20035ac4..f263acf3 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt @@ -24,6 +24,7 @@ import android.widget.* import androidx.annotation.StringRes import androidx.appcompat.app.AlertDialog import androidx.core.content.FileProvider +import androidx.core.text.HtmlCompat import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.core.widget.NestedScrollView @@ -64,6 +65,7 @@ import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueT import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment.Companion.getDownloadSubsLanguageISO639_1 import com.lagradost.cloudstream3.utils.* +import com.lagradost.cloudstream3.utils.AppUtils.html import com.lagradost.cloudstream3.utils.AppUtils.isAppInstalled import com.lagradost.cloudstream3.utils.AppUtils.isCastApiAvailable import com.lagradost.cloudstream3.utils.AppUtils.isConnectedToChromecast @@ -103,9 +105,6 @@ import kotlinx.coroutines.withContext import java.io.File import java.util.concurrent.TimeUnit - -const val MAX_SYNO_LENGH = 1000 - const val START_ACTION_NORMAL = 0 const val START_ACTION_RESUME_LATEST = 1 const val START_ACTION_LOAD_EP = 2 @@ -1908,21 +1907,18 @@ class ResultFragment : ResultTrailerPlayer() { )*/ //result_plot_header?.text = // if (d.type == TvType.Torrent) getString(R.string.torrent_plot) else getString(R.string.result_plot) - if (!d.plot.isNullOrEmpty()) { - var syno = d.plot!! - if (syno.length > MAX_SYNO_LENGH) { - syno = syno.substring(0, MAX_SYNO_LENGH) + "..." - } - result_description.setOnClickListener { + val syno = d.plot + if (!syno.isNullOrEmpty()) { + result_description?.setOnClickListener { val builder: AlertDialog.Builder = AlertDialog.Builder(requireContext(), R.style.AlertDialogCustom) - builder.setMessage(d.plot) + builder.setMessage(syno.html()) .setTitle(if (d.type == TvType.Torrent) R.string.torrent_plot else R.string.result_plot) .show() } - result_description.text = syno + result_description?.text = syno.html() } else { - result_description.text = + result_description?.text = if (d.type == TvType.Torrent) getString(R.string.torrent_no_plot) else getString( R.string.normal_no_plot ) diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/AppUtils.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/AppUtils.kt index a910c6a7..bdfc7b7d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/AppUtils.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/AppUtils.kt @@ -18,11 +18,14 @@ import android.os.Build import android.os.Environment import android.os.ParcelFileDescriptor import android.provider.MediaStore +import android.text.Spanned import android.util.Log import androidx.annotation.RequiresApi import androidx.annotation.WorkerThread import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat +import androidx.core.text.HtmlCompat +import androidx.core.text.toSpanned import androidx.tvprovider.media.tv.PreviewChannelHelper import androidx.tvprovider.media.tv.TvContractCompat import androidx.tvprovider.media.tv.WatchNextProgram @@ -61,6 +64,22 @@ object AppUtils { // ) // } //} + fun String?.html(): Spanned { + return getHtmlText(this ?: return "".toSpanned()) + } + + private fun getHtmlText(text: String): Spanned { + return try { + // I have no idea if this can throw any error, but I dont want to try + HtmlCompat.fromHtml( + text, HtmlCompat.FROM_HTML_MODE_LEGACY + ) + } catch (e: Exception) { + logError(e) + text.toSpanned() + } + } + @SuppressLint("RestrictedApi") private fun buildWatchNextProgramUri( context: Context, @@ -222,7 +241,7 @@ object AppUtils { /** Any object as json string */ fun Any.toJson(): String { - if(this is String) return this + if (this is String) return this return mapper.writeValueAsString(this) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/SyncUtil.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/SyncUtil.kt index b5a34b86..993180c7 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/SyncUtil.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/SyncUtil.kt @@ -3,6 +3,7 @@ package com.lagradost.cloudstream3.utils import android.util.Log import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.module.kotlin.readValue +import com.lagradost.cloudstream3.animeproviders.AniflixProvider import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.mapper import com.lagradost.cloudstream3.mvvm.logError @@ -81,7 +82,11 @@ object SyncUtil { "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() val pages = response.pages ?: return emptyList() - return pages.gogoanime.values.union(pages.nineanime.values).union(pages.twistmoe.values).mapNotNull { it.url } + 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 } data class SyncPage( diff --git a/app/src/main/java/com/lagradost/cloudstream3/widget/FlowLayout.kt b/app/src/main/java/com/lagradost/cloudstream3/widget/FlowLayout.kt index 5002573d..d4725d53 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/widget/FlowLayout.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/widget/FlowLayout.kt @@ -35,9 +35,10 @@ class FlowLayout : ViewGroup { measureChild(child, widthMeasureSpec, heightMeasureSpec) val childWidth = child.measuredWidth val childHeight = child.measuredHeight + currentHeight = max(currentHeight, currentChildHookPointy + childHeight) //check if child can be placed in the current row, else go to next line - if (currentChildHookPointx + childWidth - child.marginEnd - child.paddingEnd - itemSpacing > realWidth) { + if (currentChildHookPointx + childWidth - child.marginEnd - child.paddingEnd > realWidth) { //new line currentWidth = max(currentWidth, currentChildHookPointx) @@ -47,8 +48,8 @@ class FlowLayout : ViewGroup { } val nextChildHookPointx = currentChildHookPointx + childWidth + if (childWidth == 0) 0 else itemSpacing + val nextChildHookPointy = currentChildHookPointy - currentHeight = max(currentHeight, currentChildHookPointy + childHeight) val lp = child.layoutParams as LayoutParams lp.x = currentChildHookPointx lp.y = currentChildHookPointy diff --git a/app/src/main/res/layout/download_header_episode.xml b/app/src/main/res/layout/download_header_episode.xml index 32f98be1..da4b3617 100644 --- a/app/src/main/res/layout/download_header_episode.xml +++ b/app/src/main/res/layout/download_header_episode.xml @@ -8,7 +8,9 @@ app:cardBackgroundColor="?attr/boxItemBackground" android:id="@+id/episode_holder" android:foreground="@drawable/outline_drawable" - android:layout_marginBottom="10dp"> + android:layout_marginStart="10dp" + android:layout_marginEnd="10dp" + android:layout_marginTop="10dp"> + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_result.xml b/app/src/main/res/layout/fragment_result.xml index 9024858f..c36cc9d9 100644 --- a/app/src/main/res/layout/fragment_result.xml +++ b/app/src/main/res/layout/fragment_result.xml @@ -363,6 +363,8 @@ android:layout_height="match_parent"> + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a8138ba9..70e3c4d9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -136,6 +136,7 @@ Download Canceled Download Done %s - %s + Stream Error Loading Links Internal Storage