From 0b8c02f9e2a2f903fe0cd318b4e1771984d25d0c Mon Sep 17 00:00:00 2001 From: Stormunblessed <86633626+Stormunblessed@users.noreply.github.com> Date: Mon, 24 Jan 2022 21:15:40 +0000 Subject: [PATCH] Fixes on providers and 3 new spanish sites (#511) * Added Monoschinos and DoramasYT * Fixes on Monoschinos and DoramasYT * Monoschinos, PelisplusHD updates, 3 new spanish providers * fixes --- .../com/lagradost/cloudstream3/MainAPI.kt | 3 + .../animeproviders/AnimeflvProvider.kt | 144 ++++++++++++++ .../animeproviders/MonoschinosProvider.kt | 13 +- .../cloudstream3/extractors/Evolaod.kt | 5 +- .../movieproviders/CinecalidadProvider.kt | 178 ++++++++++++++++++ .../movieproviders/PeliSmartProvider.kt | 161 ++++++++++++++++ .../movieproviders/PelisplusHDProvider.kt | 17 +- .../cloudstream3/utils/ExtractorApi.kt | 1 + 8 files changed, 503 insertions(+), 19 deletions(-) create mode 100644 app/src/main/java/com/lagradost/cloudstream3/animeproviders/AnimeflvProvider.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/movieproviders/CinecalidadProvider.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/movieproviders/PeliSmartProvider.kt diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt index 02dc3011..488f4e80 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt @@ -30,17 +30,20 @@ object APIHolder { val apis = arrayListOf( PelisplusProvider(), PelisplusHDProvider(), + PeliSmartProvider(), GogoanimeProvider(), AllAnimeProvider(), //ShiroProvider(), // v2 fucked me //AnimePaheProvider(), //ddos guard AnimeFlickProvider(), + AnimeflvnetProvider(), TenshiProvider(), WcoProvider(), // MeloMovieProvider(), // Captcha for links DubbedAnimeProvider(), DoramasYTProvider(), + CinecalidadProvider(), IHaveNoTvProvider(), // Documentaries provider //LookMovieProvider(), // RECAPTCHA (Please allow up to 5 seconds...) VMoveeProvider(), diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/AnimeflvProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/AnimeflvProvider.kt new file mode 100644 index 00000000..3cbd0040 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/AnimeflvProvider.kt @@ -0,0 +1,144 @@ +package com.lagradost.cloudstream3.animeproviders + +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.utils.* +import java.util.* +import kotlin.collections.ArrayList + +class AnimeflvnetProvider:MainAPI() { + companion object { + fun getType(t: String): TvType { + return if (t.contains("OVA") || t.contains("Especial")) TvType.ONA + else if (t.contains("Película")) TvType.AnimeMovie + else TvType.Anime + } + } + override val mainUrl: String + get() = "https://m.animeflv.net" + override val name: String + get() = "AnimeFLV" + override val lang = "es" + override val hasMainPage = true + override val hasChromecastSupport = true + override val hasDownloadSupport = true + override val supportedTypes = setOf( + TvType.AnimeMovie, + TvType.ONA, + TvType.Anime, + ) + + + + override suspend fun getMainPage(): HomePageResponse { + val urls = listOf( + Pair("$mainUrl/browse?type[]=movie&order=updated", "Peliculas actualizadas"), + Pair("$mainUrl/browse?order=updated", "Animes recientemente actualizados"), + Pair("$mainUrl/browse?status[]=2&order=default", "Animes finalizados"), + Pair("$mainUrl/browse?status[]=1&order=rating", "En emision"), + ) + val items = ArrayList() + for (i in urls) { + try { + val doc = app.get(i.first).document + val home = doc.select("ul.List-Animes li.Anime").map { + val title = it.selectFirst("h2.title").text() + val poster = it.selectFirst(".Image img").attr("src") + AnimeSearchResponse( + title, + fixUrl(it.selectFirst("a").attr("href")), + this.name, + TvType.Anime, + fixUrl(poster), + null, + if (title.contains("Latino") || title.contains("Castellano")) EnumSet.of(DubStatus.Dubbed) else EnumSet.of(DubStatus.Subbed), + ) + } + + items.add(HomePageList(i.second, home)) + } catch (e: Exception) { + e.printStackTrace() + } + } + if (items.size <= 0) throw ErrorLoadingException() + return HomePageResponse(items) + } + + override suspend fun search(query: String): ArrayList { + + val url = "${mainUrl}/browse?q=${query}" + val doc = app.get(url).document + val episodes = doc.select("ul.List-Animes li.Anime").map { + val title = it.selectFirst("h2.title").text() + val href = fixUrl(it.selectFirst("a").attr("href")) + val image = it.selectFirst(".Image img").attr("src") + AnimeSearchResponse( + title, + href, + this.name, + TvType.Anime, + fixUrl(image), + null, + if (title.contains("Latino") || title.contains("Castellano")) EnumSet.of(DubStatus.Dubbed) else EnumSet.of(DubStatus.Subbed), + ) + } + return ArrayList(episodes) + } + + override suspend fun load(url: String): LoadResponse { + val doc = app.get(url).document + val title = doc.selectFirst("h1.Title").text() + val description = doc.selectFirst(".Anime > header:nth-child(1) > p:nth-child(3)").text().replace("Sinopsis: ","") + val poster = doc.selectFirst(".Image img").attr("src") + val episodes = doc.select("li.Episode").map { li -> + val href = fixUrl(li.selectFirst("a").attr("href")) + AnimeEpisode( + fixUrl(href), "Episodio" + li.selectFirst("a").text().replace(title,"") + ) + } + val type = doc.selectFirst("span.Type.A").text() + val genre = doc.select("a.Tag") + .map { it?.text()?.trim().toString() } + + val status = when (doc.selectFirst("article.Anime.Single.Bglg header p strong.Anm-On")?.text()) { + "En emisión" -> ShowStatus.Ongoing + "Finalizado" -> ShowStatus.Completed + else -> null + } + return newAnimeLoadResponse(title, url, getType(type)) { + posterUrl = fixUrl(poster) + addEpisodes(DubStatus.Subbed, episodes) + showStatus = status + plot = description + tags = genre + } + } + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + //There might be a better way to do this, but this one works + val html = app.get(data).text + val linkRegex = Regex("""(https:.*?\.html.*)""") + val videos = linkRegex.findAll(html).map { + it.value.replace("\\/", "/") + }.toList() + val serversRegex = Regex("(https?:\\/\\/(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b([-a-zA-Z0-9()@:%_\\+.~#?&\\/\\/=]*))") + val links = serversRegex.findAll(videos.toString()).map { + it.value.replace("https://embedsb.com","https://watchsb.com") + }.toList() + for (link in links) { + for (extractor in extractorApis) { + if (link.startsWith(extractor.mainUrl)) { + extractor.getSafeUrl(link, data)?.forEach { + callback(it) + } + } + } + + } + return true + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/MonoschinosProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/MonoschinosProvider.kt index ac56ddb7..e07f8d8b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/MonoschinosProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/MonoschinosProvider.kt @@ -1,9 +1,10 @@ package com.lagradost.cloudstream3.animeproviders import com.lagradost.cloudstream3.* -import java.util.* import com.lagradost.cloudstream3.extractors.FEmbed +import java.util.* import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.extractorApis import com.lagradost.cloudstream3.utils.loadExtractor import kotlin.collections.ArrayList @@ -108,30 +109,26 @@ class MonoschinosProvider:MainAPI() { val doc = app.get(url, timeout = 120).document val poster = doc.selectFirst(".chapterpic img").attr("src") val title = doc.selectFirst(".chapterdetails h1").text() - val type = doc.selectFirst(".activecrumb a").text() - val year = doc.selectFirst(".btn2").text().toIntOrNull() + val type = doc.selectFirst("div.chapterdetls2").text() val description = doc.selectFirst("p.textComplete").text().replace("Ver menos","") val genres = doc.select(".breadcrumb-item a").map { it.text() } - val status = when (doc.selectFirst("btn1")?.text()) { + val status = when (doc.selectFirst("button.btn1")?.text()) { "Estreno" -> ShowStatus.Ongoing "Finalizado" -> ShowStatus.Completed else -> null } - val rat = doc.select(".chapterpic p").toString().toIntOrNull() val episodes = doc.select("div.col-item").map { val name = it.selectFirst("p.animetitles").text() val link = it.selectFirst("a").attr("href") - val epThumb = it.selectFirst("img.animeimghv").attr("data-src") + val epThumb = it.selectFirst(".animeimghv").attr("data-src") AnimeEpisode(link, name, posterUrl = epThumb) } return newAnimeLoadResponse(title, url, getType(type)) { posterUrl = poster - this.year = year addEpisodes(DubStatus.Subbed, episodes) showStatus = status plot = description tags = genres - rating = rat } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Evolaod.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Evolaod.kt index 10841148..27991b88 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Evolaod.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Evolaod.kt @@ -3,8 +3,11 @@ package com.lagradost.cloudstream3.extractors import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.app +class Evoload1 : Evoload() { + override val mainUrl = "https://evoload.io" +} -class Evoload : ExtractorApi() { +open class Evoload : ExtractorApi() { override val name: String = "Evoload" override val mainUrl: String = "https://www.evoload.io" //private val srcRegex = Regex("""video .*src="(.*)""""") // would be possible to use the parse and find src attribute diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/CinecalidadProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/CinecalidadProvider.kt new file mode 100644 index 00000000..e5a998a1 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/CinecalidadProvider.kt @@ -0,0 +1,178 @@ +package com.lagradost.cloudstream3.movieproviders + +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.utils.* +import java.util.* + +class CinecalidadProvider:MainAPI() { + override val mainUrl: String + get() = "https://cinecalidad.lol" + override val name: String + get() = "Cinecalidad" + override val lang = "es" + override val hasMainPage = true + override val hasChromecastSupport = true + override val hasDownloadSupport = true + override val supportedTypes = setOf( + TvType.Movie, + TvType.TvSeries, + ) + override suspend fun getMainPage(): HomePageResponse { + val items = ArrayList() + val urls = listOf( + Pair("$mainUrl/", "Peliculas"), + Pair("$mainUrl/genero-de-la-pelicula/peliculas-en-calidad-4k/", "4K UHD"), + ) + + items.add(HomePageList("Series",app.get("$mainUrl/ver-serie/").document.select(".item.tvshows").map{ + val title = it.selectFirst("div.in_title").text() + TvSeriesSearchResponse( + title, + it.selectFirst("a").attr("href"), + this.name, + TvType.TvSeries, + it.selectFirst(".poster.custom img").attr("data-src"), + null, + null, + ) + })) + + for (i in urls) { + try { + val soup = app.get(i.first).document + val home = soup.select(".item.movies").map { + val title = it.selectFirst("div.in_title").text() + val link = it.selectFirst("a").attr("href") + TvSeriesSearchResponse( + title, + link, + this.name, + if (link.contains("/ver-pelicula/")) TvType.Movie else TvType.TvSeries, + it.selectFirst(".poster.custom img").attr("data-src"), + null, + null, + ) + } + + items.add(HomePageList(i.second, home)) + } catch (e: Exception) { + e.printStackTrace() + } + } + + if (items.size <= 0) throw ErrorLoadingException() + return HomePageResponse(items) + } + + override suspend fun search(query: String): List { + val url = "$mainUrl/buscar/?s=${query}" + val document = app.get(url).document + + return document.select("article").map { + val title = it.selectFirst("div.in_title").text() + val href = it.selectFirst("a").attr("href") + val image = it.selectFirst(".poster.custom img").attr("data-src") + val isMovie = href.contains("/ver-pelicula/") + + if (isMovie) { + MovieSearchResponse( + title, + href, + this.name, + TvType.Movie, + image, + null + ) + } else { + TvSeriesSearchResponse( + title, + href, + this.name, + TvType.TvSeries, + image, + null, + null + ) + } + } + } + + + override suspend fun load(url: String): LoadResponse? { + val soup = app.get(url, timeout = 120).document + + val title = soup.selectFirst(".single_left h1").text() + val description = soup.selectFirst(".single_left > table:nth-child(3) > tbody:nth-child(1) > tr:nth-child(1) > td:nth-child(2) > p")?.text()?.trim() + val poster: String? = soup.selectFirst(".alignnone").attr("data-src") + val episodes = soup.select("div.se-c div.se-a ul.episodios li").map { li -> + val href = li.selectFirst("a").attr("href") + val epThumb = li.selectFirst("div.imagen img").attr("data-src") + val name = li.selectFirst(".episodiotitle a").text() + TvSeriesEpisode( + name, + null, + null, + href, + epThumb + ) + } + return when (val tvType = if (url.contains("/ver-pelicula/")) TvType.Movie else TvType.TvSeries) { + TvType.TvSeries -> { + TvSeriesLoadResponse( + title, + url, + this.name, + tvType, + episodes, + poster, + null, + description, + ) + } + TvType.Movie -> { + MovieLoadResponse( + title, + url, + this.name, + tvType, + url, + poster, + null, + description, + ) + } + else -> null + } + } + + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + app.get(data).document.select(".ajax_mode .dooplay_player_option").forEach { + val movieID = it.attr("data-post") + val serverID = it.attr("data-nume") + val url = "$mainUrl/wp-json/dooplayer/v2/$movieID/movie/$serverID" + val urlserver = app.get(url).text + val serverRegex = Regex("(https:.*?\\\")") + val videos = serverRegex.findAll(urlserver).map { + it.value.replace("\\/", "/").replace("\"","") + }.toList() + val serversRegex = Regex("(https?:\\/\\/(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b([-a-zA-Z0-9()@:%_\\+.~#?&\\/\\/=]*))") + val links = serversRegex.findAll(videos.toString()).map { it.value }.toList() + for (link in links) { + for (extractor in extractorApis) { + if (link.startsWith(extractor.mainUrl)) { + extractor.getSafeUrl(link, data)?.forEach { + callback(it) + } + } + } + } + } + return true + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/PeliSmartProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/PeliSmartProvider.kt new file mode 100644 index 00000000..9a96f8cf --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/PeliSmartProvider.kt @@ -0,0 +1,161 @@ +package com.lagradost.cloudstream3.movieproviders + +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.extractorApis +import java.util.ArrayList + +class PeliSmartProvider: MainAPI() { + override val mainUrl: String + get() = "https://pelismart.com" + override val name: String + get() = "PeliSmart" + override val lang = "es" + override val hasMainPage = true + override val hasChromecastSupport = true + override val hasDownloadSupport = true + override val supportedTypes = setOf( + TvType.Movie, + TvType.TvSeries, + ) + override suspend fun getMainPage(): HomePageResponse { + val items = ArrayList() + val urls = listOf( + Pair("$mainUrl/peliculas/", "Peliculas"), + Pair("$mainUrl/series/", "Series"), + Pair("$mainUrl/documentales/", "Documentales"), + ) + + for (i in urls) { + try { + val soup = app.get(i.first).document + val home = soup.select(".description-off").map { + val title = it.selectFirst("h3.entry-title a").text() + val link = it.selectFirst("a").attr("href") + TvSeriesSearchResponse( + title, + link, + this.name, + if (link.contains("pelicula")) TvType.Movie else TvType.TvSeries, + it.selectFirst("div img").attr("src"), + null, + null, + ) + } + + items.add(HomePageList(i.second, home)) + } catch (e: Exception) { + e.printStackTrace() + } + } + + if (items.size <= 0) throw ErrorLoadingException() + return HomePageResponse(items) + } + + override suspend fun search(query: String): List { + val url = "$mainUrl?s=${query}&post_type=post" + val document = app.get(url).document + + return document.select(".description-off").map { + val title = it.selectFirst("h3.entry-title a").text() + val href = it.selectFirst("a").attr("href") + val image = it.selectFirst("div img").attr("src") + val isMovie = href.contains("pelicula") + + if (isMovie) { + MovieSearchResponse( + title, + href, + this.name, + TvType.Movie, + image, + null + ) + } else { + TvSeriesSearchResponse( + title, + href, + this.name, + TvType.TvSeries, + image, + null, + null + ) + } + } + } + + + override suspend fun load(url: String): LoadResponse? { + val soup = app.get(url, timeout = 120).document + val title = soup.selectFirst(".wpb_wrapper h1").text() + val description = soup.selectFirst("div.wpb_wrapper p")?.text()?.trim() + val poster: String? = soup.selectFirst(".vc_single_image-img").attr("src") + val episodes = soup.select("div.vc_tta-panel-body div a").map { li -> + val href = li.selectFirst("a").attr("href") + val preregex = Regex("(\\d+)\\. ") + val name = li.selectFirst("a").text().replace(preregex,"") + TvSeriesEpisode( + name, + null, + null, + href, + ) + } + return when (val tvType = if (episodes.isEmpty()) TvType.Movie else TvType.TvSeries) { + TvType.TvSeries -> { + TvSeriesLoadResponse( + title, + url, + this.name, + tvType, + episodes, + poster, + null, + description, + ) + } + TvType.Movie -> { + MovieLoadResponse( + title, + url, + this.name, + tvType, + url, + poster, + null, + description, + ) + } + else -> null + } + } + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + val soup = app.get(data).text + val linkRegex = Regex("""(https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&\/\/=]*))""") + val link1 = linkRegex.findAll(soup).map { + it.value.replace("https://pelismart.com/p/1.php?v=","https://evoload.io/e/") + .replace("https://pelismart.com/p/2.php?v=","https://streamtape.com/e/") + .replace("https://pelismart.com/p/4.php?v=","https://dood.to/e/") + .replace("https://pelismarthd.com/p/1.php?v=","https://evoload.io/e/") + .replace("https://pelismarthd.com/p/2.php?v=","https://streamtape.com/e/") + .replace("https://pelismarthd.com/p/4.php?v=","https://dood.to/e/") + }.toList() + for (link in link1) { + for (extractor in extractorApis) { + if (link.startsWith(extractor.mainUrl)) { + extractor.getSafeUrl(link, data)?.forEach { + callback(it) + } + } + } + } + return true + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/PelisplusHDProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/PelisplusHDProvider.kt index 81a717a7..1aea6c1b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/PelisplusHDProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/PelisplusHDProvider.kt @@ -2,7 +2,6 @@ package com.lagradost.cloudstream3.movieproviders import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.utils.* -import org.jsoup.Jsoup import java.util.* class PelisplusHDProvider:MainAPI() { @@ -29,8 +28,7 @@ class PelisplusHDProvider:MainAPI() { ) for (i in urls) { try { - val response = app.get(i.first) - val soup = Jsoup.parse(response.text) + val soup = app.get(i.first).document val home = soup.select("a.Posters-link").map { val title = it.selectFirst(".listing-content p").text() val link = it.selectFirst("a").attr("href") @@ -57,8 +55,7 @@ class PelisplusHDProvider:MainAPI() { override suspend fun search(query: String): List { val url = "https://pelisplushd.net/search?s=${query}" - val html = app.get(url).text - val document = Jsoup.parse(html) + val document = app.get(url).document return document.select("a.Posters-link").map { val title = it.selectFirst(".listing-content p").text() @@ -90,16 +87,16 @@ class PelisplusHDProvider:MainAPI() { } override suspend fun load(url: String): LoadResponse? { - val html = app.get(url).text - val soup = Jsoup.parse(html) + val soup = app.get(url, timeout = 120).document val title = soup.selectFirst(".m-b-5").text() val description = soup.selectFirst("div.text-large")?.text()?.trim() val poster: String? = soup.selectFirst(".img-fluid").attr("src") val episodes = soup.select("div.tab-pane .btn").map { li -> val href = li.selectFirst("a").attr("href") + val name = li.selectFirst(".btn-primary.btn-block").text() TvSeriesEpisode( - null, + name, null, null, href, @@ -152,12 +149,12 @@ class PelisplusHDProvider:MainAPI() { subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit ): Boolean { - val html = app.get(data).text - val soup = Jsoup.parse(html) + val soup = app.get(data).document val selector = soup.selectFirst("div.player > script").toString() val linkRegex = Regex("""(https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&\/\/=]*))""") val links = linkRegex.findAll(selector).map { it.value.replace("https://pelisplushd.net/fembed.php?url=","https://www.fembed.com/v/") + .replace("https://pelistop.co/","https://watchsb.com/") }.toList() for (link in links) { for (extractor in extractorApis) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt index a85fc77e..f22d653a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt @@ -105,6 +105,7 @@ val extractorApis: Array = arrayOf( Uqload(), Uqload1(), Evoload(), + Evoload1(), VoeExtractor(), UpstreamExtractor(),