[skip ci] Update dependencies (#1343)
* Bump harmless dependencies Note that bumping the android plugin will make compilation show lots of "hey bro use namespace instead of AndroidManifest.xml" warnings * Remove duktape dependency Zero extensions are using it, so its safe to remove. For executing js in extensions we can use quickjs instead of ducktape. * Upgrade gradle to 7.6 * Update kotlin * Update OkHttp * Update JSoup Jesus Christ this was boring asf * Update KtLint * [skip ci] refactor on some build.gradle.kts files * Expose coroutines to all extension by default
This commit is contained in:
@ -33,8 +33,8 @@ class AOAPIInterceptor(client: OkHttpClient) : Interceptor {
|
||||
"https://auth.animeonsen.xyz/oauth/token",
|
||||
headers,
|
||||
body,
|
||||
)
|
||||
).execute().body!!.string()
|
||||
),
|
||||
).execute().body.string()
|
||||
|
||||
val tokenObject = Json.decodeFromString<JsonObject>(tokenResponse)
|
||||
|
||||
|
@ -70,7 +70,7 @@ class AnimeOnsen : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
GET("$apiUrl/content/index?start=${(page - 1) * 20}&limit=20")
|
||||
|
||||
override fun popularAnimeParse(response: Response): AnimesPage {
|
||||
val responseJson = json.decodeFromString<AnimeListResponse>(response.body!!.string())
|
||||
val responseJson = json.decodeFromString<AnimeListResponse>(response.body.string())
|
||||
val animes = responseJson.content.map {
|
||||
it.toSAnime()
|
||||
}
|
||||
@ -82,7 +82,7 @@ class AnimeOnsen : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
override fun episodeListParse(response: Response): List<SEpisode> {
|
||||
val contentId = response.request.url.toString().substringBeforeLast("/episodes")
|
||||
.substringAfterLast("/")
|
||||
val responseJson = json.decodeFromString<JsonObject>(response.body!!.string())
|
||||
val responseJson = json.decodeFromString<JsonObject>(response.body.string())
|
||||
return responseJson.keys.map { epNum ->
|
||||
SEpisode.create().apply {
|
||||
url = "$contentId/video/$epNum"
|
||||
@ -100,12 +100,14 @@ class AnimeOnsen : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
|
||||
// ============================ Video Links =============================
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val videoData = json.decodeFromString<VideoData>(response.body!!.string())
|
||||
val videoData = json.decodeFromString<VideoData>(response.body.string())
|
||||
val videoUrl = videoData.uri.stream
|
||||
val subtitleLangs = videoData.metadata.subtitles
|
||||
val headers = Headers.headersOf(
|
||||
"referer", baseUrl,
|
||||
"user-agent", AO_USER_AGENT,
|
||||
"referer",
|
||||
baseUrl,
|
||||
"user-agent",
|
||||
AO_USER_AGENT,
|
||||
)
|
||||
val video = try {
|
||||
val subtitles = videoData.uri.subtitles.keys.toList().sortSubs().map {
|
||||
@ -126,7 +128,7 @@ class AnimeOnsen : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
|
||||
// =============================== Search ===============================
|
||||
override fun searchAnimeParse(response: Response): AnimesPage {
|
||||
val searchResult = json.decodeFromString<SearchResponse>(response.body!!.string()).result
|
||||
val searchResult = json.decodeFromString<SearchResponse>(response.body.string()).result
|
||||
val results = searchResult.map {
|
||||
it.toSAnime()
|
||||
}
|
||||
@ -137,7 +139,7 @@ class AnimeOnsen : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
|
||||
// =========================== Anime Details ============================
|
||||
override fun animeDetailsParse(response: Response): SAnime {
|
||||
val details = json.decodeFromString<AnimeDetails>(response.body!!.string())
|
||||
val details = json.decodeFromString<AnimeDetails>(response.body.string())
|
||||
val anime = SAnime.create().apply {
|
||||
url = details.content_id
|
||||
title = details.content_title ?: details.content_title_en!!
|
||||
@ -223,10 +225,10 @@ private const val PREF_SUB_TITLE = "Preferred sub language"
|
||||
private val PREF_SUB_ENTRIES = arrayOf(
|
||||
"العربية", "Deutsch", "English", "Español (Spain)",
|
||||
"Español (Latin)", "Français", "Italiano",
|
||||
"Português (Brasil)", "Русский"
|
||||
"Português (Brasil)", "Русский",
|
||||
)
|
||||
private val PREF_SUB_VALUES = arrayOf(
|
||||
"ar-ME", "de-DE", "en-US", "es-ES",
|
||||
"es-LA", "fr-FR", "it-IT",
|
||||
"pt-BR", "ru-RU"
|
||||
"pt-BR", "ru-RU",
|
||||
)
|
||||
|
@ -7,7 +7,7 @@ import kotlinx.serialization.json.JsonObject
|
||||
@Serializable
|
||||
data class AnimeListResponse(
|
||||
val content: List<AnimeListItem>,
|
||||
val cursor: AnimeListCursor
|
||||
val cursor: AnimeListCursor,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
@ -19,7 +19,7 @@ data class AnimeListItem(
|
||||
|
||||
@Serializable
|
||||
data class AnimeListCursor(
|
||||
val next: JsonArray
|
||||
val next: JsonArray,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
|
@ -11,7 +11,6 @@ ext {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly libs.bundles.coroutines
|
||||
implementation(project(':lib-okru-extractor'))
|
||||
implementation "dev.datlag.jsunpacker:jsunpacker:1.0.1"
|
||||
}
|
||||
|
@ -72,9 +72,9 @@ class AnimeXin : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
||||
|
||||
override fun popularAnimeFromElement(element: Element): SAnime {
|
||||
return SAnime.create().apply {
|
||||
setUrlWithoutDomain(element.selectFirst("a.series").attr("href").toHttpUrl().encodedPath)
|
||||
thumbnail_url = element.selectFirst("img").attr("src").substringBefore("?resize")
|
||||
title = element.selectFirst("a.series:not(:has(img))").text()
|
||||
setUrlWithoutDomain(element.selectFirst("a.series")!!.attr("href").toHttpUrl().encodedPath)
|
||||
thumbnail_url = element.selectFirst("img")!!.attr("src").substringBefore("?resize")
|
||||
title = element.selectFirst("a.series:not(:has(img))")!!.text()
|
||||
}
|
||||
}
|
||||
|
||||
@ -120,9 +120,9 @@ class AnimeXin : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
||||
|
||||
override fun searchAnimeFromElement(element: Element): SAnime {
|
||||
return SAnime.create().apply {
|
||||
setUrlWithoutDomain(element.selectFirst("a").attr("href").toHttpUrl().encodedPath)
|
||||
thumbnail_url = element.selectFirst("img").attr("src").substringBefore("?resize")
|
||||
title = element.selectFirst("div.tt").text()
|
||||
setUrlWithoutDomain(element.selectFirst("a")!!.attr("href").toHttpUrl().encodedPath)
|
||||
thumbnail_url = element.selectFirst("img")!!.attr("src").substringBefore("?resize")
|
||||
title = element.selectFirst("div.tt")!!.text()
|
||||
}
|
||||
}
|
||||
|
||||
@ -132,8 +132,8 @@ class AnimeXin : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
||||
|
||||
override fun animeDetailsParse(document: Document): SAnime {
|
||||
return SAnime.create().apply {
|
||||
title = document.selectFirst("h1.entry-title").text()
|
||||
thumbnail_url = document.selectFirst("div.thumb > img").attr("src").substringBefore("?resize")
|
||||
title = document.selectFirst("h1.entry-title")!!.text()
|
||||
thumbnail_url = document.selectFirst("div.thumb > img")!!.attr("src").substringBefore("?resize")
|
||||
status = SAnime.COMPLETED
|
||||
description = document.select("div[itemprop=description] p")?.let {
|
||||
it.joinToString("\n\n") { t -> t.text() } +
|
||||
@ -151,7 +151,7 @@ class AnimeXin : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
||||
val document = response.asJsoup()
|
||||
|
||||
return document.select("div.eplister > ul > li").map { episodeElement ->
|
||||
val numberText = episodeElement.selectFirst("div.epl-num").text()
|
||||
val numberText = episodeElement.selectFirst("div.epl-num")!!.text()
|
||||
val numberString = numberText.substringBefore(" ")
|
||||
val episodeNumber = if (numberText.contains("part 2", true)) {
|
||||
numberString.toFloatOrNull()?.plus(0.5F) ?: 0F
|
||||
@ -163,7 +163,7 @@ class AnimeXin : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
||||
episode_number = episodeNumber
|
||||
name = numberText
|
||||
date_upload = parseDate(episodeElement.selectFirst("div.epl-date")?.text() ?: "")
|
||||
setUrlWithoutDomain(episodeElement.selectFirst("a").attr("href").toHttpUrl().encodedPath)
|
||||
setUrlWithoutDomain(episodeElement.selectFirst("a")!!.attr("href").toHttpUrl().encodedPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -182,7 +182,7 @@ class AnimeXin : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
||||
document.select("select.mirror > option[value~=.]").parallelMap { source ->
|
||||
runCatching {
|
||||
var decoded = Jsoup.parse(
|
||||
String(Base64.decode(source.attr("value"), Base64.DEFAULT))
|
||||
String(Base64.decode(source.attr("value"), Base64.DEFAULT)),
|
||||
).select("iframe[src~=.]").attr("src")
|
||||
if (!decoded.startsWith("http")) decoded = "https:$decoded"
|
||||
val prefix = "${source.text()} - "
|
||||
@ -246,7 +246,7 @@ class AnimeXin : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
||||
else -> null
|
||||
}
|
||||
}.getOrNull()
|
||||
}.filterNotNull().flatten()
|
||||
}.filterNotNull().flatten(),
|
||||
)
|
||||
|
||||
return videoList.sort()
|
||||
@ -268,7 +268,7 @@ class AnimeXin : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
||||
compareBy(
|
||||
{ it.quality.contains(quality) },
|
||||
{ it.quality.contains(language, true) },
|
||||
)
|
||||
),
|
||||
).reversed()
|
||||
}
|
||||
|
||||
|
@ -7,10 +7,10 @@ object AnimeXinFilters {
|
||||
|
||||
open class QueryPartFilter(
|
||||
displayName: String,
|
||||
val vals: Array<Pair<String, String>>
|
||||
val vals: Array<Pair<String, String>>,
|
||||
) : AnimeFilter.Select<String>(
|
||||
displayName,
|
||||
vals.map { it.first }.toTypedArray()
|
||||
vals.map { it.first }.toTypedArray(),
|
||||
) {
|
||||
fun toQueryPart() = vals[state].second
|
||||
}
|
||||
@ -33,28 +33,33 @@ object AnimeXinFilters {
|
||||
): String {
|
||||
return (this.getFirst<R>() as CheckBoxFilterList).state
|
||||
.mapNotNull { checkbox ->
|
||||
if (checkbox.state)
|
||||
if (checkbox.state) {
|
||||
options.find { it.first == checkbox.name }!!.second
|
||||
else null
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}.joinToString("&$name[]=").let {
|
||||
if (it.isBlank()) ""
|
||||
else "$name[]=$it"
|
||||
if (it.isBlank()) {
|
||||
""
|
||||
} else {
|
||||
"$name[]=$it"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class GenresFilter : CheckBoxFilterList(
|
||||
"Genres",
|
||||
AnimeXinFiltersData.genres.map { CheckBoxVal(it.first, false) }
|
||||
AnimeXinFiltersData.genres.map { CheckBoxVal(it.first, false) },
|
||||
)
|
||||
|
||||
class SeasonsFilter : CheckBoxFilterList(
|
||||
"Seasons",
|
||||
AnimeXinFiltersData.seasons.map { CheckBoxVal(it.first, false) }
|
||||
AnimeXinFiltersData.seasons.map { CheckBoxVal(it.first, false) },
|
||||
)
|
||||
|
||||
class StudiosFilter : CheckBoxFilterList(
|
||||
"Studios",
|
||||
AnimeXinFiltersData.studios.map { CheckBoxVal(it.first, false) }
|
||||
AnimeXinFiltersData.studios.map { CheckBoxVal(it.first, false) },
|
||||
)
|
||||
|
||||
class StatusFilter : QueryPartFilter("Status", AnimeXinFiltersData.status)
|
||||
@ -80,7 +85,7 @@ object AnimeXinFilters {
|
||||
val status: String = "",
|
||||
val type: String = "",
|
||||
val sub: String = "",
|
||||
val order: String = ""
|
||||
val order: String = "",
|
||||
)
|
||||
|
||||
internal fun getSearchParameters(filters: AnimeFilterList): FilterSearchParams {
|
||||
@ -119,7 +124,7 @@ object AnimeXinFilters {
|
||||
Pair("School", "school"),
|
||||
Pair("Sci-fi", "sci-fi"),
|
||||
Pair("Supernatural", "supernatural"),
|
||||
Pair("War", "war")
|
||||
Pair("War", "war"),
|
||||
)
|
||||
|
||||
val seasons = arrayOf(
|
||||
@ -133,7 +138,7 @@ object AnimeXinFilters {
|
||||
Pair("Season 7", "season-7"),
|
||||
Pair("Season 8", "season-8"),
|
||||
Pair("season1", "season1"),
|
||||
Pair("Winter 2023", "winter-2023")
|
||||
Pair("Winter 2023", "winter-2023"),
|
||||
)
|
||||
|
||||
val studios = arrayOf(
|
||||
@ -178,7 +183,7 @@ object AnimeXinFilters {
|
||||
Pair("Wonder Cat Animation", "wonder-cat-animation"),
|
||||
Pair("Xing Yi Kai Chen", "xing-yi-kai-chen"),
|
||||
Pair("Xuan Yuan", "xuan-yuan"),
|
||||
Pair("Year Young Culture", "year-young-culture")
|
||||
Pair("Year Young Culture", "year-young-culture"),
|
||||
)
|
||||
|
||||
val status = arrayOf(
|
||||
@ -198,7 +203,7 @@ object AnimeXinFilters {
|
||||
Pair("Special", "special"),
|
||||
Pair("BD", "bd"),
|
||||
Pair("ONA", "ona"),
|
||||
Pair("Music", "music")
|
||||
Pair("Music", "music"),
|
||||
)
|
||||
|
||||
val sub = arrayOf(
|
||||
|
@ -13,27 +13,27 @@ import uy.kohesive.injekt.injectLazy
|
||||
@Serializable
|
||||
data class DailyQuality(
|
||||
val qualities: Auto,
|
||||
val subtitles: Subtitle? = null
|
||||
val subtitles: Subtitle? = null,
|
||||
) {
|
||||
@Serializable
|
||||
data class Auto(
|
||||
val auto: List<Item>
|
||||
val auto: List<Item>,
|
||||
) {
|
||||
@Serializable
|
||||
data class Item(
|
||||
val type: String,
|
||||
val url: String
|
||||
val url: String,
|
||||
)
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class Subtitle(
|
||||
val data: Map<String, SubtitleObject>
|
||||
val data: Map<String, SubtitleObject>,
|
||||
) {
|
||||
@Serializable
|
||||
data class SubtitleObject(
|
||||
val label: String,
|
||||
val urls: List<String>
|
||||
val urls: List<String>,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -44,7 +44,7 @@ class DailymotionExtractor(private val client: OkHttpClient) {
|
||||
|
||||
fun videosFromUrl(url: String, prefix: String): List<Video> {
|
||||
val videoList = mutableListOf<Video>()
|
||||
val htmlString = client.newCall(GET(url)).execute().body!!.string()
|
||||
val htmlString = client.newCall(GET(url)).execute().body.string()
|
||||
|
||||
val internalData = htmlString.substringAfter("\"dmInternalData\":").substringBefore("</script>")
|
||||
val ts = internalData.substringAfter("\"ts\":").substringBefore(",")
|
||||
@ -53,7 +53,7 @@ class DailymotionExtractor(private val client: OkHttpClient) {
|
||||
val jsonUrl = "https://www.dailymotion.com/player/metadata/video/${url.toHttpUrl().encodedPath}?locale=en-US&dmV1st=$v1st&dmTs=$ts&is_native_app=0"
|
||||
val parsed = json.decodeFromString<DailyQuality>(
|
||||
client.newCall(GET(jsonUrl))
|
||||
.execute().body!!.string()
|
||||
.execute().body.string(),
|
||||
)
|
||||
|
||||
val subtitleList = mutableListOf<Track>()
|
||||
@ -63,16 +63,16 @@ class DailymotionExtractor(private val client: OkHttpClient) {
|
||||
parsed.subtitles.data.map { k ->
|
||||
Track(
|
||||
k.value.urls.first(),
|
||||
k.value.label
|
||||
k.value.label,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
} catch (a: Exception) { }
|
||||
}
|
||||
|
||||
val masterUrl = parsed.qualities.auto.first().url
|
||||
|
||||
val masterPlaylist = client.newCall(GET(masterUrl)).execute().body!!.string()
|
||||
val masterPlaylist = client.newCall(GET(masterUrl)).execute().body.string()
|
||||
|
||||
val separator = "#EXT-X-STREAM-INF"
|
||||
masterPlaylist.substringAfter(separator).split(separator).map {
|
||||
|
@ -11,7 +11,7 @@ class DoodExtractor(private val client: OkHttpClient) {
|
||||
fun videoFromUrl(
|
||||
url: String,
|
||||
quality: String? = null,
|
||||
redirect: Boolean = true
|
||||
redirect: Boolean = true,
|
||||
): Video? {
|
||||
val newQuality = quality ?: "Doodstream" + if (redirect) " mirror" else ""
|
||||
|
||||
@ -20,7 +20,7 @@ class DoodExtractor(private val client: OkHttpClient) {
|
||||
val newUrl = if (redirect) response.request.url.toString() else url
|
||||
|
||||
val doodTld = newUrl.substringAfter("https://dood.").substringBefore("/")
|
||||
val content = response.body!!.string()
|
||||
val content = response.body.string()
|
||||
|
||||
val subtitleList = mutableListOf<Track>()
|
||||
val subtitleRegex = """src:'//(srt[^']*?)',\s*label:'([^']*?)'""".toRegex()
|
||||
@ -29,9 +29,9 @@ class DoodExtractor(private val client: OkHttpClient) {
|
||||
subtitleRegex.findAll(content).map {
|
||||
Track(
|
||||
"https://" + it.groupValues[1],
|
||||
it.groupValues[2]
|
||||
it.groupValues[2],
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
} catch (a: Exception) { }
|
||||
|
||||
@ -43,9 +43,9 @@ class DoodExtractor(private val client: OkHttpClient) {
|
||||
val videoUrlStart = client.newCall(
|
||||
GET(
|
||||
"https://dood.$doodTld/pass_md5/$md5",
|
||||
Headers.headersOf("referer", newUrl)
|
||||
)
|
||||
).execute().body!!.string()
|
||||
Headers.headersOf("referer", newUrl),
|
||||
),
|
||||
).execute().body.string()
|
||||
val videoUrl = "$videoUrlStart$randomString?token=$token&expiry=$expiry"
|
||||
try {
|
||||
Video(newUrl, newQuality, videoUrl, headers = doodHeaders(doodTld), subtitleTracks = subtitleList)
|
||||
@ -60,7 +60,7 @@ class DoodExtractor(private val client: OkHttpClient) {
|
||||
fun videosFromUrl(
|
||||
url: String,
|
||||
quality: String? = null,
|
||||
redirect: Boolean = true
|
||||
redirect: Boolean = true,
|
||||
): List<Video> {
|
||||
val video = videoFromUrl(url, quality, redirect)
|
||||
return video?.let { listOf(it) } ?: emptyList<Video>()
|
||||
|
@ -15,12 +15,12 @@ import okhttp3.OkHttpClient
|
||||
data class FembedResponse(
|
||||
val success: Boolean,
|
||||
val data: List<FembedVideo> = emptyList(),
|
||||
val captions: List<Caption> = emptyList()
|
||||
val captions: List<Caption> = emptyList(),
|
||||
) {
|
||||
@Serializable
|
||||
data class FembedVideo(
|
||||
val file: String,
|
||||
val label: String
|
||||
val label: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
@ -45,11 +45,11 @@ class FembedExtractor(private val client: OkHttpClient) {
|
||||
url.replace("/v/", "/api/source/")
|
||||
}
|
||||
val body = runCatching {
|
||||
client.newCall(POST(videoApi)).execute().body?.string().orEmpty()
|
||||
client.newCall(POST(videoApi)).execute().body.string()
|
||||
}.getOrNull() ?: return emptyList()
|
||||
|
||||
val userId = client.newCall(GET(url)).execute().asJsoup()
|
||||
.selectFirst("script:containsData(USER_ID)")
|
||||
.selectFirst("script:containsData(USER_ID)")!!
|
||||
.data()
|
||||
.substringAfter("USER_ID")
|
||||
.substringAfter("'")
|
||||
@ -64,16 +64,19 @@ class FembedExtractor(private val client: OkHttpClient) {
|
||||
jsonResponse.captions.map {
|
||||
Track(
|
||||
"https://${url.toHttpUrl().host}/asset/userdata/$userId/caption/${it.hash}/${it.id}.${it.extension}",
|
||||
it.language
|
||||
it.language,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
} catch (a: Exception) { }
|
||||
|
||||
jsonResponse.data.map {
|
||||
val quality = ("Fembed:${it.label}").let {
|
||||
if (prefix.isNotBlank()) "$prefix $it"
|
||||
else it
|
||||
if (prefix.isNotBlank()) {
|
||||
"$prefix $it"
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
try {
|
||||
Video(it.file, quality, it.file, subtitleTracks = subtitleList)
|
||||
|
@ -22,14 +22,18 @@ class GdrivePlayerExtractor(private val client: OkHttpClient) {
|
||||
|
||||
fun videosFromUrl(url: String, name: String): List<Video> {
|
||||
val headers = Headers.headersOf(
|
||||
"Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
|
||||
"Host", "gdriveplayer.to",
|
||||
"Referer", "https://animexin.vip/",
|
||||
"User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:101.0) Gecko/20100101 Firefox/101.0"
|
||||
"Accept",
|
||||
"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
|
||||
"Host",
|
||||
"gdriveplayer.to",
|
||||
"Referer",
|
||||
"https://animexin.vip/",
|
||||
"User-Agent",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:101.0) Gecko/20100101 Firefox/101.0",
|
||||
)
|
||||
|
||||
val body = client.newCall(GET(url.replace(".me", ".to"), headers = headers)).execute()
|
||||
.body!!.string()
|
||||
.body.string()
|
||||
|
||||
val subtitleUrl = Jsoup.parse(body).selectFirst("div:contains(\\.srt)")
|
||||
val subtitleList = mutableListOf<Track>()
|
||||
@ -38,8 +42,8 @@ class GdrivePlayerExtractor(private val client: OkHttpClient) {
|
||||
subtitleList.add(
|
||||
Track(
|
||||
"https://gdriveplayer.to/?subtitle=" + subtitleUrl.text(),
|
||||
"Subtitles"
|
||||
)
|
||||
"Subtitles",
|
||||
),
|
||||
)
|
||||
} catch (a: Exception) { }
|
||||
}
|
||||
@ -89,9 +93,8 @@ class GdrivePlayerExtractor(private val client: OkHttpClient) {
|
||||
hashAlgorithm: String = "MD5",
|
||||
keyLength: Int = 32,
|
||||
ivLength: Int = 16,
|
||||
iterations: Int = 1
|
||||
iterations: Int = 1,
|
||||
): List<ByteArray>? {
|
||||
|
||||
val md = MessageDigest.getInstance(hashAlgorithm)
|
||||
val digestLength = md.getDigestLength()
|
||||
val targetKeySize = keyLength + ivLength
|
||||
@ -103,12 +106,13 @@ class GdrivePlayerExtractor(private val client: OkHttpClient) {
|
||||
md.reset()
|
||||
|
||||
while (generatedLength < targetKeySize) {
|
||||
if (generatedLength > 0)
|
||||
if (generatedLength > 0) {
|
||||
md.update(
|
||||
generatedData,
|
||||
generatedLength - digestLength,
|
||||
digestLength
|
||||
digestLength,
|
||||
)
|
||||
}
|
||||
|
||||
md.update(password)
|
||||
md.update(salt, 0, 8)
|
||||
@ -123,7 +127,7 @@ class GdrivePlayerExtractor(private val client: OkHttpClient) {
|
||||
}
|
||||
val result = listOf(
|
||||
generatedData.copyOfRange(0, keyLength),
|
||||
generatedData.copyOfRange(keyLength, targetKeySize)
|
||||
generatedData.copyOfRange(keyLength, targetKeySize),
|
||||
)
|
||||
return result
|
||||
} catch (e: DigestException) {
|
||||
|
@ -50,7 +50,7 @@ class StreamSBExtractor(private val client: OkHttpClient) {
|
||||
val master = fixUrl(url, common)
|
||||
val json = Json.decodeFromString<JsonObject>(
|
||||
client.newCall(GET(master, newHeaders))
|
||||
.execute().body!!.string()
|
||||
.execute().body.string(),
|
||||
)
|
||||
val subtitleList = mutableListOf<Track>()
|
||||
val subsList = json["stream_data"]!!.jsonObject["subs"]
|
||||
@ -62,7 +62,7 @@ class StreamSBExtractor(private val client: OkHttpClient) {
|
||||
it.jsonObject["file"]!!.jsonPrimitive.content,
|
||||
it.jsonObject["label"]!!.jsonPrimitive.content,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
} catch (a: Exception) { }
|
||||
}
|
||||
@ -70,7 +70,7 @@ class StreamSBExtractor(private val client: OkHttpClient) {
|
||||
val masterUrl = json["stream_data"]!!.jsonObject["file"].toString().trim('"')
|
||||
val masterPlaylist = client.newCall(GET(masterUrl, newHeaders))
|
||||
.execute()
|
||||
.body!!.string()
|
||||
.body.string()
|
||||
val separator = "#EXT-X-STREAM-INF"
|
||||
masterPlaylist.substringAfter(separator).split(separator).map {
|
||||
val resolution = it.substringAfter("RESOLUTION=")
|
||||
@ -78,11 +78,17 @@ class StreamSBExtractor(private val client: OkHttpClient) {
|
||||
.substringAfter("x")
|
||||
.substringBefore(",") + "p"
|
||||
val quality = ("StreamSB:" + resolution).let {
|
||||
if (prefix.isNotBlank()) "$prefix $it"
|
||||
else it
|
||||
if (prefix.isNotBlank()) {
|
||||
"$prefix $it"
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}.let {
|
||||
if (suffix.isNotBlank()) "$it $suffix"
|
||||
else it
|
||||
if (suffix.isNotBlank()) {
|
||||
"$it $suffix"
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
val videoUrl = it.substringAfter("\n").substringBefore("\n")
|
||||
try {
|
||||
@ -98,9 +104,9 @@ class StreamSBExtractor(private val client: OkHttpClient) {
|
||||
|
||||
fun videosFromDecryptedUrl(realUrl: String, headers: Headers, prefix: String = "", suffix: String = ""): List<Video> {
|
||||
return try {
|
||||
val json = Json.decodeFromString<JsonObject>(client.newCall(GET(realUrl, headers)).execute().body!!.string())
|
||||
val json = Json.decodeFromString<JsonObject>(client.newCall(GET(realUrl, headers)).execute().body.string())
|
||||
val masterUrl = json["stream_data"]!!.jsonObject["file"].toString().trim('"')
|
||||
val masterPlaylist = client.newCall(GET(masterUrl, headers)).execute().body!!.string()
|
||||
val masterPlaylist = client.newCall(GET(masterUrl, headers)).execute().body.string()
|
||||
val separator = "#EXT-X-STREAM-INF"
|
||||
masterPlaylist.substringAfter(separator).split(separator).map {
|
||||
val resolution = it.substringAfter("RESOLUTION=")
|
||||
@ -108,11 +114,17 @@ class StreamSBExtractor(private val client: OkHttpClient) {
|
||||
.substringAfter("x")
|
||||
.substringBefore(",") + "p"
|
||||
val quality = ("StreamSB:$resolution").let {
|
||||
if (prefix.isNotBlank()) "$prefix $it"
|
||||
else it
|
||||
if (prefix.isNotBlank()) {
|
||||
"$prefix $it"
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}.let {
|
||||
if (suffix.isNotBlank()) "$it $suffix"
|
||||
else it
|
||||
if (suffix.isNotBlank()) {
|
||||
"$it $suffix"
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
val videoUrl = it.substringAfter("\n").substringBefore("\n")
|
||||
Video(videoUrl, quality, videoUrl, headers = headers)
|
||||
|
@ -41,7 +41,9 @@ class VidstreamingExtractor(private val client: OkHttpClient) {
|
||||
val encryptAjaxParams = cryptoHandler(
|
||||
document.select("script[data-value]")
|
||||
.attr("data-value"),
|
||||
iv, secretKey, false
|
||||
iv,
|
||||
secretKey,
|
||||
false,
|
||||
).substringAfter("&")
|
||||
|
||||
val httpUrl = serverUrl.toHttpUrl()
|
||||
@ -55,10 +57,11 @@ class VidstreamingExtractor(private val client: OkHttpClient) {
|
||||
GET(
|
||||
"${host}encrypt-ajax.php?id=$encryptedId&$encryptAjaxParams&alias=$id",
|
||||
Headers.headersOf(
|
||||
"X-Requested-With", "XMLHttpRequest"
|
||||
)
|
||||
)
|
||||
).execute().body!!.string()
|
||||
"X-Requested-With",
|
||||
"XMLHttpRequest",
|
||||
),
|
||||
),
|
||||
).execute().body.string()
|
||||
val data = json.decodeFromString<JsonObject>(jsonResponse)["data"]!!.jsonPrimitive.content
|
||||
val decryptedData = cryptoHandler(data, iv, decryptionKey, false)
|
||||
val videoList = mutableListOf<Video>()
|
||||
@ -66,7 +69,7 @@ class VidstreamingExtractor(private val client: OkHttpClient) {
|
||||
val array = json.decodeFromString<JsonObject>(decryptedData)["source"]!!.jsonArray
|
||||
if (array.size == 1 && array[0].jsonObject["type"]!!.jsonPrimitive.content == "hls") {
|
||||
val fileURL = array[0].jsonObject["file"].toString().trim('"')
|
||||
val masterPlaylist = client.newCall(GET(fileURL)).execute().body!!.string()
|
||||
val masterPlaylist = client.newCall(GET(fileURL)).execute().body.string()
|
||||
masterPlaylist.substringAfter("#EXT-X-STREAM-INF:")
|
||||
.split("#EXT-X-STREAM-INF:").forEach {
|
||||
val quality = it.substringAfter("RESOLUTION=").substringAfter("x").substringBefore(",").substringBefore("\n") + "p"
|
||||
@ -76,20 +79,25 @@ class VidstreamingExtractor(private val client: OkHttpClient) {
|
||||
}
|
||||
videoList.add(Video(videoUrl, prefix + quality + qualitySuffix, videoUrl))
|
||||
}
|
||||
} else array.forEach {
|
||||
val label = it.jsonObject["label"].toString().lowercase(Locale.ROOT)
|
||||
.trim('"').replace(" ", "")
|
||||
val fileURL = it.jsonObject["file"].toString().trim('"')
|
||||
val videoHeaders = Headers.headersOf("Referer", serverUrl)
|
||||
if (label == "auto") autoList.add(
|
||||
Video(
|
||||
fileURL,
|
||||
label + qualitySuffix,
|
||||
fileURL,
|
||||
headers = videoHeaders
|
||||
)
|
||||
)
|
||||
else videoList.add(Video(fileURL, label + qualitySuffix, fileURL, headers = videoHeaders))
|
||||
} else {
|
||||
array.forEach {
|
||||
val label = it.jsonObject["label"].toString().lowercase(Locale.ROOT)
|
||||
.trim('"').replace(" ", "")
|
||||
val fileURL = it.jsonObject["file"].toString().trim('"')
|
||||
val videoHeaders = Headers.headersOf("Referer", serverUrl)
|
||||
if (label == "auto") {
|
||||
autoList.add(
|
||||
Video(
|
||||
fileURL,
|
||||
label + qualitySuffix,
|
||||
fileURL,
|
||||
headers = videoHeaders,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
videoList.add(Video(fileURL, label + qualitySuffix, fileURL, headers = videoHeaders))
|
||||
}
|
||||
}
|
||||
}
|
||||
return videoList.sortedByDescending {
|
||||
it.quality.substringBefore(qualitySuffix).substringBefore("p").toIntOrNull() ?: -1
|
||||
@ -103,7 +111,7 @@ class VidstreamingExtractor(private val client: OkHttpClient) {
|
||||
string: String,
|
||||
iv: ByteArray,
|
||||
secretKeyString: ByteArray,
|
||||
encrypt: Boolean = true
|
||||
encrypt: Boolean = true,
|
||||
): String {
|
||||
val ivParameterSpec = IvParameterSpec(iv)
|
||||
val secretKey = SecretKeySpec(secretKeyString, "AES")
|
||||
|
@ -34,7 +34,7 @@ class YouTubeExtractor(private val client: OkHttpClient) {
|
||||
val videoId = url.substringAfter("/embed/")
|
||||
|
||||
val document = client.newCall(
|
||||
GET(url.replace("/embed/", "/watch?v="))
|
||||
GET(url.replace("/embed/", "/watch?v=")),
|
||||
).execute().asJsoup()
|
||||
|
||||
for (element in document.select("script")) {
|
||||
@ -78,14 +78,14 @@ class YouTubeExtractor(private val client: OkHttpClient) {
|
||||
"X-YouTube-Client-Version", "17.31.35",
|
||||
"Origin", "https://www.youtube.com",
|
||||
"User-Agent", "com.google.android.youtube/17.31.35 (Linux; U; Android 11) gzip",
|
||||
"content-type", "application/json"
|
||||
"content-type", "application/json",
|
||||
)
|
||||
|
||||
val postResponse = client.newCall(
|
||||
POST(playerUrl, headers = headers, body = body)
|
||||
POST(playerUrl, headers = headers, body = body),
|
||||
).execute()
|
||||
|
||||
val responseObject = json.decodeFromString<JsonObject>(postResponse.body!!.string())
|
||||
val responseObject = json.decodeFromString<JsonObject>(postResponse.body.string())
|
||||
val videoList = mutableListOf<Video>()
|
||||
|
||||
val formats = responseObject["streamingData"]!!
|
||||
@ -103,8 +103,8 @@ class YouTubeExtractor(private val client: OkHttpClient) {
|
||||
Track(
|
||||
format.jsonObject["url"]!!.jsonPrimitive.content,
|
||||
format.jsonObject["audioQuality"]!!.jsonPrimitive.content +
|
||||
" (${formatBits(format.jsonObject["averageBitrate"]!!.jsonPrimitive.long)}ps)"
|
||||
)
|
||||
" (${formatBits(format.jsonObject["averageBitrate"]!!.jsonPrimitive.long)}ps)",
|
||||
),
|
||||
)
|
||||
} catch (a: Exception) { }
|
||||
}
|
||||
@ -124,8 +124,8 @@ class YouTubeExtractor(private val client: OkHttpClient) {
|
||||
Track(
|
||||
// TODO: Would replacing srv3 with vtt work for every video?
|
||||
captionJson["baseUrl"]!!.jsonPrimitive.content.replace("srv3", "vtt"),
|
||||
captionJson["name"]!!.jsonObject["runs"]!!.jsonArray[0].jsonObject["text"]!!.jsonPrimitive.content
|
||||
)
|
||||
captionJson["name"]!!.jsonObject["runs"]!!.jsonArray[0].jsonObject["text"]!!.jsonPrimitive.content,
|
||||
),
|
||||
)
|
||||
} catch (a: Exception) { }
|
||||
}
|
||||
@ -143,16 +143,16 @@ class YouTubeExtractor(private val client: OkHttpClient) {
|
||||
" (${mimeType.substringAfter("codecs=\"").substringBefore("\"")})",
|
||||
format.jsonObject["url"]!!.jsonPrimitive.content,
|
||||
audioTracks = audioTracks,
|
||||
subtitleTracks = subtitleTracks
|
||||
subtitleTracks = subtitleTracks,
|
||||
)
|
||||
} catch (a: Exception) {
|
||||
Video(
|
||||
format.jsonObject["url"]!!.jsonPrimitive.content,
|
||||
prefix + format.jsonObject["qualityLabel"]!!.jsonPrimitive.content +
|
||||
" (${mimeType.substringAfter("codecs=\"").substringBefore("\"")})",
|
||||
format.jsonObject["url"]!!.jsonPrimitive.content
|
||||
format.jsonObject["url"]!!.jsonPrimitive.content,
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
)
|
||||
}
|
||||
|
@ -650,7 +650,7 @@ class ConsumyBili : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
}
|
||||
|
||||
override fun popularAnimeParse(response: Response): AnimesPage {
|
||||
val parsed = json.decodeFromString<AnilistResponse>(response.body!!.string())
|
||||
val parsed = json.decodeFromString<AnilistResponse>(response.body.string())
|
||||
|
||||
val animeList = parsed.data.Page.media.map { ani ->
|
||||
SAnime.create().apply {
|
||||
@ -659,7 +659,7 @@ class ConsumyBili : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
author = ani.studios.nodes.firstOrNull()?.name ?: ""
|
||||
genre = ani.genres.joinToString(", ")
|
||||
description = Jsoup.parse(
|
||||
ani.description.replace("<br>", "br2n")
|
||||
ani.description.replace("<br>", "br2n"),
|
||||
).text().replace("br2n", "\n")
|
||||
status = parseStatus(ani.status)
|
||||
setUrlWithoutDomain(ani.id.toString())
|
||||
@ -734,7 +734,7 @@ class ConsumyBili : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
}
|
||||
|
||||
override fun episodeListParse(response: Response): List<SEpisode> {
|
||||
val parsed = json.decodeFromString<EpisodeResponse>(response.body!!.string())
|
||||
val parsed = json.decodeFromString<EpisodeResponse>(response.body.string())
|
||||
val episodeList = mutableListOf<SEpisode>()
|
||||
|
||||
if (parsed.episodes != null) {
|
||||
@ -745,7 +745,7 @@ class ConsumyBili : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
episode_number = it.episodeNumber
|
||||
setUrlWithoutDomain("/server/source?episode_id=${it.sourceEpisodeId}&source_media_id=${it.sourceMediaId}&source_id=${it.sourceId}")
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@ -759,19 +759,19 @@ class ConsumyBili : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
val subtitleList = mutableListOf<Track>()
|
||||
val audioList = mutableListOf<Track>()
|
||||
val sources = json.decodeFromString<SourcesResponse>(
|
||||
client.newCall(GET(baseUrl + episode.url)).execute().body!!.string()
|
||||
client.newCall(GET(baseUrl + episode.url)).execute().body.string(),
|
||||
)
|
||||
subtitleList.addAll(
|
||||
sources.subtitles.map {
|
||||
Track(it.file, "${it.language} - ${it.lang}")
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
sources.sources.forEach { source ->
|
||||
if (source.type == "dash") {
|
||||
// Parsing dash with Jsoup :YEP:
|
||||
val document = client.newCall(
|
||||
GET(source.file)
|
||||
GET(source.file),
|
||||
).execute().asJsoup()
|
||||
document.select("Representation[mimetype~=audio]").forEach { audioSrc ->
|
||||
audioList.add(Track(audioSrc.text(), formatBits(audioSrc.attr("bandwidth").toLongOrNull() ?: 0L) ?: "audio"))
|
||||
@ -783,8 +783,8 @@ class ConsumyBili : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
"${videoSrc.attr("height")}p - ${formatBits(videoSrc.attr("bandwidth").toLongOrNull() ?: 0L)}",
|
||||
videoSrc.text(),
|
||||
audioTracks = audioList,
|
||||
subtitleTracks = subtitleList
|
||||
)
|
||||
subtitleTracks = subtitleList,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -826,7 +826,7 @@ class ConsumyBili : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
compareBy(
|
||||
{ it.quality.contains(quality) },
|
||||
{ it.quality.substringBefore("p ").toIntOrNull() ?: 0 },
|
||||
)
|
||||
),
|
||||
).reversed()
|
||||
}
|
||||
|
||||
|
@ -4,20 +4,20 @@ import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class AnilistResponse(
|
||||
val data: DataObject
|
||||
val data: DataObject,
|
||||
) {
|
||||
@Serializable
|
||||
data class DataObject(
|
||||
val Page: PageObject
|
||||
val Page: PageObject,
|
||||
) {
|
||||
@Serializable
|
||||
data class PageObject(
|
||||
val pageInfo: PageInfoObject,
|
||||
val media: List<AnimeMedia>
|
||||
val media: List<AnimeMedia>,
|
||||
) {
|
||||
@Serializable
|
||||
data class PageInfoObject(
|
||||
val hasNextPage: Boolean
|
||||
val hasNextPage: Boolean,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
@ -28,25 +28,25 @@ data class AnilistResponse(
|
||||
val studios: StudioNode,
|
||||
val genres: List<String>,
|
||||
val description: String,
|
||||
val status: String
|
||||
val status: String,
|
||||
) {
|
||||
@Serializable
|
||||
data class TitleObject(
|
||||
val romaji: String
|
||||
val romaji: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class ImageObject(
|
||||
val extraLarge: String
|
||||
val extraLarge: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class StudioNode(
|
||||
val nodes: List<Node>
|
||||
val nodes: List<Node>,
|
||||
) {
|
||||
@Serializable
|
||||
data class Node(
|
||||
val name: String
|
||||
val name: String,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -56,7 +56,7 @@ data class AnilistResponse(
|
||||
|
||||
@Serializable
|
||||
data class EpisodeResponse(
|
||||
val episodes: List<EpisodeObject>? = null
|
||||
val episodes: List<EpisodeObject>? = null,
|
||||
) {
|
||||
@Serializable
|
||||
data class EpisodeObject(
|
||||
@ -70,18 +70,18 @@ data class EpisodeResponse(
|
||||
@Serializable
|
||||
data class SourcesResponse(
|
||||
val sources: List<SourceObject>,
|
||||
val subtitles: List<SubtitleObject>
|
||||
val subtitles: List<SubtitleObject>,
|
||||
) {
|
||||
@Serializable
|
||||
data class SourceObject(
|
||||
val file: String,
|
||||
val type: String
|
||||
val type: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class SubtitleObject(
|
||||
val file: String,
|
||||
val lang: String,
|
||||
val language: String
|
||||
val language: String,
|
||||
)
|
||||
}
|
||||
|
@ -10,8 +10,4 @@ ext {
|
||||
libVersion = '13'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly libs.bundles.coroutines
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
||||
|
@ -5,7 +5,7 @@ import kotlinx.serialization.Serializable
|
||||
@Serializable
|
||||
data class ItemsResponse(
|
||||
val TotalRecordCount: Int,
|
||||
val Items: List<Item>
|
||||
val Items: List<Item>,
|
||||
) {
|
||||
@Serializable
|
||||
data class Item(
|
||||
@ -24,7 +24,7 @@ data class ItemsResponse(
|
||||
) {
|
||||
@Serializable
|
||||
data class ImageObject(
|
||||
val Primary: String? = null
|
||||
val Primary: String? = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -36,7 +36,7 @@ data class SessionResponse(
|
||||
) {
|
||||
@Serializable
|
||||
data class MediaObject(
|
||||
val MediaStreams: List<MediaStream>
|
||||
val MediaStreams: List<MediaStream>,
|
||||
) {
|
||||
@Serializable
|
||||
data class MediaStream(
|
||||
@ -47,7 +47,7 @@ data class SessionResponse(
|
||||
val Language: String? = null,
|
||||
val DisplayTitle: String? = null,
|
||||
val Height: Int? = null,
|
||||
val Width: Int? = null
|
||||
val Width: Int? = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -17,22 +17,28 @@ object JFConstants {
|
||||
const val HOSTURL_DEFAULT = "http://127.0.0.1:8096"
|
||||
|
||||
fun getPrefApiKey(preferences: SharedPreferences): String? = preferences.getString(
|
||||
APIKEY_KEY, null
|
||||
APIKEY_KEY,
|
||||
null,
|
||||
)
|
||||
fun getPrefUserId(preferences: SharedPreferences): String? = preferences.getString(
|
||||
USERID_KEY, null
|
||||
USERID_KEY,
|
||||
null,
|
||||
)
|
||||
fun getPrefHostUrl(preferences: SharedPreferences): String = preferences.getString(
|
||||
HOSTURL_KEY, HOSTURL_DEFAULT
|
||||
HOSTURL_KEY,
|
||||
HOSTURL_DEFAULT,
|
||||
)!!
|
||||
fun getPrefUsername(preferences: SharedPreferences): String = preferences.getString(
|
||||
USERNAME_KEY, ""
|
||||
USERNAME_KEY,
|
||||
"",
|
||||
)!!
|
||||
fun getPrefPassword(preferences: SharedPreferences): String = preferences.getString(
|
||||
PASSWORD_KEY, ""
|
||||
PASSWORD_KEY,
|
||||
"",
|
||||
)!!
|
||||
fun getPrefParentId(preferences: SharedPreferences): String = preferences.getString(
|
||||
MEDIALIB_KEY, ""
|
||||
MEDIALIB_KEY,
|
||||
"",
|
||||
)!!
|
||||
|
||||
const val PREF_AUDIO_KEY = "preferred_audioLang"
|
||||
@ -54,7 +60,7 @@ object JFConstants {
|
||||
Quality(1920, 1080, 39808000, 192000, "1080p - 40 Mbps"),
|
||||
Quality(1920, 1080, 59808000, 192000, "1080p - 60 Mbps"),
|
||||
Quality(3840, 2160, 80000000, 192000, "4K - 80 Mbps"),
|
||||
Quality(3840, 2160, 120000000, 192000, "4K - 120 Mbps")
|
||||
Quality(3840, 2160, 120000000, 192000, "4K - 120 Mbps"),
|
||||
)
|
||||
|
||||
data class Quality(
|
||||
@ -91,7 +97,7 @@ object JFConstants {
|
||||
"tem", "ter", "tet", "tgk", "tgl", "tha", "tig", "tir", "tiv", "tkl", "tlh", "tli", "tmh", "tog", "ton", "tpi", "tsi",
|
||||
"tsn", "tso", "tuk", "tum", "tup", "tur", "tvl", "twi", "tyv", "udm", "uga", "uig", "ukr", "umb", "urd", "uzb", "vai",
|
||||
"ven", "vie", "vol", "vot", "wal", "war", "was", "wen", "wln", "wol", "xal", "xho", "yao", "yap", "yid", "yor", "zap",
|
||||
"zbl", "zen", "zgh", "zha", "zho", "zul", "zun", "zza"
|
||||
"zbl", "zen", "zgh", "zha", "zho", "zul", "zun", "zza",
|
||||
)
|
||||
|
||||
val PREF_ENTRIES = arrayOf(
|
||||
@ -163,6 +169,6 @@ object JFConstants {
|
||||
"ꕙꔤ", "Tshivenḓa", "Tiếng Việt", "Volapük", "vađđa ceeli", "Wolaitta; Wolaytta", "Winaray; Samareño; Lineyte-Samarnon; Binisayâ nga Winaray; Binisayâ nga Samar-Leyte; “Binisayâ nga Waray”",
|
||||
"wá:šiw ʔítlu", "Serbsce / Serbski", "Walon", "Wolof", "Хальмг келн / Xaľmg keln", "isiXhosa", "Yao", "Yapese",
|
||||
"ייִדיש; יידיש; אידיש Yidiš", "èdè Yorùbá", "Diidxazá/Dizhsa", "Blissymbols; Blissymbolics; Bliss", "Tuḍḍungiyya",
|
||||
"ⵜⴰⵎⴰⵣⵉⵖⵜ ⵜⴰⵏⴰⵡⴰⵢⵜ", "Vahcuengh / 話僮", "中文 Zhōngwén; 汉语; 漢語 Hànyǔ", "isiZulu", "Shiwi'ma", "kirmanckî; dimilkî; kirdkî; zazakî"
|
||||
"ⵜⴰⵎⴰⵣⵉⵖⵜ ⵜⴰⵏⴰⵡⴰⵢⵜ", "Vahcuengh / 話僮", "中文 Zhōngwén; 汉语; 漢語 Hànyǔ", "isiZulu", "Shiwi'ma", "kirmanckî; dimilkî; kirdkî; zazakî",
|
||||
)
|
||||
}
|
||||
|
@ -203,12 +203,12 @@ class Jellyfin : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
url.addQueryParameter("SearchTerm", query)
|
||||
|
||||
val response = client.newCall(
|
||||
GET(url.build().toString(), headers = headers)
|
||||
GET(url.build().toString(), headers = headers),
|
||||
).execute()
|
||||
val items = json.decodeFromString<ItemsResponse>(response.body!!.string())
|
||||
val items = json.decodeFromString<ItemsResponse>(response.body.string())
|
||||
items.Items.forEach {
|
||||
animeList.addAll(
|
||||
getAnimeFromId(it.Id)
|
||||
getAnimeFromId(it.Id),
|
||||
)
|
||||
}
|
||||
|
||||
@ -228,7 +228,7 @@ class Jellyfin : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
url.addQueryParameter("ParentId", id)
|
||||
|
||||
val response = client.newCall(
|
||||
GET(url.build().toString())
|
||||
GET(url.build().toString()),
|
||||
).execute()
|
||||
return animeParse(response, 0).animes
|
||||
}
|
||||
@ -253,7 +253,7 @@ class Jellyfin : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
}
|
||||
|
||||
override fun animeDetailsParse(response: Response): SAnime {
|
||||
val info = json.decodeFromString<ItemsResponse.Item>(response.body!!.string())
|
||||
val info = json.decodeFromString<ItemsResponse.Item>(response.body.string())
|
||||
|
||||
val anime = SAnime.create()
|
||||
|
||||
@ -265,7 +265,7 @@ class Jellyfin : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
info.Overview
|
||||
.replace("<br>\n", "br2n")
|
||||
.replace("<br>", "br2n")
|
||||
.replace("\n", "br2n")
|
||||
.replace("\n", "br2n"),
|
||||
).text().replace("br2n", "\n")
|
||||
} else {
|
||||
""
|
||||
@ -289,14 +289,14 @@ class Jellyfin : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
|
||||
override fun episodeListParse(response: Response): List<SEpisode> {
|
||||
val episodeList = if (response.request.url.toString().startsWith("$baseUrl/Users/")) {
|
||||
val parsed = json.decodeFromString<ItemsResponse.Item>(response.body!!.string())
|
||||
val parsed = json.decodeFromString<ItemsResponse.Item>(response.body.string())
|
||||
val episode = SEpisode.create()
|
||||
episode.episode_number = 1.0F
|
||||
episode.name = "Movie ${parsed.Name}"
|
||||
episode.setUrlWithoutDomain(response.request.url.toString().substringAfter(baseUrl))
|
||||
listOf(episode)
|
||||
} else {
|
||||
val parsed = json.decodeFromString<ItemsResponse>(response.body!!.string())
|
||||
val parsed = json.decodeFromString<ItemsResponse>(response.body.string())
|
||||
|
||||
parsed.Items.map { ep ->
|
||||
|
||||
@ -326,12 +326,12 @@ class Jellyfin : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val videoList = mutableListOf<Video>()
|
||||
val id = json.decodeFromString<ItemsResponse.Item>(response.body!!.string()).Id
|
||||
val id = json.decodeFromString<ItemsResponse.Item>(response.body.string()).Id
|
||||
|
||||
val sessionResponse = client.newCall(
|
||||
GET("$baseUrl/Items/$id/PlaybackInfo?userId=$userId&api_key=$apiKey")
|
||||
GET("$baseUrl/Items/$id/PlaybackInfo?userId=$userId&api_key=$apiKey"),
|
||||
).execute()
|
||||
val parsed = json.decodeFromString<SessionResponse>(sessionResponse.body!!.string())
|
||||
val parsed = json.decodeFromString<SessionResponse>(sessionResponse.body.string())
|
||||
|
||||
val subtitleList = mutableListOf<Track>()
|
||||
|
||||
@ -401,10 +401,12 @@ class Jellyfin : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
url.addQueryParameter("VideoCodec", "h264")
|
||||
url.addQueryParameter("VideoCodec", "h264")
|
||||
url.addQueryParameter(
|
||||
"VideoBitrate", quality.videoBitrate.toString()
|
||||
"VideoBitrate",
|
||||
quality.videoBitrate.toString(),
|
||||
)
|
||||
url.addQueryParameter(
|
||||
"AudioBitrate", quality.audioBitrate.toString()
|
||||
"AudioBitrate",
|
||||
quality.audioBitrate.toString(),
|
||||
)
|
||||
url.addQueryParameter("PlaySessionId", parsed.PlaySessionId)
|
||||
url.addQueryParameter("TranscodingMaxAudioChannels", "6")
|
||||
@ -434,7 +436,7 @@ class Jellyfin : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
// ============================= Utilities ==============================
|
||||
|
||||
private fun animeParse(response: Response, page: Int): AnimesPage {
|
||||
val items = json.decodeFromString<ItemsResponse>(response.body!!.string())
|
||||
val items = json.decodeFromString<ItemsResponse>(response.body.string())
|
||||
val animesList = mutableListOf<SAnime>()
|
||||
|
||||
items.Items.forEach { item ->
|
||||
@ -446,8 +448,8 @@ class Jellyfin : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
LinkData(
|
||||
path = "/Shows/${item.SeriesId}/Episodes?SeasonId=${item.Id}&api_key=$apiKey",
|
||||
seriesId = item.SeriesId!!,
|
||||
seasonId = item.Id
|
||||
).toJsonString()
|
||||
seasonId = item.Id,
|
||||
).toJsonString(),
|
||||
)
|
||||
// Virtual if show doesn't have any sub-folders, i.e. no seasons
|
||||
if (item.LocationType == "Virtual") {
|
||||
@ -471,8 +473,8 @@ class Jellyfin : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
LinkData(
|
||||
"/Users/$userId/Items/${item.Id}?api_key=$apiKey",
|
||||
item.Id,
|
||||
item.Id
|
||||
).toJsonString()
|
||||
item.Id,
|
||||
).toJsonString(),
|
||||
)
|
||||
animesList.add(anime)
|
||||
}
|
||||
@ -489,7 +491,7 @@ class Jellyfin : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
url.addQueryParameter("EnableImageTypes", "Primary")
|
||||
|
||||
val response = client.newCall(
|
||||
GET(url.build().toString(), headers = headers)
|
||||
GET(url.build().toString(), headers = headers),
|
||||
).execute()
|
||||
animesList.addAll(animeParse(response, page).animes)
|
||||
}
|
||||
@ -510,18 +512,36 @@ class Jellyfin : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
val mediaLibPref = medialibPreference(screen)
|
||||
screen.addPreference(
|
||||
screen.editTextPreference(
|
||||
JFConstants.HOSTURL_KEY, JFConstants.HOSTURL_TITLE, JFConstants.HOSTURL_DEFAULT, baseUrl, false, "", mediaLibPref
|
||||
)
|
||||
JFConstants.HOSTURL_KEY,
|
||||
JFConstants.HOSTURL_TITLE,
|
||||
JFConstants.HOSTURL_DEFAULT,
|
||||
baseUrl,
|
||||
false,
|
||||
"",
|
||||
mediaLibPref,
|
||||
),
|
||||
)
|
||||
screen.addPreference(
|
||||
screen.editTextPreference(
|
||||
JFConstants.USERNAME_KEY, JFConstants.USERNAME_TITLE, "", username, false, "", mediaLibPref
|
||||
)
|
||||
JFConstants.USERNAME_KEY,
|
||||
JFConstants.USERNAME_TITLE,
|
||||
"",
|
||||
username,
|
||||
false,
|
||||
"",
|
||||
mediaLibPref,
|
||||
),
|
||||
)
|
||||
screen.addPreference(
|
||||
screen.editTextPreference(
|
||||
JFConstants.PASSWORD_KEY, JFConstants.PASSWORD_TITLE, "", password, true, "••••••••", mediaLibPref
|
||||
)
|
||||
JFConstants.PASSWORD_KEY,
|
||||
JFConstants.PASSWORD_TITLE,
|
||||
"",
|
||||
password,
|
||||
true,
|
||||
"••••••••",
|
||||
mediaLibPref,
|
||||
),
|
||||
)
|
||||
screen.addPreference(mediaLibPref)
|
||||
val subLangPref = ListPreference(screen.context).apply {
|
||||
@ -587,9 +607,9 @@ class Jellyfin : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
Thread {
|
||||
try {
|
||||
val mediaLibsResponse = client.newCall(
|
||||
GET("$baseUrl/Users/$userId/Items?api_key=$apiKey")
|
||||
GET("$baseUrl/Users/$userId/Items?api_key=$apiKey"),
|
||||
).execute()
|
||||
val mediaJson = mediaLibsResponse.body?.let { json.decodeFromString<ItemsResponse>(it.string()) }?.Items
|
||||
val mediaJson = mediaLibsResponse.body.let { json.decodeFromString<ItemsResponse>(it.string()) }?.Items
|
||||
|
||||
val entriesArray = mutableListOf<String>()
|
||||
val entriesValueArray = mutableListOf<String>()
|
||||
|
@ -49,7 +49,7 @@ class JellyfinAuthenticator(
|
||||
""".trimIndent()
|
||||
.toRequestBody("application/json".toMediaType())
|
||||
val request = POST("$baseUrl/Users/authenticatebyname", headers = authHeader, body = body)
|
||||
val response = client.newCall(request).execute().body?.string()
|
||||
val response = client.newCall(request).execute().body.string()
|
||||
return response?.let { Json.decodeFromString<JsonObject>(it) }
|
||||
}
|
||||
|
||||
@ -68,11 +68,13 @@ class JellyfinAuthenticator(
|
||||
}
|
||||
|
||||
private fun getPrefDeviceId(): String? = preferences.getString(
|
||||
DEVICEID_KEY, null
|
||||
DEVICEID_KEY,
|
||||
null,
|
||||
)
|
||||
|
||||
private fun setPrefDeviceId(value: String) = preferences.edit().putString(
|
||||
DEVICEID_KEY, value
|
||||
DEVICEID_KEY,
|
||||
value,
|
||||
).apply()
|
||||
}
|
||||
|
||||
|
@ -10,8 +10,4 @@ ext {
|
||||
libVersion = '13'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly libs.bundles.coroutines
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
||||
|
@ -22,7 +22,7 @@ import java.net.Proxy
|
||||
class AccessTokenInterceptor(
|
||||
private val crUrl: String,
|
||||
private val json: Json,
|
||||
private val preferences: SharedPreferences
|
||||
private val preferences: SharedPreferences,
|
||||
) : Interceptor {
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
@ -42,7 +42,7 @@ class AccessTokenInterceptor(
|
||||
val refreshedToken = refreshAccessToken()
|
||||
// Retry the request
|
||||
return chain.proceed(
|
||||
newRequestWithAccessToken(chain.request(), refreshedToken)
|
||||
newRequestWithAccessToken(chain.request(), refreshedToken),
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -68,8 +68,8 @@ class AccessTokenInterceptor(
|
||||
.proxy(
|
||||
Proxy(
|
||||
Proxy.Type.SOCKS,
|
||||
InetSocketAddress("cr-unblocker.us.to", 1080)
|
||||
)
|
||||
InetSocketAddress("cr-unblocker.us.to", 1080),
|
||||
),
|
||||
)
|
||||
.build()
|
||||
|
||||
@ -78,29 +78,31 @@ class AccessTokenInterceptor(
|
||||
override fun getPasswordAuthentication(): PasswordAuthentication {
|
||||
return PasswordAuthentication("crunblocker", "crunblocker".toCharArray())
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
// Thanks Stormzy
|
||||
val refreshTokenResp = client.newCall(GET("https://raw.githubusercontent.com/Samfun75/File-host/main/aniyomi/refreshToken.txt")).execute()
|
||||
val refreshToken = refreshTokenResp.body!!.string().replace("[\n\r]".toRegex(), "")
|
||||
val refreshToken = refreshTokenResp.body.string().replace("[\n\r]".toRegex(), "")
|
||||
val headers = Headers.headersOf(
|
||||
"Content-Type", "application/x-www-form-urlencoded",
|
||||
"Authorization", "Basic a3ZvcGlzdXZ6Yy0teG96Y21kMXk6R21JSTExenVPVnRnTjdlSWZrSlpibzVuLTRHTlZ0cU8="
|
||||
"Content-Type",
|
||||
"application/x-www-form-urlencoded",
|
||||
"Authorization",
|
||||
"Basic a3ZvcGlzdXZ6Yy0teG96Y21kMXk6R21JSTExenVPVnRnTjdlSWZrSlpibzVuLTRHTlZ0cU8=",
|
||||
)
|
||||
val postBody = "grant_type=refresh_token&refresh_token=$refreshToken&scope=offline_access".toRequestBody("application/x-www-form-urlencoded".toMediaType())
|
||||
val response = proxy.newCall(POST("$crUrl/auth/v1/token", headers, postBody)).execute()
|
||||
val parsedJson = json.decodeFromString<AccessToken>(response.body!!.string())
|
||||
val parsedJson = json.decodeFromString<AccessToken>(response.body.string())
|
||||
|
||||
val policy = proxy.newCall(newRequestWithAccessToken(GET("$crUrl/index/v2"), parsedJson)).execute()
|
||||
val policyJson = json.decodeFromString<Policy>(policy.body!!.string())
|
||||
val policyJson = json.decodeFromString<Policy>(policy.body.string())
|
||||
val allTokens = AccessToken(
|
||||
parsedJson.access_token,
|
||||
parsedJson.token_type,
|
||||
policyJson.cms.policy,
|
||||
policyJson.cms.signature,
|
||||
policyJson.cms.key_pair_id,
|
||||
policyJson.cms.bucket
|
||||
policyJson.cms.bucket,
|
||||
)
|
||||
preferences.edit().putString(TOKEN_PREF_KEY, allTokens.toJsonString()).apply()
|
||||
return allTokens
|
||||
|
@ -11,38 +11,38 @@ data class AccessToken(
|
||||
val policy: String? = null,
|
||||
val signature: String? = null,
|
||||
val key_pair_id: String? = null,
|
||||
val bucket: String? = null
|
||||
val bucket: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class Policy(
|
||||
val cms: Tokens
|
||||
val cms: Tokens,
|
||||
) {
|
||||
@Serializable
|
||||
data class Tokens(
|
||||
val policy: String,
|
||||
val signature: String,
|
||||
val key_pair_id: String,
|
||||
val bucket: String
|
||||
val bucket: String,
|
||||
)
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class LinkData(
|
||||
val id: String,
|
||||
val media_type: String
|
||||
val media_type: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class Images(
|
||||
val poster_tall: List<ArrayList<Image>>? = null
|
||||
val poster_tall: List<ArrayList<Image>>? = null,
|
||||
) {
|
||||
@Serializable
|
||||
data class Image(
|
||||
val width: Int,
|
||||
val height: Int,
|
||||
val type: String,
|
||||
val source: String
|
||||
val source: String,
|
||||
)
|
||||
}
|
||||
|
||||
@ -58,7 +58,7 @@ data class Anime(
|
||||
val series_metadata: Metadata? = null,
|
||||
@SerialName("movie_listing_metadata")
|
||||
val movie_metadata: MovieMeta? = null,
|
||||
val content_provider: String? = null
|
||||
val content_provider: String? = null,
|
||||
) {
|
||||
@Serializable
|
||||
data class Metadata(
|
||||
@ -69,7 +69,7 @@ data class Anime(
|
||||
val is_dubbed: Boolean,
|
||||
val is_subbed: Boolean,
|
||||
@SerialName("tenant_categories")
|
||||
val genres: ArrayList<String>? = null
|
||||
val genres: ArrayList<String>? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
@ -79,14 +79,14 @@ data class Anime(
|
||||
val maturity_ratings: ArrayList<String>,
|
||||
val subtitle_locales: ArrayList<String>,
|
||||
@SerialName("tenant_categories")
|
||||
val genres: ArrayList<String>? = null
|
||||
val genres: ArrayList<String>? = null,
|
||||
)
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class AnimeResult(
|
||||
val total: Int,
|
||||
val data: ArrayList<Anime>
|
||||
val data: ArrayList<Anime>,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
@ -97,28 +97,28 @@ data class SearchAnimeResult(
|
||||
data class SearchAnime(
|
||||
val type: String,
|
||||
val count: Int,
|
||||
val items: ArrayList<Anime>
|
||||
val items: ArrayList<Anime>,
|
||||
)
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class SeasonResult(
|
||||
val total: Int,
|
||||
val data: ArrayList<Season>
|
||||
val data: ArrayList<Season>,
|
||||
) {
|
||||
@Serializable
|
||||
data class Season(
|
||||
val id: String,
|
||||
val season_number: Int? = null,
|
||||
@SerialName("premium_available_date")
|
||||
val date: String? = null
|
||||
val date: String? = null,
|
||||
)
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class EpisodeResult(
|
||||
val total: Int,
|
||||
val data: ArrayList<Episode>
|
||||
val data: ArrayList<Episode>,
|
||||
) {
|
||||
@Serializable
|
||||
data class Episode(
|
||||
@ -130,13 +130,13 @@ data class EpisodeResult(
|
||||
@SerialName("episode_air_date")
|
||||
val airDate: String? = null,
|
||||
val versions: ArrayList<Version>? = null,
|
||||
val streams_link: String
|
||||
val streams_link: String,
|
||||
) {
|
||||
@Serializable
|
||||
data class Version(
|
||||
val audio_locale: String,
|
||||
@SerialName("media_guid")
|
||||
val mediaId: String
|
||||
val mediaId: String,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -146,19 +146,19 @@ data class TempEpisode(
|
||||
var name: String,
|
||||
var episode_number: Float,
|
||||
var date_upload: Long,
|
||||
var scanlator: String?
|
||||
var scanlator: String?,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class EpisodeData(
|
||||
val ids: List<Pair<String, String>>
|
||||
val ids: List<Pair<String, String>>,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class VideoStreams(
|
||||
val streams: Stream,
|
||||
val subtitles: JsonObject,
|
||||
val audio_locale: String
|
||||
val audio_locale: String,
|
||||
) {
|
||||
@Serializable
|
||||
data class Stream(
|
||||
@ -169,13 +169,13 @@ data class VideoStreams(
|
||||
@Serializable
|
||||
data class HlsLinks(
|
||||
val hardsub_locale: String,
|
||||
val url: String
|
||||
val url: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class Subtitle(
|
||||
val locale: String,
|
||||
val url: String
|
||||
val url: String,
|
||||
)
|
||||
|
||||
fun <T> List<T>.thirdLast(): T? {
|
||||
|
@ -74,7 +74,7 @@ class Yomiroll : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
}
|
||||
|
||||
override fun popularAnimeParse(response: Response): AnimesPage {
|
||||
val parsed = json.decodeFromString<AnimeResult>(response.body!!.string())
|
||||
val parsed = json.decodeFromString<AnimeResult>(response.body.string())
|
||||
val animeList = parsed.data.parallelMap { ani ->
|
||||
runCatching {
|
||||
ani.toSAnime()
|
||||
@ -111,7 +111,7 @@ class Yomiroll : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
}
|
||||
|
||||
override fun searchAnimeParse(response: Response): AnimesPage {
|
||||
val bod = response.body!!.string()
|
||||
val bod = response.body.string()
|
||||
val total: Int
|
||||
val animeList = (
|
||||
if (response.request.url.encodedPath.contains("search")) {
|
||||
@ -142,10 +142,13 @@ class Yomiroll : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
override fun fetchAnimeDetails(anime: SAnime): Observable<SAnime> {
|
||||
val mediaId = json.decodeFromString<LinkData>(anime.url)
|
||||
val resp = client.newCall(
|
||||
if (mediaId.media_type == "series") GET("$crUrl/content/v2/cms/series/${mediaId.id}?locale=en-US")
|
||||
else GET("$crUrl/content/v2/cms/movie_listings/${mediaId.id}?locale=en-US")
|
||||
if (mediaId.media_type == "series") {
|
||||
GET("$crUrl/content/v2/cms/series/${mediaId.id}?locale=en-US")
|
||||
} else {
|
||||
GET("$crUrl/content/v2/cms/movie_listings/${mediaId.id}?locale=en-US")
|
||||
},
|
||||
).execute()
|
||||
val info = json.decodeFromString<AnimeResult>(resp.body!!.string())
|
||||
val info = json.decodeFromString<AnimeResult>(resp.body.string())
|
||||
return Observable.just(
|
||||
anime.apply {
|
||||
author = info.data.first().content_provider
|
||||
@ -153,7 +156,7 @@ class Yomiroll : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
if (genre.isNullOrBlank()) {
|
||||
genre = info.data.first().genres?.joinToString { gen -> gen.replaceFirstChar { it.uppercase() } }
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@ -171,7 +174,7 @@ class Yomiroll : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
}
|
||||
|
||||
override fun episodeListParse(response: Response): List<SEpisode> {
|
||||
val seasons = json.decodeFromString<SeasonResult>(response.body!!.string())
|
||||
val seasons = json.decodeFromString<SeasonResult>(response.body.string())
|
||||
val series = response.request.url.encodedPath.contains("series/")
|
||||
// Why all this? well crunchy sends same season twice with different quality eg. One Piece
|
||||
// which causes the number of episodes to be higher that what it actually is.
|
||||
@ -184,7 +187,7 @@ class Yomiroll : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
client.newCall(GET("$crUrl/content/v2/cms/seasons/${seasonData.id}/episodes"))
|
||||
.execute()
|
||||
val episodes =
|
||||
json.decodeFromString<EpisodeResult>(episodeResp.body!!.string())
|
||||
json.decodeFromString<EpisodeResult>(episodeResp.body.string())
|
||||
episodes.data.sortedBy { it.episode_number }.parallelMap { ep ->
|
||||
TempEpisode(
|
||||
epData = EpisodeData(
|
||||
@ -193,9 +196,9 @@ class Yomiroll : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
Pair(
|
||||
ep.streams_link.substringAfter("videos/")
|
||||
.substringBefore("/streams"),
|
||||
ep.audio_locale
|
||||
)
|
||||
)
|
||||
ep.audio_locale,
|
||||
),
|
||||
),
|
||||
),
|
||||
name = if (ep.episode_number > 0 && ep.episode.isNumeric()) {
|
||||
"Season ${seasonData.season_number} Ep ${df.format(ep.episode_number)}: " + ep.title
|
||||
@ -206,7 +209,7 @@ class Yomiroll : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
date_upload = ep.airDate?.let { parseDate(it) } ?: 0L,
|
||||
scanlator = ep.versions?.sortedBy { it.audio_locale }
|
||||
?.joinToString { it.audio_locale.substringBefore("-") }
|
||||
?: ep.audio_locale.substringBefore("-")
|
||||
?: ep.audio_locale.substringBefore("-"),
|
||||
)
|
||||
}
|
||||
}.getOrNull()
|
||||
@ -257,7 +260,7 @@ class Yomiroll : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
private fun extractVideo(media: Pair<String, String>, policyJson: AccessToken): List<Video> {
|
||||
val (mediaId, aud) = media
|
||||
val response = client.newCall(GET("$crUrl/cms/v2${policyJson.bucket}/videos/$mediaId/streams?Policy=${policyJson.policy}&Signature=${policyJson.signature}&Key-Pair-Id=${policyJson.key_pair_id}")).execute()
|
||||
val streams = json.decodeFromString<VideoStreams>(response.body!!.string())
|
||||
val streams = json.decodeFromString<VideoStreams>(response.body.string())
|
||||
|
||||
var subsList = emptyList<Track>()
|
||||
val subLocale = preferences.getString("preferred_sub", "en-US")!!.getLocale()
|
||||
@ -268,8 +271,8 @@ class Yomiroll : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
}.sortedWith(
|
||||
compareBy(
|
||||
{ it.lang },
|
||||
{ it.lang.contains(subLocale) }
|
||||
)
|
||||
{ it.lang.contains(subLocale) },
|
||||
),
|
||||
)
|
||||
} catch (_: Error) {}
|
||||
|
||||
@ -277,7 +280,7 @@ class Yomiroll : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
return streams.streams.adaptive_hls.entries.parallelMap { (_, value) ->
|
||||
val stream = json.decodeFromString<HlsLinks>(value.jsonObject.toString())
|
||||
runCatching {
|
||||
val playlist = client.newCall(GET(stream.url)).execute().body!!.string()
|
||||
val playlist = client.newCall(GET(stream.url)).execute().body.string()
|
||||
playlist.substringAfter("#EXT-X-STREAM-INF:")
|
||||
.split("#EXT-X-STREAM-INF:").map {
|
||||
val hardsub = stream.hardsub_locale.let { hs ->
|
||||
@ -294,7 +297,7 @@ class Yomiroll : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
videoUrl,
|
||||
quality,
|
||||
videoUrl,
|
||||
subtitleTracks = if (hardsub.isNotBlank()) emptyList() else subsList
|
||||
subtitleTracks = if (hardsub.isNotBlank()) emptyList() else subsList,
|
||||
)
|
||||
} catch (_: Error) {
|
||||
Video(videoUrl, quality, videoUrl)
|
||||
@ -341,7 +344,7 @@ class Yomiroll : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
Pair("ro-RO", "Romanian"),
|
||||
Pair("sv-SE", "Swedish"),
|
||||
Pair("zh-CN", "Chinese (PRC)"),
|
||||
Pair("zh-HK", "Chinese (Hong Kong)")
|
||||
Pair("zh-HK", "Chinese (Hong Kong)"),
|
||||
)
|
||||
|
||||
private fun LinkData.toJsonString(): String {
|
||||
@ -372,12 +375,20 @@ class Yomiroll : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
if (this@toSAnime.series_metadata?.subtitle_locales?.any() == true ||
|
||||
this@toSAnime.movie_metadata?.subtitle_locales?.any() == true ||
|
||||
this@toSAnime.series_metadata?.is_subbed == true
|
||||
) " Sub" else ""
|
||||
) {
|
||||
" Sub"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
) +
|
||||
(
|
||||
if (this@toSAnime.series_metadata?.audio_locales?.any() == true ||
|
||||
this@toSAnime.movie_metadata?.is_dubbed == true
|
||||
) " Dub" else ""
|
||||
) {
|
||||
" Dub"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
)
|
||||
desc += "\nMaturity Ratings: " +
|
||||
(
|
||||
@ -410,8 +421,8 @@ class Yomiroll : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
{ it.quality.contains(quality) },
|
||||
{ it.quality.contains("Aud: ${dubLocale.getLocale()}") },
|
||||
{ it.quality.contains("HardSub") == shouldContainHard },
|
||||
{ it.quality.contains(subLocale) }
|
||||
)
|
||||
{ it.quality.contains(subLocale) },
|
||||
),
|
||||
).reversed()
|
||||
}
|
||||
|
||||
|
@ -7,10 +7,10 @@ object YomirollFilters {
|
||||
|
||||
open class QueryPartFilter(
|
||||
displayName: String,
|
||||
val vals: Array<Pair<String, String>>
|
||||
val vals: Array<Pair<String, String>>,
|
||||
) : AnimeFilter.Select<String>(
|
||||
displayName,
|
||||
vals.map { it.first }.toTypedArray()
|
||||
vals.map { it.first }.toTypedArray(),
|
||||
) {
|
||||
fun toQueryPart() = vals[state].second
|
||||
}
|
||||
@ -28,13 +28,15 @@ object YomirollFilters {
|
||||
}
|
||||
|
||||
private inline fun <reified R> AnimeFilterList.parseCheckbox(
|
||||
options: Array<Pair<String, String>>
|
||||
options: Array<Pair<String, String>>,
|
||||
): String {
|
||||
return (this.getFirst<R>() as CheckBoxFilterList).state
|
||||
.mapNotNull { checkbox ->
|
||||
if (checkbox.state)
|
||||
if (checkbox.state) {
|
||||
options.find { it.first == checkbox.name }!!.second
|
||||
else null
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}.joinToString("")
|
||||
}
|
||||
|
||||
@ -45,7 +47,7 @@ object YomirollFilters {
|
||||
|
||||
class LanguageFilter : CheckBoxFilterList(
|
||||
"Language",
|
||||
CrunchyFiltersData.language.map { CheckBoxVal(it.first, false) }
|
||||
CrunchyFiltersData.language.map { CheckBoxVal(it.first, false) },
|
||||
)
|
||||
|
||||
val filterList = AnimeFilterList(
|
||||
@ -56,7 +58,7 @@ object YomirollFilters {
|
||||
CategoryFilter(),
|
||||
SortFilter(),
|
||||
MediaFilter(),
|
||||
LanguageFilter()
|
||||
LanguageFilter(),
|
||||
)
|
||||
|
||||
data class FilterSearchParams(
|
||||
@ -64,7 +66,7 @@ object YomirollFilters {
|
||||
val category: String = "",
|
||||
val sort: String = "",
|
||||
val language: String = "",
|
||||
val media: String = ""
|
||||
val media: String = "",
|
||||
)
|
||||
|
||||
internal fun getSearchParameters(filters: AnimeFilterList): FilterSearchParams {
|
||||
@ -83,7 +85,7 @@ object YomirollFilters {
|
||||
val searchType = arrayOf(
|
||||
Pair("Top Results", "top_results"),
|
||||
Pair("Series", "series"),
|
||||
Pair("Movies", "movie_listing")
|
||||
Pair("Movies", "movie_listing"),
|
||||
)
|
||||
|
||||
val categories = arrayOf(
|
||||
@ -176,24 +178,24 @@ object YomirollFilters {
|
||||
Pair("Thriller, Drama", "&categories=thriller,drama"),
|
||||
Pair("Thriller, Fantasy", "&categories=thriller,fantasy"),
|
||||
Pair("Thriller, Sci-Fi", "&categories=thriller,sci-fi"),
|
||||
Pair("Thriller, Supernatural", "&categories=thriller,supernatural")
|
||||
Pair("Thriller, Supernatural", "&categories=thriller,supernatural"),
|
||||
)
|
||||
|
||||
val sortType = arrayOf(
|
||||
Pair("Popular", "popularity"),
|
||||
Pair("New", "newly_added"),
|
||||
Pair("Alphabetical", "alphabetical")
|
||||
Pair("Alphabetical", "alphabetical"),
|
||||
)
|
||||
|
||||
val language = arrayOf(
|
||||
Pair("Sub", "&is_subbed=true"),
|
||||
Pair("Dub", "&is_dubbed=true")
|
||||
Pair("Dub", "&is_dubbed=true"),
|
||||
)
|
||||
|
||||
val mediaType = arrayOf(
|
||||
Pair("All", ""),
|
||||
Pair("Series", "&type=series"),
|
||||
Pair("Movies", "&type=movie_listing")
|
||||
Pair("Movies", "&type=movie_listing"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class CategoryResponse(
|
||||
val data: List<CategoryData>
|
||||
val data: List<CategoryData>,
|
||||
) {
|
||||
@Serializable
|
||||
data class CategoryData(
|
||||
@ -12,13 +12,13 @@ data class CategoryResponse(
|
||||
val domainType: Int,
|
||||
val id: String,
|
||||
val name: String,
|
||||
val sort: String
|
||||
val sort: String,
|
||||
)
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class AnimeInfoResponse(
|
||||
val data: InfoData
|
||||
val data: InfoData,
|
||||
) {
|
||||
@Serializable
|
||||
data class InfoData(
|
||||
@ -33,45 +33,45 @@ data class AnimeInfoResponse(
|
||||
@Serializable
|
||||
data class EpisodeInfo(
|
||||
val id: Int,
|
||||
val seriesNo: Float
|
||||
val seriesNo: Float,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class IdInfo(
|
||||
val name: String
|
||||
val name: String,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class SearchResponse(
|
||||
val data: InfoData
|
||||
val data: InfoData,
|
||||
) {
|
||||
@Serializable
|
||||
data class InfoData(
|
||||
val results: List<CategoryResponse.CategoryData>
|
||||
val results: List<CategoryResponse.CategoryData>,
|
||||
)
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class EpisodeResponse(
|
||||
val data: EpisodeData
|
||||
val data: EpisodeData,
|
||||
) {
|
||||
@Serializable
|
||||
data class EpisodeData(
|
||||
val qualities: List<Quality>,
|
||||
val subtitles: List<Subtitle>
|
||||
val subtitles: List<Subtitle>,
|
||||
) {
|
||||
@Serializable
|
||||
data class Quality(
|
||||
val quality: Int,
|
||||
val url: String
|
||||
val url: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class Subtitle(
|
||||
val language: String,
|
||||
val url: String
|
||||
val url: String,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -80,5 +80,5 @@ data class EpisodeResponse(
|
||||
data class LinkData(
|
||||
val category: String,
|
||||
val id: String,
|
||||
val episodeId: String? = null
|
||||
val episodeId: String? = null,
|
||||
)
|
||||
|
@ -55,7 +55,7 @@ class NetFilm : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
}
|
||||
|
||||
override fun popularAnimeParse(response: Response): AnimesPage {
|
||||
val parsed = json.decodeFromString<CategoryResponse>(response.body!!.string())
|
||||
val parsed = json.decodeFromString<CategoryResponse>(response.body.string())
|
||||
if (parsed.data.isEmpty()) {
|
||||
return AnimesPage(emptyList(), false)
|
||||
}
|
||||
@ -67,8 +67,8 @@ class NetFilm : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
setUrlWithoutDomain(
|
||||
LinkData(
|
||||
ani.domainType.toString(),
|
||||
ani.id
|
||||
).toJsonString()
|
||||
ani.id,
|
||||
).toJsonString(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -104,7 +104,7 @@ class NetFilm : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
return if (url.startsWith("/api/category")) {
|
||||
popularAnimeParse(response)
|
||||
} else {
|
||||
val parsed = json.decodeFromString<SearchResponse>(response.body!!.string())
|
||||
val parsed = json.decodeFromString<SearchResponse>(response.body.string())
|
||||
if (parsed.data.results.isEmpty()) {
|
||||
return AnimesPage(emptyList(), false)
|
||||
}
|
||||
@ -116,8 +116,8 @@ class NetFilm : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
setUrlWithoutDomain(
|
||||
LinkData(
|
||||
ani.domainType.toString(),
|
||||
ani.id
|
||||
).toJsonString()
|
||||
ani.id,
|
||||
).toJsonString(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -144,7 +144,7 @@ class NetFilm : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
Pair("Recent TV Series", "/category?area=&category=1&order=up¶ms=TV,SETI,MINISERIES,VARIETY,TALK,DOCUMENTARY&size=30"),
|
||||
Pair("Popular Anime", "/category?area=&category=1&order=count¶ms=COMIC&size=30"),
|
||||
Pair("Recent Anime", "/category?area=&category=1&order=up¶ms=COMIC&size=30"),
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) :
|
||||
@ -157,14 +157,14 @@ class NetFilm : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
override fun fetchAnimeDetails(anime: SAnime): Observable<SAnime> {
|
||||
val parsed = json.decodeFromString<LinkData>(anime.url)
|
||||
val resp = client.newCall(GET("$baseUrl/detail?category=${parsed.category}&id=${parsed.id}")).execute()
|
||||
val data = json.decodeFromString<AnimeInfoResponse>(resp.body!!.string()).data
|
||||
val data = json.decodeFromString<AnimeInfoResponse>(resp.body.string()).data
|
||||
return Observable.just(
|
||||
anime.apply {
|
||||
title = data.name
|
||||
thumbnail_url = data.coverVerticalUrl
|
||||
description = data.introduction
|
||||
genre = data.tagList.joinToString(", ") { it.name }
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@ -175,7 +175,7 @@ class NetFilm : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
override fun fetchEpisodeList(anime: SAnime): Observable<List<SEpisode>> {
|
||||
val parsed = json.decodeFromString<LinkData>(anime.url)
|
||||
val resp = client.newCall(GET("$baseUrl/detail?category=${parsed.category}&id=${parsed.id}")).execute()
|
||||
val data = json.decodeFromString<AnimeInfoResponse>(resp.body!!.string()).data
|
||||
val data = json.decodeFromString<AnimeInfoResponse>(resp.body.string()).data
|
||||
val episodeList = data.episodeVo.map { ep ->
|
||||
val formattedEpNum = if (floor(ep.seriesNo) == ceil(ep.seriesNo)) {
|
||||
ep.seriesNo.toInt()
|
||||
@ -188,8 +188,8 @@ class NetFilm : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
LinkData(
|
||||
data.category.toString(),
|
||||
data.id,
|
||||
ep.id.toString()
|
||||
).toJsonString()
|
||||
ep.id.toString(),
|
||||
).toJsonString(),
|
||||
)
|
||||
name = "Episode $formattedEpNum"
|
||||
}
|
||||
@ -204,7 +204,7 @@ class NetFilm : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
override fun fetchVideoList(episode: SEpisode): Observable<List<Video>> {
|
||||
val parsed = json.decodeFromString<LinkData>(episode.url)
|
||||
val resp = client.newCall(GET("$baseUrl/episode?category=${parsed.category}&id=${parsed.id}&episode=${parsed.episodeId!!}")).execute()
|
||||
val episodeParsed = json.decodeFromString<EpisodeResponse>(resp.body!!.string())
|
||||
val episodeParsed = json.decodeFromString<EpisodeResponse>(resp.body.string())
|
||||
val subtitleList = episodeParsed.data.subtitles.map { sub ->
|
||||
Track(sub.url, sub.language)
|
||||
}
|
||||
@ -224,7 +224,7 @@ class NetFilm : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
val quality = preferences.getString("preferred_quality", "1080")!!
|
||||
|
||||
return this.sortedWith(
|
||||
compareBy { it.quality.contains(quality) }
|
||||
compareBy { it.quality.contains(quality) },
|
||||
).reversed()
|
||||
}
|
||||
|
||||
|
@ -10,8 +10,4 @@ ext {
|
||||
containsNsfw = false
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly libs.bundles.coroutines
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
||||
|
@ -72,7 +72,7 @@ open class Onepace(override val lang: String, override val name: String) : Confi
|
||||
}
|
||||
}
|
||||
},
|
||||
false
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@ class OnepaceFactory : AnimeSourceFactory {
|
||||
override fun createSources(): List<AnimeSource> = listOf(
|
||||
OnepaceEspa(),
|
||||
OnepaceFr(),
|
||||
OnepaceEn()
|
||||
OnepaceEn(),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,7 @@ import org.jsoup.Jsoup
|
||||
class ZippyExtractor {
|
||||
fun getVideoUrl(url: String, json: Json): String {
|
||||
val document = Jsoup.connect(url).get()
|
||||
val jscript = document.selectFirst("script:containsData(dlbutton)").data()
|
||||
val jscript = document.selectFirst("script:containsData(dlbutton)")!!.data()
|
||||
.replace("document.getElementById('dlbutton').href", "a")
|
||||
.replace("document.getElementById('fimage').href", "b")
|
||||
.replace("document.getElementById('fimage')", "false")
|
||||
|
Reference in New Issue
Block a user