Skip to content
This repository has been archived by the owner on Oct 2, 2022. It is now read-only.

Update CrunchyrollProvider.kt #21

Merged
merged 1 commit into from
Mar 20, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
package com.lagradost.cloudstream3.animeproviders

import com.fasterxml.jackson.module.kotlin.readValue
package com.lagradost.cloudstream3.animeproviders

import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.json.JsonMapper
import com.fasterxml.jackson.module.kotlin.KotlinModule
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.utils.*
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
import org.jsoup.Jsoup
import java.util.*

private fun String.toAscii() = this.map { it.toInt() }.joinToString()


class CrunchyrollGeoBypasser {
class KrunchyGeoBypasser {
companion object {
const val BYPASS_SERVER = "https://cr-unblocker.us.to/start_session"
val headers = mapOf(
Expand All @@ -27,41 +28,55 @@ class CrunchyrollGeoBypasser {
val session = HttpSession()
}

private fun getSessionId(): Boolean {
data class KrunchySession (
@JsonProperty("data") var data :DataInfo? = DataInfo(),
@JsonProperty("error") var error : Boolean? = null,
@JsonProperty("code") var code : String? = null
)
data class DataInfo (
@JsonProperty("session_id") var sessionId : String? = null,
@JsonProperty("country_code") var countryCode : String? = null,
)

private suspend fun getSessionId(): Boolean {
return try {
sessionId = khttp.get(BYPASS_SERVER, params=mapOf("version" to "1.1")).jsonObject.getJSONObject("data").getString("session_id")
val response = app.get(BYPASS_SERVER, params=mapOf("version" to "1.1")).text
val json = parseJson<KrunchySession>(response)
sessionId = json.data?.sessionId
true
} catch (e: Exception) {
sessionId = null
false
}
}

private fun autoLoadSession(): Boolean {
private suspend fun autoLoadSession(): Boolean {
if (sessionId != null) return true
getSessionId()
return autoLoadSession()
}

fun geoBypassRequest(url: String): khttp.responses.Response {
suspend fun geoBypassRequest(url: String): khttp.responses.Response {
autoLoadSession()
return session.get(url, headers=headers, cookies=mapOf("session_id" to sessionId!!))
}
}


class CrunchyrollProvider : MainAPI() {
class KrunchyProvider : MainAPI() {
companion object {
val crUnblock = CrunchyrollGeoBypasser()
val crUnblock = KrunchyGeoBypasser()
val episodeNumRegex = Regex("""Episode (\d+)""")
val mapper: JsonMapper = JsonMapper.builder().addModule(KotlinModule())
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).build()
}

override var mainUrl = "http://www.crunchyroll.com"
override var name = "Crunchyroll"
override val hasQuickSearch: Boolean get() = false
override val hasMainPage: Boolean get() = true
override var name: String = "Crunchyroll"
override val lang = "en"
override val hasQuickSearch: Boolean
get() = false
override val hasMainPage: Boolean
get() = true

override val supportedTypes: Set<TvType>
get() = setOf(
Expand All @@ -75,10 +90,10 @@ class CrunchyrollProvider : MainAPI() {
Pair("$mainUrl/videos/anime/popular/ajax_page?pg=1", "Popular 1"),
Pair("$mainUrl/videos/anime/popular/ajax_page?pg=2", "Popular 2"),
Pair("$mainUrl/videos/anime/popular/ajax_page?pg=3", "Popular 3"),
Pair("$mainUrl/videos/anime/simulcasts/ajax_page", "Simulcasts"),
)

val items = ArrayList<HomePageList>()

items.add(HomePageList("Featured", Jsoup.parse(crUnblock.geoBypassRequest(mainUrl).text).select(
".js-featured-show-list > li"
).map { anime ->
Expand All @@ -94,42 +109,36 @@ class CrunchyrollProvider : MainAPI() {
null
)
}))
urls.apmap { (url, name) ->
val response = crUnblock.geoBypassRequest(url)
val soup = Jsoup.parse(response.text)

for (i in urls) {
try {
val response = crUnblock.geoBypassRequest(i.first)
val soup = Jsoup.parse(response.text)

val episodes = soup.select("li").map {

AnimeSearchResponse(
it.selectFirst("a").attr("title"),
fixUrl(it.selectFirst("a").attr("href")),
this.name,
TvType.Anime,
it.selectFirst("img").attr("src"),
null,
EnumSet.of(DubStatus.Subbed),
null,
null
)
}
val episodes = soup.select("li").map {

items.add(HomePageList(i.second, episodes))
} catch (e: Exception) {
e.printStackTrace()
AnimeSearchResponse(
it.selectFirst("a").attr("title"),
fixUrl(it.selectFirst("a").attr("href")),
this.name,
TvType.Anime,
it.selectFirst("img").attr("src"),
null,
EnumSet.of(DubStatus.Subbed),
null,
null
)
}
items.add(HomePageList(name, episodes))
}
if (items.size <= 0) throw ErrorLoadingException()
return HomePageResponse(items)
}

private fun getCloseMatches(sequence: String, items: Collection<String>): ArrayList<String> {
val closeMatches = ArrayList<String>()
val a = sequence.trim().lowercase()
val a = sequence.trim().toLowerCase()

for (item in items) {
val b = item.trim().lowercase()
val b = item.trim().toLowerCase()
if (b.contains(a)) {
closeMatches.add(item)
} else if (a.contains(b)) {
Expand All @@ -151,7 +160,7 @@ class CrunchyrollProvider : MainAPI() {

override suspend fun search(query: String): ArrayList<SearchResponse> {
val json = crUnblock.geoBypassRequest("http://www.crunchyroll.com/ajax/?req=RpcApiSearch_GetSearchCandidates").text.split("*/")[0].replace("\\/", "/")
val data = mapper.readValue<CrunchyJson>(json.split("\n").mapNotNull { if (!it.startsWith("/")) it else null }.joinToString("\n")).data
val data = parseJson<CrunchyJson>(json.split("\n").mapNotNull { if (!it.startsWith("/")) it else null }.joinToString("\n")).data

val results = getCloseMatches(query, data.map { it.name })
if (results.isEmpty()) return ArrayList()
Expand Down Expand Up @@ -189,7 +198,6 @@ class CrunchyrollProvider : MainAPI() {
val poster = soup.selectFirst(".poster")?.attr("src")

val p = soup.selectFirst(".description")

val description = if (p.selectFirst(".more") != null && !p.selectFirst(".more")?.text()?.trim().isNullOrEmpty()) {
p.selectFirst(".more").text().trim()
} else {
Expand All @@ -201,34 +209,54 @@ class CrunchyrollProvider : MainAPI() {

val subEpisodes = ArrayList<AnimeEpisode>()
val dubEpisodes = ArrayList<AnimeEpisode>()

val premiumEpisodes = ArrayList<AnimeEpisode>()
soup.select(".season").forEach {
val seasonName = it.selectFirst("a.season-dropdown")?.text()?.trim()
it.select(".episode").forEach { ep ->
val epTitle = ep.selectFirst(".short-desc")?.text()

val epNum = episodeNumRegex.find(ep.selectFirst("span.ellipsis")?.text().toString())?.destructured?.component1()
var poster = ep.selectFirst("img.landscape")?.attr("data-thumbnailurl")
val poster2 = ep.selectFirst("img")?.attr("src")
if (poster == "") { poster = poster2}

var epDesc = (if (epNum == null) "" else "Episode $epNum") + (if (!seasonName.isNullOrEmpty()) " - $seasonName" else "")
val isPremium = poster?.contains("widestar") == true
if (isPremium) {
epDesc = "★ "+epDesc+" ★"
} 




val epi = AnimeEpisode(
fixUrl(ep.attr("href")),
"$epTitle",
ep.selectFirst("img")?.attr("src")?.replace("wide", "full"),
poster?.replace("widestar","full")?.replace("wide","full"),
null,
null,
(if (epNum == null) "" else "Episode $epNum") + (if (!seasonName.isNullOrEmpty()) " - $seasonName" else ""),
epNum?.toIntOrNull()
epDesc,
null
)
if (seasonName == null) {
subEpisodes.add(epi)
} else if (seasonName.contains("(HD)")) {
// do nothing (filters our premium eps from one piece)
} else if (seasonName.contains("Dub") || seasonName.contains("Russian")) {
dubEpisodes.add(epi)
} else {
subEpisodes.add(epi)
}
if (isPremium) {
premiumEpisodes.add(epi)
} else if (seasonName.contains("Dub") || seasonName.contains("Russian") || seasonName.contains("Spanish")) {
dubEpisodes.add(epi)
} else {
subEpisodes.add(epi)
} 










}
}

return AnimeLoadResponse(
title,
null,
Expand All @@ -238,7 +266,7 @@ class CrunchyrollProvider : MainAPI() {
TvType.Anime,
poster,
year,
hashMapOf(DubStatus.Subbed to subEpisodes.reversed(), DubStatus.Dubbed to dubEpisodes.reversed()),
hashMapOf(DubStatus.Subbed to subEpisodes.reversed(), DubStatus.Dubbed to dubEpisodes.reversed(), DubStatus.Premium to premiumEpisodes.reversed()),
null,
description,
genres
Expand All @@ -261,7 +289,7 @@ class CrunchyrollProvider : MainAPI() {
@JsonProperty("title") var title : String?
)

data class CrunchyrollVideo (
data class KrunchyVideo (
@JsonProperty("streams") val streams : List<Streams>,
@JsonProperty("subtitles") val subtitles : List<Subtitles>,
)
Expand All @@ -280,47 +308,87 @@ class CrunchyrollProvider : MainAPI() {
val dat = contentRegex.find(response.text)?.destructured?.component1()

if (!dat.isNullOrEmpty()) {
val json = mapper.readValue<CrunchyrollVideo>(dat)
val json = parseJson<KrunchyVideo>(dat)
val streams = ArrayList<Streams>()

for (stream in json.streams) {
if (
listOf(
"adaptive_hls", "adaptive_dash",
"multitrack_adaptive_hls_v2",
"vo_adaptive_dash", "vo_adaptive_hls"
"vo_adaptive_dash", "vo_adaptive_hls",
"trailer_hls",
).contains(stream.format)
) {
if (stream.audioLang == "jaJP" && (listOf(null, "enUS").contains(stream.hardsubLang)) && listOf("m3u", "m3u8").contains(hlsHelper.absoluteExtensionDetermination(stream.url))) {
stream.title = if (stream.hardsubLang == "enUS") "Hardsub" else "Raw"
if (stream.format!!.contains("adaptive") && listOf("jaJP", "esLA", "esES", "enUS")
.contains(stream.audioLang) && (listOf("esLA", "esES", "enUS", null).contains(stream.hardsubLang))
&& listOf("m3u", "m3u8").contains(hlsHelper.absoluteExtensionDetermination(stream.url)))
{
stream.title = if (stream.hardsubLang == "enUS" && stream.audioLang == "jaJP") "Hardsub (English)"
else if (stream.hardsubLang == "esLA" && stream.audioLang == "jaJP") "Hardsub (Latino)"
else if (stream.hardsubLang == "esES" && stream.audioLang == "jaJP") "Hardsub (Español España)"
else if (stream.audioLang == "esLA") "Latino"
else if (stream.audioLang == "esES") "Español España"
else if (stream.audioLang == "enUS") "English (US)"
else "RAW"
streams.add(stream)
}
//Premium eps
if (stream.format == "trailer_hls" && listOf("jaJP", "esLA", "esES", "enUS").contains(stream.audioLang) &&
(listOf("esLA", "esES", "enUS", null).contains(stream.hardsubLang))) {
stream.title =
if (stream.hardsubLang == "enUS" && stream.audioLang == "jaJP") "Hardsub (English)"
else if (stream.hardsubLang == "esLA" && stream.audioLang == "jaJP") "Hardsub (Latino)"
else if (stream.hardsubLang == "esES" && stream.audioLang == "jaJP") "Hardsub (Español España)"
else if (stream.audioLang == "esLA") "Latino"
else if (stream.audioLang == "esES") "Español España"
else if (stream.audioLang == "enUS") "English (US)"
else "RAW"
streams.add(stream)
}
}
}

streams.forEach { stream ->
hlsHelper.m3u8Generation(M3u8Helper.M3u8Stream(stream.url, null), false).forEach {
streams.apmap { stream ->
if (stream.url.contains("m3u8") && stream.format!!.contains("adaptive") ) {
hlsHelper.m3u8Generation(M3u8Helper.M3u8Stream(stream.url, null), false).apmap {
callback(
ExtractorLink(
"Crunchyroll",
"Crunchy - ${stream.title} - ${it.quality}",
"Crunchy - ${stream.title} - ${it.quality}p",
it.streamUrl,
"",
getQualityFromName(it.quality.toString()),
true
)
)
}
} else if (stream.format == "trailer_hls") {
val premiumstream = stream.url
.replace("\\/", "/")
.replace(Regex("\\/clipFrom.*?index.m3u8"), "").replace("'_,'", "'_'")
.replace(stream.url.split("/")[2], "fy.v.vrv.co")
callback(
ExtractorLink(
"Crunchyroll",
"Crunchy - ${stream.title} ★",
premiumstream,
"",
Qualities.Unknown.value,
false
)
)
} else null
}

json.subtitles.forEach {
json.subtitles.apmap {
val langclean = it.language.replace("esLA","Spanish")
.replace("enUS","English")
.replace("esES","Spanish (Spain)")
subtitleCallback(
SubtitleFile(it.language, it.url)
SubtitleFile(langclean, it.url)
)
}

return true
}
return false
}
}
}