New extension: french-anime (#1543)

* New extension

* Remove unused lib extractors
This commit is contained in:
Secozzi
2023-04-28 11:29:03 +02:00
committed by GitHub
parent f096727573
commit d09721622e
16 changed files with 693 additions and 0 deletions

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="eu.kanade.tachiyomi.animeextension" />

View File

@ -0,0 +1,19 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
ext {
extName = 'French Anime'
pkgNameSuffix = 'fr.frenchanime'
extClass = '.FrenchAnime'
extVersionCode = 1
libVersion = '13'
}
dependencies {
implementation "dev.datlag.jsunpacker:jsunpacker:1.0.1"
implementation(project(':lib-okru-extractor'))
implementation(project(':lib-streamsb-extractor'))
implementation(project(':lib-dood-extractor'))
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

View File

@ -0,0 +1,373 @@
package eu.kanade.tachiyomi.animeextension.fr.frenchanime
import android.app.Application
import android.content.SharedPreferences
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.animeextension.fr.frenchanime.extractors.SibnetExtractor
import eu.kanade.tachiyomi.animeextension.fr.frenchanime.extractors.StreamHideExtractor
import eu.kanade.tachiyomi.animeextension.fr.frenchanime.extractors.StreamVidExtractor
import eu.kanade.tachiyomi.animeextension.fr.frenchanime.extractors.UpstreamExtractor
import eu.kanade.tachiyomi.animeextension.fr.frenchanime.extractors.UqloadExtractor
import eu.kanade.tachiyomi.animeextension.fr.frenchanime.extractors.VidoExtractor
import eu.kanade.tachiyomi.animeextension.fr.frenchanime.extractors.VudeoExtractor
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.SAnime
import eu.kanade.tachiyomi.animesource.model.SEpisode
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
import eu.kanade.tachiyomi.lib.doodextractor.DoodExtractor
import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor
import eu.kanade.tachiyomi.lib.streamsbextractor.StreamSBExtractor
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.json.Json
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import rx.Observable
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
class FrenchAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override val name = "French Anime"
override val baseUrl = "https://french-anime.com"
override val lang = "fr"
override val supportsLatest = false
override val client: OkHttpClient = network.cloudflareClient
private val json: Json by injectLazy()
private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
// ============================== Popular ===============================
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/animes-vostfr/page/$page/")
override fun popularAnimeSelector(): String = "div#dle-content > div.mov"
override fun popularAnimeNextPageSelector(): String = "span.navigation > span:not(.nav_ext) + a"
override fun popularAnimeFromElement(element: Element): SAnime {
return SAnime.create().apply {
setUrlWithoutDomain(element.selectFirst("a[href]")!!.attr("href").toHttpUrl().encodedPath)
thumbnail_url = element.selectFirst("img[src]")?.absUrl("src") ?: ""
title = "${element.selectFirst("a[href]")!!.text()} ${element.selectFirst("span.block-sai")?.text() ?: ""}"
}
}
// =============================== Latest ===============================
override fun latestUpdatesRequest(page: Int): Request = throw Exception("Not Used")
override fun latestUpdatesSelector(): String = throw Exception("Not Used")
override fun latestUpdatesNextPageSelector(): String = throw Exception("Not Used")
override fun latestUpdatesFromElement(element: Element): SAnime = throw Exception("Not Used")
// =============================== Search ===============================
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val filterList = if (filters.isEmpty()) getFilterList() else filters
val genreFilter = filterList.find { it is GenreFilter } as GenreFilter
val subPageFilter = filterList.find { it is SubPageFilter } as SubPageFilter
return when {
query.isNotBlank() -> {
if (query.length < 4) throw Exception("La recherche est suspendue! La chaîne de recherche est vide ou contient moins de 4 caractères.")
val postHeaders = headers.newBuilder()
.add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8")
.add("Content-Type", "application/x-www-form-urlencoded")
.add("Host", baseUrl.toHttpUrl().host)
.add("Origin", baseUrl)
.add("Referer", "$baseUrl/")
.build()
val cleanQuery = query.replace(" ", "+")
if (page == 1) {
val postBody = "do=search&subaction=search&story=$cleanQuery".toRequestBody("application/x-www-form-urlencoded".toMediaType())
POST("$baseUrl/", body = postBody, headers = postHeaders)
} else {
val postBody = "do=search&subaction=search&search_start=$page&full_search=0&result_from=11&story=$cleanQuery".toRequestBody("application/x-www-form-urlencoded".toMediaType())
POST("$baseUrl/index.php?do=search", body = postBody, headers = postHeaders)
}
}
genreFilter.state != 0 -> {
GET("$baseUrl${genreFilter.toUriPart()}page/$page/")
}
subPageFilter.state != 0 -> {
GET("$baseUrl${subPageFilter.toUriPart()}page/$page/")
}
else -> popularAnimeRequest(page)
}
}
override fun searchAnimeSelector(): String = popularAnimeSelector()
override fun searchAnimeNextPageSelector(): String = popularAnimeNextPageSelector()
override fun searchAnimeFromElement(element: Element): SAnime = popularAnimeFromElement(element)
// ============================== Filters ===============================
override fun getFilterList(): AnimeFilterList = AnimeFilterList(
AnimeFilter.Header("La recherche de texte ignore les filtres"),
SubPageFilter(),
GenreFilter(),
)
private class SubPageFilter : UriPartFilter(
"Catégories",
arrayOf(
Pair("<Sélectionner>", ""),
Pair("Animes VF", "/animes-vf/"),
Pair("Animes VOSTFR", "/animes-vostfr/"),
Pair("Films VF et VOSTFR", "/films-vf-vostfr/"),
),
)
private class GenreFilter : UriPartFilter(
"Animes par genre",
arrayOf(
Pair("<Sélectionner>", ""),
Pair("Action", "/genre/action/"),
Pair("Aventure", "/genre/aventure/"),
Pair("Arts martiaux", "/genre/arts-martiaux/"),
Pair("Combat", "/genre/combat/"),
Pair("Comédie", "/genre/comedie/"),
Pair("Drame", "/genre/drame/"),
Pair("Epouvante", "/genre/epouvante/"),
Pair("Fantastique", "/genre/fantastique/"),
Pair("Fantasy", "/genre/fantasy/"),
Pair("Mystère", "/genre/mystere/"),
Pair("Romance", "/genre/romance/"),
Pair("Shonen", "/genre/shonen/"),
Pair("Surnaturel", "/genre/surnaturel/"),
Pair("Sci-Fi", "/genre/sci-fi/"),
Pair("School life", "/genre/school-life/"),
Pair("Ninja", "/genre/ninja/"),
Pair("Seinen", "/genre/seinen/"),
Pair("Horreur", "/genre/horreur/"),
Pair("Tranche de vie", "/genre/tranchedevie/"),
Pair("Psychologique", "/genre/psychologique/"),
),
)
private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) :
AnimeFilter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
fun toUriPart() = vals[state].second
}
// =========================== Anime Details ============================
override fun fetchAnimeDetails(anime: SAnime): Observable<SAnime> {
return client.newCall(animeDetailsRequest(anime))
.asObservableSuccess()
.map { response ->
animeDetailsParse(response, anime).apply { initialized = true }
}
}
override fun animeDetailsParse(document: Document): SAnime = throw Exception("Not used")
private fun animeDetailsParse(response: Response, baseAnime: SAnime): SAnime {
val document = response.asJsoup()
val anime = SAnime.create()
anime.title = baseAnime.title
anime.thumbnail_url = baseAnime.thumbnail_url
anime.description = document.selectFirst("div.mov-desc span[itemprop=description]")?.text() ?: ""
anime.genre = document.select("div.mov-desc span[itemprop=genre] a").joinToString(", ") { it.text() }
return anime
}
// ============================== Episodes ==============================
override fun episodeListParse(response: Response): List<SEpisode> {
val document = response.asJsoup()
val episodeList = mutableListOf<SEpisode>()
val epsData = document.selectFirst("div.eps")?.text() ?: return emptyList()
epsData.split(" ").filter { it.isNotBlank() }.forEach {
val data = it.split("!", limit = 2)
val episode = SEpisode.create()
episode.episode_number = data[0].toFloatOrNull() ?: 0F
episode.name = "Episode ${data[0]}"
episode.url = data[1]
episodeList.add(episode)
}
return episodeList.reversed()
}
override fun episodeListSelector(): String = throw Exception("Not used")
override fun episodeFromElement(element: Element): SEpisode = throw Exception("Not used")
// ============================ Video Links =============================
override fun fetchVideoList(episode: SEpisode): Observable<List<Video>> {
val videoList = mutableListOf<Video>()
episode.url.split(",").filter { it.isNotBlank() }.parallelMap { source ->
runCatching {
when {
source.contains("https://dood") -> {
videoList.addAll(
DoodExtractor(client).videosFromUrl(source),
)
}
source.contains("https://upstream") -> {
videoList.addAll(
UpstreamExtractor(client).videosFromUrl(source, headers),
)
}
source.contains("https://vudeo") -> {
videoList.addAll(
VudeoExtractor(client).videosFromUrl(source),
)
}
source.contains("https://uqload") -> {
videoList.addAll(
UqloadExtractor(client).videosFromUrl(source, headers),
)
}
source.contains("sbembed.com") || source.contains("sbembed1.com") || source.contains("sbplay.org") ||
source.contains("sbvideo.net") || source.contains("streamsb.net") || source.contains("sbplay.one") ||
source.contains("cloudemb.com") || source.contains("playersb.com") || source.contains("tubesb.com") ||
source.contains("sbplay1.com") || source.contains("embedsb.com") || source.contains("watchsb.com") ||
source.contains("sbplay2.com") || source.contains("japopav.tv") || source.contains("viewsb.com") ||
source.contains("sbfast") || source.contains("sbfull.com") || source.contains("javplaya.com") ||
source.contains("ssbstream.net") || source.contains("p1ayerjavseen.com") || source.contains("sbthe.com") ||
source.contains("lvturbo") || source.contains("sbface.com") || source.contains("sblongvu.com") -> {
videoList.addAll(
StreamSBExtractor(client).videosFromUrl(source, headers),
)
}
source.contains("https://guccihide") || source.contains("https://streamhide") -> {
videoList.addAll(
StreamHideExtractor(client).videosFromUrl(source, headers),
)
}
source.contains("https://streamvid") -> {
videoList.addAll(
StreamVidExtractor(client).videosFromUrl(source, headers),
)
}
source.contains("https://vido") -> {
videoList.addAll(
VidoExtractor(client).videosFromUrl(source, headers),
)
}
source.contains("sibnet") -> {
videoList.addAll(
SibnetExtractor(client).getVideosFromUrl(source),
)
}
source.contains("ok.ru") -> {
videoList.addAll(
OkruExtractor(client).videosFromUrl(source),
)
}
else -> {}
}
}
}
return Observable.just(videoList.sort())
}
override fun videoFromElement(element: Element): Video = throw Exception("Not Used")
override fun videoListSelector(): String = throw Exception("Not Used")
override fun videoUrlParse(document: Document): String = throw Exception("Not Used")
// ============================= Utilities ==============================
override fun List<Video>.sort(): List<Video> {
val quality = preferences.getString("preferred_quality", "720")!!
val server = preferences.getString("preferred_server", "Upstream")!!
return this.sortedWith(
compareBy(
{ it.quality.contains(quality) },
{ it.quality.contains(server, true) },
),
).reversed()
}
// From Dopebox
private fun <A, B> Iterable<A>.parallelMap(f: suspend (A) -> B): List<B> =
runBlocking {
map { async(Dispatchers.Default) { f(it) } }.awaitAll()
}
private fun parseStatus(statusString: String): Int {
return when (statusString) {
"Currently Airing" -> SAnime.ONGOING
"Finished Airing" -> SAnime.COMPLETED
else -> SAnime.UNKNOWN
}
}
override fun setupPreferenceScreen(screen: PreferenceScreen) {
val videoQualityPref = ListPreference(screen.context).apply {
key = "preferred_quality"
title = "Preferred quality"
entries = arrayOf("1080p", "720p", "480p", "360p")
entryValues = arrayOf("1080", "720", "480", "360")
setDefaultValue("720")
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
}
}
val videoServerPref = ListPreference(screen.context).apply {
key = "preferred_server"
title = "Preferred server"
entries = arrayOf("Upstream", "Vido", "StreamVid", "StreamHide", "StreamSB", "Uqload", "Vudeo", "Doodstream", "Sibnet", "Okru")
entryValues = arrayOf("Upstream", "Vido", "StreamVid", "StreamHide", "StreamSB", "Uqload", "Vudeo", "dood", "sibnet", "okru")
setDefaultValue("Upstream")
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(videoQualityPref)
screen.addPreference(videoServerPref)
}
}

View File

@ -0,0 +1,38 @@
package eu.kanade.tachiyomi.animeextension.fr.frenchanime.extractors
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
class SibnetExtractor(private val client: OkHttpClient) {
fun getVideosFromUrl(url: String): List<Video> {
val videoList = mutableListOf<Video>()
val document = client.newCall(
GET(url),
).execute().asJsoup()
val script = document.selectFirst("script:containsData(player.src)")?.data() ?: return emptyList()
val slug = script.substringAfter("player.src").substringAfter("src:")
.substringAfter("\"").substringBefore("\"")
val videoUrl = if (slug.contains("http")) {
slug
} else {
"https://${url.toHttpUrl().host}$slug"
}
val videoHeaders = Headers.headersOf(
"Referer",
url,
)
videoList.add(
Video(videoUrl, "Sibnet", videoUrl, headers = videoHeaders),
)
return videoList
}
}

View File

@ -0,0 +1,49 @@
package eu.kanade.tachiyomi.animeextension.fr.frenchanime.extractors
import dev.datlag.jsunpacker.JsUnpacker
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
class StreamHideExtractor(private val client: OkHttpClient) {
fun videosFromUrl(url: String, headers: Headers): List<Video> {
val videoList = mutableListOf<Video>()
val packed = client.newCall(GET(url)).execute()
.asJsoup().selectFirst("script:containsData(eval)")?.data() ?: return emptyList()
val unpacked = JsUnpacker.unpackAndCombine(packed) ?: return emptyList()
val masterUrl = Regex("""file: ?"(.*?)"""").find(unpacked)?.groupValues?.get(1) ?: return emptyList()
val masterHeaders = headers.newBuilder()
.add("Accept", "*/*")
.add("Host", masterUrl.toHttpUrl().host)
.add("Origin", "https://${url.toHttpUrl().host}")
.add("Referer", "https://${url.toHttpUrl().host}/")
.build()
val masterPlaylist = client.newCall(
GET(masterUrl, headers = masterHeaders),
).execute().body.string()
val masterBase = "https://${masterUrl.toHttpUrl().host}${masterUrl.toHttpUrl().encodedPath}"
.substringBeforeLast("/") + "/"
masterPlaylist.substringAfter("#EXT-X-STREAM-INF:").split("#EXT-X-STREAM-INF:")
.forEach {
val quality = "StreamHide - " + it.substringAfter("RESOLUTION=").substringAfter("x").substringBefore(",") + "p "
val videoUrl = masterBase + it.substringAfter("\n").substringBefore("\n")
val videoHeaders = headers.newBuilder()
.add("Accept", "*/*")
.add("Host", videoUrl.toHttpUrl().host)
.add("Origin", "https://${url.toHttpUrl().host}")
.add("Referer", "https://${url.toHttpUrl().host}/")
.build()
videoList.add(Video(videoUrl, quality, videoUrl, headers = videoHeaders))
}
return videoList
}
}

View File

@ -0,0 +1,46 @@
package eu.kanade.tachiyomi.animeextension.fr.frenchanime.extractors
import dev.datlag.jsunpacker.JsUnpacker
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
class StreamVidExtractor(private val client: OkHttpClient) {
fun videosFromUrl(url: String, headers: Headers): List<Video> {
val videoList = mutableListOf<Video>()
val packed = client.newCall(GET(url)).execute()
.asJsoup().selectFirst("script:containsData(m3u8)")?.data() ?: return emptyList()
val unpacked = JsUnpacker.unpackAndCombine(packed) ?: return emptyList()
val masterUrl = Regex("""src: ?"(.*?)"""").find(unpacked)?.groupValues?.get(1) ?: return emptyList()
val masterHeaders = headers.newBuilder()
.add("Accept", "*/*")
.add("Host", masterUrl.toHttpUrl().host)
.add("Origin", "https://${url.toHttpUrl().host}")
.add("Referer", "https://${url.toHttpUrl().host}/")
.build()
val masterPlaylist = client.newCall(
GET(masterUrl, headers = masterHeaders),
).execute().body.string()
masterPlaylist.substringAfter("#EXT-X-STREAM-INF:").split("#EXT-X-STREAM-INF:")
.forEach {
val quality = "StreamVid - " + it.substringAfter("RESOLUTION=").substringAfter("x").substringBefore(",") + "p "
val videoUrl = it.substringAfter("\n").substringBefore("\n")
val videoHeaders = headers.newBuilder()
.add("Accept", "*/*")
.add("Host", videoUrl.toHttpUrl().host)
.add("Origin", "https://${url.toHttpUrl().host}")
.add("Referer", "https://${url.toHttpUrl().host}/")
.build()
videoList.add(Video(videoUrl, quality, videoUrl, headers = videoHeaders))
}
return videoList
}
}

View File

@ -0,0 +1,38 @@
package eu.kanade.tachiyomi.animeextension.fr.frenchanime.extractors
import dev.datlag.jsunpacker.JsUnpacker
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
class UpstreamExtractor(private val client: OkHttpClient) {
fun videosFromUrl(url: String, headers: Headers): List<Video> {
try {
val jsE = client.newCall(GET(url)).execute().asJsoup().selectFirst("script:containsData(eval)")!!.data()
val masterUrl = JsUnpacker.unpackAndCombine(jsE)!!
.substringAfter("{file:\"").substringBefore("\"}")
val masterBase = masterUrl.substringBefore("master")
val masterPlaylist = client.newCall(GET(masterUrl)).execute().body.string()
val videoList = mutableListOf<Video>()
masterPlaylist.substringAfter("#EXT-X-STREAM-INF:").split("#EXT-X-STREAM-INF:")
.forEach {
val quality = "Upstream - " + it.substringAfter("RESOLUTION=").substringAfter("x").substringBefore(",") + "p "
val videoUrl = masterBase + it.substringAfter("\n").substringBefore("\n")
val videoHeaders = headers.newBuilder()
.add("Accept", "*/*")
.add("Host", videoUrl.toHttpUrl().host)
.add("Origin", "https://upstream.to")
.add("Referer", "https://upstream.to/")
.build()
videoList.add(Video(videoUrl, quality, videoUrl, headers = videoHeaders))
}
return videoList
} catch (e: Exception) {
return emptyList()
}
}
}

View File

@ -0,0 +1,26 @@
package eu.kanade.tachiyomi.animeextension.fr.frenchanime.extractors
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
class UqloadExtractor(private val client: OkHttpClient) {
fun videosFromUrl(url: String, headers: Headers, quality: String = "Uqload"): List<Video> {
val document = client.newCall(GET(url)).execute().asJsoup()
val check = document.selectFirst("script:containsData(sources)")!!.data()
val videoUrl = check.substringAfter("sources: [\"").substringBefore("\"")
val videoHeaders = headers.newBuilder()
.add("Accept", "video/webm,video/ogg,video/*;q=0.9,application/ogg;q=0.7,audio/*;q=0.6,*/*;q=0.5")
.add("Host", videoUrl.toHttpUrl().host)
.add("Referer", "https://uqload.co/")
.build()
return if (check.contains("sources")) {
listOf(Video(url, quality, videoUrl, headers = videoHeaders))
} else {
emptyList()
}
}
}

View File

@ -0,0 +1,81 @@
package eu.kanade.tachiyomi.animeextension.fr.frenchanime.extractors
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.RequestBody.Companion.toRequestBody
class VidoExtractor(private val client: OkHttpClient) {
fun videosFromUrl(url: String, headers: Headers): List<Video> {
val videoList = mutableListOf<Video>()
val id = url.substringAfterLast("/").substringBefore(".html")
val document = client.newCall(
GET(url),
).execute().asJsoup()
val postBodyValues = mutableListOf<String>()
document.select("form > input").forEach {
val name = it.attr("name")
val value = if (name == "file_code") {
id
} else {
it.attr("value")
}
postBodyValues.add("$name=$value")
}
val postBody = postBodyValues.joinToString("&").toRequestBody("application/x-www-form-urlencoded".toMediaType())
val postHeaders = headers.newBuilder()
.add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8")
.add("Content-Type", "application/x-www-form-urlencoded")
.add("Host", url.toHttpUrl().host)
.add("Origin", "https://${url.toHttpUrl().host}")
.add("Referer", url)
.build()
val postDocument = client.newCall(
POST("https://${url.toHttpUrl().host}/dl", body = postBody, headers = postHeaders),
).execute().asJsoup()
val sourcesScript = postDocument.selectFirst("script:containsData(sources)")?.data() ?: return emptyList()
val sourcesString = Regex("""sources: ?(\[.*?\])""").find(sourcesScript)?.groupValues?.get(1) ?: return emptyList()
val sources = Json.decodeFromString<List<String>>(sourcesString)
sources.forEach { source ->
val masterHeaders = headers.newBuilder()
.add("Accept", "*/*")
.add("Host", source.toHttpUrl().host)
.add("Origin", "https://${url.toHttpUrl().host}")
.add("Referer", "https://${url.toHttpUrl().host}/")
.build()
val masterPlaylist = client.newCall(
GET(source, headers = masterHeaders),
).execute().body.string()
masterPlaylist.substringAfter("#EXT-X-STREAM-INF:").split("#EXT-X-STREAM-INF:")
.forEach {
val quality = "Vido - " + it.substringAfter("RESOLUTION=").substringAfter("x").substringBefore(",") + "p "
val videoUrl = it.substringAfter("\n").substringBefore("\n")
val videoHeaders = headers.newBuilder()
.add("Accept", "*/*")
.add("Host", videoUrl.toHttpUrl().host)
.add("Origin", "https://${url.toHttpUrl().host}")
.add("Referer", "https://${url.toHttpUrl().host}/")
.build()
videoList.add(Video(videoUrl, quality, videoUrl, headers = videoHeaders))
}
}
return videoList
}
}

View File

@ -0,0 +1,21 @@
package eu.kanade.tachiyomi.animeextension.fr.frenchanime.extractors
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Headers
import okhttp3.OkHttpClient
class VudeoExtractor(private val client: OkHttpClient) {
fun videosFromUrl(url: String): List<Video> {
val document = client.newCall(GET(url)).execute().asJsoup()
val videoList = mutableListOf<Video>()
document.select("script:containsData(sources: [)").forEach { script ->
val videoUrl = script.data().substringAfter("sources: [").substringBefore("]").replace("\"", "").split(",")
videoUrl.forEach {
videoList.add(Video(it, "Vudeo", it, headers = Headers.headersOf("referer", "https://vudeo.net/")))
}
}
return videoList
}
}