forked from recloudstream/cloudstream
		
	Merge remote-tracking branch 'origin/master'
This commit is contained in:
		
						commit
						e5515b575c
					
				
					 22 changed files with 1010 additions and 437 deletions
				
			
		
							
								
								
									
										75
									
								
								.github/site-list.py
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										75
									
								
								.github/site-list.py
									
										
									
									
										vendored
									
									
								
							|  | @ -1,63 +1,54 @@ | ||||||
| #!/usr/bin/python3 | #!/usr/bin/python3 | ||||||
| 
 | 
 | ||||||
| from glob import glob | from glob import glob | ||||||
| from re import findall, compile, sub, DOTALL | from re import findall, compile, DOTALL | ||||||
|  | from json import dump, load | ||||||
| from typing import List, Dict | from typing import List, Dict | ||||||
| 
 | 
 | ||||||
| # Globals | # Globals | ||||||
| URL_REGEX = compile( | URL_REGEX = compile( | ||||||
|     "override\sva[lr]\smainUrl[^\"']+[\"'](https?://[a-zA-Z0-9\.-]+)[\"']") |     "override\sva[lr]\smainUrl[^\"']+[\"'](https?://[a-zA-Z0-9\.-]+)[\"']") | ||||||
| NAME_REGEX = compile("class (.+?) ?: \w+\(\)\s\{") | NAME_REGEX = compile("([A-Za-z0-9]+)(?:.kt)$") | ||||||
| START_MARKER = "<!--SITE LIST START-->" | JSON_PATH = "docs/providers.json" | ||||||
| END_MARKER = "<!--SITE LIST END-->" |  | ||||||
| GLOB = "app/src/main/java/com/lagradost/cloudstream3/*providers/*Provider.kt" | GLOB = "app/src/main/java/com/lagradost/cloudstream3/*providers/*Provider.kt" | ||||||
| MAIN_API = "app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt" |  | ||||||
| API_REGEX = compile( |  | ||||||
|     "val\s*allProviders.*?{\s.*?arrayListOf\(([\W\w]*?)\)\s*\n*\s*}", DOTALL) |  | ||||||
| 
 | 
 | ||||||
| sites: Dict[str, str] = {} | old_sites: Dict[str, Dict] = load(open(JSON_PATH, "r", encoding="utf-8")) | ||||||
| enabled_sites: List[str] = [] | sites: Dict[str, Dict] = {} | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| with open(MAIN_API, "r", encoding="utf-8") as f: |  | ||||||
|     apis = findall(API_REGEX, f.read()) |  | ||||||
|     for api_list in apis: |  | ||||||
|         for api in api_list.split("\n"): |  | ||||||
|             if not api.strip() or api.strip().startswith("/"): |  | ||||||
|                 continue |  | ||||||
|             enabled_sites.append(api.strip().split("(")[0]) |  | ||||||
| 
 | 
 | ||||||
|  | # parse all *Provider.kt files | ||||||
| for path in glob(GLOB): | for path in glob(GLOB): | ||||||
|     with open(path, "r", encoding='utf-8') as file: |     with open(path, "r", encoding='utf-8') as file: | ||||||
|         try: |         try: | ||||||
|             site_text: str = file.read() |             site_text: str = file.read() | ||||||
|             name: List[str] = findall(NAME_REGEX, site_text) |             name: str = findall(NAME_REGEX, path)[0] | ||||||
|             provider_text: str = findall(URL_REGEX, site_text) |             provider_url: str = [*findall(URL_REGEX, site_text), ""][0] | ||||||
| 
 | 
 | ||||||
|             if name: |             if name in old_sites.keys():  # if already in previous list use old status and name | ||||||
|                 if name[0] not in enabled_sites: |                 sites[name] = { | ||||||
|                     continue |                     "name": old_sites[name]['name'], | ||||||
|                 sites[name[0]] = provider_text[0] |                     "url": provider_url if provider_url else old_sites[name]['url'], | ||||||
|  |                     "status": old_sites[name]['status'] | ||||||
|  |                 } | ||||||
|  |             else: # if not in previous list add with new data | ||||||
|  |                 display_name = name | ||||||
|  |                 if display_name.endswith("Provider"): | ||||||
|  |                     display_name = display_name[:-len("Provider")] | ||||||
|  |                 sites[name] = { | ||||||
|  |                     "name": display_name, | ||||||
|  |                     "url": provider_url if provider_url else "", | ||||||
|  |                     "status": 1 | ||||||
|  |                 } | ||||||
| 
 | 
 | ||||||
