New Source Pornhits

This commit is contained in:
KillerDogeEmpire 2024-02-12 17:18:58 -08:00
parent 8d30295490
commit bab20b7849
4 changed files with 260 additions and 0 deletions

28
Pornhits/build.gradle.kts Normal file
View 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"
}

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.KillerDogeEmpire"/>

View 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
}
}
}

View File

@ -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())
}
}