mirror of
https://github.com/Jacekun/cs3xxx-repo.git
synced 2024-08-14 23:57:09 +00:00
New Source Pornhits
This commit is contained in:
parent
8d30295490
commit
bab20b7849
4 changed files with 260 additions and 0 deletions
28
Pornhits/build.gradle.kts
Normal file
28
Pornhits/build.gradle.kts
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
// use an integer for version numbers
|
||||||
|
version = 5
|
||||||
|
|
||||||
|
|
||||||
|
cloudstream {
|
||||||
|
// All of these properties are optional, you can safely remove them
|
||||||
|
|
||||||
|
description = "Pornhits"
|
||||||
|
authors = listOf("KillerDogeEmpire, Coxju")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Status int as the following:
|
||||||
|
* 0: Down
|
||||||
|
* 1: Ok
|
||||||
|
* 2: Slow
|
||||||
|
* 3: Beta only
|
||||||
|
* */
|
||||||
|
status = 1 // will be 3 if unspecified
|
||||||
|
|
||||||
|
// List of video source types. Users are able to filter for extensions in a given category.
|
||||||
|
// You can find a list of avaliable types here:
|
||||||
|
// https://recloudstream.github.io/cloudstream/html/app/com.lagradost.cloudstream3/-tv-type/index.html
|
||||||
|
tvTypes = listOf("NSFW")
|
||||||
|
|
||||||
|
iconUrl = "https://www.google.com/s2/favicons?domain=pornhits.com&sz=%size%"
|
||||||
|
|
||||||
|
language = "en"
|
||||||
|
}
|
2
Pornhits/src/main/AndroidManifest.xml
Normal file
2
Pornhits/src/main/AndroidManifest.xml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest package="com.KillerDogeEmpire"/>
|
216
Pornhits/src/main/kotlin/com/KillerDogeEmpire/Pornhits.kt
Normal file
216
Pornhits/src/main/kotlin/com/KillerDogeEmpire/Pornhits.kt
Normal file
|
@ -0,0 +1,216 @@
|
||||||
|
package com.KillerDogeEmpire
|
||||||
|
|
||||||
|
import com.lagradost.cloudstream3.HomePageList
|
||||||
|
import com.lagradost.cloudstream3.HomePageResponse
|
||||||
|
import com.lagradost.cloudstream3.LoadResponse
|
||||||
|
import com.lagradost.cloudstream3.MainAPI
|
||||||
|
import com.lagradost.cloudstream3.MainPageRequest
|
||||||
|
import com.lagradost.cloudstream3.SearchResponse
|
||||||
|
import com.lagradost.cloudstream3.SubtitleFile
|
||||||
|
import com.lagradost.cloudstream3.TvType
|
||||||
|
import com.lagradost.cloudstream3.VPNStatus
|
||||||
|
import com.lagradost.cloudstream3.app
|
||||||
|
import com.lagradost.cloudstream3.fixUrl
|
||||||
|
import com.lagradost.cloudstream3.fixUrlNull
|
||||||
|
import com.lagradost.cloudstream3.mainPageOf
|
||||||
|
import com.lagradost.cloudstream3.newHomePageResponse
|
||||||
|
import com.lagradost.cloudstream3.newMovieLoadResponse
|
||||||
|
import com.lagradost.cloudstream3.newMovieSearchResponse
|
||||||
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
|
import com.lagradost.cloudstream3.utils.Qualities
|
||||||
|
import org.json.JSONObject
|
||||||
|
import org.jsoup.nodes.Element
|
||||||
|
|
||||||
|
class Pornhits : MainAPI() {
|
||||||
|
override var mainUrl = "https://www.pornhits.com"
|
||||||
|
override var name = "Pornhits"
|
||||||
|
override val hasMainPage = true
|
||||||
|
override val hasDownloadSupport = true
|
||||||
|
override val vpnStatus = VPNStatus.MightBeNeeded
|
||||||
|
override val supportedTypes = setOf(TvType.NSFW)
|
||||||
|
|
||||||
|
override val mainPage = mainPageOf(
|
||||||
|
"$mainUrl/videos.php?p=%d&s=l" to "Latest",
|
||||||
|
"$mainUrl/videos.php?p=%d&s=pd" to "Popular last day",
|
||||||
|
"$mainUrl/videos.php?p=%d&s=bd" to "Top Rated (day)",
|
||||||
|
"$mainUrl/videos.php?p=%d&s=pw" to "Popular last week",
|
||||||
|
"$mainUrl/videos.php?p=%d&s=bw" to "Top Rated (week)",
|
||||||
|
"$mainUrl/videos.php?p=%d&s=pm" to "Popular last month",
|
||||||
|
"$mainUrl/videos.php?p=%d&s=bm" to "Top Rated (month)",
|
||||||
|
)
|
||||||
|
|
||||||
|
override suspend fun getMainPage(
|
||||||
|
page: Int, request: MainPageRequest
|
||||||
|
): HomePageResponse {
|
||||||
|
val document = app.get(request.data.format(page)).document
|
||||||
|
val home =
|
||||||
|
document.select("div.main-content section.main-container div.list-videos article.item")
|
||||||
|
.mapNotNull {
|
||||||
|
it.toSearchResult()
|
||||||
|
}
|
||||||
|
return newHomePageResponse(
|
||||||
|
list = HomePageList(
|
||||||
|
name = request.name, list = home, isHorizontalImages = true
|
||||||
|
), hasNext = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Element.toSearchResult(): SearchResponse? {
|
||||||
|
val title = this.selectFirst("div.item-info h2.title")?.text() ?: return null
|
||||||
|
val href = fixUrl(this.selectFirst("a")!!.attr("href"))
|
||||||
|
val posterUrl = fixUrlNull(this.select("a div.img img").attr("data-original"))
|
||||||
|
return newMovieSearchResponse(title, href, TvType.Movie) {
|
||||||
|
this.posterUrl = posterUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun search(query: String): List<SearchResponse> {
|
||||||
|
val searchResponse = mutableListOf<SearchResponse>()
|
||||||
|
for (i in 1..15) {
|
||||||
|
val document = app.get(
|
||||||
|
"$mainUrl/videos.php?p=${i}&q=${query.trim().replace(" ", "+")}"
|
||||||
|
).document
|
||||||
|
val results =
|
||||||
|
document.select("div.main-content section.main-container div.list-videos article.item")
|
||||||
|
.mapNotNull {
|
||||||
|
it.toSearchResult()
|
||||||
|
}
|
||||||
|
searchResponse.addAll(results)
|
||||||
|
if (results.isEmpty()) break
|
||||||
|
}
|
||||||
|
return searchResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun load(url: String): LoadResponse {
|
||||||
|
val document = app.get(url).document
|
||||||
|
|
||||||
|
val title =
|
||||||
|
document.selectFirst("section.video-holder div.video-info div.info-holder article#tab_video_info.tab-content div.headline h1")
|
||||||
|
?.text()
|
||||||
|
?: ""
|
||||||
|
val poster = fixUrlNull(
|
||||||
|
document.selectXpath("//script[contains(text(),'var schemaJson')]").first()?.data()
|
||||||
|
?.replace("\"", "")
|
||||||
|
?.substringAfter("thumbnailUrl:")
|
||||||
|
?.substringBefore(",uploadDate:")
|
||||||
|
?.trim() ?: ""
|
||||||
|
)
|
||||||
|
val tags =
|
||||||
|
document.select(" section.video-holder div.video-info div.info-holder article#tab_video_info.tab-content div.block-details div.info h3.item a")
|
||||||
|
.map { it.text() }
|
||||||
|
val recommendations =
|
||||||
|
document.select("div.related-videos div.list-videos article.item")
|
||||||
|
.mapNotNull {
|
||||||
|
it.toSearchResult()
|
||||||
|
}
|
||||||
|
|
||||||
|
return newMovieLoadResponse(title, url, TvType.NSFW, url) {
|
||||||
|
this.posterUrl = poster
|
||||||
|
this.tags = tags
|
||||||
|
this.recommendations = recommendations
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun loadLinks(
|
||||||
|
data: String,
|
||||||
|
isCasting: Boolean,
|
||||||
|
subtitleCallback: (SubtitleFile) -> Unit,
|
||||||
|
callback: (ExtractorLink) -> Unit
|
||||||
|
): Boolean {
|
||||||
|
val document = app.get(data).document
|
||||||
|
|
||||||
|
val script =
|
||||||
|
document.selectXpath("//script[contains(text(),'let vpage_data')]").first()?.html()
|
||||||
|
var isVHQ = false
|
||||||
|
if (script != null && script.contains("VHQ")) {
|
||||||
|
isVHQ = true
|
||||||
|
}
|
||||||
|
val pattern = Regex("""window\.initPlayer\((.*])\);""")
|
||||||
|
val matchResult = pattern.find(script ?: "")
|
||||||
|
|
||||||
|
val jsonArray = matchResult?.groups?.get(1)?.value
|
||||||
|
|
||||||
|
val encodedString = getEncodedString(jsonArray) ?: ""
|
||||||
|
|
||||||
|
val decodedString = customBase64Decoder(encodedString)
|
||||||
|
|
||||||
|
val videos = JSONObject("{ videos:$decodedString}").getJSONArray("videos")
|
||||||
|
val externalLinkList = mutableListOf<ExtractorLink>()
|
||||||
|
for (i in 0 until videos.length()) {
|
||||||
|
val video = videos.getJSONObject(i)
|
||||||
|
var quality = Qualities.Unknown.value
|
||||||
|
var isM3u8 = false
|
||||||
|
if (video.getString("format").contains("lq")) {
|
||||||
|
quality = Qualities.P480.value
|
||||||
|
}
|
||||||
|
if (video.getString("format").contains("hq")) {
|
||||||
|
quality = Qualities.P720.value
|
||||||
|
}
|
||||||
|
var url = customBase64Decoder(video.getString("video_url"))
|
||||||
|
if (isVHQ) {
|
||||||
|
url = "$url&f=video.m3u8"
|
||||||
|
isM3u8 = true
|
||||||
|
quality = Qualities.Unknown.value
|
||||||
|
}
|
||||||
|
externalLinkList.add(
|
||||||
|
ExtractorLink(
|
||||||
|
this.name,
|
||||||
|
this.name,
|
||||||
|
fixUrl(url),
|
||||||
|
referer = mainUrl,
|
||||||
|
quality = quality,
|
||||||
|
isM3u8 = isM3u8
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if (isVHQ) break
|
||||||
|
}
|
||||||
|
|
||||||
|
externalLinkList.forEach(callback)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun customBase64Decoder(encodedString: String): String {
|
||||||
|
val base64CharacterSet = "АВСDЕFGHIJKLМNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.,~"
|
||||||
|
var decodedString = ""
|
||||||
|
var currentIndex = 0
|
||||||
|
|
||||||
|
Regex("[^АВСЕМA-Za-z0-9.,~]").find(encodedString)?.let {
|
||||||
|
println("Error decoding URL")
|
||||||
|
}
|
||||||
|
|
||||||
|
val sanitizedString = encodedString.replace("[^АВСЕМA-Za-z0-9.,~]".toRegex(), "")
|
||||||
|
|
||||||
|
do {
|
||||||
|
val firstCharIndex = base64CharacterSet.indexOf(sanitizedString[currentIndex++])
|
||||||
|
val secondCharIndex = base64CharacterSet.indexOf(sanitizedString[currentIndex++])
|
||||||
|
val thirdCharIndex = base64CharacterSet.indexOf(sanitizedString[currentIndex++])
|
||||||
|
val fourthCharIndex = base64CharacterSet.indexOf(sanitizedString[currentIndex++])
|
||||||
|
|
||||||
|
val reconstructedFirstChar = (firstCharIndex shl 2) or (secondCharIndex shr 4)
|
||||||
|
val reconstructedSecondChar = ((15 and secondCharIndex) shl 4) or (thirdCharIndex shr 2)
|
||||||
|
val lastPart = ((3 and thirdCharIndex) shl 6) or fourthCharIndex
|
||||||
|
|
||||||
|
decodedString += reconstructedFirstChar.toChar().toString()
|
||||||
|
if (64 != thirdCharIndex) {
|
||||||
|
decodedString += reconstructedSecondChar.toChar().toString()
|
||||||
|
}
|
||||||
|
if (64 != fourthCharIndex) {
|
||||||
|
decodedString += lastPart.toChar().toString()
|
||||||
|
}
|
||||||
|
} while (currentIndex < sanitizedString.length)
|
||||||
|
return java.net.URLDecoder.decode(decodedString, "UTF-8")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getEncodedString(json: String?): String? {
|
||||||
|
val stringPattern = Regex("""'([^']+)',""")
|
||||||
|
|
||||||
|
val stringMatch = stringPattern.find(json ?: "")
|
||||||
|
|
||||||
|
return when {
|
||||||
|
stringMatch != null -> stringMatch.groups[1]?.value
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package com.KillerDogeEmpire
|
||||||
|
|
||||||
|
import com.lagradost.cloudstream3.plugins.CloudstreamPlugin
|
||||||
|
import com.lagradost.cloudstream3.plugins.Plugin
|
||||||
|
import android.content.Context
|
||||||
|
import com.KillerDogeEmpire.Pornhits
|
||||||
|
|
||||||
|
@CloudstreamPlugin
|
||||||
|
class PornhitsProvider: Plugin() {
|
||||||
|
override fun load(context: Context) {
|
||||||
|
// All providers should be added in this manner. Please don't edit the providers list directly.
|
||||||
|
registerMainAPI(Pornhits())
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue