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,15 @@
ext {
extName = 'Anime-Base'
extClass = '.AnimeBase'
extVersionCode = 24
isNsfw = true
}
apply from: "$rootDir/common.gradle"
dependencies {
implementation(project(":lib:voe-extractor"))
implementation(project(":lib:streamwish-extractor"))
implementation(project(":lib:playlist-utils"))
implementation("dev.datlag.jsunpacker:jsunpacker:1.0.1")
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

View file

@ -0,0 +1,299 @@
package eu.kanade.tachiyomi.animeextension.de.animebase
import android.app.Application
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.animeextension.de.animebase.extractors.UnpackerExtractor
import eu.kanade.tachiyomi.animeextension.de.animebase.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.ParsedAnimeHttpSource
import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor
import eu.kanade.tachiyomi.lib.voeextractor.VoeExtractor
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.util.asJsoup
import eu.kanade.tachiyomi.util.parallelCatchingFlatMapBlocking
import okhttp3.FormBody
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 AnimeBase : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override val name = "Anime-Base"
override val baseUrl = "https://anime-base.net"
override val lang = "de"
override val supportsLatest = true
private val preferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
// ============================== Popular ===============================
override fun popularAnimeRequest(page: Int) = GET("$baseUrl/favorites", headers)
override fun popularAnimeSelector() = "div.table-responsive > a"
override fun popularAnimeFromElement(element: Element) = SAnime.create().apply {
setUrlWithoutDomain(element.attr("href").replace("/link/", "/anime/"))
thumbnail_url = element.selectFirst("div.thumbnail img")?.absUrl("src")
title = element.selectFirst("div.caption h3")!!.text()
}
override fun popularAnimeNextPageSelector() = null
// =============================== Latest ===============================
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/updates", headers)
override fun latestUpdatesSelector() = "div.box-header + div.box-body > a"
override fun latestUpdatesFromElement(element: Element) = popularAnimeFromElement(element)
override fun latestUpdatesNextPageSelector() = null
// =============================== Search ===============================
override fun getFilterList() = AnimeBaseFilters.FILTER_LIST
private val searchToken by lazy {
client.newCall(GET("$baseUrl/searching", headers)).execute()
.asJsoup()
.selectFirst("form > input[name=_token]")!!
.attr("value")
}
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val params = AnimeBaseFilters.getSearchParameters(filters)
return when {
params.list.isEmpty() -> {
val body = FormBody.Builder()
.add("_token", searchToken)
.add("_token", searchToken)
.add("name_serie", query)
.add("jahr", params.year.toIntOrNull()?.toString() ?: "")
.apply {
params.languages.forEach { add("dubsub[]", it) }
params.genres.forEach { add("genre[]", it) }
}.build()
POST("$baseUrl/searching", headers, body)
}
else -> {
GET("$baseUrl/${params.list}${params.letter}?page=$page", headers)
}
}
}
override fun searchAnimeParse(response: Response): AnimesPage {
val doc = response.asJsoup()
return when {
doc.location().contains("/searching") -> {
val animes = doc.select(searchAnimeSelector()).map(::searchAnimeFromElement)
AnimesPage(animes, false)
}
else -> { // pages like filmlist or animelist
val animes = doc.select(popularAnimeSelector()).map(::popularAnimeFromElement)
val hasNext = doc.selectFirst(searchAnimeNextPageSelector()) != null
AnimesPage(animes, hasNext)
}
}
}
override fun searchAnimeSelector() = "div.col-lg-9.col-md-8 div.box-body > a"
override fun searchAnimeFromElement(element: Element) = popularAnimeFromElement(element)
override fun searchAnimeNextPageSelector() = "ul.pagination li > a[rel=next]"
// =========================== Anime Details ============================
override fun animeDetailsParse(document: Document) = SAnime.create().apply {
setUrlWithoutDomain(document.location())
val boxBody = document.selectFirst("div.box-body.box-profile > center")!!
title = boxBody.selectFirst("h3")!!.text()
thumbnail_url = boxBody.selectFirst("img")!!.absUrl("src")
val infosDiv = document.selectFirst("div.box-body > div.col-md-9")!!
status = parseStatus(infosDiv.getInfo("Status"))
genre = infosDiv.select("strong:contains(Genre) + p > a").eachText()
.joinToString()
.takeIf(String::isNotBlank)
description = buildString {
infosDiv.getInfo("Beschreibung")?.also(::append)
infosDiv.getInfo("Originalname")?.also { append("\nOriginal name: $it") }
infosDiv.getInfo("Erscheinungsjahr")?.also { append("\nErscheinungsjahr: $it") }
}
}
private fun parseStatus(status: String?) = when (status?.orEmpty()) {
"Laufend" -> SAnime.ONGOING
"Abgeschlossen" -> SAnime.COMPLETED
else -> SAnime.UNKNOWN
}
private fun Element.getInfo(selector: String) =
selectFirst("strong:contains($selector) + p")?.text()?.trim()
// ============================== Episodes ==============================
override fun episodeListParse(response: Response) =
super.episodeListParse(response).sortedWith(
compareBy(
{ it.name.startsWith("Film ") },
{ it.name.startsWith("Special ") },
{ it.episode_number },
),
).reversed()
override fun episodeListSelector() = "div.tab-content > div > div.panel"
override fun episodeFromElement(element: Element) = SEpisode.create().apply {
val epname = element.selectFirst("h3")?.text() ?: "Episode 1"
val language = when (element.selectFirst("button")?.attr("data-dubbed").orEmpty()) {
"0" -> "Subbed"
else -> "Dubbed"
}
name = epname
scanlator = language
episode_number = epname.substringBefore(":").substringAfter(" ").toFloatOrNull() ?: 0F
val selectorClass = element.classNames().first { it.startsWith("episode-div") }
setUrlWithoutDomain(element.baseUri() + "?selector=div.panel.$selectorClass")
}
// ============================ Video Links =============================
private val hosterSettings by lazy {
mapOf(
"Streamwish" to "https://streamwish.to/e/",
"Voe.SX" to "https://voe.sx/e/",
"Lulustream" to "https://lulustream.com/e/",
"VTube" to "https://vtbe.to/embed-",
"VidGuard" to "https://vembed.net/e/",
)
}
override fun videoListParse(response: Response): List<Video> {
val doc = response.asJsoup()
val selector = response.request.url.queryParameter("selector")
?: return emptyList()
return doc.select("$selector div.panel-body > button").toList()
.filter { it.text() in hosterSettings.keys }
.parallelCatchingFlatMapBlocking {
val language = when (it.attr("data-dubbed")) {
"0" -> "SUB"
else -> "DUB"
}
getVideosFromHoster(it.text(), it.attr("data-streamlink"))
.map { video ->
Video(
video.url,
"$language ${video.quality}",
video.videoUrl,
video.headers,
video.subtitleTracks,
video.audioTracks,
)
}
}
}
private val streamWishExtractor by lazy { StreamWishExtractor(client, headers) }
private val voeExtractor by lazy { VoeExtractor(client) }
private val unpackerExtractor by lazy { UnpackerExtractor(client, headers) }
private val vidguardExtractor by lazy { VidGuardExtractor(client) }
private fun getVideosFromHoster(hoster: String, urlpart: String): List<Video> {
val url = hosterSettings.get(hoster)!! + urlpart
return when (hoster) {
"Streamwish" -> streamWishExtractor.videosFromUrl(url)
"Voe.SX" -> voeExtractor.videosFromUrl(url)
"VTube", "Lulustream" -> unpackerExtractor.videosFromUrl(url, hoster)
"VidGuard" -> vidguardExtractor.videosFromUrl(url)
else -> null
} ?: emptyList()
}
override fun List<Video>.sort(): List<Video> {
val lang = preferences.getString(PREF_LANG_KEY, PREF_LANG_DEFAULT)!!
val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
return sortedWith(
compareBy(
{ it.quality.contains(lang) },
{ it.quality.contains(quality) },
{ Regex("""(\d+)p""").find(it.quality)?.groupValues?.get(1)?.toIntOrNull() ?: 0 },
),
).reversed()
}
override fun videoListSelector() = throw UnsupportedOperationException()
override fun videoFromElement(element: Element) = throw UnsupportedOperationException()
override fun videoUrlParse(document: Document) = throw UnsupportedOperationException()
// =============================== Search ===============================
override fun setupPreferenceScreen(screen: PreferenceScreen) {
ListPreference(screen.context).apply {
key = PREF_LANG_KEY
title = PREF_LANG_TITLE
entries = PREF_LANG_ENTRIES
entryValues = PREF_LANG_VALUES
setDefaultValue(PREF_LANG_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()
}
}.also(screen::addPreference)
ListPreference(screen.context).apply {
key = PREF_QUALITY_KEY
title = PREF_QUALITY_TITLE
entries = PREF_QUALITY_ENTRIES
entryValues = PREF_QUALITY_VALUES
setDefaultValue(PREF_QUALITY_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()
}
}.also(screen::addPreference)
}
// ============================= Utilities ==============================
companion object {
private const val PREF_LANG_KEY = "preferred_sub"
private const val PREF_LANG_TITLE = "Standardmäßig Sub oder Dub?"
private const val PREF_LANG_DEFAULT = "SUB"
private val PREF_LANG_ENTRIES = arrayOf("Sub", "Dub")
private val PREF_LANG_VALUES = arrayOf("SUB", "DUB")
private const val PREF_QUALITY_KEY = "preferred_quality"
private const val PREF_QUALITY_TITLE = "Preferred quality"
private const val PREF_QUALITY_DEFAULT = "720p"
private val PREF_QUALITY_ENTRIES = arrayOf("1080p", "720p", "480p", "360p")
private val PREF_QUALITY_VALUES = arrayOf("1080p", "720p", "480p", "360p")
}
}

View file

@ -0,0 +1,285 @@
package eu.kanade.tachiyomi.animeextension.de.animebase
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
object AnimeBaseFilters {
open class QueryPartFilter(
displayName: String,
val vals: Array<Pair<String, String>>,
) : AnimeFilter.Select<String>(
displayName,
vals.map { it.first }.toTypedArray(),
) {
fun toQueryPart() = vals[state].second
}
private inline fun <reified R> AnimeFilterList.getFirst(): R {
return first { it is R } as R
}
private inline fun <reified R> AnimeFilterList.asQueryPart(): String {
return (getFirst<R>() as QueryPartFilter).toQueryPart()
}
open class CheckBoxFilterList(name: String, val pairs: Array<Pair<String, String>>) :
AnimeFilter.Group<AnimeFilter.CheckBox>(name, pairs.map { CheckBoxVal(it.first, false) })
private class CheckBoxVal(name: String, state: Boolean = false) : AnimeFilter.CheckBox(name, state)
private inline fun <reified R> AnimeFilterList.parseCheckbox(
options: Array<Pair<String, String>>,
): List<String> {
return (getFirst<R>() as CheckBoxFilterList).state
.filter { it.state }
.map { checkbox -> options.find { it.first == checkbox.name }!!.second }
.filter(String::isNotBlank)
}
class YearFilter : AnimeFilter.Text("Erscheinungsjahr")
class LanguagesFilter : CheckBoxFilterList("Sprache", AnimeBaseFiltersData.LANGUAGES)
class GenresFilter : CheckBoxFilterList("Genre", AnimeBaseFiltersData.GENRES)
class ListFilter : QueryPartFilter("Liste der Konten", AnimeBaseFiltersData.LISTS)
class LetterFilter : QueryPartFilter("Schreiben", AnimeBaseFiltersData.LETTERS)
val FILTER_LIST get() = AnimeFilterList(
YearFilter(),
LanguagesFilter(),
GenresFilter(),
AnimeFilter.Separator(),
// >imagine using deepL
AnimeFilter.Header("Die untenstehenden Filter ignorieren die textsuche!"),
ListFilter(),
LetterFilter(),
)
data class FilterSearchParams(
val year: String = "",
val languages: List<String> = emptyList(),
val genres: List<String> = emptyList(),
val list: String = "",
val letter: String = "",
)
internal fun getSearchParameters(filters: AnimeFilterList): FilterSearchParams {
if (filters.isEmpty()) return FilterSearchParams()
return FilterSearchParams(
filters.getFirst<YearFilter>().state,
filters.parseCheckbox<LanguagesFilter>(AnimeBaseFiltersData.LANGUAGES),
filters.parseCheckbox<GenresFilter>(AnimeBaseFiltersData.GENRES),
filters.asQueryPart<ListFilter>(),
filters.asQueryPart<LetterFilter>(),
)
}
private object AnimeBaseFiltersData {
val LANGUAGES = arrayOf(
Pair("German Sub", "0"), // Literally Jmir
Pair("German Dub", "1"),
Pair("English Sub", "2"), // Average bri'ish
Pair("English Dub", "3"),
)
val GENRES = arrayOf(
Pair("Abenteuer", "1"),
Pair("Abenteuerkomödie", "261"),
Pair("Action", "2"),
Pair("Actiondrama", "3"),
Pair("Actionkomödie", "4"),
Pair("Adeliger", "258"),
Pair("Airing", "59"),
Pair("Alltagsdrama", "6"),
Pair("Alltagsleben", "7"),
Pair("Ältere Frau, jüngerer Mann", "210"),
Pair("Älterer Mann, jüngere Frau", "222"),
Pair("Alternative Welt", "53"),
Pair("Altes Asien", "187"),
Pair("Animation", "193"),
Pair("Anime & Film", "209"),
Pair("Anthologie", "260"),
Pair("Auftragsmörder / Attentäter", "265"),
Pair("Außerirdische", "204"),
Pair("Badminton", "259"),
Pair("Band", "121"),
Pair("Baseball", "234"),
Pair("Basketball", "239"),
Pair("Bionische Kräfte", "57"),
Pair("Boxen", "218"),
Pair("Boys Love", "226"),
Pair("Büroangestellter", "248"),
Pair("CG-Anime", "81"),
Pair("Charakterschwache Heldin", "102"),
Pair("Charakterschwacher Held", "101"),
Pair("Charakterstarke Heldin", "100"),
Pair("Charakterstarker Held", "88"),
Pair("Cyberpunk", "60"),
Pair("Cyborg", "109"),
Pair("Dämon", "58"),
Pair("Delinquent", "114"),
Pair("Denk- und Glücksspiele", "227"),
Pair("Detektiv", "91"),
Pair("Dialogwitz", "93"),
Pair("Dieb", "245"),
Pair("Diva", "112"),
Pair("Donghua", "257"),
Pair("Drache", "263"),
Pair("Drama", "8"),
Pair("Dunkle Fantasy", "90"),
Pair("Ecchi", "9"),
Pair("Elf", "89"),
Pair("Endzeit", "61"),
Pair("Epische Fantasy", "95"),
Pair("Episodisch", "92"),
Pair("Erotik", "186"),
Pair("Erwachsen", "70"),
Pair("Erwachsenwerden", "125"),
Pair("Essenszubereitung", "206"),
Pair("Familie", "63"),
Pair("Fantasy", "11"),
Pair("Fee", "264"),
Pair("Fighting-Shounen", "12"),
Pair("Football", "241"),
Pair("Frühe Neuzeit", "113"),
Pair("Fußball", "220"),
Pair("Gaming Kartenspiele", "250"),
Pair("Ganbatte", "13"),
Pair("Gedächtnisverlust", "115"),
Pair("Gegenwart", "46"),
Pair("Geist", "75"),
Pair("Geistergeschichten", "14"),
Pair("Gender Bender", "216"),
Pair("Genie", "116"),
Pair("Girls Love", "201"),
Pair("Grundschule", "103"),
Pair("Harem", "15"),
Pair("Hentai", "16"),
Pair("Hexe", "97"),
Pair("Himmlische Wesen", "105"),
Pair("Historisch", "49"),
Pair("Horror", "17"),
Pair("Host-Club", "247"),
Pair("Idol", "122"),
Pair("In einem Raumschiff", "208"),
Pair("Independent Anime", "251"),
Pair("Industrialisierung", "230"),
Pair("Isekai", "120"),
Pair("Kami", "98"),
Pair("Kampfkunst", "246"),
Pair("Kampfsport", "79"),
Pair("Kemonomimi", "106"),
Pair("Kinder", "41"),
Pair("Kindergarten", "243"),
Pair("Klubs", "189"),
Pair("Kodomo", "40"),
Pair("Komödie", "18"),
Pair("Kopfgeldjäger", "211"),
Pair("Krieg", "68"),
Pair("Krimi", "19"),
Pair("Liebesdrama", "20"),
Pair("Mafia", "127"),
Pair("Magical Girl", "21"),
Pair("Magie", "52"),
Pair("Maid", "244"),
Pair("Malerei", "231"),
Pair("Manga & Doujinshi", "217"),
Pair("Mannschaftssport", "262"),
Pair("Martial Arts", "64"),
Pair("Mecha", "22"),
Pair("Mediziner", "238"),
Pair("Mediziner", "254"),
Pair("Meiji-Ära", "242"),
Pair("Militär", "62"),
Pair("Mittelalter", "76"),
Pair("Mittelschule", "190"),
Pair("Moe", "43"),
Pair("Monster", "54"),
Pair("Musik", "69"),
Pair("Mystery", "23"),
Pair("Ninja", "55"),
Pair("Nonsense-Komödie", "24"),
Pair("Oberschule", "83"),
Pair("Otaku", "215"),
Pair("Parodie", "94"),
Pair("Pirat", "252"),
Pair("Polizist", "84"),
Pair("PSI-Kräfte", "78"),
Pair("Psychodrama", "25"),
Pair("Real Robots", "212"),
Pair("Rennsport", "207"),
Pair("Ritter", "50"),
Pair("Roboter ", "73"),
Pair("Roboter & Android", "110"),
Pair("Romantische Komödie", "26"),
Pair("Romanze", "27"),
Pair("Samurai", "47"),
Pair("Satire", "232"),
Pair("Schule", "119"),
Pair("Schusswaffen", "82"),
Pair("Schwerter & Co", "51"),
Pair("Schwimmen", "223"),
Pair("Scifi", "28"),
Pair("Seinen", "39"),
Pair("Sentimentales Drama", "29"),
Pair("Shounen", "37"),
Pair("Slapstick", "56"),
Pair("Slice of Life", "5"),
Pair("Solosänger", "219"),
Pair("Space Opera", "253"),
Pair("Splatter", "36"),
Pair("Sport", "30"),
Pair("Stoische Heldin", "123"),
Pair("Stoischer Held", "85"),
Pair("Super Robots", "203"),
Pair("Super-Power", "71"),
Pair("Superhelden", "256"),
Pair("Supernatural", "225"),
Pair("Tanzen", "249"),
Pair("Tennis", "233"),
Pair("Theater", "224"),
Pair("Thriller", "31"),
Pair("Tiermensch", "111"),
Pair("Tomboy", "104"),
Pair("Tragödie", "86"),
Pair("Tsundere", "107"),
Pair("Überlebenskampf", "117"),
Pair("Übermäßige Gewaltdarstellung", "34"),
Pair("Unbestimmt", "205"),
Pair("Universität", "214"),
Pair("Vampir", "35"),
Pair("Verworrene Handlung", "126"),
Pair("Virtuelle Welt", "108"),
Pair("Volleyball", "191"),
Pair("Volljährig", "67"),
Pair("Wassersport", "266"),
Pair("Weiblich", "45"),
Pair("Weltkriege", "128"),
Pair("Weltraum", "74"),
Pair("Widerwillige Heldin", "124"),
Pair("Widerwilliger Held", "188"),
Pair("Yandere", "213"),
Pair("Yaoi", "32"),
Pair("Youkai", "99"),
Pair("Yuri", "33"),
Pair("Zeichentrick", "77"),
Pair("Zeichentrick", "255"),
Pair("Zeitgenössische Fantasy", "80"),
Pair("Zeitsprung", "240"),
Pair("Zombie", "87"),
)
val LISTS = arrayOf(
Pair("Keine", ""),
Pair("Anime", "animelist"),
Pair("Film", "filmlist"),
Pair("Hentai", "hentailist"),
Pair("Sonstiges", "misclist"),
)
val LETTERS = arrayOf(Pair("Jede", "")) + ('A'..'Z').map {
Pair(it.toString(), "/$it")
}.toTypedArray()
}
}

View file

@ -0,0 +1,31 @@
package eu.kanade.tachiyomi.animeextension.de.animebase.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()
.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" },
)
}
}

View file

@ -0,0 +1,124 @@
package eu.kanade.tachiyomi.animeextension.de.animebase.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().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()
.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()
}
}

View file

