mirror of
				https://github.com/recloudstream/cloudstream.git
				synced 2024-08-15 01:53:11 +00:00 
			
		
		
		
	Merge remote-tracking branch 'origin/master'
This commit is contained in:
		
						commit
						10f4d33c59
					
				
					 70 changed files with 3433 additions and 268 deletions
				
			
		
							
								
								
									
										15
									
								
								.github/ISSUE_TEMPLATE/application-bug.yml
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										15
									
								
								.github/ISSUE_TEMPLATE/application-bug.yml
									
										
									
									
										vendored
									
									
								
							|  | @ -39,11 +39,11 @@ body: | |||
|   - type: input | ||||
|     id: cloudstream-version | ||||
|     attributes: | ||||
|       label: Cloudstream version | ||||
|       label: Cloudstream version and commit hash | ||||
|       description: | | ||||
|         You can find your Cloudstream version in **Settings**. | ||||
|         You can find your Cloudstream version in **Settings**. Commit hash is the 7 character string next to the version. | ||||
|       placeholder: | | ||||
|         Example: "2.8.16" | ||||
|         Example: "2.8.16 a49f466" | ||||
|     validations: | ||||
|       required: true | ||||
| 
 | ||||
|  | @ -57,6 +57,15 @@ body: | |||
|         Example: "Android 12" | ||||
|     validations: | ||||
|       required: true | ||||
|     | ||||
|   - type: textarea | ||||
|     id: logcat | ||||
|     attributes: | ||||
|       label: Logcat | ||||
|       placeholder: | | ||||
|         To get logcat please go to Settings > Updates and backup > Show logcat 🐈. | ||||
|         You can attach a file or link to some pastebin service if the file is too big. | ||||
|       render: java | ||||
| 
 | ||||
|   - type: textarea | ||||
|     id: other-details | ||||
|  |  | |||
							
								
								
									
										8
									
								
								.github/ISSUE_TEMPLATE/config.yml
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								.github/ISSUE_TEMPLATE/config.yml
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,8 @@ | |||
| blank_issues_enabled: false | ||||
| contact_links: | ||||
|   - name: Report provider bug | ||||
|     url: https://github.com/recloudstream | ||||
|     about: Please do not report any provider bugs here. This repository does not contain any providers. Please find the appropriate repository and report your issue there or join the discord. | ||||
|   - name: Discord | ||||
|     url: https://discord.gg/5Hus6fM | ||||
|     about: Join our discord for faster support on smaller issues. | ||||
							
								
								
									
										84
									
								
								.github/ISSUE_TEMPLATE/provider-bug.yml
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										84
									
								
								.github/ISSUE_TEMPLATE/provider-bug.yml
									
										
									
									
										vendored
									
									
								
							|  | @ -1,84 +0,0 @@ | |||
| name: 🐞 Provider Issue Report | ||||
| description: Report a source issue | ||||
| labels: [bug] | ||||
| body: | ||||
| 
 | ||||
|   - type: input | ||||
|     id: source | ||||
|     attributes: | ||||
|       label: Source information | ||||
|       description: | | ||||
|         You can find the source name in navigation drawer. | ||||
|       placeholder: | | ||||
|         Example: "Bflix" | ||||
|     validations: | ||||
|       required: true | ||||
| 
 | ||||
|   - type: input | ||||
|     id: source-url | ||||
|     attributes: | ||||
|       label: Source link | ||||
|       placeholder: | | ||||
|         Example: | ||||
|           "www.example.org" | ||||
|     validations: | ||||
|       required: true | ||||
| 
 | ||||
|   - type: textarea | ||||
|     id: reproduce-steps | ||||
|     attributes: | ||||
|       label: Steps to reproduce | ||||
|       description: Provide an example of the issue. | ||||
|       placeholder: | | ||||
|         Example: | ||||
|           1. First step | ||||
|           2. Second step | ||||
|           3. Issue here | ||||
|     validations: | ||||
|       required: true | ||||
| 
 | ||||
|   - type: input | ||||
|     id: cloudstream-version | ||||
|     attributes: | ||||
|       label: CloudStream version | ||||
|       description: | | ||||
|         You can find your CloudStream version in **Settings**. | ||||
|       placeholder: | | ||||
|         Example: "2.8.16" | ||||
|     validations: | ||||
|       required: true | ||||
| 
 | ||||
|   - type: input | ||||
|     id: android-version | ||||
|     attributes: | ||||
|       label: Android version | ||||
|       description: | | ||||
|         You can find this somewhere in your Android settings. | ||||
|       placeholder: | | ||||
|         Example: "Android 12" | ||||
|     validations: | ||||
|       required: true | ||||
| 
 | ||||
|   - type: textarea | ||||
|     id: other-details | ||||
|     attributes: | ||||
|       label: Other details | ||||
|       placeholder: | | ||||
|         Additional details and attachments. | ||||
| 
 | ||||