|         except Exception as ex: |         except Exception as ex: | ||||||
|             print("{0}: {1}".format(path, ex)) |             print("{0}: {1}".format(path, ex)) | ||||||
|              |              | ||||||
|  | # add sites from old_sites that are missing in new list | ||||||
|  | for name in old_sites.keys(): | ||||||
|  |     if name not in sites.keys(): | ||||||
|  |         sites[name] = { | ||||||
|  |             "name": old_sites[name]['name'], | ||||||
|  |             "url": old_sites[name]['url'], | ||||||
|  |             "status": old_sites[name]['status'] | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
| with open("README.md", "r+", encoding='utf-8') as readme: | dump(sites, open(JSON_PATH, "w+", encoding="utf-8"), indent=4, sort_keys=True) | ||||||
|     raw = readme.read() |  | ||||||
|     if START_MARKER not in raw or END_MARKER not in raw: |  | ||||||
|         raise RuntimeError("Missing start and end markers") |  | ||||||
|     readme.seek(0) |  | ||||||
| 
 |  | ||||||
|     readme.write(raw.split(START_MARKER)[0]) |  | ||||||
|     readme.write(START_MARKER+"\n") |  | ||||||
| 
 |  | ||||||
|     for site in enabled_sites: |  | ||||||
|         if site in sites: |  | ||||||
|             readme.write( |  | ||||||
|                 "- [{0}]({1}) \n".format(sub("^https?://(?:www\.)?", "", sites[site]), sites[site])) |  | ||||||
| 
 |  | ||||||
|     readme.write(END_MARKER) |  | ||||||
|     readme.write(raw.split(END_MARKER)[-1]) |  | ||||||
| 
 |  | ||||||
|     readme.truncate() |  | ||||||
|  |  | ||||||
							
								
								
									
										4
									
								
								.github/workflows/site_list.yml
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/site_list.yml
									
										
									
									
										vendored
									
									
								
							|  | @ -4,10 +4,10 @@ on: | ||||||
|   push: |   push: | ||||||
|     branches: [ master ] |     branches: [ master ] | ||||||
|     paths: |     paths: | ||||||
|       - 'README.md' |  | ||||||
|       - 'app/src/main/java/com/lagradost/cloudstream3/*providers/*Provider.kt' |       - 'app/src/main/java/com/lagradost/cloudstream3/*providers/*Provider.kt' | ||||||
|       - '.github/workflows/site_list.yml' |       - '.github/workflows/site_list.yml' | ||||||
|       - '.github/site-list.py' |       - '.github/site-list.py' | ||||||
|  |       - 'docs/providers.json' | ||||||
|       - 'app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt' |       - 'app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt' | ||||||
| 
 | 
 | ||||||
| concurrency:  | concurrency:  | ||||||
|  | @ -19,7 +19,7 @@ jobs: | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     steps: |     steps: | ||||||
|     - uses: actions/checkout@v2 |     - uses: actions/checkout@v2 | ||||||
|     - name: Edit README.md |     - name: Edit providers.json | ||||||
|       run: | |       run: | | ||||||
|         python3 .github/site-list.py |         python3 .github/site-list.py | ||||||
|     - name: Commit to the repo |     - name: Commit to the repo | ||||||
|  |  | ||||||
							
								
								
									
										52
									
								
								README.md
									
										
									
									
									
								
							
							
						
						
									
										52
									
								
								README.md
									
										
									
									
									
								
							|  | @ -58,53 +58,5 @@ It merely scrapes 3rd-party websites that are publicly accessable via any regula | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| ***Sites used:*** | ***Sites used:*** | ||||||
| https://lagradost.github.io/CloudStream-3/ | 
 | ||||||
| <!-- Do not remove those two comments --> | Look [here](https://lagradost.github.io/CloudStream-3/) for a comprehensive list | ||||||
| <!--SITE LIST START--> |  | ||||||
| - [pelisplus.icu](https://pelisplus.icu)  |  | ||||||
| - [pelismart.com](https://pelismart.com)  |  | ||||||
| - [melomovie.com](https://melomovie.com)  |  | ||||||
| - [doramasyt.com](https://doramasyt.com)  |  | ||||||
| - [cuevana3.me](https://cuevana3.me)  |  | ||||||
| - [pelisflix.li](https://pelisflix.li)  |  | ||||||
| - [seriesflix.video](https://seriesflix.video)  |  | ||||||
| - [ihavenotv.com](https://ihavenotv.com)  |  | ||||||
| - [lookmovie.io](https://lookmovie.io)  |  | ||||||
| - [vmovee.watch](https://www.vmovee.watch)  |  | ||||||
| - [allmoviesforyou.net](https://allmoviesforyou.net)  |  | ||||||
| - [vidembed.cc](https://vidembed.cc)  |  | ||||||
| - [vf-film.me](https://vf-film.me)  |  | ||||||
| - [vf-serie.org](https://vf-serie.org)  |  | ||||||
| - [asianembed.io](https://asianembed.io)  |  | ||||||
| - [asiaflix.app](https://asiaflix.app)  |  | ||||||
| - [fmovies.to](https://fmovies.to)  |  | ||||||
| - [filman.cc](https://filman.cc)  |  | ||||||
| - [dopebox.to](https://dopebox.to)  |  | ||||||
| - [pinoymoviepedia.ru](https://pinoymoviepedia.ru)  |  | ||||||
| - [pinoy-hd.xyz](https://www.pinoy-hd.xyz)  |  | ||||||
| - [pinoymovies.es](https://pinoymovies.es)  |  | ||||||
| - [trailers.to](https://trailers.to)  |  | ||||||
| - [2embed.ru](https://www.2embed.ru)  |  | ||||||
| - [dramasee.net](https://dramasee.net)  |  | ||||||
| - [watchasian.sh](https://watchasian.sh)  |  | ||||||
| - [kdramahood.com](https://kdramahood.com)  |  | ||||||
| - [akwam.to](https://akwam.to)  |  | ||||||
| - [mycima.tv](https://mycima.tv)  |  | ||||||
| - [egy.best](https://egy.best)  |  | ||||||
| - [hdm.to](https://hdm.to)  |  | ||||||
| - [theflix.to](https://theflix.to)  |  | ||||||
| - [v2.apimdb.net](https://v2.apimdb.net)  |  | ||||||
| - [wcostream.com](https://www.wcostream.com)  |  | ||||||
| - [gogoanime.film](https://gogoanime.film)  |  | ||||||
| - [allanime.site](https://allanime.site)  |  | ||||||
| - [animekisa.in](https://animekisa.in)  |  | ||||||
| - [animeflick.net](https://animeflick.net)  |  | ||||||
| - [tenshi.moe](https://tenshi.moe)  |  | ||||||
| - [wcostream.cc](https://wcostream.cc)  |  | ||||||
| - [9anime.id](https://9anime.id)  |  | ||||||
| - [animeworld.tv](https://www.animeworld.tv)  |  | ||||||
| - [zoro.to](https://zoro.to)  |  | ||||||
| - [bestdubbedanime.com](https://bestdubbedanime.com)  |  | ||||||
| - [monoschinos2.com](https://monoschinos2.com)  |  | ||||||
| - [kawaiifu.com](https://kawaiifu.com)  |  | ||||||
| <!--SITE LIST END--> |  | ||||||
|  |  | ||||||
|  | @ -19,6 +19,7 @@ import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.malApi | ||||||
| import com.lagradost.cloudstream3.ui.player.SubtitleData | import com.lagradost.cloudstream3.ui.player.SubtitleData | ||||||
| import com.lagradost.cloudstream3.utils.AppUtils.toJson | import com.lagradost.cloudstream3.utils.AppUtils.toJson | ||||||
| import com.lagradost.cloudstream3.utils.ExtractorLink | import com.lagradost.cloudstream3.utils.ExtractorLink | ||||||
|  | import okhttp3.Headers | ||||||
| import okhttp3.Interceptor | import okhttp3.Interceptor | ||||||
| import java.text.SimpleDateFormat | import java.text.SimpleDateFormat | ||||||
| import java.util.* | import java.util.* | ||||||
|  | @ -107,6 +108,7 @@ object APIHolder { | ||||||
|             MonoschinosProvider(), |             MonoschinosProvider(), | ||||||
|             KawaiifuProvider(), // disabled due to cloudflare |             KawaiifuProvider(), // disabled due to cloudflare | ||||||
|             //MultiAnimeProvider(), |             //MultiAnimeProvider(), | ||||||
|  | 	        NginxProvider(), | ||||||
|         ) |         ) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -307,6 +309,7 @@ const val PROVIDER_STATUS_DOWN = 0 | ||||||
| data class ProvidersInfoJson( | data class ProvidersInfoJson( | ||||||
|     @JsonProperty("name") var name: String, |     @JsonProperty("name") var name: String, | ||||||
|     @JsonProperty("url") var url: String, |     @JsonProperty("url") var url: String, | ||||||
|  |     @JsonProperty("credentials") var credentials: String? = null, | ||||||
|     @JsonProperty("status") var status: Int, |     @JsonProperty("status") var status: Int, | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -319,6 +322,7 @@ abstract class MainAPI { | ||||||
|     fun overrideWithNewData(data: ProvidersInfoJson) { |     fun overrideWithNewData(data: ProvidersInfoJson) { | ||||||
|         this.name = data.name |         this.name = data.name | ||||||
|         this.mainUrl = data.url |         this.mainUrl = data.url | ||||||
|  | 	    this.storedCredentials = data.credentials | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     init { |     init { | ||||||
|  | @ -329,6 +333,7 @@ abstract class MainAPI { | ||||||
| 
 | 
 | ||||||
|     open var name = "NONE" |     open var name = "NONE" | ||||||
|     open var mainUrl = "NONE" |     open var mainUrl = "NONE" | ||||||
|  |     open var storedCredentials: String? = null | ||||||
| 
 | 
 | ||||||
|     //open val uniqueId : Int by lazy { this.name.hashCode() } // in case of duplicate providers you can have a shared id |     //open val uniqueId : Int by lazy { this.name.hashCode() } // in case of duplicate providers you can have a shared id | ||||||
| 
 | 
 | ||||||
|  | @ -591,17 +596,22 @@ fun getQualityFromString(string: String?): SearchQuality? { | ||||||
|         "cam" -> SearchQuality.Cam |         "cam" -> SearchQuality.Cam | ||||||
|         "camrip" -> SearchQuality.CamRip |         "camrip" -> SearchQuality.CamRip | ||||||
|         "hdcam" -> SearchQuality.HdCam |         "hdcam" -> SearchQuality.HdCam | ||||||
|  |         "hdtc" -> SearchQuality.HdCam | ||||||
|  |         "hdts" -> SearchQuality.HdCam | ||||||
|         "highquality" -> SearchQuality.HQ |         "highquality" -> SearchQuality.HQ | ||||||
|         "hq" -> SearchQuality.HQ |         "hq" -> SearchQuality.HQ | ||||||
|         "highdefinition" -> SearchQuality.HD |         "highdefinition" -> SearchQuality.HD | ||||||
|         "hdrip" -> SearchQuality.HD |         "hdrip" -> SearchQuality.HD | ||||||
|         "hd" -> SearchQuality.HD |         "hd" -> SearchQuality.HD | ||||||
|  |         "hdtv" -> SearchQuality.HD | ||||||
|         "rip" -> SearchQuality.CamRip |         "rip" -> SearchQuality.CamRip | ||||||
|         "telecine" -> SearchQuality.Telecine |         "telecine" -> SearchQuality.Telecine | ||||||
|         "tc" -> SearchQuality.Telecine |         "tc" -> SearchQuality.Telecine | ||||||
|         "telesync" -> SearchQuality.Telesync |         "telesync" -> SearchQuality.Telesync | ||||||
|         "ts" -> SearchQuality.Telesync |         "ts" -> SearchQuality.Telesync | ||||||
|         "dvd" -> SearchQuality.DVD |         "dvd" -> SearchQuality.DVD | ||||||
|  |         "dvdrip" -> SearchQuality.DVD | ||||||
|  |         "dvdscr" -> SearchQuality.DVD | ||||||
|         "blueray" -> SearchQuality.BlueRay |         "blueray" -> SearchQuality.BlueRay | ||||||
|         "bluray" -> SearchQuality.BlueRay |         "bluray" -> SearchQuality.BlueRay | ||||||
|         "br" -> SearchQuality.BlueRay |         "br" -> SearchQuality.BlueRay | ||||||
|  | @ -613,6 +623,7 @@ fun getQualityFromString(string: String?): SearchQuality? { | ||||||
|         "wp" -> SearchQuality.WorkPrint |         "wp" -> SearchQuality.WorkPrint | ||||||
|         "workprint" -> SearchQuality.WorkPrint |         "workprint" -> SearchQuality.WorkPrint | ||||||
|         "webrip" -> SearchQuality.WebRip |         "webrip" -> SearchQuality.WebRip | ||||||
|  |         "webdl" -> SearchQuality.WebRip | ||||||
|         "web" -> SearchQuality.WebRip |         "web" -> SearchQuality.WebRip | ||||||
|         "hdr" -> SearchQuality.HDR |         "hdr" -> SearchQuality.HDR | ||||||
|         "sdr" -> SearchQuality.SDR |         "sdr" -> SearchQuality.SDR | ||||||
|  |  | ||||||
|  | @ -64,12 +64,14 @@ import com.lagradost.cloudstream3.utils.UIHelper.getResourceColor | ||||||
| import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard | import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard | ||||||
| import com.lagradost.cloudstream3.utils.UIHelper.navigate | import com.lagradost.cloudstream3.utils.UIHelper.navigate | ||||||
| import com.lagradost.cloudstream3.utils.UIHelper.requestRW | import com.lagradost.cloudstream3.utils.UIHelper.requestRW | ||||||
|  | import com.lagradost.cloudstream3.movieproviders.NginxProvider | ||||||
| import kotlinx.android.synthetic.main.activity_main.* | import kotlinx.android.synthetic.main.activity_main.* | ||||||
| import kotlinx.android.synthetic.main.fragment_result_swipe.* | import kotlinx.android.synthetic.main.fragment_result_swipe.* | ||||||
| import kotlinx.coroutines.Dispatchers | import kotlinx.coroutines.Dispatchers | ||||||
| import kotlinx.coroutines.runBlocking | import kotlinx.coroutines.runBlocking | ||||||
| import kotlinx.coroutines.withContext | import kotlinx.coroutines.withContext | ||||||
| import java.io.File | import java.io.File | ||||||
|  | import kotlin.collections.HashMap | ||||||
| import kotlin.concurrent.thread | import kotlin.concurrent.thread | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -360,6 +362,57 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { | ||||||
|             e.printStackTrace() |             e.printStackTrace() | ||||||
|             false |             false | ||||||
|         } |         } | ||||||
|  |     fun addNginxToJson(data: java.util.HashMap<String, ProvidersInfoJson>): java.util.HashMap<String, ProvidersInfoJson>? { | ||||||
|  |         try { | ||||||
|  |             val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) | ||||||
|  |             val nginxUrl = | ||||||
|  |                 settingsManager.getString(getString(R.string.nginx_url_key), "nginx_url_key").toString() | ||||||
|  |             val nginxCredentials = | ||||||
|  |                 settingsManager.getString(getString(R.string.nginx_credentials), "nginx_credentials") | ||||||
|  |                     .toString() | ||||||
|  |             val StoredNginxProvider = NginxProvider() | ||||||
|  |             if (nginxUrl == "nginx_url_key" || nginxUrl == "") { // if key is default value, or empty: | ||||||
|  |                 data[StoredNginxProvider.javaClass.simpleName] = ProvidersInfoJson( | ||||||
|  |                     url = nginxUrl, | ||||||
|  |                     name = StoredNginxProvider.name, | ||||||
|  |                     status = PROVIDER_STATUS_DOWN,  // the provider will not be display | ||||||
|  |                     credentials = nginxCredentials | ||||||
|  |                 ) | ||||||
|  |             } else {  // valid url | ||||||
|  |                 data[StoredNginxProvider.javaClass.simpleName] = ProvidersInfoJson( | ||||||
|  |                     url = nginxUrl, | ||||||
|  |                     name = StoredNginxProvider.name, | ||||||
|  |                     status = PROVIDER_STATUS_OK, | ||||||
|  |                     credentials = nginxCredentials | ||||||
|  |                 ) | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             return data | ||||||
|  |         } catch (e: Exception) { | ||||||
|  |             logError(e) | ||||||
|  |             return data | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     fun createNginxJson() : ProvidersInfoJson? { //java.util.HashMap<String, ProvidersInfoJson> | ||||||
|  |         return try { | ||||||
|  |             val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) | ||||||
|  |             val nginxUrl = settingsManager.getString(getString(R.string.nginx_url_key), "nginx_url_key").toString() | ||||||
|  |             val nginxCredentials = settingsManager.getString(getString(R.string.nginx_credentials), "nginx_credentials").toString() | ||||||
|  |             if (nginxUrl == "nginx_url_key" || nginxUrl == "") { // if key is default value or empty: | ||||||
|  |                 null // don't overwrite anything | ||||||
|  |             } else { | ||||||
|  |                 ProvidersInfoJson( | ||||||
|  |                     url = nginxUrl, | ||||||
|  |                     name = NginxProvider().name, | ||||||
|  |                     status = PROVIDER_STATUS_OK, | ||||||
|  |                     credentials = nginxCredentials | ||||||
|  |                 ) | ||||||
|  |             } | ||||||
|  |         } catch (e: Exception) { | ||||||
|  |             logError(e) | ||||||
|  |             null | ||||||
|  |         } | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|         // this pulls the latest data so ppl don't have to update to simply change provider url |         // this pulls the latest data so ppl don't have to update to simply change provider url | ||||||
|         if (downloadFromGithub) { |         if (downloadFromGithub) { | ||||||
|  | @ -379,8 +432,11 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { | ||||||
|                                             tryParseJson<HashMap<String, ProvidersInfoJson>>(txt) |                                             tryParseJson<HashMap<String, ProvidersInfoJson>>(txt) | ||||||
|                                         setKey(PROVIDER_STATUS_KEY, txt) |                                         setKey(PROVIDER_STATUS_KEY, txt) | ||||||
|                                         MainAPI.overrideData = newCache // update all new providers |                                         MainAPI.overrideData = newCache // update all new providers | ||||||
|  |                                          | ||||||
|  |                                         val newUpdatedCache = newCache?.let { addNginxToJson(it) ?: it } | ||||||
|  | 
 | ||||||
| 					                    for (api in apis) { // update current providers | 					                    for (api in apis) { // update current providers | ||||||
|                                             newCache?.get(api.javaClass.simpleName)?.let { data -> |                                             newUpdatedCache?.get(api.javaClass.simpleName)?.let { data -> | ||||||
|                                                 api.overrideWithNewData(data) |                                                 api.overrideWithNewData(data) | ||||||
|                                             } |                                             } | ||||||
|                                         } |                                         } | ||||||
|  | @ -397,12 +453,13 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { | ||||||
|                                 newCache |                                 newCache | ||||||
|                             }?.let { providersJsonMap -> |                             }?.let { providersJsonMap -> | ||||||
|                                 MainAPI.overrideData = providersJsonMap |                                 MainAPI.overrideData = providersJsonMap | ||||||
|  |                                 val providersJsonMapUpdated = addNginxToJson(providersJsonMap)?: providersJsonMap // if return null, use unchanged one | ||||||
|                                 val acceptableProviders = |                                 val acceptableProviders = | ||||||
|                                     providersJsonMap.filter { it.value.status == PROVIDER_STATUS_OK || it.value.status == PROVIDER_STATUS_SLOW } |                                     providersJsonMapUpdated.filter { it.value.status == PROVIDER_STATUS_OK || it.value.status == PROVIDER_STATUS_SLOW } | ||||||
|                                         .map { it.key }.toSet() |                                         .map { it.key }.toSet() | ||||||
| 
 | 
 | ||||||
|                                 val restrictedApis = |                                 val restrictedApis = | ||||||
|                                     if (hasBenene) providersJsonMap.filter { it.value.status == PROVIDER_STATUS_BETA_ONLY } |                                     if (hasBenene) providersJsonMapUpdated.filter { it.value.status == PROVIDER_STATUS_BETA_ONLY } | ||||||
|                                         .map { it.key }.toSet() else emptySet() |                                         .map { it.key }.toSet() else emptySet() | ||||||
| 
 | 
 | ||||||
|                                 apis = allProviders.filter { api -> |                                 apis = allProviders.filter { api -> | ||||||
|  | @ -425,6 +482,17 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { | ||||||
|             } |             } | ||||||
|         } else { |         } else { | ||||||
|             apis = allProviders |             apis = allProviders | ||||||
|  |             try { | ||||||
|  |                 val nginxProviderName = NginxProvider().name | ||||||
|  |                 val nginxProviderIndex = apis.indexOf(APIHolder.getApiFromName(nginxProviderName)) | ||||||
|  |                 val createdJsonProvider = createNginxJson() | ||||||
|  |                 if (createdJsonProvider != null) { | ||||||
|  |                     apis[nginxProviderIndex].overrideWithNewData(createdJsonProvider) // people will have access to it if they disable metadata check (they are not filtered) | ||||||
|  |                 } | ||||||
|  |             } catch (e: Exception) { | ||||||
|  |                 logError(e) | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         loadThemes(this) |         loadThemes(this) | ||||||
|  |  | ||||||
|  | @ -30,6 +30,14 @@ class VizcloudLive : WcoStream() { | ||||||
|     override var mainUrl = "https://vizcloud.live" |     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" | ||||||
|  | } | ||||||
|  | 
 | ||||||
| open class WcoStream : ExtractorApi() { | open class WcoStream : ExtractorApi() { | ||||||
|     override var name = "VidStream" //Cause works for animekisa and wco |     override var name = "VidStream" //Cause works for animekisa and wco | ||||||
|     override var mainUrl = "https://vidstream.pro" |     override var mainUrl = "https://vidstream.pro" | ||||||
|  | @ -103,7 +111,8 @@ open class WcoStream : ExtractorApi() { | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|                 if (mainUrl == "https://vidstream.pro" || mainUrl == "https://vidstreamz.online" || mainUrl == "https://vizcloud2.online" |                 if (mainUrl == "https://vidstream.pro" || mainUrl == "https://vidstreamz.online" || mainUrl == "https://vizcloud2.online" | ||||||
|                     || mainUrl == "https://vizcloud.xyz" || mainUrl == "https://vizcloud.live") { |                     || mainUrl == "https://vizcloud.xyz" || mainUrl == "https://vizcloud.live" || mainUrl == "https://vizcloud.info" | ||||||
|  |                      || mainUrl == "https://mwvn.vizcloud.info") { | ||||||
|                  if (it.file.contains("m3u8")) { |                  if (it.file.contains("m3u8")) { | ||||||
|                     hlsHelper.m3u8Generation(M3u8Helper.M3u8Stream(it.file.replace("#.mp4",""), null, |                     hlsHelper.m3u8Generation(M3u8Helper.M3u8Stream(it.file.replace("#.mp4",""), null, | ||||||
|                     headers = mapOf("Referer" to url)), true) |                     headers = mapOf("Referer" to url)), true) | ||||||
|  |  | ||||||
|  | @ -8,7 +8,7 @@ import org.jsoup.nodes.Element | ||||||
| 
 | 
 | ||||||
| class EgyBestProvider : MainAPI() { | class EgyBestProvider : MainAPI() { | ||||||
|     override val lang = "ar" |     override val lang = "ar" | ||||||
|     override var mainUrl = "https://egy.best" |     override var mainUrl = "https://www.egy.best" | ||||||
|     override var name = "EgyBest" |     override var name = "EgyBest" | ||||||
|     override val usesWebView = false |     override val usesWebView = false | ||||||
|     override val hasMainPage = true |     override val hasMainPage = true | ||||||
|  | @ -26,6 +26,7 @@ class EgyBestProvider : MainAPI() { | ||||||
|         val isMovie = Regex(".*/movie/.*|.*/masrahiya/.*").matches(url) |         val isMovie = Regex(".*/movie/.*|.*/masrahiya/.*").matches(url) | ||||||
|         val tvType = if (isMovie) TvType.Movie else TvType.TvSeries |         val tvType = if (isMovie) TvType.Movie else TvType.TvSeries | ||||||
|         title = if (year !== null) title else title.split(" (")[0].trim() |         title = if (year !== null) title else title.split(" (")[0].trim() | ||||||
|  |         val quality = select("span.ribbon span").text().replace("-", "") | ||||||
|         // If you need to differentiate use the url. |         // If you need to differentiate use the url. | ||||||
|         return MovieSearchResponse( |         return MovieSearchResponse( | ||||||
|             title, |             title, | ||||||
|  | @ -35,18 +36,22 @@ class EgyBestProvider : MainAPI() { | ||||||
|             posterUrl, |             posterUrl, | ||||||
|             year, |             year, | ||||||
|             null, |             null, | ||||||
|  |             quality = getQualityFromString(quality) | ||||||
|         ) |         ) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override suspend fun getMainPage(): HomePageResponse { |     override suspend fun getMainPage(): HomePageResponse { | ||||||
|         // url, title |         // url, title | ||||||
|         val doc = app.get(mainUrl).document |         val doc = app.get(mainUrl).document | ||||||
|         val pages = doc.select("#mainLoad div.mbox").apmap { |         val pages = arrayListOf<HomePageList>() | ||||||
|  |             doc.select("#mainLoad div.mbox").apmap { | ||||||
|             val name = it.select(".bdb.pda > strong").text() |             val name = it.select(".bdb.pda > strong").text() | ||||||
|             val list = it.select(".movie").mapNotNull { element -> |             if (it.select(".movie").first().attr("href").contains("season-(.....)|ep-(.....)".toRegex())) return@apmap | ||||||
|                 element.toSearchResponse() |             val list = arrayListOf<SearchResponse>() | ||||||
|  |             it.select(".movie").map { element -> | ||||||
|  |                 list.add(element.toSearchResponse()!!) | ||||||
|             } |             } | ||||||
|             HomePageList(name, list) |             pages.add(HomePageList(name, list)) | ||||||
|         } |         } | ||||||
|         return HomePageResponse(pages) |         return HomePageResponse(pages) | ||||||
|     } |     } | ||||||
|  | @ -72,7 +77,7 @@ class EgyBestProvider : MainAPI() { | ||||||
|         val isMovie = Regex(".*/movie/.*|.*/masrahiya/.*").matches(url) |         val isMovie = Regex(".*/movie/.*|.*/masrahiya/.*").matches(url) | ||||||
|         val posterUrl = doc.select("div.movie_img a img")?.attr("src") |         val posterUrl = doc.select("div.movie_img a img")?.attr("src") | ||||||
|         val year = doc.select("div.movie_title h1 a")?.text()?.toIntOrNull() |         val year = doc.select("div.movie_title h1 a")?.text()?.toIntOrNull() | ||||||
|         val title = doc.select("div.movie_title h1 span[itemprop=\"name\"]").text() |         val title = doc.select("div.movie_title h1 span").text() | ||||||
| 
 | 
 | ||||||
|         val synopsis = doc.select("div.mbox").firstOrNull { |         val synopsis = doc.select("div.mbox").firstOrNull { | ||||||
|             it.text().contains("القصة") |             it.text().contains("القصة") | ||||||
|  |  | ||||||
|  | @ -0,0 +1,268 @@ | ||||||
|  | package com.lagradost.cloudstream3.movieproviders | ||||||
|  | 
 | ||||||
|  | import com.lagradost.cloudstream3.* | ||||||
|  | import com.lagradost.cloudstream3.LoadResponse.Companion.addRating | ||||||
|  | import com.lagradost.cloudstream3.utils.ExtractorLink | ||||||
|  | import com.lagradost.cloudstream3.TvType | ||||||
|  | import com.lagradost.cloudstream3.app | ||||||
|  | import com.lagradost.cloudstream3.utils.Qualities | ||||||
|  | import java.lang.Exception | ||||||
|  | 
 | ||||||
|  | class NginxProvider : MainAPI() { | ||||||
|  |     override var name = "Nginx" | ||||||
|  |     override val hasQuickSearch = false | ||||||
|  |     override val hasMainPage = true | ||||||
|  |     override val supportedTypes = setOf(TvType.AnimeMovie, TvType.TvSeries, TvType.Movie) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     fun getAuthHeader(storedCredentials: String?): Map<String, String> { | ||||||
|  |         if (storedCredentials == null) { | ||||||
|  |             return mapOf(Pair("Authorization", "Basic "))  // no Authorization headers | ||||||
|  |         } | ||||||
|  |         val basicAuthToken = base64Encode(storedCredentials.toByteArray())  // will this be loaded when not using the provider ??? can increase load | ||||||
|  |         return mapOf(Pair("Authorization", "Basic $basicAuthToken")) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     override suspend fun load(url: String): LoadResponse { | ||||||
|  |         val authHeader = getAuthHeader(storedCredentials)  // call again because it isn't reloaded if in main class and storedCredentials loads after | ||||||
|  |         // url can be tvshow.nfo for series or mediaRootUrl for movies | ||||||
|  | 
 | ||||||
|  |         val mediaRootDocument = app.get(url, authHeader).document | ||||||
|  | 
 | ||||||
|  |         val nfoUrl = url + mediaRootDocument.getElementsByAttributeValueContaining("href", ".nfo").attr("href")  // metadata url file | ||||||
|  | 
 | ||||||
|  |         val metadataDocument = app.get(nfoUrl, authHeader).document  // get the metadata nfo file | ||||||
|  | 
 | ||||||
|  |         val isMovie = !nfoUrl.contains("tvshow.nfo") | ||||||
|  | 
 | ||||||
|  |         val title = metadataDocument.selectFirst("title").text() | ||||||
|  | 
 | ||||||
|  |         val description = metadataDocument.selectFirst("plot").text() | ||||||
|  | 
 | ||||||
|  |         if (isMovie) { | ||||||
|  |             val poster = metadataDocument.selectFirst("thumb").text() | ||||||
|  |             val trailer = metadataDocument.select("trailer")?.mapNotNull { | ||||||
|  |                it?.text()?.replace( | ||||||
|  |                    "plugin://plugin.video.youtube/play/?video_id=", | ||||||
|  |                    "https://www.youtube.com/watch?v=" | ||||||
|  |                ) | ||||||
|  |             } | ||||||
|  |             val partialUrl = mediaRootDocument.getElementsByAttributeValueContaining("href", ".nfo").attr("href").replace(".nfo", ".") | ||||||
|  |             val date = metadataDocument.selectFirst("year")?.text()?.toIntOrNull() | ||||||
|  |             val ratingAverage = metadataDocument.selectFirst("value")?.text()?.toIntOrNull() | ||||||
|  |             val tagsList = metadataDocument.select("genre") | ||||||
|  |                 ?.mapNotNull {   // all the tags like action, thriller ... | ||||||
|  |                     it?.text() | ||||||
|  | 
 | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |             val dataList = mediaRootDocument.getElementsByAttributeValueContaining(  // list of all urls of the webpage | ||||||
|  |                 "href", | ||||||
|  |                 partialUrl | ||||||
|  |             ) | ||||||
|  | 
 | ||||||
|  |             val data = url + dataList.firstNotNullOf { item -> item.takeIf { (!it.attr("href").contains(".nfo") &&  !it.attr("href").contains(".jpg"))} }.attr("href").toString()  // exclude poster and nfo (metadata) file | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |             return MovieLoadResponse( | ||||||
|  |                 title, | ||||||
|  |                 data, | ||||||
|  |                 this.name, | ||||||
|  |                 TvType.Movie, | ||||||
|  |                 data, | ||||||
|  |                 poster, | ||||||
|  |                 date, | ||||||
|  |                 description, | ||||||
|  |                 ratingAverage, | ||||||
|  |                 tagsList, | ||||||
|  |                 null, | ||||||
|  |                 trailer, | ||||||
|  |                 null, | ||||||
|  |                 null, | ||||||
|  |             ) | ||||||
|  |         } else  // a tv serie | ||||||
|  |         { | ||||||
|  | 
 | ||||||
|  |             val list = ArrayList<Pair<Int, String>>() | ||||||
|  |             val mediaRootUrl = url.replace("tvshow.nfo", "") | ||||||
|  |             val posterUrl = mediaRootUrl + "poster.jpg" | ||||||
|  |             val mediaRootDocument = app.get(mediaRootUrl, authHeader).document | ||||||
|  |             val seasons = | ||||||
|  |                 mediaRootDocument.getElementsByAttributeValueContaining("href", "Season%20") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |             val tagsList = metadataDocument.select("genre") | ||||||
|  |                 ?.mapNotNull {   // all the tags like action, thriller ...; unused variable | ||||||
|  |                     it?.text() | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |             //val actorsList = document.select("actor") | ||||||
|  |             //    ?.mapNotNull {   // all the tags like action, thriller ...; unused variable | ||||||
|  |             //        it?.text() | ||||||
|  |             //    } | ||||||
|  | 
 | ||||||
|  |             seasons.forEach { element -> | ||||||
|  |                 val season = | ||||||
|  |                     element.attr("href")?.replace("Season%20", "")?.replace("/", "")?.toIntOrNull() | ||||||
|  |                 val href = mediaRootUrl + element.attr("href") | ||||||
|  |                 if (season != null && season > 0 && href.isNotBlank()) { | ||||||
|  |                     list.add(Pair(season, href)) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if (list.isEmpty()) throw ErrorLoadingException("No Seasons Found") | ||||||
|  | 
 | ||||||
|  |             val episodeList = ArrayList<Episode>() | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |             list.apmap { (seasonInt, seasonString) -> | ||||||
|  |                 val seasonDocument = app.get(seasonString, authHeader).document | ||||||
|  |                 val episodes = seasonDocument.getElementsByAttributeValueContaining( | ||||||
|  |                     "href", | ||||||
|  |                     ".nfo" | ||||||
|  |                 ) // get metadata | ||||||
|  |                     episodes.forEach { episode -> | ||||||
|  |                         val nfoDocument = app.get(seasonString + episode.attr("href"), authHeader).document // get episode metadata file | ||||||
|  |                         val epNum = nfoDocument.selectFirst("episode")?.text()?.toIntOrNull() | ||||||
|  |                         val poster = | ||||||
|  |                             seasonString + episode.attr("href").replace(".nfo", "-thumb.jpg") | ||||||
|  |                         val name = nfoDocument.selectFirst("title").text() | ||||||
|  |                         // val seasonInt = nfoDocument.selectFirst("season").text().toIntOrNull() | ||||||
|  |                         val date = nfoDocument.selectFirst("aired")?.text() | ||||||
|  |                         val plot = nfoDocument.selectFirst("plot")?.text() | ||||||
|  | 
 | ||||||
|  |                         val dataList = seasonDocument.getElementsByAttributeValueContaining( | ||||||
|  |                             "href", | ||||||
|  |                             episode.attr("href").replace(".nfo", "") | ||||||
|  |                         ) | ||||||
|  |                         val data = seasonString + dataList.firstNotNullOf { item -> item.takeIf { (!it.attr("href").contains(".nfo") &&  !it.attr("href").contains(".jpg"))} }.attr("href").toString()  // exclude poster and nfo (metadata) file | ||||||
|  | 
 | ||||||
|  |                         episodeList.add( | ||||||
|  |                             newEpisode(data) { | ||||||
|  |                                     this.name = name | ||||||
|  |                                     this.season = seasonInt | ||||||
|  |                                     this.episode = epNum | ||||||
|  |                                     this.posterUrl = poster | ||||||
|  |                                     addDate(date) | ||||||
|  |                                     this.description = plot | ||||||
|  |                             } | ||||||
|  |                         ) | ||||||
|  |                     } | ||||||
|  |             } | ||||||
|  |             return newTvSeriesLoadResponse(title, url, TvType.TvSeries, episodeList) { | ||||||
|  |                 this.name = title | ||||||
|  |                 this.url = url | ||||||
|  |                 this.posterUrl = posterUrl | ||||||
|  |                 this.episodes = episodeList | ||||||
|  |                 this.plot = description | ||||||
|  |                 this.tags = tagsList | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     override suspend fun loadLinks( | ||||||
|  |         data: String, | ||||||
|  |         isCasting: Boolean, | ||||||
|  |         subtitleCallback: (SubtitleFile) -> Unit, | ||||||
|  |         callback: (ExtractorLink) -> Unit | ||||||
|  |     ): Boolean { | ||||||
|  |         // loadExtractor(data, null) { callback(it.copy(headers=authHeader)) } | ||||||
|  |         val authHeader = getAuthHeader(storedCredentials)  // call again because it isn't reloaded if in main class and storedCredentials loads after | ||||||
|  |         callback.invoke ( | ||||||
|  |             ExtractorLink( | ||||||
|  |                 name, | ||||||
|  |                 name, | ||||||
|  |                 data, | ||||||
|  |                 data,  // referer not needed | ||||||
|  |                 Qualities.Unknown.value, | ||||||
|  |                 false, | ||||||
|  |                 authHeader, | ||||||
|  |             ) | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         return true | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     override suspend fun getMainPage(): HomePageResponse? { | ||||||
|  |         val authHeader = getAuthHeader(storedCredentials)  // call again because it isn't reloaded if in main class and storedCredentials loads after | ||||||
|  |         if (mainUrl == "NONE"){ | ||||||
|  |             throw ErrorLoadingException("No nginx url specified in the settings: Nginx Settigns > Nginx server url, try again in a few seconds") | ||||||
|  |         } | ||||||
|  |         val document = app.get(mainUrl, authHeader).document | ||||||
|  |         val categories = document.select("a") | ||||||
|  |         val returnList = categories.mapNotNull { | ||||||
|  |             val categoryPath = mainUrl + it.attr("href") ?: return@mapNotNull null // get the url of the category; like http://192.168.1.10/media/Movies/ | ||||||
|  |             val categoryTitle = it.text()  // get the category title like Movies or Series | ||||||
|  |             if (categoryTitle != "../" && categoryTitle != "Music/") {  // exclude parent dir and Music dir | ||||||
|  |                 val categoryDocument = app.get(categoryPath, authHeader).document // queries the page http://192.168.1.10/media/Movies/ | ||||||
|  |                 val contentLinks = categoryDocument.select("a") | ||||||
|  |                 val currentList = contentLinks.mapNotNull { head -> | ||||||
|  |                     if (head.attr("href") != "../") { | ||||||
|  |                         try { | ||||||
|  |                             val mediaRootUrl = | ||||||
|  |                                 categoryPath + head.attr("href")// like http://192.168.1.10/media/Series/Chernobyl/ | ||||||
|  |                             val mediaDocument = app.get(mediaRootUrl, authHeader).document | ||||||
|  |                             val nfoFilename = mediaDocument.getElementsByAttributeValueContaining( | ||||||
|  |                                 "href", | ||||||
|  |                                 ".nfo" | ||||||
|  |                             )[0].attr("href") | ||||||
|  |                             val isMovieType = nfoFilename != "tvshow.nfo" | ||||||
|  |                             val nfoPath = | ||||||
|  |                                 mediaRootUrl + nfoFilename // must exist or will raise errors, only the first one is taken | ||||||
|  |                             val nfoContent = | ||||||
|  |                                 app.get(nfoPath, authHeader).document  // all the metadata | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |                             if (isMovieType) { | ||||||
|  |                                 val movieName = nfoContent.select("title").text() | ||||||
|  | 
 | ||||||
|  |                                 val posterUrl = mediaRootUrl + "poster.jpg" | ||||||
|  | 
 | ||||||
|  |                                 return@mapNotNull MovieSearchResponse( | ||||||
|  |                                     movieName, | ||||||
|  |                                     mediaRootUrl, | ||||||
|  |                                     this.name, | ||||||
|  |                                     TvType.Movie, | ||||||
|  |                                     posterUrl, | ||||||
|  |                                     null, | ||||||
|  |                                 ) | ||||||
|  |                             } else {  // tv serie | ||||||
|  |                                 val serieName = nfoContent.select("title").text() | ||||||
|  | 
 | ||||||
|  |                                 val posterUrl = mediaRootUrl + "poster.jpg" | ||||||
|  | 
 | ||||||
|  |                                 TvSeriesSearchResponse( | ||||||
|  |                                     serieName, | ||||||
|  |                                     nfoPath, | ||||||
|  |                                     this.name, | ||||||
|  |                                     TvType.TvSeries, | ||||||
|  |                                     posterUrl, | ||||||
|  |                                     null, | ||||||
|  |                                     null, | ||||||
|  |                                 ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |                             } | ||||||
|  |                         } catch (e: Exception) {  // can cause issues invisible errors | ||||||
|  |                             null | ||||||
|  |                             //logError(e) // not working because it changes the return type of currentList to Any | ||||||
|  |                         } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |                     } else null | ||||||
|  |                 } | ||||||
|  |                 if (currentList.isNotEmpty() && categoryTitle != "../") {  // exclude upper dir | ||||||
|  |                     HomePageList(categoryTitle, currentList) | ||||||
|  |                 } else null | ||||||
|  |             } else null  // the path is ../ which is parent directory | ||||||
|  |         } | ||||||
|  |         // if (returnList.isEmpty()) return null // maybe doing nothing idk | ||||||
|  |         return HomePageResponse(returnList) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -663,7 +663,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { | ||||||
|         @JsonProperty("name") val name: String, |         @JsonProperty("name") val name: String, | ||||||
|         @JsonProperty("location") val location: String, |         @JsonProperty("location") val location: String, | ||||||
|         @JsonProperty("joined_at") val joined_at: String, |         @JsonProperty("joined_at") val joined_at: String, | ||||||
|         @JsonProperty("picture") val picture: String, |         @JsonProperty("picture") val picture: String?, | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|     data class MalMainPicture( |     data class MalMainPicture( | ||||||
|  |  | ||||||
|  | @ -545,10 +545,14 @@ class GeneratorPlayer : FullScreenPlayer() { | ||||||
|                 tvType = meta.tvType |                 tvType = meta.tvType | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 |         //Get limit of characters on Video Title | ||||||
|         player_episode_filler_holder?.isVisible = isFiller ?: false |         var limitTitle = 0 | ||||||
| 
 |         context?.let { | ||||||
|         player_video_title?.text = if (headerName != null) { |             val settingsManager = PreferenceManager.getDefaultSharedPreferences(it) | ||||||
|  |             limitTitle = settingsManager.getInt(getString(R.string.prefer_limit_title_key), 0) | ||||||
|  |         } | ||||||
|  |         //Generate video title | ||||||
|  |         var playerVideoTitle = if (headerName != null) { | ||||||
|             (headerName + |             (headerName + | ||||||
|                     if (tvType.isEpisodeBased() && episode != null) |                     if (tvType.isEpisodeBased() && episode != null) | ||||||
|                         if (season == null) |                         if (season == null) | ||||||
|  | @ -559,6 +563,15 @@ class GeneratorPlayer : FullScreenPlayer() { | ||||||
|         } else { |         } else { | ||||||
|             "" |             "" | ||||||
|         } |         } | ||||||
|  |         //Truncate video title if it exceeds limit | ||||||
|  |         val differenceInLength = playerVideoTitle.length - limitTitle | ||||||
|  |         val margin = 3 //If the difference is smaller than or equal to this value, ignore it | ||||||
|  |         if (limitTitle > 0 && differenceInLength > margin) { | ||||||
|  |             playerVideoTitle = playerVideoTitle.substring(0, limitTitle-1) + "..." | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         player_episode_filler_holder?.isVisible = isFiller ?: false | ||||||
|  |         player_video_title?.text = playerVideoTitle | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @SuppressLint("SetTextI18n") |     @SuppressLint("SetTextI18n") | ||||||
|  |  | ||||||
|  | @ -47,6 +47,7 @@ import com.lagradost.cloudstream3.utils.InAppUpdater.Companion.runAutoUpdate | ||||||
| import com.lagradost.cloudstream3.utils.Qualities | import com.lagradost.cloudstream3.utils.Qualities | ||||||
| import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog | 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.showNginxTextInputDialog | ||||||
| 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 | ||||||
| import com.lagradost.cloudstream3.utils.SubtitleHelper.getFlagFromIso | import com.lagradost.cloudstream3.utils.SubtitleHelper.getFlagFromIso | ||||||
|  | @ -485,6 +486,32 @@ class SettingsFragment : PreferenceFragmentCompat() { | ||||||
|             return@setOnPreferenceClickListener true |             return@setOnPreferenceClickListener true | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | 	getPref(R.string.nginx_url_key)?.setOnPreferenceClickListener { | ||||||
|  | 
 | ||||||
|  |             activity?.showNginxTextInputDialog( | ||||||
|  |                 settingsManager.getString(getString(R.string.nginx_url_pref), "Nginx server url").toString(), | ||||||
|  |                 settingsManager.getString(getString(R.string.nginx_url_key), "").toString(),  // key: the actual you use rn | ||||||
|  |                 android.text.InputType.TYPE_TEXT_VARIATION_URI,  // uri | ||||||
|  |                 {}) { | ||||||
|  |                 settingsManager.edit() | ||||||
|  |                     .putString(getString(R.string.nginx_url_key), it).apply()  // change the stored url in nginx_url_key to it | ||||||
|  |             } | ||||||
|  |             return@setOnPreferenceClickListener true | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         getPref(R.string.nginx_credentials)?.setOnPreferenceClickListener { | ||||||
|  | 
 | ||||||
|  |             activity?.showNginxTextInputDialog( | ||||||
|  |                 settingsManager.getString(getString(R.string.nginx_credentials_title), "Nginx Credentials").toString(), | ||||||
|  |                 settingsManager.getString(getString(R.string.nginx_credentials), "").toString(),  // key: the actual you use rn | ||||||
|  |                 android.text.InputType.TYPE_TEXT_VARIATION_URI, | ||||||
|  |                 {}) { | ||||||
|  |                 settingsManager.edit() | ||||||
|  |                     .putString(getString(R.string.nginx_credentials), it).apply()  // change the stored url in nginx_url_key to it | ||||||
|  |             } | ||||||
|  |             return@setOnPreferenceClickListener true | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         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 prefNames = resources.getStringArray(R.array.media_type_pref) | ||||||
|             val prefValues = resources.getIntArray(R.array.media_type_pref_values) |             val prefValues = resources.getIntArray(R.array.media_type_pref_values) | ||||||
|  | @ -678,6 +705,24 @@ class SettingsFragment : PreferenceFragmentCompat() { | ||||||
|             return@setOnPreferenceClickListener true |             return@setOnPreferenceClickListener true | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         getPref(R.string.prefer_limit_title_key)?.setOnPreferenceClickListener { | ||||||
|  |             val prefNames = resources.getStringArray(R.array.limit_title_pref_names) | ||||||
|  |             val prefValues = resources.getIntArray(R.array.limit_title_pref_values) | ||||||
|  |             val current = settingsManager.getInt(getString(R.string.prefer_limit_title_key), 0) | ||||||
|  | 
 | ||||||
|  |             activity?.showBottomDialog( | ||||||
|  |                 prefNames.toList(), | ||||||
|  |                 prefValues.indexOf(current), | ||||||
|  |                 getString(R.string.limit_title), | ||||||
|  |                 true, | ||||||
|  |                 {}) { | ||||||
|  |                 settingsManager.edit() | ||||||
|  |                     .putInt(getString(R.string.prefer_limit_title_key), prefValues[it]) | ||||||
|  |                     .apply() | ||||||
|  |             } | ||||||
|  |             return@setOnPreferenceClickListener true | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         getPref(R.string.dns_key)?.setOnPreferenceClickListener { |         getPref(R.string.dns_key)?.setOnPreferenceClickListener { | ||||||
|             val prefNames = resources.getStringArray(R.array.dns_pref) |             val prefNames = resources.getStringArray(R.array.dns_pref) | ||||||
|             val prefValues = resources.getIntArray(R.array.dns_pref_values) |             val prefValues = resources.getIntArray(R.array.dns_pref_values) | ||||||
|  |  | ||||||
|  | @ -100,6 +100,8 @@ val extractorApis: Array<ExtractorApi> = arrayOf( | ||||||
|     VizcloudOnline(), |     VizcloudOnline(), | ||||||
|     VizcloudXyz(), |     VizcloudXyz(), | ||||||
|     VizcloudLive(), |     VizcloudLive(), | ||||||
|  |     VizcloudInfo(), | ||||||
|  |     MwvnVizcloudInfo(), | ||||||
|     Mp4Upload(), |     Mp4Upload(), | ||||||
|     StreamTape(), |     StreamTape(), | ||||||
|     MixDrop(), |     MixDrop(), | ||||||
|  |  | ||||||
|  | @ -144,6 +144,46 @@ object SingleSelectionHelper { | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     private fun Activity.showInputDialog( | ||||||
|  |         dialog: Dialog, | ||||||
|  |         value: String, | ||||||
|  |         name: String, | ||||||
|  |         textInputType: Int?, | ||||||
|  |         callback: (String) -> Unit, | ||||||
|  |         dismissCallback: () -> Unit | ||||||
|  |     ) { | ||||||
|  |         val inputView = dialog.findViewById<EditText>(R.id.nginx_text_input)!! | ||||||
|  |         val textView = dialog.findViewById<TextView>(R.id.text1)!! | ||||||
|  |         val applyButton = dialog.findViewById<TextView>(R.id.apply_btt)!! | ||||||
|  |         val cancelButton = dialog.findViewById<TextView>(R.id.cancel_btt)!! | ||||||
|  |         val applyHolder = dialog.findViewById<LinearLayout>(R.id.apply_btt_holder)!! | ||||||
|  | 
 | ||||||
|  |         applyHolder.isVisible = true | ||||||
|  |         textView.text = name | ||||||
|  | 
 | ||||||
|  |         if (textInputType != null) { | ||||||
|  |             inputView.inputType = textInputType // 16 for website url input type | ||||||
|  |         } | ||||||
|  |         inputView.setText(value, TextView.BufferType.EDITABLE) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |         applyButton.setOnClickListener { | ||||||
|  |             callback.invoke(inputView.text.toString())  // try to save the setting, using callback | ||||||
|  |             dialog.dismissSafe(this) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         cancelButton.setOnClickListener {  // just dismiss | ||||||
|  |             dialog.dismissSafe(this) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         dialog.setOnDismissListener { | ||||||
|  |             dismissCallback.invoke() | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     fun Activity.showMultiDialog( |     fun Activity.showMultiDialog( | ||||||
|         items: List<String>, |         items: List<String>, | ||||||
|         selectedIndex: List<Int>, |         selectedIndex: List<Int>, | ||||||
|  | @ -211,4 +251,25 @@ object SingleSelectionHelper { | ||||||
|             dismissCallback |             dismissCallback | ||||||
|         ) |         ) | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |         fun Activity.showNginxTextInputDialog( | ||||||
|  |             name: String, | ||||||
|  |             value: String, | ||||||
|  |             textInputType: Int?, | ||||||
|  |             dismissCallback: () -> Unit, | ||||||
|  |             callback: (String) -> Unit, | ||||||
|  |     ) { | ||||||
|  |         val builder = BottomSheetDialog(this)  // probably the stuff at the bottom | ||||||
|  |         builder.setContentView(R.layout.bottom_input_dialog)  // input layout | ||||||
|  | 
 | ||||||
|  |         builder.show() | ||||||
|  |         showInputDialog( | ||||||
|  |             builder, | ||||||
|  |             value, | ||||||
|  |             name, | ||||||
|  |             textInputType,  // type is a uri | ||||||
|  |             callback, | ||||||
|  |             dismissCallback | ||||||
|  |         ) | ||||||
|  |     } | ||||||
| } | } | ||||||
							
								
								
									
										10
									
								
								app/src/main/res/drawable/ic_baseline_text_format_24.xml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								app/src/main/res/drawable/ic_baseline_text_format_24.xml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,10 @@ | ||||||
|  | <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |     android:width="24dp" | ||||||
|  |     android:height="24dp" | ||||||
|  |     android:viewportWidth="24" | ||||||
|  |     android:viewportHeight="24" | ||||||
|  |     android:tint="?attr/white"> | ||||||
|  |   <path | ||||||
|  |       android:fillColor="@android:color/white" | ||||||
|  |       android:pathData="M5,17v2h14v-2L5,17zM9.5,12.8h5l0.9,2.2h2.1L12.75,4h-1.5L6.5,15h2.1l0.9,-2.2zM12,5.98L13.87,11h-3.74L12,5.98z"/> | ||||||
|  | </vector> | ||||||
							
								
								
									
										62
									
								
								app/src/main/res/layout/bottom_input_dialog.xml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								app/src/main/res/layout/bottom_input_dialog.xml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,62 @@ | ||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |         xmlns:tools="http://schemas.android.com/tools" | ||||||
|  |         android:nextFocusDown="@id/nginx_text_input" | ||||||
|  |         android:orientation="vertical" | ||||||
|  |         android:layout_width="match_parent" | ||||||
|  |         android:layout_height="match_parent"> | ||||||
|  | 
 | ||||||
|  |     <TextView | ||||||
|  |         android:id="@+id/text1" | ||||||
|  |         android:layout_width="match_parent" | ||||||
|  |         android:layout_height="wrap_content" | ||||||
|  |         android:layout_rowWeight="1" | ||||||
|  |         android:layout_marginTop="20dp" | ||||||
|  |         android:layout_marginBottom="10dp" | ||||||
|  |         android:paddingStart="?android:attr/listPreferredItemPaddingStart" | ||||||
|  |         android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" | ||||||
|  |         android:textColor="?attr/textColor" | ||||||
|  |         android:textSize="20sp" | ||||||
|  |         android:textStyle="bold" | ||||||
|  |         tools:text="Test" /> | ||||||
|  | 
 | ||||||
|  |     <EditText | ||||||
|  |             android:id="@+id/nginx_text_input" | ||||||
|  |             android:nextFocusRight="@id/cancel_btt" | ||||||
|  |             android:nextFocusLeft="@id/apply_btt" | ||||||
|  |             android:layout_marginBottom="60dp" | ||||||
|  |             android:layout_marginHorizontal="10dp" | ||||||
|  |             android:paddingTop="10dp" | ||||||
|  |             android:requiresFadingEdge="vertical" | ||||||
|  |             tools:text="nginx.com" | ||||||
|  |             android:layout_width="match_parent" | ||||||
|  |             android:layout_height="match_parent" | ||||||
|  |             android:layout_rowWeight="1" | ||||||
|  |             android:autofillHints="Autofill Hint" | ||||||
|  |             android:inputType="text" | ||||||
|  |             tools:ignore="LabelFor" /> | ||||||
|  | 
 | ||||||
|  |     <LinearLayout | ||||||
|  |             android:id="@+id/apply_btt_holder" | ||||||
|  |             android:orientation="horizontal" | ||||||
|  |             android:layout_gravity="bottom" | ||||||
|  |             android:gravity="bottom|end" | ||||||
|  |             android:layout_marginTop="-60dp" | ||||||
|  |             android:layout_width="match_parent" | ||||||
|  |             android:layout_height="60dp"> | ||||||
|  | 
 | ||||||
|  |         <com.google.android.material.button.MaterialButton | ||||||
|  |                 style="@style/WhiteButton" | ||||||
|  |                 android:layout_gravity="center_vertical|end" | ||||||
|  |                 android:text="@string/sort_apply" | ||||||
|  |                 android:id="@+id/apply_btt" | ||||||
|  |                 android:layout_width="wrap_content" /> | ||||||
|  | 
 | ||||||
|  |         <com.google.android.material.button.MaterialButton | ||||||
|  |                 style="@style/BlackButton" | ||||||
|  |                 android:layout_gravity="center_vertical|end" | ||||||
|  |                 android:text="@string/sort_cancel" | ||||||
|  |                 android:id="@+id/cancel_btt" | ||||||
|  |                 android:layout_width="wrap_content" /> | ||||||
|  |     </LinearLayout> | ||||||
|  | </LinearLayout> | ||||||
|  | @ -32,7 +32,7 @@ | ||||||
|     </array> |     </array> | ||||||
| 
 | 
 | ||||||
|     <array name="dns_pref"> |     <array name="dns_pref"> | ||||||
|         <item>None</item> |         <item>@string/none</item> | ||||||
|         <item>Google</item> |         <item>Google</item> | ||||||
|         <item>Cloudflare</item> |         <item>Cloudflare</item> | ||||||
|         <!--        <item>OpenDns</item>--> |         <!--        <item>OpenDns</item>--> | ||||||
|  | @ -59,6 +59,21 @@ | ||||||
|         <item>3</item> |         <item>3</item> | ||||||
|     </array> |     </array> | ||||||
| 
 | 
 | ||||||
|  |     <array name="limit_title_pref_names"> | ||||||
|  |         <item>@string/none</item> | ||||||
|  |         <item>16 characters</item> | ||||||
|  |         <item>32 characters</item> | ||||||
|  |         <item>64 characters</item> | ||||||
|  |         <item>128 characters</item> | ||||||
|  |     </array> | ||||||
|  |     <array name="limit_title_pref_values"> | ||||||
|  |         <item>0</item> | ||||||
|  |         <item>16</item> | ||||||
|  |         <item>32</item> | ||||||
|  |         <item>64</item> | ||||||
|  |         <item>128</item> | ||||||
|  |     </array> | ||||||
|  | 
 | ||||||
|     <array name="video_buffer_length_names"> |     <array name="video_buffer_length_names"> | ||||||
|         <item>@string/automatic</item> |         <item>@string/automatic</item> | ||||||
|         <item>1min</item> |         <item>1min</item> | ||||||
|  |  | ||||||
|  | @ -13,6 +13,7 @@ | ||||||
|     <string name="subtitle_settings_key" translatable="false">subtitle_settings_key</string> |     <string name="subtitle_settings_key" translatable="false">subtitle_settings_key</string> | ||||||
|     <string name="subtitle_settings_chromecast_key" translatable="false">subtitle_settings_chromecast_key</string> |     <string name="subtitle_settings_chromecast_key" translatable="false">subtitle_settings_chromecast_key</string> | ||||||
|     <string name="quality_pref_key" translatable="false">quality_pref_key</string> |     <string name="quality_pref_key" translatable="false">quality_pref_key</string> | ||||||
|  |     <string name="prefer_limit_title_key" translatable="false">prefer_limit_title_key</string> | ||||||
|     <string name="video_buffer_size_key" translatable="false">video_buffer_size_key</string> |     <string name="video_buffer_size_key" translatable="false">video_buffer_size_key</string> | ||||||
|     <string name="video_buffer_length_key" translatable="false">video_buffer_length_key</string> |     <string name="video_buffer_length_key" translatable="false">video_buffer_length_key</string> | ||||||
|     <string name="video_buffer_clear_key" translatable="false">video_buffer_clear_key</string> |     <string name="video_buffer_clear_key" translatable="false">video_buffer_clear_key</string> | ||||||
|  | @ -31,6 +32,9 @@ | ||||||
|     <string name="provider_lang_key" translatable="false">provider_lang_key</string> |     <string name="provider_lang_key" translatable="false">provider_lang_key</string> | ||||||
|     <string name="dns_key" translatable="false">dns_key</string> |     <string name="dns_key" translatable="false">dns_key</string> | ||||||
|     <string name="download_path_key" translatable="false">download_path_key</string> |     <string name="download_path_key" translatable="false">download_path_key</string> | ||||||
|  |     <string name="nginx_url_key" translatable="false">nginx_url_key</string> | ||||||
|  |     <string name="nginx_credentials" translatable="false">nginx_credentials</string> | ||||||
|  |     <string name="nginx_info" translatable="false">nginx_info</string> | ||||||
|     <string name="app_name_download_path" translatable="false">Cloudstream</string> |     <string name="app_name_download_path" translatable="false">Cloudstream</string> | ||||||
|     <string name="app_layout_key" translatable="false">app_layout_key</string> |     <string name="app_layout_key" translatable="false">app_layout_key</string> | ||||||
|     <string name="primary_color_key" translatable="false">primary_color_key</string> |     <string name="primary_color_key" translatable="false">primary_color_key</string> | ||||||
|  | @ -227,6 +231,12 @@ | ||||||
|     <string name="backup_failed_error_format">Error backing up %s</string> |     <string name="backup_failed_error_format">Error backing up %s</string> | ||||||
| 
 | 
 | ||||||
|     <string name="search">Search</string> |     <string name="search">Search</string> | ||||||
|  |     <string name="nginx_category">Nginx Settings</string> | ||||||
|  |     <string name="nginx_credentials_title">Nginx Credential</string> | ||||||
|  |     <string name="nginx_credentials_summary">You have to use the following format mycoolusername:mysecurepassword123</string> | ||||||
|  |     <string name="nginx_info_title">What is Nginx ?</string> | ||||||
|  |     <string name="nginx_info_summary">Nginx is a software that can be used to display files from a server that you own. Click to see a Nginx setup guide</string> | ||||||
|  | 
 | ||||||
|     <string name="settings_info">Info</string> |     <string name="settings_info">Info</string> | ||||||
|     <string name="advanced_search">Advanced Search</string> |     <string name="advanced_search">Advanced Search</string> | ||||||
|     <string name="advanced_search_des">Gives you the search results separated by provider</string> |     <string name="advanced_search_des">Gives you the search results separated by provider</string> | ||||||
|  | @ -339,6 +349,7 @@ | ||||||
|     <string name="dont_show_again">Don\'t show again</string> |     <string name="dont_show_again">Don\'t show again</string> | ||||||
|     <string name="update">Update</string> |     <string name="update">Update</string> | ||||||
|     <string name="watch_quality_pref">Preferred watch quality</string> |     <string name="watch_quality_pref">Preferred watch quality</string> | ||||||
|  |     <string name="limit_title">Limit title characters on player</string> | ||||||
|     <string name="video_buffer_size_settings">Video buffer size</string> |     <string name="video_buffer_size_settings">Video buffer size</string> | ||||||
|     <string name="video_buffer_length_settings">Video buffer length</string> |     <string name="video_buffer_length_settings">Video buffer length</string> | ||||||
|     <string name="video_buffer_disk_settings">Video cache on disk</string> |     <string name="video_buffer_disk_settings">Video cache on disk</string> | ||||||
|  | @ -352,6 +363,8 @@ | ||||||
| 
 | 
 | ||||||
|     <string name="download_path_pref">Download path</string> |     <string name="download_path_pref">Download path</string> | ||||||
| 
 | 
 | ||||||
|  |     <string name="nginx_url_pref">Nginx server url</string> | ||||||
|  | 
 | ||||||
|     <string name="display_subbed_dubbed_settings">Display Dubbed/Subbed Anime</string> |     <string name="display_subbed_dubbed_settings">Display Dubbed/Subbed Anime</string> | ||||||
| 
 | 
 | ||||||
|     <string name="resize_fit">Fit to screen</string> |     <string name="resize_fit">Fit to screen</string> | ||||||
|  |  | ||||||
|  | @ -20,6 +20,10 @@ | ||||||
|                 android:key="@string/quality_pref_key" |                 android:key="@string/quality_pref_key" | ||||||
|                 android:title="@string/watch_quality_pref" |                 android:title="@string/watch_quality_pref" | ||||||
|                 android:icon="@drawable/ic_baseline_hd_24" /> |                 android:icon="@drawable/ic_baseline_hd_24" /> | ||||||
|  |         <Preference | ||||||
|  |             android:key="@string/prefer_limit_title_key" | ||||||
|  |             android:title="@string/limit_title" | ||||||
|  |             android:icon="@drawable/ic_baseline_text_format_24" /> | ||||||
| 
 | 
 | ||||||
|         <SwitchPreference |         <SwitchPreference | ||||||
|                 android:icon="@drawable/ic_baseline_picture_in_picture_alt_24" |                 android:icon="@drawable/ic_baseline_picture_in_picture_alt_24" | ||||||
|  | @ -162,6 +166,31 @@ | ||||||
|                 app:defaultValue="true" /> |                 app:defaultValue="true" /> | ||||||
|     </PreferenceCategory> |     </PreferenceCategory> | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  |     <PreferenceCategory | ||||||
|  |         android:key="@string/nginx_category" | ||||||
|  |         android:title="@string/nginx_category" | ||||||
|  |         app:isPreferenceVisible="true"> | ||||||
|  |         <Preference | ||||||
|  |             android:key="@string/nginx_url_key" | ||||||
|  |             android:title="@string/nginx_url_pref" | ||||||
|  |             android:icon="@drawable/ic_baseline_play_arrow_24" /> | ||||||
|  |         <Preference | ||||||
|  |             android:key="@string/nginx_credentials" | ||||||
|  |             android:title="@string/nginx_credentials_title" | ||||||
|  |             android:icon="@drawable/video_locked" | ||||||
|  |             android:summary="@string/nginx_credentials_summary"/> | ||||||
|  |         <Preference | ||||||
|  |             android:key="@string/nginx_info" | ||||||
|  |             android:title="@string/nginx_info_title" | ||||||
|  |             android:icon="@drawable/ic_baseline_play_arrow_24" | ||||||
|  | 	        android:summary="@string/nginx_info_summary" > | ||||||
|  |             <intent | ||||||
|  |                 android:action="android.intent.action.VIEW" | ||||||
|  |                 android:data="https://www.sarlays.com/use-nginx-with-cloudstream/" /> | ||||||
|  |         </Preference> | ||||||
|  |     </PreferenceCategory> | ||||||
|  | 
 | ||||||
|     <PreferenceCategory |     <PreferenceCategory | ||||||
|             android:key="info" |             android:key="info" | ||||||
|             android:title="@string/settings_info" |             android:title="@string/settings_info" | ||||||
|  |  | ||||||
							
								
								
									
										120
									
								
								docs/index.html
									
										
									
									
									
								
							
							
						
						
									
										120
									
								
								docs/index.html
									
										
									
									
									
								
							|  | @ -7,131 +7,17 @@ | ||||||
|     <meta http-equiv="X-UA-Compatible" content="ie=edge"> |     <meta http-equiv="X-UA-Compatible" content="ie=edge"> | ||||||
| 	<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto"> | 	<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto"> | ||||||
|     <title>CloudStream-3 Supported Sites</title> |     <title>CloudStream-3 Supported Sites</title> | ||||||
|     <script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js'></script> | 	<link rel="stylesheet" href="style.css"> | ||||||
| 	 |  | ||||||
| 	<style> |  | ||||||
| 	body { |  | ||||||
| 		font-family: "Roboto", sans-serif; |  | ||||||
| 		background-color: #FFF; |  | ||||||
| 	} |  | ||||||
| 	.whiteText { |  | ||||||
| 		color : #FFF; |  | ||||||
| 	} |  | ||||||
| 	 |  | ||||||
| 	.button { |  | ||||||
| 		color : #000; |  | ||||||
| 		text-decoration: none; |  | ||||||
| 	} |  | ||||||
| 	 |  | ||||||
| 	.redButton {	 |  | ||||||
| 	} |  | ||||||
| 	.blueButton { |  | ||||||
| 	} |  | ||||||
| 	.greenButton { |  | ||||||
| 	} |  | ||||||
| 	.yellowButton { |  | ||||||
| 	} |  | ||||||
| 	.row { |  | ||||||
| 		padding: 0px 10px; |  | ||||||
| 		white-space: nowrap; |  | ||||||
| 	} |  | ||||||
| 	table { |  | ||||||
| 		border-spacing: 0.5rem; |  | ||||||
| 	} |  | ||||||
| 	 |  | ||||||
| 	.yellowButton::before { |  | ||||||
| 		background-image: url("data:image/svg+xml;charset=utf8,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='m12 24c6.6274 0 12-5.3726 12-12 0-6.62744-5.3726-12-12-12-6.62744 0-12 5.37256-12 12 0 6.6274 5.37256 12 12 12zm0-17.5c.4141 0 .75.33582.75.75v4.5c0 .4142-.3359.75-.75.75s-.75-.3358-.75-.75v-4.5c0-.41418.3359-.75.75-.75zm.8242 9.5658c.0635-.0919.1118-.195.1416-.3054.0132-.0482.0225-.0979.0283-.1487.0039-.0366.0059-.074.0059-.1117 0-.5522-.4478-1-1-1s-1 .4478-1 1 .4478 1 1 1c.3423 0 .644-.172.8242-.4342z' fill='%23dbab09'/%3E%3C/svg%3e"); |  | ||||||
| 	} |  | ||||||
| 	 |  | ||||||
| 	.blueButton::before { |  | ||||||
| 		filter: sepia(100%) saturate(300%) brightness(70%) hue-rotate(180deg); |  | ||||||
| 		background-image: url("data:image/svg+xml;charset=utf8,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='m12 24c6.6274 0 12-5.3726 12-12 0-6.62744-5.3726-12-12-12-6.62744 0-12 5.37256-12 12 0 6.6274 5.37256 12 12 12zm0-17.5c.4141 0 .75.33582.75.75v4.5c0 .4142-.3359.75-.75.75s-.75-.3358-.75-.75v-4.5c0-.41418.3359-.75.75-.75zm.8242 9.5658c.0635-.0919.1118-.195.1416-.3054.0132-.0482.0225-.0979.0283-.1487.0039-.0366.0059-.074.0059-.1117 0-.5522-.4478-1-1-1s-1 .4478-1 1 .4478 1 1 1c.3423 0 .644-.172.8242-.4342z' fill='%23dbab09'/%3E%3C/svg%3e"); |  | ||||||
| 	} |  | ||||||
| 	 |  | ||||||
| 	.redButton::before { |  | ||||||
| 		background-image: url("data:image/svg+xml;charset=utf8,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='m12 24c6.6274 0 12-5.3726 12-12 0-6.62744-5.3726-12-12-12-6.62744 0-12 5.37256-12 12 0 6.6274 5.37256 12 12 12zm0-17.5c.4141 0 .75.33582.75.75v4.5c0 .4142-.3359.75-.75.75s-.75-.3358-.75-.75v-4.5c0-.41418.3359-.75.75-.75zm.8242 9.5658c.0635-.0919.1118-.195.1416-.3054.0132-.0482.0225-.0979.0283-.1487.0039-.0366.0059-.074.0059-.1117 0-.5522-.4478-1-1-1s-1 .4478-1 1 .4478 1 1 1c.3423 0 .644-.172.8242-.4342z' fill='%23d73a49'/%3E%3C/svg%3e"); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	.greenButton::before{ |  | ||||||
| 		background-image: url("data:image/svg+xml;charset=utf8,%3Csvg width='15' height='15' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='m1 12c0-6.07513 4.92487-11 11-11 6.0751 0 11 4.92487 11 11 0 6.0751-4.9249 11-11 11-6.07513 0-11-4.9249-11-11zm16.2803-2.71967c.2929-.29289.2929-.76777 0-1.06066s-.7677-.29289-1.0606 0l-5.9697 5.96963-2.46967-2.4696c-.29289-.2929-.76777-.2929-1.06066 0s-.29289.7677 0 1.0606l3 3c.29293.2929.76773.2929 1.06063 0z' fill='%2328a745'/%3E%3C/svg%3e");ontent: ''; |  | ||||||
|     } |  | ||||||
| 	 |  | ||||||
| 	.indicator::before { |  | ||||||
| 		display: inline-block; |  | ||||||
| 		width: 24px; |  | ||||||
| 		height: 24px; |  | ||||||
| 		content: ""; |  | ||||||
| 		vertical-align: text-bottom; |  | ||||||
| 		background-size: 100% 100%; |  | ||||||
| 		background-repeat: no-repeat; |  | ||||||
| 		background-position: center center; |  | ||||||
| 		margin-right:10px; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	</style> |  | ||||||
| </head> | </head> | ||||||
| 
 | 
 | ||||||
| <body> | <body> | ||||||
|     <div> |     <div> | ||||||
|         <h1>Site supported:</h1> |         <h1>Sites supported (<span id="count">0</span>):</h1> | ||||||
|         <table> |         <table> | ||||||
| 		<tbody id="siteList"></tbody> | 		<tbody id="siteList"></tbody> | ||||||
|         </table> |         </table> | ||||||
|     </div> |     </div> | ||||||
|     <script> |     <script src="script.js" type="text/javascript"></script> | ||||||
|         var status = document.getElementById("status"); |  | ||||||
|         var mainContainer = document.getElementById("siteList"); |  | ||||||
|         $(document).ready(function () { |  | ||||||
|             $.getJSON("providers.json", function (data) { |  | ||||||
|                 status.innerHTML = "Parsing..."; |  | ||||||
|                 for (var key in data) { |  | ||||||
|                     status.innerHTML = "Reading..." + key; |  | ||||||
|                     if (data.hasOwnProperty(key)) { |  | ||||||
|                         var value = data[key]; |  | ||||||
|                         if(value.url == "NONE") { continue; } |  | ||||||
|                          |  | ||||||
|                         var _status = value.status |  | ||||||
| 
 |  | ||||||
|                         var node = document.createElement("tr"); |  | ||||||
| 						node.classList.add("row"); |  | ||||||
| 
 |  | ||||||
|                         var _a = document.createElement("a"); |  | ||||||
|                         _a.setAttribute('href', value.url); |  | ||||||
|                         _a.innerHTML = value.name |  | ||||||
| 						 |  | ||||||
| 					    var _statusText = "Unknown"; |  | ||||||
|                         var _buttonText = "yellow"; |  | ||||||
|                         switch (_status) { |  | ||||||
|                             case 0: |  | ||||||
|                                 _statusText = "Unavailable"; |  | ||||||
|                                 _buttonText = "red"; |  | ||||||
|                                 break; |  | ||||||
|                             case 1: |  | ||||||
|                                 _statusText = "Available";	 |  | ||||||
| 								_buttonText = "green"; |  | ||||||
| 
 |  | ||||||
|                                 break; |  | ||||||
|                             case 2: |  | ||||||
|                                 _statusText = "Slow"; |  | ||||||
| 								_buttonText = "yellow"; |  | ||||||
|                                 break; |  | ||||||
|                             case 3: |  | ||||||
|                                 _statusText = "Beta"; |  | ||||||
| 								_buttonText = "blue"; |  | ||||||
|                                 break; |  | ||||||
|                         } |  | ||||||
| 						_a.classList.add(_buttonText+"Button"); |  | ||||||
| 						_a.classList.add("indicator"); |  | ||||||
| 						_a.classList.add("button"); |  | ||||||
| 						node.appendChild(_a); |  | ||||||
|                         mainContainer.appendChild(node); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             }).fail(function () { |  | ||||||
|                 console.log("An error has occurred."); |  | ||||||
|             }); |  | ||||||
|         }); |  | ||||||
|     </script> |  | ||||||
| </body> | </body> | ||||||
| 
 | 
 | ||||||
| </html> | </html> | ||||||
|  |  | ||||||
|  | @ -1,287 +1,322 @@ | ||||||
| { | { | ||||||
|     "AkwamProvider": { |     "AkwamProvider": { | ||||||
|         "name": "Akwam", |         "name": "Akwam", | ||||||
|         "url": "https://akwam.to", |         "status": 1, | ||||||
|         "status": 1 |         "url": "https://akwam.to" | ||||||
|     }, |     }, | ||||||
|     "AllAnimeProvider": { |     "AllAnimeProvider": { | ||||||
|         "name": "AllAnime", |         "name": "AllAnime", | ||||||
|         "url": "https://allanime.site", |         "status": 1, | ||||||
|         "status": 1 |         "url": "https://allanime.site" | ||||||
|     }, |     }, | ||||||
|     "AllMoviesForYouProvider": { |     "AllMoviesForYouProvider": { | ||||||
|         "name": "AllMoviesForYou", |         "name": "AllMoviesForYou", | ||||||
|         "url": "https://allmoviesforyou.net", |         "status": 1, | ||||||
|         "status": 1 |         "url": "https://allmoviesforyou.net" | ||||||
|     }, |     }, | ||||||
|     "AnimeFlickProvider": { |     "AnimeFlickProvider": { | ||||||
|         "name": "AnimeFlick", |         "name": "AnimeFlick", | ||||||
|         "url": "https://animeflick.net", |         "status": 1, | ||||||
|         "status": 1 |         "url": "https://animeflick.net" | ||||||
|     }, |     }, | ||||||
|     "AnimePaheProvider": { |     "AnimePaheProvider": { | ||||||
|         "name": "AnimePahe", |         "name": "AnimePahe", | ||||||
|         "url": "https://animepahe.com", |         "status": 0, | ||||||
|         "status": 0 |         "url": "https://animepahe.com" | ||||||
|     }, |     }, | ||||||
|     "AnimeWorldProvider": { |     "AnimeWorldProvider": { | ||||||
|         "name": "AnimeWorld", |         "name": "AnimeWorld", | ||||||
|         "url": "https://www.animeworld.tv", |         "status": 1, | ||||||
|         "status": 1 |         "url": "https://www.animeworld.tv" | ||||||
|  |     }, | ||||||
|  |     "AnimeflvProvider": { | ||||||
|  |         "name": "Animeflv", | ||||||
|  |         "status": 1, | ||||||
|  |         "url": "https://www3.animeflv.net" | ||||||
|     }, |     }, | ||||||
|     "AnimeflvnetProvider": { |     "AnimeflvnetProvider": { | ||||||
|         "name": "Animeflv.net", |         "name": "Animeflv.net", | ||||||
|         "url": "https://www3.animeflv.net", |         "status": 1, | ||||||
|         "status": 1 |         "url": "https://www3.animeflv.net" | ||||||
|     }, |     }, | ||||||
|     "AnimekisaProvider": { |     "AnimekisaProvider": { | ||||||
|         "name": "Animekisa", |         "name": "Animekisa", | ||||||
|         "url": "https://animekisa.in", |         "status": 1, | ||||||
|         "status": 1 |         "url": "https://animekisa.in" | ||||||
|     }, |     }, | ||||||
|     "AsianLoadProvider": { |     "ApiMDBProvider": { | ||||||
|         "name": "AsianLoad", |         "name": "ApiMDB", | ||||||
|         "url": "https://asianembed.io", |         "status": 1, | ||||||
|         "status": 1 |         "url": "https://v2.apimdb.net" | ||||||
|     }, |     }, | ||||||
|     "AsiaFlixProvider": { |     "AsiaFlixProvider": { | ||||||
|         "name": "AsiaFlix", |         "name": "AsiaFlix", | ||||||
|         "url": "https://asiaflix.app", |         "status": 3, | ||||||
|         "status": 3 |         "url": "https://asiaflix.app" | ||||||
|  |     }, | ||||||
|  |     "AsianLoadProvider": { | ||||||
|  |         "name": "AsianLoad", | ||||||
|  |         "status": 1, | ||||||
|  |         "url": "https://asianembed.io" | ||||||
|     }, |     }, | ||||||
|     "BflixProvider": { |     "BflixProvider": { | ||||||
|         "name": "Bflix", |         "name": "Bflix", | ||||||
|         "url": "https://bflix.to", |         "status": 1, | ||||||
|         "status": 1 |         "url": "https://fmovies.to" | ||||||
|     }, |  | ||||||
|     "FmoviesToProvider": { |  | ||||||
|         "name": "Fmovies.to", |  | ||||||
|         "url": "https://fmovies.to", |  | ||||||
|         "status": 1 |  | ||||||
|     }, |  | ||||||
|     "SflixProProvider": { |  | ||||||
|         "name": "Sflix.pro", |  | ||||||
|         "url": "https://sflix.pro", |  | ||||||
|         "status": 1 |  | ||||||
|     }, |     }, | ||||||
|     "CinecalidadProvider": { |     "CinecalidadProvider": { | ||||||
|         "name": "Cinecalidad", |         "name": "Cinecalidad", | ||||||
|         "url": "https://cinecalidad.lol", |         "status": 1, | ||||||
|         "status": 1 |         "url": "https://cinecalidad.lol" | ||||||
|     }, |     }, | ||||||
|     "CrossTmdbProvider": { |     "CrossTmdbProvider": { | ||||||
|         "name": "MultiMovie", |         "name": "MultiMovie", | ||||||
|         "url": "NONE", |         "status": 1, | ||||||
|         "status": 1 |         "url": "NONE" | ||||||
|     }, |     }, | ||||||
|     "CuevanaProvider": { |     "CuevanaProvider": { | ||||||
|         "name": "Cuevana", |         "name": "Cuevana", | ||||||
|         "url": "https://cuevana3.me", |         "status": 1, | ||||||
|         "status": 1 |         "url": "https://cuevana3.me" | ||||||
|     }, |  | ||||||
|     "DoramasYTProvider": { |  | ||||||
|         "name": "DoramasYT", |  | ||||||
|         "url": "https://doramasyt.com", |  | ||||||
|         "status": 1 |  | ||||||
|     }, |  | ||||||
|     "DramaSeeProvider": { |  | ||||||
|         "name": "DramaSee", |  | ||||||
|         "url": "https://dramasee.net", |  | ||||||
|         "status": 1 |  | ||||||
|     }, |  | ||||||
|     "DubbedAnimeProvider": { |  | ||||||
|         "name": "DubbedAnime", |  | ||||||
|         "url": "https://bestdubbedanime.com", |  | ||||||
|         "status": 1 |  | ||||||
|     }, |  | ||||||
|     "EgyBestProvider": { |  | ||||||
|         "name": "EgyBest", |  | ||||||
|         "url": "https://egy.best", |  | ||||||
|         "status": 1 |  | ||||||
|     }, |  | ||||||
|     "EntrepeliculasyseriesProvider": { |  | ||||||
|         "name": "EntrePeliculasySeries", |  | ||||||
|         "url": "https://entrepeliculasyseries.nu", |  | ||||||
|         "status": 1 |  | ||||||
|     }, |  | ||||||
|     "FilmanProvider": { |  | ||||||
|         "name": "filman.cc", |  | ||||||
|         "url": "https://filman.cc", |  | ||||||
|         "status": 1 |  | ||||||
|     }, |  | ||||||
|     "FrenchStreamProvider": { |  | ||||||
|         "name": "French Stream", |  | ||||||
|         "url": "https://french-stream.re", |  | ||||||
|         "status": 1 |  | ||||||
|     }, |  | ||||||
|     "GogoanimeProvider": { |  | ||||||
|         "name": "GogoAnime", |  | ||||||
|         "url": "https://gogoanime.film", |  | ||||||
|         "status": 1 |  | ||||||
|     }, |  | ||||||
|     "KawaiifuProvider": { |  | ||||||
|         "name": "Kawaiifu", |  | ||||||
|         "url": "https://kawaiifu.com", |  | ||||||
|         "status": 0 |  | ||||||
|     }, |  | ||||||
|     "HDMProvider": { |  | ||||||
|         "name": "HD Movies", |  | ||||||
|         "url": "https://hdm.to", |  | ||||||
|         "status": 0 |  | ||||||
|     }, |  | ||||||
|     "IHaveNoTvProvider": { |  | ||||||
|         "name": "I Have No TV", |  | ||||||
|         "url": "https://ihavenotv.com", |  | ||||||
|         "status": 1 |  | ||||||
|     }, |  | ||||||
|     "KdramaHoodProvider": { |  | ||||||
|         "name": "KDramaHood", |  | ||||||
|         "url": "https://kdramahood.com", |  | ||||||
|         "status": 1 |  | ||||||
|     }, |  | ||||||
|     "LookMovieProvider": { |  | ||||||
|         "name": "LookMovie", |  | ||||||
|         "url": "https://lookmovie.io", |  | ||||||
|         "status": 0 |  | ||||||
|     }, |  | ||||||
|     "MeloMovieProvider": { |  | ||||||
|         "name": "MeloMovie", |  | ||||||
|         "url": "https://melomovie.com", |  | ||||||
|         "status": 0 |  | ||||||
|     }, |  | ||||||
|     "MonoschinosProvider": { |  | ||||||
|         "name": "Monoschinos", |  | ||||||
|         "url": "https://monoschinos2.com", |  | ||||||
|         "status": 1 |  | ||||||
|     }, |  | ||||||
|     "MyCimaProvider": { |  | ||||||
|         "name": "MyCima", |  | ||||||
|         "url": "https://mycima.tv", |  | ||||||
|         "status": 1 |  | ||||||
|     }, |  | ||||||
|     "NineAnimeProvider": { |  | ||||||
|         "name": "9Anime", |  | ||||||
|         "url": "https://9anime.id", |  | ||||||
|         "status": 1 |  | ||||||
|     }, |  | ||||||
|     "PeliSmartProvider": { |  | ||||||
|         "name": "PeliSmart", |  | ||||||
|         "url": "https://pelismart.com", |  | ||||||
|         "status": 1 |  | ||||||
|     }, |  | ||||||
|     "PelisflixProvider": { |  | ||||||
|         "name": "Pelisflix", |  | ||||||
|         "url": "https://pelisflix.li", |  | ||||||
|         "status": 1 |  | ||||||
|     }, |  | ||||||
|     "PelisplusHDProvider": { |  | ||||||
|         "name": "PelisplusHD", |  | ||||||
|         "url": "https://pelisplushd.net", |  | ||||||
|         "status": 1 |  | ||||||
|     }, |  | ||||||
|     "PelisplusProvider": { |  | ||||||
|         "name": "Pelisplus", |  | ||||||
|         "url": "https://pelisplus.icu", |  | ||||||
|         "status": 1 |  | ||||||
|     }, |  | ||||||
|     "PinoyHDXyzProvider": { |  | ||||||
|         "name": "Pinoy-HD", |  | ||||||
|         "url": "https://www.pinoy-hd.xyz", |  | ||||||
|         "status": 1 |  | ||||||
|     }, |  | ||||||
|     "PinoyMoviePediaProvider": { |  | ||||||
|         "name": "Pinoy Moviepedia", |  | ||||||
|         "url": "https://pinoymoviepedia.ru", |  | ||||||
|         "status": 1 |  | ||||||
|     }, |  | ||||||
|     "PinoyMoviesEsProvider": { |  | ||||||
|         "name": "Pinoy Movies", |  | ||||||
|         "url": "https://pinoymovies.es", |  | ||||||
|         "status": 1 |  | ||||||
|     }, |  | ||||||
|     "SflixProvider": { |  | ||||||
|         "name": "Sflix.to", |  | ||||||
|         "url": "https://sflix.to", |  | ||||||
|         "status": 1 |  | ||||||
|     }, |     }, | ||||||
|     "DopeboxProvider": { |     "DopeboxProvider": { | ||||||
|         "name": "Dopebox", |         "name": "Dopebox", | ||||||
|         "url": "https://dopebox.to", |         "status": 1, | ||||||
|         "status": 1 |         "url": "https://dopebox.to" | ||||||
|     }, |     }, | ||||||
|     "SolarmovieProvider": { |     "DoramasYTProvider": { | ||||||
|         "name": "Solarmovie", |         "name": "DoramasYT", | ||||||
|         "url": "https://solarmovie.pe", |         "status": 1, | ||||||
|         "status": 1 |         "url": "https://doramasyt.com" | ||||||
|  |     }, | ||||||
|  |     "DramaSeeProvider": { | ||||||
|  |         "name": "DramaSee", | ||||||
|  |         "status": 1, | ||||||
|  |         "url": "https://dramasee.net" | ||||||
|  |     }, | ||||||
|  |     "DubbedAnimeProvider": { | ||||||
|  |         "name": "DubbedAnime", | ||||||
|  |         "status": 1, | ||||||
|  |         "url": "https://bestdubbedanime.com" | ||||||
|  |     }, | ||||||
|  |     "EgyBestProvider": { | ||||||
|  |         "name": "EgyBest", | ||||||
|  |         "status": 1, | ||||||
|  |         "url": "https://www.egy.best" | ||||||
|  |     }, | ||||||
|  |     "EntrePeliculasySeriesProvider": { | ||||||
|  |         "name": "EntrePeliculasySeries", | ||||||
|  |         "status": 1, | ||||||
|  |         "url": "https://entrepeliculasyseries.nu" | ||||||
|  |     }, | ||||||
|  |     "EntrepeliculasyseriesProvider": { | ||||||
|  |         "name": "EntrePeliculasySeries", | ||||||
|  |         "status": 1, | ||||||
|  |         "url": "https://entrepeliculasyseries.nu" | ||||||
|  |     }, | ||||||
|  |     "FilmanProvider": { | ||||||
|  |         "name": "filman.cc", | ||||||
|  |         "status": 1, | ||||||
|  |         "url": "https://filman.cc" | ||||||
|  |     }, | ||||||
|  |     "FmoviesToProvider": { | ||||||
|  |         "name": "Fmovies.to", | ||||||
|  |         "status": 1, | ||||||
|  |         "url": "https://fmovies.to" | ||||||
|  |     }, | ||||||
|  |     "FrenchStreamProvider": { | ||||||
|  |         "name": "French Stream", | ||||||
|  |         "status": 1, | ||||||
|  |         "url": "https://french-stream.re" | ||||||
|  |     }, | ||||||
|  |     "GogoanimeProvider": { | ||||||
|  |         "name": "GogoAnime", | ||||||
|  |         "status": 1, | ||||||
|  |         "url": "https://gogoanime.film" | ||||||
|  |     }, | ||||||
|  |     "HDMProvider": { | ||||||
|  |         "name": "HD Movies", | ||||||
|  |         "status": 0, | ||||||
|  |         "url": "https://hdm.to" | ||||||
|  |     }, | ||||||
|  |     "IHaveNoTvProvider": { | ||||||
|  |         "name": "I Have No TV", | ||||||
|  |         "status": 1, | ||||||
|  |         "url": "https://ihavenotv.com" | ||||||
|  |     }, | ||||||
|  |     "KawaiifuProvider": { | ||||||
|  |         "name": "Kawaiifu", | ||||||
|  |         "status": 0, | ||||||
|  |         "url": "https://kawaiifu.com" | ||||||
|  |     }, | ||||||
|  |     "KdramaHoodProvider": { | ||||||
|  |         "name": "KDramaHood", | ||||||
|  |         "status": 1, | ||||||
|  |         "url": "https://kdramahood.com" | ||||||
|  |     }, | ||||||
|  |     "LookMovieProvider": { | ||||||
|  |         "name": "LookMovie", | ||||||
|  |         "status": 0, | ||||||
|  |         "url": "https://lookmovie.io" | ||||||
|  |     }, | ||||||
|  |     "MeloMovieProvider": { | ||||||
|  |         "name": "MeloMovie", | ||||||
|  |         "status": 0, | ||||||
|  |         "url": "https://melomovie.com" | ||||||
|  |     }, | ||||||
|  |     "MonoschinosProvider": { | ||||||
|  |         "name": "Monoschinos", | ||||||
|  |         "status": 1, | ||||||
|  |         "url": "https://monoschinos2.com" | ||||||
|  |     }, | ||||||
|  |     "MultiAnimeProvider": { | ||||||
|  |         "name": "MultiAnime", | ||||||
|  |         "status": 1, | ||||||
|  |         "url": "" | ||||||
|  |     }, | ||||||
|  |     "MyCimaProvider": { | ||||||
|  |         "name": "MyCima", | ||||||
|  |         "status": 1, | ||||||
|  |         "url": "https://mycima.tv" | ||||||
|  |     }, | ||||||
|  |     "NginxProvider": { | ||||||
|  |         "name": "Nginx", | ||||||
|  |         "status": 1, | ||||||
|  |         "url": "" | ||||||
|  |     }, | ||||||
|  |     "NineAnimeProvider": { | ||||||
|  |         "name": "9Anime", | ||||||
|  |         "status": 1, | ||||||
|  |         "url": "https://9anime.id" | ||||||
|  |     }, | ||||||
|  |     "NyaaProvider": { | ||||||
|  |         "name": "Nyaa", | ||||||
|  |         "status": 1, | ||||||
|  |         "url": "https://nyaa.si" | ||||||
|  |     }, | ||||||
|  |     "PeliSmartProvider": { | ||||||
|  |         "name": "PeliSmart", | ||||||
|  |         "status": 1, | ||||||
|  |         "url": "https://pelismart.com" | ||||||
|  |     }, | ||||||
|  |     "PelisflixProvider": { | ||||||
|  |         "name": "Pelisflix", | ||||||
|  |         "status": 1, | ||||||
|  |         "url": "https://pelisflix.li" | ||||||
|  |     }, | ||||||
|  |     "PelisplusHDProvider": { | ||||||
|  |         "name": "PelisplusHD", | ||||||
|  |         "status": 1, | ||||||
|  |         "url": "https://pelisplushd.net" | ||||||
|  |     }, | ||||||
|  |     "PelisplusProvider": { | ||||||
|  |         "name": "Pelisplus", | ||||||
|  |         "status": 1, | ||||||
|  |         "url": "https://pelisplus.icu" | ||||||
|  |     }, | ||||||
|  |     "PinoyHDXyzProvider": { | ||||||
|  |         "name": "Pinoy-HD", | ||||||
|  |         "status": 1, | ||||||
|  |         "url": "https://www.pinoy-hd.xyz" | ||||||
|  |     }, | ||||||
|  |     "PinoyMoviePediaProvider": { | ||||||
|  |         "name": "Pinoy Moviepedia", | ||||||
|  |         "status": 1, | ||||||
|  |         "url": "https://pinoymoviepedia.ru" | ||||||
|  |     }, | ||||||
|  |     "PinoyMoviesEsProvider": { | ||||||
|  |         "name": "Pinoy Movies", | ||||||
|  |         "status": 1, | ||||||
|  |         "url": "https://pinoymovies.es" | ||||||
|     }, |     }, | ||||||
|     "SeriesflixProvider": { |     "SeriesflixProvider": { | ||||||
|         "name": "Seriesflix", |         "name": "Seriesflix", | ||||||
|         "url": "https://seriesflix.video", |         "status": 1, | ||||||
|         "status": 1 |         "url": "https://seriesflix.video" | ||||||
|  |     }, | ||||||
|  |     "SflixProProvider": { | ||||||
|  |         "name": "Sflix.pro", | ||||||
|  |         "status": 1, | ||||||
|  |         "url": "https://sflix.pro" | ||||||
|  |     }, | ||||||
|  |     "SflixProvider": { | ||||||
|  |         "name": "Sflix.to", | ||||||
|  |         "status": 1, | ||||||
|  |         "url": "https://dopebox.to" | ||||||
|     }, |     }, | ||||||
|     "SoaptwoDayProvider": { |     "SoaptwoDayProvider": { | ||||||
|         "name": "Soap2Day", |         "name": "Soap2Day", | ||||||
|         "url": "https://secretlink.xyz", |         "status": 1, | ||||||
|         "status": 1 |         "url": "https://secretlink.xyz" | ||||||
|  |     }, | ||||||
|  |     "SolarmovieProvider": { | ||||||
|  |         "name": "Solarmovie", | ||||||
|  |         "status": 1, | ||||||
|  |         "url": "https://solarmovie.pe" | ||||||
|     }, |     }, | ||||||
|     "TenshiProvider": { |     "TenshiProvider": { | ||||||
|         "name": "Tenshi.moe", |         "name": "Tenshi.moe", | ||||||
|         "url": "https://tenshi.moe", |         "status": 1, | ||||||
|         "status": 1 |         "url": "https://tenshi.moe" | ||||||
|     }, |  | ||||||
|     "TrailersTwoProvider": { |  | ||||||
|         "name": "Trailers.to", |  | ||||||
|         "url": "https://trailers.to", |  | ||||||
|         "status": 1 |  | ||||||
|     }, |     }, | ||||||
|     "TheFlixToProvider": { |     "TheFlixToProvider": { | ||||||
|         "name": "TheFlix.to", |         "name": "TheFlix.to", | ||||||
|         "url": "https://theflix.to", |         "status": 0, | ||||||
|         "status": 0 |         "url": "https://theflix.to" | ||||||
|  |     }, | ||||||
|  |     "TmdbProvider": { | ||||||
|  |         "name": "Tmdb", | ||||||
|  |         "status": 1, | ||||||
|  |         "url": "" | ||||||
|  |     }, | ||||||
|  |     "TrailersTwoProvider": { | ||||||
|  |         "name": "Trailers.to", | ||||||
|  |         "status": 1, | ||||||
|  |         "url": "https://trailers.to" | ||||||
|     }, |     }, | ||||||
|     "TwoEmbedProvider": { |     "TwoEmbedProvider": { | ||||||
|         "name": "2Embed", |         "name": "2Embed", | ||||||
|         "url": "https://www.2embed.ru", |         "status": 1, | ||||||
|         "status": 1 |         "url": "https://www.2embed.ru" | ||||||
|     }, |     }, | ||||||
|     "VMoveeProvider": { |     "VMoveeProvider": { | ||||||
|         "name": "VMovee", |         "name": "VMovee", | ||||||
|         "url": "https://www.vmovee.watch", |         "status": 1, | ||||||
|         "status": 1 |         "url": "https://www.vmovee.watch" | ||||||
|     }, |     }, | ||||||
|     "VfFilmProvider": { |     "VfFilmProvider": { | ||||||
|         "name": "vf-film.me", |         "name": "vf-film.me", | ||||||
|         "url": "https://vf-film.me", |         "status": 1, | ||||||
|         "status": 1 |         "url": "https://vf-film.me" | ||||||
|     }, |     }, | ||||||
|     "VfSerieProvider": { |     "VfSerieProvider": { | ||||||
|         "name": "vf-serie.org", |         "name": "vf-serie.org", | ||||||
|         "url": "https://vf-serie.org", |         "status": 1, | ||||||
|         "status": 1 |         "url": "https://vf-serie.org" | ||||||
|     }, |     }, | ||||||
|     "VidEmbedProvider": { |     "VidEmbedProvider": { | ||||||
|         "name": "VidEmbed", |         "name": "VidEmbed", | ||||||
|         "url": "https://vidembed.cc", |         "status": 1, | ||||||
|         "status": 1 |         "url": "https://vidembed.cc" | ||||||
|     }, |     }, | ||||||
|     "WatchAsianProvider": { |     "WatchAsianProvider": { | ||||||
|         "name": "WatchAsian", |         "name": "WatchAsian", | ||||||
|         "url": "https://watchasian.sh", |         "status": 1, | ||||||
|         "status": 1 |         "url": "https://watchasian.sh" | ||||||
|     }, |     }, | ||||||
|     "WatchCartoonOnlineProvider": { |     "WatchCartoonOnlineProvider": { | ||||||
|         "name": "WatchCartoonOnline", |         "name": "WatchCartoonOnline", | ||||||
|         "url": "https://www.wcostream.com", |         "status": 1, | ||||||
|         "status": 1 |         "url": "https://www.wcostream.com" | ||||||
|     }, |     }, | ||||||
|     "WcoProvider": { |     "WcoProvider": { | ||||||
|         "name": "WCO Stream", |         "name": "WCO Stream", | ||||||
|         "url": "https://wcostream.cc", |         "status": 1, | ||||||
|         "status": 1 |         "url": "https://wcostream.cc" | ||||||
|     }, |     }, | ||||||
|     "ZoroProvider": { |     "ZoroProvider": { | ||||||
|         "name": "Zoro", |         "name": "Zoro", | ||||||
|         "url": "https://zoro.to", |         "status": 1, | ||||||
|         "status": 1 |         "url": "https://zoro.to" | ||||||
|     } |     } | ||||||
| } | } | ||||||
							
								
								
									
										49
									
								
								docs/script.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								docs/script.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,49 @@ | ||||||
|  | const count = document.getElementById("count") | ||||||
|  | const mainContainer = document.getElementById("siteList"); | ||||||
|  | fetch("providers.json" + "?v=" + Date.now()) | ||||||
|  |     .then(r => r.json()) | ||||||
|  |     .then(function (data) { | ||||||
|  |         count.innerHTML = Object.keys(data).length; | ||||||
|  |         for (var key in data) { | ||||||
|  |             if (data.hasOwnProperty(key)) { | ||||||
|  |                 var value = data[key]; | ||||||
|  |                 if (value.url == "NONE") { continue; } | ||||||
|  | 
 | ||||||
|  |                 var _status = value.status | ||||||
|  | 
 | ||||||
|  |                 var node = document.createElement("tr"); | ||||||
|  |                 node.classList.add("row"); | ||||||
|  | 
 | ||||||
|  |                 var _a = document.createElement("a"); | ||||||
|  |                 _a.setAttribute('href', value.url); | ||||||
|  |                 _a.innerHTML = value.name | ||||||
|  | 
 | ||||||
|  |                 var _statusText = "Unknown"; | ||||||
|  |                 var _buttonText = "yellow"; | ||||||
|  |                 switch (_status) { | ||||||
|  |                     case 0: | ||||||
|  |                         _statusText = "Unavailable"; | ||||||
|  |                         _buttonText = "red"; | ||||||
|  |                         break; | ||||||
|  |                     case 1: | ||||||
|  |                         _statusText = "Available"; | ||||||
|  |                         _buttonText = "green"; | ||||||
|  | 
 | ||||||
|  |                         break; | ||||||
|  |                     case 2: | ||||||
|  |                         _statusText = "Slow"; | ||||||
|  |                         _buttonText = "yellow"; | ||||||
|  |                         break; | ||||||
|  |                     case 3: | ||||||
|  |                         _statusText = "Beta"; | ||||||
|  |                         _buttonText = "blue"; | ||||||
|  |                         break; | ||||||
|  |                 } | ||||||
|  |                 _a.classList.add(_buttonText + "Button"); | ||||||
|  |                 _a.classList.add("indicator"); | ||||||
|  |                 _a.classList.add("button"); | ||||||
|  |                 node.appendChild(_a); | ||||||
|  |                 mainContainer.appendChild(node); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     }) | ||||||
							
								
								
									
										49
									
								
								docs/style.css
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								docs/style.css
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,49 @@ | ||||||
|  | body { | ||||||
|  |     font-family: "Roboto", sans-serif; | ||||||
|  |     background-color: #FFF; | ||||||
|  | } | ||||||
|  | .whiteText { | ||||||
|  |     color : #FFF; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .button { | ||||||
|  |     color : #000; | ||||||
|  |     text-decoration: none; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .row { | ||||||
|  |     padding: 0px 10px; | ||||||
|  |     white-space: nowrap; | ||||||
|  | } | ||||||
|  | table { | ||||||
|  |     border-spacing: 0.5rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .yellowButton::before { | ||||||
|  |     background-image: url("data:image/svg+xml;charset=utf8,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='m12 24c6.6274 0 12-5.3726 12-12 0-6.62744-5.3726-12-12-12-6.62744 0-12 5.37256-12 12 0 6.6274 5.37256 12 12 12zm0-17.5c.4141 0 .75.33582.75.75v4.5c0 .4142-.3359.75-.75.75s-.75-.3358-.75-.75v-4.5c0-.41418.3359-.75.75-.75zm.8242 9.5658c.0635-.0919.1118-.195.1416-.3054.0132-.0482.0225-.0979.0283-.1487.0039-.0366.0059-.074.0059-.1117 0-.5522-.4478-1-1-1s-1 .4478-1 1 .4478 1 1 1c.3423 0 .644-.172.8242-.4342z' fill='%23dbab09'/%3E%3C/svg%3e"); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .blueButton::before { | ||||||
|  |     filter: sepia(100%) saturate(300%) brightness(70%) hue-rotate(180deg); | ||||||
|  |     background-image: url("data:image/svg+xml;charset=utf8,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='m12 24c6.6274 0 12-5.3726 12-12 0-6.62744-5.3726-12-12-12-6.62744 0-12 5.37256-12 12 0 6.6274 5.37256 12 12 12zm0-17.5c.4141 0 .75.33582.75.75v4.5c0 .4142-.3359.75-.75.75s-.75-.3358-.75-.75v-4.5c0-.41418.3359-.75.75-.75zm.8242 9.5658c.0635-.0919.1118-.195.1416-.3054.0132-.0482.0225-.0979.0283-.1487.0039-.0366.0059-.074.0059-.1117 0-.5522-.4478-1-1-1s-1 .4478-1 1 .4478 1 1 1c.3423 0 .644-.172.8242-.4342z' fill='%23dbab09'/%3E%3C/svg%3e"); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .redButton::before { | ||||||
|  |     background-image: url("data:image/svg+xml;charset=utf8,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='m12 24c6.6274 0 12-5.3726 12-12 0-6.62744-5.3726-12-12-12-6.62744 0-12 5.37256-12 12 0 6.6274 5.37256 12 12 12zm0-17.5c.4141 0 .75.33582.75.75v4.5c0 .4142-.3359.75-.75.75s-.75-.3358-.75-.75v-4.5c0-.41418.3359-.75.75-.75zm.8242 9.5658c.0635-.0919.1118-.195.1416-.3054.0132-.0482.0225-.0979.0283-.1487.0039-.0366.0059-.074.0059-.1117 0-.5522-.4478-1-1-1s-1 .4478-1 1 .4478 1 1 1c.3423 0 .644-.172.8242-.4342z' fill='%23d73a49'/%3E%3C/svg%3e"); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .greenButton::before{ | ||||||
|  |     background-image: url("data:image/svg+xml;charset=utf8,%3Csvg width='15' height='15' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='m1 12c0-6.07513 4.92487-11 11-11 6.0751 0 11 4.92487 11 11 0 6.0751-4.9249 11-11 11-6.07513 0-11-4.9249-11-11zm16.2803-2.71967c.2929-.29289.2929-.76777 0-1.06066s-.7677-.29289-1.0606 0l-5.9697 5.96963-2.46967-2.4696c-.29289-.2929-.76777-.2929-1.06066 0s-.29289.7677 0 1.0606l3 3c.29293.2929.76773.2929 1.06063 0z' fill='%2328a745'/%3E%3C/svg%3e");ontent: ''; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .indicator::before { | ||||||
|  |     display: inline-block; | ||||||
|  |     width: 24px; | ||||||
|  |     height: 24px; | ||||||
|  |     content: ""; | ||||||
|  |     vertical-align: text-bottom; | ||||||
|  |     background-size: 100% 100%; | ||||||
|  |     background-repeat: no-repeat; | ||||||
|  |     background-position: center center; | ||||||
|  |     margin-right:10px; | ||||||
|  | } | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue