chore(pt): Purge (known) dead sources (#2736)

This commit is contained in:
Claudemirovsky 2024-01-13 08:25:09 -03:00 committed by GitHub
parent 7ab9dc9a61
commit 4c00f7863e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 0 additions and 788 deletions

View File

@ -1,3 +0,0 @@
dependencies {
implementation(project(':lib-dailymotion-extractor'))
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

View File

@ -1,359 +0,0 @@
package eu.kanade.tachiyomi.animeextension.pt.donghuax
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
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.SEpisode
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.lib.dailymotionextractor.DailymotionExtractor
import eu.kanade.tachiyomi.multisrc.dooplay.DooPlay
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import okhttp3.FormBody
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Element
import uy.kohesive.injekt.injectLazy
class DonghuaX : DooPlay(
"pt-BR",
"DonghuaX",
"https://donghuax.com",
) {
private val json: Json by injectLazy()
// ============================== Popular ===============================
override fun popularAnimeSelector(): String = "div > aside article.w_item_a"
// =============================== Latest ===============================
override fun latestUpdatesSelector(): String = "div#archive-content > article.item"
override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/donghua/page/$page/")
override fun latestUpdatesNextPageSelector(): 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).reversed()
}
}
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 = episodeName
setUrlWithoutDomain(href.attr("href"))
}
}
// =============================== Search ===============================
override fun searchAnimeParse(response: Response): AnimesPage {
val document = response.asJsoup()
val url = response.request.url.toString()
val animes = when {
"/?s=" in url -> { // Search by name.
document.select(searchAnimeSelector()).map { element ->
searchAnimeFromElement(element)
}
}
else -> { // Search by some kind of filter, like genres or popularity.
document.select("div.items > article.item").map { element ->
latestUpdatesFromElement(element)
}
}
}
val hasNextPage = document.selectFirst(searchAnimeNextPageSelector()) != null
return AnimesPage(animes, hasNextPage)
}
// ============================== Filters ===============================
override val fetchGenres = false
override fun getFilterList(): AnimeFilterList = AnimeFilterList(
AnimeFilter.Header(genreFilterHeader),
GenreFilter(),
YearFilter(),
LetterFilter(),
)
private class GenreFilter : UriPartFilter(
"Gêneros",
arrayOf(
Pair("<Selecione>", ""),
Pair("Ação", "genero/acao/"),
Pair("Artes Marciais", "genero/artes-marciais/"),
Pair("Aventura", "genero/aventura/"),
Pair("BL", "genero/bl/"),
Pair("Comédia", "genero/comedia/"),
Pair("Drama", "genero/drama/"),
Pair("Escolar", "genero/escolar/"),
Pair("Fantasia", "genero/fantasia/"),
Pair("Ficção Científica", "genero/ficcao-cientifica/"),
Pair("Gourmet", "genero/gourmet/"),
Pair("Harem", "genero/harem/"),
Pair("Histórico", "genero/historico/"),
Pair("Mistério", "genero/misterio/"),
Pair("Mitologia", "genero/mitologia/"),
Pair("Reencarnação", "genero/reencarnacao/"),
Pair("Romance", "genero/romance/"),
Pair("Slice of Life", "genero/slice-of-life/"),
Pair("Sobrenatural", "genero/sobrenatural/"),
Pair("Suspense", "genero/suspense/"),
Pair("Vampiro", "genero/vampiro/"),
Pair("Viagem no Tempo", "genero/viagem-no-tempo/"),
Pair("Video Game", "genero/video-game/"),
),
)
private class YearFilter : UriPartFilter(
"Anos",
arrayOf(
Pair("<Selecione>", ""),
Pair("2023", "ano/2023/"),
Pair("2022", "ano/2022/"),
Pair("2021", "ano/2021/"),
Pair("2020", "ano/2020/"),
Pair("2019", "ano/2019/"),
Pair("2018", "ano/2018/"),
Pair("2017", "ano/2017/"),
Pair("2016", "ano/2016/"),
Pair("2015", "ano/2015/"),
Pair("2014", "ano/2014/"),
),
)
private class LetterFilter : UriPartFilter(
"Letra",
arrayOf(
Pair("<Selecione>", ""),
Pair("#", "letra/0-9"),
Pair("A", "letra/a"),
Pair("B", "letra/b"),
Pair("C", "letra/c"),
Pair("D", "letra/d"),
Pair("E", "letra/e"),
Pair("F", "letra/f"),
Pair("G", "letra/g"),
Pair("H", "letra/h"),
Pair("I", "letra/i"),
Pair("J", "letra/j"),
Pair("K", "letra/k"),
Pair("L", "letra/l"),
Pair("M", "letra/m"),
Pair("N", "letra/n"),
Pair("O", "letra/o"),
Pair("P", "letra/p"),
Pair("Q", "letra/q"),
Pair("R", "letra/r"),
Pair("S", "letra/s"),
Pair("T", "letra/t"),
Pair("U", "letra/u"),
Pair("V", "letra/v"),
Pair("W", "letra/w"),
Pair("X", "letra/x"),
Pair("Y", "letra/y"),
Pair("Z", "letra/z"),
),
)
// ============================ Video Links =============================
override fun videoListParse(response: Response): List<Video> {
val playerUrls = response.asJsoup().select("ul#playeroptionsul li").map {
getPlayerUrl(it)
}
return playerUrls.parallelMap { media ->
runCatching {
getPlayerVideos(media)
}.getOrNull()
}.filterNotNull().flatten()
}
private fun getPlayerVideos(url: String): List<Video> {
return when {
url.contains("${baseUrl.toHttpUrl().host}/jwplayer/", true) -> {
val videoUrl = url.toHttpUrl().queryParameter("source")
videoUrl?.let {
listOf(Video(videoUrl, "Internal Video - ${videoUrl.toHttpUrl().host}", videoUrl))
} ?: emptyList()
}
url.contains("play.${baseUrl.toHttpUrl().host}/player1", true) -> {
val playerScript = client.newCall(
GET(url),
).execute().asJsoup().selectFirst("script:containsData(sources)")?.data()
playerScript?.let {
json.decodeFromString<List<PlayerSource>>(
"[${it.substringAfter("sources: [").substringBefore("]")}]",
).map { source ->
Video(source.file, "Internal player - ${source.label}", source.file)
}
} ?: emptyList()
}
url.contains("play.${baseUrl.toHttpUrl().host}/mdplayer", true) -> {
val id = client.newCall(
GET(url),
).execute().asJsoup().selectFirst("vm-dailymotion[video-id]")?.attr("video-id")
id?.let {
DailymotionExtractor(client, headers).videosFromUrl("https://www.dailymotion.com/embed/video/$it")
} ?: emptyList()
}
url.contains("csst.online") -> {
val urlRegex = Regex("""\[(.*?)\](https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&\/=]*))""")
val dataScript = client.newCall(GET(url)).execute()
.asJsoup()
.selectFirst("script:containsData(isMobile)")
?.data()
dataScript?.let {
urlRegex.findAll(it).distinctBy { match ->
match.groupValues[2]
}.map { match ->
val videoUrl = match.groupValues[2]
val videoHeaders = Headers.Builder()
.add("Referer", "https://${videoUrl.toHttpUrl().host}/")
.build()
Video(videoUrl, "AllVideo - ${match.groupValues[1]}", videoUrl, headers = videoHeaders)
}.toList()
} ?: emptyList()
}
url.contains("blogger.com") -> {
val response = client.newCall(GET(url, headers = headers)).execute()
val streams = response.body.string().substringAfter("\"streams\":[").substringBefore("]")
streams.split("},")
.map {
val url = it.substringAfter("{\"play_url\":\"").substringBefore('"')
val quality = when (it.substringAfter("\"format_id\":").substringBefore("}")) {
"18" -> "Blogger - 360p"
"22" -> "Blogger - 720p"
else -> "Unknown Resolution"
}
Video(url, quality, url, null, headers)
}
}
else -> emptyList()
}
}
private fun getPlayerUrl(player: Element): String {
val body = FormBody.Builder()
.add("action", "doo_player_ajax")
.add("post", player.attr("data-post"))
.add("nume", player.attr("data-nume"))
.add("type", player.attr("data-type"))
.build()
return client.newCall(POST("$baseUrl/wp-admin/admin-ajax.php", headers, body))
.execute()
.use { response ->
response.body.string()
.substringAfter("\"embed_url\":\"")
.substringBefore("\",")
.replace("\\", "")
}
}
// ============================== Settings ==============================
override fun setupPreferenceScreen(screen: PreferenceScreen) {
super.setupPreferenceScreen(screen) // Quality preference
val langPref = ListPreference(screen.context).apply {
key = PREF_SERVER_KEY
title = PREF_SERVER_TITLE
entries = PREF_SERVER_ENTRIES
entryValues = PREF_SERVER_VALUES
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()
}
}
screen.addPreference(langPref)
}
// ============================= Utilities ==============================
override val prefQualityValues = arrayOf("288p", "360p", "480p", "720p", "1080p")
override val prefQualityEntries = prefQualityValues
override fun List<Video>.sort(): List<Video> {
val quality = preferences.getString(videoSortPrefKey, videoSortPrefDefault)!!
val server = preferences.getString(PREF_SERVER_KEY, PREF_SERVER_DEFAULT)!!
return sortedWith(
compareBy(
{ it.quality.lowercase().contains(quality.lowercase()) },
{ it.quality.lowercase().contains(server.lowercase()) },
),
).reversed()
}
// From Dopebox
private fun <A, B> Iterable<A>.parallelMap(f: suspend (A) -> B): List<B> =
runBlocking {
map { async(Dispatchers.Default) { f(it) } }.awaitAll()
}
@Serializable
data class PlayerSource(
val file: String,
val label: String,
)
companion object {
private const val PREF_SERVER_KEY = "preferred_server"
private const val PREF_SERVER_TITLE = "Preferred server"
private const val PREF_SERVER_DEFAULT = "AllVideo"
private val PREF_SERVER_ENTRIES = arrayOf("AllVideo", "Dailymotion", "Internal Player", "Internal Video", "Blogger")
private val PREF_SERVER_VALUES = PREF_SERVER_ENTRIES
}
}

