mirror of
https://github.com/hexated/cloudstream-extensions-hexated.git
synced 2024-08-15 00:03:22 +00:00
sora: make my own mapping for anime
This commit is contained in:
parent
aa25976a1b
commit
03d3731979
4 changed files with 184 additions and 72 deletions
|
@ -15,6 +15,7 @@ import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
|||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import okio.ByteString.Companion.encode
|
||||
import org.jsoup.Jsoup
|
||||
import java.time.LocalDate
|
||||
|
||||
val session = Session(Requests().baseClient)
|
||||
|
||||
|
@ -729,6 +730,16 @@ object SoraExtractor : SoraStream() {
|
|||
|
||||
val servers = tryParseJson<HashMap<String, String>>(serversKname)
|
||||
|
||||
val sub = app.get("$fmoviesAPI/ajax/episode/subtitles/${servers?.get("28")}").text
|
||||
tryParseJson<List<FmoviesSubtitles>>(sub)?.map {
|
||||
subtitleCallback.invoke(
|
||||
SubtitleFile(
|
||||
it.label ?: "",
|
||||
it.file ?: return@map
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
servers?.apmap { server ->
|
||||
val decryptServer = app.get("$fmoviesAPI/ajax/episode/info?id=${server.value}")
|
||||
.parsedSafe<FmoviesResponses>()?.url?.let { decodeVrf(it) } ?: return@apmap
|
||||
|
@ -738,16 +749,6 @@ object SoraExtractor : SoraStream() {
|
|||
loadExtractor(decryptServer, fmoviesAPI, subtitleCallback, callback)
|
||||
}
|
||||
}
|
||||
|
||||
val sub = app.get("$fmoviesAPI/ajax/episode/subtitles/${servers?.get("28") ?: return}").text
|
||||
tryParseJson<List<FmoviesSubtitles>>(sub)?.map {
|
||||
subtitleCallback.invoke(
|
||||
SubtitleFile(
|
||||
it.label ?: "",
|
||||
it.file ?: return@map
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun invokeKisskh(
|
||||
|
@ -829,19 +830,17 @@ object SoraExtractor : SoraStream() {
|
|||
}
|
||||
|
||||
suspend fun invokeAnimes(
|
||||
id: Int? = null,
|
||||
title: String? = null,
|
||||
jpTitle: String? = null,
|
||||
epsTitle: String? = null,
|
||||
year: Int? = null,
|
||||
date: String?,
|
||||
airedDate: String?,
|
||||
season: Int? = null,
|
||||
episode: Int? = null,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
) {
|
||||
val (aniId, malId) = app.get(
|
||||
if (season == null) "$tmdb2anilist/movie/?id=$id" else "$tmdb2anilist/tv/?id=$id&s=$season"
|
||||
).parsedSafe<Tmdb2Anilist>().let { it?.anilist_id to it?.mal_id }
|
||||
|
||||
val (aniId, malId) = convertTmdbToAnimeId(title, date, airedDate, if(season == null) TvType.AnimeMovie else TvType.Anime)
|
||||
|
||||
argamap(
|
||||
{
|
||||
|
@ -860,7 +859,7 @@ object SoraExtractor : SoraStream() {
|
|||
}
|
||||
|
||||
private suspend fun invokeBiliBili(
|
||||
aniId: String? = null,
|
||||
aniId: Int? = null,
|
||||
episode: Int? = null,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
|
@ -904,66 +903,68 @@ object SoraExtractor : SoraStream() {
|
|||
}
|
||||
|
||||
private suspend fun invokeZoro(
|
||||
aniId: String? = null,
|
||||
aniId: Int? = null,
|
||||
episode: Int? = null,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
) {
|
||||
val animeId =
|
||||
app.get("https://raw.githubusercontent.com/MALSync/MAL-Sync-Backup/master/data/anilist/anime/$aniId.json")
|
||||
.parsedSafe<MALSyncResponses>()?.pages?.zoro?.keys?.firstOrNull()
|
||||
app.get("https://raw.githubusercontent.com/MALSync/MAL-Sync-Backup/master/data/anilist/anime/${aniId ?: return}.json")
|
||||
.parsedSafe<MALSyncResponses>()?.pages?.zoro?.keys?.map { it }
|
||||
|
||||
val episodeId = app.get("$zoroAPI/ajax/v2/episode/list/${animeId ?: return}")
|
||||
.parsedSafe<ZoroResponses>()?.html?.let {
|
||||
Jsoup.parse(it)
|
||||
}?.select("div.ss-list a")?.find { it.attr("data-number") == "$episode" }
|
||||
?.attr("data-id")
|
||||
animeId?.apmap { id ->
|
||||
val episodeId = app.get("$zoroAPI/ajax/v2/episode/list/${id ?: return@apmap}")
|
||||
.parsedSafe<ZoroResponses>()?.html?.let {
|
||||
Jsoup.parse(it)
|
||||
}?.select("div.ss-list a")?.find { it.attr("data-number") == "$episode" }
|
||||
?.attr("data-id")
|
||||
|
||||
val servers = app.get("$zoroAPI/ajax/v2/episode/servers?episodeId=${episodeId ?: return@apmap}")
|
||||
.parsedSafe<ZoroResponses>()?.html?.let { Jsoup.parse(it) }
|
||||
?.select("div.item.server-item")?.map {
|
||||
Triple(
|
||||
it.text(),
|
||||
it.attr("data-id"),
|
||||
it.attr("data-type"),
|
||||
)
|
||||
}
|
||||
|
||||
servers?.apmap servers@{ server ->
|
||||
val iframe = app.get("$zoroAPI/ajax/v2/episode/sources?id=${server.second ?: return@servers}")
|
||||
.parsedSafe<ZoroResponses>()?.link ?: return@servers
|
||||
val audio = if(server.third == "sub") "Raw" else "English Dub"
|
||||
if(server.first == "Vidstreaming" || server.first == "Vidcloud") {
|
||||
extractRabbitStream(
|
||||
"${server.first} [$audio]",
|
||||
iframe,
|
||||
"$zoroAPI/",
|
||||
subtitleCallback,
|
||||
callback,
|
||||
false,
|
||||
decryptKey = RabbitStream.getZoroKey()
|
||||
) { it }
|
||||
} else {
|
||||
loadExtractor(iframe,"$zoroAPI/", subtitleCallback, callback)
|
||||
}
|
||||
|
||||
val servers = app.get("$zoroAPI/ajax/v2/episode/servers?episodeId=${episodeId ?: return}")
|
||||
.parsedSafe<ZoroResponses>()?.html?.let { Jsoup.parse(it) }
|
||||
?.select("div.item.server-item")?.map {
|
||||
Triple(
|
||||
it.text(),
|
||||
it.attr("data-id"),
|
||||
it.attr("data-type"),
|
||||
)
|
||||
}
|
||||
|
||||
servers?.apmap { server ->
|
||||
val iframe = app.get("$zoroAPI/ajax/v2/episode/sources?id=${server.second ?: return@apmap}")
|
||||
.parsedSafe<ZoroResponses>()?.link ?: return@apmap
|
||||
val audio = if(server.third == "sub") "Raw" else "English Dub"
|
||||
if(server.first == "Vidstreaming" || server.first == "Vidcloud") {
|
||||
extractRabbitStream(
|
||||
"${server.first} [$audio]",
|
||||
iframe,
|
||||
"$zoroAPI/",
|
||||
subtitleCallback,
|
||||
callback,
|
||||
false,
|
||||
decryptKey = RabbitStream.getZoroKey()
|
||||
) { it }
|
||||
} else {
|
||||
loadExtractor(iframe,"$zoroAPI/", subtitleCallback, callback)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private suspend fun invokeAnimeKaizoku(
|
||||
malId: String? = null,
|
||||
malId: Int? = null,
|
||||
epsTitle: String? = null,
|
||||
season: Int? = null,
|
||||
episode: Int? = null,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
) {
|
||||
val search = app.get("$animeKaizokuAPI/?s=$malId").document
|
||||
val search = app.get("$animeKaizokuAPI/?s=${malId ?: return}").document
|
||||
val detailHref =
|
||||
search.select("ul#posts-container li").map { it.selectFirst("a")?.attr("href") }
|
||||
.find {
|
||||
it?.contains(malId ?: return) == true
|
||||
it?.contains("$malId") == true
|
||||
}?.let { fixUrl(it, animeKaizokuAPI) }
|
||||
|
||||
val detail = app.get(detailHref ?: return).document
|
||||
|
@ -1467,14 +1468,14 @@ object SoraExtractor : SoraStream() {
|
|||
}
|
||||
|
||||
suspend fun invokeCrunchyroll(
|
||||
aniId: String? = null,
|
||||
aniId: Int? = null,
|
||||
epsTitle: String? = null,
|
||||
season: Int? = null,
|
||||
episode: Int? = null,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
) {
|
||||
val id = getCrunchyrollId(aniId)
|
||||
val id = getCrunchyrollId("${aniId ?: return}")
|
||||
val audioLocal = listOf(
|
||||
"ja-JP",
|
||||
"en-US",
|
||||
|
@ -2875,6 +2876,28 @@ data class BaymoviesConfig(
|
|||
val workers: List<String>
|
||||
)
|
||||
|
||||
data class AniIds(
|
||||
var id: Int? = null,
|
||||
var idMal: Int? = null
|
||||
)
|
||||
|
||||
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()
|
||||
)
|
||||
|
||||
data class AniData(
|
||||
@JsonProperty("Page") var Page: AniPage? = AniPage()
|
||||
)
|
||||
|
||||
data class AniSearch(
|
||||
@JsonProperty("data") var data: AniData? = AniData()
|
||||
)
|
||||
|
||||
data class Tmdb2Anilist(
|
||||
@JsonProperty("tmdb_id") val tmdb_id: String? = null,
|
||||
@JsonProperty("anilist_id") val anilist_id: String? = null,
|
||||
|
@ -3264,7 +3287,7 @@ data class CrunchyrollSourcesResponses(
|
|||
)
|
||||
|
||||
data class MALSyncPages(
|
||||
@JsonProperty("Zoro") val zoro: HashMap<String, HashMap<String, String>>? = hashMapOf(),
|
||||
@JsonProperty("Zoro") val zoro: HashMap<String?, HashMap<String, String?>>? = hashMapOf(),
|
||||
)
|
||||
|
||||
data class MALSyncResponses(
|
||||
|
|
|
@ -74,8 +74,8 @@ open class SoraStream : TmdbProvider() {
|
|||
companion object {
|
||||
/** TOOLS */
|
||||
private const val tmdbAPI = "https://api.themoviedb.org/3"
|
||||
const val tmdb2anilist = "https://tmdb2anilist.slidemovies.org"
|
||||
const val gdbot = "https://gdtot.pro"
|
||||
const val anilistAPI = "https://graphql.anilist.co"
|
||||
|
||||
private val apiKey = base64DecodeAPI("ZTM=NTg=MjM=MjM=ODc=MzI=OGQ=MmE=Nzk=Nzk=ZjI=NTA=NDY=NDA=MzA=YjA=") // PLEASE DON'T STEAL
|
||||
|
||||
|
@ -291,7 +291,9 @@ open class SoraStream : TmdbProvider() {
|
|||
airedYear = year,
|
||||
lastSeason = lastSeason,
|
||||
epsTitle = eps.name,
|
||||
jpTitle = res.alternative_titles?.results?.find { it.iso_3166_1 == "JP" }?.title
|
||||
jpTitle = res.alternative_titles?.results?.find { it.iso_3166_1 == "JP" }?.title,
|
||||
date = season.airDate,
|
||||
airedDate = res.releaseDate ?: res.firstAirDate
|
||||
).toJson(),
|
||||
name = eps.name,
|
||||
season = eps.seasonNumber,
|
||||
|
@ -334,7 +336,8 @@ open class SoraStream : TmdbProvider() {
|
|||
year = year,
|
||||
orgTitle = orgTitle,
|
||||
isAnime = isAnime,
|
||||
jpTitle = res.alternative_titles?.results?.find { it.iso_3166_1 == "JP" }?.title
|
||||
jpTitle = res.alternative_titles?.results?.find { it.iso_3166_1 == "JP" }?.title,
|
||||
airedDate = res.releaseDate ?: res.firstAirDate
|
||||
).toJson(),
|
||||
) {
|
||||
this.posterUrl = poster
|
||||
|
@ -399,11 +402,10 @@ open class SoraStream : TmdbProvider() {
|
|||
// },
|
||||
{
|
||||
if (res.isAnime) invokeAnimes(
|
||||
res.id,
|
||||
res.title,
|
||||
res.jpTitle,
|
||||
res.epsTitle,
|
||||
res.airedYear ?: res.year,
|
||||
res.date,
|
||||
res.airedDate,
|
||||
res.season,
|
||||
res.episode,
|
||||
subtitleCallback,
|
||||
|
@ -790,6 +792,8 @@ open class SoraStream : TmdbProvider() {
|
|||
val lastSeason: Int? = null,
|
||||
val epsTitle: String? = null,
|
||||
val jpTitle: String? = null,
|
||||
val date: String? = null,
|
||||
val airedDate: String? = null,
|
||||
)
|
||||
|
||||
data class Data(
|
||||
|
|
|
@ -107,11 +107,10 @@ class SoraStreamLite : SoraStream() {
|
|||
},
|
||||
{
|
||||
if (res.isAnime) invokeAnimes(
|
||||
res.id,
|
||||
res.title,
|
||||
res.jpTitle,
|
||||
res.epsTitle,
|
||||
res.airedYear ?: res.year,
|
||||
res.date,
|
||||
res.airedDate,
|
||||
res.season,
|
||||
res.episode,
|
||||
subtitleCallback,
|
||||
|
|
|
@ -2,6 +2,7 @@ package com.hexated
|
|||
|
||||
import android.util.Base64
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.hexated.SoraStream.Companion.anilistAPI
|
||||
import com.hexated.SoraStream.Companion.base64DecodeAPI
|
||||
import com.hexated.SoraStream.Companion.baymoviesAPI
|
||||
import com.hexated.SoraStream.Companion.crunchyrollAPI
|
||||
|
@ -10,7 +11,6 @@ import com.hexated.SoraStream.Companion.gdbot
|
|||
import com.hexated.SoraStream.Companion.putlockerAPI
|
||||
import com.hexated.SoraStream.Companion.smashyStreamAPI
|
||||
import com.hexated.SoraStream.Companion.tvMoviesAPI
|
||||
import com.hexated.SoraStream.Companion.twoEmbedAPI
|
||||
import com.hexated.SoraStream.Companion.watchOnlineAPI
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.APIHolder.getCaptchaToken
|
||||
|
@ -40,7 +40,8 @@ import javax.crypto.spec.SecretKeySpec
|
|||
import kotlin.collections.ArrayList
|
||||
import kotlin.math.min
|
||||
|
||||
val soraAPI = base64DecodeAPI("cA==YXA=cy8=Y20=di8=LnQ=b2s=a2w=bG8=aS4=YXA=ZS0=aWw=b2I=LW0=Z2E=Ly8=czo=dHA=aHQ=")
|
||||
val soraAPI =
|
||||
base64DecodeAPI("cA==YXA=cy8=Y20=di8=LnQ=b2s=a2w=bG8=aS4=YXA=ZS0=aWw=b2I=LW0=Z2E=Ly8=czo=dHA=aHQ=")
|
||||
val bflixChipperKey = base64DecodeAPI("Yjc=ejM=TzA=YTk=WHE=WnU=bXU=RFo=")
|
||||
val bflixKey = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
||||
const val kaguyaBaseUrl = "https://kaguya.app/"
|
||||
|
@ -535,7 +536,8 @@ suspend fun invokeSmashyRip(
|
|||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit,
|
||||
) {
|
||||
val script = app.get(url).document.selectFirst("script:containsData(player =)")?.data() ?: return
|
||||
val script =
|
||||
app.get(url).document.selectFirst("script:containsData(player =)")?.data() ?: return
|
||||
|
||||
val source = Regex("file:\\s*\"([^\"]+)").find(script)?.groupValues?.get(1)
|
||||
val subtitle = Regex("subtitle:\\s*\"([^\"]+)").find(script)?.groupValues?.get(1)
|
||||
|
@ -957,10 +959,11 @@ suspend fun getCrunchyrollId(aniId: String?): String? {
|
|||
"variables" to variables
|
||||
).toJson().toRequestBody(RequestBodyTypes.JSON.toMediaTypeOrNull())
|
||||
|
||||
val externalLinks = app.post("https://graphql.anilist.co", requestBody = data)
|
||||
val externalLinks = app.post(anilistAPI, requestBody = data)
|
||||
.parsedSafe<AnilistResponses>()?.data?.Media?.externalLinks
|
||||
|
||||
return (externalLinks?.find { it.site == "VRV" } ?: externalLinks?.find { it.site == "Crunchyroll" })?.url?.let {
|
||||
return (externalLinks?.find { it.site == "VRV" }
|
||||
?: externalLinks?.find { it.site == "Crunchyroll" })?.url?.let {
|
||||
Regex("series/(\\w+)/?").find(it)?.groupValues?.get(1)
|
||||
}
|
||||
}
|
||||
|
@ -1011,6 +1014,89 @@ suspend fun PutlockerResponses?.callback(
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun convertTmdbToAnimeId(
|
||||
title: String?,
|
||||
date: String?,
|
||||
airedDate: String?,
|
||||
type: TvType
|
||||
): AniIds {
|
||||
val sDate = date?.split("-")
|
||||
val sAiredDate = airedDate?.split("-")
|
||||
|
||||
val year = sDate?.firstOrNull()?.toIntOrNull()
|
||||
val airedYear = sAiredDate?.firstOrNull()?.toIntOrNull()
|
||||
val season = getSeason(sDate?.get(1)?.toIntOrNull())
|
||||
val airedSeason = getSeason(sAiredDate?.get(1)?.toIntOrNull())
|
||||
|
||||
return if (type == TvType.AnimeMovie) {
|
||||
tmdbToAnimeId(title, airedYear, "", type)
|
||||
} else {
|
||||
val ids = tmdbToAnimeId(title, year, season, type)
|
||||
if (ids.id == null && ids.idMal == null) tmdbToAnimeId(
|
||||
title,
|
||||
airedYear,
|
||||
airedSeason,
|
||||
type
|
||||
) else ids
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun tmdbToAnimeId(title: String?, year: Int?, season: String?, type: TvType): AniIds {
|
||||
val query = """
|
||||
query (
|
||||
${'$'}page: Int = 1
|
||||
${'$'}search: String
|
||||
${'$'}sort: [MediaSort] = [POPULARITY_DESC, SCORE_DESC]
|
||||
${'$'}type: MediaType
|
||||
${'$'}season: MediaSeason
|
||||
${'$'}seasonYear: Int
|
||||
${'$'}format: [MediaFormat]
|
||||
) {
|
||||
Page(page: ${'$'}page, perPage: 20) {
|
||||
media(
|
||||
search: ${'$'}search
|
||||
sort: ${'$'}sort
|
||||
type: ${'$'}type
|
||||
season: ${'$'}season
|
||||
seasonYear: ${'$'}seasonYear
|
||||
format_in: ${'$'}format
|
||||
) {
|
||||
id
|
||||
idMal
|
||||
}
|
||||
}
|
||||
}
|
||||
""".trimIndent().trim()
|
||||
|
||||
val variables = mapOf(
|
||||
"search" to title,
|
||||
"sort" to "SEARCH_MATCH",
|
||||
"type" to "ANIME",
|
||||
"season" to season?.uppercase(),
|
||||
"seasonYear" to year,
|
||||
"format" to listOf(if (type == TvType.AnimeMovie) "MOVIE" else "TV")
|
||||
).filterValues { value -> value != null && value.toString().isNotEmpty() }
|
||||
|
||||
val data = mapOf(
|
||||
"query" to query,
|
||||
"variables" to variables
|
||||
).toJson().toRequestBody(RequestBodyTypes.JSON.toMediaTypeOrNull())
|
||||
|
||||
val res = app.post(anilistAPI, requestBody = data)
|
||||
.parsedSafe<AniSearch>()?.data?.Page?.media?.firstOrNull()
|
||||
return AniIds(res?.id, res?.idMal)
|
||||
|
||||
}
|
||||
|
||||
fun getSeason(month: Int?): String? {
|
||||
val seasons = arrayOf(
|
||||
"Winter", "Winter", "Spring", "Spring", "Spring", "Summer",
|
||||
"Summer", "Summer", "Fall", "Fall", "Fall", "Winter"
|
||||
)
|
||||
if(month == null) return null
|
||||
return seasons[month - 1]
|
||||
}
|
||||
|
||||
fun getPutlockerQuality(quality: String): Int {
|
||||
return when {
|
||||
quality.contains("NAME=\"1080p\"") || quality.contains("RESOLUTION=1920x1080") -> Qualities.P1080.value
|
||||
|
|
Loading…
Reference in a new issue