Initial commit

This commit is contained in:
almightyhak 2024-06-20 11:54:12 +07:00
commit 98ed7e8839
2263 changed files with 108711 additions and 0 deletions

View file

@ -0,0 +1,12 @@
ext {
extName = 'FilmPalast'
extClass = '.FilmPalast'
extVersionCode = 17
}
apply from: "$rootDir/common.gradle"
dependencies {
implementation(project(':lib:filemoon-extractor'))
implementation(project(':lib:voe-extractor'))
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View file

@ -0,0 +1,292 @@
package eu.kanade.tachiyomi.animeextension.de.filmpalast
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.filmpalast.extractors.EvoloadExtractor
import eu.kanade.tachiyomi.animeextension.de.filmpalast.extractors.StreamHideVidExtractor
import eu.kanade.tachiyomi.animeextension.de.filmpalast.extractors.UpstreamExtractor
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
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.ParsedAnimeHttpSource
import eu.kanade.tachiyomi.lib.filemoonextractor.FilemoonExtractor
import eu.kanade.tachiyomi.lib.voeextractor.VoeExtractor
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Headers
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class FilmPalast : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override val name = "FilmPalast"
override val baseUrl = "https://filmpalast.to"
override val lang = "de"
override val supportsLatest = true
private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
override fun popularAnimeSelector(): String = "article.liste > a"
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/movies/top/page/$page")
override fun popularAnimeFromElement(element: Element): SAnime {
val anime = SAnime.create()
anime.setUrlWithoutDomain(element.attr("href"))
val file = element.select("img").attr("src")
anime.thumbnail_url = "$baseUrl$file"
anime.title = element.attr("title")
return anime
}
override fun popularAnimeNextPageSelector(): String = "a.pageing:contains(vorwärts)"
// episodes
override fun episodeListSelector() = throw UnsupportedOperationException()
override fun episodeListParse(response: Response): List<SEpisode> {
val document = response.asJsoup()
val episodeList = mutableListOf<SEpisode>()
val episode = SEpisode.create()
episode.name = "Film"
episode.episode_number = 1F
episode.setUrlWithoutDomain(document.select("link[rel=canonical]").attr("href"))
episodeList.add(episode)
return episodeList.reversed()
}
override fun episodeFromElement(element: Element): SEpisode = throw UnsupportedOperationException()
// Video Extractor
override fun videoListParse(response: Response): List<Video> {
val document = response.asJsoup()
return videosFromElement(document)
}
private fun videosFromElement(document: Document): List<Video> {
val elements = document.select("ul.currentStreamLinks > li > a")
val hosterSelection = preferences.getStringSet(PREF_SELECTION_KEY, PREF_SELECTION_DEFAULT)!!
return elements.mapNotNull { element ->
val url = element.attr("abs:href").ifEmpty {
element.attr("abs:data-player-url")
}
when {
url.contains("https://voe.sx") && hosterSelection.contains("voe") ->
VoeExtractor(client).videosFromUrl(url)
url.contains("https://upstream.to") && hosterSelection.contains("up") ->
UpstreamExtractor(client).videoFromUrl(url)
url.contains("https://streamtape.com") && hosterSelection.contains("stape") -> {
runCatching {
val stapeHeaders = Headers.headersOf(
"Referer",
baseUrl,
"Cookie",
"Fuck Streamtape because they add concatenation to fuck up scrapers",
)
// from lib streamtape-extractor
// TODO: add headers param to lib, so we can use the
// lib in cases like this.
val doc = client.newCall(GET(url, headers = stapeHeaders))
.execute()
.asJsoup()
val targetLine = "document.getElementById('robotlink')"
val script = doc.selectFirst("script:containsData($targetLine)")
?.data()
?.substringAfter("$targetLine.innerHTML = '")
?: return@runCatching null
val videoUrl = "https:" + script.substringBefore("'") +
script.substringAfter("+ ('xcd").substringBefore("'")
listOf(Video(videoUrl, "Streamtape", videoUrl))
}.getOrNull()
}
url.contains("https://evoload.io") && hosterSelection.contains("evo") -> {
val quality = "Evoload"
document.selectFirst("#EvoVid_html5_api")?.attr("src")?.let { videoUrl ->
if (videoUrl.contains("EvoStreams")) {
listOf(Video(videoUrl, quality, videoUrl))
} else {
EvoloadExtractor(client).videoFromUrl(url, quality)
}
}
}
url.contains("filemoon.sx") && hosterSelection.contains("moon") ->
FilemoonExtractor(client).videosFromUrl(url)
url.contains("hide.com") && hosterSelection.contains("hide") ->
StreamHideVidExtractor(client).videosFromUrl(url, "StreamHide")
url.contains("streamvid.net") && hosterSelection.contains("vid") ->
StreamHideVidExtractor(client).videosFromUrl(url, "StreamVid")
"wolfstream" in url && hosterSelection.contains("wolf") -> {
client.newCall(GET(url, headers)).execute()
.asJsoup()
.selectFirst("script:containsData(sources)")
?.data()
?.let { jsData ->
val videoUrl = jsData.substringAfter("{file:\"").substringBefore("\"")
listOf(Video(videoUrl, "WolfStream", videoUrl, headers = headers))
}
}
else -> null
}
}.flatten()
}
override fun List<Video>.sort(): List<Video> {
val hoster = preferences.getString(PREF_HOSTER_KEY, PREF_HOSTER_DEFAULT)!!
return sortedWith(
compareBy { it.url.contains(hoster) },
).reversed()
}
override fun videoListSelector() = throw UnsupportedOperationException()
override fun videoFromElement(element: Element) = throw UnsupportedOperationException()
override fun videoUrlParse(document: Document) = throw UnsupportedOperationException()
// Search
override fun searchAnimeFromElement(element: Element): SAnime {
val anime = SAnime.create()
anime.setUrlWithoutDomain(element.attr("href"))
val file = element.select("img").attr("src")
anime.thumbnail_url = "$baseUrl$file"
anime.title = element.attr("title")
return anime
}
override fun searchAnimeNextPageSelector(): String = "a.pageing:contains(vorwärts)"
override fun searchAnimeSelector(): String = "article.liste > a"
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val url = "$baseUrl/search/title/$query/$page"
return GET(url)
}
// Details
override fun animeDetailsParse(document: Document): SAnime {
val anime = SAnime.create()
val file = document.select("img.cover2").attr("src")
anime.thumbnail_url = "$baseUrl$file"
anime.title = document.select("h2.bgDark").text()
anime.genre = document.select("#detail-content-list > li:nth-child(2) > span").joinToString(", ") { it.text() }
anime.description = document.select("#detail-content-list > li:nth-child(3) > span").text()
anime.author = document.select("#detail-content-list > li:nth-child(4) > span").joinToString(", ") { it.text() }
anime.status = SAnime.COMPLETED
return anime
}
// Latest
override fun latestUpdatesNextPageSelector(): String = "a.pageing:contains(vorwärts)"
override fun latestUpdatesFromElement(element: Element): SAnime {
val anime = SAnime.create()
anime.setUrlWithoutDomain(element.attr("href"))
val file = element.select("img").attr("src")
anime.thumbnail_url = "$baseUrl$file"
anime.title = element.attr("title")
return anime
}
override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/page/$page")
override fun latestUpdatesSelector(): String = "article.liste > a"
// Preferences
override fun setupPreferenceScreen(screen: PreferenceScreen) {
val hosterPref = ListPreference(screen.context).apply {
key = PREF_HOSTER_KEY
title = PREF_HOSTER_TITLE
entries = PREF_HOSTER_ENTRIES
entryValues = PREF_HOSTER_VALUES
setDefaultValue(PREF_HOSTER_DEFAULT)
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 = PREF_SELECTION_KEY
title = PREF_SELECTION_TITLE
entries = PREF_SELECTION_ENTRIES
entryValues = PREF_SELECTION_VALUES
setDefaultValue(PREF_SELECTION_DEFAULT)
setOnPreferenceChangeListener { _, newValue ->
preferences.edit().putStringSet(key, newValue as Set<String>).commit()
}
}
screen.addPreference(hosterPref)
screen.addPreference(subSelection)
}
companion object {
private const val PREF_HOSTER_KEY = "preferred_hoster"
private const val PREF_HOSTER_TITLE = "Standard-Hoster"
private const val PREF_HOSTER_DEFAULT = "https://voe.sx"
private val PREF_HOSTER_ENTRIES = arrayOf(
"Voe",
"Streamtape",
"Evoload",
"Upstream",
"Filemoon",
"StreamHide",
"StreamVid",
"WolfStream",
)
private val PREF_HOSTER_VALUES = arrayOf(
"https://voe.sx",
"https://streamtape.com",
"https://evoload.io",
"https://upstream.to",
"https://filemoon.sx",
"hide.com",
"streamvid.net",
"https://wolfstream",
)
private const val PREF_SELECTION_KEY = "hoster_selection"
private const val PREF_SELECTION_TITLE = "Hoster auswählen"
private val PREF_SELECTION_ENTRIES = PREF_HOSTER_ENTRIES
private val PREF_SELECTION_VALUES = arrayOf(
"voe",
"stape",
"evo",
"up",
"moon",
"hide",
"vid",
"wolf",
)
private val PREF_SELECTION_DEFAULT = PREF_SELECTION_VALUES.toSet()
}
}

