mirror of
https://github.com/hexated/cloudstream-extensions-hexated.git
synced 2024-08-15 00:03:22 +00:00
Anichi: added new sources and optimize code
This commit is contained in:
parent
682d6c4748
commit
1476eef085
5 changed files with 663 additions and 500 deletions
|
@ -1,7 +1,7 @@
|
||||||
import org.jetbrains.kotlin.konan.properties.Properties
|
import org.jetbrains.kotlin.konan.properties.Properties
|
||||||
|
|
||||||
// use an integer for version numbers
|
// use an integer for version numbers
|
||||||
version = 5
|
version = 6
|
||||||
|
|
||||||
android {
|
android {
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
|
|
|
@ -1,24 +1,19 @@
|
||||||
package com.hexated
|
package com.hexated
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty
|
import com.hexated.AnichiExtractors.invokeExternalSources
|
||||||
|
import com.hexated.AnichiExtractors.invokeInternalSources
|
||||||
import com.lagradost.cloudstream3.*
|
import com.lagradost.cloudstream3.*
|
||||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addActors
|
import com.lagradost.cloudstream3.LoadResponse.Companion.addActors
|
||||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addAniListId
|
import com.lagradost.cloudstream3.LoadResponse.Companion.addAniListId
|
||||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addMalId
|
import com.lagradost.cloudstream3.LoadResponse.Companion.addMalId
|
||||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
|
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
|
||||||
import com.lagradost.cloudstream3.extractors.helper.GogoHelper
|
|
||||||
import com.lagradost.cloudstream3.mvvm.logError
|
|
||||||
import com.lagradost.cloudstream3.mvvm.safeApiCall
|
|
||||||
import com.lagradost.cloudstream3.utils.*
|
import com.lagradost.cloudstream3.utils.*
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
|
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.toJson
|
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
|
|
||||||
import com.lagradost.nicehttp.RequestBodyTypes
|
import com.lagradost.nicehttp.RequestBodyTypes
|
||||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
import java.net.URI
|
|
||||||
|
|
||||||
class Anichi : MainAPI() {
|
open class Anichi : MainAPI() {
|
||||||
override var name = "Anichi"
|
override var name = "Anichi"
|
||||||
override val instantLinkLoading = true
|
override val instantLinkLoading = true
|
||||||
override val hasQuickSearch = false
|
override val hasQuickSearch = false
|
||||||
|
@ -134,12 +129,20 @@ class Anichi : MainAPI() {
|
||||||
val description = showData.description
|
val description = showData.description
|
||||||
val poster = showData.thumbnail
|
val poster = showData.thumbnail
|
||||||
|
|
||||||
|
val trackers = getTracker(
|
||||||
|
title,
|
||||||
|
showData.altNames?.firstOrNull(),
|
||||||
|
showData.airedStart?.year,
|
||||||
|
showData.season?.quarter,
|
||||||
|
showData.type
|
||||||
|
)
|
||||||
|
|
||||||
val episodes = showData.availableEpisodesDetail.let {
|
val episodes = showData.availableEpisodesDetail.let {
|
||||||
if (it == null) return@let Pair(null, null)
|
if (it == null) return@let Pair(null, null)
|
||||||
if (showData.Id == null) return@let Pair(null, null)
|
if (showData.Id == null) return@let Pair(null, null)
|
||||||
Pair(
|
Pair(
|
||||||
it.getEpisode("sub", showData.Id),
|
it.getEpisode("sub", showData.Id, trackers?.idMal),
|
||||||
it.getEpisode("dub", showData.Id),
|
it.getEpisode("dub", showData.Id, trackers?.idMal),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,8 +158,6 @@ class Anichi : MainAPI() {
|
||||||
Pair(Actor(name, image), role)
|
Pair(Actor(name, image), role)
|
||||||
}
|
}
|
||||||
|
|
||||||
val trackers = getTracker(title, showData.altNames?.firstOrNull(), showData.airedStart?.year, showData.season?.quarter, showData.type)
|
|
||||||
|
|
||||||
return newAnimeLoadResponse(title ?: "", url, TvType.Anime) {
|
return newAnimeLoadResponse(title ?: "", url, TvType.Anime) {
|
||||||
engName = showData.altNames?.firstOrNull()
|
engName = showData.altNames?.firstOrNull()
|
||||||
posterUrl = trackers?.coverImage?.extraLarge ?: trackers?.coverImage?.large ?: poster
|
posterUrl = trackers?.coverImage?.extraLarge ?: trackers?.coverImage?.large ?: poster
|
||||||
|
@ -167,7 +168,6 @@ class Anichi : MainAPI() {
|
||||||
duration = showData.episodeDuration?.div(60_000)
|
duration = showData.episodeDuration?.div(60_000)
|
||||||
addTrailer(showData.prevideos.filter { it.isNotBlank() }
|
addTrailer(showData.prevideos.filter { it.isNotBlank() }
|
||||||
.map { "https://www.youtube.com/watch?v=$it" })
|
.map { "https://www.youtube.com/watch?v=$it" })
|
||||||
|
|
||||||
addEpisodes(DubStatus.Subbed, episodes.first)
|
addEpisodes(DubStatus.Subbed, episodes.first)
|
||||||
addEpisodes(DubStatus.Dubbed, episodes.second)
|
addEpisodes(DubStatus.Dubbed, episodes.second)
|
||||||
addActors(characters)
|
addActors(characters)
|
||||||
|
@ -189,507 +189,48 @@ class Anichi : MainAPI() {
|
||||||
|
|
||||||
val loadData = parseJson<AnichiLoadData>(data)
|
val loadData = parseJson<AnichiLoadData>(data)
|
||||||
|
|
||||||
val apiUrl =
|
argamap(
|
||||||
"""$apiUrl?variables={"showId":"${loadData.hash}","translationType":"${loadData.dubStatus}","episodeString":"${loadData.episode}"}&extensions={"persistedQuery":{"version":1,"sha256Hash":"$serverHash"}}"""
|
{
|
||||||
val apiResponse = app.get(apiUrl, headers = headers).parsed<LinksQuery>()
|
invokeInternalSources(
|
||||||
|
loadData.hash,
|
||||||
|
loadData.dubStatus,
|
||||||
|
loadData.episode,
|
||||||
|
subtitleCallback,
|
||||||
|
callback
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
invokeExternalSources(
|
||||||
|
loadData.idMal,
|
||||||
|
loadData.dubStatus,
|
||||||
|
loadData.episode,
|
||||||
|
subtitleCallback,
|
||||||
|
callback
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
apiResponse.data?.episode?.sourceUrls?.apmap { source ->
|
|
||||||
safeApiCall {
|
|
||||||
val link = fixSourceUrls(source.sourceUrl ?: return@safeApiCall, source.sourceName) ?: return@safeApiCall
|
|
||||||
if (URI(link).isAbsolute || link.startsWith("//")) {
|
|
||||||
val fixedLink = if (link.startsWith("//")) "https:$link" else link
|
|
||||||
val host = link.getHost()
|
|
||||||
|
|
||||||
when {
|
|
||||||
fixedLink.contains(Regex("(?i)playtaku|gogo")) || source.sourceName == "Vid-mp4" -> {
|
|
||||||
invokeGogo(fixedLink, subtitleCallback, callback)
|
|
||||||
}
|
|
||||||
embedIsBlacklisted(fixedLink) -> {
|
|
||||||
loadExtractor(fixedLink, subtitleCallback, callback)
|
|
||||||
}
|
|
||||||
URI(fixedLink).path.contains(".m3u") -> {
|
|
||||||
getM3u8Qualities(fixedLink, serverUrl, host).forEach(callback)
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
callback(
|
|
||||||
ExtractorLink(
|
|
||||||
name,
|
|
||||||
host,
|
|
||||||
fixedLink,
|
|
||||||
serverUrl,
|
|
||||||
Qualities.P1080.value,
|
|
||||||
false
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
val fixedLink = link.fixUrlPath()
|
|
||||||
val links = app.get(fixedLink).parsedSafe<AnichiVideoApiResponse>()?.links
|
|
||||||
?: emptyList()
|
|
||||||
links.forEach { server ->
|
|
||||||
val host = server.link.getHost()
|
|
||||||
when {
|
|
||||||
source.sourceName == "Default" -> {
|
|
||||||
if (server.resolutionStr == "SUB" || server.resolutionStr == "Alt vo_SUB") {
|
|
||||||
getM3u8Qualities(
|
|
||||||
server.link,
|
|
||||||
"https://static.crunchyroll.com/",
|
|
||||||
host,
|
|
||||||
).forEach(callback)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
server.hls != null && server.hls -> {
|
|
||||||
getM3u8Qualities(
|
|
||||||
server.link,
|
|
||||||
"$apiEndPoint/player?uri=" + (if (URI(server.link).host.isNotEmpty()) server.link else apiEndPoint + URI(
|
|
||||||
server.link
|
|
||||||
).path),
|
|
||||||
host
|
|
||||||
).forEach(callback)
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
callback(
|
|
||||||
ExtractorLink(
|
|
||||||
host,
|
|
||||||
host,
|
|
||||||
server.link,
|
|
||||||
"$apiEndPoint/player?uri=" + (if (URI(server.link).host.isNotEmpty()) server.link else apiEndPoint + URI(
|
|
||||||
server.link
|
|
||||||
).path),
|
|
||||||
server.resolutionStr.removeSuffix("p").toIntOrNull()
|
|
||||||
?: Qualities.P1080.value,
|
|
||||||
false,
|
|
||||||
isDash = server.resolutionStr == "Dash 1"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
server.subtitles?.map { sub ->
|
|
||||||
subtitleCallback.invoke(
|
|
||||||
SubtitleFile(
|
|
||||||
SubtitleHelper.fromTwoLettersToLanguage(sub.lang ?: "") ?: sub.lang ?: "",
|
|
||||||
httpsify(sub.src ?: return@map)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
private val embedBlackList = listOf(
|
|
||||||
"https://mp4upload.com/",
|
|
||||||
"https://streamsb.net/",
|
|
||||||
"https://dood.to/",
|
|
||||||
"https://videobin.co/",
|
|
||||||
"https://ok.ru",
|
|
||||||
"https://streamlare.com",
|
|
||||||
"streaming.php",
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun embedIsBlacklisted(url: String): Boolean {
|
|
||||||
embedBlackList.forEach {
|
|
||||||
if (it.javaClass.name == "kotlin.text.Regex") {
|
|
||||||
if ((it as Regex).matches(url)) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (url.contains(it)) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun AvailableEpisodesDetail.getEpisode(
|
|
||||||
lang: String,
|
|
||||||
id: String
|
|
||||||
): List<com.lagradost.cloudstream3.Episode> {
|
|
||||||
val meta = if (lang == "sub") this.sub else this.dub
|
|
||||||
return meta.map { eps ->
|
|
||||||
Episode(
|
|
||||||
AnichiLoadData(id, lang, eps).toJson(),
|
|
||||||
"Ep $eps",
|
|
||||||
episode = eps.toIntOrNull()
|
|
||||||
)
|
|
||||||
}.reversed()
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun getM3u8Qualities(
|
|
||||||
m3u8Link: String,
|
|
||||||
referer: String,
|
|
||||||
qualityName: String,
|
|
||||||
): List<ExtractorLink> {
|
|
||||||
return M3u8Helper.generateM3u8(
|
|
||||||
this.name,
|
|
||||||
m3u8Link,
|
|
||||||
referer,
|
|
||||||
name = qualityName
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun invokeGogo(
|
|
||||||
link: String,
|
|
||||||
subtitleCallback: (SubtitleFile) -> Unit,
|
|
||||||
callback: (ExtractorLink) -> Unit
|
|
||||||
) {
|
|
||||||
val iframe = app.get(link)
|
|
||||||
val iframeDoc = iframe.document
|
|
||||||
argamap({
|
|
||||||
iframeDoc.select(".list-server-items > .linkserver")
|
|
||||||
.forEach { element ->
|
|
||||||
val status = element.attr("data-status") ?: return@forEach
|
|
||||||
if (status != "1") return@forEach
|
|
||||||
val extractorData = element.attr("data-video") ?: return@forEach
|
|
||||||
loadExtractor(extractorData, iframe.url, subtitleCallback, callback)
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
val iv = "3134003223491201"
|
|
||||||
val secretKey = "37911490979715163134003223491201"
|
|
||||||
val secretDecryptKey = "54674138327930866480207815084989"
|
|
||||||
GogoHelper.extractVidstream(
|
|
||||||
iframe.url,
|
|
||||||
"Gogoanime",
|
|
||||||
callback,
|
|
||||||
iv,
|
|
||||||
secretKey,
|
|
||||||
secretDecryptKey,
|
|
||||||
isUsingAdaptiveKeys = false,
|
|
||||||
isUsingAdaptiveData = true,
|
|
||||||
iframeDocument = iframeDoc
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun String.getHost(): String {
|
|
||||||
return fixTitle(URI(this).host.substringBeforeLast(".").substringAfterLast("."))
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun String.fixUrlPath() : String {
|
|
||||||
return if(this.contains(".json?")) apiEndPoint + this else apiEndPoint + URI(this).path + ".json?" + URI(this).query
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun fixSourceUrls(url: String, source: String?) : String? {
|
|
||||||
return if(source == "Ak" || url.contains("/player/vitemb")) {
|
|
||||||
tryParseJson<AkIframe>(base64Decode(url.substringAfter("=")))?.idUrl
|
|
||||||
} else {
|
|
||||||
url.replace(" ", "%20")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun getTracker(name: String?, altName: String?, year: Int?, season: String?, type: String?): AniMedia? {
|
|
||||||
val ids = fetchId(name, year, season, type)
|
|
||||||
return if (ids?.id == null && ids?.idMal == null) fetchId(
|
|
||||||
altName,
|
|
||||||
year,
|
|
||||||
season,
|
|
||||||
type
|
|
||||||
) else ids
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun fetchId(title: String?, year: Int?, season: String?, type: String?): AniMedia? {
|
|
||||||
val query = """
|
|
||||||
query (
|
|
||||||
${'$'}page: Int = 1
|
|
||||||
${'$'}search: String
|
|
||||||
${'$'}sort: [MediaSort] = [POPULARITY_DESC, SCORE_DESC]
|
|
||||||
${'$'}type: MediaType
|
|
||||||
${'$'}season: MediaSeason
|
|
||||||
${'$'}year: String
|
|
||||||
${'$'}format: [MediaFormat]
|
|
||||||
) {
|
|
||||||
Page(page: ${'$'}page, perPage: 20) {
|
|
||||||
media(
|
|
||||||
search: ${'$'}search
|
|
||||||
sort: ${'$'}sort
|
|
||||||
type: ${'$'}type
|
|
||||||
season: ${'$'}season
|
|
||||||
startDate_like: ${'$'}year
|
|
||||||
format_in: ${'$'}format
|
|
||||||
) {
|
|
||||||
id
|
|
||||||
idMal
|
|
||||||
coverImage { extraLarge large }
|
|
||||||
bannerImage
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
""".trimIndent().trim()
|
|
||||||
|
|
||||||
val variables = mapOf(
|
|
||||||
"search" to title,
|
|
||||||
"sort" to "SEARCH_MATCH",
|
|
||||||
"type" to "ANIME",
|
|
||||||
"season" to if(type.equals("ona", true)) "" else season?.uppercase(),
|
|
||||||
"year" to "$year%",
|
|
||||||
"format" to listOf(type?.uppercase())
|
|
||||||
).filterValues { value -> value != null && value.toString().isNotEmpty() }
|
|
||||||
|
|
||||||
val data = mapOf(
|
|
||||||
"query" to query,
|
|
||||||
"variables" to variables
|
|
||||||
).toJson().toRequestBody(RequestBodyTypes.JSON.toMediaTypeOrNull())
|
|
||||||
|
|
||||||
return try {
|
|
||||||
app.post("https://graphql.anilist.co", requestBody = data)
|
|
||||||
.parsedSafe<AniSearch>()?.data?.Page?.media?.firstOrNull()
|
|
||||||
} catch (t: Throwable) {
|
|
||||||
logError(t)
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val apiUrl = BuildConfig.ANICHI_API
|
const val apiUrl = BuildConfig.ANICHI_API
|
||||||
private const val serverUrl = BuildConfig.ANICHI_SERVER
|
const val serverUrl = BuildConfig.ANICHI_SERVER
|
||||||
private const val apiEndPoint = BuildConfig.ANICHI_ENDPOINT
|
const val apiEndPoint = BuildConfig.ANICHI_ENDPOINT
|
||||||
|
|
||||||
|
const val marinHost = "https://marin.moe"
|
||||||
|
|
||||||
private const val mainHash = "e42a4466d984b2c0a2cecae5dd13aa68867f634b16ee0f17b380047d14482406"
|
private const val mainHash = "e42a4466d984b2c0a2cecae5dd13aa68867f634b16ee0f17b380047d14482406"
|
||||||
private const val popularHash = "31a117653812a2547fd981632e8c99fa8bf8a75c4ef1a77a1567ef1741a7ab9c"
|
private const val popularHash = "31a117653812a2547fd981632e8c99fa8bf8a75c4ef1a77a1567ef1741a7ab9c"
|
||||||
private const val slugHash = "bf603205eb2533ca21d0324a11f623854d62ed838a27e1b3fcfb712ab98b03f4"
|
private const val slugHash = "bf603205eb2533ca21d0324a11f623854d62ed838a27e1b3fcfb712ab98b03f4"
|
||||||
private const val detailHash = "bb263f91e5bdd048c1c978f324613aeccdfe2cbc694a419466a31edb58c0cc0b"
|
private const val detailHash = "bb263f91e5bdd048c1c978f324613aeccdfe2cbc694a419466a31edb58c0cc0b"
|
||||||
private const val serverHash = "5e7e17cdd0166af5a2d8f43133d9ce3ce9253d1fdb5160a0cfd515564f98d061"
|
const val serverHash = "5e7e17cdd0166af5a2d8f43133d9ce3ce9253d1fdb5160a0cfd515564f98d061"
|
||||||
|
|
||||||
private val headers = mapOf(
|
val headers = mapOf(
|
||||||
"app-version" to "android_c-247",
|
"app-version" to "android_c-247",
|
||||||
"from-app" to BuildConfig.ANICHI_APP,
|
"from-app" to BuildConfig.ANICHI_APP,
|
||||||
"platformstr" to "android_c",
|
"platformstr" to "android_c",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class AnichiLoadData(
|
|
||||||
val hash: String,
|
|
||||||
val dubStatus: String,
|
|
||||||
val episode: String
|
|
||||||
)
|
|
||||||
|
|
||||||
data class CoverImage(
|
|
||||||
@JsonProperty("extraLarge") var extraLarge: String? = null,
|
|
||||||
@JsonProperty("large") var large: String? = null,
|
|
||||||
)
|
|
||||||
|
|
||||||
data class AniMedia(
|
|
||||||
@JsonProperty("id") var id: Int? = null,
|
|
||||||
@JsonProperty("idMal") var idMal: Int? = null,
|
|
||||||
@JsonProperty("coverImage") var coverImage: CoverImage? = null,
|
|
||||||
@JsonProperty("bannerImage") var bannerImage: String? = null,
|
|
||||||
)
|
|
||||||
|
|
||||||
data class AniPage(
|
|
||||||
@JsonProperty("media") var media: ArrayList<AniMedia> = arrayListOf()
|
|
||||||
)
|
|
||||||
|
|
||||||
data class AniData(
|
|
||||||
@JsonProperty("Page") var Page: AniPage? = AniPage()
|
|
||||||
)
|
|
||||||
|
|
||||||
data class AniSearch(
|
|
||||||
@JsonProperty("data") var data: AniData? = AniData()
|
|
||||||
)
|
|
||||||
|
|
||||||
data class AkIframe(
|
|
||||||
@JsonProperty("idUrl") val idUrl: String? = null,
|
|
||||||
)
|
|
||||||
|
|
||||||
data class Stream(
|
|
||||||
@JsonProperty("format") val format: String? = null,
|
|
||||||
@JsonProperty("audio_lang") val audio_lang: String? = null,
|
|
||||||
@JsonProperty("hardsub_lang") val hardsub_lang: String? = null,
|
|
||||||
@JsonProperty("url") val url: String? = null,
|
|
||||||
)
|
|
||||||
|
|
||||||
data class PortData(
|
|
||||||
@JsonProperty("streams") val streams: ArrayList<Stream>? = arrayListOf(),
|
|
||||||
)
|
|
||||||
|
|
||||||
data class Subtitles(
|
|
||||||
@JsonProperty("lang") val lang: String?,
|
|
||||||
@JsonProperty("label") val label: String?,
|
|
||||||
@JsonProperty("src") val src: String?,
|
|
||||||
)
|
|
||||||
|
|
||||||
data class Links(
|
|
||||||
@JsonProperty("link") val link: String,
|
|
||||||
@JsonProperty("hls") val hls: Boolean?,
|
|
||||||
@JsonProperty("resolutionStr") val resolutionStr: String,
|
|
||||||
@JsonProperty("src") val src: String?,
|
|
||||||
@JsonProperty("portData") val portData: PortData? = null,
|
|
||||||
@JsonProperty("subtitles") val subtitles: ArrayList<Subtitles>? = arrayListOf(),
|
|
||||||
)
|
|
||||||
|
|
||||||
data class AnichiVideoApiResponse(
|
|
||||||
@JsonProperty("links") val links: List<Links>
|
|
||||||
)
|
|
||||||
|
|
||||||
data class Data(
|
|
||||||
@JsonProperty("shows") val shows: Shows? = null,
|
|
||||||
@JsonProperty("queryListForTag") val queryListForTag: Shows? = null,
|
|
||||||
@JsonProperty("queryPopular") val queryPopular: Shows? = null,
|
|
||||||
)
|
|
||||||
|
|
||||||
data class Shows(
|
|
||||||
@JsonProperty("edges") val edges: List<Edges>? = arrayListOf(),
|
|
||||||
@JsonProperty("recommendations") val recommendations: List<EdgesCard>? = arrayListOf(),
|
|
||||||
)
|
|
||||||
|
|
||||||
data class EdgesCard(
|
|
||||||
@JsonProperty("anyCard") val anyCard: Edges? = null,
|
|
||||||
)
|
|
||||||
|
|
||||||
data class CharacterImage(
|
|
||||||
@JsonProperty("large") val large: String?,
|
|
||||||
@JsonProperty("medium") val medium: String?
|
|
||||||
)
|
|
||||||
|
|
||||||
data class CharacterName(
|
|
||||||
@JsonProperty("full") val full: String?,
|
|
||||||
@JsonProperty("native") val native: String?
|
|
||||||
)
|
|
||||||
|
|
||||||
data class Characters(
|
|
||||||
@JsonProperty("image") val image: CharacterImage?,
|
|
||||||
@JsonProperty("role") val role: String?,
|
|
||||||
@JsonProperty("name") val name: CharacterName?,
|
|
||||||
)
|
|
||||||
|
|
||||||
data class Edges(
|
|
||||||
@JsonProperty("_id") val Id: String?,
|
|
||||||
@JsonProperty("name") val name: String?,
|
|
||||||
@JsonProperty("englishName") val englishName: String?,
|
|
||||||
@JsonProperty("nativeName") val nativeName: String?,
|
|
||||||
@JsonProperty("thumbnail") val thumbnail: String?,
|
|
||||||
@JsonProperty("type") val type: String?,
|
|
||||||
@JsonProperty("season") val season: Season?,
|
|
||||||
@JsonProperty("score") val score: Double?,
|
|
||||||
@JsonProperty("airedStart") val airedStart: AiredStart?,
|
|
||||||
@JsonProperty("availableEpisodes") val availableEpisodes: AvailableEpisodes?,
|
|
||||||
@JsonProperty("availableEpisodesDetail") val availableEpisodesDetail: AvailableEpisodesDetail?,
|
|
||||||
@JsonProperty("studios") val studios: List<String>?,
|
|
||||||
@JsonProperty("genres") val genres: List<String>?,
|
|
||||||
@JsonProperty("averageScore") val averageScore: Int?,
|
|
||||||
@JsonProperty("characters") val characters: List<Characters>?,
|
|
||||||
@JsonProperty("altNames") val altNames: List<String>?,
|
|
||||||
@JsonProperty("description") val description: String?,
|
|
||||||
@JsonProperty("status") val status: String?,
|
|
||||||
@JsonProperty("banner") val banner: String?,
|
|
||||||
@JsonProperty("episodeDuration") val episodeDuration: Int?,
|
|
||||||
@JsonProperty("prevideos") val prevideos: List<String> = emptyList(),
|
|
||||||
)
|
|
||||||
|
|
||||||
data class AvailableEpisodes(
|
|
||||||
@JsonProperty("sub") val sub: Int,
|
|
||||||
@JsonProperty("dub") val dub: Int,
|
|
||||||
@JsonProperty("raw") val raw: Int
|
|
||||||
)
|
|
||||||
|
|
||||||
data class AiredStart(
|
|
||||||
@JsonProperty("year") val year: Int,
|
|
||||||
@JsonProperty("month") val month: Int,
|
|
||||||
@JsonProperty("date") val date: Int
|
|
||||||
)
|
|
||||||
|
|
||||||
data class Season(
|
|
||||||
@JsonProperty("quarter") val quarter: String,
|
|
||||||
@JsonProperty("year") val year: Int
|
|
||||||
)
|
|
||||||
|
|
||||||
data class AnichiQuery(
|
|
||||||
@JsonProperty("data") val data: Data? = null
|
|
||||||
)
|
|
||||||
|
|
||||||
data class Detail(
|
|
||||||
@JsonProperty("data") val data: DetailShow
|
|
||||||
)
|
|
||||||
|
|
||||||
data class DetailShow(
|
|
||||||
@JsonProperty("show") val show: Edges
|
|
||||||
)
|
|
||||||
|
|
||||||
data class AvailableEpisodesDetail(
|
|
||||||
@JsonProperty("sub") val sub: List<String>,
|
|
||||||
@JsonProperty("dub") val dub: List<String>,
|
|
||||||
@JsonProperty("raw") val raw: List<String>
|
|
||||||
)
|
|
||||||
|
|
||||||
data class LinksQuery(
|
|
||||||
@JsonProperty("data") val data: LinkData? = LinkData()
|
|
||||||
)
|
|
||||||
|
|
||||||
data class LinkData(
|
|
||||||
@JsonProperty("episode") val episode: Episode? = Episode()
|
|
||||||
)
|
|
||||||
|
|
||||||
data class SourceUrls(
|
|
||||||
@JsonProperty("sourceUrl") val sourceUrl: String? = null,
|
|
||||||
@JsonProperty("priority") val priority: Int? = null,
|
|
||||||
@JsonProperty("sourceName") val sourceName: String? = null,
|
|
||||||
@JsonProperty("type") val type: String? = null,
|
|
||||||
@JsonProperty("className") val className: String? = null,
|
|
||||||
@JsonProperty("streamerId") val streamerId: String? = null
|
|
||||||
)
|
|
||||||
|
|
||||||
data class Episode(
|
|
||||||
@JsonProperty("sourceUrls") val sourceUrls: ArrayList<SourceUrls> = arrayListOf(),
|
|
||||||
)
|
|
||||||
|
|
||||||
data class Sub(
|
|
||||||
@JsonProperty("hour") val hour: Int? = null,
|
|
||||||
@JsonProperty("minute") val minute: Int? = null,
|
|
||||||
@JsonProperty("year") val year: Int? = null,
|
|
||||||
@JsonProperty("month") val month: Int? = null,
|
|
||||||
@JsonProperty("date") val date: Int? = null
|
|
||||||
)
|
|
||||||
|
|
||||||
data class LastEpisodeDate(
|
|
||||||
@JsonProperty("dub") val dub: Sub? = Sub(),
|
|
||||||
@JsonProperty("sub") val sub: Sub? = Sub(),
|
|
||||||
@JsonProperty("raw") val raw: Sub? = Sub()
|
|
||||||
)
|
|
||||||
|
|
||||||
data class AnyCard(
|
|
||||||
@JsonProperty("_id") val Id: String? = null,
|
|
||||||
@JsonProperty("name") val name: String? = null,
|
|
||||||
@JsonProperty("englishName") val englishName: String? = null,
|
|
||||||
@JsonProperty("nativeName") val nativeName: String? = null,
|
|
||||||
@JsonProperty("availableEpisodes") val availableEpisodes: AvailableEpisodes? = null,
|
|
||||||
@JsonProperty("score") val score: Double? = null,
|
|
||||||
@JsonProperty("lastEpisodeDate") val lastEpisodeDate: LastEpisodeDate? = LastEpisodeDate(),
|
|
||||||
@JsonProperty("thumbnail") val thumbnail: String? = null,
|
|
||||||
@JsonProperty("lastChapterDate") val lastChapterDate: String? = null,
|
|
||||||
@JsonProperty("availableChapters") val availableChapters: String? = null,
|
|
||||||
@JsonProperty("__typename") val _typename: String? = null
|
|
||||||
)
|
|
||||||
|
|
||||||
data class PageStatus(
|
|
||||||
@JsonProperty("_id") val Id: String? = null,
|
|
||||||
@JsonProperty("views") val views: String? = null,
|
|
||||||
@JsonProperty("showId") val showId: String? = null,
|
|
||||||
@JsonProperty("rangeViews") val rangeViews: String? = null,
|
|
||||||
@JsonProperty("isManga") val isManga: Boolean? = null,
|
|
||||||
@JsonProperty("__typename") val _typename: String? = null
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
data class Recommendations(
|
|
||||||
@JsonProperty("anyCard") val anyCard: AnyCard? = null,
|
|
||||||
@JsonProperty("pageStatus") val pageStatus: PageStatus? = PageStatus(),
|
|
||||||
@JsonProperty("__typename") val _typename: String? = null
|
|
||||||
)
|
|
||||||
|
|
||||||
data class QueryPopular(
|
|
||||||
@JsonProperty("total") val total: Int? = null,
|
|
||||||
@JsonProperty("recommendations") val recommendations: ArrayList<Recommendations> = arrayListOf(),
|
|
||||||
@JsonProperty("__typename") val _typename: String? = null
|
|
||||||
)
|
|
||||||
|
|
||||||
data class DataPopular(
|
|
||||||
@JsonProperty("queryPopular") val queryPopular: QueryPopular? = QueryPopular()
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
217
Anichi/src/main/kotlin/com/hexated/AnichiExtractors.kt
Normal file
217
Anichi/src/main/kotlin/com/hexated/AnichiExtractors.kt
Normal file
|
@ -0,0 +1,217 @@
|
||||||
|
package com.hexated
|
||||||
|
|
||||||
|
import com.lagradost.cloudstream3.SubtitleFile
|
||||||
|
import com.lagradost.cloudstream3.apmap
|
||||||
|
import com.lagradost.cloudstream3.app
|
||||||
|
import com.lagradost.cloudstream3.argamap
|
||||||
|
import com.lagradost.cloudstream3.extractors.helper.GogoHelper
|
||||||
|
import com.lagradost.cloudstream3.mvvm.safeApiCall
|
||||||
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
|
import com.lagradost.cloudstream3.utils.Qualities
|
||||||
|
import com.lagradost.cloudstream3.utils.SubtitleHelper
|
||||||
|
import com.lagradost.cloudstream3.utils.httpsify
|
||||||
|
import com.lagradost.cloudstream3.utils.loadExtractor
|
||||||
|
import java.net.URI
|
||||||
|
|
||||||
|
object AnichiExtractors : Anichi() {
|
||||||
|
|
||||||
|
suspend fun invokeInternalSources(
|
||||||
|
hash: String,
|
||||||
|
dubStatus: String,
|
||||||
|
episode: String,
|
||||||
|
subtitleCallback: (SubtitleFile) -> Unit,
|
||||||
|
callback: (ExtractorLink) -> Unit,
|
||||||
|
) {
|
||||||
|
val apiUrl =
|
||||||
|
"""$apiUrl?variables={"showId":"$hash","translationType":"$dubStatus","episodeString":"$episode"}&extensions={"persistedQuery":{"version":1,"sha256Hash":"$serverHash"}}"""
|
||||||
|
val apiResponse = app.get(apiUrl, headers = headers).parsed<LinksQuery>()
|
||||||
|
|
||||||
|
apiResponse.data?.episode?.sourceUrls?.apmap { source ->
|
||||||
|
safeApiCall {
|
||||||
|
val link = fixSourceUrls(source.sourceUrl ?: return@safeApiCall, source.sourceName)
|
||||||
|
?: return@safeApiCall
|
||||||
|
if (URI(link).isAbsolute || link.startsWith("//")) {
|
||||||
|
val fixedLink = if (link.startsWith("//")) "https:$link" else link
|
||||||
|
val host = link.getHost()
|
||||||
|
|
||||||
|
when {
|
||||||
|
fixedLink.contains(Regex("(?i)playtaku|gogo")) || source.sourceName == "Vid-mp4" -> {
|
||||||
|
invokeGogo(fixedLink, subtitleCallback, callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
embedIsBlacklisted(fixedLink) -> {
|
||||||
|
loadExtractor(fixedLink, subtitleCallback, callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
URI(fixedLink).path.contains(".m3u") -> {
|
||||||
|
getM3u8Qualities(fixedLink, serverUrl, host).forEach(callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
callback(
|
||||||
|
ExtractorLink(
|
||||||
|
name,
|
||||||
|
host,
|
||||||
|
fixedLink,
|
||||||
|
serverUrl,
|
||||||
|
Qualities.P1080.value,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val fixedLink = link.fixUrlPath()
|
||||||
|
val links = app.get(fixedLink).parsedSafe<AnichiVideoApiResponse>()?.links
|
||||||
|
?: emptyList()
|
||||||
|
links.forEach { server ->
|
||||||
|
val host = server.link.getHost()
|
||||||
|
when {
|
||||||
|
source.sourceName == "Default" -> {
|
||||||
|
if (server.resolutionStr == "SUB" || server.resolutionStr == "Alt vo_SUB") {
|
||||||
|
getM3u8Qualities(
|
||||||
|
server.link,
|
||||||
|
"https://static.crunchyroll.com/",
|
||||||
|
host,
|
||||||
|
).forEach(callback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
server.hls != null && server.hls -> {
|
||||||
|
getM3u8Qualities(
|
||||||
|
server.link,
|
||||||
|
"$apiEndPoint/player?uri=" + (if (URI(server.link).host.isNotEmpty()) server.link else apiEndPoint + URI(
|
||||||
|
server.link
|
||||||
|
).path),
|
||||||
|
host
|
||||||
|
).forEach(callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
callback(
|
||||||
|
ExtractorLink(
|
||||||
|
host,
|
||||||
|
host,
|
||||||
|
server.link,
|
||||||
|
"$apiEndPoint/player?uri=" + (if (URI(server.link).host.isNotEmpty()) server.link else apiEndPoint + URI(
|
||||||
|
server.link
|
||||||
|
).path),
|
||||||
|
server.resolutionStr.removeSuffix("p").toIntOrNull()
|
||||||
|
?: Qualities.P1080.value,
|
||||||
|
false,
|
||||||
|
isDash = server.resolutionStr == "Dash 1"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
server.subtitles?.map { sub ->
|
||||||
|
subtitleCallback.invoke(
|
||||||
|
SubtitleFile(
|
||||||
|
SubtitleHelper.fromTwoLettersToLanguage(sub.lang ?: "")
|
||||||
|
?: sub.lang ?: "",
|
||||||
|
httpsify(sub.src ?: return@map)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun invokeExternalSources(
|
||||||
|
idMal: Int? = null,
|
||||||
|
dubStatus: String,
|
||||||
|
episode: String,
|
||||||
|
subtitleCallback: (SubtitleFile) -> Unit,
|
||||||
|
callback: (ExtractorLink) -> Unit,
|
||||||
|
) {
|
||||||
|
val ids = app.get("https://api.malsync.moe/mal/anime/${idMal ?: return}")
|
||||||
|
.parsedSafe<MALSyncResponses>()?.sites
|
||||||
|
|
||||||
|
if (dubStatus == "sub") invokeMarin(ids?.marin?.keys?.firstOrNull(), episode, callback)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun invokeMarin(
|
||||||
|
id: String? = null,
|
||||||
|
episode: String,
|
||||||
|
callback: (ExtractorLink) -> Unit
|
||||||
|
) {
|
||||||
|
val url = "$marinHost/anime/${id ?: return}/$episode"
|
||||||
|
val cookies = app.get(
|
||||||
|
"$marinHost/anime",
|
||||||
|
headers = mapOf(
|
||||||
|
"Cookie" to "__ddg1_=;__ddg2_=;"
|
||||||
|
),
|
||||||
|
referer = "$marinHost/anime",
|
||||||
|
).cookies.let {
|
||||||
|
it["XSRF-TOKEN"] to it["marin_session"]
|
||||||
|
}
|
||||||
|
|
||||||
|
app.get(
|
||||||
|
url,
|
||||||
|
headers = mapOf(
|
||||||
|
"Referer" to "$marinHost/",
|
||||||
|
"Cookie" to "__ddg1=;__ddg2_=; XSRF-TOKEN=${cookies.first}; marin_session=${cookies.second};",
|
||||||
|
"x-inertia" to "true",
|
||||||
|
"x-inertia-version" to "5ee7503af8c9844b1e8d34466b727694",
|
||||||
|
"X-Requested-With" to "XMLHttpRequest",
|
||||||
|
"X-XSRF-TOKEN" to decode(cookies.first.toString())
|
||||||
|
)
|
||||||
|
).parsedSafe<MarinResponses>()?.props?.video?.data?.mirror?.map { video ->
|
||||||
|
callback.invoke(
|
||||||
|
ExtractorLink(
|
||||||
|
"Marin",
|
||||||
|
"Marin",
|
||||||
|
video.code?.file ?: return@map,
|
||||||
|
url,
|
||||||
|
video.code.height ?: Qualities.Unknown.value,
|
||||||
|
headers = mapOf(
|
||||||
|
"Accept" to "video/webm,video/ogg,video/*;q=0.9,application/ogg;q=0.7,audio/*;q=0.6,*/*;q=0.5",
|
||||||
|
"Accept-Language" to "en-US,en;q=0.5",
|
||||||
|
"Cookie" to "__ddg1=;__ddg2_=; XSRF-TOKEN=${cookies.first}; marin_session=${cookies.second};",
|
||||||
|
"Connection" to "keep-alive",
|
||||||
|
"Sec-Fetch-Dest" to "video",
|
||||||
|
"Sec-Fetch-Mode" to "cors",
|
||||||
|
"Sec-Fetch-Site" to "cross-site",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun invokeGogo(
|
||||||
|
link: String,
|
||||||
|
subtitleCallback: (SubtitleFile) -> Unit,
|
||||||
|
callback: (ExtractorLink) -> Unit
|
||||||
|
) {
|
||||||
|
val iframe = app.get(link)
|
||||||
|
val iframeDoc = iframe.document
|
||||||
|
argamap({
|
||||||
|
iframeDoc.select(".list-server-items > .linkserver")
|
||||||
|
.forEach { element ->
|
||||||
|
val status = element.attr("data-status") ?: return@forEach
|
||||||
|
if (status != "1") return@forEach
|
||||||
|
val extractorData = element.attr("data-video") ?: return@forEach
|
||||||
|
loadExtractor(extractorData, iframe.url, subtitleCallback, callback)
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
val iv = "3134003223491201"
|
||||||
|
val secretKey = "37911490979715163134003223491201"
|
||||||
|
val secretDecryptKey = "54674138327930866480207815084989"
|
||||||
|
GogoHelper.extractVidstream(
|
||||||
|
iframe.url,
|
||||||
|
"Gogoanime",
|
||||||
|
callback,
|
||||||
|
iv,
|
||||||
|
secretKey,
|
||||||
|
secretDecryptKey,
|
||||||
|
isUsingAdaptiveKeys = false,
|
||||||
|
isUsingAdaptiveData = true,
|
||||||
|
iframeDocument = iframeDoc
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
267
Anichi/src/main/kotlin/com/hexated/AnichiParser.kt
Normal file
267
Anichi/src/main/kotlin/com/hexated/AnichiParser.kt
Normal file
|
@ -0,0 +1,267 @@
|
||||||
|
package com.hexated
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
|
||||||
|
data class AnichiLoadData(
|
||||||
|
val hash: String,
|
||||||
|
val dubStatus: String,
|
||||||
|
val episode: String,
|
||||||
|
val idMal: Int? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class CoverImage(
|
||||||
|
@JsonProperty("extraLarge") var extraLarge: String? = null,
|
||||||
|
@JsonProperty("large") var large: String? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class AniMedia(
|
||||||
|
@JsonProperty("id") var id: Int? = null,
|
||||||
|
@JsonProperty("idMal") var idMal: Int? = null,
|
||||||
|
@JsonProperty("coverImage") var coverImage: CoverImage? = null,
|
||||||
|
@JsonProperty("bannerImage") var bannerImage: String? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class AniPage(
|
||||||
|
@JsonProperty("media") var media: ArrayList<AniMedia> = arrayListOf()
|
||||||
|
)
|
||||||
|
|
||||||
|
data class AniData(
|
||||||
|
@JsonProperty("Page") var Page: AniPage? = AniPage()
|
||||||
|
)
|
||||||
|
|
||||||
|
data class AniSearch(
|
||||||
|
@JsonProperty("data") var data: AniData? = AniData()
|
||||||
|
)
|
||||||
|
|
||||||
|
data class AkIframe(
|
||||||
|
@JsonProperty("idUrl") val idUrl: String? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Stream(
|
||||||
|
@JsonProperty("format") val format: String? = null,
|
||||||
|
@JsonProperty("audio_lang") val audio_lang: String? = null,
|
||||||
|
@JsonProperty("hardsub_lang") val hardsub_lang: String? = null,
|
||||||
|
@JsonProperty("url") val url: String? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class PortData(
|
||||||
|
@JsonProperty("streams") val streams: ArrayList<Stream>? = arrayListOf(),
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Subtitles(
|
||||||
|
@JsonProperty("lang") val lang: String?,
|
||||||
|
@JsonProperty("label") val label: String?,
|
||||||
|
@JsonProperty("src") val src: String?,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Links(
|
||||||
|
@JsonProperty("link") val link: String,
|
||||||
|
@JsonProperty("hls") val hls: Boolean?,
|
||||||
|
@JsonProperty("resolutionStr") val resolutionStr: String,
|
||||||
|
@JsonProperty("src") val src: String?,
|
||||||
|
@JsonProperty("portData") val portData: PortData? = null,
|
||||||
|
@JsonProperty("subtitles") val subtitles: ArrayList<Subtitles>? = arrayListOf(),
|
||||||
|
)
|
||||||
|
|
||||||
|
data class AnichiVideoApiResponse(
|
||||||
|
@JsonProperty("links") val links: List<Links>
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Data(
|
||||||
|
@JsonProperty("shows") val shows: Shows? = null,
|
||||||
|
@JsonProperty("queryListForTag") val queryListForTag: Shows? = null,
|
||||||
|
@JsonProperty("queryPopular") val queryPopular: Shows? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Shows(
|
||||||
|
@JsonProperty("edges") val edges: List<Edges>? = arrayListOf(),
|
||||||
|
@JsonProperty("recommendations") val recommendations: List<EdgesCard>? = arrayListOf(),
|
||||||
|
)
|
||||||
|
|
||||||
|
data class EdgesCard(
|
||||||
|
@JsonProperty("anyCard") val anyCard: Edges? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class CharacterImage(
|
||||||
|
@JsonProperty("large") val large: String?,
|
||||||
|
@JsonProperty("medium") val medium: String?
|
||||||
|
)
|
||||||
|
|
||||||
|
data class CharacterName(
|
||||||
|
@JsonProperty("full") val full: String?,
|
||||||
|
@JsonProperty("native") val native: String?
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Characters(
|
||||||
|
@JsonProperty("image") val image: CharacterImage?,
|
||||||
|
@JsonProperty("role") val role: String?,
|
||||||
|
@JsonProperty("name") val name: CharacterName?,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Edges(
|
||||||
|
@JsonProperty("_id") val Id: String?,
|
||||||
|
@JsonProperty("name") val name: String?,
|
||||||
|
@JsonProperty("englishName") val englishName: String?,
|
||||||
|
@JsonProperty("nativeName") val nativeName: String?,
|
||||||
|
@JsonProperty("thumbnail") val thumbnail: String?,
|
||||||
|
@JsonProperty("type") val type: String?,
|
||||||
|
@JsonProperty("season") val season: Season?,
|
||||||
|
@JsonProperty("score") val score: Double?,
|
||||||
|
@JsonProperty("airedStart") val airedStart: AiredStart?,
|
||||||
|
@JsonProperty("availableEpisodes") val availableEpisodes: AvailableEpisodes?,
|
||||||
|
@JsonProperty("availableEpisodesDetail") val availableEpisodesDetail: AvailableEpisodesDetail?,
|
||||||
|
@JsonProperty("studios") val studios: List<String>?,
|
||||||
|
@JsonProperty("genres") val genres: List<String>?,
|
||||||
|
@JsonProperty("averageScore") val averageScore: Int?,
|
||||||
|
@JsonProperty("characters") val characters: List<Characters>?,
|
||||||
|
@JsonProperty("altNames") val altNames: List<String>?,
|
||||||
|
@JsonProperty("description") val description: String?,
|
||||||
|
@JsonProperty("status") val status: String?,
|
||||||
|
@JsonProperty("banner") val banner: String?,
|
||||||
|
@JsonProperty("episodeDuration") val episodeDuration: Int?,
|
||||||
|
@JsonProperty("prevideos") val prevideos: List<String> = emptyList(),
|
||||||
|
)
|
||||||
|
|
||||||
|
data class AvailableEpisodes(
|
||||||
|
@JsonProperty("sub") val sub: Int,
|
||||||
|
@JsonProperty("dub") val dub: Int,
|
||||||
|
@JsonProperty("raw") val raw: Int
|
||||||
|
)
|
||||||
|
|
||||||
|
data class AiredStart(
|
||||||
|
@JsonProperty("year") val year: Int,
|
||||||
|
@JsonProperty("month") val month: Int,
|
||||||
|
@JsonProperty("date") val date: Int
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Season(
|
||||||
|
@JsonProperty("quarter") val quarter: String,
|
||||||
|
@JsonProperty("year") val year: Int
|
||||||
|
)
|
||||||
|
|
||||||
|
data class AnichiQuery(
|
||||||
|
@JsonProperty("data") val data: Data? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Detail(
|
||||||
|
@JsonProperty("data") val data: DetailShow
|
||||||
|
)
|
||||||
|
|
||||||
|
data class DetailShow(
|
||||||
|
@JsonProperty("show") val show: Edges
|
||||||
|
)
|
||||||
|
|
||||||
|
data class AvailableEpisodesDetail(
|
||||||
|
@JsonProperty("sub") val sub: List<String>,
|
||||||
|
@JsonProperty("dub") val dub: List<String>,
|
||||||
|
@JsonProperty("raw") val raw: List<String>
|
||||||
|
)
|
||||||
|
|
||||||
|
data class LinksQuery(
|
||||||
|
@JsonProperty("data") val data: LinkData? = LinkData()
|
||||||
|
)
|
||||||
|
|
||||||
|
data class LinkData(
|
||||||
|
@JsonProperty("episode") val episode: Episode? = Episode()
|
||||||
|
)
|
||||||
|
|
||||||
|
data class SourceUrls(
|
||||||
|
@JsonProperty("sourceUrl") val sourceUrl: String? = null,
|
||||||
|
@JsonProperty("priority") val priority: Int? = null,
|
||||||
|
@JsonProperty("sourceName") val sourceName: String? = null,
|
||||||
|
@JsonProperty("type") val type: String? = null,
|
||||||
|
@JsonProperty("className") val className: String? = null,
|
||||||
|
@JsonProperty("streamerId") val streamerId: String? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Episode(
|
||||||
|
@JsonProperty("sourceUrls") val sourceUrls: ArrayList<SourceUrls> = arrayListOf(),
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Sub(
|
||||||
|
@JsonProperty("hour") val hour: Int? = null,
|
||||||
|
@JsonProperty("minute") val minute: Int? = null,
|
||||||
|
@JsonProperty("year") val year: Int? = null,
|
||||||
|
@JsonProperty("month") val month: Int? = null,
|
||||||
|
@JsonProperty("date") val date: Int? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
data class LastEpisodeDate(
|
||||||
|
@JsonProperty("dub") val dub: Sub? = Sub(),
|
||||||
|
@JsonProperty("sub") val sub: Sub? = Sub(),
|
||||||
|
@JsonProperty("raw") val raw: Sub? = Sub()
|
||||||
|
)
|
||||||
|
|
||||||
|
data class AnyCard(
|
||||||
|
@JsonProperty("_id") val Id: String? = null,
|
||||||
|
@JsonProperty("name") val name: String? = null,
|
||||||
|
@JsonProperty("englishName") val englishName: String? = null,
|
||||||
|
@JsonProperty("nativeName") val nativeName: String? = null,
|
||||||
|
@JsonProperty("availableEpisodes") val availableEpisodes: AvailableEpisodes? = null,
|
||||||
|
@JsonProperty("score") val score: Double? = null,
|
||||||
|
@JsonProperty("lastEpisodeDate") val lastEpisodeDate: LastEpisodeDate? = LastEpisodeDate(),
|
||||||
|
@JsonProperty("thumbnail") val thumbnail: String? = null,
|
||||||
|
@JsonProperty("lastChapterDate") val lastChapterDate: String? = null,
|
||||||
|
@JsonProperty("availableChapters") val availableChapters: String? = null,
|
||||||
|
@JsonProperty("__typename") val _typename: String? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
data class PageStatus(
|
||||||
|
@JsonProperty("_id") val Id: String? = null,
|
||||||
|
@JsonProperty("views") val views: String? = null,
|
||||||
|
@JsonProperty("showId") val showId: String? = null,
|
||||||
|
@JsonProperty("rangeViews") val rangeViews: String? = null,
|
||||||
|
@JsonProperty("isManga") val isManga: Boolean? = null,
|
||||||
|
@JsonProperty("__typename") val _typename: String? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
data class Recommendations(
|
||||||
|
@JsonProperty("anyCard") val anyCard: AnyCard? = null,
|
||||||
|
@JsonProperty("pageStatus") val pageStatus: PageStatus? = PageStatus(),
|
||||||
|
@JsonProperty("__typename") val _typename: String? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
data class QueryPopular(
|
||||||
|
@JsonProperty("total") val total: Int? = null,
|
||||||
|
@JsonProperty("recommendations") val recommendations: ArrayList<Recommendations> = arrayListOf(),
|
||||||
|
@JsonProperty("__typename") val _typename: String? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
data class DataPopular(
|
||||||
|
@JsonProperty("queryPopular") val queryPopular: QueryPopular? = QueryPopular()
|
||||||
|
)
|
||||||
|
|
||||||
|
data class MALSyncSites(
|
||||||
|
@JsonProperty("Zoro") val zoro: HashMap<String?, HashMap<String, String?>>? = hashMapOf(),
|
||||||
|
@JsonProperty("Marin") val marin: HashMap<String?, HashMap<String, String?>>? = hashMapOf(),
|
||||||
|
)
|
||||||
|
|
||||||
|
data class MALSyncResponses(
|
||||||
|
@JsonProperty("Sites") val sites: MALSyncSites? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class MarinCode(
|
||||||
|
@JsonProperty("file") val file: String? = null,
|
||||||
|
@JsonProperty("height") val height: Int? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class MarinMirror(
|
||||||
|
@JsonProperty("code") val code: MarinCode? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class MarinData(
|
||||||
|
@JsonProperty("mirror") val mirror: ArrayList<MarinMirror>? = arrayListOf(),
|
||||||
|
)
|
||||||
|
|
||||||
|
data class MarinVideos(
|
||||||
|
@JsonProperty("data") val data: MarinData? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class MarinProps(
|
||||||
|
@JsonProperty("video") val video: MarinVideos? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class MarinResponses(
|
||||||
|
@JsonProperty("props") val props: MarinProps? = null,
|
||||||
|
)
|
138
Anichi/src/main/kotlin/com/hexated/AnichiUtils.kt
Normal file
138
Anichi/src/main/kotlin/com/hexated/AnichiUtils.kt
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
package com.hexated
|
||||||
|
|
||||||
|
import com.hexated.Anichi.Companion.apiEndPoint
|
||||||
|
import com.lagradost.cloudstream3.Episode
|
||||||
|
import com.lagradost.cloudstream3.app
|
||||||
|
import com.lagradost.cloudstream3.base64Decode
|
||||||
|
import com.lagradost.cloudstream3.fixTitle
|
||||||
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
|
import com.lagradost.cloudstream3.utils.AppUtils
|
||||||
|
import com.lagradost.cloudstream3.utils.AppUtils.toJson
|
||||||
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
|
import com.lagradost.cloudstream3.utils.M3u8Helper
|
||||||
|
import com.lagradost.nicehttp.RequestBodyTypes
|
||||||
|
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||||
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
|
import java.net.URI
|
||||||
|
import java.net.URLDecoder
|
||||||
|
|
||||||
|
suspend fun getTracker(name: String?, altName: String?, year: Int?, season: String?, type: String?): AniMedia? {
|
||||||
|
return fetchId(name, year, season, type).takeIf { it?.id != null } ?: fetchId(
|
||||||
|
altName,
|
||||||
|
year,
|
||||||
|
season,
|
||||||
|
type
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun fetchId(title: String?, year: Int?, season: String?, type: String?): AniMedia? {
|
||||||
|
val query = """
|
||||||
|
query (
|
||||||
|
${'$'}page: Int = 1
|
||||||
|
${'$'}search: String
|
||||||
|
${'$'}sort: [MediaSort] = [POPULARITY_DESC, SCORE_DESC]
|
||||||
|
${'$'}type: MediaType
|
||||||
|
${'$'}season: MediaSeason
|
||||||
|
${'$'}year: String
|
||||||
|
${'$'}format: [MediaFormat]
|
||||||
|
) {
|
||||||
|
Page(page: ${'$'}page, perPage: 20) {
|
||||||
|
media(
|
||||||
|
search: ${'$'}search
|
||||||
|
sort: ${'$'}sort
|
||||||
|
type: ${'$'}type
|
||||||
|
season: ${'$'}season
|
||||||
|
startDate_like: ${'$'}year
|
||||||
|
format_in: ${'$'}format
|
||||||
|
) {
|
||||||
|
id
|
||||||
|
idMal
|
||||||
|
coverImage { extraLarge large }
|
||||||
|
bannerImage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".trimIndent().trim()
|
||||||
|
|
||||||
|
val variables = mapOf(
|
||||||
|
"search" to title,
|
||||||
|
"sort" to "SEARCH_MATCH",
|
||||||
|
"type" to "ANIME",
|
||||||
|
"season" to if(type.equals("ona", true)) "" else season?.uppercase(),
|
||||||
|
"year" to "$year%",
|
||||||
|
"format" to listOf(type?.uppercase())
|
||||||
|
).filterValues { value -> value != null && value.toString().isNotEmpty() }
|
||||||
|
|
||||||
|
val data = mapOf(
|
||||||
|
"query" to query,
|
||||||
|
"variables" to variables
|
||||||
|
).toJson().toRequestBody(RequestBodyTypes.JSON.toMediaTypeOrNull())
|
||||||
|
|
||||||
|
return try {
|
||||||
|
app.post("https://graphql.anilist.co", requestBody = data)
|
||||||
|
.parsedSafe<AniSearch>()?.data?.Page?.media?.firstOrNull()
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
logError(t)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun decode(input: String): String = URLDecoder.decode(input, "utf-8")
|
||||||
|
|
||||||
|
private val embedBlackList = listOf(
|
||||||
|
"https://mp4upload.com/",
|
||||||
|
"https://streamsb.net/",
|
||||||
|
"https://dood.to/",
|
||||||
|
"https://videobin.co/",
|
||||||
|
"https://ok.ru",
|
||||||
|
"https://streamlare.com",
|
||||||
|
"streaming.php",
|
||||||
|
)
|
||||||
|
|
||||||
|
fun embedIsBlacklisted(url: String): Boolean {
|
||||||
|
return embedBlackList.any { url.contains(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun AvailableEpisodesDetail.getEpisode(
|
||||||
|
lang: String,
|
||||||
|
id: String,
|
||||||
|
malId: Int?,
|
||||||
|
): List<Episode> {
|
||||||
|
val meta = if (lang == "sub") this.sub else this.dub
|
||||||
|
return meta.map { eps ->
|
||||||
|
Episode(
|
||||||
|
AnichiLoadData(id, lang, eps, malId).toJson(),
|
||||||
|
"Ep $eps",
|
||||||
|
episode = eps.toIntOrNull()
|
||||||
|
)
|
||||||
|
}.reversed()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getM3u8Qualities(
|
||||||
|
m3u8Link: String,
|
||||||
|
referer: String,
|
||||||
|
qualityName: String,
|
||||||
|
): List<ExtractorLink> {
|
||||||
|
return M3u8Helper.generateM3u8(
|
||||||
|
qualityName,
|
||||||
|
m3u8Link,
|
||||||
|
referer,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun String.getHost(): String {
|
||||||
|
return fixTitle(URI(this).host.substringBeforeLast(".").substringAfterLast("."))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun String.fixUrlPath() : String {
|
||||||
|
return if(this.contains(".json?")) apiEndPoint + this else apiEndPoint + URI(this).path + ".json?" + URI(this).query
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fixSourceUrls(url: String, source: String?) : String? {
|
||||||
|
return if(source == "Ak" || url.contains("/player/vitemb")) {
|
||||||
|
AppUtils.tryParseJson<AkIframe>(base64Decode(url.substringAfter("=")))?.idUrl
|
||||||
|
} else {
|
||||||
|
url.replace(" ", "%20")
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue