fix(ar/anime4up): Update baseUrl + more extractors (#2436)

This commit is contained in:
Claudemirovsky
2023-10-30 09:42:44 -03:00
committed by GitHub
parent 93cba14068
commit 9516ee6b1f
4 changed files with 293 additions and 317 deletions

View File

@ -1,21 +1,26 @@
apply plugin: 'com.android.application' plugins {
apply plugin: 'kotlin-android' alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.serialization)
}
ext { ext {
extName = 'Anime4up' extName = 'Anime4up'
pkgNameSuffix = 'ar.anime4up' pkgNameSuffix = 'ar.anime4up'
extClass = '.Anime4Up' extClass = '.Anime4Up'
extVersionCode = 50 extVersionCode = 51
libVersion = '13' libVersion = '13'
} }
dependencies { dependencies {
implementation(project(':lib-streamwish-extractor'))
implementation(project(':lib-gdriveplayer-extractor'))
implementation(project(':lib-dood-extractor')) implementation(project(':lib-dood-extractor'))
implementation(project(':lib-voe-extractor')) implementation(project(':lib-gdriveplayer-extractor'))
implementation(project(':lib-vidbom-extractor')) implementation(project(':lib-mp4upload-extractor'))
implementation(project(':lib-okru-extractor')) implementation(project(':lib-okru-extractor'))
implementation(project(':lib-streamwish-extractor'))
implementation(project(':lib-uqload-extractor'))
implementation(project(':lib-vidbom-extractor'))
implementation(project(':lib-voe-extractor'))
implementation "dev.datlag.jsunpacker:jsunpacker:1.0.1" implementation "dev.datlag.jsunpacker:jsunpacker:1.0.1"
} }

View File