View file

@ -0,0 +1,45 @@
package eu.kanade.tachiyomi.animeextension.de.filmpalast.extractors
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.RequestBody.Companion.toRequestBody
class EvoloadExtractor(private val client: OkHttpClient) {
fun videoFromUrl(url: String, quality: String): List<Video> {
val videoList = mutableListOf<Video>()
val id = url.substringAfter("https://evoload.io/e/")
val csrvToken =
client.newCall(GET("https://csrv.evosrv.com/captcha?m412548=")).execute().body.string() // whatever that is
val captchaPass = client.newCall(GET("https://cd2.evosrv.com/html/jsx/e.jsx")).execute().toString()
.substringAfter("var captcha_pass = '").substringBefore("'")
val file = client.newCall(
POST(
"https://evoload.io/SecurePlayer",
body = "{\"code\":\"$id\",\"token\":\"ok\",\"csrv_token\":\"$csrvToken\",\"pass\":\"$captchaPass\",\"reff\":\"https://filmpalast.to/\"}".toRequestBody("application/json".toMediaType()),
),
).execute().body.string()
if (file.contains("backup")) {
val videoUrl = file.substringAfter("\"encoded_src\":\"").substringBefore("\",")
when {
!file.substringAfter("\"xstatus\":\"").substringBefore("\",").contains("del") -> {
val video = Video(url, quality, videoUrl)
videoList.add(video)
}
}
} else {
val videoUrl = file.substringAfter("\"src\":\"").substringBefore("\",")
when {
!file.substringAfter("\"xstatus\":\"").substringBefore("\",").contains("del") -> {
val video = Video(url, quality, videoUrl)
videoList.add(video)
}
}
}
return videoList
}
}