@ -0,0 +1,14 @@
ext {
extName = 'Anime-Loads'
extClass = '.AnimeLoads'
extVersionCode = 15
isNsfw = true
}
apply from: "$rootDir/common.gradle"
dependencies {
implementation(project(':lib:voe-extractor'))
implementation(project(':lib:streamtape-extractor'))
implementation(project(':lib:dood-extractor'))
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 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: 26 KiB

View file

@ -0,0 +1,813 @@
package eu.kanade.tachiyomi.animeextension.de.animeloads
import android.app.Application
import android.content.SharedPreferences
import android.util.Log
import androidx.preference.ListPreference
import androidx.preference.MultiSelectListPreference
import androidx.preference.PreferenceScreen
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.doodextractor.DoodExtractor
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
import eu.kanade.tachiyomi.lib.voeextractor.VoeExtractor
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Headers
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import org.jsoup.select.Elements
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.net.URLDecoder
import kotlin.Exception
class AnimeLoads : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override val name = "Anime-Loads"
override val baseUrl = "https://www.anime-loads.org"
override val lang = "de"
override val supportsLatest = false
override val id: Long = 655155856096L
override val client = network.client.newBuilder()
.addInterceptor(DdosGuardInterceptor(network.client))
.build()
private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
override fun popularAnimeSelector(): String = "div.row div.col-sm-6 div.panel-body"
override fun popularAnimeRequest(page: Int) = GET("$baseUrl/anime-series/page/$page")
override fun popularAnimeFromElement(element: Element): SAnime {
val anime = SAnime.create()
anime.setUrlWithoutDomain(element.select("div.row a.cover-img").attr("href"))
anime.thumbnail_url = element.select("div.row a.cover-img img").attr("src")
anime.title = element.select("div.row h4.title-list a").text()
return anime
}
override fun popularAnimeNextPageSelector(): String = "i.glyphicon-forward"
// 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()
val series = document.select("a[title=\"Anime Serien\"]")
if (series.attr("title").contains("Anime Serien")) {
val eplist = document.select("#streams_episodes_1 div.list-group")
val url = document.select("meta[property=\"og:url\"]").attr("content")
val ep = parseEpisodesFromSeries(eplist, url)
episodeList.addAll(ep)
} else {
episode.name = document.select("div.page-header > h1").attr("title")
episode.episode_number = 1F
episode.setUrlWithoutDomain(document.select("meta[property=\"og:url\"]").attr("content"))
episodeList.add(episode)
}
return episodeList.reversed()
}
private fun parseEpisodesFromSeries(element: Elements, url: String): List<SEpisode> {
val episodeElement = element.select("a.list-group-item")
return episodeElement.map { episodeFromElement(it, url) }
}
override fun episodeFromElement(element: Element): SEpisode = throw UnsupportedOperationException()
private fun episodeFromElement(element: Element, url: String): SEpisode {
val episode = SEpisode.create()
val id = element.attr("aria-controls")
episode.setUrlWithoutDomain("$url#$id")
episode.name = "Ep." + element.select("span:nth-child(1)").text()
episode.episode_number = element.select("span strong").text().toFloat()
return episode
}
// Video Extractor
override fun videoListParse(response: Response): List<Video> {
val document = response.asJsoup()
val url = response.request.url.toString()
val idep = url
.substringAfter("#")
return videosFromElement(document, idep, url)
}
private fun videosFromElement(document: Document, idep: String, url: String): List<Video> {
val videoList = mutableListOf<Video>()
val hosterSelection = preferences.getStringSet("hoster_selection", setOf("dood", "voe", "stape"))
val subSelection = preferences.getStringSet("sub_selection", setOf("sub", "dub"))
val lang = document.select("div#streams ul.nav li[role=\"presentation\"]")
lang.forEach { langit ->
Log.i("videosFromElement", "Langit: $langit")
when {
langit.select("a i.flag-de").attr("title").contains("Subtitles: German") || langit.select("a i.flag-de").attr("title").contains("Untertitel: Deutsch") && subSelection?.contains("sub") == true -> {
val aria = langit.select("a").attr("aria-controls")
val id = document.select("#$aria div.episodes").attr("id")
val epnum = idep.substringAfter("streams_episodes_1")
val element = document.select("div#$id$epnum")
val enc = element.attr("data-enc")
val capfiles = client.newCall(
POST(
"$baseUrl/files/captcha",
body = "cID=0&rT=1".toRequestBody("application/x-www-form-urlencoded".toMediaType()),
headers = Headers.headersOf(
"X-Requested-With",
"XMLHttpRequest",
"Referer",
url.replace("#$id$epnum", ""),
"Accept",
"application/json, text/javascript, */*; q=0.01",
"cache-control",
"max-age=15",
),
),
).execute().asJsoup()
val hashes = capfiles.toString().substringAfter("[").substringBefore("]").split(",")
val hashlist = mutableListOf<String>()
val pnglist = mutableListOf<String>()
var max = "1"
var min = "99999"
hashes.forEach {
val hash = it.replace("<body>", "")
.replace("[", "")
.replace("\"", "").replace("]", "")
.replace("</body>", "").replace("%20", "")
val png = client.newCall(
GET(
"$baseUrl/files/captcha?cid=0&hash=$hash",
headers = Headers.headersOf(
"Referer",
url.replace("#$id$epnum", ""),
"Accept",
"image/avif,image/webp,*/*",
"cache-control",
"max-age=15",
),
),
).execute().body.byteString()
val size = png.toString()
.substringAfter("[size=").substringBefore(" hex")
pnglist.add("$size | $hash")
hashlist.add(size)
for (num in hashlist) {
if (max < num) {
max = num
}
}
for (num in hashlist) {
if (min > num) {
min = num
}
}
}
var int = 0
pnglist.forEach { diffit ->
if (int == 0) {
if (diffit.substringBefore(" |").toInt() != max.toInt() && diffit.substringBefore(" |").toInt() != min.toInt()) {
int = 1
val hash = diffit.substringBefore(" |").toInt()
val diffmax = max.toInt() - hash
val diffmin = hash - min.toInt()
if (diffmax > diffmin) {
pnglist.forEach { itmax ->
if (max.toInt() == itmax.substringBefore(" |").toInt()) {
val maxhash = itmax.substringAfter("| ")
network.client.newCall(
POST(
"$baseUrl/files/captcha",
body = "cID=0&pC=$maxhash&rT=2".toRequestBody("application/x-www-form-urlencoded".toMediaType()),
headers = Headers.headersOf(
"Origin", baseUrl, "X-Requested-With", "XMLHttpRequest", "Referer", url.replace("#$id$epnum", ""), "Accept", "*/*", "cache-control", "max-age=15",
),
),
).execute()
val maxdoc = client.newCall(
POST(
"$baseUrl/ajax/captcha",
body = "enc=${enc.replace("=", "%3D")}&response=captcha&captcha-idhf=0&captcha-hf=$maxhash".toRequestBody("application/x-www-form-urlencoded".toMediaType()),
headers = Headers.headersOf(
"Origin", baseUrl, "X-Requested-With", "XMLHttpRequest", "Referer", url.replace("#$id$epnum", ""),
"Accept", "application/json, text/javascript, */*; q=0.01", "cache-control", "max-age=15",
),
),
).execute().asJsoup().toString()
if (maxdoc.substringAfter("\"code\":\"").substringBefore("\",").contains("error")) {
throw Exception("Captcha bypass failed! Clear Cookies & Webview data. Or wait some time.")
} else {
val links = maxdoc.substringAfter("\"content\":").substringBefore("</body>").split("{\"links\":")
links.forEach {
if (it.contains("link")) {
val hoster = it.substringAfter("\"hoster\":\"").substringBefore("\",\"")
val linkpart = it.substringAfter("\"link\":\"").substringBefore("\"}]")
val leaveurl = client.newCall(GET("$baseUrl/leave/$linkpart")).execute().request.url.toString()
val decode = "https://www." + URLDecoder.decode(leaveurl.substringAfter("www."), "utf-8")
if (decode.contains(baseUrl)) {
val link = client.newCall(GET(decode)).execute().request.url.toString()
when {
hoster.contains("voesx") && hosterSelection?.contains("voe") == true -> {
videoList.addAll(VoeExtractor(client).videosFromUrl(link, "(Deutsch Sub) "))
}
hoster.contains("streamtapecom") && hosterSelection?.contains("stape") == true -> {
val quality = "Streamtape Deutsch Sub"
val video = try {
StreamTapeExtractor(client).videoFromUrl(link, quality)
} catch (e: Exception) {
null
}
if (video != null) {
videoList.add(video)
}
}
hoster.contains("doodstream") && hosterSelection?.contains("dood") == true -> {
val quality = "Doodstreams Deutsch Sub"
val video = try {
DoodExtractor(client).videoFromUrl(link, quality)
} catch (e: Exception) {
null
}
if (video != null) {
videoList.add(video)
}
}
}
} else {
when {
hoster.contains("voesx") && hosterSelection?.contains("voe") == true -> {
videoList.addAll(VoeExtractor(client).videosFromUrl(leaveurl, "(Deutsch Sub) "))
}
hoster.contains("streamtapecom") && hosterSelection?.contains("stape") == true -> {
val quality = "Streamtape Deutsch Sub"
val video = try {
StreamTapeExtractor(client).videoFromUrl(leaveurl, quality)
} catch (e: Exception) {
null
}
if (video != null) {
videoList.add(video)
}
}
hoster.contains("doodstream") && hosterSelection?.contains("dood") == true -> {
val quality = "Doodstreams Deutsch Sub"
val video = try {
DoodExtractor(client).videoFromUrl(leaveurl, quality)
} catch (e: Exception) {
null
}
if (video != null) {
videoList.add(video)
}
}
}
}
}
}
}
}
}
} else {
pnglist.forEach { itmin ->
if (min.toInt() == itmin.substringBefore(" |").toInt()) {
val minhash = itmin.substringAfter("| ")
network.client.newCall(
POST(
"$baseUrl/files/captcha",
body = "cID=0&pC=$minhash&rT=2".toRequestBody("application/x-www-form-urlencoded".toMediaType()),
headers = Headers.headersOf(
"Origin",
baseUrl,
"X-Requested-With",
"XMLHttpRequest",
"Referer",
url.replace("#$id$epnum", ""),
"Accept",
"*/*",
),
),
).execute()
val mindoc = client.newCall(
POST(
"$baseUrl/ajax/captcha",
body = "enc=${enc.replace("=", "%3D")}&response=captcha&captcha-idhf=0&captcha-hf=$minhash".toRequestBody("application/x-www-form-urlencoded".toMediaType()),
headers = Headers.headersOf(
"Origin",
baseUrl,
"X-Requested-With",
"XMLHttpRequest",
"Referer",
url.replace("#$id$epnum", ""),
"Accept",
"application/json, text/javascript, */*; q=0.01",
),
),
).execute().asJsoup().toString()
if (mindoc.substringAfter("\"code\":\"").substringBefore("\",").contains("error")) {
throw Exception("Captcha bypass failed! Clear Cookies & Webview data. Or wait some time.")
} else {
val links = mindoc.substringAfter("\"content\":[").substringBefore("</body>").split("{\"links\":")
links.forEach {
if (it.contains("link")) {
val hoster = it.substringAfter("\"hoster\":\"").substringBefore("\",\"")
val linkpart = it.substringAfter("\"link\":\"").substringBefore("\"}]")
val leaveurl = client.newCall(GET("$baseUrl/leave/$linkpart")).execute().request.url.toString()
val decode = "https://www." + URLDecoder.decode(leaveurl.substringAfter("www."), "utf-8")
if (decode.contains(baseUrl)) {
val link = client.newCall(GET(decode)).execute().request.url.toString()
when {
hoster.contains("voesx") && hosterSelection?.contains("voe") == true -> {
videoList.addAll(VoeExtractor(client).videosFromUrl(link, "(Deutsch Sub) "))
}
hoster.contains("streamtapecom") && hosterSelection?.contains("stape") == true -> {
val quality = "Streamtape Deutsch Sub"
val video = try {
StreamTapeExtractor(client).videoFromUrl(link, quality)
} catch (e: Exception) {
null
}
if (video != null) {
videoList.add(video)
}
}
hoster.contains("doodstream") && hosterSelection?.contains("dood") == true -> {
val quality = "Doodstreams Deutsch Sub"
val video = try {
DoodExtractor(client).videoFromUrl(link, quality)
} catch (e: Exception) {
null
}
if (video != null) {
videoList.add(video)
}
}
}
} else {
when {
hoster.contains("voesx") && hosterSelection?.contains("voe") == true -> {
videoList.addAll(VoeExtractor(client).videosFromUrl(leaveurl, "(Deutsch Sub) "))
}
hoster.contains("streamtapecom") && hosterSelection?.contains("stape") == true -> {
val quality = "Streamtape Deutsch Sub"
val video = try {
StreamTapeExtractor(client).videoFromUrl(leaveurl, quality)
} catch (e: Exception) {
null
}
if (video != null) {
videoList.add(video)
}
}
hoster.contains("doodstream") && hosterSelection?.contains("dood") == true -> {
val quality = "Doodstreams Deutsch Sub"
val video = try {
DoodExtractor(client).videoFromUrl(leaveurl, quality)
} catch (e: Exception) {
null
}
if (video != null) {
videoList.add(video)
}
}
}
}
}
}
}
}
}
}
}
}
}
}
langit.select("a i.flag-de").attr("title").contains("Language: German") || langit.select("a i.flag-de").attr("title").contains("Sprache: Deutsch") && subSelection?.contains("dub") == true -> {
val aria = langit.select("a").attr("aria-controls")
val id = document.select("#$aria div.episodes").attr("id")
val epnum = idep.substringAfter("streams_episodes_1")
val element = document.select("div#$id$epnum")
val enc = element.attr("data-enc")
val capfiles = client.newCall(
POST(
"$baseUrl/files/captcha",
body = "cID=0&rT=1".toRequestBody("application/x-www-form-urlencoded".toMediaType()),
headers = Headers.headersOf(
"X-Requested-With",
"XMLHttpRequest",
"Referer",
url.replace("#$id$epnum", ""),
"Accept",
"application/json, text/javascript, */*; q=0.01",
"cache-control",
"max-age=15",
),
),
).execute().asJsoup()
val hashes = capfiles.toString().substringAfter("[").substringBefore("]").split(",")
val hashlist = mutableListOf<String>()
val pnglist = mutableListOf<String>()
var max = "1"
var min = "99999"
hashes.forEach {
val hash = it.replace("<body>", "")
.replace("[", "")
.replace("\"", "").replace("]", "")
.replace("</body>", "").replace("%20", "")
val png = client.newCall(
GET(
"$baseUrl/files/captcha?cid=0&hash=$hash",
headers = Headers.headersOf(
"Referer",
url.replace("#$id$epnum", ""),
"Accept",
"image/avif,image/webp,*/*",
"cache-control",
"max-age=15",
),
),
).execute().body.byteString()
val size = png.toString()
.substringAfter("[size=").substringBefore(" hex")
pnglist.add("$size | $hash")
hashlist.add(size)
for (num in hashlist) {
if (max < num) {
max = num
}
}
for (num in hashlist) {
if (min > num) {
min = num
}
}
}
var int = 0
pnglist.forEach { diffit ->
if (int == 0) {
if (diffit.substringBefore(" |").toInt() != max.toInt() && diffit.substringBefore(" |").toInt() != min.toInt()) {
int = 1
val hash = diffit.substringBefore(" |").toInt()
val diffmax = max.toInt() - hash
val diffmin = hash - min.toInt()
if (diffmax > diffmin) {
pnglist.forEach { itmax ->
if (max.toInt() == itmax.substringBefore(" |").toInt()) {
val maxhash = itmax.substringAfter("| ")
network.client.newCall(
POST(
"$baseUrl/files/captcha",
body = "cID=0&pC=$maxhash&rT=2".toRequestBody("application/x-www-form-urlencoded".toMediaType()),
headers = Headers.headersOf(
"Origin", baseUrl, "X-Requested-With", "XMLHttpRequest", "Referer", url.replace("#$id$epnum", ""), "Accept", "*/*", "cache-control", "max-age=15",
),
),
).execute()
val maxdoc = client.newCall(
POST(
"$baseUrl/ajax/captcha",
body = "enc=${enc.replace("=", "%3D")}&response=captcha&captcha-idhf=0&captcha-hf=$maxhash".toRequestBody("application/x-www-form-urlencoded".toMediaType()),
headers = Headers.headersOf(
"Origin", baseUrl, "X-Requested-With", "XMLHttpRequest", "Referer", url.replace("#$id$epnum", ""),
"Accept", "application/json, text/javascript, */*; q=0.01", "cache-control", "max-age=15",
),
),
).execute().asJsoup().toString()
if (maxdoc.substringAfter("\"code\":\"").substringBefore("\",").contains("error")) {
throw Exception("Captcha bypass failed! Clear Cookies & Webview data. Or wait some time.")
} else {
val links = maxdoc.substringAfter("\"content\":").substringBefore("</body>").split("{\"links\":")
links.forEach {
if (it.contains("link")) {
val hoster = it.substringAfter("\"hoster\":\"").substringBefore("\",\"")
val linkpart = it.substringAfter("\"link\":\"").substringBefore("\"}]")
val leaveurl = client.newCall(GET("$baseUrl/leave/$linkpart")).execute().request.url.toString()
val decode = "https://www." + URLDecoder.decode(leaveurl.substringAfter("www."), "utf-8")
if (decode.contains(baseUrl)) {
val link = client.newCall(GET(decode)).execute().request.url.toString()
when {
hoster.contains("voesx") && hosterSelection?.contains("voe") == true -> {
videoList.addAll(VoeExtractor(client).videosFromUrl(link, "(Deutsch Dub) "))
}
hoster.contains("streamtapecom") && hosterSelection?.contains("stape") == true -> {
val quality = "Streamtape Deutsch Dub"
val video = try {
StreamTapeExtractor(client).videoFromUrl(link, quality)
} catch (e: Exception) {
null
}
if (video != null) {
videoList.add(video)
}
}
hoster.contains("doodstream") && hosterSelection?.contains("dood") == true -> {
val quality = "Doodstream Deutsch Dub"
val video = try {
DoodExtractor(client).videoFromUrl(link, quality)
} catch (e: Exception) {
null
}
if (video != null) {
videoList.add(video)
}
}
}
} else {
when {
hoster.contains("voesx") && hosterSelection?.contains("voe") == true -> {
videoList.addAll(VoeExtractor(client).videosFromUrl(leaveurl, "(Deutsch Dub) "))
}
hoster.contains("streamtapecom") && hosterSelection?.contains("stape") == true -> {
val quality = "Streamtape Deutsch Dub"
val video = try {
StreamTapeExtractor(client).videoFromUrl(leaveurl, quality)
} catch (e: Exception) {
null
}
if (video != null) {
videoList.add(video)
}
}
hoster.contains("doodstream") && hosterSelection?.contains("dood") == true -> {
val quality = "Doodstream Deutsch Dub"
val video = try {
DoodExtractor(client).videoFromUrl(leaveurl, quality)
} catch (e: Exception) {
null
}
if (video != null) {
videoList.add(video)
}
}
}
}
}
}
}
}
}
} else {
pnglist.forEach { itmin ->
if (min.toInt() == itmin.substringBefore(" |").toInt()) {
val minhash = itmin.substringAfter("| ")
network.client.newCall(
POST(
"$baseUrl/files/captcha",
body = "cID=0&pC=$minhash&rT=2".toRequestBody("application/x-www-form-urlencoded".toMediaType()),
headers = Headers.headersOf(
"Origin",
baseUrl,
"X-Requested-With",
"XMLHttpRequest",
"Referer",
url.replace("#$id$epnum", ""),
"Accept",
"*/*",
),
),
).execute()
val mindoc = client.newCall(
POST(
"$baseUrl/ajax/captcha",
body = "enc=${enc.replace("=", "%3D")}&response=captcha&captcha-idhf=0&captcha-hf=$minhash".toRequestBody("application/x-www-form-urlencoded".toMediaType()),
headers = Headers.headersOf(
"Origin",
baseUrl,
"X-Requested-With",
"XMLHttpRequest",
"Referer",
url.replace("#$id$epnum", ""),
"Accept",
"application/json, text/javascript, */*; q=0.01",
),
),
).execute().asJsoup().toString()
if (mindoc.substringAfter("\"code\":\"").substringBefore("\",").contains("error")) {
throw Exception("Captcha bypass failed! Clear Cookies & Webview data. Or wait some time.")
} else {
val links = mindoc.substringAfter("\"content\":[").substringBefore("</body>").split("{\"links\":")
links.forEach {
if (it.contains("link")) {
val hoster = it.substringAfter("\"hoster\":\"").substringBefore("\",\"")
val linkpart = it.substringAfter("\"link\":\"").substringBefore("\"}]")
val leaveurl = client.newCall(GET("$baseUrl/leave/$linkpart")).execute().request.url.toString()
val decode = "https://www." + URLDecoder.decode(leaveurl.substringAfter("www."), "utf-8")
if (decode.contains(baseUrl)) {
val link = client.newCall(GET(decode)).execute().request.url.toString()
when {
hoster.contains("voesx") && hosterSelection?.contains("voe") == true -> {
videoList.addAll(VoeExtractor(client).videosFromUrl(link, "(Deutsch Dub) "))
}
hoster.contains("streamtapecom") && hosterSelection?.contains("stape") == true -> {
val quality = "Streamtape Deutsch Dub"
val video = try {
StreamTapeExtractor(client).videoFromUrl(link, quality)
} catch (e: Exception) {
null
}
if (video != null) {
videoList.add(video)
}
}
hoster.contains("doodstream") && hosterSelection?.contains("dood") == true -> {
val quality = "Doodstream Deutsch Dub"
val video = try {
DoodExtractor(client).videoFromUrl(link, quality)
} catch (e: Exception) {
null
}
if (video != null) {
videoList.add(video)
}
}
}
} else {
when {
hoster.contains("voesx") && hosterSelection?.contains("voe") == true -> {
videoList.addAll(VoeExtractor(client).videosFromUrl(leaveurl, "(Deutsch Dub) "))
}
hoster.contains("streamtapecom") && hosterSelection?.contains("stape") == true -> {
val quality = "Streamtape Deutsch Dub"
val video = try {
StreamTapeExtractor(client).videoFromUrl(leaveurl, quality)
} catch (e: Exception) {
null
}
if (video != null) {
videoList.add(video)
}
}
hoster.contains("doodstream") && hosterSelection?.contains("dood") == true -> {
val quality = "Doodstream Deutsch Dub"
val video = try {
DoodExtractor(client).videoFromUrl(leaveurl, quality)
} catch (e: Exception) {
null
}
if (video != null) {
videoList.add(video)
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
return videoList.reversed()
}
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.quality.contains(hoster)) {
newList.add(preferred, video)
preferred++
} else {
newList.add(video)
}
}
return newList
}
return this
}
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.select("div.row a.cover-img").attr("href"))
anime.thumbnail_url = element.select("div.row a.cover-img img").attr("src")
anime.title = element.select("div.row h4.title-list a").text()
return anime
}
override fun searchAnimeNextPageSelector(): String = "i.glyphicon-forward"
override fun searchAnimeSelector(): String = "div.row div.col-sm-6 div.panel-body"
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request = GET("$baseUrl/search/page/$page?q=$query")
// Details
override fun animeDetailsParse(document: Document): SAnime {
val anime = SAnime.create()
anime.thumbnail_url = document.select("#description img.img-responsive").attr("src")
anime.title = document.select("div.page-header > h1").attr("title")
anime.genre = document.select("#description div.label-group a.label.label-info").joinToString(", ") { it.text() }
anime.description = document.select("div.pt20").not("strong").text()
anime.author = document.select("div.col-md-6.text-left p:nth-child(3) a").joinToString(", ") { it.text() }
anime.status = SAnime.COMPLETED
return anime
}
// Latest
override fun latestUpdatesNextPageSelector(): String = throw UnsupportedOperationException()
override fun latestUpdatesFromElement(element: Element): SAnime = throw UnsupportedOperationException()
override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException()
override fun latestUpdatesSelector(): String = throw UnsupportedOperationException()
// Preferences
override fun setupPreferenceScreen(screen: PreferenceScreen) {
val hosterPref = ListPreference(screen.context).apply {
key = "preferred_hoster"
title = "Standard-Hoster"
entries = arrayOf("Doodstream", "Voe", "MIXdrop")
entryValues = arrayOf("https://dood", "https://voe.sx", "https://streamtape.com")
setDefaultValue("https://voe.sx")
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 hostSelection = MultiSelectListPreference(screen.context).apply {
key = "hoster_selection"
title = "Hoster auswählen"
entries = arrayOf("Doodstream", "Voe", "Streamtape")
entryValues = arrayOf("dood", "voe", "stape")
setDefaultValue(setOf("dood", "voe", "stape"))
setOnPreferenceChangeListener { _, newValue ->
preferences.edit().putStringSet(key, newValue as Set<String>).commit()
}
}
val subSelection = MultiSelectListPreference(screen.context).apply {
key = "sub_selection"
title = "Sprache auswählen"
entries = arrayOf("SUB", "DUB")
entryValues = arrayOf("sub", "dub")
setDefaultValue(setOf("sub"))
setOnPreferenceChangeListener { _, newValue ->
preferences.edit().putStringSet(key, newValue as Set<String>).commit()
}
}
screen.addPreference(hosterPref)
screen.addPreference(hostSelection)
screen.addPreference(subSelection)
}
}

View file

@ -0,0 +1,74 @@
package eu.kanade.tachiyomi.animeextension.de.animeloads
import android.util.Log
import android.webkit.CookieManager
import eu.kanade.tachiyomi.network.GET
import okhttp3.Cookie
import okhttp3.HttpUrl
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Response
class DdosGuardInterceptor(private val client: OkHttpClient) : Interceptor {
private val cookieManager by lazy { CookieManager.getInstance() }
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
val response = chain.proceed(originalRequest)
// Check if DDos-GUARD is on
if (response.code !in ERROR_CODES || response.header("Server") !in SERVER_CHECK) {
return response
}
response.close()
val cookies = cookieManager.getCookie(originalRequest.url.toString())
val oldCookie = if (cookies != null && cookies.isNotEmpty()) {
cookies.split(";").mapNotNull { Cookie.parse(originalRequest.url, it) }
} else {
emptyList()
}
Log.i("newCookie", "OldCookies: $oldCookie")
val ddg2Cookie = oldCookie.firstOrNull { it.name == "__ddg2_" }
if (!ddg2Cookie?.value.isNullOrEmpty()) {
return chain.proceed(originalRequest)
}
val newCookie = getNewCookie(originalRequest.url) ?: return chain.proceed(originalRequest)
val newCookieHeader = buildString {
(oldCookie + newCookie).forEachIndexed { index, cookie ->
if (index > 0) append("; ")
append(cookie.name).append('=').append(cookie.value)
}
}
return chain.proceed(originalRequest.newBuilder().addHeader("cookie", newCookieHeader).build())
}
fun getNewCookie(url: HttpUrl): Cookie? {
val cookies = cookieManager.getCookie(url.toString())
val oldCookie = if (cookies != null && cookies.isNotEmpty()) {
cookies.split(";").mapNotNull { Cookie.parse(url, it) }
} else {
emptyList()
}
val ddg2Cookie = oldCookie.firstOrNull { it.name == "__ddg2_" }
if (!ddg2Cookie?.value.isNullOrEmpty()) {
return ddg2Cookie
}
val wellKnown = client.newCall(GET("https://check.ddos-guard.net/check.js"))
.execute().body.string()
.substringAfter("'", "")
.substringBefore("'", "")
val checkUrl = "${url.scheme}://${url.host + wellKnown}"
return client.newCall(GET(checkUrl)).execute().header("set-cookie")?.let {
Cookie.parse(url, it)
}
}
companion object {
private val ERROR_CODES = listOf(403)
private val SERVER_CHECK = listOf("ddos-guard")
}
}

View file

@ -0,0 +1,7 @@
ext {
extName = 'Anime-Stream'
extClass = '.AnimeStream'
extVersionCode = 2
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

View file

@ -0,0 +1,138 @@
package eu.kanade.tachiyomi.animeextension.de.animestream
import eu.kanade.tachiyomi.animeextension.de.animestream.extractors.MetaExtractor
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.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import uy.kohesive.injekt.api.get
class AnimeStream : ParsedAnimeHttpSource() {
override val name = "Anime-Stream"
override val baseUrl = "https://anime-stream.to"
override val lang = "de"
override val id: Long = 314593699490737069
override val supportsLatest = false
override fun popularAnimeSelector(): String = "div.movies-list div.ml-item"
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/series/page/$page/")
override fun popularAnimeFromElement(element: Element): SAnime {
val anime = SAnime.create()
anime.setUrlWithoutDomain(element.select("a").attr("href"))
anime.thumbnail_url = element.select("a img").attr("data-original")
anime.title = element.select("a img").attr("alt")
return anime
}
override fun popularAnimeNextPageSelector(): String = "li.active ~ li"
// episodes
override fun episodeListSelector() = throw UnsupportedOperationException()
override fun episodeListParse(response: Response): List<SEpisode> {
val document = response.asJsoup()
val episodeList = mutableListOf<SEpisode>()
val episodeElement = document.select("div.les-content a")
episodeElement.forEach {
var num = 0
val episode = nEpisodeFromElement(it, num)
episodeList.add(episode)
}
return episodeList.reversed()
}
private fun nEpisodeFromElement(element: Element, num: Int): SEpisode {
num + 1
val episode = SEpisode.create()
episode.episode_number = num.toFloat()
episode.name = element.text()
episode.setUrlWithoutDomain(element.attr("href"))
return episode
}
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 videoList = mutableListOf<Video>()
val url = document.select("div a.lnk-lnk").attr("href")
val quality = "Metastream"
val video = MetaExtractor(client).videoFromUrl(url, quality)
if (video != null) {
videoList.add(video)
}
return videoList.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.select("a").attr("href"))
anime.thumbnail_url = element.select("a img").attr("data-original")
anime.title = element.select("a img").attr("alt")
return anime
}
override fun searchAnimeNextPageSelector(): String = "li.active ~ li"
override fun searchAnimeSelector(): String = "div.movies-list div.ml-item"
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request = GET("$baseUrl/page/$page/?s=$query")
// Details
override fun animeDetailsParse(document: Document): SAnime {
val anime = SAnime.create()
anime.thumbnail_url = document.select("div.thumb img").attr("src")
anime.title = document.select("div.thumb img").attr("alt")
anime.description = document.select("div.desc p.f-desc").text()
anime.status = parseStatus(document.select("div.mvici-right span[itemprop=\"duration\"]").text())
anime.genre = document.select("div.mvici-left p a[rel=\"category tag\"]").joinToString(", ") { it.text() }
return anime
}
private fun parseStatus(status: String?) = when {
status == null -> SAnime.UNKNOWN
status.contains("Abgeschlossen", ignoreCase = true) -> SAnime.ONGOING
else -> SAnime.COMPLETED
}
// Latest
override fun latestUpdatesNextPageSelector(): String = throw UnsupportedOperationException()
override fun latestUpdatesFromElement(element: Element): SAnime = throw UnsupportedOperationException()
override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException()
override fun latestUpdatesSelector(): String = throw UnsupportedOperationException()
}

View file

@ -0,0 +1,17 @@
package eu.kanade.tachiyomi.animeextension.de.animestream.extractors
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.OkHttpClient
class MetaExtractor(private val client: OkHttpClient) {
fun videoFromUrl(url: String, quality: String): Video? {
val document = client.newCall(GET(url)).execute().asJsoup()
val script = document.select("script:containsData(sources: [{src:)")
.firstOrNull()?.data()?.substringAfter("sources: [{src: \"") ?: return null
val videoUrl = script.substringAfter("sources: [{src: \"").substringBefore("\", type:")
return Video(url, quality, videoUrl)
}
}

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View file

@ -0,0 +1,355 @@
package eu.kanade.tachiyomi.animeextension.de.animetoast
import android.app.Application
import android.content.SharedPreferences
import androidx.preference.ListPreference
import androidx.preference.MultiSelectListPreference
import androidx.preference.PreferenceScreen
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.doodextractor.DoodExtractor
import eu.kanade.tachiyomi.lib.filemoonextractor.FilemoonExtractor
import eu.kanade.tachiyomi.lib.mp4uploadextractor.Mp4uploadExtractor
import eu.kanade.tachiyomi.lib.voeextractor.VoeExtractor
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
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
import kotlin.Exception
class AnimeToast : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override val name = "AnimeToast"
override val baseUrl = "https://www.animetoast.cc"
override val lang = "de"
override val supportsLatest = false
private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
override fun popularAnimeSelector(): String = "div.row div.col-md-4 div.video-item"
override fun popularAnimeRequest(page: Int): Request = GET(baseUrl)
override fun popularAnimeFromElement(element: Element): SAnime {
val anime = SAnime.create()
anime.setUrlWithoutDomain(element.select("div.item-thumbnail a").attr("href"))
anime.thumbnail_url = element.select("div.item-thumbnail a img").attr("src")
anime.title = element.select("div.item-thumbnail a").attr("title")
return anime
}
override fun popularAnimeNextPageSelector(): String? = null
// episodes
override fun episodeListSelector() = throw UnsupportedOperationException()
override fun episodeListParse(response: Response): List<SEpisode> {
val document = response.asJsoup()
val episodeList = mutableListOf<SEpisode>()
val file = document.select("a[rel=\"category tag\"]").text()
if (file.contains("Serie")) {
if (document.select("#multi_link_tab0").attr("id").isNotEmpty()) {
val elements = document.select("#multi_link_tab0")
elements.forEach {
val episode = parseEpisodesFromSeries(it)
episodeList.addAll(episode)
}
} else {
val elements = document.select("#multi_link_tab1")
elements.forEach {
val episode = parseEpisodesFromSeries(it)
episodeList.addAll(episode)
}
}
} else {
val episode = SEpisode.create()
episode.setUrlWithoutDomain(document.select("link[rel=canonical]").attr("href"))
episode.name = document.select("h1.light-title").text()
episode.episode_number = 1F
episodeList.add(episode)
}
return episodeList.reversed()
}
private fun parseEpisodesFromSeries(element: Element): List<SEpisode> {
val episodeElements = element.select("div.tab-pane a")
val epT = episodeElements.text()
if (epT.contains(":") || epT.contains("-")) {
val url = episodeElements.attr("href")
val document = client.newCall(GET(url)).execute().asJsoup()
val nUrl = document.select("#player-embed a").attr("href")
val nDoc = client.newCall(GET(nUrl)).execute().asJsoup()
val nEpEl = nDoc.select("div.tab-pane a")
return nEpEl.map { episodeFromElement(it) }
} else {
return episodeElements.map { episodeFromElement(it) }
}
}
override fun episodeFromElement(element: Element): SEpisode {
val episode = SEpisode.create()
episode.episode_number = try {
element.text().replace("Ep. ", "").toFloat()
} catch (e: Exception) {
100.0f
}
episode.name = element.text()
episode.setUrlWithoutDomain(element.attr("href"))
return episode
}
// Video Extractor
override fun videoListParse(response: Response): List<Video> {
val document = response.asJsoup()
return videosFromElement(document)
}
private fun videosFromElement(document: Document): List<Video> {
val videoList = mutableListOf<Video>()
val hosterSelection = preferences.getStringSet("hoster_selection", setOf("voe", "dood", "fmoon", "mp4u"))
val fEp = document.select("div.tab-pane")
if (fEp.text().contains(":") || fEp.text().contains("-")) {
val tx = document.select("div.tab-pane")
var here = false
tx.forEach {
if ((it.text().contains(":") || it.text().contains("-")) && !here) {
here = true
val sUrl = it.select("a").attr("href")
val doc = client.newCall(GET(sUrl)).execute().asJsoup()
val nUrl = doc.select("#player-embed a").attr("href")
val nDoc = client.newCall(GET(nUrl)).execute().asJsoup()
val nEpEl = nDoc.select("div.tab-pane a")
val nEpcu = try {
nDoc.select("div.tab-pane a.current-link").text()
.substringAfter("Ep.").toFloat()
} catch (e: Exception) {
100.0f
}
nEpEl.forEach { tIt ->
if (try { tIt.text().substringAfter("Ep.").toFloat() } catch (_: Exception) {} == nEpcu) {
val url = tIt.attr("href")
val newdoc = client.newCall(GET(url)).execute().asJsoup()
val element = newdoc.select("#player-embed")
for (elements in element) {
val link = element.select("a").attr("abs:href")
when {
link.contains("https://voe.sx") && hosterSelection?.contains(
"voe",
) == true -> {
videoList.addAll(VoeExtractor(client).videosFromUrl(link))
}
}
}
for (elements in element) {
val link = element.select("iframe").attr("abs:src")
when {
(link.contains("https://dood") || link.contains("https://ds2play")) && hosterSelection?.contains(
"dood",
) == true -> {
val quality = "DoodStream"
val video =
DoodExtractor(client).videoFromUrl(
link,
quality,
false,
)
if (video != null) {
videoList.add(video)
}
}
link.contains("https://filemoon.sx") && hosterSelection?.contains(
"fmoon",
) == true -> {
val videos =
FilemoonExtractor(client).videosFromUrl(link)
videoList.addAll(videos)
}
link.contains("mp4upload") && hosterSelection?.contains("mp4u") == true -> {
val videos =
Mp4uploadExtractor(client).videosFromUrl(
link,
headers,
)
videoList.addAll(videos)
}
}
}
}
}
}
}
} else {
val epcu = try {
document.select("div.tab-pane a.current-link").text().substringAfter("Ep.")
.toFloat()
} catch (e: Exception) {
100.0f
}
val ep = document.select("div.tab-pane a")
ep.forEach {
if (try { it.text().substringAfter("Ep.").toFloat() } catch (_: Exception) {} == epcu) {
val url = it.attr("href")
val newdoc = client.newCall(GET(url)).execute().asJsoup()
val element = newdoc.select("#player-embed")
for (elements in element) {
val link = element.select("a").attr("abs:href")
when {
link.contains("https://voe.sx") && hosterSelection?.contains("voe") == true -> {
videoList.addAll(VoeExtractor(client).videosFromUrl(link))
}
}
}
for (elements in element) {
val link = element.select("iframe").attr("abs:src")
when {
(link.contains("https://dood") || link.contains("https://ds2play")) && hosterSelection?.contains(
"dood",
) == true -> {
val quality = "DoodStream"
val video =
DoodExtractor(client).videoFromUrl(link, quality, false)
if (video != null) {
videoList.add(video)
}
}
link.contains("https://filemoon.sx") && hosterSelection?.contains("fmoon") == true -> {
val videos = FilemoonExtractor(client).videosFromUrl(link)
videoList.addAll(videos)
}
link.contains("mp4upload") && hosterSelection?.contains("mp4u") == true -> {
val videos =
Mp4uploadExtractor(client).videosFromUrl(link, headers)
videoList.addAll(videos)
}
}
}
}
}
}
return videoList.reversed()
}
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.quality.contains(hoster)) {
newList.add(preferred, video)
preferred++
} else {
newList.add(video)
}
}
return newList
}
return this
}
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"))
anime.thumbnail_url = element.select("a img").attr("src")
anime.title = element.attr("title")
return anime
}
override fun searchAnimeNextPageSelector(): String = ".nextpostslink"
override fun searchAnimeSelector(): String = "div.item-thumbnail a[href]"
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val url = "$baseUrl/page/$page/?s=$query"
return GET(url)
}
// Details
override fun animeDetailsParse(document: Document): SAnime {
val anime = SAnime.create()
anime.thumbnail_url = document.select(".item-content p img").attr("src")
anime.title = document.select("h1.light-title.entry-title").text()
anime.genre = document.select("a[rel=tag]").joinToString(", ") { it.text() }
val height = document.select("div.item-content p img").attr("height")
anime.description = document.select("div.item-content div + p").text()
anime.status = parseStatus(document.select("a[rel=\"category tag\"]").text())
return anime
}
private fun parseStatus(status: String?) = when {
status == null -> SAnime.UNKNOWN
status.contains("Airing", ignoreCase = true) -> SAnime.ONGOING
else -> SAnime.COMPLETED
}
// Latest
override fun latestUpdatesNextPageSelector(): String = throw UnsupportedOperationException()
override fun latestUpdatesFromElement(element: Element): SAnime = throw UnsupportedOperationException()
override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException()
override fun latestUpdatesSelector(): String = throw UnsupportedOperationException()
// Preferences
override fun setupPreferenceScreen(screen: PreferenceScreen) {
val hosterPref = ListPreference(screen.context).apply {
key = "preferred_hoster"
title = "Standard-Hoster"
entries = arrayOf("Voe", "DoodStream", "Filemoon", "Mp4upload")
entryValues = arrayOf("https://voe.sx", "https://dood", "https://filemoon", "https://www.mp4upload")
setDefaultValue("https://voe.sx")
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("Voe", "DoodStream", "Filemoon", "Mp4upload")
entryValues = arrayOf("voe", "dood", "fmoon", "mp4u")
setDefaultValue(setOf("voe", "dood", "fmoon", "mp4u"))
setOnPreferenceChangeListener { _, newValue ->
preferences.edit().putStringSet(key, newValue as Set<String>).commit()
}
}
screen.addPreference(hosterPref)
screen.addPreference(subSelection)
}
}

View file

@ -0,0 +1,13 @@
ext {
extName = 'AniWorld'
extClass = '.AniWorld'
extVersionCode = 23
}
apply from: "$rootDir/common.gradle"
dependencies {
implementation(project(':lib:voe-extractor'))
implementation(project(':lib:streamtape-extractor'))
implementation(project(':lib:dood-extractor'))
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View file

@ -0,0 +1,30 @@
package eu.kanade.tachiyomi.animeextension.de.aniworld
object AWConstants {
const val NAME_DOOD = "Doodstream"
const val NAME_STAPE = "Streamtape"
const val NAME_VOE = "VOE"
const val NAME_VIZ = "Vidoza"
const val URL_DOOD = "https://dood"
const val URL_STAPE = "https://streamtape.com"
const val URL_VOE = "https://voe"
const val URL_VIZ = "https://vidoza"
val HOSTER_NAMES = arrayOf(NAME_VOE, NAME_DOOD, NAME_STAPE, NAME_VIZ)
val HOSTER_URLS = arrayOf(URL_VOE, URL_DOOD, URL_STAPE, URL_VIZ)
const val KEY_GER_DUB = 1
const val KEY_ENG_SUB = 2
const val KEY_GER_SUB = 3
const val LANG_GER_SUB = "Deutscher Sub"
const val LANG_GER_DUB = "Deutscher Dub"
const val LANG_ENG_SUB = "Englischer Sub"
val LANGS = arrayOf(LANG_GER_SUB, LANG_GER_DUB, LANG_ENG_SUB)
const val PREFERRED_HOSTER = "preferred_hoster"
const val PREFERRED_LANG = "preferred_lang"
const val HOSTER_SELECTION = "hoster_selection"
}

View file

@ -0,0 +1,368 @@
package eu.kanade.tachiyomi.animeextension.de.aniworld
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.aniworld.extractors.VidozaExtractor
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.ParsedAnimeHttpSource
import eu.kanade.tachiyomi.lib.doodextractor.DoodExtractor
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
import eu.kanade.tachiyomi.lib.voeextractor.VoeExtractor
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.FormBody
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
import uy.kohesive.injekt.injectLazy
class AniWorld : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override val name = "AniWorld"
override val baseUrl = "https://aniworld.to"
override val lang = "de"
override val id: Long = 8286900189409315836
override val supportsLatest = true
private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
override val client = network.client.newBuilder()
.addInterceptor(DdosGuardInterceptor(network.client))
.build()
private val json: Json by injectLazy()
// ===== POPULAR ANIME =====
override fun popularAnimeSelector(): String = "div.seriesListContainer div"
override fun popularAnimeNextPageSelector(): String? = null
override fun popularAnimeRequest(page: Int): Request {
return GET("$baseUrl/beliebte-animes")
}
override fun popularAnimeFromElement(element: Element): SAnime {
val anime = SAnime.create()
val linkElement = element.selectFirst("a")!!
anime.url = linkElement.attr("href")
anime.thumbnail_url = baseUrl + linkElement.selectFirst("img")!!.attr("data-src")
anime.title = element.selectFirst("h3")!!.text()
return anime
}
// ===== LATEST ANIME =====
override fun latestUpdatesSelector(): String = "div.seriesListContainer div"
override fun latestUpdatesNextPageSelector(): String? = null
override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/neu")
override fun latestUpdatesFromElement(element: Element): SAnime {
val anime = SAnime.create()
val linkElement = element.selectFirst("a")!!
anime.url = linkElement.attr("href")
anime.thumbnail_url = baseUrl + linkElement.selectFirst("img")!!.attr("data-src")
anime.title = element.selectFirst("h3")!!.text()
return anime
}
// ===== SEARCH =====
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val headers = Headers.Builder()
.add("Referer", "https://aniworld.to/search")
.add("origin", baseUrl)
.add("connection", "keep-alive")
.add("user-agent", "Mozilla/5.0 (Linux; Android 12; Pixel 5 Build/SP2A.220405.004; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/100.0.4896.127 Safari/537.36")
.add("Upgrade-Insecure-Requests", "1")
.add("content-length", query.length.plus(8).toString())
.add("cache-control", "")
.add("accept", "*/*")
.add("content-type", "application/x-www-form-urlencoded; charset=UTF-8")
.add("x-requested-with", "XMLHttpRequest")
.build()
return POST("$baseUrl/ajax/search", body = FormBody.Builder().add("keyword", query).build(), headers = headers)
}
override fun searchAnimeSelector() = throw UnsupportedOperationException()
override fun searchAnimeNextPageSelector() = throw UnsupportedOperationException()
override fun searchAnimeParse(response: Response): AnimesPage {
val body = response.body.string()
val results = json.decodeFromString<JsonArray>(body)
val animes = results.filter {
val link = it.jsonObject["link"]!!.jsonPrimitive.content
link.startsWith("/anime/stream/") &&
link.count { c -> c == '/' } == 3
}.map {
animeFromSearch(it.jsonObject)
}
return AnimesPage(animes, false)
}
private fun animeFromSearch(result: JsonObject): SAnime {
val anime = SAnime.create()
val title = result["title"]!!.jsonPrimitive.content
val link = result["link"]!!.jsonPrimitive.content
anime.title = title.replace("<em>", "").replace("</em>", "")
val thumpage = client.newCall(GET("$baseUrl$link")).execute().asJsoup()
anime.thumbnail_url = baseUrl +
thumpage.selectFirst("div.seriesCoverBox img")!!.attr("data-src")
anime.url = link
return anime
}
override fun searchAnimeFromElement(element: Element) = throw UnsupportedOperationException()
// ===== ANIME DETAILS =====
override fun animeDetailsParse(document: Document): SAnime {
val anime = SAnime.create()
anime.title = document.selectFirst("div.series-title h1 span")!!.text()
anime.thumbnail_url = baseUrl +
document.selectFirst("div.seriesCoverBox img")!!.attr("data-src")
anime.genre = document.select("div.genres ul li").joinToString { it.text() }
anime.description = document.selectFirst("p.seri_des")!!.attr("data-full-description")
document.selectFirst("div.cast li:contains(Produzent:) ul")?.let {
val author = it.select("li").joinToString { li -> li.text() }
anime.author = author
}
anime.status = SAnime.UNKNOWN
return anime
}
// ===== EPISODE =====
override fun episodeListSelector() = throw UnsupportedOperationException()
override fun episodeListParse(response: Response): List<SEpisode> {
val document = response.asJsoup()
val episodeList = mutableListOf<SEpisode>()
val seasonsElements = document.select("#stream > ul:nth-child(1) > li > a")
if (seasonsElements.attr("href").contains("/filme")) {
seasonsElements.forEach {
val seasonEpList = parseMoviesFromSeries(it)
episodeList.addAll(seasonEpList)
}
} else {
seasonsElements.forEach {
val seasonEpList = parseEpisodesFromSeries(it)
episodeList.addAll(seasonEpList)
}
}
return episodeList.reversed()
}
private fun parseEpisodesFromSeries(element: Element): List<SEpisode> {
val seasonId = element.attr("abs:href")
val episodesHtml = client.newCall(GET(seasonId)).execute().asJsoup()
val episodeElements = episodesHtml.select("table.seasonEpisodesList tbody tr")
return episodeElements.map { episodeFromElement(it) }
}
private fun parseMoviesFromSeries(element: Element): List<SEpisode> {
val seasonId = element.attr("abs:href")
val episodesHtml = client.newCall(GET(seasonId)).execute().asJsoup()
val episodeElements = episodesHtml.select("table.seasonEpisodesList tbody tr")
return episodeElements.map { episodeFromElement(it) }
}
override fun episodeFromElement(element: Element): SEpisode {
val episode = SEpisode.create()
if (element.select("td.seasonEpisodeTitle a").attr("href").contains("/film")) {
val num = element.attr("data-episode-season-id")
episode.name = "Film $num" + " : " + element.select("td.seasonEpisodeTitle a span").text()
episode.episode_number = element.attr("data-episode-season-id").toFloat()
episode.url = element.selectFirst("td.seasonEpisodeTitle a")!!.attr("href")
} else {
val season = element.select("td.seasonEpisodeTitle a").attr("href")
.substringAfter("staffel-").substringBefore("/episode")
val num = element.attr("data-episode-season-id")
episode.name = "Staffel $season Folge $num" + " : " + element.select("td.seasonEpisodeTitle a span").text()
episode.episode_number = element.select("td meta").attr("content").toFloat()
episode.url = element.selectFirst("td.seasonEpisodeTitle a")!!.attr("href")
}
return episode
}
// ===== VIDEO SOURCES =====
override fun videoListSelector() = throw UnsupportedOperationException()
override fun videoListParse(response: Response): List<Video> {
val document = response.asJsoup()
val redirectlink = document.select("ul.row li")
val videoList = mutableListOf<Video>()
val hosterSelection = preferences.getStringSet(AWConstants.HOSTER_SELECTION, null)
redirectlink.forEach {
val langkey = it.attr("data-lang-key")
val language = getlanguage(langkey)
val redirectgs = baseUrl + it.selectFirst("a.watchEpisode")!!.attr("href")
val hoster = it.select("a h4").text()
if (hosterSelection != null) {
when {
hoster.contains("VOE") && hosterSelection.contains(AWConstants.NAME_VOE) -> {
val url = client.newCall(GET(redirectgs)).execute().request.url.toString()
videoList.addAll(VoeExtractor(client).videosFromUrl(url, "($language) "))
}
hoster.contains("Doodstream") && hosterSelection.contains(AWConstants.NAME_DOOD) -> {
val quality = "Doodstream $language"
val url = client.newCall(GET(redirectgs)).execute().request.url.toString()
val video = DoodExtractor(client).videoFromUrl(url, quality)
if (video != null) {
videoList.add(video)
}
}
hoster.contains("Streamtape") && hosterSelection.contains(AWConstants.NAME_STAPE) -> {
val quality = "Streamtape $language"
val url = client.newCall(GET(redirectgs)).execute().request.url.toString()
val video = StreamTapeExtractor(client).videoFromUrl(url, quality)
if (video != null) {
videoList.add(video)
}
}
hoster.contains("Vidoza") && hosterSelection.contains(AWConstants.NAME_VIZ) -> {
val quality = "Vidoza $language"
val url = client.newCall(GET(redirectgs)).execute().request.url.toString()
val video = VidozaExtractor(client).videoFromUrl(url, quality)
if (video != null) {
videoList.add(video)
}
}
}
}
}
return videoList
}
private fun getlanguage(langkey: String): String? {
when {
langkey.contains("${AWConstants.KEY_GER_SUB}") -> {
return "Deutscher Sub"
}
langkey.contains("${AWConstants.KEY_GER_DUB}") -> {
return "Deutscher Dub"
}
langkey.contains("${AWConstants.KEY_ENG_SUB}") -> {
return "Englischer Sub"
}
else -> {
return null
}
}
}
override fun videoFromElement(element: Element): Video = throw UnsupportedOperationException()
override fun List<Video>.sort(): List<Video> {
val hoster = preferences.getString(AWConstants.PREFERRED_HOSTER, null)
val subPreference = preferences.getString(AWConstants.PREFERRED_LANG, "Sub")!!
val hosterList = mutableListOf<Video>()
val otherList = mutableListOf<Video>()
if (hoster != null) {
for (video in this) {
if (video.url.contains(hoster)) {
hosterList.add(video)
} else {
otherList.add(video)
}
}
} else {
otherList += this
}
val newList = mutableListOf<Video>()
var preferred = 0
for (video in hosterList) {
if (video.quality.contains(subPreference)) {
newList.add(preferred, video)
preferred++
} else {
newList.add(video)
}
}
for (video in otherList) {
if (video.quality.contains(subPreference)) {
newList.add(preferred, video)
preferred++
} else {
newList.add(video)
}
}
return newList
}
override fun videoUrlParse(document: Document): String = throw UnsupportedOperationException()
// ===== PREFERENCES ======
@Suppress("UNCHECKED_CAST")
override fun setupPreferenceScreen(screen: PreferenceScreen) {
val hosterPref = ListPreference(screen.context).apply {
key = AWConstants.PREFERRED_HOSTER
title = "Standard-Hoster"
entries = AWConstants.HOSTER_NAMES
entryValues = AWConstants.HOSTER_URLS
setDefaultValue(AWConstants.URL_STAPE)
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 subPref = ListPreference(screen.context).apply {
key = AWConstants.PREFERRED_LANG
title = "Bevorzugte Sprache"
entries = AWConstants.LANGS
entryValues = AWConstants.LANGS
setDefaultValue(AWConstants.LANG_GER_SUB)
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 hosterSelection = MultiSelectListPreference(screen.context).apply {
key = AWConstants.HOSTER_SELECTION
title = "Hoster auswählen"
entries = AWConstants.HOSTER_NAMES
entryValues = AWConstants.HOSTER_NAMES
setDefaultValue(AWConstants.HOSTER_NAMES.toSet())
setOnPreferenceChangeListener { _, newValue ->
preferences.edit().putStringSet(key, newValue as Set<String>).commit()
}
}
screen.addPreference(subPref)
screen.addPreference(hosterPref)
screen.addPreference(hosterSelection)
}
}

View file

@ -0,0 +1,72 @@
package eu.kanade.tachiyomi.animeextension.de.aniworld
import android.webkit.CookieManager
import eu.kanade.tachiyomi.network.GET
import okhttp3.Cookie
import okhttp3.HttpUrl
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Response
class DdosGuardInterceptor(private val client: OkHttpClient) : Interceptor {
private val cookieManager by lazy { CookieManager.getInstance() }
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
val response = chain.proceed(originalRequest)
// Check if DDos-GUARD is on
if (response.code !in ERROR_CODES || response.header("Server") !in SERVER_CHECK) {
return response
}
response.close()
val cookies = cookieManager.getCookie(originalRequest.url.toString())
val oldCookie = if (cookies != null && cookies.isNotEmpty()) {
cookies.split(";").mapNotNull { Cookie.parse(originalRequest.url, it) }
} else {
emptyList()
}
val ddg2Cookie = oldCookie.firstOrNull { it.name == "__ddg2_" }
if (!ddg2Cookie?.value.isNullOrEmpty()) {
return chain.proceed(originalRequest)
}
val newCookie = getNewCookie(originalRequest.url) ?: return chain.proceed(originalRequest)
val newCookieHeader = buildString {
(oldCookie + newCookie).forEachIndexed { index, cookie ->
if (index > 0) append("; ")
append(cookie.name).append('=').append(cookie.value)
}
}
return chain.proceed(originalRequest.newBuilder().addHeader("cookie", newCookieHeader).build())
}
fun getNewCookie(url: HttpUrl): Cookie? {
val cookies = cookieManager.getCookie(url.toString())
val oldCookie = if (cookies != null && cookies.isNotEmpty()) {
cookies.split(";").mapNotNull { Cookie.parse(url, it) }
} else {
emptyList()
}
val ddg2Cookie = oldCookie.firstOrNull { it.name == "__ddg2_" }
if (!ddg2Cookie?.value.isNullOrEmpty()) {
return ddg2Cookie
}
val wellKnown = client.newCall(GET("https://check.ddos-guard.net/check.js"))
.execute().body.string()
.substringAfter("'", "")
.substringBefore("'", "")
val checkUrl = "${url.scheme}://${url.host + wellKnown}"
return client.newCall(GET(checkUrl)).execute().header("set-cookie")?.let {
Cookie.parse(url, it)
}
}
companion object {
private val ERROR_CODES = listOf(403)
private val SERVER_CHECK = listOf("ddos-guard")
}
}

View file

@ -0,0 +1,17 @@
package eu.kanade.tachiyomi.animeextension.de.aniworld.extractors
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.OkHttpClient
class VidozaExtractor(private val client: OkHttpClient) {
fun videoFromUrl(url: String, quality: String): Video? {
val document = client.newCall(GET(url)).execute().asJsoup()
val script = document.select("script:containsData(window.pData = {)")
.firstOrNull()?.data()?.substringAfter("sourcesCode: [{ src: \"") ?: return null
val videoUrl = script.substringAfter("sourcesCode: [{ src: \"").substringBefore("\", type:")
return Video(url, quality, videoUrl)
}
}

View file

@ -0,0 +1,16 @@
ext {
extName = 'CineClix'
extClass = '.CineClix'
extVersionCode = 14
}
apply from: "$rootDir/common.gradle"
dependencies {
implementation(project(':lib:streamtape-extractor'))
implementation(project(':lib:mixdrop-extractor'))
implementation(project(':lib:dood-extractor'))
implementation(project(':lib:voe-extractor'))
implementation(project(':lib:playlist-utils'))
implementation "dev.datlag.jsunpacker:jsunpacker:1.0.1"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 842 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 448 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -0,0 +1,364 @@
package eu.kanade.tachiyomi.animeextension.de.cineclix
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.cineclix.extractors.StreamVidExtractor
import eu.kanade.tachiyomi.animeextension.de.cineclix.extractors.SuperVideoExtractor
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.doodextractor.DoodExtractor
import eu.kanade.tachiyomi.lib.mixdropextractor.MixDropExtractor
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
import eu.kanade.tachiyomi.lib.voeextractor.VoeExtractor
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.Request
import okhttp3.Response
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class CineClix : ConfigurableAnimeSource, AnimeHttpSource() {
override val name = "CineClix"
override val baseUrl = "https://cineclix.de"
override val lang = "de"
override val supportsLatest = false
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/64?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"]!!.jsonPrimitive.int
// .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", "supv", "mix", "svid", "dood", "voe"))
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("supervideo") && hosterSelection?.contains("supv") == true -> {
val video = SuperVideoExtractor(client).videosFromUrl(eUrl)
videoList.addAll(video)
}
host.contains("mixdrop") && hosterSelection?.contains("mix") == true -> {
val video = MixDropExtractor(client).videoFromUrl(eUrl)
videoList.addAll(video)
}
host.contains("streamvid") && hosterSelection?.contains("svid") == true -> {
val video = StreamVidExtractor(client).videosFromUrl(eUrl)
videoList.addAll(video)
}
host.contains("DoodStream") && hosterSelection?.contains("dood") == true -> {
val video = DoodExtractor(client).videoFromUrl(eUrl)
if (video != null) {
videoList.add(video)
}
}
host.contains("VOE.SX") && hosterSelection?.contains("voe") == true -> {
videoList.addAll(VoeExtractor(client).videosFromUrl(eUrl))
}
}
}
} 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("supervideo") && hosterSelection?.contains("supv") == true -> {
val video = SuperVideoExtractor(client).videosFromUrl(fUrl)
videoList.addAll(video)
}
host.contains("mixdrop") && hosterSelection?.contains("mix") == true -> {
val video = MixDropExtractor(client).videoFromUrl(fUrl)
videoList.addAll(video)
}
host.contains("streamvid") && hosterSelection?.contains("svid") == true -> {
val video = StreamVidExtractor(client).videosFromUrl(fUrl)
videoList.addAll(video)
}
host.contains("DoodStream") && hosterSelection?.contains("dood") == true -> {
val video = DoodExtractor(client).videoFromUrl(fUrl)
if (video != null) {
videoList.add(video)
}
}
host.contains("VOE.SX") && hosterSelection?.contains("voe") == true -> {
videoList.addAll(VoeExtractor(client).videosFromUrl(fUrl))
}
}
}
}
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.quality.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 UnsupportedOperationException()
override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException()
// Preferences
override fun setupPreferenceScreen(screen: PreferenceScreen) {
val hosterPref = ListPreference(screen.context).apply {
key = "preferred_hoster"
title = "Standard-Hoster"
entries = arrayOf("Streamtape", "SuperVideo", "MixDrop", "StreamVid", "DoodStream", "Voe")
entryValues = arrayOf("https://streamtape", "https://supervideo", "https://mixdrop", "https://streamvid", "https://dood", "https://voe")
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", "SuperVideo", "MixDrop", "StreamVid", "DoodStream", "Voe")
entryValues = arrayOf("stape", "supv", "mix", "svid", "dood", "voe")
setDefaultValue(setOf("stape", "supv", "mix", "svid", "dood", "voe"))
setOnPreferenceChangeListener { _, newValue ->
preferences.edit().putStringSet(key, newValue as Set<String>).commit()
}
}
screen.addPreference(hosterPref)
screen.addPreference(subSelection)
}
}