|   - type: checkboxes | ||||
|     id: acknowledgements | ||||
|     attributes: | ||||
|       label: Acknowledgements | ||||
|       description: Your issue will be closed if you haven't done these steps. | ||||
|       options: | ||||
|         - label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open issue. | ||||
|           required: true | ||||
|         - label: I have written a short but informative title. | ||||
|           required: true | ||||
|         - label: I have updated the app to pre-release version **[Latest](https://github.com/LagradOst/CloudStream-3/releases)**. | ||||
|           required: true | ||||
|         - label: If related to a provider, I have checked the site and it works, but not the app.  | ||||
|           required: true | ||||
|         - label: I will fill out all of the requested information in this form. | ||||
|           required: true | ||||
							
								
								
									
										63
									
								
								.github/workflows/issue-action.yml
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								.github/workflows/issue-action.yml
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,63 @@ | |||
| name: Issue automatic actions | ||||
| 
 | ||||
| on: | ||||
|   issues: | ||||
|     types: [opened, edited] | ||||
| 
 | ||||
| jobs: | ||||
|   issue-moderator: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: Generate access token | ||||
|         id: generate_token | ||||
|         uses: tibdex/github-app-token@v1 | ||||
|         with: | ||||
|           app_id: ${{ secrets.GH_APP_ID }} | ||||
|           private_key: ${{ secrets.GH_APP_KEY }} | ||||
|       - name: Similarity analysis | ||||
|         uses: actions-cool/issues-similarity-analysis@v1 | ||||
|         with: | ||||
|           token: ${{ steps.generate_token.outputs.token }} | ||||
|           filter-threshold: 0.5 | ||||
|           title-excludes: '' | ||||
|           comment-title: | | ||||
|             ### Your issue looks similar to these issues: | ||||
|             Please close if duplicate. | ||||
|           comment-body: '${index}. ${similarity} #${number}' | ||||
|       - uses: actions/checkout@v2 | ||||
|       - name: Automatically close issues that dont follow the issue template | ||||
|         uses: lucasbento/auto-close-issues@v1.0.2 | ||||
|         with: | ||||
|           github-token: ${{ steps.generate_token.outputs.token }} | ||||
|           issue-close-message: | | ||||
|             @${issue.user.login}: hello! :wave: | ||||
|             This issue is being automatically closed because it does not follow the issue template."  | ||||
|           closed-issues-label: "invalid" | ||||
|       - name: Check if issue mentions a provider | ||||
|         id: provider_check | ||||
|         env: | ||||
|           GH_TEXT: "${{ github.event.issue.title }} ${{ github.event.issue.body }}" | ||||
|         run: | | ||||
|           wget --output-document check_issue.py "https://raw.githubusercontent.com/recloudstream/.github/master/.github/check_issue.py" | ||||
|           pip3 install httpx | ||||
|           RES="$(python3 ./check_issue.py)" | ||||
|           echo "::set-output name=name::${RES}" | ||||
|       - name: Comment if issue mentions a provider | ||||
|         if: steps.provider_check.outputs.name != 'none' | ||||
|         uses: actions-cool/issues-helper@v3 | ||||
|         with: | ||||
|           actions: 'create-comment' | ||||
|           token: ${{ steps.generate_token.outputs.token }} | ||||
|           body: | | ||||
|             Hello ${{ github.event.issue.user.login }}.  | ||||
|             Please do not report any provider bugs here. This repository does not contain any providers. Please find the appropriate repository and report your issue there or join the [discord](https://discord.gg/5Hus6fM). | ||||
|              | ||||
|             Found provider name: `${{ steps.provider_check.outputs.name }}` | ||||
|       - name: Add eyes reaction to all issues | ||||
|         uses: actions-cool/emoji-helper@v1.0.0 | ||||
|         with: | ||||
|           type: 'issue' | ||||
|           token: ${{ steps.generate_token.outputs.token }} | ||||
|           emoji: 'eyes' | ||||
|            | ||||
|            | ||||
|  | @ -232,26 +232,23 @@ object APIHolder { | |||
|     } | ||||
| 
 | ||||
|     fun Context.filterProviderByPreferredMedia(hasHomePageIsRequired: Boolean = true): List<MainAPI> { | ||||
|         val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) | ||||
|         val currentPrefMedia = | ||||
|             settingsManager.getInt(this.getString(R.string.prefer_media_type_key), 0) | ||||
|         val default = enumValues<TvType>().sorted().filter { it != TvType.NSFW }.map { it.ordinal } | ||||
|         val defaultSet = default.map { it.toString() }.toSet() | ||||
|         val currentPrefMedia = try { | ||||
|             PreferenceManager.getDefaultSharedPreferences(this) | ||||
|             .getStringSet(this.getString(R.string.prefer_media_type_key), defaultSet) | ||||
|             ?.mapNotNull { it.toIntOrNull() ?: return@mapNotNull null } | ||||
|         } catch (e: Throwable) { | ||||
|             null | ||||
|         } ?: default | ||||
|         val langs = this.getApiProviderLangSettings() | ||||
|         val allApis = apis.filter { langs.contains(it.lang) } | ||||
|             .filter { api -> api.hasMainPage || !hasHomePageIsRequired } | ||||
|         return if (currentPrefMedia < 1) { | ||||
|         return if (currentPrefMedia.isEmpty()) { | ||||
|             allApis | ||||
|         } else { | ||||
|             // Filter API depending on preferred media type | ||||
|             val listEnumAnime = listOf(TvType.Anime, TvType.AnimeMovie, TvType.OVA) | ||||
|             val listEnumMovieTv = | ||||
|                 listOf(TvType.Movie, TvType.TvSeries, TvType.Cartoon, TvType.AsianDrama) | ||||
|             val listEnumDoc = listOf(TvType.Documentary) | ||||
|             val mediaTypeList = when (currentPrefMedia) { | ||||
|                 2 -> listEnumAnime | ||||
|                 3 -> listEnumDoc | ||||
|                 else -> listEnumMovieTv | ||||
|             } | ||||
|             allApis.filter { api -> api.supportedTypes.any { it in mediaTypeList } } | ||||
|             allApis.filter { api -> api.supportedTypes.any { currentPrefMedia.contains(it.ordinal) } } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | @ -591,19 +588,19 @@ enum class DubStatus(val id: Int) { | |||
|     Subbed(0), | ||||
| } | ||||
| 
 | ||||
| enum class TvType { | ||||
|     Movie, | ||||
|     AnimeMovie, | ||||
|     TvSeries, | ||||
|     Cartoon, | ||||
|     Anime, | ||||
|     OVA, | ||||
|     Torrent, | ||||
|     Documentary, | ||||
|     AsianDrama, | ||||
|     Live, | ||||
|     NSFW, | ||||
|     Others | ||||
| enum class TvType(value: Int?) { | ||||
|     Movie(1), | ||||
|     AnimeMovie(2), | ||||
|     TvSeries(3), | ||||
|     Cartoon(4), | ||||
|     Anime(5), | ||||
|     OVA(6), | ||||
|     Torrent(7), | ||||
|     Documentary(8), | ||||
|     AsianDrama(9), | ||||
|     Live(10), | ||||
|     NSFW(11), | ||||
|     Others(12) | ||||
| } | ||||
| 
 | ||||
| // IN CASE OF FUTURE ANIME MOVIE OR SMTH | ||||
|  |  | |||
|  | @ -697,17 +697,5 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { | |||
| //            } | ||||
| //        } | ||||
| 
 | ||||
| /* | ||||
| 
 | ||||
|         val relativePath = (Environment.DIRECTORY_DOWNLOADS) + File.separatorChar | ||||
|         val displayName = "output.dex" //""output.dex" | ||||
|         val file =  getExternalFilesDir(null)?.absolutePath + File.separatorChar + displayName//"${Environment.getExternalStorageDirectory()}${File.separatorChar}$relativePath$displayName" | ||||
|         println(file) | ||||
| 
 | ||||
|         val realFile = File(file) | ||||
|         println("REAALFILE: ${realFile.exists()} at ${realFile.length()}"  ) | ||||
|         val src = ExtensionManager.getSourceFromDex(this, "com.example.testdex2.TestClassToDex", File(file)) | ||||
|         val output = src?.doMath() | ||||
|         println("MASTER OUTPUT = $output")*/ | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -0,0 +1,40 @@ | |||
| package com.lagradost.cloudstream3.extractors | ||||
| 
 | ||||
| import com.lagradost.cloudstream3.app | ||||
| import com.lagradost.cloudstream3.base64Decode | ||||
| import com.lagradost.cloudstream3.utils.* | ||||
| 
 | ||||
| class Acefile : ExtractorApi() { | ||||
|     override val name = "Acefile" | ||||
|     override val mainUrl = "https://acefile.co" | ||||
|     override val requiresReferer = false | ||||
| 
 | ||||
|     override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> { | ||||
|         val sources = mutableListOf<ExtractorLink>() | ||||
|         app.get(url).document.select("script").map { script -> | ||||
|             if (script.data().contains("eval(function(p,a,c,k,e,d)")) { | ||||
|                 val data = getAndUnpack(script.data()) | ||||
|                 val id = data.substringAfter("{\"id\":\"").substringBefore("\",") | ||||
|                 val key = data.substringAfter("var nfck=\"").substringBefore("\";") | ||||
|                 app.get("https://acefile.co/local/$id?key=$key").text.let { | ||||
|                     base64Decode( | ||||
|                         it.substringAfter("JSON.parse(atob(\"").substringBefore("\"))") | ||||
|                     ).let { res -> | ||||
|                         sources.add( | ||||
|                             ExtractorLink( | ||||
|                                 name, | ||||
|                                 name, | ||||
|                                 res.substringAfter("\"file\":\"").substringBefore("\","), | ||||
|                                 "$mainUrl/", | ||||
|                                 Qualities.Unknown.value, | ||||
|                                 headers = mapOf("range" to "bytes=0-") | ||||
|                             ) | ||||
|                         ) | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return sources | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -0,0 +1,46 @@ | |||
| package com.lagradost.cloudstream3.extractors | ||||
| 
 | ||||
| import com.lagradost.cloudstream3.app | ||||
| import com.lagradost.cloudstream3.utils.ExtractorApi | ||||
| import com.lagradost.cloudstream3.utils.ExtractorLink | ||||
| import com.lagradost.cloudstream3.utils.M3u8Helper | ||||
| import com.lagradost.cloudstream3.utils.getQualityFromName | ||||
| import java.net.URI | ||||
| 
 | ||||
| class AsianLoad : ExtractorApi() { | ||||
|     override var name = "AsianLoad" | ||||
|     override var mainUrl = "https://asianembed.io" | ||||
|     override val requiresReferer = true | ||||
| 
 | ||||
|     private val sourceRegex = Regex("""sources:[\W\w]*?file:\s*?["'](.*?)["']""") | ||||
|     override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> { | ||||
|         val extractedLinksList: MutableList<ExtractorLink> = mutableListOf() | ||||
|         with(app.get(url, referer = referer)) { | ||||
|             sourceRegex.findAll(this.text).forEach { sourceMatch -> | ||||
|                 val extractedUrl = sourceMatch.groupValues[1] | ||||
|                 // Trusting this isn't mp4, may fuck up stuff | ||||
|                 if (URI(extractedUrl).path.endsWith(".m3u8")) { | ||||
|                     M3u8Helper.generateM3u8( | ||||
|                         name, | ||||
|                         extractedUrl, | ||||
|                         url, | ||||
|                         headers = mapOf("referer" to this.url) | ||||
|                     ).forEach { link -> | ||||
|                         extractedLinksList.add(link) | ||||
|                     } | ||||
|                 } else if (extractedUrl.endsWith(".mp4")) { | ||||
|                     extractedLinksList.add( | ||||
|                         ExtractorLink( | ||||
|                             name, | ||||
|                             name, | ||||
|                             extractedUrl, | ||||
|                             url.replace(" ", "%20"), | ||||
|                             getQualityFromName(sourceMatch.groupValues[2]), | ||||
|                         ) | ||||
|                     ) | ||||
|                 } | ||||
|             } | ||||
|             return extractedLinksList | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,45 @@ | |||
| package com.lagradost.cloudstream3.extractors | ||||
| 
 | ||||
| import com.fasterxml.jackson.annotation.JsonProperty | ||||
| import com.lagradost.cloudstream3.app | ||||
| import com.lagradost.cloudstream3.utils.* | ||||
| import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson | ||||
| 
 | ||||
| class Blogger : ExtractorApi() { | ||||
|     override val name = "Blogger" | ||||
|     override val mainUrl = "https://www.blogger.com" | ||||
|     override val requiresReferer = false | ||||
| 
 | ||||
|     override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> { | ||||
|         val sources = mutableListOf<ExtractorLink>() | ||||
|         with(app.get(url).document) { | ||||
|             this.select("script").map { script -> | ||||
|                 if (script.data().contains("\"streams\":[")) { | ||||
|                     val data = script.data().substringAfter("\"streams\":[") | ||||
|                         .substringBefore("]") | ||||
|                     tryParseJson<List<ResponseSource>>("[$data]")?.map { | ||||
|                         sources.add( | ||||
|                             ExtractorLink( | ||||
|                                 name, | ||||
|                                 name, | ||||
|                                 it.play_url, | ||||
|                                 referer = "https://www.youtube.com/", | ||||
|                                 quality = when (it.format_id) { | ||||
|                                     18 -> 360 | ||||
|                                     22 -> 720 | ||||
|                                     else -> Qualities.Unknown.value | ||||
|                                 } | ||||
|                             ) | ||||
|                         ) | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return sources | ||||
|     } | ||||
| 
 | ||||
|     private data class ResponseSource( | ||||
|         @JsonProperty("play_url") val play_url: String, | ||||
|         @JsonProperty("format_id") val format_id: Int | ||||
|     ) | ||||
| } | ||||
|  | @ -0,0 +1,29 @@ | |||
| package com.lagradost.cloudstream3.extractors | ||||
| 
 | ||||
| import com.lagradost.cloudstream3.app | ||||
| import com.lagradost.cloudstream3.utils.ExtractorApi | ||||
| import com.lagradost.cloudstream3.utils.ExtractorLink | ||||
| import com.lagradost.cloudstream3.utils.M3u8Helper | ||||
| 
 | ||||
| class BullStream : ExtractorApi() { | ||||
|     override val name = "BullStream" | ||||
|     override val mainUrl = "https://bullstream.xyz" | ||||
|     override val requiresReferer = false | ||||
|     val regex = Regex("(?<=sniff\\()(.*)(?=\\)\\);)") | ||||
| 
 | ||||
|     override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? { | ||||
|         val data = regex.find(app.get(url).text)?.value | ||||
|             ?.replace("\"", "") | ||||
|             ?.split(",") | ||||
|             ?: return null | ||||
| 
 | ||||
|         val m3u8 = "$mainUrl/m3u8/${data[1]}/${data[2]}/master.txt?s=1&cache=${data[4]}" | ||||
|         println("shiv : $m3u8") | ||||
|         return M3u8Helper.generateM3u8( | ||||
|             name, | ||||
|             m3u8, | ||||
|             url, | ||||
|             headers = mapOf("referer" to url, "accept" to "*/*") | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,64 @@ | |||
| package com.lagradost.cloudstream3.extractors | ||||
| 
 | ||||
| import com.lagradost.cloudstream3.app | ||||
| import com.lagradost.cloudstream3.utils.ExtractorApi | ||||
| import com.lagradost.cloudstream3.utils.ExtractorLink | ||||
| import com.lagradost.cloudstream3.utils.Qualities | ||||
| import com.lagradost.cloudstream3.utils.getQualityFromName | ||||
| import kotlinx.coroutines.delay | ||||
| 
 | ||||
| class DoodCxExtractor : DoodLaExtractor() { | ||||
|     override var mainUrl = "https://dood.cx" | ||||
| } | ||||
| 
 | ||||
| class DoodShExtractor : DoodLaExtractor() { | ||||
|     override var mainUrl = "https://dood.sh" | ||||
| } | ||||
| class DoodWatchExtractor : DoodLaExtractor() { | ||||
|     override var mainUrl = "https://dood.watch" | ||||
| } | ||||
| 
 | ||||
| class DoodPmExtractor : DoodLaExtractor() { | ||||
|     override var mainUrl = "https://dood.pm" | ||||
| } | ||||
| 
 | ||||
| class DoodToExtractor : DoodLaExtractor() { | ||||
|     override var mainUrl = "https://dood.to" | ||||
| } | ||||
| 
 | ||||
| class DoodSoExtractor : DoodLaExtractor() { | ||||
|     override var mainUrl = "https://dood.so" | ||||
| } | ||||
| 
 | ||||
| class DoodWsExtractor : DoodLaExtractor() { | ||||
|     override var mainUrl = "https://dood.ws" | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| open class DoodLaExtractor : ExtractorApi() { | ||||
|     override var name = "DoodStream" | ||||
|     override var mainUrl = "https://dood.la" | ||||
|     override val requiresReferer = false | ||||
| 
 | ||||
|     override fun getExtractorUrl(id: String): String { | ||||
|         return "$mainUrl/d/$id" | ||||
|     } | ||||
| 
 | ||||
|     override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? { | ||||
|         val response0 = app.get(url).text // html of DoodStream page to look for /pass_md5/... | ||||
|         val md5 =mainUrl+(Regex("/pass_md5/[^']*").find(response0)?.value ?: return null)  // get https://dood.ws/pass_md5/... | ||||
|         val trueUrl = app.get(md5, referer = url).text + "zUEJeL3mUN?token=" + md5.substringAfterLast("/")   //direct link to extract  (zUEJeL3mUN is random) | ||||
|         val quality = Regex("\\d{3,4}p").find(response0.substringAfter("<title>").substringBefore("</title>"))?.groupValues?.get(0) | ||||
|         return listOf( | ||||
|             ExtractorLink( | ||||
|                 trueUrl, | ||||
|                 this.name, | ||||
|                 trueUrl, | ||||
|                 mainUrl, | ||||
|                 getQualityFromName(quality), | ||||
|                 false | ||||
|             ) | ||||
|         ) // links are valid in 8h | ||||
| 
 | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,54 @@ | |||
| package com.lagradost.cloudstream3.extractors | ||||
| 
 | ||||
| import com.lagradost.cloudstream3.utils.* | ||||
| import com.lagradost.cloudstream3.app | ||||
| 
 | ||||
| class Evoload1 : Evoload() { | ||||
|     override var mainUrl = "https://evoload.io" | ||||
| } | ||||
| 
 | ||||
| open class Evoload : ExtractorApi() { | ||||
|     override val name: String = "Evoload" | ||||
|     override val mainUrl: String = "https://www.evoload.io" | ||||
|     //private val srcRegex = Regex("""video .*src="(.*)""""")  // would be possible to use the parse and find src attribute | ||||
|     override val requiresReferer = true | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|     override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> { | ||||
|         val lang = url.substring(0, 2) | ||||
|         val flag = | ||||
|             if (lang == "vo") { | ||||
|                 " \uD83C\uDDEC\uD83C\uDDE7" | ||||
|             } | ||||
|             else if (lang == "vf"){ | ||||
|                 " \uD83C\uDDE8\uD83C\uDDF5" | ||||
|             } else { | ||||
|                 "" | ||||
|             } | ||||
|          | ||||
|         val cleaned_url = if (lang == "ht") {  // if url doesn't contain a flag and the url starts with http:// | ||||
|             url | ||||
|         } else { | ||||
|             url.substring(2, url.length) | ||||
|         } | ||||
| 	//println(lang) | ||||
|         //println(cleaned_url) | ||||
| 
 | ||||
|         val id = cleaned_url.replace("https://evoload.io/e/", "")  // wanted media id | ||||
|         val csrv_token = app.get("https://csrv.evosrv.com/captcha?m412548=").text  // whatever that is | ||||
|         val captchaPass = app.get("https://cd2.evosrv.com/html/jsx/e.jsx").text.take(300).split("captcha_pass = '")[1].split("\'")[0]  //extract the captcha pass from the js response (located in the 300 first chars) | ||||
|         val payload = mapOf("code" to id, "csrv_token" to csrv_token, "pass" to captchaPass) | ||||
|         val r = app.post("https://evoload.io/SecurePlayer", data=(payload)).text | ||||
|         val link = Regex("src\":\"(.*?)\"").find(r)?.destructured?.component1() ?: return listOf() | ||||
|         return listOf( | ||||
|             ExtractorLink( | ||||
|                 name, | ||||
|                 name + flag, | ||||
|                 link, | ||||
|                 cleaned_url, | ||||
|                 Qualities.Unknown.value, | ||||
|             ) | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,39 @@ | |||
| package com.lagradost.cloudstream3.extractors | ||||
| 
 | ||||
| import com.lagradost.cloudstream3.apmap | ||||
| import com.lagradost.cloudstream3.app | ||||
| import com.lagradost.cloudstream3.utils.ExtractorApi | ||||
| import com.lagradost.cloudstream3.utils.ExtractorLink | ||||
| import com.lagradost.cloudstream3.utils.M3u8Helper.Companion.generateM3u8 | ||||
| 
 | ||||
| class Fastream: ExtractorApi() { | ||||
|     override var mainUrl = "https://fastream.to" | ||||
|     override var name = "Fastream" | ||||
|     override val requiresReferer = false | ||||
| 
 | ||||
| 
 | ||||
|     override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? { | ||||
|         val id = Regex("emb\\.html\\?(.*)\\=(enc|)").find(url)?.destructured?.component1() ?: return emptyList() | ||||
|         val sources = mutableListOf<ExtractorLink>() | ||||
|         val response = app.post("$mainUrl/dl", | ||||
|         data = mapOf( | ||||
|             Pair("op","embed"), | ||||
|             Pair("file_code",id), | ||||
|             Pair("auto","1") | ||||
|         )).document | ||||
|         response.select("script").apmap { script -> | ||||
|             if (script.data().contains("sources")) { | ||||
|                 val m3u8regex = Regex("((https:|http:)\\/\\/.*\\.m3u8)") | ||||
|                 val m3u8 = m3u8regex.find(script.data())?.value ?: return@apmap | ||||
|                 generateM3u8( | ||||
|                     name, | ||||
|                     m3u8, | ||||
|                     mainUrl | ||||
|                 ).forEach { link -> | ||||
|                     sources.add(link) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return sources | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,38 @@ | |||
| package com.lagradost.cloudstream3.extractors | ||||
| 
 | ||||
| import com.fasterxml.jackson.annotation.JsonProperty | ||||
| import com.lagradost.cloudstream3.app | ||||
| import com.lagradost.cloudstream3.utils.* | ||||
| import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson | ||||
| 
 | ||||
| class Filesim : ExtractorApi() { | ||||
|     override val name = "Filesim" | ||||
|     override val mainUrl = "https://files.im" | ||||
|     override val requiresReferer = false | ||||
| 
 | ||||
|     override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> { | ||||
|         val sources = mutableListOf<ExtractorLink>() | ||||
|         with(app.get(url).document) { | ||||
|             this.select("script").map { script -> | ||||
|                 if (script.data().contains("eval(function(p,a,c,k,e,d)")) { | ||||
|                     val data = getAndUnpack(script.data()).substringAfter("sources:[").substringBefore("]") | ||||
|                     tryParseJson<List<ResponseSource>>("[$data]")?.map { | ||||
|                         M3u8Helper.generateM3u8( | ||||
|                             name, | ||||
|                             it.file, | ||||
|                             "$mainUrl/", | ||||
|                         ).forEach { m3uData -> sources.add(m3uData) } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return sources | ||||
|     } | ||||
| 
 | ||||
|     private data class ResponseSource( | ||||
|         @JsonProperty("file") val file: String, | ||||
|         @JsonProperty("type") val type: String?, | ||||
|         @JsonProperty("label") val label: String? | ||||
|     ) | ||||
| 
 | ||||
| } | ||||
|  | @ -0,0 +1,39 @@ | |||
| package com.lagradost.cloudstream3.extractors | ||||
| 
 | ||||
| import com.lagradost.cloudstream3.app | ||||
| import com.lagradost.cloudstream3.utils.ExtractorApi | ||||
| import com.lagradost.cloudstream3.utils.ExtractorLink | ||||
| import com.lagradost.cloudstream3.utils.M3u8Helper | ||||
| 
 | ||||
| class GMPlayer : ExtractorApi() { | ||||
|     override val name = "GM Player" | ||||
|     override val mainUrl = "https://gmplayer.xyz" | ||||
|     override val requiresReferer = true | ||||
| 
 | ||||
|     override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? { | ||||
|         val ref = referer ?: return null | ||||
|         val id = url.substringAfter("/video/").substringBefore("/") | ||||
| 
 | ||||
|         val m3u8 = app.post( | ||||
|             "$mainUrl/player/index.php?data=$id&do=getVideo", | ||||
|             mapOf( | ||||
|                 "accept" to "*/*", | ||||
|                 "referer" to ref, | ||||
|                 "x-requested-with" to "XMLHttpRequest", | ||||
|                 "origin" to mainUrl | ||||
|             ), | ||||
|             data = mapOf("hash" to id, "r" to ref) | ||||
|         ).parsed<GmResponse>().videoSource ?: return null | ||||
| 
 | ||||
|         return M3u8Helper.generateM3u8( | ||||
|             name, | ||||
|             m3u8, | ||||
|             ref, | ||||
|             headers = mapOf("accept" to "*/*") | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     private data class GmResponse( | ||||
|         val videoSource: String? = null | ||||
|     ) | ||||
| } | ||||
|  | @ -0,0 +1,33 @@ | |||
| package com.lagradost.cloudstream3.extractors | ||||
| 
 | ||||
| import com.lagradost.cloudstream3.app | ||||
| import com.lagradost.cloudstream3.network.WebViewResolver | ||||
| import com.lagradost.cloudstream3.utils.ExtractorApi | ||||
| import com.lagradost.cloudstream3.utils.ExtractorLink | ||||
| import com.lagradost.cloudstream3.utils.M3u8Helper | ||||
| 
 | ||||
| 
 | ||||
| open class GenericM3U8 : ExtractorApi() { | ||||
|     override var name = "Upstream" | ||||
|     override var mainUrl = "https://upstream.to" | ||||
|     override val requiresReferer = false | ||||
| 
 | ||||
|     override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> { | ||||
|         val response = app.get( | ||||
|             url, interceptor = WebViewResolver( | ||||
|                 Regex("""master\.m3u8""") | ||||
|             ) | ||||
|         ) | ||||
|         val sources = mutableListOf<ExtractorLink>() | ||||
|         if (response.url.contains("m3u8")) | ||||
|             M3u8Helper.generateM3u8( | ||||
|                 name, | ||||
|                 response.url, | ||||
|                 url, | ||||
|                 headers = response.headers.toMap() | ||||
|             ).forEach { link -> | ||||
|                 sources.add(link) | ||||
|             } | ||||
|         return sources | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,36 @@ | |||
| package com.lagradost.cloudstream3.extractors | ||||
| import com.fasterxml.jackson.annotation.JsonProperty | ||||
| import com.lagradost.cloudstream3.utils.ExtractorLink | ||||
| import com.lagradost.cloudstream3.app | ||||
| import com.lagradost.cloudstream3.utils.* | ||||
| 
 | ||||
| open class GuardareStream : ExtractorApi() { | ||||
|     override var name = "Guardare" | ||||
|     override var mainUrl = "https://guardare.stream" | ||||
|     override val requiresReferer = false | ||||
| 
 | ||||
|     data class GuardareJsonData ( | ||||
|         @JsonProperty("data") val data : List<GuardareData>, | ||||
|     ) | ||||
| 
 | ||||
|     data class GuardareData ( | ||||
|         @JsonProperty("file") val file : String, | ||||
|         @JsonProperty("label") val label : String, | ||||
|         @JsonProperty("type") val type : String | ||||
|     ) | ||||
| 
 | ||||
|     override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? { | ||||
|         val response = app.post(url.replace("/v/","/api/source/"), data = mapOf("d" to mainUrl)).text | ||||
|         val jsonvideodata = AppUtils.parseJson<GuardareJsonData>(response) | ||||
|         return jsonvideodata.data.map { | ||||
|             ExtractorLink( | ||||
|                 it.file+".${it.type}", | ||||
|                 this.name, | ||||
|                 it.file+".${it.type}", | ||||
|                 mainUrl, | ||||
|                 it.label.filter{ it.isDigit() }.toInt(), | ||||
|                 false | ||||
|             ) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,100 @@ | |||
| package com.lagradost.cloudstream3.extractors | ||||
| 
 | ||||
| import com.fasterxml.jackson.annotation.JsonProperty | ||||
| import com.lagradost.cloudstream3.app | ||||
| import com.lagradost.cloudstream3.utils.* | ||||
| import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson | ||||
| 
 | ||||
| class Neonime7n : Hxfile() { | ||||
|     override val name = "Neonime7n" | ||||
|     override val mainUrl = "https://7njctn.neonime.watch" | ||||
|     override val redirect = false | ||||
| } | ||||
| 
 | ||||
| class Neonime8n : Hxfile() { | ||||
|     override val name = "Neonime8n" | ||||
|     override val mainUrl = "https://8njctn.neonime.net" | ||||
|     override val redirect = false | ||||
| } | ||||
| 
 | ||||
| class KotakAnimeid : Hxfile() { | ||||
|     override val name = "KotakAnimeid" | ||||
|     override val mainUrl = "https://kotakanimeid.com" | ||||
|     override val requiresReferer = true | ||||
| } | ||||
| 
 | ||||
| class Yufiles : Hxfile() { | ||||
|     override val name = "Yufiles" | ||||
|     override val mainUrl = "https://yufiles.com" | ||||
| } | ||||
| 
 | ||||
| class Aico : Hxfile() { | ||||
|     override val name = "Aico" | ||||
|     override val mainUrl = "https://aico.pw" | ||||
| } | ||||
| 
 | ||||
| open class Hxfile : ExtractorApi() { | ||||
|     override val name = "Hxfile" | ||||
|     override val mainUrl = "https://hxfile.co" | ||||
|     override val requiresReferer = false | ||||
|     open val redirect = true | ||||
| 
 | ||||
|     override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? { | ||||
|         val sources = mutableListOf<ExtractorLink>() | ||||
|         val document = app.get(url, allowRedirects = redirect, referer = referer).document | ||||
|         with(document) { | ||||
|             this.select("script").map { script -> | ||||
|                 if (script.data().contains("eval(function(p,a,c,k,e,d)")) { | ||||
|                     val data = | ||||
|                         getAndUnpack(script.data()).substringAfter("sources:[").substringBefore("]") | ||||
|                     tryParseJson<List<ResponseSource>>("[$data]")?.map { | ||||
|                         sources.add( | ||||
|                             ExtractorLink( | ||||
|                                 name, | ||||
|                                 name, | ||||
|                                 it.file, | ||||
|                                 referer = mainUrl, | ||||
|                                 quality = when { | ||||
|                                     url.contains("hxfile.co") -> getQualityFromName( | ||||
|                                         Regex("\\d\\.(.*?).mp4").find( | ||||
|                                             document.select("title").text() | ||||
|                                         )?.groupValues?.get(1).toString() | ||||
|                                     ) | ||||
|                                     else -> getQualityFromName(it.label) | ||||
|                                 } | ||||
|                             ) | ||||
|                         ) | ||||
|                     } | ||||
|                 } else if (script.data().contains("\"sources\":[")) { | ||||
|                     val data = script.data().substringAfter("\"sources\":[").substringBefore("]") | ||||
|                     tryParseJson<List<ResponseSource>>("[$data]")?.map { | ||||
|                         sources.add( | ||||
|                             ExtractorLink( | ||||
|                                 name, | ||||
|                                 name, | ||||
|                                 it.file, | ||||
|                                 referer = mainUrl, | ||||
|                                 quality = when { | ||||
|                                     it.label?.contains("HD") == true -> Qualities.P720.value | ||||
|                                     it.label?.contains("SD") == true -> Qualities.P480.value | ||||
|                                     else -> getQualityFromName(it.label) | ||||
|                                 } | ||||
|                             ) | ||||
|                         ) | ||||
|                     } | ||||
|                 } | ||||
|                 else { | ||||
|                     null | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return sources | ||||
|     } | ||||
| 
 | ||||
|     private data class ResponseSource( | ||||
|         @JsonProperty("file") val file: String, | ||||
|         @JsonProperty("type") val type: String?, | ||||
|         @JsonProperty("label") val label: String? | ||||
|     ) | ||||
| 
 | ||||
| } | ||||
|  | @ -0,0 +1,81 @@ | |||
| package com.lagradost.cloudstream3.extractors | ||||
| 
 | ||||
| import com.fasterxml.jackson.annotation.JsonProperty | ||||
| import com.lagradost.cloudstream3.app | ||||
| import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson | ||||
| import com.lagradost.cloudstream3.utils.ExtractorApi | ||||
| import com.lagradost.cloudstream3.utils.ExtractorLink | ||||
| import com.lagradost.cloudstream3.utils.Qualities | ||||
| import com.lagradost.cloudstream3.utils.getQualityFromName | ||||
| 
 | ||||
| class Meownime : JWPlayer() { | ||||
|     override val name = "Meownime" | ||||
|     override val mainUrl = "https://meownime.ltd" | ||||
| } | ||||
| 
 | ||||
| class DesuOdchan : JWPlayer() { | ||||
|     override val name = "DesuOdchan" | ||||
|     override val mainUrl = "https://desustream.me/odchan/" | ||||
| } | ||||
| 
 | ||||
| class DesuArcg : JWPlayer() { | ||||
|     override val name = "DesuArcg" | ||||
|     override val mainUrl = "https://desustream.me/arcg/" | ||||
| } | ||||
| 
 | ||||
| class DesuDrive : JWPlayer() { | ||||
|     override val name = "DesuDrive" | ||||
|     override val mainUrl = "https://desustream.me/desudrive/" | ||||
| } | ||||
| 
 | ||||
| class DesuOdvip : JWPlayer() { | ||||
|     override val name = "DesuOdvip" | ||||
|     override val mainUrl = "https://desustream.me/odvip/" | ||||
| } | ||||
| 
 | ||||
| open class JWPlayer : ExtractorApi() { | ||||
|     override val name = "JWPlayer" | ||||
|     override val mainUrl = "https://www.jwplayer.com" | ||||
|     override val requiresReferer = false | ||||
| 
 | ||||
|     override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? { | ||||
|         val sources = mutableListOf<ExtractorLink>() | ||||
|         with(app.get(url).document) { | ||||
|             val data = this.select("script").mapNotNull { script -> | ||||
|                 if (script.data().contains("sources: [")) { | ||||
|                     script.data().substringAfter("sources: [") | ||||
|                         .substringBefore("],").replace("'", "\"") | ||||
|                 } else if (script.data().contains("otakudesu('")) { | ||||
|                     script.data().substringAfter("otakudesu('") | ||||
|                         .substringBefore("');") | ||||
|                 } else { | ||||
|                     null | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             tryParseJson<List<ResponseSource>>("$data")?.map { | ||||
|                 sources.add( | ||||
|                     ExtractorLink( | ||||
|                         name, | ||||
|                         name, | ||||
|                         it.file, | ||||
|                         referer = url, | ||||
|                         quality = getQualityFromName( | ||||
|                             Regex("(\\d{3,4}p)").find(it.file)?.groupValues?.get( | ||||
|                                 1 | ||||
|                             ) | ||||
|                         ) | ||||
|                     ) | ||||
|                 ) | ||||
|             } | ||||
|         } | ||||
|         return sources | ||||
|     } | ||||
| 
 | ||||
|     private data class ResponseSource( | ||||
|         @JsonProperty("file") val file: String, | ||||
|         @JsonProperty("type") val type: String?, | ||||
|         @JsonProperty("label") val label: String? | ||||
|     ) | ||||
| 
 | ||||
| } | ||||
|  | @ -0,0 +1,27 @@ | |||
| package com.lagradost.cloudstream3.extractors | ||||
| 
 | ||||
| import com.lagradost.cloudstream3.app | ||||
| import com.lagradost.cloudstream3.utils.ExtractorApi | ||||
| import com.lagradost.cloudstream3.utils.ExtractorLink | ||||
| import com.lagradost.cloudstream3.utils.M3u8Helper | ||||
| 
 | ||||
| 
 | ||||
| open class Jawcloud : ExtractorApi() { | ||||
|     override var name = "Jawcloud" | ||||
|     override var mainUrl = "https://jawcloud.co" | ||||
|     override val requiresReferer = false | ||||
| 
 | ||||
|     override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? { | ||||
|         val doc = app.get(url).document | ||||
|         val urlString = doc.select("html body div source").attr("src") | ||||
|         val sources = mutableListOf<ExtractorLink>() | ||||
|         if (urlString.contains("m3u8")) | ||||
|             M3u8Helper.generateM3u8( | ||||
|                 name, | ||||
|                 urlString, | ||||
|                 url, | ||||
|                 headers = app.get(url).headers.toMap() | ||||
|             ).forEach { link -> sources.add(link) } | ||||
|         return sources | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,46 @@ | |||
| package com.lagradost.cloudstream3.extractors | ||||
| 
 | ||||
| import com.fasterxml.jackson.annotation.JsonProperty | ||||
| import com.lagradost.cloudstream3.app | ||||
| import com.lagradost.cloudstream3.utils.ExtractorApi | ||||
| import com.lagradost.cloudstream3.utils.ExtractorLink | ||||
| import com.lagradost.cloudstream3.utils.getQualityFromName | ||||
| 
 | ||||
| class Linkbox : ExtractorApi() { | ||||
|     override val name = "Linkbox" | ||||
|     override val mainUrl = "https://www.linkbox.to" | ||||
|     override val requiresReferer = true | ||||
| 
 | ||||
|     override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> { | ||||
|         val id = url.substringAfter("id=") | ||||
|         val sources = mutableListOf<ExtractorLink>() | ||||
| 
 | ||||
|         app.get("$mainUrl/api/open/get_url?itemId=$id", referer=url).parsedSafe<Responses>()?.data?.rList?.map { link -> | ||||
|             sources.add( | ||||
|                 ExtractorLink( | ||||
|                     name, | ||||
|                     name, | ||||
|                     link.url, | ||||
|                     url, | ||||
|                     getQualityFromName(link.resolution) | ||||
|                 ) | ||||
|             ) | ||||
|         } | ||||
| 
 | ||||
|         return sources | ||||
|     } | ||||
| 
 | ||||
|     data class RList( | ||||
|         @JsonProperty("url") val url: String, | ||||
|         @JsonProperty("resolution") val resolution: String?, | ||||
|     ) | ||||
| 
 | ||||
|     data class Data( | ||||
|         @JsonProperty("rList") val rList: List<RList>?, | ||||
|     ) | ||||
| 
 | ||||
|     data class Responses( | ||||
|         @JsonProperty("data") val data: Data?, | ||||
|     ) | ||||
| 
 | ||||
| } | ||||
|  | @ -0,0 +1,16 @@ | |||
| package com.lagradost.cloudstream3.extractors | ||||
| 
 | ||||
| //{"auto":"/manifests/movies/15559/1624728920/qDwu5BOsfAwfTmnnjmkmXA/master.m3u8","1080p":"https://vdoc3.sallenes.space/qDwu5BOsfAwfTmnnjmkmXA/1624728920/storage6/movies/the-man-with-the-iron-heart-2017/1080p/index.m3u8","720p":"https://vdoc3.sallenes.space/qDwu5BOsfAwfTmnnjmkmXA/1624728920/storage6/movies/the-man-with-the-iron-heart-2017/720p/index.m3u8","360p":"https://vdoc3.sallenes.space/qDwu5BOsfAwfTmnnjmkmXA/1624728920/storage6/movies/the-man-with-the-iron-heart-2017/360p/index.m3u8","480p":"https://vdoc3.sallenes.space/qDwu5BOsfAwfTmnnjmkmXA/1624728920/storage6/movies/the-man-with-the-iron-heart-2017/480p/index.m3u8"} | ||||
| object M3u8Manifest { | ||||
|     // URL = first, QUALITY = second | ||||
|     fun extractLinks(m3u8Data: String): ArrayList<Pair<String, String>> { | ||||
|         val data: ArrayList<Pair<String, String>> = ArrayList() | ||||
|         Regex("\"(.*?)\":\"(.*?)\"").findAll(m3u8Data).forEach { | ||||
|             var quality = it.groupValues[1].replace("auto", "Auto") | ||||
|             if (quality != "Auto" && !quality.endsWith('p')) quality += "p" | ||||
|             val url = it.groupValues[2] | ||||
|             data.add(Pair(url, quality)) | ||||
|         } | ||||
|         return data | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,29 @@ | |||
| package com.lagradost.cloudstream3.extractors | ||||
| 
 | ||||
| import com.lagradost.cloudstream3.utils.ExtractorLink | ||||
| import com.lagradost.cloudstream3.app | ||||
| import com.lagradost.cloudstream3.utils.* | ||||
| 
 | ||||
| open class Maxstream : ExtractorApi() { | ||||
|     override var name = "Maxstream" | ||||
|     override var mainUrl = "https://maxstream.video/" | ||||
|     override val requiresReferer = false | ||||
|     override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? { | ||||
|         val extractedLinksList: MutableList<ExtractorLink> = mutableListOf() | ||||
|         val response = app.get(url).text | ||||
|         val jstounpack = Regex("cript\">eval((.|\\n)*?)</script>").find(response)?.groups?.get(1)?.value | ||||
|         val unpacjed = JsUnpacker(jstounpack).unpack() | ||||
|         val extractedUrl = unpacjed?.let { Regex("""src:"((.|\n)*?)",type""").find(it) }?.groups?.get(1)?.value.toString() | ||||
| 
 | ||||
|         M3u8Helper.generateM3u8( | ||||
|             name, | ||||
|             extractedUrl, | ||||
|             url, | ||||
|             headers = mapOf("referer" to url) | ||||
|         ).forEach { link -> | ||||
|             extractedLinksList.add(link) | ||||
|         } | ||||
| 
 | ||||
|         return extractedLinksList | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,7 @@ | |||
| package com.lagradost.cloudstream3.extractors | ||||
| 
 | ||||
| open class Mcloud : WcoStream() { | ||||
|     override var name = "Mcloud" | ||||
|     override var mainUrl = "https://mcloud.to" | ||||
|     override val requiresReferer = true | ||||
| } | ||||
|  | @ -0,0 +1,45 @@ | |||
| package com.lagradost.cloudstream3.extractors | ||||
| 
 | ||||
| import com.lagradost.cloudstream3.app | ||||
| import com.lagradost.cloudstream3.utils.* | ||||
| 
 | ||||
| class MixDropBz : MixDrop(){ | ||||
|     override var mainUrl = "https://mixdrop.bz" | ||||
| } | ||||
| 
 | ||||
| class MixDropCh : MixDrop(){ | ||||
|     override var mainUrl = "https://mixdrop.ch" | ||||
| } | ||||
| class MixDropTo : MixDrop(){ | ||||
|     override var mainUrl = "https://mixdrop.to" | ||||
| } | ||||
| 
 | ||||
| open class MixDrop : ExtractorApi() { | ||||
|     override var name = "MixDrop" | ||||
|     override var mainUrl = "https://mixdrop.co" | ||||
|     private val srcRegex = Regex("""wurl.*?=.*?"(.*?)";""") | ||||
|     override val requiresReferer = false | ||||
| 
 | ||||
|     override fun getExtractorUrl(id: String): String { | ||||
|         return "$mainUrl/e/$id" | ||||
|     } | ||||
| 
 | ||||
|     override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? { | ||||
|         with(app.get(url)) { | ||||
|             getAndUnpack(this.text).let { unpackedText -> | ||||
|                 srcRegex.find(unpackedText)?.groupValues?.get(1)?.let { link -> | ||||
|                     return listOf( | ||||
|                         ExtractorLink( | ||||
|                             name, | ||||
|                             name, | ||||
|                             httpsify(link), | ||||
|                             url, | ||||
|                             Qualities.Unknown.value, | ||||
|                         ) | ||||
|                     ) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return null | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,34 @@ | |||
| package com.lagradost.cloudstream3.extractors | ||||
| 
 | ||||
| import com.lagradost.cloudstream3.app | ||||
| import com.lagradost.cloudstream3.utils.ExtractorApi | ||||
| import com.lagradost.cloudstream3.utils.ExtractorLink | ||||
| import com.lagradost.cloudstream3.utils.Qualities | ||||
| import com.lagradost.cloudstream3.utils.getAndUnpack | ||||
| 
 | ||||
| class Mp4Upload : ExtractorApi() { | ||||
|     override var name = "Mp4Upload" | ||||
|     override var mainUrl = "https://www.mp4upload.com" | ||||
|     private val srcRegex = Regex("""player\.src\("(.*?)"""") | ||||
|     override val requiresReferer = true | ||||
| 
 | ||||
|     override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? { | ||||
|         with(app.get(url)) { | ||||
|             getAndUnpack(this.text).let { unpackedText -> | ||||
|                 val quality = unpackedText.lowercase().substringAfter(" height=").substringBefore(" ").toIntOrNull() | ||||
|                 srcRegex.find(unpackedText)?.groupValues?.get(1)?.let { link -> | ||||
|                     return listOf( | ||||
|                         ExtractorLink( | ||||
|                             name, | ||||
|                             name, | ||||
|                             link, | ||||
|                             url, | ||||
|                             quality ?: Qualities.Unknown.value, | ||||
|                         ) | ||||
|                     ) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return null | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,59 @@ | |||
| package com.lagradost.cloudstream3.extractors | ||||
| 
 | ||||
| import com.lagradost.cloudstream3.app | ||||
| import com.lagradost.cloudstream3.utils.ExtractorApi | ||||
| import com.lagradost.cloudstream3.utils.ExtractorLink | ||||
| import com.lagradost.cloudstream3.utils.Qualities | ||||
| import com.lagradost.cloudstream3.utils.getQualityFromName | ||||
| import java.net.URI | ||||
| 
 | ||||
| class MultiQuality : ExtractorApi() { | ||||
|     override var name = "MultiQuality" | ||||
|     override var mainUrl = "https://gogo-play.net" | ||||
|     private val sourceRegex = Regex("""file:\s*['"](.*?)['"],label:\s*['"](.*?)['"]""") | ||||
|     private val m3u8Regex = Regex(""".*?(\d*).m3u8""") | ||||
|     private val urlRegex = Regex("""(.*?)([^/]+$)""") | ||||
|     override val requiresReferer = false | ||||
| 
 | ||||
|     override fun getExtractorUrl(id: String): String { | ||||
|         return "$mainUrl/loadserver.php?id=$id" | ||||
|     } | ||||
| 
 | ||||
|     override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> { | ||||
|         val extractedLinksList: MutableList<ExtractorLink> = mutableListOf() | ||||
|         with(app.get(url)) { | ||||
|             sourceRegex.findAll(this.text).forEach { sourceMatch -> | ||||
|                 val extractedUrl = sourceMatch.groupValues[1] | ||||
|                 // Trusting this isn't mp4, may fuck up stuff | ||||
|                 if (URI(extractedUrl).path.endsWith(".m3u8")) { | ||||
|                     with(app.get(extractedUrl)) { | ||||
|                         m3u8Regex.findAll(this.text).forEach { match -> | ||||
|                             extractedLinksList.add( | ||||
|                                 ExtractorLink( | ||||
|                                     name, | ||||
|                                     name = name, | ||||
|                                     urlRegex.find(this.url)!!.groupValues[1] + match.groupValues[0], | ||||
|                                     url, | ||||
|                                     getQualityFromName(match.groupValues[1]), | ||||
|                                     isM3u8 = true | ||||
|                                 ) | ||||
|                             ) | ||||
|                         } | ||||
| 
 | ||||
|                     } | ||||
|                 } else if (extractedUrl.endsWith(".mp4")) { | ||||
|                     extractedLinksList.add( | ||||
|                         ExtractorLink( | ||||
|                             name, | ||||
|                             "$name ${sourceMatch.groupValues[2]}", | ||||
|                             extractedUrl, | ||||
|                             url.replace(" ", "%20"), | ||||
|                             Qualities.Unknown.value, | ||||
|                         ) | ||||
|                     ) | ||||
|                 } | ||||
|             } | ||||
|             return extractedLinksList | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,67 @@ | |||
| package com.lagradost.cloudstream3.extractors | ||||
| 
 | ||||
| import com.fasterxml.jackson.annotation.JsonProperty | ||||
| import com.lagradost.cloudstream3.utils.* | ||||
| import com.lagradost.cloudstream3.app | ||||
| import com.lagradost.cloudstream3.utils.AppUtils.parseJson | ||||
| 
 | ||||
| data class DataOptionsJson ( | ||||
|     @JsonProperty("flashvars") var flashvars : Flashvars? = Flashvars(), | ||||
| ) | ||||
| data class Flashvars ( | ||||
|     @JsonProperty("metadata") var metadata : String? = null, | ||||
|     @JsonProperty("hlsManifestUrl") var hlsManifestUrl : String? = null, //m3u8 | ||||
| ) | ||||
| 
 | ||||
| data class MetadataOkru ( | ||||
|     @JsonProperty("videos") var videos: ArrayList<Videos> = arrayListOf(), | ||||
| ) | ||||
| 
 | ||||
| data class Videos ( | ||||
|     @JsonProperty("name") var name : String, | ||||
|     @JsonProperty("url") var url : String, | ||||
|     @JsonProperty("seekSchema") var seekSchema : Int? = null, | ||||
|     @JsonProperty("disallowed") var disallowed : Boolean? = null | ||||
| ) | ||||
| 
 | ||||
| class OkRuHttps: OkRu(){ | ||||
|     override var mainUrl = "https://ok.ru" | ||||
| } | ||||
| 
 | ||||
| open class OkRu : ExtractorApi() { | ||||
|     override var name = "Okru" | ||||
|     override var mainUrl = "http://ok.ru" | ||||
|     override val requiresReferer = false | ||||
| 
 | ||||
|     override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? { | ||||
|         val doc = app.get(url).document | ||||
|         val sources = ArrayList<ExtractorLink>() | ||||
|         val datajson = doc.select("div[data-options]").attr("data-options") | ||||
|         if (datajson.isNotBlank()) { | ||||
|             val main = parseJson<DataOptionsJson>(datajson) | ||||
|             val metadatajson = parseJson<MetadataOkru>(main.flashvars?.metadata!!) | ||||
|             val servers = metadatajson.videos | ||||
|             servers.forEach { | ||||
|                 val quality = it.name.uppercase() | ||||
|                     .replace("MOBILE","144p") | ||||
|                     .replace("LOWEST","240p") | ||||
|                     .replace("LOW","360p") | ||||
|                     .replace("SD","480p") | ||||
|                     .replace("HD","720p") | ||||
|                     .replace("FULL","1080p") | ||||
|                     .replace("QUAD","1440p") | ||||
|                     .replace("ULTRA","4k") | ||||
|                 val extractedurl = it.url.replace("\\\\u0026", "&") | ||||
|                 sources.add(ExtractorLink( | ||||
|                     name, | ||||
|                     name = this.name, | ||||
|                     extractedurl, | ||||
|                     url, | ||||
|                     getQualityFromName(quality), | ||||
|                     isM3u8 = false | ||||
|                 )) | ||||
|             } | ||||
|         } | ||||
|         return sources | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,99 @@ | |||
| package com.lagradost.cloudstream3.extractors | ||||
| 
 | ||||
| import com.lagradost.cloudstream3.SubtitleFile | ||||
| import com.lagradost.cloudstream3.apmap | ||||
| import com.lagradost.cloudstream3.app | ||||
| import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall | ||||
| import com.lagradost.cloudstream3.utils.ExtractorLink | ||||
| import com.lagradost.cloudstream3.utils.extractorApis | ||||
| import com.lagradost.cloudstream3.utils.getQualityFromName | ||||
| import com.lagradost.cloudstream3.utils.loadExtractor | ||||
| import org.jsoup.Jsoup | ||||
| 
 | ||||
| /** | ||||
|  * overrideMainUrl is necessary for for other vidstream clones like vidembed.cc | ||||
|  * If they diverge it'd be better to make them separate. | ||||
|  * */ | ||||
| class Pelisplus(val mainUrl: String) { | ||||
|     val name: String = "Vidstream" | ||||
| 
 | ||||
|     private fun getExtractorUrl(id: String): String { | ||||
|         return "$mainUrl/play?id=$id" | ||||
|     } | ||||
| 
 | ||||
|     private fun getDownloadUrl(id: String): String { | ||||
|         return "$mainUrl/download?id=$id" | ||||
|     } | ||||
| 
 | ||||
|     private val normalApis = arrayListOf(MultiQuality()) | ||||
| 
 | ||||
|     // https://gogo-stream.com/streaming.php?id=MTE3NDg5 | ||||
|     suspend fun getUrl( | ||||
|         id: String, | ||||
|         isCasting: Boolean = false, | ||||
|         subtitleCallback: (SubtitleFile) -> Unit, | ||||
|         callback: (ExtractorLink) -> Unit | ||||
|     ): Boolean { | ||||
|         try { | ||||
|             normalApis.apmap { api -> | ||||
|                 val url = api.getExtractorUrl(id) | ||||
|                 api.getSafeUrl(url, subtitleCallback = subtitleCallback, callback = callback) | ||||
|             } | ||||
|             val extractorUrl = getExtractorUrl(id) | ||||
| 
 | ||||
|             /** Stolen from GogoanimeProvider.kt extractor */ | ||||
|             suspendSafeApiCall { | ||||
|                 val link = getDownloadUrl(id) | ||||
|                 println("Generated vidstream download link: $link") | ||||
|                 val page = app.get(link, referer = extractorUrl) | ||||
| 
 | ||||
|                 val pageDoc = Jsoup.parse(page.text) | ||||
|                 val qualityRegex = Regex("(\\d+)P") | ||||
| 
 | ||||
|                 //a[download] | ||||
|                 pageDoc.select(".dowload > a")?.apmap { element -> | ||||
|                     val href = element.attr("href") ?: return@apmap | ||||
|                     val qual = if (element.text() | ||||
|                             .contains("HDP") | ||||
|                     ) "1080" else qualityRegex.find(element.text())?.destructured?.component1() | ||||
|                         .toString() | ||||
| 
 | ||||
|                     if (!loadExtractor(href, link, subtitleCallback, callback)) { | ||||
|                         callback.invoke( | ||||
|                             ExtractorLink( | ||||
|                                 this.name, | ||||
|                                 name = this.name, | ||||
|                                 href, | ||||
|                                 page.url, | ||||
|                                 getQualityFromName(qual), | ||||
|                                 element.attr("href").contains(".m3u8") | ||||
|                             ) | ||||
|                         ) | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             with(app.get(extractorUrl)) { | ||||
|                 val document = Jsoup.parse(this.text) | ||||
|                 val primaryLinks = document.select("ul.list-server-items > li.linkserver") | ||||
|                 //val extractedLinksList: MutableList<ExtractorLink> = mutableListOf() | ||||
| 
 | ||||
|                 // All vidstream links passed to extractors | ||||
|                 primaryLinks.distinctBy { it.attr("data-video") }.forEach { element -> | ||||
|                     val link = element.attr("data-video") | ||||
|                     //val name = element.text() | ||||
| 
 | ||||
|                     // Matches vidstream links with extractors | ||||
|                     extractorApis.filter { !it.requiresReferer || !isCasting }.apmap { api -> | ||||
|                         if (link.startsWith(api.mainUrl)) { | ||||
|                             api.getSafeUrl(link, extractorUrl, subtitleCallback, callback) | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|                 return true | ||||
|             } | ||||
|         } catch (e: Exception) { | ||||
|             return false | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,31 @@ | |||
| package com.lagradost.cloudstream3.extractors | ||||
| 
 | ||||
| import com.lagradost.cloudstream3.app | ||||
| import com.lagradost.cloudstream3.utils.ExtractorApi | ||||
| import com.lagradost.cloudstream3.utils.ExtractorLink | ||||
| import com.lagradost.cloudstream3.utils.M3u8Helper | ||||
| 
 | ||||
| 
 | ||||
| open class PlayerVoxzer : ExtractorApi() { | ||||
|     override var name = "Voxzer" | ||||
|     override var mainUrl = "https://player.voxzer.org" | ||||
|     override val requiresReferer = false | ||||
| 
 | ||||
|     override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? { | ||||
|         val listurl = url.replace("/view/","/list/") | ||||
|         val urltext = app.get(listurl, referer = url).text | ||||
|         val m3u8regex = Regex("((https:|http:)\\/\\/.*\\.m3u8)") | ||||
|         val sources = mutableListOf<ExtractorLink>() | ||||
|         val listm3 = m3u8regex.find(urltext)?.value | ||||
|         if (listm3?.contains("m3u8") == true) | ||||
|             M3u8Helper.generateM3u8( | ||||
|                 name, | ||||
|                 listm3, | ||||
|                 url, | ||||
|                 headers = app.get(url).headers.toMap() | ||||
|             ).forEach { link -> | ||||
|                 sources.add(link) | ||||
|             } | ||||
|         return sources | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,86 @@ | |||
| package com.lagradost.cloudstream3.extractors | ||||
| 
 | ||||
| import com.lagradost.cloudstream3.APIHolder.unixTimeMS | ||||
| import com.lagradost.cloudstream3.app | ||||
| import com.lagradost.cloudstream3.mvvm.logError | ||||
| import com.lagradost.cloudstream3.utils.ExtractorApi | ||||
| import com.lagradost.cloudstream3.utils.ExtractorLink | ||||
| import com.lagradost.cloudstream3.utils.Qualities | ||||
| import com.lagradost.cloudstream3.utils.getPostForm | ||||
| import org.jsoup.Jsoup | ||||
| 
 | ||||
| //class SBPlay1 : SBPlay() { | ||||
| //    override var mainUrl = "https://sbplay1.com" | ||||
| //} | ||||
| 
 | ||||
| //class SBPlay2 : SBPlay() { | ||||
| //    override var mainUrl = "https://sbplay2.com" | ||||
| //} | ||||
| 
 | ||||
| open class SBPlay : ExtractorApi() { | ||||
|     override var mainUrl = "https://sbplay.one" | ||||
|     override var name = "SBPlay" | ||||
|     override val requiresReferer = false | ||||
| 
 | ||||
|     override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> { | ||||
|         val response = app.get(url, referer = referer).text | ||||
|         val document = Jsoup.parse(response) | ||||
| 
 | ||||
|         val links = ArrayList<ExtractorLink>() | ||||
| 
 | ||||
|         val tree = document.select("table > tbody > tr > td > a") | ||||
|         for (item in tree) { | ||||
|             val onDownload = item.attr("onclick") | ||||
|             val name = "${this.name} - ${item.text()}" | ||||
|             try { | ||||
|                 Regex("download_video\\('(.*?)','(.*?)','(.*?)'\\)").matchEntire(onDownload)?.let { | ||||
|                     val id = it.groupValues[1] | ||||
|                     val mode = it.groupValues[2] | ||||
|                     val hash = it.groupValues[3] | ||||
|                     val href = "https://sbplay.one/dl?op=download_orig&id=$id&mode=$mode&hash=$hash" | ||||
|                     val hrefResponse = app.get(href).text | ||||
|                     app.post("https://sbplay.one/?op=notifications&open=&_=$unixTimeMS", referer = href) | ||||
|                     val hrefDocument = Jsoup.parse(hrefResponse) | ||||
|                     val hrefSpan = hrefDocument.selectFirst("span > a") | ||||
|                     if (hrefSpan == null) { | ||||
|                         getPostForm(href, hrefResponse)?.let { form -> | ||||
|                             val postDocument = Jsoup.parse(form) | ||||
|                             val downloadBtn = postDocument.selectFirst("a.downloadbtn")?.attr("href") | ||||
|                             if (downloadBtn.isNullOrEmpty()) { | ||||
|                                 val hrefSpan2 = postDocument.selectFirst("span > a")?.attr("href") | ||||
|                                 if (hrefSpan2?.startsWith("https://") == true) { | ||||
|                                     links.add( | ||||
|                                         ExtractorLink( | ||||
|                                             this.name, name, | ||||
|                                             hrefSpan2, "", Qualities.Unknown.value, false | ||||
|                                         ) | ||||
|                                     ) | ||||
|                                 } else { | ||||
|                                     // no link found!!! | ||||
|                                 } | ||||
|                             } else { | ||||
|                                 links.add( | ||||
|                                     ExtractorLink( | ||||
|                                         this.name, | ||||
|                                         name, | ||||
|                                         downloadBtn, | ||||
|                                         "", | ||||
|                                         Qualities.Unknown.value, | ||||
|                                         false | ||||
|                                     ) | ||||
|                                 ) | ||||
|                             } | ||||
|                         } | ||||
|                     } else { | ||||
|                         val link = hrefSpan.attr("href") | ||||
|                         links.add(ExtractorLink(this.name, name, link, "", Qualities.Unknown.value, false)) | ||||
|                     } | ||||
|                 } | ||||
|             } catch (e: Exception) { | ||||
|                 logError(e) | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return links | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,45 @@ | |||
| package com.lagradost.cloudstream3.extractors | ||||
| 
 | ||||
| import com.fasterxml.jackson.annotation.JsonProperty | ||||
| import com.lagradost.cloudstream3.app | ||||
| import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson | ||||
| import com.lagradost.cloudstream3.utils.ExtractorApi | ||||
| import com.lagradost.cloudstream3.utils.ExtractorLink | ||||
| import com.lagradost.cloudstream3.utils.getQualityFromName | ||||
| 
 | ||||
| 
 | ||||
| class Solidfiles : ExtractorApi() { | ||||
|     override val name = "Solidfiles" | ||||
|     override val mainUrl = "https://www.solidfiles.com" | ||||
|     override val requiresReferer = false | ||||
| 
 | ||||
|     override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> { | ||||
|         val sources = mutableListOf<ExtractorLink>() | ||||
|         with(app.get(url).document) { | ||||
|             this.select("script").map { script -> | ||||
|                 if (script.data().contains("\"streamUrl\":")) { | ||||
|                     val data = script.data().substringAfter("constant('viewerOptions', {").substringBefore("});") | ||||
|                     val source = tryParseJson<ResponseSource>("{$data}") | ||||
|                     val quality = Regex("\\d{3,4}p").find(source!!.nodeName)?.groupValues?.get(0) | ||||
|                     sources.add( | ||||
|                         ExtractorLink( | ||||
|                             name, | ||||
|                             name, | ||||
|                             source.streamUrl, | ||||
|                             referer = url, | ||||
|                             quality = getQualityFromName(quality) | ||||
|                         ) | ||||
|                     ) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return sources | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     private data class ResponseSource( | ||||
|         @JsonProperty("streamUrl") val streamUrl: String, | ||||
|         @JsonProperty("nodeName") val nodeName: String | ||||
|     ) | ||||
| 
 | ||||
| } | ||||
|  | @ -0,0 +1,38 @@ | |||
| package com.lagradost.cloudstream3.extractors | ||||
| 
 | ||||
| import com.fasterxml.jackson.annotation.JsonProperty | ||||
| import com.lagradost.cloudstream3.app | ||||
| import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson | ||||
| import com.lagradost.cloudstream3.utils.ExtractorApi | ||||
| import com.lagradost.cloudstream3.utils.ExtractorLink | ||||
| import com.lagradost.cloudstream3.utils.M3u8Helper | ||||
| 
 | ||||
| class SpeedoStream : ExtractorApi() { | ||||
|     override val name = "SpeedoStream" | ||||
|     override val mainUrl = "https://speedostream.com" | ||||
|     override val requiresReferer = true | ||||
| 
 | ||||
|     override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> { | ||||
|         val sources = mutableListOf<ExtractorLink>() | ||||
|         app.get(url, referer = referer).document.select("script").map { script -> | ||||
|             if (script.data().contains("jwplayer(\"vplayer\").setup(")) { | ||||
|                 val data = script.data().substringAfter("sources: [") | ||||
|                     .substringBefore("],").replace("file", "\"file\"").trim() | ||||
|                 tryParseJson<File>(data)?.let { | ||||
|                     M3u8Helper.generateM3u8( | ||||
|                         name, | ||||
|                         it.file, | ||||
|                         "$mainUrl/", | ||||
|                     ).forEach { m3uData -> sources.add(m3uData) } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return sources | ||||
|     } | ||||
| 
 | ||||
|     private data class File( | ||||
|         @JsonProperty("file") val file: String, | ||||
|     ) | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
|  | @ -0,0 +1,126 @@ | |||
| package com.lagradost.cloudstream3.extractors | ||||
| 
 | ||||
| import com.fasterxml.jackson.annotation.JsonProperty | ||||
| import com.lagradost.cloudstream3.app | ||||
| import com.lagradost.cloudstream3.utils.AppUtils.parseJson | ||||
| import com.lagradost.cloudstream3.utils.ExtractorApi | ||||
| import com.lagradost.cloudstream3.utils.ExtractorLink | ||||
| import com.lagradost.cloudstream3.utils.M3u8Helper | ||||
| 
 | ||||
| class Ssbstream : StreamSB() { | ||||
|     override var mainUrl = "https://ssbstream.net" | ||||
| } | ||||
| 
 | ||||
| class SBfull : StreamSB() { | ||||
|     override var mainUrl = "https://sbfull.com" | ||||
| } | ||||
| 
 | ||||
| class StreamSB1 : StreamSB() { | ||||
|     override var mainUrl = "https://sbplay1.com" | ||||
| } | ||||
| 
 | ||||
| class StreamSB2 : StreamSB() { | ||||
|     override var mainUrl = "https://sbplay2.com" | ||||
| } | ||||
| 
 | ||||
| class StreamSB3 : StreamSB() { | ||||
|     override var mainUrl = "https://sbplay3.com" | ||||
| } | ||||
| 
 | ||||
| class StreamSB4 : StreamSB() { | ||||
|     override var mainUrl = "https://cloudemb.com" | ||||
| } | ||||
| 
 | ||||
| class StreamSB5 : StreamSB() { | ||||
|     override var mainUrl = "https://sbplay.org" | ||||
| } | ||||
| 
 | ||||
| class StreamSB6 : StreamSB() { | ||||
|     override var mainUrl = "https://embedsb.com" | ||||
| } | ||||
| 
 | ||||
| class StreamSB7 : StreamSB() { | ||||
|     override var mainUrl = "https://pelistop.co" | ||||
| } | ||||
| 
 | ||||
| class StreamSB8 : StreamSB() { | ||||
|     override var mainUrl = "https://streamsb.net" | ||||
| } | ||||
| 
 | ||||
| class StreamSB9 : StreamSB() { | ||||
|     override var mainUrl = "https://sbplay.one" | ||||
| } | ||||
| 
 | ||||
| class StreamSB10 : StreamSB() { | ||||
|     override var mainUrl = "https://sbplay2.xyz" | ||||
| } | ||||
| 
 | ||||
| // This is a modified version of https://github.com/jmir1/aniyomi-extensions/blob/master/src/en/genoanime/src/eu/kanade/tachiyomi/animeextension/en/genoanime/extractors/StreamSBExtractor.kt | ||||
| // The following code is under the Apache License 2.0 https://github.com/jmir1/aniyomi-extensions/blob/master/LICENSE | ||||
| open class StreamSB : ExtractorApi() { | ||||
|     override var name = "StreamSB" | ||||
|     override var mainUrl = "https://watchsb.com" | ||||
|     override val requiresReferer = false | ||||
| 
 | ||||
|     private val hexArray = "0123456789ABCDEF".toCharArray() | ||||
| 
 | ||||
|     private fun bytesToHex(bytes: ByteArray): String { | ||||
|         val hexChars = CharArray(bytes.size * 2) | ||||
|         for (j in bytes.indices) { | ||||
|             val v = bytes[j].toInt() and 0xFF | ||||
| 
 | ||||
|             hexChars[j * 2] = hexArray[v ushr 4] | ||||
|             hexChars[j * 2 + 1] = hexArray[v and 0x0F] | ||||
|         } | ||||
|         return String(hexChars) | ||||
|     } | ||||
| 
 | ||||
|     data class Subs ( | ||||
|         @JsonProperty("file") val file: String, | ||||
|         @JsonProperty("label") val label: String, | ||||
|     ) | ||||
| 
 | ||||
|     data class StreamData ( | ||||
|         @JsonProperty("file") val file: String, | ||||
|         @JsonProperty("cdn_img") val cdnImg: String, | ||||
|         @JsonProperty("hash") val hash: String, | ||||
|         @JsonProperty("subs") val subs: List<Subs>?, | ||||
|         @JsonProperty("length") val length: String, | ||||
|         @JsonProperty("id") val id: String, | ||||
|         @JsonProperty("title") val title: String, | ||||
|         @JsonProperty("backup") val backup: String, | ||||
|     ) | ||||
| 
 | ||||
|     data class Main ( | ||||
|         @JsonProperty("stream_data") val streamData: StreamData, | ||||
|         @JsonProperty("status_code") val statusCode: Int, | ||||
|     ) | ||||
| 
 | ||||
|     override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? { | ||||
|         val regexID = Regex("(embed-[a-zA-Z0-9]{0,8}[a-zA-Z0-9_-]+|\\/e\\/[a-zA-Z0-9]{0,8}[a-zA-Z0-9_-]+)") | ||||
|         val id = regexID.findAll(url).map { | ||||
|             it.value.replace(Regex("(embed-|\\/e\\/)"),"") | ||||
|         }.first() | ||||
|         val bytes = id.toByteArray() | ||||
|         val bytesToHex = bytesToHex(bytes) | ||||
|         val master = "$mainUrl/sources43/6d6144797752744a454267617c7c${bytesToHex.lowercase()}7c7c4e61755a56456f34385243727c7c73747265616d7362/6b4a33767968506e4e71374f7c7c343837323439333133333462353935333633373836643638376337633462333634663539343137373761333635313533333835333763376333393636363133393635366136323733343435323332376137633763373337343732363536313664373336327c7c504d754478413835306633797c7c73747265616d7362" | ||||
|         val headers = mapOf( | ||||
|             "watchsb" to "streamsb", | ||||
|             ) | ||||
|         val urltext = app.get(master, | ||||
|             headers = headers, | ||||
|             allowRedirects = false | ||||
|         ).text | ||||
|         val mapped = urltext.let { parseJson<Main>(it) } | ||||
|         val testurl = app.get(mapped.streamData.file, headers = headers).text | ||||
|         // val urlmain = mapped.streamData.file.substringBefore("/hls/") | ||||
|         if (urltext.contains("m3u8") && testurl.contains("EXTM3U")) | ||||
|             return M3u8Helper.generateM3u8( | ||||
|                 name, | ||||
|                 mapped.streamData.file, | ||||
|                 url, | ||||
|                 headers = headers | ||||
|             ) | ||||
|         return null | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,33 @@ | |||
| package com.lagradost.cloudstream3.extractors | ||||
| 
 | ||||
| import com.lagradost.cloudstream3.app | ||||
| import com.lagradost.cloudstream3.utils.ExtractorApi | ||||
| import com.lagradost.cloudstream3.utils.ExtractorLink | ||||
| import com.lagradost.cloudstream3.utils.Qualities | ||||
| 
 | ||||
| class StreamTape : ExtractorApi() { | ||||
|     override var name = "StreamTape" | ||||
|     override var mainUrl = "https://streamtape.com" | ||||
|     override val requiresReferer = false | ||||
| 
 | ||||
|     private val linkRegex = | ||||
|         Regex("""'robotlink'\)\.innerHTML = '(.+?)'\+ \('(.+?)'\)""") | ||||
| 
 | ||||
|     override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? { | ||||
|         with(app.get(url)) { | ||||
|             linkRegex.find(this.text)?.let { | ||||
|                 val extractedUrl = "https:${it.groups[1]!!.value + it.groups[2]!!.value.substring(3,)}" | ||||
|                 return listOf( | ||||
|                     ExtractorLink( | ||||
|                         name, | ||||
|                         name, | ||||
|                         extractedUrl, | ||||
|                         url, | ||||
|                         Qualities.Unknown.value, | ||||
|                     ) | ||||
|                 ) | ||||
|             } | ||||
|         } | ||||
|         return null | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,39 @@ | |||
| package com.lagradost.cloudstream3.extractors | ||||
| 
 | ||||
| import com.lagradost.cloudstream3.app | ||||
| import com.lagradost.cloudstream3.utils.ExtractorApi | ||||
| import com.lagradost.cloudstream3.utils.ExtractorLink | ||||
| import com.lagradost.cloudstream3.utils.JsUnpacker | ||||
| import com.lagradost.cloudstream3.utils.Qualities | ||||
| import java.net.URI | ||||
| 
 | ||||
| class Streamhub : ExtractorApi() { | ||||
|     override var mainUrl = "https://streamhub.to" | ||||
|     override var name = "Streamhub" | ||||
|     override val requiresReferer = false | ||||
| 
 | ||||
|     override fun getExtractorUrl(id: String): String { | ||||
|         return "$mainUrl/e/$id" | ||||
|     } | ||||
| 
 | ||||
|     override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? { | ||||
|         val response = app.get(url).text | ||||
|         Regex("eval((.|\\n)*?)</script>").find(response)?.groupValues?.get(1)?.let { jsEval -> | ||||
|             JsUnpacker("eval$jsEval").unpack()?.let { unPacked -> | ||||
|                 Regex("sources:\\[\\{src:\"(.*?)\"").find(unPacked)?.groupValues?.get(1)?.let { link -> | ||||
|                     return listOf( | ||||
|                         ExtractorLink( | ||||
|                             this.name, | ||||
|                             this.name, | ||||
|                             link, | ||||
|                             referer ?: "", | ||||
|                             Qualities.Unknown.value, | ||||
|                             URI(link).path.endsWith(".m3u8") | ||||
|                         ) | ||||
|                     ) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return null | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,60 @@ | |||
| package com.lagradost.cloudstream3.extractors | ||||
| 
 | ||||
| import com.fasterxml.jackson.annotation.JsonProperty | ||||
| import com.lagradost.cloudstream3.app | ||||
| import com.lagradost.cloudstream3.utils.ExtractorApi | ||||
| import com.lagradost.cloudstream3.utils.ExtractorLink | ||||
| import com.lagradost.cloudstream3.utils.Qualities | ||||
| import com.lagradost.nicehttp.RequestBodyTypes | ||||
| import okhttp3.MediaType.Companion.toMediaTypeOrNull | ||||
| import okhttp3.RequestBody.Companion.toRequestBody | ||||
| 
 | ||||
| 
 | ||||
| class Streamlare : Slmaxed() { | ||||
|     override val mainUrl = "https://streamlare.com/" | ||||
| } | ||||
| 
 | ||||
| open class Slmaxed : ExtractorApi() { | ||||
|     override val name = "Streamlare" | ||||
|     override val mainUrl = "https://slmaxed.com/" | ||||
|     override val requiresReferer = true | ||||
| 
 | ||||
|     // https://slmaxed.com/e/oLvgezw3LjPzbp8E -> oLvgezw3LjPzbp8E | ||||
|     val embedRegex = Regex("""/e/([^/]*)""") | ||||
| 
 | ||||
| 
 | ||||
|     data class JsonResponse( | ||||
|         @JsonProperty val status: String? = null, | ||||
|         @JsonProperty val message: String? = null, | ||||
|         @JsonProperty val type: String? = null, | ||||
|         @JsonProperty val token: String? = null, | ||||
|         @JsonProperty val result: Map<String, Result>? = null | ||||
|     ) | ||||
| 
 | ||||
|     data class Result( | ||||
|         @JsonProperty val label: String? = null, | ||||
|         @JsonProperty val file: String? = null, | ||||
|         @JsonProperty val type: String? = null | ||||
|     ) | ||||
| 
 | ||||
|     override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? { | ||||
|         val id = embedRegex.find(url)!!.groupValues[1] | ||||
|         val json = app.post( | ||||
|             "${mainUrl}api/video/stream/get", | ||||
|             requestBody = """{"id":"$id"}""".toRequestBody(RequestBodyTypes.JSON.toMediaTypeOrNull()) | ||||
|         ).parsed<JsonResponse>() | ||||
|         return json.result?.mapNotNull { | ||||
|             it.value.let { result -> | ||||
|                 ExtractorLink( | ||||
|                     this.name, | ||||
|                     this.name, | ||||
|                     result.file ?: return@mapNotNull null, | ||||
|                     url, | ||||
|                     result.label?.replace("p", "", ignoreCase = true)?.trim()?.toIntOrNull() | ||||
|                         ?: Qualities.Unknown.value, | ||||
|                     isM3u8 = result.type?.contains("hls", ignoreCase = true) == true | ||||
|                 ) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,41 @@ | |||
| package com.lagradost.cloudstream3.extractors | ||||
| 
 | ||||
| import com.fasterxml.jackson.annotation.JsonProperty | ||||
| import com.lagradost.cloudstream3.utils.ExtractorLink | ||||
| import com.lagradost.cloudstream3.app | ||||
| import com.lagradost.cloudstream3.utils.* | ||||
| import com.lagradost.cloudstream3.utils.AppUtils.parseJson | ||||
| 
 | ||||
| data class Files( | ||||
|     @JsonProperty("file") val id: String, | ||||
|     @JsonProperty("label") val label: String? = null, | ||||
| ) | ||||
| 
 | ||||
|     open class Supervideo : ExtractorApi() { | ||||
|     override var name = "Supervideo" | ||||
|     override var mainUrl = "https://supervideo.tv" | ||||
|     override val requiresReferer = false | ||||
|     override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? { | ||||
|         val extractedLinksList: MutableList<ExtractorLink> = mutableListOf() | ||||
|         val response = app.get(url).text | ||||
|         val jstounpack = Regex("eval((.|\\n)*?)</script>").find(response)?.groups?.get(1)?.value | ||||
|         val unpacjed = JsUnpacker(jstounpack).unpack() | ||||
|         val extractedUrl = unpacjed?.let { Regex("""sources:((.|\n)*?)image""").find(it) }?.groups?.get(1)?.value.toString().replace("file",""""file"""").replace("label",""""label"""").substringBeforeLast(",") | ||||
|         val parsedlinks = parseJson<List<Files>>(extractedUrl) | ||||
|         parsedlinks.forEach { data -> | ||||
|             if (data.label.isNullOrBlank()){ // mp4 links (with labels) are slow. Use only m3u8 link. | ||||
|                 M3u8Helper.generateM3u8( | ||||
|                     name, | ||||
|                     data.id, | ||||
|                     url, | ||||
|                     headers = mapOf("referer" to url) | ||||
|                 ).forEach { link -> | ||||
|                     extractedLinksList.add(link) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         return extractedLinksList | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,42 @@ | |||
| package com.lagradost.cloudstream3.extractors | ||||
| 
 | ||||
| import com.fasterxml.jackson.annotation.JsonProperty | ||||
| import com.lagradost.cloudstream3.app | ||||
| import com.lagradost.cloudstream3.utils.ExtractorApi | ||||
| import com.lagradost.cloudstream3.utils.AppUtils.parseJson | ||||
| import com.lagradost.cloudstream3.utils.ExtractorLink | ||||
| 
 | ||||
| open class Tantifilm : ExtractorApi() { | ||||
|     override var name = "Tantifilm" | ||||
|     override var mainUrl = "https://cercafilm.net" | ||||
|     override val requiresReferer = false | ||||
| 
 | ||||
|     data class TantifilmJsonData ( | ||||
|         @JsonProperty("success") val success : Boolean, | ||||
|         @JsonProperty("data") val data : List<TantifilmData>, | ||||
|         @JsonProperty("captions")val captions : List<String>, | ||||
|         @JsonProperty("is_vr") val is_vr : Boolean | ||||
|     ) | ||||
| 
 | ||||
|     data class TantifilmData ( | ||||
|         @JsonProperty("file") val file : String, | ||||
|         @JsonProperty("label") val label : String, | ||||
|         @JsonProperty("type") val type : String | ||||
|     ) | ||||
| 
 | ||||
|     override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? { | ||||
|         val link = "$mainUrl/api/source/${url.substringAfterLast("/")}" | ||||
|         val response = app.post(link).text.replace("""\""","") | ||||
|         val jsonvideodata = parseJson<TantifilmJsonData>(response) | ||||
|         return jsonvideodata.data.map { | ||||
|             ExtractorLink( | ||||
|                 it.file+".${it.type}", | ||||
|                 this.name, | ||||
|                 it.file+".${it.type}", | ||||
|                 mainUrl, | ||||
|                 it.label.filter{ it.isDigit() }.toInt(), | ||||
|                 false | ||||
|             ) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,41 @@ | |||
| package com.lagradost.cloudstream3.extractors | ||||
| 
 | ||||
| import com.fasterxml.jackson.annotation.JsonProperty | ||||
| import com.lagradost.cloudstream3.app | ||||
| import com.lagradost.cloudstream3.utils.AppUtils.parseJson | ||||
| import com.lagradost.cloudstream3.utils.ExtractorApi | ||||
| import com.lagradost.cloudstream3.utils.ExtractorLink | ||||
| import com.lagradost.cloudstream3.utils.Qualities | ||||
| 
 | ||||
| class Cinestart: Tomatomatela() { | ||||
|     override var name = "Cinestart" | ||||
|     override var mainUrl = "https://cinestart.net" | ||||
|     override val details = "vr.php?v=" | ||||
| } | ||||
| 
 | ||||
| open class Tomatomatela : ExtractorApi() { | ||||
|     override var name = "Tomatomatela" | ||||
|     override var mainUrl = "https://tomatomatela.com" | ||||
|     override val requiresReferer = false | ||||
|     private data class Tomato ( | ||||
|         @JsonProperty("status") val status: Int, | ||||
|         @JsonProperty("file") val file: String | ||||
|     ) | ||||
|     open val details = "details.php?v=" | ||||
|     override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? { | ||||
|         val link = url.replace("$mainUrl/embed.html#","$mainUrl/$details") | ||||
|         val server = app.get(link, allowRedirects = false).text | ||||
|         val json = parseJson<Tomato>(server) | ||||
|         if (json.status == 200) return listOf( | ||||
|             ExtractorLink( | ||||
|                 name, | ||||
|                 name, | ||||
|                 json.file, | ||||
|                 "", | ||||
|                 Qualities.Unknown.value, | ||||
|                 isM3u8 = false | ||||
|             ) | ||||
|         ) | ||||
|         return null | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,59 @@ | |||
| package com.lagradost.cloudstream3.extractors | ||||
| 
 | ||||
| import com.lagradost.cloudstream3.app | ||||
| import com.lagradost.cloudstream3.utils.ExtractorApi | ||||
| import com.lagradost.cloudstream3.utils.ExtractorLink | ||||
| import com.lagradost.cloudstream3.utils.Qualities | ||||
| 
 | ||||
| class UpstreamExtractor: ExtractorApi() { | ||||
|     override val name: String = "Upstream.to" | ||||
|     override val mainUrl: String = "https://upstream.to" | ||||
|     override val requiresReferer = true | ||||
| 
 | ||||
|     override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> { | ||||
|         // WIP: m3u8 link fetched but sometimes not playing | ||||
|         //Log.i(this.name, "Result => (no extractor) ${url}") | ||||
|         val sources: MutableList<ExtractorLink> = mutableListOf() | ||||
|         val doc = app.get(url, referer = referer).text | ||||
|         if (doc.isNotBlank()) { | ||||
|             var reg = Regex("(?<=master)(.*)(?=hls)") | ||||
|             val result = reg.find(doc)?.groupValues?.map { | ||||
|                 it.trim('|') | ||||
|             }?.toList() | ||||
|             reg = Regex("(?<=\\|file\\|)(.*)(?=\\|remove\\|)") | ||||
|             val domainList = reg.find(doc)?.groupValues?.get(1)?.split("|") | ||||
|             var domain = when (!domainList.isNullOrEmpty()) { | ||||
|                 true -> { | ||||
|                     if (domainList.isNotEmpty()) { | ||||
|                         var domName = "" | ||||
|                         for (part in domainList) { | ||||
|                             domName = "${part}.${domName}" | ||||
|                         } | ||||
|                         domName.trimEnd('.') | ||||
|                     } else { "" } | ||||
|                 } | ||||
|                 false -> "" | ||||
|             } | ||||
|             //Log.i(this.name, "Result => (domain) ${domain}") | ||||
|             if (domain.isEmpty()) { | ||||
|                 domain = "s96.upstreamcdn.co" | ||||
|                 //Log.i(this.name, "Result => (default domain) ${domain}") | ||||
|             } | ||||
| 
 | ||||
|             result?.forEach { | ||||
|                 val linkUrl = "https://${domain}/hls/${it}/master.m3u8" | ||||
|                 sources.add( | ||||
|                     ExtractorLink( | ||||
|                     name = "Upstream m3u8", | ||||
|                     source = this.name, | ||||
|                     url = linkUrl, | ||||
|                     quality = Qualities.Unknown.value, | ||||
|                     referer = referer ?: linkUrl, | ||||
|                     isM3u8 = true | ||||
|                     ) | ||||
|                 ) | ||||
|             } | ||||
|         } | ||||
|         return sources | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,49 @@ | |||
| package com.lagradost.cloudstream3.extractors | ||||
| 
 | ||||
| import com.lagradost.cloudstream3.utils.* | ||||
| import com.lagradost.cloudstream3.app | ||||
| 
 | ||||
| class Uqload1 : Uqload() { | ||||
|     override var mainUrl = "https://uqload.com" | ||||
| } | ||||
| 
 | ||||
| open class Uqload : ExtractorApi() { | ||||
|     override val name: String = "Uqload" | ||||
|     override val mainUrl: String = "https://www.uqload.com" | ||||
|     private val srcRegex = Regex("""sources:.\[(.*?)\]""")  // would be possible to use the parse and find src attribute | ||||
|     override val requiresReferer = true | ||||
| 
 | ||||
| 
 | ||||
|     override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? { | ||||
|         val lang = url.substring(0, 2) | ||||
|         val flag = | ||||
|             if (lang == "vo") { | ||||
|                 " \uD83C\uDDEC\uD83C\uDDE7" | ||||
|             } | ||||
|             else if (lang == "vf"){ | ||||
|                 " \uD83C\uDDE8\uD83C\uDDF5" | ||||
|             } else { | ||||
|                 "" | ||||
|             } | ||||
|          | ||||
|         val cleaned_url = if (lang == "ht") {  // if url doesn't contain a flag and the url starts with http:// | ||||
|             url | ||||
|         } else { | ||||
|             url.substring(2, url.length) | ||||
|         } | ||||
|         with(app.get(cleaned_url)) {  // raised error ERROR_CODE_PARSING_CONTAINER_UNSUPPORTED (3003) is due to the response: "error_nofile" | ||||
|             srcRegex.find(this.text)?.groupValues?.get(1)?.replace("\"", "")?.let { link -> | ||||
|                 return listOf( | ||||
|                     ExtractorLink( | ||||
|                         name, | ||||
|                         name + flag, | ||||
|                         link, | ||||
|                         cleaned_url, | ||||
|                         Qualities.Unknown.value, | ||||
|                     ) | ||||
|                 ) | ||||
|             } | ||||
|         } | ||||
|         return null | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,117 @@ | |||
| package com.lagradost.cloudstream3.extractors | ||||
| 
 | ||||
| import com.lagradost.cloudstream3.utils.ExtractorLink | ||||
| import com.lagradost.cloudstream3.app | ||||
| import com.lagradost.cloudstream3.utils.* | ||||
| import org.mozilla.javascript.Context | ||||
| import org.mozilla.javascript.EvaluatorException | ||||
| import org.mozilla.javascript.Scriptable | ||||
| import java.util.* | ||||
| 
 | ||||
| 
 | ||||
| open class Userload : ExtractorApi() { | ||||
|     override var name = "Userload" | ||||
|     override var mainUrl = "https://userload.co" | ||||
|     override val requiresReferer = false | ||||
| 
 | ||||
|     private fun splitInput(input: String): List<String> { | ||||
|         var counter = 0 | ||||
|         val array = ArrayList<String>() | ||||
|         var buffer = "" | ||||
|         for (c in input) { | ||||
|             when (c) { | ||||
|                 '(' -> counter++ | ||||
|                 ')' -> counter-- | ||||
|                 else -> {} | ||||
|             } | ||||
|             buffer += c | ||||
|             if (counter == 0) { | ||||
|                 if (buffer.isNotBlank() && buffer != "+") | ||||
|                     array.add(buffer) | ||||
|                 buffer = "" | ||||
|             } | ||||
|         } | ||||
|         return array | ||||
|     } | ||||
| 
 | ||||
|     private fun evaluateMath(mathExpression : String): String { | ||||
|         val rhino = Context.enter() | ||||
|         rhino.initStandardObjects() | ||||
|         rhino.optimizationLevel = -1 | ||||
|         val scope: Scriptable = rhino.initStandardObjects() | ||||
|         return try { | ||||
|             rhino.evaluateString(scope, "eval($mathExpression)", "JavaScript", 1, null).toString() | ||||
|         } | ||||
|         catch (e: EvaluatorException){ | ||||
|             "" | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private fun decodeVideoJs(text: String): List<String> { | ||||
|         text.replace("""\s+|/\*.*?\*/""".toRegex(), "") | ||||
|         val data = text.split("""+(゚Д゚)[゚o゚]""")[1] | ||||
|         val chars = data.split("""+ (゚Д゚)[゚ε゚]+""").drop(1) | ||||
|         val newchars = chars.map { char -> | ||||
|             char.replace("(o゚ー゚o)", "u") | ||||
|                 .replace("c", "0") | ||||
|                 .replace("(゚Д゚)['0']", "c") | ||||
|                 .replace("゚Θ゚", "1") | ||||
|                 .replace("!+[]", "1") | ||||
|                 .replace("-~", "1+") | ||||
|                 .replace("o", "3") | ||||
|                 .replace("_", "3") | ||||
|                 .replace("゚ー゚", "4") | ||||
|                 .replace("(+", "(") | ||||
|         } | ||||
| 
 | ||||
|         val subchar = mutableListOf<String>() | ||||
| 
 | ||||
|         newchars.dropLast(1).forEach { v -> | ||||
|             subchar.add(splitInput(v).map { evaluateMath(it).substringBefore(".") }.toString().filter { it.isDigit() }) | ||||
|         } | ||||
|         var txtresult = "" | ||||
|         subchar.forEach{ | ||||
|             txtresult = txtresult.plus(Char(it.toInt(8))) | ||||
|         } | ||||
|         val val1 = Regex(""""morocco="((.|\\n)*?)"&mycountry="""").find(txtresult)?.groups?.get(1)?.value.toString().drop(1).dropLast(1) | ||||
|         val val2 = txtresult.substringAfter("""&mycountry="+""").substringBefore(")") | ||||
| 
 | ||||
|         return listOf( | ||||
|             val1, | ||||
|             val2 | ||||
|         ) | ||||
| 
 | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? { | ||||
| 
 | ||||
|         val extractedLinksList: MutableList<ExtractorLink> = mutableListOf() | ||||
| 
 | ||||
|         val response = app.get(url).text | ||||
|         val jsToUnpack = Regex("ext/javascript\">eval((.|\\n)*?)</script>").find(response)?.groups?.get(1)?.value | ||||
|         val unpacked = JsUnpacker(jsToUnpack).unpack() | ||||
|         val videoJs = app.get("$mainUrl/api/assets/userload/js/videojs.js") | ||||
|         val videoJsToDecode = videoJs.text | ||||
|         val values = decodeVideoJs(videoJsToDecode) | ||||
|         val morocco = unpacked!!.split(";").filter { it.contains(values[0]) }[0].split("=")[1].drop(1).dropLast(1) | ||||
|         val mycountry = unpacked.split(";").filter { it.contains(values[1]) }[0].split("=")[1].drop(1).dropLast(1) | ||||
|         val videoLinkPage = app.post("$mainUrl/api/request/", data = mapOf( | ||||
|             "morocco" to morocco, | ||||
|             "mycountry" to mycountry | ||||
|         )) | ||||
|         val videoLink = videoLinkPage.text | ||||
|         val nameSource = app.get(url).document.head().selectFirst("title")!!.text() | ||||
|         extractedLinksList.add( | ||||
|             ExtractorLink( | ||||
|                 name, | ||||
|                 name, | ||||
|                 videoLink, | ||||
|                 mainUrl, | ||||
|                 getQualityFromName(nameSource), | ||||
|             ) | ||||
|         ) | ||||
| 
 | ||||
|         return extractedLinksList | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,68 @@ | |||
| package com.lagradost.cloudstream3.extractors | ||||
| 
 | ||||
| import com.lagradost.cloudstream3.SubtitleFile | ||||
| import com.lagradost.cloudstream3.apmap | ||||
| import com.lagradost.cloudstream3.app | ||||
| import com.lagradost.cloudstream3.utils.ExtractorApi | ||||
| import com.lagradost.cloudstream3.utils.ExtractorLink | ||||
| import com.lagradost.cloudstream3.utils.M3u8Helper | ||||
| import com.lagradost.cloudstream3.utils.loadExtractor | ||||
| 
 | ||||
| class VidSrcExtractor2 : VidSrcExtractor() { | ||||
|     override val mainUrl = "https://vidsrc.me/embed" | ||||
|     override suspend fun getUrl( | ||||
|         url: String, | ||||
|         referer: String?, | ||||
|         subtitleCallback: (SubtitleFile) -> Unit, | ||||
|         callback: (ExtractorLink) -> Unit | ||||
|     ) { | ||||
|         val newUrl = url.lowercase().replace(mainUrl, super.mainUrl) | ||||
|         super.getUrl(newUrl, referer, subtitleCallback, callback) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| open class VidSrcExtractor : ExtractorApi() { | ||||
|     override val name = "VidSrc" | ||||
|     private val absoluteUrl = "https://v2.vidsrc.me" | ||||
|     override val mainUrl = "$absoluteUrl/embed" | ||||
|     override val requiresReferer = false | ||||
| 
 | ||||
|     override suspend fun getUrl( | ||||
|         url: String, | ||||
|         referer: String?, | ||||
|         subtitleCallback: (SubtitleFile) -> Unit, | ||||
|         callback: (ExtractorLink) -> Unit | ||||
|     ) { | ||||
|         val iframedoc = app.get(url).document | ||||
| 
 | ||||
|         val serverslist = | ||||
|             iframedoc.select("div#sources.button_content div#content div#list div").map { | ||||
|                 val datahash = it.attr("data-hash") | ||||
|                 if (datahash.isNotBlank()) { | ||||
|                     val links = try { | ||||
|                         app.get("$absoluteUrl/src/$datahash", referer = "https://source.vidsrc.me/").url | ||||
|                     } catch (e: Exception) { | ||||
|                         "" | ||||
|                     } | ||||
|                     links | ||||
|                 } else "" | ||||
|             } | ||||
| 
 | ||||
|         serverslist.apmap { server -> | ||||
|             val linkfixed = server.replace("https://vidsrc.xyz/", "https://embedsito.com/") | ||||
|             if (linkfixed.contains("/pro")) { | ||||
|                 val srcresponse = app.get(server, referer = absoluteUrl).text | ||||
|                 val m3u8Regex = Regex("((https:|http:)//.*\\.m3u8)") | ||||
|                 val srcm3u8 = m3u8Regex.find(srcresponse)?.value ?: return@apmap | ||||
|                 M3u8Helper.generateM3u8( | ||||
|                     name, | ||||
|                     srcm3u8, | ||||
|                     absoluteUrl | ||||
|                 ).forEach(callback) | ||||
|             } else { | ||||
|                 loadExtractor(linkfixed, url, subtitleCallback, callback) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -0,0 +1,271 @@ | |||
| package com.lagradost.cloudstream3.extractors | ||||
| 
 | ||||
| import com.lagradost.cloudstream3.app | ||||
| import com.lagradost.cloudstream3.utils.ExtractorApi | ||||
| import com.lagradost.cloudstream3.utils.ExtractorLink | ||||
| import com.lagradost.cloudstream3.utils.M3u8Helper.Companion.generateM3u8 | ||||
| import kotlinx.coroutines.delay | ||||
| import java.math.BigInteger | ||||
| 
 | ||||
| class VideovardSX : WcoStream() { | ||||
|     override var mainUrl = "https://videovard.sx" | ||||
| } | ||||
| 
 | ||||
| class VideoVard : ExtractorApi() { | ||||
|     override var name = "Videovard" // Cause works for animekisa and wco | ||||
|     override var mainUrl = "https://videovard.to" | ||||
|     override val requiresReferer = false | ||||
| 
 | ||||
|     //The following code was extracted from https://github.com/saikou-app/saikou/blob/main/app/src/main/java/ani/saikou/parsers/anime/extractors/VideoVard.kt | ||||
|     override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> { | ||||
|         val id = url.substringAfter("e/").substringBefore("/") | ||||
|         val sources = mutableListOf<ExtractorLink>() | ||||
|         val hash = app.get("$mainUrl/api/make/download/$id").parsed<HashResponse>() | ||||
|         delay(11_000) | ||||
|         val resm3u8 = app.post( | ||||
|             "$mainUrl/api/player/setup", | ||||
|             mapOf("Referer" to "$mainUrl/"), | ||||
|             data = mapOf( | ||||
|                 "cmd" to "get_stream", | ||||
|                 "file_code" to id, | ||||
|                 "hash" to hash.hash!! | ||||
|             ) | ||||
|         ).parsed<SetupResponse>() | ||||
|         val m3u8 = decode(resm3u8.src!!, resm3u8.seed) | ||||
|         sources.addAll( | ||||
|             generateM3u8( | ||||
|                 name, | ||||
|                 m3u8, | ||||
|                 mainUrl, | ||||
|                 headers = mapOf("Referer" to mainUrl) | ||||
|             ) | ||||
|         ) | ||||
|         return sources | ||||
|     } | ||||
| 
 | ||||
|     companion object { | ||||
|         private val big0 = 0.toBigInteger() | ||||
|         private val big3 = 3.toBigInteger() | ||||
|         private val big4 = 4.toBigInteger() | ||||
|         private val big15 = 15.toBigInteger() | ||||
|         private val big16 = 16.toBigInteger() | ||||
|         private val big255 = 255.toBigInteger() | ||||
| 
 | ||||
|         private fun decode(dataFile: String, seed: String): String { | ||||
|             val dataSeed = replace(seed) | ||||
|             val newDataSeed = binaryDigest(dataSeed) | ||||
|             val newDataFile = bytes2blocks(ascii2bytes(dataFile)) | ||||
|             var list = listOf(1633837924, 1650680933).map { it.toBigInteger() } | ||||
|             val xorList = mutableListOf<BigInteger>() | ||||
|             for (i in newDataFile.indices step 2) { | ||||
|                 val temp = newDataFile.slice(i..i + 1) | ||||
|                 xorList += xorBlocks(list, tearDecode(temp, newDataSeed)) | ||||
|                 list = temp | ||||
|             } | ||||
| 
 | ||||
|             val result = replace(unPad(blocks2bytes(xorList)).map { it.toInt().toChar() }.joinToString("")) | ||||
|             return padLastChars(result) | ||||
|         } | ||||
| 
 | ||||
|         private fun binaryDigest(input: String): List<BigInteger> { | ||||
|             val keys = listOf(1633837924, 1650680933, 1667523942, 1684366951).map { it.toBigInteger() } | ||||
|             var list1 = keys.slice(0..1) | ||||
|             var list2 = list1 | ||||
|             val blocks = bytes2blocks(digestPad(input)) | ||||
| 
 | ||||
|             for (i in blocks.indices step 4) { | ||||
|                 list1 = tearCode(xorBlocks(blocks.slice(i..i + 1), list1), keys).toMutableList() | ||||
|                 list2 = tearCode(xorBlocks(blocks.slice(i + 2..i + 3), list2), keys).toMutableList() | ||||
| 
 | ||||
|                 val temp = list1[0] | ||||
|                 list1[0] = list1[1] | ||||
|                 list1[1] = list2[0] | ||||
|                 list2[0] = list2[1] | ||||
|                 list2[1] = temp | ||||
|             } | ||||
| 
 | ||||
|             return listOf(list1[0], list1[1], list2[0], list2[1]) | ||||
|         } | ||||
| 
 | ||||
|         private fun tearDecode(a90: List<BigInteger>, a91: List<BigInteger>): MutableList<BigInteger> { | ||||
|             var (a95, a96) = a90 | ||||
| 
 | ||||
|             var a97 = (-957401312).toBigInteger() | ||||
|             for (_i in 0 until 32) { | ||||
|                 a96 -= ((((a95 shl 4) xor rShift(a95, 5)) + a95) xor (a97 + a91[rShift(a97, 11).and(3.toBigInteger()).toInt()])) | ||||
|                 a97 += 1640531527.toBigInteger() | ||||
|                 a95 -= ((((a96 shl 4) xor rShift(a96, 5)) + a96) xor (a97 + a91[a97.and(3.toBigInteger()).toInt()])) | ||||
| 
 | ||||
|             } | ||||
| 
 | ||||
|             return mutableListOf(a95, a96) | ||||
|         } | ||||
| 
 | ||||
|         private fun digestPad(string: String): List<BigInteger> { | ||||
|             val empList = mutableListOf<BigInteger>() | ||||
|             val length = string.length | ||||
|             val extra = big15 - (length.toBigInteger() % big16) | ||||
|             empList.add(extra) | ||||
|             for (i in 0 until length) { | ||||
|                 empList.add(string[i].code.toBigInteger()) | ||||
|             } | ||||
|             for (i in 0 until extra.toInt()) { | ||||
|                 empList.add(big0) | ||||
|             } | ||||
| 
 | ||||
|             return empList | ||||
|         } | ||||
| 
 | ||||
|         private fun bytes2blocks(a22: List<BigInteger>): List<BigInteger> { | ||||
|             val empList = mutableListOf<BigInteger>() | ||||
|             val length = a22.size | ||||
|             var listIndex = 0 | ||||
| 
 | ||||
|             for (i in 0 until length) { | ||||
|                 val subIndex = i % 4 | ||||
|                 val shiftedByte = a22[i] shl (3 - subIndex) * 8 | ||||
| 
 | ||||
|                 if (subIndex == 0) { | ||||
|                     empList.add(shiftedByte) | ||||
|                 } else { | ||||
|                     empList[listIndex] = empList[listIndex] or shiftedByte | ||||
|                 } | ||||
| 
 | ||||
|                 if (subIndex == 3) listIndex += 1 | ||||
|             } | ||||
| 
 | ||||
|             return empList | ||||
|         } | ||||
| 
 | ||||
|         private fun blocks2bytes(inp: List<BigInteger>): List<BigInteger> { | ||||
|             val tempList = mutableListOf<BigInteger>() | ||||
|             inp.indices.forEach { i -> | ||||
|                 tempList += (big255 and rShift(inp[i], 24)) | ||||
|                 tempList += (big255 and rShift(inp[i], 16)) | ||||
|                 tempList += (big255 and rShift(inp[i], 8)) | ||||
|                 tempList += (big255 and inp[i]) | ||||
|             } | ||||
|             return tempList | ||||
|         } | ||||
| 
 | ||||
|         private fun unPad(a46: List<BigInteger>): List<BigInteger> { | ||||
|             val evenOdd = a46[0].toInt().mod(2) | ||||
|             return (1 until (a46.size - evenOdd)).map { | ||||
|                 a46[it] | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private fun xorBlocks(a76: List<BigInteger>, a77: List<BigInteger>): List<BigInteger> { | ||||
|             return listOf(a76[0] xor a77[0], a76[1] xor a77[1]) | ||||
|         } | ||||
| 
 | ||||
|         private fun rShift(input: BigInteger, by: Int): BigInteger { | ||||
|             return (input.mod(4294967296.toBigInteger()) shr by) | ||||
|         } | ||||
| 
 | ||||
|         private fun tearCode(list1: List<BigInteger>, list2: List<BigInteger>): MutableList<BigInteger> { | ||||
|             var a1 = list1[0] | ||||
|             var a2 = list1[1] | ||||
|             var temp = big0 | ||||
| 
 | ||||
|             for (_i in 0 until 32) { | ||||
|                 a1 += (a2 shl 4 xor rShift(a2, 5)) + a2 xor temp + list2[(temp and big3).toInt()] | ||||
|                 temp -= 1640531527.toBigInteger() | ||||
|                 a2 += (a1 shl 4 xor rShift(a1, 5)) + a1 xor temp + list2[(rShift(temp, 11) and big3).toInt()] | ||||
|             } | ||||
|             return mutableListOf(a1, a2) | ||||
|         } | ||||
| 
 | ||||
|         private fun ascii2bytes(input: String): List<BigInteger> { | ||||
|             val abc = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_" | ||||
|             val abcMap = abc.mapIndexed { i, c -> c to i.toBigInteger() }.toMap() | ||||
|             var index = -1 | ||||
|             val length = input.length | ||||
|             var listIndex = 0 | ||||
|             val bytes = mutableListOf<BigInteger>() | ||||
| 
 | ||||
|             while (true) { | ||||
|                 for (i in input) { | ||||
|                     if (abc.contains(i)) { | ||||
|                         index++ | ||||
|                         break | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 bytes.add((abcMap[input.getOrNull(index)?:return bytes]!! * big4)) | ||||
| 
 | ||||
|                 while (true) { | ||||
|                     index++ | ||||
|                     if (abc.contains(input[index])) { | ||||
|                         break | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 var temp = abcMap[input[index]]!! | ||||
| 
 | ||||
|                 bytes[listIndex] = bytes[listIndex] or rShift(temp, 4) | ||||
|                 listIndex++ | ||||
|                 temp = (big15.and(temp)) | ||||
| 
 | ||||
|                 if ((temp == big0) && (index == (length - 1))) return bytes | ||||
| 
 | ||||
|                 bytes.add((temp * big4 * big4)) | ||||
| 
 | ||||
|                 while (true) { | ||||
|                     index++ | ||||
|                     if (index >= length) return bytes | ||||
|                     if (abc.contains(input[index])) break | ||||
|                 } | ||||
| 
 | ||||
|                 temp = abcMap[input[index]]!! | ||||
|                 bytes[listIndex] = bytes[listIndex] or rShift(temp, 2) | ||||
|                 listIndex++ | ||||
|                 temp = (big3 and temp) | ||||
|                 if ((temp == big0) && (index == (length - 1))) { | ||||
|                     return bytes | ||||
|                 } | ||||
|                 bytes.add((temp shl 6)) | ||||
|                 for (i in input) { | ||||
|                     index++ | ||||
|                     if (abc.contains(input[index])) { | ||||
|                         break | ||||
|                     } | ||||
|                 } | ||||
|                 bytes[listIndex] = bytes[listIndex] or abcMap[input[index]]!! | ||||
|                 listIndex++ | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private fun replace(a: String): String { | ||||
|             val map = mapOf( | ||||
|                 '0' to '5', | ||||
|                 '1' to '6', | ||||
|                 '2' to '7', | ||||
|                 '5' to '0', | ||||
|                 '6' to '1', | ||||
|                 '7' to '2' | ||||
|             ) | ||||
|             var b = "" | ||||
|             a.forEach { | ||||
|                 b += if (map.containsKey(it)) map[it] else it | ||||
|             } | ||||
|             return b | ||||
|         } | ||||
| 
 | ||||
|         private fun padLastChars(input:String):String{ | ||||
|             return if(input.reversed()[3].isDigit()) input | ||||
|             else input.dropLast(4) | ||||
|         } | ||||
| 
 | ||||
|         private data class HashResponse( | ||||
|             val hash: String? = null, | ||||
|             val version:String? = null | ||||
|         ) | ||||
| 
 | ||||
|         private data class SetupResponse( | ||||
|             val seed: String, | ||||
|             val src: String?=null, | ||||
|             val link:String?=null | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,101 @@ | |||
| package com.lagradost.cloudstream3.extractors | ||||
| 
 | ||||
| import com.lagradost.cloudstream3.SubtitleFile | ||||
| import com.lagradost.cloudstream3.apmap | ||||
| import com.lagradost.cloudstream3.app | ||||
| import com.lagradost.cloudstream3.argamap | ||||
| import com.lagradost.cloudstream3.utils.ExtractorLink | ||||
| import com.lagradost.cloudstream3.utils.extractorApis | ||||
| import com.lagradost.cloudstream3.utils.getQualityFromName | ||||
| import com.lagradost.cloudstream3.utils.loadExtractor | ||||
| import org.jsoup.Jsoup | ||||
| 
 | ||||
| /** | ||||
|  * overrideMainUrl is necessary for for other vidstream clones like vidembed.cc | ||||
|  * If they diverge it'd be better to make them separate. | ||||
|  * */ | ||||
| class Vidstream(val mainUrl: String) { | ||||
|     val name: String = "Vidstream" | ||||
| 
 | ||||
|     private fun getExtractorUrl(id: String): String { | ||||
|         return "$mainUrl/streaming.php?id=$id" | ||||
|     } | ||||
| 
 | ||||
|     private fun getDownloadUrl(id: String): String { | ||||
|         return "$mainUrl/download?id=$id" | ||||
|     } | ||||
| 
 | ||||
|     private val normalApis = arrayListOf(MultiQuality()) | ||||
| 
 | ||||
|     // https://gogo-stream.com/streaming.php?id=MTE3NDg5 | ||||
|     suspend fun getUrl( | ||||
|         id: String, | ||||
|         isCasting: Boolean = false, | ||||
|         subtitleCallback: (SubtitleFile) -> Unit, | ||||
|         callback: (ExtractorLink) -> Unit, | ||||
|     ): Boolean { | ||||
|         val extractorUrl = getExtractorUrl(id) | ||||
|         argamap( | ||||
|             { | ||||
|                 normalApis.apmap { api -> | ||||
|                     val url = api.getExtractorUrl(id) | ||||
|                     api.getSafeUrl( | ||||
|                         url, | ||||
|                         callback = callback, | ||||
|                         subtitleCallback = subtitleCallback | ||||
|                     ) | ||||
|                 } | ||||
|             }, { | ||||
|                 /** Stolen from GogoanimeProvider.kt extractor */ | ||||
|                 val link = getDownloadUrl(id) | ||||
|                 println("Generated vidstream download link: $link") | ||||
|                 val page = app.get(link, referer = extractorUrl) | ||||
| 
 | ||||
|                 val pageDoc = Jsoup.parse(page.text) | ||||
|                 val qualityRegex = Regex("(\\d+)P") | ||||
| 
 | ||||
|                 //a[download] | ||||
|                 pageDoc.select(".dowload > a")?.apmap { element -> | ||||
|                     val href = element.attr("href") ?: return@apmap | ||||
|                     val qual = if (element.text() | ||||
|                             .contains("HDP") | ||||
|                     ) "1080" else qualityRegex.find(element.text())?.destructured?.component1() | ||||
|                         .toString() | ||||
| 
 | ||||
|                     if (!loadExtractor(href, link, subtitleCallback, callback)) { | ||||
|                         callback.invoke( | ||||
|                             ExtractorLink( | ||||
|                                 this.name, | ||||
|                                 name = this.name, | ||||
|                                 href, | ||||
|                                 page.url, | ||||
|                                 getQualityFromName(qual), | ||||
|                                 element.attr("href").contains(".m3u8") | ||||
|                             ) | ||||
|                         ) | ||||
|                     } | ||||
|                 } | ||||
|             }, { | ||||
|                 with(app.get(extractorUrl)) { | ||||
|                     val document = Jsoup.parse(this.text) | ||||
|                     val primaryLinks = document.select("ul.list-server-items > li.linkserver") | ||||
|                     //val extractedLinksList: MutableList<ExtractorLink> = mutableListOf() | ||||
| 
 | ||||
|                     // All vidstream links passed to extractors | ||||
|                     primaryLinks.distinctBy { it.attr("data-video") }.forEach { element -> | ||||
|                         val link = element.attr("data-video") | ||||
|                         //val name = element.text() | ||||
| 
 | ||||
|                         // Matches vidstream links with extractors | ||||
|                         extractorApis.filter { !it.requiresReferer || !isCasting }.apmap { api -> | ||||
|                             if (link.startsWith(api.mainUrl)) { | ||||
|                                 api.getSafeUrl(link, extractorUrl, subtitleCallback, callback) | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         ) | ||||
|         return true | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,51 @@ | |||
| package com.lagradost.cloudstream3.extractors | ||||
| 
 | ||||
| import com.fasterxml.jackson.annotation.JsonProperty | ||||
| import com.lagradost.cloudstream3.app | ||||
| import com.lagradost.cloudstream3.utils.AppUtils.parseJson | ||||
| import com.lagradost.cloudstream3.utils.ExtractorApi | ||||
| import com.lagradost.cloudstream3.utils.ExtractorLink | ||||
| import com.lagradost.cloudstream3.utils.getQualityFromName | ||||
| 
 | ||||
| open class VoeExtractor : ExtractorApi() { | ||||
|     override val name: String = "Voe" | ||||
|     override val mainUrl: String = "https://voe.sx" | ||||
|     override val requiresReferer = false | ||||
| 
 | ||||
|     private data class ResponseLinks( | ||||
|         @JsonProperty("hls") val url: String?, | ||||
|         @JsonProperty("video_height") val label: Int? | ||||
|         //val type: String // Mp4 | ||||
|     ) | ||||
| 
 | ||||
|     override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> { | ||||
|         val extractedLinksList: MutableList<ExtractorLink> = mutableListOf() | ||||
|         val doc = app.get(url).text | ||||
|         if (doc.isNotBlank()) { | ||||
|             val start = "const sources =" | ||||
|             var src = doc.substring(doc.indexOf(start)) | ||||
|             src = src.substring(start.length, src.indexOf(";")) | ||||
|                 .replace("0,", "0") | ||||
|                 .trim() | ||||
|             //Log.i(this.name, "Result => (src) ${src}") | ||||
|             parseJson<ResponseLinks?>(src)?.let { voelink -> | ||||
|                 //Log.i(this.name, "Result => (voelink) ${voelink}") | ||||
|                 val linkUrl = voelink.url | ||||
|                 val linkLabel = voelink.label?.toString() ?: "" | ||||
|                 if (!linkUrl.isNullOrEmpty()) { | ||||
|                     extractedLinksList.add( | ||||
|                         ExtractorLink( | ||||
|                             name = this.name, | ||||
|                             source = this.name, | ||||
|                             url = linkUrl, | ||||
|                             quality = getQualityFromName(linkLabel), | ||||
|                             referer = url, | ||||
|                             isM3u8 = true | ||||
|                         ) | ||||
|                     ) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return extractedLinksList | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,23 @@ | |||
| package com.lagradost.cloudstream3.extractors | ||||
| 
 | ||||
| import com.lagradost.cloudstream3.app | ||||
| import com.lagradost.cloudstream3.network.WebViewResolver | ||||
| import com.lagradost.cloudstream3.utils.ExtractorApi | ||||
| import com.lagradost.cloudstream3.utils.ExtractorLink | ||||
| import com.lagradost.cloudstream3.utils.M3u8Helper.Companion.generateM3u8 | ||||
| 
 | ||||
| open class WatchSB : ExtractorApi() { | ||||
|     override var name = "WatchSB" | ||||
|     override var mainUrl = "https://watchsb.com" | ||||
|     override val requiresReferer = false | ||||
| 
 | ||||
|     override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> { | ||||
|         val response = app.get( | ||||
|             url, interceptor = WebViewResolver( | ||||
|                 Regex("""master\.m3u8""") | ||||
|             ) | ||||
|         ) | ||||
| 
 | ||||
|         return generateM3u8(name, response.url, url, headers = response.headers.toMap()) | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,127 @@ | |||
| package com.lagradost.cloudstream3.extractors | ||||
| 
 | ||||
| import com.fasterxml.jackson.annotation.JsonProperty | ||||
| import com.lagradost.cloudstream3.ErrorLoadingException | ||||
| import com.lagradost.cloudstream3.extractors.helper.NineAnimeHelper.cipher | ||||
| import com.lagradost.cloudstream3.extractors.helper.NineAnimeHelper.encrypt | ||||
| import com.lagradost.cloudstream3.app | ||||
| import com.lagradost.cloudstream3.utils.ExtractorApi | ||||
| import com.lagradost.cloudstream3.utils.ExtractorLink | ||||
| import com.lagradost.cloudstream3.utils.Qualities | ||||
| 
 | ||||
| class Vidstreamz : WcoStream() { | ||||
|     override var mainUrl = "https://vidstreamz.online" | ||||
| } | ||||
| 
 | ||||
| class Vizcloud : WcoStream() { | ||||
|     override var mainUrl = "https://vizcloud2.ru" | ||||
| } | ||||
| 
 | ||||
| class Vizcloud2 : WcoStream() { | ||||
|     override var mainUrl = "https://vizcloud2.online" | ||||
| } | ||||
| 
 | ||||
| class VizcloudOnline : WcoStream() { | ||||
|     override var mainUrl = "https://vizcloud.online" | ||||
| } | ||||
| 
 | ||||
| class VizcloudXyz : WcoStream() { | ||||
|     override var mainUrl = "https://vizcloud.xyz" | ||||
| } | ||||
| 
 | ||||
| class VizcloudLive : WcoStream() { | ||||
|     override var mainUrl = "https://vizcloud.live" | ||||
| } | ||||
| 
 | ||||
| class VizcloudInfo : WcoStream() { | ||||
|     override var mainUrl = "https://vizcloud.info" | ||||
| } | ||||
| 
 | ||||
| class MwvnVizcloudInfo : WcoStream() { | ||||
|     override var mainUrl = "https://mwvn.vizcloud.info" | ||||
| } | ||||
| 
 | ||||
| class VizcloudDigital : WcoStream() { | ||||
|     override var mainUrl = "https://vizcloud.digital" | ||||
| } | ||||
| 
 | ||||
| class VizcloudCloud : WcoStream() { | ||||
|     override var mainUrl = "https://vizcloud.cloud" | ||||
| } | ||||
| 
 | ||||
| class VizcloudSite : WcoStream() { | ||||
|     override var mainUrl = "https://vizcloud.site" | ||||
| } | ||||
| 
 | ||||
| open class WcoStream : ExtractorApi() { | ||||
|     override var name = "VidStream" // Cause works for animekisa and wco | ||||
|     override var mainUrl = "https://vidstream.pro" | ||||
|     override val requiresReferer = false | ||||
|     private val regex = Regex("(.+?/)e(?:mbed)?/([a-zA-Z0-9]+)") | ||||
| 
 | ||||
|     companion object { | ||||
|         // taken from https://github.com/saikou-app/saikou/blob/b35364c8c2a00364178a472fccf1ab72f09815b4/app/src/main/java/ani/saikou/parsers/anime/extractors/VizCloud.kt | ||||
|         // GNU General Public License v3.0 https://github.com/saikou-app/saikou/blob/main/LICENSE.md | ||||
|         private var lastChecked = 0L | ||||
|         private const val jsonLink = | ||||
|             "https://raw.githubusercontent.com/chenkaslowankiya/BruhFlow/main/keys.json" | ||||
|         private var cipherKey: VizCloudKey? = null | ||||
|         suspend fun getKey(): VizCloudKey { | ||||
|             cipherKey = | ||||
|                 if (cipherKey != null && (lastChecked - System.currentTimeMillis()) < 1000 * 60 * 30) cipherKey!! | ||||
|                 else { | ||||
|                     lastChecked = System.currentTimeMillis() | ||||
|                     app.get(jsonLink).parsed() | ||||
|                 } | ||||
|             return cipherKey!! | ||||
|         } | ||||
| 
 | ||||
|         data class VizCloudKey( | ||||
|             @JsonProperty("cipherKey") val cipherKey: String, | ||||
|             @JsonProperty("mainKey") val mainKey: String, | ||||
|             @JsonProperty("encryptKey") val encryptKey: String, | ||||
|             @JsonProperty("dashTable") val dashTable: String | ||||
|         ) | ||||
| 
 | ||||
|         private const val baseTable = | ||||
|             "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+=/_" | ||||
| 
 | ||||
|         private fun dashify(id: String, dashTable: String): String { | ||||
|             val table = dashTable.split(" ") | ||||
|             return id.mapIndexedNotNull { i, c -> | ||||
|                 table.getOrNull((baseTable.indexOf(c) * 16) + (i % 16)) | ||||
|             }.joinToString("-") | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     //private val key = "LCbu3iYC7ln24K7P" // key credits @Modder4869 | ||||
|     override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> { | ||||
|         val group = regex.find(url)?.groupValues!! | ||||
| 
 | ||||
|         val host = group[1] | ||||
|         val viz = getKey() | ||||
|         val id = encrypt( | ||||
|             cipher( | ||||
|                 viz.cipherKey, | ||||
|                 encrypt(group[2], viz.encryptKey).also { println(it) } | ||||
|             ).also { println(it) }, | ||||
|             viz.encryptKey | ||||
|         ).also { println(it) } | ||||
| 
 | ||||
|         val link = | ||||
|             "${host}mediainfo/${dashify(id, viz.dashTable)}?key=${viz.mainKey}" // | ||||
|         val response = app.get(link, referer = referer) | ||||
| 
 | ||||
|         data class Sources(@JsonProperty("file") val file: String) | ||||
|         data class Media(@JsonProperty("sources") val sources: List<Sources>) | ||||
|         data class Data(@JsonProperty("media") val media: Media) | ||||
|         data class Response(@JsonProperty("data") val data: Data) | ||||
| 
 | ||||
| 
 | ||||
|         if (!response.text.startsWith("{")) throw ErrorLoadingException("Seems like 9Anime kiddies changed stuff again, Go touch some grass for bout an hour Or use a different Server") | ||||
|         return response.parsed<Response>().data.media.sources.map { | ||||
|             ExtractorLink(name, it.file,it.file,host,Qualities.Unknown.value,it.file.contains(".m3u8")) | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,93 @@ | |||
| package com.lagradost.cloudstream3.extractors | ||||
| 
 | ||||
| import com.fasterxml.jackson.annotation.JsonProperty | ||||
| import com.lagradost.cloudstream3.app | ||||
| import com.lagradost.cloudstream3.utils.AppUtils | ||||
| import com.lagradost.cloudstream3.utils.ExtractorApi | ||||
| import com.lagradost.cloudstream3.utils.ExtractorLink | ||||
| import com.lagradost.cloudstream3.utils.getQualityFromName | ||||
| 
 | ||||
| class LayarKaca: XStreamCdn() { | ||||
|     override val name: String = "LayarKaca-xxi" | ||||
|     override val mainUrl: String = "https://layarkacaxxi.icu" | ||||
| } | ||||
| 
 | ||||
| class DBfilm: XStreamCdn() { | ||||
|     override val name: String = "DBfilm" | ||||
|     override val mainUrl: String = "https://dbfilm.bar" | ||||
| } | ||||
| 
 | ||||
| class Luxubu : XStreamCdn(){ | ||||
|     override val name: String = "FE" | ||||
|     override val mainUrl: String = "https://www.luxubu.review" | ||||
| } | ||||
| 
 | ||||
| class FEmbed: XStreamCdn() { | ||||
|     override val name: String = "FEmbed" | ||||
|     override val mainUrl: String = "https://www.fembed.com" | ||||
| } | ||||
| 
 | ||||
| class Fplayer: XStreamCdn() { | ||||
|     override val name: String = "Fplayer" | ||||
|     override val mainUrl: String = "https://fplayer.info" | ||||
| } | ||||
| 
 | ||||
| class FeHD: XStreamCdn() { | ||||
|     override val name: String = "FeHD" | ||||
|     override val mainUrl: String = "https://fembed-hd.com" | ||||
|     override var domainUrl: String = "fembed-hd.com" | ||||
| } | ||||
| 
 | ||||
| open class XStreamCdn : ExtractorApi() { | ||||
|     override val name: String = "XStreamCdn" | ||||
|     override val mainUrl: String = "https://embedsito.com" | ||||
|     override val requiresReferer = false | ||||
|     open var domainUrl: String = "embedsito.com" | ||||
| 
 | ||||
|     private data class ResponseData( | ||||
|         @JsonProperty("file") val file: String, | ||||
|         @JsonProperty("label") val label: String, | ||||
|         //val type: String // Mp4 | ||||
|     ) | ||||
| 
 | ||||
|     private data class ResponseJson( | ||||
|         @JsonProperty("success") val success: Boolean, | ||||
|         @JsonProperty("data") val data: List<ResponseData>? | ||||
|     ) | ||||
| 
 | ||||
|     override fun getExtractorUrl(id: String): String { | ||||
|         return "$domainUrl/api/source/$id" | ||||
|     } | ||||
| 
 | ||||
|     override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> { | ||||
|         val headers = mapOf( | ||||
|             "Referer" to url, | ||||
|             "User-Agent" to "Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0", | ||||
|         ) | ||||
|         val id = url.trimEnd('/').split("/").last() | ||||
|         val newUrl = "https://${domainUrl}/api/source/${id}" | ||||
|         val extractedLinksList: MutableList<ExtractorLink> = mutableListOf() | ||||
|         with(app.post(newUrl, headers = headers)) { | ||||
|             if (this.code != 200) return listOf() | ||||
|             val text = this.text | ||||
|             if (text.isEmpty()) return listOf() | ||||
|             if (text == """{"success":false,"data":"Video not found or has been removed"}""") return listOf() | ||||
|             AppUtils.parseJson<ResponseJson?>(text)?.let { | ||||
|                 if (it.success && it.data != null) { | ||||
|                     it.data.forEach { data -> | ||||
|                         extractedLinksList.add( | ||||
|                             ExtractorLink( | ||||
|                                 name, | ||||
|                                 name = name, | ||||
|                                 data.file, | ||||
|                                 url, | ||||
|                                 getQualityFromName(data.label), | ||||
|                             ) | ||||
|                         ) | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return extractedLinksList | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,47 @@ | |||
| package com.lagradost.cloudstream3.extractors | ||||
| 
 | ||||
| import com.fasterxml.jackson.annotation.JsonProperty | ||||
| import com.lagradost.cloudstream3.app | ||||
| import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson | ||||
| import com.lagradost.cloudstream3.utils.ExtractorApi | ||||
| import com.lagradost.cloudstream3.utils.ExtractorLink | ||||
| import com.lagradost.cloudstream3.utils.getQualityFromName | ||||
| 
 | ||||
| class YourUpload: ExtractorApi() { | ||||
|     override val name = "Yourupload" | ||||
|     override val mainUrl = "https://www.yourupload.com" | ||||
|     override val requiresReferer = false | ||||
| 
 | ||||
|     override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> { | ||||
|         val sources = mutableListOf<ExtractorLink>() | ||||
|         with(app.get(url).document) { | ||||
|             val quality = Regex("\\d{3,4}p").find(this.select("title").text())?.groupValues?.get(0) | ||||
|             this.select("script").map { script -> | ||||
|                 if (script.data().contains("var jwplayerOptions = {")) { | ||||
|                     val data = | ||||
|                         script.data().substringAfter("var jwplayerOptions = {").substringBefore(",\n") | ||||
|                     val link = tryParseJson<ResponseSource>( | ||||
|                         "{${ | ||||
|                             data.replace("file", "\"file\"").replace("'", "\"") | ||||
|                         }}" | ||||
|                     ) | ||||
|                     sources.add( | ||||
|                         ExtractorLink( | ||||
|                             source = name, | ||||
|                             name = name, | ||||
|                             url = link!!.file, | ||||
|                             referer = url, | ||||
|                             quality = getQualityFromName(quality) | ||||
|                         ) | ||||
|                     ) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return sources | ||||
|     } | ||||
| 
 | ||||
|     private data class ResponseSource( | ||||
|         @JsonProperty("file") val file: String, | ||||
|     ) | ||||
| 
 | ||||
| } | ||||
|  | @ -0,0 +1,88 @@ | |||
| package com.lagradost.cloudstream3.extractors | ||||
| 
 | ||||
| import com.lagradost.cloudstream3.SubtitleFile | ||||
| import com.lagradost.cloudstream3.mvvm.logError | ||||
| import com.lagradost.cloudstream3.utils.ExtractorApi | ||||
| import com.lagradost.cloudstream3.utils.ExtractorLink | ||||
| import com.lagradost.cloudstream3.utils.schemaStripRegex | ||||
| import org.schabi.newpipe.extractor.ServiceList | ||||
| import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeStreamExtractor | ||||
| import org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeStreamLinkHandlerFactory | ||||
| import org.schabi.newpipe.extractor.stream.SubtitlesStream | ||||
| import org.schabi.newpipe.extractor.stream.VideoStream | ||||
| 
 | ||||
| class YoutubeShortLinkExtractor : YoutubeExtractor() { | ||||
|     override val mainUrl = "https://youtu.be" | ||||
| 
 | ||||
|     override fun getExtractorUrl(id: String): String { | ||||
|         return "$mainUrl/$id" | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| class YoutubeMobileExtractor  : YoutubeExtractor() { | ||||
|     override val mainUrl = "https://m.youtube.com" | ||||
| } | ||||
| class YoutubeNoCookieExtractor  : YoutubeExtractor() { | ||||
|     override val mainUrl = "https://www.youtube-nocookie.com" | ||||
| } | ||||
| 
 | ||||
| open class YoutubeExtractor : ExtractorApi() { | ||||
|     override val mainUrl = "https://www.youtube.com" | ||||
|     override val requiresReferer = false | ||||
|     override val name = "YouTube" | ||||
| 
 | ||||
|     companion object { | ||||
|         private var ytVideos: MutableMap<String, List<VideoStream>> = mutableMapOf() | ||||
|         private var ytVideosSubtitles: MutableMap<String, List<SubtitlesStream>> = mutableMapOf() | ||||
|     } | ||||
| 
 | ||||
|     override fun getExtractorUrl(id: String): String { | ||||
|         return "$mainUrl/watch?v=$id" | ||||
|     } | ||||
| 
 | ||||
|     override suspend fun getUrl( | ||||
|         url: String, | ||||
|         referer: String?, | ||||
|         subtitleCallback: (SubtitleFile) -> Unit, | ||||
|         callback: (ExtractorLink) -> Unit | ||||
|     ) { | ||||
|         if (ytVideos[url].isNullOrEmpty()) { | ||||
|             val link = | ||||
|                 YoutubeStreamLinkHandlerFactory.getInstance().fromUrl( | ||||
|                     url.replace( | ||||
|                         schemaStripRegex, "" | ||||
|                     ) | ||||
|                 ) | ||||
| 
 | ||||
|             val s = object : YoutubeStreamExtractor( | ||||
|                 ServiceList.YouTube, | ||||
|                 link | ||||
|             ) { | ||||
| 
 | ||||
|             } | ||||
|             s.fetchPage() | ||||
|             ytVideos[url] = s.videoStreams | ||||
|             ytVideosSubtitles[url] = try { | ||||
|                 s.subtitlesDefault.filterNotNull() | ||||
|             } catch (e: Exception) { | ||||
|                 logError(e) | ||||
|                 emptyList() | ||||
|             } | ||||
|         } | ||||
|         ytVideos[url]?.mapNotNull { | ||||
|             if (it.isVideoOnly || it.height <= 0) return@mapNotNull null | ||||
| 
 | ||||
|             ExtractorLink( | ||||
|                 this.name, | ||||
|                 this.name, | ||||
|                 it.url ?: return@mapNotNull null, | ||||
|                 "", | ||||
|                 it.height | ||||
|             ) | ||||
|         }?.forEach(callback) | ||||
|         ytVideosSubtitles[url]?.mapNotNull { | ||||
|             SubtitleFile(it.languageTag ?: return@mapNotNull null, it.url ?: return@mapNotNull null) | ||||
|         }?.forEach(subtitleCallback) | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -0,0 +1,58 @@ | |||
| package com.lagradost.cloudstream3.extractors | ||||
| 
 | ||||
| import com.lagradost.cloudstream3.apmap | ||||
| import com.lagradost.cloudstream3.app | ||||
| import com.lagradost.cloudstream3.utils.ExtractorApi | ||||
| import com.lagradost.cloudstream3.utils.ExtractorLink | ||||
| import com.lagradost.cloudstream3.utils.M3u8Helper | ||||
| import com.lagradost.cloudstream3.utils.getAndUnpack | ||||
| 
 | ||||
| class Zplayer: ZplayerV2() { | ||||
|     override var name: String = "Zplayer" | ||||
|     override var mainUrl: String = "https://zplayer.live" | ||||
| } | ||||
| 
 | ||||
| class Upstream: ZplayerV2() { | ||||
|     override var name: String = "Upstream" //Here 'cause works | ||||
|     override var mainUrl: String = "https://upstream.to" | ||||
| } | ||||
| 
 | ||||
| class Streamhub2: ZplayerV2() { | ||||
|     override var name = "Streamhub" //Here 'cause works | ||||
|     override var mainUrl = "https://streamhub.to" | ||||
| } | ||||
| 
 | ||||
| open class ZplayerV2 : ExtractorApi() { | ||||
|     override var name = "Zplayer V2" | ||||
|     override var mainUrl = "https://v2.zplayer.live" | ||||
|     override val requiresReferer = false | ||||
| 
 | ||||
|     override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> { | ||||
|         val doc = app.get(url).document | ||||
|         val sources = mutableListOf<ExtractorLink>() | ||||
|         doc.select("script").map { script -> | ||||
|             if (script.data().contains("eval(function(p,a,c,k,e,d)")) { | ||||
|                 val testdata = getAndUnpack(script.data()) | ||||
|                 val m3u8regex = Regex("((https:|http:)\\/\\/.*\\.m3u8)") | ||||
|                 m3u8regex.findAll(testdata).map { | ||||
|                     it.value | ||||
|                 }.toList().apmap { urlm3u8 -> | ||||
|                     if (urlm3u8.contains("m3u8")) { | ||||
|                         val testurl = app.get(urlm3u8, headers = mapOf("Referer" to url)).text | ||||
|                         if (testurl.contains("EXTM3U")) { | ||||
|                             M3u8Helper.generateM3u8( | ||||
|                                 name, | ||||
|                                 urlm3u8, | ||||
|                                 url, | ||||
|                                 headers = mapOf("Referer" to url) | ||||
|                             ).forEach { link -> | ||||
|                                 sources.add(link) | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return sources | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,32 @@ | |||
| package com.lagradost.cloudstream3.extractors.helper | ||||
| 
 | ||||
| import android.util.Log | ||||
| import com.lagradost.cloudstream3.SubtitleFile | ||||
| import com.lagradost.cloudstream3.apmap | ||||
| import com.lagradost.cloudstream3.app | ||||
| import com.lagradost.cloudstream3.utils.ExtractorLink | ||||
| import com.lagradost.cloudstream3.utils.loadExtractor | ||||
| 
 | ||||
| class AsianEmbedHelper { | ||||
|     companion object { | ||||
|         suspend fun getUrls( | ||||
|             url: String, | ||||
|             subtitleCallback: (SubtitleFile) -> Unit, | ||||
|             callback: (ExtractorLink) -> Unit | ||||
|         ) { | ||||
|             // Fetch links | ||||
|             val doc = app.get(url).document | ||||
|             val links = doc.select("div#list-server-more > ul > li.linkserver") | ||||
|             if (!links.isNullOrEmpty()) { | ||||
|                 links.apmap { | ||||
|                     val datavid = it.attr("data-video") ?: "" | ||||
|                     //Log.i("AsianEmbed", "Result => (datavid) ${datavid}") | ||||
|                     if (datavid.isNotBlank()) { | ||||
|                         val res = loadExtractor(datavid, url, subtitleCallback, callback) | ||||
|                         Log.i("AsianEmbed", "Result => ($res) (datavid) $datavid") | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,112 @@ | |||
| package com.lagradost.cloudstream3.extractors.helper | ||||
| 
 | ||||
| // taken from https://github.com/saikou-app/saikou/blob/b35364c8c2a00364178a472fccf1ab72f09815b4/app/src/main/java/ani/saikou/parsers/anime/NineAnime.kt | ||||
| // GNU General Public License v3.0 https://github.com/saikou-app/saikou/blob/main/LICENSE.md | ||||
| object NineAnimeHelper { | ||||
|     private const val nineAnimeKey = | ||||
|         "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" | ||||
|     private const val cipherKey = "kMXzgyNzT3k5dYab" | ||||
| 
 | ||||
|     fun encodeVrf(text: String, mainKey: String): String { | ||||
|         return encode( | ||||
|             encrypt( | ||||
|                 cipher(mainKey, encode(text)), | ||||
|                 nineAnimeKey | ||||
|         )//.replace("""=+$""".toRegex(), "") | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     fun decodeVrf(text: String, mainKey: String): String { | ||||
|         return decode(cipher(mainKey, decrypt(text, nineAnimeKey))) | ||||
|     } | ||||
| 
 | ||||
|     fun encrypt(input: String, key: String): String { | ||||
|         if (input.any { it.code > 255 }) throw Exception("illegal characters!") | ||||
|         var output = "" | ||||
|         for (i in input.indices step 3) { | ||||
|             val a = intArrayOf(-1, -1, -1, -1) | ||||
|             a[0] = input[i].code shr 2 | ||||
|             a[1] = (3 and input[i].code) shl 4 | ||||
|             if (input.length > i + 1) { | ||||
|                 a[1] = a[1] or (input[i + 1].code shr 4) | ||||
|                 a[2] = (15 and input[i + 1].code) shl 2 | ||||
|             } | ||||
|             if (input.length > i + 2) { | ||||
|                 a[2] = a[2] or (input[i + 2].code shr 6) | ||||
|                 a[3] = 63 and input[i + 2].code | ||||
|             } | ||||
|             for (n in a) { | ||||
|                 if (n == -1) output += "=" | ||||
|                 else { | ||||
|                     if (n in 0..63) output += key[n] | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return output | ||||
|     } | ||||
| 
 | ||||
|     fun cipher(key: String, text: String): String { | ||||
|         val arr = IntArray(256) { it } | ||||
| 
 | ||||
|         var u = 0 | ||||
|         var r: Int | ||||
|         arr.indices.forEach { | ||||
|             u = (u + arr[it] + key[it % key.length].code) % 256 | ||||
|             r = arr[it] | ||||
|             arr[it] = arr[u] | ||||
|             arr[u] = r | ||||
|         } | ||||
|         u = 0 | ||||
|         var c = 0 | ||||
| 
 | ||||
|         return text.indices.map { j -> | ||||
|             c = (c + 1) % 256 | ||||
|             u = (u + arr[c]) % 256 | ||||
|             r = arr[c] | ||||
|             arr[c] = arr[u] | ||||
|             arr[u] = r | ||||
|             (text[j].code xor arr[(arr[c] + arr[u]) % 256]).toChar() | ||||
|         }.joinToString("") | ||||
|     } | ||||
| 
 | ||||
|     @Suppress("SameParameterValue") | ||||
|     private fun decrypt(input: String, key: String): String { | ||||
|         val t = if (input.replace("""[\t\n\f\r]""".toRegex(), "").length % 4 == 0) { | ||||
|             input.replace("""==?$""".toRegex(), "") | ||||
|         } else input | ||||
|         if (t.length % 4 == 1 || t.contains("""[^+/0-9A-Za-z]""".toRegex())) throw Exception("bad input") | ||||
|         var i: Int | ||||
|         var r = "" | ||||
|         var e = 0 | ||||
|         var u = 0 | ||||
|         for (o in t.indices) { | ||||
|             e = e shl 6 | ||||
|             i = key.indexOf(t[o]) | ||||
|             e = e or i | ||||
|             u += 6 | ||||
|             if (24 == u) { | ||||
|                 r += ((16711680 and e) shr 16).toChar() | ||||
|                 r += ((65280 and e) shr 8).toChar() | ||||
|                 r += (255 and e).toChar() | ||||
|                 e = 0 | ||||
|                 u = 0 | ||||
|             } | ||||
|         } | ||||
|         return if (12 == u) { | ||||
|             e = e shr 4 | ||||
|             r + e.toChar() | ||||
|         } else { | ||||
|             if (18 == u) { | ||||
|                 e = e shr 2 | ||||
|                 r += ((65280 and e) shr 8).toChar() | ||||
|                 r += (255 and e).toChar() | ||||
|             } | ||||
|             r | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fun encode(input: String): String = | ||||
|         java.net.URLEncoder.encode(input, "utf-8").replace("+", "%20") | ||||
| 
 | ||||
|     private fun decode(input: String): String = java.net.URLDecoder.decode(input, "utf-8") | ||||
| } | ||||
|  | @ -0,0 +1,58 @@ | |||
| package com.lagradost.cloudstream3.extractors.helper | ||||
| 
 | ||||
| import com.lagradost.cloudstream3.SubtitleFile | ||||
| import com.lagradost.cloudstream3.app | ||||
| import com.lagradost.cloudstream3.utils.ExtractorLink | ||||
| import com.lagradost.cloudstream3.utils.Qualities | ||||
| import com.lagradost.cloudstream3.utils.loadExtractor | ||||
| 
 | ||||
| class VstreamhubHelper { | ||||
|     companion object { | ||||
|         private val baseUrl: String = "https://vstreamhub.com" | ||||
|         private val baseName: String = "Vstreamhub" | ||||
| 
 | ||||
|         suspend fun getUrls( | ||||
|             url: String, | ||||
|             subtitleCallback: (SubtitleFile) -> Unit, | ||||
|             callback: (ExtractorLink) -> Unit | ||||
|         ) { | ||||
|             if (url.startsWith(baseUrl)) { | ||||
|                 // Fetch links | ||||
|                 val doc = app.get(url).document.select("script") | ||||
|                 doc?.forEach { | ||||
|                     val innerText = it?.toString() | ||||
|                     if (!innerText.isNullOrEmpty()) { | ||||
|                         if (innerText.contains("file:")) { | ||||
|                             val startString = "file: " | ||||
|                             val aa = innerText.substring(innerText.indexOf(startString)) | ||||
|                             val linkUrl = | ||||
|                                 aa.substring(startString.length + 1, aa.indexOf("\",")).trim() | ||||
|                             //Log.i(baseName, "Result => (linkUrl) ${linkUrl}") | ||||
|                             val exlink = ExtractorLink( | ||||
|                                 name = "$baseName m3u8", | ||||
|                                 source = baseName, | ||||
|                                 url = linkUrl, | ||||
|                                 quality = Qualities.Unknown.value, | ||||
|                                 referer = url, | ||||
|                                 isM3u8 = true | ||||
|                             ) | ||||
|                             callback.invoke(exlink) | ||||
|                         } | ||||
|                         if (innerText.contains("playerInstance")) { | ||||
|                             val aa = | ||||
|                                 innerText.substring(innerText.indexOf("playerInstance.addButton")) | ||||
|                             val startString = "window.open([" | ||||
|                             val bb = aa.substring(aa.indexOf(startString)) | ||||
|                             val datavid = bb.substring(startString.length, bb.indexOf("]")) | ||||
|                                 .removeSurrounding("\"") | ||||
|                             if (datavid.isNotBlank()) { | ||||
|                                 loadExtractor(datavid, url, subtitleCallback, callback) | ||||
|                                 //Log.i(baseName, "Result => (datavid) ${datavid}") | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,56 @@ | |||
| package com.lagradost.cloudstream3.extractors.helper | ||||
| 
 | ||||
| import com.fasterxml.jackson.annotation.JsonProperty | ||||
| import com.lagradost.cloudstream3.AcraApplication.Companion.getKey | ||||
| import com.lagradost.cloudstream3.AcraApplication.Companion.setKey | ||||
| import com.lagradost.cloudstream3.app | ||||
| 
 | ||||
| class WcoHelper { | ||||
|     companion object { | ||||
|         private const val BACKUP_KEY_DATA = "github_keys_backup" | ||||
| 
 | ||||
|         data class ExternalKeys( | ||||
|             @JsonProperty("wco_key") | ||||
|             val wcoKey: String? = null, | ||||
|             @JsonProperty("wco_cipher_key") | ||||
|             val wcocipher: String? = null | ||||
|         ) | ||||
| 
 | ||||
|         data class NewExternalKeys( | ||||
|             @JsonProperty("cipherKey") | ||||
|             val cipherkey: String? = null, | ||||
|             @JsonProperty("encryptKey") | ||||
|             val encryptKey: String? = null, | ||||
|             @JsonProperty("mainKey") | ||||
|             val mainKey: String? = null, | ||||
|         ) | ||||
| 
 | ||||
|         private var keys: ExternalKeys? = null | ||||
|         private var newKeys: NewExternalKeys? = null | ||||
|         private suspend fun getKeys() { | ||||
|             keys = keys | ||||
|                 ?: app.get("https://raw.githubusercontent.com/reduplicated/Cloudstream/master/docs/keys.json") | ||||
|                     .parsedSafe<ExternalKeys>()?.also { setKey(BACKUP_KEY_DATA, it) } ?: getKey( | ||||
|                     BACKUP_KEY_DATA | ||||
|                 ) | ||||
|         } | ||||
| 
 | ||||
|         suspend fun getWcoKey(): ExternalKeys? { | ||||
|             getKeys() | ||||
|             return keys | ||||
|         } | ||||
| 
 | ||||
|         private suspend fun getNewKeys() { | ||||
|             newKeys = newKeys | ||||
|                 ?: app.get("https://raw.githubusercontent.com/chekaslowakiya/BruhFlow/main/keys.json") | ||||
|                     .parsedSafe<NewExternalKeys>()?.also { setKey(BACKUP_KEY_DATA, it) } ?: getKey( | ||||
|                     BACKUP_KEY_DATA | ||||
|                 ) | ||||
|         } | ||||
| 
 | ||||
|         suspend fun getNewWcoKey(): NewExternalKeys? { | ||||
|             getNewKeys() | ||||
|             return newKeys | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -143,8 +143,9 @@ class OpenSubtitlesApi(index: Int) : InAppAuthAPIManager(index), AbstractSubApi | |||
|     /** | ||||
|      * Some languages do not use the normal country codes on OpenSubtitles | ||||
|      * */ | ||||
|     private val languageExceptions = mapOf( | ||||
|         "pt" to "pt-PT" | ||||
|     private val languageExceptions = mapOf<String, String>( | ||||
| //        "pt" to "pt-PT", | ||||
| //        "pt" to "pt-BR" | ||||
|     ) | ||||
|     private fun fixLanguage(language: String?) : String? { | ||||
|         return languageExceptions[language] ?: language | ||||
|  |  | |||
|  | @ -734,7 +734,7 @@ class GeneratorPlayer : FullScreenPlayer() { | |||
|         if ((currentMeta as? ResultEpisode)?.tvType?.isLiveStream() == true) return | ||||
| 
 | ||||
|         val (position, duration) = posDur | ||||
|         if(duration == 0L) return // idk how you achieved this, but div by zero crash | ||||
|         if (duration == 0L) return // idk how you achieved this, but div by zero crash | ||||
| 
 | ||||
|         viewModel.getId()?.let { | ||||
|             DataStoreHelper.setViewPos(it, position, duration) | ||||
|  | @ -1015,7 +1015,8 @@ class GeneratorPlayer : FullScreenPlayer() { | |||
|             limitTitle = settingsManager.getInt(ctx.getString(R.string.prefer_limit_title_key), 0) | ||||
|             updateForcedEncoding(ctx) | ||||
| 
 | ||||
|             filterSubByLang = settingsManager.getBoolean(getString(R.string.filter_sub_lang_key), false) | ||||
|             filterSubByLang = | ||||
|                 settingsManager.getBoolean(getString(R.string.filter_sub_lang_key), false) | ||||
|             if (filterSubByLang) { | ||||
|                 val langFromPrefMedia = settingsManager.getStringSet( | ||||
|                     this.getString(R.string.provider_lang_key), | ||||
|  |  | |||
|  | @ -5,19 +5,15 @@ import android.os.Bundle | |||
| import android.view.View | ||||
| import androidx.preference.PreferenceFragmentCompat | ||||
| import androidx.preference.PreferenceManager | ||||
| import com.lagradost.cloudstream3.APIHolder | ||||
| import com.lagradost.cloudstream3.* | ||||
| import com.lagradost.cloudstream3.APIHolder.getApiDubstatusSettings | ||||
| import com.lagradost.cloudstream3.APIHolder.getApiProviderLangSettings | ||||
| import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey | ||||
| import com.lagradost.cloudstream3.CommonActivity | ||||
| import com.lagradost.cloudstream3.DubStatus | ||||
| import com.lagradost.cloudstream3.R | ||||
| import com.lagradost.cloudstream3.mvvm.logError | ||||
| import com.lagradost.cloudstream3.ui.APIRepository | ||||
| import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref | ||||
| import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar | ||||
| import com.lagradost.cloudstream3.utils.USER_SELECTED_HOMEPAGE_API | ||||
| import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog | ||||
| import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog | ||||
| import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showMultiDialog | ||||
| import com.lagradost.cloudstream3.utils.SubtitleHelper | ||||
|  | @ -100,25 +96,26 @@ class SettingsLang : PreferenceFragmentCompat() { | |||
|         } | ||||
| 
 | ||||
|         getPref(R.string.prefer_media_type_key)?.setOnPreferenceClickListener { | ||||
|             val prefNames = resources.getStringArray(R.array.media_type_pref) | ||||
|             val prefValues = resources.getIntArray(R.array.media_type_pref_values) | ||||
|             val names = enumValues<TvType>().sorted().map { it.name } | ||||
|             val default = enumValues<TvType>().sorted().filter { it != TvType.NSFW }.map { it.ordinal } | ||||
|             val defaultSet = default.map { it.toString() }.toSet() | ||||
|             val currentList = settingsManager.getStringSet(getString(R.string.prefer_media_type_key), defaultSet)?.map { | ||||
|                 it.toInt() | ||||
|             } ?: default | ||||
| 
 | ||||
|             val currentPrefMedia = | ||||
|                 settingsManager.getInt(getString(R.string.prefer_media_type_key), 0) | ||||
| 
 | ||||
|             activity?.showBottomDialog( | ||||
|                 prefNames.toList(), | ||||
|                 prefValues.indexOf(currentPrefMedia), | ||||
|             activity?.showMultiDialog( | ||||
|                 names, | ||||
|                 currentList, | ||||
|                 getString(R.string.preferred_media_settings), | ||||
|                 true, | ||||
|                 {}) { | ||||
|                 settingsManager.edit() | ||||
|                     .putInt(getString(R.string.prefer_media_type_key), prefValues[it]) | ||||
|                     .apply() | ||||
| 
 | ||||
|                 {}) { selectedList -> | ||||
|                 settingsManager.edit().putStringSet( | ||||
|                     this.getString(R.string.prefer_media_type_key), | ||||
|                     selectedList.map { it.toString() }.toMutableSet() | ||||
|                 ).apply() | ||||
|                 removeKey(USER_SELECTED_HOMEPAGE_API) | ||||
| //                (context ?: AcraApplication.context)?.let { ctx -> app.initClient(ctx) } | ||||
|                 //(context ?: AcraApplication.context)?.let { ctx -> app.initClient(ctx) } | ||||
|             } | ||||
| 
 | ||||
|             return@setOnPreferenceClickListener true | ||||
|         } | ||||
| 
 | ||||
|  |  | |||
|  | @ -175,12 +175,12 @@ class PluginAdapter( | |||
|             itemView.ext_version?.isVisible = true | ||||
|             itemView.ext_version?.text = "v${metadata.version}" | ||||
| 
 | ||||
|             if (metadata.language != null) { | ||||
|             if (metadata.language.isNullOrBlank()) { | ||||
|                 itemView.lang_icon?.isVisible = false | ||||
|             } else { | ||||
|                 itemView.lang_icon?.isVisible = true | ||||
|                 //itemView.lang_icon.text = getFlagFromIso(metadata.language) | ||||
|                 itemView.lang_icon.text = fromTwoLettersToLanguage(metadata.language) | ||||
|             } else { | ||||
|                 itemView.lang_icon?.isVisible = false | ||||
|             } | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -6,10 +6,12 @@ import android.view.View | |||
| import android.view.ViewGroup | ||||
| import android.widget.AbsListView | ||||
| import android.widget.ArrayAdapter | ||||
| import androidx.core.util.forEach | ||||
| import androidx.fragment.app.Fragment | ||||
| import androidx.navigation.fragment.findNavController | ||||
| import androidx.preference.PreferenceManager | ||||
| import com.lagradost.cloudstream3.R | ||||
| import com.lagradost.cloudstream3.TvType | ||||
| import com.lagradost.cloudstream3.utils.DataStore.removeKey | ||||
| import com.lagradost.cloudstream3.utils.USER_SELECTED_HOMEPAGE_API | ||||
| import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar | ||||
|  | @ -35,24 +37,34 @@ class SetupFragmentMedia : Fragment() { | |||
|             val arrayAdapter = | ||||
|                 ArrayAdapter<String>(this, R.layout.sort_bottom_single_choice) | ||||
| 
 | ||||
|             val currentPrefMedia = | ||||
|                 settingsManager.getInt(getString(R.string.prefer_media_type_key), 0) | ||||
|             val names = enumValues<TvType>().sorted().map { it.name } | ||||
|             val selected = mutableListOf<Int>() | ||||
| 
 | ||||
|             val prefNames = resources.getStringArray(R.array.media_type_pref) | ||||
|             val prefValues = resources.getIntArray(R.array.media_type_pref_values) | ||||
|             arrayAdapter.addAll(names) | ||||
|             listview1?.let { | ||||
|                 it.adapter = arrayAdapter | ||||
|                 it.choiceMode = AbsListView.CHOICE_MODE_MULTIPLE | ||||
| 
 | ||||
|             arrayAdapter.addAll(prefNames.toList()) | ||||
|             listview1?.adapter = arrayAdapter | ||||
|             listview1?.choiceMode = AbsListView.CHOICE_MODE_SINGLE | ||||
|             listview1?.setItemChecked(currentPrefMedia, true) | ||||
|                 it.setOnItemClickListener { _, _, _, _ -> | ||||
|                     it.checkedItemPositions?.forEach { key, value -> | ||||
|                         if (value) { | ||||
|                             selected.add(key) | ||||
|                         } else { | ||||
|                             selected.remove(key) | ||||
|                         } | ||||
|                     } | ||||
|                     val prefValues = selected.mapNotNull { pos -> | ||||
|                         val item = it.getItemAtPosition(pos)?.toString() ?: return@mapNotNull null | ||||
|                         val itemVal = TvType.valueOf(item) | ||||
|                         itemVal.ordinal.toString() | ||||
|                     }.toSet() | ||||
|                     settingsManager.edit() | ||||
|                         .putStringSet(getString(R.string.prefer_media_type_key), prefValues) | ||||
|                         .apply() | ||||
| 
 | ||||
|             listview1?.setOnItemClickListener { _, _, position, _ -> | ||||
|                 settingsManager.edit() | ||||
|                     .putInt(getString(R.string.prefer_media_type_key), prefValues[position]) | ||||
|                     .apply() | ||||
| 
 | ||||
|                 // Regenerate set homepage | ||||
|                 removeKey(USER_SELECTED_HOMEPAGE_API) | ||||
|                     // Regenerate set homepage | ||||
|                     removeKey(USER_SELECTED_HOMEPAGE_API) | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             next_btt?.setOnClickListener { | ||||
|  |  | |||
|  | @ -88,6 +88,58 @@ object BackupUtils { | |||
|         @JsonProperty("settings") val settings: BackupVars | ||||
|     ) | ||||
| 
 | ||||
|     fun Context.getBackup(): BackupFile { | ||||
|         val allData = getSharedPrefs().all.filter { it.key.isTransferable() } | ||||
|         val allSettings = getDefaultSharedPrefs().all.filter { it.key.isTransferable() } | ||||
| 
 | ||||
|         val allDataSorted = BackupVars( | ||||
|             allData.filter { it.value is Boolean } as? Map<String, Boolean>, | ||||
|             allData.filter { it.value is Int } as? Map<String, Int>, | ||||
|             allData.filter { it.value is String } as? Map<String, String>, | ||||
|             allData.filter { it.value is Float } as? Map<String, Float>, | ||||
|             allData.filter { it.value is Long } as? Map<String, Long>, | ||||
|             allData.filter { it.value as? Set<String> != null } as? Map<String, Set<String>> | ||||
|         ) | ||||
| 
 | ||||
|         val allSettingsSorted = BackupVars( | ||||
|             allSettings.filter { it.value is Boolean } as? Map<String, Boolean>, | ||||
|             allSettings.filter { it.value is Int } as? Map<String, Int>, | ||||
|             allSettings.filter { it.value is String } as? Map<String, String>, | ||||
|             allSettings.filter { it.value is Float } as? Map<String, Float>, | ||||
|             allSettings.filter { it.value is Long } as? Map<String, Long>, | ||||
|             allSettings.filter { it.value as? Set<String> != null } as? Map<String, Set<String>> | ||||
|         ) | ||||
| 
 | ||||
|         return BackupFile( | ||||
|             allDataSorted, | ||||
|             allSettingsSorted | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     fun Context.restore( | ||||
|         backupFile: BackupFile, | ||||
|         restoreSettings: Boolean, | ||||
|         restoreDataStore: Boolean | ||||
|     ) { | ||||
|         if (restoreSettings) { | ||||
|             restoreMap(backupFile.settings._Bool, true) | ||||
|             restoreMap(backupFile.settings._Int, true) | ||||
|             restoreMap(backupFile.settings._String, true) | ||||
|             restoreMap(backupFile.settings._Float, true) | ||||
|             restoreMap(backupFile.settings._Long, true) | ||||
|             restoreMap(backupFile.settings._StringSet, true) | ||||
|         } | ||||
| 
 | ||||
|         if (restoreDataStore) { | ||||
|             restoreMap(backupFile.datastore._Bool) | ||||
|             restoreMap(backupFile.datastore._Int) | ||||
|             restoreMap(backupFile.datastore._String) | ||||
|             restoreMap(backupFile.datastore._Float) | ||||
|             restoreMap(backupFile.datastore._Long) | ||||
|             restoreMap(backupFile.datastore._StringSet) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fun FragmentActivity.backup() { | ||||
|         try { | ||||
|             if (checkWrite()) { | ||||
|  | @ -95,32 +147,7 @@ object BackupUtils { | |||
|                 val date = SimpleDateFormat("yyyy_MM_dd_HH_mm").format(Date(currentTimeMillis())) | ||||
|                 val ext = "json" | ||||
|                 val displayName = "CS3_Backup_${date}" | ||||
| 
 | ||||
|                 val allData = getSharedPrefs().all.filter { it.key.isTransferable() } | ||||
|                 val allSettings = getDefaultSharedPrefs().all.filter { it.key.isTransferable() } | ||||
| 
 | ||||
|                 val allDataSorted = BackupVars( | ||||
|                     allData.filter { it.value is Boolean } as? Map<String, Boolean>, | ||||
|                     allData.filter { it.value is Int } as? Map<String, Int>, | ||||
|                     allData.filter { it.value is String } as? Map<String, String>, | ||||
|                     allData.filter { it.value is Float } as? Map<String, Float>, | ||||
|                     allData.filter { it.value is Long } as? Map<String, Long>, | ||||
|                     allData.filter { it.value as? Set<String> != null } as? Map<String, Set<String>> | ||||
|                 ) | ||||
| 
 | ||||
|                 val allSettingsSorted = BackupVars( | ||||
|                     allSettings.filter { it.value is Boolean } as? Map<String, Boolean>, | ||||
|                     allSettings.filter { it.value is Int } as? Map<String, Int>, | ||||
|                     allSettings.filter { it.value is String } as? Map<String, String>, | ||||
|                     allSettings.filter { it.value is Float } as? Map<String, Float>, | ||||
|                     allSettings.filter { it.value is Long } as? Map<String, Long>, | ||||
|                     allSettings.filter { it.value as? Set<String> != null } as? Map<String, Set<String>> | ||||
|                 ) | ||||
| 
 | ||||
|                 val backupFile = BackupFile( | ||||
|                     allDataSorted, | ||||
|                     allSettingsSorted | ||||
|                 ) | ||||
|                 val backupFile = getBackup() | ||||
| 
 | ||||
|                 val steam = | ||||
|                     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && subDir?.isDownloadDir() == true) { | ||||
|  | @ -251,28 +278,4 @@ object BackupUtils { | |||
|             setKeyRaw(it.key, it.value, isEditingAppSettings) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fun Context.restore( | ||||
|         backupFile: BackupFile, | ||||
|         restoreSettings: Boolean, | ||||
|         restoreDataStore: Boolean | ||||
|     ) { | ||||
|         if (restoreSettings) { | ||||
|             restoreMap(backupFile.settings._Bool, true) | ||||
|             restoreMap(backupFile.settings._Int, true) | ||||
|             restoreMap(backupFile.settings._String, true) | ||||
|             restoreMap(backupFile.settings._Float, true) | ||||
|             restoreMap(backupFile.settings._Long, true) | ||||
|             restoreMap(backupFile.settings._StringSet, true) | ||||
|         } | ||||
| 
 | ||||
|         if (restoreDataStore) { | ||||
|             restoreMap(backupFile.datastore._Bool) | ||||
|             restoreMap(backupFile.datastore._Int) | ||||
|             restoreMap(backupFile.datastore._String) | ||||
|             restoreMap(backupFile.datastore._Float) | ||||
|             restoreMap(backupFile.datastore._Long) | ||||
|             restoreMap(backupFile.datastore._StringSet) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -1,23 +0,0 @@ | |||
| package com.lagradost.cloudstream3.utils | ||||
| 
 | ||||
| /* | ||||
| import android.content.Context | ||||
| import dalvik.system.PathClassLoader | ||||
| import java.io.File | ||||
| open class TestSource { | ||||
|     open fun doMath(): Int { | ||||
|         return 33 | ||||
|     } | ||||
| } | ||||
| object ExtensionManager { | ||||
|     fun getSourceFromDex(context: Context, pkgName: String, file: File): TestSource? { | ||||
|         val loader = PathClassLoader(file.absolutePath, context.classLoader) | ||||
| 
 | ||||
|         val obj = Class.forName(pkgName, false, loader).newInstance() | ||||
|         if (obj is TestSource) { | ||||
|             println("MATH :  ${obj.doMath()}") | ||||
|         } | ||||
| 
 | ||||
|         return null | ||||
|     } | ||||
| }*/ | ||||
|  | @ -6,6 +6,7 @@ import com.lagradost.cloudstream3.TvType | |||
| import com.lagradost.cloudstream3.USER_AGENT | ||||
| import com.lagradost.cloudstream3.app | ||||
| import com.lagradost.cloudstream3.mvvm.logError | ||||
| import com.lagradost.cloudstream3.extractors.* | ||||
| import kotlinx.coroutines.delay | ||||
| import org.jsoup.Jsoup | ||||
| import kotlin.collections.MutableList | ||||
|  | @ -189,7 +190,138 @@ suspend fun loadExtractor( | |||
|     return false | ||||
| } | ||||
| 
 | ||||
| val extractorApis: MutableList<ExtractorApi> = arrayListOf() | ||||
| val extractorApis: MutableList<ExtractorApi> = arrayListOf( | ||||
|     //AllProvider(), | ||||
|     WcoStream(), | ||||
|     Vidstreamz(), | ||||
|     Vizcloud(), | ||||
|     Vizcloud2(), | ||||
|     VizcloudOnline(), | ||||
|     VizcloudXyz(), | ||||
|     VizcloudLive(), | ||||
|     VizcloudInfo(), | ||||
|     MwvnVizcloudInfo(), | ||||
|     VizcloudDigital(), | ||||
|     VizcloudCloud(), | ||||
|     VizcloudSite(), | ||||
|     VideoVard(), | ||||
|     VideovardSX(), | ||||
|     Mp4Upload(), | ||||
|     StreamTape(), | ||||
| 
 | ||||
|     //mixdrop extractors | ||||
|     MixDropBz(), | ||||
|     MixDropCh(), | ||||
|     MixDropTo(), | ||||
| 
 | ||||
|     MixDrop(), | ||||
| 
 | ||||
|     Mcloud(), | ||||
|     XStreamCdn(), | ||||
| 
 | ||||
|     StreamSB(), | ||||
|     StreamSB1(), | ||||
|     StreamSB2(), | ||||
|     StreamSB3(), | ||||
|     StreamSB4(), | ||||
|     StreamSB5(), | ||||
|     StreamSB6(), | ||||
|     StreamSB7(), | ||||
|     StreamSB8(), | ||||
|     StreamSB9(), | ||||
|     StreamSB10(), | ||||
|     SBfull(), | ||||
|     // Streamhub(), cause Streamhub2() works | ||||
|     Streamhub2(), | ||||
|     Ssbstream(), | ||||
| 
 | ||||
|     Fastream(), | ||||
|      | ||||
|     FEmbed(), | ||||
|     FeHD(), | ||||
|     Fplayer(), | ||||
|     DBfilm(), | ||||
|     Luxubu(), | ||||
|     LayarKaca(), | ||||
|     //  WatchSB(), 'cause StreamSB.kt works | ||||
|     Uqload(), | ||||
|     Uqload1(), | ||||
|     Evoload(), | ||||
|     Evoload1(), | ||||
|     VoeExtractor(), | ||||
|     // UpstreamExtractor(), GenericM3U8.kt works | ||||
| 
 | ||||
|     Tomatomatela(), | ||||
|     Cinestart(), | ||||
|     OkRu(), | ||||
|     OkRuHttps(), | ||||
| 
 | ||||
|     // dood extractors | ||||
|     DoodCxExtractor(), | ||||
|     DoodPmExtractor(), | ||||
|     DoodToExtractor(), | ||||
|     DoodSoExtractor(), | ||||
|     DoodLaExtractor(), | ||||
|     DoodWsExtractor(), | ||||
|     DoodShExtractor(), | ||||
|     DoodWatchExtractor(), | ||||
| 
 | ||||
|     AsianLoad(), | ||||
| 
 | ||||
|     // GenericM3U8(), | ||||
|     Jawcloud(), | ||||
|     Zplayer(), | ||||
|     ZplayerV2(), | ||||
|     Upstream(), | ||||
| 
 | ||||
|     Maxstream(), | ||||
|     Tantifilm(), | ||||
|     Userload(), | ||||
|     Supervideo(), | ||||
|     GuardareStream(), | ||||
| 
 | ||||
|     // StreamSB.kt works | ||||
|     //  SBPlay(), | ||||
|     //  SBPlay1(), | ||||
|     //  SBPlay2(), | ||||
| 
 | ||||
|     PlayerVoxzer(), | ||||
| 
 | ||||
|     BullStream(), | ||||
|     GMPlayer(), | ||||
| 
 | ||||
|     Blogger(), | ||||
|     Solidfiles(), | ||||
|     YourUpload(), | ||||
| 
 | ||||
|     Hxfile(), | ||||
|     KotakAnimeid(), | ||||
|     Neonime8n(), | ||||
|     Neonime7n(), | ||||
|     Yufiles(), | ||||
|     Aico(), | ||||
| 
 | ||||
|     JWPlayer(), | ||||
|     Meownime(), | ||||
|     DesuArcg(), | ||||
|     DesuOdchan(), | ||||
|     DesuOdvip(), | ||||
|     DesuDrive(), | ||||
| 
 | ||||
|     Filesim(), | ||||
|     Linkbox(), | ||||
|     Acefile(), | ||||
|     SpeedoStream(), | ||||
| 
 | ||||
|     YoutubeExtractor(), | ||||
|     YoutubeShortLinkExtractor(), | ||||
|     YoutubeMobileExtractor(), | ||||
|     YoutubeNoCookieExtractor(), | ||||
|     Streamlare(), | ||||
|     VidSrcExtractor(), | ||||
|     VidSrcExtractor2(), | ||||
| ) | ||||
| 
 | ||||
| 
 | ||||
| fun getExtractorApiFromName(name: String): ExtractorApi { | ||||
|     for (api in extractorApis) { | ||||
|  |  | |||
|  | @ -71,7 +71,8 @@ object SubtitleHelper { | |||
| 
 | ||||
|     /** ISO_639_1 -> lang*/ | ||||
|     fun fromTwoLettersToLanguage(input: String): String? { | ||||
|         if (input.length != 2) return null | ||||
|         // pr-BR | ||||
|         if (input.substringBefore("-").length != 2) return null | ||||
|         if (ISO_639_1Map.isEmpty()) { | ||||
|             initISO6391Map() | ||||
|         } | ||||
|  | @ -269,6 +270,8 @@ object SubtitleHelper { | |||
|         "pl" to "PL", | ||||
|         "ps" to "AF", | ||||
|         "pt" to "PT", | ||||
|         "pt-pt" to "PT", | ||||
|         "pt-br" to "BR", | ||||
|         "rm" to "CH", | ||||
|         "rn" to "BI", | ||||
|         "ro" to "RO", | ||||
|  | @ -452,7 +455,9 @@ object SubtitleHelper { | |||
|         Language639("Persian", "فارسی", "fa", "fas", "", "fas", ""), | ||||
|         Language639("Polish", "język polski, polszczyzna", "pl", "pol", "pol", "pol", "pols"), | ||||
|         Language639("Pashto", "پښتو", "ps", "pus", "pus", "pus", ""), | ||||
|         Language639("Portuguese", "português", "pt", "por", "por", "por", ""), | ||||
|         Language639("Portuguese", "português", "pt-pt", "por", "por", "por", ""), | ||||
|         // Addition to support Brazilian Portuguese properly, might break other things | ||||
|         Language639("Portuguese (Brazilian)", "português", "pt-br", "por", "por", "por", ""), | ||||
|         Language639("Quechua", "Runa Simi, Kichwa", "qu", "que", "que", "que", ""), | ||||
|         Language639("Romansh", "rumantsch grischun", "rm", "roh", "roh", "roh", ""), | ||||
|         Language639("Kirundi", "Ikirundi", "rn", "run", "run", "run", ""), | ||||
|  |  | |||
|  | @ -228,6 +228,7 @@ | |||
|                 android:layout_gravity="center" | ||||
|                 android:gravity="center" | ||||
|                 android:orientation="horizontal" | ||||
|                 android:layoutDirection="ltr" | ||||
|                 app:layout_constraintBottom_toBottomOf="parent" | ||||
|                 app:layout_constraintLeft_toLeftOf="parent" | ||||
|                 app:layout_constraintRight_toRightOf="parent" | ||||
|  | @ -390,6 +391,7 @@ | |||
|                     android:id="@+id/player_video_bar" | ||||
|                     android:layout_width="match_parent" | ||||
|                     android:layout_height="wrap_content" | ||||
|                     android:layoutDirection="ltr" | ||||
|                     android:orientation="horizontal"> | ||||
| 
 | ||||
|                 <TextView | ||||
|  | @ -532,6 +534,7 @@ | |||
|     <LinearLayout | ||||
|             android:layout_width="match_parent" | ||||
|             android:layout_height="match_parent" | ||||
|             android:layoutDirection="ltr" | ||||
|             android:orientation="horizontal"> | ||||
| 
 | ||||
|         <RelativeLayout | ||||
|  | @ -591,14 +594,14 @@ | |||
|                 tools:visibility="visible"> | ||||
| 
 | ||||
|             <ImageView | ||||
|                     android:id="@+id/player_progressbar_right_icon" | ||||
|                     android:layout_width="wrap_content" | ||||
|                     android:layout_height="wrap_content" | ||||
|                     android:layout_centerHorizontal="true" | ||||
|                     android:layout_marginBottom="220dp" | ||||
|                     android:src="@drawable/ic_baseline_brightness_7_24" | ||||
|                     app:tint="@android:color/white" | ||||
|                     tools:ignore="ContentDescription"> | ||||
|                 android:id="@+id/player_progressbar_right_icon" | ||||
|                 android:layout_width="wrap_content" | ||||
|                 android:layout_height="wrap_content" | ||||
|                 android:layout_centerHorizontal="true" | ||||
|                 android:layout_marginBottom="220dp" | ||||
|                 android:src="@drawable/ic_baseline_brightness_7_24" | ||||
|                 app:tint="@android:color/white" | ||||
|                 tools:ignore="ContentDescription"> | ||||
| 
 | ||||
|             </ImageView> | ||||
| 
 | ||||
|  |  | |||
|  | @ -226,6 +226,7 @@ | |||
|                     android:id="@+id/player_video_bar" | ||||
|                     android:layout_width="match_parent" | ||||
|                     android:layout_height="wrap_content" | ||||
|                     android:layoutDirection="ltr" | ||||
|                     android:orientation="horizontal"> | ||||
| 
 | ||||
|                 <ImageView | ||||
|  |  | |||
|  | @ -44,6 +44,7 @@ | |||
|                 android:id="@+id/lang_icon" | ||||
|                 android:layout_width="wrap_content" | ||||
|                 android:layout_height="match_parent" | ||||
|                 android:layout_marginEnd="5dp" | ||||
|                 android:text="🇷🇼" | ||||
| 
 | ||||
|                 android:textColor="?attr/grayTextColor" | ||||
|  | @ -55,7 +56,7 @@ | |||
|                 android:layout_width="wrap_content" | ||||
|                 android:layout_height="wrap_content" | ||||
|                 android:layout_gravity="center_vertical" | ||||
|                 android:layout_marginStart="5dp" | ||||
|                 android:layout_marginEnd="5dp" | ||||
|                 android:text="v1" | ||||
|                 android:textColor="?attr/grayTextColor" | ||||
|                 android:visibility="gone" | ||||
|  | @ -66,7 +67,7 @@ | |||
|                 android:layout_width="wrap_content" | ||||
|                 android:layout_height="wrap_content" | ||||
|                 android:layout_gravity="center_vertical" | ||||
|                 android:layout_marginStart="5dp" | ||||
|                 android:layout_marginEnd="5dp" | ||||
|                 android:text="100MB" | ||||
|                 android:textColor="?attr/grayTextColor" | ||||
|                 android:visibility="gone" | ||||
|  | @ -76,7 +77,6 @@ | |||
|                 android:id="@+id/nsfw_marker" | ||||
|                 android:layout_width="wrap_content" | ||||
|                 android:layout_height="wrap_content" | ||||
|                 android:layout_marginStart="5dp" | ||||
|                 android:text="@string/is_adult" | ||||
|                 android:textColor="@color/adultColor" | ||||
|                 android:visibility="gone" | ||||
|  |  | |||
|  | @ -29,19 +29,6 @@ | |||
|         <item>4</item> | ||||
|     </array> | ||||
| 
 | ||||
|     <array name="media_type_pref"> | ||||
|         <item>All</item> | ||||
|         <item>Movies and TV</item> | ||||
|         <item>Anime</item> | ||||
|         <item>Documentary</item> | ||||
|     </array> | ||||
|     <array name="media_type_pref_values"> | ||||
|         <item>0</item> | ||||
|         <item>1</item> | ||||
|         <item>2</item> | ||||
|         <item>3</item> | ||||
|     </array> | ||||
| 
 | ||||
|     <array name="limit_title_rez_pref_names"> | ||||
|         <item>@string/resolution_and_title</item> | ||||
|         <item>@string/title</item> | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue