Dantotsu/app/src/main/java/ani/dantotsu/parsers/AnimeParser.kt
Finnley Somdahl afa960c808 reformat
2023-12-01 01:22:15 -06:00

279 lines
8.9 KiB
Kotlin

package ani.dantotsu.parsers
import android.net.Uri
import ani.dantotsu.FileUrl
import ani.dantotsu.R
import ani.dantotsu.asyncMap
import ani.dantotsu.currContext
import ani.dantotsu.loadData
import ani.dantotsu.others.MalSyncBackup
import ani.dantotsu.saveData
import ani.dantotsu.tryWithSuspend
import eu.kanade.tachiyomi.animesource.model.SAnime
import eu.kanade.tachiyomi.animesource.model.SEpisode
import kotlin.properties.Delegates
/**
* An abstract class for creating a new Source
*
* Most of the functions & variables that need to be overridden are abstract
* **/
abstract class AnimeParser : BaseParser() {
/**
* Takes ShowResponse.link & ShowResponse.extra (if you added any) as arguments & gives a list of total episodes present on the site.
* **/
abstract suspend fun loadEpisodes(
animeLink: String,
extra: Map<String, String>?,
sAnime: SAnime
): List<Episode>
/**
* Takes ShowResponse.link, ShowResponse.extra & the Last Largest Episode Number known by app as arguments
*
* Returns the latest episode (If overriding, Make sure the episode is actually the latest episode)
* Returns null, if no latest episode is found.
* **/
open suspend fun getLatestEpisode(
animeLink: String,
extra: Map<String, String>?,
sAnime: SAnime,
latest: Float
): Episode? {
val episodes = loadEpisodes(animeLink, extra, sAnime)
val max = episodes
.maxByOrNull { it.number.toFloatOrNull() ?: 0f }
return max
?.takeIf { latest < (it.number.toFloatOrNull() ?: 0.001f) }
}
/**
* Takes Episode.link as a parameter
*
* This returns a Map of "Video Server's Name" & "Link/Data" of all the Video Servers present on the site, which can be further used by loadVideoServers() & loadSingleVideoServer()
* **/
abstract suspend fun loadVideoServers(
episodeLink: String,
extra: Map<String, String>?,
sEpisode: SEpisode
): List<VideoServer>
/**
* This function will receive **url of the embed** & **name** of a Video Server present on the site to host the episode.
*
*
* Create a new VideoExtractor for the video server you are trying to scrape, if there's not one already.
*
*
* (Some sites might not have separate video hosts. In that case, just create a new VideoExtractor for that particular site)
*
*
* returns a **VideoExtractor** containing **`server`**, the app will further load the videos using `extract()` function inside it
*
* **Example for Site with multiple Video Servers**
* ```
val domain = Uri.parse(server.embed.url).host ?: ""
val extractor: VideoExtractor? = when {
"fembed" in domain -> FPlayer(server)
"sb" in domain -> StreamSB(server)
"streamta" in domain -> StreamTape(server)
else -> null
}
return extractor
```
* You can use your own way to get the Extractor for reliability.
* if there's only extractor, you can directly return it.
* **/
open suspend fun getVideoExtractor(server: VideoServer): VideoExtractor? {
var domain = Uri.parse(server.embed.url).host ?: return null
if (domain.startsWith("www.")) {
domain = domain.substring(4)
}
val extractor: VideoExtractor? = when (domain) {
else -> {
println("$name : No extractor found for: $domain | ${server.embed.url}")
null
}
}
return extractor
}
/**
* If the Video Servers support preloading links for the videos
* typically depends on what Video Extractor is being used
* **/
open val allowsPreloading = true
/**
* This Function used when there "isn't" a default Server set by the user, or when user wants to switch the Server
*
* Doesn't need to be overridden, if the parser is following the norm.
* **/
open suspend fun loadByVideoServers(
episodeUrl: String,
extra: Map<String, String>?,
sEpisode: SEpisode,
callback: (VideoExtractor) -> Unit
) {
tryWithSuspend(true) {
loadVideoServers(episodeUrl, extra, sEpisode).asyncMap {
getVideoExtractor(it)?.apply {
tryWithSuspend(true) {
load()
}
callback.invoke(this)
}
}
}
}
/**
* This Function used when there "is" a default Server set by the user, only loads a Single Server for faster response.
*
* Doesn't need to be overridden, if the parser is following the norm.
* **/
open suspend fun loadSingleVideoServer(
serverName: String,
episodeUrl: String,
extra: Map<String, String>?,
sEpisode: SEpisode,
post: Boolean
): VideoExtractor? {
return tryWithSuspend(post) {
loadVideoServers(episodeUrl, extra, sEpisode).apply {
find { it.name == serverName }?.also {
return@tryWithSuspend getVideoExtractor(it)?.apply {
load()
}
}
}
null
}
}
/**
* Many sites have Dub & Sub anime as separate Shows
*
* make this `true`, if they are separated else `false`
*
* **NOTE : do not forget to override `search` if the site does not support only dub search**
* **/
open val isDubAvailableSeparately by Delegates.notNull<Boolean>()
/**
* The app changes this, depending on user's choice.
* **/
open var selectDub = false
/**
* Name used to get Shows Directly from MALSyncBackup's github dump
*
* Do not override if the site is not present on it.
* **/
open val malSyncBackupName = ""
/**
* Overridden to add MalSyncBackup support for Anime Sites
* **/
override suspend fun loadSavedShowResponse(mediaId: Int): ShowResponse? {
checkIfVariablesAreEmpty()
val dub = if (isDubAvailableSeparately) "_${if (selectDub) "dub" else "sub"}" else ""
var loaded = loadData<ShowResponse>("${saveName}${dub}_$mediaId")
if (loaded == null && malSyncBackupName.isNotEmpty())
loaded = MalSyncBackup.get(mediaId, malSyncBackupName, selectDub)
?.also { saveShowResponse(mediaId, it, true) }
return loaded
}
override fun saveShowResponse(mediaId: Int, response: ShowResponse?, selected: Boolean) {
if (response != null) {
checkIfVariablesAreEmpty()
setUserText(
"${
if (selected) currContext()!!.getString(R.string.selected) else currContext()!!.getString(
R.string.found
)
} : ${response.name}"
)
val dub = if (isDubAvailableSeparately) "_${if (selectDub) "dub" else "sub"}" else ""
saveData("${saveName}${dub}_$mediaId", response)
}
}
}
class EmptyAnimeParser : AnimeParser() {
override val name: String = "None"
override val saveName: String = "None"
override val isDubAvailableSeparately: Boolean = false
override suspend fun loadEpisodes(
animeLink: String,
extra: Map<String, String>?,
sAnime: SAnime
): List<Episode> = emptyList()
override suspend fun loadVideoServers(
episodeLink: String,
extra: Map<String, String>?,
sEpisode: SEpisode
): List<VideoServer> = emptyList()
override suspend fun search(query: String): List<ShowResponse> = emptyList()
}
/**
* A class for containing Episode data of a particular parser
* **/
data class Episode(
/**
* Number of the Episode in "String",
*
* useful in cases where episode is not a number
* **/
val number: String,
/**
* Link that links to the episode page containing videos
* **/
val link: String,
//Self-Descriptive
val title: String? = null,
val thumbnail: FileUrl? = null,
val description: String? = null,
val isFiller: Boolean = false,
/**
* In case, you want to pass extra data
* **/
val extra: Map<String, String>? = null,
//SEpisode from Aniyomi
val sEpisode: SEpisode? = null
) {
constructor(
number: String,
link: String,
title: String? = null,
thumbnail: String,
description: String? = null,
isFiller: Boolean = false,
extra: Map<String, String>? = null
) : this(number, link, title, FileUrl(thumbnail), description, isFiller, extra)
constructor(
number: String,
link: String,
title: String? = null,
thumbnail: String,
description: String? = null,
isFiller: Boolean = false,
extra: Map<String, String>? = null,
sEpisode: SEpisode? = null
) : this(number, link, title, FileUrl(thumbnail), description, isFiller, extra, sEpisode)
}