View file

@ -0,0 +1,22 @@
package eu.kanade.tachiyomi.animeextension.de.cineclix.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.OkHttpClient
class StreamVidExtractor(private val client: OkHttpClient) {
fun videosFromUrl(url: String, prefix: String = ""): 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("\",")
PlaylistUtils(client).extractFromHls(masterUrl, videoNameGen = { "${prefix}StreamVid - $it" })
}.getOrElse { emptyList() }
}
}

View file

@ -0,0 +1,22 @@
package eu.kanade.tachiyomi.animeextension.de.cineclix.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.OkHttpClient
class SuperVideoExtractor(private val client: OkHttpClient) {
fun videosFromUrl(url: String, prefix: String = ""): 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:[{file:\"").substringBefore("\"}]")
PlaylistUtils(client).extractFromHls(masterUrl, videoNameGen = { "${prefix}SuperVideo - $it" })
}.getOrElse { emptyList() }
}
}

View file

@ -0,0 +1,18 @@
ext {
extName = 'Cinemathek'
extClass = '.Cinemathek'
themePkg = 'dooplay'
baseUrl = 'https://cinemathek.net'
overrideVersionCode = 20
isNsfw = true
}
apply from: "$rootDir/common.gradle"
dependencies {
implementation(project(':lib:filemoon-extractor'))
implementation(project(':lib:dood-extractor'))
implementation(project(':lib:streamlare-extractor'))
implementation(project(':lib:streamtape-extractor'))
implementation(project(':lib:streamwish-extractor'))
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 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.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Some files were not shown because too many files have changed in this diff Show more