From e545b98dbad3818c56cfc682e15c84c060afa471 Mon Sep 17 00:00:00 2001 From: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sat, 14 Oct 2023 14:41:22 +0200 Subject: [PATCH 1/4] feat(src/de): New source: Moflix-Stream --- .../lib/playlistutils/PlaylistUtils.kt | 13 +- .../streamvidextractor/StreamVidExtractor.kt | 8 +- .../StreamWishExtractor.kt | 4 +- src/de/moflixstream/AndroidManifest.xml | 2 + src/de/moflixstream/build.gradle | 22 ++ .../res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 2132 bytes .../res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 1276 bytes .../res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 3202 bytes .../res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 5535 bytes .../res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 8274 bytes .../de/moflixstream/MoflixStream.kt | 356 ++++++++++++++++++ .../extractors/VidGuardExtractor.kt | 124 ++++++ 12 files changed, 522 insertions(+), 7 deletions(-) create mode 100644 src/de/moflixstream/AndroidManifest.xml create mode 100644 src/de/moflixstream/build.gradle create mode 100644 src/de/moflixstream/res/mipmap-hdpi/ic_launcher.png create mode 100644 src/de/moflixstream/res/mipmap-mdpi/ic_launcher.png create mode 100644 src/de/moflixstream/res/mipmap-xhdpi/ic_launcher.png create mode 100644 src/de/moflixstream/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 src/de/moflixstream/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 src/de/moflixstream/src/eu/kanade/tachiyomi/animeextension/de/moflixstream/MoflixStream.kt create mode 100644 src/de/moflixstream/src/eu/kanade/tachiyomi/animeextension/de/moflixstream/extractors/VidGuardExtractor.kt diff --git a/lib/playlist-utils/src/main/java/eu/kanade/tachiyomi/lib/playlistutils/PlaylistUtils.kt b/lib/playlist-utils/src/main/java/eu/kanade/tachiyomi/lib/playlistutils/PlaylistUtils.kt index a902e80aba..bbe306b62a 100644 --- a/lib/playlist-utils/src/main/java/eu/kanade/tachiyomi/lib/playlistutils/PlaylistUtils.kt +++ b/lib/playlist-utils/src/main/java/eu/kanade/tachiyomi/lib/playlistutils/PlaylistUtils.kt @@ -30,6 +30,7 @@ class PlaylistUtils(private val client: OkHttpClient, private val headers: Heade fun extractFromHls( playlistUrl: String, referer: String = "", + mB: Boolean = true, masterHeaders: Headers, videoHeaders: Headers, videoNameGen: (String) -> String = { quality -> quality }, @@ -39,6 +40,7 @@ class PlaylistUtils(private val client: OkHttpClient, private val headers: Heade return extractFromHls( playlistUrl, referer, + mB, { _, _ -> masterHeaders }, { _, _, _ -> videoHeaders }, videoNameGen, @@ -71,6 +73,7 @@ class PlaylistUtils(private val client: OkHttpClient, private val headers: Heade fun extractFromHls( playlistUrl: String, referer: String = "", + mB: Boolean = true, masterHeadersGen: (Headers, String) -> Headers = { baseHeaders, referer -> generateMasterHeaders(baseHeaders, referer) }, @@ -97,9 +100,13 @@ class PlaylistUtils(private val client: OkHttpClient, private val headers: Heade val playlistHttpUrl = playlistUrl.toHttpUrl() - val masterBase = playlistHttpUrl.newBuilder().apply { - removePathSegment(playlistHttpUrl.pathSize - 1) - }.build().toString() + "/" + val masterBase = if(mB) { + playlistHttpUrl.newBuilder().apply { + removePathSegment(playlistHttpUrl.pathSize - 1) + }.build().toString() + "/" + } else { + playlistUrl + } // Get subtitles val subtitleTracks = subtitleList + SUBTITLE_REGEX.findAll(masterPlaylist).mapNotNull { diff --git a/lib/streamvid-extractor/src/main/java/eu/kanade/tachiyomi/lib/streamvidextractor/StreamVidExtractor.kt b/lib/streamvid-extractor/src/main/java/eu/kanade/tachiyomi/lib/streamvidextractor/StreamVidExtractor.kt index 75f31b99ee..7e98317466 100644 --- a/lib/streamvid-extractor/src/main/java/eu/kanade/tachiyomi/lib/streamvidextractor/StreamVidExtractor.kt +++ b/lib/streamvid-extractor/src/main/java/eu/kanade/tachiyomi/lib/streamvidextractor/StreamVidExtractor.kt @@ -8,14 +8,18 @@ import eu.kanade.tachiyomi.util.asJsoup import okhttp3.OkHttpClient class StreamVidExtractor(private val client: OkHttpClient) { - fun videosFromUrl(url: String, prefix: String = ""): List<Video> { + fun videosFromUrl(url: String, prefix: String = "", sourceChange: Boolean = false): List<Video> { return runCatching { val doc = client.newCall(GET(url)).execute().asJsoup() val script = doc.selectFirst("script:containsData(eval):containsData(p,a,c,k,e,d)")?.data() ?.let(JsUnpacker::unpackAndCombine) ?: return emptyList() - val masterUrl = script.substringAfter("sources:[{src:\"").substringBefore("\",") + val masterUrl = if(!sourceChange) { + script.substringAfter("sources:[{src:\"").substringBefore("\",") + } else { + script.substringAfter("sources:[{file:\"").substringBefore("\"") + } PlaylistUtils(client).extractFromHls(masterUrl, videoNameGen = { "${prefix}StreamVid - (${it}p)" }) }.getOrElse { emptyList() } } diff --git a/lib/streamwish-extractor/src/main/java/eu/kanade/tachiyomi/lib/streamwishextractor/StreamWishExtractor.kt b/lib/streamwish-extractor/src/main/java/eu/kanade/tachiyomi/lib/streamwishextractor/StreamWishExtractor.kt index f182976c1f..e81fe18fea 100644 --- a/lib/streamwish-extractor/src/main/java/eu/kanade/tachiyomi/lib/streamwishextractor/StreamWishExtractor.kt +++ b/lib/streamwish-extractor/src/main/java/eu/kanade/tachiyomi/lib/streamwishextractor/StreamWishExtractor.kt @@ -8,7 +8,7 @@ import eu.kanade.tachiyomi.util.asJsoup import okhttp3.Headers import okhttp3.OkHttpClient -class StreamWishExtractor(private val client: OkHttpClient, private val headers: Headers) { +class StreamWishExtractor(private val client: OkHttpClient, private val headers: Headers, private val mB: Boolean = true) { private val playlistUtils by lazy { PlaylistUtils(client, headers) } fun videosFromUrl(url: String, prefix: String) = videosFromUrl(url) { "$prefix - $it" } @@ -31,6 +31,6 @@ class StreamWishExtractor(private val client: OkHttpClient, private val headers: ?.takeIf(String::isNotBlank) ?: return emptyList() - return playlistUtils.extractFromHls(masterUrl, url, videoNameGen = videoNameGen) + return playlistUtils.extractFromHls(masterUrl, url, videoNameGen = videoNameGen, mB = mB) } } diff --git a/src/de/moflixstream/AndroidManifest.xml b/src/de/moflixstream/AndroidManifest.xml new file mode 100644 index 0000000000..568741e54f --- /dev/null +++ b/src/de/moflixstream/AndroidManifest.xml @@ -0,0 +1,2 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest /> \ No newline at end of file diff --git a/src/de/moflixstream/build.gradle b/src/de/moflixstream/build.gradle new file mode 100644 index 0000000000..4889937828 --- /dev/null +++ b/src/de/moflixstream/build.gradle @@ -0,0 +1,22 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlinx-serialization' + +ext { + extName = 'Moflix-Stream' + pkgNameSuffix = 'de.moflixstream' + extClass = '.MoflixStream' + extVersionCode = 1 + libVersion = '13' +} + +dependencies { + implementation(project(':lib-streamvid-extractor')) + implementation(project(':lib-streamwish-extractor')) + implementation(project(':lib-streamtape-extractor')) + implementation(project(':lib-playlist-utils')) + implementation "dev.datlag.jsunpacker:jsunpacker:1.0.1" +} + + +apply from: "$rootDir/common.gradle" diff --git a/src/de/moflixstream/res/mipmap-hdpi/ic_launcher.png b/src/de/moflixstream/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..6d89f9a7fa90708131c4fd3daf73c2f3b30a5cad GIT binary patch literal 2132 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 KMNUMnLSTY3N$42> literal 0 HcmV?d00001 diff --git a/src/de/moflixstream/res/mipmap-mdpi/ic_launcher.png b/src/de/moflixstream/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..b2cd5e41e890439e4defde7c2c1a33448a645be2 GIT binary patch literal 1276 zcmV<Y1OxktP)<h;3K|Lk000e1NJLTq001xm001xu1^@s6R|5Hm000ETNkl<ZXhZE; zOG{fp6h6sKG`^zI_(X$<FIv$>-L-D(uC!1W{XN~=ZVIK_f?z>PH?4cSDHVz<u|m<} zgOZdaDpq42Ju}S><7DQ}nVXbUXkfU>+?hMych33F+>;5w4gUmgIDYs~pwS-Gd4Tl? zv9W8Ty;MRBtQE`GYc^AXb7p2{CJTkan^LKiz-v^tGWPQ>@#=ne`*punbH!)5Yq4?e z*l;-fJ`@T)-P_yyjQ0y79+?399hsV%dV6wmvY5~3uYr^o5*IN6tA#WYfcEzGgJd!} zg&2N`o{$N!u!#;24}ZV7xabg>ieP2y`~7W0Iv5PX)zwuT*UE@qkO??=GuGSNo0LO< zVtrHi$oFjsu(=q{{doClBG40$$A5^ir~>cT>eP8E4Hb(;IsugjycXA4UpI#;dhxkr z0zExF+ymkw6!4TWwqYZVw<1srcvF~o>C1~CnLu}UcSQpxt5QNoCth38TN5y4A{99b zA-`cnCJ>9os<IG86Q<DdO2sDP<$Z{jg%|;y25i!+OvonoRbhwoX#~2ux|AN!?FU|= zMDdDgj1Y1(0-c?m*F@}PbzATzu;N+@t@tCL+tlg--W2=N8gv3C4Vb{&SlUb6lJRH+ zqS2^U0~8%30uGE~J;YvZ8G!v#0lr}~rB8zBX#_etI;u1<GBN_Qv$Jq~d<^5`<Lu24 zB9RE3o}R+y<|d4ej>5#m1i%0I<7c>)%R>&qHzTSeCkV@TZo}Bv7-X|qxV*gNjm>Ro zY2kgC`QY&I5c>Q3VS9TUj*gD5y~>oy`y;?6Pfkw4($W$H0s+8UfiFHiJq^3NyRg2# z4hstlfY}RHS65*<bp|tmAl$o1!>33$q{0rocyJ%u+S*`aV*_xfhxz$=NT<`#+S<y2 z_Vx8~(1}C>R#sMEYirBZvq*$aK-R$E;2<n6F2caT04IUP1z%uiXNOfw+$*fVsi_I( zp1*`d_5?BrERH30vMB_wpFD!0p&?jXTjLhTnuB{}Zf*`7#{t}B`TT=}13qSdf1f{J z!a*Z|fIKyz(}BC#Lni_W^gxi+wKzCcEWip}0V@qz=(r!ObAh#7E>{7{h^R{0@*Elg zbhUCHpa__a=|W5PmHvphh$K?@heE1UtP-(Trczj+b@?OUl~SqqYzrj$xmF%PVR`Pv z;%-@q2Ps}nfPGk>?g5d2&4#9OyPW%F@~Zn9>(dEfZm#lx?xD7?89puV@}_Bk5%91= zReGc9phoa?58w{#O9Rv_Wf?Z2%xE5P2~fgDC1{FAyh2YzEPbaDXl`z<iw0C7<K-m} z;C33o*8%NDMoFQzp+o>)9|Dw=ssm4*Yal}lA=d*o8lYNTwO7>L1NhRV{9{YE5M|e` z5x89l;TvL28c=!8w674oKno$}0V)VnY}W}muNClFBmyz4i9ej5pU+b@KpjW4a@8;f z9#i{QtOW1k%`~>kIhpE+sSOL7J2>bS*1Z#6vHQ1yyG$^ozhGi{mdRw^Vc!`xc4Emt zs#D4^_P1k;VQa<4j-49$Vb(s!mc@38jn%S$j!88H+%w`(8T`L+PRuNx$HvH1!0QM! m(%RN*H`=<^faM7{p1^PMh%AeLbGhpP0000<MNUMnLSTXx8(O*m literal 0 HcmV?d00001 diff --git a/src/de/moflixstream/res/mipmap-xhdpi/ic_launcher.png b/src/de/moflixstream/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..9f2ee6f606d98424b0bbe216c09e1c360d81458f GIT binary patch literal 3202 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 literal 0 HcmV?d00001 diff --git a/src/de/moflixstream/res/mipmap-xxhdpi/ic_launcher.png b/src/de/moflixstream/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..ddbd0cd0c5a43f34c894165ff9017155d2dd9167 GIT binary patch literal 5535 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 aIpn4R?e-3aBbCy41+>)kF?Fi85&r}73|p-L literal 0 HcmV?d00001 diff --git a/src/de/moflixstream/res/mipmap-xxxhdpi/ic_launcher.png b/src/de/moflixstream/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..3813213dd0460a339431fd9bdc609f3e0be4bd8e GIT binary patch literal 8274 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 literal 0 HcmV?d00001 diff --git a/src/de/moflixstream/src/eu/kanade/tachiyomi/animeextension/de/moflixstream/MoflixStream.kt b/src/de/moflixstream/src/eu/kanade/tachiyomi/animeextension/de/moflixstream/MoflixStream.kt new file mode 100644 index 0000000000..1356cb5387 --- /dev/null +++ b/src/de/moflixstream/src/eu/kanade/tachiyomi/animeextension/de/moflixstream/MoflixStream.kt @@ -0,0 +1,356 @@ +package eu.kanade.tachiyomi.animeextension.de.moflixstream + +import android.app.Application +import android.content.SharedPreferences +import androidx.preference.ListPreference +import androidx.preference.MultiSelectListPreference +import androidx.preference.PreferenceScreen +import eu.kanade.tachiyomi.animeextension.de.moflixstream.extractors.VidGuardExtractor +import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource +import eu.kanade.tachiyomi.animesource.model.AnimeFilterList +import eu.kanade.tachiyomi.animesource.model.AnimesPage +import eu.kanade.tachiyomi.animesource.model.SAnime +import eu.kanade.tachiyomi.animesource.model.SEpisode +import eu.kanade.tachiyomi.animesource.model.Video +import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource +import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor +import eu.kanade.tachiyomi.lib.streamvidextractor.StreamVidExtractor +import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor +import eu.kanade.tachiyomi.network.GET +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.int +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive +import okhttp3.Headers +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get +import kotlin.Exception + +class MoflixStream : ConfigurableAnimeSource, AnimeHttpSource() { + + override val name = "Moflix-Stream" + + override val baseUrl = "https://moflix-stream.xyz" + + override val lang = "de" + + override val supportsLatest = false + + override val client: OkHttpClient = network.cloudflareClient + + private val preferences: SharedPreferences by lazy { + Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) + } + + private val json = Json { + isLenient = true + ignoreUnknownKeys = true + } + + override fun popularAnimeRequest(page: Int): Request = GET( + "$baseUrl/api/v1/channel/345?returnContentOnly=true&restriction=&order=rating:desc&paginate=simple&perPage=50&query=&page=$page", + headers = Headers.headersOf("referer", "$baseUrl/movies?order=rating%3Adesc"), + ) + + override fun popularAnimeParse(response: Response): AnimesPage { + val responseString = response.body.string() + return parsePopularAnimeJson(responseString) + } + + private fun parsePopularAnimeJson(jsonLine: String?): AnimesPage { + val jsonData = jsonLine ?: return AnimesPage(emptyList(), false) + val jObject = json.decodeFromString<JsonObject>(jsonData) + val jO = jObject.jsonObject["pagination"]!!.jsonObject + val nextPage = jO.jsonObject["next_page_url"]!!.jsonPrimitive.content + .substringAfter("page=").toInt() + val page = jO.jsonObject["current_page"]!!.jsonPrimitive.int + val hasNextPage = page < nextPage + val array = jO["data"]!!.jsonArray + val animeList = mutableListOf<SAnime>() + for (item in array) { + val anime = SAnime.create() + anime.title = item.jsonObject["name"]!!.jsonPrimitive.content + val animeId = item.jsonObject["id"]!!.jsonPrimitive.content + anime.setUrlWithoutDomain("$baseUrl/api/v1/titles/$animeId?load=images,genres,productionCountries,keywords,videos,primaryVideo,seasons,compactCredits") + anime.thumbnail_url = item.jsonObject["poster"]?.jsonPrimitive?.content ?: item.jsonObject["backdrop"]?.jsonPrimitive?.content + animeList.add(anime) + } + return AnimesPage(animeList, hasNextPage) + } + + // episodes + + override fun episodeListRequest(anime: SAnime): Request = GET(baseUrl + anime.url, headers = Headers.headersOf("referer", baseUrl)) + + override fun episodeListParse(response: Response): List<SEpisode> { + val responseString = response.body.string() + val url = response.request.url.toString() + return parseEpisodeAnimeJson(responseString, url) + } + + private fun parseEpisodeAnimeJson(jsonLine: String?, url: String): List<SEpisode> { + val jsonData = jsonLine ?: return emptyList() + val jObject = json.decodeFromString<JsonObject>(jsonData) + val episodeList = mutableListOf<SEpisode>() + val mId = jObject.jsonObject["title"]!!.jsonObject["id"]!!.jsonPrimitive.content + val season = jObject.jsonObject["seasons"]?.jsonObject + if (season != null) { + val dataArray = season.jsonObject["data"]!!.jsonArray + val next = season.jsonObject["next_page"]?.jsonPrimitive?.content + if (next != null) { + val seNextJsonData = client.newCall(GET("$baseUrl/api/v1/titles/$mId/seasons?perPage=8&query=&page=$next", headers = Headers.headersOf("referer", baseUrl))).execute().body.string() + val seNextJObject = json.decodeFromString<JsonObject>(seNextJsonData) + val seasonNext = seNextJObject.jsonObject["pagination"]!!.jsonObject + val dataNextArray = seasonNext.jsonObject["data"]!!.jsonArray + val dataAllArray = dataArray.plus(dataNextArray) + for (item in dataAllArray) { + val id = item.jsonObject["title_id"]!!.jsonPrimitive.content + val num = item.jsonObject["number"]!!.jsonPrimitive.content + val seUrl = "$baseUrl/api/v1/titles/$id/seasons/$num?load=episodes,primaryVideo" + val seJsonData = client.newCall(GET(seUrl, headers = Headers.headersOf("referer", baseUrl))).execute().body.string() + val seJObject = json.decodeFromString<JsonObject>(seJsonData) + val epObject = seJObject.jsonObject["episodes"]!!.jsonObject + val epDataArray = epObject.jsonObject["data"]!!.jsonArray.reversed() + for (epItem in epDataArray) { + val episode = SEpisode.create() + val seNum = epItem.jsonObject["season_number"]!!.jsonPrimitive.content + val epNum = epItem.jsonObject["episode_number"]!!.jsonPrimitive.content + episode.name = "Staffel $seNum Folge $epNum : " + epItem.jsonObject["name"]!!.jsonPrimitive.content + episode.episode_number = epNum.toFloat() + val epId = epItem.jsonObject["title_id"]!!.jsonPrimitive.content + episode.setUrlWithoutDomain("$baseUrl/api/v1/titles/$epId/seasons/$seNum/episodes/$epNum?load=videos,compactCredits,primaryVideo") + episodeList.add(episode) + } + } + } else { + for (item in dataArray) { + val id = item.jsonObject["title_id"]!!.jsonPrimitive.content + val num = item.jsonObject["number"]!!.jsonPrimitive.content + val seUrl = "$baseUrl/api/v1/titles/$id/seasons/$num?load=episodes,primaryVideo" + val seJsonData = client.newCall(GET(seUrl, headers = Headers.headersOf("referer", baseUrl))).execute().body.string() + val seJObject = json.decodeFromString<JsonObject>(seJsonData) + val epObject = seJObject.jsonObject["episodes"]!!.jsonObject + val epDataArray = epObject.jsonObject["data"]!!.jsonArray.reversed() + for (epItem in epDataArray) { + val episode = SEpisode.create() + val seNum = epItem.jsonObject["season_number"]!!.jsonPrimitive.content + val epNum = epItem.jsonObject["episode_number"]!!.jsonPrimitive.content + episode.name = "Staffel $seNum Folge $epNum : " + epItem.jsonObject["name"]!!.jsonPrimitive.content + episode.episode_number = epNum.toFloat() + val epId = epItem.jsonObject["title_id"]!!.jsonPrimitive.content + episode.setUrlWithoutDomain("$baseUrl/api/v1/titles/$epId/seasons/$seNum/episodes/$epNum?load=videos,compactCredits,primaryVideo") + episodeList.add(episode) + } + } + } + } else { + val episode = SEpisode.create() + episode.episode_number = 1F + episode.name = "Film" + episode.setUrlWithoutDomain(url) + episodeList.add(episode) + } + return episodeList + } + + // Video Extractor + + override fun videoListRequest(episode: SEpisode): Request { + return GET(baseUrl + episode.url, headers = Headers.headersOf("referer", baseUrl)) + } + + override fun videoListParse(response: Response): List<Video> { + val responseString = response.body.string() + val url = response.request.url.toString() + return videosFromJson(responseString, url) + } + + private fun videosFromJson(jsonLine: String?, url: String): List<Video> { + val videoList = mutableListOf<Video>() + val hosterSelection = preferences.getStringSet("hoster_selection", setOf("stape", "vidg", "svid", "hstream", "flions")) + val jsonData = jsonLine ?: return emptyList() + val jObject = json.decodeFromString<JsonObject>(jsonData) + if (url.contains("episodes")) { + val epObject = jObject.jsonObject["episode"]!!.jsonObject + val videoArray = epObject.jsonObject["videos"]!!.jsonArray + for (item in videoArray) { + val host = item.jsonObject["name"]!!.jsonPrimitive.content + val eUrl = item.jsonObject["src"]!!.jsonPrimitive.content + when { + host.contains("Streamtape") && hosterSelection?.contains("stape") == true -> { + val quality = "Streamtape" + val video = StreamTapeExtractor(client).videoFromUrl(eUrl, quality) + if (video != null) { + videoList.add(video) + } + } + host.contains("Streamvid") && hosterSelection?.contains("svid") == true -> { + val video = StreamVidExtractor(client).videosFromUrl(eUrl) + videoList.addAll(video) + } + host.contains("Highstream") && hosterSelection?.contains("hstream") == true -> { + val videos = StreamVidExtractor(client).videosFromUrl(eUrl, prefix = "Highstream - ") + videoList.addAll(videos) + } + host.contains("VidGuard") && hosterSelection?.contains("vidg") == true -> { + val videos = VidGuardExtractor(client).videosFromUrl(eUrl) + videoList.addAll(videos) + } + host.contains("Filelions") && hosterSelection?.contains("flions") == true -> { + val videos = StreamWishExtractor(client, headers, mB = false).videosFromUrl(eUrl, videoNameGen = { quality -> "FileLions - $quality" }) + videoList.addAll(videos) + } + } + } + } else { + val titleObject = jObject.jsonObject["title"]!!.jsonObject + val videoArray = titleObject.jsonObject["videos"]!!.jsonArray + for (item in videoArray) { + val host = item.jsonObject["name"]!!.jsonPrimitive.content + val fUrl = item.jsonObject["src"]!!.jsonPrimitive.content + when { + host.contains("Streamtape") && hosterSelection?.contains("stape") == true -> { + val quality = "Streamtape" + val video = StreamTapeExtractor(client).videoFromUrl(fUrl, quality) + if (video != null) { + videoList.add(video) + } + } + host.contains("Streamvid") && hosterSelection?.contains("svid") == true -> { + val video = StreamVidExtractor(client).videosFromUrl(fUrl) + videoList.addAll(video) + } + host.contains("Highstream") && hosterSelection?.contains("hstream") == true -> { + val videos = StreamVidExtractor(client).videosFromUrl(fUrl, prefix = "Highstream - ", sourceChange = true) + videoList.addAll(videos) + } + host.contains("Filelions") && hosterSelection?.contains("flions") == true -> { + val videos = StreamWishExtractor(client, headers, mB = false).videosFromUrl(fUrl, videoNameGen = { quality -> "FileLions - $quality" }) + videoList.addAll(videos) + } + host.contains("VidGuard") && hosterSelection?.contains("vidg") == true -> { + val videos = VidGuardExtractor(client).videosFromUrl(fUrl) + videoList.addAll(videos) + } + } + } + } + return videoList + } + + override fun List<Video>.sort(): List<Video> { + val hoster = preferences.getString("preferred_hoster", null) + if (hoster != null) { + val newList = mutableListOf<Video>() + var preferred = 0 + for (video in this) { + if (video.url.contains(hoster)) { + newList.add(preferred, video) + preferred++ + } else { + newList.add(video) + } + } + return newList + } + return this + } + + // Search + + override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request = GET( + "$baseUrl/api/v1/search/$query?query=$query", + headers = Headers.headersOf("referer", "$baseUrl/search/$query"), + ) + + override fun searchAnimeParse(response: Response): AnimesPage { + val responseString = response.body.string() + return parseSearchAnimeJson(responseString) + } + + private fun parseSearchAnimeJson(jsonLine: String?): AnimesPage { + val jsonData = jsonLine ?: return AnimesPage(emptyList(), false) + val jObject = json.decodeFromString<JsonObject>(jsonData) + val array = jObject["results"]!!.jsonArray + val animeList = mutableListOf<SAnime>() + for (item in array) { + val anime = SAnime.create() + anime.title = item.jsonObject["name"]!!.jsonPrimitive.content + val animeId = item.jsonObject["id"]!!.jsonPrimitive.content + anime.setUrlWithoutDomain("$baseUrl/api/v1/titles/$animeId?load=images,genres,productionCountries,keywords,videos,primaryVideo,seasons,compactCredits") + anime.thumbnail_url = item.jsonObject["poster"]?.jsonPrimitive?.content ?: item.jsonObject["backdrop"]?.jsonPrimitive?.content + animeList.add(anime) + } + return AnimesPage(animeList, hasNextPage = false) + } + // Details + + override fun animeDetailsRequest(anime: SAnime): Request = GET(baseUrl + anime.url, headers = Headers.headersOf("referer", baseUrl)) + + override fun animeDetailsParse(response: Response): SAnime { + val responseString = response.body.string() + return parseAnimeDetailsParseJson(responseString) + } + + private fun parseAnimeDetailsParseJson(jsonLine: String?): SAnime { + val anime = SAnime.create() + val jsonData = jsonLine ?: return anime + val jObject = json.decodeFromString<JsonObject>(jsonData) + val jO = jObject.jsonObject["title"]!!.jsonObject + anime.title = jO.jsonObject["name"]!!.jsonPrimitive.content + anime.description = jO.jsonObject["description"]!!.jsonPrimitive.content + val genArray = jO.jsonObject["genres"]!!.jsonArray + val genres = mutableListOf<String>() + for (item in genArray) { + val genre = item.jsonObject["display_name"]!!.jsonPrimitive.content + genres.add(genre) + } + anime.genre = genres.joinToString { it } + anime.thumbnail_url = jO.jsonObject["poster"]?.jsonPrimitive?.content ?: jO.jsonObject["backdrop"]?.jsonPrimitive?.content + return anime + } + + // Latest + + override fun latestUpdatesParse(response: Response): AnimesPage = throw Exception("not Used") + + override fun latestUpdatesRequest(page: Int): Request = throw Exception("Not used") + + // Preferences + + override fun setupPreferenceScreen(screen: PreferenceScreen) { + val hosterPref = ListPreference(screen.context).apply { + key = "preferred_hoster" + title = "Standard-Hoster" + entries = arrayOf("Streamtape", "VidGuard", "Streamvid", "Highstream", "Filelions") + entryValues = arrayOf("https://streamtape", "https://moflix-stream", "https://streamvid", "https://highstream", "https://moflix-stream") + setDefaultValue("https://streamtape") + summary = "%s" + + setOnPreferenceChangeListener { _, newValue -> + val selected = newValue as String + val index = findIndexOfValue(selected) + val entry = entryValues[index] as String + preferences.edit().putString(key, entry).commit() + } + } + val subSelection = MultiSelectListPreference(screen.context).apply { + key = "hoster_selection" + title = "Hoster auswählen" + entries = arrayOf("Streamtape", "VidGuard", "Streamvid", "Highstream", "Filelions") + entryValues = arrayOf("stape", "vidg", "svid", "hstream", "flions") + setDefaultValue(setOf("stape", "vidg", "svid", "hstream", "flions")) + + setOnPreferenceChangeListener { _, newValue -> + preferences.edit().putStringSet(key, newValue as Set<String>).commit() + } + } + screen.addPreference(hosterPref) + screen.addPreference(subSelection) + } +} diff --git a/src/de/moflixstream/src/eu/kanade/tachiyomi/animeextension/de/moflixstream/extractors/VidGuardExtractor.kt b/src/de/moflixstream/src/eu/kanade/tachiyomi/animeextension/de/moflixstream/extractors/VidGuardExtractor.kt new file mode 100644 index 0000000000..4924088afd --- /dev/null +++ b/src/de/moflixstream/src/eu/kanade/tachiyomi/animeextension/de/moflixstream/extractors/VidGuardExtractor.kt @@ -0,0 +1,124 @@ +package eu.kanade.tachiyomi.animeextension.de.moflixstream.extractors + +import android.app.Application +import android.os.Handler +import android.os.Looper +import android.util.Base64 +import android.webkit.JavascriptInterface +import android.webkit.WebSettings +import android.webkit.WebView +import android.webkit.WebViewClient +import eu.kanade.tachiyomi.animesource.model.Video +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.util.asJsoup +import okhttp3.Headers +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.OkHttpClient +import uy.kohesive.injekt.injectLazy +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit + +class VidGuardExtractor(private val client: OkHttpClient) { + private val context: Application by injectLazy() + private val handler by lazy { Handler(Looper.getMainLooper()) } + + class JsObject(private val latch: CountDownLatch) { + var payload: String = "" + + @JavascriptInterface + fun passPayload(passedPayload: String) { + payload = passedPayload + latch.countDown() + } + } + + fun videosFromUrl(url: String): List<Video> { + val doc = client.newCall(GET(url)).execute().use { it.asJsoup() } + val scriptUrl = doc.selectFirst("script[src*=ad/plugin]") + ?.absUrl("src") + ?: return emptyList() + + val headers = Headers.headersOf("Referer", url) + val script = client.newCall(GET(scriptUrl, headers)).execute() + .use { it.body.string() } + + val sources = getSourcesFromScript(script, url) + .takeIf { it.isNotBlank() && it != "undefined" } + ?: return emptyList() + + return sources.substringAfter("stream:[").substringBefore("}]") + .split('{') + .drop(1) + .mapNotNull { line -> + val resolution = line.substringAfter("Label\":\"").substringBefore('"') + val videoUrl = line.substringAfter("URL\":\"").substringBefore('"') + .takeIf(String::isNotBlank) + ?.let(::fixUrl) + ?: return@mapNotNull null + Video(videoUrl, "VidGuard - $resolution", videoUrl, headers) + } + } + + private fun getSourcesFromScript(script: String, url: String): String { + val latch = CountDownLatch(1) + + var webView: WebView? = null + + val jsinterface = JsObject(latch) + + handler.post { + val webview = WebView(context) + webView = webview + with(webview.settings) { + javaScriptEnabled = true + domStorageEnabled = true + databaseEnabled = true + useWideViewPort = false + loadWithOverviewMode = false + cacheMode = WebSettings.LOAD_NO_CACHE + } + + webview.addJavascriptInterface(jsinterface, "android") + webview.webViewClient = object : WebViewClient() { + override fun onPageFinished(view: WebView?, url: String?) { + view?.clearCache(true) + view?.clearFormData() + view?.evaluateJavascript(script) {} + view?.evaluateJavascript("window.android.passPayload(JSON.stringify(window.svg))") {} + } + } + + webview.loadDataWithBaseURL(url, "<html></html>", "text/html", "UTF-8", null) + } + + latch.await(5, TimeUnit.SECONDS) + + handler.post { + webView?.stopLoading() + webView?.destroy() + webView = null + } + + return jsinterface.payload + } + + private fun fixUrl(url: String): String { + val httpUrl = url.toHttpUrl() + val originalSign = httpUrl.queryParameter("sig")!! + val newSign = originalSign.chunked(2).joinToString("") { + Char(it.toInt(16) xor 2).toString() + } + .let { String(Base64.decode(it, Base64.DEFAULT)) } + .substring(5) + .chunked(2) + .reversed() + .joinToString("") + .substring(5) + + return httpUrl.newBuilder() + .removeAllQueryParameters("sig") + .addQueryParameter("sig", newSign) + .build() + .toString() + } +} From 349ad9ec343e257cc418d0af0bc2a3f2f0e0d71f Mon Sep 17 00:00:00 2001 From: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sat, 14 Oct 2023 14:55:57 +0200 Subject: [PATCH 2/4] add lulustream --- .../de/moflixstream/MoflixStream.kt | 21 +++++++++---- .../extractors/UnpackerExtractor.kt | 31 +++++++++++++++++++ 2 files changed, 46 insertions(+), 6 deletions(-) create mode 100644 src/de/moflixstream/src/eu/kanade/tachiyomi/animeextension/de/moflixstream/extractors/UnpackerExtractor.kt diff --git a/src/de/moflixstream/src/eu/kanade/tachiyomi/animeextension/de/moflixstream/MoflixStream.kt b/src/de/moflixstream/src/eu/kanade/tachiyomi/animeextension/de/moflixstream/MoflixStream.kt index 1356cb5387..cd7c908f7e 100644 --- a/src/de/moflixstream/src/eu/kanade/tachiyomi/animeextension/de/moflixstream/MoflixStream.kt +++ b/src/de/moflixstream/src/eu/kanade/tachiyomi/animeextension/de/moflixstream/MoflixStream.kt @@ -5,6 +5,7 @@ import android.content.SharedPreferences import androidx.preference.ListPreference import androidx.preference.MultiSelectListPreference import androidx.preference.PreferenceScreen +import eu.kanade.tachiyomi.animeextension.de.moflixstream.extractors.UnpackerExtractor import eu.kanade.tachiyomi.animeextension.de.moflixstream.extractors.VidGuardExtractor import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource import eu.kanade.tachiyomi.animesource.model.AnimeFilterList @@ -172,7 +173,7 @@ class MoflixStream : ConfigurableAnimeSource, AnimeHttpSource() { private fun videosFromJson(jsonLine: String?, url: String): List<Video> { val videoList = mutableListOf<Video>() - val hosterSelection = preferences.getStringSet("hoster_selection", setOf("stape", "vidg", "svid", "hstream", "flions")) + val hosterSelection = preferences.getStringSet("hoster_selection", setOf("stape", "vidg", "svid", "hstream", "flions", "lstream")) val jsonData = jsonLine ?: return emptyList() val jObject = json.decodeFromString<JsonObject>(jsonData) if (url.contains("episodes")) { @@ -205,6 +206,10 @@ class MoflixStream : ConfigurableAnimeSource, AnimeHttpSource() { val videos = StreamWishExtractor(client, headers, mB = false).videosFromUrl(eUrl, videoNameGen = { quality -> "FileLions - $quality" }) videoList.addAll(videos) } + host.contains("LuluStream") && hosterSelection?.contains("lstream") == true -> { + val videos = UnpackerExtractor(client, headers).videosFromUrl(eUrl, "LuluStream") + videoList.addAll(videos) + } } } } else { @@ -237,6 +242,10 @@ class MoflixStream : ConfigurableAnimeSource, AnimeHttpSource() { val videos = VidGuardExtractor(client).videosFromUrl(fUrl) videoList.addAll(videos) } + host.contains("LuluStream") && hosterSelection?.contains("lstream") == true -> { + val videos = UnpackerExtractor(client, headers).videosFromUrl(fUrl, "LuluStream") + videoList.addAll(videos) + } } } } @@ -327,8 +336,8 @@ class MoflixStream : ConfigurableAnimeSource, AnimeHttpSource() { val hosterPref = ListPreference(screen.context).apply { key = "preferred_hoster" title = "Standard-Hoster" - entries = arrayOf("Streamtape", "VidGuard", "Streamvid", "Highstream", "Filelions") - entryValues = arrayOf("https://streamtape", "https://moflix-stream", "https://streamvid", "https://highstream", "https://moflix-stream") + entries = arrayOf("Streamtape", "VidGuard", "Streamvid", "Highstream", "Filelions", "LuluStream") + entryValues = arrayOf("https://streamtape", "https://moflix-stream", "https://streamvid", "https://highstream", "https://moflix-stream", "https://luluvdo") setDefaultValue("https://streamtape") summary = "%s" @@ -342,9 +351,9 @@ class MoflixStream : ConfigurableAnimeSource, AnimeHttpSource() { val subSelection = MultiSelectListPreference(screen.context).apply { key = "hoster_selection" title = "Hoster auswählen" - entries = arrayOf("Streamtape", "VidGuard", "Streamvid", "Highstream", "Filelions") - entryValues = arrayOf("stape", "vidg", "svid", "hstream", "flions") - setDefaultValue(setOf("stape", "vidg", "svid", "hstream", "flions")) + entries = arrayOf("Streamtape", "VidGuard", "Streamvid", "Highstream", "Filelions", "LuluStream") + entryValues = arrayOf("stape", "vidg", "svid", "hstream", "flions", "lstream") + setDefaultValue(setOf("stape", "vidg", "svid", "hstream", "flions", "lstream")) setOnPreferenceChangeListener { _, newValue -> preferences.edit().putStringSet(key, newValue as Set<String>).commit() diff --git a/src/de/moflixstream/src/eu/kanade/tachiyomi/animeextension/de/moflixstream/extractors/UnpackerExtractor.kt b/src/de/moflixstream/src/eu/kanade/tachiyomi/animeextension/de/moflixstream/extractors/UnpackerExtractor.kt new file mode 100644 index 0000000000..df7371dbaa --- /dev/null +++ b/src/de/moflixstream/src/eu/kanade/tachiyomi/animeextension/de/moflixstream/extractors/UnpackerExtractor.kt @@ -0,0 +1,31 @@ +package eu.kanade.tachiyomi.animeextension.de.moflixstream.extractors + +import dev.datlag.jsunpacker.JsUnpacker +import eu.kanade.tachiyomi.animesource.model.Video +import eu.kanade.tachiyomi.lib.playlistutils.PlaylistUtils +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.util.asJsoup +import okhttp3.Headers +import okhttp3.OkHttpClient + +class UnpackerExtractor(private val client: OkHttpClient, private val headers: Headers) { + private val playlistUtils by lazy { PlaylistUtils(client, headers) } + + fun videosFromUrl(url: String, hoster: String): List<Video> { + val doc = client.newCall(GET(url, headers)).execute() + .use { it.asJsoup() } + + val script = doc.selectFirst("script:containsData(eval)") + ?.data() + ?.let(JsUnpacker::unpackAndCombine) + ?: return emptyList() + + val playlistUrl = script.substringAfter("file:\"").substringBefore('"') + + return playlistUtils.extractFromHls( + playlistUrl, + referer = playlistUrl, + videoNameGen = { "$hoster - $it" }, + ) + } +} From 834f1fa55cdb536395c42614565a9b6a77c4ee2b Mon Sep 17 00:00:00 2001 From: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sat, 14 Oct 2023 15:18:43 +0200 Subject: [PATCH 3/4] rename paramter --- .../kanade/tachiyomi/lib/playlistutils/PlaylistUtils.kt | 8 ++++---- .../lib/streamwishextractor/StreamWishExtractor.kt | 4 ++-- .../animeextension/de/moflixstream/MoflixStream.kt | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/playlist-utils/src/main/java/eu/kanade/tachiyomi/lib/playlistutils/PlaylistUtils.kt b/lib/playlist-utils/src/main/java/eu/kanade/tachiyomi/lib/playlistutils/PlaylistUtils.kt index bbe306b62a..846e36236c 100644 --- a/lib/playlist-utils/src/main/java/eu/kanade/tachiyomi/lib/playlistutils/PlaylistUtils.kt +++ b/lib/playlist-utils/src/main/java/eu/kanade/tachiyomi/lib/playlistutils/PlaylistUtils.kt @@ -30,7 +30,7 @@ class PlaylistUtils(private val client: OkHttpClient, private val headers: Heade fun extractFromHls( playlistUrl: String, referer: String = "", - mB: Boolean = true, + baseOnOff: Boolean = true, masterHeaders: Headers, videoHeaders: Headers, videoNameGen: (String) -> String = { quality -> quality }, @@ -40,7 +40,7 @@ class PlaylistUtils(private val client: OkHttpClient, private val headers: Heade return extractFromHls( playlistUrl, referer, - mB, + baseOnOff, { _, _ -> masterHeaders }, { _, _, _ -> videoHeaders }, videoNameGen, @@ -73,7 +73,7 @@ class PlaylistUtils(private val client: OkHttpClient, private val headers: Heade fun extractFromHls( playlistUrl: String, referer: String = "", - mB: Boolean = true, + baseOnOff: Boolean = true, masterHeadersGen: (Headers, String) -> Headers = { baseHeaders, referer -> generateMasterHeaders(baseHeaders, referer) }, @@ -100,7 +100,7 @@ class PlaylistUtils(private val client: OkHttpClient, private val headers: Heade val playlistHttpUrl = playlistUrl.toHttpUrl() - val masterBase = if(mB) { + val masterBase = if(baseOnOff) { playlistHttpUrl.newBuilder().apply { removePathSegment(playlistHttpUrl.pathSize - 1) }.build().toString() + "/" diff --git a/lib/streamwish-extractor/src/main/java/eu/kanade/tachiyomi/lib/streamwishextractor/StreamWishExtractor.kt b/lib/streamwish-extractor/src/main/java/eu/kanade/tachiyomi/lib/streamwishextractor/StreamWishExtractor.kt index e81fe18fea..32e17f6690 100644 --- a/lib/streamwish-extractor/src/main/java/eu/kanade/tachiyomi/lib/streamwishextractor/StreamWishExtractor.kt +++ b/lib/streamwish-extractor/src/main/java/eu/kanade/tachiyomi/lib/streamwishextractor/StreamWishExtractor.kt @@ -8,7 +8,7 @@ import eu.kanade.tachiyomi.util.asJsoup import okhttp3.Headers import okhttp3.OkHttpClient -class StreamWishExtractor(private val client: OkHttpClient, private val headers: Headers, private val mB: Boolean = true) { +class StreamWishExtractor(private val client: OkHttpClient, private val headers: Headers, private val baseOnOff: Boolean = true) { private val playlistUtils by lazy { PlaylistUtils(client, headers) } fun videosFromUrl(url: String, prefix: String) = videosFromUrl(url) { "$prefix - $it" } @@ -31,6 +31,6 @@ class StreamWishExtractor(private val client: OkHttpClient, private val headers: ?.takeIf(String::isNotBlank) ?: return emptyList() - return playlistUtils.extractFromHls(masterUrl, url, videoNameGen = videoNameGen, mB = mB) + return playlistUtils.extractFromHls(masterUrl, url, videoNameGen = videoNameGen, baseOnOff = baseOnOff) } } diff --git a/src/de/moflixstream/src/eu/kanade/tachiyomi/animeextension/de/moflixstream/MoflixStream.kt b/src/de/moflixstream/src/eu/kanade/tachiyomi/animeextension/de/moflixstream/MoflixStream.kt index cd7c908f7e..5bf15ab887 100644 --- a/src/de/moflixstream/src/eu/kanade/tachiyomi/animeextension/de/moflixstream/MoflixStream.kt +++ b/src/de/moflixstream/src/eu/kanade/tachiyomi/animeextension/de/moflixstream/MoflixStream.kt @@ -203,7 +203,7 @@ class MoflixStream : ConfigurableAnimeSource, AnimeHttpSource() { videoList.addAll(videos) } host.contains("Filelions") && hosterSelection?.contains("flions") == true -> { - val videos = StreamWishExtractor(client, headers, mB = false).videosFromUrl(eUrl, videoNameGen = { quality -> "FileLions - $quality" }) + val videos = StreamWishExtractor(client, headers, baseOnOff = false).videosFromUrl(eUrl, videoNameGen = { quality -> "FileLions - $quality" }) videoList.addAll(videos) } host.contains("LuluStream") && hosterSelection?.contains("lstream") == true -> { @@ -235,7 +235,7 @@ class MoflixStream : ConfigurableAnimeSource, AnimeHttpSource() { videoList.addAll(videos) } host.contains("Filelions") && hosterSelection?.contains("flions") == true -> { - val videos = StreamWishExtractor(client, headers, mB = false).videosFromUrl(fUrl, videoNameGen = { quality -> "FileLions - $quality" }) + val videos = StreamWishExtractor(client, headers, baseOnOff = false).videosFromUrl(fUrl, videoNameGen = { quality -> "FileLions - $quality" }) videoList.addAll(videos) } host.contains("VidGuard") && hosterSelection?.contains("vidg") == true -> { From 277d0f9040da78a571188e3c289b2816781610b4 Mon Sep 17 00:00:00 2001 From: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sat, 14 Oct 2023 15:25:53 +0200 Subject: [PATCH 4/4] refactoring --- .../lib/playlistutils/PlaylistUtils.kt | 22 ++++++++----------- .../StreamWishExtractor.kt | 4 ++-- .../de/moflixstream/MoflixStream.kt | 4 ++-- 3 files changed, 13 insertions(+), 17 deletions(-) diff --git a/lib/playlist-utils/src/main/java/eu/kanade/tachiyomi/lib/playlistutils/PlaylistUtils.kt b/lib/playlist-utils/src/main/java/eu/kanade/tachiyomi/lib/playlistutils/PlaylistUtils.kt index 846e36236c..f310910639 100644 --- a/lib/playlist-utils/src/main/java/eu/kanade/tachiyomi/lib/playlistutils/PlaylistUtils.kt +++ b/lib/playlist-utils/src/main/java/eu/kanade/tachiyomi/lib/playlistutils/PlaylistUtils.kt @@ -30,7 +30,6 @@ class PlaylistUtils(private val client: OkHttpClient, private val headers: Heade fun extractFromHls( playlistUrl: String, referer: String = "", - baseOnOff: Boolean = true, masterHeaders: Headers, videoHeaders: Headers, videoNameGen: (String) -> String = { quality -> quality }, @@ -40,7 +39,6 @@ class PlaylistUtils(private val client: OkHttpClient, private val headers: Heade return extractFromHls( playlistUrl, referer, - baseOnOff, { _, _ -> masterHeaders }, { _, _, _ -> videoHeaders }, videoNameGen, @@ -73,7 +71,6 @@ class PlaylistUtils(private val client: OkHttpClient, private val headers: Heade fun extractFromHls( playlistUrl: String, referer: String = "", - baseOnOff: Boolean = true, masterHeadersGen: (Headers, String) -> Headers = { baseHeaders, referer -> generateMasterHeaders(baseHeaders, referer) }, @@ -100,18 +97,17 @@ class PlaylistUtils(private val client: OkHttpClient, private val headers: Heade val playlistHttpUrl = playlistUrl.toHttpUrl() - val masterBase = if(baseOnOff) { - playlistHttpUrl.newBuilder().apply { - removePathSegment(playlistHttpUrl.pathSize - 1) - }.build().toString() + "/" - } else { - playlistUrl - } + val masterUrlBasePath = playlistHttpUrl.newBuilder().apply { + removePathSegment(playlistHttpUrl.pathSize - 1) + addPathSegment("") + query(null) + fragment(null) + }.build().toString() // Get subtitles val subtitleTracks = subtitleList + SUBTITLE_REGEX.findAll(masterPlaylist).mapNotNull { Track( - getAbsoluteUrl(it.groupValues[2], playlistUrl, masterBase) ?: return@mapNotNull null, + getAbsoluteUrl(it.groupValues[2], playlistUrl, masterUrlBasePath ) ?: return@mapNotNull null, it.groupValues[1] ) }.toList() @@ -119,7 +115,7 @@ class PlaylistUtils(private val client: OkHttpClient, private val headers: Heade // Get audio tracks val audioTracks = audioList + AUDIO_REGEX.findAll(masterPlaylist).mapNotNull { Track( - getAbsoluteUrl(it.groupValues[2], playlistUrl, masterBase) ?: return@mapNotNull null, + getAbsoluteUrl(it.groupValues[2], playlistUrl, masterUrlBasePath ) ?: return@mapNotNull null, it.groupValues[1] ) }.toList() @@ -131,7 +127,7 @@ class PlaylistUtils(private val client: OkHttpClient, private val headers: Heade .substringBefore(",") + "p" val videoUrl = it.substringAfter("\n").substringBefore("\n").let { url -> - getAbsoluteUrl(url, playlistUrl, masterBase) + getAbsoluteUrl(url, playlistUrl, masterUrlBasePath ) } ?: return@mapNotNull null diff --git a/lib/streamwish-extractor/src/main/java/eu/kanade/tachiyomi/lib/streamwishextractor/StreamWishExtractor.kt b/lib/streamwish-extractor/src/main/java/eu/kanade/tachiyomi/lib/streamwishextractor/StreamWishExtractor.kt index 32e17f6690..f182976c1f 100644 --- a/lib/streamwish-extractor/src/main/java/eu/kanade/tachiyomi/lib/streamwishextractor/StreamWishExtractor.kt +++ b/lib/streamwish-extractor/src/main/java/eu/kanade/tachiyomi/lib/streamwishextractor/StreamWishExtractor.kt @@ -8,7 +8,7 @@ import eu.kanade.tachiyomi.util.asJsoup import okhttp3.Headers import okhttp3.OkHttpClient -class StreamWishExtractor(private val client: OkHttpClient, private val headers: Headers, private val baseOnOff: Boolean = true) { +class StreamWishExtractor(private val client: OkHttpClient, private val headers: Headers) { private val playlistUtils by lazy { PlaylistUtils(client, headers) } fun videosFromUrl(url: String, prefix: String) = videosFromUrl(url) { "$prefix - $it" } @@ -31,6 +31,6 @@ class StreamWishExtractor(private val client: OkHttpClient, private val headers: ?.takeIf(String::isNotBlank) ?: return emptyList() - return playlistUtils.extractFromHls(masterUrl, url, videoNameGen = videoNameGen, baseOnOff = baseOnOff) + return playlistUtils.extractFromHls(masterUrl, url, videoNameGen = videoNameGen) } } diff --git a/src/de/moflixstream/src/eu/kanade/tachiyomi/animeextension/de/moflixstream/MoflixStream.kt b/src/de/moflixstream/src/eu/kanade/tachiyomi/animeextension/de/moflixstream/MoflixStream.kt index 5bf15ab887..75c096f8ad 100644 --- a/src/de/moflixstream/src/eu/kanade/tachiyomi/animeextension/de/moflixstream/MoflixStream.kt +++ b/src/de/moflixstream/src/eu/kanade/tachiyomi/animeextension/de/moflixstream/MoflixStream.kt @@ -203,7 +203,7 @@ class MoflixStream : ConfigurableAnimeSource, AnimeHttpSource() { videoList.addAll(videos) } host.contains("Filelions") && hosterSelection?.contains("flions") == true -> { - val videos = StreamWishExtractor(client, headers, baseOnOff = false).videosFromUrl(eUrl, videoNameGen = { quality -> "FileLions - $quality" }) + val videos = StreamWishExtractor(client, headers).videosFromUrl(eUrl, videoNameGen = { quality -> "FileLions - $quality" }) videoList.addAll(videos) } host.contains("LuluStream") && hosterSelection?.contains("lstream") == true -> { @@ -235,7 +235,7 @@ class MoflixStream : ConfigurableAnimeSource, AnimeHttpSource() { videoList.addAll(videos) } host.contains("Filelions") && hosterSelection?.contains("flions") == true -> { - val videos = StreamWishExtractor(client, headers, baseOnOff = false).videosFromUrl(fUrl, videoNameGen = { quality -> "FileLions - $quality" }) + val videos = StreamWishExtractor(client, headers).videosFromUrl(fUrl, videoNameGen = { quality -> "FileLions - $quality" }) videoList.addAll(videos) } host.contains("VidGuard") && hosterSelection?.contains("vidg") == true -> {