@ -1,15 +1,12 @@
package eu.kanade.tachiyomi.animeextension.ar.anime4up package eu.kanade.tachiyomi.animeextension.ar.anime4up
import android.app.Application import android.app.Application
import android.content.SharedPreferences import android.util.Base64
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.preference.ListPreference import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.animeextension.ar.anime4up.extractors.SharedExtractor import eu.kanade.tachiyomi.animeextension.ar.anime4up.extractors.SharedExtractor
import eu.kanade.tachiyomi.animeextension.ar.anime4up.extractors.VidYardExtractor import eu.kanade.tachiyomi.animeextension.ar.anime4up.extractors.VidYardExtractor
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource 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.AnimeFilterList
import eu.kanade.tachiyomi.animesource.model.SAnime import eu.kanade.tachiyomi.animesource.model.SAnime
import eu.kanade.tachiyomi.animesource.model.SEpisode import eu.kanade.tachiyomi.animesource.model.SEpisode
@ -17,8 +14,10 @@ import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
import eu.kanade.tachiyomi.lib.doodextractor.DoodExtractor import eu.kanade.tachiyomi.lib.doodextractor.DoodExtractor
import eu.kanade.tachiyomi.lib.gdriveplayerextractor.GdrivePlayerExtractor import eu.kanade.tachiyomi.lib.gdriveplayerextractor.GdrivePlayerExtractor
import eu.kanade.tachiyomi.lib.mp4uploadextractor.Mp4uploadExtractor
import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor
import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor
import eu.kanade.tachiyomi.lib.uqloadextractor.UqloadExtractor
import eu.kanade.tachiyomi.lib.vidbomextractor.VidBomExtractor import eu.kanade.tachiyomi.lib.vidbomextractor.VidBomExtractor
import eu.kanade.tachiyomi.lib.voeextractor.VoeExtractor import eu.kanade.tachiyomi.lib.voeextractor.VoeExtractor
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
@ -27,266 +26,184 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonObject
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.lang.Exception import java.lang.Exception
import java.util.Base64
class Anime4Up : ConfigurableAnimeSource, ParsedAnimeHttpSource() { class Anime4Up : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override val name = "Anime4Up" override val name = "Anime4Up"
override val baseUrl = "https://w1.anime4up.tv" override val baseUrl = "https://anime4up.cam"
override val lang = "ar" override val lang = "ar"
override val supportsLatest = false override val supportsLatest = false
override val client: OkHttpClient = network.cloudflareClient override val client = network.cloudflareClient
private val json = Json { private val json: Json by injectLazy()
ignoreUnknownKeys = true
}
private val preferences: SharedPreferences by lazy { private val preferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
} }
override fun headersBuilder(): Headers.Builder { override fun headersBuilder() = super.headersBuilder().add("Referer", "$baseUrl/")
return super.headersBuilder()
.add("Referer", "https://w1.anime4up.tv/") // https://s12.gemzawy.com https://moshahda.net
}
// Popular // ============================== Popular ===============================
override fun popularAnimeRequest(page: Int) = GET("$baseUrl/anime-list-3/page/$page/")
override fun popularAnimeSelector(): String = "div.anime-list-content div.row div.col-lg-2" override fun popularAnimeSelector() = "div.anime-list-content div.anime-card-poster > div.hover"
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/anime-list-3/page/$page/") override fun popularAnimeFromElement(element: Element) = SAnime.create().apply {
element.selectFirst("img")!!.run {
override fun popularAnimeFromElement(element: Element): SAnime { thumbnail_url = absUrl("src")
val anime = SAnime.create() title = attr("alt")
anime.thumbnail_url = element.select("div.anime-card-poster div.ehover6 img").attr("src")
anime.setUrlWithoutDomain(element.select("div.anime-card-poster div.ehover6 a").attr("href"))
anime.title = element.select("div.anime-card-poster div.ehover6 img").attr("alt")
return anime
}
override fun popularAnimeNextPageSelector(): String = "ul.pagination li a.next"
// episodes
override fun episodeListParse(response: Response): List<SEpisode> {
return super.episodeListParse(response).reversed()
}
override fun episodeListSelector() = "div.episodes-list-content div#DivEpisodesList div.col-md-3" // "ul.episodes-links li a"
override fun episodeFromElement(element: Element): SEpisode {
val episode = SEpisode.create()
val epNum = getNumberFromEpsString(element.select("div.episodes-card-container div.episodes-card div.ehover6 h3 a").text())
episode.setUrlWithoutDomain(element.select("div.episodes-card-container div.episodes-card div.ehover6 h3 a").attr("href"))
// episode.episode_number = element.select("span:nth-child(3)").text().replace(" - ", "").toFloat()
episode.episode_number = when {
epNum.isNotEmpty() -> epNum.toFloatOrNull() ?: 1F
else -> 1F
} }
episode.name = element.select("div.episodes-card-container div.episodes-card div.ehover6 h3 a").text() setUrlWithoutDomain(element.selectFirst("a")!!.attr("href"))
return episode
} }
private fun getNumberFromEpsString(epsStr: String): String { override fun popularAnimeNextPageSelector() = "ul.pagination > li > a.next"
return epsStr.filter { it.isDigit() }
// =============================== Latest ===============================
override fun latestUpdatesRequest(page: Int) = throw Exception("not used")
override fun latestUpdatesSelector() = throw Exception("not used")
override fun latestUpdatesFromElement(element: Element) = throw Exception("not used")
override fun latestUpdatesNextPageSelector() = throw Exception("not used")
// =============================== Search ===============================
override fun getFilterList() = Anime4UpFilters.FILTER_LIST
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
if (query.isNotBlank()) {
return GET("$baseUrl/?search_param=animes&s=$query", headers)
}
return with(Anime4UpFilters.getSearchParameters(filters)) {
val url = when {
genre.isNotBlank() -> "$baseUrl/anime-genre/$genre"
type.isNotBlank() -> "$baseUrl/anime-type/$type"
status.isNotBlank() -> "$baseUrl/anime-status/$status"
else -> throw Exception("اختر فلتر")
}
GET(url, headers)
}
} }
// Video links override fun searchAnimeFromElement(element: Element) = popularAnimeFromElement(element)
override fun searchAnimeNextPageSelector() = popularAnimeNextPageSelector()
override fun searchAnimeSelector() = popularAnimeSelector()
// =========================== Anime Details ============================
override fun animeDetailsParse(document: Document) = SAnime.create().apply {
val doc = document // Shortcut
thumbnail_url = doc.selectFirst("img.thumbnail")!!.attr("src")
title = doc.selectFirst("h1.anime-details-title")!!.text()
// Genres + useful info
genre = doc.select("ul.anime-genres > li > a, div.anime-info > a").eachText().joinToString()
description = buildString {
// Additional info
doc.select("div.anime-info").eachText().forEach {
append("$it\n")
}
// Description
doc.selectFirst("p.anime-story")?.text()?.also {
append("\n$it")
}
}
doc.selectFirst("div.anime-info:contains(حالة الأنمي)")?.text()?.also {
status = when {
it.contains("يعرض الان", true) -> SAnime.ONGOING
it.contains("مكتمل", true) -> SAnime.COMPLETED
else -> SAnime.UNKNOWN
}
}
}
// ============================== Episodes ==============================
override fun episodeListParse(response: Response) = super.episodeListParse(response).reversed()
override fun episodeListSelector() = "div.ehover6 > div.episodes-card-title > h3 > a, ul.all-episodes-list li > a"
override fun episodeFromElement(element: Element) = SEpisode.create().apply {
setUrlWithoutDomain(element.attr("href"))
name = element.text()
episode_number = name.substringAfterLast(" ").toFloatOrNull() ?: 0F
}
// ============================ Video Links =============================
@Serializable
data class Qualities(
val fhd: Map<String, String> = emptyMap(),
val hd: Map<String, String> = emptyMap(),
val sd: Map<String, String> = emptyMap(),
)
@RequiresApi(Build.VERSION_CODES.O)
override fun videoListParse(response: Response): List<Video> { override fun videoListParse(response: Response): List<Video> {
val base64 = response.asJsoup().select("input[name=wl]").attr("value") val base64 = response.use { it.asJsoup() }.selectFirst("input[name=wl]")
val jHash = String(Base64.getDecoder().decode(base64)) ?.attr("value")
val parsedJ = json.decodeFromString<JsonObject>(jHash) ?.let { String(Base64.decode(it, Base64.DEFAULT)) }
val streamLinks = parsedJ["fhd"]!!.jsonObject.entries + parsedJ["hd"]!!.jsonObject.entries + parsedJ["sd"]!!.jsonObject.entries ?: return emptyList()
return streamLinks.distinctBy { it.key }.parallelMap {
val url = it.value.toString().replace("\"", "") val parsedData = json.decodeFromString<Qualities>(base64)
runCatching { extractVideos(url) }.getOrElse { emptyList() } val streamLinks = with(parsedData) { fhd + hd + sd }
}.flatten()
return streamLinks.values.distinct().parallelCatchingFlatMap(::extractVideos)
} }
private inline fun <A, B> Iterable<A>.parallelMap(crossinline f: suspend (A) -> B): List<B> = private val uqloadExtractor by lazy { UqloadExtractor(client) }
runBlocking { private val doodExtractor by lazy { DoodExtractor(client) }
map { async(Dispatchers.Default) { f(it) } }.awaitAll() private val gdriveplayerExtractor by lazy { GdrivePlayerExtractor(client) }
} private val mp4uploadExtractor by lazy { Mp4uploadExtractor(client) }
private val okruExtractor by lazy { OkruExtractor(client) }
private val sharedExtractor by lazy { SharedExtractor(client) }
private val streamwishExtractor by lazy { StreamWishExtractor(client, headers) }
private val vidbomExtractor by lazy { VidBomExtractor(client) }
private val vidyardExtractor by lazy { VidYardExtractor(client, headers) }
private val voeExtractor by lazy { VoeExtractor(client) }
private fun extractVideos(url: String): List<Video> { private fun extractVideos(url: String): List<Video> {
return when { return when {
url.contains("shared") -> {
SharedExtractor(client).videosFromUrl(url)?.let(::listOf)
}
url.contains("drive.google") -> { url.contains("drive.google") -> {
val embedUrlG = "https://gdriveplayer.to/embed2.php?link=$url" val embedUrlG = "https://gdriveplayer.to/embed2.php?link=$url"
GdrivePlayerExtractor(client).videosFromUrl(embedUrlG, "GdrivePlayer", headers = headers) gdriveplayerExtractor.videosFromUrl(embedUrlG, "GdrivePlayer", headers)
}
url.contains("vidyard") -> {
val headers = headers.newBuilder()
.set("Referer", "https://play.vidyard.com")
.set("Accept-Encoding", "gzip, deflate, br")
.set("Accept-Language", "en-US,en;q=0.5")
.set("TE", "trailers")
.set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:101.0) Gecko/20100101 Firefox/101.0")
.build()
val id = url.substringAfter("com/").substringBefore("?")
val vidUrl = "https://play.vidyard.com/player/$id.json"
VidYardExtractor(client).videosFromUrl(vidUrl, headers)
}
url.contains("ok.ru") -> {
OkruExtractor(client).videosFromUrl(url)
}
url.contains("voe") -> {
VoeExtractor(client).videoFromUrl(url)?.let(::listOf)
}
DOOD_REGEX.containsMatchIn(url) -> {
DoodExtractor(client).videoFromUrl(url, "Dood mirror")?.let(::listOf)
}
VIDBOM_REGEX.containsMatchIn(url) -> {
val finalUrl = VIDBOM_REGEX.find(url)!!.groupValues[0]
VidBomExtractor(client).videosFromUrl("https://www.$finalUrl.html")
}
STREAMWISH_REGEX.containsMatchIn(url) -> {
val headers = headers.newBuilder()
.set("Referer", url)
.set("Accept-Encoding", "gzip, deflate, br")
.set("Accept-Language", "en-US,en;q=0.5")
.set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:101.0) Gecko/20100101 Firefox/101.0")
.build()
val finalUrl = STREAMWISH_REGEX.find(url)!!.groupValues[0]
StreamWishExtractor(client, headers).videosFromUrl("https://www.$finalUrl") { "Mirror: $it" }
} }
url.contains("vidyard") -> vidyardExtractor.videosFromUrl(url)
url.contains("ok.ru") -> okruExtractor.videosFromUrl(url)
url.contains("mp4upload") -> mp4uploadExtractor.videosFromUrl(url, headers)
url.contains("uqload") -> uqloadExtractor.videosFromUrl(url)
url.contains("voe") -> voeExtractor.videoFromUrl(url)?.let(::listOf)
url.contains("shared") -> sharedExtractor.videosFromUrl(url)?.let(::listOf)
DOOD_REGEX.containsMatchIn(url) -> doodExtractor.videosFromUrl(url, "Dood mirror")
VIDBOM_REGEX.containsMatchIn(url) -> vidbomExtractor.videosFromUrl(url)
STREAMWISH_REGEX.containsMatchIn(url) -> streamwishExtractor.videosFromUrl(url) { "Mirror: $it" }
else -> null else -> null
} ?: emptyList() } ?: emptyList()
} }
// override fun videoListSelector() = "script:containsData(m3u8)" override fun videoListSelector() = throw Exception("not used")
override fun videoListSelector() = "li[data-i] a"
override fun List<Video>.sort(): List<Video> {
val quality = preferences.getString("preferred_quality", null)
if (quality != null) {
val newList = mutableListOf<Video>()
var preferred = 0
for (video in this) {
if (video.quality.contains(quality)) {
newList.add(preferred, video)
preferred++
} else {
newList.add(video)
}
}
return newList
}
return this
}
override fun videoFromElement(element: Element) = throw Exception("not used") override fun videoFromElement(element: Element) = throw Exception("not used")
override fun videoUrlParse(document: Document) = throw Exception("not used") override fun videoUrlParse(document: Document) = throw Exception("not used")
// Search // ============================== Settings ==============================
override fun searchAnimeFromElement(element: Element): SAnime {
val anime = SAnime.create()
anime.thumbnail_url = element.select("div.anime-card-container div.anime-card-poster div.ehover6 img").attr("src")
anime.setUrlWithoutDomain(element.select("div.anime-card-container div.anime-card-poster div.ehover6 a").attr("href"))
anime.title = element.select("div.anime-card-container div.anime-card-poster div.ehover6 img").attr("alt")
return anime
}
override fun searchAnimeNextPageSelector(): String = "ul.pagination li a.next"
override fun searchAnimeSelector(): String = "div.anime-list-content div.row.display-flex div.col-md-4"
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val url = if (query.isNotBlank()) {
"$baseUrl/?search_param=animes&s=$query"
} else {
(if (filters.isEmpty()) getFilterList() else filters).forEach { filter ->
when (filter) {
is GenreList -> {
if (filter.state > 0) {
val genreN = getGenreList()[filter.state].query
val genreUrl = "$baseUrl/anime-genre/$genreN".toHttpUrlOrNull()!!.newBuilder()
return GET(genreUrl.toString(), headers)
}
}
is StatusList -> {
if (filter.state > 0) {
val statusN = getStatusList()[filter.state].query
val statusUrl = "$baseUrl/anime-status/$statusN".toHttpUrlOrNull()!!.newBuilder()
return GET(statusUrl.toString(), headers)
}
}
is TypeList -> {
if (filter.state > 0) {
val typeN = getTypeList()[filter.state].query
val typeUrl = "$baseUrl/anime-type/$typeN".toHttpUrlOrNull()!!.newBuilder()
return GET(typeUrl.toString(), headers)
}
}
else -> {}
}
}
throw Exception("اختر فلتر")
}
return GET(url, headers)
}
// Anime Details
override fun animeDetailsParse(document: Document): SAnime {
val anime = SAnime.create()
anime.thumbnail_url = document.selectFirst("img.thumbnail")!!.attr("src")
anime.title = document.select("h1.anime-details-title").text()
anime.genre = document.select("ul.anime-genres > li > a, div.anime-info > a").joinToString(", ") { it.text() }
anime.description = document.select("p.anime-story").text()
document.select("div.anime-info a").text().also { statusText ->
when {
statusText.contains("يعرض الان", true) -> anime.status = SAnime.ONGOING
statusText.contains("مكتمل", true) -> anime.status = SAnime.COMPLETED
else -> anime.status = SAnime.UNKNOWN
}
}
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")
// Settings
override fun setupPreferenceScreen(screen: PreferenceScreen) { override fun setupPreferenceScreen(screen: PreferenceScreen) {
val videoQualityPref = ListPreference(screen.context).apply { ListPreference(screen.context).apply {
key = "preferred_quality" key = PREF_QUALITY_KEY
title = "Preferred quality" title = PREF_QUALITY_TITLE
entries = arrayOf("1080p", "720p", "480p", "360p", "DOODStream") entries = PREF_QUALITY_ENTRIES
entryValues = arrayOf("1080", "720", "480", "360", "dood") entryValues = PREF_QUALITY_VALUES
setDefaultValue("1080") setDefaultValue(PREF_QUALITY_DEFAULT)
summary = "%s" summary = "%s"
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
@ -295,100 +212,36 @@ class Anime4Up : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
val entry = entryValues[index] as String val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit() preferences.edit().putString(key, entry).commit()
} }
} }.also(screen::addPreference)
screen.addPreference(videoQualityPref)
} }
// Filter // ============================= Utilities ==============================
private inline fun <A, B> Iterable<A>.parallelCatchingFlatMap(crossinline f: suspend (A) -> Iterable<B>): List<B> =
runBlocking {
map {
async(Dispatchers.Default) {
runCatching { f(it) }.getOrElse { emptyList() }
}
}.awaitAll().flatten()
}
override fun getFilterList() = AnimeFilterList( override fun List<Video>.sort(): List<Video> {
AnimeFilter.Header("الفلترات مش هتشتغل لو بتبحث او وهي فاضيه"), val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
GenreList(genresName),
TypeList(typesName),
StatusList(statusesName),
)
private class GenreList(genres: Array<String>) : AnimeFilter.Select<String>("تصنيف الأنمي", genres) return sortedWith(
private data class Genre(val name: String, val query: String) compareBy { it.quality.contains(quality) },
private val genresName = getGenreList().map { ).reversed()
it.name }
}.toTypedArray()
private class TypeList(types: Array<String>) : AnimeFilter.Select<String>("نوع الأنمي", types)
private data class Type(val name: String, val query: String)
private val typesName = getTypeList().map {
it.name
}.toTypedArray()
private class StatusList(statuse: Array<String>) : AnimeFilter.Select<String>("حالة الأنمي", statuse)
private data class Status(val name: String, val query: String)
private val statusesName = getStatusList().map {
it.name
}.toTypedArray()
private fun getGenreList() = listOf(
Genre("اختر", ""),
Genre("أطفال", "%d8%a3%d8%b7%d9%81%d8%a7%d9%84"),
Genre("أكشن", "%d8%a3%d9%83%d8%b4%d9%86/"),
Genre("إيتشي", "%d8%a5%d9%8a%d8%aa%d8%b4%d9%8a/"),
Genre("اثارة", "%d8%a7%d8%ab%d8%a7%d8%b1%d8%a9/"),
Genre("العاب", "%d8%a7%d9%84%d8%b9%d8%a7%d8%a8/"),
Genre("بوليسي", "%d8%a8%d9%88%d9%84%d9%8a%d8%b3%d9%8a/"),
Genre("تاريخي", "%d8%aa%d8%a7%d8%b1%d9%8a%d8%ae%d9%8a/"),
Genre("جنون", "%d8%ac%d9%86%d9%88%d9%86/"),
Genre("جوسي", "%d8%ac%d9%88%d8%b3%d9%8a/"),
Genre("حربي", "%d8%ad%d8%b1%d8%a8%d9%8a/"),
Genre("حريم", "%d8%ad%d8%b1%d9%8a%d9%85/"),
Genre("خارق للعادة", "%d8%ae%d8%a7%d8%b1%d9%82-%d9%84%d9%84%d8%b9%d8%a7%d8%af%d8%a9/"),
Genre("خيال علمي", "%d8%ae%d9%8a%d8%a7%d9%84-%d8%b9%d9%84%d9%85%d9%8a/"),
Genre("دراما", "%d8%af%d8%b1%d8%a7%d9%85%d8%a7/"),
Genre("رعب", "%d8%b1%d8%b9%d8%a8/"),
Genre("رومانسي", "%d8%b1%d9%88%d9%85%d8%a7%d9%86%d8%b3%d9%8a/"),
Genre("رياضي", "%d8%b1%d9%8a%d8%a7%d8%b6%d9%8a/"),
Genre("ساموراي", "%d8%b3%d8%a7%d9%85%d9%88%d8%b1%d8%a7%d9%8a/"),
Genre("سحر", "%d8%b3%d8%ad%d8%b1/"),
Genre("سينين", "%d8%b3%d9%8a%d9%86%d9%8a%d9%86/"),
Genre("شريحة من الحياة", "%d8%b4%d8%b1%d9%8a%d8%ad%d8%a9-%d9%85%d9%86-%d8%a7%d9%84%d8%ad%d9%8a%d8%a7%d8%a9/"),
Genre("شوجو", "%d8%b4%d9%88%d8%ac%d9%88/"),
Genre("شوجو اَي", "%d8%b4%d9%88%d8%ac%d9%88-%d8%a7%d9%8e%d9%8a/"),
Genre("شونين", "%d8%b4%d9%88%d9%86%d9%8a%d9%86/"),
Genre("شونين اي", "%d8%b4%d9%88%d9%86%d9%8a%d9%86-%d8%a7%d9%8a/"),
Genre("شياطين", "%d8%b4%d9%8a%d8%a7%d8%b7%d9%8a%d9%86/"),
Genre("غموض", "%d8%ba%d9%85%d9%88%d8%b6/"),
Genre("فضائي", "%d9%81%d8%b6%d8%a7%d8%a6%d9%8a/"),
Genre("فنتازيا", "%d9%81%d9%86%d8%aa%d8%a7%d8%b2%d9%8a%d8%a7/"),
Genre("فنون قتالية", "%d9%81%d9%86%d9%88%d9%86-%d9%82%d8%aa%d8%a7%d9%84%d9%8a%d8%a9/"),
Genre("قوى خارقة", "%d9%82%d9%88%d9%89-%d8%ae%d8%a7%d8%b1%d9%82%d8%a9/"),
Genre("كوميدي", "%d9%83%d9%88%d9%85%d9%8a%d8%af%d9%8a/"),
Genre("محاكاة ساخرة", "%d9%85%d8%ad%d8%a7%d9%83%d8%a7%d8%a9-%d8%b3%d8%a7%d8%ae%d8%b1%d8%a9/"),
Genre("مدرسي", "%d9%85%d8%af%d8%b1%d8%b3%d9%8a/"),
Genre("مصاصي دماء", "%d9%85%d8%b5%d8%a7%d8%b5%d9%8a-%d8%af%d9%85%d8%a7%d8%a1/"),
Genre("مغامرات", "%d9%85%d8%ba%d8%a7%d9%85%d8%b1%d8%a7%d8%aa/"),
Genre("موسيقي", "%d9%85%d9%88%d8%b3%d9%8a%d9%82%d9%8a/"),
Genre("ميكا", "%d9%85%d9%8a%d9%83%d8%a7/"),
Genre("نفسي", "%d9%86%d9%81%d8%b3%d9%8a/"),
)
private fun getTypeList() = listOf(
Type("أختر", ""),
Type("Movie", "movie-3"),
Type("ONA", "ona1"),
Type("OVA", "ova1"),
Type("Special", "special1"),
Type("TV", "tv2"),
)
private fun getStatusList() = listOf(
Status("أختر", ""),
Status("لم يعرض بعد", "%d9%84%d9%85-%d9%8a%d8%b9%d8%b1%d8%b6-%d8%a8%d8%b9%d8%af"),
Status("مكتمل", "complete"),
Status("يعرض الان", "%d9%8a%d8%b9%d8%b1%d8%b6-%d8%a7%d9%84%d8%a7%d9%86-1"),
)
companion object { companion object {
private val VIDBOM_REGEX = Regex("(?:v[aie]d[bp][aoe]?m|myvii?d|segavid|v[aei]{1,2}dshar[er]?)\\.(?:com|net|org|xyz)(?::\\d+)?/(?:embed[/-])?([A-Za-z0-9]+)") private val VIDBOM_REGEX = Regex("(?:v[aie]d[bp][aoe]?m|myvii?d|segavid|v[aei]{1,2}dshar[er]?)\\.(?:com|net|org|xyz)(?::\\d+)?/(?:embed[/-])?([A-Za-z0-9]+)")
private val DOOD_REGEX = Regex("(do*d(?:stream)?\\.(?:com?|watch|to|s[ho]|cx|la|w[sf]|pm|re|yt|stream))/[de]/([0-9a-zA-Z]+)") private val DOOD_REGEX = Regex("(do*d(?:stream)?\\.(?:com?|watch|to|s[ho]|cx|la|w[sf]|pm|re|yt|stream))/[de]/([0-9a-zA-Z]+)")
private val STREAMWISH_REGEX = Regex("((?:streamwish|anime7u|animezd|ajmidyad|khadhnayad|yadmalik|hayaatieadhab)\\.(?:com|to|sbs))/(?:e/|v/|f/)?([0-9a-zA-Z]+)") private val STREAMWISH_REGEX = Regex("((?:streamwish|anime7u|animezd|ajmidyad|khadhnayad|yadmalik|hayaatieadhab)\\.(?:com|to|sbs))/(?:e/|v/|f/)?([0-9a-zA-Z]+)")
private const val PREF_QUALITY_KEY = "preferred_quality"
private const val PREF_QUALITY_TITLE = "Preferred quality"
private const val PREF_QUALITY_DEFAULT = "1080p"
private val PREF_QUALITY_ENTRIES = arrayOf("1080p", "720p", "480p", "360p", "dood")
private val PREF_QUALITY_VALUES = PREF_QUALITY_ENTRIES
} }
} }