View file

@ -0,0 +1,205 @@
package eu.kanade.tachiyomi.animeextension.de.filmpalast.extractors
import java.util.regex.Pattern
import kotlin.math.pow
// https://github.com/cylonu87/JsUnpacker
class JsUnpacker(packedJS: String?) {
private var packedJS: String? = null
/**
* Detects whether the javascript is P.A.C.K.E.R. coded.
*
* @return true if it's P.A.C.K.E.R. coded.
*/
fun detect(): Boolean {
val js = packedJS!!.replace(" ", "")
val p = Pattern.compile("eval\\(function\\(p,a,c,k,e,[rd]")
val m = p.matcher(js)
return m.find()
}
/**
* Unpack the javascript
*
* @return the javascript unpacked or null.
*/
fun unpack(): String? {
val js = packedJS
runCatching {
var p =
Pattern.compile("""\}\s*\('(.*)',\s*(.*?),\s*(\d+),\s*'(.*?)'\.split\('\|'\)""", Pattern.DOTALL)
var m = p.matcher(js)
if (m.find() && m.groupCount() == 4) {
val payload = m.group(1).replace("\\'", "'")
val radixStr = m.group(2)
val countStr = m.group(3)
val symtab = m.group(4).split("\\|".toRegex()).toTypedArray()
val radix = radixStr.toIntOrNull() ?: 36
val count = countStr.toIntOrNull() ?: 0
if (symtab.size != count) {
throw Exception("Unknown p.a.c.k.e.r. encoding")
}
val unbase = Unbase(radix)
p = Pattern.compile("\\b\\w+\\b")
m = p.matcher(payload)
val decoded = StringBuilder(payload)
var replaceOffset = 0
while (m.find()) {
val word = m.group(0)
val x = unbase.unbase(word)
var value: String? = null
if (x < symtab.size && x >= 0) {
value = symtab[x]
}
if (value != null && value.isNotEmpty()) {
decoded.replace(m.start() + replaceOffset, m.end() + replaceOffset, value)
replaceOffset += value.length - word.length
}
}
return decoded.toString()
}
}
return null
}
private inner class Unbase(private val radix: Int) {
private val alphabet62 = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
private val alphabet95 =
" !\"#$%&\\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
private var alphabet: String? = null
private var dictionary: HashMap<String, Int>? = null
fun unbase(str: String): Int {
var ret = 0
if (alphabet == null) {
ret = str.toInt(radix)
} else {
val tmp = StringBuilder(str).reverse().toString()
for (i in tmp.indices) {
ret += (radix.toDouble().pow(i.toDouble()) * dictionary!![tmp.substring(i, i + 1)]!!).toInt()
}
}
return ret
}
init {
if (radix > 36) {
when {
radix < 62 -> {
alphabet = alphabet62.substring(0, radix)
}
radix in 63..94 -> {
alphabet = alphabet95.substring(0, radix)
}
radix == 62 -> {
alphabet = alphabet62
}
radix == 95 -> {
alphabet = alphabet95
}
}
dictionary = HashMap(95)
for (i in 0 until alphabet!!.length) {
dictionary!![alphabet!!.substring(i, i + 1)] = i
}
}
}
}
/**
* @param packedJS javascript P.A.C.K.E.R. coded.
*/
init {
this.packedJS = packedJS
}
companion object {
private val C =
listOf(
0x63,
0x6f,
0x6d,
0x2e,
0x67,
0x6f,
0x6f,
0x67,
0x6c,
0x65,
0x2e,
0x61,
0x6e,
0x64,
0x72,
0x6f,
0x69,
0x64,
0x2e,
0x67,
0x6d,
0x73,
0x2e,
0x61,
0x64,
0x73,
0x2e,
0x4d,
0x6f,
0x62,
0x69,
0x6c,
0x65,
0x41,
0x64,
0x73,
)
private val Z =
listOf(
0x63,
0x6f,
0x6d,
0x2e,
0x66,
0x61,
0x63,
0x65,
0x62,
0x6f,
0x6f,
0x6b,
0x2e,
0x61,
0x64,
0x73,
0x2e,
0x41,
0x64,
)
fun String.load(): String? {
return try {
var load = this
for (q in C.indices) {
if (C[q % 4] > 270) {
load += C[q % 3]
} else {
load += C[q].toChar()
}
}
Class.forName(load.substring(load.length - C.size, load.length)).name
} catch (_: Exception) {
try {
var f = C[2].toChar().toString()
for (w in Z.indices) {
f += Z[w].toChar()
}
return Class.forName(f.substring(0b001, f.length)).name
} catch (_: Exception) {
null
}
}
}
}
}

