This commit is contained in:
LagradOst 2022-06-29 03:20:23 +02:00
parent 7bf77cb408
commit f7b23d6467
13 changed files with 370 additions and 27 deletions

View File

@ -134,6 +134,7 @@ object APIHolder {
//MultiAnimeProvider(),
NginxProvider(),
OlgplyProvider(),
AniflixProvider(),
)

View File

@ -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<SearchResponse>? {
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<Search>()
?: 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<AnimeResponsePage>()?.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<AniLoadResponse>().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<Sources> = 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<Anime> = 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<String>? = 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<Anime>,
@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<Nodes?> = 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?,
)
}

View File

@ -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)

View File

@ -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))

View File

@ -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
)

View File

@ -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)
}

View File

@ -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<SyncPage>()
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(

View File

@ -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

View File

@ -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">
<LinearLayout
android:foreground="?android:attr/selectableItemBackgroundBorderless"

View File

@ -11,6 +11,7 @@
<com.google.android.material.appbar.AppBarLayout
android:background="?attr/primaryGrayBackground"
tools:layout_height="100dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!--
@ -124,7 +125,6 @@
android:background="?attr/primaryBlackBackground"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
android:padding="10dp"
android:id="@+id/download_list"
android:layout_width="match_parent"
tools:listitem="@layout/download_header_episode"
@ -154,6 +154,7 @@
</ProgressBar>-->
<com.facebook.shimmer.ShimmerFrameLayout
tools:visibility="gone"
android:id="@+id/download_loading"
app:shimmer_base_alpha="0.2"
app:shimmer_highlight_alpha="0.3"
@ -185,4 +186,13 @@
<include layout="@layout/loading_downloads" />
</LinearLayout>
</com.facebook.shimmer.ShimmerFrameLayout>
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:text="@string/stream"
android:visibility="gone"
android:id="@+id/download_stream_button"
app:icon="@drawable/netflix_play"
style="@style/ExtendedFloatingActionButton"
android:textColor="?attr/textColor"
tools:ignore="ContentDescription" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -363,6 +363,8 @@
android:layout_height="match_parent">
<TextView
android:maxLength="1000"
android:ellipsize="end"
android:id="@+id/result_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -832,6 +834,7 @@
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:gravity="center"
@ -842,6 +845,7 @@
android:textSize="17sp"
android:textStyle="normal"
android:text="Episode 1022 will be released in" />
<TextView
android:paddingStart="5dp"
android:gravity="center"

View File

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:layout_weight="20"
android:id="@+id/subtitle_offset_input"
android:inputType="textUri"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/subtitle_offset_hint"
tools:ignore="LabelFor" />
<LinearLayout
android:orientation="horizontal"
android:layout_gravity="bottom"
android:gravity="bottom|end"
android:layout_width="match_parent"
android:layout_height="60dp">
<com.google.android.material.button.MaterialButton
style="@style/WhiteButton"
android:layout_gravity="center_vertical|end"
android:visibility="visible"
android:text="@string/sort_apply"
android:id="@+id/apply_btt"
android:layout_width="wrap_content">
<requestFocus />
</com.google.android.material.button.MaterialButton>
<com.google.android.material.button.MaterialButton
style="@style/BlackButton"
android:layout_gravity="center_vertical|end"
android:text="@string/sort_cancel"
android:id="@+id/cancel_btt"
android:layout_width="wrap_content" />
</LinearLayout>
</LinearLayout>

View File

@ -136,6 +136,7 @@
<string name="download_canceled">Download Canceled</string>
<string name="download_done">Download Done</string>
<string name="download_format" translatable="false">%s - %s</string>
<string name="stream">Stream</string>
<string name="error_loading_links_toast">Error Loading Links</string>
<string name="download_storage_text">Internal Storage</string>