chore(src/pt): Purge (known) dead sources (#3190)

This commit is contained in:
Claudemirovsky 2024-04-26 10:29:32 +00:00 committed by GitHub
parent ebd306b83a
commit 2009c1363d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
46 changed files with 0 additions and 1604 deletions

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.animesaria.AnimesAriaUrlActivity"
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="animesaria.com"
android:pathPattern="/anime/..*/..*"
android:scheme="https" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@ -1,7 +0,0 @@
ext {
extName = 'Animes Aria'
extClass = '.AnimesAria'
extVersionCode = 2
}
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: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

View File

@ -1,186 +0,0 @@
package eu.kanade.tachiyomi.animeextension.pt.animesaria
import eu.kanade.tachiyomi.animeextension.pt.animesaria.extractors.LinkfunBypasser
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.awaitSuccess
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
class AnimesAria : ParsedAnimeHttpSource() {
override val name = "Animes Aria"
override val baseUrl = "https://animesaria.com"
override val lang = "pt-BR"
override val supportsLatest = true
// ============================== Popular ===============================
override fun popularAnimeRequest(page: Int) = GET("$baseUrl/novos/animes?page=$page")
override fun popularAnimeSelector() = "div.item > a"
override fun popularAnimeFromElement(element: Element) = SAnime.create().apply {
setUrlWithoutDomain(element.attr("href"))
title = element.attr("title")
thumbnail_url = element.selectFirst("img")!!.attr("src")
}
override fun popularAnimeNextPageSelector() = latestUpdatesNextPageSelector()
// =============================== Latest ===============================
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/novos/episodios?page=$page")
override fun latestUpdatesSelector() = "div.item > div.pos-rlt"
override fun latestUpdatesFromElement(element: Element) = SAnime.create().apply {
thumbnail_url = element.selectFirst("img")?.attr("src")
val ahref = element.selectFirst("a")!!
title = ahref.attr("title")
setUrlWithoutDomain(ahref.attr("href").substringBefore("/episodio/"))
}
override fun latestUpdatesNextPageSelector() = "a:containsOwn(Próximo):not(.disabled)"
// =============================== Search ===============================
override fun getFilterList() = AnimesAriaFilters.FILTER_LIST
override suspend fun getSearchAnime(page: Int, query: String, filters: AnimeFilterList): AnimesPage {
return if (query.startsWith(PREFIX_SEARCH)) { // URL intent handler
val id = query.removePrefix(PREFIX_SEARCH)
client.newCall(GET("$baseUrl/anime/$id"))
.awaitSuccess()
.use(::searchAnimeByIdParse)
} else {
super.getSearchAnime(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 params = AnimesAriaFilters.getSearchParameters(filters)
val url = "$baseUrl/anime/buscar".toHttpUrl().newBuilder()
.addQueryParameter("q", query)
.addQueryParameter("page", page.toString())
.addQueryParameter("tipo", params.type)
.addQueryParameter("genero", params.genre)
.addQueryParameter("status", params.status)
.addQueryParameter("letra", params.letter)
.addQueryParameter("audio", params.audio)
.addQueryParameter("ano", params.year)
.addQueryParameter("temporada", params.season)
.build()
return GET(url.toString())
}
override fun searchAnimeFromElement(element: Element) = popularAnimeFromElement(element)
override fun searchAnimeNextPageSelector() = latestUpdatesNextPageSelector()
override fun searchAnimeSelector() = popularAnimeSelector()
// =========================== Anime Details ============================
override fun animeDetailsParse(document: Document) = SAnime.create().apply {
setUrlWithoutDomain(document.location())
val row = document.selectFirst("div.anime_background_w div.row")!!
title = row.selectFirst("h1 > span")!!.text()
status = row.selectFirst("div.clear span.btn")?.text().toStatus()
thumbnail_url = document.selectFirst("link[as=image]")?.attr("href")
genre = row.select("div.clear a.btn").eachText().joinToString()
description = buildString {
document.selectFirst("li.active > small")!!
.ownText()
.substringAfter(": ")
.also(::append)
append("\n\n")
row.selectFirst("h1 > small")?.text()?.also {
append("Títulos Alternativos: $it\n")
}
// Additional info
row.select("div.pull-right > a").forEach {
val title = it.selectFirst("small")!!.text()
val value = it.selectFirst("span")!!.text()
append("$title: $value\n")
}
}
}
// ============================== Episodes ==============================
override fun episodeListParse(response: Response) = super.episodeListParse(response).reversed()
override fun episodeFromElement(element: Element) = SEpisode.create().apply {
element.parent()!!.selectFirst("a > b")!!.ownText().also {
name = it
episode_number = it.substringAfter(" ").toFloatOrNull() ?: 0F
}
setUrlWithoutDomain(element.attr("href"))
scanlator = element.text().substringAfter(" ") // sub/dub
}
override fun episodeListSelector() = "td div.clear > a.btn-xs"
// ============================ Video Links =============================
override fun videoListParse(response: Response): List<Video> {
val document = response.asJsoup()
val formUrl = document.selectFirst("center > a.btn")!!.attr("href")
val bypasser = LinkfunBypasser(client)
return client.newCall(GET(formUrl, headers))
.execute()
.use(bypasser::getIframeResponse)
.use(::extractVideoFromResponse)
.let(::listOf)
}
private fun extractVideoFromResponse(response: Response): Video {
val decodedBody = LinkfunBypasser.decodeAtob(response.body.string())
val url = decodedBody
.substringAfter("sources")
.substringAfter("file: \"")
.substringBefore('"')
val videoHeaders = Headers.headersOf("Referer", response.request.url.toString())
return Video(url, "default", url, videoHeaders)
}
override fun videoFromElement(element: Element): Video {
TODO("Not yet implemented")
}
override fun videoListSelector(): String {
TODO("Not yet implemented")
}
override fun videoUrlParse(document: Document): String {
TODO("Not yet implemented")
}
// ============================= Utilities ==============================
private fun String?.toStatus() = when (this) {
"Finalizado" -> SAnime.COMPLETED
"Lançamento" -> SAnime.ONGOING
else -> SAnime.UNKNOWN
}
companion object {
const val PREFIX_SEARCH = "id:"
}
}

View File

@ -1,154 +0,0 @@
package eu.kanade.tachiyomi.animeextension.pt.animesaria
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
object AnimesAriaFilters {
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 TypeFilter : QueryPartFilter("Tipo", AnimesAriaFiltersData.TYPES)
class GenreFilter : QueryPartFilter("Gênero", AnimesAriaFiltersData.GENRES)
class StatusFilter : QueryPartFilter("Status", AnimesAriaFiltersData.STATUS)
class LetterFilter : QueryPartFilter("Letra inicial", AnimesAriaFiltersData.LETTERS)
class AudioFilter : QueryPartFilter("Áudio", AnimesAriaFiltersData.AUDIO)
class YearFilter : QueryPartFilter("Ano", AnimesAriaFiltersData.YEARS)
class SeasonFilter : QueryPartFilter("Temporada", AnimesAriaFiltersData.SEASONS)
val FILTER_LIST get() = AnimeFilterList(
TypeFilter(),
GenreFilter(),
StatusFilter(),
LetterFilter(),
AudioFilter(),
YearFilter(),
SeasonFilter(),
)
data class FilterSearchParams(
val type: String = "",
val genre: String = "",
val status: String = "",
val letter: String = "",
val audio: String = "",
val year: String = "",
val season: String = "",
)
internal fun getSearchParameters(filters: AnimeFilterList): FilterSearchParams {
if (filters.isEmpty()) return FilterSearchParams()
return FilterSearchParams(
filters.asQueryPart<TypeFilter>(),
filters.asQueryPart<GenreFilter>(),
filters.asQueryPart<StatusFilter>(),
filters.asQueryPart<LetterFilter>(),
filters.asQueryPart<AudioFilter>(),
filters.asQueryPart<YearFilter>(),
filters.asQueryPart<SeasonFilter>(),
)
}
private object AnimesAriaFiltersData {
val EVERY = Pair("Todos", "todos")
val EVERY_F = Pair("Todas", "todas")
val TYPES = arrayOf(
EVERY,
Pair("Série de TV", "serie"),
Pair("OVA", "ova"),
Pair("Filme", "filme"),
Pair("Especial", "especial"),
Pair("ONA", "ona"),
)
val GENRES = arrayOf(
EVERY,
Pair("Ação", "acao"),
Pair("Artes Maciais", "artes_maciais"),
Pair("Aventura", "aventura"),
Pair("Carros", "carros"),
Pair("Comédia", "comedia"),
Pair("Demência", "demencia"),
Pair("Demônios", "demonios"),
Pair("Drama", "drama"),
Pair("Ecchi", "ecchi"),
Pair("Erótico", "erotico"),
Pair("Escolar", "escolar"),
Pair("Espaço", "espaco"),
Pair("Esporte", "esporte"),
Pair("Fantasia", "fantasia"),
Pair("Ficção Científica", "ficcao_cientifica"),
Pair("Gourmet", "gourmet"),
Pair("Harem", "harem"),
Pair("Hentai", "hentai"),
Pair("Histórico", "historico"),
Pair("Infantil", "infantil"),
Pair("Jogos", "jogos"),
Pair("Josei", "josei"),
Pair("Magia", "magia"),
Pair("Mecha", "mecha"),
Pair("Militar", "militar"),
Pair("Mistério", "misterio"),
Pair("Música", "musica"),
Pair("Paródia", "parodia"),
Pair("Polícia", "policia"),
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("Sobrenatural", "sobrenatural"),
Pair("Super Poder", "super_poder"),
Pair("Terror", "terror"),
Pair("Thriller", "thriller"),
Pair("Vampiro", "vampiro"),
Pair("Vida Diária", "vida_diaria"),
Pair("Yaoi", "yaoi"),
Pair("Yuri", "yuri"),
)
val STATUS = arrayOf(
EVERY,
Pair("Em lançamento", "lancamento"),
Pair("Finalizado", "finalizado"),
)
val LETTERS = arrayOf(EVERY_F) + ('A'..'Z').map {
Pair(it.toString(), it.toString().lowercase())
}.toTypedArray()
val AUDIO = arrayOf(
EVERY,
Pair("Dublado", "dublado"),
Pair("Legendado", "legendado"),
)
val YEARS = arrayOf(EVERY) + (2023 downTo 1962).map {
Pair(it.toString(), it.toString())
}.toTypedArray()
val SEASONS = arrayOf(
EVERY_F,
Pair("Primavera", "primavera"),
Pair("Verão", "verao"),
Pair("Outono", "outono"),
Pair("Inverno", "inverno"),
)
}
}

View File

@ -1,42 +0,0 @@
package eu.kanade.tachiyomi.animeextension.pt.animesaria
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://animesaria.com/anime/<id>/<slug> intents
* and redirects them to the main Aniyomi process.
*/
class AnimesAriaUrlActivity : 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 id = pathSegments[1]
val slug = pathSegments[2]
val mainIntent = Intent().apply {
action = "eu.kanade.tachiyomi.ANIMESEARCH"
putExtra("query", "${AnimesAria.PREFIX_SEARCH}$id/$slug")
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,51 +0,0 @@
package eu.kanade.tachiyomi.animeextension.pt.animesaria.extractors
import android.util.Base64
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.FormBody
import okhttp3.Headers
import okhttp3.OkHttpClient
import okhttp3.Response
class LinkfunBypasser(private val client: OkHttpClient) {
fun getIframeResponse(page: Response): Response {
val document = page.asJsoup(decodeAtob(page.body.string()))
val newHeaders = Headers.headersOf("Referer", document.location())
val iframe = document.selectFirst("iframe")
return if (iframe != null) {
client.newCall(GET(iframe.attr("src"), newHeaders)).execute()
} else {
val formBody = FormBody.Builder().apply {
document.select("input[name]").forEach {
add(it.attr("name"), it.attr("value"))
}
}.build()
val formUrl = document.selectFirst("form")!!.attr("action")
client.newCall(POST(formUrl, newHeaders, formBody))
.execute()
.let(::getIframeResponse)
}
}
companion object {
fun decodeAtob(html: String): String {
val atobContent = html.substringAfter("atob(\"").substringBefore("\"));")
val hexAtob = atobContent.replace("\\x", "").decodeHex()
val decoded = Base64.decode(hexAtob, Base64.DEFAULT)
return String(decoded)
}
// Stolen from AnimixPlay(EN) / GogoCdnExtractor
private fun String.decodeHex(): ByteArray {
check(length % 2 == 0) { "Must have an even length" }
return chunked(2)
.map { it.toInt(16).toByte() }
.toByteArray()
}
}
}

View File

@ -1,13 +0,0 @@
ext {
extName = 'Animes House'
extClass = '.AnimesHouse'
themePkg = 'dooplay'
baseUrl = 'https://animeshouse.net'
overrideVersionCode = 12
}
apply from: "$rootDir/common.gradle"
dependencies {
implementation(project(":lib:playlist-utils"))
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

View File

@ -1,115 +0,0 @@
package eu.kanade.tachiyomi.animeextension.pt.animeshouse
import eu.kanade.tachiyomi.animeextension.pt.animeshouse.extractors.EdifierExtractor
import eu.kanade.tachiyomi.animeextension.pt.animeshouse.extractors.EmbedExtractor
import eu.kanade.tachiyomi.animeextension.pt.animeshouse.extractors.GenericExtractor
import eu.kanade.tachiyomi.animeextension.pt.animeshouse.extractors.JsUnpacker
import eu.kanade.tachiyomi.animeextension.pt.animeshouse.extractors.McpExtractor
import eu.kanade.tachiyomi.animeextension.pt.animeshouse.extractors.MpFourDooExtractor
import eu.kanade.tachiyomi.animeextension.pt.animeshouse.extractors.RedplayBypasser
import eu.kanade.tachiyomi.animesource.model.Video
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 okhttp3.FormBody
import okhttp3.Response
import org.jsoup.nodes.Element
class AnimesHouse : DooPlay(
"pt-BR",
"Animes House",
"https://animeshouse.net",
) {
override fun headersBuilder() = super.headersBuilder()
.add("Accept-Language", "pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7")
// ============================== Popular ===============================
// This source does not have a "popular" animes page, so we're going to
// use latest updates page instead.
override suspend fun getPopularAnime(page: Int) = getLatestUpdates(page)
// =============================== Latest ===============================
override fun latestUpdatesNextPageSelector(): String = "div.resppages > a > span.icon-chevron-right"
// ============================ Video Links =============================
private val redplayBypasser by lazy { RedplayBypasser(client, headers) }
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()
.asJsoup().selectFirst("iframe")!!.attr("src")
.let {
when {
it.contains("/redplay") -> {
val url = if (it.startsWith("/")) baseUrl + it else it
redplayBypasser.fromUrl(url)
}
else -> it
}
}
}
override fun videoListParse(response: Response): List<Video> {
val players = response.asJsoup().select("ul#playeroptionsul li")
return players.flatMap { player ->
runCatching {
val url = getPlayerUrl(player)
getPlayerVideos(url)
}.getOrElse { emptyList<Video>() }
}
}
private val embedExtractor by lazy { EmbedExtractor(headers) }
private val edifierExtractor by lazy { EdifierExtractor(client, headers) }
private val mp4dooExtractor by lazy { MpFourDooExtractor(client, headers) }
private val genericExtractor by lazy { GenericExtractor(client, headers) }
private val mcpExtractor by lazy { McpExtractor(client, headers) }
private fun getPlayerVideos(url: String): List<Video> {
val iframeBody = client.newCall(GET(url, headers)).execute()
.body.string()
val unpackedBody = JsUnpacker.unpack(iframeBody)
return when {
"embed.php?" in url -> embedExtractor.getVideoList(url, iframeBody)
"edifier" in url -> edifierExtractor.getVideoList(url)
"mp4doo" in url || "doomp4" in url -> mp4dooExtractor.getVideoList(unpackedBody)
"clp-new" in url || "gcloud" in url -> genericExtractor.getVideoList(url, unpackedBody)
"mcp_comm" in unpackedBody -> mcpExtractor.getVideoList(unpackedBody)
"cloudg" in url -> {
unpackedBody.substringAfter("sources:[").substringBefore(']')
.split('{')
.drop(1)
.mapNotNull {
val videoUrl = it.substringAfter("\"file\":\"").substringBefore('"')
.takeUnless(String::isBlank) ?: return@mapNotNull null
val label = it.substringAfter("\"label\":\"").substringBefore('"')
Video(videoUrl, "CloudG - $label", videoUrl, headers)
}
}
else -> emptyList()
}
}
// ============================== Settings ==============================
override val prefQualityEntries = arrayOf(
"SD - 240p",
"SD - 360p",
"SD - 480p",
"HD - 720p",
"FULLHD - 1080p",
)
override val prefQualityValues = arrayOf("240p", "360p", "480p", "720p", "1080p")
// ============================= Utilities ==============================
override val animeMenuSelector = "div.pag_episodes div.item a[href] i.icon-bars"
}

View File

@ -1,25 +0,0 @@
package eu.kanade.tachiyomi.animeextension.pt.animeshouse.extractors
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.network.POST
import okhttp3.Headers
import okhttp3.OkHttpClient
class EdifierExtractor(
private val client: OkHttpClient,
private val headers: Headers,
) {
private val regexEdifier = Regex(""""file":"(.*?)","label":"(\S+?)"""")
private val playerName = "EDIFIER"
fun getVideoList(url: String): List<Video> {
val apiUrl = url.replace("/v/", "/api/source/")
val req = client.newCall(POST(apiUrl)).execute()
val body = req.body.string()
return regexEdifier.findAll(body).map {
val videoUrl = it.groupValues.get(1).replace("\\", "")
val quality = "$playerName: " + it.groupValues.get(2)
Video(videoUrl, quality, videoUrl, headers)
}.toList()
}
}

View File

@ -1,19 +0,0 @@
package eu.kanade.tachiyomi.animeextension.pt.animeshouse.extractors
import eu.kanade.tachiyomi.animesource.model.Video
import okhttp3.Headers
class EmbedExtractor(private val headers: Headers) {
private val regexEmbedPlayer = Regex("""file: "(\S+)",\s+"label":"(\w+)"""")
private val playerName = "EmbedPlayer"
fun getVideoList(url: String, iframeBody: String): List<Video> {
val hostUrl = url.substringBefore("/embed")
return regexEmbedPlayer.findAll(iframeBody).map {
val newUrl = "$hostUrl/${it.groupValues[1]}"
val quality = "$playerName: " + it.groupValues[2]
Video(newUrl, quality, newUrl, headers)
}.toList()
}
}

View File

@ -1,29 +0,0 @@
package eu.kanade.tachiyomi.animeextension.pt.animeshouse.extractors
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.lib.playlistutils.PlaylistUtils
import okhttp3.Headers
import okhttp3.OkHttpClient
class GenericExtractor(
private val client: OkHttpClient,
private val headers: Headers,
) {
private val playlistUtils by lazy { PlaylistUtils(client, headers) }
private val regexClpPlayer = Regex("player\\('(\\S+)',")
private val regexGcloudPlayer = "file\":\"(\\S+)\"".toRegex()
fun getVideoList(url: String, js: String): List<Video> {
val (player, regex) = when {
"gcloud" in url -> Pair("GCLOUD", regexGcloudPlayer)
else -> Pair("CLP", regexClpPlayer)
}
val playlistUrl = regex.find(js)!!.groupValues.get(1)
if ("m3u8.php" in playlistUrl) {
return playlistUtils.extractFromHls(playlistUrl, videoNameGen = { "$player: $it" })
}
return listOf(Video(playlistUrl, player, playlistUrl, headers))
}
}

View File

@ -1,40 +0,0 @@
package eu.kanade.tachiyomi.animeextension.pt.animeshouse.extractors
object JsUnpacker {
private val REGEX_REPLACE = "\\b\\w+\\b".toRegex()
private val REGEX_EVAL = """\}\('(.*)',(\d+),(\d+),'(.*)'\.split""".toRegex()
private fun hasPacker(js: String): Boolean = REGEX_EVAL.containsMatchIn(js)
private fun getPackerArgs(js: String): List<String> = REGEX_EVAL.findAll(js)
.last().groupValues
private fun convert(base: Int, num: Int): String {
val firstPart = if (num < base) "" else (num / base).toString()
val calc = num % base
if (calc > 35) {
return firstPart + (calc + 29).toChar().toString()
}
return firstPart + calc.toString(36)
}
fun unpack(js: String): String {
if (!hasPacker(js)) return js
val args = getPackerArgs(js)
val origJS = args[1]
val base = args[2].toInt()
val count = args[3].toInt()
val origList = args[4].split("|")
val replaceMap = (0..count - 1).map {
val key = convert(base, it)
key to try { origList[it] } catch (e: Exception) { key }
}.toMap()
val result = origJS.replace(REGEX_REPLACE) {
replaceMap.get(it.value) ?: it.value
}.replace("\\", "")
return unpack(result)
}
}

View File

@ -1,29 +0,0 @@
package eu.kanade.tachiyomi.animeextension.pt.animeshouse.extractors
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.network.GET
import okhttp3.Headers
import okhttp3.OkHttpClient
class McpExtractor(
private val client: OkHttpClient,
private val headers: Headers,
) {
private val regexEpId = Regex("ss,\"(\\d+)\"")
private val regexVideoUrl = Regex("h\":\"(\\S+?)\"")
private val apiUrl = "https://clp-new.animeshouse.net/ah-clp-new"
fun getVideoList(js: String): List<Video> {
val epId = regexEpId.find(js)!!.groupValues[1]
val videoUrl = client.newCall(GET("$apiUrl/s_control.php?mid=$epId", headers))
.execute()
.let { req ->
val reqBody = req.body.string()
regexVideoUrl.find(reqBody)!!.groupValues
.get(1)
.replace("\\", "")
}
return listOf(Video(videoUrl, "default_mcp", videoUrl, headers))
}
}

View File

@ -1,26 +0,0 @@
package eu.kanade.tachiyomi.animeextension.pt.animeshouse.extractors
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.lib.playlistutils.PlaylistUtils
import okhttp3.Headers
import okhttp3.OkHttpClient
class MpFourDooExtractor(
private val client: OkHttpClient,
private val headers: Headers,
) {
private val regexMpdoo = Regex("file\":\"(.*?)\"")
private val playerName = "Mp4Doo"
private val playlistUtils by lazy { PlaylistUtils(client, headers) }
fun getVideoList(js: String): List<Video> {
val videoUrl = regexMpdoo.find(js)!!.groupValues
.get(1)
.replace("fy..", "fy.v.")
return if (videoUrl.endsWith("playlist.m3u8")) {
playlistUtils.extractFromHls(videoUrl, videoNameGen = { "$playerName: $it" })
} else {
listOf(Video(videoUrl, playerName, videoUrl, headers))
}
}
}

View File

@ -1,61 +0,0 @@
package eu.kanade.tachiyomi.animeextension.pt.animeshouse.extractors
import android.util.Base64
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.FormBody
import okhttp3.Headers
import okhttp3.OkHttpClient
import okhttp3.Response
class RedplayBypasser(
private val client: OkHttpClient,
private val headers: Headers,
) {
fun fromUrl(url: String): String {
val linkUrl = client.newCall(GET(url, headers)).execute().asJsoup()
.selectFirst("a")!!.attr("href")
val newHeaders = headers.newBuilder().set("Referer", linkUrl).build()
val response = client.newCall(GET(linkUrl, newHeaders)).execute()
return getIframeUrl(response, newHeaders)
}
private fun getIframeUrl(page: Response, newHeaders: Headers): String {
val document = page.asJsoup(decodeAtob(page.body.string()))
val iframe = document.selectFirst("iframe")
return if (iframe != null) {
iframe.attr("src")
} else {
val formUrl = document.selectFirst("form")!!.attr("action")
val formBody = FormBody.Builder().apply {
document.select("input[name]").forEach {
add(it.attr("name"), it.attr("value"))
}
}.build()
client.newCall(POST(formUrl, newHeaders, formBody))
.execute()
.let { getIframeUrl(it, newHeaders) }
}
}
private fun decodeAtob(html: String): String {
val atobContent = html.substringAfter("atob(\"").substringBefore("\"));")
val hexAtob = atobContent.replace("\\x", "").decodeHex()
val decoded = Base64.decode(hexAtob, Base64.DEFAULT)
return String(decoded)
}
// Stolen from AnimixPlay(EN) / GogoCdnExtractor
private fun String.decodeHex(): ByteArray {
check(length % 2 == 0) { "Must have an even length" }
return chunked(2)
.map { it.toInt(16).toByte() }
.toByteArray()
}
}

View File

@ -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=".pt.animesorion.AnimesOrionUrlActivity"
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="animesorion.com"
android:pathPattern="/anime/..*"
android:scheme="https" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@ -1,8 +0,0 @@
ext {
extName = 'Animes Órion'
extClass = '.AnimesOrion'
extVersionCode = 1
isNsfw = true
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

View File

@ -1,195 +0,0 @@
package eu.kanade.tachiyomi.animeextension.pt.animesorion
import eu.kanade.tachiyomi.animeextension.pt.animesorion.extractors.LinkfunBypasser
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.awaitSuccess
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
class AnimesOrion : ParsedAnimeHttpSource() {
override val name = "Animes Órion"
override val baseUrl = "https://animesorion.com"
override val lang = "pt-BR"
override val supportsLatest = true
// ============================== Popular ===============================
override fun popularAnimeRequest(page: Int) = GET(baseUrl, headers)
override fun popularAnimeSelector() = "div.tab-content-block article > a"
override fun popularAnimeFromElement(element: Element) = SAnime.create().apply {
setUrlWithoutDomain(element.attr("href"))
title = element.selectFirst("h2.ttl")!!.text()
thumbnail_url = element.selectFirst("img")?.attr("src")
}
override fun popularAnimeNextPageSelector() = null
// =============================== Latest ===============================
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/lista-de-episodios?page=$page")
override fun latestUpdatesSelector() = "div.mv-list > article"
override fun latestUpdatesFromElement(element: Element) = SAnime.create().apply {
setUrlWithoutDomain(element.selectFirst("a.lnk-blk")!!.attr("href"))
title = element.selectFirst("h2")!!.text()
thumbnail_url = element.selectFirst("img")?.attr("src")
}
override fun latestUpdatesNextPageSelector() = "nav.pagination > a.next"
// =============================== Search ===============================
override suspend fun getSearchAnime(page: Int, query: String, filters: AnimeFilterList): AnimesPage {
return if (query.startsWith(PREFIX_SEARCH)) { // URL intent handler
val id = query.removePrefix(PREFIX_SEARCH)
client.newCall(GET("$baseUrl/anime/$id"))
.awaitSuccess()
.use(::searchAnimeByIdParse)
} else {
super.getSearchAnime(page, query, filters)
}
}
private fun searchAnimeByIdParse(response: Response): AnimesPage {
val details = animeDetailsParse(response.asJsoup())
return AnimesPage(listOf(details), false)
}
override fun getFilterList() = AnimesOrionFilters.FILTER_LIST
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val params = AnimesOrionFilters.getSearchParameters(filters)
val url = "$baseUrl/animes".toHttpUrl().newBuilder().apply {
addQueryParameter("q", query)
addQueryParameter("page", page.toString())
addQueryParameter("tipo", params.type)
addQueryParameter("genero", params.genre)
addQueryParameter("status", params.status)
addQueryParameter("letra", params.letter)
addQueryParameter("audio", params.audio)
addQueryParameter("ano", params.year)
addQueryParameter("temporada", params.season)
}.build().toString()
return GET(url, headers)
}
override fun searchAnimeSelector() = latestUpdatesSelector()
override fun searchAnimeFromElement(element: Element) = latestUpdatesFromElement(element)
override fun searchAnimeNextPageSelector() = latestUpdatesNextPageSelector()
// =========================== Anime Details ============================
override fun animeDetailsParse(document: Document) = SAnime.create().apply {
val doc = getRealDoc(document)
setUrlWithoutDomain(doc.location())
thumbnail_url = doc.selectFirst("img.lnk-blk")?.attr("src")
val infos = doc.selectFirst("header.hd > div.rght")!!
title = infos.selectFirst("h2.title")!!.text()
genre = infos.select(">a").eachText().joinToString()
status = parseStatus(infos.selectFirst("span.tag + a")?.text())
description = buildString {
infos.selectFirst("h2.ttl")?.text()
?.takeIf(String::isNotBlank)
?.also { append("Títulos alternativos: $it\n\n") }
doc.select("div.entry > p").eachText().forEach {
append("$it\n")
}
}
}
private fun parseStatus(status: String?): Int {
return when (status?.trim()) {
"Em Lançamento" -> SAnime.ONGOING
"Finalizado" -> SAnime.COMPLETED
else -> SAnime.UNKNOWN
}
}
// ============================== Episodes ==============================
override fun episodeListSelector() = "article.epsd > a"
override fun episodeFromElement(element: Element) = SEpisode.create().apply {
setUrlWithoutDomain(element.attr("href"))
val num = element.selectFirst("span.ttl")!!.text()
episode_number = num.toFloatOrNull() ?: 1F
name = "Episódio $num"
scanlator = element.selectFirst("span.pdx")?.text() ?: "Leg"
}
override fun episodeListParse(response: Response) =
super.episodeListParse(response)
.sortedWith(
compareBy(
{ it.scanlator != "Leg" }, // Dub first
{ it.episode_number },
),
).reversed()
// ============================ Video Links =============================
override fun videoListParse(response: Response): List<Video> {
val doc = response.asJsoup()
val url = doc.selectFirst("div.rvwf > a")!!.attr("href")
val bypasser = LinkfunBypasser(client)
return client.newCall(GET(url, headers))
.execute()
.use(bypasser::getIframeResponse)
.use(::extractVideoFromResponse)
.let(::listOf)
}
private fun extractVideoFromResponse(response: Response): Video {
val decodedBody = LinkfunBypasser.decodeAtob(response.body.string())
val url = decodedBody
.substringAfter("sources")
.substringAfter("file: \"")
.substringBefore('"')
val videoHeaders = Headers.headersOf("Referer", response.request.url.toString())
return Video(url, "default", url, videoHeaders)
}
override fun videoListSelector(): String {
throw UnsupportedOperationException()
}
override fun videoFromElement(element: Element): Video {
throw UnsupportedOperationException()
}
override fun videoUrlParse(document: Document): String {
throw UnsupportedOperationException()
}
// ============================= Utilities ==============================
private fun getRealDoc(document: Document): Document {
if (!document.location().contains("/episodio/")) return document
return document.selectFirst("div.epsdsnv > a:has(i.fa-indent)")?.let {
client.newCall(GET(it.attr("href"), headers)).execute()
.asJsoup()
} ?: document
}
companion object {
const val PREFIX_SEARCH = "id:"
}
}

View File

@ -1,155 +0,0 @@
package eu.kanade.tachiyomi.animeextension.pt.animesorion
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
object AnimesOrionFilters {
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 TypeFilter : QueryPartFilter("Tipo", AnimesOrionFiltersData.TYPES)
class GenreFilter : QueryPartFilter("Gênero", AnimesOrionFiltersData.GENRES)
class StatusFilter : QueryPartFilter("Status", AnimesOrionFiltersData.STATUS)
class LetterFilter : QueryPartFilter("Letra", AnimesOrionFiltersData.LETTERS)
class AudioFilter : QueryPartFilter("Áudio", AnimesOrionFiltersData.AUDIOS)
class YearFilter : QueryPartFilter("Ano", AnimesOrionFiltersData.YEARS)
class SeasonFilter : QueryPartFilter("Temporada", AnimesOrionFiltersData.SEASONS)
val FILTER_LIST get() = AnimeFilterList(
TypeFilter(),
GenreFilter(),
StatusFilter(),
LetterFilter(),
AudioFilter(),
YearFilter(),
SeasonFilter(),
)
data class FilterSearchParams(
val type: String = "todos",
val genre: String = "todos",
val status: String = "todos",
val letter: String = "todas",
val audio: String = "todos",
val year: String = "todos",
val season: String = "todas",
)
internal fun getSearchParameters(filters: AnimeFilterList): FilterSearchParams {
if (filters.isEmpty()) return FilterSearchParams()
return FilterSearchParams(
filters.asQueryPart<TypeFilter>(),
filters.asQueryPart<GenreFilter>(),
filters.asQueryPart<StatusFilter>(),
filters.asQueryPart<LetterFilter>(),
filters.asQueryPart<AudioFilter>(),
filters.asQueryPart<YearFilter>(),
filters.asQueryPart<SeasonFilter>(),
)
}
private object AnimesOrionFiltersData {
val EVERY = Pair("<Escolha>", "todos")
val EVERY_F = Pair("<Escolha>", "todas")
val TYPES = arrayOf(
EVERY,
Pair("Especial", "especial"),
Pair("Filme", "filme"),
Pair("ONA", "ona"),
Pair("OVA", "ova"),
Pair("Série de TV", "serie"),
)
val GENRES = arrayOf(
EVERY,
Pair("Artes Maciais", "artes_maciais"),
Pair("Aventura", "aventura"),
Pair("Ação", "acao"),
Pair("Carros", "carros"),
Pair("Comédia", "comedia"),
Pair("Demência", "demencia"),
Pair("Demônios", "demonios"),
Pair("Drama", "drama"),
Pair("Ecchi", "ecchi"),
Pair("Erótico", "erotico"),
Pair("Escolar", "escolar"),
Pair("Espaço", "espaco"),
Pair("Esporte", "esporte"),
Pair("Fantasia", "fantasia"),
Pair("Ficção Científica", "ficcao_cientifica"),
Pair("Gourmet", "gourmet"),
Pair("Harem", "harem"),
Pair("Hentai", "hentai"),
Pair("Histórico", "historico"),
Pair("Infantil", "infantil"),
Pair("Jogos", "jogos"),
Pair("Josei", "josei"),
Pair("Magia", "magia"),
Pair("Mecha", "mecha"),
Pair("Militar", "militar"),
Pair("Mistério", "misterio"),
Pair("Música", "musica"),
Pair("Paródia", "parodia"),
Pair("Polícia", "policia"),
Pair("Psicológico", "psicologico"),
Pair("Romance", "romance"),
Pair("Samurai", "samurai"),
Pair("Seinen", "seinen"),
Pair("Shoujo Ai", "shoujo_ai"),
Pair("Shoujo", "shoujo"),
Pair("Shounen Ai", "shounen_ai"),
Pair("Shounen", "shounen"),
Pair("Sobrenatural", "sobrenatural"),
Pair("Super Poder", "super_poder"),
Pair("Terror", "terror"),
Pair("Thriller", "thriller"),
Pair("Vampiro", "vampiro"),
Pair("Vida Diária", "vida_diaria"),
Pair("Yaoi", "yaoi"),
Pair("Yuri", "yuri"),
)
val STATUS = arrayOf(
EVERY,
Pair("Em lançamento", "lancamento"),
Pair("Finalizado", "finalizado"),
)
val LETTERS = arrayOf(EVERY_F) + ('a'..'z').map {
Pair(it.toString().uppercase(), it.toString())
}.toTypedArray()
val AUDIOS = arrayOf(
EVERY,
Pair("Dublado", "dublado"),
Pair("Legendado", "legendado"),
)
val YEARS = arrayOf(EVERY) + (2023 downTo 1962).map {
Pair(it.toString(), it.toString())
}.toTypedArray()
val SEASONS = arrayOf(
EVERY_F,
Pair("Inverno", "inverno"),
Pair("Outono", "outono"),
Pair("Primavera", "primavera"),
Pair("Verão", "verao"),
)
}
}

View File

@ -1,41 +0,0 @@
package eu.kanade.tachiyomi.animeextension.pt.animesorion
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://animesorion.com/anime/<item> intents
* and redirects them to the main Aniyomi process.
*/
class AnimesOrionUrlActivity : 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", "${AnimesOrion.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,51 +0,0 @@
package eu.kanade.tachiyomi.animeextension.pt.animesorion.extractors
import android.util.Base64
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.FormBody
import okhttp3.Headers
import okhttp3.OkHttpClient
import okhttp3.Response
class LinkfunBypasser(private val client: OkHttpClient) {
fun getIframeResponse(page: Response): Response {
val document = page.asJsoup(decodeAtob(page.body.string()))
val newHeaders = Headers.headersOf("Referer", document.location())
val iframe = document.selectFirst("iframe")
return if (iframe != null) {
client.newCall(GET(iframe.attr("src"), newHeaders)).execute()
} else {
val formBody = FormBody.Builder().apply {
document.select("input[name]").forEach {
add(it.attr("name"), it.attr("value"))
}
}.build()
val formUrl = document.selectFirst("form")!!.attr("action")
client.newCall(POST(formUrl, newHeaders, formBody))
.execute()
.let(::getIframeResponse)
}
}
companion object {
fun decodeAtob(html: String): String {
val atobContent = html.substringAfter("atob(\"").substringBefore("\"));")
val hexAtob = atobContent.replace("\\x", "").decodeHex()
val decoded = Base64.decode(hexAtob, Base64.DEFAULT)
return String(decoded)
}
// Stolen from AnimixPlay(EN) / GogoCdnExtractor
private fun String.decodeHex(): ByteArray {
check(length % 2 == 0) { "Must have an even length" }
return chunked(2)
.map { it.toInt(16).toByte() }
.toByteArray()
}
}
}

View File

@ -1,17 +0,0 @@
ext {
extName = 'Pobreflix'
extClass = '.Pobreflix'
themePkg = 'dooplay'
baseUrl = 'https://pobreflix.biz'
overrideVersionCode = 8
isNsfw = true
}
apply from: "$rootDir/common.gradle"
dependencies {
implementation(project(":lib:filemoon-extractor"))
implementation(project(":lib:streamwish-extractor"))
implementation(project(":lib:streamtape-extractor"))
implementation(project(":lib:playlist-utils"))
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

View File

@ -1,72 +0,0 @@
package eu.kanade.tachiyomi.animeextension.pt.pobreflix
import android.util.Base64
import eu.kanade.tachiyomi.animeextension.pt.pobreflix.extractors.EplayerExtractor
import eu.kanade.tachiyomi.animeextension.pt.pobreflix.extractors.MyStreamExtractor
import eu.kanade.tachiyomi.animeextension.pt.pobreflix.extractors.SuperFlixExtractor
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.lib.filemoonextractor.FilemoonExtractor
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor
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.Response
class Pobreflix : DooPlay(
"pt-BR",
"Pobreflix",
"https://pobreflix.biz",
) {
// ============================== Popular ===============================
override fun popularAnimeSelector() = "div.featured div.poster"
// =============================== Latest ===============================
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/series/page/$page/", headers)
// ============================ Video Links =============================
private val eplayerExtractor by lazy { EplayerExtractor(client) }
private val filemoonExtractor by lazy { FilemoonExtractor(client) }
private val mystreamExtractor by lazy { MyStreamExtractor(client, headers) }
private val streamtapeExtractor by lazy { StreamTapeExtractor(client) }
private val streamwishExtractor by lazy { StreamWishExtractor(client, headers) }
private val superflixExtractor by lazy { SuperFlixExtractor(client, headers, ::genericExtractor) }
override fun videoListParse(response: Response): List<Video> {
val doc = response.asJsoup()
return doc.select("div.source-box > a").flatMap {
runCatching {
val data = it.attr("href").toHttpUrl().queryParameter("auth")
?.let { Base64.decode(it, Base64.DEFAULT) }
?.let(::String)
?: return@flatMap emptyList()
val url = data.replace("\\", "").substringAfter("url\":\"").substringBefore('"')
when {
url.contains("superflix") ->
superflixExtractor.videosFromUrl(url)
else -> genericExtractor(url)
}
}.getOrElse { emptyList() }
}
}
private fun genericExtractor(url: String, language: String = ""): List<Video> {
val langSubstr = "[$language]"
return when {
url.contains("filemoon") ->
filemoonExtractor.videosFromUrl(url, "$langSubstr Filemoon - ", headers = headers)
url.contains("watch.brplayer") || url.contains("/watch?v=") ->
mystreamExtractor.videosFromUrl(url, language)
url.contains("embedplayer") ->
eplayerExtractor.videosFromUrl(url, language)
url.contains("streamtape") ->
streamtapeExtractor.videosFromUrl(url, "$langSubstr Streamtape")
url.contains("filelions") ->
streamwishExtractor.videosFromUrl(url, videoNameGen = { "$langSubstr FileLions - $it" })
url.contains("streamwish") ->
streamwishExtractor.videosFromUrl(url, videoNameGen = { "$langSubstr Streamwish - $it" })
else -> emptyList()
}
}
}

View File

@ -1,45 +0,0 @@
package eu.kanade.tachiyomi.animeextension.pt.pobreflix.extractors
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.lib.playlistutils.PlaylistUtils
import eu.kanade.tachiyomi.network.POST
import okhttp3.FormBody
import okhttp3.Headers
import okhttp3.OkHttpClient
class EplayerExtractor(private val client: OkHttpClient) {
private val headers by lazy {
Headers.headersOf(
"X-Requested-With",
"XMLHttpRequest",
"Referer",
EPLAYER_HOST,
"Origin",
EPLAYER_HOST,
)
}
private val playlistUtils by lazy { PlaylistUtils(client, headers) }
fun videosFromUrl(url: String, lang: String): List<Video> {
val id = url.substringAfterLast("/")
val postUrl = "$EPLAYER_HOST/player/index.php?data=$id&do=getVideo"
val body = FormBody.Builder()
.add("hash", id)
.add("r", "")
.build()
val masterUrl = client.newCall(POST(postUrl, headers, body = body)).execute()
.body.string()
.substringAfter("videoSource\":\"")
.substringBefore('"')
.replace("\\", "")
return playlistUtils.extractFromHls(masterUrl, videoNameGen = { "[$lang] EmbedPlayer - $it" })
}
companion object {
private const val EPLAYER_HOST = "https://embedplayer.online"
}
}

View File

@ -1,50 +0,0 @@
package eu.kanade.tachiyomi.animeextension.pt.pobreflix.extractors
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.lib.playlistutils.PlaylistUtils
import eu.kanade.tachiyomi.network.GET
import okhttp3.Headers
import okhttp3.OkHttpClient
// From animeworldindia
class MyStreamExtractor(private val client: OkHttpClient, private val headers: Headers) {
private val playlistUtils by lazy { PlaylistUtils(client, headers) }
fun videosFromUrl(url: String, language: String): List<Video> {
val host = url.substringBefore("/watch?")
return runCatching {
val response = client.newCall(GET(url, headers)).execute()
val body = response.body.string()
val codePart = body
.substringAfter("sniff(") // Video function
.substringBefore(",[")
val streamCode = codePart
.substringAfterLast(",\"") // our beloved hash
.substringBefore('"')
val id = codePart.substringAfter(",\"").substringBefore('"') // required ID
val streamUrl = "$host/m3u8/$id/$streamCode/master.txt?s=1&cache=1"
val cookie = response.headers.firstOrNull {
it.first.startsWith("set-cookie", true) && it.second.startsWith("PHPSESSID", true)
}?.second?.substringBefore(";") ?: ""
val newHeaders = headers.newBuilder()
.set("cookie", cookie)
.set("accept", "*/*")
.build()
playlistUtils.extractFromHls(
streamUrl,
masterHeaders = newHeaders,
videoHeaders = newHeaders,
videoNameGen = { "[$language] MyStream: $it" },
)
}.getOrElse { emptyList<Video>() }
}
}

View File

@ -1,128 +0,0 @@
package eu.kanade.tachiyomi.animeextension.pt.pobreflix.extractors
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.util.asJsoup
import eu.kanade.tachiyomi.util.parallelCatchingFlatMapBlocking
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import okhttp3.FormBody
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import uy.kohesive.injekt.injectLazy
class SuperFlixExtractor(
private val client: OkHttpClient,
private val defaultHeaders: Headers,
private val genericExtractor: (String, String) -> List<Video>,
) {
private val json: Json by injectLazy()
fun videosFromUrl(url: String): List<Video> {
val links = linksFromUrl(url)
val fixedLinks = links.parallelCatchingFlatMapBlocking {
val (language, linkUrl) = it
when {
linkUrl.contains("?vid=") -> linksFromPlayer(linkUrl, language)
else -> listOf(it)
}
}
return fixedLinks.parallelCatchingFlatMapBlocking { genericExtractor(it.second, it.first) }
}
private suspend fun linksFromPlayer(url: String, language: String): List<Pair<String, String>> {
val httpUrl = url.toHttpUrl()
val id = httpUrl.queryParameter("vid")!!
val headers = defaultHeaders.newBuilder()
.set("referer", "$API_DOMAIN/")
.set("origin", API_DOMAIN)
.build()
val doc = client.newCall(GET(url, headers)).await().asJsoup()
val baseUrl = "https://" + httpUrl.host
val apiUrl = "$baseUrl/ajax_sources.php"
val apiHeaders = headers.newBuilder()
.set("referer", url)
.set("origin", baseUrl)
.set("X-Requested-With", "XMLHttpRequest")
.build()
return doc.select("ul > li[data-order-value]").mapNotNull {
val name = it.attr("data-dropdown-value")
val order = it.attr("data-order-value")
val formBody = FormBody.Builder()
.add("vid", id)
.add("alternative", name)
.add("ord", order)
.build()
val req = client.newCall(POST(apiUrl, apiHeaders, formBody)).await()
.body.string()
runCatching {
val iframeUrl = json.decodeFromString<PlayerLinkDto>(req).iframe!!
val iframeServer = iframeUrl.toHttpUrl().queryParameter("sv")!!
language to when (name) {
"1xbet" -> "https://watch.brplayer.site/watch?v=${iframeServer.trim('/')}"
else -> iframeServer
}
}.getOrNull()
}
}
@Serializable
data class PlayerLinkDto(val iframe: String? = null)
private fun linksFromUrl(url: String): List<Pair<String, String>> {
val doc = client.newCall(GET(url, defaultHeaders)).execute().asJsoup()
val items = doc.select("div.select_language").mapNotNull {
val target = it.attr("data-target")
val id = doc.selectFirst("div.players_select div[data-target=$target] div[data-id]")
?.attr("data-id")
?: return@mapNotNull null
it.text() to id // (Language, videoId)
}
val headers = defaultHeaders.newBuilder()
.set("Origin", API_DOMAIN)
.set("Referer", url)
.set("X-Requested-With", "XMLHttpRequest")
.build()
return items.mapNotNull {
runCatching {
it.first to getLink(it.second, headers)!!
}.getOrNull()
}
}
private fun getLink(id: String, headers: Headers): String? {
val body = FormBody.Builder()
.add("action", "getPlayer")
.add("video_id", id)
.build()
val res = client.newCall(POST("$API_DOMAIN/api", headers, body)).execute()
.body.string()
return json.decodeFromString<ApiResponseDto>(res).data?.video_url
}
@Serializable
data class ApiResponseDto(val data: DataDto? = null)
@Serializable
data class DataDto(val video_url: String? = null)
}
private const val API_DOMAIN = "https://superflixapi.top"