feat(en/hanime): Episode grouping (#3072)
This commit is contained in:
parent
7449da50c5
commit
9af8932a69
@ -1,7 +1,7 @@
|
||||
ext {
|
||||
extName = 'hanime.tv'
|
||||
extClass = '.Hanime'
|
||||
extVersionCode = 17
|
||||
extVersionCode = 18
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,255 @@
|
||||
package eu.kanade.tachiyomi.animeextension.en.hanime
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
data class SearchParameters(
|
||||
val includedTags: ArrayList<String>,
|
||||
val blackListedTags: ArrayList<String>,
|
||||
val brands: ArrayList<String>,
|
||||
val tagsMode: String,
|
||||
val orderBy: String,
|
||||
val ordering: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class HAnimeResponse(
|
||||
val page: Long,
|
||||
val nbPages: Long,
|
||||
val nbHits: Long,
|
||||
val hitsPerPage: Long,
|
||||
val hits: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class HitsModel(
|
||||
val id: Long? = null,
|
||||
val name: String,
|
||||
val titles: List<String> = emptyList(),
|
||||
val slug: String? = null,
|
||||
val description: String? = null,
|
||||
val views: Long? = null,
|
||||
val interests: Long? = null,
|
||||
@SerialName("poster_url")
|
||||
val posterUrl: String? = null,
|
||||
@SerialName("cover_url")
|
||||
val coverUrl: String? = null,
|
||||
val brand: String? = null,
|
||||
@SerialName("brand_id")
|
||||
val brandId: Long? = null,
|
||||
@SerialName("duration_in_ms")
|
||||
val durationInMs: Long? = null,
|
||||
@SerialName("is_censored")
|
||||
val isCensored: Boolean? = false,
|
||||
val rating: Long? = null,
|
||||
val likes: Long? = null,
|
||||
val dislikes: Long? = null,
|
||||
val downloads: Long? = null,
|
||||
@SerialName("monthly_rank")
|
||||
val monthlyRank: Long? = null,
|
||||
val tags: List<String> = emptyList(),
|
||||
@SerialName("created_at")
|
||||
val createdAt: Long? = null,
|
||||
@SerialName("released_at")
|
||||
val releasedAt: Long? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class VideoModel(
|
||||
@SerialName("player_base_url")
|
||||
val playerBaseUrl: String? = null,
|
||||
@SerialName("hentai_video")
|
||||
val hentaiVideo: HentaiVideo? = HentaiVideo(),
|
||||
@SerialName("hentai_tags")
|
||||
val hentaiTags: List<HentaiTag>? = emptyList(),
|
||||
@SerialName("hentai_franchise_hentai_videos")
|
||||
val hentaiFranchiseHentaiVideos: List<HentaiFranchiseHentaiVideo>? = emptyList(),
|
||||
@SerialName("videos_manifest")
|
||||
val videosManifest: VideosManifest? = VideosManifest(),
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class HentaiVideo(
|
||||
val id: Long? = null,
|
||||
@SerialName("is_visible")
|
||||
val isVisible: Boolean? = false,
|
||||
val name: String? = null,
|
||||
val slug: String? = null,
|
||||
@SerialName("created_at")
|
||||
val createdAt: String? = null,
|
||||
@SerialName("released_at")
|
||||
val releasedAt: String? = null,
|
||||
val description: String? = null,
|
||||
val views: Long? = null,
|
||||
val interests: Long? = null,
|
||||
@SerialName("poster_url")
|
||||
val posterUrl: String? = null,
|
||||
@SerialName("cover_url")
|
||||
val coverUrl: String? = null,
|
||||
@SerialName("is_hard_subtitled")
|
||||
val isHardSubtitled: Boolean? = false,
|
||||
val brand: String? = null,
|
||||
@SerialName("duration_in_ms")
|
||||
val durationInMs: Long? = null,
|
||||
@SerialName("is_censored")
|
||||
val isCensored: Boolean? = false,
|
||||
val rating: Double? = null,
|
||||
val likes: Long? = null,
|
||||
val dislikes: Long? = null,
|
||||
val downloads: Long? = null,
|
||||
@SerialName("monthly_rank")
|
||||
val monthlyRank: Long? = null,
|
||||
@SerialName("brand_id")
|
||||
val brandId: String? = null,
|
||||
@SerialName("is_banned_in")
|
||||
val isBannedIn: String? = null,
|
||||
@SerialName("created_at_unix")
|
||||
val createdAtUnix: Long? = null,
|
||||
@SerialName("released_at_unix")
|
||||
val releasedAtUnix: Long? = null,
|
||||
@SerialName("hentai_tags")
|
||||
val hentaiTags: List<HentaiTag>? = emptyList(),
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class HentaiTag(
|
||||
val id: Long? = null,
|
||||
val text: String? = null,
|
||||
val count: Long? = null,
|
||||
val description: String? = null,
|
||||
@SerialName("wide_image_url")
|
||||
val wideImageUrl: String? = null,
|
||||
@SerialName("tall_image_url")
|
||||
val tallImageUrl: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class HentaiFranchiseHentaiVideo(
|
||||
val id: Long? = null,
|
||||
val name: String? = null,
|
||||
val slug: String? = null,
|
||||
@SerialName("created_at")
|
||||
val createdAt: String? = null,
|
||||
@SerialName("released_at")
|
||||
val releasedAt: String? = null,
|
||||
val views: Long? = null,
|
||||
val interests: Long? = null,
|
||||
@SerialName("poster_url")
|
||||
val posterUrl: String? = null,
|
||||
@SerialName("cover_url")
|
||||
val coverUrl: String? = null,
|
||||
@SerialName("is_hard_subtitled")
|
||||
val isHardSubtitled: Boolean? = false,
|
||||
val brand: String? = null,
|
||||
@SerialName("duration_in_ms")
|
||||
val durationInMs: Long? = null,
|
||||
@SerialName("is_censored")
|
||||
val isCensored: Boolean? = false,
|
||||
val rating: Double? = null,
|
||||
val likes: Long? = null,
|
||||
val dislikes: Long? = null,
|
||||
val downloads: Long? = null,
|
||||
@SerialName("monthly_rank")
|
||||
val monthlyRank: Long? = null,
|
||||
@SerialName("brand_id")
|
||||
val brandId: String? = null,
|
||||
@SerialName("is_banned_in")
|
||||
val isBannedIn: String? = null,
|
||||
@SerialName("created_at_unix")
|
||||
val createdAtUnix: Long? = null,
|
||||
@SerialName("released_at_unix")
|
||||
val releasedAtUnix: Long? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class VideosManifest(
|
||||
val servers: List<Server>? = emptyList(),
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class Server(
|
||||
val id: Long? = null,
|
||||
val name: String? = null,
|
||||
val slug: String? = null,
|
||||
@SerialName("na_rating")
|
||||
val naRating: Long? = null,
|
||||
@SerialName("eu_rating")
|
||||
val euRating: Long? = null,
|
||||
@SerialName("asia_rating")
|
||||
val asiaRating: Long? = null,
|
||||
val sequence: Long? = null,
|
||||
@SerialName("is_permanent")
|
||||
val isPermanent: Boolean? = false,
|
||||
val streams: List<Stream> = emptyList(),
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class Stream(
|
||||
val id: Long? = null,
|
||||
@SerialName("server_id")
|
||||
val serverId: Long? = null,
|
||||
val slug: String? = null,
|
||||
val kind: String? = null,
|
||||
val extension: String? = null,
|
||||
@SerialName("mime_type")
|
||||
val mimeType: String? = null,
|
||||
val width: Long? = null,
|
||||
val height: String,
|
||||
@SerialName("duration_in_ms")
|
||||
val durationInMs: Long? = null,
|
||||
@SerialName("filesize_mbs")
|
||||
val filesizeMbs: Long? = null,
|
||||
val filename: String? = null,
|
||||
val url: String,
|
||||
@SerialName("is_guest_allowed")
|
||||
val isGuestAllowed: Boolean? = false,
|
||||
@SerialName("is_member_allowed")
|
||||
val isMemberAllowed: Boolean? = false,
|
||||
@SerialName("is_premium_allowed")
|
||||
val isPremiumAllowed: Boolean? = false,
|
||||
@SerialName("is_downloadable")
|
||||
val isDownloadable: Boolean? = false,
|
||||
val compatibility: String? = null,
|
||||
@SerialName("hv_id")
|
||||
val hvId: Long? = null,
|
||||
@SerialName("server_sequence")
|
||||
val serverSequence: Long? = null,
|
||||
@SerialName("video_stream_group_id")
|
||||
val videoStreamGroupId: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class WindowNuxt(
|
||||
val state: State,
|
||||
) {
|
||||
@Serializable
|
||||
data class State(
|
||||
val data: Data,
|
||||
) {
|
||||
@Serializable
|
||||
data class Data(
|
||||
val video: DataVideo,
|
||||
) {
|
||||
@Serializable
|
||||
data class DataVideo(
|
||||
val videos_manifest: VideosManifest,
|
||||
) {
|
||||
@Serializable
|
||||
data class VideosManifest(
|
||||
val servers: List<Server>,
|
||||
) {
|
||||
@Serializable
|
||||
data class Server(
|
||||
val streams: List<Stream>,
|
||||
) {
|
||||
@Serializable
|
||||
data class Stream(
|
||||
val height: String,
|
||||
val url: String,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -15,16 +15,8 @@ import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.POST
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import eu.kanade.tachiyomi.util.parseAs
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonArray
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.int
|
||||
import kotlinx.serialization.json.jsonArray
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import kotlinx.serialization.json.long
|
||||
import okhttp3.Headers
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
@ -73,137 +65,106 @@ class Hanime : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
private val popularRequestHeaders =
|
||||
Headers.headersOf("authority", "search.htv-services.com", "accept", "application/json, text/plain, */*", "content-type", "application/json;charset=UTF-8")
|
||||
|
||||
override fun popularAnimeRequest(page: Int): Request {
|
||||
return POST("https://search.htv-services.com/", popularRequestHeaders, searchRequestBody("", page, AnimeFilterList()))
|
||||
}
|
||||
override fun popularAnimeRequest(page: Int): Request =
|
||||
POST("https://search.htv-services.com/", popularRequestHeaders, searchRequestBody("", page, AnimeFilterList()))
|
||||
|
||||
override fun popularAnimeParse(response: Response): AnimesPage {
|
||||
val responseString = response.body.string()
|
||||
return parseSearchJson(responseString)
|
||||
}
|
||||
private fun parseSearchJson(jsonLine: String?): AnimesPage {
|
||||
val jsonData = jsonLine ?: return AnimesPage(emptyList(), false)
|
||||
val jObject = json.decodeFromString<JsonObject>(jsonData)
|
||||
val nbPages = jObject["nbPages"]!!.jsonPrimitive.int
|
||||
val page = jObject["page"]!!.jsonPrimitive.int
|
||||
val hasNextPage = page < nbPages - 1
|
||||
val arrayString = jObject["hits"]!!.jsonPrimitive.content
|
||||
val array = json.decodeFromString<JsonArray>(arrayString)
|
||||
val animeList = mutableListOf<SAnime>()
|
||||
for (item in array) {
|
||||
val anime = SAnime.create()
|
||||
anime.title = item.jsonObject["name"]!!.jsonPrimitive.content
|
||||
anime.thumbnail_url = item.jsonObject["cover_url"]!!.jsonPrimitive.content
|
||||
anime.setUrlWithoutDomain("https://hanime.tv/videos/hentai/" + item.jsonObject["slug"]!!.jsonPrimitive.content)
|
||||
anime.author = item.jsonObject["brand"]!!.jsonPrimitive.content
|
||||
anime.description = item.jsonObject["description"]!!.jsonPrimitive.content.replace("<p>", "").replace("</p>", "")
|
||||
anime.status = SAnime.COMPLETED
|
||||
val tags = item.jsonObject["tags"]!!.jsonArray
|
||||
anime.genre = tags.joinToString(", ") { it.jsonPrimitive.content }
|
||||
anime.initialized = true
|
||||
animeList.add(anime)
|
||||
override fun popularAnimeParse(response: Response) = parseSearchJson(response)
|
||||
|
||||
private fun parseSearchJson(response: Response): AnimesPage {
|
||||
val jsonLine = response.body.string().ifEmpty { return AnimesPage(emptyList(), false) }
|
||||
|
||||
val jResponse = jsonLine.parseAs<HAnimeResponse>()
|
||||
val hasNextPage = jResponse.page < jResponse.nbPages - 1
|
||||
val array = jResponse.hits.parseAs<Array<HitsModel>>()
|
||||
|
||||
val animeList = array.groupBy { getTitle(it.name) }.map { (_, items) -> items.first() }.map { item ->
|
||||
SAnime.create().apply {
|
||||
title = getTitle(item.name)
|
||||
thumbnail_url = item.coverUrl
|
||||
author = item.brand
|
||||
description = item.description?.replace(Regex("<[^>]*>"), "")
|
||||
status = SAnime.UNKNOWN
|
||||
genre = item.tags.joinToString { it }
|
||||
initialized = true
|
||||
setUrlWithoutDomain("https://hanime.tv/videos/hentai/" + item.slug)
|
||||
}
|
||||
}
|
||||
|
||||
return AnimesPage(animeList, hasNextPage)
|
||||
}
|
||||
|
||||
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request = POST("https://search.htv-services.com/", popularRequestHeaders, searchRequestBody(query, page, filters))
|
||||
private fun isNumber(num: String) = (num.toIntOrNull() != null)
|
||||
|
||||
override fun searchAnimeParse(response: Response): AnimesPage {
|
||||
val responseString = response.body.string()
|
||||
return parseSearchJson(responseString)
|
||||
private fun getTitle(title: String): String {
|
||||
return if (title.contains(" Ep ")) {
|
||||
title.split(" Ep ")[0].trim()
|
||||
} else {
|
||||
if (isNumber(title.trim().split(" ").last())) {
|
||||
val split = title.trim().split(" ")
|
||||
split.slice(0..split.size - 2).joinToString(" ").trim()
|
||||
} else {
|
||||
title.trim()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request =
|
||||
POST("https://search.htv-services.com/", popularRequestHeaders, searchRequestBody(query, page, filters))
|
||||
|
||||
override fun searchAnimeParse(response: Response): AnimesPage = parseSearchJson(response)
|
||||
|
||||
override fun animeDetailsParse(response: Response): SAnime {
|
||||
val document = response.asJsoup()
|
||||
return SAnime.create().apply {
|
||||
title = document.select("h1.tv-title").text()
|
||||
title = getTitle(document.select("h1.tv-title").text())
|
||||
thumbnail_url = document.select("img.hvpi-cover").attr("src")
|
||||
setUrlWithoutDomain(document.location())
|
||||
author = document.select("a.hvpimbc-text").text()
|
||||
description = document.select("div.hvpist-description p")
|
||||
.joinToString("\n\n") { it.text() }
|
||||
status = SAnime.COMPLETED
|
||||
description = document.select("div.hvpist-description p").joinToString("\n\n") { it.text() }
|
||||
status = SAnime.UNKNOWN
|
||||
genre = document.select("div.hvpis-text div.btn__content").joinToString { it.text() }
|
||||
initialized = true
|
||||
setUrlWithoutDomain(document.location())
|
||||
}
|
||||
}
|
||||
|
||||
override fun videoListRequest(episode: SEpisode): Request {
|
||||
return GET(episode.url)
|
||||
}
|
||||
override fun videoListRequest(episode: SEpisode) = GET(episode.url)
|
||||
|
||||
override suspend fun getVideoList(episode: SEpisode): List<Video> {
|
||||
setAuthCookie()
|
||||
|
||||
if (authCookie != null) {
|
||||
return fetchVideoListPremium(episode)
|
||||
}
|
||||
|
||||
return super.getVideoList(episode)
|
||||
}
|
||||
|
||||
private fun fetchVideoListPremium(episode: SEpisode): List<Video> {
|
||||
val videoList = mutableListOf<Video>()
|
||||
val id = episode.url.substringAfter("?id=")
|
||||
val headers = headers.newBuilder()
|
||||
.add("cookie", authCookie!!)
|
||||
val document = client.newCall(
|
||||
GET("$baseUrl/videos/hentai/$id", headers = headers.build()),
|
||||
).execute().asJsoup()
|
||||
val data = document.selectFirst("script:containsData(__NUXT__)")!!.data()
|
||||
.substringAfter("__NUXT__=").substringBeforeLast(";")
|
||||
val parsed = json.decodeFromString<WindowNuxt>(data)
|
||||
parsed.state.data.video.videos_manifest.servers.forEach { server ->
|
||||
server.streams.forEach { stream ->
|
||||
videoList.add(
|
||||
Video(
|
||||
stream.url,
|
||||
stream.height + "p",
|
||||
stream.url,
|
||||
),
|
||||
)
|
||||
}
|
||||
val headers = headers.newBuilder().add("cookie", authCookie!!)
|
||||
val document = client.newCall(GET("$baseUrl/videos/hentai/$id", headers = headers.build())).execute().asJsoup()
|
||||
|
||||
val parsed = document.selectFirst("script:containsData(__NUXT__)")!!.data()
|
||||
.substringAfter("__NUXT__=").substringBeforeLast(";").parseAs<WindowNuxt>()
|
||||
|
||||
return parsed.state.data.video.videos_manifest.servers.flatMap { server ->
|
||||
server.streams.map { stream -> Video(stream.url, stream.height + "p", stream.url) }
|
||||
}
|
||||
return videoList
|
||||
}
|
||||
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val responseString = response.body.string()
|
||||
val jObject = json.decodeFromString<JsonObject>(responseString)
|
||||
val server = jObject["videos_manifest"]!!.jsonObject["servers"]!!.jsonArray[0].jsonObject
|
||||
val streams = server["streams"]!!.jsonArray
|
||||
val linkList = mutableListOf<Video>()
|
||||
for (stream in streams) {
|
||||
val streamObject = stream.jsonObject
|
||||
if (streamObject["kind"]!!.jsonPrimitive.content != "premium_alert") {
|
||||
linkList.add(
|
||||
Video(
|
||||
url = streamObject["url"]!!.jsonPrimitive.content,
|
||||
quality = streamObject["height"]!!.jsonPrimitive.content + "p",
|
||||
videoUrl = streamObject["url"]!!.jsonPrimitive.content,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
return linkList
|
||||
val responseString = response.body.string().ifEmpty { return emptyList() }
|
||||
return responseString.parseAs<VideoModel>().videosManifest?.servers?.get(0)?.streams?.filter { it.kind != "premium_alert" }?.map {
|
||||
Video(it.url, "${it.height}p", it.url)
|
||||
} ?: emptyList()
|
||||
}
|
||||
|
||||
override fun List<Video>.sort(): List<Video> {
|
||||
val quality = preferences.getString("preferred_quality", null)
|
||||
if (quality != null) {
|
||||
val newList = mutableListOf<Video>()
|
||||
var preferred = 0
|
||||
for (video in this) {
|
||||
if (video.quality.contains(quality)) {
|
||||
newList.add(preferred, video)
|
||||
preferred++
|
||||
} else {
|
||||
newList.add(video)
|
||||
}
|
||||
}
|
||||
return newList
|
||||
}
|
||||
return this
|
||||
val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
|
||||
return this.sortedWith(
|
||||
compareBy(
|
||||
{ it.quality.contains(quality) },
|
||||
{ Regex("""(\d+)p""").find(it.quality)?.groupValues?.get(1)?.toIntOrNull() ?: 0 },
|
||||
),
|
||||
).reversed()
|
||||
}
|
||||
|
||||
override fun episodeListRequest(anime: SAnime): Request {
|
||||
@ -212,14 +173,15 @@ class Hanime : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
}
|
||||
|
||||
override fun episodeListParse(response: Response): List<SEpisode> {
|
||||
val responseString = response.body.string()
|
||||
val jObject = json.decodeFromString<JsonObject>(responseString)
|
||||
val episode = SEpisode.create()
|
||||
episode.date_upload = jObject.jsonObject["hentai_video"]!!.jsonObject["released_at_unix"]!!.jsonPrimitive.long * 1000
|
||||
episode.name = jObject.jsonObject["hentai_video"]!!.jsonObject["name"]!!.jsonPrimitive.content
|
||||
episode.url = response.request.url.toString()
|
||||
episode.episode_number = 1F
|
||||
return listOf(episode)
|
||||
val responseString = response.body.string().ifEmpty { return emptyList() }
|
||||
return responseString.parseAs<VideoModel>().hentaiFranchiseHentaiVideos?.mapIndexed { idx, it ->
|
||||
SEpisode.create().apply {
|
||||
episode_number = idx + 1f
|
||||
name = "Episode ${idx + 1}"
|
||||
date_upload = (it.releasedAtUnix ?: 0) * 1000
|
||||
url = "$baseUrl/api/v8/video?id=${it.id}"
|
||||
}
|
||||
}?.reversed() ?: emptyList()
|
||||
}
|
||||
|
||||
private fun setAuthCookie() {
|
||||
@ -244,12 +206,9 @@ class Hanime : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
""".trimIndent().toRequestBody("application/json".toMediaType())
|
||||
}
|
||||
|
||||
override fun latestUpdatesRequest(page: Int): Request = POST("https://search.htv-services.com/", popularRequestHeaders, latestSearchRequestBody(page))
|
||||
override fun latestUpdatesRequest(page: Int) = POST("https://search.htv-services.com/", popularRequestHeaders, latestSearchRequestBody(page))
|
||||
|
||||
override fun latestUpdatesParse(response: Response): AnimesPage {
|
||||
val responseString = response.body.string()
|
||||
return parseSearchJson(responseString)
|
||||
}
|
||||
override fun latestUpdatesParse(response: Response) = parseSearchJson(response)
|
||||
|
||||
// Filters
|
||||
override fun getFilterList(): AnimeFilterList = AnimeFilterList(
|
||||
@ -257,23 +216,13 @@ class Hanime : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
BrandList(getBrands()),
|
||||
SortFilter(sortableList.map { it.first }.toTypedArray()),
|
||||
TagInclusionMode(),
|
||||
|
||||
)
|
||||
|
||||
internal class Tag(val id: String, name: String) : AnimeFilter.TriState(name)
|
||||
internal class Brand(val id: String, name: String) : AnimeFilter.CheckBox(name)
|
||||
private class TagList(tags: List<Tag>) : AnimeFilter.Group<Tag>("Tags", tags)
|
||||
private class BrandList(brands: List<Brand>) : AnimeFilter.Group<Brand>("Brands", brands)
|
||||
private class TagInclusionMode :
|
||||
AnimeFilter.Select<String>("Included tags mode", arrayOf("And", "Or"), 0)
|
||||
|
||||
data class SearchParameters(
|
||||
val includedTags: ArrayList<String>,
|
||||
val blackListedTags: ArrayList<String>,
|
||||
val brands: ArrayList<String>,
|
||||
val tagsMode: String,
|
||||
val orderBy: String,
|
||||
val ordering: String,
|
||||
)
|
||||
private class TagInclusionMode : AnimeFilter.Select<String>("Included tags mode", arrayOf("And", "Or"), 0)
|
||||
|
||||
private fun getSearchParameters(filters: AnimeFilterList): SearchParameters {
|
||||
val includedTags = ArrayList<String>()
|
||||
@ -567,13 +516,19 @@ class Hanime : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
class SortFilter(sortables: Array<String>) : AnimeFilter.Sort("Sort", sortables, Selection(2, false))
|
||||
|
||||
// Preferences
|
||||
companion object {
|
||||
private const val PREF_QUALITY_KEY = "preferred_quality"
|
||||
private const val PREF_QUALITY_DEFAULT = "1080p"
|
||||
private val QUALITY_LIST = arrayOf("1080p", "720p", "480p", "360p")
|
||||
}
|
||||
|
||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||
val videoQualityPref = ListPreference(screen.context).apply {
|
||||
key = "preferred_quality"
|
||||
key = PREF_QUALITY_KEY
|
||||
title = "Preferred quality"
|
||||
entries = arrayOf("1080p", "720p", "480p", "360p")
|
||||
entryValues = arrayOf("1080", "720", "480", "360")
|
||||
setDefaultValue("720")
|
||||
entries = QUALITY_LIST
|
||||
entryValues = QUALITY_LIST
|
||||
setDefaultValue(PREF_QUALITY_DEFAULT)
|
||||
summary = "%s"
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
@ -585,40 +540,4 @@ class Hanime : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
}
|
||||
screen.addPreference(videoQualityPref)
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class WindowNuxt(
|
||||
val state: State,
|
||||
) {
|
||||
@Serializable
|
||||
data class State(
|
||||
val data: Data,
|
||||
) {
|
||||
@Serializable
|
||||
data class Data(
|
||||
val video: DataVideo,
|
||||
) {
|
||||
@Serializable
|
||||
data class DataVideo(
|
||||
val videos_manifest: VideosManifest,
|
||||
) {
|
||||
@Serializable
|
||||
data class VideosManifest(
|
||||
val servers: List<Server>,
|
||||
) {
|
||||
@Serializable
|
||||
data class Server(
|
||||
val streams: List<Stream>,
|
||||
) {
|
||||
@Serializable
|
||||
data class Stream(
|
||||
val height: String,
|
||||
val url: String,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user