update aniking, add de.movie4k, remove anime-shitai & xcine (#998)
@ -6,7 +6,7 @@ ext {
|
||||
extName = 'Aniking'
|
||||
pkgNameSuffix = 'de.aniking'
|
||||
extClass = '.Aniking'
|
||||
extVersionCode = 1
|
||||
extVersionCode = 2
|
||||
libVersion = '13'
|
||||
}
|
||||
|
||||
|
@ -65,7 +65,16 @@ class Aniking : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
||||
// episodes
|
||||
|
||||
override fun episodeListRequest(anime: SAnime): Request {
|
||||
return GET("$baseUrl${anime.url}", headers = Headers.headersOf("if-modified-since", ""))
|
||||
val interceptor = client.newBuilder().addInterceptor(CloudflareInterceptor()).build()
|
||||
val headers = interceptor.newCall(
|
||||
GET(
|
||||
"$baseUrl${anime.url}",
|
||||
headers =
|
||||
Headers.headersOf("user-agent", "Mozilla/5.0 (Linux; Android 12; SM-T870 Build/SP2A.220305.013; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/106.0.5249.126 Safari/537.36")
|
||||
)
|
||||
)
|
||||
.execute().request.headers
|
||||
return GET("$baseUrl${anime.url}", headers = headers)
|
||||
}
|
||||
|
||||
override fun episodeListSelector() = throw Exception("not used")
|
||||
@ -268,6 +277,19 @@ class Aniking : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
||||
|
||||
// Details
|
||||
|
||||
override fun animeDetailsRequest(anime: SAnime): Request {
|
||||
val interceptor = client.newBuilder().addInterceptor(CloudflareInterceptor()).build()
|
||||
val headers = interceptor.newCall(
|
||||
GET(
|
||||
"$baseUrl${anime.url}",
|
||||
headers =
|
||||
Headers.headersOf("user-agent", "Mozilla/5.0 (Linux; Android 12; SM-T870 Build/SP2A.220305.013; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/106.0.5249.126 Safari/537.36")
|
||||
)
|
||||
)
|
||||
.execute().request.headers
|
||||
return GET("$baseUrl${anime.url}", headers = headers)
|
||||
}
|
||||
|
||||
override fun animeDetailsParse(document: Document): SAnime {
|
||||
val anime = SAnime.create()
|
||||
anime.thumbnail_url = document.select("div.tv-poster img").attr("src")
|
||||
|
@ -53,8 +53,7 @@ class CloudflareInterceptor() : Interceptor {
|
||||
databaseEnabled = true
|
||||
useWideViewPort = false
|
||||
loadWithOverviewMode = false
|
||||
userAgentString = request.header("User-Agent")
|
||||
?: "\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36 Edg/88.0.705.63\""
|
||||
userAgentString = "Mozilla/5.0 (Linux; Android 12; SM-T870 Build/SP2A.220305.013; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/106.0.5249.126 Safari/537.36"
|
||||
}
|
||||
|
||||
webview.webViewClient = object : WebViewClient() {
|
||||
@ -62,7 +61,7 @@ class CloudflareInterceptor() : Interceptor {
|
||||
view: WebView,
|
||||
request: WebResourceRequest,
|
||||
): WebResourceResponse? {
|
||||
if (request.url.toString().contains("https://aniking.cc")) {
|
||||
if (request.url.toString().contains("wp-content/themes/moviewp")) {
|
||||
newRequest = GET(request.url.toString(), request.requestHeaders.toHeaders())
|
||||
latch.countDown()
|
||||
}
|
||||
|
Before Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 4.7 KiB |
@ -1,21 +0,0 @@
|
||||
package eu.kanade.tachiyomi.animeextension.de.animeshitai
|
||||
|
||||
object ASConstants {
|
||||
const val NAME_DOOD = "Doodstream"
|
||||
const val NAME_STAPE = "Streamtape"
|
||||
|
||||
const val URL_DOOD = "https://dood"
|
||||
const val URL_STAPE = "https://streamtape.com"
|
||||
|
||||
val HOSTER_NAMES = arrayOf(NAME_DOOD, NAME_STAPE)
|
||||
val HOSTER_URLS = arrayOf(URL_DOOD, URL_STAPE)
|
||||
|
||||
const val LANG_SUB = "Sub"
|
||||
const val LANG_DUB = "Dub"
|
||||
|
||||
val LANGS = arrayOf(LANG_SUB, LANG_DUB)
|
||||
|
||||
const val PREFERRED_HOSTER = "preferred_hoster"
|
||||
const val PREFERRED_LANG = "preferred_sub"
|
||||
const val HOSTER_SELECTION = "hoster_selection"
|
||||
}
|
@ -1,204 +0,0 @@
|
||||
package eu.kanade.tachiyomi.animeextension.de.animeshitai
|
||||
|
||||
import eu.kanade.tachiyomi.animeextension.de.animeshitai.model.ASAnime
|
||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
|
||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
||||
import java.util.Calendar
|
||||
|
||||
object ASFilters {
|
||||
open class CheckBoxFilterList(name: String, values: List<CheckBox>) : AnimeFilter.Group<AnimeFilter.CheckBox>(name, values)
|
||||
private class CheckBoxVal(name: String, state: Boolean = false) : AnimeFilter.CheckBox(name, state)
|
||||
|
||||
open class TriStateFilterList(name: String, values: List<TriState>) : AnimeFilter.Group<AnimeFilter.TriState>(name, values)
|
||||
private class TriStateVal(name: String) : AnimeFilter.TriState(name)
|
||||
|
||||
class SortFilter : AnimeFilter.Sort(
|
||||
"Sortieren",
|
||||
ASFilterData.sortables.map { it.first }.toTypedArray(),
|
||||
Selection(0, true)
|
||||
)
|
||||
|
||||
class FormatFilter : CheckBoxFilterList(
|
||||
"Format",
|
||||
ASFilterData.formats.map { CheckBoxVal(it.first, true) }
|
||||
)
|
||||
|
||||
class LanguageFilter : CheckBoxFilterList(
|
||||
"Sprache",
|
||||
ASFilterData.languages.map { CheckBoxVal(it.first, true) }
|
||||
)
|
||||
|
||||
class GenreFilter : TriStateFilterList(
|
||||
"Genres",
|
||||
ASFilterData.genres.map { TriStateVal(it) }
|
||||
)
|
||||
|
||||
class YearsFilter : CheckBoxFilterList(
|
||||
"Jahre",
|
||||
ASFilterData.years.map { CheckBoxVal(it.toString()) }
|
||||
)
|
||||
|
||||
class ABCFilter : CheckBoxFilterList(
|
||||
"ABC",
|
||||
ASFilterData.abc.map { CheckBoxVal(it.toString()) }
|
||||
)
|
||||
|
||||
val filterList = AnimeFilterList(
|
||||
SortFilter(),
|
||||
FormatFilter(),
|
||||
LanguageFilter(),
|
||||
GenreFilter(),
|
||||
YearsFilter(),
|
||||
ABCFilter(),
|
||||
)
|
||||
|
||||
data class FilterSearchParams(
|
||||
var orderBy: String = "az",
|
||||
var orderAscending: Boolean = true,
|
||||
val includedFormats: ArrayList<String> = ArrayList(),
|
||||
val includedLangs: ArrayList<String> = ArrayList(),
|
||||
val includedGenres: ArrayList<String> = ArrayList(),
|
||||
val blackListedGenres: ArrayList<String> = ArrayList(),
|
||||
val includedYears: ArrayList<String> = ArrayList(),
|
||||
val includedLetters: ArrayList<String> = ArrayList(),
|
||||
)
|
||||
|
||||
internal fun getSearchParameters(filters: AnimeFilterList): FilterSearchParams {
|
||||
val params = FilterSearchParams()
|
||||
filters.forEach { filter ->
|
||||
when (filter) {
|
||||
is SortFilter -> {
|
||||
if (filter.state != null) {
|
||||
val query = ASFilterData.sortables[filter.state!!.index].second
|
||||
params.orderAscending = filter.state!!.ascending
|
||||
params.orderBy = query
|
||||
}
|
||||
}
|
||||
is FormatFilter -> {
|
||||
filter.state.forEach { format ->
|
||||
if (format.state) {
|
||||
val query = ASFilterData.formats.find { it.first == format.name }!!.second
|
||||
params.includedFormats.add(query)
|
||||
}
|
||||
}
|
||||
}
|
||||
is LanguageFilter -> {
|
||||
filter.state.forEach { lang ->
|
||||
if (lang.state) {
|
||||
val query = ASFilterData.languages.find { it.first == lang.name }!!.second
|
||||
params.includedLangs.add(query)
|
||||
}
|
||||
}
|
||||
}
|
||||
is GenreFilter -> {
|
||||
filter.state.forEach { genre ->
|
||||
if (genre.isIncluded()) {
|
||||
params.includedGenres.add(genre.name)
|
||||
} else if (genre.isExcluded()) {
|
||||
params.blackListedGenres.add(genre.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
is YearsFilter -> {
|
||||
filter.state.forEach { year ->
|
||||
if (year.state)
|
||||
params.includedYears.add(year.name)
|
||||
}
|
||||
}
|
||||
is ABCFilter -> {
|
||||
filter.state.forEach { letter ->
|
||||
if (letter.state)
|
||||
params.includedLetters.add(letter.name)
|
||||
}
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
return params
|
||||
}
|
||||
|
||||
fun MutableList<ASAnime>.applyFilterParams(params: FilterSearchParams) {
|
||||
// Remove all entries with blacklisted genres
|
||||
this.removeAll { anime -> params.blackListedGenres.any { it in anime.genre ?: "" } }
|
||||
|
||||
// Sort animes
|
||||
when (params.orderBy) {
|
||||
// Sort animes alphabetically
|
||||
"az" -> {
|
||||
if (!params.orderAscending)
|
||||
this.reverse()
|
||||
}
|
||||
// Sort animes by year
|
||||
"year" -> {
|
||||
if (params.orderAscending)
|
||||
this.sortBy { it.year }
|
||||
else
|
||||
this.sortByDescending { it.year }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private object ASFilterData {
|
||||
// (1990..(Year.now().value)).reversed()
|
||||
val years = (1990..(Calendar.getInstance().get(Calendar.YEAR))).reversed()
|
||||
|
||||
val abc = 'A'..'Z'
|
||||
|
||||
val sortables = arrayOf(
|
||||
Pair("Alphabetisch", "az"),
|
||||
Pair("Jahr", "year"),
|
||||
)
|
||||
|
||||
val formats = arrayOf(
|
||||
Pair("\uD83D\uDCFA Serien", "Serien"),
|
||||
Pair("\uD83C\uDF10 OVAs", "OVAs"),
|
||||
Pair("\uD83C\uDFAC Filme", "Filme")
|
||||
)
|
||||
|
||||
val languages = arrayOf(
|
||||
Pair("\uD83C\uDDE9\uD83C\uDDEA Ger Dub", "Ger Dub"),
|
||||
Pair("\uD83C\uDDEF\uD83C\uDDF5 Ger Sub", "Ger Sub"),
|
||||
Pair("\uD83C\uDDEC\uD83C\uDDE7 Eng Dub & Ger Sub", "Eng Dub")
|
||||
)
|
||||
|
||||
val genres = listOf(
|
||||
"Abenteuer",
|
||||
"Action",
|
||||
"Alltagsleben",
|
||||
"Alternative Welt",
|
||||
"Drama",
|
||||
"Ecchi",
|
||||
"Erotik",
|
||||
"Fantasy",
|
||||
"Fighting-Shounen",
|
||||
"Ganbatte",
|
||||
"Geistergeschichten",
|
||||
"Harem",
|
||||
"Historisch",
|
||||
"Horror",
|
||||
"Komödie",
|
||||
"Krimi",
|
||||
"Liebesdrama",
|
||||
"Magical Girl",
|
||||
"Magie",
|
||||
"Mecha",
|
||||
"Mystery",
|
||||
"Nonsense-Komödie",
|
||||
"Psychodrama",
|
||||
"Romantische Komödie",
|
||||
"Romanze",
|
||||
"Schule",
|
||||
"Sci-Fi",
|
||||
"Sentimentales Drama",
|
||||
"Shoujou",
|
||||
"Shounen",
|
||||
"Sport",
|
||||
"Superpower",
|
||||
"Thriller",
|
||||
"Übermäßige Gewaltdarstellung",
|
||||
"Unbestimmt",
|
||||
"Yaoi",
|
||||
"Yuri"
|
||||
)
|
||||
}
|
@ -1,356 +0,0 @@
|
||||
package eu.kanade.tachiyomi.animeextension.de.animeshitai
|
||||
|
||||
import android.app.Application
|
||||
import android.content.SharedPreferences
|
||||
import android.util.Base64
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.MultiSelectListPreference
|
||||
import androidx.preference.PreferenceScreen
|
||||
import eu.kanade.tachiyomi.animeextension.de.animeshitai.ASFilters.applyFilterParams
|
||||
import eu.kanade.tachiyomi.animeextension.de.animeshitai.model.ASAnime
|
||||
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
|
||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
||||
import eu.kanade.tachiyomi.animesource.model.AnimesPage
|
||||
import eu.kanade.tachiyomi.animesource.model.SAnime
|
||||
import eu.kanade.tachiyomi.animesource.model.SEpisode
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
|
||||
import eu.kanade.tachiyomi.lib.doodextractor.DoodExtractor
|
||||
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.POST
|
||||
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import okhttp3.FormBody
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import rx.Observable
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
class AnimeShitai : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
||||
|
||||
override val name = "Anime Shitai"
|
||||
|
||||
override val baseUrl = "https://anime-shitai.com"
|
||||
|
||||
override val lang = "de"
|
||||
|
||||
override val supportsLatest = true
|
||||
|
||||
override val client: OkHttpClient = network.cloudflareClient
|
||||
|
||||
private val preferences: SharedPreferences by lazy {
|
||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||
}
|
||||
|
||||
// ===== POPULAR ANIME =====
|
||||
override fun popularAnimeSelector(): String = ".newanimes .newbox"
|
||||
|
||||
override fun popularAnimeNextPageSelector(): String? = null
|
||||
|
||||
override fun popularAnimeRequest(page: Int): Request = GET(baseUrl)
|
||||
|
||||
override fun popularAnimeFromElement(element: Element): SAnime {
|
||||
val anime = SAnime.create()
|
||||
val linkElement = element.selectFirst("a")
|
||||
anime.url = linkElement.attr("href")
|
||||
anime.thumbnail_url = linkElement.selectFirst("img").attr("src")
|
||||
anime.title = element.selectFirst(".ntitel").text()
|
||||
return anime
|
||||
}
|
||||
|
||||
// ===== LATEST ANIME =====
|
||||
override fun latestUpdatesSelector(): String = "a"
|
||||
|
||||
override fun latestUpdatesNextPageSelector(): String? = null
|
||||
|
||||
override fun latestUpdatesRequest(page: Int): Request = POST("$baseUrl/ichigo/indexep.php?option=1")
|
||||
|
||||
override fun latestUpdatesParse(response: Response): AnimesPage {
|
||||
val document = Jsoup.parseBodyFragment(response.body?.string())
|
||||
val animeList = mutableSetOf<String>()
|
||||
val animes = document.select(latestUpdatesSelector()).mapNotNull { element ->
|
||||
val animeName = element.selectFirst("span.t").text().substringBefore("...")
|
||||
// Check if any anime is multiple times in the list
|
||||
if (!animeList.contains(animeName)) {
|
||||
animeList.add(animeName)
|
||||
latestUpdatesFromElement(element)
|
||||
} else
|
||||
null
|
||||
}
|
||||
|
||||
return AnimesPage(animes, false)
|
||||
}
|
||||
|
||||
override fun latestUpdatesFromElement(element: Element): SAnime {
|
||||
val anime = SAnime.create()
|
||||
val animeId = element.attr("href").substringAfter("/anschauen/").substringBefore('/')
|
||||
anime.url = "/anime/$animeId/"
|
||||
anime.thumbnail_url = element.selectFirst("div.c").attr("style").substringAfter("background:url('").substringBefore("');")
|
||||
anime.title = anime.thumbnail_url!!.substringAfter("/cover/").substringBefore(".jpg").replace("__", ": ").replace('_', ' ')
|
||||
return anime
|
||||
}
|
||||
|
||||
// ===== ANIME SEARCH =====
|
||||
override fun fetchSearchAnime(page: Int, query: String, filters: AnimeFilterList): Observable<AnimesPage> {
|
||||
val params = ASFilters.getSearchParameters(filters)
|
||||
return client.newCall(searchAnimeRequest(page, query, params))
|
||||
.asObservableSuccess()
|
||||
.map { response ->
|
||||
searchAnimeParse(response, params)
|
||||
}
|
||||
}
|
||||
|
||||
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request = throw UnsupportedOperationException("Not used.")
|
||||
|
||||
private fun searchAnimeRequest(page: Int, query: String, params: ASFilters.FilterSearchParams): Request {
|
||||
fun listToFormStr(list: ArrayList<String>): String {
|
||||
if (list.size > 0)
|
||||
return list.joinToString(" - ", " - ")
|
||||
return ""
|
||||
}
|
||||
val body = FormBody.Builder()
|
||||
.add("p", page.toString())
|
||||
.add("get_val", query)
|
||||
.add("genre", listToFormStr(params.includedGenres))
|
||||
.add("jahr", listToFormStr(params.includedYears))
|
||||
.add("format", listToFormStr(params.includedFormats))
|
||||
.add("omu", listToFormStr(params.includedLangs))
|
||||
.add("abc", listToFormStr(params.includedLetters))
|
||||
.build()
|
||||
|
||||
return POST("$baseUrl/ichigo/get_anilist.php", headers, body)
|
||||
}
|
||||
|
||||
override fun searchAnimeSelector(): String = ".listinganime"
|
||||
|
||||
override fun searchAnimeNextPageSelector(): String? = ".nav"
|
||||
|
||||
override fun searchAnimeParse(response: Response): AnimesPage = throw UnsupportedOperationException("Not used.")
|
||||
|
||||
private fun searchAnimeParse(response: Response, params: ASFilters.FilterSearchParams): AnimesPage {
|
||||
val document = Jsoup.parseBodyFragment(response.body?.string())
|
||||
|
||||
val animeTrackList = mutableSetOf<String>()
|
||||
val animes = document.select(searchAnimeSelector()).mapNotNull { element ->
|
||||
val anime = searchAnimeFromElement(element)
|
||||
// Shows with sub and dub have two separate entries, exclude the second entry
|
||||
if (!animeTrackList.contains(anime.thumbnail_url)) {
|
||||
animeTrackList.add(anime.thumbnail_url!!)
|
||||
anime
|
||||
} else
|
||||
null
|
||||
}.toMutableList()
|
||||
|
||||
// Apply client-side filters
|
||||
animes.applyFilterParams(params)
|
||||
|
||||
val hasNextPage = searchAnimeNextPageSelector()?.let { selector ->
|
||||
document.select(selector).first()
|
||||
} != null
|
||||
|
||||
return AnimesPage(animes, hasNextPage)
|
||||
}
|
||||
|
||||
override fun searchAnimeFromElement(element: Element): ASAnime {
|
||||
val anime = ASAnime.create()
|
||||
anime.url = element.selectFirst("a").attr("href")
|
||||
anime.thumbnail_url = element.selectFirst(".listinganicover").attr("style").substringAfter("url('").substringBefore("')")
|
||||
anime.title = element.selectFirst(".listingtitle").text().replaceAfterLast(' ', "").trimStart()
|
||||
// Get genres for client-side genre filtering
|
||||
anime.genre = element.select(".listingdesc .alternativ").mapNotNull {
|
||||
if (it.text().startsWith("Hauptgenre") || it.text().startsWith("Genres"))
|
||||
it.text().substringAfter(':')
|
||||
else
|
||||
null
|
||||
}.joinToString(", ")
|
||||
|
||||
anime.year = element.selectFirst(".listingtitle").text().substringAfterLast('(').substringBefore(')').toInt()
|
||||
return anime
|
||||
}
|
||||
|
||||
override fun getFilterList(): AnimeFilterList = ASFilters.filterList
|
||||
|
||||
// ===== ANIME DETAILS =====
|
||||
override fun animeDetailsParse(document: Document): SAnime {
|
||||
val anime = SAnime.create()
|
||||
val content = document.selectFirst("#ani .body")
|
||||
anime.title = content.selectFirst("animename").ownText().trim()
|
||||
anime.thumbnail_url = document.selectFirst("#ani .pic").attr("data-src")
|
||||
anime.genre = content.select("div:eq(1) .hg, a").joinToString(", ") { it.text() }
|
||||
anime.description = content.selectFirst(".br").ownText()
|
||||
anime.status = SAnime.UNKNOWN
|
||||
return anime
|
||||
}
|
||||
|
||||
// ===== EPISODE =====
|
||||
override fun episodeListSelector(): String = ".ep_table tbody tr"
|
||||
|
||||
override fun episodeListParse(response: Response): List<SEpisode> {
|
||||
val document = response.asJsoup()
|
||||
return document.select(episodeListSelector()).reversed().map { episodeFromElement(it) }
|
||||
}
|
||||
|
||||
override fun episodeFromElement(element: Element): SEpisode {
|
||||
val episode = SEpisode.create()
|
||||
episode.setUrlWithoutDomain(baseUrl + element.attr("onclick").substringAfter("window.location.href='").substringBefore('#'))
|
||||
val ep = element.child(0).text()
|
||||
episode.episode_number = ep.toFloat()
|
||||
episode.name = "Episode $ep"
|
||||
return episode
|
||||
}
|
||||
|
||||
// ===== VIDEO SOURCES =====
|
||||
override fun videoListSelector(): String = "center a"
|
||||
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val document = response.asJsoup()
|
||||
return document.select(videoListSelector()).mapNotNull { videoFromElement(it, document) }
|
||||
}
|
||||
|
||||
private fun getVidLink(document: Document): String {
|
||||
val b64html = document.selectFirst(".c36 > script").data().substringAfter("atob('").substringBefore("');")
|
||||
val decodedHtml = Base64.decode(b64html, Base64.DEFAULT).decodeToString()
|
||||
return baseUrl + decodedHtml.substringAfter("src=\"").substringBefore('"')
|
||||
}
|
||||
|
||||
override fun videoFromElement(element: Element): Video = throw UnsupportedOperationException("Not used.")
|
||||
|
||||
private fun videoFromElement(element: Element, document: Document): Video? {
|
||||
val hosterLinkName = element.attr("href").substringAfter("/folge-").substringAfter('/')
|
||||
val hosterName = when (hosterLinkName) {
|
||||
"DODO" -> ASConstants.NAME_DOOD
|
||||
"Streamtape" -> ASConstants.NAME_STAPE
|
||||
else -> null
|
||||
} ?: return null
|
||||
|
||||
val lang =
|
||||
when (element.selectFirst(".languagehoster img").attr("src").substringAfter("/img/").substringBefore(".")) {
|
||||
"de" -> ASConstants.LANG_DUB
|
||||
"jap" -> ASConstants.LANG_SUB
|
||||
"en" -> ASConstants.LANG_SUB
|
||||
else -> "Unknown"
|
||||
}
|
||||
|
||||
val link: String = when {
|
||||
// If video link of loaded page is already the right one
|
||||
element.selectFirst(".host").hasClass("hoston") -> getVidLink(document)
|
||||
// If video link of loaded page is not the right one, get url for the right page and extract video link
|
||||
else -> {
|
||||
val response = client.newCall(GET("$baseUrl${element.attr("href")}")).execute()
|
||||
getVidLink(response.asJsoup())
|
||||
}
|
||||
}
|
||||
|
||||
val quality = "$hosterName, $lang"
|
||||
val hosterSelection = preferences.getStringSet(ASConstants.HOSTER_SELECTION, null)
|
||||
when {
|
||||
hosterName == ASConstants.NAME_DOOD && hosterSelection?.contains(ASConstants.NAME_DOOD) == true -> {
|
||||
val video = try {
|
||||
DoodExtractor(client).videoFromUrl(link, quality)
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
if (video != null) {
|
||||
return video
|
||||
}
|
||||
}
|
||||
hosterName == ASConstants.NAME_STAPE && hosterSelection?.contains(ASConstants.NAME_STAPE) == true -> {
|
||||
val video = StreamTapeExtractor(client).videoFromUrl(link, quality)
|
||||
if (video != null) {
|
||||
return video
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun List<Video>.sort(): List<Video> {
|
||||
val hoster = preferences.getString(ASConstants.PREFERRED_HOSTER, null)
|
||||
val subPreference = preferences.getString(ASConstants.PREFERRED_LANG, "Sub")!!
|
||||
val hosterList = mutableListOf<Video>()
|
||||
val otherList = mutableListOf<Video>()
|
||||
if (hoster != null) {
|
||||
for (video in this) {
|
||||
if (video.url.contains(hoster)) {
|
||||
hosterList.add(video)
|
||||
} else {
|
||||
otherList.add(video)
|
||||
}
|
||||
}
|
||||
} else otherList += this
|
||||
val newList = mutableListOf<Video>()
|
||||
var preferred = 0
|
||||
for (video in hosterList) {
|
||||
if (video.quality.contains(subPreference)) {
|
||||
newList.add(preferred, video)
|
||||
preferred++
|
||||
} else newList.add(video)
|
||||
}
|
||||
for (video in otherList) {
|
||||
if (video.quality.contains(subPreference)) {
|
||||
newList.add(preferred, video)
|
||||
preferred++
|
||||
} else newList.add(video)
|
||||
}
|
||||
|
||||
return newList
|
||||
}
|
||||
|
||||
override fun videoUrlParse(document: Document): String = throw UnsupportedOperationException("Not used.")
|
||||
|
||||
// ===== PREFERENCES ======
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||
val hosterPref = ListPreference(screen.context).apply {
|
||||
key = ASConstants.PREFERRED_HOSTER
|
||||
title = "Standard-Hoster"
|
||||
entries = ASConstants.HOSTER_NAMES
|
||||
entryValues = ASConstants.HOSTER_URLS
|
||||
setDefaultValue(ASConstants.URL_STAPE)
|
||||
summary = "%s"
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val selected = newValue as String
|
||||
val index = findIndexOfValue(selected)
|
||||
val entry = entryValues[index] as String
|
||||
preferences.edit().putString(key, entry).commit()
|
||||
}
|
||||
}
|
||||
val subPref = ListPreference(screen.context).apply {
|
||||
key = ASConstants.PREFERRED_LANG
|
||||
title = "Standardmäßig Sub oder Dub?"
|
||||
entries = ASConstants.LANGS
|
||||
entryValues = ASConstants.LANGS
|
||||
setDefaultValue(ASConstants.LANG_SUB)
|
||||
summary = "%s"
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val selected = newValue as String
|
||||
val index = findIndexOfValue(selected)
|
||||
val entry = entryValues[index] as String
|
||||
preferences.edit().putString(key, entry).commit()
|
||||
}
|
||||
}
|
||||
val subSelection = MultiSelectListPreference(screen.context).apply {
|
||||
key = ASConstants.HOSTER_SELECTION
|
||||
title = "Hoster auswählen"
|
||||
entries = ASConstants.HOSTER_NAMES
|
||||
entryValues = ASConstants.HOSTER_NAMES
|
||||
setDefaultValue(ASConstants.HOSTER_NAMES.toSet())
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
preferences.edit().putStringSet(key, newValue as Set<String>).commit()
|
||||
}
|
||||
}
|
||||
screen.addPreference(subPref)
|
||||
screen.addPreference(hosterPref)
|
||||
screen.addPreference(subSelection)
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
package eu.kanade.tachiyomi.animeextension.de.animeshitai.model
|
||||
|
||||
import eu.kanade.tachiyomi.animesource.model.SAnime
|
||||
|
||||
class ASAnime : SAnime {
|
||||
override lateinit var url: String
|
||||
override lateinit var title: String
|
||||
override var artist: String? = null
|
||||
override var author: String? = null
|
||||
override var description: String? = null
|
||||
override var genre: String? = null
|
||||
override var status: Int = 0
|
||||
override var thumbnail_url: String? = null
|
||||
override var initialized: Boolean = false
|
||||
|
||||
var year: Int = 0
|
||||
|
||||
companion object {
|
||||
fun create(): ASAnime {
|
||||
return ASAnime()
|
||||
}
|
||||
}
|
||||
}
|
@ -3,16 +3,16 @@ apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlinx-serialization'
|
||||
|
||||
ext {
|
||||
extName = 'Anime Shitai'
|
||||
pkgNameSuffix = 'de.animeshitai'
|
||||
extClass = '.AnimeShitai'
|
||||
extVersionCode = 6
|
||||
extName = 'Movie4k'
|
||||
pkgNameSuffix = 'de.movie4k'
|
||||
extClass = '.Movie4k'
|
||||
extVersionCode = 1
|
||||
libVersion = '13'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(':lib-streamtape-extractor'))
|
||||
implementation(project(':lib-dood-extractor'))
|
||||
implementation(project(':lib-voe-extractor'))
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
BIN
src/de/movie4k/res/mipmap-hdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
BIN
src/de/movie4k/res/mipmap-hdpi/ic_launcher_adaptive_fore.png
Normal file
After Width: | Height: | Size: 5.4 KiB |
BIN
src/de/movie4k/res/mipmap-mdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
BIN
src/de/movie4k/res/mipmap-mdpi/ic_launcher_adaptive_fore.png
Normal file
After Width: | Height: | Size: 3.0 KiB |
BIN
src/de/movie4k/res/mipmap-xhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 5.6 KiB |
BIN
src/de/movie4k/res/mipmap-xhdpi/ic_launcher_adaptive_fore.png
Normal file
After Width: | Height: | Size: 8.0 KiB |
BIN
src/de/movie4k/res/mipmap-xxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 7.5 KiB |
Before Width: | Height: | Size: 8.9 KiB After Width: | Height: | Size: 8.9 KiB |
BIN
src/de/movie4k/res/mipmap-xxhdpi/ic_launcher_adaptive_fore.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
src/de/movie4k/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
BIN
src/de/movie4k/res/mipmap-xxxhdpi/ic_launcher_adaptive_fore.png
Normal file
After Width: | Height: | Size: 24 KiB |
@ -0,0 +1,452 @@
|
||||
package eu.kanade.tachiyomi.animeextension.de.movie4k
|
||||
|
||||
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.movie4k.extractors.StreamZExtractor
|
||||
import eu.kanade.tachiyomi.animeextension.de.movie4k.extractors.VidozaExtractor
|
||||
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
|
||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
||||
import eu.kanade.tachiyomi.animesource.model.AnimesPage
|
||||
import eu.kanade.tachiyomi.animesource.model.SAnime
|
||||
import eu.kanade.tachiyomi.animesource.model.SEpisode
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
|
||||
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
|
||||
import eu.kanade.tachiyomi.lib.voeextractor.VoeExtractor
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.float
|
||||
import kotlinx.serialization.json.int
|
||||
import kotlinx.serialization.json.jsonArray
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import okhttp3.Headers
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import kotlin.Exception
|
||||
|
||||
class Movie4k : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
|
||||
override val name = "Movie4k"
|
||||
|
||||
override val baseUrl = "https://movie4k.stream"
|
||||
|
||||
private val apiUrl = "https://api.movie4k.stream"
|
||||
|
||||
override val lang = "de"
|
||||
|
||||
override val supportsLatest = false
|
||||
|
||||
override val client: OkHttpClient = network.client
|
||||
|
||||
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("$apiUrl/data/browse/?lang=2&keyword=&year=&rating=&votes=&genre=&country=&cast=&directors=&type=movies&order_by=trending&page=$page", headers = Headers.headersOf("if-none-match", ""))
|
||||
|
||||
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 pager = jObject["pager"]!!.jsonObject
|
||||
val lastPage = pager.jsonObject["endPage"]!!.jsonPrimitive.int
|
||||
val page = pager.jsonObject["currentPage"]!!.jsonPrimitive.int
|
||||
val hasNextPage = page < lastPage
|
||||
val array = jObject["movies"]!!.jsonArray
|
||||
val animeList = mutableListOf<SAnime>()
|
||||
for (item in array) {
|
||||
val anime = SAnime.create()
|
||||
anime.title = item.jsonObject["title"]!!.jsonPrimitive.content
|
||||
val animeId = item.jsonObject["_id"]!!.jsonPrimitive.content
|
||||
anime.setUrlWithoutDomain("$apiUrl/data/watch/?_id=$animeId")
|
||||
anime.thumbnail_url = "https://image.tmdb.org/t/p/w300" + (item.jsonObject["poster_path"]?.jsonPrimitive?.content ?: "")
|
||||
animeList.add(anime)
|
||||
}
|
||||
return AnimesPage(animeList, hasNextPage)
|
||||
}
|
||||
|
||||
// episodes
|
||||
|
||||
override fun episodeListRequest(anime: SAnime): Request {
|
||||
return GET(apiUrl + anime.url, headers = Headers.headersOf("if-none-match", ""))
|
||||
}
|
||||
|
||||
override fun episodeListParse(response: Response): List<SEpisode> {
|
||||
val responseString = response.body!!.string()
|
||||
return parseEpisodePage(responseString)
|
||||
}
|
||||
|
||||
private fun parseEpisodePage(jsonLine: String?): MutableList<SEpisode> {
|
||||
val jsonData = jsonLine ?: return mutableListOf()
|
||||
val jObject = json.decodeFromString<JsonObject>(jsonData)
|
||||
val episodeList = mutableListOf<SEpisode>()
|
||||
if (jObject["tv"]!!.jsonPrimitive.int == 1) {
|
||||
val array = jObject["streams"]!!.jsonArray.distinctBy { it.jsonObject["e"] }.sortedBy { it.jsonObject["e"]!!.jsonPrimitive.int }
|
||||
for (item in array) {
|
||||
val episode = SEpisode.create()
|
||||
val id = jObject["_id"]!!.jsonPrimitive.content
|
||||
episode.episode_number = item.jsonObject["e"]!!.jsonPrimitive.float
|
||||
val epNumString = if (episode.episode_number % 1F == 0F) episode.episode_number.toInt().toString() else episode.episode_number.toString()
|
||||
val season = jObject["s"]!!.jsonPrimitive.content
|
||||
episode.name = "Staffel $season Folge $epNumString"
|
||||
episode.setUrlWithoutDomain("$apiUrl/data/watch/?_id=$id&e=$epNumString")
|
||||
episodeList.add(episode)
|
||||
}
|
||||
} else {
|
||||
val episode = SEpisode.create()
|
||||
episode.episode_number = 1F
|
||||
episode.name = jObject["title"]!!.jsonPrimitive.content
|
||||
val id = jObject["_id"]!!.jsonPrimitive.content
|
||||
episode.setUrlWithoutDomain("$apiUrl/data/watch/?_id=$id")
|
||||
episodeList.add(episode)
|
||||
}
|
||||
return episodeList.asReversed()
|
||||
}
|
||||
|
||||
// Video Extractor
|
||||
|
||||
override fun videoListRequest(episode: SEpisode): Request {
|
||||
val url = episode.url.replace("&e=${episode.episode_number.toInt()}", "")
|
||||
return GET(apiUrl + url, headers = Headers.headersOf("if-none-match", "", "e", episode.episode_number.toInt().toString()))
|
||||
}
|
||||
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
return videosFromElement(response)
|
||||
}
|
||||
|
||||
private fun videosFromElement(response: Response): List<Video> {
|
||||
val jsonData = response.body!!.string()
|
||||
val jObject = json.decodeFromString<JsonObject>(jsonData)
|
||||
val array = jObject["streams"]!!.jsonArray
|
||||
val videoList = mutableListOf<Video>()
|
||||
val hosterSelection = preferences.getStringSet("hoster_selection", setOf("voe", "stape", "streamz", "vidoza"))
|
||||
if (jObject["tv"]!!.jsonPrimitive.int == 1) {
|
||||
for (item in array) {
|
||||
if (item.jsonObject["e"]!!.jsonPrimitive.content == response.request.header("e").toString()) {
|
||||
val link = item.jsonObject["stream"]!!.jsonPrimitive.content
|
||||
when {
|
||||
link.contains("//streamtape") && hosterSelection?.contains("stape") == true -> {
|
||||
if (!link.contains("https:")) {
|
||||
val url = "https:$link"
|
||||
val quality = "Streamtape"
|
||||
val video = StreamTapeExtractor(client).videoFromUrl(url, quality)
|
||||
if (video != null) {
|
||||
videoList.add(video)
|
||||
}
|
||||
} else {
|
||||
val quality = "Streamtape"
|
||||
val video = StreamTapeExtractor(client).videoFromUrl(link, quality)
|
||||
if (video != null) {
|
||||
videoList.add(video)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
link.contains("//streamcrypt.net") || link.contains("//streamz") && hosterSelection?.contains("streamz") == true -> {
|
||||
if (!link.contains("https:")) {
|
||||
if (link.contains("//streamcrypt.net")) {
|
||||
val url = "https:$link"
|
||||
val zurl = client.newCall(GET(url)).execute().request.url.toString()
|
||||
val quality = "StreamZ"
|
||||
val video = StreamZExtractor(client).videoFromUrl(zurl, quality)
|
||||
if (video != null) {
|
||||
videoList.add(video)
|
||||
}
|
||||
} else {
|
||||
val url = "https:$link"
|
||||
val quality = "StreamZ"
|
||||
val video = StreamZExtractor(client).videoFromUrl(url, quality)
|
||||
if (video != null) {
|
||||
videoList.add(video)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (link.contains("https://streamcrypt.net")) {
|
||||
val url = client.newCall(GET(link)).execute().request.url.toString()
|
||||
val quality = "StreamZ"
|
||||
val video = StreamZExtractor(client).videoFromUrl(url, quality)
|
||||
if (video != null) {
|
||||
videoList.add(video)
|
||||
}
|
||||
} else {
|
||||
val quality = "StreamZ"
|
||||
val video = StreamZExtractor(client).videoFromUrl(link, quality)
|
||||
if (video != null) {
|
||||
videoList.add(video)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
link.contains("vidoza") && hosterSelection?.contains("vidoza") == true -> {
|
||||
if (!link.contains("https:")) {
|
||||
val url = "https:$link"
|
||||
val quality = "Vidoza"
|
||||
val video = VidozaExtractor(client).videoFromUrl(url, quality)
|
||||
if (video != null) {
|
||||
videoList.add(video)
|
||||
}
|
||||
} else {
|
||||
val quality = "Vidoza"
|
||||
val video = VidozaExtractor(client).videoFromUrl(link, quality)
|
||||
if (video != null) {
|
||||
videoList.add(video)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
link.contains("//voe.sx") || link.contains("//launchreliantcleaverriver") ||
|
||||
link.contains("//fraudclatterflyingcar") ||
|
||||
link.contains("//uptodatefinishconferenceroom") || link.contains("//realfinanceblogcenter") && hosterSelection?.contains("voe") == true -> {
|
||||
if (!link.contains("https:")) {
|
||||
val url = "https:$link"
|
||||
val quality = "Voe"
|
||||
val video = VoeExtractor(client).videoFromUrl(url, quality)
|
||||
if (video != null) {
|
||||
videoList.add(video)
|
||||
}
|
||||
} else {
|
||||
val quality = "Voe"
|
||||
val video = VoeExtractor(client).videoFromUrl(link, quality)
|
||||
if (video != null) {
|
||||
videoList.add(video)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (item in array) {
|
||||
val link = item.jsonObject["stream"]!!.jsonPrimitive.content
|
||||
when {
|
||||
link.contains("//streamtape") && hosterSelection?.contains("stape") == true -> {
|
||||
if (!link.contains("https:")) {
|
||||
val url = "https:$link"
|
||||
val quality = "Streamtape"
|
||||
val video = StreamTapeExtractor(client).videoFromUrl(url, quality)
|
||||
if (video != null) {
|
||||
videoList.add(video)
|
||||
}
|
||||
} else {
|
||||
val quality = "Streamtape"
|
||||
val video = StreamTapeExtractor(client).videoFromUrl(link, quality)
|
||||
if (video != null) {
|
||||
videoList.add(video)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
link.contains("//streamcrypt.net") || link.contains("https://streamz") && hosterSelection?.contains("streamz") == true -> {
|
||||
if (!link.contains("https:")) {
|
||||
if (link.contains("//streamcrypt.net")) {
|
||||
val url = "https:$link"
|
||||
val zurl = client.newCall(GET(url)).execute().request.url.toString()
|
||||
val quality = "StreamZ"
|
||||
val video = StreamZExtractor(client).videoFromUrl(zurl, quality)
|
||||
if (video != null) {
|
||||
videoList.add(video)
|
||||
}
|
||||
} else {
|
||||
val url = "https:$link"
|
||||
val quality = "StreamZ"
|
||||
val video = StreamZExtractor(client).videoFromUrl(url, quality)
|
||||
if (video != null) {
|
||||
videoList.add(video)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (link.contains("https://streamcrypt.net")) {
|
||||
val url = client.newCall(GET(link)).execute().request.url.toString()
|
||||
val quality = "StreamZ"
|
||||
val video = StreamZExtractor(client).videoFromUrl(url, quality)
|
||||
if (video != null) {
|
||||
videoList.add(video)
|
||||
}
|
||||
} else {
|
||||
val quality = "StreamZ"
|
||||
val video = StreamZExtractor(client).videoFromUrl(link, quality)
|
||||
if (video != null) {
|
||||
videoList.add(video)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
link.contains("vidoza") && hosterSelection?.contains("vidoza") == true -> {
|
||||
if (!link.contains("https:")) {
|
||||
val url = "https:$link"
|
||||
val quality = "Vidoza"
|
||||
val video = VidozaExtractor(client).videoFromUrl(url, quality)
|
||||
if (video != null) {
|
||||
videoList.add(video)
|
||||
}
|
||||
} else {
|
||||
val quality = "Vidoza"
|
||||
val video = VidozaExtractor(client).videoFromUrl(link, quality)
|
||||
if (video != null) {
|
||||
videoList.add(video)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
link.contains("//voe.sx") || link.contains("//launchreliantcleaverriver") ||
|
||||
link.contains("//fraudclatterflyingcar") ||
|
||||
link.contains("//uptodatefinishconferenceroom") || link.contains("//realfinanceblogcenter") && hosterSelection?.contains("voe") == true -> {
|
||||
if (!link.contains("https:")) {
|
||||
val url = "https:$link"
|
||||
val quality = "Voe"
|
||||
val video = VoeExtractor(client).videoFromUrl(url, quality)
|
||||
if (video != null) {
|
||||
videoList.add(video)
|
||||
}
|
||||
} else {
|
||||
val quality = "Voe"
|
||||
val video = VoeExtractor(client).videoFromUrl(link, quality)
|
||||
if (video != null) {
|
||||
videoList.add(video)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return videoList.reversed()
|
||||
}
|
||||
|
||||
override fun List<Video>.sort(): List<Video> {
|
||||
val hoster = preferences.getString("preferred_hoster", null)
|
||||
if (hoster != null) {
|
||||
val newList = mutableListOf<Video>()
|
||||
var preferred = 0
|
||||
for (video in this) {
|
||||
if (video.quality.contains(hoster)) {
|
||||
newList.add(preferred, video)
|
||||
preferred++
|
||||
} else {
|
||||
newList.add(video)
|
||||
}
|
||||
}
|
||||
return newList
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
// Search
|
||||
|
||||
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request =
|
||||
GET("$apiUrl/data/browse/?lang=2&keyword=$query&year=&rating=&votes=&genre=&country=&cast=&directors=&type=&order_by=&page=$page", headers = Headers.headersOf("if-none-match", ""))
|
||||
|
||||
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 pager = jObject["pager"]!!.jsonObject
|
||||
val lastPage = pager.jsonObject["endPage"]!!.jsonPrimitive.int
|
||||
val page = pager.jsonObject["currentPage"]!!.jsonPrimitive.int
|
||||
val hasNextPage = page < lastPage
|
||||
val array = jObject["movies"]!!.jsonArray
|
||||
val animeList = mutableListOf<SAnime>()
|
||||
for (item in array) {
|
||||
val anime = SAnime.create()
|
||||
anime.title = item.jsonObject["title"]!!.jsonPrimitive.content
|
||||
val animeId = item.jsonObject["_id"]!!.jsonPrimitive.content
|
||||
anime.setUrlWithoutDomain("$apiUrl/data/watch/?_id=$animeId")
|
||||
if (item.jsonObject["tv"]!!.jsonPrimitive.int == 1) {
|
||||
anime.thumbnail_url = "https://image.tmdb.org/t/p/w300" + (
|
||||
item.jsonObject["poster_path_season"]?.jsonPrimitive?.content
|
||||
?: ""
|
||||
)
|
||||
} else {
|
||||
anime.thumbnail_url = "https://image.tmdb.org/t/p/w300" + (
|
||||
item.jsonObject["poster_path"]?.jsonPrimitive?.content
|
||||
?: ""
|
||||
)
|
||||
}
|
||||
animeList.add(anime)
|
||||
}
|
||||
return AnimesPage(animeList, hasNextPage)
|
||||
}
|
||||
|
||||
// Details
|
||||
|
||||
override fun animeDetailsRequest(anime: SAnime): Request {
|
||||
return GET(apiUrl + anime.url, headers = Headers.headersOf("if-modified-since", ""))
|
||||
}
|
||||
|
||||
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)
|
||||
anime.title = jObject.jsonObject["title"]!!.jsonPrimitive.content
|
||||
anime.description = jObject.jsonObject["storyline"]!!.jsonPrimitive.content
|
||||
return anime
|
||||
}
|
||||
|
||||
// Latest
|
||||
|
||||
override fun latestUpdatesRequest(page: Int): Request = throw Exception("Not used")
|
||||
|
||||
override fun latestUpdatesParse(response: Response): AnimesPage = throw Exception("not Used")
|
||||
|
||||
// Preferences
|
||||
|
||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||
val hosterPref = ListPreference(screen.context).apply {
|
||||
key = "preferred_hoster"
|
||||
title = "Standard-Hoster"
|
||||
entries = arrayOf("Streamtape", "Voe", "StreamZ", "Vidoza")
|
||||
entryValues = arrayOf("https://streamtape.com", "https://voe.sx", "https://streamz.ws", "https://vidoza.net")
|
||||
setDefaultValue("https://streamtape.com")
|
||||
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", "Voe", "StreamZ", "Vidoza")
|
||||
entryValues = arrayOf("stape", "voe", "streamz", "vidoza")
|
||||
setDefaultValue(setOf("stape", "voe", "streamz", "vidoza"))
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
preferences.edit().putStringSet(key, newValue as Set<String>).commit()
|
||||
}
|
||||
}
|
||||
screen.addPreference(hosterPref)
|
||||
screen.addPreference(subSelection)
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package eu.kanade.tachiyomi.animeextension.de.movie4k.extractors
|
||||
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import okhttp3.OkHttpClient
|
||||
|
||||
class StreamZExtractor(private val client: OkHttpClient) {
|
||||
|
||||
fun videoFromUrl(url: String, quality: String): Video? {
|
||||
val link = client.newCall(GET(url)).execute().request.url.toString()
|
||||
val dllpart = link.substringAfter("/x")
|
||||
val videoUrl = client.newCall(GET("https://get.streamz.tw/getlink-$dllpart.dll")).execute().request.url.toString()
|
||||
return Video(url, quality, videoUrl)
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package eu.kanade.tachiyomi.animeextension.de.movie4k.extractors
|
||||
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import okhttp3.OkHttpClient
|
||||
|
||||
class VidozaExtractor(private val client: OkHttpClient) {
|
||||
|
||||
fun videoFromUrl(url: String, quality: String): Video? {
|
||||
val document = client.newCall(GET(url)).execute().asJsoup()
|
||||
val script = document.select("script:containsData(window.pData = {)")
|
||||
.firstOrNull()?.data()?.substringAfter("sourcesCode: [{ src: \"") ?: return null
|
||||
val videoUrl = script.substringAfter("sourcesCode: [{ src: \"").substringBefore("\", type:")
|
||||
return Video(url, quality, videoUrl)
|
||||
}
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest package="eu.kanade.tachiyomi.animeextension" />
|
@ -1,13 +0,0 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlinx-serialization'
|
||||
|
||||
ext {
|
||||
extName = 'xCine'
|
||||
pkgNameSuffix = 'de.xcine'
|
||||
extClass = '.xCine'
|
||||
extVersionCode = 4
|
||||
libVersion = '13'
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
Before Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 6.2 KiB |
Before Width: | Height: | Size: 6.2 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 9.2 KiB |
Before Width: | Height: | Size: 19 KiB |
@ -1,88 +0,0 @@
|
||||
package eu.kanade.tachiyomi.animeextension.de.xcine
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Application
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.webkit.WebResourceRequest
|
||||
import android.webkit.WebResourceResponse
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import okhttp3.Headers.Companion.toHeaders
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class CookieInterceptor : Interceptor {
|
||||
|
||||
private val context = Injekt.get<Application>()
|
||||
private val handler by lazy { Handler(Looper.getMainLooper()) }
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val originalRequest = chain.request()
|
||||
|
||||
val newRequest = resolveWithWebView(originalRequest) ?: throw Exception("bruh")
|
||||
|
||||
return chain.proceed(newRequest)
|
||||
}
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
private fun resolveWithWebView(request: Request): Request? {
|
||||
// We need to lock this thread until the WebView finds the challenge solution url, because
|
||||
// OkHttp doesn't support asynchronous interceptors.
|
||||
val latch = CountDownLatch(1)
|
||||
|
||||
var webView: WebView? = null
|
||||
|
||||
val origRequestUrl = request.url.toString()
|
||||
val headers = request.headers.toMultimap().mapValues { it.value.getOrNull(0) ?: "" }.toMutableMap()
|
||||
|
||||
var newRequest: Request? = null
|
||||
|
||||
handler.post {
|
||||
val webview = WebView(context)
|
||||
webView = webview
|
||||
with(webview.settings) {
|
||||
javaScriptEnabled = true
|
||||
domStorageEnabled = true
|
||||
databaseEnabled = true
|
||||
useWideViewPort = false
|
||||
loadWithOverviewMode = false
|
||||
userAgentString = request.header("User-Agent")
|
||||
?: "\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36 Edg/88.0.705.63\""
|
||||
}
|
||||
|
||||
webview.webViewClient = object : WebViewClient() {
|
||||
override fun shouldInterceptRequest(
|
||||
view: WebView,
|
||||
request: WebResourceRequest,
|
||||
): WebResourceResponse? {
|
||||
if (request.url.toString().contains("favicon.ico")) {
|
||||
newRequest = GET(request.url.toString(), request.requestHeaders.toHeaders())
|
||||
latch.countDown()
|
||||
}
|
||||
return super.shouldInterceptRequest(view, request)
|
||||
}
|
||||
}
|
||||
|
||||
webView?.loadUrl(origRequestUrl, headers)
|
||||
}
|
||||
|
||||
// Wait a reasonable amount of time to retrieve the solution. The minimum should be
|
||||
// around 4 seconds but it can take more due to slow networks or server issues.
|
||||
latch.await(12, TimeUnit.SECONDS)
|
||||
|
||||
handler.post {
|
||||
webView?.stopLoading()
|
||||
webView?.destroy()
|
||||
webView = null
|
||||
}
|
||||
|
||||
return newRequest
|
||||
}
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
package eu.kanade.tachiyomi.animeextension.de.xcine.extractors
|
||||
|
||||
import eu.kanade.tachiyomi.animeextension.de.xcine.CookieInterceptor
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.POST
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import okhttp3.Headers
|
||||
import okhttp3.OkHttpClient
|
||||
import org.jsoup.nodes.Document
|
||||
|
||||
class HDFilmExtractor(private val client: OkHttpClient) {
|
||||
|
||||
fun videosFromUrl(document: Document): List<Video> {
|
||||
val videoList = mutableListOf<Video>()
|
||||
val id = document.select("#movie_report_error_form input[name=movie_id]").attr("value")
|
||||
val link = if (document.select("#breadcrumbDiv a[title=\"Serien stream\"]").attr("href").contains("/serien1")) {
|
||||
document.select("link[rel=canonical]").attr("href")
|
||||
} else {
|
||||
val movielink = document.select("a.play-film").attr("href")
|
||||
// val cookies = cookieClient.newCall(GET(movielink)).execute().request.headers
|
||||
val moviehtml = client.newCall(GET(movielink)).execute().asJsoup()
|
||||
moviehtml.select("link[rel=canonical]").attr("href")
|
||||
}
|
||||
val loadid = if (document.select("#breadcrumbDiv a[title=\"Serien stream\"]").attr("href").contains("/serien1")) {
|
||||
document.select("a[href=$link]").attr("data-episode-id")
|
||||
} else {
|
||||
val moviehtml = client.newCall(GET(link)).execute().asJsoup()
|
||||
moviehtml.select("a#nextEpisodeToPlay").attr("data-episode-id")
|
||||
}
|
||||
val cookieClient = client.newBuilder().addInterceptor(CookieInterceptor()).build()
|
||||
val cookieheaders = cookieClient.newCall(GET(link)).execute().headers
|
||||
val headers = Headers.Builder()
|
||||
.addAll(cookieheaders)
|
||||
.add("origin", "https://xcine.me")
|
||||
.add("referer", link)
|
||||
.add("X-Requested-With", "XMLHttpRequest")
|
||||
.add("Content-Type", "application/x-www-form-urlencoded")
|
||||
.build()
|
||||
val playlist = client.newCall(POST("https://xcine.me/movie/load-stream/$id/$loadid?server=1", headers = headers)).execute().asJsoup()
|
||||
playlist.toString().substringAfter("var vip_source = [{\"file\":\"").substringBefore("];").split("\"file\":\"").forEach {
|
||||
val quality = it.substringAfter("\"label\":\"").substringBefore("\"}")
|
||||
val videoUrl = it.substringAfter("\"file\":\"").substringBefore("\",\"type").replace("\\", "")
|
||||
if (client.newCall(GET(videoUrl)).execute().code == 204) {
|
||||
throw Exception("Einmal WebView öffnen und wieder schließen")
|
||||
} else {
|
||||
videoList.addAll((listOf(Video(videoUrl, quality, videoUrl))))
|
||||
}
|
||||
}
|
||||
return videoList
|
||||
}
|
||||
}
|
@ -1,216 +0,0 @@
|
||||
package eu.kanade.tachiyomi.animeextension.de.xcine
|
||||
|
||||
import android.app.Application
|
||||
import android.content.SharedPreferences
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.PreferenceScreen
|
||||
import eu.kanade.tachiyomi.animeextension.de.xcine.extractors.HDFilmExtractor
|
||||
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.network.POST
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import okhttp3.Headers
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import okhttp3.Response
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import kotlin.Exception
|
||||
|
||||
class xCine : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
||||
|
||||
override val name = "xCine"
|
||||
|
||||
override val baseUrl = "https://xcine.me"
|
||||
|
||||
override val lang = "de"
|
||||
|
||||
override val supportsLatest = true
|
||||
|
||||
override val client: OkHttpClient = network.cloudflareClient
|
||||
|
||||
private val preferences: SharedPreferences by lazy {
|
||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||
}
|
||||
|
||||
override fun headersBuilder(): Headers.Builder = Headers.Builder()
|
||||
.add("cookie", "PHPSESSID=2d88418b469e522a75dd4c8327c3a936; SERVERID=s2; _ga=GA1.2.826139063.1658618592; _gid=GA1.2.162365688.1658618592; _gat_gtag_UA_144665518_1=1; __gads=ID=4fe53672dd9aeab8-22d4864165d40078:T=1658618593:RT=1658618593:S=ALNI_MYrmBvhl86smHeaNwW8I5UMS2ZOlA; _pop=1; dom3ic8zudi28v8lr6fgphwffqoz0j6c=3f6f53a9-7930-4d7a-87b1-c67125d128e0%3A2%3A1; m5a4xojbcp2nx3gptmm633qal3gzmadn=hopefullyapricot.com; _TqHT6=_0xc53")
|
||||
|
||||
override fun popularAnimeSelector(): String = "div.group-film-small a"
|
||||
|
||||
override fun popularAnimeRequest(page: Int): Request = POST(
|
||||
"$baseUrl/filme1?page=$page&sort=view_total&sort_type=desc", body = "load=full-page".toRequestBody("application/x-www-form-urlencoded".toMediaType()),
|
||||
headers = headersBuilder().build()
|
||||
)
|
||||
|
||||
override fun popularAnimeFromElement(element: Element): SAnime {
|
||||
val anime = SAnime.create()
|
||||
anime.setUrlWithoutDomain(element.attr("href"))
|
||||
val urlpart = element.select("div.poster-film-small").toString()
|
||||
.substringAfter("data-src=\"https://cdn.xcine.me/img/").substringBefore("\"")
|
||||
anime.thumbnail_url = "https://cdn.xcine.me/img/$urlpart"
|
||||
anime.title = element.attr("title").replace(" stream", "")
|
||||
return anime
|
||||
}
|
||||
|
||||
override fun popularAnimeNextPageSelector(): String = ".pag-next a[title=Next]"
|
||||
|
||||
// episodes
|
||||
|
||||
override fun episodeListSelector() = throw Exception("not used")
|
||||
|
||||
override fun episodeListParse(response: Response): List<SEpisode> {
|
||||
val document = response.asJsoup()
|
||||
val episodeList = mutableListOf<SEpisode>()
|
||||
if (document.select("#breadcrumbDiv a[title=\"Serien stream\"]").attr("href").contains("/serien1")) {
|
||||
val seriesLink = document.select("link[rel=canonical]").attr("abs:href")
|
||||
val seasonHtml = client.newCall(GET("$seriesLink/folge-1", headers = Headers.headersOf("Referer", document.location())))
|
||||
.execute().asJsoup()
|
||||
val episodeElement = seasonHtml.select("ul.list-inline.list-film li")
|
||||
episodeElement.forEach {
|
||||
val episode = parseEpisodesFromSeries(it)
|
||||
episodeList.addAll(episode)
|
||||
}
|
||||
} else {
|
||||
val episode = SEpisode.create()
|
||||
episode.name = document.select("h1.title-film-detail-1").text()
|
||||
episode.episode_number = 1F
|
||||
episode.setUrlWithoutDomain(document.select("link[rel=canonical]").attr("href"))
|
||||
episodeList.add(episode)
|
||||
}
|
||||
return episodeList.reversed()
|
||||
}
|
||||
|
||||
private fun parseEpisodesFromSeries(element: Element): List<SEpisode> {
|
||||
val episodeElements = element.select("a")
|
||||
return episodeElements.map { episodeFromElement(it) }
|
||||
}
|
||||
|
||||
override fun episodeFromElement(element: Element): SEpisode {
|
||||
val episode = SEpisode.create()
|
||||
episode.episode_number = element.text().substringAfter("-").toFloat()
|
||||
episode.name = element.attr("title").replace("stream", "")
|
||||
episode.setUrlWithoutDomain(element.attr("href"))
|
||||
return episode
|
||||
}
|
||||
|
||||
// Video Extractor
|
||||
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val document = response.asJsoup()
|
||||
return videosFromElement(document)
|
||||
}
|
||||
|
||||
private fun videosFromElement(document: Document): List<Video> {
|
||||
val videoList = mutableListOf<Video>()
|
||||
val videos = HDFilmExtractor(client).videosFromUrl(document)
|
||||
videoList.addAll(videos)
|
||||
return videoList.reversed()
|
||||
}
|
||||
|
||||
override fun List<Video>.sort(): List<Video> {
|
||||
val hoster = preferences.getString("preferred_hoster", null)
|
||||
if (hoster != null) {
|
||||
val newList = mutableListOf<Video>()
|
||||
var preferred = 0
|
||||
for (video in this) {
|
||||
if (video.quality.contains(hoster)) {
|
||||
newList.add(preferred, video)
|
||||
preferred++
|
||||
} else {
|
||||
newList.add(video)
|
||||
}
|
||||
}
|
||||
return newList
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
override fun videoListSelector() = throw Exception("not used")
|
||||
|
||||
override fun videoFromElement(element: Element) = throw Exception("not used")
|
||||
|
||||
override fun videoUrlParse(document: Document) = throw Exception("not used")
|
||||
|
||||
// Search
|
||||
|
||||
override fun searchAnimeFromElement(element: Element): SAnime {
|
||||
val anime = SAnime.create()
|
||||
anime.setUrlWithoutDomain(element.attr("href"))
|
||||
val urlpart = element.select("div.poster-film-small").toString()
|
||||
.substringAfter("data-src=\"https://cdn.xcine.me/img/").substringBefore("\"")
|
||||
anime.thumbnail_url = "https://cdn.xcine.me/img/$urlpart"
|
||||
anime.title = element.attr("title").replace(" stream", "")
|
||||
return anime
|
||||
}
|
||||
|
||||
override fun searchAnimeNextPageSelector(): String = ".pag-next a[title=Next]"
|
||||
|
||||
override fun searchAnimeSelector(): String = "div.group-film-small a"
|
||||
|
||||
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
|
||||
return POST(
|
||||
"$baseUrl/search?page=$page&key=$query", body = "load=full-page".toRequestBody("application/x-www-form-urlencoded; charset=UTF-8".toMediaType()),
|
||||
headers = headersBuilder().build()
|
||||
)
|
||||
}
|
||||
|
||||
// Details
|
||||
|
||||
override fun animeDetailsParse(document: Document): SAnime {
|
||||
val anime = SAnime.create()
|
||||
anime.thumbnail_url = anime.thumbnail_url
|
||||
anime.title = document.select("h1.title-film-detail-1").text()
|
||||
anime.genre = document.select("ul[class=infomation-film]").joinToString(", ") {
|
||||
it.toString()
|
||||
.substringAfter("Genre:").replace("<span>", "").substringBefore("</span>")
|
||||
}
|
||||
anime.description = document.select("p[class=content-film]").text()
|
||||
anime.author = document.select("ul[class=infomation-film]").joinToString(", ") {
|
||||
it.toString()
|
||||
.substringAfter("Regisseur:").replace("<span>", "").substringBefore("</span>")
|
||||
}
|
||||
anime.status = SAnime.COMPLETED
|
||||
return anime
|
||||
}
|
||||
|
||||
// Latest
|
||||
|
||||
override fun latestUpdatesNextPageSelector(): String = throw Exception("Not used")
|
||||
|
||||
override fun latestUpdatesFromElement(element: Element): SAnime = throw Exception("Not used")
|
||||
|
||||
override fun latestUpdatesRequest(page: Int): Request = throw Exception("Not used")
|
||||
|
||||
override fun latestUpdatesSelector(): String = throw Exception("Not used")
|
||||
|
||||
// Preferences
|
||||
|
||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||
val hosterPref = ListPreference(screen.context).apply {
|
||||
key = "preferred_hoster"
|
||||
title = "Standard-Hoster"
|
||||
entries = arrayOf("2k", "1080p", "720p", "480p", "360p")
|
||||
entryValues = arrayOf("2", "1080", "720", "480", "360")
|
||||
setDefaultValue("2")
|
||||
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(hosterPref)
|
||||
}
|
||||
}
|