From d60ff529e23633b69080ba913acc24b91dd5a46a Mon Sep 17 00:00:00 2001 From: Secozzi <49240133+Secozzi@users.noreply.github.com> Date: Wed, 29 Nov 2023 15:38:28 +0000 Subject: [PATCH] feat(en/allanimechi): Add option to use hoster names & fix gogo (#2575) --- src/en/allanimechi/build.gradle | 2 +- .../en/allanimechi/AllAnimeChi.kt | 88 ++++++++++++++----- .../extractors/InternalExtractor.kt | 56 +++++++++--- 3 files changed, 109 insertions(+), 37 deletions(-) diff --git a/src/en/allanimechi/build.gradle b/src/en/allanimechi/build.gradle index 298526f506..3bbc0239b7 100644 --- a/src/en/allanimechi/build.gradle +++ b/src/en/allanimechi/build.gradle @@ -8,7 +8,7 @@ ext { extName = 'AllAnimeChi' pkgNameSuffix = 'en.allanimechi' extClass = '.AllAnimeChi' - extVersionCode = 1 + extVersionCode = 2 libVersion = '13' } diff --git a/src/en/allanimechi/src/eu/kanade/tachiyomi/animeextension/en/allanimechi/AllAnimeChi.kt b/src/en/allanimechi/src/eu/kanade/tachiyomi/animeextension/en/allanimechi/AllAnimeChi.kt index c4ee7130e0..57067b39c7 100644 --- a/src/en/allanimechi/src/eu/kanade/tachiyomi/animeextension/en/allanimechi/AllAnimeChi.kt +++ b/src/en/allanimechi/src/eu/kanade/tachiyomi/animeextension/en/allanimechi/AllAnimeChi.kt @@ -38,6 +38,7 @@ import kotlinx.serialization.json.putJsonArray import kotlinx.serialization.json.putJsonObject import okhttp3.Headers import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response @@ -46,6 +47,7 @@ import rx.Observable import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy +import java.util.Locale class AllAnimeChi : ConfigurableAnimeSource, AnimeHttpSource() { @@ -252,8 +254,9 @@ class AllAnimeChi : ConfigurableAnimeSource, AnimeHttpSource() { return GET(url, apiHeaders) } + // TODO: replace with getAnimeUrl when new ext-lib is available override fun animeDetailsRequest(anime: SAnime): Request { - return GET("data:text/plain,This%20extension%20does%20not%20exist%20as%20a%20website%21") + return GET("data:text/plain,This%20extension%20does%20not%20have%20a%20website.") } override fun animeDetailsParse(response: Response): SAnime { @@ -308,7 +311,7 @@ class AllAnimeChi : ConfigurableAnimeSource, AnimeHttpSource() { // ============================ Video Links ============================= - private val internalExtractor by lazy { InternalExtractor(client, apiHeaders) } + private val internalExtractor by lazy { InternalExtractor(client, apiHeaders, headers) } override fun videoListRequest(episode: SEpisode): Request { val variables = episode.url @@ -334,6 +337,7 @@ class AllAnimeChi : ConfigurableAnimeSource, AnimeHttpSource() { val hosterBlackList = preferences.getHosterBlacklist val altHosterBlackList = preferences.getAltHosterBlacklist + val useHosterNames = preferences.useHosterName val serverList = videoJson.data.episode.sourceUrls.mapNotNull { video -> when { @@ -359,26 +363,32 @@ class AllAnimeChi : ConfigurableAnimeSource, AnimeHttpSource() { } return prioritySort( - serverList.parallelCatchingFlatMap(::getVideoFromServer), + serverList.parallelCatchingFlatMap { getVideoFromServer(it, useHosterNames) }, ) } - private fun getVideoFromServer(server: Server): List> { + private fun getVideoFromServer(server: Server, useHosterName: Boolean): List> { return when (server.type) { - "player" -> getFromPlayer(server) - "internal" -> internalExtractor.videosFromServer(server, removeRaw = preferences.removeRaw) - "external" -> getFromExternal(server) + "player" -> getFromPlayer(server, useHosterName) + "internal" -> internalExtractor.videosFromServer(server, useHosterName, removeRaw = preferences.removeRaw) + "external" -> getFromExternal(server, useHosterName) else -> emptyList() } } - private fun getFromPlayer(server: Server): List> { + private fun getFromPlayer(server: Server, useHosterName: Boolean): List> { + val name = if (useHosterName) { + getHostName(server.sourceUrl, server.sourceName) + } else { + server.sourceName + } + val videoHeaders = headers.newBuilder().apply { add("origin", siteUrl) add("referer", "$siteUrl/") }.build() - val video = Video(server.sourceUrl, server.sourceName, server.sourceUrl, headers = videoHeaders) + val video = Video(server.sourceUrl, name, server.sourceUrl, headers = videoHeaders) return listOf( Pair(video, server.priority), ) @@ -392,9 +402,14 @@ class AllAnimeChi : ConfigurableAnimeSource, AnimeHttpSource() { private val allanimeExtractor by lazy { AllAnimeExtractor(client, headers) } private val streamwishExtractor by lazy { StreamWishExtractor(client, headers) } - private fun getFromExternal(server: Server): List> { + private fun getFromExternal(server: Server, useHosterName: Boolean): List> { val url = server.sourceUrl.replace(Regex("""^//"""), "https://") - val prefix = "${server.sourceName} - " + val prefix = if (useHosterName) { + "${getHostName(url, server.sourceName)} - " + } else { + "${server.sourceName} - " + } + val videoList = when { url.startsWith("https://ok") -> okruExtractor.videosFromUrl(url, prefix = prefix) url.startsWith("https://filemoon") -> filemoonExtractor.videosFromUrl(url, prefix = prefix) @@ -411,6 +426,14 @@ class AllAnimeChi : ConfigurableAnimeSource, AnimeHttpSource() { // ============================= Utilities ============================== + private fun getHostName(host: String, fallback: String): String { + return host.toHttpUrlOrNull()?.host?.split(".")?.let { + it.getOrNull(it.size - 2)?.replaceFirstChar { c -> + if (c.isLowerCase()) c.titlecase(Locale.ROOT) else c.toString() + } + } ?: fallback + } + private fun String.decodeBase64(): String { return String(Base64.decode(this, Base64.DEFAULT)) } @@ -468,6 +491,8 @@ class AllAnimeChi : ConfigurableAnimeSource, AnimeHttpSource() { } companion object { + private const val PAGE_SIZE = 30 // number of items to retrieve when calling API + private const val POPULAR_HASH = "31a117653812a2547fd981632e8c99fa8bf8a75c4ef1a77a1567ef1741a7ab9c" private const val LATEST_HASH = "e42a4466d984b2c0a2cecae5dd13aa68867f634b16ee0f17b380047d14482406" private const val DETAILS_HASH = "bb263f91e5bdd048c1c978f324613aeccdfe2cbc694a419466a31edb58c0cc0b" @@ -492,8 +517,6 @@ class AllAnimeChi : ConfigurableAnimeSource, AnimeHttpSource() { "Yt-mp4", ) - private const val PAGE_SIZE = 30 // number of items to retrieve when calling API - private const val PREF_HOSTER_BLACKLIST_KEY = "pref_hoster_blacklist" private val PREF_HOSTER_BLACKLIST_ENTRY_VALUES = INTERNAL_HOSTER_NAMES.map { it.lowercase().substringBefore(" (") @@ -542,6 +565,9 @@ class AllAnimeChi : ConfigurableAnimeSource, AnimeHttpSource() { private const val PREF_SUB_KEY = "preferred_sub" private const val PREF_SUB_DEFAULT = "sub" + + private const val PREF_USE_HOSTER_NAMES_KEY = "use_host_prefix" + private const val PREF_USE_HOSTER_NAMES_DEFAULT = false } // ============================== Settings ============================== @@ -588,17 +614,6 @@ class AllAnimeChi : ConfigurableAnimeSource, AnimeHttpSource() { } }.also(screen::addPreference) - SwitchPreferenceCompat(screen.context).apply { - key = PREF_REMOVE_RAW_KEY - title = "Attempt to filter out raw" - setDefaultValue(PREF_REMOVE_RAW_DEFAULT) - - setOnPreferenceChangeListener { _, newValue -> - val new = newValue as Boolean - preferences.edit().putBoolean(key, new).commit() - } - }.also(screen::addPreference) - ListPreference(screen.context).apply { key = PREF_QUALITY_KEY title = "Preferred quality" @@ -646,6 +661,28 @@ class AllAnimeChi : ConfigurableAnimeSource, AnimeHttpSource() { preferences.edit().putString(key, entry).commit() } }.also(screen::addPreference) + + SwitchPreferenceCompat(screen.context).apply { + key = PREF_REMOVE_RAW_KEY + title = "Attempt to filter out raw" + setDefaultValue(PREF_REMOVE_RAW_DEFAULT) + + setOnPreferenceChangeListener { _, newValue -> + val new = newValue as Boolean + preferences.edit().putBoolean(key, new).commit() + } + }.also(screen::addPreference) + + SwitchPreferenceCompat(screen.context).apply { + key = PREF_USE_HOSTER_NAMES_KEY + title = "Use names of video hoster" + setDefaultValue(PREF_USE_HOSTER_NAMES_DEFAULT) + + setOnPreferenceChangeListener { _, newValue -> + val new = newValue as Boolean + preferences.edit().putBoolean(key, new).commit() + } + }.also(screen::addPreference) } private val SharedPreferences.subPref @@ -668,4 +705,7 @@ class AllAnimeChi : ConfigurableAnimeSource, AnimeHttpSource() { private val SharedPreferences.removeRaw get() = getBoolean(PREF_REMOVE_RAW_KEY, PREF_REMOVE_RAW_DEFAULT) + + private val SharedPreferences.useHosterName + get() = getBoolean(PREF_USE_HOSTER_NAMES_KEY, PREF_USE_HOSTER_NAMES_DEFAULT) } diff --git a/src/en/allanimechi/src/eu/kanade/tachiyomi/animeextension/en/allanimechi/extractors/InternalExtractor.kt b/src/en/allanimechi/src/eu/kanade/tachiyomi/animeextension/en/allanimechi/extractors/InternalExtractor.kt index faa9fd2f49..6d41ec9d64 100644 --- a/src/en/allanimechi/src/eu/kanade/tachiyomi/animeextension/en/allanimechi/extractors/InternalExtractor.kt +++ b/src/en/allanimechi/src/eu/kanade/tachiyomi/animeextension/en/allanimechi/extractors/InternalExtractor.kt @@ -9,11 +9,13 @@ import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json import okhttp3.Headers import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.OkHttpClient import okhttp3.Response import uy.kohesive.injekt.injectLazy +import java.util.Locale -class InternalExtractor(private val client: OkHttpClient, private val apiHeaders: Headers) { +class InternalExtractor(private val client: OkHttpClient, private val apiHeaders: Headers, private val headers: Headers) { private val blogUrl = "aHR0cHM6Ly9ibG9nLmFsbGFuaW1lLnBybw==".decodeBase64() @@ -22,11 +24,11 @@ class InternalExtractor(private val client: OkHttpClient, private val apiHeaders "Dalvik/2.1.0 (Linux; U; Android 13; Pixel 5 Build/TQ3A.230705.001.B4)", ) - private val playlistUtils by lazy { PlaylistUtils(client, playlistHeaders) } + private val playlistUtils by lazy { PlaylistUtils(client, headers) } private val json: Json by injectLazy() - fun videosFromServer(server: AllAnimeChi.Server, removeRaw: Boolean): List> { + fun videosFromServer(server: AllAnimeChi.Server, useHosterName: Boolean, removeRaw: Boolean): List> { val blogHeaders = apiHeaders.newBuilder().apply { set("host", blogUrl.toHttpUrl().host) }.build() @@ -37,8 +39,8 @@ class InternalExtractor(private val client: OkHttpClient, private val apiHeaders val videoList = videoData.links.flatMap { when { - it.hls == true -> getFromHls(server, it, removeRaw) - it.mp4 == true -> getFromMp4(server, it) + it.hls == true -> getFromHls(server, it, useHosterName, removeRaw) + it.mp4 == true -> getFromMp4(server, it, useHosterName) else -> emptyList() } } @@ -46,20 +48,26 @@ class InternalExtractor(private val client: OkHttpClient, private val apiHeaders return videoList } - private fun getFromMp4(server: AllAnimeChi.Server, data: VideoData.LinkObject): List> { - val baseName = "${server.sourceName} - ${data.resolutionStr}" + private fun getFromMp4(server: AllAnimeChi.Server, data: VideoData.LinkObject, useHosterName: Boolean): List> { + val host = if (useHosterName) getHostName(data.link, server.sourceName) else server.sourceName + + val baseName = "$host - ${data.resolutionStr}" val video = Video(data.link, baseName, data.link, headers = playlistHeaders) return listOf( Pair(video, server.priority), ) } - private fun getFromHls(server: AllAnimeChi.Server, data: VideoData.LinkObject, removeRaw: Boolean): List> { + private fun getFromHls(server: AllAnimeChi.Server, data: VideoData.LinkObject, useHosterName: Boolean, removeRaw: Boolean): List> { if (removeRaw && data.resolutionStr.contains("raw", true)) return emptyList() + val host = if (useHosterName) getHostName(data.link, server.sourceName) else server.sourceName val linkHost = data.link.toHttpUrl().host + // Doesn't seem to work - if (server.sourceName.equals("Luf-mp4", true) && linkHost.contains("maverickki")) return emptyList() + if (server.sourceName.equals("Luf-mp4", true)) { + return getFromGogo(server, data, host) + } // Hardcode some names val baseName = if (linkHost.contains("crunchyroll")) { @@ -73,8 +81,7 @@ class InternalExtractor(private val client: OkHttpClient, private val apiHeaders .replace("vo_SUB", "Sub") .replace("SUB", "Sub") } else { - "${server.sourceName} - ${data.resolutionStr}" - .replace("Luf-mp4", "Luf-mp4 (gogo)") + "$host - ${data.resolutionStr}" } // Get stuff @@ -91,7 +98,7 @@ class InternalExtractor(private val client: OkHttpClient, private val apiHeaders val videoList = playlistUtils.extractFromHls( data.link, - videoNameGen = { q -> "$baseName - $q" }, + videoNameGen = { q -> "$baseName - ${data.resolutionStr} - $q" }, masterHeadersGen = { _, _ -> masterHeaders }, ) @@ -100,8 +107,33 @@ class InternalExtractor(private val client: OkHttpClient, private val apiHeaders } } + private fun getFromGogo(server: AllAnimeChi.Server, data: VideoData.LinkObject, hostName: String): List> { + val host = data.link.toHttpUrl().host + + // Seems to be dead + if (host.contains("maverickki", true)) return emptyList() + + val videoList = playlistUtils.extractFromHls( + data.link, + videoNameGen = { q -> "$hostName - ${data.resolutionStr} - $q" }, + referer = "https://playtaku.net/", + ) + + return videoList.map { + Pair(it, server.priority) + } + } + // ============================= Utilities ============================== + private fun getHostName(host: String, fallback: String): String { + return host.toHttpUrlOrNull()?.host?.split(".")?.let { + it.getOrNull(it.size - 2)?.replaceFirstChar { c -> + if (c.isLowerCase()) c.titlecase(Locale.ROOT) else c.toString() + } + } ?: fallback + } + @Serializable data class VideoData( val links: List,