View File

@ -0,0 +1,110 @@
package eu.kanade.tachiyomi.animeextension.ar.anime4up
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
object Anime4UpFilters {
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()
}
internal class GenreFilter : QueryPartFilter("تصنيف الأنمي", Anime4UpFiltersData.GENRES)
internal class TypeFilter : QueryPartFilter("نوع الأنمي", Anime4UpFiltersData.TYPES)
internal class StatusFilter : QueryPartFilter("حالة الأنمي", Anime4UpFiltersData.STATUS)
val FILTER_LIST get() = AnimeFilterList(
AnimeFilter.Header("الفلترات مش هتشتغل لو بتبحث او وهي فاضيه"),
GenreFilter(),
TypeFilter(),
StatusFilter(),
)
data class FilterSearchParams(
val genre: String = "",
val type: String = "",
val status: String = "",
)
internal fun getSearchParameters(filters: AnimeFilterList): FilterSearchParams {
if (filters.isEmpty()) return FilterSearchParams()
return FilterSearchParams(
filters.asQueryPart<GenreFilter>(),
filters.asQueryPart<TypeFilter>(),
filters.asQueryPart<StatusFilter>(),
)
}
private object Anime4UpFiltersData {
private val ANY = Pair("أختر", "")
val GENRES = arrayOf(
ANY,
Pair("أطفال", "%d8%a3%d8%b7%d9%81%d8%a7%d9%84"),
Pair("أكشن", "%d8%a3%d9%83%d8%b4%d9%86/"),
Pair("إيتشي", "%d8%a5%d9%8a%d8%aa%d8%b4%d9%8a/"),
Pair("اثارة", "%d8%a7%d8%ab%d8%a7%d8%b1%d8%a9/"),
Pair("العاب", "%d8%a7%d9%84%d8%b9%d8%a7%d8%a8/"),
Pair("بوليسي", "%d8%a8%d9%88%d9%84%d9%8a%d8%b3%d9%8a/"),
Pair("تاريخي", "%d8%aa%d8%a7%d8%b1%d9%8a%d8%ae%d9%8a/"),
Pair("جنون", "%d8%ac%d9%86%d9%88%d9%86/"),
Pair("جوسي", "%d8%ac%d9%88%d8%b3%d9%8a/"),
Pair("حربي", "%d8%ad%d8%b1%d8%a8%d9%8a/"),
Pair("حريم", "%d8%ad%d8%b1%d9%8a%d9%85/"),
Pair("خارق للعادة", "%d8%ae%d8%a7%d8%b1%d9%82-%d9%84%d9%84%d8%b9%d8%a7%d8%af%d8%a9/"),
Pair("خيال علمي", "%d8%ae%d9%8a%d8%a7%d9%84-%d8%b9%d9%84%d9%85%d9%8a/"),
Pair("دراما", "%d8%af%d8%b1%d8%a7%d9%85%d8%a7/"),
Pair("رعب", "%d8%b1%d8%b9%d8%a8/"),
Pair("رومانسي", "%d8%b1%d9%88%d9%85%d8%a7%d9%86%d8%b3%d9%8a/"),
Pair("رياضي", "%d8%b1%d9%8a%d8%a7%d8%b6%d9%8a/"),
Pair("ساموراي", "%d8%b3%d8%a7%d9%85%d9%88%d8%b1%d8%a7%d9%8a/"),
Pair("سحر", "%d8%b3%d8%ad%d8%b1/"),
Pair("سينين", "%d8%b3%d9%8a%d9%86%d9%8a%d9%86/"),
Pair("شريحة من الحياة", "%d8%b4%d8%b1%d9%8a%d8%ad%d8%a9-%d9%85%d9%86-%d8%a7%d9%84%d8%ad%d9%8a%d8%a7%d8%a9/"),
Pair("شوجو", "%d8%b4%d9%88%d8%ac%d9%88/"),
Pair("شوجو اَي", "%d8%b4%d9%88%d8%ac%d9%88-%d8%a7%d9%8e%d9%8a/"),
Pair("شونين", "%d8%b4%d9%88%d9%86%d9%8a%d9%86/"),
Pair("شونين اي", "%d8%b4%d9%88%d9%86%d9%8a%d9%86-%d8%a7%d9%8a/"),
Pair("شياطين", "%d8%b4%d9%8a%d8%a7%d8%b7%d9%8a%d9%86/"),
Pair("غموض", "%d8%ba%d9%85%d9%88%d8%b6/"),
Pair("فضائي", "%d9%81%d8%b6%d8%a7%d8%a6%d9%8a/"),
Pair("فنتازيا", "%d9%81%d9%86%d8%aa%d8%a7%d8%b2%d9%8a%d8%a7/"),
Pair("فنون قتالية", "%d9%81%d9%86%d9%88%d9%86-%d9%82%d8%aa%d8%a7%d9%84%d9%8a%d8%a9/"),
Pair("قوى خارقة", "%d9%82%d9%88%d9%89-%d8%ae%d8%a7%d8%b1%d9%82%d8%a9/"),
Pair("كوميدي", "%d9%83%d9%88%d9%85%d9%8a%d8%af%d9%8a/"),
Pair("محاكاة ساخرة", "%d9%85%d8%ad%d8%a7%d9%83%d8%a7%d8%a9-%d8%b3%d8%a7%d8%ae%d8%b1%d8%a9/"),
Pair("مدرسي", "%d9%85%d8%af%d8%b1%d8%b3%d9%8a/"),
Pair("مصاصي دماء", "%d9%85%d8%b5%d8%a7%d8%b5%d9%8a-%d8%af%d9%85%d8%a7%d8%a1/"),
Pair("مغامرات", "%d9%85%d8%ba%d8%a7%d9%85%d8%b1%d8%a7%d8%aa/"),
Pair("موسيقي", "%d9%85%d9%88%d8%b3%d9%8a%d9%82%d9%8a/"),
Pair("ميكا", "%d9%85%d9%8a%d9%83%d8%a7/"),
Pair("نفسي", "%d9%86%d9%81%d8%b3%d9%8a/"),
)
val TYPES = arrayOf(
ANY,
Pair("Movie", "movie-3"),
Pair("ONA", "ona1"),
Pair("OVA", "ova1"),
Pair("Special", "special1"),
Pair("TV", "tv2"),
)
val STATUS = arrayOf(
ANY,
Pair("لم يعرض بعد", "%d9%84%d9%85-%d9%8a%d8%b9%d8%b1%d8%b6-%d8%a8%d8%b9%d8%af"),
Pair("مكتمل", "complete"),
Pair("يعرض الان", "%d9%8a%d8%b9%d8%b1%d8%b6-%d8%a7%d9%84%d8%a7%d9%86-1"),
)
}
}

