AniPlay: New source (#115)
12
src/en/aniplay/build.gradle
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
ext {
|
||||||
|
extName = 'AniPlay'
|
||||||
|
extClass = '.AniPlay'
|
||||||
|
extVersionCode = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "$rootDir/common.gradle"
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(project(":lib-multisrc:anilist"))
|
||||||
|
implementation(project(":lib:playlist-utils"))
|
||||||
|
}
|
BIN
src/en/aniplay/ic_launcher-playstore.png
Normal file
After Width: | Height: | Size: 14 KiB |
36
src/en/aniplay/res/drawable/ic_launcher_foreground.xml
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="300"
|
||||||
|
android:viewportHeight="300">
|
||||||
|
<group android:scaleX="0.6"
|
||||||
|
android:scaleY="0.6"
|
||||||
|
android:translateX="60"
|
||||||
|
android:translateY="60">
|
||||||
|
<group>
|
||||||
|
<clip-path
|
||||||
|
android:pathData="M28.21,115.4h243.58v69.19h-243.58z"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M45.85,116.85H58.59L76.21,167.96H64.07L60.6,156.51H43.77L40.31,167.96H28.21L45.85,116.85ZM58.14,148.4L52.21,128.73L46.21,148.4H58.14Z"
|
||||||
|
android:fillColor="#ffffff"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M115.7,167.96H104.21V147.15C104.21,144.71 103.74,142.93 102.8,141.84C102.35,141.29 101.79,140.86 101.14,140.57C100.5,140.29 99.8,140.15 99.1,140.18C97.97,140.19 96.86,140.48 95.86,141.01C94.73,141.57 93.71,142.35 92.86,143.29C91.99,144.27 91.31,145.39 90.86,146.62V167.96H79.27V130.6H89.63V136.98C90.58,135.51 91.79,134.24 93.21,133.21C94.73,132.14 96.42,131.34 98.21,130.84C100.2,130.28 102.26,130.01 104.33,130.02C106.39,129.93 108.44,130.38 110.27,131.34C111.72,132.14 112.92,133.33 113.73,134.77C114.52,136.19 115.06,137.74 115.31,139.35C115.57,140.93 115.7,142.53 115.71,144.13L115.7,167.96Z"
|
||||||
|
android:fillColor="#ffffff"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M119.33,125.85V115.4H130.85V125.85H119.33ZM119.33,167.96V130.16H130.85V167.96H119.33Z"
|
||||||
|
android:fillColor="#ffffff"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M158.67,168.68C156.08,168.75 153.53,168.12 151.26,166.88C149.18,165.72 147.45,164.01 146.26,161.95V183.3H134.77V130.6H144.77V136.76C146.1,134.63 147.98,132.88 150.2,131.7C152.43,130.51 154.92,129.94 157.44,130.02C159.85,130 162.24,130.51 164.44,131.51C166.56,132.5 168.46,133.89 170.02,135.63C171.62,137.4 172.87,139.48 173.69,141.73C174.57,144.13 175.01,146.68 174.99,149.24C175.04,152.67 174.33,156.06 172.9,159.18C171.63,161.99 169.63,164.4 167.1,166.18C164.62,167.86 161.67,168.74 158.67,168.68ZM154.79,158.96C155.98,158.98 157.15,158.7 158.21,158.16C159.24,157.63 160.15,156.9 160.91,156.02C161.69,155.11 162.29,154.05 162.67,152.91C163.08,151.71 163.29,150.45 163.28,149.18C163.29,147.91 163.06,146.65 162.6,145.48C162.15,144.37 161.51,143.35 160.69,142.48C159.88,141.62 158.9,140.93 157.81,140.48C156.67,140 155.45,139.76 154.21,139.77C153.42,139.78 152.63,139.89 151.87,140.12C151.09,140.35 150.33,140.68 149.64,141.12C148.95,141.55 148.3,142.08 147.73,142.66C147.14,143.27 146.66,143.97 146.29,144.74V152.4C146.8,153.63 147.51,154.75 148.38,155.74C149.23,156.7 150.24,157.49 151.38,158.07C152.43,158.64 153.6,158.95 154.79,158.96Z"
|
||||||
|
android:fillColor="#CDAAF5"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M176.43,115.76H187.95V154.93C187.84,156.12 188.14,157.32 188.81,158.32C189.12,158.65 189.5,158.92 189.92,159.09C190.35,159.27 190.8,159.34 191.26,159.32C192.01,159.31 192.74,159.2 193.46,158.99C194.15,158.8 194.82,158.55 195.46,158.24L199.32,167.1C197.23,167.94 195.04,168.52 192.81,168.82C190.79,169.04 188.77,169.1 186.74,169.04C183.48,169.04 180.95,168.16 179.15,166.4C177.35,164.65 176.45,162.16 176.45,158.95L176.43,115.76Z"
|
||||||
|
android:fillColor="#CDAAF5"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M198.59,156.84C198.55,154.52 199.29,152.26 200.68,150.4C202.18,148.47 204.17,146.96 206.44,146.04C209.1,144.92 211.97,144.36 214.86,144.4C216.38,144.4 217.9,144.53 219.4,144.79C220.74,145.02 222.04,145.4 223.29,145.91V144.29C223.34,143.43 223.2,142.57 222.88,141.77C222.55,140.98 222.05,140.26 221.42,139.68C220.17,138.61 218.28,138.08 215.73,138.09C213.7,138.07 211.68,138.44 209.79,139.16C207.73,139.99 205.76,141.03 203.92,142.26L200.46,134.91C202.88,133.32 205.51,132.09 208.28,131.24C211.07,130.42 213.97,130.01 216.88,130.02C222.54,130.02 226.94,131.36 230.09,134.02C233.24,136.69 234.81,140.54 234.81,145.57V155.24C234.73,156.06 234.92,156.89 235.35,157.6C235.6,157.85 235.9,158.05 236.22,158.19C236.55,158.33 236.9,158.4 237.26,158.4V167.99C236.26,168.18 235.4,168.32 234.56,168.41C233.83,168.51 233.1,168.56 232.36,168.57C230.62,168.68 228.89,168.22 227.43,167.26C226.3,166.4 225.54,165.14 225.3,163.74L225.09,162.09C223.5,164.17 221.44,165.86 219.09,167.01C216.82,168.13 214.31,168.71 211.78,168.71C209.44,168.74 207.13,168.21 205.05,167.15C203.13,166.17 201.49,164.7 200.33,162.88C199.17,161.08 198.57,158.98 198.59,156.84ZM221.49,158.18C221.99,157.8 222.43,157.33 222.78,156.79C223.09,156.37 223.27,155.87 223.29,155.35V152.09C222.25,151.69 221.18,151.4 220.08,151.21C219,151.01 217.91,150.91 216.81,150.9C214.96,150.83 213.13,151.3 211.55,152.26C210.92,152.61 210.39,153.13 210.02,153.76C209.65,154.38 209.46,155.1 209.46,155.82C209.46,156.65 209.71,157.45 210.18,158.12C210.69,158.83 211.38,159.41 212.18,159.79C213.12,160.22 214.15,160.43 215.18,160.4C216.36,160.4 217.53,160.19 218.64,159.79C219.68,159.42 220.64,158.88 221.49,158.18Z"
|
||||||
|
android:fillColor="#CDAAF5"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M236.87,173.65C237.87,173.95 238.73,174.16 239.57,174.34C240.31,174.49 241.07,174.58 241.83,174.59C242.65,174.61 243.46,174.4 244.17,173.99C244.91,173.47 245.5,172.76 245.87,171.93C246.46,170.7 246.91,169.42 247.21,168.1L232.81,130.6H244.69L253.43,156.4L260.91,130.63H271.79L257.39,174.2C256.74,176.19 255.68,178.04 254.29,179.62C252.9,181.19 251.2,182.44 249.29,183.3C247.25,184.2 245.04,184.65 242.81,184.63C241.82,184.63 240.83,184.54 239.86,184.38C238.84,184.2 237.83,183.92 236.86,183.54L236.87,173.65Z"
|
||||||
|
android:fillColor="#CDAAF5"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
</vector>
|
5
src/en/aniplay/res/mipmap-anydpi-v26/ic_launcher.xml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
|
</adaptive-icon>
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
|
</adaptive-icon>
|
BIN
src/en/aniplay/res/mipmap-hdpi/ic_launcher.webp
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
src/en/aniplay/res/mipmap-hdpi/ic_launcher_round.webp
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
src/en/aniplay/res/mipmap-mdpi/ic_launcher.webp
Normal file
After Width: | Height: | Size: 816 B |
BIN
src/en/aniplay/res/mipmap-mdpi/ic_launcher_round.webp
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
src/en/aniplay/res/mipmap-xhdpi/ic_launcher.webp
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
src/en/aniplay/res/mipmap-xhdpi/ic_launcher_round.webp
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
src/en/aniplay/res/mipmap-xxhdpi/ic_launcher.webp
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
src/en/aniplay/res/mipmap-xxhdpi/ic_launcher_round.webp
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
src/en/aniplay/res/mipmap-xxxhdpi/ic_launcher.webp
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
src/en/aniplay/res/mipmap-xxxhdpi/ic_launcher_round.webp
Normal file
After Width: | Height: | Size: 6.4 KiB |
4
src/en/aniplay/res/values/ic_launcher_background.xml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="ic_launcher_background">#05010D</color>
|
||||||
|
</resources>
|
BIN
src/en/aniplay/res/web_hi_res_512.png
Normal file
After Width: | Height: | Size: 23 KiB |
|
@ -0,0 +1,375 @@
|
||||||
|
package eu.kanade.tachiyomi.animeextension.en.aniplay
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.util.Base64
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.preference.ListPreference
|
||||||
|
import androidx.preference.PreferenceScreen
|
||||||
|
import androidx.preference.SwitchPreferenceCompat
|
||||||
|
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
|
||||||
|
import eu.kanade.tachiyomi.animesource.model.SAnime
|
||||||
|
import eu.kanade.tachiyomi.animesource.model.SEpisode
|
||||||
|
import eu.kanade.tachiyomi.animesource.model.Track
|
||||||
|
import eu.kanade.tachiyomi.animesource.model.Video
|
||||||
|
import eu.kanade.tachiyomi.lib.playlistutils.PlaylistUtils
|
||||||
|
import eu.kanade.tachiyomi.multisrc.anilist.AniListAnimeHttpSource
|
||||||
|
import eu.kanade.tachiyomi.network.GET
|
||||||
|
import eu.kanade.tachiyomi.network.POST
|
||||||
|
import eu.kanade.tachiyomi.util.parallelFlatMapBlocking
|
||||||
|
import eu.kanade.tachiyomi.util.parseAs
|
||||||
|
import kotlinx.serialization.encodeToString
|
||||||
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
|
import okhttp3.Response
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
class AniPlay : AniListAnimeHttpSource(), ConfigurableAnimeSource {
|
||||||
|
override val name = "AniPlay"
|
||||||
|
override val lang = "en"
|
||||||
|
|
||||||
|
override val baseUrl: String
|
||||||
|
get() = "https://${preferences.getString(PREF_DOMAIN_KEY, PREF_DOMAIN_DEFAULT)}"
|
||||||
|
|
||||||
|
private val playlistUtils by lazy { PlaylistUtils(client, headers) }
|
||||||
|
|
||||||
|
private val preferences by lazy {
|
||||||
|
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================================= AniList configurations ================================= */
|
||||||
|
|
||||||
|
override fun mapAnimeDetailUrl(animeId: Int): String {
|
||||||
|
return "$baseUrl/anime/info/$animeId"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun mapAnimeId(animeDetailUrl: String): Int {
|
||||||
|
val httpUrl = animeDetailUrl.toHttpUrl()
|
||||||
|
|
||||||
|
return httpUrl.pathSegments[2].toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getPreferredTitleLanguage(): TitleLanguage {
|
||||||
|
val preferredLanguage = preferences.getString(PREF_TITLE_LANGUAGE_KEY, PREF_TITLE_LANGUAGE_DEFAULT)
|
||||||
|
|
||||||
|
return when (preferredLanguage) {
|
||||||
|
"romaji" -> TitleLanguage.ROMAJI
|
||||||
|
"english" -> TitleLanguage.ENGLISH
|
||||||
|
"native" -> TitleLanguage.NATIVE
|
||||||
|
else -> TitleLanguage.ROMAJI
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ====================================== Episode List ====================================== */
|
||||||
|
|
||||||
|
override fun episodeListRequest(anime: SAnime): Request {
|
||||||
|
val httpUrl = anime.url.toHttpUrl()
|
||||||
|
val animeId = httpUrl.pathSegments[2]
|
||||||
|
|
||||||
|
return GET("$baseUrl/api/anime/episode/$animeId")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun episodeListParse(response: Response): List<SEpisode> {
|
||||||
|
val isMarkFiller = preferences.getBoolean(PREF_MARK_FILLER_EPISODE_KEY, PREF_MARK_FILLER_EPISODE_DEFAULT)
|
||||||
|
val episodeListUrl = response.request.url
|
||||||
|
val animeId = episodeListUrl.pathSegments[3]
|
||||||
|
val providers = response.parseAs<List<EpisodeListResponse>>()
|
||||||
|
val episodes = mutableMapOf<Int, EpisodeListResponse.Episode>()
|
||||||
|
val episodeExtras = mutableMapOf<Int, List<EpisodeExtra>>()
|
||||||
|
|
||||||
|
providers.forEach { provider ->
|
||||||
|
provider.episodes.forEach { episode ->
|
||||||
|
if (!episodes.containsKey(episode.number)) {
|
||||||
|
episodes[episode.number] = episode
|
||||||
|
}
|
||||||
|
val existingEpisodeExtras = episodeExtras.getOrElse(episode.number) { emptyList() }
|
||||||
|
val episodeExtra = EpisodeExtra(
|
||||||
|
source = provider.providerId,
|
||||||
|
episodeId = episode.id,
|
||||||
|
hasDub = episode.hasDub,
|
||||||
|
)
|
||||||
|
episodeExtras[episode.number] = existingEpisodeExtras + listOf(episodeExtra)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return episodes.map { episodeMap ->
|
||||||
|
val episode = episodeMap.value
|
||||||
|
val episodeNumber = episode.number
|
||||||
|
val episodeExtra = episodeExtras.getValue(episodeNumber)
|
||||||
|
val episodeExtraString = json.encodeToString(episodeExtra)
|
||||||
|
.let { Base64.encode(it.toByteArray(), Base64.DEFAULT) }
|
||||||
|
.toString(Charsets.UTF_8)
|
||||||
|
|
||||||
|
val url = baseUrl.toHttpUrl().newBuilder()
|
||||||
|
.addPathSegment("anime")
|
||||||
|
.addPathSegment("watch")
|
||||||
|
.addQueryParameter("id", animeId)
|
||||||
|
.addQueryParameter("ep", episodeNumber.toString())
|
||||||
|
.addQueryParameter("extras", episodeExtraString)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val name = parseEpisodeName(episodeNumber, episode.title)
|
||||||
|
val uploadDate = parseDate(episode.createdAt)
|
||||||
|
val dub = when {
|
||||||
|
episodeExtra.any { it.hasDub } -> ", Dub"
|
||||||
|
else -> ""
|
||||||
|
}
|
||||||
|
val filler = when {
|
||||||
|
episode.isFiller && isMarkFiller -> " • Filler Episode"
|
||||||
|
else -> ""
|
||||||
|
}
|
||||||
|
val scanlator = "Sub$dub$filler"
|
||||||
|
|
||||||
|
SEpisode.create().apply {
|
||||||
|
this.url = url.toString()
|
||||||
|
this.name = name
|
||||||
|
this.date_upload = uploadDate
|
||||||
|
this.episode_number = episodeNumber.toFloat()
|
||||||
|
this.scanlator = scanlator
|
||||||
|
}
|
||||||
|
}.reversed()
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ======================================= Video List ======================================= */
|
||||||
|
|
||||||
|
override suspend fun getVideoList(episode: SEpisode): List<Video> {
|
||||||
|
val episodeUrl = episode.url.toHttpUrl()
|
||||||
|
val animeId = episodeUrl.queryParameter("id") ?: return emptyList()
|
||||||
|
val episodeNum = episodeUrl.queryParameter("ep") ?: return emptyList()
|
||||||
|
val extras = episodeUrl.queryParameter("extras")
|
||||||
|
?.let {
|
||||||
|
Base64.decode(it, Base64.DEFAULT).toString(Charsets.UTF_8)
|
||||||
|
}
|
||||||
|
?.let { json.decodeFromString<List<EpisodeExtra>>(it) }
|
||||||
|
?: emptyList()
|
||||||
|
|
||||||
|
val episodeDataList = extras.parallelFlatMapBlocking { extra ->
|
||||||
|
val languages = mutableListOf("sub")
|
||||||
|
if (extra.hasDub) {
|
||||||
|
languages.add("dub")
|
||||||
|
}
|
||||||
|
val url = "$baseUrl/api/anime/source/$animeId"
|
||||||
|
|
||||||
|
languages.map { language ->
|
||||||
|
val requestBody = json
|
||||||
|
.encodeToString(
|
||||||
|
VideoSourceRequest(
|
||||||
|
source = extra.source,
|
||||||
|
episodeId = extra.episodeId,
|
||||||
|
episodeNum = episodeNum,
|
||||||
|
subType = language,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toRequestBody("application/json".toMediaType())
|
||||||
|
|
||||||
|
val response = client
|
||||||
|
.newCall(POST(url = url, body = requestBody))
|
||||||
|
.execute()
|
||||||
|
.parseAs<VideoSourceResponse>()
|
||||||
|
|
||||||
|
EpisodeData(
|
||||||
|
source = extra.source,
|
||||||
|
language = language,
|
||||||
|
response = response,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val videos = episodeDataList.flatMap { episodeData ->
|
||||||
|
val defaultSource = episodeData.response.sources?.first {
|
||||||
|
it.quality in listOf("default", "auto")
|
||||||
|
} ?: return@flatMap emptyList()
|
||||||
|
|
||||||
|
val subtitles = episodeData.response.subtitles
|
||||||
|
?.filter { it.lang != "Thumbnails" }
|
||||||
|
?.map { Track(it.url, it.lang) }
|
||||||
|
?: emptyList()
|
||||||
|
|
||||||
|
playlistUtils.extractFromHls(
|
||||||
|
playlistUrl = defaultSource.url,
|
||||||
|
videoNameGen = { quality ->
|
||||||
|
val serverName = getServerName(episodeData.source)
|
||||||
|
val typeName = when {
|
||||||
|
subtitles.isNotEmpty() -> "SoftSub"
|
||||||
|
else -> getTypeName(episodeData.language)
|
||||||
|
}
|
||||||
|
|
||||||
|
"$serverName - $quality - $typeName"
|
||||||
|
},
|
||||||
|
subtitleList = subtitles,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return videos.sort()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun List<Video>.sort(): List<Video> {
|
||||||
|
val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
|
||||||
|
val lang = preferences.getString(PREF_TYPE_KEY, PREF_TYPE_DEFAULT)!!.let(::getTypeName)
|
||||||
|
val server = preferences.getString(PREF_SERVER_KEY, PREF_SERVER_DEFAULT)!!.let(::getServerName)
|
||||||
|
|
||||||
|
return sortedWith(
|
||||||
|
compareByDescending<Video> { it.quality.contains(lang) }
|
||||||
|
.thenByDescending { it.quality.contains(quality) }
|
||||||
|
.thenByDescending { it.quality.contains(server, true) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ====================================== Preferences ====================================== */
|
||||||
|
|
||||||
|
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||||
|
ListPreference(screen.context).apply {
|
||||||
|
key = PREF_DOMAIN_KEY
|
||||||
|
title = "Preferred domain"
|
||||||
|
entries = PREF_DOMAIN_ENTRIES
|
||||||
|
entryValues = PREF_DOMAIN_ENTRY_VALUES
|
||||||
|
setDefaultValue(PREF_DOMAIN_DEFAULT)
|
||||||
|
summary = "%s"
|
||||||
|
|
||||||
|
setOnPreferenceChangeListener { _, newValue ->
|
||||||
|
val selected = newValue as String
|
||||||
|
val index = findIndexOfValue(selected)
|
||||||
|
val entry = entryValues[index] as String
|
||||||
|
Toast.makeText(screen.context, "Restart Aniyomi to apply changes", Toast.LENGTH_LONG).show()
|
||||||
|
preferences.edit().putString(key, entry).commit()
|
||||||
|
}
|
||||||
|
}.also(screen::addPreference)
|
||||||
|
|
||||||
|
ListPreference(screen.context).apply {
|
||||||
|
key = PREF_SERVER_KEY
|
||||||
|
title = "Preferred server"
|
||||||
|
entries = PREF_SERVER_ENTRIES
|
||||||
|
entryValues = PREF_SERVER_ENTRY_VALUES
|
||||||
|
setDefaultValue(PREF_SERVER_DEFAULT)
|
||||||
|
summary = "%s"
|
||||||
|
|
||||||
|
setOnPreferenceChangeListener { _, newValue ->
|
||||||
|
val selected = newValue as String
|
||||||
|
val index = findIndexOfValue(selected)
|
||||||
|
val entry = entryValues[index] as String
|
||||||
|
preferences.edit().putString(key, entry).commit()
|
||||||
|
}
|
||||||
|
}.also(screen::addPreference)
|
||||||
|
|
||||||
|
ListPreference(screen.context).apply {
|
||||||
|
key = PREF_QUALITY_KEY
|
||||||
|
title = "Preferred quality"
|
||||||
|
entries = PREF_QUALITY_ENTRIES
|
||||||
|
entryValues = PREF_QUALITY_ENTRY_VALUES
|
||||||
|
setDefaultValue(PREF_QUALITY_DEFAULT)
|
||||||
|
summary = "%s"
|
||||||
|
|
||||||
|
setOnPreferenceChangeListener { _, newValue ->
|
||||||
|
val selected = newValue as String
|
||||||
|
val index = findIndexOfValue(selected)
|
||||||
|
val entry = entryValues[index] as String
|
||||||
|
preferences.edit().putString(key, entry).commit()
|
||||||
|
}
|
||||||
|
}.also(screen::addPreference)
|
||||||
|
|
||||||
|
ListPreference(screen.context).apply {
|
||||||
|
key = PREF_TYPE_KEY
|
||||||
|
title = "Preferred type"
|
||||||
|
entries = PREF_TYPE_ENTRIES
|
||||||
|
entryValues = PREF_TYPE_ENTRY_VALUES
|
||||||
|
setDefaultValue(PREF_TYPE_DEFAULT)
|
||||||
|
summary = "%s"
|
||||||
|
|
||||||
|
setOnPreferenceChangeListener { _, newValue ->
|
||||||
|
val selected = newValue as String
|
||||||
|
val index = findIndexOfValue(selected)
|
||||||
|
val entry = entryValues[index] as String
|
||||||
|
preferences.edit().putString(key, entry).commit()
|
||||||
|
}
|
||||||
|
}.also(screen::addPreference)
|
||||||
|
|
||||||
|
ListPreference(screen.context).apply {
|
||||||
|
key = PREF_TITLE_LANGUAGE_KEY
|
||||||
|
title = "Preferred title language"
|
||||||
|
entries = PREF_TITLE_LANGUAGE_ENTRIES
|
||||||
|
entryValues = PREF_TITLE_LANGUAGE_ENTRY_VALUES
|
||||||
|
setDefaultValue(PREF_TITLE_LANGUAGE_DEFAULT)
|
||||||
|
summary = "%s"
|
||||||
|
|
||||||
|
setOnPreferenceChangeListener { _, newValue ->
|
||||||
|
val selected = newValue as String
|
||||||
|
val index = findIndexOfValue(selected)
|
||||||
|
val entry = entryValues[index] as String
|
||||||
|
Toast.makeText(screen.context, "Refresh your anime library to apply changes", Toast.LENGTH_LONG).show()
|
||||||
|
preferences.edit().putString(key, entry).commit()
|
||||||
|
}
|
||||||
|
}.also(screen::addPreference)
|
||||||
|
|
||||||
|
SwitchPreferenceCompat(screen.context).apply {
|
||||||
|
key = PREF_MARK_FILLER_EPISODE_KEY
|
||||||
|
title = "Mark filler episodes"
|
||||||
|
setDefaultValue(PREF_MARK_FILLER_EPISODE_DEFAULT)
|
||||||
|
setOnPreferenceChangeListener { _, newValue ->
|
||||||
|
Toast.makeText(screen.context, "Refresh your anime library to apply changes", Toast.LENGTH_LONG).show()
|
||||||
|
preferences.edit().putBoolean(key, newValue as Boolean).commit()
|
||||||
|
}
|
||||||
|
}.also(screen::addPreference)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =================================== AniPlay Utilities =================================== */
|
||||||
|
|
||||||
|
private fun parseEpisodeName(number: Int, name: String): String {
|
||||||
|
return when {
|
||||||
|
listOf("EP ", "EPISODE ").any(name::startsWith) -> "Episode $number"
|
||||||
|
else -> "Episode $number: $name"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getServerName(value: String): String {
|
||||||
|
val index = PREF_SERVER_ENTRY_VALUES.indexOf(value)
|
||||||
|
return PREF_SERVER_ENTRIES[index]
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getTypeName(value: String): String {
|
||||||
|
val index = PREF_TYPE_ENTRY_VALUES.indexOf(value)
|
||||||
|
return PREF_TYPE_ENTRIES[index]
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun parseDate(dateStr: String?): Long {
|
||||||
|
return dateStr?.let {
|
||||||
|
runCatching { DATE_FORMATTER.parse(it)?.time }.getOrNull()
|
||||||
|
} ?: 0L
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val PREF_DOMAIN_KEY = "domain"
|
||||||
|
private val PREF_DOMAIN_ENTRIES = arrayOf("aniplaynow.live (default)", "aniplay.lol (backup)")
|
||||||
|
private val PREF_DOMAIN_ENTRY_VALUES = arrayOf("aniplaynow.live", "aniplay.lol")
|
||||||
|
private const val PREF_DOMAIN_DEFAULT = "aniplaynow.live"
|
||||||
|
|
||||||
|
private const val PREF_SERVER_KEY = "server"
|
||||||
|
private val PREF_SERVER_ENTRIES = arrayOf("Kuro (Gogoanime)", "Yuki (HiAnime)", "Yuno (Yugenanime)")
|
||||||
|
private val PREF_SERVER_ENTRY_VALUES = arrayOf("kuro", "yuki", "yuno")
|
||||||
|
private const val PREF_SERVER_DEFAULT = "kuro"
|
||||||
|
|
||||||
|
private const val PREF_QUALITY_KEY = "quality"
|
||||||
|
private val PREF_QUALITY_ENTRIES = arrayOf("1080p", "720p", "480p", "360p")
|
||||||
|
private val PREF_QUALITY_ENTRY_VALUES = arrayOf("1080", "720", "480", "360")
|
||||||
|
private const val PREF_QUALITY_DEFAULT = "1080"
|
||||||
|
|
||||||
|
private const val PREF_TYPE_KEY = "type"
|
||||||
|
private val PREF_TYPE_ENTRIES = arrayOf("Sub", "SoftSub", "Dub")
|
||||||
|
private val PREF_TYPE_ENTRY_VALUES = arrayOf("sub", "softsub", "dub")
|
||||||
|
private const val PREF_TYPE_DEFAULT = "sub"
|
||||||
|
|
||||||
|
private const val PREF_TITLE_LANGUAGE_KEY = "title_language"
|
||||||
|
private val PREF_TITLE_LANGUAGE_ENTRIES = arrayOf("Romaji", "English", "Native")
|
||||||
|
private val PREF_TITLE_LANGUAGE_ENTRY_VALUES = arrayOf("romaji", "english", "native")
|
||||||
|
private const val PREF_TITLE_LANGUAGE_DEFAULT = "romaji"
|
||||||
|
|
||||||
|
private const val PREF_MARK_FILLER_EPISODE_KEY = "mark_filler_episode"
|
||||||
|
private const val PREF_MARK_FILLER_EPISODE_DEFAULT = true
|
||||||
|
|
||||||
|
private val DATE_FORMATTER = SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
package eu.kanade.tachiyomi.animeextension.en.aniplay
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class EpisodeListResponse(
|
||||||
|
val episodes: List<Episode>,
|
||||||
|
val providerId: String,
|
||||||
|
val default: Boolean?,
|
||||||
|
) {
|
||||||
|
@Serializable
|
||||||
|
data class Episode(
|
||||||
|
val id: String,
|
||||||
|
val number: Int,
|
||||||
|
val title: String,
|
||||||
|
val hasDub: Boolean,
|
||||||
|
val isFiller: Boolean,
|
||||||
|
val img: String?,
|
||||||
|
val description: String?,
|
||||||
|
val createdAt: String?,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class VideoSourceRequest(
|
||||||
|
val source: String,
|
||||||
|
|
||||||
|
@SerialName("episodeid")
|
||||||
|
val episodeId: String,
|
||||||
|
|
||||||
|
@SerialName("episodenum")
|
||||||
|
val episodeNum: String,
|
||||||
|
|
||||||
|
@SerialName("subtype")
|
||||||
|
val subType: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class VideoSourceResponse(
|
||||||
|
val sources: List<Source>?,
|
||||||
|
val subtitles: List<Subtitle>?,
|
||||||
|
) {
|
||||||
|
@Serializable
|
||||||
|
data class Source(
|
||||||
|
val url: String,
|
||||||
|
val quality: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Subtitle(
|
||||||
|
val url: String,
|
||||||
|
val lang: String,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class EpisodeExtra(
|
||||||
|
val source: String,
|
||||||
|
val episodeId: String,
|
||||||
|
val hasDub: Boolean,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class EpisodeData(
|
||||||
|
val source: String,
|
||||||
|
val language: String,
|
||||||
|
val response: VideoSourceResponse,
|
||||||
|
)
|