View File

@ -18,7 +18,6 @@ class DooPlayGenerator : ThemeSourceGenerator {
SingleLang("AnimesFox BR", "https://animesfox.net", "pt-BR", isNsfw = false, overrideVersionCode = 2),
SingleLang("Animes House", "https://animeshouse.net", "pt-BR", isNsfw = false, overrideVersionCode = 8),
SingleLang("Cinemathek", "https://cinemathek.net", "de", isNsfw = true, overrideVersionCode = 17),
SingleLang("DonghuaX", "https://donghuax.com", "pt-BR", isNsfw = false, overrideVersionCode = 1),
SingleLang("GoAnimes", "https://goanimes.net", "pt-BR", isNsfw = true, overrideVersionCode = 7),
SingleLang("JetAnime", "https://ssl.jetanimes.com", "fr", isNsfw = false, overrideVersionCode = 2),
SingleLang("Kinoking", "https://kinoking.cc", "de", isNsfw = false, overrideVersionCode = 19),

View File

@ -1,23 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<activity
android:name=".pt.meusanimes.MeusAnimesUrlActivity"
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="meusanimes.org"
android:pathPattern="/animes/..*"
android:scheme="https" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@ -1,14 +0,0 @@
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
}
ext {
extName = 'Meus Animes'
pkgNameSuffix = 'pt.meusanimes'
extClass = '.MeusAnimes'
extVersionCode = 2
libVersion = '13'
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

View File

@ -1,118 +0,0 @@
package eu.kanade.tachiyomi.animeextension.pt.meusanimes
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
object MAFilters {
open class QueryPartFilter(
displayName: String,
val vals: Array<Pair<String, String>>,
) : AnimeFilter.Select<String>(
displayName,
vals.map { it.first }.toTypedArray(),
) {
fun toQueryPart() = vals[state].second
}
private inline fun <reified R> AnimeFilterList.asQueryPart(): String {
return (first { it is R } as QueryPartFilter).toQueryPart()
}
class LetterFilter : QueryPartFilter("Letra inicial", MAFiltersData.LETTERS)
class YearFilter : QueryPartFilter("Ano", MAFiltersData.YEARS)
class AudioFilter : QueryPartFilter("Áudio", MAFiltersData.AUDIO)
class GenreFilter : QueryPartFilter("Gênero", MAFiltersData.GENRES)
val FILTER_LIST get() = AnimeFilterList(
AnimeFilter.Header(MAFiltersData.IGNORE_SEARCH_MSG),
LetterFilter(),
AnimeFilter.Header(MAFiltersData.IGNORE_LETTER_MSG),
YearFilter(),
AnimeFilter.Header(MAFiltersData.IGNORE_YEAR_MSG),
AudioFilter(),
AnimeFilter.Header(MAFiltersData.IGNORE_AUDIO_MSG),
GenreFilter(),
)
data class FilterSearchParams(
val letter: String = "",
val year: String = "",
val audio: String = "",
val genre: String = "",
)
internal fun getSearchParameters(filters: AnimeFilterList): FilterSearchParams {
if (filters.isEmpty()) return FilterSearchParams()
return FilterSearchParams(
filters.asQueryPart<LetterFilter>(),
filters.asQueryPart<YearFilter>(),
filters.asQueryPart<AudioFilter>(),
filters.asQueryPart<GenreFilter>(),
)
}
private object MAFiltersData {
const val IGNORE_SEARCH_MSG = "NOTA: Os filtros abaixo irão IGNORAR a pesquisa por nome."
const val IGNORE_LETTER_MSG = "NOTA: O filtro por ano IGNORA o por letra."
const val IGNORE_YEAR_MSG = "NOTA: O filtro de áudio IGNORA o por ano."
const val IGNORE_AUDIO_MSG = "NOTA: O filtro de gêneros IGNORA o de áudio."
val EVERY = Pair("Selecione", "")
val LETTERS = arrayOf(EVERY) + ('a'..'z').map {
Pair(it.toString().uppercase(), it.toString())
}.toTypedArray()
val YEARS = arrayOf(EVERY) + (2023 downTo 1985).map {
Pair(it.toString(), it.toString())
}.toTypedArray()
val AUDIO = arrayOf(
EVERY,
Pair("Dublado", "Dublado"),
Pair("Legendado", "Legendado"),
)
val GENRES = arrayOf(
EVERY,
Pair("Artes Marciais", "artes-marciais"),
Pair("Aventura", "aventura"),
Pair("Ação", "acao"),
Pair("Comédia", "comedia"),
Pair("Demônio", "demonio"),
Pair("Drama", "drama"),
Pair("Ecchi", "ecchi"),
Pair("Esportes", "esportes"),
Pair("Fantasia", "fantasia"),
Pair("Ficção Científica", "ficcao-cientifica"),
Pair("Histórico", "historico"),
Pair("Horror", "horror"),
Pair("Jogos", "jogos"),
Pair("Josei", "josei"),
Pair("Magia", "magia"),
Pair("Mecha", "mecha"),
Pair("Militar", "militar"),
Pair("Mistério", "misterio"),
Pair("Musical", "musical"),
Pair("Psicológico", "psicologico"),
Pair("Romance", "romance"),
Pair("Samurai", "samurai"),
Pair("Seinen", "seinen"),
Pair("Shoujo", "shoujo"),
Pair("Shoujo-ai", "shoujo-ai"),
Pair("Shounen", "shounen"),
Pair("Shounen-ai", "shounen-ai"),
Pair("Slice of Life", "slice-of-life"),
Pair("Sobrenatural", "sobrenatural"),
Pair("Superpoder", "superpoder"),
Pair("Suspense", "suspense"),
Pair("Terror", "terror"),
Pair("Thriller", "thriller"),
Pair("Vampiros", "vampiros"),
Pair("Vida Escolar", "vida-escolar"),
Pair("Yaoi", "yaoi"),
Pair("Yuri", "yuri"),
)
}
}

View File

@ -1,182 +0,0 @@
package eu.kanade.tachiyomi.animeextension.pt.meusanimes
import android.app.Application
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.animeextension.pt.meusanimes.extractors.IframeExtractor
import eu.kanade.tachiyomi.animeextension.pt.meusanimes.extractors.MeusAnimesExtractor
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.network.GET
import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import rx.Observable
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class MeusAnimes : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override val name = "Meus Animes"
override val baseUrl = "https://meusanimes.org"
override val lang = "pt-BR"
override val supportsLatest = true
private val preferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
// ============================== Popular ===============================
override fun popularAnimeRequest(page: Int) = GET(baseUrl)
override fun popularAnimeSelector(): String = "div.ultAnisContainerItem > a"
override fun popularAnimeFromElement(element: Element) = latestUpdatesFromElement(element)
override fun popularAnimeNextPageSelector() = null
// =============================== Latest ===============================
override fun latestUpdatesRequest(page: Int) = GET(baseUrl)
override fun latestUpdatesSelector() = "div.ultEpsContainerItem > a"
override fun latestUpdatesFromElement(element: Element) = SAnime.create().apply {
title = element.attr("title")
thumbnail_url = element.selectFirst("img")?.attr("data-lazy-src")
val epUrl = element.attr("href")
if (epUrl.substringAfterLast("/").toIntOrNull() != null) {
setUrlWithoutDomain(epUrl.substringBeforeLast("/") + "-todos-os-episodios")
} else { setUrlWithoutDomain(epUrl) }
}
override fun latestUpdatesNextPageSelector() = null
// =============================== Search ===============================
override fun getFilterList(): AnimeFilterList = MAFilters.FILTER_LIST
override fun fetchSearchAnime(page: Int, query: String, filters: AnimeFilterList): Observable<AnimesPage> {
return if (query.startsWith(PREFIX_SEARCH)) { // URL intent handler
val id = query.removePrefix(PREFIX_SEARCH)
client.newCall(GET("$baseUrl/animes/$id"))
.asObservableSuccess()
.map(::searchAnimeByIdParse)
} else {
super.fetchSearchAnime(page, query, filters)
}
}
private fun searchAnimeByIdParse(response: Response): AnimesPage {
val details = animeDetailsParse(response.asJsoup())
return AnimesPage(listOf(details), false)
}
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val defaultUrl = "$baseUrl/lista-de-animes/$page"
val params = MAFilters.getSearchParameters(filters)
return when {
params.letter.isNotBlank() -> GET("$defaultUrl?letra=${params.letter}")
params.year.isNotBlank() -> GET("$defaultUrl?ano=${params.year}")
params.audio.isNotBlank() -> GET("$defaultUrl?audio=${params.audio}")
params.genre.isNotBlank() -> GET("$defaultUrl?genero=${params.genre}")
query.isNotBlank() -> GET("$defaultUrl?s=$query")
else -> GET(defaultUrl)
}
}
override fun searchAnimeSelector() = popularAnimeSelector()
override fun searchAnimeFromElement(element: Element) = latestUpdatesFromElement(element)
override fun searchAnimeNextPageSelector() = "div.paginacao > a.next"
// =========================== Anime Details ============================
override fun animeDetailsParse(document: Document) = SAnime.create().apply {
val infos = document.selectFirst("div.animeInfos")!!
val right = document.selectFirst("div.right")!!
setUrlWithoutDomain(document.location())
title = right.selectFirst("h1")!!.text()
genre = right.select("ul.animeGen a").eachText().joinToString()
thumbnail_url = infos.selectFirst("img")?.attr("data-lazy-src")
description = right.selectFirst("div.animeSecondContainer > p:gt(0)")!!.text()
}
// ============================== Episodes ==============================
override fun episodeListParse(response: Response) = super.episodeListParse(response).reversed()
override fun episodeListSelector() = "div#aba_epi > a"
override fun episodeFromElement(element: Element) = SEpisode.create().apply {
element.attr("href").also {
setUrlWithoutDomain(it)
episode_number = it.substringAfterLast("/").toFloatOrNull() ?: 0F
}
name = element.text()
}
// ============================ Video Links =============================
private val meusanimesExtractor by lazy { MeusAnimesExtractor(client) }
private val iframeExtractor by lazy { IframeExtractor(client, headers) }
override fun videoListParse(response: Response): List<Video> {
val document = response.use { it.asJsoup() }
val videoElement = document.selectFirst("div.playerBox > *")!!
return when (videoElement.tagName()) {
"video" -> meusanimesExtractor.videoListFromElement(videoElement)
else -> iframeExtractor.videoListFromIframe(videoElement)
}
}
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")
// ============================== Settings ==============================
override fun setupPreferenceScreen(screen: PreferenceScreen) {
ListPreference(screen.context).apply {
key = PREF_QUALITY_KEY
title = PREF_QUALITY_TITLE
entries = PREF_QUALITY_ENTRIES
entryValues = PREF_QUALITY_ENTRIES
setDefaultValue(PREF_QUALITY_DEFAULT)
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
}
}.also(screen::addPreference)
}
// ============================= Utilities ==============================
override fun List<Video>.sort(): List<Video> {
val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
return sortedWith(
compareBy { it.quality.contains(quality) },
).reversed()
}
companion object {
const val PREFIX_SEARCH = "id:"
private const val PREF_QUALITY_KEY = "pref_quality"
private const val PREF_QUALITY_TITLE = "Qualidade preferida"
private const val PREF_QUALITY_DEFAULT = "HD"
private val PREF_QUALITY_ENTRIES = arrayOf("SD", "HD")
}
}

