1+ package com.lagradost.cloudstream3.extractors
2+
3+ import com.lagradost.api.Log
4+ import com.lagradost.cloudstream3.SubtitleFile
5+ import com.lagradost.cloudstream3.amap
6+ import com.lagradost.cloudstream3.app
7+ import com.lagradost.cloudstream3.utils.ExtractorApi
8+ import com.lagradost.cloudstream3.utils.ExtractorLink
9+ import com.lagradost.cloudstream3.utils.Qualities
10+ import com.lagradost.cloudstream3.utils.loadExtractor
11+ import com.lagradost.cloudstream3.utils.newExtractorLink
12+ import java.net.URI
13+ import java.net.URL
14+
15+ class HubCloud : ExtractorApi () {
16+ override val name = " Hub-Cloud"
17+ override val mainUrl = " https://hubcloud.ink"
18+ override val requiresReferer = false
19+
20+ override suspend fun getUrl (
21+ url : String ,
22+ referer : String? ,
23+ subtitleCallback : (SubtitleFile ) -> Unit ,
24+ callback : (ExtractorLink ) -> Unit
25+ ) {
26+ val tag = " HubCloud"
27+ val realUrl = url.takeIf {
28+ try { URL (it); true } catch (e: Exception ) { Log .e(tag, " Invalid URL: ${e.message} " ); false }
29+ } ? : return
30+
31+ val baseUrl= getBaseUrl(realUrl)
32+
33+ val href = try {
34+ if (" hubcloud.php" in realUrl) {
35+ realUrl
36+ } else {
37+ val rawHref = app.get(realUrl).document.select(" #download" ).attr(" href" )
38+ if (rawHref.startsWith(" http" , ignoreCase = true )) {
39+ rawHref
40+ } else {
41+ baseUrl.trimEnd(' /' ) + " /" + rawHref.trimStart(' /' )
42+ }
43+ }
44+ } catch (e: Exception ) {
45+ Log .e(tag, " Failed to extract href: ${e.message} " )
46+ " "
47+ }
48+
49+ if (href.isBlank()) {
50+ Log .w(tag, " No valid href found" )
51+ return
52+ }
53+
54+ val document = app.get(href).document
55+ val size = document.selectFirst(" i#size" )?.text().orEmpty()
56+ val header = document.selectFirst(" div.card-header" )?.text().orEmpty()
57+
58+ val headerDetails = cleanTitle(header)
59+
60+ val labelExtras = buildString {
61+ if (headerDetails.isNotEmpty()) append(" [$headerDetails ]" )
62+ if (size.isNotEmpty()) append(" [$size ]" )
63+ }
64+ val quality = getIndexQuality(header)
65+
66+ document.select(" div.card-body h2 a.btn" ).amap { element ->
67+ val link = element.attr(" href" )
68+ val text = element.text()
69+
70+ when {
71+ text.contains(" FSL Server" , ignoreCase = true ) -> {
72+ callback.invoke(
73+ newExtractorLink(
74+ " $referer [FSL Server]" ,
75+ " $referer [FSL Server] $labelExtras " ,
76+ link,
77+ ) { this .quality = quality }
78+ )
79+ }
80+
81+ text.contains(" Download File" , ignoreCase = true ) -> {
82+ callback.invoke(
83+ newExtractorLink(
84+ " $referer " ,
85+ " $referer $labelExtras " ,
86+ link,
87+ ) { this .quality = quality }
88+ )
89+ }
90+
91+ text.contains(" BuzzServer" , ignoreCase = true ) -> {
92+ val buzzResp = app.get(" $link /download" , referer = link, allowRedirects = false )
93+ val dlink = buzzResp.headers[" hx-redirect" ].orEmpty()
94+ if (dlink.isNotBlank()) {
95+ callback.invoke(
96+ newExtractorLink(
97+ " $referer [BuzzServer]" ,
98+ " $referer [BuzzServer] $labelExtras " ,
99+ dlink,
100+ ) { this .quality = quality }
101+ )
102+ } else {
103+ Log .w(tag, " BuzzServer: No redirect" )
104+ }
105+ }
106+
107+ text.contains(" pixeldra" , ignoreCase = true ) || text.contains(" pixel" , ignoreCase = true ) -> {
108+ val baseUrlLink = getBaseUrl(link)
109+ val finalURL = if (link.contains(" download" , true )) link
110+ else " $baseUrlLink /api/file/${link.substringAfterLast(" /" )} ?download"
111+
112+ callback(
113+ newExtractorLink(
114+ " Pixeldrain" ,
115+ " Pixeldrain $labelExtras " ,
116+ finalURL
117+ ) { this .quality = quality }
118+ )
119+ }
120+
121+ text.contains(" S3 Server" , ignoreCase = true ) -> {
122+ callback.invoke(
123+ newExtractorLink(
124+ " $referer S3 Server" ,
125+ " $referer S3 Server $labelExtras " ,
126+ link,
127+ ) { this .quality = quality }
128+ )
129+ }
130+
131+ text.contains(" FSLv2" , ignoreCase = true ) -> {
132+ callback.invoke(
133+ newExtractorLink(
134+ " $referer FSLv2" ,
135+ " $referer FSLv2 $labelExtras " ,
136+ link,
137+ ) { this .quality = quality }
138+ )
139+ }
140+
141+ text.contains(" 10Gbps" , ignoreCase = true ) -> {
142+ var currentLink = link
143+ var redirectUrl: String?
144+ var redirectCount = 0
145+ val maxRedirects = 3
146+
147+ while (redirectCount < maxRedirects) {
148+ val response = app.get(currentLink, allowRedirects = false )
149+ redirectUrl = response.headers[" location" ]
150+
151+ if (redirectUrl == null ) {
152+ Log .e(tag, " 10Gbps: No redirect" )
153+ return @amap
154+ }
155+
156+ if (" link=" in redirectUrl) {
157+ val finalLink = redirectUrl.substringAfter(" link=" )
158+ callback.invoke(
159+ newExtractorLink(
160+ " 10Gbps [Download]" ,
161+ " 10Gbps [Download] $labelExtras " ,
162+ finalLink
163+ ) { this .quality = quality }
164+ )
165+ return @amap
166+ }
167+
168+ currentLink = redirectUrl
169+ redirectCount++
170+ }
171+
172+ Log .e(tag, " 10Gbps: Redirect limit reached ($maxRedirects )" )
173+ return @amap
174+ }
175+
176+ else -> {
177+ loadExtractor(link, " " , subtitleCallback, callback)
178+ }
179+ }
180+ }
181+ }
182+
183+ private fun getIndexQuality (str : String? ): Int {
184+ return Regex (" (\\ d{3,4})[pP]" ).find(str.orEmpty())?.groupValues?.getOrNull(1 )?.toIntOrNull()
185+ ? : Qualities .P2160 .value
186+ }
187+
188+ private fun getBaseUrl (url : String ): String {
189+ return try {
190+ URI (url).let { " ${it.scheme} ://${it.host} " }
191+ } catch (e: Exception ) {
192+ " "
193+ }
194+ }
195+
196+ fun cleanTitle (title : String ): String {
197+ val parts = title.split(" ." , " -" , " _" )
198+
199+ val qualityTags = listOf (
200+ " WEBRip" , " WEB-DL" , " WEB" , " BluRay" , " HDRip" , " DVDRip" , " HDTV" ,
201+ " CAM" , " TS" , " R5" , " DVDScr" , " BRRip" , " BDRip" , " DVD" , " PDTV" ,
202+ " HD"
203+ )
204+
205+ val audioTags = listOf (
206+ " AAC" , " AC3" , " DTS" , " MP3" , " FLAC" , " DD5" , " EAC3" , " Atmos"
207+ )
208+
209+ val subTags = listOf (
210+ " ESub" , " ESubs" , " Subs" , " MultiSub" , " NoSub" , " EnglishSub" , " HindiSub"
211+ )
212+
213+ val codecTags = listOf (
214+ " x264" , " x265" , " H264" , " HEVC" , " AVC"
215+ )
216+
217+ val startIndex = parts.indexOfFirst { part ->
218+ qualityTags.any { tag -> part.contains(tag, ignoreCase = true ) }
219+ }
220+
221+ val endIndex = parts.indexOfLast { part ->
222+ subTags.any { tag -> part.contains(tag, ignoreCase = true ) } ||
223+ audioTags.any { tag -> part.contains(tag, ignoreCase = true ) } ||
224+ codecTags.any { tag -> part.contains(tag, ignoreCase = true ) }
225+ }
226+
227+ return if (startIndex != - 1 && endIndex != - 1 && endIndex >= startIndex) {
228+ parts.subList(startIndex, endIndex + 1 ).joinToString(" ." )
229+ } else if (startIndex != - 1 ) {
230+ parts.subList(startIndex, parts.size).joinToString(" ." )
231+ } else {
232+ parts.takeLast(3 ).joinToString(" ." )
233+ }
234+ }
235+ }
0 commit comments