[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:
Claudemirovsky
2023-02-28 10:12:46 -03:00
committed by GitHub
parent a5c2427e70
commit 62f45e094d
387 changed files with 3380 additions and 2909 deletions

View File

@ -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)

View File

@ -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",
)

View File

@ -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

View File

@ -11,7 +11,6 @@ ext {
}
dependencies {
compileOnly libs.bundles.coroutines
implementation(project(':lib-okru-extractor'))
implementation "dev.datlag.jsunpacker:jsunpacker:1.0.1"
}

View File

@ -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()
}

View File

@ -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(

View File

@ -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 {

View File

@ -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>()

View File

@ -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)

View File

@ -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) {

View File

@ -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)

View File

@ -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")

View File

@ -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,
)
}
},
)
}

View File

@ -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()
}

View File

@ -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,
)
}

View File

@ -10,8 +10,4 @@ ext {
libVersion = '13'
}
dependencies {
compileOnly libs.bundles.coroutines
}
apply from: "$rootDir/common.gradle"

View File

@ -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,
)
}
}

View File

@ -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î",
)
}

View File

@ -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>()

View File

@ -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()
}

View File

@ -10,8 +10,4 @@ ext {
libVersion = '13'
}
dependencies {
compileOnly libs.bundles.coroutines
}
apply from: "$rootDir/common.gradle"

View File

@ -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

View File

@ -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? {

View File

@ -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()
}

View File

@ -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"),
)
}
}

View File

@ -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,
)

View File

@ -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&params=TV,SETI,MINISERIES,VARIETY,TALK,DOCUMENTARY&size=30"),
Pair("Popular Anime", "/category?area=&category=1&order=count&params=COMIC&size=30"),
Pair("Recent Anime", "/category?area=&category=1&order=up&params=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()
}

View File

@ -10,8 +10,4 @@ ext {
containsNsfw = false
}
dependencies {
compileOnly libs.bundles.coroutines
}
apply from: "$rootDir/common.gradle"

View File

@ -72,7 +72,7 @@ open class Onepace(override val lang: String, override val name: String) : Confi
}
}
},
false
false,
)
}

View File

@ -7,7 +7,7 @@ class OnepaceFactory : AnimeSourceFactory {
override fun createSources(): List<AnimeSource> = listOf(
OnepaceEspa(),
OnepaceFr(),
OnepaceEn()
OnepaceEn(),
)
}

View File

@ -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")