View File

@ -1,41 +0,0 @@
package eu.kanade.tachiyomi.animeextension.pt.meusanimes
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://meusanimes.org/animes/<item> intents
* and redirects them to the main Aniyomi process.
*/
class MeusAnimesUrlActivity : 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 item = pathSegments[1]
val mainIntent = Intent().apply {
action = "eu.kanade.tachiyomi.ANIMESEARCH"
putExtra("query", "${MeusAnimes.PREFIX_SEARCH}$item")
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)
}
}

View File

@ -1,20 +0,0 @@
package eu.kanade.tachiyomi.animeextension.pt.meusanimes.extractors
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.network.GET
import okhttp3.Headers
import okhttp3.OkHttpClient
import org.jsoup.nodes.Element
class IframeExtractor(private val client: OkHttpClient, private val headers: Headers) {
fun videoListFromIframe(iframeElement: Element): List<Video> {
val iframeUrl = iframeElement.attr("src")
val response = client.newCall(GET(iframeUrl, headers)).execute()
val html = response.body.string()
val url = html.substringAfter("play_url")
.substringAfter(":\"")
.substringBefore("\"")
val video = Video(url, "Default", url, headers = headers)
return listOf(video)
}
}

View File

@ -1,27 +0,0 @@
package eu.kanade.tachiyomi.animeextension.pt.meusanimes.extractors
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.network.GET
import okhttp3.Headers
import okhttp3.OkHttpClient
import org.jsoup.nodes.Element
class MeusAnimesExtractor(private val client: OkHttpClient) {
fun head(url: String, headers: Headers) = GET(url, headers).newBuilder().head().build()
fun videoListFromElement(element: Element): List<Video> {
val headers = Headers.headersOf("Range", "bytes=0-1")
val urls = buildMap {
val sdUrl = element.attr("src")
put("SD", sdUrl)
val hdUrl = sdUrl.replace("/sd/", "/hd/")
runCatching {
// Check if the url is playing
client.newCall(head(hdUrl, headers)).execute()
put("HD", hdUrl)
}
}
return urls.map { (quality, url) -> Video(url, quality, url) }
}
}