cloudstream-extensions-hexated/Loklok/src/main/kotlin/com/hexated/Loklok.kt

413 lines
14 KiB
Kotlin
Raw Normal View History

2022-09-14 22:16:36 +00:00
package com.hexated
import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.*
2022-12-09 14:54:20 +00:00
import com.lagradost.cloudstream3.LoadResponse.Companion.addAniListId
import com.lagradost.cloudstream3.LoadResponse.Companion.addMalId
2022-12-09 14:08:31 +00:00
import com.lagradost.cloudstream3.utils.*
2022-09-14 22:16:36 +00:00
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
import com.lagradost.cloudstream3.utils.AppUtils.toJson
2022-12-09 14:08:31 +00:00
import com.lagradost.nicehttp.RequestBodyTypes
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.RequestBody.Companion.toRequestBody
2022-09-14 22:16:36 +00:00
class Loklok : MainAPI() {
override var name = "Loklok"
override val hasMainPage = true
override val hasChromecastSupport = true
override val instantLinkLoading = true
2022-12-09 20:28:26 +00:00
override val hasQuickSearch = true
2022-09-14 22:16:36 +00:00
override val supportedTypes = setOf(
TvType.Movie,
TvType.TvSeries,
TvType.Anime,
TvType.AsianDrama,
)
2023-02-07 13:10:00 +00:00
private val headers = mutableMapOf(
2022-09-14 22:16:36 +00:00
"lang" to "en",
2023-02-07 13:10:00 +00:00
"versioncode" to "32",
"clienttype" to "android_tem3",
2022-09-14 22:16:36 +00:00
)
// no license found
// thanks to https://github.com/napthedev/filmhot for providing API
2022-09-15 02:40:50 +00:00
companion object {
2022-10-17 06:32:34 +00:00
private val api = base64DecodeAPI("dg==LnQ=b2s=a2w=bG8=aS4=YXA=ZS0=aWw=b2I=LW0=Z2E=Ly8=czo=dHA=aHQ=")
2022-09-15 02:40:50 +00:00
private val apiUrl = "$api/${base64Decode("Y21zL2FwcA==")}"
2022-10-02 06:38:07 +00:00
private val searchApi = base64Decode("aHR0cHM6Ly9sb2tsb2suY29t")
2022-09-15 02:40:50 +00:00
private const val mainImageUrl = "https://images.weserv.nl"
2022-10-17 06:32:34 +00:00
private fun base64DecodeAPI(api: String): String {
return api.chunked(4).map { base64Decode(it) }.reversed().joinToString("")
}
2022-09-15 02:40:50 +00:00
}
2022-09-14 22:16:36 +00:00
2022-10-01 23:57:22 +00:00
private fun encode(input: String): String =
java.net.URLEncoder.encode(input, "utf-8").replace("+", "%20")
2022-09-14 22:16:36 +00:00
override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse {
val home = ArrayList<HomePageList>()
for (i in 0..6) {
// delay(500)
2022-09-14 22:16:36 +00:00
app.get("$apiUrl/homePage/getHome?page=$i", headers = headers)
.parsedSafe<Home>()?.data?.recommendItems
?.filterNot { it.homeSectionType == "BLOCK_GROUP" }
?.filterNot { it.homeSectionType == "BANNER" }
?.mapNotNull { res ->
val header = res.homeSectionName ?: return@mapNotNull null
val media = res.media?.mapNotNull { media -> media.toSearchResponse() }
?: throw ErrorLoadingException("Invalid Json Reponse")
home.add(HomePageList(header, media))
}
}
return HomePageResponse(home)
}
private fun Media.toSearchResponse(): SearchResponse? {
return newMovieSearchResponse(
title ?: name ?: return null,
2022-12-10 06:14:11 +00:00
UrlData(id, category ?: domainType).toJson(),
2022-09-14 22:16:36 +00:00
TvType.Movie,
) {
this.posterUrl = (imageUrl ?: coverVerticalUrl)?.let {
"$mainImageUrl/?url=${encode(it)}&w=175&h=246&fit=cover&output=webp"
}
}
}
2022-12-09 20:28:26 +00:00
override suspend fun quickSearch(query: String): List<SearchResponse>? {
val body = mapOf(
"searchKeyWord" to query,
"size" to "50",
"sort" to "",
"searchType" to "",
).toJson().toRequestBody(RequestBodyTypes.JSON.toMediaTypeOrNull())
return app.post(
"$apiUrl/search/v1/searchWithKeyWord",
requestBody = body,
headers = headers
).parsedSafe<QuickSearchRes>()?.data?.searchResults?.mapNotNull { media ->
media.toSearchResponse()
}
}
2022-09-14 22:16:36 +00:00
override suspend fun search(query: String): List<SearchResponse> {
val res = app.get(
2022-10-02 06:38:07 +00:00
"$searchApi/search?keyword=$query",
2022-10-01 22:06:16 +00:00
).document
val script = res.select("script").find { it.data().contains("function(a,b,c,d,e") }?.data()
?.substringAfter("searchResults:[")?.substringBefore("]}],fetch")
2022-10-01 23:57:22 +00:00
return res.select("div.search-list div.search-video-card").mapIndexed { num, block ->
val name = block.selectFirst("h2.title")?.text()
val data = block.selectFirst("a")?.attr("href")?.split("/")
val id = data?.last()
val type = data?.get(2)?.toInt()
2022-10-02 06:38:07 +00:00
val image = Regex("coverVerticalUrl:\"(.*?)\",").findAll(script.toString())
2022-10-02 00:19:47 +00:00
.map { it.groupValues[1] }.toList().getOrNull(num)?.replace("\\u002F", "/")
2022-10-01 22:06:16 +00:00
2022-09-14 22:16:36 +00:00
newMovieSearchResponse(
2022-10-01 22:06:16 +00:00
"$name",
UrlData(id, type).toJson(),
2022-09-14 22:16:36 +00:00
TvType.Movie,
) {
2022-10-01 22:06:16 +00:00
this.posterUrl = image
2022-09-14 22:16:36 +00:00
}
2022-10-01 22:06:16 +00:00
2022-10-01 23:57:22 +00:00
}
2022-10-01 22:06:16 +00:00
2022-09-14 22:16:36 +00:00
}
override suspend fun load(url: String): LoadResponse? {
val data = parseJson<UrlData>(url)
val res = app.get(
"$apiUrl/movieDrama/get?id=${data.id}&category=${data.category}",
headers = headers
).parsedSafe<Load>()?.data ?: throw ErrorLoadingException("Invalid Json Reponse")
2023-02-07 13:10:00 +00:00
headers["deviceid"] = getDevideId(16)
2022-09-14 22:16:36 +00:00
val episodes = res.episodeVo?.map { eps ->
val definition = eps.definitionList?.map {
Definition(
it.code,
it.description,
)
}
val subtitling = eps.subtitlingList?.map {
Subtitling(
it.languageAbbr,
it.language,
it.subtitlingUrl
)
}
Episode(
data = UrlEpisode(
data.id.toString(),
data.category,
eps.id,
definition,
subtitling
).toJson(),
episode = eps.seriesNo
)
} ?: throw ErrorLoadingException("No Episode Found")
val recommendations = res.likeList?.mapNotNull { rec ->
rec.toSearchResponse()
}
2022-10-07 17:32:11 +00:00
val type = when {
2023-01-09 13:56:23 +00:00
res.areaList?.firstOrNull()?.id == 44 && res.tagNameList?.contains("Anime") == true -> {
TvType.Anime
}
2022-10-07 17:32:11 +00:00
data.category == 0 -> {
TvType.Movie
}
else -> {
TvType.TvSeries
}
}
2022-12-09 14:54:20 +00:00
val animeType = if(type == TvType.Anime && data.category == 0) "movie" else "tv"
2023-01-14 09:40:35 +00:00
val (malId, anilistId) = if (type == TvType.Anime) getTracker(
res.name,
animeType,
res.year
) else Tracker()
2022-12-09 14:54:20 +00:00
2022-09-14 22:16:36 +00:00
return newTvSeriesLoadResponse(
res.name ?: return null,
url,
2023-01-09 13:56:23 +00:00
if(data.category == 0) TvType.Movie else type,
2022-09-14 22:16:36 +00:00
episodes
) {
this.posterUrl = res.coverVerticalUrl
2022-12-09 14:21:35 +00:00
this.backgroundPosterUrl = res.coverHorizontalUrl
2022-09-14 22:16:36 +00:00
this.year = res.year
this.plot = res.introduction
this.tags = res.tagNameList
this.rating = res.score.toRatingInt()
2023-01-14 09:40:35 +00:00
addMalId(malId)
2022-12-09 14:54:20 +00:00
addAniListId(anilistId?.toIntOrNull())
2022-09-14 22:16:36 +00:00
this.recommendations = recommendations
}
}
private fun getLanguage(str: String): String {
return when (str) {
"in_ID" -> "Indonesian"
2022-09-16 10:23:45 +00:00
"pt" -> "Portuguese"
2022-09-14 22:16:36 +00:00
else -> str.split("_").first().let {
SubtitleHelper.fromTwoLettersToLanguage(it).toString()
}
}
}
override suspend fun loadLinks(
data: String,
isCasting: Boolean,
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit
): Boolean {
val res = parseJson<UrlEpisode>(data)
2022-12-09 21:21:13 +00:00
res.definitionList?.apmap { video ->
2023-02-07 13:10:00 +00:00
val json = app.get(
"$apiUrl/media/previewInfo?category=${res.category}&contentId=${res.id}&episodeId=${res.epId}&definition=${video.code}",
2022-12-09 20:28:26 +00:00
headers = headers,
2023-02-07 13:10:00 +00:00
).parsedSafe<PreviewResponse>()?.data
2022-12-09 14:08:31 +00:00
callback.invoke(
ExtractorLink(
this.name,
this.name,
2022-12-09 21:21:13 +00:00
json?.mediaUrl ?: return@apmap null,
2022-12-09 14:08:31 +00:00
"",
2022-12-09 20:28:26 +00:00
getQuality(json.currentDefinition ?: ""),
2022-12-09 14:08:31 +00:00
isM3u8 = true,
)
)
2022-09-14 22:16:36 +00:00
}
res.subtitlingList?.map { sub ->
subtitleCallback.invoke(
SubtitleFile(
getLanguage(sub.languageAbbr ?: return@map),
sub.subtitlingUrl ?: return@map
)
)
}
return true
}
2022-12-09 14:08:31 +00:00
private fun getQuality(quality: String): Int {
return when (quality) {
"GROOT_FD" -> Qualities.P360.value
"GROOT_LD" -> Qualities.P480.value
"GROOT_SD" -> Qualities.P720.value
"GROOT_HD" -> Qualities.P1080.value
else -> Qualities.Unknown.value
}
}
2023-02-07 13:10:00 +00:00
private fun getDevideId(length: Int): String {
val allowedChars = ('a'..'f') + ('0'..'9')
return (1..length)
.map { allowedChars.random() }
.joinToString("")
}
2023-01-14 09:40:35 +00:00
private suspend fun getTracker(title: String?, type: String?, year: Int?): Tracker {
val res = app.get("https://api.consumet.org/meta/anilist/$title")
.parsedSafe<AniSearch>()?.results?.find { media ->
(media.title?.english.equals(title, true) || media.title?.romaji.equals(
title,
true
)) || (media.type.equals(type, true) && media.releaseDate == year)
}
return Tracker(res?.malId, res?.aniId, res?.image, res?.cover)
}
data class Tracker(
val malId: Int? = null,
val aniId: String? = null,
val image: String? = null,
val cover: String? = null,
)
2022-09-14 22:16:36 +00:00
data class UrlData(
val id: Any? = null,
val category: Int? = null,
)
data class Subtitling(
val languageAbbr: String? = null,
val language: String? = null,
val subtitlingUrl: String? = null,
)
data class Definition(
val code: String? = null,
val description: String? = null,
)
data class UrlEpisode(
val id: String? = null,
val category: Int? = null,
val epId: Int? = null,
val definitionList: List<Definition>? = arrayListOf(),
val subtitlingList: List<Subtitling>? = arrayListOf(),
)
2023-01-14 09:40:35 +00:00
data class Title(
@JsonProperty("romaji") val romaji: String? = null,
@JsonProperty("english") val english: String? = null,
)
data class Results(
@JsonProperty("id") val aniId: String? = null,
@JsonProperty("malId") val malId: Int? = null,
@JsonProperty("title") val title: Title? = null,
@JsonProperty("releaseDate") val releaseDate: Int? = null,
@JsonProperty("type") val type: String? = null,
@JsonProperty("image") val image: String? = null,
@JsonProperty("cover") val cover: String? = null,
)
data class AniSearch(
@JsonProperty("results") val results: java.util.ArrayList<Results>? = arrayListOf(),
)
2022-12-09 20:28:26 +00:00
data class QuickSearchData(
@JsonProperty("searchResults") val searchResults: ArrayList<Media>? = arrayListOf(),
)
data class QuickSearchRes(
@JsonProperty("data") val data: QuickSearchData? = null,
2022-12-09 14:08:31 +00:00
)
data class PreviewResponse(
2023-02-07 13:10:00 +00:00
@JsonProperty("data") val data: PreviewVideos? = null,
2022-09-14 22:16:36 +00:00
)
2022-12-09 14:08:31 +00:00
data class PreviewVideos(
@JsonProperty("mediaUrl") val mediaUrl: String? = null,
@JsonProperty("currentDefinition") val currentDefinition: String? = null,
2022-09-14 22:16:36 +00:00
)
data class SubtitlingList(
@JsonProperty("languageAbbr") val languageAbbr: String? = null,
@JsonProperty("language") val language: String? = null,
@JsonProperty("subtitlingUrl") val subtitlingUrl: String? = null,
)
data class DefinitionList(
@JsonProperty("code") val code: String? = null,
@JsonProperty("description") val description: String? = null,
)
data class EpisodeVo(
@JsonProperty("id") val id: Int? = null,
@JsonProperty("seriesNo") val seriesNo: Int? = null,
@JsonProperty("definitionList") val definitionList: ArrayList<DefinitionList>? = arrayListOf(),
@JsonProperty("subtitlingList") val subtitlingList: ArrayList<SubtitlingList>? = arrayListOf(),
)
2023-01-09 13:56:23 +00:00
data class Region(
@JsonProperty("id") val id: Int? = null,
@JsonProperty("name") val name: String? = null,
)
2022-09-14 22:16:36 +00:00
data class MediaDetail(
@JsonProperty("name") val name: String? = null,
@JsonProperty("introduction") val introduction: String? = null,
@JsonProperty("year") val year: Int? = null,
@JsonProperty("category") val category: String? = null,
@JsonProperty("coverVerticalUrl") val coverVerticalUrl: String? = null,
2022-12-09 14:21:35 +00:00
@JsonProperty("coverHorizontalUrl") val coverHorizontalUrl: String? = null,
2022-09-14 22:16:36 +00:00
@JsonProperty("score") val score: String? = null,
2023-01-09 13:56:23 +00:00
@JsonProperty("areaList") val areaList: ArrayList<Region>? = arrayListOf(),
2022-09-14 22:16:36 +00:00
@JsonProperty("episodeVo") val episodeVo: ArrayList<EpisodeVo>? = arrayListOf(),
@JsonProperty("likeList") val likeList: ArrayList<Media>? = arrayListOf(),
@JsonProperty("tagNameList") val tagNameList: ArrayList<String>? = arrayListOf(),
)
data class Load(
@JsonProperty("data") val data: MediaDetail? = null,
)
data class Media(
@JsonProperty("id") val id: Any? = null,
@JsonProperty("category") val category: Int? = null,
2022-12-10 06:14:11 +00:00
@JsonProperty("domainType") val domainType: Int? = null,
2022-09-14 22:16:36 +00:00
@JsonProperty("imageUrl") val imageUrl: String? = null,
@JsonProperty("coverVerticalUrl") val coverVerticalUrl: String? = null,
@JsonProperty("title") val title: String? = null,
@JsonProperty("name") val name: String? = null,
@JsonProperty("jumpAddress") val jumpAddress: String? = null,
)
data class RecommendItems(
@JsonProperty("homeSectionName") val homeSectionName: String? = null,
@JsonProperty("homeSectionType") val homeSectionType: String? = null,
@JsonProperty("recommendContentVOList") val media: ArrayList<Media>? = arrayListOf(),
)
data class Data(
@JsonProperty("recommendItems") val recommendItems: ArrayList<RecommendItems>? = arrayListOf(),
)
data class Home(
@JsonProperty("data") val data: Data? = null,
)
}