mirror of
https://github.com/recloudstream/cloudstream-extensions.git
synced 2024-08-15 03:03:54 +00:00
Add Ask4movie
This commit is contained in:
parent
73062a8041
commit
4ff296dea5
4 changed files with 280 additions and 0 deletions
27
Ask4Movie/build.gradle.kts
Normal file
27
Ask4Movie/build.gradle.kts
Normal file
|
@ -0,0 +1,27 @@
|
|||
// use an integer for version numbers
|
||||
version = 3
|
||||
|
||||
|
||||
cloudstream {
|
||||
language = "en"
|
||||
// All of these properties are optional, you can safely remove them
|
||||
|
||||
// description = "Uses TMDB"
|
||||
authors = listOf("Blatzar")
|
||||
|
||||
/**
|
||||
* Status int as the following:
|
||||
* 0: Down
|
||||
* 1: Ok
|
||||
* 2: Slow
|
||||
* 3: Beta only
|
||||
* */
|
||||
status = 1 // will be 3 if unspecified
|
||||
tvTypes = listOf(
|
||||
"TvSeries",
|
||||
"Movie",
|
||||
"AnimeMovie"
|
||||
)
|
||||
|
||||
iconUrl = "https://www.google.com/s2/favicons?domain=ask4movie.mx&sz=%size%"
|
||||
}
|
2
Ask4Movie/src/main/AndroidManifest.xml
Normal file
2
Ask4Movie/src/main/AndroidManifest.xml
Normal file
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest package="com.lagradost"/>
|
14
Ask4Movie/src/main/kotlin/com/lagradost/Ask4MoviePlugin.kt
Normal file
14
Ask4Movie/src/main/kotlin/com/lagradost/Ask4MoviePlugin.kt
Normal file
|
@ -0,0 +1,14 @@
|
|||
|
||||
package com.lagradost
|
||||
|
||||
import com.lagradost.cloudstream3.plugins.CloudstreamPlugin
|
||||
import com.lagradost.cloudstream3.plugins.Plugin
|
||||
import android.content.Context
|
||||
|
||||
@CloudstreamPlugin
|
||||
class Ask4MoviePlugin: Plugin() {
|
||||
override fun load(context: Context) {
|
||||
// All providers should be added in this manner. Please don't edit the providers list directly.
|
||||
registerMainAPI(Ask4MovieProvider())
|
||||
}
|
||||
}
|
237
Ask4Movie/src/main/kotlin/com/lagradost/Ask4MovieProvider.kt
Normal file
237
Ask4Movie/src/main/kotlin/com/lagradost/Ask4MovieProvider.kt
Normal file
|
@ -0,0 +1,237 @@
|
|||
package com.lagradost
|
||||
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.main
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.loadExtractor
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Element
|
||||
import java.net.URI
|
||||
|
||||
class Ask4MovieProvider : MainAPI() {
|
||||
override var mainUrl = "https://ask4movie.mx"
|
||||
|
||||
override var name = "Ask4Movie"
|
||||
override val supportedTypes = setOf(TvType.TvSeries, TvType.Movie, TvType.AnimeMovie)
|
||||
override val hasMainPage = true
|
||||
|
||||
private fun Element.toSearchResponse(): MovieSearchResponse {
|
||||
// style="background-image: url(https://ask4movie.me/wp-content/uploads/2022/08/Your-Name.-2016-cover.jpg)"
|
||||
val posterRegex = Regex("""url\((.*?)\)""")
|
||||
val poster = posterRegex.find(this.attr("style"))?.groupValues?.get(1)
|
||||
|
||||
val a = this.select("a")
|
||||
val href = a.attr("href")
|
||||
val title = a.text().trim()
|
||||
|
||||
// Title (2022) -> 2022
|
||||
val year =
|
||||
Regex("""\((\d{4})\)$""").find(title)?.groupValues?.getOrNull(1)?.toIntOrNull()
|
||||
|
||||
return MovieSearchResponse(
|
||||
title,
|
||||
href,
|
||||
this@Ask4MovieProvider.name,
|
||||
TvType.Movie,
|
||||
poster,
|
||||
year,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
)
|
||||
}
|
||||
|
||||
// Used in movies/single seasons to get recommendations
|
||||
private fun Element.articleToSearchResponse(): MovieSearchResponse {
|
||||
val poster = this.select("img").attr("src")
|
||||
|
||||
val a = this.select("a")
|
||||
val href = a.attr("href")
|
||||
val title = a.attr("title")
|
||||
|
||||
// Title (2022) -> 2022
|
||||
val year =
|
||||
Regex("""\((\d{4})\)$""").find(title)?.groupValues?.getOrNull(1)?.toIntOrNull()
|
||||
|
||||
return MovieSearchResponse(
|
||||
title,
|
||||
href,
|
||||
this@Ask4MovieProvider.name,
|
||||
TvType.Movie,
|
||||
poster,
|
||||
year,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun search(query: String): List<SearchResponse> {
|
||||
val url = "$mainUrl/?s=$query"
|
||||
val doc = app.post(
|
||||
url,
|
||||
// data = mapOf("np_asl_data" to "customset[]=post&customset[]=ct_channel&customset[]=post&customset[]=post&customset[]=post&customset[]=post&asl_gen[]=title&asl_gen[]=exact&qtranslate_lang=0&filters_initial=1&filters_changed=0")
|
||||
).document
|
||||
return doc.select("div.item").map {
|
||||
it.toSearchResponse()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getIframe(html: String): String? {
|
||||
val data = Regex("""<script src="data:text\/javascript;base64,([^"']*)""").findAll(html)
|
||||
.lastOrNull()?.groupValues?.getOrNull(1) ?: return null
|
||||
val decoded = base64Decode(data)
|
||||
val iframeUrlRegex = Regex("""dir['"],['"]([^"']*)""")
|
||||
|
||||
val iframeEncoded = iframeUrlRegex.find(decoded)?.groupValues?.getOrNull(1) ?: return null
|
||||
val iframe = base64Decode(iframeEncoded)
|
||||
return Jsoup.parse(iframe).select("iframe").attr("src")
|
||||
}
|
||||
|
||||
private suspend fun getEpisodes(iframe: String): List<Episode> {
|
||||
val playlistDoc = app.get(iframe).document
|
||||
|
||||
// S04┋E01
|
||||
val episodeRegex = Regex("""S(\d+).E(\d+)""")
|
||||
return playlistDoc.select("span.episode").mapNotNull { episode ->
|
||||
val partialUrl = episode.attr("data-url")
|
||||
val fullUrl = "https://${URI(iframe).rawAuthority}$partialUrl"
|
||||
val info = episodeRegex.find(episode.text())
|
||||
val seasonIndex = info?.groupValues?.getOrNull(1)?.toIntOrNull()
|
||||
val episodeIndex = info?.groupValues?.getOrNull(2)?.toIntOrNull()
|
||||
Episode(
|
||||
fullUrl,
|
||||
episode = episodeIndex,
|
||||
season = seasonIndex
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse? {
|
||||
val document = app.get(mainUrl).document
|
||||
val rows = document.select("div.row")
|
||||
|
||||
val posterRegex = Regex("""url\((.*?)\)""")
|
||||
val mappedRows = rows.mapNotNull {
|
||||
val items = it.select("div.slide-item").map { element ->
|
||||
val thumb = element.select("div.item-thumb")
|
||||
val poster = posterRegex.find(thumb.attr("style"))?.groupValues?.get(1)
|
||||
val href = thumb.select("a").attr("href")
|
||||
val title = element.select("div.video-short-intro a").text()
|
||||
val year =
|
||||
Regex("""\((\d{4})\)$""").find(title)?.groupValues?.getOrNull(1)?.toIntOrNull()
|
||||
MovieSearchResponse(title, href, this.name, TvType.Movie, poster, year)
|
||||
}.ifEmpty {
|
||||
it.select("div.channel-content.clearfix").map { searchElement ->
|
||||
searchElement.articleToSearchResponse()
|
||||
}
|
||||
}
|
||||
|
||||
val title = it.select("div.title").text()
|
||||
if (title.contains("porn", true) && !settingsForProvider.enableAdult) return@mapNotNull null
|
||||
HomePageList(title, items)
|
||||
}
|
||||
return HomePageResponse(mappedRows)
|
||||
}
|
||||
|
||||
override suspend fun load(url: String): LoadResponse {
|
||||
val response = app.get(url)
|
||||
val document = response.document
|
||||
|
||||
val seasons = document.select("div.item")
|
||||
val isSingleVideo = (seasons.isNullOrEmpty())
|
||||
val yearRegex = Regex("""\((\d{4})\)$""")
|
||||
|
||||
if (isSingleVideo) {
|
||||
val description = document.select("div.custom.video-the-content").text().trim()
|
||||
val (title, year) = document.select("a.video-title").text().let {
|
||||
it.replace(yearRegex, "") to yearRegex.find(it)?.groupValues?.get(1)?.toIntOrNull()
|
||||
}
|
||||
val genres =
|
||||
document.selectFirst("div.categories.cactus-info")?.select("a")?.map { it.text() }
|
||||
|
||||
// This is actually a json with all the data, but I opted to just scrape the html
|
||||
// Try the json in the future if html turns out bad
|
||||
val posterRegex = Regex("""contentUrl['"].*?(http[^"']*)""")
|
||||
val poster = posterRegex.find(response.text)?.groupValues?.get(1)
|
||||
val recommendations = document.select("div.cactus-sub-wrap article").mapNotNull {
|
||||
it.articleToSearchResponse()
|
||||
}
|
||||
|
||||
val iframe = getIframe(response.text)
|
||||
// It can be a season as a single video iframe!
|
||||
return if (iframe?.contains("/p/") == true) {
|
||||
val episodes = getEpisodes(iframe)
|
||||
TvSeriesLoadResponse(
|
||||
title,
|
||||
url,
|
||||
this.name,
|
||||
TvType.TvSeries,
|
||||
episodes,
|
||||
poster,
|
||||
year,
|
||||
recommendations = recommendations,
|
||||
tags = genres,
|
||||
plot = description
|
||||
)
|
||||
} else {
|
||||
newMovieLoadResponse(title, url, TvType.Movie, iframe) {
|
||||
this.posterUrl = poster
|
||||
this.tags = genres
|
||||
this.plot = description
|
||||
this.year = year
|
||||
this.recommendations = recommendations
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val recommendations = document.select("div.channel.clearfix").mapNotNull {
|
||||
it.articleToSearchResponse()
|
||||
}
|
||||
|
||||
val descriptionDiv = document.select("div.channel-description")
|
||||
val description = descriptionDiv.select("p").firstOrNull()?.text()?.trim()
|
||||
|
||||
val genres = descriptionDiv.select("span.genres").let {
|
||||
if (it.isNotEmpty()) it.text().split(",")
|
||||
else descriptionDiv.select("p")
|
||||
.firstOrNull { element -> element.text().contains("Genre:") }
|
||||
?.text()?.substringAfter("Genre:")?.split(",")
|
||||
}
|
||||
|
||||
val (title, year) = document.select("h3.channel-name").text().let {
|
||||
it.replace(yearRegex, "") to yearRegex.find(it)?.groupValues?.get(1)?.toIntOrNull()
|
||||
}
|
||||
|
||||
val poster = document.select("div.channel-pic > img").attr("src")
|
||||
val mappedSeasons = seasons.apmap {
|
||||
val href = it.select("div.top-item > a").attr("href")
|
||||
val text = app.get(href).text
|
||||
val iframe = getIframe(text) ?: return@apmap emptyList()
|
||||
getEpisodes(iframe)
|
||||
}.flatten()
|
||||
|
||||
return TvSeriesLoadResponse(
|
||||
title,
|
||||
url,
|
||||
this.name,
|
||||
TvType.TvSeries,
|
||||
mappedSeasons,
|
||||
poster,
|
||||
year,
|
||||
recommendations = recommendations,
|
||||
tags = genres,
|
||||
plot = description
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun loadLinks(
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
): Boolean {
|
||||
loadExtractor(data, this.mainUrl, subtitleCallback, callback)
|
||||
return true
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue