chore: Remove (known) dead sources (#2593)
Before Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 5.6 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 86 KiB |
@ -1,136 +0,0 @@
|
||||
package eu.kanade.tachiyomi.animeextension.en.animeonline360
|
||||
|
||||
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.multisrc.dooplay.DooPlay
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.jsoup.nodes.Element
|
||||
|
||||
class AnimeOnline360 : DooPlay(
|
||||
"en",
|
||||
"AnimeOnline360",
|
||||
"https://animeonline360.me",
|
||||
) {
|
||||
// ============================== Popular ===============================
|
||||
|
||||
override fun popularAnimeSelector(): String = "div.content article > div.poster"
|
||||
|
||||
override fun popularAnimeRequest(page: Int) = GET("$baseUrl/trending/page/$page/")
|
||||
|
||||
override fun popularAnimeNextPageSelector() = latestUpdatesNextPageSelector()
|
||||
|
||||
override fun popularAnimeFromElement(element: Element): SAnime {
|
||||
return super.popularAnimeFromElement(element).apply { title = title.addSubPrefix() }
|
||||
}
|
||||
|
||||
// =============================== Latest ===============================
|
||||
|
||||
override fun latestUpdatesSelector(): String = "div#archive-content > article"
|
||||
|
||||
override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/anime-a/page/$page/")
|
||||
|
||||
override fun latestUpdatesNextPageSelector(): String = "#nextpagination"
|
||||
|
||||
override fun latestUpdatesFromElement(element: Element): SAnime {
|
||||
return super.latestUpdatesFromElement(element).apply { title = title.addSubPrefix() }
|
||||
}
|
||||
|
||||
// =============================== Search ===============================
|
||||
|
||||
override fun searchAnimeNextPageSelector(): String = "div.pagination > span.current + a"
|
||||
|
||||
// ============================== Episodes ==============================
|
||||
|
||||
override fun episodeListSelector() = "ul.episodios > li"
|
||||
|
||||
override fun episodeListParse(response: Response): List<SEpisode> {
|
||||
return if (response.request.url.pathSegments.first().contains("movie", true)) {
|
||||
val document = response.use { getRealAnimeDoc(it.asJsoup()) }
|
||||
listOf(
|
||||
SEpisode.create().apply {
|
||||
episode_number = 0F
|
||||
date_upload = document.selectFirst("div.extra > span.date")
|
||||
?.text()
|
||||
?.toDate() ?: 0L
|
||||
name = "Movie"
|
||||
setUrlWithoutDomain(response.request.url.toString())
|
||||
},
|
||||
)
|
||||
} else {
|
||||
response.use {
|
||||
getRealAnimeDoc(it.asJsoup())
|
||||
}.select(episodeListSelector()).map(::episodeFromElement)
|
||||
}
|
||||
}
|
||||
|
||||
override fun episodeFromElement(element: Element): SEpisode {
|
||||
return SEpisode.create().apply {
|
||||
val epNum = element.selectFirst("div.numerando")!!.text()
|
||||
.trim()
|
||||
.let(episodeNumberRegex::find)
|
||||
?.groupValues
|
||||
?.last() ?: "0"
|
||||
val href = element.selectFirst("a[href]")!!
|
||||
val episodeName = href.ownText()
|
||||
episode_number = epNum.toFloatOrNull() ?: 0F
|
||||
date_upload = element.selectFirst(episodeDateSelector)
|
||||
?.text()
|
||||
?.toDate() ?: 0L
|
||||
name = "Ep. $epNum - $episodeName"
|
||||
setUrlWithoutDomain(href.attr("href"))
|
||||
}
|
||||
}
|
||||
|
||||
// ============================== Filters ===============================
|
||||
|
||||
override val fetchGenres = false
|
||||
|
||||
override fun getFilterList(): AnimeFilterList = AnimeFilterList(
|
||||
AnimeFilter.Header("Text search ignores filters"),
|
||||
SubPageFilter(),
|
||||
)
|
||||
|
||||
private class SubPageFilter : UriPartFilter(
|
||||
"Sub-Page",
|
||||
arrayOf(
|
||||
Pair("<select>", ""),
|
||||
Pair("Dubbed", "genre-a/dubbed"),
|
||||
Pair("Movies", "movies-a"),
|
||||
),
|
||||
)
|
||||
|
||||
// ============================ Video Links =============================
|
||||
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val document = response.use { it.asJsoup() }
|
||||
|
||||
return document.select("iframe[src~=player]").mapNotNull {
|
||||
if (it.attr("src").toHttpUrl().queryParameter("source") != null) {
|
||||
runCatching {
|
||||
val link = it.attr("src").toHttpUrl().queryParameter("source")!!
|
||||
Video(link, "Video", link)
|
||||
}.getOrNull()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================= Utilities ==============================
|
||||
private fun String.addSubPrefix(): String {
|
||||
return if (this.contains(" dubbed", true)) {
|
||||
"[DUB] ${this.substringBefore(" Dubbed")}"
|
||||
} else if (this.contains(" subbed", true)) {
|
||||
"[SUB] ${this.substringBefore(" Subbed")}"
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
}
|
@ -11,7 +11,6 @@ class DooPlayGenerator : ThemeSourceGenerator {
|
||||
override val baseVersionCode = 1
|
||||
|
||||
override val sources = listOf(
|
||||
SingleLang("AnimeOnline360", "https://animeonline360.me", "en", isNsfw = false),
|
||||
SingleLang("AnimeOnline.Ninja", "https://ww3.animeonline.ninja", "es", className = "AnimeOnlineNinja", isNsfw = false, overrideVersionCode = 34),
|
||||
SingleLang("AnimesOnline", "https://animesonline.nz", "pt-BR", isNsfw = false, overrideVersionCode = 6, pkgName = "animesgratis"),
|
||||
SingleLang("AnimePlayer", "https://animeplayer.com.br", "pt-BR", isNsfw = true, overrideVersionCode = 2),
|
||||
|
@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest />
|
@ -1,20 +0,0 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlinx-serialization'
|
||||
|
||||
ext {
|
||||
extName = 'HDFilme'
|
||||
pkgNameSuffix = 'de.hdfilme'
|
||||
extClass = '.HDFilme'
|
||||
extVersionCode = 3
|
||||
libVersion = '13'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(':lib-vudeo-extractor'))
|
||||
implementation(project(':lib-mixdrop-extractor'))
|
||||
implementation(project(':lib-dood-extractor'))
|
||||
}
|
||||
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
Before Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 841 B |
Before Width: | Height: | Size: 5.4 KiB |
Before Width: | Height: | Size: 5.4 KiB |
Before Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 448 B |
Before Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 8.2 KiB |
Before Width: | Height: | Size: 8.2 KiB |
Before Width: | Height: | Size: 8.7 KiB |
Before Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 24 KiB |
@ -1,233 +0,0 @@
|
||||
package eu.kanade.tachiyomi.animeextension.de.hdfilme
|
||||
|
||||
import android.app.Application
|
||||
import android.content.SharedPreferences
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.MultiSelectListPreference
|
||||
import androidx.preference.PreferenceScreen
|
||||
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
|
||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
||||
import eu.kanade.tachiyomi.animesource.model.SAnime
|
||||
import eu.kanade.tachiyomi.animesource.model.SEpisode
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
|
||||
import eu.kanade.tachiyomi.lib.doodextractor.DoodExtractor
|
||||
import eu.kanade.tachiyomi.lib.mixdropextractor.MixDropExtractor
|
||||
import eu.kanade.tachiyomi.lib.vudeoextractor.VudeoExtractor
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import kotlin.Exception
|
||||
|
||||
class HDFilme : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
||||
|
||||
override val name = "HDFilme"
|
||||
|
||||
override val baseUrl = "https://www.hdfilme.fun"
|
||||
|
||||
override val lang = "de"
|
||||
|
||||
override val supportsLatest = false
|
||||
|
||||
override val client: OkHttpClient = network.cloudflareClient
|
||||
|
||||
private val preferences: SharedPreferences by lazy {
|
||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||
}
|
||||
|
||||
override fun popularAnimeSelector(): String = "#dle-content div.short"
|
||||
|
||||
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/filmestreamen/page-$page.html")
|
||||
|
||||
override fun popularAnimeFromElement(element: Element): SAnime {
|
||||
val anime = SAnime.create()
|
||||
anime.setUrlWithoutDomain(element.select("a.short-img").attr("href"))
|
||||
anime.thumbnail_url = baseUrl + element.select("a.short-img img ").attr("src")
|
||||
anime.title = element.select("a.short-img img").attr("alt")
|
||||
return anime
|
||||
}
|
||||
|
||||
override fun popularAnimeNextPageSelector(): String = "span.pnext a"
|
||||
|
||||
// episodes
|
||||
|
||||
override fun episodeListSelector() = throw Exception("not used")
|
||||
|
||||
override fun episodeListParse(response: Response): List<SEpisode> {
|
||||
val document = response.asJsoup()
|
||||
val url = document.select("link[rel=\"canonical\"]").attr("href")
|
||||
val episodeList = mutableListOf<SEpisode>()
|
||||
if (url.contains("serienstreamen")) {
|
||||
val episodeElement = document.select("div.seasontab div[align=\"center\"] a")
|
||||
episodeElement.forEach {
|
||||
val episodes = parseEpisodesFromSeries(it)
|
||||
episodeList.addAll(episodes)
|
||||
}
|
||||
} else {
|
||||
val episode = SEpisode.create()
|
||||
episode.episode_number = 1F
|
||||
episode.name = "Film"
|
||||
episode.setUrlWithoutDomain(document.select("link[rel=\"canonical\"]").attr("href"))
|
||||
episodeList.add(episode)
|
||||
}
|
||||
return episodeList.reversed()
|
||||
}
|
||||
|
||||
private fun parseEpisodesFromSeries(element: Element): List<SEpisode> {
|
||||
val seasonurl = element.attr("href")
|
||||
val episodesHtml = client.newCall(GET(seasonurl)).execute().asJsoup()
|
||||
val episodeElements = episodesHtml.select("div.seasontab div[align=\"center\"] a")
|
||||
return episodeElements.map { episodeFromElement(it) }
|
||||
}
|
||||
|
||||
override fun episodeFromElement(element: Element): SEpisode {
|
||||
val episode = SEpisode.create()
|
||||
episode.episode_number = element.select("span.number").text().toFloat()
|
||||
episode.name = element.attr("title")
|
||||
episode.setUrlWithoutDomain(element.attr("href"))
|
||||
return episode
|
||||
}
|
||||
|
||||
// Video Extractor
|
||||
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val document = response.asJsoup()
|
||||
return videosFromElement(document)
|
||||
}
|
||||
|
||||
private fun videosFromElement(document: Document): List<Video> {
|
||||
val videoList = mutableListOf<Video>()
|
||||
val hosterSelection = preferences.getStringSet("hoster_selection", setOf("dood", "vud", "mix"))
|
||||
document.select("div.undervideo li").forEach {
|
||||
val url = it.select("div.lien").attr("data-url")
|
||||
when {
|
||||
url.contains("https://dood") && hosterSelection?.contains("dood") == true -> {
|
||||
val quality = "Doodstream"
|
||||
val video = DoodExtractor(client).videoFromUrl(url, quality)
|
||||
if (video != null) {
|
||||
videoList.add(video)
|
||||
}
|
||||
}
|
||||
url.contains("https://vudeo") && hosterSelection?.contains("vud") == true -> {
|
||||
val video = VudeoExtractor(client).videosFromUrl(url)
|
||||
videoList.addAll(video)
|
||||
}
|
||||
url.contains("https://mixdrop") && hosterSelection?.contains("mix") == true -> {
|
||||
val video = MixDropExtractor(client).videoFromUrl(url)
|
||||
videoList.addAll(video)
|
||||
}
|
||||
}
|
||||
}
|
||||
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 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.select("a.short-img").attr("href"))
|
||||
anime.thumbnail_url = baseUrl + element.select("a.short-img img ").attr("src")
|
||||
anime.title = element.select("a.short-img img").attr("alt")
|
||||
return anime
|
||||
}
|
||||
|
||||
override fun searchAnimeNextPageSelector(): String = "span.pnext a"
|
||||
|
||||
override fun searchAnimeSelector(): String = "#dle-content div.short"
|
||||
|
||||
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request = GET("$baseUrl/recherche?q=$query&page=$page")
|
||||
|
||||
// Details
|
||||
|
||||
override fun animeDetailsParse(document: Document): SAnime {
|
||||
val anime = SAnime.create()
|
||||
anime.thumbnail_url = baseUrl + document.select("div.movie-page div.mimg img").attr("src")
|
||||
anime.title = document.select("meta[property=\"og:title\"]").attr("content")
|
||||
anime.description = document.select("meta[property=\"og:title\"]").attr("content")
|
||||
document.select("div.short-info div.short-info").forEach { el ->
|
||||
if (el.text().contains("Schauspieler:")) {
|
||||
anime.author = el.text().replace("Schauspieler:", "").split(" , ").joinToString(", ") { it }
|
||||
}
|
||||
}
|
||||
document.select("div.short-info div.short-info").forEach { el ->
|
||||
if (el.text().contains("Genre:")) {
|
||||
anime.genre = el.select("a").joinToString(", ") { it.text() }
|
||||
}
|
||||
}
|
||||
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("Doodstream", "Vudeo", "Mixdrop")
|
||||
entryValues = arrayOf("https://dood", "https://vudeo", "https://mixdrop")
|
||||
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", "Vudeo", "MixDrop")
|
||||
entryValues = arrayOf("dood", "vud", "mix")
|
||||
setDefaultValue(setOf("dood", "vud", "mix"))
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
preferences.edit().putStringSet(key, newValue as Set<String>).commit()
|
||||
}
|
||||
}
|
||||
screen.addPreference(hosterPref)
|
||||
screen.addPreference(subSelection)
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<application>
|
||||
<activity
|
||||
android:name=".de.kiste.KisteUrlActivity"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="true"
|
||||
android:theme="@android:style/Theme.NoDisplay">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:host="kiste.to"
|
||||
android:pathPattern="/..*/..*"
|
||||
android:scheme="https" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
@ -1,20 +0,0 @@
|
||||
plugins {
|
||||
alias(libs.plugins.android.application)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
alias(libs.plugins.kotlin.serialization)
|
||||
}
|
||||
|
||||
ext {
|
||||
extName = 'Kiste'
|
||||
pkgNameSuffix = 'de.kiste'
|
||||
extClass = '.Kiste'
|
||||
extVersionCode = 4
|
||||
libVersion = '13'
|
||||
containsNsfw = true
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":lib-playlist-utils"))
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
Before Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 5.3 KiB |
Before Width: | Height: | Size: 9.8 KiB |
Before Width: | Height: | Size: 14 KiB |
@ -1,204 +0,0 @@
|
||||
package eu.kanade.tachiyomi.animeextension.de.kiste
|
||||
|
||||
import android.util.Base64
|
||||
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.playlistutils.PlaylistUtils
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.json.Json
|
||||
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.injectLazy
|
||||
import java.net.URLEncoder
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
class Kiste : ParsedAnimeHttpSource() {
|
||||
|
||||
override val name = "Kiste"
|
||||
|
||||
override val baseUrl = "https://kiste.to"
|
||||
|
||||
override val lang = "de"
|
||||
|
||||
override val supportsLatest = true
|
||||
|
||||
private val json: Json by injectLazy()
|
||||
|
||||
// ============================== Popular ===============================
|
||||
override fun popularAnimeRequest(page: Int) = GET("$baseUrl/search?sort=imdb:desc&page=$page")
|
||||
|
||||
override fun popularAnimeSelector() = "div.filmlist > div.item > a"
|
||||
|
||||
override fun popularAnimeFromElement(element: Element) = SAnime.create().apply {
|
||||
setUrlWithoutDomain(element.attr("href"))
|
||||
title = element.attr("title")
|
||||
thumbnail_url = element.selectFirst("img")?.absUrl("src")
|
||||
}
|
||||
|
||||
override fun popularAnimeNextPageSelector() = "li > a[rel=next]"
|
||||
|
||||
// =============================== Latest ===============================
|
||||
override fun latestUpdatesRequest(page: Int) = GET(baseUrl)
|
||||
|
||||
override fun latestUpdatesSelector() = "div[data-name=alles] > div.filmlist > div.item > a"
|
||||
|
||||
override fun latestUpdatesFromElement(element: Element) = popularAnimeFromElement(element)
|
||||
|
||||
override fun latestUpdatesNextPageSelector() = null
|
||||
|
||||
// =============================== Search ===============================
|
||||
override fun getFilterList() = KisteFilters.FILTER_LIST
|
||||
|
||||
override fun fetchSearchAnime(page: Int, query: String, filters: AnimeFilterList): Observable<AnimesPage> {
|
||||
return if (query.startsWith(PREFIX_SEARCH)) { // URL intent handler
|
||||
val path = query.removePrefix(PREFIX_SEARCH)
|
||||
client.newCall(GET("$baseUrl/$path"))
|
||||
.asObservableSuccess()
|
||||
.map(::searchAnimeByIdParse)
|
||||
} else {
|
||||
super.fetchSearchAnime(page, query, filters)
|
||||
}
|
||||
}
|
||||
|
||||
private fun searchAnimeByIdParse(response: Response): AnimesPage {
|
||||
val details = animeDetailsParse(response.use { it.asJsoup() })
|
||||
return AnimesPage(listOf(details), false)
|
||||
}
|
||||
|
||||
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
|
||||
val params = KisteFilters.getSearchParameters(filters)
|
||||
val url = buildString {
|
||||
append("$baseUrl/search?page=$page")
|
||||
if (query.isNotBlank()) append("&keyword=$query")
|
||||
with(params) {
|
||||
listOf(genres, types, countries, years, qualities)
|
||||
.filter(String::isNotBlank)
|
||||
.forEach { append("&$it") }
|
||||
}
|
||||
}
|
||||
return GET(url, headers)
|
||||
}
|
||||
|
||||
override fun searchAnimeSelector() = popularAnimeSelector()
|
||||
|
||||
override fun searchAnimeFromElement(element: Element) = popularAnimeFromElement(element)
|
||||
|
||||
override fun searchAnimeNextPageSelector() = popularAnimeNextPageSelector()
|
||||
|
||||
// =========================== Anime Details ============================
|
||||
override fun animeDetailsParse(document: Document) = SAnime.create().apply {
|
||||
val section = document.selectFirst("section.info")!!
|
||||
thumbnail_url = section.selectFirst("img")?.absUrl("src")
|
||||
title = section.selectFirst("h1.title")!!.text()
|
||||
genre = section.select("span:containsOwn(Genre:) + span > a")
|
||||
.eachText()
|
||||
.joinToString()
|
||||
.takeIf(String::isNotBlank)
|
||||
description = section.selectFirst("div.desc")?.text()
|
||||
}
|
||||
|
||||
// ============================== Episodes ==============================
|
||||
@Serializable data class HtmlData(val html: String)
|
||||
|
||||
override fun fetchEpisodeList(anime: SAnime): Observable<List<SEpisode>> {
|
||||
val slug = anime.url.substringAfterLast("/")
|
||||
val vrf = URLEncoder.encode(encryptRC4(slug).trimEnd(), "utf-8")
|
||||
val newDoc = client.newCall(GET("$baseUrl/ajax/film/servers.php?id=$slug&vrf=$vrf&episode=1-1&token="))
|
||||
.execute()
|
||||
.use { json.decodeFromString<HtmlData>(it.body.string()).html }
|
||||
.let(Jsoup::parse)
|
||||
|
||||
val episodes = newDoc.select(episodeListSelector())
|
||||
.map(::episodeFromElement)
|
||||
.sortedByDescending { it.episode_number }
|
||||
|
||||
return Observable.just(episodes)
|
||||
}
|
||||
|
||||
override fun episodeListParse(response: Response) = throw Exception("not used")
|
||||
|
||||
override fun episodeListSelector() = "div.episode > a"
|
||||
|
||||
override fun episodeFromElement(element: Element) = SEpisode.create().apply {
|
||||
val id = element.attr("data-ep").substringAfter("\":\"").substringBefore('"')
|
||||
setUrlWithoutDomain(element.attr("href") + "?id=$id")
|
||||
val kname = element.attr("data-kname")
|
||||
val (seasonNum, epNum) = kname.split("-", limit = 2)
|
||||
name = "Staffel $seasonNum - Episode $epNum"
|
||||
episode_number = "$seasonNum.${epNum.padStart(3, '0')}".toFloatOrNull() ?: 1F
|
||||
}
|
||||
|
||||
// ============================ Video Links =============================
|
||||
private val playlistUtils by lazy { PlaylistUtils(client, headers) }
|
||||
|
||||
override fun fetchVideoList(episode: SEpisode): Observable<List<Video>> {
|
||||
val id = episode.url.substringAfter("?id=")
|
||||
val headers = headersBuilder()
|
||||
.add("Referer", episode.url)
|
||||
.add("X-Requested-With", "XMLHttpRequest")
|
||||
.build()
|
||||
|
||||
val url = client.newCall(GET("$baseUrl/ajax/episode/info.php?id=$id", headers))
|
||||
.execute()
|
||||
.use { it.body.string().substringAfter(":\"").substringBefore('"') }
|
||||
.let(::decryptRC4)
|
||||
|
||||
val playlistUrl = baseUrl + client.newCall(GET(url, headers)).execute()
|
||||
.use { it.body.string() }
|
||||
.substringAfter("file: \"", "")
|
||||
.substringBefore('"')
|
||||
|
||||
return Observable.just(playlistUtils.extractFromHls(playlistUrl, baseUrl + episode.url))
|
||||
}
|
||||
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
throw UnsupportedOperationException("Not used.")
|
||||
}
|
||||
|
||||
override fun videoListSelector(): String {
|
||||
throw UnsupportedOperationException("Not used.")
|
||||
}
|
||||
|
||||
override fun videoFromElement(element: Element): Video {
|
||||
throw UnsupportedOperationException("Not used.")
|
||||
}
|
||||
|
||||
override fun videoUrlParse(document: Document): String {
|
||||
throw UnsupportedOperationException("Not used.")
|
||||
}
|
||||
|
||||
// ============================= Utilities ==============================
|
||||
private fun decryptRC4(data: String): String {
|
||||
val b64decoded = Base64.decode(data, Base64.DEFAULT)
|
||||
val rc4Key = SecretKeySpec(KISTE_KEY, "RC4")
|
||||
val cipher = Cipher.getInstance("RC4")
|
||||
cipher.init(Cipher.DECRYPT_MODE, rc4Key, cipher.getParameters())
|
||||
return cipher.doFinal(b64decoded).toString(Charsets.UTF_8)
|
||||
}
|
||||
|
||||
private fun encryptRC4(data: String): String {
|
||||
val rc4Key = SecretKeySpec(KISTE_KEY, "RC4")
|
||||
val cipher = Cipher.getInstance("RC4")
|
||||
cipher.init(Cipher.ENCRYPT_MODE, rc4Key, cipher.getParameters())
|
||||
return Base64.encodeToString(cipher.doFinal(data.toByteArray()), Base64.DEFAULT)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val PREFIX_SEARCH = "path:"
|
||||
|
||||
private val KISTE_KEY = "DZmuZuXqa9O0z3b7".toByteArray()
|
||||
}
|
||||
}
|
@ -1,202 +0,0 @@
|
||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
|
||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
||||
|
||||
object KisteFilters {
|
||||
open class CheckBoxFilterList(name: String, val pairs: Array<Pair<String, String>>) :
|
||||
AnimeFilter.Group<AnimeFilter.CheckBox>(name, pairs.map { CheckBoxVal(it.first, false) })
|
||||
|
||||
private class CheckBoxVal(name: String, state: Boolean = false) : AnimeFilter.CheckBox(name, state)
|
||||
|
||||
private inline fun <reified R> AnimeFilterList.parseCheckbox(
|
||||
options: Array<Pair<String, String>>,
|
||||
name: String,
|
||||
): String {
|
||||
return (first { it is R } as CheckBoxFilterList).state
|
||||
.filter { it.state }
|
||||
.map { checkbox -> options.find { it.first == checkbox.name }!!.second }
|
||||
.filter(String::isNotBlank)
|
||||
.joinToString("&") { "$name[]=$it" }
|
||||
}
|
||||
|
||||
class GenresFilter : CheckBoxFilterList("Genres", KisteFiltersData.GENRES)
|
||||
class TypesFilter : CheckBoxFilterList("Types", KisteFiltersData.TYPES)
|
||||
class CountriesFilter : CheckBoxFilterList("Countries", KisteFiltersData.COUNTRIES)
|
||||
class YearsFilter : CheckBoxFilterList("Jaht", KisteFiltersData.YEARS)
|
||||
class QualitiesFilter : CheckBoxFilterList("Qualitat", KisteFiltersData.QUALITIES)
|
||||
|
||||
val FILTER_LIST get() = AnimeFilterList(
|
||||
GenresFilter(),
|
||||
TypesFilter(),
|
||||
CountriesFilter(),
|
||||
YearsFilter(),
|
||||
QualitiesFilter(),
|
||||
)
|
||||
|
||||
data class FilterSearchParams(
|
||||
val genres: String = "",
|
||||
val types: String = "",
|
||||
val countries: String = "",
|
||||
val years: String = "",
|
||||
val qualities: String = "",
|
||||
)
|
||||
|
||||
internal fun getSearchParameters(filters: AnimeFilterList): FilterSearchParams {
|
||||
if (filters.isEmpty()) return FilterSearchParams()
|
||||
|
||||
return FilterSearchParams(
|
||||
filters.parseCheckbox<GenresFilter>(KisteFiltersData.GENRES, "genre"),
|
||||
filters.parseCheckbox<TypesFilter>(KisteFiltersData.TYPES, "type"),
|
||||
filters.parseCheckbox<CountriesFilter>(KisteFiltersData.COUNTRIES, "country"),
|
||||
filters.parseCheckbox<YearsFilter>(KisteFiltersData.YEARS, "release"),
|
||||
filters.parseCheckbox<QualitiesFilter>(KisteFiltersData.QUALITIES, "quality"),
|
||||
)
|
||||
}
|
||||
|
||||
private object KisteFiltersData {
|
||||
val GENRES = arrayOf(
|
||||
Pair("Abenteuer", "43"),
|
||||
Pair("Action", "1"),
|
||||
Pair("Adventure", "2"),
|
||||
Pair("Amerikanisch", "53"),
|
||||
Pair("Animation", "14"),
|
||||
Pair("Anime", "19"),
|
||||
Pair("Biography", "36"),
|
||||
Pair("Britisch", "54"),
|
||||
Pair("Cartoon", "55"),
|
||||
Pair("Children", "22"),
|
||||
Pair("Chinesisch", "52"),
|
||||
Pair("Comedy", "5"),
|
||||
Pair("Crime", "10"),
|
||||
Pair("Deutsch", "49"),
|
||||
Pair("Documentary", "18"),
|
||||
Pair("Dokumentarfilm", "40"),
|
||||
Pair("Drama", "9"),
|
||||
Pair("Familie", "39"),
|
||||
Pair("Family", "23"),
|
||||
Pair("Fantasy", "3"),
|
||||
Pair("Film", "4"),
|
||||
Pair("Food", "16"),
|
||||
Pair("Game Show", "17"),
|
||||
Pair("Historie", "46"),
|
||||
Pair("History", "20"),
|
||||
Pair("Horror", "15"),
|
||||
Pair("Japanisch", "50"),
|
||||
Pair("Kids", "29"),
|
||||
Pair("Kinofilme", "57"),
|
||||
Pair("Komödie", "37"),
|
||||
Pair("Koreanisch", "51"),
|
||||
Pair("Kriegsfilm", "47"),
|
||||
Pair("Krimi", "38"),
|
||||
Pair("Liebesfilm", "44"),
|
||||
Pair("Martial Arts", "24"),
|
||||
Pair("Mini-Series", "31"),
|
||||
Pair("Musical", "28"),
|
||||
Pair("Musik", "48"),
|
||||
Pair("Mystery", "11"),
|
||||
Pair("News", "45"),
|
||||
Pair("Politics", "41"),
|
||||
Pair("Reality", "26"),
|
||||
Pair("Romance", "7"),
|
||||
Pair("Sci-Fi", "34"),
|
||||
Pair("Science Fiction", "6"),
|
||||
Pair("Serie", "12"),
|
||||
Pair("Short", "25"),
|
||||
Pair("Soap", "30"),
|
||||
Pair("Sport", "33"),
|
||||
Pair("Supernatural", "27"),
|
||||
Pair("Suspense", "13"),
|
||||
Pair("Talk", "42"),
|
||||
Pair("Thriller", "8"),
|
||||
Pair("Travel", "32"),
|
||||
Pair("War", "21"),
|
||||
Pair("Western", "35"),
|
||||
)
|
||||
|
||||
val TYPES = arrayOf(
|
||||
Pair("Filme", "movie"),
|
||||
Pair("TV-Serien", "series"),
|
||||
)
|
||||
|
||||
val COUNTRIES = arrayOf(
|
||||
Pair("Argentinien", "AR"),
|
||||
Pair("Austrailen", "AU"),
|
||||
Pair("Belgien", "BE"),
|
||||
Pair("Brasilien", "BR"),
|
||||
Pair("China", "CN"),
|
||||
Pair("Deutschland", "DE"),
|
||||
Pair("Dänemark", "DK"),
|
||||
Pair("Finnland", "FI"),
|
||||
Pair("Frankreich", "FR"),
|
||||
Pair("Großbritannien", "GB"),
|
||||
Pair("Hong-Kong", "HK"),
|
||||
Pair("Indien", "IN"),
|
||||
Pair("Irland", "IE"),
|
||||
Pair("Israel", "IL"),
|
||||
Pair("Italien", "IT"),
|
||||
Pair("Japan", "JP"),
|
||||
Pair("Kanada", "CA"),
|
||||
Pair("Mexiko", "MX"),
|
||||
Pair("Neuseeland", "NZ"),
|
||||
Pair("Niederlanden", "NL"),
|
||||
Pair("Norwergen", "NO"),
|
||||
Pair("Philippinen", "PH"),
|
||||
Pair("Poland", "PL"),
|
||||
Pair("Rumänien", "RO"),
|
||||
Pair("Russland", "RU"),
|
||||
Pair("Schweden", "SE"),
|
||||
Pair("Schweiz", "CH"),
|
||||
Pair("Spanien", "ES"),
|
||||
Pair("Südafrika", "ZA"),
|
||||
Pair("Südkorea", "KR"),
|
||||
Pair("Thailand", "TH"),
|
||||
Pair("Tschechien", "CZ"),
|
||||
Pair("Türkei", "TR"),
|
||||
Pair("USA", "US"),
|
||||
Pair("Ungarn", "HU"),
|
||||
Pair("Österreich", "AT"),
|
||||
)
|
||||
|
||||
val YEARS = arrayOf(
|
||||
Pair("2023", "2023"),
|
||||
Pair("2022", "2022"),
|
||||
Pair("2021", "2021"),
|
||||
Pair("2020", "2020"),
|
||||
Pair("2019", "2019"),
|
||||
Pair("2018", "2018"),
|
||||
Pair("2017", "2017"),
|
||||
Pair("2016", "2016"),
|
||||
Pair("2015", "2015"),
|
||||
Pair("2014", "2014"),
|
||||
Pair("2013", "2013"),
|
||||
Pair("2012", "2012"),
|
||||
Pair("2011", "2011"),
|
||||
Pair("2010", "2010"),
|
||||
Pair("2009", "2009"),
|
||||
Pair("2008", "2008"),
|
||||
Pair("2007", "2007"),
|
||||
Pair("2006", "2006"),
|
||||
Pair("2005", "2005"),
|
||||
Pair("2004", "2004"),
|
||||
Pair("2003", "2003"),
|
||||
Pair("2000s", "2000s"),
|
||||
Pair("1990s", "1990s"),
|
||||
Pair("1980s", "1980s"),
|
||||
Pair("1970s", "1970s"),
|
||||
Pair("1960s", "1960s"),
|
||||
Pair("1950s", "1950s"),
|
||||
Pair("1940s", "1940s"),
|
||||
Pair("1930s", "1930s"),
|
||||
Pair("1920s", "1920s"),
|
||||
Pair("1910s", "1910s"),
|
||||
Pair("1900s", "1900s"),
|
||||
)
|
||||
|
||||
val QUALITIES = arrayOf(
|
||||
Pair("CAM", "CAM"),
|
||||
Pair("HD", "HD"),
|
||||
Pair("HDRip", "HDRip"),
|
||||
Pair("SD", "SD"),
|
||||
Pair("TS", "TS"),
|
||||
)
|
||||
}
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
package eu.kanade.tachiyomi.animeextension.de.kiste
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
/**
|
||||
* Springboard that accepts https://kiste.to/<type>/<item> intents
|
||||
* and redirects them to the main Aniyomi process.
|
||||
*/
|
||||
class KisteUrlActivity : Activity() {
|
||||
|
||||
private val tag = javaClass.simpleName
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val pathSegments = intent?.data?.pathSegments
|
||||
if (pathSegments != null && pathSegments.size > 1) {
|
||||
val type = pathSegments[0]
|
||||
val item = pathSegments[1]
|
||||
val path = "$type/$item"
|
||||
val mainIntent = Intent().apply {
|
||||
action = "eu.kanade.tachiyomi.ANIMESEARCH"
|
||||
putExtra("query", "${Kiste.PREFIX_SEARCH}$path")
|
||||
putExtra("filter", packageName)
|
||||
}
|
||||
|
||||
try {
|
||||
startActivity(mainIntent)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Log.e(tag, e.toString())
|
||||
}
|
||||
} else {
|
||||
Log.e(tag, "could not parse uri from intent $intent")
|
||||
}
|
||||
|
||||
finish()
|
||||
exitProcess(0)
|
||||
}
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest />
|
@ -1,12 +0,0 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
ext {
|
||||
extName = 'NollyVerse'
|
||||
pkgNameSuffix = 'en.nollyverse'
|
||||
extClass = '.NollyVerse'
|
||||
extVersionCode = 2
|
||||
libVersion = '13'
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 6.6 KiB |
Before Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 77 KiB |
Before Width: | Height: | Size: 371 KiB |
@ -1,555 +0,0 @@
|
||||
package eu.kanade.tachiyomi.animeextension.en.nollyverse
|
||||
|
||||
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.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.network.GET
|
||||
import eu.kanade.tachiyomi.network.POST
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import okhttp3.FormBody
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.lang.Exception
|
||||
|
||||
class NollyVerse : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
||||
|
||||
override val name = "NollyVerse"
|
||||
|
||||
override val baseUrl = "https://www.nollyverse.com"
|
||||
|
||||
override val lang = "en"
|
||||
|
||||
override val supportsLatest = true
|
||||
|
||||
override val client: OkHttpClient = network.cloudflareClient
|
||||
|
||||
private val preferences: SharedPreferences by lazy {
|
||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||
}
|
||||
|
||||
// ============================== Popular ===============================
|
||||
|
||||
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/category/trending-movies/page/$page/")
|
||||
|
||||
override fun popularAnimeParse(response: Response): AnimesPage {
|
||||
val document = response.asJsoup()
|
||||
|
||||
val animes = document.select(popularAnimeSelector()).map { element ->
|
||||
popularAnimeFromElement(element)
|
||||
}
|
||||
|
||||
val hasNextPage = popularAnimeNextPageSelector()?.let { selector ->
|
||||
if (document.select(selector).text() != ">") {
|
||||
return AnimesPage(animes, false)
|
||||
}
|
||||
document.select(selector).first()
|
||||
} != null
|
||||
|
||||
return AnimesPage(animes, hasNextPage)
|
||||
}
|
||||
|
||||
override fun popularAnimeSelector(): String = "div.col-md-8 div.row div.col-md-6"
|
||||
|
||||
override fun popularAnimeFromElement(element: Element): SAnime = SAnime.create().apply {
|
||||
title = element.select("div.post-body h3 a").text()
|
||||
thumbnail_url = element.select("a.post-img img").attr("data-src")
|
||||
setUrlWithoutDomain(element.select("a.post-img").attr("href"))
|
||||
}
|
||||
|
||||
override fun popularAnimeNextPageSelector(): String = "div.loadmore ul.pagination.pagination-md li:nth-last-child(2)"
|
||||
|
||||
// =============================== Latest ===============================
|
||||
|
||||
override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/category/new-series/page/$page/")
|
||||
|
||||
override fun latestUpdatesParse(response: Response): AnimesPage {
|
||||
val document = response.asJsoup()
|
||||
|
||||
val animes = document.select(latestUpdatesSelector()).map { element ->
|
||||
latestUpdatesFromElement(element)
|
||||
}
|
||||
|
||||
val hasNextPage = latestUpdatesNextPageSelector()?.let { selector ->
|
||||
if (document.select(selector).text() != ">") {
|
||||
return AnimesPage(animes, false)
|
||||
}
|
||||
document.select(selector).first()
|
||||
} != null
|
||||
|
||||
return AnimesPage(animes, hasNextPage)
|
||||
}
|
||||
|
||||
override fun latestUpdatesSelector(): String = "div.section div.container div.row div.post.post-row"
|
||||
|
||||
override fun latestUpdatesFromElement(element: Element): SAnime = SAnime.create().apply {
|
||||
title = element.select("div.post-body h3 a").text()
|
||||
thumbnail_url = element.select("a.post-img img").attr("data-src").ifEmpty {
|
||||
element.select("a.post-img img").attr("src")
|
||||
}
|
||||
setUrlWithoutDomain(element.select("a.post-img").attr("href"))
|
||||
}
|
||||
|
||||
override fun latestUpdatesNextPageSelector(): String = "div.loadmore ul.pagination.pagination-md li:nth-last-child(2)"
|
||||
|
||||
// =============================== Search ===============================
|
||||
|
||||
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
|
||||
return if (query.isNotBlank()) {
|
||||
val body = FormBody.Builder()
|
||||
.add("name", "$query")
|
||||
.build()
|
||||
POST("$baseUrl/livesearch.php", body = body)
|
||||
} else {
|
||||
var searchPath = ""
|
||||
filters.filter { it.state != 0 }.forEach { filter ->
|
||||
when (filter) {
|
||||
is CategoryFilter -> searchPath = if (filter.toUriPart() == "/series/") filter.toUriPart() else "${filter.toUriPart()}page/$page"
|
||||
is MovieGenreFilter -> searchPath = "/movies/genre/${filter.toUriPart()}/page/$page"
|
||||
is SeriesGenreFilter -> searchPath = "/series/genre/${filter.toUriPart()}/page/$page"
|
||||
else -> ""
|
||||
}
|
||||
}
|
||||
|
||||
require(searchPath.isNotEmpty()) { "Search must not be empty" }
|
||||
|
||||
GET(baseUrl + searchPath)
|
||||
}
|
||||
}
|
||||
|
||||
override fun searchAnimeParse(response: Response): AnimesPage {
|
||||
val document = response.asJsoup()
|
||||
val path = response.request.url.encodedPath
|
||||
|
||||
var hasNextPage: Boolean
|
||||
var animes: List<SAnime>
|
||||
|
||||
when {
|
||||
path.startsWith("/livesearch") -> {
|
||||
hasNextPage = false
|
||||
animes = document.select(searchAnimeSelector()).map { element ->
|
||||
searchAnimeFromElement(element)
|
||||
}
|
||||
}
|
||||
|
||||
path.startsWith("/movies/genre/") -> {
|
||||
animes = document.select(movieGenreSelector()).map { element ->
|
||||
movieGenreFromElement(element)
|
||||
}
|
||||
hasNextPage = nextPageSelector()?.let { selector ->
|
||||
if (document.select(selector).text() != ">") {
|
||||
return AnimesPage(animes, false)
|
||||
}
|
||||
document.select(selector).first()
|
||||
} != null
|
||||
}
|
||||
|
||||
path.startsWith("/series/genre/") ||
|
||||
path.startsWith("/category/popular-movies/") ||
|
||||
path.startsWith("/category/trending-movies/") -> {
|
||||
animes = document.select(seriesGenreSelector()).map { element ->
|
||||
seriesGenreFromElement(element)
|
||||
}
|
||||
hasNextPage = nextPageSelector()?.let { selector ->
|
||||
if (document.select(selector).text() != ">") {
|
||||
return AnimesPage(animes, false)
|
||||
}
|
||||
document.select(selector).first()
|
||||
} != null
|
||||
}
|
||||
|
||||
path.startsWith("/category/korean-movies/") || path.startsWith("/category/korean-series/") -> {
|
||||
animes = document.select(koreanSelector()).map { element ->
|
||||
koreanFromElement(element)
|
||||
}
|
||||
hasNextPage = nextPageSelector()?.let { selector ->
|
||||
if (document.select(selector).text() != ">") {
|
||||
return AnimesPage(animes, false)
|
||||
}
|
||||
document.select(selector).first()
|
||||
} != null
|
||||
}
|
||||
|
||||
path.startsWith("/category/latest-movies/") ||
|
||||
path.startsWith("/category/new-series/") ||
|
||||
path.startsWith("/category/latest-uploads/") -> {
|
||||
animes = document.select(latestSelector()).map { element ->
|
||||
latestFromElement(element)
|
||||
}
|
||||
hasNextPage = nextPageSelector()?.let { selector ->
|
||||
if (document.select(selector).text() != ">") {
|
||||
return AnimesPage(animes, false)
|
||||
}
|
||||
document.select(selector).first()
|
||||
} != null
|
||||
}
|
||||
|
||||
path.startsWith("/series/") -> {
|
||||
animes = document.select(seriesSelector()).map { element ->
|
||||
seriesFromElement(element)
|
||||
}
|
||||
hasNextPage = false
|
||||
}
|
||||
|
||||
else -> {
|
||||
animes = document.select(movieSelector()).map { element ->
|
||||
movieFromElement(element)
|
||||
}
|
||||
hasNextPage = nextPageSelector()?.let { selector ->
|
||||
if (document.select(selector).text() != ">") {
|
||||
return AnimesPage(animes, false)
|
||||
}
|
||||
document.select(selector).first()
|
||||
} != null
|
||||
}
|
||||
}
|
||||
|
||||
return AnimesPage(animes, hasNextPage)
|
||||
}
|
||||
|
||||
private fun movieGenreSelector(): String = "div.container > div.row > div.col-md-4"
|
||||
|
||||
private fun movieGenreFromElement(element: Element): SAnime = latestUpdatesFromElement(element)
|
||||
|
||||
private fun seriesGenreSelector(): String = "div.row div.col-md-8 div.col-md-6"
|
||||
|
||||
private fun seriesGenreFromElement(element: Element): SAnime = latestUpdatesFromElement(element)
|
||||
|
||||
private fun koreanSelector(): String = "div.col-md-8 div.row div.col-md-6"
|
||||
|
||||
private fun koreanFromElement(element: Element): SAnime = latestUpdatesFromElement(element)
|
||||
|
||||
private fun latestSelector(): String = latestUpdatesSelector()
|
||||
|
||||
private fun latestFromElement(element: Element): SAnime = latestUpdatesFromElement(element)
|
||||
|
||||
private fun seriesSelector(): String = "div.section-row ul.list-style li"
|
||||
|
||||
private fun seriesFromElement(element: Element): SAnime = SAnime.create().apply {
|
||||
title = element.select("a").text()
|
||||
thumbnail_url = toImgUrl(element.select("a").attr("href"))
|
||||
setUrlWithoutDomain(element.select("a").attr("href"))
|
||||
}
|
||||
|
||||
private fun movieSelector(): String = "div.container div.row div.col-md-12 div.col-md-4"
|
||||
|
||||
private fun movieFromElement(element: Element): SAnime = SAnime.create().apply {
|
||||
title = element.select("h3 a").text()
|
||||
thumbnail_url = element.select("a img").attr("src")
|
||||
setUrlWithoutDomain(element.select("a.post-img").attr("href"))
|
||||
}
|
||||
|
||||
override fun searchAnimeSelector(): String = "a"
|
||||
|
||||
override fun searchAnimeFromElement(element: Element): SAnime = SAnime.create().apply {
|
||||
title = element.text()
|
||||
thumbnail_url = toImgUrl(element.attr("href"))
|
||||
setUrlWithoutDomain(element.attr("href"))
|
||||
}
|
||||
|
||||
private fun nextPageSelector(): String = "ul.pagination.pagination-md li:nth-last-child(2)"
|
||||
|
||||
override fun searchAnimeNextPageSelector(): String = throw Exception("Not used")
|
||||
|
||||
// =========================== Anime Details ============================
|
||||
|
||||
override fun animeDetailsParse(document: Document): SAnime = SAnime.create().apply {
|
||||
title = document.select("div.page-header div.container div.row div.text-center h1").text()
|
||||
description = document.select("blockquote.blockquote small").text()
|
||||
genre = document.select("div.col-md-8 ul.list-style li").firstOrNull {
|
||||
it.text().startsWith("Genre: ")
|
||||
}?.text()?.substringAfter("Genre: ")?.replace(",", ", ")
|
||||
}
|
||||
|
||||
// ============================== Episodes ==============================
|
||||
|
||||
override fun episodeListRequest(anime: SAnime): Request {
|
||||
return if (anime.url.startsWith("/movie/")) {
|
||||
GET(baseUrl + anime.url + "/download/", headers)
|
||||
} else {
|
||||
GET(baseUrl + anime.url + "/seasons/", headers)
|
||||
}
|
||||
}
|
||||
|
||||
override fun episodeListParse(response: Response): List<SEpisode> {
|
||||
val path = response.request.url.encodedPath
|
||||
|
||||
val document = response.asJsoup()
|
||||
val episodeList = mutableListOf<SEpisode>()
|
||||
|
||||
if (path.startsWith("/movie/")) {
|
||||
episodeList.add(
|
||||
SEpisode.create().apply {
|
||||
name = "Movie"
|
||||
episode_number = 1F
|
||||
setUrlWithoutDomain(path)
|
||||
},
|
||||
)
|
||||
} else {
|
||||
var counter = 1
|
||||
for (season in document.select("table.table.table-striped tbody tr").reversed()) {
|
||||
val seasonUrl = season.select("td a[href]").attr("href")
|
||||
val seasonSoup = client.newCall(
|
||||
GET(seasonUrl, headers),
|
||||
).execute().asJsoup()
|
||||
|
||||
val episodeTable = seasonSoup.select("table.table.table-striped")
|
||||
val seasonNumber = episodeTable.select("thead th").eachText().find {
|
||||
t ->
|
||||
"""Season (\d+)""".toRegex().matches(t)
|
||||
}?.split(" ")!![1]
|
||||
|
||||
for (ep in episodeTable.select("tbody tr")) {
|
||||
episodeList.add(
|
||||
SEpisode.create().apply {
|
||||
name = "Episode S${seasonNumber}E${ep.selectFirst("td")!!.text().split(" ")!![1]}"
|
||||
episode_number = counter.toFloat()
|
||||
setUrlWithoutDomain(seasonUrl + "#$counter")
|
||||
},
|
||||
)
|
||||
counter++
|
||||
}
|
||||
|
||||
// Stop abuse
|
||||
Thread.sleep(500)
|
||||
}
|
||||
}
|
||||
|
||||
return episodeList.reversed()
|
||||
}
|
||||
|
||||
override fun episodeFromElement(element: Element): SEpisode {
|
||||
val seasonNum = element.ownerDocument()!!.select("div.Title span").text()
|
||||
|
||||
return SEpisode.create().apply {
|
||||
name = "Season $seasonNum" + "x" + element.select("td span.Num").text() + " : " + element.select("td.MvTbTtl > a").text()
|
||||
episode_number = element.select("td > span.Num").text().toFloat()
|
||||
setUrlWithoutDomain(element.select("td.MvTbPly > a.ClA").attr("abs:href"))
|
||||
}
|
||||
}
|
||||
|
||||
override fun episodeListSelector() = throw Exception("not used")
|
||||
|
||||
// ============================ Video Links =============================
|
||||
|
||||
override fun videoListRequest(episode: SEpisode): Request {
|
||||
return if (episode.name == "Movie") {
|
||||
GET(baseUrl + episode.url + "#movie", headers)
|
||||
} else {
|
||||
val episodeIndex = """Episode S(\d+)E(?<num>\d+)""".toRegex().matchEntire(
|
||||
episode.name,
|
||||
)!!.groups["num"]!!.value
|
||||
GET(baseUrl + episode.url.replaceAfterLast("#", "") + episodeIndex, headers)
|
||||
}
|
||||
}
|
||||
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val videoList = mutableListOf<Video>()
|
||||
val document = response.asJsoup()
|
||||
|
||||
val fragment = response.request.url.fragment!!
|
||||
if (fragment == "movie") {
|
||||
for (res in document.select("table.table.table-striped tbody tr")) {
|
||||
val url = res.select("td a").attr("href")
|
||||
val name = res.select("td:not(:has(a))").text().trim()
|
||||
videoList.add(Video(url, name, url))
|
||||
}
|
||||
} else {
|
||||
val episodeIndex = fragment.toInt() - 1
|
||||
|
||||
val episodeList = document.select("table.table.table-striped tbody tr").toList()
|
||||
|
||||
for (res in episodeList[episodeIndex].select("td").reversed()) {
|
||||
val url = res.select("a").attr("href")
|
||||
if (url.isNotEmpty()) {
|
||||
videoList.add(
|
||||
Video(url, res.text().trim(), url),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
require(videoList.isNotEmpty()) { "Failed to fetch videos" }
|
||||
|
||||
return videoList.sort()
|
||||
}
|
||||
|
||||
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")
|
||||
|
||||
// ============================= Utilities ==============================
|
||||
|
||||
override fun List<Video>.sort(): List<Video> {
|
||||
val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
|
||||
val codec = preferences.getString(PREF_CODEC_KEY, PREF_CODEC_KEY)!!
|
||||
|
||||
return this.sortedWith(
|
||||
compareBy(
|
||||
{ it.quality.contains(quality) },
|
||||
{ it.quality.contains(codec) },
|
||||
),
|
||||
).reversed()
|
||||
}
|
||||
|
||||
private fun toImgUrl(inputUrl: String): String {
|
||||
val url = inputUrl.removeSuffix("/").toHttpUrl()
|
||||
val pathSeg = url.encodedPathSegments.toMutableList()
|
||||
pathSeg.add(1, "img")
|
||||
return url.scheme +
|
||||
"://" +
|
||||
url.host +
|
||||
"/" +
|
||||
pathSeg.joinToString(separator = "/") +
|
||||
".jpg"
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val PREF_QUALITY_KEY = "preferred_quality"
|
||||
private const val PREF_QUALITY_DEFAULT = "1080"
|
||||
|
||||
private const val PREF_CODEC_KEY = "preferred_codec"
|
||||
private const val PREF_CODEC_DEFAULT = "265"
|
||||
}
|
||||
|
||||
// ============================== Settings ==============================
|
||||
|
||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||
ListPreference(screen.context).apply {
|
||||
key = PREF_QUALITY_KEY
|
||||
title = "Preferred quality"
|
||||
entries = arrayOf("1080p", "720p", "480p", "360p", "240p")
|
||||
entryValues = arrayOf("1080", "720", "480", "360", "240")
|
||||
setDefaultValue(PREF_QUALITY_DEFAULT)
|
||||
summary = "%s"
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val selected = newValue as String
|
||||
val index = findIndexOfValue(selected)
|
||||
val entry = entryValues[index] as String
|
||||
preferences.edit().putString(key, entry).commit()
|
||||
}
|
||||
}.also(screen::addPreference)
|
||||
|
||||
ListPreference(screen.context).apply {
|
||||
key = PREF_CODEC_KEY
|
||||
title = "Preferred Video Codec"
|
||||
entries = arrayOf("h265", "h264")
|
||||
entryValues = arrayOf("265", "264")
|
||||
setDefaultValue(PREF_CODEC_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)
|
||||
}
|
||||
|
||||
// ============================== Filters ===============================
|
||||
|
||||
override fun getFilterList() = AnimeFilterList(
|
||||
AnimeFilter.Header("NOTE: Only one works at a time"),
|
||||
AnimeFilter.Separator(),
|
||||
CategoryFilter(getCategoryList()),
|
||||
MovieGenreFilter(getMovieGenreList()),
|
||||
SeriesGenreFilter(getSeriesGenreList()),
|
||||
)
|
||||
|
||||
private class CategoryFilter(vals: Array<Pair<String, String>>) : UriPartFilter("Category", vals)
|
||||
private class MovieGenreFilter(vals: Array<Pair<String, String>>) : UriPartFilter("Movies Genre", vals)
|
||||
private class SeriesGenreFilter(vals: Array<Pair<String, String>>) : UriPartFilter("Series Genres", vals)
|
||||
|
||||
private fun getCategoryList() = arrayOf(
|
||||
Pair("None", "none"),
|
||||
Pair("All series", "/series/"),
|
||||
Pair("All movies", "/movies/"),
|
||||
Pair("Koren movies", "/category/korean-movies/"),
|
||||
Pair("Koren series", "/category/korean-series/"),
|
||||
Pair("Latest movies", "/category/latest-movies/"),
|
||||
Pair("Latest series", "/category/new-series/"),
|
||||
Pair("Latest uploads", "/category/latest-uploads/"),
|
||||
Pair("Popular movies", "/category/popular-movies/"),
|
||||
Pair("Trending movies", "/category/trending-movies/"),
|
||||
)
|
||||
|
||||
private fun getMovieGenreList() = arrayOf(
|
||||
Pair("All", "all"),
|
||||
Pair("Drama", "drama"),
|
||||
Pair("Comedy", "comedy"),
|
||||
Pair("Action", "action"),
|
||||
Pair("Thriller", "thriller"),
|
||||
Pair("Crime", "crime"),
|
||||
Pair("Adventure", "adventure"),
|
||||
Pair("Mystery", "mystery"),
|
||||
Pair("Sci-Fi", "sci-Fi"),
|
||||
Pair("Fantasy", "fantasy"),
|
||||
Pair("Horror", "horror"),
|
||||
Pair("Animation", "animation"),
|
||||
Pair("Romance", "romance"),
|
||||
Pair("Documentary", "documentary"),
|
||||
Pair("Family", "family"),
|
||||
Pair("Biography", "biography"),
|
||||
Pair("History", "history"),
|
||||
Pair("Music", "music"),
|
||||
Pair("Sport", "sport"),
|
||||
Pair("Musical", "musical"),
|
||||
Pair("War", "war"),
|
||||
Pair("Western", "western"),
|
||||
Pair("News", "news"),
|
||||
)
|
||||
|
||||
private fun getSeriesGenreList() = arrayOf(
|
||||
Pair("All", "all"),
|
||||
Pair("Drama", "drama"),
|
||||
Pair("Comedy", "comedy"),
|
||||
Pair("Action", "action"),
|
||||
Pair("Thriller", "thriller"),
|
||||
Pair("Crime", "crime"),
|
||||
Pair("Adventure", "adventure"),
|
||||
Pair("Mystery", "mystery"),
|
||||
Pair("Sci-Fi", "sci-Fi"),
|
||||
Pair("Fantasy", "fantasy"),
|
||||
Pair("Horror", "horror"),
|
||||
Pair("Animation", "animation"),
|
||||
Pair("Romance", "romance"),
|
||||
Pair("Documentary", "documentary"),
|
||||
Pair("Family", "family"),
|
||||
Pair("Biography", "biography"),
|
||||
Pair("History", "history"),
|
||||
Pair("Music", "music"),
|
||||
Pair("Sport", "sport"),
|
||||
Pair("Musical", "musical"),
|
||||
Pair("War", "war"),
|
||||
Pair("Western", "western"),
|
||||
Pair("Reality-TV", "reality-TV"),
|
||||
Pair("Game-Show", "game-show"),
|
||||
Pair("News", "news"),
|
||||
Pair("Sitcom", "sitcom"),
|
||||
Pair("Talk-Show", "talk-show"),
|
||||
Pair("Costume", "Costume"),
|
||||
)
|
||||
|
||||
open class UriPartFilter(displayName: String, private val vals: Array<Pair<String, String>>) :
|
||||
AnimeFilter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
|
||||
fun toUriPart() = vals[state].second
|
||||
}
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest />
|
@ -1,18 +0,0 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
ext {
|
||||
extName = 'AnimeLove'
|
||||
pkgNameSuffix = 'it.animelove'
|
||||
extClass = '.AnimeLove'
|
||||
extVersionCode = 2
|
||||
libVersion = '13'
|
||||
containsNsfw = true
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(':lib-streamtape-extractor'))
|
||||
}
|
||||
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
Before Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 9.8 KiB |
Before Width: | Height: | Size: 46 KiB |
@ -1,361 +0,0 @@
|
||||
package eu.kanade.tachiyomi.animeextension.it.animelove
|
||||
|
||||
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.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.streamtapeextractor.StreamTapeExtractor
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import okhttp3.OkHttpClient
|
||||
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 AnimeLove : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
||||
|
||||
override val name = "AnimeLove"
|
||||
|
||||
override val baseUrl = "https://www.animelove.tv"
|
||||
|
||||
override val lang = "it"
|
||||
|
||||
override val supportsLatest = true
|
||||
|
||||
override val client: OkHttpClient = network.cloudflareClient
|
||||
|
||||
private val preferences: SharedPreferences by lazy {
|
||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||
}
|
||||
|
||||
// ============================== Popular ===============================
|
||||
|
||||
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/anime-in-corso/page/$page/")
|
||||
|
||||
override fun popularAnimeSelector(): String = "div.containerlista > div.row > div.col-6"
|
||||
|
||||
override fun popularAnimeFromElement(element: Element): SAnime = SAnime.create().apply {
|
||||
setUrlWithoutDomain(element.selectFirst("a")!!.attr("href"))
|
||||
thumbnail_url = element.selectFirst("img")?.attr("src")
|
||||
title = element.selectFirst("div.default-text")!!.text()
|
||||
}
|
||||
|
||||
override fun popularAnimeNextPageSelector(): String = "div > ul.page-nav > li:last-child:not(:has(a.disabled))"
|
||||
|
||||
// =============================== Latest ===============================
|
||||
|
||||
override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/nuovi-anime/page/$page/")
|
||||
|
||||
override fun latestUpdatesSelector(): String = popularAnimeSelector()
|
||||
|
||||
override fun latestUpdatesFromElement(element: Element): SAnime = popularAnimeFromElement(element)
|
||||
|
||||
override fun latestUpdatesNextPageSelector(): String = popularAnimeNextPageSelector()
|
||||
|
||||
// =============================== Search ===============================
|
||||
|
||||
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
|
||||
val letterFilter = filterList.find { it is LetterFilter } as LetterFilter
|
||||
|
||||
return when {
|
||||
query.isNotBlank() -> GET("$baseUrl/cerca?q=$query")
|
||||
genreFilter.state != 0 -> GET("$baseUrl/genere/${genreFilter.toUriPart()}/page/$page/")
|
||||
letterFilter.state != 0 -> {
|
||||
val slug = if (page == 1) "/lista-anime?alphabet=${letterFilter.toUriPart()}" else "/lista-anime/page/$page/${letterFilter.toUriPart()}/"
|
||||
GET(baseUrl + slug)
|
||||
}
|
||||
else -> popularAnimeRequest(page)
|
||||
}
|
||||
}
|
||||
|
||||
override fun searchAnimeParse(response: Response): AnimesPage {
|
||||
if (response.request.url.encodedPath != "/cerca") {
|
||||
return super.searchAnimeParse(response)
|
||||
}
|
||||
|
||||
val animeList = response.asJsoup()
|
||||
.select(searchAnimeSelectorSearch())
|
||||
.map(::searchAnimeFromElementSearch)
|
||||
|
||||
return AnimesPage(animeList, false)
|
||||
}
|
||||
|
||||
override fun searchAnimeSelector(): String = popularAnimeSelector()
|
||||
|
||||
private fun searchAnimeSelectorSearch(): String = "div.col-md-8 > div.card > div.card-body > div.row > div.col-6"
|
||||
|
||||
override fun searchAnimeFromElement(element: Element): SAnime = popularAnimeFromElement(element)
|
||||
|
||||
private fun searchAnimeFromElementSearch(element: Element): SAnime = SAnime.create().apply {
|
||||
setUrlWithoutDomain(element.selectFirst("a")!!.attr("href"))
|
||||
thumbnail_url = element.selectFirst("img")?.attr("src")
|
||||
title = element.selectFirst("p.card-text")!!.text()
|
||||
}
|
||||
|
||||
override fun searchAnimeNextPageSelector(): String = popularAnimeNextPageSelector()
|
||||
|
||||
// ============================== Filters ===============================
|
||||
|
||||
override fun getFilterList(): AnimeFilterList = AnimeFilterList(
|
||||
AnimeFilter.Header("La ricerca testuale ignora i filtri"),
|
||||
GenreFilter(),
|
||||
LetterFilter(),
|
||||
)
|
||||
|
||||
private class GenreFilter : UriPartFilter(
|
||||
"Generi",
|
||||
arrayOf(
|
||||
Pair("<Selezionare>", ""),
|
||||
Pair("Action", "Action"),
|
||||
Pair("Adventure", "Adventure"),
|
||||
Pair("Avant GardeAvant Garde", "Avant-GardeAvant-Garde"),
|
||||
Pair("Award WiningAward Wining", "Award-WiningAward-Wining"),
|
||||
Pair("Bender", "Bender"),
|
||||
Pair("Boys LoveBoys Love", "Boys-LoveBoys-Love"),
|
||||
Pair("Cars", "Cars"),
|
||||
Pair("Comedy", "Comedy"),
|
||||
Pair("Dantasy", "Dantasy"),
|
||||
Pair("Dementia", "Dementia"),
|
||||
Pair("Demons", "Demons"),
|
||||
Pair("Detective", "Detective"),
|
||||
Pair("Drama", "Drama"),
|
||||
Pair("Drammatico", "Drammatico"),
|
||||
Pair("Ecchi", "Ecchi"),
|
||||
Pair("Erotic", "Erotic"),
|
||||
Pair("Erotica", "Erotica"),
|
||||
Pair("Fantasy", "Fantasy"),
|
||||
Pair("Fighting", "Fighting"),
|
||||
Pair("Game", "Game"),
|
||||
Pair("Gender", "Gender"),
|
||||
Pair("Girls LoveGirls Love", "Girls-LoveGirls-Love"),
|
||||
Pair("Goofy", "Goofy"),
|
||||
Pair("Gourmet", "Gourmet"),
|
||||
Pair("Harem", "Harem"),
|
||||
Pair("Hentai", "Hentai"),
|
||||
Pair("Historical", "Historical"),
|
||||
Pair("Horror", "Horror"),
|
||||
Pair("Josei", "Josei"),
|
||||
Pair("Kids", "Kids"),
|
||||
Pair("Lifestyle", "Lifestyle"),
|
||||
Pair("Lolicon", "Lolicon"),
|
||||
Pair("Magic", "Magic"),
|
||||
Pair("Martial Arts", "Martial-Arts"),
|
||||
Pair("Mecha", "Mecha"),
|
||||
Pair("Military", "Military"),
|
||||
Pair("Music", "Music"),
|
||||
Pair("Mystery", "Mystery"),
|
||||
Pair("N/A", "A"),
|
||||
Pair("null", "null"),
|
||||
Pair("Parody", "Parody"),
|
||||
Pair("Police", "Police"),
|
||||
Pair("Psychological", "Psychological"),
|
||||
Pair("Romance", "Romance"),
|
||||
Pair("SAction", "SAction"),
|
||||
Pair("Samurai", "Samurai"),
|
||||
Pair("School", "School"),
|
||||
Pair("Sci-fi", "Sci-fi"),
|
||||
Pair("Sci-FiSci-Fi", "Sci-FiSci-Fi"),
|
||||
Pair("Seinen", "Seinen"),
|
||||
Pair("Sentimental", "Sentimental"),
|
||||
Pair("Shoujo", "Shoujo"),
|
||||
Pair("Shoujo Ai", "Shoujo-Ai"),
|
||||
Pair("Shounen", "Shounen"),
|
||||
Pair("Shounen Ai", "Shounen-Ai"),
|
||||
Pair("Slice of Life", "Slice-of-Life"),
|
||||
Pair("Slice of LifeSlice of Life", "Slice-of-LifeSlice-of-Life"),
|
||||
Pair("Smut", "Smut"),
|
||||
Pair("Space", "Space"),
|
||||
Pair("Splatter", "Splatter"),
|
||||
Pair("Sport", "Sport"),
|
||||
Pair("Super Power", "Super-Power"),
|
||||
Pair("Supernatural", "Supernatural"),
|
||||
Pair("Suspense", "Suspense"),
|
||||
Pair("Tamarro", "Tamarro"),
|
||||
Pair("Thriller", "Thriller"),
|
||||
Pair("Vampire", "Vampire"),
|
||||
Pair("Yaoi", "Yaoi"),
|
||||
Pair("Yuri", "Yuri"),
|
||||
),
|
||||
)
|
||||
|
||||
private class LetterFilter : UriPartFilter(
|
||||
"Lettera",
|
||||
arrayOf(
|
||||
Pair("<Selezionare>", ""),
|
||||
Pair("A", "A"),
|
||||
Pair("B", "B"),
|
||||
Pair("C", "C"),
|
||||
Pair("D", "D"),
|
||||
Pair("E", "E"),
|
||||
Pair("F", "F"),
|
||||
Pair("G", "G"),
|
||||
Pair("H", "H"),
|
||||
Pair("I", "I"),
|
||||
Pair("J", "J"),
|
||||
Pair("K", "K"),
|
||||
Pair("L", "L"),
|
||||
Pair("M", "M"),
|
||||
Pair("N", "N"),
|
||||
Pair("O", "O"),
|
||||
Pair("P", "P"),
|
||||
Pair("Q", "Q"),
|
||||
Pair("R", "R"),
|
||||
Pair("S", "S"),
|
||||
Pair("T", "T"),
|
||||
Pair("U", "U"),
|
||||
Pair("V", "V"),
|
||||
Pair("W", "W"),
|
||||
Pair("X", "X"),
|
||||
Pair("Y", "Y"),
|
||||
Pair("Z", "Z"),
|
||||
),
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// =========================== Anime Details ============================
|
||||
|
||||
override fun animeDetailsParse(document: Document): SAnime = SAnime.create().apply {
|
||||
title = document.selectFirst("div.card-body > p:contains(TITOLO:)")?.ownText() ?: ""
|
||||
thumbnail_url = document.selectFirst("div.card-body > div > img")?.attr("src") ?: ""
|
||||
author = document.selectFirst("div.card-body > p:contains(STUDIO:)")?.ownText() ?: ""
|
||||
status = document.selectFirst("div.card-body > p:contains(STATO:)")?.let {
|
||||
parseStatus(it.ownText())
|
||||
} ?: SAnime.UNKNOWN
|
||||
genre = document.selectFirst("div.card-body > p:contains(GENERI:)")?.ownText() ?: ""
|
||||
description = buildString {
|
||||
append(document.selectFirst("div.card-body > p:contains(TRAMA:) ~ p")?.text() ?: "")
|
||||
append("\n\n")
|
||||
append(document.selectFirst("div.card-body > p:contains(TIPO:)")?.text() ?: "")
|
||||
append("\n")
|
||||
append(document.selectFirst("div.card-body > p:contains(ANNO)")?.text() ?: "")
|
||||
}
|
||||
}
|
||||
|
||||
// ============================== Episodes ==============================
|
||||
|
||||
override fun episodeListParse(response: Response): List<SEpisode> = super.episodeListParse(response).reversed()
|
||||
|
||||
override fun episodeListSelector(): String = "div.card ul.page-nav-list-episodi > li"
|
||||
|
||||
override fun episodeFromElement(element: Element): SEpisode = SEpisode.create().apply {
|
||||
name = "Episodi ${element.text()}"
|
||||
episode_number = element.selectFirst("span")?.text()?.toFloatOrNull() ?: 0F
|
||||
setUrlWithoutDomain(element.selectFirst("a[href]")!!.attr("href"))
|
||||
}
|
||||
|
||||
// ============================ Video Links =============================
|
||||
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val document = response.asJsoup()
|
||||
val videoList = mutableListOf<Video>()
|
||||
|
||||
val videosREGEX = Regex("""\('\.video-container'\)\.append\((.*?)\);""")
|
||||
|
||||
val script = document.selectFirst("script:containsData(.video-container)")
|
||||
if (script == null) {
|
||||
val video = document.selectFirst("div.video-container source")?.attr("src")
|
||||
if (video != null) {
|
||||
videoList.add(extractAnimeLove(video))
|
||||
}
|
||||
} else {
|
||||
videosREGEX.findAll(script.data()).forEach { videoSource ->
|
||||
val url = videoSource.groupValues[1].substringAfter("src=\"").substringBefore("\"")
|
||||
when {
|
||||
url.contains("animelove.tv") -> {
|
||||
videoList.add(extractAnimeLove(url))
|
||||
}
|
||||
url.contains("streamtape") -> {
|
||||
StreamTapeExtractor(client).videoFromUrl(url)?.let {
|
||||
videoList.add(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
require(videoList.isNotEmpty()) { "Failed to fetch videos" }
|
||||
|
||||
return videoList
|
||||
}
|
||||
|
||||
override fun videoListSelector(): String = throw Exception("Not Used")
|
||||
|
||||
override fun videoFromElement(element: Element): Video = throw Exception("Not Used")
|
||||
|
||||
override fun videoUrlParse(document: Document): String = throw Exception("Not Used")
|
||||
|
||||
// ============================= Utilities ==============================
|
||||
|
||||
private fun extractAnimeLove(videoUrl: String): Video {
|
||||
val redirected = client.newCall(GET(videoUrl)).execute().request
|
||||
val videoHeaders = headers.newBuilder()
|
||||
.add("Accept", "video/webm,video/ogg,video/*;q=0.9,application/ogg;q=0.7,audio/*;q=0.6,*/*;q=0.5")
|
||||
.add("Host", redirected.url.host)
|
||||
.build()
|
||||
|
||||
return Video(
|
||||
redirected.url.toString(),
|
||||
"AnimeLove",
|
||||
redirected.url.toString(),
|
||||
headers = videoHeaders,
|
||||
)
|
||||
}
|
||||
|
||||
override fun List<Video>.sort(): List<Video> {
|
||||
val server = preferences.getString(PREF_SERVER_KEY, PREF_SERVER_DEFAULT)!!
|
||||
|
||||
return this.sortedWith(
|
||||
compareBy { it.quality.contains(server, true) },
|
||||
).reversed()
|
||||
}
|
||||
|
||||
private fun parseStatus(statusString: String): Int {
|
||||
return when (statusString) {
|
||||
"In Corso" -> SAnime.ONGOING
|
||||
"Terminato" -> SAnime.COMPLETED
|
||||
else -> SAnime.UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val PREF_SERVER_KEY = "preferred_server"
|
||||
private const val PREF_SERVER_DEFAULT = "AnimeLove"
|
||||
}
|
||||
|
||||
// ============================== Settings ==============================
|
||||
|
||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||
ListPreference(screen.context).apply {
|
||||
key = PREF_SERVER_KEY
|
||||
title = "Preferred server"
|
||||
entries = arrayOf("AnimeLove", "StreamTape")
|
||||
entryValues = arrayOf("AnimeLove", "StreamTape")
|
||||
setDefaultValue(PREF_SERVER_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)
|
||||
}
|
||||
}
|