remove anime support

This commit is contained in:
Aria Moradi
2021-10-24 22:58:25 +03:30
parent 92ede2a2b3
commit 6389899507
44 changed files with 38 additions and 2982 deletions

View File

@@ -1,46 +0,0 @@
package eu.kanade.tachiyomi.animesource
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
import eu.kanade.tachiyomi.animesource.model.AnimesPage
import rx.Observable
interface AnimeCatalogueSource : AnimeSource {
/**
* An ISO 639-1 compliant language code (two letters in lower case).
*/
override val lang: String
/**
* Whether the source has support for latest updates.
*/
val supportsLatest: Boolean
/**
* Returns an observable containing a page with a list of anime.
*
* @param page the page number to retrieve.
*/
fun fetchPopularAnime(page: Int): Observable<AnimesPage>
/**
* Returns an observable containing a page with a list of anime.
*
* @param page the page number to retrieve.
* @param query the search query.
* @param filters the list of filters to apply.
*/
fun fetchSearchAnime(page: Int, query: String, filters: AnimeFilterList): Observable<AnimesPage>
/**
* Returns an observable containing a page with a list of latest anime updates.
*
* @param page the page number to retrieve.
*/
fun fetchLatestUpdates(page: Int): Observable<AnimesPage>
/**
* Returns the list of filters for the source.
*/
fun getFilterList(): AnimeFilterList
}

View File

@@ -1,81 +0,0 @@
package eu.kanade.tachiyomi.animesource
import eu.kanade.tachiyomi.animesource.model.SAnime
import eu.kanade.tachiyomi.animesource.model.SEpisode
import eu.kanade.tachiyomi.animesource.model.Video
import rx.Observable
/**
* A basic interface for creating a source. It could be an online source, a local source, etc...
*/
interface AnimeSource {
/**
* Id for the source. Must be unique.
*/
val id: Long
/**
* Name of the source.
*/
val name: String
val lang: String
get() = ""
/**
* Returns an observable with the updated details for a anime.
*
* @param anime the anime to update.
*/
// @Deprecated("Use getAnimeDetails instead")
fun fetchAnimeDetails(anime: SAnime): Observable<SAnime>
/**
* Returns an observable with all the available episodes for an anime.
*
* @param anime the anime to update.
*/
// @Deprecated("Use getEpisodeList instead")
fun fetchEpisodeList(anime: SAnime): Observable<List<SEpisode>>
/**
* Returns an observable with a list of video for the episode of an anime.
*
* @param episode the episode to get the link for.
*/
// @Deprecated("Use getEpisodeList instead")
fun fetchVideoList(episode: SEpisode): Observable<List<Video>>
// /**
// * [1.x API] Get the updated details for a anime.
// */
// @Suppress("DEPRECATION")
// override suspend fun getAnimeDetails(anime: AnimeInfo): AnimeInfo {
// val sAnime = anime.toSAnime()
// val networkAnime = fetchAnimeDetails(sAnime).awaitSingle()
// sAnime.copyFrom(networkAnime)
// return sAnime.toAnimeInfo()
// }
// /**
// * [1.x API] Get all the available episodes for a anime.
// */
// @Suppress("DEPRECATION")
// override suspend fun getEpisodeList(anime: AnimeInfo): List<EpisodeInfo> {
// return fetchEpisodeList(anime.toSAnime()).awaitSingle()
// .map { it.toEpisodeInfo() }
// }
// /**
// * [1.x API] Get a link for the episode of an anime.
// */
// @Suppress("DEPRECATION")
// override suspend fun getEpisodeLink(episode: EpisodeInfo): String {
// return fetchEpisodeLink(episode.toSEpisode()).awaitSingle()
// }
}
// fun AnimeSource.icon(): Drawable? = Injekt.get<AnimeExtensionManager>().getAppIconForSource(this)
fun AnimeSource.getPreferenceKey(): String = "source_$id"

View File

