mirror of
https://github.com/hexated/cloudstream-extensions-hexated.git
synced 2024-08-15 00:03:22 +00:00
sora: added aniwave & fix few sources
This commit is contained in:
parent
b77d6ead50
commit
081e5aa1fd
7 changed files with 394 additions and 252 deletions
|
@ -1,7 +1,7 @@
|
|||
import org.jetbrains.kotlin.konan.properties.Properties
|
||||
|
||||
// use an integer for version numbers
|
||||
version = 215
|
||||
version = 216
|
||||
|
||||
android {
|
||||
defaultConfig {
|
||||
|
|
|
@ -263,8 +263,7 @@ open class Streamruby : ExtractorApi() {
|
|||
} else {
|
||||
response.document.selectFirst("script:containsData(sources:)")?.data()
|
||||
}
|
||||
val m3u8 =
|
||||
Regex("file:\\s*\"(.*?m3u8.*?)\"").find(script ?: return)?.groupValues?.getOrNull(1)
|
||||
val m3u8 = Regex("file:\\s*\"(.*?m3u8.*?)\"").find(script ?: return)?.groupValues?.getOrNull(1)
|
||||
M3u8Helper.generateM3u8(
|
||||
name,
|
||||
m3u8 ?: return,
|
||||
|
@ -466,7 +465,7 @@ class Animefever : Filesim() {
|
|||
override var mainUrl = "https://animefever.fun"
|
||||
}
|
||||
|
||||
class Multimovies : Filesim() {
|
||||
class Multimovies : Ridoo() {
|
||||
override val name = "Multimovies"
|
||||
override var mainUrl = "https://multimovies.cloud"
|
||||
}
|
||||
|
|
|
@ -492,7 +492,8 @@ object SoraExtractor : SoraStream() {
|
|||
val req = app.get(source, referer = "$host/")
|
||||
val server = getBaseUrl(req.url)
|
||||
val script = req.text.substringAfter("wc0 = '").substringBefore("'")
|
||||
val video = tryParseJson<Map<String, String>>(base64Decode(script))?.get("file")
|
||||
val video =
|
||||
tryParseJson<Map<String, String>>(base64Decode(script))?.get("file")
|
||||
M3u8Helper.generateM3u8(
|
||||
"Voe",
|
||||
video ?: return@apmap,
|
||||
|
@ -781,9 +782,11 @@ object SoraExtractor : SoraStream() {
|
|||
val mediaId = app.get(url).document.selectFirst("ul.episodes li a")?.attr("data-id")
|
||||
?: return
|
||||
|
||||
app.get("$vidsrctoAPI/ajax/embed/episode/$mediaId/sources", headers = mapOf(
|
||||
app.get(
|
||||
"$vidsrctoAPI/ajax/embed/episode/$mediaId/sources", headers = mapOf(
|
||||
"X-Requested-With" to "XMLHttpRequest"
|
||||
)).parsedSafe<VidsrctoSources>()?.result?.apmap {
|
||||
)
|
||||
).parsedSafe<VidsrctoSources>()?.result?.apmap {
|
||||
val encUrl = app.get("$vidsrctoAPI/ajax/embed/source/${it.id}")
|
||||
.parsedSafe<VidsrctoResponse>()?.result?.url
|
||||
loadExtractor(
|
||||
|
@ -905,11 +908,23 @@ object SoraExtractor : SoraStream() {
|
|||
if (season == null) TvType.AnimeMovie else TvType.Anime
|
||||
)
|
||||
|
||||
argamap({
|
||||
val malsync = app.get("$malsyncAPI/mal/anime/${malId ?: return}")
|
||||
.parsedSafe<MALSyncResponses>()?.sites
|
||||
|
||||
val zoroIds = malsync?.zoro?.keys?.map { it }
|
||||
val aniwaveId = malsync?.nineAnime?.firstNotNullOf { it.value["url"] }
|
||||
|
||||
argamap(
|
||||
{
|
||||
invokeAnimetosho(malId, season, episode, subtitleCallback, callback)
|
||||
}, {
|
||||
invokeAniwatch(malId, episode, subtitleCallback, callback)
|
||||
}, {
|
||||
},
|
||||
{
|
||||
invokeAniwatch(zoroIds, episode, subtitleCallback, callback)
|
||||
},
|
||||
{
|
||||
invokeAniwave(aniwaveId, episode, subtitleCallback, callback)
|
||||
},
|
||||
{
|
||||
if (season != null) invokeCrunchyroll(
|
||||
aniId,
|
||||
malId,
|
||||
|
@ -919,7 +934,42 @@ object SoraExtractor : SoraStream() {
|
|||
subtitleCallback,
|
||||
callback
|
||||
)
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun invokeAniwave(
|
||||
url: String? = null,
|
||||
episode: Int? = null,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
) {
|
||||
val res = app.get(url ?: return).document
|
||||
val id = res.select("div#watch-main").attr("data-id")
|
||||
val episodeId =
|
||||
app.get("$aniwaveAPI/ajax/episode/list/$id?vrf=${AniwaveUtils.encodeVrf(id)}")
|
||||
.parsedSafe<AniwaveResponse>()?.asJsoup()
|
||||
?.selectFirst("ul.ep-range li a[data-num=${episode ?: 1}]")?.attr("data-ids")
|
||||
?: return
|
||||
val servers =
|
||||
app.get("$aniwaveAPI/ajax/server/list/$episodeId?vrf=${AniwaveUtils.encodeVrf(episodeId)}")
|
||||
.parsedSafe<AniwaveResponse>()?.asJsoup()
|
||||
?.select("div.servers > div[data-type!=sub] ul li") ?: return
|
||||
|
||||
servers.apmap {
|
||||
val linkId = it.attr("data-link-id")
|
||||
val iframe =
|
||||
app.get("$aniwaveAPI/ajax/server/$linkId?vrf=${AniwaveUtils.encodeVrf(linkId)}")
|
||||
.parsedSafe<AniwaveServer>()?.result?.decrypt()
|
||||
val audio = if (it.attr("data-cmid").endsWith("softsub")) "Raw" else "English Dub"
|
||||
loadCustomExtractor(
|
||||
"${it.text()} [$audio]",
|
||||
iframe ?: return@apmap,
|
||||
"$aniwaveAPI/",
|
||||
subtitleCallback,
|
||||
callback,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun invokeAnimetosho(
|
||||
|
@ -968,7 +1018,7 @@ object SoraExtractor : SoraStream() {
|
|||
}
|
||||
|
||||
private suspend fun invokeAniwatch(
|
||||
malId: Int? = null,
|
||||
animeIds: List<String?>? = null,
|
||||
episode: Int? = null,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
|
@ -976,9 +1026,7 @@ object SoraExtractor : SoraStream() {
|
|||
val headers = mapOf(
|
||||
"X-Requested-With" to "XMLHttpRequest",
|
||||
)
|
||||
val animeId = app.get("$malsyncAPI/mal/anime/${malId ?: return}")
|
||||
.parsedSafe<MALSyncResponses>()?.sites?.zoro?.keys?.map { it }
|
||||
animeId?.apmap { id ->
|
||||
animeIds?.apmap { id ->
|
||||
val episodeId = app.get(
|
||||
"$aniwatchAPI/ajax/v2/episode/list/${id ?: return@apmap}",
|
||||
headers = headers
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package com.hexated
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Document
|
||||
|
||||
data class CrunchyrollAccessToken(
|
||||
val accessToken: String? = null,
|
||||
|
@ -25,7 +27,30 @@ data class TmdbDate(
|
|||
val nextWeek: String,
|
||||
)
|
||||
|
||||
data class AniMedia(@JsonProperty("id") var id: Int? = null, @JsonProperty("idMal") var idMal: Int? = null)
|
||||
data class AniwaveResponse(
|
||||
val result: String
|
||||
) {
|
||||
fun asJsoup(): Document {
|
||||
return Jsoup.parse(result)
|
||||
}
|
||||
}
|
||||
|
||||
data class AniwaveServer(
|
||||
val result: Result
|
||||
) {
|
||||
data class Result(
|
||||
val url: String
|
||||
) {
|
||||
fun decrypt(): String {
|
||||
return AniwaveUtils.decodeVrf(url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class AniMedia(
|
||||
@JsonProperty("id") var id: Int? = null,
|
||||
@JsonProperty("idMal") var idMal: Int? = null
|
||||
)
|
||||
|
||||
data class AniPage(@JsonProperty("media") var media: java.util.ArrayList<AniMedia> = arrayListOf())
|
||||
|
||||
|
@ -235,6 +260,7 @@ data class CrunchyrollSourcesResponses(
|
|||
|
||||
data class MALSyncSites(
|
||||
@JsonProperty("Zoro") val zoro: HashMap<String?, HashMap<String, String?>>? = hashMapOf(),
|
||||
@JsonProperty("9anime") val nineAnime: HashMap<String?, HashMap<String, String?>>? = hashMapOf(),
|
||||
)
|
||||
|
||||
data class MALSyncResponses(
|
||||
|
@ -364,7 +390,10 @@ data class SFMoviesAttributes(
|
|||
@JsonProperty("contentId") var contentId: String? = null,
|
||||
)
|
||||
|
||||
data class SFMoviesData(@JsonProperty("id") var id: Int? = null, @JsonProperty("attributes") var attributes: SFMoviesAttributes? = SFMoviesAttributes())
|
||||
data class SFMoviesData(
|
||||
@JsonProperty("id") var id: Int? = null,
|
||||
@JsonProperty("attributes") var attributes: SFMoviesAttributes? = SFMoviesAttributes()
|
||||
)
|
||||
|
||||
data class SFMoviesSearch(
|
||||
@JsonProperty("data") var data: ArrayList<SFMoviesData>? = arrayListOf(),
|
||||
|
|
|
@ -91,6 +91,7 @@ open class SoraStream : TmdbProvider() {
|
|||
const val filmxyAPI = "https://www.filmxy.vip"
|
||||
const val kimcartoonAPI = "https://kimcartoon.li"
|
||||
const val aniwatchAPI = "https://aniwatch.to"
|
||||
const val aniwaveAPI = "https://aniwave.to"
|
||||
const val crunchyrollAPI = "https://beta-api.crunchyroll.com"
|
||||
const val kissKhAPI = "https://kisskh.co"
|
||||
const val lingAPI = "https://ling-online.net"
|
||||
|
@ -584,7 +585,15 @@ open class SoraStream : TmdbProvider() {
|
|||
if (!res.isAnime) invokeNowTv(res.id, res.imdbId, res.season, res.episode, callback)
|
||||
},
|
||||
{
|
||||
if (!res.isAnime && res.season == null) invokeRidomovies(res.id, res.imdbId, res.title, res.season, res.episode, subtitleCallback, callback)
|
||||
if (!res.isAnime) invokeRidomovies(
|
||||
res.id,
|
||||
res.imdbId,
|
||||
res.title,
|
||||
res.season,
|
||||
res.episode,
|
||||
subtitleCallback,
|
||||
callback
|
||||
)
|
||||
},
|
||||
{
|
||||
if (!res.isAnime) invokeAllMovieland(res.imdbId, res.season, res.episode, callback)
|
||||
|
|
|
@ -223,7 +223,15 @@ class SoraStreamLite : SoraStream() {
|
|||
)
|
||||
},
|
||||
{
|
||||
if (!res.isAnime && res.season == null) invokeRidomovies(res.id, res.imdbId, res.title, res.season, res.episode, subtitleCallback, callback)
|
||||
if (!res.isAnime) invokeRidomovies(
|
||||
res.id,
|
||||
res.imdbId,
|
||||
res.title,
|
||||
res.season,
|
||||
res.episode,
|
||||
subtitleCallback,
|
||||
callback
|
||||
)
|
||||
},
|
||||
{
|
||||
if (!res.isAnime) invokeEmovies(
|
||||
|
|
|
@ -1304,6 +1304,55 @@ suspend fun request(
|
|||
return client.newCall(request).await()
|
||||
}
|
||||
|
||||
// steal from https://github.com/aniyomiorg/aniyomi-extensions/blob/master/src/en/aniwave/src/eu/kanade/tachiyomi/animeextension/en/nineanime/AniwaveUtils.kt
|
||||
// credits to @samfundev
|
||||
object AniwaveUtils {
|
||||
|
||||
fun encodeVrf(input: String): String {
|
||||
val rc4Key = SecretKeySpec("ysJhV6U27FVIjjuk".toByteArray(), "RC4")
|
||||
val cipher = Cipher.getInstance("RC4")
|
||||
cipher.init(Cipher.DECRYPT_MODE, rc4Key, cipher.parameters)
|
||||
var vrf = cipher.doFinal(input.toByteArray())
|
||||
vrf = Base64.encode(vrf, Base64.URL_SAFE or Base64.NO_WRAP)
|
||||
vrf = Base64.encode(vrf, Base64.DEFAULT or Base64.NO_WRAP)
|
||||
vrf = vrfShift(vrf)
|
||||
vrf = Base64.encode(vrf, Base64.DEFAULT)
|
||||
vrf = rot13(vrf)
|
||||
val stringVrf = vrf.toString(Charsets.UTF_8)
|
||||
return encode(stringVrf)
|
||||
}
|
||||
|
||||
fun decodeVrf(input: String): String {
|
||||
var vrf = input.toByteArray()
|
||||
vrf = Base64.decode(vrf, Base64.URL_SAFE)
|
||||
val rc4Key = SecretKeySpec("hlPeNwkncH0fq9so".toByteArray(), "RC4")
|
||||
val cipher = Cipher.getInstance("RC4")
|
||||
cipher.init(Cipher.DECRYPT_MODE, rc4Key, cipher.parameters)
|
||||
vrf = cipher.doFinal(vrf)
|
||||
return decode(vrf.toString(Charsets.UTF_8))
|
||||
}
|
||||
|
||||
private fun rot13(vrf: ByteArray): ByteArray {
|
||||
for (i in vrf.indices) {
|
||||
val byte = vrf[i]
|
||||
if (byte in 'A'.code..'Z'.code) {
|
||||
vrf[i] = ((byte - 'A'.code + 13) % 26 + 'A'.code).toByte()
|
||||
} else if (byte in 'a'.code..'z'.code) {
|
||||
vrf[i] = ((byte - 'a'.code + 13) % 26 + 'a'.code).toByte()
|
||||
}
|
||||
}
|
||||
return vrf
|
||||
}
|
||||
|
||||
private fun vrfShift(vrf: ByteArray): ByteArray {
|
||||
for (i in vrf.indices) {
|
||||
val shift = arrayOf(-3, 3, -4, 2, -2, 5, 4, 5)[i % 8]
|
||||
vrf[i] = vrf[i].plus(shift).toByte()
|
||||
}
|
||||
return vrf
|
||||
}
|
||||
}
|
||||
|
||||
object DumpUtils {
|
||||
|
||||
private val deviceId = getDeviceId()
|
||||
|
|
Loading…
Reference in a new issue