fix(it/animeunity): Update playlist name, remove no longer existent URL
argument, fix file name without proper extension (#2694)
This commit is contained in:
parent
328b0daff1
commit
d6d20c08ee
@ -6,7 +6,7 @@ ext {
|
||||
extName = 'AnimeUnity'
|
||||
pkgNameSuffix = 'it.animeunity'
|
||||
extClass = '.AnimeUnity'
|
||||
extVersionCode = 6
|
||||
extVersionCode = 7
|
||||
libVersion = '13'
|
||||
}
|
||||
|
||||
|
@ -36,8 +36,9 @@ import uy.kohesive.injekt.injectLazy
|
||||
import java.lang.Exception
|
||||
import java.text.SimpleDateFormat
|
||||
|
||||
class AnimeUnity : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
|
||||
class AnimeUnity :
|
||||
AnimeHttpSource(),
|
||||
ConfigurableAnimeSource {
|
||||
override val name = "AnimeUnity"
|
||||
|
||||
override val baseUrl by lazy { preferences.getString(PREF_DOMAIN_KEY, PREF_DOMAIN_DEFAULT)!! }
|
||||
@ -56,17 +57,19 @@ class AnimeUnity : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
|
||||
// ============================== Popular ===============================
|
||||
|
||||
override fun popularAnimeRequest(page: Int): Request =
|
||||
GET("$baseUrl/top-anime?popular=true&page=$page", headers = headers)
|
||||
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/top-anime?popular=true&page=$page", headers = headers)
|
||||
|
||||
override fun popularAnimeParse(response: Response): AnimesPage {
|
||||
val parsed = response.parseAs<AnimeResponse> {
|
||||
it.substringAfter("top-anime animes=\"")
|
||||
val parsed =
|
||||
response.parseAs<AnimeResponse> {
|
||||
it
|
||||
.substringAfter("top-anime animes=\"")
|
||||
.substringBefore("\"></top-anime>")
|
||||
.replace(""", "\"")
|
||||
}
|
||||
|
||||
val animeList = parsed.data.map { ani ->
|
||||
val animeList =
|
||||
parsed.data.map { ani ->
|
||||
SAnime.create().apply {
|
||||
title = ani.title_eng
|
||||
url = "${ani.id}-${ani.slug}"
|
||||
@ -84,7 +87,8 @@ class AnimeUnity : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
override fun latestUpdatesParse(response: Response): AnimesPage {
|
||||
val document = response.asJsoup()
|
||||
|
||||
val animeList = document.select("div.home-wrapper-body > div.row > div.latest-anime-container").map {
|
||||
val animeList =
|
||||
document.select("div.home-wrapper-body > div.row > div.latest-anime-container").map {
|
||||
SAnime.create().apply {
|
||||
title = it.select("a > strong").text()
|
||||
url = it.selectFirst("a")!!.attr("href").substringAfter("/anime/")
|
||||
@ -99,19 +103,34 @@ class AnimeUnity : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
|
||||
// =============================== Search ===============================
|
||||
|
||||
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request = throw Exception("Not used")
|
||||
override fun searchAnimeRequest(
|
||||
page: Int,
|
||||
query: String,
|
||||
filters: AnimeFilterList,
|
||||
): Request = throw Exception("Not used")
|
||||
|
||||
override fun fetchSearchAnime(page: Int, query: String, filters: AnimeFilterList): Observable<AnimesPage> {
|
||||
override fun fetchSearchAnime(
|
||||
page: Int,
|
||||
query: String,
|
||||
filters: AnimeFilterList,
|
||||
): Observable<AnimesPage> {
|
||||
val params = AnimeUnityFilters.getSearchParameters(filters)
|
||||
return client.newCall(searchAnimeRequest(page, query, params))
|
||||
return client
|
||||
.newCall(searchAnimeRequest(page, query, params))
|
||||
.asObservableSuccess()
|
||||
.map { response ->
|
||||
searchAnimeParse(response, page)
|
||||
}
|
||||
}
|
||||
|
||||
private fun searchAnimeRequest(page: Int, query: String, filters: AnimeUnityFilters.FilterSearchParams): Request {
|
||||
val archivioResponse = client.newCall(
|
||||
private fun searchAnimeRequest(
|
||||
page: Int,
|
||||
query: String,
|
||||
filters: AnimeUnityFilters.FilterSearchParams,
|
||||
): Request {
|
||||
val archivioResponse =
|
||||
client
|
||||
.newCall(
|
||||
GET("$baseUrl/archivio", headers = headers),
|
||||
).execute()
|
||||
|
||||
@ -121,25 +140,36 @@ class AnimeUnity : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
var newHeadersBuilder = headers.newBuilder()
|
||||
for (cookie in archivioResponse.headers) {
|
||||
if (cookie.first == "set-cookie" && cookie.second.startsWith("XSRF-TOKEN")) {
|
||||
newHeadersBuilder.add("X-XSRF-TOKEN", cookie.second.substringAfter("=").substringBefore(";").replace("%3D", "="))
|
||||
newHeadersBuilder.add(
|
||||
"X-XSRF-TOKEN",
|
||||
cookie
|
||||
.second
|
||||
.substringAfter("=")
|
||||
.substringBefore(";")
|
||||
.replace("%3D", "="),
|
||||
)
|
||||
}
|
||||
|
||||
if (cookie.first == "set-cookie" && cookie.second.startsWith("animeunity_session")) {
|
||||
newHeadersBuilder.add("Cookie", cookie.second.substringBefore(";").replace("%3D", "="))
|
||||
}
|
||||
}
|
||||
newHeadersBuilder.add("X-CSRF-TOKEN", crsfToken)
|
||||
newHeadersBuilder
|
||||
.add("X-CSRF-TOKEN", crsfToken)
|
||||
.add("Accept-Language", "en-US,en;q=0.5")
|
||||
.add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:101.0) Gecko/20100101 Firefox/101.0")
|
||||
|
||||
if (filters.top.isNotEmpty()) {
|
||||
val topHeaders = newHeadersBuilder.add("X-CSRF-TOKEN", crsfToken)
|
||||
val topHeaders =
|
||||
newHeadersBuilder
|
||||
.add("X-CSRF-TOKEN", crsfToken)
|
||||
.add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8")
|
||||
.add("Referer", "$baseUrl/${filters.top}")
|
||||
return GET("$baseUrl/${filters.top}", headers = topHeaders.build())
|
||||
}
|
||||
|
||||
val searchHeaders = newHeadersBuilder
|
||||
val searchHeaders =
|
||||
newHeadersBuilder
|
||||
.add("Accept", "application/json, text/plain, */*")
|
||||
.add("Content-Type", "application/json;charset=utf-8")
|
||||
.add("Origin", baseUrl)
|
||||
@ -147,7 +177,8 @@ class AnimeUnity : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
.add("X-Requested-With", "XMLHttpRequest")
|
||||
.build()
|
||||
|
||||
val body = """
|
||||
val body =
|
||||
"""
|
||||
{
|
||||
"title": ${query.falseIfEmpty()},
|
||||
"type": ${filters.type.falseIfEmpty()},
|
||||
@ -166,11 +197,15 @@ class AnimeUnity : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
|
||||
override fun searchAnimeParse(response: Response): AnimesPage = throw Exception("Not used")
|
||||
|
||||
private fun searchAnimeParse(response: Response, page: Int): AnimesPage {
|
||||
return if (response.request.method == "POST") {
|
||||
private fun searchAnimeParse(
|
||||
response: Response,
|
||||
page: Int,
|
||||
): AnimesPage =
|
||||
if (response.request.method == "POST") {
|
||||
val data = response.parseAs<SearchResponse>()
|
||||
|
||||
val animeList = data.records.map {
|
||||
val animeList =
|
||||
data.records.map {
|
||||
SAnime.create().apply {
|
||||
title = it.title_eng
|
||||
thumbnail_url = it.imageurl
|
||||
@ -182,7 +217,6 @@ class AnimeUnity : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
} else {
|
||||
popularAnimeParse(response)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getFilterList(): AnimeFilterList = AnimeUnityFilters.FILTER_LIST
|
||||
|
||||
@ -195,7 +229,8 @@ class AnimeUnity : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
|
||||
val videoPlayer = document.selectFirst("video-player[episodes_count]")!!
|
||||
|
||||
val animeDetails = json.decodeFromString<AnimeInfo>(
|
||||
val animeDetails =
|
||||
json.decodeFromString<AnimeInfo>(
|
||||
videoPlayer.attr("anime").replace(""", "\""),
|
||||
)
|
||||
|
||||
@ -204,7 +239,8 @@ class AnimeUnity : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
status = parseStatus(animeDetails.status)
|
||||
artist = animeDetails.studio ?: ""
|
||||
genre = animeDetails.genres.joinToString(", ") { it.name }
|
||||
description = buildString {
|
||||
description =
|
||||
buildString {
|
||||
append(animeDetails.plot)
|
||||
append("\n\nTipo: ${animeDetails.type}")
|
||||
append("\nStagione: ${animeDetails.season} ${animeDetails.date}")
|
||||
@ -225,14 +261,22 @@ class AnimeUnity : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
var newHeadersBuilder = headers.newBuilder()
|
||||
for (cookie in response.headers) {
|
||||
if (cookie.first == "set-cookie" && cookie.second.startsWith("XSRF-TOKEN")) {
|
||||
newHeadersBuilder.add("X-XSRF-TOKEN", cookie.second.substringAfter("=").substringBefore(";").replace("%3D", "="))
|
||||
newHeadersBuilder.add(
|
||||
"X-XSRF-TOKEN",
|
||||
cookie
|
||||
.second
|
||||
.substringAfter("=")
|
||||
.substringBefore(";")
|
||||
.replace("%3D", "="),
|
||||
)
|
||||
}
|
||||
|
||||
if (cookie.first == "set-cookie" && cookie.second.startsWith("animeunity_session")) {
|
||||
newHeadersBuilder.add("Cookie", cookie.second.substringBefore(";").replace("%3D", "="))
|
||||
}
|
||||
}
|
||||
newHeadersBuilder.add("X-CSRF-TOKEN", crsfToken)
|
||||
newHeadersBuilder
|
||||
.add("X-CSRF-TOKEN", crsfToken)
|
||||
.add("Content-Type", "application/json")
|
||||
.add("Referer", response.request.url.toString())
|
||||
.add("Accept", "application/json, text/plain, */*")
|
||||
@ -242,14 +286,22 @@ class AnimeUnity : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
|
||||
val videoPlayer = document.selectFirst("video-player[episodes_count]")!!
|
||||
val episodeCount = videoPlayer.attr("episodes_count").toInt()
|
||||
val animeId = response.request.url.toString().substringAfter("/anime/").substringBefore("-")
|
||||
val animeId =
|
||||
response
|
||||
.request
|
||||
.url
|
||||
.toString()
|
||||
.substringAfter("/anime/")
|
||||
.substringBefore("-")
|
||||
|
||||
val episodes = json.decodeFromString<List<Episode>>(
|
||||
val episodes =
|
||||
json.decodeFromString<List<Episode>>(
|
||||
videoPlayer.attr("episodes").replace(""", "\""),
|
||||
)
|
||||
|
||||
episodeList.addAll(
|
||||
episodes.filter {
|
||||
episodes
|
||||
.filter {
|
||||
it.id != null
|
||||
}.map {
|
||||
SEpisode.create().apply {
|
||||
@ -257,7 +309,10 @@ class AnimeUnity : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
date_upload = parseDate(it.created_at)
|
||||
episode_number = it.number.split("-")[0].toFloatOrNull() ?: 0F
|
||||
setUrlWithoutDomain(
|
||||
response.request.url.newBuilder()
|
||||
response
|
||||
.request
|
||||
.url
|
||||
.newBuilder()
|
||||
.addPathSegment(it.id.toString())
|
||||
.toString(),
|
||||
)
|
||||
@ -291,46 +346,64 @@ class AnimeUnity : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
|
||||
override fun fetchVideoList(episode: SEpisode): Observable<List<Video>> {
|
||||
val videoList = mutableListOf<Video>()
|
||||
|
||||
val doc = client.newCall(
|
||||
val doc =
|
||||
client
|
||||
.newCall(
|
||||
GET(baseUrl + episode.url, headers),
|
||||
).execute().asJsoup()
|
||||
val iframeUrl = doc.selectFirst("video-player[embed_url]")?.attr("abs:embed_url") ?: error("Failed to extract iframe")
|
||||
|
||||
val iframeHeaders = headers.newBuilder()
|
||||
).execute()
|
||||
.asJsoup()
|
||||
val iframeUrl =
|
||||
doc.selectFirst("video-player[embed_url]")?.attr("abs:embed_url")
|
||||
?: error("Failed to extract iframe")
|
||||
val iframeHeaders =
|
||||
headers
|
||||
.newBuilder()
|
||||
.add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8")
|
||||
.add("Host", iframeUrl.toHttpUrl().host)
|
||||
.add("Referer", "$baseUrl/")
|
||||
.build()
|
||||
|
||||
val iframe = client.newCall(
|
||||
val iframe =
|
||||
client
|
||||
.newCall(
|
||||
GET(iframeUrl, headers = iframeHeaders),
|
||||
).execute().asJsoup()
|
||||
val script = iframe.selectFirst("script:containsData(masterPlaylistParams)")!!.data()
|
||||
).execute()
|
||||
.asJsoup()
|
||||
val scripts = iframe.select("script")
|
||||
val script = scripts.find { it.data().contains("masterPlaylist") }!!.data().replace("\n", "\t")
|
||||
var playlistUrl = Regex("""url: ?'(.*?)'""").find(script)!!.groupValues[1]
|
||||
val filename = playlistUrl.slice(playlistUrl.lastIndexOf("/") + 1 until playlistUrl.length)
|
||||
if (!filename.endsWith(".m3u8")) {
|
||||
playlistUrl = playlistUrl.replace(filename, filename + ".m3u8")
|
||||
}
|
||||
|
||||
val playlistUrl = Regex("""masterPlaylistUrl.*?'(.*?)'""").find(script)!!.groupValues[1]
|
||||
val expires = Regex("""'expires': ?'(\d+)'""").find(script)!!.groupValues[1]
|
||||
val canCast = Regex("""'canCast': ?'(\d*)'""").find(script)!!.groupValues[1]
|
||||
val token = Regex("""'token': ?'([\w-]+)'""").find(script)!!.groupValues[1]
|
||||
|
||||
// Get subtitles
|
||||
val masterPlUrl = "$playlistUrl?token=$token&expires=$expires&canCast=$canCast&n=1"
|
||||
val masterPl = client.newCall(GET(masterPlUrl)).execute().body.string()
|
||||
val subList = Regex("""#EXT-X-MEDIA:TYPE=SUBTITLES.*?NAME="(.*?)".*?URI="(.*?)"""").findAll(masterPl).map {
|
||||
val masterPlUrl = "$playlistUrl?token=$token&expires=$expires&n=1"
|
||||
val masterPl =
|
||||
client
|
||||
.newCall(GET(masterPlUrl))
|
||||
.execute()
|
||||
.body
|
||||
.string()
|
||||
val subList =
|
||||
Regex("""#EXT-X-MEDIA:TYPE=SUBTITLES.*?NAME="(.*?)".*?URI="(.*?)"""")
|
||||
.findAll(masterPl)
|
||||
.map {
|
||||
Track(it.groupValues[2], it.groupValues[1])
|
||||
}.toList()
|
||||
|
||||
Regex("""'token(\d+p?)': ?'([\w-]+)'""").findAll(script).forEach { match ->
|
||||
val quality = match.groupValues[1]
|
||||
|
||||
val videoUrl = buildString {
|
||||
val videoUrl =
|
||||
buildString {
|
||||
append(playlistUrl)
|
||||
append("?type=video&rendition=")
|
||||
append(quality)
|
||||
append("&token=")
|
||||
append(match.groupValues[2])
|
||||
append("&expires=$expires")
|
||||
append("&canCast=$canCast")
|
||||
append("&n=1")
|
||||
}
|
||||
videoList.add(Video(videoUrl, quality, videoUrl, subtitleTracks = subList))
|
||||
@ -352,18 +425,29 @@ class AnimeUnity : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
return json.decodeFromString(responseBody)
|
||||
}
|
||||
|
||||
private fun parseStatus(statusString: String): Int = when (statusString) {
|
||||
private fun parseStatus(statusString: String): Int =
|
||||
when (statusString) {
|
||||
"In Corso" -> SAnime.ONGOING
|
||||
"Terminato" -> SAnime.COMPLETED
|
||||
else -> SAnime.UNKNOWN
|
||||
}
|
||||
|
||||
private fun addFromApi(start: Int, end: Int, animeId: String, headers: Headers, url: HttpUrl): List<SEpisode> {
|
||||
val response = client.newCall(
|
||||
private fun addFromApi(
|
||||
start: Int,
|
||||
end: Int,
|
||||
animeId: String,
|
||||
headers: Headers,
|
||||
url: HttpUrl,
|
||||
): List<SEpisode> {
|
||||
val response =
|
||||
client
|
||||
.newCall(
|
||||
GET("$baseUrl/info_api/$animeId/1?start_range=$start&end_range=$end", headers = headers),
|
||||
).execute()
|
||||
val json = response.parseAs<ApiResponse>()
|
||||
return json.episodes.filter {
|
||||
return json
|
||||
.episodes
|
||||
.filter {
|
||||
it.id != null
|
||||
}.map {
|
||||
SEpisode.create().apply {
|
||||
@ -371,7 +455,8 @@ class AnimeUnity : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
date_upload = parseDate(it.created_at)
|
||||
episode_number = it.number.split("-")[0].toFloatOrNull() ?: 0F
|
||||
setUrlWithoutDomain(
|
||||
url.newBuilder()
|
||||
url
|
||||
.newBuilder()
|
||||
.addPathSegment(it.id.toString())
|
||||
.toString(),
|
||||
)
|
||||
@ -379,7 +464,8 @@ class AnimeUnity : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.falseIfEmpty(): String = if (this.isEmpty()) {
|
||||
private fun String.falseIfEmpty(): String =
|
||||
if (this.isEmpty()) {
|
||||
"false"
|
||||
} else {
|
||||
"\"${this}\""
|
||||
@ -404,7 +490,8 @@ class AnimeUnity : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
override fun List<Video>.sort(): List<Video> {
|
||||
val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
|
||||
|
||||
return this.sortedWith(
|
||||
return this
|
||||
.sortedWith(
|
||||
compareBy(
|
||||
{ it.quality.contains(quality) },
|
||||
{ it.quality.substringBefore("p").toIntOrNull() ?: 0 },
|
||||
@ -425,7 +512,8 @@ class AnimeUnity : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
// ============================== Settings ==============================
|
||||
|
||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||
EditTextPreference(screen.context).apply {
|
||||
EditTextPreference(screen.context)
|
||||
.apply {
|
||||
key = PREF_DOMAIN_KEY
|
||||
title = PREF_DOMAIN_TITLE
|
||||
summary = PREF_DOMAIN_SUMMARY
|
||||
@ -440,7 +528,8 @@ class AnimeUnity : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
}
|
||||
}.also(screen::addPreference)
|
||||
|
||||
ListPreference(screen.context).apply {
|
||||
ListPreference(screen.context)
|
||||
.apply {
|
||||
key = PREF_QUALITY_KEY
|
||||
title = "Preferred quality"
|
||||
entries = arrayOf("1080p", "720p", "480p", "360p", "240p", "80p")
|
||||
|
Loading…
x
Reference in New Issue
Block a user