diff --git a/.github/ISSUE_TEMPLATE/application-bug.yml b/.github/ISSUE_TEMPLATE/application-bug.yml index 57608890..931db3bd 100644 --- a/.github/ISSUE_TEMPLATE/application-bug.yml +++ b/.github/ISSUE_TEMPLATE/application-bug.yml @@ -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 diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..cd3c2574 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -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. diff --git a/.github/ISSUE_TEMPLATE/provider-bug.yml b/.github/ISSUE_TEMPLATE/provider-bug.yml deleted file mode 100644 index 7b311141..00000000 --- a/.github/ISSUE_TEMPLATE/provider-bug.yml +++ /dev/null @@ -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 diff --git a/.github/workflows/issue-action.yml b/.github/workflows/issue-action.yml new file mode 100644 index 00000000..bfcb10d0 --- /dev/null +++ b/.github/workflows/issue-action.yml @@ -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' + + diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt index ed0e3c99..67283de3 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt @@ -232,26 +232,23 @@ object APIHolder { } fun Context.filterProviderByPreferredMedia(hasHomePageIsRequired: Boolean = true): List { - val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) - val currentPrefMedia = - settingsManager.getInt(this.getString(R.string.prefer_media_type_key), 0) + val default = enumValues().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 diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index 240fa3ae..7fb5cad2 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -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")*/ } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Acefile.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Acefile.kt new file mode 100644 index 00000000..fe46791b --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Acefile.kt @@ -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 { + val sources = mutableListOf() + 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 + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/AsianLoad.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/AsianLoad.kt new file mode 100644 index 00000000..cf16f200 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/AsianLoad.kt @@ -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 { + val extractedLinksList: MutableList = 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 + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Blogger.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Blogger.kt new file mode 100644 index 00000000..cae77322 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Blogger.kt @@ -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 { + val sources = mutableListOf() + with(app.get(url).document) { + this.select("script").map { script -> + if (script.data().contains("\"streams\":[")) { + val data = script.data().substringAfter("\"streams\":[") + .substringBefore("]") + tryParseJson>("[$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 + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/BullStream.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/BullStream.kt new file mode 100644 index 00000000..d4f87f4c --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/BullStream.kt @@ -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? { + 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 "*/*") + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/DoodExtractor.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/DoodExtractor.kt new file mode 100644 index 00000000..c5eaf40e --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/DoodExtractor.kt @@ -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? { + 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("").substringBefore(""))?.groupValues?.get(0) + return listOf( + ExtractorLink( + trueUrl, + this.name, + trueUrl, + mainUrl, + getQualityFromName(quality), + false + ) + ) // links are valid in 8h + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Evolaod.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Evolaod.kt new file mode 100644 index 00000000..4a9f2f52 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Evolaod.kt @@ -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 { + 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, + ) + ) + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Fastream.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Fastream.kt new file mode 100644 index 00000000..16b109be --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Fastream.kt @@ -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? { + val id = Regex("emb\\.html\\?(.*)\\=(enc|)").find(url)?.destructured?.component1() ?: return emptyList() + val sources = mutableListOf() + 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 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Filesim.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Filesim.kt new file mode 100644 index 00000000..5c8af1c5 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Filesim.kt @@ -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 { + val sources = mutableListOf() + 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>("[$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? + ) + +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/GMPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/GMPlayer.kt new file mode 100644 index 00000000..e36a03d3 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/GMPlayer.kt @@ -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? { + 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().videoSource ?: return null + + return M3u8Helper.generateM3u8( + name, + m3u8, + ref, + headers = mapOf("accept" to "*/*") + ) + } + + private data class GmResponse( + val videoSource: String? = null + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/GenericM3U8.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/GenericM3U8.kt new file mode 100644 index 00000000..65cf1be4 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/GenericM3U8.kt @@ -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 { + val response = app.get( + url, interceptor = WebViewResolver( + Regex("""master\.m3u8""") + ) + ) + val sources = mutableListOf() + if (response.url.contains("m3u8")) + M3u8Helper.generateM3u8( + name, + response.url, + url, + headers = response.headers.toMap() + ).forEach { link -> + sources.add(link) + } + return sources + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/GuardareStream.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/GuardareStream.kt new file mode 100644 index 00000000..57435161 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/GuardareStream.kt @@ -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, + ) + + 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? { + val response = app.post(url.replace("/v/","/api/source/"), data = mapOf("d" to mainUrl)).text + val jsonvideodata = AppUtils.parseJson(response) + return jsonvideodata.data.map { + ExtractorLink( + it.file+".${it.type}", + this.name, + it.file+".${it.type}", + mainUrl, + it.label.filter{ it.isDigit() }.toInt(), + false + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Hxfile.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Hxfile.kt new file mode 100644 index 00000000..f5dde774 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Hxfile.kt @@ -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? { + val sources = mutableListOf() + 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>("[$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>("[$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? + ) + +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/JWPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/JWPlayer.kt new file mode 100644 index 00000000..6e6f6516 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/JWPlayer.kt @@ -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? { + val sources = mutableListOf() + 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>("$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? + ) + +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Jawcloud.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Jawcloud.kt new file mode 100644 index 00000000..203a266c --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Jawcloud.kt @@ -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? { + val doc = app.get(url).document + val urlString = doc.select("html body div source").attr("src") + val sources = mutableListOf() + if (urlString.contains("m3u8")) + M3u8Helper.generateM3u8( + name, + urlString, + url, + headers = app.get(url).headers.toMap() + ).forEach { link -> sources.add(link) } + return sources + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Linkbox.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Linkbox.kt new file mode 100644 index 00000000..52fc5532 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Linkbox.kt @@ -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 { + val id = url.substringAfter("id=") + val sources = mutableListOf() + + app.get("$mainUrl/api/open/get_url?itemId=$id", referer=url).parsedSafe()?.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?, + ) + + data class Responses( + @JsonProperty("data") val data: Data?, + ) + +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/M3u8Manifest.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/M3u8Manifest.kt new file mode 100644 index 00000000..545dc309 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/M3u8Manifest.kt @@ -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> { + val data: ArrayList> = 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 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Maxstream.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Maxstream.kt new file mode 100644 index 00000000..51b0827d --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Maxstream.kt @@ -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? { + val extractedLinksList: MutableList = mutableListOf() + val response = app.get(url).text + val jstounpack = Regex("cript\">eval((.|\\n)*?)").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 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Mcloud.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Mcloud.kt new file mode 100644 index 00000000..29d98557 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Mcloud.kt @@ -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 +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/MixDrop.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/MixDrop.kt new file mode 100644 index 00000000..cd7e2e26 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/MixDrop.kt @@ -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? { + 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 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Mp4Upload.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Mp4Upload.kt new file mode 100644 index 00000000..68a4a103 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Mp4Upload.kt @@ -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? { + 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 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/MultiQuality.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/MultiQuality.kt new file mode 100644 index 00000000..0c0b5c68 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/MultiQuality.kt @@ -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 { + val extractedLinksList: MutableList = 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 + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/OkRuExtractor.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/OkRuExtractor.kt new file mode 100644 index 00000000..70e87fbf --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/OkRuExtractor.kt @@ -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 = 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? { + val doc = app.get(url).document + val sources = ArrayList() + val datajson = doc.select("div[data-options]").attr("data-options") + if (datajson.isNotBlank()) { + val main = parseJson(datajson) + val metadatajson = parseJson(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 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Pelisplus.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Pelisplus.kt new file mode 100644 index 00000000..cc743d5e --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Pelisplus.kt @@ -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 = 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 + } + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/PlayerVoxzer.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/PlayerVoxzer.kt new file mode 100644 index 00000000..950dbfef --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/PlayerVoxzer.kt @@ -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? { + val listurl = url.replace("/view/","/list/") + val urltext = app.get(listurl, referer = url).text + val m3u8regex = Regex("((https:|http:)\\/\\/.*\\.m3u8)") + val sources = mutableListOf() + 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 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/SBPlay.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/SBPlay.kt new file mode 100644 index 00000000..6bc768df --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/SBPlay.kt @@ -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 { + val response = app.get(url, referer = referer).text + val document = Jsoup.parse(response) + + val links = ArrayList() + + 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 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Solidfiles.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Solidfiles.kt new file mode 100644 index 00000000..849f5fc8 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Solidfiles.kt @@ -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 { + val sources = mutableListOf() + 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("{$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 + ) + +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/SpeedoStream.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/SpeedoStream.kt new file mode 100644 index 00000000..6153a7c1 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/SpeedoStream.kt @@ -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 { + val sources = mutableListOf() + 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(data)?.let { + M3u8Helper.generateM3u8( + name, + it.file, + "$mainUrl/", + ).forEach { m3uData -> sources.add(m3uData) } + } + } + } + return sources + } + + private data class File( + @JsonProperty("file") val file: String, + ) + + +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/StreamSB.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/StreamSB.kt new file mode 100644 index 00000000..da3ef278 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/StreamSB.kt @@ -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?, + @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? { + 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
(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 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/StreamTape.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/StreamTape.kt new file mode 100644 index 00000000..af436ff3 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/StreamTape.kt @@ -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? { + 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 + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Streamhub.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Streamhub.kt new file mode 100644 index 00000000..2765ae17 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Streamhub.kt @@ -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? { + val response = app.get(url).text + Regex("eval((.|\\n)*?)").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 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Streamlare.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Streamlare.kt new file mode 100644 index 00000000..396327ba --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Streamlare.kt @@ -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? = 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? { + val id = embedRegex.find(url)!!.groupValues[1] + val json = app.post( + "${mainUrl}api/video/stream/get", + requestBody = """{"id":"$id"}""".toRequestBody(RequestBodyTypes.JSON.toMediaTypeOrNull()) + ).parsed() + 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 + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Supervideo.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Supervideo.kt new file mode 100644 index 00000000..955345aa --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Supervideo.kt @@ -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? { + val extractedLinksList: MutableList = mutableListOf() + val response = app.get(url).text + val jstounpack = Regex("eval((.|\\n)*?)").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>(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 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Tantifilm.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Tantifilm.kt new file mode 100644 index 00000000..d721dea8 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Tantifilm.kt @@ -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, + @JsonProperty("captions")val captions : List, + @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? { + val link = "$mainUrl/api/source/${url.substringAfterLast("/")}" + val response = app.post(link).text.replace("""\""","") + val jsonvideodata = parseJson(response) + return jsonvideodata.data.map { + ExtractorLink( + it.file+".${it.type}", + this.name, + it.file+".${it.type}", + mainUrl, + it.label.filter{ it.isDigit() }.toInt(), + false + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Tomatomatela.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Tomatomatela.kt new file mode 100644 index 00000000..20bd69ba --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Tomatomatela.kt @@ -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? { + val link = url.replace("$mainUrl/embed.html#","$mainUrl/$details") + val server = app.get(link, allowRedirects = false).text + val json = parseJson(server) + if (json.status == 200) return listOf( + ExtractorLink( + name, + name, + json.file, + "", + Qualities.Unknown.value, + isM3u8 = false + ) + ) + return null + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/UpstreamExtractor.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/UpstreamExtractor.kt new file mode 100644 index 00000000..79c657b6 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/UpstreamExtractor.kt @@ -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 { + // WIP: m3u8 link fetched but sometimes not playing + //Log.i(this.name, "Result => (no extractor) ${url}") + val sources: MutableList = 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 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Uqload.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Uqload.kt new file mode 100644 index 00000000..e5d2875f --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Uqload.kt @@ -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? { + 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 + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Userload.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Userload.kt new file mode 100644 index 00000000..7752b824 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Userload.kt @@ -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 { + var counter = 0 + val array = ArrayList() + 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 { + 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() + + 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? { + + val extractedLinksList: MutableList = mutableListOf() + + val response = app.get(url).text + val jsToUnpack = Regex("ext/javascript\">eval((.|\\n)*?)").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 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/VidSrcExtractor.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/VidSrcExtractor.kt new file mode 100644 index 00000000..7b087157 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/VidSrcExtractor.kt @@ -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) + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/VideoVard.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/VideoVard.kt new file mode 100644 index 00000000..41e77967 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/VideoVard.kt @@ -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 { + val id = url.substringAfter("e/").substringBefore("/") + val sources = mutableListOf() + val hash = app.get("$mainUrl/api/make/download/$id").parsed() + 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() + 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() + 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 { + 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, a91: List): MutableList { + 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 { + val empList = mutableListOf() + 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): List { + val empList = mutableListOf() + 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): List { + val tempList = mutableListOf() + 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): List { + val evenOdd = a46[0].toInt().mod(2) + return (1 until (a46.size - evenOdd)).map { + a46[it] + } + } + + private fun xorBlocks(a76: List, a77: List): List { + 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, list2: List): MutableList { + 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 { + 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() + + 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 + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Vidstream.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Vidstream.kt new file mode 100644 index 00000000..1d853b2d --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Vidstream.kt @@ -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 = 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 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/VoeExtractor.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/VoeExtractor.kt new file mode 100644 index 00000000..d2f3f832 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/VoeExtractor.kt @@ -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 { + val extractedLinksList: MutableList = 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(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 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/WatchSB.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/WatchSB.kt new file mode 100644 index 00000000..20f0a2b5 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/WatchSB.kt @@ -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 { + val response = app.get( + url, interceptor = WebViewResolver( + Regex("""master\.m3u8""") + ) + ) + + return generateM3u8(name, response.url, url, headers = response.headers.toMap()) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/WcoStream.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/WcoStream.kt new file mode 100644 index 00000000..d99485ea --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/WcoStream.kt @@ -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 { + 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) + 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().data.media.sources.map { + ExtractorLink(name, it.file,it.file,host,Qualities.Unknown.value,it.file.contains(".m3u8")) + } + + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/XStreamCdn.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/XStreamCdn.kt new file mode 100644 index 00000000..89f4ca67 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/XStreamCdn.kt @@ -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? + ) + + override fun getExtractorUrl(id: String): String { + return "$domainUrl/api/source/$id" + } + + override suspend fun getUrl(url: String, referer: String?): List { + 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 = 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(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 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/YourUpload.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/YourUpload.kt new file mode 100644 index 00000000..3c564f67 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/YourUpload.kt @@ -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 { + val sources = mutableListOf() + 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( + "{${ + 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, + ) + +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/YoutubeExtractor.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/YoutubeExtractor.kt new file mode 100644 index 00000000..23704e90 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/YoutubeExtractor.kt @@ -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> = mutableMapOf() + private var ytVideosSubtitles: MutableMap> = 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) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Zplayer.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Zplayer.kt new file mode 100644 index 00000000..6108d2c5 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Zplayer.kt @@ -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 { + val doc = app.get(url).document + val sources = mutableListOf() + 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 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/helper/AsianEmbedHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/helper/AsianEmbedHelper.kt new file mode 100644 index 00000000..e70a9474 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/helper/AsianEmbedHelper.kt @@ -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") + } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/helper/NineAnimeHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/helper/NineAnimeHelper.kt new file mode 100644 index 00000000..be75375f --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/helper/NineAnimeHelper.kt @@ -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") +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/helper/VstreamhubHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/helper/VstreamhubHelper.kt new file mode 100644 index 00000000..5c2d6e7c --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/helper/VstreamhubHelper.kt @@ -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}") + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/helper/WcoHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/helper/WcoHelper.kt new file mode 100644 index 00000000..768fa1f6 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/helper/WcoHelper.kt @@ -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()?.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()?.also { setKey(BACKUP_KEY_DATA, it) } ?: getKey( + BACKUP_KEY_DATA + ) + } + + suspend fun getNewWcoKey(): NewExternalKeys? { + getNewKeys() + return newKeys + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/OpenSubtitlesApi.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/OpenSubtitlesApi.kt index ed48ad04..bfa65f62 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/OpenSubtitlesApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/OpenSubtitlesApi.kt @@ -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( +// "pt" to "pt-PT", +// "pt" to "pt-BR" ) private fun fixLanguage(language: String?) : String? { return languageExceptions[language] ?: language diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt index d0500e83..c0c24f85 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt @@ -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), diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsLang.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsLang.kt index f011bd40..ccfd55b3 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsLang.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsLang.kt @@ -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().sorted().map { it.name } + val default = enumValues().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 } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginAdapter.kt index 8fa18b0c..d49ea10c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginAdapter.kt @@ -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 } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentMedia.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentMedia.kt index 8d152886..257ce5c1 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentMedia.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentMedia.kt @@ -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(this, R.layout.sort_bottom_single_choice) - val currentPrefMedia = - settingsManager.getInt(getString(R.string.prefer_media_type_key), 0) + val names = enumValues().sorted().map { it.name } + val selected = mutableListOf() - 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 { diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/BackupUtils.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/BackupUtils.kt index 2d641f25..48d96bb5 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/BackupUtils.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/BackupUtils.kt @@ -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, + allData.filter { it.value is Int } as? Map, + allData.filter { it.value is String } as? Map, + allData.filter { it.value is Float } as? Map, + allData.filter { it.value is Long } as? Map, + allData.filter { it.value as? Set != null } as? Map> + ) + + val allSettingsSorted = BackupVars( + allSettings.filter { it.value is Boolean } as? Map, + allSettings.filter { it.value is Int } as? Map, + allSettings.filter { it.value is String } as? Map, + allSettings.filter { it.value is Float } as? Map, + allSettings.filter { it.value is Long } as? Map, + allSettings.filter { it.value as? Set != null } as? Map> + ) + + 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, - allData.filter { it.value is Int } as? Map, - allData.filter { it.value is String } as? Map, - allData.filter { it.value is Float } as? Map, - allData.filter { it.value is Long } as? Map, - allData.filter { it.value as? Set != null } as? Map> - ) - - val allSettingsSorted = BackupVars( - allSettings.filter { it.value is Boolean } as? Map, - allSettings.filter { it.value is Int } as? Map, - allSettings.filter { it.value is String } as? Map, - allSettings.filter { it.value is Float } as? Map, - allSettings.filter { it.value is Long } as? Map, - allSettings.filter { it.value as? Set != null } as? Map> - ) - - 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) - } - } } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/ExtensionManager.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/ExtensionManager.kt deleted file mode 100644 index 497c72c1..00000000 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/ExtensionManager.kt +++ /dev/null @@ -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 - } -}*/ \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt index 16645547..de3c013a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt @@ -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 = arrayListOf() +val extractorApis: MutableList = 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) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/SubtitleHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/SubtitleHelper.kt index 13b72664..33f1b6ff 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/SubtitleHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/SubtitleHelper.kt @@ -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", ""), diff --git a/app/src/main/res/layout/player_custom_layout.xml b/app/src/main/res/layout/player_custom_layout.xml index bbadab09..5e10e471 100644 --- a/app/src/main/res/layout/player_custom_layout.xml +++ b/app/src/main/res/layout/player_custom_layout.xml @@ -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"> + 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"> diff --git a/app/src/main/res/layout/player_custom_layout_tv.xml b/app/src/main/res/layout/player_custom_layout_tv.xml index de010761..1abfafed 100644 --- a/app/src/main/res/layout/player_custom_layout_tv.xml +++ b/app/src/main/res/layout/player_custom_layout_tv.xml @@ -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"> 4 - - All - Movies and TV - Anime - Documentary - - - 0 - 1 - 2 - 3 - - @string/resolution_and_title @string/title