@@ -1,12 +0,0 @@
package eu.kanade.tachiyomi.animesource
/**
* A factory for creating sources at runtime.
*/
interface AnimeSourceFactory {
/**
* Create a new copy of the sources
* @return The created sources
*/
fun createSources(): List<AnimeSource>
}

View File

@@ -1,8 +0,0 @@
package eu.kanade.tachiyomi.animesource
import androidx.preference.PreferenceScreen
interface ConfigurableAnimeSource : AnimeSource {
fun setupPreferenceScreen(screen: PreferenceScreen)
}

View File

@@ -1,40 +0,0 @@
package eu.kanade.tachiyomi.animesource.model
sealed class AnimeFilter<T>(val name: String, var state: T) {
open class Header(name: String) : AnimeFilter<Any>(name, 0)
open class Separator(name: String = "") : AnimeFilter<Any>(name, 0)
abstract class Select<V>(name: String, val values: Array<V>, state: Int = 0) : AnimeFilter<Int>(name, state)
abstract class Text(name: String, state: String = "") : AnimeFilter<String>(name, state)
abstract class CheckBox(name: String, state: Boolean = false) : AnimeFilter<Boolean>(name, state)
abstract class TriState(name: String, state: Int = STATE_IGNORE) : AnimeFilter<Int>(name, state) {
fun isIgnored() = state == STATE_IGNORE
fun isIncluded() = state == STATE_INCLUDE
fun isExcluded() = state == STATE_EXCLUDE
companion object {
const val STATE_IGNORE = 0
const val STATE_INCLUDE = 1
const val STATE_EXCLUDE = 2
}
}
abstract class Group<V>(name: String, state: List<V>) : AnimeFilter<List<V>>(name, state)
abstract class Sort(name: String, val values: Array<String>, state: Selection? = null) :
AnimeFilter<Sort.Selection?>(name, state) {
data class Selection(val index: Int, val ascending: Boolean)
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is AnimeFilter<*>) return false
return name == other.name && state == other.state
}
override fun hashCode(): Int {
var result = name.hashCode()
result = 31 * result + (state?.hashCode() ?: 0)
return result
}
}

View File

@@ -1,6 +0,0 @@
package eu.kanade.tachiyomi.animesource.model
data class AnimeFilterList(val list: List<AnimeFilter<*>>) : List<AnimeFilter<*>> by list {
constructor(vararg fs: AnimeFilter<*>) : this(if (fs.isNotEmpty()) fs.asList() else emptyList())
}

View File

@@ -1,3 +0,0 @@
package eu.kanade.tachiyomi.animesource.model
data class AnimesPage(val animes: List<SAnime>, val hasNextPage: Boolean)

View File

@@ -1,93 +0,0 @@
package eu.kanade.tachiyomi.animesource.model
// import tachiyomi.animesource.model.AnimeInfo
import java.io.Serializable
interface SAnime : Serializable {
var url: String
var title: String
var artist: String?
var author: String?
var description: String?
var genre: String?
var status: Int
var thumbnail_url: String?
var initialized: Boolean
fun copyFrom(other: SAnime) {
title = other.title
if (other.author != null) {
author = other.author
}
if (other.artist != null) {
artist = other.artist
}
if (other.description != null) {
description = other.description
}
if (other.genre != null) {
genre = other.genre
}
if (other.thumbnail_url != null) {
thumbnail_url = other.thumbnail_url
}
status = other.status
if (!initialized) {
initialized = other.initialized
}
}
companion object {
const val UNKNOWN = 0
const val ONGOING = 1
const val COMPLETED = 2
const val LICENSED = 3
fun create(): SAnime {
return SAnimeImpl()
}
}
}
// fun SAnime.toAnimeInfo(): AnimeInfo {
// return AnimeInfo(
// key = this.url,
// title = this.title,
// artist = this.artist ?: "",
// author = this.author ?: "",
// description = this.description ?: "",
// genres = this.genre?.split(", ") ?: emptyList(),
// status = this.status,
// cover = this.thumbnail_url ?: ""
// )
// }
// fun AnimeInfo.toSAnime(): SAnime {
// val animeInfo = this
// return SAnime.create().apply {
// url = animeInfo.key
// title = animeInfo.title
// artist = animeInfo.artist
// author = animeInfo.author
// description = animeInfo.description
// genre = animeInfo.genres.joinToString(", ")
// status = animeInfo.status
// thumbnail_url = animeInfo.cover
// }
// }

View File

@@ -1,22 +0,0 @@
package eu.kanade.tachiyomi.animesource.model
class SAnimeImpl : SAnime {
override lateinit var url: String
override lateinit var title: String
override var artist: String? = null
override var author: String? = null
override var description: String? = null
override var genre: String? = null
override var status: Int = 0
override var thumbnail_url: String? = null
override var initialized: Boolean = false
}

View File

@@ -1,52 +0,0 @@
package eu.kanade.tachiyomi.animesource.model
// import tachiyomi.animesource.model.EpisodeInfo
import java.io.Serializable
interface SEpisode : Serializable {
var url: String
var name: String
var date_upload: Long
var episode_number: Float
var scanlator: String?
fun copyFrom(other: SEpisode) {
name = other.name
url = other.url
date_upload = other.date_upload
episode_number = other.episode_number
scanlator = other.scanlator
}
companion object {
fun create(): SEpisode {
return SEpisodeImpl()
}
}
}
// fun SEpisode.toEpisodeInfo(): EpisodeInfo {
// return EpisodeInfo(
// dateUpload = this.date_upload,
// key = this.url,
// name = this.name,
// number = this.episode_number,
// scanlator = this.scanlator ?: ""
// )
// }
//
// fun EpisodeInfo.toSEpisode(): SEpisode {
// val episode = this
// return SEpisode.create().apply {
// url = episode.key
// name = episode.name
// date_upload = episode.dateUpload
// episode_number = episode.number
// scanlator = episode.scanlator
// }
// }

View File

@@ -1,14 +0,0 @@
package eu.kanade.tachiyomi.animesource.model
class SEpisodeImpl : SEpisode {
override lateinit var url: String
override lateinit var name: String
override var date_upload: Long = 0
override var episode_number: Float = -1f
override var scanlator: String? = null
}

View File

@@ -1,73 +0,0 @@
package eu.kanade.tachiyomi.animesource.model
import android.net.Uri
import eu.kanade.tachiyomi.network.ProgressListener
import rx.subjects.Subject
// import tachiyomi.animesource.model.VideoUrl
open class Video(
val url: String = "",
val quality: String = "",
var videoUrl: String? = null,
@Transient var uri: Uri? = null // Deprecated but can't be deleted due to extensions
) : ProgressListener {
@Transient
@Volatile
var status: Int = 0
set(value) {
field = value
statusSubject?.onNext(value)
statusCallback?.invoke(this)
}
@Transient
@Volatile
var progress: Int = 0
set(value) {
field = value
statusCallback?.invoke(this)
}
@Transient
private var statusSubject: Subject<Int, Int>? = null
@Transient
private var statusCallback: ((Video) -> Unit)? = null
override fun update(bytesRead: Long, contentLength: Long, done: Boolean) {
progress = if (contentLength > 0) {
(100 * bytesRead / contentLength).toInt()
} else {
-1
}
}
fun setStatusSubject(subject: Subject<Int, Int>?) {
this.statusSubject = subject
}
fun setStatusCallback(f: ((Video) -> Unit)?) {
statusCallback = f
}
companion object {
const val QUEUE = 0
const val LOAD_VIDEO = 1
const val DOWNLOAD_IMAGE = 2
const val READY = 3
const val ERROR = 4
}
}
// fun Video.toVideoUrl(): VideoUrl {
// return VideoUrl(
// url = this.videoUrl ?: this.url
// )
// }
//
// fun VideoUrl.toVideo(index: Int): Video {
// return Video(
// videoUrl = this.url
// )
// }

View File

@@ -1,376 +0,0 @@
package eu.kanade.tachiyomi.animesource.online
import eu.kanade.tachiyomi.animesource.AnimeCatalogueSource
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
import eu.kanade.tachiyomi.animesource.model.AnimesPage
import eu.kanade.tachiyomi.animesource.model.SAnime
import eu.kanade.tachiyomi.animesource.model.SEpisode
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.network.newCallWithProgress
import okhttp3.Headers
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import rx.Observable
import uy.kohesive.injekt.injectLazy
import java.net.URI
import java.net.URISyntaxException
import java.security.MessageDigest
/**
* A simple implementation for sources from a website.
*/
abstract class AnimeHttpSource : AnimeCatalogueSource {
/**
* Network service.
*/
protected val network: NetworkHelper by injectLazy()
// /**
// * Preferences that a source may need.
// */
// val preferences: SharedPreferences by lazy {
// Injekt.get<Application>().getSharedPreferences(source.getPreferenceKey(), Context.MODE_PRIVATE)
// }
/**
* Base url of the website without the trailing slash, like: http://mysite.com
*/
abstract val baseUrl: String
/**
* Version id used to generate the source id. If the site completely changes and urls are
* incompatible, you may increase this value and it'll be considered as a new source.
*/
open val versionId = 1
/**
* Id of the source. By default it uses a generated id using the first 16 characters (64 bits)
* of the MD5 of the string: sourcename/language/versionId
* Note the generated id sets the sign bit to 0.
*/
override val id by lazy {
val key = "${name.lowercase()}/$lang/$versionId"
val bytes = MessageDigest.getInstance("MD5").digest(key.toByteArray())
(0..7).map { bytes[it].toLong() and 0xff shl 8 * (7 - it) }.reduce(Long::or) and Long.MAX_VALUE
}
/**
* Headers used for requests.
*/
val headers: Headers by lazy { headersBuilder().build() }
/**
* Default network client for doing requests.
*/
open val client: OkHttpClient
get() = network.client
/**
* Headers builder for requests. Implementations can override this method for custom headers.
*/
protected open fun headersBuilder() = Headers.Builder().apply {
add("User-Agent", DEFAULT_USER_AGENT)
}
/**
* Visible name of the source.
*/
override fun toString() = "$name (${lang.uppercase()})"
/**
* Returns an observable containing a page with a list of anime. Normally it's not needed to
* override this method.
*
* @param page the page number to retrieve.
*/
override fun fetchPopularAnime(page: Int): Observable<AnimesPage> {
return client.newCall(popularAnimeRequest(page))
.asObservableSuccess()
.map { response ->
popularAnimeParse(response)
}
}
/**
* Returns the request for the popular anime given the page.
*
* @param page the page number to retrieve.
*/
protected abstract fun popularAnimeRequest(page: Int): Request
/**
* Parses the response from the site and returns a [AnimesPage] object.
*
* @param response the response from the site.
*/
protected abstract fun popularAnimeParse(response: Response): AnimesPage
/**
* Returns an observable containing a page with a list of anime. Normally it's not needed to
* override this method.
*
* @param page the page number to retrieve.
* @param query the search query.
* @param filters the list of filters to apply.
*/
override fun fetchSearchAnime(page: Int, query: String, filters: AnimeFilterList): Observable<AnimesPage> {
return client.newCall(searchAnimeRequest(page, query, filters))
.asObservableSuccess()
.map { response ->
searchAnimeParse(response)
}
}
/**
* Returns the request for the search anime given the page.
*
* @param page the page number to retrieve.
* @param query the search query.
* @param filters the list of filters to apply.
*/
protected abstract fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request
/**
* Parses the response from the site and returns a [AnimesPage] object.
*
* @param response the response from the site.
*/
protected abstract fun searchAnimeParse(response: Response): AnimesPage
/**
* Returns an observable containing a page with a list of latest anime updates.
*
* @param page the page number to retrieve.
*/
override fun fetchLatestUpdates(page: Int): Observable<AnimesPage> {
return client.newCall(latestUpdatesRequest(page))
.asObservableSuccess()
.map { response ->
latestUpdatesParse(response)
}
}
/**
* Returns the request for latest anime given the page.
*
* @param page the page number to retrieve.
*/
protected abstract fun latestUpdatesRequest(page: Int): Request
/**
* Parses the response from the site and returns a [AnimesPage] object.
*
* @param response the response from the site.
*/
protected abstract fun latestUpdatesParse(response: Response): AnimesPage
/**
* Returns an observable with the updated details for a anime. Normally it's not needed to
* override this method.
*
* @param anime the anime to be updated.
*/
override fun fetchAnimeDetails(anime: SAnime): Observable<SAnime> {
return client.newCall(animeDetailsRequest(anime))
.asObservableSuccess()
.map { response ->
animeDetailsParse(response).apply { initialized = true }
}
}
/**
* Returns the request for the details of a anime. Override only if it's needed to change the
* url, send different headers or request method like POST.
*
* @param anime the anime to be updated.
*/
open fun animeDetailsRequest(anime: SAnime): Request {
return GET(baseUrl + anime.url, headers)
}
/**
* Parses the response from the site and returns the details of a anime.
*
* @param response the response from the site.
*/
protected abstract fun animeDetailsParse(response: Response): SAnime
/**
* Returns an observable with the updated episode list for a anime. Normally it's not needed to
* override this method. If a anime is licensed an empty episode list observable is returned
*
* @param anime the anime to look for episodes.
*/
override fun fetchEpisodeList(anime: SAnime): Observable<List<SEpisode>> {
return if (anime.status != SAnime.LICENSED) {
client.newCall(episodeListRequest(anime))
.asObservableSuccess()
.map { response ->
episodeListParse(response)
}
} else {
Observable.error(Exception("Licensed - No episodes to show"))
}
}
/**
* Returns the request for updating the episode list. Override only if it's needed to override
* the url, send different headers or request method like POST.
*
* @param anime the anime to look for episodes.
*/
protected open fun episodeListRequest(anime: SAnime): Request {
return GET(baseUrl + anime.url, headers)
}
/**
* Parses the response from the site and returns a list of episodes.
*
* @param response the response from the site.
*/
protected abstract fun episodeListParse(response: Response): List<SEpisode>
/**
* Returns an observable with the page list for a chapter.
*
* @param chapter the chapter whose page list has to be fetched.
*/
override fun fetchVideoList(episode: SEpisode): Observable<List<Video>> {
return client.newCall(videoListRequest(episode))
.asObservableSuccess()
.map { response ->
videoListParse(response)
}
}
/**
* Returns the request for getting the episode link. Override only if it's needed to override
* the url, send different headers or request method like POST.
*
* @param episode the episode to look for links.
*/
protected open fun videoListRequest(episode: SEpisode): Request {
return GET(baseUrl + episode.url, headers)
}
/**
* Parses the response from the site and returns a list of pages.
*
* @param response the response from the site.
*/
protected abstract fun videoListParse(response: Response): List<Video>
/**
* Returns an observable with the page containing the source url of the image. If there's any
* error, it will return null instead of throwing an exception.
*
* @param page the page whose source image has to be fetched.
*/
open fun fetchVideoUrl(video: Video): Observable<String> {
return client.newCall(videoUrlRequest(video))
.asObservableSuccess()
.map { videoUrlParse(it) }
}
/**
* Returns the request for getting the url to the source image. Override only if it's needed to
* override the url, send different headers or request method like POST.
*
* @param page the chapter whose page list has to be fetched
*/
protected open fun videoUrlRequest(video: Video): Request {
return GET(video.url, headers)
}
/**
* Parses the response from the site and returns the absolute url to the source image.
*
* @param response the response from the site.
*/
protected abstract fun videoUrlParse(response: Response): String
/**
* Returns an observable with the response of the source image.
*
* @param page the page whose source image has to be downloaded.
*/
fun fetchVideo(video: Video): Observable<Response> {
return client.newCallWithProgress(videoRequest(video), video)
.asObservableSuccess()
}
/**
* Returns the request for getting the source image. Override only if it's needed to override
* the url, send different headers or request method like POST.
*
* @param video the video whose link has to be fetched
*/
protected open fun videoRequest(video: Video): Request {
return GET(video.videoUrl!!, headers)
}
/**
* Assigns the url of the episode without the scheme and domain. It saves some redundancy from
* database and the urls could still work after a domain change.
*
* @param url the full url to the episode.
*/
fun SEpisode.setUrlWithoutDomain(url: String) {
this.url = getUrlWithoutDomain(url)
}
/**
* Assigns the url of the anime without the scheme and domain. It saves some redundancy from
* database and the urls could still work after a domain change.
*
* @param url the full url to the anime.
*/
fun SAnime.setUrlWithoutDomain(url: String) {
this.url = getUrlWithoutDomain(url)
}
/**
* Returns the url of the given string without the scheme and domain.
*
* @param orig the full url.
*/
private fun getUrlWithoutDomain(orig: String): String {
return try {
val uri = URI(orig)
var out = uri.path
if (uri.query != null) {
out += "?" + uri.query
}
if (uri.fragment != null) {
out += "#" + uri.fragment
}
out
} catch (e: URISyntaxException) {
orig
}
}
/**
* Called before inserting a new episode into database. Use it if you need to override episode
* fields, like the title or the episode number. Do not change anything to [anime].
*
* @param episode the episode to be added.
* @param anime the anime of the episode.
*/
open fun prepareNewEpisode(episode: SEpisode, anime: SAnime) {
}
/**
* Returns the list of filters for the source.
*/
override fun getFilterList() = AnimeFilterList()
companion object {
const val DEFAULT_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36 Edg/88.0.705.63"
}
}

