diff --git a/app/build.gradle b/app/build.gradle index 97c05e9b..53cb1299 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -88,9 +88,9 @@ android { dependencies { // FireBase - googleImplementation platform('com.google.firebase:firebase-bom:32.7.4') - googleImplementation 'com.google.firebase:firebase-analytics-ktx:21.5.1' - googleImplementation 'com.google.firebase:firebase-crashlytics-ktx:18.6.2' + googleImplementation platform('com.google.firebase:firebase-bom:32.8.1') + googleImplementation 'com.google.firebase:firebase-analytics-ktx:21.6.2' + googleImplementation 'com.google.firebase:firebase-crashlytics-ktx:18.6.4' // Core implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'androidx.browser:browser:1.8.0' @@ -118,7 +118,7 @@ dependencies { implementation 'jp.wasabeef:glide-transformations:4.3.0' // Exoplayer - ext.exo_version = '1.3.0' + ext.exo_version = '1.3.1' implementation "androidx.media3:media3-exoplayer:$exo_version" implementation "androidx.media3:media3-ui:$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 'androidx.paging:paging-runtime-ktx:3.2.1' implementation 'com.github.eltos:simpledialogfragments:v3.7' - implementation 'com.github.AAChartModel:AAChartCore-Kotlin:93972bc' + implementation 'com.github.AAChartModel:AAChartCore-Kotlin:7.2.1' // Markwon ext.markwon_version = '4.6.2' diff --git a/app/src/main/java/ani/dantotsu/parsers/AniyomiAdapter.kt b/app/src/main/java/ani/dantotsu/parsers/AniyomiAdapter.kt index 823c2d73..0b59fe14 100644 --- a/app/src/main/java/ani/dantotsu/parsers/AniyomiAdapter.kt +++ b/app/src/main/java/ani/dantotsu/parsers/AniyomiAdapter.kt @@ -245,17 +245,19 @@ class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() { } as? AnimeHttpSource ?: (extension.sources[sourceLanguage] as? AnimeCatalogueSource ?: return emptyList()) return try { - val res = source.fetchSearchAnime(1, query, source.getFilterList()).awaitSingle() + val res = source.getSearchAnime(1, query, source.getFilterList()) Logger.log("query: $query") convertAnimesPageToShowResponse(res) } catch (e: CloudflareBypassException) { Logger.log("Exception in search: $e") + Logger.log(e) withContext(Dispatchers.Main) { snackString("Failed to bypass Cloudflare") } emptyList() } catch (e: Exception) { Logger.log("General exception in search: $e") + Logger.log(e) emptyList() } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/animesource/AnimeCatalogueSource.kt b/app/src/main/java/eu/kanade/tachiyomi/animesource/AnimeCatalogueSource.kt index 67d99256..57700e55 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/animesource/AnimeCatalogueSource.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/animesource/AnimeCatalogueSource.kt @@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.animesource import eu.kanade.tachiyomi.animesource.model.AnimeFilterList import eu.kanade.tachiyomi.animesource.model.AnimesPage +import eu.kanade.tachiyomi.util.lang.awaitSingle import rx.Observable interface AnimeCatalogueSource : AnimeSource { @@ -17,30 +18,63 @@ interface AnimeCatalogueSource : AnimeSource { 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. */ - fun fetchPopularAnime(page: Int): Observable + @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 query the search query. * @param filters the list of filters to apply. */ - fun fetchSearchAnime(page: Int, query: String, filters: AnimeFilterList): Observable + @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. */ - fun fetchLatestUpdates(page: Int): Observable + @Suppress("DEPRECATION") + suspend fun getLatestUpdates(page: Int): AnimesPage { + return fetchLatestUpdates(page).awaitSingle() + } /** * Returns the list of filters for the source. */ 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 + + // 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 + + // 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 } diff --git a/app/src/main/java/eu/kanade/tachiyomi/animesource/online/AnimeHttpSource.kt b/app/src/main/java/eu/kanade/tachiyomi/animesource/online/AnimeHttpSource.kt index 51f593a9..61e8f5db 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/animesource/online/AnimeHttpSource.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/animesource/online/AnimeHttpSource.kt @@ -9,8 +9,11 @@ import eu.kanade.tachiyomi.animesource.model.Video import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.NetworkHelper 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.awaitSuccess import eu.kanade.tachiyomi.network.newCachelessCallWithProgress +import eu.kanade.tachiyomi.util.lang.awaitSingle import okhttp3.Headers import okhttp3.OkHttpClient import okhttp3.Request @@ -25,8 +28,8 @@ import java.util.concurrent.TimeUnit /** * A simple implementation for sources from a website. */ +@Suppress("unused") abstract class AnimeHttpSource : AnimeCatalogueSource { - /** * Network service. */ @@ -44,16 +47,16 @@ abstract class AnimeHttpSource : AnimeCatalogueSource { 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. + * ID of the source. By default it uses a generated id using the first 16 characters (64 bits) + * of the MD5 of the string `"${name.lowercase()}/$lang/$versionId"`. + * + * 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 { - 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 - } + override val id by lazy { generateId(name, lang, versionId) } /** * Headers used for requests. @@ -66,11 +69,34 @@ abstract class AnimeHttpSource : AnimeCatalogueSource { open val client: OkHttpClient 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. */ 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. */ + @Deprecated( + "Use the non-RxJava API instead", + ReplaceWith("getPopularAnime"), + ) override fun fetchPopularAnime(page: Int): Observable { return client.newCall(popularAnimeRequest(page)) .asObservableSuccess() @@ -114,11 +144,11 @@ abstract class AnimeHttpSource : AnimeCatalogueSource { * @param query the search query. * @param filters the list of filters to apply. */ - override fun fetchSearchAnime( - page: Int, - query: String, - filters: AnimeFilterList - ): Observable { + @Deprecated( + "Use the non-RxJava API instead", + ReplaceWith("getSearchAnime"), + ) + override fun fetchSearchAnime(page: Int, query: String, filters: AnimeFilterList): Observable { return Observable.defer { try { client.newCall(searchAnimeRequest(page, query, filters)).asObservableSuccess() @@ -140,11 +170,7 @@ abstract class AnimeHttpSource : AnimeCatalogueSource { * @param query the search query. * @param filters the list of filters to apply. */ - protected abstract fun searchAnimeRequest( - page: Int, - query: String, - filters: AnimeFilterList - ): Request + protected abstract fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request /** * 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. */ + @Deprecated( + "Use the non-RxJava API instead", + ReplaceWith("getLatestUpdates"), + ) override fun fetchLatestUpdates(page: Int): Observable { return client.newCall(latestUpdatesRequest(page)) .asObservableSuccess() @@ -181,11 +211,18 @@ abstract class AnimeHttpSource : AnimeCatalogueSource { protected abstract fun latestUpdatesParse(response: Response): AnimesPage /** - * Returns an observable with the updated details for a nanime. Normally it's not needed to - * override this method. + * Get the updated details for a anime. + * Normally it's not needed to override this method. * * @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 { return client.newCall(animeDetailsRequest(anime)) .asObservableSuccess() @@ -212,11 +249,23 @@ abstract class AnimeHttpSource : AnimeCatalogueSource { protected abstract fun animeDetailsParse(response: Response): SAnime /** - * Returns an observable with the updated episode list for an anime. Normally it's not needed to - * override this method. If an anime is licensed an empty episode list observable is returned + * Get all the available episodes for an anime. + * 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 { + 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> { return if (anime.status != SAnime.LICENSED) { client.newCall(episodeListRequest(anime)) @@ -225,7 +274,7 @@ abstract class AnimeHttpSource : AnimeCatalogueSource { episodeListParse(response) } } 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 /** - * 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