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 -> {