chore: update extension api
This commit is contained in:
parent
bf33f5d9c8
commit
126bc6134e
6 changed files with 229 additions and 97 deletions
|
@ -88,9 +88,9 @@ android {
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
||||||
// FireBase
|
// FireBase
|
||||||
googleImplementation platform('com.google.firebase:firebase-bom:32.7.4')
|
googleImplementation platform('com.google.firebase:firebase-bom:32.8.1')
|
||||||
googleImplementation 'com.google.firebase:firebase-analytics-ktx:21.5.1'
|
googleImplementation 'com.google.firebase:firebase-analytics-ktx:21.6.2'
|
||||||
googleImplementation 'com.google.firebase:firebase-crashlytics-ktx:18.6.2'
|
googleImplementation 'com.google.firebase:firebase-crashlytics-ktx:18.6.4'
|
||||||
// Core
|
// Core
|
||||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||||
implementation 'androidx.browser:browser:1.8.0'
|
implementation 'androidx.browser:browser:1.8.0'
|
||||||
|
@ -118,7 +118,7 @@ dependencies {
|
||||||
implementation 'jp.wasabeef:glide-transformations:4.3.0'
|
implementation 'jp.wasabeef:glide-transformations:4.3.0'
|
||||||
|
|
||||||
// Exoplayer
|
// Exoplayer
|
||||||
ext.exo_version = '1.3.0'
|
ext.exo_version = '1.3.1'
|
||||||
implementation "androidx.media3:media3-exoplayer:$exo_version"
|
implementation "androidx.media3:media3-exoplayer:$exo_version"
|
||||||
implementation "androidx.media3:media3-ui:$exo_version"
|
implementation "androidx.media3:media3-ui:$exo_version"
|
||||||
implementation "androidx.media3:media3-exoplayer-hls:$exo_version"
|
implementation "androidx.media3:media3-exoplayer-hls:$exo_version"
|
||||||
|
@ -138,7 +138,7 @@ dependencies {
|
||||||
implementation 'com.github.VipulOG:ebook-reader:0.1.6'
|
implementation 'com.github.VipulOG:ebook-reader:0.1.6'
|
||||||
implementation 'androidx.paging:paging-runtime-ktx:3.2.1'
|
implementation 'androidx.paging:paging-runtime-ktx:3.2.1'
|
||||||
implementation 'com.github.eltos:simpledialogfragments:v3.7'
|
implementation 'com.github.eltos:simpledialogfragments:v3.7'
|
||||||
implementation 'com.github.AAChartModel:AAChartCore-Kotlin:93972bc'
|
implementation 'com.github.AAChartModel:AAChartCore-Kotlin:7.2.1'
|
||||||
|
|
||||||
// Markwon
|
// Markwon
|
||||||
ext.markwon_version = '4.6.2'
|
ext.markwon_version = '4.6.2'
|
||||||
|
|
|
@ -245,17 +245,19 @@ class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() {
|
||||||
} as? AnimeHttpSource ?: (extension.sources[sourceLanguage] as? AnimeCatalogueSource
|
} as? AnimeHttpSource ?: (extension.sources[sourceLanguage] as? AnimeCatalogueSource
|
||||||
?: return emptyList())
|
?: return emptyList())
|
||||||
return try {
|
return try {
|
||||||
val res = source.fetchSearchAnime(1, query, source.getFilterList()).awaitSingle()
|
val res = source.getSearchAnime(1, query, source.getFilterList())
|
||||||
Logger.log("query: $query")
|
Logger.log("query: $query")
|
||||||
convertAnimesPageToShowResponse(res)
|
convertAnimesPageToShowResponse(res)
|
||||||
} catch (e: CloudflareBypassException) {
|
} catch (e: CloudflareBypassException) {
|
||||||
Logger.log("Exception in search: $e")
|
Logger.log("Exception in search: $e")
|
||||||
|
Logger.log(e)
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
snackString("Failed to bypass Cloudflare")
|
snackString("Failed to bypass Cloudflare")
|
||||||
}
|
}
|
||||||
emptyList()
|
emptyList()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Logger.log("General exception in search: $e")
|
Logger.log("General exception in search: $e")
|
||||||
|
Logger.log(e)
|
||||||
emptyList()
|
emptyList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.animesource
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
||||||
import eu.kanade.tachiyomi.animesource.model.AnimesPage
|
import eu.kanade.tachiyomi.animesource.model.AnimesPage
|
||||||
|
import eu.kanade.tachiyomi.util.lang.awaitSingle
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
|
|
||||||
interface AnimeCatalogueSource : AnimeSource {
|
interface AnimeCatalogueSource : AnimeSource {
|
||||||
|
@ -17,30 +18,63 @@ interface AnimeCatalogueSource : AnimeSource {
|
||||||
val supportsLatest: Boolean
|
val supportsLatest: Boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an observable containing a page with a list of anime.
|
* Get a page with a list of anime.
|
||||||
*
|
*
|
||||||
|
* @since extensions-lib 1.5
|
||||||
* @param page the page number to retrieve.
|
* @param page the page number to retrieve.
|
||||||
*/
|
*/
|
||||||
fun fetchPopularAnime(page: Int): Observable<AnimesPage>
|
@Suppress("DEPRECATION")
|
||||||
|
suspend fun getPopularAnime(page: Int): AnimesPage {
|
||||||
|
return fetchPopularAnime(page).awaitSingle()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an observable containing a page with a list of anime.
|
* Get a page with a list of anime.
|
||||||
*
|
*
|
||||||
|
* @since extensions-lib 1.5
|
||||||
* @param page the page number to retrieve.
|
* @param page the page number to retrieve.
|
||||||
* @param query the search query.
|
* @param query the search query.
|
||||||
* @param filters the list of filters to apply.
|
* @param filters the list of filters to apply.
|
||||||
*/
|
*/
|
||||||
fun fetchSearchAnime(page: Int, query: String, filters: AnimeFilterList): Observable<AnimesPage>
|
@Suppress("DEPRECATION")
|
||||||
|
suspend fun getSearchAnime(page: Int, query: String, filters: AnimeFilterList): AnimesPage {
|
||||||
|
return fetchSearchAnime(page, query, filters).awaitSingle()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an observable containing a page with a list of latest anime updates.
|
* Get a page with a list of latest anime updates.
|
||||||
*
|
*
|
||||||
|
* @since extensions-lib 1.5
|
||||||
* @param page the page number to retrieve.
|
* @param page the page number to retrieve.
|
||||||
*/
|
*/
|
||||||
fun fetchLatestUpdates(page: Int): Observable<AnimesPage>
|
@Suppress("DEPRECATION")
|
||||||
|
suspend fun getLatestUpdates(page: Int): AnimesPage {
|
||||||
|
return fetchLatestUpdates(page).awaitSingle()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the list of filters for the source.
|
* Returns the list of filters for the source.
|
||||||
*/
|
*/
|
||||||
fun getFilterList(): AnimeFilterList
|
fun getFilterList(): AnimeFilterList
|
||||||
|
|
||||||
|
// Should be replaced as soon as Anime Extension reach 1.5
|
||||||
|
@Deprecated(
|
||||||
|
"Use the non-RxJava API instead",
|
||||||
|
ReplaceWith("getPopularAnime"),
|
||||||
|
)
|
||||||
|
fun fetchPopularAnime(page: Int): Observable<AnimesPage>
|
||||||
|
|
||||||
|
// Should be replaced as soon as Anime Extension reach 1.5
|
||||||
|
@Deprecated(
|
||||||
|
"Use the non-RxJava API instead",
|
||||||
|
ReplaceWith("getSearchAnime"),
|
||||||
|
)
|
||||||
|
fun fetchSearchAnime(page: Int, query: String, filters: AnimeFilterList): Observable<AnimesPage>
|
||||||
|
|
||||||
|
// Should be replaced as soon as Anime Extension reach 1.5
|
||||||
|
@Deprecated(
|
||||||
|
"Use the non-RxJava API instead",
|
||||||
|
ReplaceWith("getLatestUpdates"),
|
||||||
|
)
|
||||||
|
fun fetchLatestUpdates(page: Int): Observable<AnimesPage>
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,8 +9,11 @@ import eu.kanade.tachiyomi.animesource.model.Video
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||||
import eu.kanade.tachiyomi.network.NetworkHelper.Companion.defaultUserAgentProvider
|
import eu.kanade.tachiyomi.network.NetworkHelper.Companion.defaultUserAgentProvider
|
||||||
|
import eu.kanade.tachiyomi.network.ProgressListener
|
||||||
import eu.kanade.tachiyomi.network.asObservableSuccess
|
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||||
|
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||||
import eu.kanade.tachiyomi.network.newCachelessCallWithProgress
|
import eu.kanade.tachiyomi.network.newCachelessCallWithProgress
|
||||||
|
import eu.kanade.tachiyomi.util.lang.awaitSingle
|
||||||
import okhttp3.Headers
|
import okhttp3.Headers
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
|
@ -25,8 +28,8 @@ import java.util.concurrent.TimeUnit
|
||||||
/**
|
/**
|
||||||
* A simple implementation for sources from a website.
|
* A simple implementation for sources from a website.
|
||||||
*/
|
*/
|
||||||
|
@Suppress("unused")
|
||||||
abstract class AnimeHttpSource : AnimeCatalogueSource {
|
abstract class AnimeHttpSource : AnimeCatalogueSource {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Network service.
|
* Network service.
|
||||||
*/
|
*/
|
||||||
|
@ -44,16 +47,16 @@ abstract class AnimeHttpSource : AnimeCatalogueSource {
|
||||||
open val versionId = 1
|
open val versionId = 1
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Id of the source. By default it uses a generated id using the first 16 characters (64 bits)
|
* 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
|
* of the MD5 of the string `"${name.lowercase()}/$lang/$versionId"`.
|
||||||
* Note the generated id sets the sign bit to 0.
|
*
|
||||||
|
* The ID is generated by the [generateId] function, which can be reused if needed
|
||||||
|
* to generate outdated IDs for cases where the source name or language needs to
|
||||||
|
* be changed but migrations can be avoided.
|
||||||
|
*
|
||||||
|
* Note: the generated ID sets the sign bit to `0`.
|
||||||
*/
|
*/
|
||||||
override val id by lazy {
|
override val id by lazy { generateId(name, lang, versionId) }
|
||||||
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.
|
* Headers used for requests.
|
||||||
|
@ -66,11 +69,34 @@ abstract class AnimeHttpSource : AnimeCatalogueSource {
|
||||||
open val client: OkHttpClient
|
open val client: OkHttpClient
|
||||||
get() = network.client
|
get() = network.client
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a unique ID for the source based on the provided [name], [lang] and
|
||||||
|
* [versionId]. It will use the first 16 characters (64 bits) of the MD5 of the string
|
||||||
|
* `"${name.lowercase()}/$lang/$versionId"`.
|
||||||
|
*
|
||||||
|
* Note: the generated ID sets the sign bit to `0`.
|
||||||
|
*
|
||||||
|
* Can be used to generate outdated IDs, such as when the source name or language
|
||||||
|
* needs to be changed but migrations can be avoided.
|
||||||
|
*
|
||||||
|
* @since extensions-lib 1.5
|
||||||
|
* @param name [String] the name of the source
|
||||||
|
* @param lang [String] the language of the source
|
||||||
|
* @param versionId [Int] the version ID of the source
|
||||||
|
* @return a unique ID for the source
|
||||||
|
*/
|
||||||
|
@Suppress("MemberVisibilityCanBePrivate")
|
||||||
|
protected fun generateId(name: String, lang: String, versionId: Int): Long {
|
||||||
|
val key = "${name.lowercase()}/$lang/$versionId"
|
||||||
|
val bytes = MessageDigest.getInstance("MD5").digest(key.toByteArray())
|
||||||
|
return (0..7).map { bytes[it].toLong() and 0xff shl 8 * (7 - it) }.reduce(Long::or) and Long.MAX_VALUE
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Headers builder for requests. Implementations can override this method for custom headers.
|
* Headers builder for requests. Implementations can override this method for custom headers.
|
||||||
*/
|
*/
|
||||||
protected open fun headersBuilder() = Headers.Builder().apply {
|
protected open fun headersBuilder() = Headers.Builder().apply {
|
||||||
add("User-Agent", defaultUserAgentProvider())
|
add("User-Agent", NetworkHelper.defaultUserAgentProvider())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -84,6 +110,10 @@ abstract class AnimeHttpSource : AnimeCatalogueSource {
|
||||||
*
|
*
|
||||||
* @param page the page number to retrieve.
|
* @param page the page number to retrieve.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated(
|
||||||
|
"Use the non-RxJava API instead",
|
||||||
|
ReplaceWith("getPopularAnime"),
|
||||||
|
)
|
||||||
override fun fetchPopularAnime(page: Int): Observable<AnimesPage> {
|
override fun fetchPopularAnime(page: Int): Observable<AnimesPage> {
|
||||||
return client.newCall(popularAnimeRequest(page))
|
return client.newCall(popularAnimeRequest(page))
|
||||||
.asObservableSuccess()
|
.asObservableSuccess()
|
||||||
|
@ -114,11 +144,11 @@ abstract class AnimeHttpSource : AnimeCatalogueSource {
|
||||||
* @param query the search query.
|
* @param query the search query.
|
||||||
* @param filters the list of filters to apply.
|
* @param filters the list of filters to apply.
|
||||||
*/
|
*/
|
||||||
override fun fetchSearchAnime(
|
@Deprecated(
|
||||||
page: Int,
|
"Use the non-RxJava API instead",
|
||||||
query: String,
|
ReplaceWith("getSearchAnime"),
|
||||||
filters: AnimeFilterList
|
)
|
||||||
): Observable<AnimesPage> {
|
override fun fetchSearchAnime(page: Int, query: String, filters: AnimeFilterList): Observable<AnimesPage> {
|
||||||
return Observable.defer {
|
return Observable.defer {
|
||||||
try {
|
try {
|
||||||
client.newCall(searchAnimeRequest(page, query, filters)).asObservableSuccess()
|
client.newCall(searchAnimeRequest(page, query, filters)).asObservableSuccess()
|
||||||
|
@ -140,11 +170,7 @@ abstract class AnimeHttpSource : AnimeCatalogueSource {
|
||||||
* @param query the search query.
|
* @param query the search query.
|
||||||
* @param filters the list of filters to apply.
|
* @param filters the list of filters to apply.
|
||||||
*/
|
*/
|
||||||
protected abstract fun searchAnimeRequest(
|
protected abstract fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request
|
||||||
page: Int,
|
|
||||||
query: String,
|
|
||||||
filters: AnimeFilterList
|
|
||||||
): Request
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses the response from the site and returns a [AnimesPage] object.
|
* Parses the response from the site and returns a [AnimesPage] object.
|
||||||
|
@ -158,6 +184,10 @@ abstract class AnimeHttpSource : AnimeCatalogueSource {
|
||||||
*
|
*
|
||||||
* @param page the page number to retrieve.
|
* @param page the page number to retrieve.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated(
|
||||||
|
"Use the non-RxJava API instead",
|
||||||
|
ReplaceWith("getLatestUpdates"),
|
||||||
|
)
|
||||||
override fun fetchLatestUpdates(page: Int): Observable<AnimesPage> {
|
override fun fetchLatestUpdates(page: Int): Observable<AnimesPage> {
|
||||||
return client.newCall(latestUpdatesRequest(page))
|
return client.newCall(latestUpdatesRequest(page))
|
||||||
.asObservableSuccess()
|
.asObservableSuccess()
|
||||||
|
@ -181,11 +211,18 @@ abstract class AnimeHttpSource : AnimeCatalogueSource {
|
||||||
protected abstract fun latestUpdatesParse(response: Response): AnimesPage
|
protected abstract fun latestUpdatesParse(response: Response): AnimesPage
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an observable with the updated details for a nanime. Normally it's not needed to
|
* Get the updated details for a anime.
|
||||||
* override this method.
|
* Normally it's not needed to override this method.
|
||||||
*
|
*
|
||||||
* @param anime the anime to be updated.
|
* @param anime the anime to be updated.
|
||||||
|
* @return the updated anime.
|
||||||
*/
|
*/
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
override suspend fun getAnimeDetails(anime: SAnime): SAnime {
|
||||||
|
return fetchAnimeDetails(anime).awaitSingle()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getAnimeDetails"))
|
||||||
override fun fetchAnimeDetails(anime: SAnime): Observable<SAnime> {
|
override fun fetchAnimeDetails(anime: SAnime): Observable<SAnime> {
|
||||||
return client.newCall(animeDetailsRequest(anime))
|
return client.newCall(animeDetailsRequest(anime))
|
||||||
.asObservableSuccess()
|
.asObservableSuccess()
|
||||||
|
@ -212,11 +249,23 @@ abstract class AnimeHttpSource : AnimeCatalogueSource {
|
||||||
protected abstract fun animeDetailsParse(response: Response): SAnime
|
protected abstract fun animeDetailsParse(response: Response): SAnime
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an observable with the updated episode list for an anime. Normally it's not needed to
|
* Get all the available episodes for an anime.
|
||||||
* override this method. If an anime is licensed an empty episode list observable is returned
|
* Normally it's not needed to override this method.
|
||||||
*
|
*
|
||||||
* @param anime the anime to look for episodes.
|
* @param anime the anime to update.
|
||||||
|
* @return the chapters for the manga.
|
||||||
|
* @throws LicensedEntryItemsException if a anime is licensed and therefore no episodes are available.
|
||||||
*/
|
*/
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
override suspend fun getEpisodeList(anime: SAnime): List<SEpisode> {
|
||||||
|
if (anime.status == SAnime.LICENSED) {
|
||||||
|
throw LicensedEntryItemsException()
|
||||||
|
}
|
||||||
|
|
||||||
|
return fetchEpisodeList(anime).awaitSingle()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getEpisodeList"))
|
||||||
override fun fetchEpisodeList(anime: SAnime): Observable<List<SEpisode>> {
|
override fun fetchEpisodeList(anime: SAnime): Observable<List<SEpisode>> {
|
||||||
return if (anime.status != SAnime.LICENSED) {
|
return if (anime.status != SAnime.LICENSED) {
|
||||||
client.newCall(episodeListRequest(anime))
|
client.newCall(episodeListRequest(anime))
|
||||||
|
@ -225,7 +274,7 @@ abstract class AnimeHttpSource : AnimeCatalogueSource {
|
||||||
episodeListParse(response)
|
episodeListParse(response)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Observable.error(Exception("Licensed - No episodes to show"))
|
Observable.error(LicensedEntryItemsException())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -247,10 +296,25 @@ abstract class AnimeHttpSource : AnimeCatalogueSource {
|
||||||
protected abstract fun episodeListParse(response: Response): List<SEpisode>
|
protected abstract fun episodeListParse(response: Response): List<SEpisode>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an observable with the page list for a chapter.
|
* Parses the response from the site and returns a SEpisode Object.
|
||||||
*
|
*
|
||||||
* @param episode the episode whose video list has to be fetched.
|
* @param response the response from the site.
|
||||||
*/
|
*/
|
||||||
|
protected abstract fun episodeVideoParse(response: Response): SEpisode
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the list of videos a episode has. Videos should be returned
|
||||||
|
* in the expected order; the index is ignored.
|
||||||
|
*
|
||||||
|
* @param episode the episode.
|
||||||
|
* @return the videos for the episode.
|
||||||
|
*/
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
override suspend fun getVideoList(episode: SEpisode): List<Video> {
|
||||||
|
return fetchVideoList(episode).awaitSingle()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getVideoList"))
|
||||||
override fun fetchVideoList(episode: SEpisode): Observable<List<Video>> {
|
override fun fetchVideoList(episode: SEpisode): Observable<List<Video>> {
|
||||||
return client.newCall(videoListRequest(episode))
|
return client.newCall(videoListRequest(episode))
|
||||||
.asObservableSuccess()
|
.asObservableSuccess()
|
||||||
|
@ -287,8 +351,15 @@ abstract class AnimeHttpSource : AnimeCatalogueSource {
|
||||||
* Returns an observable with the page containing the source url of the image. If there's any
|
* 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.
|
* error, it will return null instead of throwing an exception.
|
||||||
*
|
*
|
||||||
|
* @since extensions-lib 1.5
|
||||||
* @param video the video whose source image has to be fetched.
|
* @param video the video whose source image has to be fetched.
|
||||||
*/
|
*/
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
open suspend fun getVideoUrl(video: Video): String {
|
||||||
|
return fetchVideoUrl(video).awaitSingle()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getVideoUrl"))
|
||||||
open fun fetchVideoUrl(video: Video): Observable<String> {
|
open fun fetchVideoUrl(video: Video): Observable<String> {
|
||||||
return client.newCall(videoUrlRequest(video))
|
return client.newCall(videoUrlRequest(video))
|
||||||
.asObservableSuccess()
|
.asObservableSuccess()
|
||||||
|
@ -313,37 +384,80 @@ abstract class AnimeHttpSource : AnimeCatalogueSource {
|
||||||
protected abstract fun videoUrlParse(response: Response): String
|
protected abstract fun videoUrlParse(response: Response): String
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an observable with the response of the source image.
|
* Returns the response of the source video.
|
||||||
|
* Typically does not need to be overridden.
|
||||||
*
|
*
|
||||||
* @param video the page whose source image has to be downloaded.
|
* @since extensions-lib 1.5
|
||||||
|
* @param request the http request for the video that has to be downloaded.
|
||||||
|
* @param listener the progress listener that has to be attached to the http request
|
||||||
*/
|
*/
|
||||||
fun fetchVideo(video: Video): Observable<Response> {
|
suspend fun getVideo(
|
||||||
val animeDownloadClient = client.newBuilder()
|
request: Request,
|
||||||
.callTimeout(30, TimeUnit.MINUTES)
|
listener: ProgressListener,
|
||||||
.build()
|
): Response {
|
||||||
return animeDownloadClient.newCachelessCallWithProgress(
|
return client.newCachelessCallWithProgress(request, listener)
|
||||||
videoRequest(
|
.awaitSuccess()
|
||||||
video,
|
}
|
||||||
video.totalBytesDownloaded
|
|
||||||
), video
|
fun getVideoSize(
|
||||||
)
|
video: Video,
|
||||||
.asObservableSuccess()
|
tries: Int,
|
||||||
|
): Long {
|
||||||
|
val headers = Headers.Builder().addAll(video.headers ?: headers).add("Range", "bytes=0-1").build()
|
||||||
|
val request = GET(video.videoUrl!!, headers)
|
||||||
|
val response = client.newCall(request).execute()
|
||||||
|
// parse the response headers to get the size of the video, in particular the content-range header
|
||||||
|
val contentRange = response.header("Content-Range")
|
||||||
|
if (contentRange != null) {
|
||||||
|
return contentRange.split("/")[1].toLong()
|
||||||
|
}
|
||||||
|
if (tries > 0) {
|
||||||
|
return getVideoSize(video, tries - 1)
|
||||||
|
}
|
||||||
|
return -1L
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the request for getting the source image. Override only if it's needed to override
|
* Returns the request for getting the source image, with range header attributes. Override only if it's needed to override
|
||||||
* the url, send different headers or request method like POST.
|
* the url, send different headers or request method like POST.
|
||||||
*
|
*
|
||||||
|
* If end is over start than the request is a range request
|
||||||
|
* If end if equal or less than start then the request is initial-point request
|
||||||
|
*
|
||||||
|
* @param video the video whose link has to be fetched
|
||||||
|
* @param start starting byte of chunk
|
||||||
|
* @param end ending byte of chunk
|
||||||
|
*/
|
||||||
|
fun videoRequest(
|
||||||
|
video: Video,
|
||||||
|
start: Long,
|
||||||
|
end: Long,
|
||||||
|
): Request {
|
||||||
|
val headers = video.headers ?: headers
|
||||||
|
val newHeaders =
|
||||||
|
if (end - start > 0L) {
|
||||||
|
Headers.Builder().addAll(headers).add("Range", "bytes=$start-$end").build()
|
||||||
|
} else if (start >= 0L) {
|
||||||
|
Headers.Builder().addAll(headers).add("Range", "bytes=$start-").build()
|
||||||
|
} else {
|
||||||
|
// logcat(LogPriority.ERROR) { "Error: end-start is less than 0" }
|
||||||
|
null
|
||||||
|
}
|
||||||
|
return GET(video.videoUrl!!, newHeaders ?: headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the request for getting the source image without range header attributes. 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
|
* @param video the video whose link has to be fetched
|
||||||
*/
|
*/
|
||||||
protected open fun videoRequest(video: Video, bytes: Long = 0L): Request {
|
|
||||||
val headers = video.headers ?: headers
|
fun safeVideoRequest(
|
||||||
val newHeaders = if (bytes > 0L) {
|
video: Video,
|
||||||
Headers.Builder().addAll(headers).add("Range", "bytes=$bytes-").build()
|
): Request {
|
||||||
} else {
|
return GET(video.videoUrl!!, video.headers ?: headers)
|
||||||
null
|
|
||||||
}
|
|
||||||
return GET(video.videoUrl!!, newHeaders ?: headers)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -405,8 +519,8 @@ abstract class AnimeHttpSource : AnimeCatalogueSource {
|
||||||
* @param episode the episode
|
* @param episode the episode
|
||||||
* @return url of the episode
|
* @return url of the episode
|
||||||
*/
|
*/
|
||||||
open fun getChapterUrl(episode: SEpisode): String {
|
open fun getEpisodeUrl(episode: SEpisode): String {
|
||||||
return episode.url.toString()
|
return episode.url
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -423,3 +537,5 @@ abstract class AnimeHttpSource : AnimeCatalogueSource {
|
||||||
*/
|
*/
|
||||||
override fun getFilterList() = AnimeFilterList()
|
override fun getFilterList() = AnimeFilterList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class LicensedEntryItemsException : RuntimeException()
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.animesource.online
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.animesource.model.Video
|
|
||||||
import rx.Observable
|
|
||||||
|
|
||||||
fun AnimeHttpSource.fetchUrlFromVideo(video: Video): Observable<Video> {
|
|
||||||
return Observable.just(video)
|
|
||||||
.filter { !it.videoUrl.isNullOrEmpty() }
|
|
||||||
.mergeWith(fetchRemainingVideoUrlsFromVideoList(video))
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun AnimeHttpSource.fetchRemainingVideoUrlsFromVideoList(video: Video): Observable<Video> {
|
|
||||||
return Observable.just(video)
|
|
||||||
.filter { it.videoUrl.isNullOrEmpty() }
|
|
||||||
.concatMap { getVideoUrl(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun AnimeHttpSource.getVideoUrl(video: Video): Observable<Video> {
|
|
||||||
video.status = Video.State.LOAD_VIDEO
|
|
||||||
return fetchVideoUrl(video)
|
|
||||||
.doOnError { video.status = Video.State.ERROR }
|
|
||||||
.onErrorReturn { null }
|
|
||||||
.doOnNext { video.videoUrl = it }
|
|
||||||
.map { video }
|
|
||||||
}
|
|
|
@ -10,6 +10,7 @@ import eu.kanade.tachiyomi.animesource.model.AnimesPage
|
||||||
import eu.kanade.tachiyomi.animesource.model.SAnime
|
import eu.kanade.tachiyomi.animesource.model.SAnime
|
||||||
import eu.kanade.tachiyomi.animesource.model.SEpisode
|
import eu.kanade.tachiyomi.animesource.model.SEpisode
|
||||||
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import tachiyomi.core.util.lang.withIOContext
|
import tachiyomi.core.util.lang.withIOContext
|
||||||
import tachiyomi.domain.entries.anime.model.Anime
|
import tachiyomi.domain.entries.anime.model.Anime
|
||||||
|
@ -35,17 +36,21 @@ class LocalAnimeSource(
|
||||||
override val supportsLatest = true
|
override val supportsLatest = true
|
||||||
|
|
||||||
// Browse related
|
// Browse related
|
||||||
|
override suspend fun getPopularAnime(page: Int) = getSearchAnime(page, "", POPULAR_FILTERS)
|
||||||
|
|
||||||
|
override suspend fun getLatestUpdates(page: Int) = getSearchAnime(page, "", LATEST_FILTERS)
|
||||||
|
|
||||||
|
@Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getPopularAnime"))
|
||||||
override fun fetchPopularAnime(page: Int) = fetchSearchAnime(page, "", POPULAR_FILTERS)
|
override fun fetchPopularAnime(page: Int) = fetchSearchAnime(page, "", POPULAR_FILTERS)
|
||||||
|
|
||||||
|
@Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getLatestUpdates"))
|
||||||
override fun fetchLatestUpdates(page: Int) = fetchSearchAnime(page, "", LATEST_FILTERS)
|
override fun fetchLatestUpdates(page: Int) = fetchSearchAnime(page, "", LATEST_FILTERS)
|
||||||
|
|
||||||
override fun fetchSearchAnime(
|
@Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getSearchAnime"))
|
||||||
page: Int,
|
override fun fetchSearchAnime(page: Int, query: String, filters: AnimeFilterList): Observable<AnimesPage> {
|
||||||
query: String,
|
return runBlocking {
|
||||||
filters: AnimeFilterList
|
Observable.just(getSearchAnime(page, query, filters))
|
||||||
): Observable<AnimesPage> {
|
}
|
||||||
//return emptyObservable()
|
|
||||||
return Observable.just(AnimesPage(emptyList(), false))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Anime details related
|
// Anime details related
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue