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

Commit

Permalink
Fix KrunchyrollProvider.kt (#21)
Browse files Browse the repository at this point in the history
  • Loading branch information
KillerDogeEmpire committed Mar 20, 2022
1 parent 5ba430e commit 1bf2d52
Showing 1 changed file with 137 additions and 69 deletions.
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
}
}
}

0 comments on commit 1bf2d52

Please sign in to comment.