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