View File

@ -5,18 +5,26 @@ import eu.kanade.tachiyomi.network.GET
import okhttp3.Headers import okhttp3.Headers
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
class VidYardExtractor(private val client: OkHttpClient) { class VidYardExtractor(private val client: OkHttpClient, private val headers: Headers) {
fun videosFromUrl(url: String, headers: Headers): List<Video> { private val newHeaders by lazy {
val callPlayer = client.newCall(GET(url)).execute().body.string() headers.newBuilder().set("Referer", VIDYARD_URL).build()
}
fun videosFromUrl(url: String): List<Video> {
val id = url.substringAfter("com/").substringBefore("?")
val playerUrl = "$VIDYARD_URL/player/$id.json"
val callPlayer = client.newCall(GET(playerUrl, newHeaders)).execute()
.use { it.body.string() }
val data = callPlayer.substringAfter("hls\":[").substringBefore("]") val data = callPlayer.substringAfter("hls\":[").substringBefore("]")
val sources = data.split("profile\":\"").drop(1) val sources = data.split("profile\":\"").drop(1)
val videoList = mutableListOf<Video>()
for (source in sources) { return sources.map { source ->
val src = source.substringAfter("url\":\"").substringBefore("\"") val src = source.substringAfter("url\":\"").substringBefore("\"")
val quality = source.substringBefore("\"") val quality = source.substringBefore("\"")
val video = Video(src, quality, src, headers = headers) Video(src, quality, src, headers = newHeaders)
videoList.add(video)
} }
return videoList
} }
} }
private const val VIDYARD_URL = "https://play.vidyard.com"