sora: make my own mapping for anime

This commit is contained in:
hexated 2023-05-22 22:46:24 +07:00
parent aa25976a1b
commit 03d3731979
4 changed files with 184 additions and 72 deletions

View File

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

View File

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

View File

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

View File

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