src/ar+de+es dead sources (#814)
* src/es pelisflix/seriesflix * src/de streamcloud * src/de movie2k * src/de cineclix * src/ar xsanime/xsmovie * src/ar tuktuk * src/ar akwam --------- Co-authored-by: Your Name <you@example.com>
|
@ -1,7 +0,0 @@
|
||||||
ext {
|
|
||||||
extName = 'Akwam'
|
|
||||||
extClass = '.Akwam'
|
|
||||||
extVersionCode = 9
|
|
||||||
}
|
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
|
Before Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 4.8 KiB |
Before Width: | Height: | Size: 7.9 KiB |
Before Width: | Height: | Size: 12 KiB |
|
@ -1,355 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.animeextension.ar.akwam
|
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import android.content.SharedPreferences
|
|
||||||
import androidx.preference.ListPreference
|
|
||||||
import androidx.preference.PreferenceScreen
|
|
||||||
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
|
|
||||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
|
|
||||||
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.Headers
|
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
|
||||||
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 Akwam : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|
||||||
|
|
||||||
override val name = "أكوام"
|
|
||||||
|
|
||||||
override val baseUrl = "https://akw-cdn1.link"
|
|
||||||
|
|
||||||
override val lang = "ar"
|
|
||||||
|
|
||||||
override val supportsLatest = false
|
|
||||||
|
|
||||||
private val preferences: SharedPreferences by lazy {
|
|
||||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Popular
|
|
||||||
|
|
||||||
override fun popularAnimeSelector(): String = "div.entry-box-1 div.entry-image a.box"
|
|
||||||
|
|
||||||
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/movies?page=$page")
|
|
||||||
|
|
||||||
override fun popularAnimeFromElement(element: Element): SAnime {
|
|
||||||
val anime = SAnime.create()
|
|
||||||
anime.thumbnail_url = element.select("picture img").attr("data-src")
|
|
||||||
anime.setUrlWithoutDomain(element.attr("href"))
|
|
||||||
anime.title = element.select("picture img").attr("alt")
|
|
||||||
return anime
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun popularAnimeNextPageSelector(): String = "ul.pagination li.page-item a[rel=next]"
|
|
||||||
|
|
||||||
// episodes
|
|
||||||
override fun episodeListSelector() = "div.bg-primary2 h2 a"
|
|
||||||
|
|
||||||
override fun episodeListParse(response: Response): List<SEpisode> {
|
|
||||||
val episodes = mutableListOf<SEpisode>()
|
|
||||||
fun addEpisodes(document: Document) {
|
|
||||||
if (document.select(episodeListSelector()).isNullOrEmpty()) {
|
|
||||||
// add movie
|
|
||||||
document.select("input#reportInputUrl").map { episodes.add(episodeFromElement(it)) }
|
|
||||||
} else {
|
|
||||||
document.select(episodeListSelector()).map { episodes.add(episodesFromElement(it)) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
addEpisodes(response.asJsoup())
|
|
||||||
return episodes
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun episodeFromElement(element: Element): SEpisode {
|
|
||||||
val episode = SEpisode.create()
|
|
||||||
episode.setUrlWithoutDomain(element.attr("value"))
|
|
||||||
episode.name = "مشاهدة"
|
|
||||||
return episode
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun episodesFromElement(element: Element): SEpisode {
|
|
||||||
val episode = SEpisode.create()
|
|
||||||
val epNum = getNumberFromEpsString(element.text())
|
|
||||||
episode.setUrlWithoutDomain(element.attr("href"))
|
|
||||||
episode.name = element.text()
|
|
||||||
episode.episode_number = when {
|
|
||||||
epNum.isNotEmpty() -> epNum.toFloatOrNull() ?: 1F
|
|
||||||
else -> 1F
|
|
||||||
}
|
|
||||||
return episode
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getNumberFromEpsString(epsStr: String): String {
|
|
||||||
return epsStr.filter { it.isDigit() }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Video links
|
|
||||||
|
|
||||||
override fun videoListParse(response: Response): List<Video> {
|
|
||||||
val document = response.asJsoup()
|
|
||||||
val iframe = "https://akw-cdn1.link/watch" + document.select("a.link-show").attr("href").substringAfter("watch") + "/" + document.ownerDocument()!!.select("input#page_id").attr("value")
|
|
||||||
val referer = response.request.url.toString()
|
|
||||||
val refererHeaders = Headers.headersOf("referer", referer)
|
|
||||||
val iframeResponse = client.newCall(GET(iframe, refererHeaders))
|
|
||||||
.execute().asJsoup()
|
|
||||||
return iframeResponse.select(videoListSelector()).map { videoFromElement(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun videoListSelector() = "source"
|
|
||||||
|
|
||||||
override fun videoFromElement(element: Element): Video {
|
|
||||||
return Video(element.attr("src").replace("https", "http"), element.attr("size") + "p", element.attr("src").replace("https", "http"))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun List<Video>.sort(): List<Video> {
|
|
||||||
val quality = preferences.getString("preferred_quality", null)
|
|
||||||
if (quality != null) {
|
|
||||||
val newList = mutableListOf<Video>()
|
|
||||||
var preferred = 0
|
|
||||||
for (video in this) {
|
|
||||||
if (video.quality.contains(quality)) {
|
|
||||||
newList.add(preferred, video)
|
|
||||||
preferred++
|
|
||||||
} else {
|
|
||||||
newList.add(video)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return newList
|
|
||||||
}
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun videoUrlParse(document: Document) = throw UnsupportedOperationException()
|
|
||||||
|
|
||||||
// Search
|
|
||||||
|
|
||||||
override fun searchAnimeFromElement(element: Element): SAnime {
|
|
||||||
val anime = SAnime.create()
|
|
||||||
anime.thumbnail_url = element.select("picture img").attr("data-src")
|
|
||||||
anime.setUrlWithoutDomain(element.attr("href"))
|
|
||||||
anime.title = element.select("picture img").attr("alt")
|
|
||||||
return anime
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun searchAnimeNextPageSelector(): String = "ul.pagination li.page-item a[rel=next]"
|
|
||||||
|
|
||||||
override fun searchAnimeSelector(): String = "div.widget div.widget-body div.col-lg-auto div.entry-box div.entry-image a.box"
|
|
||||||
|
|
||||||
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
|
|
||||||
val url = if (query.isNotBlank()) {
|
|
||||||
val url = "$baseUrl/search?q=$query&page=$page".toHttpUrlOrNull()!!.newBuilder()
|
|
||||||
filters.forEach { filter ->
|
|
||||||
when (filter) {
|
|
||||||
is SectionFilter -> url.addQueryParameter("section", filter.toUriPart())
|
|
||||||
is RatingFilter -> url.addQueryParameter("rating", filter.toUriPart())
|
|
||||||
is FormatFilter -> url.addQueryParameter("formats", filter.toUriPart())
|
|
||||||
is QualityFilter -> url.addQueryParameter("quality", filter.toUriPart())
|
|
||||||
else -> {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
url.toString()
|
|
||||||
} else {
|
|
||||||
val url = "$baseUrl/search?page=$page".toHttpUrlOrNull()!!.newBuilder()
|
|
||||||
var type = "movies"
|
|
||||||
filters.forEach { filter ->
|
|
||||||
when (filter) {
|
|
||||||
is TypeFilter -> type = filter.toUriPart().toString()
|
|
||||||
is SectionSFilter -> url.addQueryParameter("section", filter.toUriPart())
|
|
||||||
is CategorySFilter -> url.addQueryParameter("category", filter.toUriPart())
|
|
||||||
is RatingSFilter -> url.addQueryParameter("rating", filter.toUriPart())
|
|
||||||
// is LanguageSFilter -> url.addQueryParameter("quality", filter.toUriPart())
|
|
||||||
else -> {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
url.toString().replace("search", type)
|
|
||||||
}
|
|
||||||
return GET(url, headers)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Anime Details
|
|
||||||
|
|
||||||
override fun animeDetailsParse(document: Document): SAnime {
|
|
||||||
val anime = SAnime.create()
|
|
||||||
// anime.thumbnail_url = document.select("div.container div div a picture > img.img-fluid").attr("data-src")
|
|
||||||
anime.title = document.select("picture > img.img-fluid").attr("alt")
|
|
||||||
anime.genre = document.select("div.font-size-16.d-flex.align-items-center.mt-3 a.badge, span.badge-info, span:contains(جودة الفيلم), span:contains(انتاج)").joinToString(", ") { it.text().replace("جودة الفيلم : ", "") }
|
|
||||||
anime.author = document.select("span:contains(انتاج)").text().replace("انتاج : ", "")
|
|
||||||
anime.description = document.select("div.widget:contains(قصة )").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()
|
|
||||||
|
|
||||||
// Filters
|
|
||||||
|
|
||||||
override fun getFilterList() = AnimeFilterList(
|
|
||||||
AnimeFilter.Header("فلترات البحث"),
|
|
||||||
AnimeFilter.Separator(),
|
|
||||||
SectionFilter(getSectionFilter()),
|
|
||||||
RatingFilter(getRatingFilter()),
|
|
||||||
FormatFilter(getFormatFilter()),
|
|
||||||
QualityFilter(getQualityFilter()),
|
|
||||||
AnimeFilter.Header("تصفح الموقع (تعمل فقط لو كان البحث فارغ)"),
|
|
||||||
AnimeFilter.Separator(),
|
|
||||||
TypeFilter(getTypeFilter()),
|
|
||||||
SectionSFilter(getSectionSFilter()),
|
|
||||||
CategorySFilter(getCategorySFilter()),
|
|
||||||
RatingSFilter(getRatingSFilter()),
|
|
||||||
)
|
|
||||||
|
|
||||||
private class SectionFilter(vals: Array<Pair<String?, String>>) : UriPartFilter("الأقسام", vals)
|
|
||||||
private class RatingFilter(vals: Array<Pair<String?, String>>) : UriPartFilter("التقيم", vals)
|
|
||||||
private class FormatFilter(vals: Array<Pair<String?, String>>) : UriPartFilter("الجودة", vals)
|
|
||||||
private class QualityFilter(vals: Array<Pair<String?, String>>) : UriPartFilter("الدقة", vals)
|
|
||||||
private class TypeFilter(vals: Array<Pair<String?, String>>) : UriPartFilter("النوع", vals)
|
|
||||||
private class SectionSFilter(vals: Array<Pair<String?, String>>) : UriPartFilter("القسم", vals)
|
|
||||||
private class CategorySFilter(vals: Array<Pair<String?, String>>) : UriPartFilter("التصنيف", vals)
|
|
||||||
private class RatingSFilter(vals: Array<Pair<String?, String>>) : UriPartFilter("التقييم", vals)
|
|
||||||
private fun getTypeFilter(): Array<Pair<String?, String>> = arrayOf(
|
|
||||||
Pair("movies", "افلام"),
|
|
||||||
Pair("series", "مسلسلات"),
|
|
||||||
)
|
|
||||||
private fun getSectionFilter(): Array<Pair<String?, String>> = arrayOf(
|
|
||||||
Pair("0", "الكل"),
|
|
||||||
Pair("movie", "افلام"),
|
|
||||||
Pair("series", "مسلسلات"),
|
|
||||||
Pair("show", "تلفزيون"),
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun getRatingFilter(): Array<Pair<String?, String>> = arrayOf(
|
|
||||||
Pair("0", "التقييم"),
|
|
||||||
Pair("1", "1+"),
|
|
||||||
Pair("2", "2+"),
|
|
||||||
Pair("3", "3+"),
|
|
||||||
Pair("4", "4+"),
|
|
||||||
Pair("5", "5+"),
|
|
||||||
Pair("6", "6+"),
|
|
||||||
Pair("7", "7+"),
|
|
||||||
Pair("8", "8+"),
|
|
||||||
Pair("9", "9+"),
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun getFormatFilter(): Array<Pair<String?, String>> = arrayOf(
|
|
||||||
Pair("0", "الكل"),
|
|
||||||
Pair("BluRay", "BluRay"),
|
|
||||||
Pair("WebRip", "WebRip"),
|
|
||||||
Pair("BRRIP", "BRRIP"),
|
|
||||||
Pair("DVDrip", "DVDrip"),
|
|
||||||
Pair("DVDSCR", "DVDSCR"),
|
|
||||||
Pair("HD", "HD"),
|
|
||||||
Pair("HDTS", "HDTS"),
|
|
||||||
Pair("HDTV", "HDTV"),
|
|
||||||
Pair("CAM", "CAM"),
|
|
||||||
Pair("WEB-DL", "WEB-DL"),
|
|
||||||
Pair("HDTC", "HDTC"),
|
|
||||||
Pair("BDRIP", "BDRIP"),
|
|
||||||
Pair("HDRIP", "HDRIP"),
|
|
||||||
Pair("HC+HDRIP", "HC HDRIP"),
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun getQualityFilter(): Array<Pair<String?, String>> = arrayOf(
|
|
||||||
Pair("0", "الدقة"),
|
|
||||||
Pair("240p", "240p"),
|
|
||||||
Pair("360p", "360p"),
|
|
||||||
Pair("480p", "480p"),
|
|
||||||
Pair("720p", "720p"),
|
|
||||||
Pair("1080p", "1080p"),
|
|
||||||
Pair("3D", "3D"),
|
|
||||||
Pair("4K", "4K"),
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun getSectionSFilter(): Array<Pair<String?, String>> = arrayOf(
|
|
||||||
Pair("0", "القسم"),
|
|
||||||
Pair("29", "عربي"),
|
|
||||||
Pair("30", "اجنبي"),
|
|
||||||
Pair("31", "هندي"),
|
|
||||||
Pair("32", "تركي"),
|
|
||||||
Pair("33", "اسيوي"),
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun getCategorySFilter(): Array<Pair<String?, String>> = arrayOf(
|
|
||||||
Pair("0", "التصنيف"),
|
|
||||||
Pair("87", "رمضان"),
|
|
||||||
Pair("30", "انمي"),
|
|
||||||
Pair("18", "اكشن"),
|
|
||||||
Pair("71", "مدبلج"),
|
|
||||||
Pair("72", "NETFLIX"),
|
|
||||||
Pair("20", "كوميدي"),
|
|
||||||
Pair("35", "اثارة"),
|
|
||||||
Pair("34", "غموض"),
|
|
||||||
Pair("33", "عائلي"),
|
|
||||||
Pair("88", "اطفال"),
|
|
||||||
Pair("25", "حربي"),
|
|
||||||
Pair("32", "رياضي"),
|
|
||||||
Pair("89", "قصير"),
|
|
||||||
Pair("43", "فانتازيا"),
|
|
||||||
Pair("24", "خيال علمي"),
|
|
||||||
Pair("31", "موسيقى"),
|
|
||||||
Pair("29", "سيرة ذاتية"),
|
|
||||||
Pair("28", "وثائقي"),
|
|
||||||
Pair("27", "رومانسي"),
|
|
||||||
Pair("26", "تاريخي"),
|
|
||||||
Pair("23", "دراما"),
|
|
||||||
Pair("22", "رعب"),
|
|
||||||
Pair("21", "جريمة"),
|
|
||||||
Pair("19", "مغامرة"),
|
|
||||||
Pair("91", "غربي"),
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun getRatingSFilter(): Array<Pair<String?, String>> = arrayOf(
|
|
||||||
Pair("0", "التقييم"),
|
|
||||||
Pair("1", "1+"),
|
|
||||||
Pair("2", "2+"),
|
|
||||||
Pair("3", "3+"),
|
|
||||||
Pair("4", "4+"),
|
|
||||||
Pair("5", "5+"),
|
|
||||||
Pair("6", "6+"),
|
|
||||||
Pair("7", "7+"),
|
|
||||||
Pair("8", "8+"),
|
|
||||||
Pair("9", "9+"),
|
|
||||||
)
|
|
||||||
|
|
||||||
open class UriPartFilter(displayName: String, private val vals: Array<Pair<String?, String>>) :
|
|
||||||
AnimeFilter.Select<String>(displayName, vals.map { it.second }.toTypedArray()) {
|
|
||||||
fun toUriPart() = vals[state].first
|
|
||||||
}
|
|
||||||
|
|
||||||
// preferred quality settings
|
|
||||||
|
|
||||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
|
||||||
val videoQualityPref = ListPreference(screen.context).apply {
|
|
||||||
key = "preferred_quality"
|
|
||||||
title = "Preferred quality"
|
|
||||||
entries = arrayOf("1080p", "720p", "480p", "360p", "240p")
|
|
||||||
entryValues = arrayOf("1080", "720", "480", "360", "240")
|
|
||||||
setDefaultValue("1080")
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
screen.addPreference(videoQualityPref)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
ext {
|
|
||||||
extName = 'Tuktuk Cinema'
|
|
||||||
extClass = '.Tuktukcinema'
|
|
||||||
extVersionCode = 24
|
|
||||||
}
|
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation(project(':lib:uqload-extractor'))
|
|
||||||
implementation(project(':lib:okru-extractor'))
|
|
||||||
implementation(project(':lib:dood-extractor'))
|
|
||||||
implementation(project(':lib:streamtape-extractor'))
|
|
||||||
implementation(project(':lib:vidbom-extractor'))
|
|
||||||
implementation(project(':lib:playlist-utils'))
|
|
||||||
implementation(libs.jsunpacker)
|
|
||||||
}
|
|
Before Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 5.4 KiB |
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 15 KiB |
|
@ -1,309 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.animeextension.ar.tuktukcinema
|
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import android.content.SharedPreferences
|
|
||||||
import androidx.preference.ListPreference
|
|
||||||
import androidx.preference.PreferenceScreen
|
|
||||||
import dev.datlag.jsunpacker.JsUnpacker
|
|
||||||
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
|
|
||||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
|
|
||||||
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.okruextractor.OkruExtractor
|
|
||||||
import eu.kanade.tachiyomi.lib.playlistutils.PlaylistUtils
|
|
||||||
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
|
|
||||||
import eu.kanade.tachiyomi.lib.uqloadextractor.UqloadExtractor
|
|
||||||
import eu.kanade.tachiyomi.lib.vidbomextractor.VidBomExtractor
|
|
||||||
import eu.kanade.tachiyomi.network.GET
|
|
||||||
import eu.kanade.tachiyomi.util.asJsoup
|
|
||||||
import eu.kanade.tachiyomi.util.parallelCatchingFlatMapBlocking
|
|
||||||
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 Tuktukcinema : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|
||||||
|
|
||||||
override val name = "توك توك سينما"
|
|
||||||
|
|
||||||
// TODO: Check frequency of url changes to potentially
|
|
||||||
// add back overridable baseurl preference
|
|
||||||
override val baseUrl = "https://w.tuktokcinema.com"
|
|
||||||
|
|
||||||
override val lang = "ar"
|
|
||||||
|
|
||||||
override val supportsLatest = true
|
|
||||||
|
|
||||||
private val preferences: SharedPreferences by lazy {
|
|
||||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================ popular ===============================
|
|
||||||
|
|
||||||
override fun popularAnimeSelector(): String = "div.Block--Item, div.Small--Box"
|
|
||||||
|
|
||||||
override fun popularAnimeRequest(page: Int): Request = GET(baseUrl)
|
|
||||||
|
|
||||||
override fun popularAnimeFromElement(element: Element): SAnime {
|
|
||||||
val anime = SAnime.create()
|
|
||||||
anime.title = titleEdit(element.select("a").attr("title"), true).trim()
|
|
||||||
anime.thumbnail_url = element.select("img").attr("data-src")
|
|
||||||
anime.setUrlWithoutDomain(element.select("a").attr("href") + "watch/")
|
|
||||||
return anime
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun titleEdit(title: String, details: Boolean = false): String {
|
|
||||||
return if (Regex("(?:فيلم|عرض)\\s(.*\\s[0-9]+)\\s(.+?)\\s").containsMatchIn(title)) {
|
|
||||||
val titleGroup = Regex("(?:فيلم|عرض)\\s(.*\\s[0-9]+)\\s(.+?)\\s").find(title)
|
|
||||||
val movieName = titleGroup!!.groupValues[1]
|
|
||||||
val type = titleGroup.groupValues[2]
|
|
||||||
movieName + if (details) " ($type)" else ""
|
|
||||||
} else if (Regex("(?:مسلسل|برنامج|انمي)\\s(.+)\\sالحلقة\\s(\\d+)").containsMatchIn(title)) {
|
|
||||||
val titleGroup = Regex("(?:مسلسل|برنامج|انمي)\\s(.+)\\sالحلقة\\s(\\d+)").find(title)
|
|
||||||
val seriesName = titleGroup!!.groupValues[1]
|
|
||||||
val epNum = titleGroup.groupValues[2]
|
|
||||||
if (details) {
|
|
||||||
"$seriesName (ep:$epNum)"
|
|
||||||
} else if (seriesName.contains("الموسم")) {
|
|
||||||
seriesName.split("الموسم")[0].trim()
|
|
||||||
} else {
|
|
||||||
seriesName
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
title
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun popularAnimeNextPageSelector(): String = "div.pagination ul.page-numbers li a.next"
|
|
||||||
|
|
||||||
// ============================ episodes ===============================
|
|
||||||
|
|
||||||
private fun seasonsNextPageSelector() = "section.allseasonss div.Block--Item"
|
|
||||||
|
|
||||||
override fun episodeListParse(response: Response): List<SEpisode> {
|
|
||||||
val episodes = mutableListOf<SEpisode>()
|
|
||||||
fun addEpisodeNew(url: String, title: String) {
|
|
||||||
val episode = SEpisode.create()
|
|
||||||
episode.setUrlWithoutDomain(url)
|
|
||||||
episode.name = title
|
|
||||||
episodes.add(episode)
|
|
||||||
}
|
|
||||||
fun addEpisodes(response: Response) {
|
|
||||||
val document = response.asJsoup()
|
|
||||||
val url = response.request.url.toString()
|
|
||||||
if (document.select(seasonsNextPageSelector()).isNullOrEmpty()) {
|
|
||||||
addEpisodeNew("$url/watch/", "مشاهدة")
|
|
||||||
} else {
|
|
||||||
document.select(seasonsNextPageSelector()).reversed().forEach { season ->
|
|
||||||
val seasonNum = season.select("h3").text()
|
|
||||||
(
|
|
||||||
if (seasonNum == document.selectFirst("div#mpbreadcrumbs a span:contains(الموسم)")!!.text()) {
|
|
||||||
document
|
|
||||||
} else {
|
|
||||||
client.newCall(GET(season.selectFirst("a")!!.attr("href"))).execute().asJsoup()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.select("section.allepcont a").forEach { episode ->
|
|
||||||
addEpisodeNew(
|
|
||||||
episode.attr("href") + "watch/",
|
|
||||||
seasonNum + " : الحلقة " + episode.select("div.epnum").text().filter { it.isDigit() },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
addEpisodes(response)
|
|
||||||
return episodes
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun episodeListSelector() = "link[rel=canonical]"
|
|
||||||
|
|
||||||
override fun episodeFromElement(element: Element): SEpisode = throw UnsupportedOperationException()
|
|
||||||
|
|
||||||
// ============================ video links ============================
|
|
||||||
override fun videoListRequest(episode: SEpisode): Request {
|
|
||||||
val refererHeaders = headers.newBuilder().apply {
|
|
||||||
add("Referer", "$baseUrl/")
|
|
||||||
}.build()
|
|
||||||
|
|
||||||
return GET("$baseUrl/${episode.url}", headers = refererHeaders)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun videoListSelector() = "div.watch--servers--list ul li.server--item"
|
|
||||||
|
|
||||||
override fun videoListParse(response: Response): List<Video> {
|
|
||||||
val document = response.asJsoup()
|
|
||||||
return document.select(videoListSelector())
|
|
||||||
.parallelCatchingFlatMapBlocking(::extractVideos)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun extractVideos(element: Element): List<Video> {
|
|
||||||
val url = element.attr("data-link")
|
|
||||||
val txt = element.text()
|
|
||||||
return when {
|
|
||||||
"Main" in txt -> {
|
|
||||||
videosFromMain(url)
|
|
||||||
}
|
|
||||||
url.contains("ok") -> {
|
|
||||||
OkruExtractor(client).videosFromUrl(url)
|
|
||||||
}
|
|
||||||
"Vidbom" in txt || "Vidshare" in txt || "Govid" in txt -> {
|
|
||||||
VidBomExtractor(client).videosFromUrl(url)
|
|
||||||
}
|
|
||||||
"Doodstream" in txt -> {
|
|
||||||
DoodExtractor(client).videoFromUrl(url, "Dood mirror")?.let(::listOf)
|
|
||||||
}
|
|
||||||
url.contains("uqload") -> {
|
|
||||||
UqloadExtractor(client).videosFromUrl(url, "mirror")
|
|
||||||
}
|
|
||||||
url.contains("tape") -> {
|
|
||||||
StreamTapeExtractor(client).videoFromUrl(url)?.let(::listOf)
|
|
||||||
}
|
|
||||||
"Upstream" in txt || "Streamruby" in txt || "Streamwish" in txt -> {
|
|
||||||
videosFromOthers(url, txt)
|
|
||||||
}
|
|
||||||
else -> null
|
|
||||||
} ?: emptyList()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun List<Video>.sort(): List<Video> {
|
|
||||||
val quality = preferences.getString("preferred_quality", null)!!
|
|
||||||
return sortedWith(
|
|
||||||
compareBy { it.quality.contains(quality) },
|
|
||||||
).reversed()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun videoUrlParse(document: Document) = throw UnsupportedOperationException()
|
|
||||||
|
|
||||||
override fun videoFromElement(element: Element) = throw UnsupportedOperationException()
|
|
||||||
|
|
||||||
private fun videosFromMain(url: String): List<Video> {
|
|
||||||
val jsE = client.newCall(GET(url)).execute().asJsoup().selectFirst("script:containsData(player)")!!.data()
|
|
||||||
val fileLinks = JsUnpacker.unpackAndCombine(jsE)!!.substringAfter("file").substringBefore("\",")
|
|
||||||
return Regex("\\[(.*?)\\](.*?mp4)").findAll(fileLinks).map {
|
|
||||||
Video(it.groupValues[2], "Main: " + it.groupValues[1], it.groupValues[2])
|
|
||||||
}.toList()
|
|
||||||
}
|
|
||||||
private fun videosFromOthers(url: String, prefix: String): List<Video> {
|
|
||||||
val jsE = client.newCall(GET(url)).execute().asJsoup().selectFirst("script:containsData(source)")!!.data()
|
|
||||||
val masterUrl = JsUnpacker.unpackAndCombine(jsE)!!.substringAfter("file").substringAfter("\"").substringBefore("\"")
|
|
||||||
return PlaylistUtils(client).extractFromHls(masterUrl, url, videoNameGen = { "$prefix - $it" })
|
|
||||||
}
|
|
||||||
// ============================ search ============================
|
|
||||||
|
|
||||||
override fun searchAnimeSelector(): String = "div.Block--Item"
|
|
||||||
|
|
||||||
override fun searchAnimeNextPageSelector(): String = "div.pagination ul.page-numbers li a.next"
|
|
||||||
|
|
||||||
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
|
|
||||||
val url = if (query.isNotBlank()) {
|
|
||||||
"$baseUrl/?s=$query&page=$page"
|
|
||||||
} else {
|
|
||||||
val url = baseUrl
|
|
||||||
(if (filters.isEmpty()) getFilterList() else filters).forEach { filter ->
|
|
||||||
when (filter) {
|
|
||||||
is CategoryList -> {
|
|
||||||
if (filter.state > 0) {
|
|
||||||
val catQ = getCategoryList()[filter.state].query
|
|
||||||
val catUrl = "$baseUrl/$catQ/?page=$page/"
|
|
||||||
return GET(catUrl, headers)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return GET(url, headers)
|
|
||||||
}
|
|
||||||
return GET(url, headers)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun searchAnimeFromElement(element: Element): SAnime {
|
|
||||||
val anime = SAnime.create()
|
|
||||||
anime.title = titleEdit(element.select("h3").text(), true).trim()
|
|
||||||
anime.thumbnail_url = element.select("img").attr(if (element.ownerDocument()!!.location().contains("?s="))"src" else "data-src")
|
|
||||||
anime.setUrlWithoutDomain(element.select("a").attr("href") + "watch/")
|
|
||||||
return anime
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================ details ============================
|
|
||||||
|
|
||||||
override fun animeDetailsParse(document: Document): SAnime {
|
|
||||||
val anime = SAnime.create()
|
|
||||||
anime.genre = document.select("div.catssection li a").joinToString(", ") { it.text() }
|
|
||||||
anime.title = titleEdit(document.select("h1.post-title").text()).trim()
|
|
||||||
anime.author = document.select("ul.RightTaxContent li:contains(دولة) a").text()
|
|
||||||
anime.description = document.select("div.story").text().trim()
|
|
||||||
anime.status = SAnime.COMPLETED
|
|
||||||
anime.thumbnail_url = document.select("div.left div.image img").attr("src")
|
|
||||||
return anime
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================ latest ============================
|
|
||||||
|
|
||||||
override fun latestUpdatesSelector(): String = "div.Block--Item"
|
|
||||||
|
|
||||||
override fun latestUpdatesNextPageSelector(): String = "div.pagination ul.page-numbers li a.next"
|
|
||||||
|
|
||||||
override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/recent/page/$page/")
|
|
||||||
|
|
||||||
override fun latestUpdatesFromElement(element: Element): SAnime {
|
|
||||||
val anime = SAnime.create()
|
|
||||||
anime.title = titleEdit(element.select("a").attr("title"), true).trim()
|
|
||||||
anime.thumbnail_url = element.select("img").attr("data-src")
|
|
||||||
anime.setUrlWithoutDomain(element.select("a").attr("href") + "watch/")
|
|
||||||
return anime
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================ filters ============================
|
|
||||||
|
|
||||||
override fun getFilterList() = AnimeFilterList(
|
|
||||||
CategoryList(categoriesName),
|
|
||||||
)
|
|
||||||
|
|
||||||
private class CategoryList(categories: Array<String>) : AnimeFilter.Select<String>("الأقسام", categories)
|
|
||||||
private data class CatUnit(val name: String, val query: String)
|
|
||||||
private val categoriesName = getCategoryList().map {
|
|
||||||
it.name
|
|
||||||
}.toTypedArray()
|
|
||||||
|
|
||||||
private fun getCategoryList() = listOf(
|
|
||||||
CatUnit("اختر", ""),
|
|
||||||
CatUnit("كل الافلام", "category/movies-33/"),
|
|
||||||
CatUnit("افلام اجنبى", "category/movies-33/افلام-اجنبي/"),
|
|
||||||
CatUnit("افلام انمى", "category/anime-6/افلام-انمي/"),
|
|
||||||
CatUnit("افلام تركيه", "category/movies-33/افلام-تركي/"),
|
|
||||||
CatUnit("افلام اسيويه", "category/movies-33/افلام-اسيوي/"),
|
|
||||||
CatUnit("افلام هنديه", "category/movies-33/افلام-هندى/"),
|
|
||||||
CatUnit("كل المسسلسلات", "category/series-9/"),
|
|
||||||
CatUnit("مسلسلات اجنبى", "category/series-9/مسلسلات-اجنبي/"),
|
|
||||||
CatUnit("مسلسلات انمى", "category/anime-6/انمي-مترجم/"),
|
|
||||||
CatUnit("مسلسلات تركى", "category/series-9/مسلسلات-تركي/"),
|
|
||||||
CatUnit("مسلسلات اسيوى", "category/series-9/مسلسلات-أسيوي/"),
|
|
||||||
CatUnit("مسلسلات هندى", "category/series-9/مسلسلات-هندي/"),
|
|
||||||
)
|
|
||||||
|
|
||||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
|
||||||
val videoQualityPref = ListPreference(screen.context).apply {
|
|
||||||
key = "preferred_quality"
|
|
||||||
title = "Preferred quality"
|
|
||||||
entries = arrayOf("720p", "480p", "Low", "Normal", "HD", "UHD", "DoodStream", "Uqload")
|
|
||||||
entryValues = arrayOf("720", "480", "Low", "Normal", "HD", "UHD", "Dood", "Uqload")
|
|
||||||
setDefaultValue("1080")
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
screen.addPreference(videoQualityPref)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
ext {
|
|
||||||
extName = 'XS Anime'
|
|
||||||
extClass = '.XsAnime'
|
|
||||||
extVersionCode = 10
|
|
||||||
}
|
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
|
Before Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 5.3 KiB |
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 14 KiB |
|
@ -1,225 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.animeextension.ar.xsanime
|
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import android.content.SharedPreferences
|
|
||||||
import androidx.preference.ListPreference
|
|
||||||
import androidx.preference.PreferenceScreen
|
|
||||||
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
|
|
||||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
|
|
||||||
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.Headers
|
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
|
||||||
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 XsAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|
||||||
|
|
||||||
override val name = "XS Anime"
|
|
||||||
|
|
||||||
override val baseUrl = "https://ww.xsanime.com"
|
|
||||||
|
|
||||||
override val lang = "ar"
|
|
||||||
|
|
||||||
override val supportsLatest = false
|
|
||||||
|
|
||||||
private val preferences: SharedPreferences by lazy {
|
|
||||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Popular Anime
|
|
||||||
override fun popularAnimeSelector(): String = "ul.boxes--holder div.itemtype_anime a"
|
|
||||||
|
|
||||||
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/anime_list/page/$page")
|
|
||||||
|
|
||||||
override fun popularAnimeFromElement(element: Element): SAnime {
|
|
||||||
val anime = SAnime.create()
|
|
||||||
anime.setUrlWithoutDomain(element.attr("href"))
|
|
||||||
anime.title = element.attr("title")
|
|
||||||
anime.thumbnail_url = element.selectFirst("div.itemtype_anime_poster img")!!.attr("data-src")
|
|
||||||
return anime
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun popularAnimeNextPageSelector(): String = "ul.page-numbers li a.next"
|
|
||||||
|
|
||||||
// Episodes
|
|
||||||
override fun episodeListSelector() = "div.EpisodesList > a"
|
|
||||||
|
|
||||||
override fun episodeFromElement(element: Element): SEpisode {
|
|
||||||
val episode = SEpisode.create()
|
|
||||||
val epNum = getNumberFromEpsString(element.select("a > em").text())
|
|
||||||
episode.setUrlWithoutDomain(element.attr("abs:href"))
|
|
||||||
episode.name = element.select("a > em").text()
|
|
||||||
episode.episode_number = when {
|
|
||||||
epNum.isNotEmpty() -> epNum.toFloatOrNull() ?: 1F
|
|
||||||
else -> 1F
|
|
||||||
}
|
|
||||||
|
|
||||||
return episode
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getNumberFromEpsString(epsStr: String): String {
|
|
||||||
return epsStr.filter { it.isDigit() }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Video Links
|
|
||||||
|
|
||||||
override fun videoListParse(response: Response): List<Video> {
|
|
||||||
val document = response.asJsoup()
|
|
||||||
val srcVid = preferences.getString("preferred_quality", "الجودة العالية")!!
|
|
||||||
val iframe = document.select("div.downloads ul div.listServ:contains($srcVid) div.serL a[href~=4shared]").attr("href").substringBeforeLast("/").replace("video", "web/embed/file")
|
|
||||||
val referer = response.request.url.toString()
|
|
||||||
val refererHeaders = Headers.headersOf("referer", referer)
|
|
||||||
val iframeResponse = client.newCall(GET(iframe, refererHeaders))
|
|
||||||
.execute().asJsoup()
|
|
||||||
return iframeResponse.select(videoListSelector()).map { videoFromElement(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun videoListSelector() = "source"
|
|
||||||
|
|
||||||
override fun videoFromElement(element: Element): Video {
|
|
||||||
element.attr("src")
|
|
||||||
return Video(element.attr("src"), "Default: If you want to change the quality go to extension settings", element.attr("src"))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun videoUrlParse(document: Document) = throw UnsupportedOperationException()
|
|
||||||
|
|
||||||
// Search
|
|
||||||
|
|
||||||
override fun searchAnimeFromElement(element: Element): SAnime {
|
|
||||||
val anime = SAnime.create()
|
|
||||||
anime.setUrlWithoutDomain(element.attr("href"))
|
|
||||||
anime.title = element.attr("title")
|
|
||||||
anime.thumbnail_url = element.selectFirst("div.itemtype_anime_poster img")!!.attr("data-src")
|
|
||||||
return anime
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun searchAnimeNextPageSelector(): String = "ul.page-numbers li a.next"
|
|
||||||
|
|
||||||
override fun searchAnimeSelector(): String = "ul.boxes--holder div.itemtype_anime a"
|
|
||||||
|
|
||||||
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
|
|
||||||
return if (query.isNotBlank()) {
|
|
||||||
GET("$baseUrl/?s=$query&type=anime&page=$page", headers)
|
|
||||||
} else {
|
|
||||||
val url = "$baseUrl/anime_list/page/$page/?".toHttpUrlOrNull()!!.newBuilder()
|
|
||||||
filters.forEach { filter ->
|
|
||||||
when (filter) {
|
|
||||||
// is SeasonFilter -> url.addQueryParameter("season", filter.toUriPart())
|
|
||||||
is GenreFilter -> url.addQueryParameter("genre", filter.toUriPart())
|
|
||||||
is StatusFilter -> url.addQueryParameter("status", filter.toUriPart())
|
|
||||||
// is LetterFilter -> url.addQueryParameter("letter", filter.toUriPart())
|
|
||||||
else -> {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
GET(url.build().toString(), headers)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Anime Details
|
|
||||||
|
|
||||||
override fun animeDetailsParse(document: Document): SAnime {
|
|
||||||
val anime = SAnime.create()
|
|
||||||
anime.thumbnail_url = document.selectFirst("div.inner--image img")!!.attr("src")
|
|
||||||
anime.title = document.select("h1.post--inner-title").text()
|
|
||||||
anime.genre = document.select("ul.terms--and--metas > li:contains(تصنيفات الأنمي) > a").joinToString(", ") { it.text() }
|
|
||||||
anime.description = document.select("div.post--content--inner").text()
|
|
||||||
document.select("ul.terms--and--metas li:contains(عدد الحلقات) a").text()?.also { statusText ->
|
|
||||||
when {
|
|
||||||
statusText.contains("غير معروف", true) -> anime.status = SAnime.ONGOING
|
|
||||||
else -> anime.status = SAnime.COMPLETED
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return anime
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filters
|
|
||||||
|
|
||||||
override fun getFilterList() = AnimeFilterList(
|
|
||||||
AnimeFilter.Header("NOTE: Ignored if using text search!"),
|
|
||||||
AnimeFilter.Separator(),
|
|
||||||
GenreFilter(getGenreFilters()),
|
|
||||||
StatusFilter(getStatusFilters()),
|
|
||||||
// SeasonFilter(getStatusFilters()),
|
|
||||||
// LetterFilter(getLetterFilter()),
|
|
||||||
)
|
|
||||||
|
|
||||||
private class StatusFilter(vals: Array<Pair<String?, String>>) : UriPartFilter("حالة الأنمي", vals)
|
|
||||||
private class GenreFilter(vals: Array<Pair<String?, String>>) : UriPartFilter("تصنيفات الانمى", vals)
|
|
||||||
// private class SeasonFilter(vals: Array<Pair<String?, String>>) : UriPartFilter("موسم الانمى", vals)
|
|
||||||
// private class LetterFilter(vals: Array<Pair<String?, String>>) : UriPartFilter("الحرف", vals)
|
|
||||||
|
|
||||||
private fun getStatusFilters(): Array<Pair<String?, String>> = arrayOf(
|
|
||||||
Pair("", "<اختر>"),
|
|
||||||
Pair("مستمر", "مستمر"),
|
|
||||||
Pair("منتهي", "منتهي"),
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun getGenreFilters(): Array<Pair<String?, String>> = arrayOf(
|
|
||||||
Pair("", "<اختر>"),
|
|
||||||
Pair("أكشن", "أكشن"),
|
|
||||||
Pair("تاريخي", "تاريخي"),
|
|
||||||
Pair("حريم", "حريم"),
|
|
||||||
Pair("خارق للطبيعة", "خارق للطبيعة"),
|
|
||||||
Pair("خيال", "خيال"),
|
|
||||||
Pair("دراما", "دراما"),
|
|
||||||
Pair("رومانسي", "رومانسي"),
|
|
||||||
Pair("رياضي", "رياضي"),
|
|
||||||
Pair("سينين", "سينين"),
|
|
||||||
Pair("شونين", "شونين"),
|
|
||||||
Pair("شياطين", "شياطين"),
|
|
||||||
Pair("غموض", "غموض"),
|
|
||||||
Pair("قوى خارقة", "قوى خارقة"),
|
|
||||||
Pair("كوميدي", "كوميدي"),
|
|
||||||
Pair("لعبة", "لعبة"),
|
|
||||||
Pair("مدرسي", "مدرسي"),
|
|
||||||
Pair("مغامرات", "مغامرات"),
|
|
||||||
Pair("موسيقي", "موسيقي"),
|
|
||||||
Pair("نفسي", "نفسي"),
|
|
||||||
)
|
|
||||||
|
|
||||||
open class UriPartFilter(displayName: String, private val vals: Array<Pair<String?, String>>) :
|
|
||||||
AnimeFilter.Select<String>(displayName, vals.map { it.second }.toTypedArray()) {
|
|
||||||
fun toUriPart() = vals[state].first
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 qualityPref = ListPreference(screen.context).apply {
|
|
||||||
key = "preferred_quality"
|
|
||||||
title = "Preferred Quality"
|
|
||||||
entries = arrayOf("الجودة العالية", "الجودة الخارقة", "الجودة المتوسطة")
|
|
||||||
entryValues = arrayOf("الجودة العالية", "الجودة الخارقة", "الجودة المتوسطة")
|
|
||||||
setDefaultValue("الجودة العالية")
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
screen.addPreference(qualityPref)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
ext {
|
|
||||||
extName = 'XS Movie'
|
|
||||||
extClass = '.XsMovie'
|
|
||||||
extVersionCode = 5
|
|
||||||
}
|
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
|
Before Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 6.4 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 16 KiB |
|
@ -1,149 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.animeextension.ar.xsmovie
|
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import android.content.SharedPreferences
|
|
||||||
import androidx.preference.ListPreference
|
|
||||||
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.network.GET
|
|
||||||
import eu.kanade.tachiyomi.util.asJsoup
|
|
||||||
import okhttp3.Headers
|
|
||||||
import okhttp3.Request
|
|
||||||
import okhttp3.Response
|
|
||||||
import org.jsoup.nodes.Document
|
|
||||||
import org.jsoup.nodes.Element
|
|
||||||
import uy.kohesive.injekt.Injekt
|
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
|
|
||||||
class XsMovie : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|
||||||
|
|
||||||
override val name = "XS Movie"
|
|
||||||
|
|
||||||
override val baseUrl = "https://ww.xsanime.com"
|
|
||||||
|
|
||||||
override val lang = "ar"
|
|
||||||
|
|
||||||
override val supportsLatest = false
|
|
||||||
|
|
||||||
private val preferences: SharedPreferences by lazy {
|
|
||||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Popular Anime
|
|
||||||
override fun popularAnimeSelector(): String = "ul.boxes--holder div.itemtype_anime a"
|
|
||||||
|
|
||||||
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/movies_list/page/$page")
|
|
||||||
|
|
||||||
override fun popularAnimeFromElement(element: Element): SAnime {
|
|
||||||
val anime = SAnime.create()
|
|
||||||
anime.setUrlWithoutDomain(element.attr("href"))
|
|
||||||
anime.title = element.attr("title")
|
|
||||||
anime.thumbnail_url = element.selectFirst("div.itemtype_anime_poster img")!!.attr("data-src")
|
|
||||||
return anime
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun popularAnimeNextPageSelector(): String = "ul.page-numbers li a.next"
|
|
||||||
|
|
||||||
// Episodes
|
|
||||||
override fun episodeListSelector() = "h1.post--inner-title > a"
|
|
||||||
|
|
||||||
override fun episodeFromElement(element: Element): SEpisode {
|
|
||||||
val episode = SEpisode.create()
|
|
||||||
episode.setUrlWithoutDomain(element.attr("href"))
|
|
||||||
episode.name = element.text().replace("فيلم ", "").replace("مترجم ", "").replace("اون لاين ", "").replace("بلوراي", "") // "movie"
|
|
||||||
|
|
||||||
return episode
|
|
||||||
}
|
|
||||||
|
|
||||||
// Video Links
|
|
||||||
|
|
||||||
override fun videoListParse(response: Response): List<Video> {
|
|
||||||
val document = response.asJsoup()
|
|
||||||
val srcVid = preferences.getString("preferred_quality", "الجودة العالية")!!
|
|
||||||
val iframe = document.select("div.downloads ul div.listServ:contains($srcVid) div.serL a[href~=4shared]").attr("href").substringBeforeLast("/").replace("video", "web/embed/file")
|
|
||||||
val referer = response.request.url.toString()
|
|
||||||
val refererHeaders = Headers.headersOf("referer", referer)
|
|
||||||
val iframeResponse = client.newCall(GET(iframe, refererHeaders))
|
|
||||||
.execute().asJsoup()
|
|
||||||
return iframeResponse.select(videoListSelector()).map { videoFromElement(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun videoListSelector() = "source"
|
|
||||||
|
|
||||||
override fun videoFromElement(element: Element): Video {
|
|
||||||
return Video(element.attr("src"), "Default: If you want to change the quality go to extension settings", element.attr("src"))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun videoUrlParse(document: Document) = throw UnsupportedOperationException()
|
|
||||||
|
|
||||||
// Search
|
|
||||||
|
|
||||||
override fun searchAnimeFromElement(element: Element): SAnime {
|
|
||||||
val anime = SAnime.create()
|
|
||||||
anime.setUrlWithoutDomain(element.attr("href"))
|
|
||||||
anime.title = element.attr("title")
|
|
||||||
anime.thumbnail_url = element.selectFirst("div.itemtype_anime_poster img")!!.attr("data-src")
|
|
||||||
return anime
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun searchAnimeNextPageSelector(): String = "ul.page-numbers li a.next"
|
|
||||||
|
|
||||||
override fun searchAnimeSelector(): String = "ul.boxes--holder div.itemtype_anime a"
|
|
||||||
|
|
||||||
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
|
|
||||||
return GET("$baseUrl/?s=$query&type=movie&page=$page", headers)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Anime Details
|
|
||||||
|
|
||||||
override fun animeDetailsParse(document: Document): SAnime {
|
|
||||||
val anime = SAnime.create()
|
|
||||||
anime.thumbnail_url = document.selectFirst("div.inner--image img")!!.attr("src")
|
|
||||||
anime.title = document.select("h1.post--inner-title").text()
|
|
||||||
anime.genre = document.select("ul.terms--and--metas > li:contains(تصنيفات الأنمي) > a").joinToString(", ") { it.text() }
|
|
||||||
anime.description = document.select("div.post--content--inner").text()
|
|
||||||
document.select("ul.terms--and--metas li:contains(عدد الحلقات) a").text()?.also { statusText ->
|
|
||||||
when {
|
|
||||||
statusText.contains("غير معروف", true) -> anime.status = SAnime.ONGOING
|
|
||||||
else -> 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 qualityPref = ListPreference(screen.context).apply {
|
|
||||||
key = "preferred_quality"
|
|
||||||
title = "Preferred Quality"
|
|
||||||
entries = arrayOf("الجودة العالية", "الجودة الخارقة", "الجودة المتوسطة")
|
|
||||||
entryValues = arrayOf("الجودة العالية", "الجودة الخارقة", "الجودة المتوسطة")
|
|
||||||
setDefaultValue("الجودة العالية")
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
screen.addPreference(qualityPref)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
ext {
|
|
||||||
extName = 'CineClix'
|
|
||||||
extClass = '.CineClix'
|
|
||||||
extVersionCode = 18
|
|
||||||
}
|
|
||||||
|
|
||||||
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(libs.jsunpacker)
|
|
||||||
}
|
|
Before Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 6 KiB |
Before Width: | Height: | Size: 9.2 KiB |
|
@ -1,364 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
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() }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
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() }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
ext {
|
|
||||||
extName = 'Movie2k'
|
|
||||||
extClass = '.Movie2k'
|
|
||||||
extVersionCode = 8
|
|
||||||
}
|
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation(project(':lib:dood-extractor'))
|
|
||||||
implementation(project(':lib:streamtape-extractor'))
|
|
||||||
implementation(project(':lib:mixdrop-extractor'))
|
|
||||||
implementation(libs.jsunpacker)
|
|
||||||
}
|
|
Before Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 6 KiB |
Before Width: | Height: | Size: 9.1 KiB |
|
@ -1,259 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.animeextension.de.movie2k
|
|
||||||
|
|
||||||
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.movie2k.extractors.DroploadExtractor
|
|
||||||
import eu.kanade.tachiyomi.animeextension.de.movie2k.extractors.UpstreamExtractor
|
|
||||||
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
|
|
||||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
|
||||||
import eu.kanade.tachiyomi.animesource.model.SAnime
|
|
||||||
import eu.kanade.tachiyomi.animesource.model.SEpisode
|
|
||||||
import eu.kanade.tachiyomi.animesource.model.Video
|
|
||||||
import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
|
|
||||||
import eu.kanade.tachiyomi.lib.doodextractor.DoodExtractor
|
|
||||||
import eu.kanade.tachiyomi.lib.mixdropextractor.MixDropExtractor
|
|
||||||
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
|
|
||||||
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
|
|
||||||
|
|
||||||
class Movie2k : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|
||||||
|
|
||||||
override val name = "Movie2k"
|
|
||||||
|
|
||||||
override val baseUrl = "https://movie2k.skin"
|
|
||||||
|
|
||||||
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.item-container div.item"
|
|
||||||
|
|
||||||
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/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 div.item-inner img").attr("data-src")
|
|
||||||
anime.title = element.select("a div.item-inner img").attr("alt")
|
|
||||||
return anime
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun popularAnimeNextPageSelector(): String = "div.pagination a.next"
|
|
||||||
|
|
||||||
// episodes
|
|
||||||
|
|
||||||
override fun episodeListSelector() = throw UnsupportedOperationException()
|
|
||||||
|
|
||||||
override fun episodeListParse(response: Response): List<SEpisode> {
|
|
||||||
val document = response.asJsoup()
|
|
||||||
val episodeList = mutableListOf<SEpisode>()
|
|
||||||
val episode = SEpisode.create()
|
|
||||||
episode.episode_number = 1F
|
|
||||||
episode.name = "Film"
|
|
||||||
val hostdoc = client.newCall(GET(document.select("#multiplayer a").attr("href"))).execute().asJsoup()
|
|
||||||
episode.url = hostdoc.select("#video-container div.server1 iframe").attr("src")
|
|
||||||
episodeList.add(episode)
|
|
||||||
return episodeList.reversed()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun episodeFromElement(element: Element): SEpisode = throw UnsupportedOperationException()
|
|
||||||
|
|
||||||
// Video Extractor
|
|
||||||
|
|
||||||
override fun videoListRequest(episode: SEpisode): Request {
|
|
||||||
return GET(episode.url.replace(baseUrl, ""))
|
|
||||||
}
|
|
||||||
|
|
||||||
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("dood", "stape", "mix", "up", "drop"))
|
|
||||||
document.select("ul._player-mirrors li").forEach {
|
|
||||||
val purl = it.attr("data-link")
|
|
||||||
when {
|
|
||||||
purl.contains("//dood") && hosterSelection?.contains("dood") == true -> {
|
|
||||||
val quality = "Doodstream"
|
|
||||||
if (!purl.contains("https://")) {
|
|
||||||
val url = "https:$purl"
|
|
||||||
val video = DoodExtractor(client).videoFromUrl(url, quality)
|
|
||||||
if (video != null) {
|
|
||||||
videoList.add(video)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
val video = DoodExtractor(client).videoFromUrl(purl, quality)
|
|
||||||
if (video != null) {
|
|
||||||
videoList.add(video)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
purl.contains("//streamtape.com") && hosterSelection?.contains("stape") == true -> {
|
|
||||||
val quality = "Streamtape"
|
|
||||||
if (!purl.contains("https://")) {
|
|
||||||
val url = "https:$purl"
|
|
||||||
val video = StreamTapeExtractor(client).videoFromUrl(url, quality)
|
|
||||||
if (video != null) {
|
|
||||||
videoList.add(video)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
val video = StreamTapeExtractor(client).videoFromUrl(purl, quality)
|
|
||||||
if (video != null) {
|
|
||||||
videoList.add(video)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
purl.contains("//mixdrop") && hosterSelection?.contains("mix") == true -> {
|
|
||||||
if (!purl.contains("https://")) {
|
|
||||||
val url = "https:$purl"
|
|
||||||
val videos = MixDropExtractor(client).videoFromUrl(url)
|
|
||||||
videoList.addAll(videos)
|
|
||||||
} else {
|
|
||||||
val videos = MixDropExtractor(client).videoFromUrl(purl)
|
|
||||||
videoList.addAll(videos)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
purl.contains("//upstream") && hosterSelection?.contains("up") == true -> {
|
|
||||||
if (!purl.contains("https://")) {
|
|
||||||
val url = "https:$purl"
|
|
||||||
val videos = UpstreamExtractor(client).videoFromUrl(url)
|
|
||||||
if (videos != null) {
|
|
||||||
videoList.addAll(videos)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
val videos = UpstreamExtractor(client).videoFromUrl(purl)
|
|
||||||
if (videos != null) {
|
|
||||||
videoList.addAll(videos)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
purl.contains("//dropload") && hosterSelection?.contains("drop") == true -> {
|
|
||||||
if (!purl.contains("https://")) {
|
|
||||||
val url = "https:$purl"
|
|
||||||
val videos = DroploadExtractor(client).videoFromUrl(url)
|
|
||||||
if (videos != null) {
|
|
||||||
videoList.addAll(videos)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
val videos = DroploadExtractor(client).videoFromUrl(purl)
|
|
||||||
if (videos != null) {
|
|
||||||
videoList.addAll(videos)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return videoList
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun List<Video>.sort(): List<Video> {
|
|
||||||
val hoster = preferences.getString("preferred_hoster", null)
|
|
||||||
if (hoster != null) {
|
|
||||||
val newList = mutableListOf<Video>()
|
|
||||||
var preferred = 0
|
|
||||||
for (video in this) {
|
|
||||||
if (video.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("a").attr("href"))
|
|
||||||
anime.thumbnail_url = element.select("a div.item-inner img").attr("data-src")
|
|
||||||
anime.title = element.select("a div.item-inner img").attr("alt")
|
|
||||||
return anime
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun searchAnimeNextPageSelector(): String = "div.pagination a.next"
|
|
||||||
|
|
||||||
override fun searchAnimeSelector(): String = "div.item-container div.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.movie-image img").attr("src")
|
|
||||||
anime.title = document.select("div.movie-image img").attr("alt")
|
|
||||||
anime.description = document.select("p.movie-description span").text()
|
|
||||||
anime.author = document.select("span[itemprop=\"director\"] a").joinToString(", ") { it.text() }
|
|
||||||
anime.genre = document.select("span[itemprop=\"genre\"] 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", "Streamtape", "Mixdrop", "Upstream", "Dropload")
|
|
||||||
entryValues = arrayOf("https://dood", "https://streamtape", "https://mixdrop", "https://upstream", "https://dropload")
|
|
||||||
setDefaultValue("https://dood")
|
|
||||||
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("Doodstream", "Streamtape", "MixDrop", "Upstream", "Dropload")
|
|
||||||
entryValues = arrayOf("dood", "stape", "mix", "up", "drop")
|
|
||||||
setDefaultValue(setOf("dood", "stape", "mix", "up", "drop"))
|
|
||||||
|
|
||||||
setOnPreferenceChangeListener { _, newValue ->
|
|
||||||
preferences.edit().putStringSet(key, newValue as Set<String>).commit()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
screen.addPreference(hosterPref)
|
|
||||||
screen.addPreference(subSelection)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,31 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.animeextension.de.movie2k.extractors
|
|
||||||
|
|
||||||
import dev.datlag.jsunpacker.JsUnpacker
|
|
||||||
import eu.kanade.tachiyomi.animesource.model.Video
|
|
||||||
import eu.kanade.tachiyomi.network.GET
|
|
||||||
import eu.kanade.tachiyomi.util.asJsoup
|
|
||||||
import okhttp3.Headers
|
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
|
|
||||||
class DroploadExtractor(private val client: OkHttpClient) {
|
|
||||||
|
|
||||||
fun videoFromUrl(url: String): MutableList<Video>? {
|
|
||||||
try {
|
|
||||||
val jsE = client.newCall(GET(url)).execute().asJsoup().selectFirst("script:containsData(eval)")!!.data()
|
|
||||||
val masterUrl = JsUnpacker.unpackAndCombine(jsE).toString()
|
|
||||||
.substringAfter("{file:\"").substringBefore("\"}")
|
|
||||||
val masterBase = masterUrl.substringBefore("master")
|
|
||||||
val masterPlaylist = client.newCall(GET(masterUrl)).execute().body.string()
|
|
||||||
val videoList = mutableListOf<Video>()
|
|
||||||
masterPlaylist.substringAfter("#EXT-X-STREAM-INF:").split("#EXT-X-STREAM-INF:")
|
|
||||||
.forEach {
|
|
||||||
val quality = "Dropload:" + it.substringAfter("RESOLUTION=").substringAfter("x").substringBefore(",") + "p "
|
|
||||||
val videoUrl = masterBase + it.substringAfter("\n").substringBefore("\n")
|
|
||||||
videoList.add(Video(videoUrl, quality, videoUrl, headers = Headers.headersOf("origin", "https://dropload.io", "referer", "https://dropload.io/")))
|
|
||||||
}
|
|
||||||
return videoList
|
|
||||||
} catch (e: Exception) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,31 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.animeextension.de.movie2k.extractors
|
|
||||||
|
|
||||||
import dev.datlag.jsunpacker.JsUnpacker
|
|
||||||
import eu.kanade.tachiyomi.animesource.model.Video
|
|
||||||
import eu.kanade.tachiyomi.network.GET
|
|
||||||
import eu.kanade.tachiyomi.util.asJsoup
|
|
||||||
import okhttp3.Headers
|
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
|
|
||||||
class UpstreamExtractor(private val client: OkHttpClient) {
|
|
||||||
|
|
||||||
fun videoFromUrl(url: String): MutableList<Video>? {
|
|
||||||
try {
|
|
||||||
val jsE = client.newCall(GET(url)).execute().asJsoup().selectFirst("script:containsData(eval)")!!.data()
|
|
||||||
val masterUrl = JsUnpacker.unpackAndCombine(jsE).toString()
|
|
||||||
.substringAfter("{file:\"").substringBefore("\"}")
|
|
||||||
val masterBase = masterUrl.substringBefore("master")
|
|
||||||
val masterPlaylist = client.newCall(GET(masterUrl)).execute().body.string()
|
|
||||||
val videoList = mutableListOf<Video>()
|
|
||||||
masterPlaylist.substringAfter("#EXT-X-STREAM-INF:").split("#EXT-X-STREAM-INF:")
|
|
||||||
.forEach {
|
|
||||||
val quality = "Upstream:" + it.substringAfter("RESOLUTION=").substringAfter("x").substringBefore(",") + "p "
|
|
||||||
val videoUrl = masterBase + it.substringAfter("\n").substringBefore("\n")
|
|
||||||
videoList.add(Video(videoUrl, quality, videoUrl, headers = Headers.headersOf("origin", "https://upstream.to", "referer", "https://upstream.to/")))
|
|
||||||
}
|
|
||||||
return videoList
|
|
||||||
} catch (e: Exception) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
ext {
|
|
||||||
extName = 'StreamCloud'
|
|
||||||
extClass = '.StreamCloud'
|
|
||||||
extVersionCode = 10
|
|
||||||
}
|
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation(project(':lib:dood-extractor'))
|
|
||||||
implementation(project(':lib:streamtape-extractor'))
|
|
||||||
implementation(project(':lib:mixdrop-extractor'))
|
|
||||||
}
|
|
Before Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 17 KiB |
|
@ -1,198 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.animeextension.de.streamcloud
|
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
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.mixdropextractor.MixDropExtractor
|
|
||||||
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
|
|
||||||
import eu.kanade.tachiyomi.network.GET
|
|
||||||
import eu.kanade.tachiyomi.util.asJsoup
|
|
||||||
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 StreamCloud : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|
||||||
|
|
||||||
override val name = "StreamCloud"
|
|
||||||
|
|
||||||
override val baseUrl = "https://streamcloud.movie"
|
|
||||||
|
|
||||||
override val lang = "de"
|
|
||||||
|
|
||||||
override val supportsLatest = false
|
|
||||||
|
|
||||||
private val preferences by lazy {
|
|
||||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================== Popular ===============================
|
|
||||||
override fun popularAnimeRequest(page: Int) = GET("$baseUrl/beliebte-filme/")
|
|
||||||
|
|
||||||
override fun popularAnimeSelector() = "div#dle-content > div.item > div.thumb > a"
|
|
||||||
|
|
||||||
override fun popularAnimeFromElement(element: Element) = SAnime.create().apply {
|
|
||||||
setUrlWithoutDomain(element.attr("href"))
|
|
||||||
element.selectFirst("img")!!.run {
|
|
||||||
thumbnail_url = absUrl("src")
|
|
||||||
title = attr("alt")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun popularAnimeNextPageSelector() = null
|
|
||||||
|
|
||||||
// =============================== Latest ===============================
|
|
||||||
override fun latestUpdatesNextPageSelector() = throw UnsupportedOperationException()
|
|
||||||
|
|
||||||
override fun latestUpdatesFromElement(element: Element) = throw UnsupportedOperationException()
|
|
||||||
|
|
||||||
override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException()
|
|
||||||
|
|
||||||
override fun latestUpdatesSelector() = throw UnsupportedOperationException()
|
|
||||||
|
|
||||||
// =============================== Search ===============================
|
|
||||||
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList) =
|
|
||||||
GET("$baseUrl/index.php?do=search&subaction=search&search_start=$page&full_search=0&story=$query")
|
|
||||||
|
|
||||||
override fun searchAnimeSelector() = popularAnimeSelector()
|
|
||||||
|
|
||||||
override fun searchAnimeFromElement(element: Element) = popularAnimeFromElement(element)
|
|
||||||
|
|
||||||
override fun searchAnimeNextPageSelector() = "#nextlink"
|
|
||||||
|
|
||||||
// =========================== Anime Details ============================
|
|
||||||
override fun animeDetailsParse(document: Document) = SAnime.create().apply {
|
|
||||||
title = document.selectFirst("#title span.title")!!.text()
|
|
||||||
status = SAnime.COMPLETED
|
|
||||||
with(document.selectFirst("div#longInfo")!!) {
|
|
||||||
thumbnail_url = selectFirst("img")?.absUrl("src")
|
|
||||||
genre = selectFirst("span.masha_index10")?.let {
|
|
||||||
it.text().split("/").joinToString()
|
|
||||||
}
|
|
||||||
description = select("#storyline > span > p").eachText().joinToString("\n")
|
|
||||||
author = selectFirst("strong:contains(Regie:) + div > a")?.text()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================== Episodes ==============================
|
|
||||||
override fun episodeListParse(response: Response): List<SEpisode> {
|
|
||||||
val document = response.asJsoup()
|
|
||||||
val episode = SEpisode.create().apply {
|
|
||||||
name = document.selectFirst("#title span.title")!!.text()
|
|
||||||
episode_number = 1F
|
|
||||||
setUrlWithoutDomain(document.location())
|
|
||||||
}
|
|
||||||
return listOf(episode)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun episodeListSelector() = throw UnsupportedOperationException()
|
|
||||||
|
|
||||||
override fun episodeFromElement(element: Element): SEpisode = throw UnsupportedOperationException()
|
|
||||||
|
|
||||||
// ============================ Video Links =============================
|
|
||||||
private val streamtapeExtractor by lazy { StreamTapeExtractor(client) }
|
|
||||||
private val doodExtractor by lazy { DoodExtractor(client) }
|
|
||||||
private val mixdropExtractor by lazy { MixDropExtractor(client) }
|
|
||||||
|
|
||||||
override fun videoListParse(response: Response): List<Video> {
|
|
||||||
val document = response.asJsoup()
|
|
||||||
val iframeurl = document.selectFirst("div.player-container-wrap > iframe")
|
|
||||||
?.attr("src")
|
|
||||||
?: error("No videos!")
|
|
||||||
val iframeDoc = client.newCall(GET(iframeurl)).execute().asJsoup()
|
|
||||||
|
|
||||||
val hosterSelection = preferences.getStringSet(PREF_HOSTER_SELECTION_KEY, PREF_HOSTER_SELECTION_DEFAULT)!!
|
|
||||||
val items = iframeDoc.select("div._player ul._player-mirrors li")
|
|
||||||
|
|
||||||
return items.flatMap { element ->
|
|
||||||
val url = element.attr("data-link").run {
|
|
||||||
takeIf { startsWith("https") }
|
|
||||||
?: "https:$this"
|
|
||||||
}
|
|
||||||
|
|
||||||
runCatching {
|
|
||||||
when {
|
|
||||||
url.contains("streamtape") && hosterSelection.contains("stape") -> {
|
|
||||||
streamtapeExtractor.videosFromUrl(url)
|
|
||||||
}
|
|
||||||
url.startsWith("https://dood") && hosterSelection.contains("dood") -> {
|
|
||||||
doodExtractor.videosFromUrl(url)
|
|
||||||
}
|
|
||||||
url.contains("mixdrop.") && hosterSelection.contains("mixdrop") -> {
|
|
||||||
mixdropExtractor.videosFromUrl(url)
|
|
||||||
}
|
|
||||||
else -> emptyList()
|
|
||||||
}
|
|
||||||
}.getOrElse { emptyList() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun List<Video>.sort(): List<Video> {
|
|
||||||
val hoster = preferences.getString(PREF_HOSTER_KEY, PREF_HOSTER_DEFAULT)!!
|
|
||||||
|
|
||||||
return sortedWith(
|
|
||||||
compareBy { it.url.contains(hoster) },
|
|
||||||
).reversed()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun videoListSelector() = throw UnsupportedOperationException()
|
|
||||||
|
|
||||||
override fun videoFromElement(element: Element) = throw UnsupportedOperationException()
|
|
||||||
|
|
||||||
override fun videoUrlParse(document: Document) = throw UnsupportedOperationException()
|
|
||||||
|
|
||||||
// ============================== Settings ==============================
|
|
||||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
|
||||||
ListPreference(screen.context).apply {
|
|
||||||
key = PREF_HOSTER_KEY
|
|
||||||
title = PREF_HOSTER_TITLE
|
|
||||||
entries = PREF_HOSTER_ENTRIES
|
|
||||||
entryValues = PREF_HOSTER_VALUES
|
|
||||||
setDefaultValue(PREF_HOSTER_DEFAULT)
|
|
||||||
summary = "%s"
|
|
||||||
|
|
||||||
setOnPreferenceChangeListener { _, newValue ->
|
|
||||||
val selected = newValue as String
|
|
||||||
val index = findIndexOfValue(selected)
|
|
||||||
val entry = entryValues[index] as String
|
|
||||||
preferences.edit().putString(key, entry).commit()
|
|
||||||
}
|
|
||||||
}.also(screen::addPreference)
|
|
||||||
|
|
||||||
MultiSelectListPreference(screen.context).apply {
|
|
||||||
key = PREF_HOSTER_SELECTION_KEY
|
|
||||||
title = PREF_HOSTER_SELECTION_TITLE
|
|
||||||
entries = PREF_HOSTER_SELECTION_ENTRIES
|
|
||||||
entryValues = PREF_HOSTER_SELECTION_VALUES
|
|
||||||
setDefaultValue(PREF_HOSTER_SELECTION_DEFAULT)
|
|
||||||
|
|
||||||
setOnPreferenceChangeListener { _, newValue ->
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
preferences.edit().putStringSet(key, newValue as Set<String>).commit()
|
|
||||||
}
|
|
||||||
}.also(screen::addPreference)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val PREF_HOSTER_KEY = "preferred_hoster"
|
|
||||||
private const val PREF_HOSTER_TITLE = "Standard-Hoster"
|
|
||||||
private const val PREF_HOSTER_DEFAULT = "https://streamtape.com"
|
|
||||||
private val PREF_HOSTER_ENTRIES = arrayOf("Streamtape", "DoodStream")
|
|
||||||
private val PREF_HOSTER_VALUES = arrayOf("https://streamtape.com", "https://dood.")
|
|
||||||
|
|
||||||
private const val PREF_HOSTER_SELECTION_KEY = "hoster_selection"
|
|
||||||
private const val PREF_HOSTER_SELECTION_TITLE = "Hoster auswählen"
|
|
||||||
private val PREF_HOSTER_SELECTION_ENTRIES = arrayOf("Streamtape", "DoodStream", "MixDrop")
|
|
||||||
private val PREF_HOSTER_SELECTION_VALUES = arrayOf("stape", "dood", "mixdrop")
|
|
||||||
private val PREF_HOSTER_SELECTION_DEFAULT by lazy { PREF_HOSTER_SELECTION_VALUES.toSet() }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
ext {
|
|
||||||
extName = 'Pelisflix'
|
|
||||||
extClass = '.PelisflixFactory'
|
|
||||||
extVersionCode = 18
|
|
||||||
}
|
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation(project(':lib:streamtape-extractor'))
|
|
||||||
implementation(project(':lib:dood-extractor'))
|
|
||||||
}
|
|
Before Width: | Height: | Size: 6 KiB |
Before Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 8.8 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 22 KiB |
|
@ -1,267 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.animeextension.es.pelisflix
|
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import android.content.SharedPreferences
|
|
||||||
import android.util.Base64
|
|
||||||
import androidx.preference.ListPreference
|
|
||||||
import androidx.preference.PreferenceScreen
|
|
||||||
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
|
|
||||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
|
|
||||||
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 kotlinx.serialization.json.Json
|
|
||||||
import okhttp3.Request
|
|
||||||
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 uy.kohesive.injekt.injectLazy
|
|
||||||
import java.lang.Exception
|
|
||||||
|
|
||||||
open class Pelisflix(override val name: String, override val baseUrl: String) : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|
||||||
|
|
||||||
override val lang = "es"
|
|
||||||
|
|
||||||
override val supportsLatest = false
|
|
||||||
|
|
||||||
private val json: Json by injectLazy()
|
|
||||||
|
|
||||||
private val preferences: SharedPreferences by lazy {
|
|
||||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/peliculas-online/page/$page")
|
|
||||||
|
|
||||||
override fun popularAnimeSelector(): String = "#Tf-Wp div.TpRwCont ul.MovieList li.TPostMv article.TPost"
|
|
||||||
|
|
||||||
override fun popularAnimeFromElement(element: Element): SAnime {
|
|
||||||
val anime = SAnime.create()
|
|
||||||
anime.setUrlWithoutDomain(element.select("a").attr("href"))
|
|
||||||
anime.title = element.select("a h2.Title").text()
|
|
||||||
anime.thumbnail_url = externalOrInternalImg(element.selectFirst("a div.Image figure.Objf img.imglazy")!!.attr("data-src"))
|
|
||||||
anime.description = element.select("div.TPMvCn div.Description p:nth-child(1)").text().removeSurrounding("\"")
|
|
||||||
return anime
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun popularAnimeNextPageSelector(): String = "nav.wp-pagenavi div.nav-links a ~ a"
|
|
||||||
|
|
||||||
override fun episodeListParse(response: Response): List<SEpisode> {
|
|
||||||
val document = response.asJsoup()
|
|
||||||
val episodeList = mutableListOf<SEpisode>()
|
|
||||||
val movie = document.select("ul.optnslst li div[data-url]")
|
|
||||||
val seasons = document.select("main .SeasonBx a")
|
|
||||||
if (movie.any()) {
|
|
||||||
val movieEp = SEpisode.create()
|
|
||||||
movieEp.name = "Pelicula"
|
|
||||||
movieEp.episode_number = 1f
|
|
||||||
movieEp.setUrlWithoutDomain(response.request.url.toString())
|
|
||||||
episodeList.add(movieEp)
|
|
||||||
} else if (seasons.any()) {
|
|
||||||
val seasonEp = extractEpisodesFromSeasons(seasons)
|
|
||||||
if (seasonEp.isNotEmpty()) episodeList.addAll(seasonEp)
|
|
||||||
}
|
|
||||||
return episodeList.reversed()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun extractEpisodesFromSeasons(seasons: Elements): List<SEpisode> {
|
|
||||||
val episodeList = mutableListOf<SEpisode>()
|
|
||||||
var noEp = 1f
|
|
||||||
var noTemp = 1
|
|
||||||
seasons!!.forEach {
|
|
||||||
var request = client.newCall(GET(it!!.attr("href"))).execute()
|
|
||||||
if (request.isSuccessful) {
|
|
||||||
val document = request.asJsoup()
|
|
||||||
document.select("div.TPTblCn table tbody tr.Viewed")!!.forEach { epContainer ->
|
|
||||||
val urlEp = epContainer.selectFirst("td.MvTbPly > a")!!.attr("href")
|
|
||||||
val ep = SEpisode.create()
|
|
||||||
ep.name = "T$noTemp - Episodio $noEp"
|
|
||||||
ep.episode_number = noEp
|
|
||||||
ep.setUrlWithoutDomain(urlEp)
|
|
||||||
episodeList.add(ep)
|
|
||||||
noEp++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
noTemp++
|
|
||||||
}
|
|
||||||
return episodeList
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun episodeListSelector() = "uwu"
|
|
||||||
|
|
||||||
override fun episodeFromElement(element: Element) = throw UnsupportedOperationException()
|
|
||||||
|
|
||||||
override fun videoListParse(response: Response): List<Video> {
|
|
||||||
val document = response.asJsoup()
|
|
||||||
val videoList = mutableListOf<Video>()
|
|
||||||
document.select("div.TPost.A.D div.Container div.optns-bx div.drpdn button.bstd").forEach { serverList ->
|
|
||||||
serverList.select("ul.optnslst li div[data-url]").forEach {
|
|
||||||
val langTag = it.selectFirst("span:nth-child(2)")!!
|
|
||||||
.text().substringBefore("HD")
|
|
||||||
.substringBefore("SD")
|
|
||||||
.trim()
|
|
||||||
val langVideo = if (langTag.contains("LATINO")) "LAT" else if (langTag.contains("CASTELLANO")) "CAST" else "SUB"
|
|
||||||
val encryptedUrl = it.attr("data-url")
|
|
||||||
val url = String(Base64.decode(encryptedUrl, Base64.DEFAULT))
|
|
||||||
val nuploadDomains = arrayOf("nuuuppp", "nupload")
|
|
||||||
if (nuploadDomains.any { x -> url.contains(x) } && !url.contains("/iframe/")) {
|
|
||||||
nuploadExtractor(langVideo, url).map { video -> videoList.add(video) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return videoList
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun nuploadExtractor(prefix: String, url: String): List<Video> {
|
|
||||||
val videoList = mutableListOf<Video>()
|
|
||||||
val request = client.newCall(GET(url)).execute()
|
|
||||||
if (request.isSuccessful) {
|
|
||||||
val document = request.asJsoup()
|
|
||||||
document.select("script").forEach { script ->
|
|
||||||
if (script!!.data().contains("var sesz=\"")) {
|
|
||||||
val key = script.data().substringAfter("var sesz=\"").substringBefore("\",")
|
|
||||||
var preUrl = script.data().substringAfter("file:\"").substringBefore("\"+")
|
|
||||||
var headersNupload = headers.newBuilder()
|
|
||||||
.set("authority", preUrl.substringAfter("https://").substringBefore("/"))
|
|
||||||
.set("accept-language", "es-MX,es-419;q=0.9,es;q=0.8,en;q=0.7")
|
|
||||||
.set("referer", "https://nupload.co/")
|
|
||||||
.set("dnt", "1")
|
|
||||||
.set("range", "bytes=0-")
|
|
||||||
.set("sec-ch-ua", "\"Chromium\";v=\"104\", \" Not A;Brand\";v=\"99\", \"Google Chrome\";v=\"104\"")
|
|
||||||
.set("sec-fetch-dest", "video")
|
|
||||||
.set("sec-fetch-mode", "no-cors")
|
|
||||||
.set("sec-fetch-site", "cross-site")
|
|
||||||
.set("sec-gpc", "1")
|
|
||||||
.set("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36")
|
|
||||||
.build()
|
|
||||||
videoList.add(Video(preUrl, "$prefix Nupload", preUrl + key, headers = headersNupload))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return videoList
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun videoListSelector() = throw UnsupportedOperationException()
|
|
||||||
|
|
||||||
override fun videoUrlParse(document: Document) = throw UnsupportedOperationException()
|
|
||||||
|
|
||||||
override fun videoFromElement(element: Element) = throw UnsupportedOperationException()
|
|
||||||
|
|
||||||
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
|
|
||||||
val filterList = if (filters.isEmpty()) getFilterList() else filters
|
|
||||||
val genreFilter = filterList.find { it is GenreFilter } as GenreFilter
|
|
||||||
|
|
||||||
return when {
|
|
||||||
query.isNotBlank() -> GET("$baseUrl/?s=$query&page=$page")
|
|
||||||
genreFilter.state != 0 -> GET("$baseUrl/${genreFilter.toUriPart()}/page/$page")
|
|
||||||
else -> popularAnimeRequest(page)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getFilterList(): AnimeFilterList = AnimeFilterList(
|
|
||||||
AnimeFilter.Header("La busqueda por texto ignora el filtro"),
|
|
||||||
GenreFilter(),
|
|
||||||
)
|
|
||||||
|
|
||||||
private class GenreFilter : UriPartFilter(
|
|
||||||
"Géneros",
|
|
||||||
arrayOf(
|
|
||||||
Pair("<Selecionar>", ""),
|
|
||||||
Pair("Peliculas", "peliculas-online"),
|
|
||||||
Pair("Series", "series-online"),
|
|
||||||
Pair("Estrenos", "genero/estrenos"),
|
|
||||||
Pair("Aventura", "genero/aventura"),
|
|
||||||
Pair("Acción", "genero/accion"),
|
|
||||||
Pair("Ciencia ficción", "genero/ciencia-ficcion"),
|
|
||||||
Pair("Comedia", "genero/comedia"),
|
|
||||||
Pair("Crimen", "genero/crimen"),
|
|
||||||
Pair("Drama", "genero/drama"),
|
|
||||||
Pair("Romance", "genero/romance"),
|
|
||||||
Pair("Terror", "genero/terror"),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) :
|
|
||||||
AnimeFilter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
|
|
||||||
fun toUriPart() = vals[state].second
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun searchAnimeFromElement(element: Element): SAnime {
|
|
||||||
return popularAnimeFromElement(element)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun searchAnimeNextPageSelector(): String = popularAnimeNextPageSelector()
|
|
||||||
|
|
||||||
override fun searchAnimeSelector(): String = popularAnimeSelector()
|
|
||||||
|
|
||||||
override fun animeDetailsParse(document: Document): SAnime {
|
|
||||||
val anime = SAnime.create()
|
|
||||||
var description = try {
|
|
||||||
document.selectFirst("article.TPost header.Container div.TPMvCn div.Description")!!
|
|
||||||
.text().removeSurrounding("\"")
|
|
||||||
.substringAfter("Online:")
|
|
||||||
.substringBefore("Recuerda ")
|
|
||||||
.substringBefore("Director:")
|
|
||||||
.trim()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
document.selectFirst("article.TPost header div.TPMvCn div.Description p:nth-child(1)")!!.text().removeSurrounding("\"").trim()
|
|
||||||
}
|
|
||||||
|
|
||||||
var title = try {
|
|
||||||
document.selectFirst("article.TPost header.Container div.TPMvCn h1.Title")!!.text().removePrefix("Serie").trim()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
document.selectFirst("article.TPost header.Container div.TPMvCn a h1.Title")!!.text().removePrefix("Serie").trim()
|
|
||||||
}
|
|
||||||
|
|
||||||
anime.title = title
|
|
||||||
anime.description = description
|
|
||||||
anime.genre = document.select("article.TPost header.Container div.TPMvCn div.Description p.Genre a").joinToString { it.text().replace(",", "") }
|
|
||||||
anime.status = SAnime.UNKNOWN
|
|
||||||
return anime
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun externalOrInternalImg(url: String): String {
|
|
||||||
return if (url.contains("https")) url else if (url.startsWith("//")) "https:$url" else "$baseUrl/$url"
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun parseStatus(statusString: String): Int {
|
|
||||||
return when {
|
|
||||||
statusString.contains("En emision") -> SAnime.ONGOING
|
|
||||||
statusString.contains("Finalizado") -> SAnime.COMPLETED
|
|
||||||
else -> SAnime.UNKNOWN
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun latestUpdatesNextPageSelector() = popularAnimeNextPageSelector()
|
|
||||||
|
|
||||||
override fun latestUpdatesFromElement(element: Element) = popularAnimeFromElement(element)
|
|
||||||
|
|
||||||
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/browse?order=added&page=$page")
|
|
||||||
|
|
||||||
override fun latestUpdatesSelector() = popularAnimeSelector()
|
|
||||||
|
|
||||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
|
||||||
val videoQualityPref = ListPreference(screen.context).apply {
|
|
||||||
key = "preferred_quality"
|
|
||||||
title = "Preferred quality"
|
|
||||||
entries = arrayOf("LAT Nupload", "CAST Nupload", "SUB Nupload")
|
|
||||||
entryValues = arrayOf("LAT Nupload", "CAST Nupload", "SUB Nupload")
|
|
||||||
setDefaultValue("LAT Nupload")
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
screen.addPreference(videoQualityPref)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,226 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.animeextension.es.pelisflix
|
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import android.content.SharedPreferences
|
|
||||||
import android.util.Log
|
|
||||||
import androidx.preference.ListPreference
|
|
||||||
import androidx.preference.PreferenceScreen
|
|
||||||
import eu.kanade.tachiyomi.animesource.AnimeSource
|
|
||||||
import eu.kanade.tachiyomi.animesource.AnimeSourceFactory
|
|
||||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
|
|
||||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
|
||||||
import eu.kanade.tachiyomi.animesource.model.SAnime
|
|
||||||
import eu.kanade.tachiyomi.animesource.model.Video
|
|
||||||
import eu.kanade.tachiyomi.lib.doodextractor.DoodExtractor
|
|
||||||
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
|
|
||||||
import eu.kanade.tachiyomi.network.GET
|
|
||||||
import eu.kanade.tachiyomi.util.asJsoup
|
|
||||||
import okhttp3.MediaType.Companion.toMediaType
|
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
import okhttp3.Request
|
|
||||||
import okhttp3.RequestBody
|
|
||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
|
||||||
import okhttp3.Response
|
|
||||||
import org.jsoup.nodes.Element
|
|
||||||
import uy.kohesive.injekt.Injekt
|
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
|
|
||||||
class PelisflixFactory : AnimeSourceFactory {
|
|
||||||
override fun createSources(): List<AnimeSource> = listOf(PelisflixClass(), SeriesflixClass())
|
|
||||||
}
|
|
||||||
|
|
||||||
class PelisflixClass : Pelisflix("Pelisflix", "https://pelisflix2.green")
|
|
||||||
|
|
||||||
class SeriesflixClass : Pelisflix("Seriesflix", "https://seriesflix.video") {
|
|
||||||
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/ver-series-online/page/$page")
|
|
||||||
|
|
||||||
override fun popularAnimeSelector() = "li[id*=post-] > article"
|
|
||||||
|
|
||||||
private val preferences: SharedPreferences by lazy {
|
|
||||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun popularAnimeFromElement(element: Element): SAnime {
|
|
||||||
val anime = SAnime.create()
|
|
||||||
anime.setUrlWithoutDomain(element.select("a").attr("href"))
|
|
||||||
anime.title = element.select("a h2.Title").text()
|
|
||||||
anime.thumbnail_url = element.selectFirst("a div.Image figure.Objf img")!!.attr("src")
|
|
||||||
anime.description = element.select("div.TPMvCn div.Description p:nth-child(1)").text().removeSurrounding("\"")
|
|
||||||
return anime
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun loadVideoSources(urlResponse: String, lang: String): List<Video> {
|
|
||||||
val videoList = mutableListOf<Video>()
|
|
||||||
fetchUrls(urlResponse).map { serverUrl ->
|
|
||||||
Log.i("bruh url", serverUrl)
|
|
||||||
if (serverUrl.contains("doodstream")) {
|
|
||||||
val video = DoodExtractor(client).videoFromUrl(serverUrl.replace("https://doodstream.com", "https://dood.wf"), lang + "DoodStream")
|
|
||||||
if (video != null) videoList.add(video)
|
|
||||||
}
|
|
||||||
if (serverUrl.contains("streamtape")) {
|
|
||||||
val video = StreamTapeExtractor(client).videoFromUrl(serverUrl, lang + "StreamTape")
|
|
||||||
if (video != null) videoList.add(video)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return videoList
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun fetchUrls(text: String?): List<String> {
|
|
||||||
if (text.isNullOrEmpty()) return listOf()
|
|
||||||
val linkRegex = "(http|ftp|https):\\/\\/([\\w_-]+(?:(?:\\.[\\w_-]+)+))([\\w.,@?^=%&:\\/~+#-]*[\\w@?^=%&\\/~+#-])".toRegex()
|
|
||||||
return linkRegex.findAll(text).map { it.value.trim().removeSurrounding("\"") }.toList()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun videoListParse(response: Response): List<Video> {
|
|
||||||
val document = response.asJsoup()
|
|
||||||
val videoList = mutableListOf<Video>()
|
|
||||||
document.select("ul.ListOptions li").forEach { serverList ->
|
|
||||||
val movieID = serverList.attr("data-id")
|
|
||||||
val serverID = serverList.attr("data-key")
|
|
||||||
val type = if (response.request.url.toString().contains("movies")) 1 else 2
|
|
||||||
val url = "$baseUrl/?trembed=$serverID&trid=$movieID&trtype=$type"
|
|
||||||
val langTag = serverList.selectFirst("p.AAIco-language")!!.text().substring(3).uppercase()
|
|
||||||
|
|
||||||
val lang = if (langTag.contains("LATINO")) "[LAT]" else if (langTag.contains("CASTELLANO")) "[CAST]" else "[SUB]"
|
|
||||||
var request = client.newCall(GET(url)).execute()
|
|
||||||
if (request.isSuccessful) {
|
|
||||||
val serverLinks = request.asJsoup()
|
|
||||||
serverLinks.select("div.Video iframe").map {
|
|
||||||
val iframe = it.attr("src")
|
|
||||||
if (iframe.contains("https://sc.seriesflix.video/index.php")) {
|
|
||||||
val postKey = iframe.replace("https://sc.seriesflix.video/index.php?h=", "")
|
|
||||||
val mediaType = "application/x-www-form-urlencoded".toMediaType()
|
|
||||||
val body: RequestBody = "h=$postKey".toRequestBody(mediaType)
|
|
||||||
val newClient = OkHttpClient().newBuilder().build()
|
|
||||||
val requestServer = Request.Builder()
|
|
||||||
.url("https://sc.seriesflix.video/r.php?h=$postKey")
|
|
||||||
.method("POST", body)
|
|
||||||
.addHeader("Host", "sc.seriesflix.video")
|
|
||||||
.addHeader(
|
|
||||||
"User-Agent",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
|
|
||||||
)
|
|
||||||
.addHeader(
|
|
||||||
"Accept",
|
|
||||||
"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
|
|
||||||
)
|
|
||||||
.addHeader("Accept-Language", "en-US,en;q=0.5")
|
|
||||||
.addHeader("Content-Type", "application/x-www-form-urlencoded")
|
|
||||||
.addHeader("Origin", "null")
|
|
||||||
.addHeader("DNT", "1")
|
|
||||||
.addHeader("Connection", "keep-alive")
|
|
||||||
.addHeader("Upgrade-Insecure-Requests", "1")
|
|
||||||
.addHeader("Sec-Fetch-Dest", "iframe")
|
|
||||||
.addHeader("Sec-Fetch-Mode", "no-cors")
|
|
||||||
.addHeader("sec-fetch-site", "same-origin")
|
|
||||||
.addHeader("Sec-Fetch-User", "?1")
|
|
||||||
.addHeader("Alt-Used", "sc.seriesflix.video")
|
|
||||||
.addHeader("Access-Control-Allow-Methods", "POST")
|
|
||||||
.build()
|
|
||||||
val document = newClient.newCall(requestServer).execute()
|
|
||||||
val urlResponse = document!!.networkResponse!!.toString()
|
|
||||||
|
|
||||||
loadVideoSources(urlResponse, lang)!!.forEach { source ->
|
|
||||||
videoList.add(source)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
loadVideoSources(iframe, lang)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return videoList
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
|
|
||||||
val filterList = if (filters.isEmpty()) getFilterList() else filters
|
|
||||||
val genreFilter = filterList.find { it is GenreFilter } as GenreFilter
|
|
||||||
|
|
||||||
return when {
|
|
||||||
query.isNotBlank() -> GET("$baseUrl/?s=$query&page=$page")
|
|
||||||
genreFilter.state != 0 -> GET("$baseUrl/${genreFilter.toUriPart()}/page/$page")
|
|
||||||
else -> popularAnimeRequest(page)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getFilterList(): AnimeFilterList = AnimeFilterList(
|
|
||||||
AnimeFilter.Header("La busqueda por texto ignora el filtro"),
|
|
||||||
GenreFilter(),
|
|
||||||
)
|
|
||||||
|
|
||||||
private class GenreFilter : UriPartFilter(
|
|
||||||
"Géneros",
|
|
||||||
arrayOf(
|
|
||||||
Pair("<Selecionar>", ""),
|
|
||||||
Pair("Acción", "genero/accion"),
|
|
||||||
Pair("Animación", "genero/animacion"),
|
|
||||||
Pair("Anime", "genero/anime"),
|
|
||||||
Pair("Antiguas", "genero/series-antiguas"),
|
|
||||||
Pair("Aventura", "genero/aventura"),
|
|
||||||
Pair("Ciencia ficción", "genero/ciencia-ficcion"),
|
|
||||||
Pair("Comedia", "genero/comedia"),
|
|
||||||
Pair("Crimen", "genero/crimen"),
|
|
||||||
Pair("DC Comics", "genero/dc-comics"),
|
|
||||||
Pair("Drama", "genero/drama"),
|
|
||||||
Pair("Dorama", "genero/dorama"),
|
|
||||||
Pair("Estrenos", "genero/estrenos"),
|
|
||||||
Pair("Fantasía", "genero/fantasia"),
|
|
||||||
Pair("Misterio", "genero/misterio"),
|
|
||||||
Pair("Romance", "genero/romance"),
|
|
||||||
Pair("Terror", "genero/terror"),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) :
|
|
||||||
AnimeFilter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
|
|
||||||
fun toUriPart() = vals[state].second
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun List<Video>.sort(): List<Video> {
|
|
||||||
return try {
|
|
||||||
val videoSorted = this.sortedWith(
|
|
||||||
compareBy<Video> { it.quality.replace("[0-9]".toRegex(), "") }.thenByDescending { getNumberFromString(it.quality) },
|
|
||||||
).toTypedArray()
|
|
||||||
val userPreferredQuality = preferences.getString("preferred_quality", "[LAT]DoodStream")
|
|
||||||
val preferredIdx = videoSorted.indexOfFirst { x -> x.quality == userPreferredQuality }
|
|
||||||
if (preferredIdx != -1) {
|
|
||||||
videoSorted.drop(preferredIdx + 1)
|
|
||||||
videoSorted[0] = videoSorted[preferredIdx]
|
|
||||||
}
|
|
||||||
videoSorted.toList()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
this
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getNumberFromString(epsStr: String): String {
|
|
||||||
return epsStr.filter { it.isDigit() }.ifEmpty { "0" }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
|
||||||
val qualities = arrayOf(
|
|
||||||
"[LAT]DoodStream",
|
|
||||||
"[CAST]DoodStream",
|
|
||||||
"[SUB]DoodStream",
|
|
||||||
"[LAT]StreamTape",
|
|
||||||
"[CAST]StreamTape",
|
|
||||||
"[SUB]StreamTape", // video servers without resolution
|
|
||||||
)
|
|
||||||
val videoQualityPref = ListPreference(screen.context).apply {
|
|
||||||
key = "preferred_quality"
|
|
||||||
title = "Preferred quality"
|
|
||||||
entries = qualities
|
|
||||||
entryValues = qualities
|
|
||||||
setDefaultValue("[LAT]DoodStream")
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
screen.addPreference(videoQualityPref)
|
|
||||||
}
|
|
||||||
}
|
|