mirror of
https://github.com/hexated/cloudstream-extensions-hexated.git
synced 2024-08-15 00:03:22 +00:00
added Loklok
This commit is contained in:
parent
28efa65404
commit
1ecb1bc64e
6 changed files with 351 additions and 2 deletions
29
Loklok/build.gradle.kts
Normal file
29
Loklok/build.gradle.kts
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
// use an integer for version numbers
|
||||||
|
version = 1
|
||||||
|
|
||||||
|
|
||||||
|
cloudstream {
|
||||||
|
language = "id"
|
||||||
|
// All of these properties are optional, you can safely remove them
|
||||||
|
|
||||||
|
// description = "Lorem Ipsum"
|
||||||
|
authors = listOf("Hexated")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Status int as the following:
|
||||||
|
* 0: Down
|
||||||
|
* 1: Ok
|
||||||
|
* 2: Slow
|
||||||
|
* 3: Beta only
|
||||||
|
* */
|
||||||
|
status = 1 // will be 3 if unspecified
|
||||||
|
tvTypes = listOf(
|
||||||
|
"AsianDrama",
|
||||||
|
"Anime",
|
||||||
|
"TvSeries",
|
||||||
|
"Movie",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
iconUrl = "https://www.google.com/s2/favicons?domain=loklok.com&sz=%size%"
|
||||||
|
}
|
2
Loklok/src/main/AndroidManifest.xml
Normal file
2
Loklok/src/main/AndroidManifest.xml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest package="com.hexated"/>
|
304
Loklok/src/main/kotlin/com/hexated/Loklok.kt
Normal file
304
Loklok/src/main/kotlin/com/hexated/Loklok.kt
Normal file
|
@ -0,0 +1,304 @@
|
||||||
|
package com.hexated
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
import com.lagradost.cloudstream3.*
|
||||||
|
import com.lagradost.cloudstream3.mvvm.safeApiCall
|
||||||
|
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
|
||||||
|
import com.lagradost.cloudstream3.utils.AppUtils.toJson
|
||||||
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
|
import com.lagradost.cloudstream3.utils.SubtitleHelper
|
||||||
|
import com.lagradost.cloudstream3.utils.getQualityFromName
|
||||||
|
|
||||||
|
class Loklok : MainAPI() {
|
||||||
|
override var name = "Loklok"
|
||||||
|
override val hasMainPage = true
|
||||||
|
override val hasChromecastSupport = true
|
||||||
|
override val instantLinkLoading = true
|
||||||
|
override val supportedTypes = setOf(
|
||||||
|
TvType.Movie,
|
||||||
|
TvType.TvSeries,
|
||||||
|
TvType.Anime,
|
||||||
|
TvType.AsianDrama,
|
||||||
|
)
|
||||||
|
|
||||||
|
private val headers = mapOf(
|
||||||
|
"lang" to "en",
|
||||||
|
"versioncode" to "11",
|
||||||
|
"clienttype" to "ios_jike_default"
|
||||||
|
)
|
||||||
|
|
||||||
|
// no license found
|
||||||
|
// thanks to https://github.com/napthedev/filmhot for providing API
|
||||||
|
private val api = base64Decode("aHR0cHM6Ly9nYS1tb2JpbGUtYXBpLmxva2xvay50dg==")
|
||||||
|
private val apiUrl = "$api/${base64Decode("Y21zL2FwcA==")}"
|
||||||
|
|
||||||
|
private val mainImageUrl = "https://images.weserv.nl"
|
||||||
|
|
||||||
|
private fun encode(input: String): String? = java.net.URLEncoder.encode(input, "utf-8")
|
||||||
|
|
||||||
|
override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse {
|
||||||
|
val home = ArrayList<HomePageList>()
|
||||||
|
for (i in 0..10) {
|
||||||
|
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,
|
||||||
|
UrlData(id, category).toJson(),
|
||||||
|
TvType.Movie,
|
||||||
|
) {
|
||||||
|
this.posterUrl = (imageUrl ?: coverVerticalUrl)?.let {
|
||||||
|
"$mainImageUrl/?url=${encode(it)}&w=175&h=246&fit=cover&output=webp"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun search(query: String): List<SearchResponse> {
|
||||||
|
// val res = app.post(
|
||||||
|
// "$apiUrl/search/v1/searchWithKeyWord", data = mapOf(
|
||||||
|
// "searchKeyWord" to query,
|
||||||
|
// "size" to "50",
|
||||||
|
// "sort" to "",
|
||||||
|
// "searchType" to ""
|
||||||
|
// ), headers = headers
|
||||||
|
// )
|
||||||
|
val searchApi =
|
||||||
|
base64Decode("aHR0cHM6Ly9maWxtaG90LmxpdmUvX25leHQvZGF0YS9NeXQzRm4tVHRXaHJ2a1RBaG45SGw=")
|
||||||
|
val res = app.get(
|
||||||
|
"$searchApi/search.json?q=$query",
|
||||||
|
headers = mapOf("x-nextjs-data" to "1")
|
||||||
|
)
|
||||||
|
return res.parsedSafe<Search>()?.pageProps?.result?.mapNotNull { media ->
|
||||||
|
newMovieSearchResponse(
|
||||||
|
media.name ?: return@mapNotNull null,
|
||||||
|
UrlData(media.id?.toIntOrNull(), media.domainType).toJson(),
|
||||||
|
TvType.Movie,
|
||||||
|
) {
|
||||||
|
this.posterUrl = media.coverVerticalUrl
|
||||||
|
}
|
||||||
|
} ?: throw ErrorLoadingException("Invalid Json Reponse")
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
return newTvSeriesLoadResponse(
|
||||||
|
res.name ?: return null,
|
||||||
|
url,
|
||||||
|
if (data.category == 0) TvType.Movie else TvType.TvSeries,
|
||||||
|
episodes
|
||||||
|
) {
|
||||||
|
this.posterUrl = res.coverVerticalUrl
|
||||||
|
this.year = res.year
|
||||||
|
this.plot = res.introduction
|
||||||
|
this.tags = res.tagNameList
|
||||||
|
this.rating = res.score.toRatingInt()
|
||||||
|
this.recommendations = recommendations
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getLanguage(str: String): String {
|
||||||
|
return when (str) {
|
||||||
|
"in_ID" -> "Indonesian"
|
||||||
|
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)
|
||||||
|
|
||||||
|
res.definitionList?.apmap { video ->
|
||||||
|
safeApiCall {
|
||||||
|
app.get(
|
||||||
|
"$apiUrl/media/previewInfo?category=${res.category}&contentId=${res.id}&episodeId=${res.epId}&definition=${video.code}",
|
||||||
|
headers = headers
|
||||||
|
).parsedSafe<Video>()?.data.let { link ->
|
||||||
|
callback.invoke(
|
||||||
|
ExtractorLink(
|
||||||
|
this.name,
|
||||||
|
this.name,
|
||||||
|
link?.mediaUrl ?: return@let,
|
||||||
|
"",
|
||||||
|
getQualityFromName(video.description),
|
||||||
|
isM3u8 = true,
|
||||||
|
headers = headers
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res.subtitlingList?.map { sub ->
|
||||||
|
subtitleCallback.invoke(
|
||||||
|
SubtitleFile(
|
||||||
|
getLanguage(sub.languageAbbr ?: return@map),
|
||||||
|
sub.subtitlingUrl ?: return@map
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
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(),
|
||||||
|
)
|
||||||
|
|
||||||
|
data class VideoData(
|
||||||
|
@JsonProperty("mediaUrl") val mediaUrl: String? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Video(
|
||||||
|
@JsonProperty("data") val data: VideoData? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
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(),
|
||||||
|
)
|
||||||
|
|
||||||
|
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,
|
||||||
|
@JsonProperty("score") val score: String? = null,
|
||||||
|
@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 MediaSearch(
|
||||||
|
@JsonProperty("id") val id: String? = null,
|
||||||
|
@JsonProperty("domainType") val domainType: Int? = null,
|
||||||
|
@JsonProperty("name") val name: String? = null,
|
||||||
|
@JsonProperty("coverVerticalUrl") val coverVerticalUrl: String? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Result(
|
||||||
|
@JsonProperty("result") val result: ArrayList<MediaSearch>? = arrayListOf(),
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Search(
|
||||||
|
@JsonProperty("pageProps") val pageProps: Result? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Media(
|
||||||
|
@JsonProperty("id") val id: Any? = null,
|
||||||
|
@JsonProperty("category") val category: Int? = null,
|
||||||
|
@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,
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
14
Loklok/src/main/kotlin/com/hexated/LoklokPlugin.kt
Normal file
14
Loklok/src/main/kotlin/com/hexated/LoklokPlugin.kt
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
|
||||||
|
package com.hexated
|
||||||
|
|
||||||
|
import com.lagradost.cloudstream3.plugins.CloudstreamPlugin
|
||||||
|
import com.lagradost.cloudstream3.plugins.Plugin
|
||||||
|
import android.content.Context
|
||||||
|
|
||||||
|
@CloudstreamPlugin
|
||||||
|
class LoklokPlugin: Plugin() {
|
||||||
|
override fun load(context: Context) {
|
||||||
|
// All providers should be added in this manner. Please don't edit the providers list directly.
|
||||||
|
registerMainAPI(Loklok())
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
// use an integer for version numbers
|
// use an integer for version numbers
|
||||||
version = 1
|
version = 2
|
||||||
|
|
||||||
|
|
||||||
cloudstream {
|
cloudstream {
|
||||||
|
|
|
@ -8,7 +8,7 @@ import org.jsoup.nodes.Element
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class NeonimeProvider : MainAPI() {
|
class NeonimeProvider : MainAPI() {
|
||||||
override var mainUrl = "https://neonime.watch"
|
override var mainUrl = "https://neonime.cloud"
|
||||||
override var name = "Neonime"
|
override var name = "Neonime"
|
||||||
override val hasQuickSearch = false
|
override val hasQuickSearch = false
|
||||||
override val hasMainPage = true
|
override val hasMainPage = true
|
||||||
|
|
Loading…
Reference in a new issue