View file

@ -0,0 +1,35 @@
package eu.kanade.tachiyomi.animeextension.de.filmpalast.extractors
import eu.kanade.tachiyomi.animesource.model.Track
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.network.GET
import okhttp3.OkHttpClient
class StreamHideVidExtractor(private val client: OkHttpClient) {
// from nineanime / ask4movie FilemoonExtractor
private val subtitleRegex = Regex("""#EXT-X-MEDIA:TYPE=SUBTITLES.*?NAME="(.*?)".*?URI="(.*?)"""")
fun videosFromUrl(url: String, name: String): List<Video> {
val page = client.newCall(GET(url)).execute().body.string()
val unpacked = JsUnpacker(page).unpack() ?: return emptyList()
val playlistUrl = unpacked.substringAfter("sources:")
.substringAfter("file:\"") // StreamHide
.substringAfter("src:\"") // StreamVid
.substringBefore('"')
val playlistData = client.newCall(GET(playlistUrl)).execute().body.string()
val subs = subtitleRegex.findAll(playlistData).map {
val urlPart = it.groupValues[2]
val subUrl = when {
!urlPart.startsWith("https:") ->
playlistUrl.substringBeforeLast("/") + "/$urlPart"
else -> urlPart
}
Track(subUrl, it.groupValues[1])
}.toList()
// The playlist usually only have one video quality.
return listOf(Video(playlistUrl, name, playlistUrl, subtitleTracks = subs))
}
}

View file

@ -0,0 +1,30 @@
package eu.kanade.tachiyomi.animeextension.de.filmpalast.extractors
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Headers
import okhttp3.OkHttpClient
class UpstreamExtractor(private val client: OkHttpClient) {
fun videoFromUrl(url: String): MutableList<Video>? {
try {
val jsE = client.newCall(GET(url)).execute().asJsoup().selectFirst("script:containsData(eval)")!!.data()
val masterUrl = JsUnpacker(jsE).unpack().toString()
.substringAfter("{file:\"").substringBefore("\"}")
val masterBase = masterUrl.substringBefore("master")
val masterPlaylist = client.newCall(GET(masterUrl)).execute().body.string()
val videoList = mutableListOf<Video>()
masterPlaylist.substringAfter("#EXT-X-STREAM-INF:").split("#EXT-X-STREAM-INF:")
.forEach {
val quality = "Upstream:" + it.substringAfter("RESOLUTION=").substringAfter("x").substringBefore(",") + "p "
val videoUrl = masterBase + it.substringAfter("\n").substringBefore("\n")
videoList.add(Video(videoUrl, quality, videoUrl, headers = Headers.headersOf("origin", "https://upstream.to", "referer", "https://upstream.to/")))
}
return videoList
} catch (e: Exception) {
return null
}
}
}