View File

@@ -1,25 +0,0 @@
package eu.kanade.tachiyomi.animesource.online
import eu.kanade.tachiyomi.animesource.model.Video
import rx.Observable
fun AnimeHttpSource.getVideoUrl(video: Video): Observable<Video> {
video.status = Video.LOAD_VIDEO
return fetchVideoUrl(video)
.doOnError { video.status = Video.ERROR }
.onErrorReturn { null }
.doOnNext { video.videoUrl = it }
.map { video }
}
fun AnimeHttpSource.fetchUrlFromVideo(video: Video): Observable<Video> {
return Observable.just(video)
.filter { !it.videoUrl.isNullOrEmpty() }
.mergeWith(fetchRemainingVideoUrlsFromVideoList(video))
}
fun AnimeHttpSource.fetchRemainingVideoUrlsFromVideoList(video: Video): Observable<Video> {
return Observable.just(video)
.filter { it.videoUrl.isNullOrEmpty() }
.concatMap { getVideoUrl(it) }
}

View File

@@ -1,206 +0,0 @@
package eu.kanade.tachiyomi.animesource.online
import eu.kanade.tachiyomi.animesource.model.AnimesPage
import eu.kanade.tachiyomi.animesource.model.SAnime
import eu.kanade.tachiyomi.animesource.model.SEpisode
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
/**
* A simple implementation for sources from a website using Jsoup, an HTML parser.
*/
abstract class ParsedAnimeHttpSource : AnimeHttpSource() {
/**
* Parses the response from the site and returns a [AnimesPage] object.
*
* @param response the response from the site.
*/
override fun popularAnimeParse(response: Response): AnimesPage {
val document = response.asJsoup()
val animes = document.select(popularAnimeSelector()).map { element ->
popularAnimeFromElement(element)
}
val hasNextPage = popularAnimeNextPageSelector()?.let { selector ->
document.select(selector).first()
} != null
return AnimesPage(animes, hasNextPage)
}
/**
* Returns the Jsoup selector that returns a list of [Element] corresponding to each anime.
*/
protected abstract fun popularAnimeSelector(): String
/**
* Returns a anime from the given [element]. Most sites only show the title and the url, it's
* totally fine to fill only those two values.
*
* @param element an element obtained from [popularAnimeSelector].
*/
protected abstract fun popularAnimeFromElement(element: Element): SAnime
/**
* Returns the Jsoup selector that returns the <a> tag linking to the next page, or null if
* there's no next page.
*/
protected abstract fun popularAnimeNextPageSelector(): String?
/**
* Parses the response from the site and returns a [AnimesPage] object.
*
* @param response the response from the site.
*/
override fun searchAnimeParse(response: Response): AnimesPage {
val document = response.asJsoup()
val animes = document.select(searchAnimeSelector()).map { element ->
searchAnimeFromElement(element)
}
val hasNextPage = searchAnimeNextPageSelector()?.let { selector ->
document.select(selector).first()
} != null
return AnimesPage(animes, hasNextPage)
}
/**
* Returns the Jsoup selector that returns a list of [Element] corresponding to each anime.
*/
protected abstract fun searchAnimeSelector(): String
/**
* Returns a anime from the given [element]. Most sites only show the title and the url, it's
* totally fine to fill only those two values.
*
* @param element an element obtained from [searchAnimeSelector].
*/
protected abstract fun searchAnimeFromElement(element: Element): SAnime
/**
* Returns the Jsoup selector that returns the <a> tag linking to the next page, or null if
* there's no next page.
*/
protected abstract fun searchAnimeNextPageSelector(): String?
/**
* Parses the response from the site and returns a [AnimesPage] object.
*
* @param response the response from the site.
*/
override fun latestUpdatesParse(response: Response): AnimesPage {
val document = response.asJsoup()
val animes = document.select(latestUpdatesSelector()).map { element ->
latestUpdatesFromElement(element)
}
val hasNextPage = latestUpdatesNextPageSelector()?.let { selector ->
document.select(selector).first()
} != null
return AnimesPage(animes, hasNextPage)
}
/**
* Returns the Jsoup selector that returns a list of [Element] corresponding to each anime.
*/
protected abstract fun latestUpdatesSelector(): String
/**
* Returns a anime from the given [element]. Most sites only show the title and the url, it's
* totally fine to fill only those two values.
*
* @param element an element obtained from [latestUpdatesSelector].
*/
protected abstract fun latestUpdatesFromElement(element: Element): SAnime
/**
* Returns the Jsoup selector that returns the <a> tag linking to the next page, or null if
* there's no next page.
*/
protected abstract fun latestUpdatesNextPageSelector(): String?
/**
* Parses the response from the site and returns the details of a anime.
*
* @param response the response from the site.
*/
override fun animeDetailsParse(response: Response): SAnime {
return animeDetailsParse(response.asJsoup())
}
/**
* Returns the details of the anime from the given [document].
*
* @param document the parsed document.
*/
protected abstract fun animeDetailsParse(document: Document): SAnime
/**
* Parses the response from the site and returns a list of episodes.
*
* @param response the response from the site.
*/
override fun episodeListParse(response: Response): List<SEpisode> {
val document = response.asJsoup()
return document.select(episodeListSelector()).map { episodeFromElement(it) }
}
/**
* Returns the Jsoup selector that returns a list of [Element] corresponding to each episode.
*/
protected abstract fun episodeListSelector(): String
/**
* Returns a episode from the given element.
*
* @param element an element obtained from [episodeListSelector].
*/
protected abstract fun episodeFromElement(element: Element): SEpisode
/**
* Parses the response from the site and returns the page list.
*
* @param response the response from the site.
*/
override fun videoListParse(response: Response): List<Video> {
val document = response.asJsoup()
return document.select(videoListSelector()).map { videoFromElement(it) }
}
/**
* Returns the Jsoup selector that returns a list of [Element] corresponding to each video.
*/
protected abstract fun videoListSelector(): String
/**
* Returns a video from the given element.
*
* @param element an element obtained from [videoListSelector].
*/
protected abstract fun videoFromElement(element: Element): Video
/**
* Parse the response from the site and returns the absolute url to the source video.
*
* @param response the response from the site.
*/
override fun videoUrlParse(response: Response): String {
return videoUrlParse(response.asJsoup())
}
/**
* Returns the absolute url to the source image from the document.
*
* @param document the parsed document.
*/
protected abstract fun videoUrlParse(document: Document): String
}